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 |
---|
49 | extern int errno; |
---|
50 | #endif |
---|
51 | |
---|
52 | static void |
---|
53 | set_cloexec (gint fd) |
---|
54 | { |
---|
55 | fcntl (fd, F_SETFD, FD_CLOEXEC); |
---|
56 | } |
---|
57 | |
---|
58 | static ssize_t |
---|
59 | safe_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 | **/ |
---|
83 | int |
---|
84 | gnome_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 | **/ |
---|
241 | int |
---|
242 | gnome_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 | **/ |
---|
261 | int |
---|
262 | gnome_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 | **/ |
---|
280 | int |
---|
281 | gnome_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 | **/ |
---|
301 | int |
---|
302 | gnome_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 | **/ |
---|
337 | int |
---|
338 | gnome_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 | **/ |
---|
357 | void |
---|
358 | gnome_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 | **/ |
---|
484 | int |
---|
485 | gnome_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 | **/ |
---|
533 | int |
---|
534 | gnome_execute_terminal_shell (const char *dir, const char *commandline) |
---|
535 | { |
---|
536 | return gnome_execute_terminal_shell_fds (dir, commandline, TRUE); |
---|
537 | } |
---|