mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-22 07:03:38 +00:00
get_index now gets a new template parameter. Setting it to true will make the index parsing less picky about missing important data. This can be used to parse a partial index file.
307 lines
11 KiB
OCaml
307 lines
11 KiB
OCaml
(* virt-builder
|
||
* Copyright (C) 2013 Red Hat Inc.
|
||
*
|
||
* This program is free software; you can redistribute it and/or modify
|
||
* it under the terms of the GNU General Public License as published by
|
||
* the Free Software Foundation; either version 2 of the License, or
|
||
* (at your option) any later version.
|
||
*
|
||
* This program is distributed in the hope that it will be useful,
|
||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
* GNU General Public License for more details.
|
||
*
|
||
* You should have received a copy of the GNU General Public License along
|
||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||
*)
|
||
|
||
open Std_utils
|
||
open Tools_utils
|
||
open Common_gettext.Gettext
|
||
|
||
open Utils
|
||
|
||
open Printf
|
||
open Unix
|
||
|
||
let get_index ~downloader ~sigchecker ?(template = false) { Sources.uri; proxy } =
|
||
let corrupt_file () =
|
||
error (f_"The index file downloaded from ‘%s’ is corrupt.\nYou need to ask the supplier of this file to fix it and upload a fixed version.") uri
|
||
in
|
||
|
||
let rec get_index () =
|
||
(* Get the index page. *)
|
||
let tmpfile, _ = Downloader.download downloader ~proxy uri in
|
||
|
||
(* Check index file signature (also verifies it was fully
|
||
* downloaded and not corrupted in transit).
|
||
*)
|
||
Sigchecker.verify sigchecker tmpfile;
|
||
|
||
(* Try parsing the file. *)
|
||
let sections = Ini_reader.read_ini tmpfile in
|
||
|
||
(* Check for repeated os-version+arch combination. *)
|
||
let name_arch_map = List.map (
|
||
fun (n, fields) ->
|
||
let rec find_arch = function
|
||
| ("arch", None, value) :: y -> value
|
||
| _ :: y -> find_arch y
|
||
| [] -> ""
|
||
in
|
||
n, (find_arch fields)
|
||
) sections in
|
||
let nseen = Hashtbl.create 13 in
|
||
List.iter (
|
||
fun (n, arch) ->
|
||
let id = n, arch in
|
||
if Hashtbl.mem nseen id then (
|
||
eprintf (f_"%s: index is corrupt: os-version ‘%s’ with architecture ‘%s’ appears two or more times\n") prog n arch;
|
||
corrupt_file ()
|
||
);
|
||
Hashtbl.add nseen id true
|
||
) name_arch_map;
|
||
|
||
(* Check for repeated fields. *)
|
||
List.iter (
|
||
fun (n, fields) ->
|
||
let fseen = Hashtbl.create 13 in
|
||
List.iter (
|
||
fun (field, subkey, _) ->
|
||
let hashkey = (field, subkey) in
|
||
if Hashtbl.mem fseen hashkey then (
|
||
(match subkey with
|
||
| Some value ->
|
||
eprintf (f_"%s: index is corrupt: %s: field ‘%s[%s]’ appears two or more times\n") prog n field value
|
||
| None ->
|
||
eprintf (f_"%s: index is corrupt: %s: field ‘%s’ appears two or more times\n") prog n field);
|
||
corrupt_file ()
|
||
);
|
||
Hashtbl.add fseen hashkey true
|
||
) fields
|
||
) sections;
|
||
|
||
(* Turn the sections into the final index. *)
|
||
let entries =
|
||
List.map (
|
||
fun (n, fields) ->
|
||
let fields = List.map (fun (k, sk, v) -> (k, sk), v) fields in
|
||
let printable_name =
|
||
try Some (List.assoc ("name", None) fields) with Not_found -> None in
|
||
let osinfo =
|
||
try Some (List.assoc ("osinfo", None) fields) with Not_found -> None in
|
||
let file_uri =
|
||
try make_absolute_uri (List.assoc ("file", None) fields)
|
||
with Not_found ->
|
||
eprintf (f_"%s: no ‘file’ (URI) entry for ‘%s’\n") prog n;
|
||
corrupt_file () in
|
||
let arch =
|
||
try Index.Arch (List.assoc ("arch", None) fields)
|
||
with Not_found ->
|
||
if template then
|
||
let g = open_guestfs ~identifier:"template" () in
|
||
g#add_drive_ro file_uri;
|
||
g#launch ();
|
||
let roots = g#inspect_os () in
|
||
let nroots = Array.length roots in
|
||
if nroots <> 1 then (
|
||
eprintf (f_"%s: no ‘arch’ entry for %s and failed to guess it\n") prog n;
|
||
corrupt_file ()
|
||
);
|
||
let inspected_arch = g#inspect_get_arch (Array.get roots 0) in
|
||
g#close();
|
||
Index.GuessedArch inspected_arch
|
||
else (
|
||
eprintf (f_"%s: no ‘arch’ entry for ‘%s’\n") prog n;
|
||
corrupt_file ()
|
||
) in
|
||
let signature_uri =
|
||
try Some (make_absolute_uri (List.assoc ("sig", None) fields))
|
||
with Not_found -> None in
|
||
let checksum_sha512 =
|
||
try Some (List.assoc ("checksum", Some "sha512") fields)
|
||
with Not_found ->
|
||
try Some (List.assoc ("checksum", None) fields)
|
||
with Not_found -> None in
|
||
let revision =
|
||
try Rev_int (int_of_string (List.assoc ("revision", None) fields))
|
||
with
|
||
| Not_found -> if template then Rev_int 0 else Rev_int 1
|
||
| Failure _ ->
|
||
eprintf (f_"%s: cannot parse ‘revision’ field for ‘%s’\n") prog n;
|
||
corrupt_file () in
|
||
let format =
|
||
try Some (List.assoc ("format", None) fields) with Not_found -> None in
|
||
let size =
|
||
let get_image_size filepath =
|
||
(* If a compressed image manages to reach this code, qemu-img just
|
||
returns a virtual-size equal to actual-size *)
|
||
match detect_file_type filepath with
|
||
| `Unknown ->
|
||
let infos = Utils.get_image_infos filepath in
|
||
Yajl.object_get_number "virtual-size" infos
|
||
| `XZ | `GZip | `Tar | ` Zip ->
|
||
eprintf (f_"%s: cannot determine the virtual size of %s due to compression")
|
||
prog filepath;
|
||
corrupt_file () in
|
||
|
||
try Int64.of_string (List.assoc ("size", None) fields)
|
||
with
|
||
| Not_found ->
|
||
if template then
|
||
get_image_size file_uri
|
||
else (
|
||
eprintf (f_"%s: no ‘size’ field for ‘%s’\n") prog n;
|
||
corrupt_file ()
|
||
)
|
||
| Failure _ ->
|
||
if template then
|
||
get_image_size file_uri
|
||
else (
|
||
eprintf (f_"%s: cannot parse ‘size’ field for ‘%s’\n") prog n;
|
||
corrupt_file ()
|
||
) in
|
||
let compressed_size =
|
||
try Some (Int64.of_string (List.assoc ("compressed_size", None) fields))
|
||
with
|
||
| Not_found ->
|
||
None
|
||
| Failure _ ->
|
||
eprintf (f_"%s: cannot parse ‘compressed_size’ field for ‘%s’\n")
|
||
prog n;
|
||
corrupt_file () in
|
||
let expand =
|
||
try Some (List.assoc ("expand", None) fields) with Not_found -> None in
|
||
let lvexpand =
|
||
try Some (List.assoc ("lvexpand", None) fields) with Not_found -> None in
|
||
let notes =
|
||
let rec loop = function
|
||
| [] -> []
|
||
| (("notes", subkey), value) :: xs ->
|
||
let subkey = match subkey with
|
||
| None -> ""
|
||
| Some v -> v in
|
||
(subkey, value) :: loop xs
|
||
| _ :: xs -> loop xs in
|
||
List.sort (
|
||
fun (k1, _) (k2, _) ->
|
||
String.compare k1 k2
|
||
) (loop fields) in
|
||
let hidden =
|
||
try bool_of_string (List.assoc ("hidden", None) fields)
|
||
with
|
||
| Not_found -> false
|
||
| Failure _ ->
|
||
eprintf (f_"%s: cannot parse ‘hidden’ field for ‘%s’\n")
|
||
prog n;
|
||
corrupt_file () in
|
||
let aliases =
|
||
let l =
|
||
try String.nsplit " " (List.assoc ("aliases", None) fields)
|
||
with Not_found -> [] in
|
||
match l with
|
||
| [] -> None
|
||
| l -> Some l in
|
||
|
||
let checksums =
|
||
match checksum_sha512 with
|
||
| Some c -> Some [Checksums.SHA512 c]
|
||
| None -> None in
|
||
|
||
let entry = { Index.printable_name = printable_name;
|
||
osinfo = osinfo;
|
||
file_uri = file_uri;
|
||
arch = arch;
|
||
signature_uri = signature_uri;
|
||
checksums = checksums;
|
||
revision = revision;
|
||
format = format;
|
||
size = size;
|
||
compressed_size = compressed_size;
|
||
expand = expand;
|
||
lvexpand = lvexpand;
|
||
notes = notes;
|
||
hidden = hidden;
|
||
aliases = aliases;
|
||
proxy = proxy;
|
||
sigchecker = sigchecker } in
|
||
n, entry
|
||
) sections in
|
||
|
||
if verbose () then (
|
||
printf "index file (%s) after parsing (C parser):\n" uri;
|
||
List.iter (Index.print_entry Pervasives.stdout) entries
|
||
);
|
||
|
||
entries
|
||
|
||
(* Verify same-origin policy for the file= and sig= fields. *)
|
||
and make_absolute_uri path =
|
||
if String.length path = 0 then (
|
||
eprintf (f_"%s: zero length path in the index file\n") prog;
|
||
corrupt_file ()
|
||
)
|
||
else if String.find path "://" >= 0 then (
|
||
eprintf (f_"%s: cannot use a URI (‘%s’) in the index file\n") prog path;
|
||
corrupt_file ()
|
||
)
|
||
else if path.[0] = '/' then (
|
||
eprintf (f_"%s: you must use relative paths (not ‘%s’) in the index file\n") prog path;
|
||
corrupt_file ()
|
||
)
|
||
else (
|
||
(* Construct the URI. *)
|
||
try
|
||
let i = String.rindex uri '/' in
|
||
String.sub uri 0 (i+1) ^ path
|
||
with
|
||
Not_found -> uri // path
|
||
)
|
||
in
|
||
|
||
get_index ()
|
||
|
||
let write_entry chan (name, { Index.printable_name; file_uri; arch; osinfo;
|
||
signature_uri; checksums; revision; format; size;
|
||
compressed_size; expand; lvexpand; notes;
|
||
aliases; hidden}) =
|
||
let fp fs = fprintf chan fs in
|
||
fp "[%s]\n" name;
|
||
Option.may (fp "name=%s\n") printable_name;
|
||
Option.may (fp "osinfo=%s\n") osinfo;
|
||
fp "file=%s\n" file_uri;
|
||
fp "arch=%s\n" (Index.string_of_arch arch);
|
||
Option.may (fp "sig=%s\n") signature_uri;
|
||
(match checksums with
|
||
| None -> ()
|
||
| Some checksums ->
|
||
List.iter (
|
||
fun c ->
|
||
fp "checksum[%s]=%s\n"
|
||
(Checksums.string_of_csum_t c) (Checksums.string_of_csum c)
|
||
) checksums
|
||
);
|
||
fp "revision=%s\n" (string_of_revision revision);
|
||
Option.may (fp "format=%s\n") format;
|
||
fp "size=%Ld\n" size;
|
||
Option.may (fp "compressed_size=%Ld\n") compressed_size;
|
||
Option.may (fp "expand=%s\n") expand;
|
||
Option.may (fp "lvexpand=%s\n") lvexpand;
|
||
|
||
let format_notes notes =
|
||
String.concat "\n " (String.nsplit "\n" notes) in
|
||
|
||
List.iter (
|
||
fun (lang, notes) ->
|
||
match lang with
|
||
| "" -> fp "notes=%s\n" (format_notes notes)
|
||
| lang -> fp "notes[%s]=%s\n" lang (format_notes notes)
|
||
) notes;
|
||
(match aliases with
|
||
| None -> ()
|
||
| Some l -> fp "aliases=%s\n" (String.concat " " l)
|
||
);
|
||
if hidden then fp "hidden=true\n";
|
||
fp "\n"
|