blob: 54921d1afb82622df04c4321fc5d607066b5575b [file] [log] [blame]
Bill Richardsonfeb25182013-03-07 12:54:29 -08001/*
2 * Copyright (c) 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
Bill Richardsonfeb25182013-03-07 12:54:29 -08007#include <errno.h>
8#include <fcntl.h>
9#include <limits.h>
10#include <stdint.h>
11#include <stdio.h>
12#include <stdlib.h>
13#include <string.h>
14#include <sys/stat.h>
15#include <unistd.h>
16
17#include "futility.h"
18
19#define MYNAME "futility"
Bill Richardson6db8c752013-04-05 13:30:43 -070020#define MYNAME_S MYNAME "_s"
Bill Richardsonfeb25182013-03-07 12:54:29 -080021#ifdef OLDDIR
22#define XSTR(A) STR(A)
23#define STR(A) #A
24#define SUBDIR XSTR(OLDDIR)
25#else
26#define SUBDIR "old_bins"
27#endif
28
29/* File to use for logging, if present */
30#define LOGFILE "/tmp/futility.log"
31
32/* Normally logging will only happen if the logfile already exists. Uncomment
33 * this to force log file creation (and thus logging) always. */
34/* #define FORCE_LOGGING_ON */
35
36/******************************************************************************/
37
38static const char * const usage= "\n\
39Usage: " MYNAME " PROGRAM|COMMAND [args...]\n\
40\n\
41This is the unified firmware utility, which will eventually replace\n\
42all the distinct userspace tools formerly produced by the\n\
43vboot_reference package.\n\
44\n\
45When symlinked under the name of one of those previous tools, it can\n\
46do one of two things: either it will fully implement the original\n\
47behavior, or (until that functionality is complete) it will just exec\n\
48the original binary.\n\
49\n\
50In either case it can append some usage information to " LOGFILE "\n\
51to help improve coverage and correctness.\n\
52\n\
53If you invoke it directly instead of via a symlink, it requires one\n\
54argument, which is the name of the old binary to exec. That binary\n\
55must be located in a directory named \"" SUBDIR "\" underneath\n\
56the " MYNAME " executable.\n\
57\n";
58
59static int help(int argc, char *argv[])
60{
61 futil_cmd_t *cmd;
62 int i;
63
64 fputs(usage, stdout);
65
66 printf("The following commands are built-in:\n");
67
68 for (cmd = futil_cmds_start(); cmd < futil_cmds_end(); cmd++)
Bill Richardson6db8c752013-04-05 13:30:43 -070069 printf(" %-20s %s\n", cmd->name, cmd->shorthelp);
Bill Richardsonfeb25182013-03-07 12:54:29 -080070 printf("\n");
71
Bill Richardson6db8c752013-04-05 13:30:43 -070072 if (argc) {
73 printf("FYI, you added these args that I'm ignoring:\n");
74 for (i = 0; i < argc; i++)
75 printf("argv[%d] = %s\n", i, argv[i]);
76 }
Bill Richardsonfeb25182013-03-07 12:54:29 -080077
78 return 0;
79}
Bill Richardson6db8c752013-04-05 13:30:43 -070080DECLARE_FUTIL_COMMAND(help, help, "show a bit of help");
Bill Richardsonfeb25182013-03-07 12:54:29 -080081
82
83/******************************************************************************/
84/* Logging stuff */
85
86static int log_fd = -1;
87
88/* Write the string and a newline. Silently give up on errors */
89static void log_str(char *str)
90{
91 int len, done, n;
92
93 if (log_fd < 0)
94 return;
95
96 if (!str)
97 str = "(NULL)";
98
99 len = strlen(str);
100 if (len == 0) {
101 str = "(EMPTY)";
102 len = strlen(str);
103 }
104
105 for (done = 0; done < len; done += n) {
106 n = write(log_fd, str + done, len - done);
107 if (n < 0)
108 return;
109 }
110
111 write(log_fd, "\n", 1);
112}
113
114static void log_close(void)
115{
116 struct flock lock;
117
118 if (log_fd >= 0) {
119 memset(&lock, 0, sizeof(lock));
120 lock.l_type = F_UNLCK;
121 lock.l_whence = SEEK_SET;
122 if (fcntl(log_fd, F_SETLKW, &lock))
123 perror("Unable to unlock log file");
124
125 close(log_fd);
126 log_fd = -1;
127 }
128}
129
130static void log_open(void)
131{
132 struct flock lock;
133 int ret;
134
135#ifdef FORCE_LOGGING_ON
136 log_fd = open(LOGFILE, O_WRONLY|O_APPEND|O_CREAT, 0666);
137#else
138 log_fd = open(LOGFILE, O_WRONLY|O_APPEND);
139#endif
140 if (log_fd < 0) {
141
142 if (errno != EACCES)
143 return;
144
145 /* Permission problems should improve shortly ... */
146 sleep(1);
147 log_fd = open(LOGFILE, O_WRONLY|O_APPEND|O_CREAT, 0666);
148 if (log_fd < 0) /* Nope, they didn't */
149 return;
150 }
151
152 /* Let anyone have a turn */
153 fchmod(log_fd, 0666);
154
155 /* But only one at a time */
156 memset(&lock, 0, sizeof(lock));
157 lock.l_type = F_WRLCK;
158 lock.l_whence = SEEK_END;
159
160 ret = fcntl(log_fd, F_SETLKW, &lock); /* this blocks */
161 if (ret < 0)
162 log_close();
163}
164
165#define CALLER_PREFIX "CALLER:"
166static void log_args(int argc, char *argv[])
167{
168 int i;
169 ssize_t r;
170 pid_t parent;
171 char buf[80];
172 char str_caller[PATH_MAX + sizeof(CALLER_PREFIX)] = CALLER_PREFIX;
173 char *truename = str_caller + sizeof(CALLER_PREFIX) - 1;
174 /* Note: truename starts on the \0 from CALLER_PREFIX, so we can write
175 * PATH_MAX chars into truename and still append a \0 at the end. */
176
177 log_open();
178
179 /* delimiter */
180 log_str("##### HEY #####");
181
182 /* Can we tell who called us? */
183 parent = getppid();
184 snprintf(buf, sizeof(buf), "/proc/%d/exe", parent);
185 r = readlink(buf, truename, PATH_MAX);
186 if (r >= 0) {
187 truename[r] = '\0';
188 log_str(str_caller);
189 }
190
191 /* Now log the stuff about ourselves */
192 for (i = 0; i < argc; i++)
193 log_str(argv[i]);
194
195 log_close();
196}
197
198
199/******************************************************************************/
200/* Here we go */
201
202int main(int argc, char *argv[], char *envp[])
203{
204 char *progname;
205 char truename[PATH_MAX];
206 char oldname[PATH_MAX];
207 char buf[80];
208 pid_t myproc;
209 ssize_t r;
210 char *s;
211 futil_cmd_t *cmd;
212
213 log_args(argc, argv);
214
215 /* How were we invoked? */
216 progname = strrchr(argv[0], '/');
217 if (progname)
218 progname++;
219 else
220 progname = argv[0];
221
222 /* Invoked directly by name */
Bill Richardson6db8c752013-04-05 13:30:43 -0700223 if (0 == strcmp(progname, MYNAME) || 0 == strcmp(progname, MYNAME_S)) {
Bill Richardsonfeb25182013-03-07 12:54:29 -0800224 if (argc < 2) { /* must have an argument */
Bill Richardson6db8c752013-04-05 13:30:43 -0700225 help(0, 0);
Bill Richardsonfeb25182013-03-07 12:54:29 -0800226 exit(1);
227 }
228
229 /* We can just pass the rest along, then */
230 argc--;
231 argv++;
232
233 /* So now what name do we want to invoke? */
234 progname = strrchr(argv[0], '/');
235 if (progname)
236 progname++;
237 else
238 progname = argv[0];
239 }
240
241 /* See if it's asking for something we know how to do ourselves */
242 for (cmd = futil_cmds_start(); cmd < futil_cmds_end(); cmd++)
243 if (0 == strcmp(cmd->name, progname))
244 return cmd->handler(argc, argv);
245
246 /* Nope, it must be wrapped */
247
248 /* The old binaries live under the true executable. Find out where that is. */
249 myproc = getpid();
250 snprintf(buf, sizeof(buf), "/proc/%d/exe", myproc);
251 r = readlink(buf, truename, PATH_MAX - 1);
252 if (r < 0) {
253 fprintf(stderr, "%s is lost: %s => %s: %s\n", MYNAME, argv[0],
254 buf, strerror(errno));
255 exit(1);
256 } else if (r == PATH_MAX - 1) {
257 /* Yes, it might _just_ fit, but we'll count that as wrong anyway. We can't
258 * determine the right size using the example in the readlink manpage,
259 * because the /proc symlink returns an st_size of 0. */
260 fprintf(stderr, "%s is too long: %s => %s\n", MYNAME, argv[0], buf);
261 exit(1);
262 }
263
264 truename[r] = '\0';
265 s = strrchr(truename, '/'); /* Find the true directory */
266 if (s) {
267 *s = '\0';
268 } else { /* I don't think this can happen */
269 fprintf(stderr, "%s says %s doesn't make sense\n", MYNAME, truename);
270 exit(1);
271 }
272 /* If the old binary path doesn't fit, just give up. */
273 r = snprintf(oldname, PATH_MAX, "%s/%s/%s", truename, SUBDIR, progname);
274 if (r >= PATH_MAX) {
275 fprintf(stderr, "%s/%s/%s is too long\n", truename, SUBDIR, progname);
276 exit(1);
277 }
278
279 fflush(0);
280 execve(oldname, argv, envp);
281
282 fprintf(stderr, "%s failed to exec %s: %s\n", MYNAME,
283 oldname, strerror(errno));
284 return 1;
285}