http://ftp.ksu.edu.tw/FTP/FreeBSD/distfiles/xscreensaver-4.24.tar.gz
[xscreensaver] / driver / exec.c
1 /* exec.c --- executes a program in *this* pid, without an intervening process.
2  * xscreensaver, Copyright (c) 1991-2005 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
81 extern const char *blurb (void);
82
83 static void nice_process (int nice_level);
84
85
86 #ifndef VMS
87
88 static void
89 exec_simple_command (const char *command)
90 {
91   char *av[1024];
92   int ac = 0;
93   char *token = strtok (strdup(command), " \t");
94   while (token)
95     {
96       av[ac++] = token;
97       token = strtok(0, " \t");
98     }
99   av[ac] = 0;
100
101   execvp (av[0], av);   /* shouldn't return. */
102 }
103
104
105 static void
106 exec_complex_command (const char *shell, const char *command)
107 {
108   char *av[5];
109   int ac = 0;
110   char *command2 = (char *) malloc (strlen (command) + 10);
111   const char *s;
112   int got_eq = 0;
113   const char *after_vars;
114
115   /* Skip leading whitespace.
116    */
117   while (*command == ' ' || *command == '\t')
118     command++;
119
120   /* If the string has a series of tokens with "=" in them at them, set
121      `after_vars' to point into the string after those tokens and any
122      trailing whitespace.  Otherwise, after_vars == command.
123    */
124   after_vars = command;
125   for (s = command; *s; s++)
126     {
127       if (*s == '=') got_eq = 1;
128       else if (*s == ' ')
129         {
130           if (got_eq)
131             {
132               while (*s == ' ' || *s == '\t')
133                 s++;
134               after_vars = s;
135               got_eq = 0;
136             }
137           else
138             break;
139         }
140     }
141
142   *command2 = 0;
143   strncat (command2, command, after_vars - command);
144   strcat (command2, "exec ");
145   strcat (command2, after_vars);
146
147   /* We have now done these transformations:
148      "foo -x -y"               ==>  "exec foo -x -y"
149      "BLAT=foop      foo -x"   ==>  "BLAT=foop      exec foo -x"
150      "BLAT=foop A=b  foo -x"   ==>  "BLAT=foop A=b  exec foo -x"
151    */
152
153
154   /* Invoke the shell as "/bin/sh -c 'exec prog -arg -arg ...'" */
155   av [ac++] = (char *) shell;
156   av [ac++] = "-c";
157   av [ac++] = command2;
158   av [ac]   = 0;
159
160   execvp (av[0], av);   /* shouldn't return. */
161 }
162
163 #else  /* VMS */
164
165 static void
166 exec_vms_command (const char *command)
167 {
168   system (command);
169   fflush (stderr);
170   fflush (stdout);
171   exit (1);     /* Note that this only exits a child fork.  */
172 }
173
174 #endif /* !VMS */
175
176
177 void
178 exec_command (const char *shell, const char *command, int nice_level)
179 {
180   int hairy_p;
181
182 #ifndef VMS
183   if (nice != 0)
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