blob: 515bde2c0f40cf3ce4f45cffd17230d642444ac9 [file] [log] [blame]
/* 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;
}