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, TQT_SIGNAL(wroteStdin(TDEProcess*)), TQT_SLOT(writtenStdin(TDEProcess*)));
60  connect(this, TQT_SIGNAL(processExited(TDEProcess*)), TQT_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 }
ShellProcess(const TQString &command)
Constructor.
void shellExited(ShellProcess *)
Signal emitted when the shell process execution completes.
void writeStdin(const char *buffer, int bufflen)
Writes a string to the process&#39;s STDIN.
static bool authorised()
Returns whether the user is authorised to run shell commands.
Status status() const
Returns the current status of the shell process.
Definition: shellprocess.h:83
bool start(Communication comm=NoCommunication)
Executes the configured command.
static const TQCString & shellPath()
Determines which shell to use.
void stdinExit()
Tell the process to exit once any outstanding STDIN strings have been written.
TQString errorMessage() const
Returns the error message corresponding to the command exit status.