haiku/src/bin/setarch.cpp

257 lines
6.3 KiB
C++

/*
* Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#include <errno.h>
#include <getopt.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <Architecture.h>
#include <Path.h>
#include <PathFinder.h>
#include <StringList.h>
extern const char* __progname;
const char* kCommandName = __progname;
static const char* kUsage =
"Usage: %s [-hl]\n"
" %s [-p] <architecture> [ <command> ... ]\n"
"Executes the given command or, by default, a shell with a PATH\n"
"environment variable modified such that commands for the given\n"
"architecture will be preferred, respectively used exclusively in case of\n"
"the primary architecture.\n"
"\n"
"Options:\n"
" -h, --help\n"
" Print this usage info.\n"
" -l, --list-architectures\n"
" List all architectures.\n"
" -p, --print-path\n"
" Only print the modified PATH variable value; don't execute any\n"
" command.\n"
;
static void
print_usage_and_exit(bool error)
{
fprintf(error ? stderr : stdout, kUsage, kCommandName, kCommandName);
exit(error ? 1 : 0);
}
static bool
is_primary_architecture(const char* architecture)
{
return strcmp(architecture, get_primary_architecture()) == 0;
}
static void
get_bin_directories(const char* architecture, BStringList& _directories)
{
status_t error = BPathFinder::FindPaths(architecture,
B_FIND_PATH_BIN_DIRECTORY, NULL, 0, _directories);
if (error != B_OK) {
fprintf(stderr, "Error: Failed to get bin directories for architecture "
"%s: %s\n", architecture, strerror(error));
exit(1);
}
}
static void
compute_new_paths(const char* architecture, BStringList& _paths)
{
// get the primary architecture bin paths
BStringList primaryBinDirectories;
get_bin_directories(get_primary_architecture(), primaryBinDirectories);
// get the bin paths to insert
BStringList binDirectoriesToInsert;
if (!is_primary_architecture(architecture))
get_bin_directories(architecture, binDirectoriesToInsert);
// split the PATH variable
char* pathVariableValue = getenv("PATH");
BStringList paths;
if (pathVariableValue != NULL
&& !BString(pathVariableValue).Split(":", true, paths)) {
fprintf(stderr, "Error: Out of memory!\n");
exit(1);
}
// Filter the paths, removing any path that isn't associated with the
// primary architecture. Also find the insertion index for the architecture
// bin paths.
int32 insertionIndex = -1;
int32 count = paths.CountStrings();
for (int32 i = 0; i < count; i++) {
// We always keep relative paths. Filter absolute ones only.
const char* path = paths.StringAt(i);
if (path[0] == '/') {
// try to normalize the path
BPath normalizedPath;
if (normalizedPath.SetTo(path, NULL, true) == B_OK)
path = normalizedPath.Path();
// Check, if this is a primary bin directory. If not, determine the
// path's architecture.
int32 index = primaryBinDirectories.IndexOf(path);
if (index >= 0) {
if (insertionIndex < 0)
insertionIndex = _paths.CountStrings();
} else if (!is_primary_architecture(
guess_architecture_for_path(path))) {
// a non-primary architecture path -- skip
continue;
}
}
if (!_paths.Add(paths.StringAt(i))) {
fprintf(stderr, "Error: Out of memory!\n");
exit(1);
}
}
// Insert the paths for the specified architecture, if any.
if (!binDirectoriesToInsert.IsEmpty()) {
if (!(insertionIndex < 0
? _paths.Add(binDirectoriesToInsert)
: _paths.Add(binDirectoriesToInsert, insertionIndex))) {
fprintf(stderr, "Error: Out of memory!\n");
exit(1);
}
}
}
int
main(int argc, const char* const* argv)
{
bool printPath = false;
bool listArchitectures = false;
while (true) {
static struct option sLongOptions[] = {
{ "help", no_argument, 0, 'h' },
{ "list-architectures", no_argument, 0, 'l' },
{ "print-path", no_argument, 0, 'p' },
{ 0, 0, 0, 0 }
};
opterr = 0; // don't print errors
int c = getopt_long(argc, (char**)argv, "+hlp",
sLongOptions, NULL);
if (c == -1)
break;
switch (c) {
case 'h':
print_usage_and_exit(false);
break;
case 'l':
listArchitectures = true;
break;
case 'p':
printPath = true;
break;
default:
print_usage_and_exit(true);
break;
}
}
// only one of listArchitectures, printPath may be specified
if (listArchitectures && printPath)
print_usage_and_exit(true);
// get architectures
BStringList architectures;
status_t error = get_architectures(architectures);
if (error != B_OK) {
fprintf(stderr, "Error: Failed to get architectures: %s\n",
strerror(error));
exit(1);
}
// list architectures
if (listArchitectures) {
if (optind != argc)
print_usage_and_exit(true);
int32 count = architectures.CountStrings();
for (int32 i = 0; i < count; i++)
printf("%s\n", architectures.StringAt(i).String());
return 0;
}
// The remaining arguments are the architecture and optionally the command
// to execute.
if (optind >= argc)
print_usage_and_exit(true);
const char* architecture = optind < argc ? argv[optind++] : NULL;
int commandArgCount = argc - optind;
const char* const* commandArgs = commandArgCount > 0 ? argv + optind : NULL;
if (printPath && commandArgs != NULL)
print_usage_and_exit(true);
// check the architecture
if (!architectures.HasString(architecture)) {
fprintf(stderr, "Error: Unsupported architecture \"%s\"\n",
architecture);
exit(1);
}
// get the new paths
BStringList paths;
compute_new_paths(architecture, paths);
BString pathVariableValue = paths.Join(":");
if (!paths.IsEmpty() && pathVariableValue.IsEmpty())
fprintf(stderr, "Error: Out of memory!\n");
if (printPath) {
printf("%s\n", pathVariableValue.String());
return 0;
}
// set PATH
if (setenv("PATH", pathVariableValue, 1) != 0) {
fprintf(stderr, "Error: Failed to set PATH: %s\n", strerror(errno));
exit(1);
}
// if no command is given, get the user's shell
const char* shellCommand[3];
if (commandArgs == NULL) {
struct passwd* pwd = getpwuid(geteuid());
shellCommand[0] = pwd != NULL ? pwd->pw_shell : "/bin/sh";
shellCommand[1] = "-l";
shellCommand[2] = NULL;
commandArgs = shellCommand;
commandArgCount = 2;
}
// exec the command
execvp(commandArgs[0], (char* const*)commandArgs);
fprintf(stderr, "Error: Executing \"%s\" failed: %s\n", commandArgs[0],
strerror(errno));
return 1;
}