1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2004-2006 William Jon McCann <mccann@jhu.edu>
4 * Copyright (C) 2012-2021 MATE Developers
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program 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
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
19 *
20 * Authors: William Jon McCann <mccann@jhu.edu>
21 *
22 */
23
24 #include "config.h"
25
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <signal.h>
29 #include <sys/types.h>
30 #include <sys/wait.h>
31 #include <errno.h>
32 #include <string.h>
33
34 #if defined(HAVE_SETPRIORITY)
35 #include <sys/time.h>
36 #include <sys/resource.h>
37 #endif
38
39 #include <glib.h>
40 #include <glib/gstdio.h>
41 #include <gdk/gdk.h>
42 #include <gdk/gdkx.h>
43
44 #include "gs-debug.h"
45 #include "gs-job.h"
46
47 #include "subprocs.h"
48
49 static void gs_job_finalize (GObject *object);
50
51 typedef enum
52 {
53 GS_JOB_INVALID,
54 GS_JOB_RUNNING,
55 GS_JOB_STOPPED,
56 GS_JOB_KILLED,
57 GS_JOB_DEAD
58 } GSJobStatus;
59
60 struct GSJobPrivate
61 {
62 GtkWidget *widget;
63
64 GSJobStatus status;
65 gint pid;
66 guint watch_id;
67
68 char *command;
69 };
70
G_DEFINE_TYPE_WITH_PRIVATE(GSJob,gs_job,G_TYPE_OBJECT)71 G_DEFINE_TYPE_WITH_PRIVATE (GSJob, gs_job, G_TYPE_OBJECT)
72
73 static char *
74 widget_get_id_string (GtkWidget *widget)
75 {
76 char *id = NULL;
77
78 g_return_val_if_fail (widget != NULL, NULL);
79
80 id = g_strdup_printf ("0x%X",
81 (guint32)GDK_WINDOW_XID (gtk_widget_get_window (widget)));
82 return id;
83 }
84
85 static void
gs_job_class_init(GSJobClass * klass)86 gs_job_class_init (GSJobClass *klass)
87 {
88 GObjectClass *object_class = G_OBJECT_CLASS (klass);
89
90 object_class->finalize = gs_job_finalize;
91 }
92
93 static void
gs_job_init(GSJob * job)94 gs_job_init (GSJob *job)
95 {
96 job->priv = gs_job_get_instance_private (job);
97 }
98
99 /* adapted from gspawn.c */
100 static int
wait_on_child(int pid)101 wait_on_child (int pid)
102 {
103 int status;
104
105 wait_again:
106 if (waitpid (pid, &status, 0) < 0)
107 {
108 if (errno == EINTR)
109 {
110 goto wait_again;
111 }
112 else if (errno == ECHILD)
113 {
114 ; /* do nothing, child already reaped */
115 }
116 else
117 {
118 gs_debug ("waitpid () should not fail in 'GSJob'");
119 }
120 }
121
122 return status;
123 }
124
125 static void
gs_job_died(GSJob * job)126 gs_job_died (GSJob *job)
127 {
128 if (job->priv->pid > 0)
129 {
130 int exit_status;
131
132 gs_debug ("Waiting on process %d", job->priv->pid);
133 exit_status = wait_on_child (job->priv->pid);
134
135 job->priv->status = GS_JOB_DEAD;
136
137 if (WIFEXITED (exit_status) && (WEXITSTATUS (exit_status) != 0))
138 {
139 gs_debug ("Wait on child process failed");
140 }
141 else
142 {
143 /* exited normally */
144 }
145 }
146 g_spawn_close_pid (job->priv->pid);
147 job->priv->pid = 0;
148
149 gs_debug ("Job died");
150 }
151
152 static void
gs_job_finalize(GObject * object)153 gs_job_finalize (GObject *object)
154 {
155 GSJob *job;
156
157 g_return_if_fail (object != NULL);
158 g_return_if_fail (GS_IS_JOB (object));
159
160 job = GS_JOB (object);
161
162 g_return_if_fail (job->priv != NULL);
163
164 if (job->priv->pid > 0)
165 {
166 signal_pid (job->priv->pid, SIGTERM);
167 gs_job_died (job);
168 }
169
170 g_free (job->priv->command);
171 job->priv->command = NULL;
172
173 G_OBJECT_CLASS (gs_job_parent_class)->finalize (object);
174 }
175
176 void
gs_job_set_widget(GSJob * job,GtkWidget * widget)177 gs_job_set_widget (GSJob *job,
178 GtkWidget *widget)
179 {
180 g_return_if_fail (job != NULL);
181 g_return_if_fail (GS_IS_JOB (job));
182
183 if (widget != job->priv->widget)
184 {
185 job->priv->widget = widget;
186
187 /* restart job */
188 if (gs_job_is_running (job))
189 {
190 gs_job_stop (job);
191 gs_job_start (job);
192 }
193 }
194 }
195
196 gboolean
gs_job_set_command(GSJob * job,const char * command)197 gs_job_set_command (GSJob *job,
198 const char *command)
199 {
200 g_return_val_if_fail (GS_IS_JOB (job), FALSE);
201
202 gs_debug ("Setting command for job: '%s'",
203 command != NULL ? command : "NULL");
204
205 g_free (job->priv->command);
206 job->priv->command = g_strdup (command);
207
208 return TRUE;
209 }
210
211 GSJob *
gs_job_new(void)212 gs_job_new (void)
213 {
214 GObject *job;
215
216 job = g_object_new (GS_TYPE_JOB, NULL);
217
218 return GS_JOB (job);
219 }
220
221 GSJob *
gs_job_new_for_widget(GtkWidget * widget)222 gs_job_new_for_widget (GtkWidget *widget)
223 {
224 GObject *job;
225
226 job = g_object_new (GS_TYPE_JOB, NULL);
227
228 gs_job_set_widget (GS_JOB (job), widget);
229
230 return GS_JOB (job);
231 }
232
233 static void
nice_process(int pid,int nice_level)234 nice_process (int pid,
235 int nice_level)
236 {
237 g_return_if_fail (pid > 0);
238
239 if (nice_level == 0)
240 {
241 return;
242 }
243
244 #if defined(HAVE_SETPRIORITY) && defined(PRIO_PROCESS)
245 gs_debug ("Setting child process priority to: %d", nice_level);
246 if (setpriority (PRIO_PROCESS, pid, nice_level) != 0)
247 {
248 gs_debug ("setpriority(PRIO_PROCESS, %lu, %d) failed",
249 (unsigned long) pid, nice_level);
250 }
251 #else
252 gs_debug ("don't know how to change process priority on this system.");
253 #endif
254 }
255
256 static GPtrArray *
get_env_vars(GtkWidget * widget)257 get_env_vars (GtkWidget *widget)
258 {
259 GPtrArray *env;
260 const gchar *display_name;
261 gchar *str;
262 int i;
263 static const char *allowed_env_vars [] =
264 {
265 "PATH",
266 "SESSION_MANAGER",
267 "XAUTHORITY",
268 "XAUTHLOCALHOSTNAME",
269 "LANG",
270 "LANGUAGE",
271 "DBUS_SESSION_BUS_ADDRESS"
272 };
273
274 env = g_ptr_array_new ();
275
276 display_name = gdk_display_get_name (gtk_widget_get_display (widget));
277 g_ptr_array_add (env, g_strdup_printf ("DISPLAY=%s", display_name));
278
279 g_ptr_array_add (env, g_strdup_printf ("HOME=%s",
280 g_get_home_dir ()));
281
282 for (i = 0; i < G_N_ELEMENTS (allowed_env_vars); i++)
283 {
284 const char *var;
285 const char *val;
286 var = allowed_env_vars [i];
287 val = g_getenv (var);
288 if (val != NULL)
289 {
290 g_ptr_array_add (env, g_strdup_printf ("%s=%s",
291 var,
292 val));
293 }
294 }
295
296 str = widget_get_id_string (widget);
297 g_ptr_array_add (env, g_strdup_printf ("XSCREENSAVER_WINDOW=%s", str));
298 g_free (str);
299
300 g_ptr_array_add (env, NULL);
301
302 return env;
303 }
304
305 static gboolean
spawn_on_widget(GtkWidget * widget,const char * command,int * pid,GIOFunc watch_func,gpointer user_data,guint * watch_id)306 spawn_on_widget (GtkWidget *widget,
307 const char *command,
308 int *pid,
309 GIOFunc watch_func,
310 gpointer user_data,
311 guint *watch_id)
312 {
313 char **argv;
314 GPtrArray *env;
315 gboolean result;
316 GIOChannel *channel;
317 GError *error = NULL;
318 int standard_error;
319 int child_pid;
320 int id;
321 int i;
322
323 if (command == NULL)
324 {
325 return FALSE;
326 }
327
328 if (! g_shell_parse_argv (command, NULL, &argv, &error))
329 {
330 gs_debug ("Could not parse command: %s", error->message);
331 g_error_free (error);
332 return FALSE;
333 }
334
335 env = get_env_vars (widget);
336
337 error = NULL;
338 result = g_spawn_async_with_pipes (NULL,
339 argv,
340 (char **)env->pdata,
341 G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
342 NULL,
343 NULL,
344 &child_pid,
345 NULL,
346 NULL,
347 &standard_error,
348 &error);
349
350 for (i = 0; i < env->len; i++)
351 {
352 g_free (g_ptr_array_index (env, i));
353 }
354 g_ptr_array_free (env, TRUE);
355
356 if (! result)
357 {
358 gs_debug ("Could not start command '%s': %s", command, error->message);
359 g_error_free (error);
360 g_strfreev (argv);
361 return FALSE;
362 }
363
364 g_strfreev (argv);
365
366 nice_process (child_pid, 10);
367
368 if (pid != NULL)
369 {
370 *pid = child_pid;
371 }
372 else
373 {
374 g_spawn_close_pid (child_pid);
375 }
376
377 channel = g_io_channel_unix_new (standard_error);
378 g_io_channel_set_close_on_unref (channel, TRUE);
379 g_io_channel_set_flags (channel,
380 g_io_channel_get_flags (channel) | G_IO_FLAG_NONBLOCK,
381 NULL);
382 id = g_io_add_watch (channel,
383 G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
384 watch_func,
385 user_data);
386 if (watch_id != NULL)
387 {
388 *watch_id = id;
389 }
390
391 g_io_channel_unref (channel);
392
393 return result;
394 }
395
396 static gboolean
command_watch(GIOChannel * source,GIOCondition condition,GSJob * job)397 command_watch (GIOChannel *source,
398 GIOCondition condition,
399 GSJob *job)
400 {
401 GIOStatus status;
402 GError *error = NULL;
403 gboolean done = FALSE;
404
405 g_return_val_if_fail (job != NULL, FALSE);
406
407 if (condition & G_IO_IN)
408 {
409 char *str;
410
411 status = g_io_channel_read_line (source, &str, NULL, NULL, &error);
412
413 if (status == G_IO_STATUS_NORMAL)
414 {
415 gs_debug ("command output: %s", str);
416
417 }
418 else if (status == G_IO_STATUS_EOF)
419 {
420 done = TRUE;
421
422 }
423 else if (error != NULL)
424 {
425 gs_debug ("command error: %s", error->message);
426 g_error_free (error);
427 }
428
429 g_free (str);
430 }
431 else if (condition & G_IO_HUP)
432 {
433 done = TRUE;
434 }
435
436 if (done)
437 {
438 gs_job_died (job);
439
440 job->priv->watch_id = 0;
441 return FALSE;
442 }
443
444 return TRUE;
445 }
446
447 gboolean
gs_job_is_running(GSJob * job)448 gs_job_is_running (GSJob *job)
449 {
450 gboolean running;
451
452 g_return_val_if_fail (GS_IS_JOB (job), FALSE);
453
454 running = (job->priv->pid > 0);
455
456 return running;
457 }
458
459 gboolean
gs_job_start(GSJob * job)460 gs_job_start (GSJob *job)
461 {
462 gboolean result;
463
464 g_return_val_if_fail (job != NULL, FALSE);
465 g_return_val_if_fail (GS_IS_JOB (job), FALSE);
466
467 gs_debug ("starting job");
468
469 if (job->priv->pid != 0)
470 {
471 gs_debug ("Cannot restart active job.");
472 return FALSE;
473 }
474
475 if (job->priv->widget == NULL)
476 {
477 gs_debug ("Could not start job: screensaver window is not set.");
478 return FALSE;
479 }
480
481 if (job->priv->command == NULL)
482 {
483 /* no warning here because a NULL command is interpreted
484 as a no-op job */
485 gs_debug ("No command set for job.");
486 return FALSE;
487 }
488
489 result = spawn_on_widget (job->priv->widget,
490 job->priv->command,
491 &job->priv->pid,
492 (GIOFunc)command_watch,
493 job,
494 &job->priv->watch_id);
495
496 if (result)
497 {
498 job->priv->status = GS_JOB_RUNNING;
499 }
500
501 return result;
502 }
503
504 static void
remove_command_watch(GSJob * job)505 remove_command_watch (GSJob *job)
506 {
507 if (job->priv->watch_id != 0)
508 {
509 g_source_remove (job->priv->watch_id);
510 job->priv->watch_id = 0;
511 }
512 }
513
514 gboolean
gs_job_stop(GSJob * job)515 gs_job_stop (GSJob *job)
516 {
517 g_return_val_if_fail (job != NULL, FALSE);
518 g_return_val_if_fail (GS_IS_JOB (job), FALSE);
519
520 gs_debug ("stopping job");
521
522 if (job->priv->pid == 0)
523 {
524 gs_debug ("Could not stop job: pid not defined");
525 return FALSE;
526 }
527
528 if (job->priv->status == GS_JOB_STOPPED)
529 {
530 gs_job_suspend (job, FALSE);
531 }
532
533 remove_command_watch (job);
534
535 signal_pid (job->priv->pid, SIGTERM);
536
537 job->priv->status = GS_JOB_KILLED;
538
539 gs_job_died (job);
540
541 return TRUE;
542 }
543
544 gboolean
gs_job_suspend(GSJob * job,gboolean suspend)545 gs_job_suspend (GSJob *job,
546 gboolean suspend)
547 {
548 g_return_val_if_fail (job != NULL, FALSE);
549 g_return_val_if_fail (GS_IS_JOB (job), FALSE);
550
551 gs_debug ("suspending job");
552
553 if (job->priv->pid == 0)
554 {
555 return FALSE;
556 }
557
558 signal_pid (job->priv->pid, (suspend ? SIGSTOP : SIGCONT));
559
560 job->priv->status = (suspend ? GS_JOB_STOPPED : GS_JOB_RUNNING);
561
562 return TRUE;
563 }
564