Pino Toscano
2015-Aug-12  13:12 UTC
[Libguestfs] [PATCH 0/2 v2] RFC: builder: support for Simple Streams metadata
Hi, this series adds a basic support for Simple Streams v1.0 metadata files. This makes it possible to create a repository .conf files with [cirros] uri=http://download.cirros-cloud.net format=simplestreams to read the latest version of each CirrOS image. TODO items: - check the pasted metadata: listing and creating images works, so most of the current metadata is correct - possibly wait for normalization patches: https://www.redhat.com/archives/libguestfs/2015-August/msg00058.html Thanks, Pino Toscano (2): builder: add non-int revisions builder: support Simple Streams v1.0 as index metadata builder/Makefile.am | 13 ++- builder/builder.ml | 11 +- builder/cache.ml | 2 +- builder/cache.mli | 6 +- builder/downloader.mli | 2 +- builder/index.ml | 4 +- builder/index.mli | 2 +- builder/index_parser.ml | 4 +- builder/simplestreams_parser.ml | 213 +++++++++++++++++++++++++++++++++++++++ builder/simplestreams_parser.mli | 19 ++++ builder/sources.ml | 9 ++ builder/sources.mli | 1 + builder/utils.ml | 7 ++ builder/virt-builder.pod | 7 ++ builder/yajl-c.c | 143 ++++++++++++++++++++++++++ builder/yajl.ml | 30 ++++++ builder/yajl.mli | 33 ++++++ po/POTFILES | 1 + po/POTFILES-ml | 2 + 19 files changed, 495 insertions(+), 14 deletions(-) create mode 100644 builder/simplestreams_parser.ml create mode 100644 builder/simplestreams_parser.mli create mode 100644 builder/yajl-c.c create mode 100644 builder/yajl.ml create mode 100644 builder/yajl.mli -- 2.1.0
Pino Toscano
2015-Aug-12  13:12 UTC
[Libguestfs] [PATCH 1/2] builder: add non-int revisions
Add support for non-integer revisions of entries, comparing them as
integer when possible.
---
 builder/builder.ml      | 9 ++++++++-
 builder/cache.ml        | 2 +-
 builder/cache.mli       | 6 +++---
 builder/downloader.mli  | 2 +-
 builder/index.ml        | 4 ++--
 builder/index.mli       | 2 +-
 builder/index_parser.ml | 4 ++--
 builder/utils.ml        | 7 +++++++
 8 files changed, 25 insertions(+), 11 deletions(-)
diff --git a/builder/builder.ml b/builder/builder.ml
index adfa412..260af94 100644
--- a/builder/builder.ml
+++ b/builder/builder.ml
@@ -34,6 +34,13 @@ open Printf
 let () = Random.self_init ()
 
 let remove_duplicates index +  let compare_revisions rev1 rev2 +    match rev1,
rev2 with
+    | Rev_int n1, Rev_int n2 -> compare n1 n2
+    | Rev_string s1, Rev_int n2 -> compare s1 (string_of_int n2)
+    | Rev_int n1, Rev_string s2 -> compare (string_of_int n1) s2
+    | Rev_string s1, Rev_string s2 -> compare s1 s2
+  in
   (* Fill an hash with the higher revision of the available
    * (name, arch) tuples, so it possible to ignore duplicates,
    * and versions with a lower revision.
@@ -44,7 +51,7 @@ let remove_duplicates index        let id = name, arch in
       try
         let rev = Hashtbl.find nseen id in
-        if revision > rev then
+        if compare_revisions rev revision > 0 then
           Hashtbl.replace nseen id revision
       with Not_found ->
         Hashtbl.add nseen id revision
diff --git a/builder/cache.ml b/builder/cache.ml
index e73bcfd..8b63a86 100644
--- a/builder/cache.ml
+++ b/builder/cache.ml
@@ -40,7 +40,7 @@ let create ~directory    }
 
 let cache_of_name t name arch revision -  t.directory // sprintf
"%s.%s.%d" name arch revision
+  t.directory // sprintf "%s.%s.%s" name arch (string_of_revision
revision)
 
 let is_cached t name arch revision    let filename = cache_of_name t name arch
revision in
diff --git a/builder/cache.mli b/builder/cache.mli
index 7edc670..1ab8ede 100644
--- a/builder/cache.mli
+++ b/builder/cache.mli
@@ -27,16 +27,16 @@ type t
 val create : directory:string -> t
 (** Create the abstract type. *)
 
-val cache_of_name : t -> string -> string -> int -> string
+val cache_of_name : t -> string -> string -> Utils.revision ->
string
 (** [cache_of_name t name arch revision] return the filename
     of the cached file.  (Note: It doesn't check if the filename
     exists, this is just a simple string transformation). *)
 
-val is_cached : t -> string -> string -> int -> bool
+val is_cached : t -> string -> string -> Utils.revision -> bool
 (** [is_cached t name arch revision] return whether the file with
     specified name, architecture and revision is cached. *)
 
-val print_item_status : t -> header:bool -> (string * string * int) list
-> unit
+val print_item_status : t -> header:bool -> (string * string *
Utils.revision) list -> unit
 (** [print_item_status t header items] print the status in the cache
     of the specified items (which are tuples of name, architecture,
     and revision).
diff --git a/builder/downloader.mli b/builder/downloader.mli
index 5e3cdaa..11ec498 100644
--- a/builder/downloader.mli
+++ b/builder/downloader.mli
@@ -35,7 +35,7 @@ type proxy_mode  val create : curl:string -> cache:Cache.t
option -> t
 (** Create the abstract type. *)
 
-val download : t -> ?template:(string*string*int) -> ?progress_bar:bool
-> ?proxy:proxy_mode -> uri -> (filename * bool)
+val download : t -> ?template:(string*string*Utils.revision) ->
?progress_bar:bool -> ?proxy:proxy_mode -> uri -> (filename * bool)
 (** Download the URI, returning the downloaded filename and a
     temporary file flag.  The temporary file flag is [true] iff
     the downloaded file is temporary and should be deleted by the
diff --git a/builder/index.ml b/builder/index.ml
index 3e8cb85..c59d6dd 100644
--- a/builder/index.ml
+++ b/builder/index.ml
@@ -32,7 +32,7 @@ and entry = {
   arch : string;
   signature_uri : string option;        (* deprecated, will be removed in 1.26
*)
   checksums : Checksums.csum_t list option;
-  revision : int;
+  revision : Utils.revision;
   format : string option;
   size : int64;
   compressed_size : int64 option;
@@ -86,7 +86,7 @@ let print_entry chan (name, { printable_name = printable_name;
           (Checksums.string_of_csum_t c) (Checksums.string_of_csum c)
     ) checksums
   );
-  fp "revision=%d\n" revision;
+  fp "revision=%s\n" (string_of_revision revision);
   (match format with
   | None -> ()
   | Some format -> fp "format=%s\n" format
diff --git a/builder/index.mli b/builder/index.mli
index 10ed15a..fadcad9 100644
--- a/builder/index.mli
+++ b/builder/index.mli
@@ -24,7 +24,7 @@ and entry = {
   arch : string;
   signature_uri : string option;        (* deprecated, will be removed in 1.26
*)
   checksums : Checksums.csum_t list option;
-  revision : int;
+  revision : Utils.revision;
   format : string option;
   size : int64;
   compressed_size : int64 option;
diff --git a/builder/index_parser.ml b/builder/index_parser.ml
index 845d0e9..2c78fd9 100644
--- a/builder/index_parser.ml
+++ b/builder/index_parser.ml
@@ -112,9 +112,9 @@ let get_index ~downloader ~sigchecker
               try Some (List.assoc ("checksum", None) fields)
               with Not_found -> None in
           let revision -            try int_of_string (List.assoc
("revision", None) fields)
+            try Rev_int (int_of_string (List.assoc ("revision", None)
fields))
             with
-            | Not_found -> 1
+            | Not_found -> Rev_int 1
             | Failure "int_of_string" ->
               eprintf (f_"%s: cannot parse 'revision' field for
'%s'\n") prog n;
               corrupt_file () in
diff --git a/builder/utils.ml b/builder/utils.ml
index a6628eb..986bf68 100644
--- a/builder/utils.ml
+++ b/builder/utils.ml
@@ -26,5 +26,12 @@ type gpgkey_type    | No_Key
   | Fingerprint of string
   | KeyFile of string
+and revision +  | Rev_int of int
+  | Rev_string of string
 
 let quote = Filename.quote
+
+let string_of_revision = function
+  | Rev_int n -> string_of_int n
+  | Rev_string s -> s
-- 
2.1.0
Pino Toscano
2015-Aug-12  13:12 UTC
[Libguestfs] [PATCH 2/2] builder: support Simple Streams v1.0 as index metadata
Add a simple YAJL<->OCaml bridge to expose the JSON parsing function,
and use it to parse the JSON indexes of the Simple Streams v1.0 format.
Read only datatype=image-downloads contents, and only the latest
versions of each content available as disk image (disk.img or
disk1.img).
---
 builder/Makefile.am              |  13 ++-
 builder/builder.ml               |   2 +
 builder/simplestreams_parser.ml  | 213 +++++++++++++++++++++++++++++++++++++++
 builder/simplestreams_parser.mli |  19 ++++
 builder/sources.ml               |   9 ++
 builder/sources.mli              |   1 +
 builder/virt-builder.pod         |   7 ++
 builder/yajl-c.c                 | 143 ++++++++++++++++++++++++++
 builder/yajl.ml                  |  30 ++++++
 builder/yajl.mli                 |  33 ++++++
 po/POTFILES                      |   1 +
 po/POTFILES-ml                   |   2 +
 12 files changed, 470 insertions(+), 3 deletions(-)
 create mode 100644 builder/simplestreams_parser.ml
 create mode 100644 builder/simplestreams_parser.mli
 create mode 100644 builder/yajl-c.c
 create mode 100644 builder/yajl.ml
 create mode 100644 builder/yajl.mli
diff --git a/builder/Makefile.am b/builder/Makefile.am
index f48efb0..3ab990e 100644
--- a/builder/Makefile.am
+++ b/builder/Makefile.am
@@ -48,7 +48,9 @@ SOURCES_MLI = \
 	pxzcat.mli \
 	setlocale.mli \
 	sigchecker.mli \
-	sources.mli
+	simplestreams_parser.mli \
+	sources.mli \
+	yajl.mli
 
 SOURCES_ML = \
 	utils.ml \
@@ -57,6 +59,7 @@ SOURCES_ML = \
 	checksums.ml \
 	index.ml \
 	ini_reader.ml \
+	yajl.ml \
 	paths.ml \
 	languages.ml \
 	cache.ml \
@@ -64,6 +67,7 @@ SOURCES_ML = \
 	downloader.ml \
 	sigchecker.ml \
 	index_parser.ml \
+	simplestreams_parser.ml \
 	list_entries.ml \
 	cmdline.ml \
 	builder.ml
@@ -81,7 +85,8 @@ SOURCES_C = \
 	index-parse.c \
 	index-parser-c.c \
 	pxzcat-c.c \
-	setlocale-c.c
+	setlocale-c.c \
+	yajl-c.c
 
 man_MANS  noinst_DATA @@ -106,7 +111,8 @@ virt_builder_CFLAGS = \
 	-Wno-unused-macros \
 	$(LIBLZMA_CFLAGS) \
 	$(LIBTINFO_CFLAGS) \
-	$(LIBXML2_CFLAGS)
+	$(LIBXML2_CFLAGS) \
+	$(YAJL_CFLAGS)
 
 BOBJECTS = \
 	$(top_builddir)/mllib/libdir.cmo \
@@ -156,6 +162,7 @@ OCAMLCLIBS = \
 	$(LIBCRYPT_LIBS) \
 	$(LIBLZMA_LIBS) \
 	$(LIBXML2_LIBS) \
+	$(YAJL_LIBS) \
 	$(LIBINTL) \
 	-lgnu
 
diff --git a/builder/builder.ml b/builder/builder.ml
index 260af94..5834259 100644
--- a/builder/builder.ml
+++ b/builder/builder.ml
@@ -182,6 +182,8 @@ let main ()            match source.Sources.format with
           | Sources.FormatNative ->
             Index_parser.get_index ~downloader ~sigchecker source
+          | Sources.FormatSimpleStreams ->
+            Simplestreams_parser.get_index ~downloader ~sigchecker source
       ) sources
     ) in
   let index = remove_duplicates index in
diff --git a/builder/simplestreams_parser.ml b/builder/simplestreams_parser.ml
new file mode 100644
index 0000000..afbf5b0
--- /dev/null
+++ b/builder/simplestreams_parser.ml
@@ -0,0 +1,213 @@
+(* virt-builder
+ * Copyright (C) 2015 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 Common_gettext.Gettext
+open Common_utils
+
+open Yajl
+open Utils
+
+open Printf
+
+let ensure_trailing_slash str +  if String.length str > 0 &&
str.[String.length str - 1] <> '/' then str ^ "/"
+  else str
+
+let object_find_optional key = function
+  | Yajl_object o ->
+    (match List.filter (fun (k, _) -> k = key) (Array.to_list o) with
+    | [(k, v)] -> Some v
+    | [] -> None
+    | _ -> error (f_"more than value for the key '%s'")
key)
+  | _ -> error (f_"the value of the key '%s' is not an
object") key
+
+let object_find key yv +  (match object_find_optional key yv with
+  | None -> error (f_"missing value for the key '%s'") key
+  | Some v -> v
+  )
+
+let object_get_string key yv +  (match object_find key yv with
+  | Yajl_string s -> s
+  | _ -> error (f_"the value for the key '%s' is not a
string") key
+  )
+
+let object_find_object key yv +  (match object_find key yv with
+  | Yajl_object _ as o -> o
+  | _ -> error (f_"the value for the key '%s' is not an
object") key
+  )
+
+let object_find_objects fn = function
+  | Yajl_object o -> filter_map fn (Array.to_list o)
+  | _ -> error (f_"the value is not an object")
+
+let object_get_object key yv +  (match object_find_object key yv with
+  | Yajl_object o -> o
+  | _ -> assert false (* object_find_object already errors out. *)
+  )
+
+let object_get_number key yv +  (match object_find key yv with
+  | Yajl_number n -> n
+  | Yajl_double d -> Int64.of_float d
+  | _ -> error (f_"the value for the key '%s' is not an
integer") key
+  )
+
+let objects_get_string key yvs +  let rec loop = function
+    | [] -> None
+    | x :: xs ->
+      (match object_find_optional key x with
+      | Some (Yajl_string s) -> Some s
+      | Some _ -> error (f_"the value for key '%s' is not a
string as expected") key
+      | None -> loop xs
+      )
+  in
+  match loop yvs with
+  | Some s -> s
+  | None -> error (f_"the key '%s' was not found in a list of
objects") key
+
+let get_index ~downloader ~sigchecker
+  { Sources.uri = uri; proxy = proxy } +
+  let uri = ensure_trailing_slash uri in
+
+  let download_and_parse uri +    let tmpfile, delete_tmpfile =
Downloader.download downloader ~proxy uri in
+    if delete_tmpfile then
+      unlink_on_exit tmpfile;
+    let file +      if Sigchecker.verifying_signatures sigchecker then (
+        let tmpunsigned +          Sigchecker.verify_and_remove_signature
sigchecker tmpfile in
+        match tmpunsigned with
+        | None -> assert false (* only when not verifying signatures *)
+        | Some f -> f
+      ) else
+        tmpfile in
+    yajl_tree_parse (read_whole_file file) in
+
+  let downloads +    let uri_index +      if Sigchecker.verifying_signatures
sigchecker then
+        uri ^ "streams/v1/index.sjson"
+      else
+        uri ^ "streams/v1/index.json" in
+    let tree = download_and_parse uri_index in
+
+    let format = object_get_string "format" tree in
+    if format <> "index:1.0" then
+      error (f_"%s is not a Simple Streams (index) v1.0 JSON file (format:
%s)")
+        uri format;
+
+    let index = Array.to_list (object_get_object "index" tree) in
+    filter_map (
+      fun (_, desc) ->
+        let format = object_get_string "format" desc in
+        let datatype = object_get_string "datatype" desc in
+        match format, datatype with
+        | "products:1.0", "image-downloads" ->
+          Some (object_get_string "path" desc)
+        | _ -> None
+    ) index in
+
+  let scan_product_list path +    let tree = download_and_parse (uri ^ path) in
+
+    let format = object_get_string "format" tree in
+    if format <> "products:1.0" then
+      error (f_"%s is not a Simple Streams (products) v1.0 JSON file
(format: %s)")
+        uri format;
+
+    let products_node = object_get_object "products" tree in
+
+    let products = Array.to_list products_node in
+    filter_map (
+      fun (prod, prod_desc) ->
+        let arch +          (* XXX *)
+          match object_get_string "arch" prod_desc with
+          | "amd64" -> "x86_64"
+          | a -> a in
+        let prods = Array.to_list (object_get_object "versions"
prod_desc) in
+        let prods = filter_map (
+          fun (rel, rel_desc) ->
+            let pubname = objects_get_string "pubname" [rel_desc;
prod_desc] in
+            let items = object_find_object "items" rel_desc in
+            let disk_items = object_find_objects (
+              function
+              | (("disk.img"|"disk1.img"), v) -> Some v
+              | _ -> None
+            ) items in
+            (match disk_items with
+            | [] -> None
+            | disk_item :: _ ->
+              let printable_name = Some pubname in
+              let file_uri = uri ^ (object_get_string "path"
disk_item) in
+              let checksums +                let checksums =
object_find_objects (
+                  function
+                  | ("sha256", Yajl_string c) -> Some
(Checksums.SHA256 c)
+                  | ("sha512", Yajl_string c) -> Some
(Checksums.SHA512 c)
+                  | _ -> None
+                ) disk_item in
+                match checksums with
+                | [] -> None
+                | x -> Some x in
+              let revision = Rev_string rel in
+              let size = object_get_number "size" disk_item in
+              let aliases = Some [pubname;] in
+
+              let entry = { Index.printable_name = printable_name;
+                            osinfo = None;
+                            file_uri = file_uri;
+                            arch = arch;
+                            signature_uri = None;
+                            checksums = checksums;
+                            revision = revision;
+                            format = None;
+                            size = size;
+                            compressed_size = None;
+                            expand = None;
+                            lvexpand = None;
+                            notes = [];
+                            hidden = false;
+                            aliases = aliases;
+                            sigchecker = sigchecker;
+                            proxy = proxy; } in
+              Some (rel, (prod, entry))
+            )
+        ) prods in
+        (* Select the disk image with the bigger version (i.e. usually
+         * the most recent one. *)
+        let reverse_revision_compare (rev1, _) (rev2, _) = compare rev2 rev1 in
+        let prods = List.sort reverse_revision_compare prods in
+        match prods with
+        | [] -> None
+        | (_, entry) :: _ -> Some entry
+    ) products in
+
+  let entries = List.flatten (List.map scan_product_list downloads) in
+  if verbose () then (
+    printf "simplestreams tree (%s) after parsing:\n" uri;
+    List.iter (Index.print_entry Pervasives.stdout) entries
+  );
+  entries
diff --git a/builder/simplestreams_parser.mli b/builder/simplestreams_parser.mli
new file mode 100644
index 0000000..a4b91ba
--- /dev/null
+++ b/builder/simplestreams_parser.mli
@@ -0,0 +1,19 @@
+(* virt-builder
+ * Copyright (C) 2015 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.
+ *)
+
+val get_index : downloader:Downloader.t -> sigchecker:Sigchecker.t ->
Sources.source -> Index.index
diff --git a/builder/sources.ml b/builder/sources.ml
index b21e8fc..149db6f 100644
--- a/builder/sources.ml
+++ b/builder/sources.ml
@@ -31,6 +31,7 @@ type source = {
 }
 and source_format  | FormatNative
+| FormatSimpleStreams
 
 module StringSet = Set.Make (String)
 
@@ -82,6 +83,14 @@ let parse_conf file            try
             (match (List.assoc ("format", None) fields) with
             | "native" | "" -> FormatNative
+            | "simplestreams" as fmt ->
+              if not (Yajl.yajl_is_available ()) then (
+                if verbose () then (
+                  eprintf (f_"%s: repository type '%s' not
supported (missing YAJL support), skipping it\n") prog fmt;
+                );
+                invalid_arg fmt
+              ) else
+                FormatSimpleStreams
             | fmt ->
               if verbose () then (
                 eprintf (f_"%s: unknown repository type '%s' in
%s, skipping it\n") prog fmt file;
diff --git a/builder/sources.mli b/builder/sources.mli
index e861310..e621a9f 100644
--- a/builder/sources.mli
+++ b/builder/sources.mli
@@ -25,5 +25,6 @@ type source = {
 }
 and source_format  | FormatNative
+| FormatSimpleStreams
 
 val read_sources : unit -> source list
diff --git a/builder/virt-builder.pod b/builder/virt-builder.pod
index fc49d4d..0de643a 100644
--- a/builder/virt-builder.pod
+++ b/builder/virt-builder.pod
@@ -1181,6 +1181,13 @@ The possible values are:
 The native format of the C<virt-builder> repository.  See also
 L</Creating and signing the index file> below.
 
+=item B<simplestreams>
+
+The URI represents the root of a Simple Streams v1.0 tree of metadata.
+
+For more information about Simple Streams, see also
+L<https://launchpad.net/simplestreams>.
+
 =back
 
 If not present, the assumed value is C<native>.
diff --git a/builder/yajl-c.c b/builder/yajl-c.c
new file mode 100644
index 0000000..6a96d59
--- /dev/null
+++ b/builder/yajl-c.c
@@ -0,0 +1,143 @@
+/* virt-builder
+ * Copyright (C) 2015 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.
+ */
+
+#include <config.h>
+
+#include <caml/alloc.h>
+#include <caml/fail.h>
+#include <caml/memory.h>
+#include <caml/mlvalues.h>
+
+#if HAVE_YAJL
+#include <yajl/yajl_tree.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+
+#define Val_none (Val_int (0))
+
+extern value virt_builder_yajl_is_available (value unit);
+extern value virt_builder_yajl_tree_parse (value stringv);
+
+#if HAVE_YAJL
+static value
+convert_yajl_value (yajl_val val, int level)
+{
+  CAMLparam0 ();
+  CAMLlocal4 (rv, lv, v, sv);
+
+  if (level > 20)
+    caml_invalid_argument ("too many levels of object/array
nesting");
+
+  if (YAJL_IS_OBJECT (val)) {
+    size_t len = YAJL_GET_OBJECT(val)->len;
+    size_t i;
+    rv = caml_alloc (1, 3);
+    lv = caml_alloc_tuple (len);
+    for (i = 0; i < len; ++i) {
+      v = caml_alloc_tuple (2);
+      sv = caml_copy_string (YAJL_GET_OBJECT(val)->keys[i]);
+      Store_field (v, 0, sv);
+      sv = convert_yajl_value (YAJL_GET_OBJECT(val)->values[i], level + 1);
+      Store_field (v, 1, sv);
+      Store_field (lv, i, v);
+    }
+    Store_field (rv, 0, lv);
+  } else if (YAJL_IS_ARRAY (val)) {
+    size_t len = YAJL_GET_ARRAY(val)->len;
+    size_t i;
+    rv = caml_alloc (1, 4);
+    lv = caml_alloc_tuple (len);
+    for (i = 0; i < len; ++i) {
+      v = convert_yajl_value (YAJL_GET_ARRAY(val)->values[i], level + 1);
+      Store_field (lv, i, v);
+    }
+    Store_field (rv, 0, lv);
+  } else if (YAJL_IS_STRING (val)) {
+    rv = caml_alloc (1, 0);
+    v = caml_copy_string (YAJL_GET_STRING(val));
+    Store_field (rv, 0, v);
+  } else if (YAJL_IS_DOUBLE (val)) {
+    rv = caml_alloc (1, 2);
+    lv = caml_alloc_tuple (1);
+    Store_double_field (lv, 0, YAJL_GET_DOUBLE(val));
+    Store_field (rv, 0, lv);
+  } else if (YAJL_IS_INTEGER (val)) {
+    rv = caml_alloc (1, 1);
+    v = caml_copy_int64 (YAJL_GET_INTEGER(val));
+    Store_field (rv, 0, v);
+  } else if (YAJL_IS_TRUE (val)) {
+    rv = caml_alloc (1, 5);
+    Store_field (rv, 0, Val_true);
+  } else if (YAJL_IS_FALSE (val)) {
+    rv = caml_alloc (1, 5);
+    Store_field (rv, 0, Val_false);
+  } else
+    rv = Val_none;
+
+  CAMLreturn (rv);
+}
+
+value
+virt_builder_yajl_is_available (value unit)
+{
+  /* NB: noalloc */
+  return Val_true;
+}
+
+value
+virt_builder_yajl_tree_parse (value stringv)
+{
+  CAMLparam1 (stringv);
+  CAMLlocal1 (rv);
+  yajl_val tree;
+  char error_buf[256];
+
+  tree = yajl_tree_parse (String_val (stringv), error_buf, sizeof error_buf);
+  if (tree == NULL) {
+    char buf[256 + sizeof error_buf];
+    if (strlen (error_buf) > 0)
+      snprintf (buf, sizeof buf, "JSON parse error: %s", error_buf);
+    else
+      snprintf (buf, sizeof buf, "unknown JSON parse error");
+    caml_invalid_argument (buf);
+  }
+
+  rv = convert_yajl_value (tree, 1);
+  yajl_tree_free (tree);
+
+  CAMLreturn (rv);
+}
+
+#else
+
+value
+virt_builder_yajl_is_available (value unit)
+{
+  /* NB: noalloc */
+  return Val_false;
+}
+
+value
+virt_builder_yajl_tree_parse (value stringv)
+{
+  caml_invalid_argument ("virt-builder was compiled without yajl
support");
+}
+
+#endif
diff --git a/builder/yajl.ml b/builder/yajl.ml
new file mode 100644
index 0000000..f2d5c2b
--- /dev/null
+++ b/builder/yajl.ml
@@ -0,0 +1,30 @@
+(* virt-builder
+ * Copyright (C) 2015 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.
+ *)
+
+type yajl_val +| Yajl_null
+| Yajl_string of string
+| Yajl_number of int64
+| Yajl_double of float
+| Yajl_object of (string * yajl_val) array
+| Yajl_array of yajl_val array
+| Yajl_bool of bool
+
+external yajl_is_available : unit -> bool =
"virt_builder_yajl_is_available" "noalloc"
+
+external yajl_tree_parse : string -> yajl_val =
"virt_builder_yajl_tree_parse"
diff --git a/builder/yajl.mli b/builder/yajl.mli
new file mode 100644
index 0000000..aaa9389
--- /dev/null
+++ b/builder/yajl.mli
@@ -0,0 +1,33 @@
+(* virt-builder
+ * Copyright (C) 2015 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.
+ *)
+
+type yajl_val +| Yajl_null
+| Yajl_string of string
+| Yajl_number of int64
+| Yajl_double of float
+| Yajl_object of (string * yajl_val) array
+| Yajl_array of yajl_val array
+| Yajl_bool of bool
+
+val yajl_is_available : unit -> bool
+(** Is YAJL built in? If not, calling any of the other yajl_*
+    functions will result in an error. *)
+
+val yajl_tree_parse : string -> yajl_val
+(** Parse the JSON string. *)
diff --git a/po/POTFILES b/po/POTFILES
index 7f1580c..8fb68a4 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -6,6 +6,7 @@ builder/index-struct.c
 builder/index-validate.c
 builder/pxzcat-c.c
 builder/setlocale-c.c
+builder/yajl-c.c
 cat/cat.c
 cat/filesystems.c
 cat/log.c
diff --git a/po/POTFILES-ml b/po/POTFILES-ml
index 7933c8e..87f10ee 100644
--- a/po/POTFILES-ml
+++ b/po/POTFILES-ml
@@ -12,8 +12,10 @@ builder/paths.ml
 builder/pxzcat.ml
 builder/setlocale.ml
 builder/sigchecker.ml
+builder/simplestreams_parser.ml
 builder/sources.ml
 builder/utils.ml
+builder/yajl.ml
 customize/crypt.ml
 customize/customize_cmdline.ml
 customize/customize_main.ml
-- 
2.1.0
Richard W.M. Jones
2015-Aug-12  16:08 UTC
Re: [Libguestfs] [PATCH 1/2] builder: add non-int revisions
On Wed, Aug 12, 2015 at 03:12:21PM +0200, Pino Toscano wrote:> Add support for non-integer revisions of entries, comparing them as > integer when possible.Have you got an example of revisions? It may be possible to use Common_utils.compare_version which does RPM revision-style comparison. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com virt-df lists disk usage of guests without needing to install any software inside the virtual machine. Supports Linux and Windows. http://people.redhat.com/~rjones/virt-df/
Richard W.M. Jones
2015-Aug-12  16:28 UTC
Re: [Libguestfs] [PATCH 2/2] builder: support Simple Streams v1.0 as index metadata
On Wed, Aug 12, 2015 at 03:12:22PM +0200, Pino Toscano wrote:> +let object_find key yv > + (match object_find_optional key yv with > + | None -> error (f_"missing value for the key '%s'") key > + | Some v -> v > + )You don't need the extra parentheses around the match expr. Could the yajl bindings be added in a separate patch? Also be nice to have a little unit test for the yajl bindings. I wasn't able to spot any bugs in the bindings. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com virt-top is 'top' for virtual machines. Tiny program with many powerful monitoring features, net stats, disk stats, logging, etc. http://people.redhat.com/~rjones/virt-top
Apparently Analagous Threads
- [PATCH 0/4 v3] builder: support for Simple Streams metadata
- [PATCH 0/4] mltools: JSON unification
- [PATCH 00/10] RFC: builder: first support for Simple Streams metadata
- [PATCH 0/5] Introducing virt-builder-repository
- [PATCH v3 4/4] v2v: Add --print-estimate option to print copy size