[PATCH] Attempted fix for occasional execv error
[progcomp2012.git] / judge / manager / program.cpp
1 #include <sstream>
2
3 #include <stdarg.h>
4
5 #include <cassert>
6
7 #include "thread_util.h"
8 #include "program.h"
9 #include <vector>
10 #include <string.h>
11 #include <stdio.h>
12
13 using namespace std;
14
15
16 /**
17  * Constructor
18  * @param executablePath - path to the program that will be run
19  *
20  * Creates two pipes - one for each direction between the parent process and the AI program
21  * Forks the process. 
22  *      The child process closes unused sides of the pipe, and then calls exec to replace itself with the AI program
23  *      The parent process closes unused sides of the pipe, and sets up member variables - associates streams with the pipe fd's for convenience.
24  */
25 Program::Program(const char * executablePath) : input(NULL), output(NULL), pid(0), paused(false)
26 {
27         
28                 
29         
30         vector<char*> args;
31         if (executablePath[0] != '"')
32                 args.push_back((char*)executablePath);
33         else
34                 args.push_back((char*)(executablePath)+1);
35         char * token = NULL;
36         do
37         {
38                 token = strstr(args[args.size()-1], " ");
39                 if (token == NULL)
40                         break;
41
42                 *token = '\0';
43                 do
44                 {
45                         ++token;
46                         if (*token == '"')
47                                 *token = '\0';
48                 }
49                 while (*token != '\0' && iswspace(*token));
50
51                 if (*token != '\0' && !iswspace(*token))
52                 {
53                         args.push_back(token);
54                 }
55                 else
56                         break;
57         }
58         while (token != NULL);
59
60         char **  arguments = NULL;
61         if (args.size() > 0)
62         {
63                 arguments = new char*[args.size()+2];
64                 for (unsigned int i=0; i < args.size(); ++i)
65                         arguments[i] = args[i];
66         }
67         //See if file exists and is executable...
68         if (access(executablePath, X_OK) != 0)
69         {
70                 pid = -1;
71                 return;
72         }
73         
74         int readPipe[2]; int writePipe[2];
75         assert(pipe(readPipe) == 0);
76         assert(pipe(writePipe) == 0);
77
78         pid = fork();
79         if (pid == 0)
80         {
81                 close(readPipe[0]);  //close end that parent reads from
82                 close(writePipe[1]); //close end that parent writes to
83
84                 //TODO: Fix possible bug here if the process is already a daemon
85                 assert(writePipe[0] != 0 && readPipe[1] != 1);
86                 dup2(writePipe[0],0); close(writePipe[0]); //pipe end child reads from goes to STDIN
87                 dup2(readPipe[1], 1); close(readPipe[1]); //pipe end child writes to goes to STDOUT
88
89                 //TODO: Somehow force the exec'd process to be unbuffered
90                 setbuf(stdin, NULL); //WARNING: These lines don't appear to have any affect
91                 setbuf(stdout, NULL); //You should add them at the start of the wrapped program.
92                                         //If your wrapped program is not written in C/C++, you will probably have a problem
93                                 
94
95                 if (access(executablePath, X_OK) == 0) //Check we STILL have permissions to start the file
96                 {
97                         execv(executablePath,arguments); ///Replace process with desired executable
98                 }
99                 perror("execv error:\n");
100                 fprintf(stderr, "Program::Program - Could not run program \"%s\"!\n", executablePath);
101                 exit(EXIT_FAILURE); //We will probably have to terminate the whole program if this happens
102         }
103         else
104         {
105                 close(writePipe[0]); //close end that child writes to
106                 close(readPipe[1]); //close end that child reads from
107
108                 input = fdopen(readPipe[0],"r"); output = fdopen(writePipe[1],"w");
109                 setbuf(input, NULL);
110                 setbuf(output, NULL);
111         }
112         
113 }
114
115 /**
116  * Destructor
117  * Writes EOF to the wrapped program and then closes all streams
118  * Kills the wrapped program if it does not exit within 1 second.
119  */
120 Program::~Program()
121 {
122         if (Running()) //Check if the process created is still running...
123         {
124                 //fputc(EOF, output); //If it was, tell it to stop with EOF
125
126                 TimerThread timer(2); //Wait for 2 seconds
127                 timer.Start();          
128                 while (!timer.Finished())
129                 {
130                         if (!Running())
131                         {
132                                 timer.Stop();
133                                 break;
134                         }
135                 }
136                 timer.Stop();
137                 kill(pid, SIGKILL);
138         }
139         if (pid > 0)
140         {
141                 fclose(input);
142                 fclose(output);
143         }
144         
145 }
146
147 /**
148  * Forces the program to pause by sending SIGSTOP
149  * Program can be resumed by calling Continue() (which sends SIGCONT)
150  * @returns true if the program could be paused, false if it couldn't (probably because it wasn't running)
151  */
152 bool Program::Pause()
153 {
154         if (pid > 0 && kill(pid,SIGSTOP) == 0)
155         {
156                 paused = true;
157                 return true;
158         }
159         return false;
160 }
161
162 /**
163  * Causes a paused program to continue
164  * @returns true if the program could be continued, false if it couldn't (probably because it wasn't running)
165  */
166 bool Program::Continue()
167 {
168         if (pid > 0 && kill(pid,SIGCONT) == 0)
169         {
170                 paused = false;
171                 return true;
172         }
173         return false;
174 }
175
176 /**
177  * @returns true iff the program is paused
178  */
179 bool Program::Paused() const
180 {
181         return paused;
182 }
183
184
185 /**
186  * Sends a message to the wrapped AI program
187  * WARNING: Always prints a new line after the message (so don't include a new line)
188  *      This is because everything is always line buffered.
189  * @returns true if the message was successfully sent; false if it was not (ie: the process was not running!)
190  */
191 bool Program::SendMessage(const char * print, ...)
192 {
193         if (!Running()) //Is the process running...
194                 return false; 
195
196         va_list ap;
197         va_start(ap, print);
198
199         if (vfprintf(output, print, ap) < 0 || fprintf(output, "\n") < 0)
200         {
201                 va_end(ap);
202                 return false;
203         }
204         va_end(ap);
205         
206
207
208
209         return true;
210 }
211
212
213 /**
214  * Retrieves a message from the wrapped AI program, waiting a maximum amount of time
215  * @param buffer - C++ string to store the resultant message in
216  * @param timeout - Maximum amount of time to wait before failure. If timeout <= 0, then GetMessage will wait indefinately.
217  * @returns true if the response was recieved within the specified time, false if it was not, or an EOF was recieved, or the process was not running.
218  */
219 bool Program::GetMessage(string & buffer, double timeout)
220 {
221         if (!Running() || timeout == 0)
222                 return false;
223
224         assert(&buffer != NULL);
225         GetterThread getterThread(input, buffer);
226         assert(&(getterThread.buffer) != NULL);
227
228         TimerThread timerThread(timeout*1000000);
229
230         getterThread.Start();
231         if (timeout > 0)
232                 timerThread.Start();
233
234         
235         while (!getterThread.Finished())
236         {
237                 if (timeout > 0 && timerThread.Finished())
238                 {
239                         getterThread.Stop();
240                         timerThread.Stop();
241                         return false;
242                 }
243         }
244
245         getterThread.Stop();
246         if (timeout > 0)
247                 timerThread.Stop();
248
249         
250
251         if (buffer.size() == 1 && buffer[0] == EOF)
252                 return false;
253         return true;
254
255
256 }
257
258 /**
259  * Returns true iff the process is running
260  * @returns what I just said, fool
261  */
262 bool Program::Running() const
263 {
264         return (pid > 0 && kill(pid,0) == 0);
265 }
266
267
268
269

UCC git Repository :: git.ucc.asn.au