package mirage

  1. Overview
  2. Docs

Source file block.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
module Dune = Functoria.Dune
module Info = Functoria.Info
module Install = Functoria.Install
open Functoria.DSL
open Functoria.Action
open Misc
open Kv
open Cmdliner

type block = BLOCK

let block = typ BLOCK

type block_t = { filename : string; number : int }

let all_blocks = Hashtbl.create 7

let make_block_t =
  (* NB: reserve number 0 for the boot disk *)
  let next_number = ref 1 in
  fun filename ->
    let b =
      if Hashtbl.mem all_blocks filename then Hashtbl.find all_blocks filename
      else
        let number = !next_number in
        incr next_number;
        let b = { filename; number } in
        Hashtbl.add all_blocks filename b;
        b
    in
    b

let xen_block_packages =
  [ package ~min:"2.1.0" ~max:"3.0.0" ~sublibs:[ "front" ] "mirage-block-xen" ]

(* this function takes a string rather than an int as `id` to allow
   the user to pass stuff like "/dev/xvdi1", which mirage-block-xen
   also understands *)
let xenstore_conf id =
  let configure i =
    match get_target i with
    | `Qubes | `Xen -> ok ()
    | _ ->
        error
          "XenStore IDs are only valid ways of specifying block devices when \
           the target is Xen or Qubes."
  in
  let connect _ impl_name _ = code ~pos:__POS__ "%s.connect %S" impl_name id in
  impl ~configure ~connect ~packages:xen_block_packages "Block" block

let block_of_xenstore_id id = xenstore_conf id

(* calculate the XenStore ID for the nth available block device.
   Taken from https://github.com/mirage/mirage-block-xen/blob/
   a64d152586c7ebc1d23c5adaa4ddd440b45a3a83/lib/device_number.ml#L64 . *)
let xenstore_id_of_index number =
  if number < 16 then (202 lsl 8) lor (number lsl 4)
  else (1 lsl 28) lor (number lsl 8)

let block_conf file =
  let connect_name target =
    match target with
    | #Key.mode_unix -> file (* open the file directly *)
    | #Key.mode_xen ->
        let b = make_block_t file in
        xenstore_id_of_index b.number |> string_of_int
    | #Key.mode_solo5 ->
        (* XXX For now, on Solo5, just pass the "file" name through directly as
         * the Solo5 block device name *)
        file
  in
  let packages_v =
    Key.match_ Key.(value target) @@ function
    | #Key.mode_xen -> xen_block_packages
    | #Key.mode_solo5 ->
        [ package ~min:"0.8.0" ~max:"0.9.0" "mirage-block-solo5" ]
    | #Key.mode_unix ->
        [ package ~min:"2.12.0" ~max:"3.0.0" "mirage-block-unix" ]
  in
  let configure _ =
    let (_ : block_t) = make_block_t file in
    ok ()
  in
  let connect i s _ =
    match get_target i with
    | `Muen -> failwith "Block devices not supported on Muen target."
    | _ -> code ~pos:__POS__ "%s.connect %S" s (connect_name (get_target i))
  in
  Functoria.Device.v ~configure ~packages_v ~connect "Block" block

let block_of_file file = of_device (block_conf file)

let ramdisk rname =
  let packages = [ package "mirage-block-ramdisk" ] in
  let connect _ m _ = code ~pos:__POS__ "%s.connect ~name:%S" m rname in
  impl ~connect ~packages "Ramdisk" block

let generic_block ?group ?(key = Key.(value @@ block ?group ())) name =
  match_impl key
    [
      (`XenstoreId, block_of_xenstore_id name);
      (`BlockFile, block_of_file name);
      (`Ramdisk, ramdisk name);
    ]
    ~default:(ramdisk name)

let tar_kv_ro_conf =
  let packages = [ package ~min:"1.0.0" ~max:"4.0.0" "tar-mirage" ] in
  let connect _ modname = function
    | [ block ] -> code ~pos:__POS__ "%s.connect %s" modname block
    | _ -> connect_err "tar_kv_ro" 1
  in
  impl ~packages ~connect "Tar_mirage.Make_KV_RO" (block @-> Kv.ro)

let tar_kv_rw_conf =
  let packages = [ package ~min:"2.2.0" ~max:"4.0.0" "tar-mirage" ] in
  let connect _ modname = function
    | [ block ] -> code ~pos:__POS__ "%s.connect %s" modname block
    | _ -> connect_err "tar_kv_rw" 1
  in
  impl ~packages ~connect "Tar_mirage.Make_KV_RW" (block @-> Kv.rw)

let tar_kv_ro block = tar_kv_ro_conf $ block
let tar_kv_rw block = tar_kv_rw_conf $ block

let fat_conf =
  let packages = [ package ~min:"0.15.0" ~max:"0.16.0" "fat-filesystem" ] in
  let connect _ modname = function
    | [ block ] -> code ~pos:__POS__ "%s.connect %s" modname block
    | _ -> connect_err "fat" 1
  in
  impl ~packages ~connect "Fat.KV_RO" (block @-> Kv.ro)

let fat_ro block = fat_conf $ block

type mode = [ `Fast | `Light ]

let pp_mode ppf = function
  | `Fast -> Fmt.string ppf "Fast"
  | `Light -> Fmt.string ppf "Light"

let pp_branch ppf = function
  | None -> ()
  | Some branch -> Fmt.pf ppf " -b %s" branch

let docteur_unix (mode : mode) extra_deps ~name:_ ~output branch analyze remote
    =
  let dune info =
    let ctx = Info.context info in
    let output = Key.get ctx output in
    let source_tree =
      let uri = Uri.of_string remote in
      match Uri.scheme uri with
      | Some "file" ->
          let path = Uri.host_with_default ~default:"" uri ^ Uri.path uri in
          Fmt.str " (source_tree /%s)" path
      | Some "relativize" ->
          let path = Uri.host_with_default ~default:"" uri ^ Uri.path uri in
          Fmt.str " (source_tree %s)" path
      | _ -> ""
    in
    let dune =
      Dune.stanzaf
        {dune|
(rule
 (targets %s)
 (enabled_if (= %%{context_name} "default"))
 (deps (:make %%{bin:docteur.make})%a%s)
 (action (run %%{make} %s%a %s)))
|dune}
        output
        Fmt.(list ~sep:nop (const string " " ++ string))
        extra_deps source_tree remote pp_branch branch output
    in
    [ dune ]
  in
  let install info =
    let ctx = Info.context info in
    let output = Fpath.v (Key.get ctx output) in
    Install.v ~etc:[ output ] ()
  in
  let configure info =
    let ctx = Info.context info in
    let name = Key.get ctx output in
    let (_ : block_t) = make_block_t name in
    ok ()
  in
  let connect info modname = function
    | [ analyze ] ->
        let ctx = Info.context info in
        let name = Key.get ctx output in
        code ~pos:__POS__
          {ocaml|let ( <.> ) f g = fun x -> f (g x) in
             let f = Rresult.R.(failwith_error_msg <.> reword_error (msgf "%%a" %s.pp_error)) in
             Lwt.map f (%s.connect ~analyze:%s %S)|ocaml}
          modname modname analyze name
    | _ -> connect_err "docteur_unix" 1
  in
  let keys = [ Key.v output ] in
  let runtime_args = Runtime_arg.[ v analyze ] in
  let packages = [ package "docteur-unix" ~min:"0.0.6" ] in
  impl ~runtime_args ~keys ~packages ~dune ~install ~configure ~connect
    (Fmt.str "Docteur_unix.%a" pp_mode mode)
    ro

let docteur_solo5 (mode : mode) extra_deps ~name ~output branch analyze remote =
  let dune info =
    let ctx = Info.context info in
    let output = Key.get ctx output in
    let source_tree =
      let uri = Uri.of_string remote in
      match Uri.scheme uri with
      | Some "file" ->
          let path = Uri.host_with_default ~default:"" uri ^ Uri.path uri in
          Fmt.str " (source_tree /%s)" path
      | Some "relativize" ->
          let path = Uri.host_with_default ~default:"" uri ^ Uri.path uri in
          Fmt.str " (source_tree %s)" path
      | _ -> ""
    in
    let dune =
      Dune.stanzaf
        {dune|
(rule
 (targets %s)
 (enabled_if (= %%{context_name} "default"))
 (deps (:make %%{bin:docteur.make})%a%s)
 (action (run %%{make} %s%a %s)))
|dune}
        output
        Fmt.(list ~sep:nop (const string " " ++ string))
        extra_deps source_tree remote pp_branch branch output
    in
    [ dune ]
  in
  let install info =
    let ctx = Info.context info in
    let output = Fpath.v (Key.get ctx output) in
    Install.v ~etc:[ output ] ()
  in
  let configure info =
    let ctx = Info.context info in
    let name = Key.get ctx name in
    let (_ : block_t) = make_block_t name in
    ok ()
  in
  let connect info modname = function
    | [ analyze ] ->
        let ctx = Info.context info in
        let name = Key.get ctx name in
        code ~pos:__POS__
          {ocaml|let ( <.> ) f g = fun x -> f (g x) in
             let f = Rresult.R.(failwith_error_msg <.> reword_error (msgf "%%a" %s.pp_error)) in
             Lwt.map f (%s.connect ~analyze:%s %S)|ocaml}
          modname modname analyze name
    | _ -> connect_err "docteur_solo5" 1
  in
  let keys = [ Key.v output; Key.v name ] in
  let runtime_args = Runtime_arg.[ v analyze ] in
  let packages = [ package "docteur-solo5" ~min:"0.0.6" ] in
  impl ~keys ~runtime_args ~packages ~dune ~install ~configure ~connect
    (Fmt.str "Docteur_solo5.%a" pp_mode mode)
    ro

let disk_name =
  let doc =
    Arg.info
      ~doc:
        "Name of the docteur disk (for Solo5 targets, the name must contains \
         only alpanumeric characters)."
      [ "disk-name" ]
  in
  let key = Key.Arg.opt Arg.string "docteur" doc in
  Key.create "disk-name" key

let disk_output =
  let doc =
    Arg.info ~doc:"The output of the generated docteur image." [ "disk-output" ]
  in
  let key = Key.Arg.opt Arg.string "disk.img" doc in
  Key.create "disk-output" key

let docteur_solo5 (mode : mode) extra_deps ?(name = disk_name)
    ?(output = disk_output) branch analyze remote =
  docteur_solo5 mode extra_deps ~name ~output branch analyze remote

let docteur_unix (mode : mode) extra_deps ?(name = disk_name)
    ?(output = disk_output) branch analyze remote =
  docteur_unix mode extra_deps ~name ~output branch analyze remote

let analyze = Runtime_arg.create ~pos:__POS__ "Mirage_runtime.analyze"

let docteur ?(mode = `Fast) ?name ?output ?(analyze = analyze) ?branch
    ?(extra_deps = []) remote =
  match_impl
    Key.(value target)
    [
      (`Xen, docteur_solo5 mode extra_deps ?name ?output branch analyze remote);
      (`Qubes, docteur_solo5 mode extra_deps ?name ?output branch analyze remote);
      ( `Virtio,
        docteur_solo5 mode extra_deps ?name ?output branch analyze remote );
      (`Hvt, docteur_solo5 mode extra_deps ?name ?output branch analyze remote);
      (`Spt, docteur_solo5 mode extra_deps ?name ?output branch analyze remote);
      (`Muen, docteur_solo5 mode extra_deps ?name ?output branch analyze remote);
      ( `Genode,
        docteur_solo5 mode extra_deps ?name ?output branch analyze remote );
    ]
    ~default:(docteur_unix mode extra_deps ?name ?output branch analyze remote)

let chamelon ~program_block_size =
  let runtime_args = Runtime_arg.[ v program_block_size ] in
  let packages = [ package "chamelon" ~sublibs:[ "kv" ] ~min:"0.0.8" ] in
  let connect _ modname = function
    | [ block; program_block_size ] ->
        code ~pos:__POS__
          {ocaml|%s.connect ~program_block_size:%s %s
                 >|= Result.map_error (Fmt.str "%%a" %s.pp_error)
                 >|= Result.fold ~ok:Fun.id ~error:failwith|ocaml}
          modname program_block_size block modname
    | _ -> connect_err "chameleon" 2
  in
  impl ~packages ~runtime_args ~connect "Kv.Make" (block @-> Kv.rw)

let ccm_block ?nonce_len key =
  let runtime_args = Runtime_arg.[ v key ] in
  let packages = [ package "mirage-block-ccm" ~min:"2.0.0" ~max:"3.0.0" ] in
  let connect _ modname = function
    | [ block; key ] ->
        code ~pos:__POS__
          {ocaml|let key = %s in
                 let key =
                   if String.length key >= 2 && String.(equal "0x" (sub key 0 2)) then
                     String.sub key 2 (String.length key - 2)
                   else
                     key
                 in
                 %s.connect ?nonce_len:%a ~key:(Cstruct.of_hex key) %s|ocaml}
          key modname
          Fmt.(parens (Dump.option int))
          nonce_len block
    | _ -> connect_err "ccm_block" 2
  in
  impl ~packages ~runtime_args ~connect "Block_ccm.Make" (block @-> block)
OCaml

Innovation. Community. Security.