File: pdiff.ml

package info (click to toggle)
approx 4.5-1%2Bsqueeze1
  • links: PTS, VCS
  • area: main
  • in suites: squeeze
  • size: 428 kB
  • ctags: 683
  • sloc: ml: 1,957; sh: 45; makefile: 36
file content (134 lines) | stat: -rw-r--r-- 5,012 bytes parent folder | download
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
(* approx: proxy server for Debian archive files
   Copyright (C) 2010  Eric C. Cooper <ecc@cmu.edu>
   Released under the GNU General Public License *)

open Util
open Config
open Log

let file_of_diff_index diff_index =
  Filename.chop_suffix (Filename.dirname diff_index) ".diff"

let directory file = without_extension file ^ ".diff"

let read_diff_index dir =
  let diff_index = dir ^/ "Index" in
  if not (Sys.file_exists diff_index) then
    failwith (diff_index ^ " does not exist");
  let items = Control_file.read diff_index in
  let current = Control_file.lookup "sha1-current" items ^ " current" in
  let current_info =
    match Control_file.info_list current with
    | [info, "current"] -> info
    | _ -> failwith ("unexpected SHA1-Current entry: " ^ current)
  in
  let combine (index_info, name) (patch_info, name') =
    if name <> name' then failwith (diff_index ^ " is inconsistent");
    (index_info, dir ^/ name, patch_info)
  in
  let history = Control_file.lookup_info "sha1-history" items in
  let patches = Control_file.lookup_info "sha1-patches" items in
  List.map2 combine history patches, current_info

let rec find_tail p = function
  | x :: rest as list -> if p x then list else find_tail p rest
  | [] -> []

let find_pdiff pdiff (diffs, final) =
  let check_pdiff (index_info, _, pdiff_info) next =
    let check pdiff' =
      if Control_file.is_valid file_sha1sum pdiff_info pdiff' then begin
        debug_message "Parsing %s" pdiff;
        let cmds = with_in_channel open_in pdiff' Patch.parse in
        Some (index_info, cmds, next)
      end else begin
        debug_message "Removing invalid %s" pdiff;
        rm pdiff;
        None
      end
    in
    with_decompressed pdiff check
  in
  let id = without_extension pdiff in
  match find_tail (fun (_, name, _) -> name = id) diffs with
  | cur :: (next, _, _) :: _ -> check_pdiff cur next
  | [cur] -> check_pdiff cur final
  | [] -> None

(* Pdiff application must result in a Packages or Sources file
   that is identical to the one in the official archive.
   So this function must use the same gzip parameters that dak does --
   see http://ftp-master.debian.org/git/dak.git *)

let compress ~src ~dst =
  let cmd = Printf.sprintf "/bin/gzip --rsyncable -9cn %s > %s" src dst in
  debug_message "Compressing: %s" cmd;
  if Sys.command cmd <> 0 then failwith "compress"

(* Apply a pdiff to the given file *)

let apply_pdiff cmds file =
  let file' =
    with_in_channel open_in file
      (fun chan -> with_temp_file file (Patch.apply cmds chan))
  in
  Sys.rename file' file

let apply pdiff =
  let dir = Filename.dirname pdiff in
  match find_pdiff pdiff (read_diff_index dir) with
  | None -> debug_message "%s not found in DiffIndex" pdiff
  | Some (info, cmds, info') ->
      let index = Filename.chop_suffix dir ".diff" ^ ".gz" in
      let patch file =
        if Control_file.is_valid file_sha1sum info file then begin
          apply_pdiff cmds file;
          if Control_file.is_valid file_sha1sum info' file then begin
            debug_message "Applied %s" pdiff;
            compress ~src: file ~dst: index;
            rm pdiff
          end else debug_message "Invalid result from %s" pdiff
        end else debug_message "Cannot apply %s" pdiff
      in
      if Sys.file_exists index then decompress_and_apply patch index
      else debug_message "Index %s not found" index

let remove_pdiffs pdiffs =
  List.iter (fun (_, file, _) -> rm (file ^ ".gz"))  pdiffs

let apply_pdiffs file pdiffs final index =
  let patch (index_info, name, pdiff_info) =
    let pdiff = name ^ ".gz" in
    let check_and_apply pdiff' =
      if Control_file.is_valid file_sha1sum pdiff_info pdiff' then begin
        debug_message "Parsing %s" pdiff;
        let cmds = with_in_channel open_in pdiff' Patch.parse in
        if Control_file.is_valid file_sha1sum index_info file then
          apply_pdiff cmds file
        else (debug_message "Invalid index %s" file; raise Exit)
      end else (debug_message "Invalid pdiff %s" pdiff; raise Exit)
    in
    if not (Sys.file_exists pdiff) then Url.download_file pdiff;
    with_decompressed pdiff check_and_apply
  in
  try
    List.iter patch pdiffs;
    if Control_file.is_valid file_sha1sum final file then begin
      debug_message "Updated %s" (shorten index);
      compress ~src: file ~dst: index;
      remove_pdiffs pdiffs
    end else debug_message "Invalid update of %s" (shorten index)
  with Exit -> ()

let update index =
  if not (Filename.check_suffix index ".gz") then invalid_arg "Pdiff.update";
  let diffs, final = read_diff_index (directory index) in
  let update_index file =
    let info = (file_sha1sum file, file_size file) in
    if info = final then debug_message "%s is current" index
    else
      match find_tail (fun (i, _, _) -> i = info) diffs with
      | [] -> debug_message "%s not found in DiffIndex" index; raise Not_found
      | list -> apply_pdiffs file list final index
  in
  decompress_and_apply update_index index