package topup

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file topup_runtime.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
type io_hook = {
  read : string -> bytes;
  write : string -> bytes -> unit;
}

let xfer_default_max_bytes = 16 * 1024 * 1024

let xfer_max_bytes () =
  match Sys.getenv_opt "TOPUP_XFER_MAX_BYTES" with
  | None | Some "" -> xfer_default_max_bytes
  | Some s -> (
      match int_of_string_opt (String.trim s) with
      | Some n when n > 0 -> n
      | _ -> xfer_default_max_bytes)

let rec mkdir_p path =
  if path = "" || path = "/" || path = "." then ()
  else if Sys.file_exists path then ()
  else begin
    mkdir_p (Filename.dirname path);
    try Unix.mkdir path 0o700
    with Unix.Unix_error (Unix.EEXIST, _, _) -> ()
  end

let read_file path =
  match Unix.stat path with
  | exception Unix.Unix_error (Unix.ENOENT, _, _) ->
      failwith ("Topup.read_back: no such file: " ^ path)
  | exception Unix.Unix_error (err, _, _) ->
      failwith
        ("Topup.read_back: " ^ Unix.error_message err ^ ": " ^ path)
  | st ->
      if st.Unix.st_kind <> Unix.S_REG then
        failwith ("Topup.read_back: not a regular file: " ^ path)
      else
        let cap = xfer_max_bytes () in
        if st.Unix.st_size > cap then
          failwith
            (Printf.sprintf
               "Topup.read_back: file too large: %s is %d bytes; cap is %d \
                (TOPUP_XFER_MAX_BYTES)"
               path st.Unix.st_size cap)
        else begin
          let ic = open_in_bin path in
          Fun.protect
            ~finally:(fun () -> close_in_noerr ic)
            (fun () ->
              let n = st.Unix.st_size in
              let b = Bytes.create n in
              really_input ic b 0 n;
              b)
        end

let write_file_atomic path bytes =
  let cap = xfer_max_bytes () in
  let n = Bytes.length bytes in
  if n > cap then
    failwith
      (Printf.sprintf
         "Topup.write_back: payload too large: %d bytes; cap is %d \
          (TOPUP_XFER_MAX_BYTES)"
         n cap)
  else begin
    let dir = Filename.dirname path in
    (try mkdir_p dir with _ -> ());
    let tmp = path ^ ".tmp" in
    match
      let oc =
        open_out_gen
          [ Open_wronly; Open_creat; Open_trunc; Open_binary ] 0o600 tmp
      in
      Fun.protect
        ~finally:(fun () -> close_out_noerr oc)
        (fun () -> output_bytes oc bytes);
      Unix.rename tmp path
    with
    | () -> ()
    | exception Unix.Unix_error (err, _, _) ->
        (try Sys.remove tmp with _ -> ());
        failwith
          ("Topup.write_back: " ^ Unix.error_message err ^ ": " ^ path)
  end

let direct_hook = { read = read_file; write = write_file_atomic }
let current_hook = ref direct_hook
let install_hook h = current_hook := h
let read_back path = !current_hook.read path
let write_back path bytes = !current_hook.write path bytes