From http://www.jwz.org/xscreensaver/xscreensaver-5.16.tar.gz
[xscreensaver] / driver / exec.c
1 /* exec.c --- executes a program in *this* pid, without an intervening process.
2  * xscreensaver, Copyright (c) 1991-2008 Jamie Zawinski <jwz@jwz.org>
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or 
10  * implied warranty.
11  */
12
13
14 /* I don't believe what a sorry excuse for an operating system UNIX is!
15
16    - I want to spawn a process.
17    - I want to know it's pid so that I can kill it.
18    - I would like to receive a message when it dies of natural causes.
19    - I want the spawned process to have user-specified arguments.
20
21    If shell metacharacters are present (wildcards, backquotes, etc), the
22    only way to parse those arguments is to run a shell to do the parsing
23    for you.
24
25    And the only way to know the pid of the process is to fork() and exec()
26    it in the spawned side of the fork.
27
28    But if you're running a shell to parse your arguments, this gives you
29    the pid of the *shell*, not the pid of the *process* that you're
30    actually interested in, which is an *inferior* of the shell.  This also
31    means that the SIGCHLD you get applies to the shell, not its inferior.
32    (Why isn't that sufficient?  I don't remember any more, but it turns
33    out that it isn't.)
34
35    So, the only solution, when metacharacters are present, is to force the
36    shell to exec() its inferior.  What a fucking hack!  We prepend "exec "
37    to the command string, and hope it doesn't contain unquoted semicolons
38    or ampersands (we don't search for them, because we don't want to
39    prohibit their use in quoted strings (messages, for example) and parsing
40    out the various quote characters is too much of a pain.)
41
42    (Actually, Clint Wong <clint@jts.com> points out that process groups
43    might be used to take care of this problem; this may be worth considering
44    some day, except that, 1: this code works now, so why fix it, and 2: from
45    what I've seen in Emacs, dealing with process groups isn't especially
46    portable.)
47  */
48
49 #ifdef HAVE_CONFIG_H
50 # include "config.h"
51 #endif
52
53 #include <stdlib.h>
54 #ifdef HAVE_UNISTD_H
55 # include <unistd.h>
56 #endif
57
58 #include <string.h>
59 #include <stdio.h>
60 #include <ctype.h>
61 #include <sys/stat.h>
62
63 #ifndef ESRCH
64 # include <errno.h>
65 #endif
66
67 #if defined(HAVE_SETPRIORITY) && defined(PRIO_PROCESS)
68 # include <sys/resource.h>      /* for setpriority() and PRIO_PROCESS */
69                                 /* and also setrlimit() and RLIMIT_AS */
70 #endif
71
72 #ifdef VMS
73 # include <processes.h>
74 # include <unixio.h>            /* for close */
75 # include <unixlib.h>           /* for getpid */
76 # define pid_t int
77 # define fork  vfork
78 #endif /* VMS */
79
80 #include "exec.h"
81
82 extern const char *blurb (void);
83
84 static void nice_process (int nice_level);
85
86
87 #ifndef VMS
88
89 static void
90 exec_simple_command (const char *command)
91 {
92   char *av[1024];
93   int ac = 0;
94   char *token = strtok (strdup(command), " \t");
95   while (token)
96     {
97       av[ac++] = token;
98       token = strtok(0, " \t");
99     }
100   av[ac] = 0;
101
102   execvp (av[0], av);   /* shouldn't return. */
103 }
104
105
106 static void
107 exec_complex_command (const char *shell, const char *command)
108 {
109   char *av[5];
110   int ac = 0;
111   char *command2 = (char *) malloc (strlen (command) + 10);
112   const char *s;
113   int got_eq = 0;
114   const char *after_vars;
115
116   /* Skip leading whitespace.
117    */
118   while (*command == ' ' || *command == '\t')
119     command++;
120
121   /* If the string has a series of tokens with "=" in them at them, set
122      `after_vars' to point into the string after those tokens and any
123      trailing whitespace.  Otherwise, after_vars == command.
124    */
125   after_vars = command;
126   for (s = command; *s; s++)
127     {
128       if (*s == '=') got_eq = 1;
129       else if (*s == ' ')
130         {
131           if (got_eq)
132             {
133               while (*s == ' ' || *s == '\t')
134                 s++;
135               after_vars = s;
136               got_eq = 0;
137             }
138           else
139             break;
140         }
141     }
142
143   *command2 = 0;
144   strncat (command2, command, after_vars - command);
145   strcat (command2, "exec ");
146   strcat (command2, after_vars);
147
148   /* We have now done these transformations:
149      "foo -x -y"               ==>  "exec foo -x -y"
150      "BLAT=foop      foo -x"   ==>  "BLAT=foop      exec foo -x"
151      "BLAT=foop A=b  foo -x"   ==>  "BLAT=foop A=b  exec foo -x"
152    */
153
154
155   /* Invoke the shell as "/bin/sh -c 'exec prog -arg -arg ...'" */
156   av [ac++] = (char *) shell;
157   av [ac++] = "-c";
158   av [ac++] = command2;
159   av [ac]   = 0;
160
161   execvp (av[0], av);   /* shouldn't return. */
162 }
163
164 #else  /* VMS */
165
166 static void
167 exec_vms_command (const char *command)
168 {
169   system (command);
170   fflush (stderr);
171   fflush (stdout);
172   exit (1);     /* Note that this only exits a child fork.  */
173 }
174
175 #endif /* !VMS */
176
177
178 void
179 exec_command (const char *shell, const char *command, int nice_level)
180 {
181   int hairy_p;
182
183 #ifndef VMS
184   nice_process (nice_level);
185
186   hairy_p = !!strpbrk (command, "*?$&!<>[];`'\\\"=");
187   /* note: = is in the above because of the sh syntax "FOO=bar cmd". */
188
189   if (getuid() == (uid_t) 0 || geteuid() == (uid_t) 0)
190     {
191       /* If you're thinking of commenting this out, think again.
192          If you do so, you will open a security hole.  Mail jwz
193          so that he may enlighten you as to the error of your ways.
194        */
195       fprintf (stderr, "%s: we're still running as root!  Disaster!\n",
196                blurb());
197       exit (-1);
198     }
199
200   if (hairy_p)
201     /* If it contains any shell metacharacters, do it the hard way,
202        and fork a shell to parse the arguments for us. */
203     exec_complex_command (shell, command);
204   else
205     /* Otherwise, we can just exec the program directly. */
206     exec_simple_command (command);
207
208 #else  /* VMS */
209   exec_vms_command (command);
210 #endif /* VMS */
211 }
212
213 \f
214 /* Setting process priority
215  */
216
217 static void
218 nice_process (int nice_level)
219 {
220   if (nice_level == 0)
221     return;
222
223 #if defined(HAVE_NICE)
224   {
225     int old_nice = nice (0);
226     int n = nice_level - old_nice;
227     errno = 0;
228     if (nice (n) == -1 && errno != 0)
229       {
230         char buf [512];
231         sprintf (buf, "%s: nice(%d) failed", blurb(), n);
232         perror (buf);
233     }
234   }
235 #elif defined(HAVE_SETPRIORITY) && defined(PRIO_PROCESS)
236   if (setpriority (PRIO_PROCESS, getpid(), nice_level) != 0)
237     {
238       char buf [512];
239       sprintf (buf, "%s: setpriority(PRIO_PROCESS, %lu, %d) failed",
240                blurb(), (unsigned long) getpid(), nice_level);
241       perror (buf);
242     }
243 #else
244   fprintf (stderr,
245            "%s: don't know how to change process priority on this system.\n",
246            blurb());
247
248 #endif
249 }
250
251
252 /* Whether the given command exists on $PATH.
253    (Anything before the first space is considered to be the program name.)
254  */
255 int
256 on_path_p (const char *program)
257 {
258   int result = 0;
259   struct stat st;
260   char *cmd = strdup (program);
261   char *token = strchr (cmd, ' ');
262   char *path = 0;
263   int L;
264
265   if (token) *token = 0;
266   token = 0;
267
268   if (strchr (cmd, '/'))
269     {
270       result = (0 == stat (cmd, &st));
271       goto DONE;
272     }
273
274   path = getenv("PATH");
275   if (!path || !*path)
276     goto DONE;
277
278   L = strlen (cmd);
279   path = strdup (path);
280   token = strtok (path, ":");
281
282   while (token)
283     {
284       char *p2 = (char *) malloc (strlen (token) + L + 3);
285       strcpy (p2, token);
286       strcat (p2, "/");
287       strcat (p2, cmd);
288       result = (0 == stat (p2, &st));
289       if (result)
290         goto DONE;
291       token = strtok (0, ":");
292     }
293
294  DONE:
295   free (cmd);
296   if (path) free (path);
297   return result;
298 }
299