Home | History | Annotate | Download | only in futility
      1 /*
      2  * Copyright 2013 The Chromium OS Authors. All rights reserved.
      3  * Use of this source code is governed by a BSD-style license that can be
      4  * found in the LICENSE file.
      5  */
      6 
      7 #include <errno.h>
      8 #include <fcntl.h>
      9 #include <getopt.h>
     10 #include <limits.h>
     11 #include <stdint.h>
     12 #include <stdio.h>
     13 #include <stdlib.h>
     14 #include <string.h>
     15 #include <sys/stat.h>
     16 #include <unistd.h>
     17 
     18 #include "futility.h"
     19 
     20 
     21 /******************************************************************************/
     22 /* Logging stuff */
     23 
     24 /* File to use for logging, if present */
     25 #define LOGFILE "/tmp/futility.log"
     26 
     27 /* Normally logging will only happen if the logfile already exists. Uncomment
     28  * this to force log file creation (and thus logging) always. */
     29 
     30 /* #define FORCE_LOGGING_ON */
     31 
     32 static int log_fd = -1;
     33 
     34 /* Write the string and a newline. Silently give up on errors */
     35 static void log_str(char *prefix, char *str)
     36 {
     37 	int len, done, n;
     38 
     39 	if (log_fd < 0)
     40 		return;
     41 
     42 	if (!str)
     43 		str = "(NULL)";
     44 
     45 	if (prefix && *prefix) {
     46 		len = strlen(prefix);
     47 		for (done = 0; done < len; done += n) {
     48 			n = write(log_fd, prefix + done, len - done);
     49 			if (n < 0)
     50 				return;
     51 		}
     52 	}
     53 
     54 	len = strlen(str);
     55 	if (len == 0) {
     56 		str = "(EMPTY)";
     57 		len = strlen(str);
     58 	}
     59 
     60 	for (done = 0; done < len; done += n) {
     61 		n = write(log_fd, str + done, len - done);
     62 		if (n < 0)
     63 			return;
     64 	}
     65 
     66 	if (write(log_fd, "\n", 1) < 0)
     67 		return;
     68 }
     69 
     70 static void log_close(void)
     71 {
     72 	struct flock lock;
     73 
     74 	if (log_fd >= 0) {
     75 		memset(&lock, 0, sizeof(lock));
     76 		lock.l_type = F_UNLCK;
     77 		lock.l_whence = SEEK_SET;
     78 		if (fcntl(log_fd, F_SETLKW, &lock))
     79 			perror("Unable to unlock log file");
     80 
     81 		close(log_fd);
     82 		log_fd = -1;
     83 	}
     84 }
     85 
     86 static void log_open(void)
     87 {
     88 	struct flock lock;
     89 	int ret;
     90 
     91 #ifdef FORCE_LOGGING_ON
     92 	log_fd = open(LOGFILE, O_WRONLY | O_APPEND | O_CREAT, 0666);
     93 #else
     94 	log_fd = open(LOGFILE, O_WRONLY | O_APPEND);
     95 #endif
     96 	if (log_fd < 0) {
     97 
     98 		if (errno != EACCES)
     99 			return;
    100 
    101 		/* Permission problems should improve shortly ... */
    102 		sleep(1);
    103 		log_fd = open(LOGFILE, O_WRONLY | O_APPEND | O_CREAT, 0666);
    104 		if (log_fd < 0)	/* Nope, they didn't */
    105 			return;
    106 	}
    107 
    108 	/* Let anyone have a turn */
    109 	fchmod(log_fd, 0666);
    110 
    111 	/* But only one at a time */
    112 	memset(&lock, 0, sizeof(lock));
    113 	lock.l_type = F_WRLCK;
    114 	lock.l_whence = SEEK_END;
    115 
    116 	ret = fcntl(log_fd, F_SETLKW, &lock);	/* this blocks */
    117 	if (ret < 0)
    118 		log_close();
    119 }
    120 
    121 static void log_args(int argc, char *argv[])
    122 {
    123 	int i;
    124 	ssize_t r;
    125 	pid_t parent;
    126 	char buf[80];
    127 	FILE *fp;
    128 	char caller_buf[PATH_MAX];
    129 
    130 	log_open();
    131 
    132 	/* delimiter */
    133 	log_str(NULL, "##### LOG #####");
    134 
    135 	/* Can we tell who called us? */
    136 	parent = getppid();
    137 	snprintf(buf, sizeof(buf), "/proc/%d/exe", parent);
    138 	r = readlink(buf, caller_buf, sizeof(caller_buf) - 1);
    139 	if (r >= 0) {
    140 		caller_buf[r] = '\0';
    141 		log_str("CALLER:", caller_buf);
    142 	}
    143 
    144 	/* From where? */
    145 	snprintf(buf, sizeof(buf), "/proc/%d/cwd", parent);
    146 	r = readlink(buf, caller_buf, sizeof(caller_buf) - 1);
    147 	if (r >= 0) {
    148 		caller_buf[r] = '\0';
    149 		log_str("DIR:", caller_buf);
    150 	}
    151 
    152 	/* And maybe the args? */
    153 	snprintf(buf, sizeof(buf), "/proc/%d/cmdline", parent);
    154 	fp = fopen(buf, "r");
    155 	if (fp) {
    156 		memset(caller_buf, 0, sizeof(caller_buf));
    157 		r = fread(caller_buf, 1, sizeof(caller_buf) - 1, fp);
    158 		if (r > 0) {
    159 			char *s = caller_buf;
    160 			for (i = 0; i < r && *s; ) {
    161 				log_str("CMDLINE:", s);
    162 				while (i < r && *s)
    163 					i++, s++;
    164 				i++, s++;
    165 			}
    166 		}
    167 		fclose(fp);
    168 	}
    169 
    170 	/* Now log the stuff about ourselves */
    171 	for (i = 0; i < argc; i++)
    172 		log_str(NULL, argv[i]);
    173 
    174 	log_close();
    175 }
    176 
    177 /******************************************************************************/
    178 
    179 /* Default is to support everything we can */
    180 enum vboot_version vboot_version = VBOOT_VERSION_ALL;
    181 
    182 static const char *const usage = "\n"
    183 "Usage: " MYNAME " [options] COMMAND [args...]\n"
    184 "\n"
    185 "This is the unified firmware utility, which will eventually replace\n"
    186 "most of the distinct verified boot tools formerly produced by the\n"
    187 "vboot_reference package.\n"
    188 "\n"
    189 "When symlinked under the name of one of those previous tools, it should\n"
    190 "fully implement the original behavior. It can also be invoked directly\n"
    191 "as " MYNAME ", followed by the original name as the first argument.\n"
    192 "\n";
    193 
    194 static const char *const options =
    195 "Global options:\n"
    196 "\n"
    197 "  --vb1        Use only vboot v1.0 binary formats\n"
    198 "  --vb21       Use only vboot v2.1 binary formats\n"
    199 "\n";
    200 
    201 static const struct futil_cmd_t *find_command(const char *name)
    202 {
    203 	const struct futil_cmd_t *const *cmd;
    204 
    205 	for (cmd = futil_cmds; *cmd; cmd++)
    206 		if (0 == strcmp((*cmd)->name, name))
    207 			return *cmd;
    208 
    209 	return NULL;
    210 }
    211 
    212 static void list_commands(void)
    213 {
    214 	const struct futil_cmd_t *const *cmd;
    215 
    216 	for (cmd = futil_cmds; *cmd; cmd++)
    217 		if (vboot_version & (*cmd)->version)
    218 			printf("  %-20s %s\n",
    219 			       (*cmd)->name, (*cmd)->shorthelp);
    220 }
    221 
    222 static int do_help(int argc, char *argv[])
    223 {
    224 	const struct futil_cmd_t *cmd;
    225 	const char *vstr;
    226 
    227 	if (argc >= 2) {
    228 		cmd = find_command(argv[1]);
    229 		if (cmd) {
    230 			printf("\n%s - %s\n", argv[1], cmd->shorthelp);
    231 			if (cmd->longhelp)
    232 				cmd->longhelp(argv[1]);
    233 			return 0;
    234 		}
    235 	}
    236 
    237 	fputs(usage, stdout);
    238 
    239 	if (vboot_version == VBOOT_VERSION_ALL)
    240 		fputs(options, stdout);
    241 
    242 	switch (vboot_version) {
    243 	case VBOOT_VERSION_1_0:
    244 		vstr = "version 1.0 ";
    245 		break;
    246 	case VBOOT_VERSION_2_1:
    247 		vstr = "version 2.1 ";
    248 		break;
    249 	case VBOOT_VERSION_ALL:
    250 		vstr = "";
    251 		break;
    252 	}
    253 	printf("The following %scommands are built-in:\n\n", vstr);
    254 	list_commands();
    255 	printf("\nUse \"" MYNAME " help COMMAND\" for more information.\n\n");
    256 
    257 	return 0;
    258 }
    259 
    260 DECLARE_FUTIL_COMMAND(help, do_help, VBOOT_VERSION_ALL,
    261 		      "Show a bit of help (you're looking at it)",
    262 		      NULL);
    263 
    264 static int do_version(int argc, char *argv[])
    265 {
    266 	printf("%s\n", futility_version);
    267 	return 0;
    268 }
    269 
    270 DECLARE_FUTIL_COMMAND(version, do_version, VBOOT_VERSION_ALL,
    271 		      "Show the futility source revision and build date",
    272 		      NULL);
    273 
    274 int run_command(const struct futil_cmd_t *cmd, int argc, char *argv[])
    275 {
    276 	/* Handle the "CMD --help" case ourselves */
    277 	if (2 == argc && 0 == strcmp(argv[1], "--help")) {
    278 		char *fake_argv[] = {"help",
    279 				     (char *)cmd->name,
    280 				     NULL};
    281 		return do_help(2, fake_argv);
    282 	}
    283 
    284 	return cmd->handler(argc, argv);
    285 }
    286 
    287 static char *simple_basename(char *str)
    288 {
    289 	char *s = strrchr(str, '/');
    290 	if (s)
    291 		s++;
    292 	else
    293 		s = str;
    294 	return s;
    295 }
    296 
    297 /* Here we go */
    298 int main(int argc, char *argv[], char *envp[])
    299 {
    300 	char *progname;
    301 	const struct futil_cmd_t *cmd;
    302 	int i, errorcnt = 0;
    303 	int vb_ver = VBOOT_VERSION_ALL;
    304 	struct option long_opts[] = {
    305 		{"vb1" , 0,  &vb_ver,  VBOOT_VERSION_1_0},
    306 		{"vb21", 0,  &vb_ver,  VBOOT_VERSION_2_1},
    307 		{ 0, 0, 0, 0},
    308 	};
    309 
    310 	log_args(argc, argv);
    311 
    312 	/* How were we invoked? */
    313 	progname = simple_basename(argv[0]);
    314 
    315 	/* See if the program name is a command we recognize */
    316 	cmd = find_command(progname);
    317 	if (cmd)
    318 		/* Yep, just do that */
    319 		return run_command(cmd, argc, argv);
    320 
    321 	/* Parse the global options, stopping at the first non-option. */
    322 	opterr = 0;				/* quiet, you. */
    323 	while ((i = getopt_long(argc, argv, "+:", long_opts, NULL)) != -1) {
    324 		switch (i) {
    325 		case '?':
    326 			if (optopt)
    327 				fprintf(stderr, "Unrecognized option: -%c\n",
    328 					optopt);
    329 			else
    330 				fprintf(stderr, "Unrecognized option: %s\n",
    331 					argv[optind - 1]);
    332 			errorcnt++;
    333 			break;
    334 		case ':':
    335 			fprintf(stderr, "Missing argument to -%c\n", optopt);
    336 			errorcnt++;
    337 			break;
    338 		case 0:				/* handled option */
    339 			break;
    340 		default:
    341 			Debug("i=%d\n", i);
    342 			DIE;
    343 		}
    344 	}
    345 	vboot_version = vb_ver;
    346 
    347 	/* Reset the getopt state so commands can parse their own options. */
    348 	argc -= optind;
    349 	argv += optind;
    350 	optind = 0;
    351 
    352 	/* We require a command name. */
    353 	if (errorcnt || argc < 1) {
    354 		do_help(0, 0);
    355 		return 1;
    356 	}
    357 
    358 	/* For reasons I've forgotten, treat /blah/blah/CMD the same as CMD */
    359 	progname = simple_basename(argv[0]);
    360 
    361 	/* Do we recognize the command? */
    362 	cmd = find_command(progname);
    363 	if (cmd)
    364 		return run_command(cmd, argc, argv);
    365 
    366 	/* Nope. We've no clue what we're being asked to do. */
    367 	do_help(0, 0);
    368 	return 1;
    369 }
    370