Source file mirage_impl_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
open Functoria
open Mirage_impl_misc
open Mirage_impl_kv
module Key = Mirage_key

type block = BLOCK

let block = Type.v 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 -> Action.ok ()
    | _ ->
        failwith
          "XenStore IDs are only valid ways of specifying block devices when \
           the target is Xen or Qubes."
  in
  let connect _ impl_name _ = Fmt.str "%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
    | #Mirage_key.mode_unix -> file (* open the file directly *)
    | #Mirage_key.mode_xen ->
        let b = make_block_t file in
        xenstore_id_of_index b.number |> string_of_int
    | #Mirage_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
    | #Mirage_key.mode_xen -> xen_block_packages
    | #Mirage_key.mode_solo5 ->
        [ package ~min:"0.8.0" ~max:"0.9.0" "mirage-block-solo5" ]
    | #Mirage_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
    Action.ok ()
  in
  let connect i s _ =
    match get_target i with
    | `Muen -> failwith "Block devices not supported on Muen target."
    | _ -> Fmt.str "%s.connect %S" s (connect_name (get_target i))
  in
  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 _ = Fmt.str "%s.connect ~name:%S" m rname in
  impl ~connect ~packages "Ramdisk" block

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

let count =
  let i = ref 0 in
  fun () ->
    incr i;
    !i

let tar_block dir =
  let name = "tar_block" ^ string_of_int (count ()) in
  let block_file = name ^ ".img" in
  let dune _ =
    let dune =
      Dune.stanzaf
        {|
(rule
 (targets %s)
 (deps (source_tree %s))
 (action (run tar -cvf %%{targets} %%{deps})))
|}
        block_file dir
    in
    [ dune ]
  in
  of_device @@ Device.extend ~dune (block_conf block_file)

let archive_conf =
  let packages = [ package ~min:"1.0.0" ~max:"2.0.0" "tar-mirage" ] in
  let connect _ modname = function
    | [ block ] -> Fmt.str "%s.connect %s" modname block
    | _ -> failwith (connect_err "archive" 1)
  in
  impl ~packages ~connect "Tar_mirage.Make_KV_RO" (block @-> Mirage_impl_kv.ro)

let archive block = archive_conf $ block
let archive_of_files ?(dir = ".") () = archive @@ tar_block dir

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) disk branch analyze remote =
  let dune info =
    let ctx = Info.context info in
    let disk = Key.get ctx disk in
    let dune =
      Dune.stanzaf
        {dune|
(rule
 (targets %s)
 (deps (:make %%{bin:docteur.make}))
 (action (run %%{make} %s%a %s)))
|dune}
        disk remote pp_branch branch disk
    in
    [ dune ]
  in
  let install info =
    let ctx = Info.context info in
    let disk = Fpath.v (Key.get ctx disk) in
    Install.v ~bin:[ (disk, disk) ] ()
  in
  let configure info =
    let ctx = Info.context info in
    let disk = Key.get ctx disk in
    let (_ : block_t) = make_block_t disk in
    Action.ok ()
  in
  let connect _info modname _ =
    Fmt.str
      {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:%a %a)|ocaml}
      modname modname Key.serialize_call (Key.v analyze) Key.serialize_call
      (Key.v disk)
  in
  let keys = [ Key.v disk; Key.v analyze ] in
  let packages = [ package "docteur-unix" ~min:"0.0.3" ] in
  impl ~keys ~packages ~dune ~install ~configure ~connect
    (Fmt.str "Docteur_unix.%a" pp_mode mode)
    ro

let docteur_solo5 (mode : mode) disk branch analyze remote =
  let dune info =
    let ctx = Info.context info in
    let disk = Key.get ctx disk in
    let dune =
      Dune.stanzaf
        {dune|
(rule
 (targets %s)
 (deps (:make %%{bin:docteur.make}))
 (action (run %%{make} %s%a %s)))
|dune}
        disk remote pp_branch branch disk
    in
    [ dune ]
  in
  let install info =
    let ctx = Info.context info in
    let disk = Fpath.v (Key.get ctx disk) in
    Install.v ~bin:[ (disk, disk) ] ()
  in
  let configure info =
    let ctx = Info.context info in
    let disk = Key.get ctx disk in
    let (_ : block_t) = make_block_t disk in
    Action.ok ()
  in
  let connect _info modname _ =
    Fmt.str
      {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:%a %a)|ocaml}
      modname modname Key.serialize_call (Key.v analyze) Key.serialize_call
      (Key.v disk)
  in
  let keys = [ Key.v disk; Key.v analyze ] in
  let packages = [ package "docteur-solo5" ~min:"0.0.3" ] in
  impl ~keys ~packages ~dune ~install ~configure ~connect
    (Fmt.str "Docteur_solo5.%a" pp_mode mode)
    ro

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

let analyze =
  let doc =
    Key.Arg.info ~doc:"Analyze at the boot time the given docteur disk."
      [ "analyze" ]
  in
  Key.(create "analyze" Arg.(opt bool true doc))

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