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