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
21extern const char* __progname;
22const char* kCommandName = __progname;
23
24
25static 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
44static void
45print_usage_and_exit(bool error)
46{
47	fprintf(error ? stderr : stdout, kUsage, kCommandName, kCommandName);
48	exit(error ? 1 : 0);
49}
50
51
52static bool
53is_primary_architecture(const char* architecture)
54{
55	return strcmp(architecture, get_primary_architecture()) == 0;
56}
57
58
59static void
60get_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
72static void
73compute_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
138int
139main(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