package qcow-types

  1. Overview
  2. Docs

Source file qcow_cache.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
(*
 * Copyright (C) 2017 David Scott <dave@recoil.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 *)
open Qcow_types

let src =
  let src = Logs.Src.create "qcow" ~doc:"qcow2-formatted BLOCK device" in
  Logs.Src.set_level src (Some Logs.Info) ;
  src

module Log = (val Logs.src_log src : Logs.LOG)

type t = {
    read_cluster: Cluster.t -> (Cstruct.t, Mirage_block.error) result Lwt.t
  ; write_cluster:
      Cluster.t -> Cstruct.t -> (unit, Mirage_block.write_error) result Lwt.t
  ; mutable clusters: Cstruct.t Cluster.Map.t
  ; seekable: bool
  ; last_read_cluster: Cluster.t ref
}

let create ~read_cluster ~write_cluster ?(seekable = true) () =
  let clusters = Cluster.Map.empty in
  {
    read_cluster
  ; write_cluster
  ; clusters
  ; seekable
  ; last_read_cluster= ref (Cluster.of_int 0)
  }

let read t cluster =
  if Cluster.Map.mem cluster t.clusters then
    let data = Cluster.Map.find cluster t.clusters in
    Lwt.return (Ok data)
  else
    let open Lwt.Infix in
    let read_cluster cluster =
      t.read_cluster cluster >>= function
      | Error e ->
          Lwt.return (Error e)
      | Ok data ->
          t.clusters <- Cluster.Map.add cluster data t.clusters ;
          Lwt.return (Ok data)
    in
    let next_cluster = Cluster.succ !(t.last_read_cluster) in
    if t.seekable then
      read_cluster cluster
    else
      (* If we can't seek, we need to read sequential clusters until we reach
         the one we want. Previous clusters will still be stored in the cache
         for when we need them later (since we can't seek back) *)
      let rec read_clusters ~from ~until =
        let open Lwt.Syntax in
        let* data = read_cluster from in
        t.last_read_cluster := from ;
        if from < until then
          read_clusters ~from:(Cluster.succ from) ~until
        else
          Lwt.return data
      in
      read_clusters ~from:next_cluster ~until:cluster

let write t cluster data =
  if not (Cluster.Map.mem cluster t.clusters) then (
    Log.err (fun f ->
        f
          "Cache.write %s: cluster is nolonger in cache, so update will be \
           dropped"
          (Cluster.to_string cluster)
    ) ;
    assert false
  ) ;
  t.clusters <- Cluster.Map.add cluster data t.clusters ;
  t.write_cluster cluster data

let remove t cluster =
  if Cluster.Map.mem cluster t.clusters then
    Printf.fprintf stderr "Dropping cache for cluster %s\n"
      (Cluster.to_string cluster) ;
  t.clusters <- Cluster.Map.remove cluster t.clusters

let resize t new_size_clusters =
  let to_keep, to_drop =
    Cluster.Map.partition
      (fun cluster _ -> cluster < new_size_clusters)
      t.clusters
  in
  t.clusters <- to_keep ;
  if not (Cluster.Map.is_empty to_drop) then
    Log.info (fun f ->
        f "After file resize dropping cached clusters: %s"
          (String.concat ", "
          @@ List.map Cluster.to_string
          @@ List.map fst
          @@ Cluster.Map.bindings to_drop
          )
    )

module Debug = struct
  let assert_not_cached t cluster =
    if Cluster.Map.mem cluster t.clusters then (
      Printf.fprintf stderr "Cluster %s still in the metadata cache\n"
        (Cluster.to_string cluster) ;
      assert false
    )

  let all_cached_clusters t =
    Cluster.Map.fold
      (fun cluster _ set ->
        Cluster.IntervalSet.(add (Interval.make cluster cluster) set)
      )
      t.clusters Cluster.IntervalSet.empty

  let check_disk t =
    let open Lwt.Infix in
    let rec loop = function
      | [] ->
          Lwt.return (Ok ())
      | (cluster, expected) :: rest -> (
          (t.read_cluster cluster >>= function
           | Error e ->
               Lwt.return (Error e)
           | Ok data ->
               if not (Cstruct.equal expected data) then (
                 Log.err (fun f ->
                     f "Cache for cluster %s disagrees with disk"
                       (Cluster.to_string cluster)
                 ) ;
                 Log.err (fun f -> f "Cached:") ;
                 let buffer = Buffer.create 65536 in
                 Cstruct.hexdump_to_buffer buffer expected ;
                 Log.err (fun f -> f "%s" (Buffer.contents buffer)) ;
                 let buffer = Buffer.create 65536 in
                 Cstruct.hexdump_to_buffer buffer data ;
                 Log.err (fun f -> f "On disk:") ;
                 Log.err (fun f -> f "%s" (Buffer.contents buffer)) ;
                 Lwt.return (Ok ())
               ) else
                 Lwt.return (Ok ())
          )
          >>= function
          | Error e ->
              Lwt.return (Error e)
          | Ok () ->
              loop rest
        )
    in
    loop (Cluster.Map.bindings t.clusters)
end