blob: 0f262c2e7acca11dc9dd696e746b76a95ba812b1 [file] [log] [blame]
/* 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;
}