refacotring
[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 static bool
57 run_commands (const struct string_array *commands, double time_limit)
58 {
59   if (time_limit != DBL_MAX)
60     {
61       msg (SE, _("Time limit not supported on this platform."));
62       return false;
63     }
64
65   for (size_t i = 0; i < commands->n; i++)
66     {
67       /* XXX No way to capture command output */
68       char *s = recode_string (locale_charset (), "UTF-8",
69                                commands->strings[i], -1);
70       int retval = system (s);
71       free (s);
72
73       if (retval)
74         {
75           msg (SE, _("%s: Command exited with status %d."),
76                commands->strings[i], retval);
77           return false;
78         }
79     }
80   return true;
81 }
82 #else
83 static bool
84 run_command (const char *command, struct timespec timeout)
85 {
86   /* Same exit codes used by 'sh'. */
87   enum {
88     EXIT_CANNOT_INVOKE = 126,
89     EXIT_ENOENT = 127,
90   };
91
92   /* Create a temporary file to capture command output. */
93   FILE *output_file = create_temp_file ();
94   if (!output_file)
95     {
96       msg (SE, _("Failed to create temporary file (%s)."), strerror (errno));
97       return false;
98     }
99
100   int dev_null_fd = open ("/dev/null", O_RDONLY);
101   if (dev_null_fd < 0)
102     {
103       msg (SE, _("/dev/null: Failed to open (%s)."), strerror (errno));
104       fclose (output_file);
105       return false;
106     }
107
108   char *locale_command = recode_string (locale_charset (), "UTF-8",
109                                         command, -1);
110
111   pid_t pid = fork ();
112   if (pid < 0)
113     {
114       close (dev_null_fd);
115       fclose (output_file);
116       free (locale_command);
117
118       msg (SE, _("Couldn't fork: %s."), strerror (errno));
119       return false;
120     }
121   else if (!pid)
122     {
123       /* Running in the child. */
124
125 #if __GNU__
126       /* Hurd doesn't support inheriting process timers in a way that works. */
127       if (setpgid (0, 0) < 0)
128         error (1, errno, _("Failed to set process group."));
129 #else
130       /* Set up timeout. */
131       if (timeout.tv_sec < TYPE_MAXIMUM (time_t))
132         {
133           signal (SIGALRM, SIG_DFL);
134
135           struct timespec left = timespec_sub (timeout, current_timespec ());
136           if (timespec_sign (left) <= 0)
137             raise (SIGALRM);
138
139           struct itimerval it = {
140             .it_value = {
141               .tv_sec = left.tv_sec,
142               .tv_usec = left.tv_nsec / 1000
143             }
144           };
145           if (setitimer (ITIMER_REAL, &it, NULL) < 0)
146             error (1, errno, _("Failed to set timeout."));
147         }
148 #endif
149
150       /* Set up file descriptors:
151          - /dev/null for stdin
152          - Temporary file to capture stdout and stderr.
153          - Close everything else.
154       */
155       dup2 (dev_null_fd, 0);
156       dup2 (fileno (output_file), 1);
157       dup2 (fileno (output_file), 2);
158       close (dev_null_fd);
159       for (int fd = 3; fd < 256; fd++)
160         close (fd);
161
162       /* Choose the shell. */
163       const char *shell = getenv ("SHELL");
164       if (shell == NULL)
165         shell = "/bin/sh";
166
167       /* Run subprocess. */
168       execl (shell, shell, "-c", locale_command, NULL);
169
170       /* Failed to start the shell. */
171       _exit (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
172     }
173
174   /* Running in the parent. */
175   close (dev_null_fd);
176   free (locale_command);
177
178   /* Wait for child to exit. */
179   int status = 0;
180   int error = 0;
181   for (;;)
182     {
183 #if __GNU__
184       if (timespec_cmp (current_timespec (), timeout) >= 0)
185         kill (-pid, SIGALRM);
186
187       int flags = WNOHANG;
188 #else
189       int flags = 0;
190 #endif
191       pid_t retval = waitpid (pid, &status, flags);
192       if (retval == pid)
193         break;
194       else if (retval < 0)
195         {
196           if (errno != EINTR)
197             {
198               error = errno;
199               break;
200             }
201         }
202 #if __GNU__
203       else if (retval == 0)
204         sleep (1);
205 #endif
206       else
207         NOT_REACHED ();
208     }
209
210   bool ok = true;
211   if (error)
212     {
213       msg (SW, _("While running \"%s\", waiting for child process "
214                  "failed (%s)."),
215            command, strerror (errno));
216       ok = false;
217     }
218
219   if (WIFSIGNALED (status))
220     {
221       int signum = WTERMSIG (status);
222       if (signum == SIGALRM)
223         msg (SW, _("Command \"%s\" timed out."), command);
224       else
225         msg (SW, _("Command \"%s\" terminated by signal %d."), command, signum);
226       ok = false;
227     }
228   else if (WIFEXITED (status) && WEXITSTATUS (status))
229     {
230       int exit_code = WEXITSTATUS (status);
231       const char *detail = (exit_code == EXIT_ENOENT
232                             ? _("Command or shell not found")
233                             : exit_code == EXIT_CANNOT_INVOKE
234                             ? _("Could not invoke command or shell")
235                             : NULL);
236       if (detail)
237         msg (SW, _("Command \"%s\" exited with status %d (%s)."),
238              command, exit_code, detail);
239       else
240         msg (SW, _("Command \"%s\" exited with status %d."),
241              command, exit_code);
242       ok = false;
243     }
244
245   rewind (output_file);
246   size_t length;
247   char *locale_output = fread_file (output_file, 0, &length);
248   if (!locale_output)
249     {
250       msg (SW, _("Command \"%s\" output could not be read (%s)."),
251            command, strerror (errno));
252       ok = false;
253     }
254   else if (length > 0)
255     {
256       char *output = recode_string ("UTF-8", locale_charset (),
257                                     locale_output, -1);
258
259       /* Drop final new-line, if any. */
260       char *end = strchr (output, '\0');
261       if (end > output && end[-1] == '\n')
262         end[-1] = '\0';
263
264       output_log_nocopy (output);
265     }
266   free (locale_output);
267
268   return ok;
269 }
270
271 static bool
272 run_commands (const struct string_array *commands, double time_limit)
273 {
274   struct timespec timeout = timespec_add (dtotimespec (time_limit),
275                                           current_timespec ());
276
277   for (size_t i = 0; i < commands->n; i++)
278     {
279       if (!run_command (commands->strings[i], timeout))
280         return false;
281     }
282
283   return true;
284 }
285 #endif
286
287 int
288 cmd_host (struct lexer *lexer, struct dataset *ds UNUSED)
289 {
290   if (settings_get_safer_mode ())
291     {
292       msg (SE, _("This command not allowed when the %s option is set."), "SAFER");
293       return CMD_FAILURE;
294     }
295
296   if (!lex_force_match_id (lexer, "COMMAND")
297       || !lex_force_match (lexer, T_EQUALS)
298       || !lex_force_match (lexer, T_LBRACK)
299       || !lex_force_string (lexer))
300     return CMD_FAILURE;
301
302   struct string_array commands = STRING_ARRAY_INITIALIZER;
303   while (lex_token (lexer) == T_STRING)
304     {
305       string_array_append (&commands, lex_tokcstr (lexer));
306       lex_get (lexer);
307     }
308   if (!lex_force_match (lexer, T_RBRACK))
309     {
310       string_array_destroy (&commands);
311       return CMD_FAILURE;
312     }
313
314   double time_limit = DBL_MAX;
315   if (lex_match_id (lexer, "TIMELIMIT"))
316     {
317       if (!lex_force_match (lexer, T_EQUALS)
318           || !lex_force_num (lexer))
319         {
320           string_array_destroy (&commands);
321           return CMD_FAILURE;
322         }
323
324       double num = lex_number (lexer);
325       lex_get (lexer);
326       time_limit = num < 0.0 ? 0.0 : num;
327     }
328
329   enum cmd_result result = lex_end_of_command (lexer);
330   if (result == CMD_SUCCESS && !run_commands (&commands, time_limit))
331     result = CMD_FAILURE;
332   string_array_destroy (&commands);
333   return result;
334 }