blob: 356208654fe6bc559e44f4b2f4923f934ddc06b4 [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
Simon Glass3401fdc2013-08-15 21:32:08 -0600111 if (write(log_fd, "\n", 1) < 0)
112 return;
Bill Richardsonfeb25182013-03-07 12:54:29 -0800113}
114
115static void log_close(void)
116{
117 struct flock lock;
118
119 if (log_fd >= 0) {
120 memset(&lock, 0, sizeof(lock));
121 lock.l_type = F_UNLCK;
122 lock.l_whence = SEEK_SET;
123 if (fcntl(log_fd, F_SETLKW, &lock))
124 perror("Unable to unlock log file");
125
126 close(log_fd);
127 log_fd = -1;
128 }
129}
130
131static void log_open(void)
132{
133 struct flock lock;
134 int ret;
135
136#ifdef FORCE_LOGGING_ON
137 log_fd = open(LOGFILE, O_WRONLY|O_APPEND|O_CREAT, 0666);
138#else
139 log_fd = open(LOGFILE, O_WRONLY|O_APPEND);
140#endif
141 if (log_fd < 0) {
142
143 if (errno != EACCES)
144 return;
145
146 /* Permission problems should improve shortly ... */
147 sleep(1);
148 log_fd = open(LOGFILE, O_WRONLY|O_APPEND|O_CREAT, 0666);
149 if (log_fd < 0) /* Nope, they didn't */
150 return;
151 }
152
153 /* Let anyone have a turn */
154 fchmod(log_fd, 0666);
155
156 /* But only one at a time */
157 memset(&lock, 0, sizeof(lock));
158 lock.l_type = F_WRLCK;
159 lock.l_whence = SEEK_END;
160
161 ret = fcntl(log_fd, F_SETLKW, &lock); /* this blocks */
162 if (ret < 0)
163 log_close();
164}
165
166#define CALLER_PREFIX "CALLER:"
167static void log_args(int argc, char *argv[])
168{
169 int i;
170 ssize_t r;
171 pid_t parent;
172 char buf[80];
173 char str_caller[PATH_MAX + sizeof(CALLER_PREFIX)] = CALLER_PREFIX;
174 char *truename = str_caller + sizeof(CALLER_PREFIX) - 1;
175 /* Note: truename starts on the \0 from CALLER_PREFIX, so we can write
176 * PATH_MAX chars into truename and still append a \0 at the end. */
177
178 log_open();
179
180 /* delimiter */
181 log_str("##### HEY #####");
182
183 /* Can we tell who called us? */
184 parent = getppid();
185 snprintf(buf, sizeof(buf), "/proc/%d/exe", parent);
186 r = readlink(buf, truename, PATH_MAX);
187 if (r >= 0) {
188 truename[r] = '\0';
189 log_str(str_caller);
190 }
191
192 /* Now log the stuff about ourselves */
193 for (i = 0; i < argc; i++)
194 log_str(argv[i]);
195
196 log_close();
197}
198
199
200/******************************************************************************/
201/* Here we go */
202
203int main(int argc, char *argv[], char *envp[])
204{
205 char *progname;
206 char truename[PATH_MAX];
207 char oldname[PATH_MAX];
208 char buf[80];
209 pid_t myproc;
210 ssize_t r;
211 char *s;
212 futil_cmd_t *cmd;
213
214 log_args(argc, argv);
215
216 /* How were we invoked? */
217 progname = strrchr(argv[0], '/');
218 if (progname)
219 progname++;
220 else
221 progname = argv[0];
222
223 /* Invoked directly by name */
Bill Richardson6db8c752013-04-05 13:30:43 -0700224 if (0 == strcmp(progname, MYNAME) || 0 == strcmp(progname, MYNAME_S)) {
Bill Richardsonfeb25182013-03-07 12:54:29 -0800225 if (argc < 2) { /* must have an argument */
Bill Richardson6db8c752013-04-05 13:30:43 -0700226 help(0, 0);
Bill Richardsonfeb25182013-03-07 12:54:29 -0800227 exit(1);
228 }
229
230 /* We can just pass the rest along, then */
231 argc--;
232 argv++;
233
234 /* So now what name do we want to invoke? */
235 progname = strrchr(argv[0], '/');
236 if (progname)
237 progname++;
238 else
239 progname = argv[0];
240 }
241
242 /* See if it's asking for something we know how to do ourselves */
243 for (cmd = futil_cmds_start(); cmd < futil_cmds_end(); cmd++)
244 if (0 == strcmp(cmd->name, progname))
245 return cmd->handler(argc, argv);
246
247 /* Nope, it must be wrapped */
248
249 /* The old binaries live under the true executable. Find out where that is. */
250 myproc = getpid();
251 snprintf(buf, sizeof(buf), "/proc/%d/exe", myproc);
252 r = readlink(buf, truename, PATH_MAX - 1);
253 if (r < 0) {
254 fprintf(stderr, "%s is lost: %s => %s: %s\n", MYNAME, argv[0],
255 buf, strerror(errno));
256 exit(1);
257 } else if (r == PATH_MAX - 1) {
258 /* Yes, it might _just_ fit, but we'll count that as wrong anyway. We can't
259 * determine the right size using the example in the readlink manpage,
260 * because the /proc symlink returns an st_size of 0. */
261 fprintf(stderr, "%s is too long: %s => %s\n", MYNAME, argv[0], buf);
262 exit(1);
263 }
264
265 truename[r] = '\0';
266 s = strrchr(truename, '/'); /* Find the true directory */
267 if (s) {
268 *s = '\0';
269 } else { /* I don't think this can happen */
270 fprintf(stderr, "%s says %s doesn't make sense\n", MYNAME, truename);
271 exit(1);
272 }
273 /* If the old binary path doesn't fit, just give up. */
274 r = snprintf(oldname, PATH_MAX, "%s/%s/%s", truename, SUBDIR, progname);
275 if (r >= PATH_MAX) {
276 fprintf(stderr, "%s/%s/%s is too long\n", truename, SUBDIR, progname);
277 exit(1);
278 }
279
280 fflush(0);
281 execve(oldname, argv, envp);
282
283 fprintf(stderr, "%s failed to exec %s: %s\n", MYNAME,
284 oldname, strerror(errno));
285 return 1;
286}