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