diff options
Diffstat (limited to 'futility/futility.c')
-rw-r--r-- | futility/futility.c | 285 |
1 files changed, 285 insertions, 0 deletions
diff --git a/futility/futility.c b/futility/futility.c new file mode 100644 index 00000000..c30e3e8e --- /dev/null +++ b/futility/futility.c @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#define _GNU_SOURCE +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "futility.h" + +#define MYNAME "futility" +#ifdef OLDDIR +#define XSTR(A) STR(A) +#define STR(A) #A +#define SUBDIR XSTR(OLDDIR) +#else +#define SUBDIR "old_bins" +#endif + +/* File to use for logging, if present */ +#define LOGFILE "/tmp/futility.log" + +/* Normally logging will only happen if the logfile already exists. Uncomment + * this to force log file creation (and thus logging) always. */ +/* #define FORCE_LOGGING_ON */ + +/******************************************************************************/ + +static const char * const usage= "\n\ +Usage: " MYNAME " PROGRAM|COMMAND [args...]\n\ +\n\ +This is the unified firmware utility, which will eventually replace\n\ +all the distinct userspace tools formerly produced by the\n\ +vboot_reference package.\n\ +\n\ +When symlinked under the name of one of those previous tools, it can\n\ +do one of two things: either it will fully implement the original\n\ +behavior, or (until that functionality is complete) it will just exec\n\ +the original binary.\n\ +\n\ +In either case it can append some usage information to " LOGFILE "\n\ +to help improve coverage and correctness.\n\ +\n\ +If you invoke it directly instead of via a symlink, it requires one\n\ +argument, which is the name of the old binary to exec. That binary\n\ +must be located in a directory named \"" SUBDIR "\" underneath\n\ +the " MYNAME " executable.\n\ +\n"; + +static int help(int argc, char *argv[]) +{ + futil_cmd_t *cmd; + int i; + + fputs(usage, stdout); + + printf("The following commands are built-in:\n"); + + for (cmd = futil_cmds_start(); cmd < futil_cmds_end(); cmd++) + printf(" %-20s %s\n", + cmd->name, cmd->shorthelp); + + printf("\n"); + + printf("FYI, you added these args that I'm ignoring:\n"); + for (i = 0; i < argc; i++) + printf("argv[%d] = %s\n", i, argv[i]); + + return 0; +} +DECLARE_FUTIL_COMMAND(help, help, "Show a bit of help"); + + +/******************************************************************************/ +/* Logging stuff */ + +static int log_fd = -1; + +/* Write the string and a newline. Silently give up on errors */ +static void log_str(char *str) +{ + int len, done, n; + + if (log_fd < 0) + return; + + if (!str) + str = "(NULL)"; + + len = strlen(str); + if (len == 0) { + str = "(EMPTY)"; + len = strlen(str); + } + + for (done = 0; done < len; done += n) { + n = write(log_fd, str + done, len - done); + if (n < 0) + return; + } + + write(log_fd, "\n", 1); +} + +static void log_close(void) +{ + struct flock lock; + + if (log_fd >= 0) { + memset(&lock, 0, sizeof(lock)); + lock.l_type = F_UNLCK; + lock.l_whence = SEEK_SET; + if (fcntl(log_fd, F_SETLKW, &lock)) + perror("Unable to unlock log file"); + + close(log_fd); + log_fd = -1; + } +} + +static void log_open(void) +{ + struct flock lock; + int ret; + +#ifdef FORCE_LOGGING_ON + log_fd = open(LOGFILE, O_WRONLY|O_APPEND|O_CREAT, 0666); +#else + log_fd = open(LOGFILE, O_WRONLY|O_APPEND); +#endif + if (log_fd < 0) { + + if (errno != EACCES) + return; + + /* Permission problems should improve shortly ... */ + sleep(1); + log_fd = open(LOGFILE, O_WRONLY|O_APPEND|O_CREAT, 0666); + if (log_fd < 0) /* Nope, they didn't */ + return; + } + + /* Let anyone have a turn */ + fchmod(log_fd, 0666); + + /* But only one at a time */ + memset(&lock, 0, sizeof(lock)); + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_END; + + ret = fcntl(log_fd, F_SETLKW, &lock); /* this blocks */ + if (ret < 0) + log_close(); +} + +#define CALLER_PREFIX "CALLER:" +static void log_args(int argc, char *argv[]) +{ + int i; + ssize_t r; + pid_t parent; + char buf[80]; + char str_caller[PATH_MAX + sizeof(CALLER_PREFIX)] = CALLER_PREFIX; + char *truename = str_caller + sizeof(CALLER_PREFIX) - 1; + /* Note: truename starts on the \0 from CALLER_PREFIX, so we can write + * PATH_MAX chars into truename and still append a \0 at the end. */ + + log_open(); + + /* delimiter */ + log_str("##### HEY #####"); + + /* Can we tell who called us? */ + parent = getppid(); + snprintf(buf, sizeof(buf), "/proc/%d/exe", parent); + r = readlink(buf, truename, PATH_MAX); + if (r >= 0) { + truename[r] = '\0'; + log_str(str_caller); + } + + /* Now log the stuff about ourselves */ + for (i = 0; i < argc; i++) + log_str(argv[i]); + + log_close(); +} + + +/******************************************************************************/ +/* Here we go */ + +int main(int argc, char *argv[], char *envp[]) +{ + char *progname; + char truename[PATH_MAX]; + char oldname[PATH_MAX]; + char buf[80]; + pid_t myproc; + ssize_t r; + char *s; + futil_cmd_t *cmd; + + log_args(argc, argv); + + /* How were we invoked? */ + progname = strrchr(argv[0], '/'); + if (progname) + progname++; + else + progname = argv[0]; + + /* Invoked directly by name */ + if (0 == strcmp(progname, MYNAME)) { + if (argc < 2) { /* must have an argument */ + fputs(usage, stderr); + exit(1); + } + + /* We can just pass the rest along, then */ + argc--; + argv++; + + /* So now what name do we want to invoke? */ + progname = strrchr(argv[0], '/'); + if (progname) + progname++; + else + progname = argv[0]; + } + + /* See if it's asking for something we know how to do ourselves */ + for (cmd = futil_cmds_start(); cmd < futil_cmds_end(); cmd++) + if (0 == strcmp(cmd->name, progname)) + return cmd->handler(argc, argv); + + /* Nope, it must be wrapped */ + + /* The old binaries live under the true executable. Find out where that is. */ + myproc = getpid(); + snprintf(buf, sizeof(buf), "/proc/%d/exe", myproc); + r = readlink(buf, truename, PATH_MAX - 1); + if (r < 0) { + fprintf(stderr, "%s is lost: %s => %s: %s\n", MYNAME, argv[0], + buf, strerror(errno)); + exit(1); + } else if (r == PATH_MAX - 1) { + /* Yes, it might _just_ fit, but we'll count that as wrong anyway. We can't + * determine the right size using the example in the readlink manpage, + * because the /proc symlink returns an st_size of 0. */ + fprintf(stderr, "%s is too long: %s => %s\n", MYNAME, argv[0], buf); + exit(1); + } + + truename[r] = '\0'; + s = strrchr(truename, '/'); /* Find the true directory */ + if (s) { + *s = '\0'; + } else { /* I don't think this can happen */ + fprintf(stderr, "%s says %s doesn't make sense\n", MYNAME, truename); + exit(1); + } + /* If the old binary path doesn't fit, just give up. */ + r = snprintf(oldname, PATH_MAX, "%s/%s/%s", truename, SUBDIR, progname); + if (r >= PATH_MAX) { + fprintf(stderr, "%s/%s/%s is too long\n", truename, SUBDIR, progname); + exit(1); + } + + fflush(0); + execve(oldname, argv, envp); + + fprintf(stderr, "%s failed to exec %s: %s\n", MYNAME, + oldname, strerror(errno)); + return 1; +} |