source: trunk/third/libgnome/libgnome/gnome-exec.c @ 20807

Revision 20807, 14.5 KB checked in by ghudson, 20 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r20806, which included commits to RCS files with non-trunk default branches.
Line 
1/* gnome-exec.c - Execute some command.
2
3   Copyright (C) 1998 Tom Tromey
4   All rights reserved.
5
6   The Gnome Library is free software; you can redistribute it and/or
7   modify it under the terms of the GNU Library General Public License as
8   published by the Free Software Foundation; either version 2 of the
9   License, or (at your option) any later version.
10
11   The Gnome Library is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   Library General Public License for more details.
15
16   You should have received a copy of the GNU Library General Public
17   License along with the Gnome Library; see the file COPYING.LIB.  If not,
18   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19   Boston, MA 02111-1307, USA.  */
20/*
21  @NOTATION@
22 */
23
24#include <config.h>
25
26#include "gnome-i18nP.h"
27
28#include "gnome-exec.h"
29#include "gnome-util.h"
30#include "gnome-i18n.h"
31#include "gnome-gconfP.h"
32#include "gnome-init.h"
33
34#include <unistd.h>
35#include <fcntl.h>
36#include <stdio.h>
37#include <sys/types.h>
38#include <sys/wait.h>
39#include <stdlib.h>
40#include <string.h>
41#include <signal.h>
42
43#include <gconf/gconf-client.h>
44
45#include <popt.h>
46
47#include <errno.h>
48#ifndef errno
49extern int errno;
50#endif
51
52static void
53set_cloexec (gint fd)
54{
55  fcntl (fd, F_SETFD, FD_CLOEXEC);
56}
57
58static ssize_t
59safe_read (int fd, void *buf, size_t count)
60{
61  ssize_t n;
62
63  while ((n = read (fd, buf, count)) < 0 && (errno == EINTR || errno == EAGAIN));
64
65  return n;
66}
67
68/**
69 * gnome_execute_async_with_env_fds:
70 * @dir: Directory in which child should be executed, or %NULL for current
71 *       directory
72 * @argc: Number of arguments
73 * @argv: Argument vector to exec child
74 * @envc: Number of environment slots
75 * @envv: Environment vector
76 * @close_fds: If %TRUE will close all fds but 0,1, and 2
77 *
78 * Description:  Like gnome_execute_async_with_env() but has a flag to
79 * decide whether or not to close fd's
80 *
81 * Returns: the process id, or %-1 on error.
82 **/
83int
84gnome_execute_async_with_env_fds (const char *dir, int argc,
85                                  char * const argv[], int envc,
86                                  char * const envv[],
87                                  gboolean close_fds)
88{
89  int parent_comm_pipes[2], child_comm_pipes[2];
90  int child_errno, itmp, i, open_max;
91  gssize res;
92  char **cpargv;
93  pid_t child_pid, immediate_child_pid; /* XXX this routine assumes
94                                           pid_t is signed */
95
96  if(pipe(parent_comm_pipes))
97    return -1;
98
99  child_pid = immediate_child_pid = fork();
100
101  switch(child_pid) {
102  case -1:
103    close(parent_comm_pipes[0]);
104    close(parent_comm_pipes[1]);
105    return -1;
106
107  case 0: /* START PROCESS 1: child */
108    child_pid = -1;
109    res = pipe(child_comm_pipes);
110    close(parent_comm_pipes[0]);
111    if(!res)
112      child_pid = fork();
113
114    switch(child_pid) {
115    case -1:
116      itmp = errno;
117      child_pid = -1; /* simplify parent code */
118      write(parent_comm_pipes[1], &child_pid, sizeof(child_pid));
119      write(parent_comm_pipes[1], &itmp, sizeof(itmp));
120      close(child_comm_pipes[0]);
121      close(child_comm_pipes[1]);
122      _exit(0); break;      /* END PROCESS 1: monkey in the middle dies */
123
124    default:
125      {
126        char buf[16];
127       
128        close(child_comm_pipes[1]);
129        while((res = safe_read(child_comm_pipes[0], buf, sizeof(buf))) > 0)
130          write(parent_comm_pipes[1], buf, res);
131        close(child_comm_pipes[0]);
132        _exit(0); /* END PROCESS 1: monkey in the middle dies */
133      }
134      break;
135
136    case 0:                 /* START PROCESS 2: child of child */
137      close(parent_comm_pipes[1]);
138      /* pre-exec setup */
139      close (child_comm_pipes[0]);
140      set_cloexec (child_comm_pipes[1]);
141      child_pid = getpid();
142      res = write(child_comm_pipes[1], &child_pid, sizeof(child_pid));
143
144      if(envv) {
145        for(itmp = 0; itmp < envc; itmp++)
146          putenv(envv[itmp]);
147      }
148
149      if(dir) {
150        if(chdir(dir))
151          _exit(-1); 
152      }
153     
154      cpargv = g_alloca((argc + 1) * sizeof(char *));
155      memcpy(cpargv, argv, argc * sizeof(char *));
156      cpargv[argc] = NULL;
157
158      if(close_fds)
159        {
160          int stdinfd;
161          /* Close all file descriptors but stdin stdout and stderr */
162          open_max = sysconf (_SC_OPEN_MAX);
163          for (i = 3; i < open_max; i++)
164            set_cloexec (i);
165
166          if(child_comm_pipes[1] != 0) {
167            close(0);
168            /* Open stdin as being nothingness, so that if someone tries to
169               read from this they don't hang up the whole GNOME session. BUGFIX #1548 */
170            stdinfd = open("/dev/null", O_RDONLY);
171            g_assert(stdinfd >= 0);
172            if(stdinfd != 0)
173              {
174                dup2(stdinfd, 0);
175                close(stdinfd);
176              }
177          }
178        }
179      setsid ();
180      signal (SIGPIPE, SIG_DFL);
181      /* doit */
182      execvp(cpargv[0], cpargv);
183
184      /* failed */
185      itmp = errno;
186      write(child_comm_pipes[1], &itmp, sizeof(itmp));
187      _exit(1); break;      /* END PROCESS 2 */
188    }
189    break;
190
191  default: /* parent process */
192    /* do nothing */
193    break;
194  }
195
196  close(parent_comm_pipes[1]);
197
198  res = safe_read (parent_comm_pipes[0], &child_pid, sizeof(child_pid));
199  if (res != sizeof(child_pid))
200    {
201      g_message("res is %ld instead of %d",
202                (long)res, (int)sizeof(child_pid));
203      child_pid = -1; /* really weird things happened */
204    }
205  else if (safe_read (parent_comm_pipes[0], &child_errno, sizeof(child_errno))
206          == sizeof(child_errno))
207    {
208      errno = child_errno;
209      child_pid = -1;
210    }
211
212  /* do this after the read's in case some OS's handle blocking on pipe writes
213     differently */
214   while ((waitpid(immediate_child_pid, &itmp, 0)== -1) && (errno == EINTR)); /* eat zombies */
215
216  close(parent_comm_pipes[0]);
217
218  if(child_pid < 0)
219    g_message("gnome_execute_async_with_env_fds: returning %d", child_pid);
220
221  return child_pid;
222}
223
224/**
225 * gnome_execute_async_with_env:
226 * @dir: Directory in which child should be executed, or NULL for current
227 *       directory
228 * @argc: Number of arguments
229 * @argv: Argument vector to exec child
230 * @envc: Number of environment slots
231 * @envv: Environment vector
232 *
233 * Description: This function forks and executes some program in the
234 * background.  On error, returns %-1; in this case, #errno should hold a useful
235 * value.  Searches the path to find the child.  Environment settings in @envv
236 * are added to the existing environment -- they do not completely replace it.
237 * This function closes all fds besides 0, 1, and 2 for the child
238 *
239 * Returns: the process id, or %-1 on error.
240 **/
241int
242gnome_execute_async_with_env (const char *dir, int argc, char * const argv[],
243                              int envc, char * const envv[])
244{
245  return gnome_execute_async_with_env_fds (dir, argc, argv, envc, envv, TRUE);
246}
247
248
249/**
250 * gnome_execute_async:
251 * @dir: Directory in which child should be executesd, or %NULL for current
252 *       directory
253 * @argc: Number of arguments
254 * @argv: Argument vector to exec child
255 *
256 * Description: Like gnome_execute_async_with_env(), but doesn't add anything
257 * to child's environment.
258 *
259 * Returns: process id of child, or %-1 on error.
260 **/
261int
262gnome_execute_async (const char *dir, int argc, char * const argv[])
263{
264  return gnome_execute_async_with_env (dir, argc, argv, 0, NULL);
265}
266
267/**
268 * gnome_execute_async_fds:
269 * @dir: Directory in which child should be executed, or %NULL for current
270 *       directory
271 * @argc: Number of arguments
272 * @argv: Argument vector to exec child
273 * @close_fds: If %TRUE, will close all but file descriptors 0, 1 and 2.
274 *
275 * Description: Like gnome_execute_async_with_env_fds(), but doesn't add
276 * anything to child's environment.
277 *
278 * Returns: process id of child, or %-1 on error.
279 **/
280int
281gnome_execute_async_fds (const char *dir, int argc,
282                         char * const argv[], gboolean close_fds)
283{
284  return gnome_execute_async_with_env_fds (dir, argc, argv, 0, NULL,
285                                           close_fds);
286}
287
288/**
289 * gnome_execute_shell_fds:
290 * @dir: Directory in which child should be executed, or %NULL for current
291 *       directory
292 * @commandline: Shell command to execute
293 * @close_fds: Like close_fds in gnome_execute_async_with_env_fds()
294 *
295 * Description: Like gnome_execute_async_with_env_fds(), but uses the user's
296 * shell to run the desired program.  Note that the pid of the shell is
297 * returned, not the pid of the user's program.
298 *
299 * Returns: process id of shell, or %-1 on error.
300 **/
301int
302gnome_execute_shell_fds (const char *dir, const char *commandline,
303                         gboolean close_fds)
304{
305  char *user_shell;
306  char * argv[4];
307  int r;
308
309  g_return_val_if_fail(commandline != NULL, -1);
310
311  user_shell = gnome_util_user_shell ();
312
313  argv[0] = user_shell;
314  argv[1] = "-c";
315  /* necessary cast, to avoid warning, but safe */
316  argv[2] = (char *)commandline;
317  argv[3] = NULL;
318
319  r = gnome_execute_async_with_env_fds (dir, 4, argv, 0, NULL, close_fds);
320
321  g_free (user_shell);
322  return r;
323}
324
325/**
326 * gnome_execute_shell:
327 * @dir: Directory in which child should be executed, or %NULL for current
328 *       directory
329 * @commandline: Shell command to execute
330 *
331 * Description: Like gnome_execute_async_with_env(), but uses the user's shell
332 * to run the desired program.  Note that the pid of the shell is returned, not
333 * the pid of the user's program.
334 *
335 * Returns: process id of shell, or %-1 on error.
336 **/
337int
338gnome_execute_shell (const char *dir, const char *commandline)
339{
340  return gnome_execute_shell_fds(dir, commandline, TRUE);
341}
342
343/**
344 * gnome_prepend_terminal_to_vector:
345 * @argc: a pointer to the vector size
346 * @argv: a pointer to the vector
347 *
348 * Description:  Prepends a terminal (either the one configured as default in
349 * the user's GNOME setup, or one of the common xterm emulators) to the passed
350 * in vector, modifying it in the process.  The vector should be allocated with
351 * #g_malloc, as this will #g_free the original vector.  Also all elements must
352 * have been allocated separately.  That is the standard glib/GNOME way of
353 * doing vectors however.  If the integer that @argc points to is negative, the
354 * size will first be computed.  Also note that passing in pointers to a vector
355 * that is empty, will just create a new vector for you.
356 **/
357void
358gnome_prepend_terminal_to_vector (int *argc, char ***argv)
359{
360        char **real_argv;
361        int real_argc;
362        int i, j;
363        char **term_argv = NULL;
364        int term_argc = 0;
365        GConfClient *client;
366
367        gchar *terminal = NULL;
368
369        char **the_argv;
370
371        g_return_if_fail (argc != NULL);
372        g_return_if_fail (argv != NULL);
373
374        /* sanity */
375        if(*argv == NULL)
376                *argc = 0;
377
378        the_argv = *argv;
379
380        /* compute size if not given */
381        if (*argc < 0) {
382                for (i = 0; the_argv[i] != NULL; i++)
383                        ;
384                *argc = i;
385        }
386
387        /* init our gconf stuff if necessary */
388        gnome_gconf_lazy_init ();
389
390        client = gconf_client_get_default ();
391        terminal = gconf_client_get_string (client, "/desktop/gnome/applications/terminal/exec", NULL);
392        g_object_unref (G_OBJECT (client));
393       
394        if (terminal) {
395                gchar *exec_flag;
396                exec_flag = gconf_client_get_string (client, "/desktop/gnome/applications/terminal/exec_arg", NULL);
397
398                if (exec_flag == NULL) {
399                        term_argc = 1;
400                        term_argv = g_new0 (char *, 2);
401                        term_argv[0] = terminal;
402                        term_argv[1] = NULL;
403                } else {
404                        term_argc = 2;
405                        term_argv = g_new0 (char *, 3);
406                        term_argv[0] = terminal;
407                        term_argv[1] = exec_flag;
408                        term_argv[2] = NULL;
409                }
410#if 0
411            poptParseArgvString (terminal, &term_argc, &temp_argv);
412            term_argv = g_strdupv ((gchar **) temp_argv);
413            g_free (terminal);
414#endif
415        }
416
417        if (term_argv == NULL) {
418                char *check;
419
420                term_argc = 2;
421                term_argv = g_new0 (char *, 3);
422
423                check = g_find_program_in_path ("gnome-terminal");
424                if (check != NULL) {
425                        term_argv[0] = check;
426                        /* Note that gnome-terminal takes -x and
427                         * as -e in gnome-terminal is broken we use that. */
428                        term_argv[1] = g_strdup ("-x");
429                } else {
430                        if (check == NULL)
431                                check = g_find_program_in_path ("nxterm");
432                        if (check == NULL)
433                                check = g_find_program_in_path ("color-xterm");
434                        if (check == NULL)
435                                check = g_find_program_in_path ("rxvt");
436                        if (check == NULL)
437                                check = g_find_program_in_path ("xterm");
438                        if (check == NULL)
439                                check = g_find_program_in_path ("dtterm");
440                        if (check == NULL) {
441                                g_warning (_("Cannot find a terminal, using "
442                                             "xterm, even if it may not work"));
443                                check = g_strdup ("xterm");
444                        }
445                        term_argv[0] = check;
446                        term_argv[1] = g_strdup ("-e");
447                }
448        }
449
450        real_argc = term_argc + *argc;
451        real_argv = g_new (char *, real_argc + 1);
452
453        for (i = 0; i < term_argc; i++)
454                real_argv[i] = term_argv[i];
455
456        for (j = 0; j < *argc; j++, i++)
457                real_argv[i] = (char *)the_argv[j];
458
459        real_argv[i] = NULL;
460
461        g_free (*argv);
462        *argv = real_argv;
463        *argc = real_argc;
464
465        /* we use g_free here as we sucked all the inner strings
466         * out from it into real_argv */
467        g_free (term_argv);
468}
469
470/**
471 * gnome_execute_terminal_shell_fds:
472 * @dir: Directory in which child should be executed, or %NULL for current
473 *       directory
474 * @commandline: Shell command to execute
475 * @close_fds: Like close_fds in gnome_execute_async_with_env_fds()
476 *
477 * Description:  Like gnome_execute_shell_fds(), except that it runs the
478 * terminal as well.  Note that the pid of the terminal is
479 * returned, not the pid of the user's program.
480 * If commandline is %NULL, just the shell is run.
481 *
482 * Returns: process id of terminal, or %-1 on error.
483 **/
484int
485gnome_execute_terminal_shell_fds (const char *dir, const char *commandline,
486                                  gboolean close_fds)
487{
488        char ** argv;
489        int argc;
490        int r;
491
492        argv = g_new (char *, 4);
493
494        argv[0] = gnome_util_user_shell ();
495        if (commandline != NULL) {
496                argc = 3;
497                argv[1] = g_strdup ("-c");
498                argv[2] = g_strdup (commandline);
499                argv[3] = NULL;
500        } else {
501                /* FIXME: really this should more be a
502                 * --login terminal, but the user preference includes
503                 * the -e, -x or whatever flag which makes this impossible,
504                 * change the preference in 2.0 to be two keys, and one
505                 * of them for a login terminal */
506                argc = 1;
507                argv[1] = NULL;
508        }
509
510        gnome_prepend_terminal_to_vector (&argc, &argv);
511
512        r = gnome_execute_async_with_env_fds (dir, argc, argv, 0, NULL,
513                                              close_fds);
514
515        g_strfreev (argv);
516
517        return r;
518}
519
520/**
521 * gnome_execute_terminal_shell:
522 * @dir: Directory in which child should be executed, or NULL for current
523 *       directory
524 * @commandline: Shell command to execute
525 *
526 * Description:  Like #gnome_execute_async, except that it runs the
527 * terminal as well.  Note that the pid of the terminal is
528 * returned, not the pid of the user's program.
529 * If commandline is %NULL, just the shell is run.
530 *
531 * Returns: process id of terminal, or %-1 on error.
532 **/
533int
534gnome_execute_terminal_shell (const char *dir, const char *commandline)
535{
536        return gnome_execute_terminal_shell_fds (dir, commandline, TRUE);
537}
Note: See TracBrowser for help on using the repository browser.