Initial snarf.
[shack.git] / libmojave / util / lm_db.ml
blobc554ac3ed1778f5c456399b43b6565e7910928ad
1 (*
2 * Simple value database. The entries in the database have
3 * the following format.
5 * - Field label (int)
6 * - Hostname (string)
7 * - Magic number (16 bytes)
8 * - Digest (used on the source file, for up-to-date info)
9 * - Value (marshaled)
11 * Invariant:
12 * - There is at most one entry for each host/field label.
14 * If the magic number doesn't match, then the entry is
15 * out-of-date, and should be replaced.
17 * In some cases, the hostname doesn't matter. Even so, if there
18 * is an entry with the current hostname, and the magic number
19 * doesn't match, it is out-of-date.
21 * NOTE: This has been updated to allowed for key-value pairs
22 * in the header. It looks pretty dumb, but I (jyh) want to keep
23 * the file format backward-compatible. So we stuff all the key/value
24 * pairs in the magic number.
26 * ----------------------------------------------------------------
28 * @begin[license]
29 * Copyright (C) 2004-2007 Mojave Group, California Institute of Technology and
30 * HRL Laboratories, LLC
32 * This library is free software; you can redistribute it and/or
33 * modify it under the terms of the GNU Lesser General Public
34 * License as published by the Free Software Foundation,
35 * version 2.1 of the License.
37 * This library is distributed in the hope that it will be useful,
38 * but WITHOUT ANY WARRANTY; without even the implied warranty of
39 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
40 * Lesser General Public License for more details.
42 * You should have received a copy of the GNU Lesser General Public
43 * License along with this library; if not, write to the Free Software
44 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
46 * Additional permission is given to link this library with the
47 * OpenSSL project's "OpenSSL" library, and with the OCaml runtime,
48 * and you may distribute the linked executables. See the file
49 * LICENSE.libmojave for more details.
51 * Author: Jason Hickey @email{jyh@cs.caltech.edu}
52 * Modified By: Aleksey Nogin @email{anogin@hrl.com}
53 * @end[license]
55 open Lm_printf
56 open Lm_debug
58 let debug_db =
59 create_debug (**)
60 { debug_name = "db";
61 debug_description = "Display debugging information for marshaling operations";
62 debug_value = false
65 type t = Unix.file_descr
67 type tag = int
68 type magic = string
69 type digest = string
70 type hostname = string
71 type named_value = string * string
73 type entry_pred = tag -> named_value list -> hostname -> digest -> bool
75 let first_entry_tag = 1000
78 * Some kinds of entries are host-independent.
80 type host =
81 HostIndependent
82 | HostDependent
85 * Codes during unmarshaling.
87 type 'a unmarshal =
88 UnmarshalValue of 'a
89 | UnmarshalNext
92 * Codes during removal.
94 type remove =
95 RemoveEntry of int
96 | RemoveNext
97 | RemoveRest
100 * Version number.
102 let magic = 0x56e50f8b
105 * Marshaling.
107 let hostname = Unix.gethostname ()
108 let digest_length = 16
111 * File operations.
115 * Win32 doesn't have a general truncate, so seek and truncate.
117 let seek_and_truncate fd pos =
118 let _ = Unix.lseek fd pos Unix.SEEK_SET in
119 Lm_unix_util.ftruncate fd
123 * When an entry is removed, copy the remaining parts of
124 * the file.
126 let bufsize = 4096
128 let file_shift fd pos1 pos2 =
129 let buf = String.create bufsize in
130 let rec copy pos1 pos2 =
131 let _ = Unix.lseek fd pos2 Unix.SEEK_SET in
132 let amount = Unix.read fd buf 0 bufsize in
133 if amount <> 0 then
134 let _ = Unix.lseek fd pos1 Unix.SEEK_SET in
135 assert (Unix.write fd buf 0 amount = amount);
136 copy (pos1 + amount) (pos2 + amount)
137 else
138 pos1
140 let pos = copy pos1 pos2 in
141 seek_and_truncate fd pos;
142 ignore (Unix.lseek fd pos1 Unix.SEEK_SET)
145 * If some kind of error happens while removing an entry,
146 * truncate the file at this point.
148 let remove_entry fd pos off =
149 try file_shift fd pos off with
150 Unix.Unix_error _ ->
151 seek_and_truncate fd pos
154 * Unmarshaling.
156 let unmarshal_magic inx =
157 try input_binary_int inx = magic with
158 End_of_file ->
159 false
161 let unmarshal_tag inx =
162 input_binary_int inx
164 let unmarshal_digest inx =
165 let s = String.create digest_length in
166 really_input inx s 0 digest_length;
169 let unmarshal_string inx =
170 let len = input_binary_int inx in
171 if len < 0 || len >= 1024 then
172 raise (Failure "unmarshal_string")
173 else
174 let s = String.create len in
175 really_input inx s 0 len;
178 let unmarshal_strings_old inx =
179 let magic = unmarshal_string inx in
180 ["MAGIC", magic]
182 let unmarshal_strings_new inx =
183 (* Total size of all the entries *)
184 let _ = input_binary_int inx in
186 (* Number of key/value pairs *)
187 let len = input_binary_int inx in
188 (* Read the key/value pairs *)
189 if len < 0 || len >= 1024 then
190 raise (Failure "unmarshal_string")
191 else
192 let rec loop strings i =
193 if i = len then
194 List.rev strings
195 else
196 let key = unmarshal_string inx in
197 let value = unmarshal_string inx in
198 loop ((key, value) :: strings) (i + 1)
200 loop [] 0
202 let unmarshal_strings inx tag =
203 if tag < first_entry_tag then
204 unmarshal_strings_old inx
205 else
206 unmarshal_strings_new inx
209 * Search for the appropriate entry.
211 let find_entry fd filename test =
212 let _ = Unix.lseek fd 0 Unix.SEEK_SET in
213 let inx = Unix.in_channel_of_descr fd in
214 let head = String.create Marshal.header_size in
216 (* Find the appropriate entry *)
217 let unmarshal_entry () =
218 (* Get the header *)
219 let tag = unmarshal_tag inx in
220 let host = unmarshal_string inx in
221 let strings = unmarshal_strings inx tag in
222 let digest = unmarshal_digest inx in
223 if test tag strings host digest then begin
224 (* Found a matching entry *)
225 if !debug_db then
226 eprintf "@[<v 3>Marshal.from_channel: %s@ save tag/digest: %d/%s@." (**)
227 filename
228 tag (Lm_string_util.hexify digest);
229 let x = UnmarshalValue (Marshal.from_channel inx) in
230 if !debug_db then
231 eprintf "Marshal.from-channel: done@.";
234 else
235 (* Skip over this entry *)
236 let () = really_input inx head 0 Marshal.header_size in
237 let size = Marshal.data_size head 0 in
238 let pos = pos_in inx + size in
239 seek_in inx pos;
240 UnmarshalNext
244 * Search through the entries. If an exception is raised,
245 * truncate the file at the start of the entry.
247 let rec search () =
248 let start = pos_in inx in
249 let code =
250 try unmarshal_entry () with
251 End_of_file
252 | Failure _
253 | Sys_error _
254 | Invalid_argument _ ->
255 if !debug_db then
256 eprintf "Lm_db.find: %s: failed@." filename;
257 seek_and_truncate fd start;
258 raise Not_found
260 match code with
261 UnmarshalValue x ->
263 | UnmarshalNext ->
264 search ()
266 if unmarshal_magic inx then
267 search ()
268 else
269 raise Not_found
271 let find fd filename (tag, host_mode) magic digest =
272 let test tag' strings host' digest' =
273 match strings with
274 ["MAGIC", magic'] ->
275 tag' = tag && magic' = magic && digest' = digest && (host_mode = HostIndependent || host' = hostname)
276 | _ ->
277 false
279 find_entry fd filename test
282 * Remove an entry. Search through the existing entries
283 * to find one with the same tag. If the host is significant,
284 * remove only the entry with the same hostname. Otherwise,
285 * remove the entry with the same magic number.
287 let marshal_magic fd =
288 seek_and_truncate fd 0;
289 let outx = Unix.out_channel_of_descr fd in
290 output_binary_int outx magic;
291 Pervasives.flush outx
293 let remove_entry fd filename test =
294 let head = String.create Marshal.header_size in
296 (* Find the appropriate entry *)
297 let unmarshal_entry inx =
298 (* Get the header *)
299 let tag = unmarshal_tag inx in
300 let host = unmarshal_string inx in
301 let strings = unmarshal_strings inx tag in
302 let digest = unmarshal_digest inx in
303 let () = really_input inx head 0 Marshal.header_size in
304 let size = Marshal.data_size head 0 in
305 let pos = pos_in inx + size in
306 if test tag strings host digest then
307 RemoveEntry pos
308 else begin
309 seek_in inx pos;
310 RemoveNext
315 * Search through the entries. If an exception is raised,
316 * truncate the file at the start of the entry.
318 let rec search inx =
319 let start = pos_in inx in
320 let code =
321 try unmarshal_entry inx with
322 End_of_file
323 | Failure _
324 | Sys_error _
325 | Invalid_argument _ ->
326 RemoveRest
328 match code with
329 RemoveEntry pos ->
330 remove_entry fd start pos;
331 ignore(Unix.lseek fd 0 Unix.SEEK_SET);
332 let inx = Unix.in_channel_of_descr fd in
333 seek_in inx start;
334 search inx
335 | RemoveNext ->
336 search inx
337 | RemoveRest ->
338 seek_and_truncate fd start
340 let _ = Unix.lseek fd 0 Unix.SEEK_SET in
341 let inx = Unix.in_channel_of_descr fd in
342 if unmarshal_magic inx then
343 search inx
344 else
345 marshal_magic fd
347 let remove fd filename (tag, host_mode) magic =
348 let test tag' strings host' digest' =
349 match strings with
350 ["MAGIC", magic'] ->
351 tag' = tag && (host' = hostname || host_mode = HostIndependent && magic' = magic)
352 | _ ->
353 false
355 remove_entry fd filename test
358 * Add an entry.
359 * Remove any existing entry, and add the new one to the end of the
360 * file.
362 let marshal_tag outx tag =
363 output_binary_int outx tag
365 let marshal_digest outx digest =
366 assert (String.length digest = digest_length);
367 Pervasives.output_string outx digest
369 let marshal_string outx s =
370 let len = String.length s in
371 output_binary_int outx len;
372 Pervasives.output_string outx s
374 let marshal_strings outx sl =
375 let len =
376 List.fold_left (fun len (key, value) ->
377 len + String.length key + String.length value + 8) 4 sl
379 output_binary_int outx len;
380 output_binary_int outx (List.length sl);
381 List.iter (fun (key, value) ->
382 marshal_string outx key;
383 marshal_string outx value) sl
385 let marshal_entry fd filename tag magic_number digest x =
386 let outx = Unix.out_channel_of_descr fd in
387 marshal_tag outx tag;
388 marshal_string outx hostname;
389 marshal_string outx magic_number;
390 marshal_digest outx digest;
391 if !debug_db then
392 eprintf "@[<v 3>Marshal.to_channel: %s@ tag/digest: %d/%s@]@." (**)
393 filename
394 tag (Lm_string_util.hexify digest);
395 Marshal.to_channel outx x [];
396 if !debug_db then
397 eprintf "Marshal.to_channel: %s: done@." filename;
398 Pervasives.flush outx
400 let add fd filename ((code, _) as tag) magic digest x =
401 remove fd filename tag magic;
402 marshal_entry fd filename code magic digest x
404 let append_entry fd filename tag strings digest x =
405 let _ = Unix.lseek fd 0 Unix.SEEK_END in
406 let outx = Unix.out_channel_of_descr fd in
407 marshal_tag outx tag;
408 marshal_string outx hostname;
409 marshal_strings outx strings;
410 marshal_digest outx digest;
411 if !debug_db then
412 eprintf "@[<v 3>Marshal.to_channel: %s@ tag/digest: %d/%s@]@." (**)
413 filename
414 tag (Lm_string_util.hexify digest);
415 Marshal.to_channel outx x [];
416 if !debug_db then
417 eprintf "Marshal.to_channel: %s: done@." filename;
418 Pervasives.flush outx
421 * -*-
422 * Local Variables:
423 * End:
424 * -*-