Files
OpenCellular/futility/futility.c
Bill Richardson 6db8c75021 Build both static and dynamic versions of futility.
In almost every case we want the dynamically linked version of futility,
because it's smaller and the openssl functions require it (they use
dl_open() to invoke the correct RSA libraries).

However, the AU shellball requires three futility functions (crossystem,
gbb_utility, and dump_fmap). Those pretty much have to be built statically,
because they run from the new rootfs and packing all the dynamic libraries
into the shellball is way too large and complicated.

This change prepares to build both futility (full featured) and futility_s
(just those functions). The scripts that create the AU shellball will
already choose futility_s from /build/$BOARD/.

BUG=chromium:224734
BRANCH=none
TEST=none
CQ-DEPEND=CL:47589

Nothing to test just yet. The AU shellball is currently broken (it uses the
dynamic version of futility and doesn't copy the symlinks anyway), so this
should have no effect. We just need to ensure that the _s version doesn't go
into any of the other images.

Change-Id: I60b8dcd17e135f12a0d29ddacfb9fe8275567c70
Signed-off-by: Bill Richardson <wfrichar@chromium.org>
Reviewed-on: https://gerrit.chromium.org/gerrit/47466
2013-04-08 22:03:00 -07:00

286 lines
7.0 KiB
C

/*
* 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.
*/
#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"
#define MYNAME_S MYNAME "_s"
#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");
if (argc) {
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) || 0 == strcmp(progname, MYNAME_S)) {
if (argc < 2) { /* must have an argument */
help(0, 0);
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;
}