lexer: New function lex_force_match_phrase().
[pspp] / src / language / utilities / host.c
1 /* pspp - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000, 2009, 2010, 2011 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program 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    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include <ctype.h>
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <signal.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <sys/time.h>
26 #include <unistd.h>
27 #if HAVE_SYS_WAIT_H
28 #include <sys/wait.h>
29 #endif
30
31 #include "data/settings.h"
32 #include "language/command.h"
33 #include "language/lexer/lexer.h"
34 #include "libpspp/assertion.h"
35 #include "libpspp/compiler.h"
36 #include "libpspp/i18n.h"
37 #include "libpspp/message.h"
38 #include "libpspp/str.h"
39 #include "libpspp/string-array.h"
40 #include "libpspp/temp-file.h"
41 #include "output/driver.h"
42
43 #include "gl/error.h"
44 #include "gl/intprops.h"
45 #include "gl/localcharset.h"
46 #include "gl/read-file.h"
47 #include "gl/timespec.h"
48 #include "gl/xalloc.h"
49 #include "gl/xmalloca.h"
50
51 #include "gettext.h"
52 #define _(msgid) gettext (msgid)
53 #define N_(msgid) msgid
54 \f
55 #if !HAVE_FORK
56 #define TIME_LIMIT_SUPPORTED 0
57 static bool
58 run_commands (const struct string_array *commands, double time_limit)
59 {
60   assert (time_limit == DBL_MAX);
61
62   for (size_t i = 0; i < commands->n; i++)
63     {
64       /* XXX No way to capture command output */
65       char *s = recode_string (locale_charset (), "UTF-8",
66                                commands->strings[i], -1);
67       int retval = system (s);
68       free (s);
69
70       if (retval)
71         {
72           msg (SE, _("%s: Command exited with status %d."),
73                commands->strings[i], retval);
74           return false;
75         }
76     }
77   return true;
78 }
79 #else
80 #define TIME_LIMIT_SUPPORTED 1
81 static bool
82 run_command (const char *command, struct timespec timeout)
83 {
84   /* Same exit codes used by 'sh'. */
85   enum {
86     EXIT_CANNOT_INVOKE = 126,
87     EXIT_ENOENT = 127,
88   };
89
90   /* Create a temporary file to capture command output. */
91   FILE *output_file = create_temp_file ();
92   if (!output_file)
93     {
94       msg (SE, _("Failed to create temporary file (%s)."), strerror (errno));
95       return false;
96     }
97
98   int dev_null_fd = open ("/dev/null", O_RDONLY);
99   if (dev_null_fd < 0)
100     {
101       msg (SE, _("/dev/null: Failed to open (%s)."), strerror (errno));
102       fclose (output_file);
103       return false;
104     }
105
106   char *locale_command = recode_string (locale_charset (), "UTF-8",
107                                         command, -1);
108
109   pid_t pid = fork ();
110   if (pid < 0)
111     {
112       close (dev_null_fd);
113       fclose (output_file);
114       free (locale_command);
115
116       msg (SE, _("Couldn't fork: %s."), strerror (errno));
117       return false;
118     }
119   else if (!pid)
120     {
121       /* Running in the child. */
122
123 #if __GNU__
124       /* Hurd doesn't support inheriting process timers in a way that works. */
125       if (setpgid (0, 0) < 0)
126         error (1, errno, _("Failed to set process group."));
127 #else
128       /* Set up timeout. */
129       if (timeout.tv_sec < TYPE_MAXIMUM (time_t))
130         {
131           signal (SIGALRM, SIG_DFL);
132
133           struct timespec left = timespec_sub (timeout, current_timespec ());
134           if (timespec_sign (left) <= 0)
135             raise (SIGALRM);
136
137           struct itimerval it = {
138             .it_value = {
139               .tv_sec = left.tv_sec,
140               .tv_usec = left.tv_nsec / 1000
141             }
142           };
143           if (setitimer (ITIMER_REAL, &it, NULL) < 0)
144             error (1, errno, _("Failed to set timeout."));
145         }
146 #endif
147
148       /* Set up file descriptors:
149          - /dev/null for stdin
150          - Temporary file to capture stdout and stderr.
151          - Close everything else.
152       */
153       dup2 (dev_null_fd, 0);
154       dup2 (fileno (output_file), 1);
155       dup2 (fileno (output_file), 2);
156       close (dev_null_fd);
157       for (int fd = 3; fd < 256; fd++)
158         close (fd);
159
160       /* Choose the shell. */
161       const char *shell = getenv ("SHELL");
162       if (shell == NULL)
163         shell = "/bin/sh";
164
165       /* Run subprocess. */
166       execl (shell, shell, "-c", locale_command, NULL);
167
168       /* Failed to start the shell. */
169       _exit (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
170     }
171
172   /* Running in the parent. */
173   close (dev_null_fd);
174   free (locale_command);
175
176   /* Wait for child to exit. */
177   int status = 0;
178   int error = 0;
179   for (;;)
180     {
181 #if __GNU__
182       if (timespec_cmp (current_timespec (), timeout) >= 0)
183         kill (-pid, SIGALRM);
184
185       int flags = WNOHANG;
186 #else
187       int flags = 0;
188 #endif
189       pid_t retval = waitpid (pid, &status, flags);
190       if (retval == pid)
191         break;
192       else if (retval < 0)
193         {
194           if (errno != EINTR)
195             {
196               error = errno;
197               break;
198             }
199         }
200 #if __GNU__
201       else if (retval == 0)
202         sleep (1);
203 #endif
204       else
205         NOT_REACHED ();
206     }
207
208   bool ok = true;
209   if (error)
210     {
211       msg (SW, _("While running \"%s\", waiting for child process "
212                  "failed (%s)."),
213            command, strerror (errno));
214       ok = false;
215     }
216
217   if (WIFSIGNALED (status))
218     {
219       int signum = WTERMSIG (status);
220       if (signum == SIGALRM)
221         msg (SW, _("Command \"%s\" timed out."), command);
222       else
223         msg (SW, _("Command \"%s\" terminated by signal %d."), command, signum);
224       ok = false;
225     }
226   else if (WIFEXITED (status) && WEXITSTATUS (status))
227     {
228       int exit_code = WEXITSTATUS (status);
229       const char *detail = (exit_code == EXIT_ENOENT
230                             ? _("Command or shell not found")
231                             : exit_code == EXIT_CANNOT_INVOKE
232                             ? _("Could not invoke command or shell")
233                             : NULL);
234       if (detail)
235         msg (SW, _("Command \"%s\" exited with status %d (%s)."),
236              command, exit_code, detail);
237       else
238         msg (SW, _("Command \"%s\" exited with status %d."),
239              command, exit_code);
240       ok = false;
241     }
242
243   rewind (output_file);
244   size_t length;
245   char *locale_output = fread_file (output_file, 0, &length);
246   if (!locale_output)
247     {
248       msg (SW, _("Command \"%s\" output could not be read (%s)."),
249            command, strerror (errno));
250       ok = false;
251     }
252   else if (length > 0)
253     {
254       char *output = recode_string ("UTF-8", locale_charset (),
255                                     locale_output, -1);
256
257       /* Drop final new-line, if any. */
258       char *end = strchr (output, '\0');
259       if (end > output && end[-1] == '\n')
260         end[-1] = '\0';
261
262       output_log_nocopy (output);
263     }
264   free (locale_output);
265
266   return ok;
267 }
268
269 static bool
270 run_commands (const struct string_array *commands, double time_limit)
271 {
272   struct timespec timeout = timespec_add (dtotimespec (time_limit),
273                                           current_timespec ());
274
275   for (size_t i = 0; i < commands->n; i++)
276     {
277       if (!run_command (commands->strings[i], timeout))
278         return false;
279     }
280
281   return true;
282 }
283 #endif
284
285 int
286 cmd_host (struct lexer *lexer, struct dataset *ds UNUSED)
287 {
288   if (settings_get_safer_mode ())
289     {
290       lex_next_error (lexer, -1, -1,
291                       _("This command not allowed when the %s option is set."),
292                       "SAFER");
293       return CMD_FAILURE;
294     }
295
296   if (!lex_force_match_phrase (lexer, "COMMAND=[")
297       || !lex_force_string (lexer))
298     return CMD_FAILURE;
299
300   struct string_array commands = STRING_ARRAY_INITIALIZER;
301   while (lex_token (lexer) == T_STRING)
302     {
303       string_array_append (&commands, lex_tokcstr (lexer));
304       lex_get (lexer);
305     }
306   if (!lex_force_match (lexer, T_RBRACK))
307     {
308       string_array_destroy (&commands);
309       return CMD_FAILURE;
310     }
311
312   double time_limit = DBL_MAX;
313   if (lex_match_id (lexer, "TIMELIMIT"))
314     {
315       int time_limit_start = lex_ofs (lexer) - 1;
316       if (!lex_force_match (lexer, T_EQUALS)
317           || !lex_force_num (lexer))
318         {
319           string_array_destroy (&commands);
320           return CMD_FAILURE;
321         }
322
323       double num = lex_number (lexer);
324       lex_get (lexer);
325       time_limit = num < 0.0 ? 0.0 : num;
326
327       int time_limit_end = lex_ofs (lexer) - 1;
328       if (!TIME_LIMIT_SUPPORTED)
329         {
330           lex_ofs_error (lexer, time_limit_start, time_limit_end,
331                          _("Time limit not supported on this platform."));
332           string_array_destroy (&commands);
333           return false;
334         }
335     }
336
337   enum cmd_result result = lex_end_of_command (lexer);
338   if (result == CMD_SUCCESS && !run_commands (&commands, time_limit))
339     result = CMD_FAILURE;
340   string_array_destroy (&commands);
341   return result;
342 }