1 | // SPDX-License-Identifier: GPL-2.0 |
2 | #include <linux/compiler.h> |
3 | #include <linux/string.h> |
4 | #include <sys/types.h> |
5 | #include <sys/stat.h> |
6 | #include <unistd.h> |
7 | #include <string.h> |
8 | #include <stdlib.h> |
9 | #include <stdio.h> |
10 | #include "subcmd-util.h" |
11 | #include "exec-cmd.h" |
12 | #include "subcmd-config.h" |
13 | |
14 | #define MAX_ARGS 32 |
15 | #define PATH_MAX 4096 |
16 | |
17 | static const char *argv_exec_path; |
18 | static const char *argv0_path; |
19 | |
20 | void exec_cmd_init(const char *exec_name, const char *prefix, |
21 | const char *exec_path, const char *exec_path_env) |
22 | { |
23 | subcmd_config.exec_name = exec_name; |
24 | subcmd_config.prefix = prefix; |
25 | subcmd_config.exec_path = exec_path; |
26 | subcmd_config.exec_path_env = exec_path_env; |
27 | |
28 | /* Setup environment variable for invoked shell script. */ |
29 | setenv("PREFIX" , prefix, 1); |
30 | } |
31 | |
32 | #define is_dir_sep(c) ((c) == '/') |
33 | |
34 | static int is_absolute_path(const char *path) |
35 | { |
36 | return path[0] == '/'; |
37 | } |
38 | |
39 | static const char *get_pwd_cwd(char *buf, size_t sz) |
40 | { |
41 | char *pwd; |
42 | struct stat cwd_stat, pwd_stat; |
43 | if (getcwd(buf, sz) == NULL) |
44 | return NULL; |
45 | pwd = getenv("PWD" ); |
46 | if (pwd && strcmp(pwd, buf)) { |
47 | stat(buf, &cwd_stat); |
48 | if (!stat(pwd, &pwd_stat) && |
49 | pwd_stat.st_dev == cwd_stat.st_dev && |
50 | pwd_stat.st_ino == cwd_stat.st_ino) { |
51 | strlcpy(buf, pwd, sz); |
52 | } |
53 | } |
54 | return buf; |
55 | } |
56 | |
57 | static const char *make_nonrelative_path(char *buf, size_t sz, const char *path) |
58 | { |
59 | if (is_absolute_path(path)) { |
60 | if (strlcpy(buf, path, sz) >= sz) |
61 | die(err: "Too long path: %.*s" , 60, path); |
62 | } else { |
63 | const char *cwd = get_pwd_cwd(buf, sz); |
64 | |
65 | if (!cwd) |
66 | die(err: "Cannot determine the current working directory" ); |
67 | |
68 | if (strlen(cwd) + strlen(path) + 2 >= sz) |
69 | die(err: "Too long path: %.*s" , 60, path); |
70 | |
71 | strcat(p: buf, q: "/" ); |
72 | strcat(p: buf, q: path); |
73 | } |
74 | return buf; |
75 | } |
76 | |
77 | char *system_path(const char *path) |
78 | { |
79 | char *buf = NULL; |
80 | |
81 | if (is_absolute_path(path)) |
82 | return strdup(path); |
83 | |
84 | astrcatf(&buf, "%s/%s" , subcmd_config.prefix, path); |
85 | |
86 | return buf; |
87 | } |
88 | |
89 | const char *(const char *argv0) |
90 | { |
91 | const char *slash; |
92 | |
93 | if (!argv0 || !*argv0) |
94 | return NULL; |
95 | slash = argv0 + strlen(argv0); |
96 | |
97 | while (argv0 <= slash && !is_dir_sep(*slash)) |
98 | slash--; |
99 | |
100 | if (slash >= argv0) { |
101 | argv0_path = strndup(argv0, slash - argv0); |
102 | return argv0_path ? slash + 1 : NULL; |
103 | } |
104 | |
105 | return argv0; |
106 | } |
107 | |
108 | void set_argv_exec_path(const char *exec_path) |
109 | { |
110 | argv_exec_path = exec_path; |
111 | /* |
112 | * Propagate this setting to external programs. |
113 | */ |
114 | setenv(subcmd_config.exec_path_env, exec_path, 1); |
115 | } |
116 | |
117 | |
118 | /* Returns the highest-priority location to look for subprograms. */ |
119 | char *get_argv_exec_path(void) |
120 | { |
121 | char *env; |
122 | |
123 | if (argv_exec_path) |
124 | return strdup(argv_exec_path); |
125 | |
126 | env = getenv(subcmd_config.exec_path_env); |
127 | if (env && *env) |
128 | return strdup(env); |
129 | |
130 | return system_path(path: subcmd_config.exec_path); |
131 | } |
132 | |
133 | static void add_path(char **out, const char *path) |
134 | { |
135 | if (path && *path) { |
136 | if (is_absolute_path(path)) |
137 | astrcat(out, add: path); |
138 | else { |
139 | char buf[PATH_MAX]; |
140 | |
141 | astrcat(out, add: make_nonrelative_path(buf, sz: sizeof(buf), path)); |
142 | } |
143 | |
144 | astrcat(out, add: ":" ); |
145 | } |
146 | } |
147 | |
148 | void setup_path(void) |
149 | { |
150 | const char *old_path = getenv("PATH" ); |
151 | char *new_path = NULL; |
152 | char *tmp = get_argv_exec_path(); |
153 | |
154 | add_path(out: &new_path, path: tmp); |
155 | add_path(out: &new_path, path: argv0_path); |
156 | free(tmp); |
157 | |
158 | if (old_path) |
159 | astrcat(out: &new_path, add: old_path); |
160 | else |
161 | astrcat(out: &new_path, add: "/usr/local/bin:/usr/bin:/bin" ); |
162 | |
163 | setenv("PATH" , new_path, 1); |
164 | |
165 | free(new_path); |
166 | } |
167 | |
168 | static const char **prepare_exec_cmd(const char **argv) |
169 | { |
170 | int argc; |
171 | const char **nargv; |
172 | |
173 | for (argc = 0; argv[argc]; argc++) |
174 | ; /* just counting */ |
175 | nargv = malloc(sizeof(*nargv) * (argc + 2)); |
176 | |
177 | nargv[0] = subcmd_config.exec_name; |
178 | for (argc = 0; argv[argc]; argc++) |
179 | nargv[argc + 1] = argv[argc]; |
180 | nargv[argc + 1] = NULL; |
181 | return nargv; |
182 | } |
183 | |
184 | int execv_cmd(const char **argv) { |
185 | const char **nargv = prepare_exec_cmd(argv); |
186 | |
187 | /* execvp() can only ever return if it fails */ |
188 | execvp(subcmd_config.exec_name, (char **)nargv); |
189 | |
190 | free(nargv); |
191 | return -1; |
192 | } |
193 | |
194 | |
195 | int execl_cmd(const char *cmd,...) |
196 | { |
197 | int argc; |
198 | const char *argv[MAX_ARGS + 1]; |
199 | const char *arg; |
200 | va_list param; |
201 | |
202 | va_start(param, cmd); |
203 | argv[0] = cmd; |
204 | argc = 1; |
205 | while (argc < MAX_ARGS) { |
206 | arg = argv[argc++] = va_arg(param, char *); |
207 | if (!arg) |
208 | break; |
209 | } |
210 | va_end(param); |
211 | if (MAX_ARGS <= argc) { |
212 | fprintf(stderr, " Error: too many args to run %s\n" , cmd); |
213 | return -1; |
214 | } |
215 | |
216 | argv[argc] = NULL; |
217 | return execv_cmd(argv); |
218 | } |
219 | |