xref: /haiku/src/bin/setarch.cpp (revision cbe0a0c436162d78cc3f92a305b64918c839d079)
1 /*
2  * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include <errno.h>
8 #include <getopt.h>
9 #include <pwd.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <unistd.h>
14 
15 #include <Architecture.h>
16 #include <Path.h>
17 #include <PathFinder.h>
18 #include <StringList.h>
19 
20 
21 extern const char* __progname;
22 const char* kCommandName = __progname;
23 
24 
25 static const char* kUsage =
26 	"Usage: %s [-hl]\n"
27 	"       %s [-p] <architecture> [ <command> ... ]\n"
28 	"Executes the given command or, by default, a shell with a PATH\n"
29 	"environment variable modified such that commands for the given\n"
30 	"architecture will be preferred, respectively used exclusively in case of\n"
31 	"the primary architecture.\n"
32 	"\n"
33 	"Options:\n"
34 	"  -h, --help\n"
35 	"    Print this usage info.\n"
36 	"  -l, --list-architectures\n"
37 	"    List all architectures.\n"
38 	"  -p, --print-path\n"
39 	"    Only print the modified PATH variable value; don't execute any\n"
40 	"    command.\n"
41 ;
42 
43 
44 static void
45 print_usage_and_exit(bool error)
46 {
47 	fprintf(error ? stderr : stdout, kUsage, kCommandName, kCommandName);
48 	exit(error ? 1 : 0);
49 }
50 
51 
52 static bool
53 is_primary_architecture(const char* architecture)
54 {
55 	return strcmp(architecture, get_primary_architecture()) == 0;
56 }
57 
58 
59 static void
60 get_bin_directories(const char* architecture, BStringList& _directories)
61 {
62 	status_t error = BPathFinder::FindPaths(architecture,
63 		B_FIND_PATH_BIN_DIRECTORY, NULL, 0, _directories);
64 	if (error != B_OK) {
65 		fprintf(stderr, "Error: Failed to get bin directories for architecture "
66 			"%s: %s\n", architecture, strerror(error));
67 		exit(1);
68 	}
69 }
70 
71 
72 static void
73 compute_new_paths(const char* architecture, BStringList& _paths)
74 {
75 	// get the primary architecture bin paths
76 	BStringList primaryBinDirectories;
77 	get_bin_directories(get_primary_architecture(), primaryBinDirectories);
78 
79 	// get the bin paths to insert
80 	BStringList binDirectoriesToInsert;
81 	if (!is_primary_architecture(architecture))
82 		get_bin_directories(architecture, binDirectoriesToInsert);
83 
84 	// split the PATH variable
85 	char* pathVariableValue = getenv("PATH");
86 	BStringList paths;
87 	if (pathVariableValue != NULL
88 		&& !BString(pathVariableValue).Split(":", true, paths)) {
89 		fprintf(stderr, "Error: Out of memory!\n");
90 		exit(1);
91 	}
92 
93 	// Filter the paths, removing any path that isn't associated with the
94 	// primary architecture. Also find the insertion index for the architecture
95 	// bin paths.
96 	int32 insertionIndex = -1;
97 	int32 count = paths.CountStrings();
98 	for (int32 i = 0; i < count; i++) {
99 		// We always keep relative paths. Filter absolute ones only.
100 		const char* path = paths.StringAt(i);
101 		if (path[0] == '/') {
102 			// try to normalize the path
103 			BPath normalizedPath;
104 			if (normalizedPath.SetTo(path, NULL, true) == B_OK)
105 				path = normalizedPath.Path();
106 
107 			// Check, if this is a primary bin directory. If not, determine the
108 			// path's architecture.
109 			int32 index = primaryBinDirectories.IndexOf(path);
110 			if (index >= 0) {
111 				if (insertionIndex < 0)
112 					insertionIndex = _paths.CountStrings();
113 			} else if (!is_primary_architecture(
114 					guess_architecture_for_path(path))) {
115 				// a non-primary architecture path -- skip
116 				continue;
117 			}
118 		}
119 
120 		if (!_paths.Add(paths.StringAt(i))) {
121 			fprintf(stderr, "Error: Out of memory!\n");
122 			exit(1);
123 		}
124 	}
125 
126 	// Insert the paths for the specified architecture, if any.
127 	if (!binDirectoriesToInsert.IsEmpty()) {
128 		if (!(insertionIndex < 0
129 				? _paths.Add(binDirectoriesToInsert)
130 				: _paths.Add(binDirectoriesToInsert, insertionIndex))) {
131 			fprintf(stderr, "Error: Out of memory!\n");
132 			exit(1);
133 		}
134 	}
135 }
136 
137 
138 int
139 main(int argc, const char* const* argv)
140 {
141 	bool printPath = false;
142 	bool listArchitectures = false;
143 
144 	while (true) {
145 		static struct option sLongOptions[] = {
146 			{ "help", no_argument, 0, 'h' },
147 			{ "list-architectures", no_argument, 0, 'l' },
148 			{ "print-path", no_argument, 0, 'p' },
149 			{ 0, 0, 0, 0 }
150 		};
151 
152 		opterr = 0; // don't print errors
153 		int c = getopt_long(argc, (char**)argv, "+hlp",
154 			sLongOptions, NULL);
155 		if (c == -1)
156 			break;
157 
158 		switch (c) {
159 			case 'h':
160 				print_usage_and_exit(false);
161 				break;
162 
163 			case 'l':
164 				listArchitectures = true;
165 				break;
166 
167 			case 'p':
168 				printPath = true;
169 				break;
170 
171 			default:
172 				print_usage_and_exit(true);
173 				break;
174 		}
175 	}
176 
177 	// only one of listArchitectures, printPath may be specified
178 	if (listArchitectures && printPath)
179 		print_usage_and_exit(true);
180 
181 	// get architectures
182 	BStringList architectures;
183 	status_t error = get_architectures(architectures);
184 	if (error != B_OK) {
185 		fprintf(stderr, "Error: Failed to get architectures: %s\n",
186 			strerror(error));
187 		exit(1);
188 	}
189 
190 	// list architectures
191 	if (listArchitectures) {
192 		if (optind != argc)
193 			print_usage_and_exit(true);
194 
195 		int32 count = architectures.CountStrings();
196 		for (int32 i = 0; i < count; i++)
197 			printf("%s\n", architectures.StringAt(i).String());
198 		return 0;
199 	}
200 
201 	// The remaining arguments are the architecture and optionally the command
202 	// to execute.
203 	if (optind >= argc)
204 		print_usage_and_exit(true);
205 	const char* architecture = optind < argc ? argv[optind++] : NULL;
206 
207 	int commandArgCount = argc - optind;
208 	const char* const* commandArgs = commandArgCount > 0 ? argv + optind : NULL;
209 
210 	if (printPath && commandArgs != NULL)
211 		print_usage_and_exit(true);
212 
213 	// check the architecture
214 	if (!architectures.HasString(architecture)) {
215 		fprintf(stderr, "Error: Unsupported architecture \"%s\"\n",
216 			architecture);
217 		exit(1);
218 	}
219 
220 	// get the new paths
221 	BStringList paths;
222 	compute_new_paths(architecture, paths);
223 
224 	BString pathVariableValue = paths.Join(":");
225 	if (!paths.IsEmpty() && pathVariableValue.IsEmpty())
226 		fprintf(stderr, "Error: Out of memory!\n");
227 
228 	if (printPath) {
229 		printf("%s\n", pathVariableValue.String());
230 		return 0;
231 	}
232 
233 	// set PATH
234 	if (setenv("PATH", pathVariableValue, 1) != 0) {
235 		fprintf(stderr, "Error: Failed to set PATH: %s\n", strerror(errno));
236 		exit(1);
237 	}
238 
239 	// if no command is given, get the user's shell
240 	const char* shellCommand[3];
241 	if (commandArgs == NULL) {
242 		struct passwd* pwd = getpwuid(geteuid());
243 		shellCommand[0] = pwd != NULL ? pwd->pw_shell : "/bin/sh";
244 		shellCommand[1] = "-l";
245 		shellCommand[2] = NULL;
246 		commandArgs = shellCommand;
247 		commandArgCount = 2;
248 	}
249 
250 	// exec the command
251 	execvp(commandArgs[0], (char* const*)commandArgs);
252 
253 	fprintf(stderr, "Error: Executing \"%s\" failed: %s\n", commandArgs[0],
254 		strerror(errno));
255 	return 1;
256 }
257