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