http://packetstormsecurity.org/UNIX/admin/xscreensaver-4.01.tar.gz
[xscreensaver] / driver / exec.c
1 /* exec.c --- executes a program in *this* pid, without an intervening process.
2  * xscreensaver, Copyright (c) 1991-2002 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
62 #ifndef ESRCH
63 # include <errno.h>
64 #endif
65
66 #if defined(HAVE_SETPRIORITY) && defined(PRIO_PROCESS)
67 # include <sys/resource.h>      /* for setpriority() and PRIO_PROCESS */
68                                 /* and also setrlimit() and RLIMIT_AS */
69 #endif
70
71 #ifdef VMS
72 # include <processes.h>
73 # include <unixio.h>            /* for close */
74 # include <unixlib.h>           /* for getpid */
75 # define pid_t int
76 # define fork  vfork
77 #endif /* VMS */
78
79
80 extern const char *blurb (void);
81
82 static void nice_process (int nice_level);
83
84
85 #ifndef VMS
86
87 static void
88 exec_simple_command (const char *command)
89 {
90   char *av[1024];
91   int ac = 0;
92   char *token = strtok (strdup(command), " \t");
93   while (token)
94     {
95       av[ac++] = token;
96       token = strtok(0, " \t");
97     }
98   av[ac] = 0;
99
100   execvp (av[0], av);   /* shouldn't return. */
101 }
102
103
104 static void
105 exec_complex_command (const char *shell, const char *command)
106 {
107   char *av[5];
108   int ac = 0;
109   char *command2 = (char *) malloc (strlen (command) + 10);
110   const char *s;
111   int got_eq = 0;
112   const char *after_vars;
113
114   /* Skip leading whitespace.
115    */
116   while (*command == ' ' || *command == '\t')
117     command++;
118
119   /* If the string has a series of tokens with "=" in them at them, set
120      `after_vars' to point into the string after those tokens and any
121      trailing whitespace.  Otherwise, after_vars == command.
122    */
123   after_vars = command;
124   for (s = command; *s; s++)
125     {
126       if (*s == '=') got_eq = 1;
127       else if (*s == ' ')
128         {
129           if (got_eq)
130             {
131               while (*s == ' ' || *s == '\t')
132                 s++;
133               after_vars = s;
134               got_eq = 0;
135             }
136           else
137             break;
138         }
139     }
140
141   *command2 = 0;
142   strncat (command2, command, after_vars - command);
143   strcat (command2, "exec ");
144   strcat (command2, after_vars);
145
146   /* We have now done these transformations:
147      "foo -x -y"               ==>  "exec foo -x -y"
148      "BLAT=foop      foo -x"   ==>  "BLAT=foop      exec foo -x"
149      "BLAT=foop A=b  foo -x"   ==>  "BLAT=foop A=b  exec foo -x"
150    */
151
152
153   /* Invoke the shell as "/bin/sh -c 'exec prog -arg -arg ...'" */
154   av [ac++] = (char *) shell;
155   av [ac++] = "-c";
156   av [ac++] = command2;
157   av [ac]   = 0;
158
159   execvp (av[0], av);   /* shouldn't return. */
160 }
161
162 #else  /* VMS */
163
164 static void
165 exec_vms_command (const char *command)
166 {
167   system (command);
168   fflush (stderr);
169   fflush (stdout);
170   exit (1);     /* Note that this only exits a child fork.  */
171 }
172
173 #endif /* !VMS */
174
175
176 void
177 exec_command (const char *shell, const char *command, int nice_level)
178 {
179   int hairy_p;
180
181 #ifndef VMS
182   if (nice != 0)
183     nice_process (nice_level);
184
185   hairy_p = !!strpbrk (command, "*?$&!<>[];`'\\\"=");
186   /* note: = is in the above because of the sh syntax "FOO=bar cmd". */
187
188   if (getuid() == (uid_t) 0 || geteuid() == (uid_t) 0)
189     {
190       /* If you're thinking of commenting this out, think again.
191          If you do so, you will open a security hole.  Mail jwz
192          so that he may enlighten you as to the error of your ways.
193        */
194       fprintf (stderr, "%s: we're still running as root!  Disaster!\n",
195                blurb());
196       exit (-1);
197     }
198
199   if (hairy_p)
200     /* If it contains any shell metacharacters, do it the hard way,
201        and fork a shell to parse the arguments for us. */
202     exec_complex_command (shell, command);
203   else
204     /* Otherwise, we can just exec the program directly. */
205     exec_simple_command (command);
206
207 #else  /* VMS */
208   exec_vms_command (command);
209 #endif /* VMS */
210 }
211
212 \f
213 /* Setting process priority
214  */
215
216 static void
217 nice_process (int nice_level)
218 {
219   if (nice_level == 0)
220     return;
221
222 #if defined(HAVE_NICE)
223   {
224     int old_nice = nice (0);
225     int n = nice_level - old_nice;
226     errno = 0;
227     if (nice (n) == -1 && errno != 0)
228       {
229         char buf [512];
230         sprintf (buf, "%s: nice(%d) failed", blurb(), n);
231         perror (buf);
232     }
233   }
234 #elif defined(HAVE_SETPRIORITY) && defined(PRIO_PROCESS)
235   if (setpriority (PRIO_PROCESS, getpid(), nice_level) != 0)
236     {
237       char buf [512];
238       sprintf (buf, "%s: setpriority(PRIO_PROCESS, %lu, %d) failed",
239                blurb(), (unsigned long) getpid(), nice_level);
240       perror (buf);
241     }
242 #else
243   fprintf (stderr,
244            "%s: don't know how to change process priority on this system.\n",
245            blurb());
246
247 #endif
248 }