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

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