chiark / gitweb /
readahead: properly initialize variable
[elogind.git] / src / readahead-collect.c
index 937231ca4282d46355b01b738c51c6cbccf896ef..50c1a0b6844b835d816a2d5f52da0ee69d16d48d 100644 (file)
@@ -40,6 +40,8 @@
 #include <linux/fiemap.h>
 #include <sys/ioctl.h>
 #include <sys/vfs.h>
+#include <getopt.h>
+#include <sys/inotify.h>
 
 #include "missing.h"
 #include "util.h"
 #include "ioprio.h"
 #include "readahead-common.h"
 
-#define MINCORE_VEC_SIZE (READAHEAD_FILE_SIZE_MAX/PAGE_SIZE)
+/* fixme:
+ *
+ * - detect ssd on btrfs/lvm...
+ * - read ahead directories
+ * - gzip?
+ * - remount rw?
+ * - handle files where nothing is in mincore
+ * - does ioprio_set work with fadvise()?
+ */
+
+static unsigned arg_files_max = 16*1024;
+static off_t arg_file_size_max = READAHEAD_FILE_SIZE_MAX;
+static usec_t arg_timeout = 2*USEC_PER_MINUTE;
 
 static int btrfs_defrag(int fd) {
         struct btrfs_ioctl_vol_args data;
@@ -62,7 +76,7 @@ static int btrfs_defrag(int fd) {
 static int pack_file(FILE *pack, const char *fn, bool on_btrfs) {
         struct stat st;
         void *start = MAP_FAILED;
-        uint8_t vec[MINCORE_VEC_SIZE];
+        uint8_t *vec;
         uint32_t b, c;
         size_t l, pages;
         bool mapped;
@@ -77,7 +91,7 @@ static int pack_file(FILE *pack, const char *fn, bool on_btrfs) {
                 goto finish;
         }
 
-        if ((k = file_verify(fd, fn, &st)) <= 0) {
+        if ((k = file_verify(fd, fn, arg_file_size_max, &st)) <= 0) {
                 r = k;
                 goto finish;
         }
@@ -92,6 +106,9 @@ static int pack_file(FILE *pack, const char *fn, bool on_btrfs) {
                 goto finish;
         }
 
+        pages = l / PAGE_SIZE;
+
+        vec = alloca(pages);
         if (mincore(start, l, vec) < 0) {
                 log_warning("mincore(%s) failed: %m", fn);
                 r = -errno;
@@ -101,10 +118,9 @@ static int pack_file(FILE *pack, const char *fn, bool on_btrfs) {
         fputs(fn, pack);
         fputc('\n', pack);
 
-        pages = l / PAGE_SIZE;
         mapped = false;
         for (c = 0; c < pages; c++) {
-                bool new_mapped = (vec[c] & 1);
+                bool new_mapped = !!(vec[c] & 1);
 
                 if (!mapped && new_mapped)
                         b = c;
@@ -184,12 +200,13 @@ static int qsort_compare(const void *a, const void *b) {
 
 static int collect(const char *root) {
         enum {
-                FD_FANOTIFY,
+                FD_FANOTIFY,  /* Get the actual fs events */
                 FD_SIGNAL,
+                FD_INOTIFY,   /* We get notifications to quit early via this fd */
                 _FD_MAX
         };
         struct pollfd pollfd[_FD_MAX];
-        int fanotify_fd = -1, signal_fd = -1, r = 0;
+        int fanotify_fd = -1, signal_fd = -1, inotify_fd = -1, r = 0;
         pid_t my_pid;
         Hashmap *files = NULL;
         Iterator i;
@@ -199,9 +216,12 @@ static int collect(const char *root) {
         char *pack_fn_new = NULL, *pack_fn = NULL;
         bool on_ssd, on_btrfs;
         struct statfs sfs;
+        usec_t not_after;
 
         assert(root);
 
+        write_one_line_file("/proc/self/oom_score_adj", "1000");
+
         if (ioprio_set(IOPRIO_WHO_PROCESS, getpid(), IOPRIO_PRIO_VALUE(IOPRIO_CLASS_IDLE, 0)) < 0)
                 log_warning("Failed to set IDLE IO priority class: %m");
 
@@ -221,7 +241,7 @@ static int collect(const char *root) {
                 goto finish;
         }
 
-        if ((fanotify_fd = fanotify_init(FAN_CLOEXEC, O_RDONLY|O_LARGEFILE|O_CLOEXEC|O_NOATIME)) < 0)  {
+        if ((fanotify_fd = fanotify_init(FAN_CLOEXEC|FAN_NONBLOCK, O_RDONLY|O_LARGEFILE|O_CLOEXEC|O_NOATIME)) < 0)  {
                 log_error("Failed to create fanotify object: %m");
                 r = -errno;
                 goto finish;
@@ -233,6 +253,13 @@ static int collect(const char *root) {
                 goto finish;
         }
 
+        if ((inotify_fd = open_inotify()) < 0) {
+                r = inotify_fd;
+                goto finish;
+        }
+
+        not_after = now(CLOCK_MONOTONIC) + arg_timeout;
+
         my_pid = getpid();
 
         zero(pollfd);
@@ -240,6 +267,8 @@ static int collect(const char *root) {
         pollfd[FD_FANOTIFY].events = POLLIN;
         pollfd[FD_SIGNAL].fd = signal_fd;
         pollfd[FD_SIGNAL].events = POLLIN;
+        pollfd[FD_INOTIFY].fd = inotify_fd;
+        pollfd[FD_INOTIFY].events = POLLIN;
 
         sd_notify(0,
                   "READY=1\n"
@@ -247,6 +276,17 @@ static int collect(const char *root) {
 
         log_debug("Collecting...");
 
+        if (access("/dev/.systemd/readahead/cancel", F_OK) >= 0) {
+                log_debug("Collection canceled");
+                r = -ECANCELED;
+                goto finish;
+        }
+
+        if (access("/dev/.systemd/readahead/done", F_OK) >= 0) {
+                log_debug("Got termination request");
+                goto done;
+        }
+
         for (;;) {
                 union {
                         struct fanotify_event_metadata metadata;
@@ -254,11 +294,21 @@ static int collect(const char *root) {
                 } data;
                 ssize_t n;
                 struct fanotify_event_metadata *m;
+                usec_t t;
+                int h;
+
+                if (hashmap_size(files) > arg_files_max) {
+                        log_debug("Reached maximum number of read ahead files, ending collection.");
+                        break;
+                }
 
-                if (hashmap_size(files) > READAHEAD_FILES_MAX)
+                t = now(CLOCK_MONOTONIC);
+                if (t >= not_after) {
+                        log_debug("Reached maximum collection time, ending collection.");
                         break;
+                }
 
-                if (poll(pollfd, _FD_MAX, -1) < 0) {
+                if ((h = poll(pollfd, _FD_MAX, (int) ((not_after - t) / USEC_PER_MSEC))) < 0) {
 
                         if (errno == EINTR)
                                 continue;
@@ -268,8 +318,51 @@ static int collect(const char *root) {
                         goto finish;
                 }
 
-                if (pollfd[FD_SIGNAL].revents != 0)
+                if (h == 0) {
+                        log_debug("Reached maximum collection time, ending collection.");
                         break;
+                }
+
+                if (pollfd[FD_SIGNAL].revents) {
+                        log_debug("Got signal.");
+                        break;
+                }
+
+                if (pollfd[FD_INOTIFY].revents) {
+                        uint8_t inotify_buffer[sizeof(struct inotify_event) + FILENAME_MAX];
+                        struct inotify_event *e;
+
+                        if ((n = read(inotify_fd, &inotify_buffer, sizeof(inotify_buffer))) < 0) {
+                                if (errno == EINTR || errno == EAGAIN)
+                                        continue;
+
+                                log_error("Failed to read inotify event: %m");
+                                r = -errno;
+                                goto finish;
+                        }
+
+                        e = (struct inotify_event*) inotify_buffer;
+                        while (n > 0) {
+                                size_t step;
+
+                                if ((e->mask & IN_CREATE) && streq(e->name, "cancel")) {
+                                        log_debug("Collection canceled");
+                                        r = -ECANCELED;
+                                        goto finish;
+                                }
+
+                                if ((e->mask & IN_CREATE) && streq(e->name, "done")) {
+                                        log_debug("Got termination request");
+                                        goto done;
+                                }
+
+                                step = sizeof(struct inotify_event) + e->len;
+                                assert(step <= (size_t) n);
+
+                                e = (struct inotify_event*) ((uint8_t*) e + step);
+                                n -= step;
+                        }
+                }
 
                 if ((n = read(fanotify_fd, &data, sizeof(data))) < 0) {
 
@@ -281,8 +374,7 @@ static int collect(const char *root) {
                         goto finish;
                 }
 
-                m = &data.metadata;
-                while (FAN_EVENT_OK(m, n)) {
+                for (m = &data.metadata; FAN_EVENT_OK(m, n); m = FAN_EVENT_NEXT(m, n)) {
 
                         if (m->pid != my_pid && m->fd >= 0) {
                                 char fn[PATH_MAX];
@@ -293,8 +385,10 @@ static int collect(const char *root) {
 
                                 if ((k = readlink_malloc(fn, &p)) >= 0) {
 
-                                        if (hashmap_get(files, p))
-                                                /* Already read */
+                                        if (startswith(p, "/tmp") ||
+                                            hashmap_get(files, p))
+                                                /* Not interesting, or
+                                                 * already read */
                                                 free(p);
                                         else {
                                                 unsigned long ul;
@@ -302,10 +396,7 @@ static int collect(const char *root) {
                                                 ul = fd_first_block(m->fd);
 
                                                 if ((k = hashmap_put(files, p, ULONG_TO_PTR(ul))) < 0) {
-
-                                                        if (k != -EEXIST)
-                                                                log_warning("set_put() failed: %s", strerror(-k));
-
+                                                        log_warning("set_put() failed: %s", strerror(-k));
                                                         free(p);
                                                 }
                                         }
@@ -316,11 +407,10 @@ static int collect(const char *root) {
 
                         if (m->fd)
                                 close_nointr_nofail(m->fd);
-
-                        m = FAN_EVENT_NEXT(m, n);
                 }
         }
 
+done:
         if (fanotify_fd >= 0) {
                 close_nointr_nofail(fanotify_fd);
                 fanotify_fd = -1;
@@ -328,7 +418,7 @@ static int collect(const char *root) {
 
         log_debug("Writing Pack File...");
 
-        on_ssd = fs_on_ssd(root);
+        on_ssd = fs_on_ssd(root) == 0;
         log_debug("On SSD: %s", yes_no(on_ssd));
 
         on_btrfs = statfs(root, &sfs) >= 0 && sfs.f_type == BTRFS_SUPER_MAGIC;
@@ -355,7 +445,7 @@ static int collect(const char *root) {
         if (on_ssd || on_btrfs) {
 
                 /* On SSD or on btrfs, just write things out in the
-                 * order the files where accessed. */
+                 * order the files were accessed. */
 
                 HASHMAP_FOREACH_KEY(q, p, files, i)
                         pack_file(pack, p, on_btrfs);
@@ -420,6 +510,9 @@ finish:
         if (signal_fd >= 0)
                 close_nointr_nofail(signal_fd);
 
+        if (inotify_fd >= 0)
+                close_nointr_nofail(inotify_fd);
+
         if (pack) {
                 fclose(pack);
                 unlink(pack_fn_new);
@@ -429,20 +522,116 @@ finish:
         free(pack_fn);
 
         while ((p = hashmap_steal_first_key(files)))
-                free(q);
+                free(p);
 
         hashmap_free(files);
 
         return r;
 }
 
+static int help(void) {
+
+        printf("%s [OPTIONS...] [DIRECTORY]\n\n"
+               "Collect read-ahead data on early boot.\n\n"
+               "  -h --help                 Show this help\n"
+               "     --max-files=INT        Maximum number of files to read ahead\n"
+               "     --max-file-size=BYTES  Maximum size of files to read ahead\n"
+               "     --timeout=USEC         Maximum time to spend collecting data\n",
+               program_invocation_short_name);
+
+        return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+        enum {
+                ARG_FILES_MAX = 0x100,
+                ARG_FILE_SIZE_MAX,
+                ARG_TIMEOUT
+        };
+
+        static const struct option options[] = {
+                { "help",          no_argument,       NULL, 'h'                },
+                { "files-max",     required_argument, NULL, ARG_FILES_MAX      },
+                { "file-size-max", required_argument, NULL, ARG_FILE_SIZE_MAX  },
+                { "timeout",       required_argument, NULL, ARG_TIMEOUT        },
+                { NULL,            0,                 NULL, 0                  }
+        };
+
+        int c;
+
+        assert(argc >= 0);
+        assert(argv);
+
+        while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
+
+                switch (c) {
+
+                case 'h':
+                        help();
+                        return 0;
+
+                case ARG_FILES_MAX:
+                        if (safe_atou(optarg, &arg_files_max) < 0 || arg_files_max <= 0) {
+                                log_error("Failed to parse maximum number of files %s.", optarg);
+                                return -EINVAL;
+                        }
+                        break;
+
+                case ARG_FILE_SIZE_MAX: {
+                        unsigned long long ull;
+
+                        if (safe_atollu(optarg, &ull) < 0 || ull <= 0) {
+                                log_error("Failed to parse maximum file size %s.", optarg);
+                                return -EINVAL;
+                        }
+
+                        arg_file_size_max = (off_t) ull;
+                        break;
+                }
+
+                case ARG_TIMEOUT:
+                        if (parse_usec(optarg, &arg_timeout) < 0 || arg_timeout <= 0) {
+                                log_error("Failed to parse timeout %s.", optarg);
+                                return -EINVAL;
+                        }
+
+                        break;
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        log_error("Unknown option code %c", c);
+                        return -EINVAL;
+                }
+        }
+
+        if (optind != argc &&
+            optind != argc-1) {
+                help();
+                return -EINVAL;
+        }
+
+        return 1;
+}
+
 int main(int argc, char *argv[]) {
+        int r;
 
         log_set_target(LOG_TARGET_SYSLOG_OR_KMSG);
         log_parse_environment();
         log_open();
 
-        if (collect(argc >= 2 ? argv[1] : "/") < 0)
+        if ((r = parse_argv(argc, argv)) <= 0)
+                return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+
+        if (!enough_ram()) {
+                log_info("Disabling readahead collector due to low memory.");
+                return 0;
+        }
+
+        if (collect(optind < argc ? argv[optind] : "/") < 0)
                 return 1;
 
         return 0;