randomtest fixes
[oweals/busybox.git] / init / bootchartd.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
4  */
5 #include "libbb.h"
6 #include <sys/mount.h>
7 #ifndef MS_SILENT
8 # define MS_SILENT      (1 << 15)
9 #endif
10 #ifndef MNT_DETACH
11 # define MNT_DETACH 0x00000002
12 #endif
13
14 #define BC_VERSION_STR "0.8"
15
16 /* For debugging, set to 0:
17  * strace won't work with DO_SIGNAL_SYNC set to 1.
18  */
19 #define DO_SIGNAL_SYNC 1
20
21
22 //Not supported: $PWD/bootchartd.conf and /etc/bootchartd.conf
23
24 //# tmpfs size
25 //# (32 MB should suffice for ~20 minutes worth of log data, but YMMV)
26 //TMPFS_SIZE=32m
27 //
28 //# Sampling period (in seconds)
29 //SAMPLE_PERIOD=0.2
30 //
31 //# Whether to enable and store BSD process accounting information.  The
32 //# kernel needs to be configured to enable v3 accounting
33 //# (CONFIG_BSD_PROCESS_ACCT_V3). accton from the GNU accounting utilities
34 //# is also required.
35 //PROCESS_ACCOUNTING="no"
36 //
37 //# Tarball for the various boot log files
38 //BOOTLOG_DEST=/var/log/bootchart.tgz
39 //
40 //# Whether to automatically stop logging as the boot process completes.
41 //# The logger will look for known processes that indicate bootup completion
42 //# at a specific runlevel (e.g. gdm-binary, mingetty, etc.).
43 //AUTO_STOP_LOGGER="yes"
44 //
45 //# Whether to automatically generate the boot chart once the boot logger
46 //# completes.  The boot chart will be generated in $AUTO_RENDER_DIR.
47 //# Note that the bootchart package must be installed.
48 //AUTO_RENDER="no"
49 //
50 //# Image format to use for the auto-generated boot chart
51 //# (choose between png, svg and eps).
52 //AUTO_RENDER_FORMAT="png"
53 //
54 //# Output directory for auto-generated boot charts
55 //AUTO_RENDER_DIR="/var/log"
56
57
58 /* Globals */
59 struct globals {
60         char jiffy_line[sizeof(bb_common_bufsiz1)];
61 } FIX_ALIASING;
62 #define G (*(struct globals*)&bb_common_bufsiz1)
63 #define INIT_G() do { } while (0)
64
65 static void dump_file(FILE *fp, const char *filename)
66 {
67         int fd = open(filename, O_RDONLY);
68         if (fd >= 0) {
69                 fputs(G.jiffy_line, fp);
70                 fflush(fp);
71                 bb_copyfd_eof(fd, fileno(fp));
72                 close(fd);
73                 fputc('\n', fp);
74         }
75 }
76
77 static int dump_procs(FILE *fp, int look_for_login_process)
78 {
79         struct dirent *entry;
80         DIR *dir = opendir("/proc");
81         int found_login_process = 0;
82
83         fputs(G.jiffy_line, fp);
84         while ((entry = readdir(dir)) != NULL) {
85                 char name[sizeof("/proc/%u/cmdline") + sizeof(int)*3];
86                 int stat_fd;
87                 unsigned pid = bb_strtou(entry->d_name, NULL, 10);
88                 if (errno)
89                         continue;
90
91                 /* Android's version reads /proc/PID/cmdline and extracts
92                  * non-truncated process name. Do we want to do that? */
93
94                 sprintf(name, "/proc/%u/stat", pid);
95                 stat_fd = open(name, O_RDONLY);
96                 if (stat_fd >= 0) {
97                         char *p;
98                         char stat_line[4*1024];
99                         int rd = safe_read(stat_fd, stat_line, sizeof(stat_line)-2);
100
101                         close(stat_fd);
102                         if (rd < 0)
103                                 continue;
104                         stat_line[rd] = '\0';
105                         p = strchrnul(stat_line, '\n');
106                         *p++ = '\n';
107                         *p = '\0';
108                         fputs(stat_line, fp);
109                         if (!look_for_login_process)
110                                 continue;
111                         p = strchr(stat_line, '(');
112                         if (!p)
113                                 continue;
114                         p++;
115                         strchrnul(p, ')')[0] = '\0';
116                         /* If is gdm, kdm or a getty? */
117                         if (((p[0] == 'g' || p[0] == 'k' || p[0] == 'x') && p[1] == 'd' && p[2] == 'm')
118                          || strstr(p, "getty")
119                         ) {
120                                 found_login_process = 1;
121                         }
122                 }
123         }
124         closedir(dir);
125         fputc('\n', fp);
126         return found_login_process;
127 }
128
129 static char *make_tempdir(const char *prog)
130 {
131         char template[] = "/tmp/bootchart.XXXXXX";
132         char *tempdir = xstrdup(mkdtemp(template));
133         if (!tempdir) {
134                 /* /tmp is not writable (happens when we are used as init).
135                  * Try to mount a tmpfs, them cd and lazily unmount it.
136                  * Since we unmount it at once, we can mount it anywhere.
137                  * Try a few locations which are likely ti exist.
138                  */
139                 static const char dirs[] = "/mnt\0""/tmp\0""/boot\0""/proc\0";
140                 const char *try_dir = dirs;
141                 while (mount("none", try_dir, "tmpfs", MS_SILENT, "size=16m") != 0) {
142                         try_dir += strlen(try_dir) + 1;
143                         if (!try_dir[0])
144                                 bb_perror_msg_and_die("can't %smount tmpfs", "");
145                 }
146                 //bb_error_msg("mounted tmpfs on %s", try_dir);
147                 xchdir(try_dir);
148                 if (umount2(try_dir, MNT_DETACH) != 0) {
149                         bb_perror_msg_and_die("can't %smount tmpfs", "un");
150                 }
151         } else {
152                 xchdir(tempdir);
153         }
154         {
155                 FILE *header_fp = xfopen("header", "w");
156                 if (prog)
157                         fprintf(header_fp, "profile.process = %s\n", prog);
158                 fputs("version = "BC_VERSION_STR"\n", header_fp);
159                 fclose(header_fp);
160         }
161
162         return tempdir;
163 }
164
165 static void do_logging(void)
166 {
167         //# Enable process accounting if configured
168         //if [ "$PROCESS_ACCOUNTING" = "yes" ]; then
169         //      [ -e kernel_pacct ] || : > kernel_pacct
170         //      accton kernel_pacct
171         //fi
172
173         FILE *proc_stat = xfopen("proc_stat.log", "w");
174         FILE *proc_diskstats = xfopen("proc_diskstats.log", "w");
175         //FILE *proc_netdev = xfopen("proc_netdev.log", "w");
176         FILE *proc_ps = xfopen("proc_ps.log", "w");
177         int look_for_login_process = (getppid() == 1);
178         unsigned count = 60*1000*1000 / (200*1000); /* ~1 minute */
179
180         while (--count && !bb_got_signal) {
181                 char *p;
182                 int len = open_read_close("/proc/uptime", G.jiffy_line, sizeof(G.jiffy_line)-2);
183                 if (len < 0)
184                         goto wait_more;
185                 /* /proc/uptime has format "NNNNNN.MM NNNNNNN.MM" */
186                 /* we convert it to "NNNNNNMM\n" (using first value) */
187                 G.jiffy_line[len] = '\0';
188                 p = strchr(G.jiffy_line, '.');
189                 if (!p)
190                         goto wait_more;
191                 while (isdigit(*++p))
192                         p[-1] = *p;
193                 p[-1] = '\n';
194                 p[0] = '\0';
195
196                 dump_file(proc_stat, "/proc/stat");
197                 dump_file(proc_diskstats, "/proc/diskstats");
198                 //dump_file(proc_netdev, "/proc/net/dev");
199                 if (dump_procs(proc_ps, look_for_login_process)) {
200                         /* dump_procs saw a getty or {g,k,x}dm
201                          * stop logging in 2 seconds:
202                          */
203                         if (count > 2*1000*1000 / (200*1000))
204                                 count = 2*1000*1000 / (200*1000);
205                 }
206                 fflush_all();
207  wait_more:
208                 usleep(200*1000);
209         }
210
211         // [ -e kernel_pacct ] && accton off
212 }
213
214 static void finalize(char *tempdir)
215 {
216         //# Stop process accounting if configured
217         //local pacct=
218         //[ -e kernel_pacct ] && pacct=kernel_pacct
219
220         //(
221         //      echo "version = $VERSION"
222         //      echo "title = Boot chart for $( hostname | sed q ) ($( date ))"
223         //      echo "system.uname = $( uname -srvm | sed q )"
224         //      echo "system.release = $( sed q /etc/SuSE-release )"
225         //      echo "system.cpu = $( grep '^model name' /proc/cpuinfo | sed q ) ($cpucount)"
226         //      echo "system.kernel.options = $( sed q /proc/cmdline )"
227         //) >> header
228
229         /* Package log files */
230         system("tar -zcf /var/log/bootchart.tgz header *.log"); // + $pacct
231         /* Clean up (if we are not in detached tmpfs) */
232         if (tempdir) {
233                 unlink("header");
234                 unlink("proc_stat.log");
235                 unlink("proc_diskstats.log");
236                 //unlink("proc_netdev.log");
237                 unlink("proc_ps.log");
238                 rmdir(tempdir);
239         }
240
241         /* shell-based bootchartd tries to run /usr/bin/bootchart if $AUTO_RENDER=yes:
242          * /usr/bin/bootchart -o "$AUTO_RENDER_DIR" -f $AUTO_RENDER_FORMAT "$BOOTLOG_DEST"
243          */
244 }
245
246 /* Usage:
247  * bootchartd start [PROG ARGS]: start logging in background, USR1 stops it.
248  *      With PROG, runs PROG, then kills background logging.
249  * bootchartd stop: same as "killall -USR1 bootchartd"
250  * bootchartd init: start logging in background
251  *      Stop when getty/gdm is seen (if AUTO_STOP_LOGGER = yes).
252  *      Meant to be used from init scripts.
253  * bootchartd (pid==1): as init, but then execs $bootchart_init, /init, /sbin/init
254  *      Meant to be used as kernel's init process.
255  */
256 int bootchartd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
257 int bootchartd_main(int argc UNUSED_PARAM, char **argv)
258 {
259         pid_t parent_pid, logger_pid;
260         smallint cmd;
261         enum {
262                 CMD_STOP = 0,
263                 CMD_START,
264                 CMD_INIT,
265                 CMD_PID1, /* used to mark pid 1 case */
266         };
267
268         INIT_G();
269
270         parent_pid = getpid();
271         if (argv[1]) {
272                 cmd = index_in_strings("stop\0""start\0""init\0", argv[1]);
273                 if (cmd < 0)
274                         bb_show_usage();
275                 if (cmd == CMD_STOP) {
276                         pid_t *pidList = find_pid_by_name("bootchartd");
277                         while (*pidList != 0) {
278                                 if (*pidList != parent_pid)
279                                         kill(*pidList, SIGUSR1);
280                                 pidList++;
281                         }
282                         return EXIT_SUCCESS;
283                 }
284         } else {
285                 if (parent_pid != 1)
286                         bb_show_usage();
287                 cmd = CMD_PID1;
288         }
289
290         /* Here we are in START or INIT state. Create logger child: */
291         logger_pid = fork_or_rexec(argv);
292
293         if (logger_pid == 0) { /* child */
294                 char *tempdir;
295
296                 bb_signals(0
297                         + (1 << SIGUSR1)
298                         + (1 << SIGUSR2)
299                         + (1 << SIGTERM)
300                         + (1 << SIGQUIT)
301                         + (1 << SIGINT)
302                         + (1 << SIGHUP)
303                         , record_signo);
304
305                 if (DO_SIGNAL_SYNC)
306                         /* Inform parent that we are ready */
307                         raise(SIGSTOP);
308
309                 /* If we are started by kernel, PATH might be unset.
310                  * In order to find "tar", let's set some sane PATH:
311                  */
312                 if (cmd == CMD_PID1 && !getenv("PATH"))
313                         putenv((char*)bb_PATH_root_path);
314
315                 tempdir = make_tempdir(cmd == CMD_START ? argv[2] : NULL);
316                 do_logging();
317                 finalize(tempdir);
318                 return EXIT_SUCCESS;
319         }
320
321         /* parent */
322
323         if (DO_SIGNAL_SYNC) {
324                 /* Wait for logger child to set handlers, then unpause it.
325                  * Otherwise with short-lived PROG (e.g. "bootchartd start true")
326                  * we might send SIGUSR1 before logger sets its handler.
327                  */
328                 waitpid(logger_pid, NULL, WUNTRACED);
329                 kill(logger_pid, SIGCONT);
330         }
331
332         if (cmd == CMD_PID1) {
333                 char *bootchart_init = getenv("bootchart_init");
334                 if (bootchart_init)
335                         execl(bootchart_init, bootchart_init, NULL);
336                 execl("/init", "init", NULL);
337                 execl("/sbin/init", "init", NULL);
338                 bb_perror_msg_and_die("can't exec '%s'", "/sbin/init");
339         }
340
341         if (cmd == CMD_START && argv[2]) { /* "start PROG ARGS" */
342                 pid_t pid = vfork();
343                 if (pid < 0)
344                         bb_perror_msg_and_die("vfork");
345                 if (pid == 0) { /* child */
346                         argv += 2;
347                         execvp(argv[0], argv);
348                         bb_perror_msg_and_die("can't exec '%s'", argv[0]);
349                 }
350                 /* parent */
351                 waitpid(pid, NULL, 0);
352                 kill(logger_pid, SIGUSR1);
353         }
354
355         return EXIT_SUCCESS;
356 }