shellprocess.cpp
00001 /* 00002 * shellprocess.cpp - execute a shell process 00003 * Program: kalarm 00004 * Copyright (c) 2004, 2005 by David Jarvie <software@astrojar.org.uk> 00005 * 00006 * This program is free software; you can redistribute it and/or modify 00007 * it under the terms of the GNU General Public License as published by 00008 * the Free Software Foundation; either version 2 of the License, or 00009 * (at your option) any later version. 00010 * 00011 * This program is distributed in the hope that it will be useful, 00012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00014 * GNU General Public License for more details. 00015 * 00016 * You should have received a copy of the GNU General Public License along 00017 * with this program; if not, write to the Free Software Foundation, Inc., 00018 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 00019 */ 00020 00021 #ifdef HAVE_CONFIG_H 00022 #include <config.h> 00023 #endif 00024 00025 #include <stdlib.h> 00026 #include <sys/stat.h> 00027 #include <tdeapplication.h> 00028 #include <tdelocale.h> 00029 #include <kdebug.h> 00030 00031 #include "shellprocess.moc" 00032 00033 00034 TQCString ShellProcess::mShellName; 00035 TQCString ShellProcess::mShellPath; 00036 bool ShellProcess::mInitialised = false; 00037 bool ShellProcess::mAuthorised = false; 00038 00039 00040 ShellProcess::ShellProcess(const TQString& command) 00041 : KShellProcess(shellName()), 00042 mCommand(command), 00043 mStatus(INACTIVE), 00044 mStdinExit(false) 00045 { 00046 } 00047 00048 /****************************************************************************** 00049 * Execute a command. 00050 */ 00051 bool ShellProcess::start(Communication comm) 00052 { 00053 if (!authorised()) 00054 { 00055 mStatus = UNAUTHORISED; 00056 return false; 00057 } 00058 KShellProcess::operator<<(mCommand); 00059 connect(this, TQT_SIGNAL(wroteStdin(TDEProcess*)), TQT_SLOT(writtenStdin(TDEProcess*))); 00060 connect(this, TQT_SIGNAL(processExited(TDEProcess*)), TQT_SLOT(slotExited(TDEProcess*))); 00061 if (!KShellProcess::start(TDEProcess::NotifyOnExit, comm)) 00062 { 00063 mStatus = START_FAIL; 00064 return false; 00065 } 00066 mStatus = RUNNING; 00067 return true; 00068 } 00069 00070 /****************************************************************************** 00071 * Called when a shell process execution completes. 00072 * Interprets the exit status according to which shell was called, and emits 00073 * a shellExited() signal. 00074 */ 00075 void ShellProcess::slotExited(TDEProcess* proc) 00076 { 00077 kdDebug(5950) << "ShellProcess::slotExited()\n"; 00078 mStdinQueue.clear(); 00079 mStatus = SUCCESS; 00080 if (!proc->normalExit()) 00081 { 00082 kdWarning(5950) << "ShellProcess::slotExited(" << mCommand << ") " << mShellName << ": died/killed\n"; 00083 mStatus = DIED; 00084 } 00085 else 00086 { 00087 // Some shells report if the command couldn't be found, or is not executable 00088 int status = proc->exitStatus(); 00089 if ((mShellName == "bash" && (status == 126 || status == 127)) 00090 || (mShellName == "ksh" && status == 127)) 00091 { 00092 kdWarning(5950) << "ShellProcess::slotExited(" << mCommand << ") " << mShellName << ": not found or not executable\n"; 00093 mStatus = NOT_FOUND; 00094 } 00095 } 00096 emit shellExited(this); 00097 } 00098 00099 /****************************************************************************** 00100 * Write a string to STDIN. 00101 */ 00102 void ShellProcess::writeStdin(const char* buffer, int bufflen) 00103 { 00104 TQCString scopy(buffer, bufflen+1); // construct a deep copy 00105 bool write = mStdinQueue.isEmpty(); 00106 mStdinQueue.append(scopy); 00107 if (write) 00108 TDEProcess::writeStdin(mStdinQueue.first(), mStdinQueue.first().length()); 00109 } 00110 00111 /****************************************************************************** 00112 * Called when output to STDIN completes. 00113 * Send the next queued output, if any. 00114 * Note that buffers written to STDIN must not be freed until the writtenStdin() 00115 * signal has been processed. 00116 */ 00117 void ShellProcess::writtenStdin(TDEProcess* proc) 00118 { 00119 mStdinQueue.pop_front(); // free the buffer which has now been written 00120 if (!mStdinQueue.isEmpty()) 00121 proc->writeStdin(mStdinQueue.first(), mStdinQueue.first().length()); 00122 else if (mStdinExit) 00123 kill(); 00124 } 00125 00126 /****************************************************************************** 00127 * Tell the process to exit once all STDIN strings have been written. 00128 */ 00129 void ShellProcess::stdinExit() 00130 { 00131 if (mStdinQueue.isEmpty()) 00132 kill(); 00133 else 00134 mStdinExit = true; 00135 } 00136 00137 /****************************************************************************** 00138 * Return the error message corresponding to the command exit status. 00139 * Reply = null string if not yet exited, or if command successful. 00140 */ 00141 TQString ShellProcess::errorMessage() const 00142 { 00143 switch (mStatus) 00144 { 00145 case UNAUTHORISED: 00146 return i18n("Failed to execute command (shell access not authorized):"); 00147 case START_FAIL: 00148 case NOT_FOUND: 00149 return i18n("Failed to execute command:"); 00150 case DIED: 00151 return i18n("Command execution error:"); 00152 case INACTIVE: 00153 case RUNNING: 00154 case SUCCESS: 00155 default: 00156 return TQString(); 00157 } 00158 } 00159 00160 /****************************************************************************** 00161 * Determine which shell to use. 00162 * This is a duplication of what KShellProcess does, but we need to know 00163 * which shell is used in order to decide what its exit code means. 00164 */ 00165 const TQCString& ShellProcess::shellPath() 00166 { 00167 if (mShellPath.isEmpty()) 00168 { 00169 // Get the path to the shell 00170 mShellPath = "/bin/sh"; 00171 TQCString envshell = TQCString(getenv("SHELL")).stripWhiteSpace(); 00172 if (!envshell.isEmpty()) 00173 { 00174 struct stat fileinfo; 00175 if (stat(envshell.data(), &fileinfo) != -1 // ensure file exists 00176 && !S_ISDIR(fileinfo.st_mode) // and it's not a directory 00177 && !S_ISCHR(fileinfo.st_mode) // and it's not a character device 00178 && !S_ISBLK(fileinfo.st_mode) // and it's not a block device 00179 #ifdef S_ISSOCK 00180 && !S_ISSOCK(fileinfo.st_mode) // and it's not a socket 00181 #endif 00182 && !S_ISFIFO(fileinfo.st_mode) // and it's not a fifo 00183 && !access(envshell.data(), X_OK)) // and it's executable 00184 mShellPath = envshell; 00185 } 00186 00187 // Get the shell filename with the path stripped off 00188 int i = mShellPath.findRev('/'); 00189 if (i >= 0) 00190 mShellName = mShellPath.mid(i + 1); 00191 else 00192 mShellName = mShellPath; 00193 } 00194 return mShellPath; 00195 } 00196 00197 /****************************************************************************** 00198 * Check whether shell commands are allowed at all. 00199 */ 00200 bool ShellProcess::authorised() 00201 { 00202 if (!mInitialised) 00203 { 00204 mAuthorised = kapp->authorize("shell_access"); 00205 mInitialised = true; 00206 } 00207 return mAuthorised; 00208 }