kalarm/lib

shellprocess.cpp
1 /*
2  * shellprocess.cpp - execute a shell process
3  * Program: kalarm
4  * Copyright (c) 2004, 2005 by David Jarvie <software@astrojar.org.uk>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24 
25 #include <stdlib.h>
26 #include <sys/stat.h>
27 #include <tdeapplication.h>
28 #include <tdelocale.h>
29 #include <kdebug.h>
30 
31 #include "shellprocess.moc"
32 
33 
34 TQCString ShellProcess::mShellName;
35 TQCString ShellProcess::mShellPath;
36 bool ShellProcess::mInitialised = false;
37 bool ShellProcess::mAuthorised = false;
38 
39 
40 ShellProcess::ShellProcess(const TQString& command)
41  : KShellProcess(shellName()),
42  mCommand(command),
43  mStatus(INACTIVE),
44  mStdinExit(false)
45 {
46 }
47 
48 /******************************************************************************
49 * Execute a command.
50 */
51 bool ShellProcess::start(Communication comm)
52 {
53  if (!authorised())
54  {
55  mStatus = UNAUTHORISED;
56  return false;
57  }
58  KShellProcess::operator<<(mCommand);
59  connect(this, TQ_SIGNAL(wroteStdin(TDEProcess*)), TQ_SLOT(writtenStdin(TDEProcess*)));
60  connect(this, TQ_SIGNAL(processExited(TDEProcess*)), TQ_SLOT(slotExited(TDEProcess*)));
61  if (!KShellProcess::start(TDEProcess::NotifyOnExit, comm))
62  {
63  mStatus = START_FAIL;
64  return false;
65  }
66  mStatus = RUNNING;
67  return true;
68 }
69 
70 /******************************************************************************
71 * Called when a shell process execution completes.
72 * Interprets the exit status according to which shell was called, and emits
73 * a shellExited() signal.
74 */
75 void ShellProcess::slotExited(TDEProcess* proc)
76 {
77  kdDebug(5950) << "ShellProcess::slotExited()\n";
78  mStdinQueue.clear();
79  mStatus = SUCCESS;
80  if (!proc->normalExit())
81  {
82  kdWarning(5950) << "ShellProcess::slotExited(" << mCommand << ") " << mShellName << ": died/killed\n";
83  mStatus = DIED;
84  }
85  else
86  {
87  // Some shells report if the command couldn't be found, or is not executable
88  int status = proc->exitStatus();
89  if ((mShellName == "bash" && (status == 126 || status == 127))
90  || (mShellName == "ksh" && status == 127))
91  {
92  kdWarning(5950) << "ShellProcess::slotExited(" << mCommand << ") " << mShellName << ": not found or not executable\n";
93  mStatus = NOT_FOUND;
94  }
95  }
96  emit shellExited(this);
97 }
98 
99 /******************************************************************************
100 * Write a string to STDIN.
101 */
102 void ShellProcess::writeStdin(const char* buffer, int bufflen)
103 {
104  TQCString scopy(buffer, bufflen+1); // construct a deep copy
105  bool write = mStdinQueue.isEmpty();
106  mStdinQueue.append(scopy);
107  if (write)
108  TDEProcess::writeStdin(mStdinQueue.first(), mStdinQueue.first().length());
109 }
110 
111 /******************************************************************************
112 * Called when output to STDIN completes.
113 * Send the next queued output, if any.
114 * Note that buffers written to STDIN must not be freed until the writtenStdin()
115 * signal has been processed.
116 */
117 void ShellProcess::writtenStdin(TDEProcess* proc)
118 {
119  mStdinQueue.pop_front(); // free the buffer which has now been written
120  if (!mStdinQueue.isEmpty())
121  proc->writeStdin(mStdinQueue.first(), mStdinQueue.first().length());
122  else if (mStdinExit)
123  kill();
124 }
125 
126 /******************************************************************************
127 * Tell the process to exit once all STDIN strings have been written.
128 */
130 {
131  if (mStdinQueue.isEmpty())
132  kill();
133  else
134  mStdinExit = true;
135 }
136 
137 /******************************************************************************
138 * Return the error message corresponding to the command exit status.
139 * Reply = null string if not yet exited, or if command successful.
140 */
142 {
143  switch (mStatus)
144  {
145  case UNAUTHORISED:
146  return i18n("Failed to execute command (shell access not authorized):");
147  case START_FAIL:
148  case NOT_FOUND:
149  return i18n("Failed to execute command:");
150  case DIED:
151  return i18n("Command execution error:");
152  case INACTIVE:
153  case RUNNING:
154  case SUCCESS:
155  default:
156  return TQString();
157  }
158 }
159 
160 /******************************************************************************
161 * Determine which shell to use.
162 * This is a duplication of what KShellProcess does, but we need to know
163 * which shell is used in order to decide what its exit code means.
164 */
165 const TQCString& ShellProcess::shellPath()
166 {
167  if (mShellPath.isEmpty())
168  {
169  // Get the path to the shell
170  mShellPath = "/bin/sh";
171  TQCString envshell = TQCString(getenv("SHELL")).stripWhiteSpace();
172  if (!envshell.isEmpty())
173  {
174  struct stat fileinfo;
175  if (stat(envshell.data(), &fileinfo) != -1 // ensure file exists
176  && !S_ISDIR(fileinfo.st_mode) // and it's not a directory
177  && !S_ISCHR(fileinfo.st_mode) // and it's not a character device
178  && !S_ISBLK(fileinfo.st_mode) // and it's not a block device
179 #ifdef S_ISSOCK
180  && !S_ISSOCK(fileinfo.st_mode) // and it's not a socket
181 #endif
182  && !S_ISFIFO(fileinfo.st_mode) // and it's not a fifo
183  && !access(envshell.data(), X_OK)) // and it's executable
184  mShellPath = envshell;
185  }
186 
187  // Get the shell filename with the path stripped off
188  int i = mShellPath.findRev('/');
189  if (i >= 0)
190  mShellName = mShellPath.mid(i + 1);
191  else
192  mShellName = mShellPath;
193  }
194  return mShellPath;
195 }
196 
197 /******************************************************************************
198 * Check whether shell commands are allowed at all.
199 */
201 {
202  if (!mInitialised)
203  {
204  mAuthorised = kapp->authorize("shell_access");
205  mInitialised = true;
206  }
207  return mAuthorised;
208 }
static bool authorised()
Returns whether the user is authorised to run shell commands.
void shellExited(ShellProcess *)
Signal emitted when the shell process execution completes.
TQString errorMessage() const
Returns the error message corresponding to the command exit status.
static const TQCString & shellPath()
Determines which shell to use.
ShellProcess(const TQString &command)
Constructor.
void writeStdin(const char *buffer, int bufflen)
Writes a string to the process's STDIN.
void stdinExit()
Tell the process to exit once any outstanding STDIN strings have been written.
bool start(Communication comm=NoCommunication)
Executes the configured command.
Status status() const
Returns the current status of the shell process.
Definition: shellprocess.h:83