/* * 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(char *prefix, 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(); } /******************************************************************************/ /* Default is to support everything we can */ enum vboot_version vboot_version = VBOOT_VERSION_ALL; 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" "\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 (0 == 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 do_help(int argc, char *argv[]) { const struct futil_cmd_t *cmd; const char *vstr; if (argc >= 2) { cmd = find_command(argv[1]); if (cmd) { printf("\n%s - %s\n", argv[1], cmd->shorthelp); if (cmd->longhelp) cmd->longhelp(argv[1]); return 0; } } 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)", NULL); static int do_version(int argc, char *argv[]) { printf("%s\n", futility_version); return 0; } DECLARE_FUTIL_COMMAND(version, do_version, VBOOT_VERSION_ALL, "Show the futility source revision and build date", NULL); int run_command(const struct futil_cmd_t *cmd, int argc, char *argv[]) { /* Handle the "CMD --help" case ourselves */ if (2 == argc && 0 == strcmp(argv[1], "--help")) { char *fake_argv[] = {"help", (char *)cmd->name, NULL}; return do_help(2, fake_argv); } return cmd->handler(argc, argv); } static char *simple_basename(char *str) { char *s = strrchr(str, '/'); if (s) s++; else s = str; return s; } /* Here we go */ 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; struct option long_opts[] = { {"vb1" , 0, &vb_ver, VBOOT_VERSION_1_0}, {"vb21", 0, &vb_ver, VBOOT_VERSION_2_1}, { 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 '?': 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: Debug("i=%d\n", i); DIE; } } vboot_version = vb_ver; /* Reset the getopt state so commands can parse their own options. */ argc -= optind; argv += optind; optind = 0; /* We require a command name. */ if (errorcnt || argc < 1) { do_help(0, 0); return 1; } /* For reasons I've forgotten, treat /blah/blah/CMD the same as CMD */ progname = simple_basename(argv[0]); /* Do we recognize the command? */ cmd = find_command(progname); if (cmd) return run_command(cmd, argc, argv); /* Nope. We've no clue what we're being asked to do. */ do_help(0, 0); return 1; }