| /* Copyright (c) 2012 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| /* |
| * bootcache reads the block trace taken during boot and |
| * makes a boot cache from it. |
| * |
| * bootcache should be run after the system has booted |
| * including bringing up chrome and login. Works in |
| * conjunction with dm-bootcache device mapper to coalesce |
| * the blocks used during boot. |
| * |
| * bootcache [-t] <device-name> |
| * |
| * -t - for testing - looks in a different place for |
| * information files. |
| * |
| * <device-name> e.g. dm-0. Device name without /dev/ |
| * prefix. |
| * |
| * Files: |
| * 1. Device - /dev/dm-0 - Where the blocks to be cached |
| * are stored. Both the original and cached |
| * copy. |
| * 2. Header - /sys/kernel/debug/dm-bootcache/dm-0/header |
| * Header for the boot cache. It contains the |
| * information the bootcache utility will need |
| * to create the bootcache. |
| * 3. Trace - /sys/kernel/debug/dm-bootcache/dm-0/trace |
| * Trace of files read during boot |
| * 4. Valid - /sys/kernel/debug/dm-bootcache/dm-0/valid |
| * Returns "1" if cache is valid |
| * 5. Free - /sys/kernel/debug/dm-bootcache/dm-0/free |
| * Write "1" to this file to free all the |
| * boot cache data including traces |
| */ |
| |
| #define _XOPEN_SOURCE 600 /* Enable pread/pwrite/posix_memalign */ |
| |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdarg.h> |
| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include "dm-bootcache.h" |
| |
| typedef uint64_t u64; |
| typedef uint32_t u32; |
| |
| #define MAX_BLOCKS 128 |
| #define MAX_FILE_NAME 256 |
| |
| static struct Bootcache_hdr Header; |
| static struct { |
| struct Trace *tr; |
| int num; |
| } Trace; |
| |
| static const char Progname[] = "bootcache"; |
| |
| static char Device_file[MAX_FILE_NAME]; |
| static char Valid_file[MAX_FILE_NAME]; |
| static char Free_file[MAX_FILE_NAME]; |
| static char Header_file[MAX_FILE_NAME]; |
| static char Trace_file[MAX_FILE_NAME]; |
| |
| static u64 Header_block; |
| static u64 Trace_start; |
| static u64 Cache_start; |
| |
| #define fatal(fmt, ...) pr_fatal(__FILE__, __FUNCTION__, __LINE__, \ |
| fmt, ## __VA_ARGS__) |
| |
| /* pr_fatal: print error message and exit */ |
| static void pr_fatal( |
| const char *file, |
| const char *func, |
| int line, |
| const char *fmt, ...) |
| { |
| va_list args; |
| |
| fflush(stdout); |
| fprintf(stderr, "Fatal %s %s:%s<%d> ", Progname, file, func, line); |
| if (fmt) { |
| va_start(args, fmt); |
| vfprintf(stderr, fmt, args); |
| va_end(args); |
| |
| if (fmt[0] != '\0' && fmt[strlen(fmt)-1] == ':') { |
| fprintf(stderr, " %s<%d>", strerror(errno), errno); |
| } |
| } |
| fprintf(stderr, "\n"); |
| exit(2); /* conventional value for failed execution */ |
| } |
| |
| static void *emalloc(size_t n) |
| { |
| void *p; |
| |
| p = malloc(n); |
| if (p == NULL) { |
| fatal("malloc of %u bytes failed:", n); |
| } |
| return p; |
| } |
| |
| static int eopen(const char *file, int flags) |
| { |
| int fd; |
| |
| fd = open(file, flags); |
| if (fd == -1) { |
| fatal("open %s:", file); |
| } |
| return fd; |
| } |
| |
| static int efsync(int fd) |
| { |
| int rc; |
| |
| rc = fsync(fd); |
| if (rc == -1) { |
| fatal("fsync:"); |
| } |
| return rc; |
| } |
| |
| static int eclose(int fd) |
| { |
| int rc; |
| |
| rc = close(fd); |
| if (rc == -1) { |
| fatal("close:"); |
| } |
| return rc; |
| } |
| |
| static void *malloc_buf(size_t npages) |
| { |
| void *buf; |
| int rc; |
| |
| rc = posix_memalign(&buf, BLK_SIZE, npages * BLK_SIZE); |
| if (rc) { |
| fatal("posix_memalign rc=%d", rc); |
| } |
| return buf; |
| } |
| |
| static u64 num_blocks_in_cache(void) |
| { |
| int i; |
| u64 sum = 0; |
| |
| for (i = 0; i < Trace.num; i++) { |
| sum += Trace.tr[i].count; |
| } |
| return sum; |
| } |
| |
| static void compute_sections(void) |
| { |
| Header.num_blks_meta = BLK_ALIGN(Trace.num * sizeof(*Trace.tr)); |
| Header.num_blks_data = num_blocks_in_cache(); |
| Header_block = Header.blkno; |
| Trace_start = Header_block + 1; |
| Cache_start = Trace_start + Header.num_blks_meta; |
| } |
| |
| static void copy_trace(int dst, int src, struct Trace tr, void *buf) |
| { |
| u64 n; |
| u64 remainder; |
| u64 offset; |
| int rc; |
| |
| offset = tr.blkno << BLK_SHIFT; |
| remainder = tr.count << BLK_SHIFT; |
| n = BLK_SIZE * MAX_BLOCKS; |
| while (remainder) { |
| if (n > remainder) { |
| n = remainder; |
| } |
| rc = pread(src, buf, n, offset); |
| if (rc < 0) { |
| fatal("pread trace offset=%llu num blocks=%llu", |
| offset >> BLK_SHIFT, n >> BLK_SHIFT); |
| } |
| if (rc != n) { |
| fatal("pread read only %u bytes expected %llu", |
| rc, n); |
| } |
| rc = write(dst, buf, n); |
| if (rc < 0) { |
| fatal("write trace offset=%llu num blocks=%llu", |
| offset >> BLK_SHIFT, n >> BLK_SHIFT); |
| } |
| if (rc != n) { |
| fatal("write wrote only %u bytes expected %llu", |
| rc, n); |
| } |
| offset += n; |
| remainder -= n; |
| } |
| } |
| |
| static void copy_blocks(const char *device) |
| { |
| int i; |
| off_t rc; |
| |
| int src = open(device, O_RDONLY); |
| int dst = open(device, O_WRONLY); |
| void *buf = malloc_buf(MAX_BLOCKS); |
| rc = lseek(dst, Cache_start << BLK_SHIFT, SEEK_SET); |
| if (rc == -1) { |
| fatal("lseek for cache start:"); |
| } |
| for (i = 0; i < Trace.num; i++) { |
| copy_trace(dst, src, Trace.tr[i], buf); |
| } |
| free(buf); |
| efsync(dst); |
| eclose(dst); |
| eclose(src); |
| } |
| |
| static void dump_trace(struct Trace *tr, int num_recs) |
| { |
| int i; |
| |
| for (i = 0; i < num_recs; i++, tr++) { |
| printf("%llu %llu %llu\n", tr->blkno, tr->count, tr->ino); |
| } |
| } |
| |
| /* |
| * Because we are reading a pseudo file, we scan it to |
| * see how big it is. |
| */ |
| static u64 num_traces(const char *file) |
| { |
| struct Trace trace[1024]; |
| ssize_t rc; |
| u64 sum = 0; |
| |
| int fd = eopen(file, O_RDONLY); |
| for (;;) { |
| rc = read(fd, trace, sizeof(trace)); |
| if (rc == -1) |
| fatal("read %s:", file); |
| if (rc == 0) |
| break; |
| sum += rc; |
| } |
| eclose(fd); |
| return sum / sizeof(struct Trace); |
| } |
| |
| static void read_trace(const char *file) |
| { |
| u64 n = num_traces(file); |
| ssize_t rc; |
| int fd; |
| |
| Trace.tr = emalloc(n * sizeof(struct Trace)); |
| Trace.num = n; |
| fd = eopen(file, O_RDONLY); |
| rc = read(fd, Trace.tr, n * sizeof(struct Trace)); |
| if (rc == -1) { |
| fatal("read %s:", file); |
| } |
| dump_trace(Trace.tr, n); |
| eclose(fd); |
| } |
| |
| static void read_header(const char *file) |
| { |
| int fd; |
| int rc; |
| |
| fd = eopen(file, O_RDONLY); |
| rc = read(fd, &Header, sizeof(Header)); |
| if (rc == -1) { |
| fatal("read %s:", file); |
| } |
| eclose(fd); |
| if (Header.magic != BOOTCACHE_MAGIC) { |
| fatal("Bad magic %u != %u", Header.magic, BOOTCACHE_MAGIC); |
| } |
| if (Header.version != BOOTCACHE_VERSION) { |
| fatal("Bad version %u != %u", Header.version, BOOTCACHE_VERSION); |
| } |
| } |
| |
| /* |
| * The header is written last after everything else, cache data and traces, |
| * have been written to the disk. The header is what tells the boot cache |
| * on the next boot that the cache is valid and should be used. |
| * For correctness, we don't have to flush the header but the default |
| * flush time is 10 minutes and there is no reason to wait. |
| */ |
| static void write_header(const char *file) |
| { |
| int fd; |
| int rc; |
| |
| fd = eopen(file, O_WRONLY); |
| rc = pwrite(fd, &Header, sizeof(Header), Header_block << BLK_SHIFT); |
| if (rc != sizeof(Header)) { |
| fatal("pwrite %s rc=%d:", file, rc); |
| } |
| efsync(fd); |
| eclose(fd); |
| } |
| |
| static void write_trace(const char *file) |
| { |
| int fd; |
| ssize_t rc; |
| ssize_t size = Trace.num * sizeof(*Trace.tr); |
| |
| fd = eopen(file, O_WRONLY); |
| rc = pwrite(fd, Trace.tr, size, Trace_start); |
| if (rc != size) { |
| fatal("pwrite %s rc=%ld size=%ld:", file, rc, size); |
| } |
| efsync(fd); |
| eclose(fd); |
| } |
| |
| /* |
| * Writing '1' to the free file indicates to |
| * the bootcache that it can free all of its |
| * resources. |
| */ |
| void free_bootcache(const char *file) |
| { |
| char buf[] = "1"; |
| int fd; |
| int rc; |
| |
| fd = eopen(file, O_WRONLY); |
| rc = write(fd, buf, 1); |
| if (rc == -1) { |
| fatal("write %s:", file); |
| } |
| eclose(fd); |
| } |
| |
| /* |
| * A '1' in the first byte of the valid file, indicates, the |
| * cache is valid. Otherwise is should be '0'; |
| */ |
| static bool is_valid(const char *file) |
| { |
| char buf[1]; |
| int fd; |
| int rc; |
| |
| fd = eopen(file, O_RDONLY); |
| rc = read(fd, buf, sizeof(buf)); |
| eclose(fd); |
| if ((rc == -1) || (rc == 0)) { |
| fatal("read %s:", file); |
| } |
| return buf[0] == '1'; |
| } |
| |
| static void gen_file_name(char *file_name, int size, |
| const char *fmt, const char *name) |
| { |
| int rc; |
| |
| rc = snprintf(file_name, size, fmt, name); |
| if (rc >= size) { |
| fatal("Name too long %s", name); |
| } |
| } |
| |
| static void gen_file_names(const char *name) |
| { |
| gen_file_name(Device_file, sizeof(Device_file), "/dev/%s", name); |
| gen_file_name(Valid_file, sizeof(Valid_file), |
| "/sys/kernel/debug/dm-bootcache/%s/valid", name); |
| gen_file_name(Free_file, sizeof(Free_file), |
| "/sys/kernel/debug/dm-bootcache/%s/free", name); |
| gen_file_name(Header_file, sizeof(Header_file), |
| "/sys/kernel/debug/dm-bootcache/%s/header", name); |
| gen_file_name(Trace_file, sizeof(Trace_file), |
| "/sys/kernel/debug/dm-bootcache/%s/trace", name); |
| } |
| |
| static void test_file_names(const char *name) |
| { |
| gen_file_name(Device_file, sizeof(Device_file), |
| "/tmp/%s/dev", name); |
| gen_file_name(Valid_file, sizeof(Valid_file), |
| "/tmp/%s/valid", name); |
| gen_file_name(Free_file, sizeof(Free_file), |
| "/tmp/%s/free", name); |
| gen_file_name(Header_file, sizeof(Header_file), |
| "/tmp/%s/header", name); |
| gen_file_name(Trace_file, sizeof(Trace_file), |
| "/tmp/%s/trace", name); |
| } |
| |
| static void usage(void) |
| { |
| fprintf(stderr, "Usage: %s [-t] <name>\n" |
| " e.g %s dm-0\n", |
| Progname, Progname); |
| exit(2); |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| bool test = false; |
| char *name = NULL; |
| |
| for (;;) { |
| int c; |
| |
| c = getopt(argc, argv, "t?"); |
| if (c == -1) |
| break; |
| switch (c) { |
| case 't': |
| test = true; |
| break; |
| case '?': |
| default: |
| usage(); |
| break; |
| } |
| } |
| if (optind >= argc) { |
| usage(); |
| } |
| name = argv[optind]; |
| if (test) { |
| test_file_names(name); |
| } else { |
| gen_file_names(name); |
| } |
| if (is_valid(Valid_file)) { |
| /* Because the boot cache is valid, the block |
| * traces are not kept so the boot cache can't |
| * be rebuilt. To force a rebuild of the cache, |
| * zero the header. |
| */ |
| return 0; |
| } |
| read_header(Header_file); |
| read_trace(Trace_file); |
| compute_sections(); |
| copy_blocks(Device_file); |
| write_trace(Device_file); |
| write_header(Device_file); |
| free_bootcache(Free_file); |
| return 0; |
| } |