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