ftp://ftp.redhat.com/pub/redhat/linux/rawhide/SRPMS/SRPMS/gnome-vfs2-2.3.8-1.src.rpm
[gnome-vfs-httpcaptive.git] / libgnomevfs / gnome-vfs-process.c
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /* gnome-vfs-process.c - Unified method for executing external processes.
3
4    Copyright (C) 1999 Free Software Foundation
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    Author: Ettore Perazzoli <ettore@gnu.org>
22 */
23
24 /* WARNING: This is *NOT* MT-safe at all.  It is designed to call all processes
25    from the main thread exclusively.  But for now this is fine, because we are
26    only using this module internally.  */
27
28 #include <config.h>
29 #include "gnome-vfs-process.h"
30
31 #include "gnome-vfs-private-utils.h"
32 #include <errno.h>
33 #include <glib/gconvert.h> /* workaround for giochannel.h bug */
34 #include <glib/ghash.h>
35 #include <glib/giochannel.h>
36 #include <glib/gstrfuncs.h>
37 #include <glib/gmessages.h>
38 #include <glib/gmem.h>
39 #include <signal.h>
40 #include <string.h>
41 #include <sys/types.h>
42 #include <sys/wait.h>
43 #include <unistd.h>
44
45 /* A launched process.  */
46 struct _GnomeVFSProcess {
47         pid_t pid;
48         GnomeVFSProcessCallback callback;
49         gpointer callback_data;
50 };
51
52 /* Have we been initialized yet?  */
53 static gboolean initialized = FALSE;
54
55 /* Table to get a pointer to a GnomeVFSProcess struct from a PID value.  */
56 static GHashTable *pid_to_process = NULL;
57
58 /* Input channel for waking up the main loop whenever a SIGCHLD is received,
59    and call our SIGCHLD handling callback.  */
60 static GIOChannel *wake_up_channel_in = NULL;
61
62 /* The output side of the previous channel.  We use low-level I/O in the signal
63    handler instead of `g_io_*' stuff.  */
64 static volatile gint wake_up_channel_out_fd = -1;
65
66 /* The sigaction we had before installing the SIGCHLD handler.  */
67 static struct sigaction old_sigchld_action;
68
69 static void
70 foreach_pid_func (gpointer key,
71                   gpointer value,
72                   gpointer data)
73 {
74         GnomeVFSProcess *process;
75         pid_t pid;
76         gint status;
77         gboolean *found;
78
79         pid = GPOINTER_TO_INT (key);
80         process = (GnomeVFSProcess *) value;
81         found = (gboolean *) data;
82
83         if (waitpid (pid, &status, WNOHANG) == pid) {
84                 write (wake_up_channel_out_fd, &process, sizeof (process));
85                 write (wake_up_channel_out_fd, &status, sizeof (status));
86                 *found = TRUE;
87         }
88 }
89
90 static void
91 sigchld_handler (int signum)
92 {
93         gboolean found = FALSE;
94
95         found = FALSE;
96         g_hash_table_foreach (pid_to_process, foreach_pid_func, &found);
97
98         if (! found && old_sigchld_action.sa_handler != NULL)
99                 (* old_sigchld_action.sa_handler) (signum);
100 }
101
102 static gboolean
103 wake_up (GIOChannel *source,
104          GIOCondition condition,
105          gpointer data)
106 {
107         GnomeVFSProcess *process;
108         GIOError result;
109         gsize bytes_read;
110         gint status;
111
112         do {
113                 result = g_io_channel_read_chars (source, (gchar *) &process,
114                                                   sizeof (process), &bytes_read, NULL);
115         } while (result == G_IO_ERROR_AGAIN);
116         if (result != G_IO_ERROR_NONE) {
117                 g_warning (__FILE__ ": Cannot read from the notification channel (error %d)",
118                            result);
119                 return TRUE;
120         }
121
122         do {
123                 result = g_io_channel_read_chars (source, (gchar *) &status,
124                                                   sizeof (status), &bytes_read, NULL);
125         } while (result == G_IO_ERROR_AGAIN);
126         if (result != G_IO_ERROR_NONE) {
127                 g_warning (__FILE__ ": Cannot read from the notification channel (error %d)",
128                            result);
129                 return TRUE;
130         }
131
132         if (process->callback != NULL)
133                 (* process->callback) (process, status,
134                                        process->callback_data);
135
136         if (WIFSIGNALED (status)) {
137                 g_hash_table_remove (pid_to_process,
138                                      GINT_TO_POINTER (process->pid));
139                 _gnome_vfs_process_free (process);
140         }
141
142         return TRUE;
143 }
144
145 gboolean
146 _gnome_vfs_process_init (void)
147 {
148         gint pipe_fd[2];
149         struct sigaction sigchld_action;
150         sigset_t sigchld_mask;
151
152         if (initialized)
153                 return TRUE;
154
155         if (pipe (pipe_fd) == -1) {
156                 g_warning ("Cannot create pipe for GnomeVFSProcess initialization: %s",
157                            g_strerror (errno));
158                 return FALSE;
159         }
160
161         pid_to_process = g_hash_table_new (NULL, NULL);
162
163         sigchld_action.sa_handler =  sigchld_handler;
164         sigemptyset (&sigchld_action.sa_mask);
165         sigchld_action.sa_flags = 0;
166
167         sigaction (SIGCHLD, &sigchld_action, &old_sigchld_action);
168
169         wake_up_channel_in = g_io_channel_unix_new (pipe_fd[0]);
170         wake_up_channel_out_fd = pipe_fd[1];
171
172         g_io_add_watch (wake_up_channel_in, G_IO_IN, wake_up, NULL);
173
174         sigemptyset (&sigchld_mask);
175         sigaddset (&sigchld_mask, SIGCHLD);
176         sigprocmask (SIG_UNBLOCK, &sigchld_mask, NULL);
177
178         return TRUE;
179 }
180
181 /**
182  * _gnome_vfs_process_new:
183  * @file_name: Name of the executable.
184  * @argv: NULL-terminated parameter list.
185  * @use_search_path: If TRUE, use the `PATH' environment variable to locate
186  * the executable.
187  * @close_file_descriptors: If TRUE, close all the open file descriptors.
188  * except stdio, stdin and stderr before launching the process.
189  * @init_func: Function to be called before launching the process.
190  * @init_data: Value to pass to @init_func.
191  * @callback: Function to invoke when the process die.
192  * @callback_data: Data to pass to @callback when the process dies.
193  * 
194  * Launch a new process.  @init_func is called immediately after calling
195  * fork(), and before closing the file descriptors and executing the program in
196  * the new process.
197  * 
198  * Return value: An opaque structure describing the launched process.
199  **/
200 GnomeVFSProcess *
201 _gnome_vfs_process_new (const gchar *file_name,
202                        const gchar * const argv[],
203                        GnomeVFSProcessOptions options,
204                        GnomeVFSProcessInitFunc init_func,
205                        gpointer init_data,
206                        GnomeVFSProcessCallback callback,
207                        gpointer callback_data)
208 {
209         GnomeVFSProcess *new;
210         sigset_t sigchld_mask, old_mask;
211         pid_t child_pid;
212
213         /* Make sure no SIGCHLD happens while we set things up.  */
214
215         sigemptyset (&sigchld_mask);
216         sigaddset (&sigchld_mask, SIGCHLD);
217         sigprocmask (SIG_BLOCK, &sigchld_mask, &old_mask);
218
219         child_pid = gnome_vfs_forkexec (file_name, argv, options,
220                                         init_func, init_data);
221
222         if (child_pid == -1)
223                 return NULL;
224
225         new = g_new (GnomeVFSProcess, 1);
226         new->pid = child_pid;
227         new->callback = callback;
228         new->callback_data = callback_data;
229
230         g_hash_table_insert (pid_to_process, GINT_TO_POINTER (child_pid), new);
231
232         sigprocmask (SIG_SETMASK, &old_mask, NULL);
233
234         return new;
235 }
236
237 /**
238  * _gnome_vfs_process_free:
239  * @process: An existing process.
240  * 
241  * Free @process.  This will not kill the process, but will prevent the
242  * associated callbacks to be called.
243  **/
244 void
245 _gnome_vfs_process_free (GnomeVFSProcess *process)
246 {
247         g_hash_table_remove (pid_to_process, GINT_TO_POINTER (process->pid));
248         g_free (process);
249 }
250
251 /**
252  * _gnome_vfs_process_signal:
253  * @process: A launched process
254  * @signal_number: A signal number
255  * 
256  * Send signal @signal_number to the specified @process.
257  * 
258  * Return value: A numeric value reporting the result of the operation.
259  **/
260 GnomeVFSProcessRunResult
261 _gnome_vfs_process_signal (GnomeVFSProcess *process,
262                           guint signal_number)
263 {
264         gint kill_result;
265
266         kill_result = kill (process->pid, signal_number);
267
268         switch (kill_result) {
269         case 0:
270                 return GNOME_VFS_PROCESS_OK;
271         case EINVAL:
272                 return GNOME_VFS_PROCESS_ERROR_INVALIDSIGNAL;
273         case EPERM:
274                 return GNOME_VFS_PROCESS_ERROR_NOPERM;
275         case ESRCH:
276                 return GNOME_VFS_PROCESS_ERROR_NOPROCESS;
277         default:
278                 return GNOME_VFS_PROCESS_ERROR_UNKNOWN;
279         }
280 }
281