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

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