Imported Upstream version 2.9.0
[debian/cc1111] / support / regression / fwk / lib / timeout.c
1 /*-------------------------------------------------------------------------
2   timeout.c - source file for running ucSim within the regression tests
3
4              Written By -  Bernhard Held . bernhard@bernhardheld.de (2001)
5    Native WIN32 port by -  Borut Razem . borut.razem@siol.net (2007)
6
7    This program is free software; you can redistribute it and/or modify it
8    under the terms of the GNU General Public License as published by the
9    Free Software Foundation; either version 2, or (at your option) any
10    later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20
21    In other words, you are welcome to use, share and improve this program.
22    You are forbidden to forbid anyone else to use, share and improve
23    what you give them.   Help stamp out software-hoarding!
24 -------------------------------------------------------------------------*/
25
26 #define PROGNAME "timeout"
27
28 #define USAGE PROGNAME " : 1.10\n" \
29               "Usage : " PROGNAME " timeout_in_seconds filename [arguments]\n" \
30               "  \"filename\" is executed, the arguments are passed to \"filename\".\n" \
31               "  When \"filename\" exits before the timeout expires, the\n" \
32               "  exit-status of \"filename\" is returned.\n" \
33               "  When the timeout expires before \"filename\" exits, \"filename\"\n" \
34               "  will be killed and an exit-status of 1 is returned.\n"
35
36 #ifdef _WIN32
37 #include <windows.h>
38 #include <stdio.h>
39 #include <malloc.h>
40
41 /*
42    Native WIN32 version:
43
44    The program creates a chile process using CreateProcess(). The waits until:
45    - the child exits
46         The exit status of the child is returned.
47    - the timeout elapses
48         The child will be killed.
49 */
50
51 int
52 makeCmdLine(char *buf, int argc, const char * const *argv)
53 {
54   int len = 0;
55
56   argv += 2;
57   while (argc-- > 2)
58     {
59       int argLen = strlen(*argv);
60       len += argLen;
61       if (buf)
62         {
63           memcpy(buf, *argv, argLen);
64           buf += argLen;
65         }
66       if (argc > 2)
67         {
68           ++len;
69           if (buf)
70             *buf++ = ' ';
71         }
72       ++argv;
73     }
74   if (buf)
75     *buf = '\0';
76
77   return len + 1;
78 }
79
80 int
81 main (int argc, const char * const *argv)
82 {
83   STARTUPINFO si;
84   PROCESS_INFORMATION pi;
85   char *cmdLine;
86   long timeout;
87   DWORD exitCode;
88   HANDLE oldStderr;
89
90   if (argc < 3)
91     {
92       fprintf (stderr, USAGE);
93       return 1;
94     }
95   timeout = atol (argv[1]);
96   if (timeout == 0)
97     {
98       fprintf (stderr, "Error parameter " PROGNAME ": must be a non-zero dezimal value\n");
99       return 1;
100     }
101
102   cmdLine = alloca(makeCmdLine(NULL, argc, argv));
103   makeCmdLine(cmdLine, argc, argv);
104
105   ZeroMemory(&si, sizeof (si));
106   si.cb = sizeof (si);
107   ZeroMemory(&pi, sizeof (pi));
108
109   /* We'll redirect here stderr to stdout, which will be redirected  */
110   /* to /dev/null by the shell. The shell could also redirect stderr */
111   /* to /dev/null, but then this program doesn't have the chance to  */
112   /* output any real error. */
113   oldStderr = GetStdHandle (STD_ERROR_HANDLE);
114   SetStdHandle (STD_ERROR_HANDLE, GetStdHandle (STD_OUTPUT_HANDLE));
115
116   /* Start the child process. */
117   if(!CreateProcess (NULL, // No module name (use command line)
118     cmdLine,        // Command line
119     NULL,           // Process handle not inheritable
120     NULL,           // Thread handle not inheritable
121     TRUE,           // Set handle inheritance to TRUE
122     0,              // No creation flags
123     NULL,           // Use parent's environment block
124     NULL,           // Use parent's starting directory 
125     &si,            // Pointer to STARTUPINFO structure
126     &pi)            // Pointer to PROCESS_INFORMATION structure
127     ) 
128     {
129       printf ("CreateProcess failed (%d).\n", GetLastError ());
130       return 1;
131     }
132
133   /* Restore stderr */
134   SetStdHandle (STD_ERROR_HANDLE, oldStderr);
135
136   /* Wait until child process exits or timeout expires. */
137   if (WaitForSingleObject (pi.hProcess, timeout * 1000) == WAIT_TIMEOUT)
138     {
139       TerminateProcess (pi.hProcess, 1);
140       WaitForSingleObject (pi.hProcess, INFINITE);
141     }
142
143   GetExitCodeProcess (pi.hProcess, &exitCode);
144
145   /* Close process and thread handles. */
146   CloseHandle (pi.hProcess);
147   CloseHandle (pi.hThread);
148
149   return exitCode;
150 }
151
152 #else
153
154 #include <signal.h>
155 #include <stdio.h>
156 #include <stdlib.h>
157 #include <sys/wait.h>
158 #include <sys/types.h>
159 #include <sys/time.h>
160 #include <sys/resource.h>
161 #include <unistd.h>
162
163 /* First the program tries to limit the maximum CPU-time to the timeout-value.
164    Then the child is run with execvp().
165
166    It's not possible to limit the CPU-time under Cygwin (V1.3.3). If setrlimit (RLIMIT_CPU, rlp)
167    fails, the program will fork() and run the child with execvp(). The fork/exec pair is slow on
168    Cygwin, but what else can we do? The parent sleeps until:
169    - a signal shows the childĀ“s exitus
170         The exit status of the child is returned.
171    - the timeout elapses
172         The child will be killed.
173 */
174
175 /* Get the status from all child processes that have terminated, without ever waiting.
176    This function is designed to be a handler for SIGCHLD, the signal that indicates
177    that at least one child process has terminated.
178    http://www.cs.utah.edu/dept/old/texinfo/glibc-manual-0.02/library_23.html#SEC401
179 */
180
181 #ifndef WAIT_ANY
182   #define WAIT_ANY -1
183 #endif
184
185 void
186 sigchld_handler (int signum)
187 {
188   int pid;
189   int status;
190   int exit_status = 0;
191
192   while (1)
193     {
194       pid = waitpid (WAIT_ANY, &status, WNOHANG);
195       if (WEXITSTATUS (status))
196         exit_status = 1; // WEXITSTATUS(status);
197       /* pid == -1: no children               */
198       /* pid ==  0: no children to be noticed */
199       if (pid <= 0)
200         break;
201     }
202   exit (exit_status);
203 }
204
205 int
206 main (int argc, char * const *argv)
207 {
208   /* if getrlimit() / setrlimit() succeed, then no fork is neeeded */
209   int flagNoFork = 0;
210   int old_stderr;
211   long timeout;
212   pid_t pid_child;
213   struct rlimit rl;
214
215   if (argc < 3)
216     {
217       fprintf (stderr, USAGE);
218       return 1;
219     }
220   timeout = atol (argv[1]);
221   if (timeout == 0)
222     {
223       fprintf (stderr, "Error parameter " PROGNAME ": must be a non-zero dezimal value\n");
224       return 1;
225     }
226
227   /* try to use getrlimit() / setrlimit() for RLIMIT_CPU */
228   /* to limit the CPU-time                               */
229   if (getrlimit (RLIMIT_CPU, &rl) == 0)
230     {
231       rl.rlim_cur = timeout;
232       if (setrlimit (RLIMIT_CPU, &rl) == 0)
233         flagNoFork = 1;
234     }
235
236   if (flagNoFork)
237     { /* the CPU-time is limited: simple execvp */
238
239       /* s51 prints warnings on stderr:                                  */
240       /* serial input/output interface connected to a non-terminal file. */
241       /* We'll redirect here stderr to stdout, which will be redirected  */
242       /* to /dev/null by the shell. The shell could also redirect stderr */
243       /* to /dev/null, but then this program doesn't have the chance to  */
244       /* output any real error. */
245       old_stderr = dup (STDERR_FILENO);
246       dup2 (STDOUT_FILENO, STDERR_FILENO);
247       /* shouldn't return */
248       execvp (argv[2], argv + 2);
249       /* restore stderr */
250       dup2 (old_stderr, STDERR_FILENO);
251       perror (argv[2]);
252       return 1; /* Error */
253     }
254   else
255     {
256       /* do it the hard way: fork/exec */
257       signal (SIGCHLD, sigchld_handler);
258       pid_child = fork();
259       if (pid_child == 0)
260         {
261            /* s51 prints warnings on stderr:                                  */
262            /* serial input/output interface connected to a non-terminal file. */
263            /* We'll redirect here stderr to stdout, which will be redirected  */
264            /* to /dev/null by the shell. The shell could also redirect stderr */
265            /* to /dev/null, but then this program doesn't have the chance to  */
266            /* output any real error. */
267            old_stderr = dup (STDERR_FILENO);
268            dup2 (STDOUT_FILENO, STDERR_FILENO);
269            /* shouldn't return */
270            execvp (argv[2], argv + 2);
271            /* restore stderr */
272            dup2 (old_stderr, STDERR_FILENO);
273            perror (argv[2]);
274            return 1; /* Error */
275         }
276       else
277         {
278           /* this timeout is hopefully aborted by a SIGCHLD */
279           sleep (timeout);
280           fprintf (stderr, PROGNAME ": timeout, killing child %s\n", argv[2]);
281           kill (pid_child, SIGTERM);
282           return 1; /* Error */
283         }
284     }
285 }
286 #endif