| /* Copyright 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. |
| */ |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <getopt.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" |
| |
| /******************************************************************************/ |
| /* Logging stuff */ |
| |
| /* 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 int log_fd = -1; |
| |
| /* Write the string and a newline. Silently give up on errors */ |
| static void log_str(const char *prefix, const char *str) |
| { |
| int len, done, n; |
| |
| if (log_fd < 0) |
| return; |
| |
| if (!str) |
| str = "(NULL)"; |
| |
| if (prefix && *prefix) { |
| len = strlen(prefix); |
| for (done = 0; done < len; done += n) { |
| n = write(log_fd, prefix + done, len - done); |
| if (n < 0) |
| return; |
| } |
| } |
| |
| 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; |
| } |
| |
| if (write(log_fd, "\n", 1) < 0) |
| return; |
| } |
| |
| 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(); |
| } |
| |
| static void log_args(int argc, char *argv[]) |
| { |
| int i; |
| ssize_t r; |
| pid_t parent; |
| char buf[80]; |
| FILE *fp; |
| char caller_buf[PATH_MAX]; |
| |
| log_open(); |
| |
| /* delimiter */ |
| log_str(NULL, "##### LOG #####"); |
| |
| /* Can we tell who called us? */ |
| parent = getppid(); |
| snprintf(buf, sizeof(buf), "/proc/%d/exe", parent); |
| r = readlink(buf, caller_buf, sizeof(caller_buf) - 1); |
| if (r >= 0) { |
| caller_buf[r] = '\0'; |
| log_str("CALLER:", caller_buf); |
| } |
| |
| /* From where? */ |
| snprintf(buf, sizeof(buf), "/proc/%d/cwd", parent); |
| r = readlink(buf, caller_buf, sizeof(caller_buf) - 1); |
| if (r >= 0) { |
| caller_buf[r] = '\0'; |
| log_str("DIR:", caller_buf); |
| } |
| |
| /* And maybe the args? */ |
| snprintf(buf, sizeof(buf), "/proc/%d/cmdline", parent); |
| fp = fopen(buf, "r"); |
| if (fp) { |
| memset(caller_buf, 0, sizeof(caller_buf)); |
| r = fread(caller_buf, 1, sizeof(caller_buf) - 1, fp); |
| if (r > 0) { |
| char *s = caller_buf; |
| for (i = 0; i < r && *s; ) { |
| log_str("CMDLINE:", s); |
| while (i < r && *s) |
| i++, s++; |
| i++, s++; |
| } |
| } |
| fclose(fp); |
| } |
| |
| /* Now log the stuff about ourselves */ |
| for (i = 0; i < argc; i++) |
| log_str(NULL, argv[i]); |
| |
| log_close(); |
| } |
| |
| /******************************************************************************/ |
| |
| static const char *const usage = "\n" |
| "Usage: " MYNAME " [options] COMMAND [args...]\n" |
| "\n" |
| "This is the unified firmware utility, which will eventually replace\n" |
| "most of the distinct verified boot tools formerly produced by the\n" |
| "vboot_reference package.\n" |
| "\n" |
| "When symlinked under the name of one of those previous tools, it should\n" |
| "fully implement the original behavior. It can also be invoked directly\n" |
| "as " MYNAME ", followed by the original name as the first argument.\n" |
| "\n"; |
| |
| static const char *const options = |
| "Global options:\n" |
| "\n" |
| " --vb1 Use only vboot v1.0 binary formats\n" |
| " --vb21 Use only vboot v2.1 binary formats\n" |
| " --debug Be noisy about what's going on\n" |
| "\n"; |
| |
| static const struct futil_cmd_t *find_command(const char *name) |
| { |
| const struct futil_cmd_t *const *cmd; |
| |
| for (cmd = futil_cmds; *cmd; cmd++) |
| if (((*cmd)->version & vboot_version) && |
| !strcmp((*cmd)->name, name)) |
| return *cmd; |
| |
| return NULL; |
| } |
| |
| static void list_commands(void) |
| { |
| const struct futil_cmd_t *const *cmd; |
| |
| for (cmd = futil_cmds; *cmd; cmd++) |
| if (vboot_version & (*cmd)->version) |
| printf(" %-20s %s\n", |
| (*cmd)->name, (*cmd)->shorthelp); |
| } |
| |
| static int run_command(const struct futil_cmd_t *cmd, int argc, char *argv[]) |
| { |
| int i; |
| VB2_DEBUG("\"%s\" ...\n", cmd->name); |
| for (i = 0; i < argc; i++) |
| VB2_DEBUG(" argv[%d] = \"%s\"\n", i, argv[i]); |
| |
| return cmd->handler(argc, argv); |
| } |
| |
| static int do_help(int argc, char *argv[]) |
| { |
| const struct futil_cmd_t *cmd; |
| const char *vstr = ""; |
| |
| /* Help about a known command? */ |
| if (argc > 1) { |
| cmd = find_command(argv[1]); |
| if (cmd) { |
| /* Let the command provide its own help */ |
| argv[0] = argv[1]; |
| argv[1] = (char *)"--help"; |
| return run_command(cmd, argc, argv); |
| } |
| } |
| |
| fputs(usage, stdout); |
| |
| if (vboot_version == VBOOT_VERSION_ALL) |
| fputs(options, stdout); |
| |
| switch (vboot_version) { |
| case VBOOT_VERSION_1_0: |
| vstr = "version 1.0 "; |
| break; |
| case VBOOT_VERSION_2_1: |
| vstr = "version 2.1 "; |
| break; |
| case VBOOT_VERSION_ALL: |
| vstr = ""; |
| break; |
| } |
| printf("The following %scommands are built-in:\n\n", vstr); |
| list_commands(); |
| printf("\nUse \"" MYNAME " help COMMAND\" for more information.\n\n"); |
| |
| return 0; |
| } |
| |
| DECLARE_FUTIL_COMMAND(help, do_help, VBOOT_VERSION_ALL, |
| "Show a bit of help (you're looking at it)"); |
| |
| static const char ver_help[] = |
| "Show the futility source revision and build date"; |
| static int do_version(int argc, char *argv[]) |
| { |
| if (argc > 1) |
| printf("%s - %s\n", argv[0], ver_help); |
| else |
| printf("%s\n", futility_version); |
| return 0; |
| } |
| |
| DECLARE_FUTIL_COMMAND(version, do_version, VBOOT_VERSION_ALL, |
| ver_help); |
| |
| static char *simple_basename(char *str) |
| { |
| char *s = strrchr(str, '/'); |
| if (s) |
| s++; |
| else |
| s = str; |
| return s; |
| } |
| |
| /* Here we go */ |
| #define OPT_HELP 1000 |
| int main(int argc, char *argv[], char *envp[]) |
| { |
| char *progname; |
| const struct futil_cmd_t *cmd; |
| int i, errorcnt = 0; |
| int vb_ver = VBOOT_VERSION_ALL; |
| int helpind = 0; |
| struct option long_opts[] = { |
| {"debug", 0, &debugging_enabled, 1}, |
| {"vb1" , 0, &vb_ver, VBOOT_VERSION_1_0}, |
| {"vb21", 0, &vb_ver, VBOOT_VERSION_2_1}, |
| {"help", 0, 0, OPT_HELP}, |
| { 0, 0, 0, 0}, |
| }; |
| |
| log_args(argc, argv); |
| |
| /* How were we invoked? */ |
| progname = simple_basename(argv[0]); |
| |
| /* See if the program name is a command we recognize */ |
| cmd = find_command(progname); |
| if (cmd) { |
| /* Yep, just do that */ |
| return !!run_command(cmd, argc, argv); |
| } |
| |
| /* Parse the global options, stopping at the first non-option. */ |
| opterr = 0; /* quiet, you. */ |
| while ((i = getopt_long(argc, argv, "+:", long_opts, NULL)) != -1) { |
| switch (i) { |
| case OPT_HELP: |
| /* Remember where we found this option */ |
| /* Note: this might be GNU-specific */ |
| helpind = optind - 1; |
| break; |
| case '?': |
| if (optopt) |
| fprintf(stderr, "Unrecognized option: -%c\n", |
| optopt); |
| else |
| fprintf(stderr, "Unrecognized option: %s\n", |
| argv[optind - 1]); |
| errorcnt++; |
| break; |
| case ':': |
| fprintf(stderr, "Missing argument to -%c\n", optopt); |
| errorcnt++; |
| break; |
| case 0: /* handled option */ |
| break; |
| default: |
| FATAL("Unrecognized getopt output: %d\n", i); |
| } |
| } |
| vboot_version = vb_ver; |
| |
| /* |
| * Translate "--help" in the args to "help" as the first parameter, |
| * by rearranging argv[]. |
| */ |
| if (helpind) { |
| int j; |
| optind--; |
| for (j = helpind; j < optind; j++) |
| argv[j] = argv[j + 1]; |
| argv[j] = (char *)"help"; |
| } |
| |
| /* We require a command name. */ |
| if (errorcnt || argc == optind) { |
| do_help(1, argv); |
| return 1; |
| } |
| |
| /* For reasons I've forgotten, treat /blah/blah/CMD the same as CMD */ |
| argv[optind] = simple_basename(argv[optind]); |
| |
| /* Do we recognize the command? */ |
| cmd = find_command(argv[optind]); |
| if (cmd) { |
| /* Reset so commands can parse their own options */ |
| argc -= optind; |
| argv += optind; |
| optind = 0; |
| return !!run_command(cmd, argc, argv); |
| } |
| |
| /* Nope. We've no clue what we're being asked to do. */ |
| do_help(1, argv); |
| return 1; |
| } |