Metadata parser.
authorRichard W.M. Jones <rjones@redhat.com>
Tue, 15 Apr 2008 17:30:49 +0000 (18:30 +0100)
committerRichard W.M. Jones <rjones@redhat.com>
Tue, 15 Apr 2008 17:30:49 +0000 (18:30 +0100)
.hgignore
Make.rules.in
virt-df/.depend
virt-df/Makefile.in
virt-df/virt_df_lvm2.ml
virt-df/virt_df_lvm2_lexer.mll [new file with mode: 0644]
virt-df/virt_df_lvm2_metadata.ml [new file with mode: 0644]
virt-df/virt_df_lvm2_metadata.mli [new file with mode: 0644]
virt-df/virt_df_lvm2_parser.mly [new file with mode: 0644]

index f8063da..f78c6f6 100644 (file)
--- a/.hgignore
+++ b/.hgignore
@@ -41,3 +41,6 @@ virt-df/virt_df_gettext.ml
 virt-top/virt_top_gettext.ml
 po/*.mo
 po/*.po.bak
+virt-df/virt_df_lvm2_lexer.ml
+virt-df/virt_df_lvm2_parser.ml
+virt-df/virt_df_lvm2_parser.mli
\ No newline at end of file
index b22fdf6..a25d485 100644 (file)
@@ -40,6 +40,11 @@ else
        $(OCAMLOPT) $(OCAMLOPTFLAGS) $(OCAMLOPTINCS) -c $<
 endif
 
+%.ml %.mli: %.mly
+       ocamlyacc $<
+.mll.ml:
+       ocamllex $<
+
 # Dependencies.
 
 depend: .depend
@@ -60,4 +65,4 @@ endif
 
 .PHONY: depend dist check-manifest dpkg doc
 
-.SUFFIXES:      .cmo .cmi .cmx .ml .mli .mll
+.SUFFIXES:      .cmo .cmi .cmx .ml .mli .mll .mly
index d253040..e7cd81e 100644 (file)
@@ -1,3 +1,4 @@
+virt_df_lvm2_parser.cmi: virt_df_lvm2_metadata.cmi 
 virt_df_ext2.cmo: virt_df_gettext.cmo virt_df.cmi \
     /usr/lib64/ocaml/bitmatch/bitmatch.cmi virt_df_ext2.cmi 
 virt_df_ext2.cmx: virt_df_gettext.cmx virt_df.cmx \
@@ -6,10 +7,18 @@ virt_df_linux_swap.cmo: virt_df_gettext.cmo virt_df.cmi \
     /usr/lib64/ocaml/bitmatch/bitmatch.cmi virt_df_linux_swap.cmi 
 virt_df_linux_swap.cmx: virt_df_gettext.cmx virt_df.cmx \
     /usr/lib64/ocaml/bitmatch/bitmatch.cmi virt_df_linux_swap.cmi 
-virt_df_lvm2.cmo: virt_df_gettext.cmo virt_df.cmi \
-    /usr/lib64/ocaml/bitmatch/bitmatch.cmi virt_df_lvm2.cmi 
-virt_df_lvm2.cmx: virt_df_gettext.cmx virt_df.cmx \
-    /usr/lib64/ocaml/bitmatch/bitmatch.cmi virt_df_lvm2.cmi 
+virt_df_lvm2_lexer.cmo: virt_df_lvm2_parser.cmi virt_df.cmi 
+virt_df_lvm2_lexer.cmx: virt_df_lvm2_parser.cmx virt_df.cmx 
+virt_df_lvm2_metadata.cmo: virt_df_lvm2_metadata.cmi 
+virt_df_lvm2_metadata.cmx: virt_df_lvm2_metadata.cmi 
+virt_df_lvm2.cmo: virt_df_lvm2_metadata.cmi virt_df_lvm2_lexer.cmo \
+    virt_df_gettext.cmo virt_df.cmi /usr/lib64/ocaml/bitmatch/bitmatch.cmi \
+    virt_df_lvm2.cmi 
+virt_df_lvm2.cmx: virt_df_lvm2_metadata.cmx virt_df_lvm2_lexer.cmx \
+    virt_df_gettext.cmx virt_df.cmx /usr/lib64/ocaml/bitmatch/bitmatch.cmi \
+    virt_df_lvm2.cmi 
+virt_df_lvm2_parser.cmo: virt_df_lvm2_metadata.cmi virt_df_lvm2_parser.cmi 
+virt_df_lvm2_parser.cmx: virt_df_lvm2_metadata.cmx virt_df_lvm2_parser.cmi 
 virt_df_main.cmo: virt_df_gettext.cmo virt_df.cmi \
     ../libvirt/libvirt_version.cmi ../libvirt/libvirt.cmi 
 virt_df_main.cmx: virt_df_gettext.cmx virt_df.cmx \
index 4a56d2d..4fb088c 100644 (file)
@@ -39,6 +39,9 @@ OBJS          := \
        virt_df.cmo \
        virt_df_ext2.cmo \
        virt_df_linux_swap.cmo \
+       virt_df_lvm2_metadata.cmo \
+       virt_df_lvm2_parser.cmo \
+       virt_df_lvm2_lexer.cmo \
        virt_df_lvm2.cmo \
        virt_df_mbr.cmo \
        virt_df_main.cmo
@@ -82,6 +85,11 @@ virt-df.opt: $(XOBJS)
          $(OCAMLOPTPACKAGES) $(OCAMLOPTFLAGS) $(OCAMLOPTLIBS) \
          ../libvirt/mllibvirt.cmxa -o $@ $^
 
+# 'make depend' doesn't catch these dependencies because the .mli file
+# is auto-generated.
+virt_df_lvm2_parser.cmo: virt_df_lvm2_parser.mli
+virt_df_lvm2_parser.cmx: virt_df_lvm2_parser.mli
+
 # Manual page.
 ifeq ($(HAVE_PERLDOC),perldoc)
 virt-df.1: virt-df.pod
index 16d8e89..fcf1fd2 100644 (file)
@@ -24,6 +24,8 @@ open Printf
 open Virt_df_gettext.Gettext
 open Virt_df
 
+open Virt_df_lvm2_metadata
+
 let plugin_name = "LVM2"
 
 let sector_size = 512
@@ -64,9 +66,16 @@ and read_pv_label dev =
     metadata_length : 32 : littleendian        (* length of metadata (bytes) *)
       when Bitmatch.string_of_bitstring labelone = "LABELONE" &&
           Bitmatch.string_of_bitstring lvm2_ver = "LVM2 001" ->
+
+    (* Metadata offset is relative to end of PV label. *)
     let metadata_offset = metadata_offset +* 0x1000_l in
+    (* Metadata length appears to include the trailing \000 which
+     * we don't want.
+     *)
+    let metadata_length = metadata_length -* 1_l in
+
     let metadata = read_metadata dev metadata_offset metadata_length in
-    (*prerr_endline metadata;*)
+
     let uuid = Bitmatch.string_of_bitstring uuid in
 
     uuid, metadata
@@ -101,11 +110,34 @@ and read_metadata dev offset32 len32 =
  * (as devices) and return them.  Note that we don't try to detect
  * what is on these LVs - that will be done in the main code.
  *)
-let list_lvs devs =
-  (* Read the UUID and metadata (again) from each device. *)
-  let uuidmetas = List.map read_pv_label devs in
+let rec list_lvs devs =
+  (* Read the UUID and metadata (again) from each device to end up with
+   * an assoc list of PVs, keyed on the UUID.
+   *)
+  let pvs = List.map read_pv_label devs in
+
+  (* Parse the metadata using the external lexer/parser. *)
+  let pvs = List.map (
+    fun (uuid, metadata) ->
+      eprintf "parsing: %s\n<<<<\n" metadata;
+      uuid, Virt_df_lvm2_lexer.parse_lvm2_metadata_from_string metadata
+  ) pvs in
+
+  (* Print the parsed metadata. *)
+  List.iter (
+    fun (uuid, metadata) ->
+      eprintf "metadata for UUID %s:\n" uuid;
+      output_metadata stderr metadata
+  ) pvs;
+
   []
 
+
+
+
+
+  
+
 (* Register with main code. *)
 let () =
   lvm_type_register plugin_name probe_pv list_lvs
diff --git a/virt-df/virt_df_lvm2_lexer.mll b/virt-df/virt_df_lvm2_lexer.mll
new file mode 100644 (file)
index 0000000..2dbe7e5
--- /dev/null
@@ -0,0 +1,165 @@
+(* 'df' command for virtual domains.
+   (C) Copyright 2007-2008 Richard W.M. Jones, Red Hat Inc.
+   http://libvirt.org/
+
+   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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *)
+
+(* Scanner for LVM2 metadata.
+ * ocamllex tutorial:
+ * http://plus.kaist.ac.kr/~shoh/ocaml/ocamllex-ocamlyacc/ocamllex-tutorial/
+ *)
+
+{
+  open Printf
+  open Lexing
+
+  open Virt_df
+  open Virt_df_lvm2_parser
+
+  (* Temporary buffer used for parsing strings, etc. *)
+  let tmp = Buffer.create 80
+
+  exception Error of string
+}
+
+let digit = ['0'-'9']
+let alpha = ['a'-'z' 'A'-'Z']
+let alphau = ['a'-'z' 'A'-'Z' '_']
+let alnum = ['a'-'z' 'A'-'Z' '0'-'9']
+let alnumu = ['a'-'z' 'A'-'Z' '0'-'9' '_']
+let ident = alphau alnumu*
+
+let whitespace = [' ' '\t' '\r' '\n']+
+
+let escaped_char = '\\' _
+
+rule token = parse
+  (* ignore whitespace and comments *)
+  | whitespace
+  | '#' [^ '\n']*
+      { token lexbuf }
+
+  (* scan single character tokens *)
+  | '{'  { LBRACE }
+  | '}'  { RBRACE }
+  | '['  { LSQUARE }
+  | ']'  { RSQUARE }
+  | '='  { EQ }
+  | ','  { COMMA }
+
+  (* strings - see LVM2/lib/config/config.c *)
+  | '"'
+      {
+       Buffer.reset tmp;
+       STRING (dq_string lexbuf)
+      }
+  | '\''
+      {
+       Buffer.reset tmp;
+       STRING (dq_string lexbuf)
+      }
+
+  (* floats *)
+  | ('-'? digit+ '.' digit*) as f
+      {
+       let f = float_of_string f in
+       FLOAT f
+      }
+
+  (* integers *)
+  | ('-'? digit+) as i
+      {
+       let i = Int64.of_string i in
+       INT i
+      }
+
+  (* identifiers *)
+  | ident as id
+      { IDENT id }
+
+  (* end of file *)
+  | eof
+      { EOF }
+
+  | _ as c
+      { raise (Error (sprintf "%c: invalid character in input" c)) }
+
+and dq_string = parse
+  | '"'
+      { Buffer.contents tmp }
+  | escaped_char as str
+      { Buffer.add_char tmp str.[1]; dq_string lexbuf }
+  | eof
+      { raise (Error "unterminated string in metadata") }
+  | _ as c
+      { Buffer.add_char tmp c; dq_string lexbuf }
+
+and q_string = parse
+  | '\''
+      { Buffer.contents tmp }
+  | escaped_char as str
+      { Buffer.add_char tmp str.[1]; q_string lexbuf }
+  | eof
+      { raise (Error "unterminated string in metadata") }
+  | _ as c
+      { Buffer.add_char tmp c; q_string lexbuf }
+
+{
+  (* Demonstration of how to wrap the token function
+     with extra debugging statements:
+  let token lexbuf =
+    try
+      let r = token lexbuf in
+      if debug then
+       eprintf "Lexer: token returned is %s\n"
+         (match r with
+          | LBRACE -> "LBRACE"
+          | RBRACE -> "RBRACE"
+          | LSQUARE -> "LSQUARE"
+          | RSQUARE -> "RSQUARE"
+          | EQ -> "EQ"
+          | COMMA -> "COMMA"
+          | STRING s -> sprintf "STRING(%S)" s
+          | INT i -> sprintf "INT(%Ld)" i
+          | FLOAT f -> sprintf "FLOAT(%g)" f
+          | IDENT s -> sprintf "IDENT(%s)" s
+           | EOF -> "EOF");
+      r
+    with
+      exn ->
+       prerr_endline (Printexc.to_string exn);
+       raise exn
+  *)
+
+  (* Lex and parse input.
+   *
+   * Return the parsed metadata structure if everything went to plan.
+   * Raises [Error msg] if there was some parsing problem.
+   *)
+  let rec parse_lvm2_metadata_from_string str =
+    let lexbuf = Lexing.from_string str in
+    parse_lvm2_metadata lexbuf
+  and parse_lvm2_metadata_from_channel chan =
+    let lexbuf = Lexing.from_channel chan in
+    parse_lvm2_metadata lexbuf
+  and parse_lvm2_metadata lexbuf =
+    try
+      input token lexbuf
+    with
+    | Error _ as exn -> raise exn
+    | Parsing.Parse_error -> raise (Error "Parse error")
+    | exn -> raise (Error ("Exception: " ^ Printexc.to_string exn))
+}
diff --git a/virt-df/virt_df_lvm2_metadata.ml b/virt-df/virt_df_lvm2_metadata.ml
new file mode 100644 (file)
index 0000000..d293577
--- /dev/null
@@ -0,0 +1,65 @@
+(* 'df' command for virtual domains.  -*- text -*-
+   (C) Copyright 2007-2008 Richard W.M. Jones, Red Hat Inc.
+   http://libvirt.org/
+
+   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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *)
+
+(* Part of the parser for LVM2 metadata. *)
+
+type metadata = metastmt list
+
+and metastmt = string * metavalue
+
+and metavalue =
+  | Metadata of metadata               (* name { ... } *)
+  | String of string                   (* name = "..." *)
+  | Int of int64
+  | Float of float
+  | List of metavalue list             (* name = [...] *)
+
+let rec output_metadata chan md =
+  _output_metadata chan "" md
+
+and _output_metadata chan prefix = function
+  | [] -> ()
+  | (name, value) :: rest ->
+      output_string chan prefix;
+      output_string chan name;
+      output_string chan " = ";
+      output_metavalue chan prefix value;
+      output_string chan "\n";
+      _output_metadata chan prefix rest
+
+and output_metavalue chan prefix = function
+  | Metadata md ->
+      output_string chan "{\n";
+      _output_metadata chan (prefix ^ "  ") md;
+      output_string chan prefix;
+      output_string chan "}\n";
+  | String str ->
+      output_char chan '"';
+      output_string chan str;
+      output_char chan '"';
+  | Int i ->
+      output_string chan (Int64.to_string i)
+  | Float f ->
+      output_string chan (string_of_float f)
+  | List [] -> ()
+  | List [x] -> output_metavalue chan prefix x
+  | List (x :: xs) ->
+      output_metavalue chan prefix x;
+      output_string chan ", ";
+      output_metavalue chan prefix (List xs)
diff --git a/virt-df/virt_df_lvm2_metadata.mli b/virt-df/virt_df_lvm2_metadata.mli
new file mode 100644 (file)
index 0000000..b7e821b
--- /dev/null
@@ -0,0 +1,38 @@
+(* 'df' command for virtual domains.  -*- text -*-
+   (C) Copyright 2007-2008 Richard W.M. Jones, Red Hat Inc.
+   http://libvirt.org/
+
+   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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *)
+
+(* Part of the parser for LVM2 metadata. *)
+
+type metadata = metastmt list
+
+and metastmt = string * metavalue
+
+and metavalue =
+  | Metadata of metadata               (* name { ... } *)
+  | String of string                   (* name = "..." *)
+  | Int of int64
+  | Float of float
+  | List of metavalue list             (* name = [...] *)
+
+val output_metadata : out_channel -> metadata -> unit
+(** This function prints out the metadata on the selected channel.
+
+    The output format isn't particularly close to the input
+    format.  This is just for debugging purposes.
+*)
diff --git a/virt-df/virt_df_lvm2_parser.mly b/virt-df/virt_df_lvm2_parser.mly
new file mode 100644 (file)
index 0000000..9f47ced
--- /dev/null
@@ -0,0 +1,70 @@
+/* 'df' command for virtual domains.  -*- text -*-
+   (C) Copyright 2007-2008 Richard W.M. Jones, Red Hat Inc.
+   http://libvirt.org/
+
+   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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* Parser for LVM2 metadata.
+   ocamlyacc tutorial:
+   http://plus.kaist.ac.kr/~shoh/ocaml/ocamllex-ocamlyacc/ocamlyacc-tutorial/
+ */
+
+%{
+  open Virt_df_lvm2_metadata
+%}
+
+%token LBRACE RBRACE                   /* { } */
+%token LSQUARE RSQUARE                 /* [ ] */
+%token EQ                              /* = */
+%token COMMA                           /* , */
+%token <string> STRING                 /* "string" */
+%token <int64> INT                     /* an integer */
+%token <float> FLOAT                   /* a float */
+%token <string> IDENT                  /* a naked keyword/identifier */
+%token EOF                             /* end of file */
+
+%start input
+%type <Virt_df_lvm2_metadata.metadata> input
+
+%%
+
+input  : lines EOF     { List.rev $1 }
+       ;
+
+lines  : /* empty */   { prerr_endline "empty line"; [] }
+       | lines line    { prerr_endline "input line"; $2 :: $1 }
+       ;
+
+line   : /* empty */   /* These dummy entries get removed after parsing. */
+                       { ("", String "") }
+       | IDENT EQ value
+                       { ($1, $3) }
+       | IDENT LBRACE lines RBRACE
+                       { ($1, Metadata (List.rev $3)) }
+       ;
+
+value  : STRING        { String $1 }
+       | INT           { Int $1 }
+       | FLOAT         { Float $1 }
+       | LSQUARE list RSQUARE
+                       { List (List.rev $2) }
+       ;
+
+list   : /* empty */   { [] }
+       | value         { [$1] }
+       | list COMMA value
+                       { $3 :: $1 }
+       ;