Example mirage-eio unikernels
0

Configure Feed

Select the types of activity you want to include in your feed.

unikernels/storage: split the FAT and tar examples

Replace the combined files demo with one read-only tar asset example and one writable FAT persistence example. Each has its own package, cram transcript, generated backend stanzas, and GIF recorded from that transcript.

+459 -214
+4 -2
README.md
··· 114 114 stored on notafs block devices. 115 115 - [`storage/kv`](bin/storage/kv) counts its boots twice, in a notafs store on 116 116 a block device and in an in-memory one, and lets the two answers diverge. 117 - - [`storage/files`](bin/storage/files) reads its assets from a read-only tar 118 - bundle and writes its output to a read-write FAT volume. 117 + - [`storage/tar`](bin/storage/tar) reads host-built assets from a read-only tar 118 + archive attached as a block device. 119 + - [`storage/fat`](bin/storage/fat) formats a blank FAT volume, writes a file, 120 + and proves on the second boot that it persisted. 119 121 - [`storage/sqlite`](bin/storage/sqlite) stores a boot counter in SQLite on a block 120 122 device. 121 123 - [`storage/sql-shell`](bin/storage/sql-shell) serves an interactive SQL shell over
+18 -5
bin/gen/dune
··· 598 598 (diff etcd.dune etcd.dune.gen))) 599 599 600 600 (rule 601 - (target files.dune.gen) 602 - (deps ../storage/files/dune) 601 + (target fat.dune.gen) 602 + (deps ../storage/fat/dune) 603 603 (action 604 604 (with-stdout-to 605 - files.dune.gen 606 - (run %{exe:../../../mirage/bin/main.exe} dune ../storage/files)))) 605 + fat.dune.gen 606 + (run %{exe:../../../mirage/bin/main.exe} dune ../storage/fat)))) 607 607 608 608 (rule 609 609 (alias runtest) 610 610 (action 611 - (diff files.dune files.dune.gen))) 611 + (diff fat.dune fat.dune.gen))) 612 612 613 613 (rule 614 614 (target git-client.dune.gen) ··· 674 674 (alias runtest) 675 675 (action 676 676 (diff sqlite.dune sqlite.dune.gen))) 677 + 678 + (rule 679 + (target tar.dune.gen) 680 + (deps ../storage/tar/dune) 681 + (action 682 + (with-stdout-to 683 + tar.dune.gen 684 + (run %{exe:../../../mirage/bin/main.exe} dune ../storage/tar)))) 685 + 686 + (rule 687 + (alias runtest) 688 + (action 689 + (diff tar.dune tar.dune.gen)))
+218
bin/gen/fat.dune
··· 1 + (executable 2 + (name unikernel) 3 + (enabled_if 4 + (= %{context_name} default.solo5)) 5 + (modules :standard) 6 + (libraries eio nox-mirage.eio nox-fat fmt nox-mirage.eio.solo5) 7 + (ocamlopt_flags 8 + (:standard \ (:include solo5-release-debug.sexp))) 9 + (link_flags 10 + ((:include solo5-release-flags.sexp) -cclib -z -cclib solo5-abi=hvt)) 11 + (foreign_stubs 12 + (language c) 13 + (names startup manifest))) 14 + 15 + (rule 16 + (target solo5-release-flags.sexp) 17 + (enabled_if 18 + (= %{profile} release)) 19 + (action 20 + (with-stdout-to solo5-release-flags.sexp 21 + (echo "(-use-lto -cclib -Wl,--gc-sections)")))) 22 + 23 + (rule 24 + (target solo5-release-flags.sexp) 25 + (enabled_if 26 + (<> %{profile} release)) 27 + (action 28 + (with-stdout-to solo5-release-flags.sexp 29 + (echo "()")))) 30 + 31 + (rule 32 + (target solo5-release-debug.sexp) 33 + (enabled_if 34 + (= %{profile} release)) 35 + (action 36 + (with-stdout-to solo5-release-debug.sexp 37 + (echo "(-g)")))) 38 + 39 + (rule 40 + (target solo5-release-debug.sexp) 41 + (enabled_if 42 + (<> %{profile} release)) 43 + (action 44 + (with-stdout-to solo5-release-debug.sexp 45 + (echo "()")))) 46 + 47 + (rule 48 + (target startup.c) 49 + (enabled_if 50 + (= %{context_name} default.solo5)) 51 + (action 52 + (with-stdout-to startup.c 53 + (run %{bin:solo5} startup)))) 54 + 55 + (rule 56 + (target manifest.json) 57 + (enabled_if 58 + (= %{context_name} default.solo5)) 59 + (action 60 + (with-stdout-to manifest.json 61 + (run %{exe:unikernel.exe} --manifest)))) 62 + 63 + (rule 64 + (target manifest.c) 65 + (enabled_if 66 + (= %{context_name} default.solo5)) 67 + (deps manifest.json) 68 + (action 69 + (run %{bin:solo5} manifest manifest.json manifest.c))) 70 + 71 + (rule 72 + (target unikernel.hvt) 73 + (enabled_if 74 + (= %{context_name} default.solo5)) 75 + (deps unikernel.exe) 76 + (action 77 + (run %{bin:aarch64-solo5-ocaml-strip} -o unikernel.hvt unikernel.exe))) 78 + (executable 79 + (name unikernel) 80 + (enabled_if 81 + (= %{context_name} default.unikraft)) 82 + (modules :standard) 83 + (libraries eio nox-mirage.eio nox-fat fmt nox-mirage.eio.unikraft) 84 + (ocamlopt_flags 85 + (:standard \ (:include unikraft-release-debug.sexp))) 86 + (link_flags 87 + ((:include unikraft-release-flags.sexp) -cclib -z -cclib 88 + unikraft-backend=qemu)) 89 + (foreign_stubs 90 + (language c) 91 + (flags :standard -z unikraft-backend=qemu) 92 + (names startup))) 93 + 94 + (rule 95 + (target unikraft-release-flags.sexp) 96 + (enabled_if 97 + (= %{profile} release)) 98 + (action 99 + (with-stdout-to unikraft-release-flags.sexp 100 + (echo "(-use-lto -cclib -Wl,--gc-sections)")))) 101 + 102 + (rule 103 + (target unikraft-release-flags.sexp) 104 + (enabled_if 105 + (<> %{profile} release)) 106 + (action 107 + (with-stdout-to unikraft-release-flags.sexp 108 + (echo "()")))) 109 + 110 + (rule 111 + (target unikraft-release-debug.sexp) 112 + (enabled_if 113 + (= %{profile} release)) 114 + (action 115 + (with-stdout-to unikraft-release-debug.sexp 116 + (echo "(-g)")))) 117 + 118 + (rule 119 + (target unikraft-release-debug.sexp) 120 + (enabled_if 121 + (<> %{profile} release)) 122 + (action 123 + (with-stdout-to unikraft-release-debug.sexp 124 + (echo "()")))) 125 + 126 + (rule 127 + (target startup.c) 128 + (enabled_if 129 + (= %{context_name} default.unikraft)) 130 + (action 131 + (with-stdout-to startup.c 132 + (echo 133 + "/* Generated by mrg, do not edit. */\n#include <caml/callback.h>\nint main(int argc, char **argv) {\n (void)argc;\n caml_startup(argv);\n return 0;\n}\n")))) 134 + 135 + (rule 136 + (target unikernel.qemu) 137 + (enabled_if 138 + (= %{context_name} default.unikraft)) 139 + (action 140 + (copy unikernel.exe unikernel.qemu))) 141 + (subdir 142 + wasi 143 + (copy_files# 144 + (files ../*.ml)) 145 + (copy_files# 146 + (files ../*.mli)) 147 + (executable 148 + (name unikernel) 149 + (enabled_if 150 + (= %{context_name} default)) 151 + (modules :standard) 152 + (modes wasm) 153 + (libraries eio nox-mirage.eio nox-fat fmt nox-mirage.eio.wasi hash.ocaml) 154 + (wasm_of_ocaml 155 + (flags 156 + (:standard --enable=wasi --effects=cps --opt 3))))) 157 + (executable 158 + (name unikernel) 159 + (enabled_if 160 + (= %{context_name} default.xen)) 161 + (modules :standard) 162 + (libraries eio nox-mirage.eio nox-fat fmt nox-mirage.eio.xen-runtime) 163 + (ocamlopt_flags 164 + (:standard \ (:include xen-release-debug.sexp))) 165 + (link_flags 166 + ((:include xen-release-flags.sexp) -cclib -z -cclib solo5-abi=xen)) 167 + (foreign_stubs 168 + (language c) 169 + (names xstartup))) 170 + 171 + (rule 172 + (target xen-release-flags.sexp) 173 + (enabled_if 174 + (= %{profile} release)) 175 + (action 176 + (with-stdout-to xen-release-flags.sexp 177 + (echo "(-use-lto -cclib -Wl,--gc-sections)")))) 178 + 179 + (rule 180 + (target xen-release-flags.sexp) 181 + (enabled_if 182 + (<> %{profile} release)) 183 + (action 184 + (with-stdout-to xen-release-flags.sexp 185 + (echo "()")))) 186 + 187 + (rule 188 + (target xen-release-debug.sexp) 189 + (enabled_if 190 + (= %{profile} release)) 191 + (action 192 + (with-stdout-to xen-release-debug.sexp 193 + (echo "(-g)")))) 194 + 195 + (rule 196 + (target xen-release-debug.sexp) 197 + (enabled_if 198 + (<> %{profile} release)) 199 + (action 200 + (with-stdout-to xen-release-debug.sexp 201 + (echo "()")))) 202 + 203 + (rule 204 + (target xstartup.c) 205 + (enabled_if 206 + (= %{context_name} default.xen)) 207 + (action 208 + (with-stdout-to xstartup.c 209 + (echo 210 + "/* Generated by mrg, do not edit. */\n#include <solo5.h>\n#define CAML_NAME_SPACE\n#include <caml/callback.h>\nstatic char *unused_argv[] = { \"mirage\", 0 };\nvoid _nolibc_init(uintptr_t heap_start, size_t heap_size);\nint solo5_app_main(const struct solo5_start_info *si) {\n _nolibc_init(si->heap_start, si->heap_size);\n caml_startup(unused_argv);\n return 0;\n}\n")))) 211 + 212 + (rule 213 + (target unikernel.xen) 214 + (enabled_if 215 + (= %{context_name} default.xen)) 216 + (deps unikernel.exe) 217 + (action 218 + (run %{bin:aarch64-solo5-ocaml-strip} -o unikernel.xen unikernel.exe)))
+4 -4
bin/gen/files.dune bin/gen/tar.dune
··· 3 3 (enabled_if 4 4 (= %{context_name} default.solo5)) 5 5 (modules :standard) 6 - (libraries eio nox-mirage.eio nox-tar-eio.kv nox-fat fmt nox-mirage.eio.solo5) 6 + (libraries eio nox-mirage.eio nox-tar-eio.kv fmt nox-mirage.eio.solo5) 7 7 (ocamlopt_flags 8 8 (:standard \ (:include solo5-release-debug.sexp))) 9 9 (link_flags ··· 80 80 (enabled_if 81 81 (= %{context_name} default.unikraft)) 82 82 (modules :standard) 83 - (libraries eio nox-mirage.eio nox-tar-eio.kv nox-fat fmt nox-mirage.eio.unikraft) 83 + (libraries eio nox-mirage.eio nox-tar-eio.kv fmt nox-mirage.eio.unikraft) 84 84 (ocamlopt_flags 85 85 (:standard \ (:include unikraft-release-debug.sexp))) 86 86 (link_flags ··· 150 150 (= %{context_name} default)) 151 151 (modules :standard) 152 152 (modes wasm) 153 - (libraries eio nox-mirage.eio nox-tar-eio.kv nox-fat fmt nox-mirage.eio.wasi hash.ocaml) 153 + (libraries eio nox-mirage.eio nox-tar-eio.kv fmt nox-mirage.eio.wasi hash.ocaml) 154 154 (wasm_of_ocaml 155 155 (flags 156 156 (:standard --enable=wasi --effects=cps --opt 3))))) ··· 159 159 (enabled_if 160 160 (= %{context_name} default.xen)) 161 161 (modules :standard) 162 - (libraries eio nox-mirage.eio nox-tar-eio.kv nox-fat fmt nox-mirage.eio.xen-runtime) 162 + (libraries eio nox-mirage.eio nox-tar-eio.kv fmt nox-mirage.eio.xen-runtime) 163 163 (ocamlopt_flags 164 164 (:standard \ (:include xen-release-debug.sexp))) 165 165 (link_flags
+23
bin/storage/fat/README.md
··· 1 + # FAT 2 + 3 + ![FAT unikernel demo](fat.gif) 4 + 5 + This example mounts a writable FAT volume from a block device. A fresh blank 6 + device is formatted on first boot; later boots mount the same volume and see 7 + the file left by the previous run. A corrupt or foreign volume is never 8 + silently reformatted. 9 + 10 + ```sh 11 + dd if=/dev/zero of=volume bs=1m count=8 12 + dune exec -- unikernel-fat 13 + dune exec -- unikernel-fat 14 + ``` 15 + 16 + Under Solo5, attach the same file as the `volume` block device: 17 + 18 + ```sh 19 + mrg run --disk volume:volume unikernels/bin/storage/fat 20 + ``` 21 + 22 + The example declares `Mirage_eio.Fat.fat (block "volume")`, a writable FAT 23 + filesystem with one block-device manifest entry.
+35
bin/storage/fat/dune
··· 1 + (executable 2 + (name unikernel) 3 + (public_name unikernel-fat) 4 + (package unikernel-fat) 5 + (enabled_if 6 + (= %{context_name} default)) 7 + (modules :standard) 8 + (libraries eio nox-mirage.eio nox-mirage.eio.native nox-fat fmt)) 9 + 10 + (cram 11 + (deps unikernel.exe) 12 + (setup_scripts setup.sh)) 13 + 14 + (rule 15 + (alias regen-gifs) 16 + (target fat.gif) 17 + (mode promote) 18 + (enabled_if 19 + (= %{env:REGEN=0} 1)) 20 + (deps unikernel.exe fat.t setup.sh) 21 + (action 22 + (chdir 23 + %{workspace_root} 24 + (setenv 25 + UNIKERNEL_EXE 26 + %{dep:unikernel.exe} 27 + (run 28 + %{exe:../../../../ocaml-vhs/bin/vhs_cram.exe} 29 + --output 30 + %{target} 31 + --setup 32 + %{dep:setup.sh} 33 + %{dep:fat.t}))))) 34 + 35 + (include ../../gen/fat.dune)
bin/storage/fat/fat.gif

This is a binary file and will not be displayed.

+17
bin/storage/fat/fat.t
··· 1 + A blank block device is formatted as FAT, then written and read through the 2 + guest filesystem: 3 + 4 + $ unikernel.exe 5 + fat: volume is empty -- first boot 6 + fat: wrote HELLO.TXT and read back "hello from FAT" 7 + 8 + The second boot mounts the existing filesystem and sees the previous file: 9 + 10 + $ unikernel.exe 11 + fat: volume already held HELLO.TXT: "hello from FAT" 12 + fat: wrote HELLO.TXT and read back "hello from FAT" 13 + 14 + FAT needs exactly one block device: 15 + 16 + $ unikernel.exe --manifest | grep -c BLOCK_BASIC 17 + 1
+9
bin/storage/fat/setup.sh
··· 1 + if [ -n "${UNIKERNEL_EXE:-}" ]; then 2 + PATH="$(cd "$(dirname "$UNIKERNEL_EXE")" && pwd):$PATH" 3 + fi 4 + export PATH="$PWD:$PATH" 5 + 6 + work=$(mktemp -d "${TMPDIR:-/tmp}/fat-demo.XXXXXX") 7 + trap 'rm -rf "$work"' EXIT 8 + cd "$work" 9 + dd if=/dev/zero of=volume bs=1m count=8 >/dev/null 2>&1
+31
bin/storage/fat/unikernel.ml
··· 1 + let path = "HELLO.TXT" 2 + let contents = "hello from FAT\n" 3 + 4 + let read fs path = 5 + match Fat.Fs.size fs path with 6 + | Error e -> Fmt.failwith "fat: size %s: %a" path Fat.Fs.pp_error e 7 + | Ok len -> ( 8 + match Fat.Fs.read fs path 0 (Int64.to_int len) with 9 + | Ok data -> data 10 + | Error e -> Fmt.failwith "fat: read %s: %a" path Fat.Fs.pp_error e) 11 + 12 + let write fs path data = 13 + let check operation = function 14 + | Ok () -> () 15 + | Error e -> 16 + Fmt.failwith "fat: %s %s: %a" operation path Fat.Fs.pp_write_error e 17 + in 18 + (match Fat.Fs.size fs path with 19 + | Ok _ -> () 20 + | Error _ -> check "create" (Fat.Fs.file fs path)); 21 + check "write" (Fat.Fs.write fs path 0 data) 22 + 23 + let () = 24 + Mirage_eio.(run [ Fat.fat (block "volume") ]) @@ fun _env fs -> 25 + (match Fat.Fs.size fs path with 26 + | Ok _ -> 27 + Fmt.pr "fat: volume already held %s: %S@." path 28 + (String.trim (read fs path)) 29 + | Error _ -> Fmt.pr "fat: volume is empty -- first boot@."); 30 + write fs path contents; 31 + Fmt.pr "fat: wrote %s and read back %S@." path (String.trim (read fs path))
-53
bin/storage/files/README.md
··· 1 - # files 2 - 3 - ![files unikernel demo](files.gif) 4 - 5 - Two ways to put a filesystem in front of a unikernel. 6 - 7 - `Mirage_eio.Tar.kv_ro` indexes a tar archive on a block device: the archive is 8 - built on the host, attached as a disk, and read by key. Nothing is written back 9 - -- a tar has no free list and no way to rewrite an entry in place -- which is 10 - exactly why it is the right shape for shipping assets *into* a guest. It is the 11 - unikernel's read-only bundle. 12 - 13 - `Mirage_eio.Fat.fat` mounts a FAT filesystem on another block device, 14 - read-write, so the guest can create and rewrite files that a host tool later 15 - reads back off the image. A blank disk is formatted rather than refused, because 16 - a guest has no mkfs -- but only a *blank* one: a device holding something that 17 - will not mount is a corrupt or foreign volume, and reformatting it would destroy 18 - it. 19 - 20 - So the guest reads its asset out of the tar, transforms it, and writes the result 21 - to FAT: the read-only bundle in, the durable output out. 22 - 23 - ## Running it 24 - 25 - ```sh 26 - printf 'hello from a tar\n' >greeting.txt 27 - tar cf assets greeting.txt 28 - dd if=/dev/zero of=output bs=1m count=8 29 - dune exec unikernels/bin/storage/files/unikernel.exe 30 - ``` 31 - 32 - ``` 33 - files: tar holds greeting.txt 34 - files: read "hello from a tar" from the tar (17 bytes) 35 - files: FAT is empty -- this is its first boot 36 - files: wrote SHOUTED.TXT to FAT, read back "HELLO FROM A TAR" 37 - ``` 38 - 39 - Boot it again and the FAT volume still holds what the last boot wrote -- which is 40 - what proves it was formatted only because it was blank. 41 - 42 - Under a tender both disks are attached by the tender: 43 - 44 - ```sh 45 - mrg build -t solo5 unikernels/bin/storage/files 46 - mrg run --disk assets:assets --disk output:output \ 47 - unikernels/bin/storage/files 48 - ``` 49 - 50 - ## Devices 51 - 52 - - `Tar.kv_ro "assets"` -- the read-only tar bundle. 53 - - `Fat.fat "output"` -- the read-write FAT volume.
+7 -13
bin/storage/files/dune bin/storage/tar/dune
··· 1 1 (executable 2 2 (name unikernel) 3 - (public_name unikernel-files) 4 - (package unikernel-files) 3 + (public_name unikernel-tar) 4 + (package unikernel-tar) 5 5 (enabled_if 6 6 (= %{context_name} default)) 7 7 (modules :standard) 8 - (libraries 9 - eio 10 - nox-mirage.eio 11 - nox-mirage.eio.native 12 - nox-tar-eio.kv 13 - nox-fat 14 - fmt)) 8 + (libraries eio nox-mirage.eio nox-mirage.eio.native nox-tar-eio.kv fmt)) 15 9 16 10 (cram 17 11 (deps unikernel.exe) ··· 19 13 20 14 (rule 21 15 (alias regen-gifs) 22 - (targets files.gif) 16 + (target tar.gif) 23 17 (mode promote) 24 18 (enabled_if 25 19 (= %{env:REGEN=0} 1)) 26 - (deps unikernel.exe files.t setup.sh) 20 + (deps unikernel.exe tar.t setup.sh) 27 21 (action 28 22 (chdir 29 23 %{workspace_root} ··· 36 30 %{target} 37 31 --setup 38 32 %{dep:setup.sh} 39 - %{dep:files.t}))))) 33 + %{dep:tar.t}))))) 40 34 41 - (include ../../gen/files.dune) 35 + (include ../../gen/tar.dune)
bin/storage/files/files.gif

This is a binary file and will not be displayed.

-35
bin/storage/files/files.t
··· 1 - Boot the files unikernel: a read-only tar bundle in, a read-write FAT volume out. 2 - 3 - setup.sh builds the tar the guest reads -- the way a host ships assets *into* a 4 - unikernel -- and a blank disk for it to write to. Both are files named after 5 - their devices. 6 - 7 - The tar is indexed by key, and the guest reads its asset out of it. A tar has no 8 - free list and no way to rewrite an entry in place, which is why it is read-only: 9 - it is a bundle, not a filesystem. 10 - 11 - The FAT volume is blank, so it is formatted rather than refused -- a unikernel 12 - has no mkfs. The guest transforms the asset and writes the result there, then 13 - reads it back off the disk rather than trusting the string it still holds: 14 - 15 - $ unikernel.exe 16 - files: tar holds greeting.txt 17 - files: read "hello from a tar" from the tar (17 bytes) 18 - files: FAT is empty -- this is its first boot 19 - files: wrote SHOUTED.TXT to FAT, read back "HELLO FROM A TAR" 20 - 21 - Boot it again on the same disks. The FAT volume still holds what the last boot 22 - wrote, which is what proves it was formatted only because it was *blank*: a boot 23 - that reformatted on every mount would find it empty again, and a corrupt volume 24 - must never be silently reformatted. 25 - 26 - $ unikernel.exe 27 - files: tar holds greeting.txt 28 - files: read "hello from a tar" from the tar (17 bytes) 29 - files: FAT already held SHOUTED.TXT from an earlier boot: "HELLO FROM A TAR" 30 - files: wrote SHOUTED.TXT to FAT, read back "HELLO FROM A TAR" 31 - 32 - Two disks, two manifest entries: 33 - 34 - $ unikernel.exe --manifest | grep -c BLOCK_BASIC 35 - 2
-20
bin/storage/files/setup.sh
··· 1 - if [ -n "${UNIKERNEL_EXE:-}" ]; then 2 - PATH="$(cd "$(dirname "$UNIKERNEL_EXE")" && pwd):$PATH" 3 - fi 4 - export PATH="$PWD:$PATH" 5 - 6 - # Both disks are files named after their devices. Work in a scratch directory so 7 - # each run starts from a fresh FAT volume, and build the tar the guest reads: it 8 - # is the read-only bundle a host ships into the unikernel. 9 - work=$(mktemp -d "${TMPDIR:-/tmp}/files-demo.XXXXXX") 10 - trap 'rm -rf "$work"' EXIT 11 - cd "$work" 12 - 13 - printf 'hello from a tar\n' >greeting.txt 14 - # COPYFILE_DISABLE keeps macOS tar from adding an AppleDouble ._ entry beside 15 - # each file, which would show up in the guest's listing. 16 - COPYFILE_DISABLE=1 tar cf assets greeting.txt 17 - rm greeting.txt 18 - 19 - # The FAT volume the guest writes to: blank, and big enough for a FAT16 volume. 20 - dd if=/dev/zero of=output bs=1m count=8 >/dev/null 2>&1
-77
bin/storage/files/unikernel.ml
··· 1 - (* Two ways to put a filesystem in front of a unikernel. 2 - 3 - [Tar.kv_ro] indexes a tar archive on a block device: the archive is built on 4 - the host, attached as a disk, and read by key. Nothing is written back -- a 5 - tar has no free list and no way to rewrite an entry in place -- which is 6 - exactly why it is the right shape for shipping assets *into* a guest. It is 7 - the unikernel's read-only bundle. 8 - 9 - [Fat.fat] mounts a FAT filesystem on another block device, read-write, so the 10 - guest can create and rewrite files that a host tool later reads back off the 11 - image. A blank disk is formatted rather than refused, because a guest has no 12 - mkfs. 13 - 14 - So the guest reads its assets out of the tar, transforms one, and writes the 15 - result to FAT -- the read-only bundle in, the durable output out. 16 - 17 - tar cf assets greeting.txt 18 - dune exec unikernels/bin/storage/files/unikernel.exe 19 - 20 - Under a tender both disks are attached by the tender: 21 - 22 - mrg build -t solo5 unikernels/bin/storage/files 23 - mrg run --disk assets:assets --disk output:output \ 24 - unikernels/bin/storage/files *) 25 - 26 - let source = "greeting.txt" 27 - let target = "SHOUTED.TXT" 28 - 29 - let read_fat fs path = 30 - match Fat.Fs.size fs path with 31 - | Error e -> Fmt.failwith "fat: size %s: %a" path Fat.Fs.pp_error e 32 - | Ok len -> ( 33 - match Fat.Fs.read fs path 0 (Int64.to_int len) with 34 - | Ok data -> data 35 - | Error e -> Fmt.failwith "fat: read %s: %a" path Fat.Fs.pp_error e) 36 - 37 - let write_fat fs path data = 38 - let check what = function 39 - | Ok () -> () 40 - | Error e -> Fmt.failwith "fat: %s %s: %a" what path Fat.Fs.pp_write_error e 41 - in 42 - (* Create the file only if it is not already there; a later boot rewrites the 43 - one the earlier boot left. *) 44 - (match Fat.Fs.size fs path with 45 - | Ok _ -> () 46 - | Error _ -> check "create" (Fat.Fs.file fs path)); 47 - check "write" (Fat.Fs.write fs path 0 data) 48 - 49 - let () = 50 - Mirage_eio.run 51 - Mirage_eio.[ Tar.kv_ro (block "assets"); Fat.fat (block "output") ] 52 - @@ fun _env tar fat -> 53 - Fmt.pr "files: tar holds %a@." 54 - Fmt.(list ~sep:(any ", ") string) 55 - (List.sort compare (Tar_kv.list tar)); 56 - let text = 57 - match Tar_kv.find tar source with 58 - | Some data -> data 59 - | None -> Fmt.failwith "tar: no %s" source 60 - in 61 - Fmt.pr "files: read %S from the tar (%d bytes)@." (String.trim text) 62 - (String.length text); 63 - (* What the FAT volume held before this boot: nothing on a blank disk, and the 64 - last boot's output on any boot after that. Seeing it survive is what proves 65 - the disk was formatted only when it was blank -- a second boot that 66 - reformatted would find the volume empty again. *) 67 - (match Fat.Fs.size fat target with 68 - | Ok _ -> 69 - Fmt.pr "files: FAT already held %s from an earlier boot: %S@." target 70 - (String.trim (read_fat fat target)) 71 - | Error _ -> Fmt.pr "files: FAT is empty -- this is its first boot@."); 72 - let shouted = String.uppercase_ascii text in 73 - write_fat fat target shouted; 74 - (* Read it back through the filesystem, not from the string we still hold: the 75 - point is that it went to the disk and came back. *) 76 - Fmt.pr "files: wrote %s to FAT, read back %S@." target 77 - (String.trim (read_fat fat target))
bin/storage/files/unikernel.mli bin/storage/fat/unikernel.mli
+23
bin/storage/tar/README.md
··· 1 + # tar 2 + 3 + ![tar unikernel demo](tar.gif) 4 + 5 + This example indexes a tar archive directly from a block device and reads its 6 + regular files as a read-only key/value store. It is the useful shape for assets 7 + shipped into a guest: the host builds one archive, and the unikernel needs no 8 + writable filesystem or unpack step. 9 + 10 + ```sh 11 + printf 'hello from a tar\n' >greeting.txt 12 + tar cf assets greeting.txt 13 + dune exec -- unikernel-tar 14 + ``` 15 + 16 + Under Solo5, attach the archive as the `assets` block device: 17 + 18 + ```sh 19 + mrg run --disk assets:assets unikernels/bin/storage/tar 20 + ``` 21 + 22 + The example declares `Mirage_eio.Tar.kv_ro (block "assets")`, a read-only tar 23 + index with one block-device manifest entry.
+11
bin/storage/tar/setup.sh
··· 1 + if [ -n "${UNIKERNEL_EXE:-}" ]; then 2 + PATH="$(cd "$(dirname "$UNIKERNEL_EXE")" && pwd):$PATH" 3 + fi 4 + export PATH="$PWD:$PATH" 5 + 6 + work=$(mktemp -d "${TMPDIR:-/tmp}/tar-demo.XXXXXX") 7 + trap 'rm -rf "$work"' EXIT 8 + cd "$work" 9 + printf 'hello from a tar\n' >greeting.txt 10 + COPYFILE_DISABLE=1 tar cf assets greeting.txt 11 + rm greeting.txt
bin/storage/tar/tar.gif

This is a binary file and will not be displayed.

+11
bin/storage/tar/tar.t
··· 1 + The host supplies a tar archive as one block device. The guest indexes the 2 + archive and reads its file without unpacking it: 3 + 4 + $ unikernel.exe 5 + tar: files: greeting.txt 6 + tar: greeting.txt: "hello from a tar" 7 + 8 + The read-only bundle needs exactly one block device: 9 + 10 + $ unikernel.exe --manifest | grep -c BLOCK_BASIC 11 + 1
+8
bin/storage/tar/unikernel.ml
··· 1 + let () = 2 + Mirage_eio.(run [ Tar.kv_ro (block "assets") ]) @@ fun _env tar -> 3 + Fmt.pr "tar: files: %a@." 4 + Fmt.(list ~sep:(any ", ") string) 5 + (List.sort compare (Tar_kv.list tar)); 6 + match Tar_kv.find tar "greeting.txt" with 7 + | Some data -> Fmt.pr "tar: greeting.txt: %S@." (String.trim data) 8 + | None -> Fmt.failwith "tar: greeting.txt is missing"
bin/storage/tar/unikernel.mli

This is a binary file and will not be displayed.

+9 -3
dune-project
··· 412 412 fmt)) 413 413 414 414 (package 415 - (name unikernel-files) 416 - (synopsis "FAT and tar filesystem unikernel, runnable natively") 415 + (name unikernel-fat) 416 + (synopsis "Writable FAT filesystem unikernel, runnable natively") 417 417 (tags (org:blacksun system storage)) 418 - (depends (eio (>= 1.0)) nox-mirage nox-tar-eio nox-fat fmt)) 418 + (depends (eio (>= 1.0)) nox-mirage nox-fat fmt)) 419 + 420 + (package 421 + (name unikernel-tar) 422 + (synopsis "Read-only tar storage unikernel, runnable natively") 423 + (tags (org:blacksun system storage)) 424 + (depends (eio (>= 1.0)) nox-mirage nox-tar-eio fmt)) 419 425 420 426 (package 421 427 (name unikernel-kv)
+1 -2
unikernel-files.opam unikernel-fat.opam
··· 1 1 # This file is generated by dune, edit dune-project instead 2 2 opam-version: "2.0" 3 - synopsis: "FAT and tar filesystem unikernel, runnable natively" 3 + synopsis: "Writable FAT filesystem unikernel, runnable natively" 4 4 tags: ["org:blacksun" "system" "storage"] 5 5 homepage: "https://tangled.org/gazagnaire.org/unikernels" 6 6 bug-reports: "https://tangled.org/gazagnaire.org/unikernels/issues" ··· 8 8 "dune" {>= "3.21"} 9 9 "eio" {>= "1.0"} 10 10 "nox-mirage" 11 - "nox-tar-eio" 12 11 "nox-fat" 13 12 "fmt" 14 13 "odoc" {with-doc}
unikernel-files.opam.template unikernel-fat.opam.template
+30
unikernel-tar.opam
··· 1 + # This file is generated by dune, edit dune-project instead 2 + opam-version: "2.0" 3 + synopsis: "Read-only tar storage unikernel, runnable natively" 4 + tags: ["org:blacksun" "system" "storage"] 5 + homepage: "https://tangled.org/gazagnaire.org/unikernels" 6 + bug-reports: "https://tangled.org/gazagnaire.org/unikernels/issues" 7 + depends: [ 8 + "dune" {>= "3.21"} 9 + "eio" {>= "1.0"} 10 + "nox-mirage" 11 + "nox-tar-eio" 12 + "fmt" 13 + "odoc" {with-doc} 14 + ] 15 + build: [ 16 + ["dune" "subst"] {dev} 17 + [ 18 + "dune" 19 + "build" 20 + "-p" 21 + name 22 + "-j" 23 + jobs 24 + "@install" 25 + "@runtest" {with-test} 26 + "@doc" {with-doc} 27 + ] 28 + ] 29 + dev-repo: "git+https://tangled.org/gazagnaire.org/unikernels" 30 + x-maintenance-intent: ["(latest)"]