-rw-r--r-- | libopie/oprocctrl.cpp | 268 |
1 files changed, 268 insertions, 0 deletions
diff --git a/libopie/oprocctrl.cpp b/libopie/oprocctrl.cpp new file mode 100644 index 0000000..e7db622 --- a/dev/null +++ b/libopie/oprocctrl.cpp | |||
@@ -0,0 +1,268 @@ | |||
1 | /* This file is part of the KDE libraries | ||
2 | Copyright (C) 1997 Christian Czezakte (e9025461@student.tuwien.ac.at) | ||
3 | |||
4 | This library is free software; you can redistribute it and/or | ||
5 | modify it under the terms of the GNU Library General Public | ||
6 | License as published by the Free Software Foundation; either | ||
7 | version 2 of the License, or (at your option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, | ||
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
12 | Library General Public License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to | ||
16 | the Free Software Foundation, Inc., 59 Temple Place - Suite 330, | ||
17 | Boston, MA 02111-1307, USA. | ||
18 | */ | ||
19 | // | ||
20 | // KPROCESSCONTROLLER -- A helper class for KProcess | ||
21 | // | ||
22 | // version 0.3.1, Jan, 8th 1997 | ||
23 | // | ||
24 | // (C) Christian Czezatke | ||
25 | // e9025461@student.tuwien.ac.at | ||
26 | // Ported by Holger Freyther | ||
27 | // | ||
28 | |||
29 | //#include <config.h> | ||
30 | |||
31 | #include <sys/types.h> | ||
32 | #include <sys/socket.h> | ||
33 | |||
34 | #include <errno.h> | ||
35 | #include <fcntl.h> | ||
36 | #include <stdio.h> | ||
37 | #include <string.h> | ||
38 | #include <unistd.h> | ||
39 | #include <assert.h> | ||
40 | |||
41 | #include <qsocketnotifier.h> | ||
42 | #include "oprocess.h" | ||
43 | #include "oprocctrl.h" | ||
44 | |||
45 | OProcessController *OProcessController::theOProcessController = 0; | ||
46 | |||
47 | struct sigaction OProcessController::oldChildHandlerData; | ||
48 | bool OProcessController::handlerSet = false; | ||
49 | |||
50 | OProcessController::OProcessController() | ||
51 | { | ||
52 | assert( theOProcessController == 0 ); | ||
53 | |||
54 | if (0 > pipe(fd)) | ||
55 | printf(strerror(errno)); | ||
56 | |||
57 | notifier = new QSocketNotifier(fd[0], QSocketNotifier::Read); | ||
58 | notifier->setEnabled(true); | ||
59 | QObject::connect(notifier, SIGNAL(activated(int)), | ||
60 | this, SLOT(slotDoHousekeeping(int))); | ||
61 | connect( &delayedChildrenCleanupTimer, SIGNAL( timeout()), | ||
62 | SLOT( delayedChildrenCleanup())); | ||
63 | |||
64 | theOProcessController = this; | ||
65 | |||
66 | setupHandlers(); | ||
67 | } | ||
68 | |||
69 | |||
70 | void OProcessController::setupHandlers() | ||
71 | { | ||
72 | if( handlerSet ) | ||
73 | return; | ||
74 | struct sigaction act; | ||
75 | act.sa_handler=theSigCHLDHandler; | ||
76 | sigemptyset(&(act.sa_mask)); | ||
77 | sigaddset(&(act.sa_mask), SIGCHLD); | ||
78 | // Make sure we don't block this signal. gdb tends to do that :-( | ||
79 | sigprocmask(SIG_UNBLOCK, &(act.sa_mask), 0); | ||
80 | |||
81 | act.sa_flags = SA_NOCLDSTOP; | ||
82 | |||
83 | // CC: take care of SunOS which automatically restarts interrupted system | ||
84 | // calls (and thus does not have SA_RESTART) | ||
85 | |||
86 | #ifdef SA_RESTART | ||
87 | act.sa_flags |= SA_RESTART; | ||
88 | #endif | ||
89 | |||
90 | sigaction( SIGCHLD, &act, &oldChildHandlerData ); | ||
91 | |||
92 | act.sa_handler=SIG_IGN; | ||
93 | sigemptyset(&(act.sa_mask)); | ||
94 | sigaddset(&(act.sa_mask), SIGPIPE); | ||
95 | act.sa_flags = 0; | ||
96 | sigaction( SIGPIPE, &act, 0L); | ||
97 | handlerSet = true; | ||
98 | } | ||
99 | |||
100 | void OProcessController::resetHandlers() | ||
101 | { | ||
102 | if( !handlerSet ) | ||
103 | return; | ||
104 | sigaction( SIGCHLD, &oldChildHandlerData, 0 ); | ||
105 | // there should be no problem with SIGPIPE staying SIG_IGN | ||
106 | handlerSet = false; | ||
107 | } | ||
108 | |||
109 | // block SIGCHLD handler, because it accesses processList | ||
110 | void OProcessController::addOProcess( OProcess* p ) | ||
111 | { | ||
112 | sigset_t newset, oldset; | ||
113 | sigemptyset( &newset ); | ||
114 | sigaddset( &newset, SIGCHLD ); | ||
115 | sigprocmask( SIG_BLOCK, &newset, &oldset ); | ||
116 | processList.append( p ); | ||
117 | sigprocmask( SIG_SETMASK, &oldset, 0 ); | ||
118 | } | ||
119 | |||
120 | void OProcessController::removeOProcess( OProcess* p ) | ||
121 | { | ||
122 | sigset_t newset, oldset; | ||
123 | sigemptyset( &newset ); | ||
124 | sigaddset( &newset, SIGCHLD ); | ||
125 | sigprocmask( SIG_BLOCK, &newset, &oldset ); | ||
126 | processList.remove( p ); | ||
127 | sigprocmask( SIG_SETMASK, &oldset, 0 ); | ||
128 | } | ||
129 | |||
130 | //using a struct which contains both the pid and the status makes it easier to write | ||
131 | //and read the data into the pipe | ||
132 | //especially this solves a problem which appeared on my box where slotDoHouseKeeping() received | ||
133 | //only 4 bytes (with some debug output around the write()'s it received all 8 bytes) | ||
134 | //don't know why this happened, but when writing all 8 bytes at once it works here, aleXXX | ||
135 | struct waitdata | ||
136 | { | ||
137 | pid_t pid; | ||
138 | int status; | ||
139 | }; | ||
140 | |||
141 | void OProcessController::theSigCHLDHandler(int arg) | ||
142 | { | ||
143 | struct waitdata wd; | ||
144 | // int status; | ||
145 | // pid_t this_pid; | ||
146 | int saved_errno; | ||
147 | |||
148 | saved_errno = errno; | ||
149 | // since waitpid and write change errno, we have to save it and restore it | ||
150 | // (Richard Stevens, Advanced programming in the Unix Environment) | ||
151 | |||
152 | bool found = false; | ||
153 | if( theOProcessController != 0 ) { | ||
154 | // iterating the list doesn't perform any system call | ||
155 | for( QValueList<OProcess*>::ConstIterator it = theOProcessController->processList.begin(); | ||
156 | it != theOProcessController->processList.end(); | ||
157 | ++it ) | ||
158 | { | ||
159 | if( !(*it)->isRunning()) | ||
160 | continue; | ||
161 | wd.pid = waitpid( (*it)->pid(), &wd.status, WNOHANG ); | ||
162 | if ( wd.pid > 0 ) { | ||
163 | ::write(theOProcessController->fd[1], &wd, sizeof(wd)); | ||
164 | found = true; | ||
165 | } | ||
166 | } | ||
167 | } | ||
168 | if( !found && oldChildHandlerData.sa_handler != SIG_IGN | ||
169 | && oldChildHandlerData.sa_handler != SIG_DFL ) | ||
170 | oldChildHandlerData.sa_handler( arg ); // call the old handler | ||
171 | // handle the rest | ||
172 | if( theOProcessController != 0 ) { | ||
173 | static const struct waitdata dwd = { 0, 0 }; // delayed waitpid() | ||
174 | ::write(theOProcessController->fd[1], &dwd, sizeof(dwd)); | ||
175 | } else { | ||
176 | int dummy; | ||
177 | while( waitpid( -1, &dummy, WNOHANG ) > 0 ) | ||
178 | ; | ||
179 | } | ||
180 | |||
181 | errno = saved_errno; | ||
182 | } | ||
183 | |||
184 | |||
185 | |||
186 | void OProcessController::slotDoHousekeeping(int ) | ||
187 | { | ||
188 | unsigned int bytes_read = 0; | ||
189 | unsigned int errcnt=0; | ||
190 | // read pid and status from the pipe. | ||
191 | struct waitdata wd; | ||
192 | while ((bytes_read < sizeof(wd)) && (errcnt < 50)) { | ||
193 | int r = ::read(fd[0], ((char *)&wd) + bytes_read, sizeof(wd) - bytes_read); | ||
194 | if (r > 0) bytes_read += r; | ||
195 | else if (r < 0) errcnt++; | ||
196 | } | ||
197 | if (errcnt >= 50) { | ||
198 | fprintf(stderr, | ||
199 | "Error: Max. error count for pipe read " | ||
200 | "exceeded in OProcessController::slotDoHousekeeping\n"); | ||
201 | return; // it makes no sense to continue here! | ||
202 | } | ||
203 | if (bytes_read != sizeof(wd)) { | ||
204 | fprintf(stderr, | ||
205 | "Error: Could not read info from signal handler %d <> %d!\n", | ||
206 | bytes_read, sizeof(wd)); | ||
207 | return; // it makes no sense to continue here! | ||
208 | } | ||
209 | if (wd.pid==0) { // special case, see delayedChildrenCleanup() | ||
210 | delayedChildrenCleanupTimer.start( 1000, true ); | ||
211 | return; | ||
212 | } | ||
213 | |||
214 | for( QValueList<OProcess*>::ConstIterator it = processList.begin(); | ||
215 | it != processList.end(); | ||
216 | ++it ) { | ||
217 | OProcess* proc = *it; | ||
218 | if (proc->pid() == wd.pid) { | ||
219 | // process has exited, so do emit the respective events | ||
220 | if (proc->run_mode == OProcess::Block) { | ||
221 | // If the reads are done blocking then set the status in proc | ||
222 | // but do nothing else because OProcess will perform the other | ||
223 | // actions of processHasExited. | ||
224 | proc->status = wd.status; | ||
225 | proc->runs = false; | ||
226 | } else { | ||
227 | proc->processHasExited(wd.status); | ||
228 | } | ||
229 | return; | ||
230 | } | ||
231 | } | ||
232 | } | ||
233 | |||
234 | // this is needed e.g. for popen(), which calls waitpid() checking | ||
235 | // for its forked child, if we did waitpid() directly in the SIGCHLD | ||
236 | // handler, popen()'s waitpid() call would fail | ||
237 | void OProcessController::delayedChildrenCleanup() | ||
238 | { | ||
239 | struct waitdata wd; | ||
240 | while(( wd.pid = waitpid( -1, &wd.status, WNOHANG ) ) > 0 ) { | ||
241 | for( QValueList<OProcess*>::ConstIterator it = processList.begin(); | ||
242 | it != processList.end(); | ||
243 | ++it ) | ||
244 | { | ||
245 | if( !(*it)->isRunning() || (*it)->pid() != wd.pid ) | ||
246 | continue; | ||
247 | // it's OProcess, handle it | ||
248 | ::write(fd[1], &wd, sizeof(wd)); | ||
249 | break; | ||
250 | } | ||
251 | } | ||
252 | } | ||
253 | |||
254 | OProcessController::~OProcessController() | ||
255 | { | ||
256 | assert( theOProcessController == this ); | ||
257 | resetHandlers(); | ||
258 | |||
259 | notifier->setEnabled(false); | ||
260 | |||
261 | close(fd[0]); | ||
262 | close(fd[1]); | ||
263 | |||
264 | delete notifier; | ||
265 | theOProcessController = 0; | ||
266 | } | ||
267 | |||
268 | //#include "kprocctrl.moc" | ||