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