began for maemo
[xscreensaver] / xscreensaver / driver / exec.c
1 /* exec.c --- executes a program in *this* pid, without an intervening process.
2  * xscreensaver, Copyright (c) 1991-2006 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   if (nice != 0)
185     nice_process (nice_level);
186
187   hairy_p = !!strpbrk (command, "*?$&!<>[];`'\\\"=");
188   /* note: = is in the above because of the sh syntax "FOO=bar cmd". */
189
190   if (getuid() == (uid_t) 0 || geteuid() == (uid_t) 0)
191     {
192       /* If you're thinking of commenting this out, think again.
193          If you do so, you will open a security hole.  Mail jwz
194          so that he may enlighten you as to the error of your ways.
195        */
196       fprintf (stderr, "%s: we're still running as root!  Disaster!\n",
197                blurb());
198       exit (-1);
199     }
200
201   if (hairy_p)
202     /* If it contains any shell metacharacters, do it the hard way,
203        and fork a shell to parse the arguments for us. */
204     exec_complex_command (shell, command);
205   else
206     /* Otherwise, we can just exec the program directly. */
207     exec_simple_command (command);
208
209 #else  /* VMS */
210   exec_vms_command (command);
211 #endif /* VMS */
212 }
213
214 \f
215 /* Setting process priority
216  */
217
218 static void
219 nice_process (int nice_level)
220 {
221   if (nice_level == 0)
222     return;
223
224 #if defined(HAVE_NICE)
225   {
226     int old_nice = nice (0);
227     int n = nice_level - old_nice;
228     errno = 0;
229     if (nice (n) == -1 && errno != 0)
230       {
231         char buf [512];
232         sprintf (buf, "%s: nice(%d) failed", blurb(), n);
233         perror (buf);
234     }
235   }
236 #elif defined(HAVE_SETPRIORITY) && defined(PRIO_PROCESS)
237   if (setpriority (PRIO_PROCESS, getpid(), nice_level) != 0)
238     {
239       char buf [512];
240       sprintf (buf, "%s: setpriority(PRIO_PROCESS, %lu, %d) failed",
241                blurb(), (unsigned long) getpid(), nice_level);
242       perror (buf);
243     }
244 #else
245   fprintf (stderr,
246            "%s: don't know how to change process priority on this system.\n",
247            blurb());
248
249 #endif
250 }
251
252
253 /* Whether the given command exists on $PATH.
254    (Anything before the first space is considered to be the program name.)
255  */
256 int
257 on_path_p (const char *program)
258 {
259   int result = 0;
260   struct stat st;
261   char *cmd = strdup (program);
262   char *token = strchr (cmd, ' ');
263   char *path = 0;
264   int L;
265
266   if (token) *token = 0;
267   token = 0;
268
269   if (strchr (cmd, '/'))
270     {
271       result = (0 == stat (cmd, &st));
272       goto DONE;
273     }
274
275   path = getenv("PATH");
276   if (!path || !*path)
277     goto DONE;
278
279   L = strlen (cmd);
280   path = strdup (path);
281   token = strtok (path, ":");
282
283   while (token)
284     {
285       char *p2 = (char *) malloc (strlen (token) + L + 3);
286       strcpy (p2, token);
287       strcat (p2, "/");
288       strcat (p2, cmd);
289       result = (0 == stat (p2, &st));
290       if (result)
291         goto DONE;
292       token = strtok (0, ":");
293     }
294
295  DONE:
296   free (cmd);
297   if (path) free (path);
298   return result;
299 }
300