1 /*
2 * Copyright 2021 Collabora Ltd.
3 *
4 * SPDX-License-Identifier: LGPL-2.1-or-later
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This 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 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, see
18 * <http://www.gnu.org/licenses/>.
19 */
20
21 #include <errno.h>
22 #include <stdio.h>
23
24 #include <glib.h>
25
26 #ifdef G_OS_UNIX
27 #include <sys/types.h>
28 #include <sys/wait.h>
29 #endif
30
31 static void
32 child_setup (gpointer user_data)
33 {
34 }
35
36 typedef struct
37 {
38 int wait_status;
39 gboolean done;
40 } ChildStatus;
41
42 static ChildStatus child_status = { -1, FALSE };
43
44 static void
45 child_watch_cb (GPid pid,
46 gint status,
47 gpointer user_data)
48 {
49 child_status.wait_status = status;
50 child_status.done = TRUE;
51 }
52
53 int
54 main (int argc,
55 char **argv)
56 {
57 gboolean search_path = FALSE;
58 gboolean search_path_from_envp = FALSE;
59 gboolean slow_path = FALSE;
60 gboolean unset_path_in_envp = FALSE;
61 gchar *chdir_child = NULL;
62 gchar *set_path_in_envp = NULL;
63 gchar **envp = NULL;
64 GOptionEntry entries[] =
65 {
66 { "chdir-child", '\0',
67 G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &chdir_child,
68 "Run PROGRAM in this working directory", NULL },
69 { "search-path", '\0',
70 G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &search_path,
71 "Search PATH for PROGRAM", NULL },
72 { "search-path-from-envp", '\0',
73 G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &search_path_from_envp,
74 "Search PATH from specified environment", NULL },
75 { "set-path-in-envp", '\0',
76 G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &set_path_in_envp,
77 "Set PATH in specified environment to this value", "PATH", },
78 { "unset-path-in-envp", '\0',
79 G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &unset_path_in_envp,
80 "Unset PATH in specified environment", NULL },
81 { "slow-path", '\0',
82 G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &slow_path,
83 "Use a child-setup function to avoid the posix_spawn fast path", NULL },
84 G_OPTION_ENTRY_NULL
85 };
86 GError *error = NULL;
87 int ret = 1;
88 GSpawnFlags spawn_flags = G_SPAWN_DO_NOT_REAP_CHILD;
89 GPid pid;
90 GOptionContext *context = NULL;
91
92 context = g_option_context_new ("PROGRAM [ARGS...]");
93 g_option_context_add_main_entries (context, entries, NULL);
94
95 if (!g_option_context_parse (context, &argc, &argv, &error))
96 {
97 ret = 2;
98 goto out;
99 }
100
101 if (argc < 2)
102 {
103 g_set_error (&error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
104 "Usage: %s [OPTIONS] PROGRAM [ARGS...]", argv[0]);
105 ret = 2;
106 goto out;
107 }
108
109 envp = g_get_environ ();
110
111 if (set_path_in_envp != NULL && unset_path_in_envp)
112 {
113 g_set_error (&error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
114 "Cannot both set PATH and unset it");
115 ret = 2;
116 goto out;
117 }
118
119 if (set_path_in_envp != NULL)
120 envp = g_environ_setenv (envp, "PATH", set_path_in_envp, TRUE);
121
122 if (unset_path_in_envp)
123 envp = g_environ_unsetenv (envp, "PATH");
124
125 if (search_path)
126 spawn_flags |= G_SPAWN_SEARCH_PATH;
127
128 if (search_path_from_envp)
129 spawn_flags |= G_SPAWN_SEARCH_PATH_FROM_ENVP;
130
131 if (!g_spawn_async_with_pipes (chdir_child,
132 &argv[1],
133 envp,
134 spawn_flags,
135 slow_path ? child_setup : NULL,
136 NULL, /* user_data */
137 &pid,
138 NULL, /* stdin */
139 NULL, /* stdout */
140 NULL, /* stderr */
141 &error))
142 {
143 ret = 1;
144 goto out;
145 }
146
147 g_child_watch_add (pid, child_watch_cb, NULL);
148
149 while (!child_status.done)
150 g_main_context_iteration (NULL, TRUE);
151
152 g_spawn_close_pid (pid);
153
154 #ifdef G_OS_UNIX
155 if (WIFEXITED (child_status.wait_status))
156 ret = WEXITSTATUS (child_status.wait_status);
157 else
158 ret = 1;
159 #else
160 ret = child_status.wait_status;
161 #endif
162
163 out:
164 if (error != NULL)
165 fprintf (stderr, "%s\n", error->message);
166
167 g_free (set_path_in_envp);
168 g_free (chdir_child);
169 g_clear_error (&error);
170 g_strfreev (envp);
171 g_option_context_free (context);
172 return ret;
173 }