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