diff --git a/userland/com.hakios.varstore.plist b/userland/com.hakios.varstore.plist index ac4f392cf..6b5b4d502 100644 --- a/userland/com.hakios.varstore.plist +++ b/userland/com.hakios.varstore.plist @@ -15,6 +15,10 @@ RunAtLoad + + ExitTimeOut + 60 StandardOutPath /dev/console StandardErrorPath diff --git a/userland/src/var_store.c b/userland/src/var_store.c index 50f55c442..0f43a8d92 100644 --- a/userland/src/var_store.c +++ b/userland/src/var_store.c @@ -20,6 +20,13 @@ * var_store stamp PATH write a line into PATH saying when: a marker for a run * var_store bootok tell the loader's boot guard that this generation came up * + * Under the Android Virtualization Framework (crosvm on the Pixel) there is no flash partition of + * ours: the store is then a second virtio disk, /dev/rrvblk1 (a writable disk image of the Scion + * app, disk.1 in its avf.properties; the kernel needs virtio_blk_rw=1), the whole of it, its two + * halves the two slots. Nor is there a way back to a loader to ask for the save at the end: there + * `watch` saves every var_store_every=SECS seconds (boot-arg) and when launchd stops it on the way + * to a shutdown or a restart (SIGTERM). + * * Nobody has to remember to save. `watch` registers with the kernel (kern.hakios.var_save), and * on the orderly way back to the loader -- the run's time up, or "xl stop" -- the kernel asks it * for the volume and waits, bounded, before it goes (bsd/dev/hakios.ufs.c, @@ -58,6 +65,9 @@ #define SLOT_BYTES 0x80000000LL /* 2 GiB a slot, two slots */ #define SLOTS 2 #define DEVICE "/dev/rufs_darwin" +#define VIRTIO_DEVICE "/dev/rrvblk1" /* AVF: the second virtio disk, all of it */ +#define DKIOCGETBLOCKSIZE 0x40046418 +#define DKIOCGETBLOCKCOUNT 0x40086419 #define STATE_AT 8566865920LL /* darwin's block 2091520: the boot state (common/flash/bundlefmt.py) */ #define BOOTOK_AFTER 60 /* seconds of a run that make it a good start */ #define ROOT "/private/var" @@ -229,6 +239,119 @@ head_checksum(const struct head *h) /* ---- the store, as a stream of bytes in one slot ---- */ static int fd = -1; + +/* + * SIGTERM, which launchd sends every job on the way to a shutdown or a restart: noted here, and the + * watch loop saves once more and ends. libmini has no signals, so this is the whole of it: the + * kernel enters the trampoline with x0 = the handler, x1 = infostyle, x2 = the signal, x4 = the + * context and x5 = the token sigreturn wants back (bsd/dev/arm/unix_signal.c, sendsig). + */ +static volatile int terminated; + +static void +on_term(int sig) +{ + (void)sig; + terminated = 1; +} + +void var_store_sigtramp(void); +__asm__( + " .text\n" + " .align 2\n" + "_var_store_sigtramp:\n" + " mov x19, x4\n" + " mov x20, x1\n" + " mov x21, x5\n" + " mov x9, x0\n" + " mov x0, x2\n" + " blr x9\n" + " mov x0, x19\n" + " mov x1, x20\n" + " mov x2, x21\n" + " mov x16, #184\n" /* SYS_sigreturn */ + " svc #0x80\n" + " brk #1\n"); + +static void +catch_term(void) +{ + struct { + void (*handler)(int); + void (*tramp)(void); + uint32_t mask; + int flags; + } act = { on_term, var_store_sigtramp, 0, 0 }; /* no SA_RESTART: a sleep is cut short */ + + (void)mini_syscall(46 /* SYS_sigaction */, 15 /* SIGTERM */, (long)&act, 0, 0, 0, 0); +} + +/* A number among the kernel's boot-args, or 0. */ +static uint64_t +boot_arg_number(const char *name) +{ + static char args[1024]; + size_t len = sizeof(args) - 1, n = strlen(name); + + if (sysctlbyname("kern.bootargs", args, &len, NULL, 0) != 0) { + return 0; + } + args[len < sizeof(args) ? len : sizeof(args) - 1] = 0; + for (char *p = args; *p;) { + while (*p == ' ') { + p++; + } + if (strncmp(p, name, n) == 0 && p[n] == '=') { + uint64_t v = 0; + for (p += n + 1; *p >= '0' && *p <= '9'; p++) { + v = v * 10 + (uint64_t)(*p - '0'); + } + return v; + } + while (*p && *p != ' ') { + p++; + } + } + return 0; +} +/* where the store is: the flash partition, or under AVF a virtio disk (pick_device) */ +static const char *device = DEVICE; +static off_t store_at = STORE_AT; +static off_t slot_bytes = SLOT_BYTES; + +/* + * The phone's partition when there is one; otherwise the second virtio disk, split in two slots. + * A disk too small to hold anything worth keeping is not taken. + */ +static void +pick_device(void) +{ + uint64_t count = 0; + uint32_t size = 0; + int t = open(device, O_RDONLY, 0); + + if (t >= 0) { + close(t); + return; + } + t = open(VIRTIO_DEVICE, O_RDONLY, 0); + if (t < 0) { + return; + } + if (mini_syscall(SYS_ioctl, t, DKIOCGETBLOCKCOUNT, (long)&count, 0, 0, 0) < 0 || + mini_syscall(SYS_ioctl, t, DKIOCGETBLOCKSIZE, (long)&size, 0, 0, 0) < 0) { + count = 0; + } + close(t); + if (count * size < (128ull << 20)) { + printf("var_store: %s holds %llu bytes: too small for a store\n", VIRTIO_DEVICE, + (unsigned long long)(count * size)); + return; + } + device = VIRTIO_DEVICE; + store_at = 0; + slot_bytes = (off_t)(count * size / 2) & ~(off_t)(BUFFER - 1); +} static uint8_t *buffer; static uint8_t *already; /* what the slot holds there now, to not write it again */ static uint64_t skipped_blocks, written_blocks; @@ -244,7 +367,7 @@ static uint64_t entries; static off_t slot_at(int slot) { - return STORE_AT + (off_t)slot * SLOT_BYTES; + return store_at + (off_t)slot * slot_bytes; } static int @@ -435,7 +558,7 @@ static void stream_start(int slot, uint64_t bytes) { at = slot_at(slot) + BLOCK; - stream_limit = slot_at(slot) + SLOT_BYTES; + stream_limit = slot_at(slot) + slot_bytes; buffered = taken = 0; stream_bytes = 0; stream_left = bytes; @@ -455,7 +578,7 @@ read_head(int slot, struct head *into) return 0; } if (memcmp(h->magic, HEAD_MAGIC, sizeof(h->magic)) != 0 || h->version != HEAD_VERSION || - h->slot != (uint32_t)slot || h->bytes > (uint64_t)SLOT_BYTES - BLOCK || + h->slot != (uint32_t)slot || h->bytes > (uint64_t)slot_bytes - BLOCK || h->head_crc != head_checksum(h)) { return 0; } @@ -869,9 +992,9 @@ save(void) } h.crc = stream_crc ^ 0xffffffffu; h.head_crc = head_checksum(&h); - if (h.bytes > (uint64_t)SLOT_BYTES - BLOCK) { + if (h.bytes > (uint64_t)slot_bytes - BLOCK) { printf("var_store: the archive is %llu bytes, more than a slot holds (%llu): nothing saved\n", - (unsigned long long)h.bytes, (unsigned long long)(SLOT_BYTES - BLOCK)); + (unsigned long long)h.bytes, (unsigned long long)(slot_bytes - BLOCK)); return 0; } memset(block, 0, sizeof(block)); @@ -1257,9 +1380,9 @@ save_once(void) * device is closed (bsd/dev/hakios.ufs.c, ufs_cclose). A watcher that kept the device open * would leave iOS reading the flash through the loader for the rest of the run. */ - fd = open(DEVICE, O_RDWR, 0); + fd = open(device, O_RDWR, 0); if (fd < 0) { - printf("var_store: %s: %s\n", DEVICE, strerror(errno)); + printf("var_store: %s: %s\n", device, strerror(errno)); return 0; } ok = claim() && save(); @@ -1340,9 +1463,14 @@ watch(int seconds) uint64_t last = 0; int asked = 0; - fd = open(DEVICE, O_RDWR, 0); + if (seconds <= 0) { + seconds = (int)boot_arg_number("var_store_every"); + } + catch_term(); + + fd = open(device, O_RDWR, 0); if (fd < 0) { - printf("var_store: %s cannot be written (%s): nothing would be saved\n", DEVICE, + printf("var_store: %s cannot be written (%s): nothing would be saved\n", device, strerror(errno)); return 0; } @@ -1373,6 +1501,12 @@ watch(int seconds) if (sysctl_int("kern.hakios.var_save", &state) == 0 && state == VAR_SAVE_WANTED) { asked = 1; } + if (terminated) { + printf("var_store: stopped (SIGTERM): saving the volume once more\n"); + int ok = save_once(); + printf("var_store: %s\n", ok ? "saved" : "this save came to nothing; the store keeps what it had"); + return ok; + } gettimeofday(&tv, NULL); if (asked || (seconds > 0 && (uint64_t)tv.tv_sec - last >= (uint64_t)seconds)) { int ok = save_once(); @@ -1458,6 +1592,7 @@ main(int argc, char **argv, char **envp) if (already == MAP_FAILED) { already = NULL; } + pick_device(); if (strcmp(what, "watch") == 0) { if (!boot_arg_is("var_store", "1")) { printf("var_store: not asked to keep the data volume (var_store=1): nothing to watch\n"); @@ -1465,9 +1600,9 @@ main(int argc, char **argv, char **envp) } return watch(argc > 2 ? (int)parse_number(argv[2]) : 0) ? 0 : 1; } - fd = open(DEVICE, strcmp(what, "load") == 0 || strcmp(what, "show") == 0 ? O_RDONLY : O_RDWR, 0); + fd = open(device, strcmp(what, "load") == 0 || strcmp(what, "show") == 0 ? O_RDONLY : O_RDWR, 0); if (fd < 0) { - printf("var_store: %s: %s (the kernel takes writes with ufs_write=1)\n", DEVICE, + printf("var_store: %s: %s (the kernel takes writes with ufs_write=1, or virtio_blk_rw=1)\n", device, strerror(errno)); return 1; }