From 81294675f6a5058a3381871f1dc99c806922d77c Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Thu, 1 Jan 1970 00:00:00 +0000 Subject: [PATCH] Metadata parser. --- .hgignore | 3 + Make.rules.in | 7 +- virt-df/.depend | 17 +++- virt-df/Makefile.in | 8 ++ virt-df/virt_df_lvm2.ml | 40 ++++++++- virt-df/virt_df_lvm2_lexer.mll | 165 ++++++++++++++++++++++++++++++++++++++ virt-df/virt_df_lvm2_metadata.ml | 65 +++++++++++++++ virt-df/virt_df_lvm2_metadata.mli | 38 +++++++++ virt-df/virt_df_lvm2_parser.mly | 70 ++++++++++++++++ 9 files changed, 404 insertions(+), 9 deletions(-) create mode 100644 virt-df/virt_df_lvm2_lexer.mll create mode 100644 virt-df/virt_df_lvm2_metadata.ml create mode 100644 virt-df/virt_df_lvm2_metadata.mli create mode 100644 virt-df/virt_df_lvm2_parser.mly diff --git a/.hgignore b/.hgignore index f8063da..f78c6f6 100644 --- 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 diff --git a/Make.rules.in b/Make.rules.in index b22fdf6..a25d485 100644 --- a/Make.rules.in +++ b/Make.rules.in @@ -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 diff --git a/virt-df/.depend b/virt-df/.depend index d253040..e7cd81e 100644 --- a/virt-df/.depend +++ b/virt-df/.depend @@ -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 \ diff --git a/virt-df/Makefile.in b/virt-df/Makefile.in index 4a56d2d..4fb088c 100644 --- a/virt-df/Makefile.in +++ b/virt-df/Makefile.in @@ -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 diff --git a/virt-df/virt_df_lvm2.ml b/virt-df/virt_df_lvm2.ml index 16d8e89..fcf1fd2 100644 --- a/virt-df/virt_df_lvm2.ml +++ b/virt-df/virt_df_lvm2.ml @@ -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 index 0000000..2dbe7e5 --- /dev/null +++ b/virt-df/virt_df_lvm2_lexer.mll @@ -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 index 0000000..d293577 --- /dev/null +++ b/virt-df/virt_df_lvm2_metadata.ml @@ -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 index 0000000..b7e821b --- /dev/null +++ b/virt-df/virt_df_lvm2_metadata.mli @@ -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 index 0000000..9f47ced --- /dev/null +++ b/virt-df/virt_df_lvm2_parser.mly @@ -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" */ +%token INT /* an integer */ +%token FLOAT /* a float */ +%token IDENT /* a naked keyword/identifier */ +%token EOF /* end of file */ + +%start input +%type 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 } + ; -- 1.8.3.1