1 // Routines to allow tasks to run under the editor
2 // Copyright (C) 2000 Core Technologies.
3
4 // This file is part of e93.
5 //
6 // e93 is free software; you can redistribute it and/or modify
7 // it under the terms of the e93 LICENSE AGREEMENT.
8 //
9 // e93 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
12 // e93 LICENSE AGREEMENT for more details.
13 //
14 // You should have received a copy of the e93 LICENSE AGREEMENT
15 // along with e93; see the file "LICENSE.TXT".
16
17 #include "includes.h"
18
19 enum
20 {
21 NO_TASK,
22 STDIN_CLOSED,
23 WRITE_FAILED
24 };
25
26 static const char *errorDescriptions[]=
27 {
28 "No current task",
29 "Task's stdin is closed",
30 "Write failed"
31 };
32
OpenTaskPipes(EDITOR_TASK * task)33 static bool OpenTaskPipes(EDITOR_TASK *task)
34 // Create pipes that will connect us to a task
35 // If there is a problem, SetError, return false
36 {
37 int
38 flags;
39
40 if(pipe(&(task->fdin[0]))==0)
41 {
42 if(pipe(&(task->fdout[0]))==0)
43 {
44 flags=fcntl(task->fdout[0],F_GETFL,0);
45 flags|=O_NDELAY; // turn off blocking on read or tasks stdout
46 fcntl(task->fdout[0],F_SETFL,flags);
47
48 task->closedStdin=false; // have not closed the input to the task
49 return(true);
50 }
51 else
52 {
53 SetStdCLibError();
54 }
55 close(task->fdin[0]);
56 close(task->fdin[1]);
57 }
58 else
59 {
60 SetStdCLibError();
61 }
62 return(false);
63 }
64
CloseUnusedParentPipes(EDITOR_TASK * task)65 static void CloseUnusedParentPipes(EDITOR_TASK *task)
66 // Close the half of the pipes that are not needed on the parent side
67 {
68 close(task->fdin[0]);
69 close(task->fdout[1]);
70 }
71
CloseUnusedChildPipes(EDITOR_TASK * task)72 static void CloseUnusedChildPipes(EDITOR_TASK *task)
73 // Close the half of the pipes that are not needed on the child side
74 {
75 close(task->fdin[1]);
76 close(task->fdout[0]);
77 }
78
CloseRemainingPipes(EDITOR_TASK * task)79 static void CloseRemainingPipes(EDITOR_TASK *task)
80 // Once the child is gone, this is used to close any pipes that remain open
81 {
82 if(!task->closedStdin) // this part of the pipe can be closed before we get here, if an EOF has been sent to the task
83 {
84 close(task->fdin[1]);
85 }
86 close(task->fdout[0]);
87 }
88
CloseTaskPipes(EDITOR_TASK * task)89 static void CloseTaskPipes(EDITOR_TASK *task)
90 // close pipes opened by OpenTaskPipes
91 {
92 close(task->fdin[0]);
93 close(task->fdin[1]);
94 close(task->fdout[0]);
95 close(task->fdout[1]);
96 }
97
SpawnTask(EDITOR_BUFFER * buffer,UINT8 * data)98 static bool SpawnTask(EDITOR_BUFFER *buffer,UINT8 *data)
99 // Spawn off a task return true if it spawned
100 // SetError, and return false if there was a problem
101 {
102 int
103 forkResult;
104
105 if((buffer->task=(EDITOR_TASK *)MNewPtr(sizeof(EDITOR_TASK)))) // create task record
106 {
107 buffer->taskBytes=0;
108 if(OpenTaskPipes(buffer->task)) // open pipes to the task
109 {
110 if((forkResult=fork())) // parent process gets child ID back
111 {
112 if(forkResult>0)
113 {
114 CloseUnusedParentPipes(buffer->task); // close the half of the pipes which are meaningless to us
115 buffer->task->pid=forkResult; // set the process ID
116 return(true);
117 }
118 else
119 {
120 SetStdCLibError();
121 }
122 }
123 else // child process gets 0 back
124 {
125 CloseUnusedChildPipes(buffer->task); // close the half of the pipes which are meaningless to us
126 setsid(); // make a process group with this as the leader
127 dup2(buffer->task->fdin[0],0); // create stdin
128 dup2(buffer->task->fdout[1],1); // create stdout
129 dup2(buffer->task->fdout[1],2); // create stderr
130
131 execl("/bin/sh","sh","-c",data,NULL); // start the shell running in the child fork (if this works, we're done)
132 _exit(-1); // our task did not start, so bail out (use _exit to avoid flushing twice)
133 }
134 CloseTaskPipes(buffer->task); // failed to fork, close down pipes
135 }
136 MDisposePtr(buffer->task);
137 }
138 buffer->task=NULL;
139 return(false);
140 }
141
WriteBufferTaskData(EDITOR_BUFFER * buffer,UINT8 * data,UINT32 numBytes)142 bool WriteBufferTaskData(EDITOR_BUFFER *buffer,UINT8 *data,UINT32 numBytes)
143 // Send numBytes to the standard input of the task running in buffer.
144 // If no task is running in buffer, send data to execl, starting
145 // a task.
146 // If there is a problem writing data, SetError, return false.
147 {
148 int
149 result;
150
151 if(buffer->task) // task??
152 {
153 if(!buffer->task->closedStdin) // make sure it has not been closed
154 {
155 result=write(buffer->task->fdin[1],data,numBytes); // write data to "stdin" of task
156 if(result>=0)
157 {
158 return(true);
159 }
160 else
161 {
162 SetError(errorDescriptions[WRITE_FAILED]);
163 }
164 }
165 else
166 {
167 SetError(errorDescriptions[STDIN_CLOSED]);
168 }
169 }
170 else
171 {
172 if(SpawnTask(buffer,data)) // start the task
173 {
174 if(buffer->window)
175 {
176 AdjustDocumentWindowStatus(buffer->window); // status bar needs updating now
177 }
178 return(true);
179 }
180 }
181 return(false);
182 }
183
SendEOFToBufferTask(EDITOR_BUFFER * buffer)184 bool SendEOFToBufferTask(EDITOR_BUFFER *buffer)
185 // Send an EOF to the input of the task running in buffer
186 // If no task is running in buffer, or there
187 // is some other problem, SetError, return false
188 {
189 if(buffer->task)
190 {
191 if(!buffer->task->closedStdin) // if closed already, complain
192 {
193 close(buffer->task->fdin[1]); // close the side of the tasks stdin pipe that we write into, this will send an EOF to the task
194 buffer->task->closedStdin=true;
195 return(true);
196 }
197 else
198 {
199 SetError(errorDescriptions[STDIN_CLOSED]);
200 }
201 }
202 else
203 {
204 SetError(errorDescriptions[NO_TASK]);
205 }
206 return(false);
207 }
208
209 #define TASK_DATA_BUFFER_SIZE 8192
210
211 static char
212 taskData[TASK_DATA_BUFFER_SIZE];
213
ReadTaskData(EDITOR_BUFFER * buffer,bool * didUpdate)214 static bool ReadTaskData(EDITOR_BUFFER *buffer,bool *didUpdate)
215 // See if the task in buffer has any output it wants to send,
216 // If so, dump it into buffer
217 // Return didUpdate=true if output was placed into buffer
218 // If there is a problem, SetError, return false
219 // NOTE: the test for errno==EWOULDBLOCK is needed, because
220 // read does not want to return the fact that it read 0 bytes in
221 // non-blocking mode, If it did return 0, we might think it hit EOF.
222 {
223 int
224 numRead;
225
226 *didUpdate=false;
227 if(((numRead=read(buffer->task->fdout[0],&(taskData[0]),TASK_DATA_BUFFER_SIZE))>=0)||errno==EWOULDBLOCK)
228 {
229 if(numRead>0)
230 {
231 buffer->taskBytes+=numRead;
232 InsertBufferTaskData(buffer,(UINT8 *)&(taskData[0]),numRead);
233 *didUpdate=true;
234 }
235 return(true);
236 }
237 else
238 {
239 SetStdCLibError();
240 }
241 return(false);
242 }
243
DisconnectBufferTask(EDITOR_BUFFER * buffer)244 void DisconnectBufferTask(EDITOR_BUFFER *buffer)
245 // Disconnect ourselves from any task that is running in buffer
246 // If no task is running, do nothing
247 // NOTE: this does not need to kill the task, and it is usually desirable
248 // NOT to kill the task
249 {
250 if(buffer->task)
251 {
252 CloseRemainingPipes(buffer->task);
253 MDisposePtr(buffer->task);
254 buffer->task=NULL;
255 if(buffer->window)
256 {
257 AdjustDocumentWindowStatus(buffer->window); // status bar needs updating now
258 }
259 }
260 }
261
UpdateBufferTask(EDITOR_BUFFER * buffer,bool * didUpdate)262 bool UpdateBufferTask(EDITOR_BUFFER *buffer,bool *didUpdate)
263 // See if there is a task connected to buffer
264 // If so, update the task by reading data from it, and adding
265 // it to buffer
266 // Return didUpdate set true if the task provided some data, false otherwise
267 // If there is a problem, SetError, and return false
268 {
269 int
270 pid;
271 int
272 statusp;
273 bool
274 hadUpdate,
275 readOk;
276
277 waitpid(-1,&statusp,WNOHANG); // get info about any of our children who have died (this will allow children that were started (but since, closed the pipes to) to die, and not lie around <defunct>)
278
279 *didUpdate=false;
280 if(buffer->task) // see if there is a task active in this buffer if not, complain
281 {
282 pid=waitpid(buffer->task->pid,&statusp,WNOHANG); // check status of the task
283 if(pid==0) // if status 0, it is still running
284 {
285 return(ReadTaskData(buffer,didUpdate)); // inhale some data from the task
286 }
287 else
288 {
289 while((readOk=ReadTaskData(buffer,&hadUpdate))&&hadUpdate) // inhale any data left in the pipeline
290 {
291 *didUpdate=true;
292 }
293 DisconnectBufferTask(buffer); // no more task associated with this buffer
294 return(readOk);
295 }
296 }
297 else
298 {
299 SetError(errorDescriptions[NO_TASK]);
300 }
301 return(false);
302 }
303
UpdateBufferTasks()304 bool UpdateBufferTasks()
305 // Run through all the buffers, see if any has a task that has
306 // data to be placed into its buffer, if so do it
307 // If there is a problem, ignore it!
308 // return true if something was updated, false otherwise
309 // NOTE: this is never called from the high-level part of the editor
310 // it is just here as a convenience to event handler.
311 {
312 EDITOR_BUFFER
313 *buffer;
314 bool
315 hadUpdate,
316 didUpdate;
317
318 didUpdate=hadUpdate=false;
319 buffer=EditorGetFirstBuffer();
320 while(buffer)
321 {
322 if(buffer->task)
323 {
324 UpdateBufferTask(buffer,&hadUpdate);
325 if(hadUpdate)
326 {
327 didUpdate=true;
328 }
329 }
330 buffer=EditorGetNextBuffer(buffer);
331 }
332 return(didUpdate);
333 }
334
KillBufferTask(EDITOR_BUFFER * buffer)335 bool KillBufferTask(EDITOR_BUFFER *buffer)
336 // Signal the task that is running in buffer to quit
337 // If no task is running, SetError, return false
338 {
339 if(buffer->task)
340 {
341 kill(-(buffer->task->pid),SIGHUP); // be nice, just send HUP, don't go for the total SIGKILL
342 return(true);
343 }
344 else
345 {
346 SetError(errorDescriptions[NO_TASK]);
347 }
348 return(false);
349 }
350
351