Bugfixes:

* xe command line options doesn't mix well with XE_EXTRA_ARGS varialbe. E.g. 
setting XE_EXTRA_ARGS to "username=xxxx,password=yyyy" (or any non-nil valid 
configuration) and calling "xe -s <some server> vm-list" will break xe. Note 
that this is a common user case in a cluster-like environment where all the 
machines have the same user/passwd config, where one can conveniently set 
user/passwd in XE_EXTRA_ARGS for once and connect to different servers by only 
specifying different "-s" arguments in the cmdline.

* Setting "compat=true" in xe's RC file won't work. E.g. xe vm-clone 
vm-name=<vm name> new-name=<new vm name> with "compat=true" in ~/.xe won't work 
(but with "compat=true" in XE_EXTRA_ARGS or in xe cmdline will work).

* Setting a password with comma via XE_EXTRA_ARGS will break the logic. After 
the fix, it's possible to specify that by using backslash to escape the comma 
(e.g. password=pass\,word)

* clean up the options handling logic, so that cmdline options, RC file setting 
and XE_EXTRA_ARGS variable can mix consistently even in some corner cases and 
follow the natural priority: cmdline option > XE_EXTRA_ARGS > ~/.xe RC > 
default settings


Improvements:

* change options "-debug" and "-debug-on-fail" to "--debug" and 
"--debug-on-fail", so that every command line option now follows the common 
naming convention of -shortcut v.s.--full-name (with the only standard 
exception of having both "-help" and "--help"). AFAICS, both debug options are 
(maybe deliberately) not documented in the manual, so changing the names might 
not be a big issue regarding compatibilities.

* complete the pair relation between command line options and RC/environment 
variables. There were some missings from either side: e.g. "compat=xxxx" has no 
"--compat" correspondence and "--debug"("--debug-on-fail") has not "debug=xxxx" 
in par.


Signed-off-by: Zheng Li <[email protected]>


 ocaml/xe-cli/newcli.ml |  577 
+++++++++++++++++++++++++++-------------------------
 1 files changed, 299 insertions(+), 278 deletions(-)


diff -r abc48d958c40 -r ad5ea8e64ad2 ocaml/xe-cli/newcli.ml
--- a/ocaml/xe-cli/newcli.ml    Fri Apr 23 19:30:04 2010 +0100
+++ b/ocaml/xe-cli/newcli.ml    Tue May 04 06:22:08 2010 +0100
@@ -15,33 +15,34 @@
 open Stringext
 open Cli_protocol
 
-(* Need to know about the host and port to know who to connect to *)
-(* Strictly, we don't need to know the username and password, but I want to be 
able *)
-(* to make a .xe file containing defaults, so we'll pull them out of Sys.argv 
anyway *)
+(* Param config priorities:
+   explicit cmd option > XE_XXX env variable > ~/.xe rc file > default
+*)
 
-(* cmdline options override .xe options override these *)
 let xapiserver = ref "127.0.0.1"
 let xapiuname = ref "root"
 let xapipword = ref "null"
 let xapicompatmode = ref false
 let xapipasswordfile = ref ""
 let xapicompathost = ref "127.0.0.1"
-
-let usessl = ref true
-let stunnel_process = ref None
-let xapiport = ref None 
+let xapiport = ref None
 let get_xapiport ssl =
   match !xapiport with
-      None -> if ssl then 443 else 80
-    | Some p -> p
+    None -> if ssl then 443 else 80
+  | Some p -> p
 
+let xeusessl = ref true
+let xedebug = ref false
+let xedebugonfail = ref false
+
+let stunnel_process = ref None
 let debug_channel = ref None
 let debug_file = ref None
 
 let error fmt = Printf.fprintf stderr fmt
-let debug fmt = 
-  let printer s = match !debug_channel with 
-    | Some c -> output_string c s 
+let debug fmt =
+  let printer s = match !debug_channel with
+    | Some c -> output_string c s
     | None -> () in
   Printf.kprintf printer fmt
 
@@ -49,12 +50,12 @@
 exception Usage
 
 let usage () =
-  if !xapicompatmode 
+  if !xapicompatmode
   then
     begin
       error "COMPATABILITY MODE\n";
       error "Usage: %s <cmd> [-h server] [-p port] ([-u username] [-pw 
password] or [-pwf <password file>]) <other arguments>\n" Sys.argv.(0);
-      error "\nA full list of commands can be obtained by running \n\t%s help 
-s <server> -p <port>\n" Sys.argv.(0)
+      error "\nA full list of commands can be obtained by running \n\t%s help 
-h <server> -p <port>\n" Sys.argv.(0)
     end
   else
     begin
@@ -68,36 +69,36 @@
 
 exception Http_parse_failure
 let hdrs = ["content-length"; "cookie"; "connection"; "transfer-encoding"; 
"authorization"; "location"]
-  
+
 let end_of_string s from =
   String.sub s from ((String.length s)-from)
-    
+
 let strip_cr r =
   if String.length r=0 then raise Http_parse_failure;
   let last_char = String.sub r ((String.length r)-1) 1 in
   if last_char <> "\r" then raise Http_parse_failure;
   String.sub r 0 ((String.length r)-1)
-    
+
 let rec read_rest_of_headers ic =
   try
     let r = input_line ic in
     let r = strip_cr r in
     if r="" then [] else
       begin
-       debug "read '%s'\n" r;
-       let hdr = List.find (fun s -> String.startswith (s^": ") 
(String.lowercase r)) hdrs in
-       let value = end_of_string r (String.length hdr + 2) in
-       (hdr,value)::read_rest_of_headers ic
+        debug "read '%s'\n" r;
+        let hdr = List.find (fun s -> String.startswith (s^": ") 
(String.lowercase r)) hdrs in
+        let value = end_of_string r (String.length hdr + 2) in
+        (hdr,value)::read_rest_of_headers ic
       end
   with
-    | Not_found -> read_rest_of_headers ic    
-    | _ -> []
-       
+  | Not_found -> read_rest_of_headers ic
+  | _ -> []
+
 let parse_url url =
   if String.startswith "https://"; url
   then
     let stripped = end_of_string url (String.length "https://";) in
-    let host, rest = 
+    let host, rest =
       let l =  String.split '/' stripped in
       List.hd l, List.tl l in
     (host,"/" ^ (String.concat "/" rest))
@@ -120,7 +121,7 @@
     exit 1
 
 
-let parse_port (x: string) = 
+let parse_port (x: string) =
   try
     let p = int_of_string x in
     if p < 0 || p > 65535 then failwith "illegal";
@@ -131,104 +132,135 @@
 
 (* Extract the arguments we're interested in. Return a list of the argumets we 
know *)
 (* nothing about. These will get passed straight into the server *)
-let parse_args args =
-  
+let parse_args =
+
   (* Set the key to the value. Return whether the key is one we know about *)
   (* compat mode is special as the argument is passed in two places. Once  *)
-  (* at the top of the message to the cli server in order to indicate that *) 
+  (* at the top of the message to the cli server in order to indicate that *)
   (* we need to use 'geneva style' parsing - that is, allow key = value as *)
   (* opposed to key=value. Secondly, the key then gets passed along with   *)
   (* all the others to the operations. So we need to register it's there,  *)
   (* but not strip it                                                      *)
+
+  let reserve_args = ref [] in
+
   let set_keyword (k,v) =
-    match k with
-       "server" -> xapiserver := v; true
-      | "port" -> xapiport := Some (parse_port v); true 
-      | "username" -> xapiuname := v; true
-      | "password" -> xapipword := v; true
-      | "passwordfile" -> xapipasswordfile := v; true
-      | "nossl"   -> usessl := not(bool_of_string v); true
-      | "compat" -> xapicompatmode := (try (bool_of_string v) with _ -> 
false); false (* dont strip it! *)
-      | _ -> false
-  in
+    try
+      (match k with
+       | "server" -> xapiserver := v
+       | "port" -> xapiport := Some (parse_port v)
+       | "username" -> xapiuname := v
+       | "password" -> xapipword := v
+       | "passwordfile" -> xapipasswordfile := v
+       | "nossl"   -> xeusessl := not(bool_of_string v)
+       | "compat" ->
+           xapicompatmode := (try (bool_of_string v) with _ -> false);
+           reserve_args := (k ^ "=" ^ v) :: !reserve_args
+       | "debug" -> xedebug := (try bool_of_string v with _ -> false)
+       | "debugonfail" -> xedebugonfail := (try bool_of_string v with _ -> 
false)
+       | _ -> raise Not_found);
+      true
+    with Not_found -> false in
 
-  let rec doit args = 
+  let parse_opt args =
     match args with
-      |        "--help"::_ 
-      | "-help"::_ ->
-         raise Usage
-      | "-s"::server::xs ->
-         xapiserver := server;
-         doit xs
-      | "-p"::port::xs ->
-         xapiport := Some (parse_port port);
-         doit xs
-      | "-u"::uname::xs ->
-         xapiuname := uname;
-         doit xs
-      | "-pw"::pw::xs ->
-         xapipword := pw;
-         doit xs
-      | "--nossl"::xs ->
-         usessl := false;
-         doit xs
-      | "-pwf"::pwf::xs ->
-         xapipasswordfile := pwf;
-         doit xs
-      | "-h"::h::xs ->
-         xapicompathost := h;
-         doit xs
-      | x::xs ->
-         (* we eat cmdline params if we know about them *)
-         let eatit = 
-           begin
-             try
-               let eq = String.index x '=' in
-               let k = String.sub x 0 eq in
-               let v = String.sub x (eq+1) (String.length x - (eq+1)) in
-               set_keyword (k,v)
-             with _ -> false 
-           end
-         in
-         if eatit then doit xs else x::(doit xs)
-      | _ -> []
-  in
+    | "-s" :: server :: xs -> Some ("server", server, xs)
+    | "-p" :: port :: xs -> Some("port", port, xs)
+    | "-u" :: uname :: xs -> Some("username", uname, xs)
+    | "-pw" :: pw :: xs -> Some("password", pw, xs)
+    | "-pwf" :: pwf :: xs -> Some("passwordfile", pwf, xs)
+    | "--nossl" :: xs -> Some("nossl", "true", xs)
+    | "--compat" :: xs -> Some("compat", "true", xs)
+    | "--debug" :: xs -> Some("debug", "true", xs)
+    | "--debug-on-fail" :: xs -> Some("debugonfail", "true", xs)
+    | "-h" :: h :: xs -> Some("server", h, xs)
+    | _ -> None in
 
-  let defaults = Options.read_rc () in
-  ignore (List.map set_keyword defaults); (* Defaults from the fil ~/.xe *)
-  let newargs = doit args in
-  (if !xapipasswordfile <> "" then read_pwf ());
-  (if !xapicompatmode then xapiserver := !xapicompathost);
-  newargs
+  let parse_eql arg =
+    try
+      let eq = String.index arg '=' in
+      let k = String.sub arg 0 eq in
+      let v = String.sub arg (eq+1) (String.length arg - (eq+1)) in
+      Some (k,v)
+    with _ -> None in
 
-let open_tcp_ssl server = 
+  let rec process_args = function
+    | [] -> []
+    | args ->
+        match parse_opt args with
+        | Some(k, v, rest) ->
+            if set_keyword(k, v) then process_args rest else process_eql args
+        | None ->
+            process_eql args
+  and process_eql = function
+    | [] -> []
+    | arg :: args ->
+        match parse_eql arg with
+        | Some(k, v) when set_keyword(k,v) -> process_args args
+        | _ -> arg :: process_args args in
+
+  fun args ->
+    let rcs = Options.read_rc() in
+    let rcs_rest =
+      List.map (fun (k,v) -> k^"="^v)
+        (List.filter (fun (k, v) -> not (set_keyword (k,v))) rcs) in
+    let extras =
+      let extra_args = try Sys.getenv "XE_EXTRA_ARGS" with Not_found -> "" in
+      let l = ref [] and pos = ref 0 and i = ref 0 in
+      while !pos < String.length extra_args do
+        if extra_args.[!pos] = ',' then (incr pos; i := !pos)
+        else
+          if !i >= String.length extra_args
+            || extra_args.[!i] = ',' && extra_args.[!i-1] <> '\\' then
+              (let seg = String.sub extra_args !pos (!i - !pos) in
+               l := String.filter_chars seg ((<>) '\\') :: !l;
+               incr i; pos := !i)
+          else incr i
+      done;
+      List.rev !l  in
+    let extras_rest = process_args extras in
+    let help = ref false in
+    let args' = List.filter (fun s -> s<>"-help" && s <> "--help") args in
+    if List.length args' < List.length args then help := true;
+    let args_rest = process_args args in
+    if !help then raise Usage;
+    let () =
+      if !xapipasswordfile <> "" then read_pwf ();
+      if !xedebug then debug_channel := Some stderr;
+      if !xedebugonfail then begin
+        let tmpfile, tmpch = Filename.open_temp_file "xe_debug" "tmp" in
+        debug_file := Some tmpfile;
+        debug_channel := Some tmpch
+      end in
+    args_rest @ extras_rest @ rcs_rest @ !reserve_args
+
+let open_tcp_ssl server =
   let port = get_xapiport true in
   debug "Connecting via stunnel to [%s] port [%d]\n%!" server port;
   (* We don't bother closing fds since this requires our close_and_exec 
wrapper *)
-  let x = Stunnel.connect ~use_external_fd_wrapper:false 
-    ~write_to_log:(fun x -> debug "stunnel: %s\n%!" x) 
+  let x = Stunnel.connect ~use_external_fd_wrapper:false
+    ~write_to_log:(fun x -> debug "stunnel: %s\n%!" x)
     ~extended_diagnosis:(!debug_file <> None) server port in
   if !stunnel_process = None then stunnel_process := Some x;
   Unix.in_channel_of_descr x.Stunnel.fd, Unix.out_channel_of_descr x.Stunnel.fd


 ( ...... 455 lines left ...... ) 

# HG changeset patch
# User Zheng Li <[email protected]>
# Date 1272950528 -3600
# Node ID ad5ea8e64ad268ea8a99ca1bda96a962a04e348a
# Parent  abc48d958c408ffa2f9aa72addeb4ca8507a5096
A few bugfixes and a few minor improvements to current xe cmdline tool implementation

Bugfixes:

* xe command line options doesn't mix well with XE_EXTRA_ARGS varialbe. E.g. setting XE_EXTRA_ARGS to "username=xxxx,password=yyyy" (or any non-nil valid configuration) and calling "xe -s <some server> vm-list" will break xe. Note that this is a common user case in a cluster-like environment where all the machines have the same user/passwd config, where one can conveniently set user/passwd in XE_EXTRA_ARGS for once and connect to different servers by only specifying different "-s" arguments in the cmdline.

* Setting "compat=true" in xe's RC file won't work. E.g. xe vm-clone vm-name=<vm name> new-name=<new vm name> with "compat=true" in ~/.xe won't work (but with "compat=true" in XE_EXTRA_ARGS or in xe cmdline will work).

* Setting a password with comma via XE_EXTRA_ARGS will break the logic. After the fix, it's possible to specify that by using backslash to escape the comma (e.g. password=pass\,word)

* clean up the options handling logic, so that cmdline options, RC file setting and XE_EXTRA_ARGS variable can mix consistently even in some corner cases and follow the natural priority: cmdline option > XE_EXTRA_ARGS > ~/.xe RC > default settings


Improvements:

* change options "-debug" and "-debug-on-fail" to "--debug" and "--debug-on-fail", so that every command line option now follows the common naming convention of -shortcut v.s.--full-name (with the only standard exception of having both "-help" and "--help"). AFAICS, both debug options are (maybe deliberately) not documented in the manual, so changing the names might not be a big issue regarding compatibilities.

* complete the pair relation between command line options and RC/environment variables. There were some missings from either side: e.g. "compat=xxxx" has no "--compat" correspondence and "--debug"("--debug-on-fail") has not "debug=xxxx" in par.


Signed-off-by: Zheng Li <[email protected]>

diff -r abc48d958c40 -r ad5ea8e64ad2 ocaml/xe-cli/newcli.ml
--- a/ocaml/xe-cli/newcli.ml	Fri Apr 23 19:30:04 2010 +0100
+++ b/ocaml/xe-cli/newcli.ml	Tue May 04 06:22:08 2010 +0100
@@ -15,33 +15,34 @@
 open Stringext
 open Cli_protocol
 
-(* Need to know about the host and port to know who to connect to *)
-(* Strictly, we don't need to know the username and password, but I want to be able *)
-(* to make a .xe file containing defaults, so we'll pull them out of Sys.argv anyway *)
+(* Param config priorities:
+   explicit cmd option > XE_XXX env variable > ~/.xe rc file > default
+*)
 
-(* cmdline options override .xe options override these *)
 let xapiserver = ref "127.0.0.1"
 let xapiuname = ref "root"
 let xapipword = ref "null"
 let xapicompatmode = ref false
 let xapipasswordfile = ref ""
 let xapicompathost = ref "127.0.0.1"
-
-let usessl = ref true
-let stunnel_process = ref None
-let xapiport = ref None 
+let xapiport = ref None
 let get_xapiport ssl =
   match !xapiport with
-      None -> if ssl then 443 else 80
-    | Some p -> p
+    None -> if ssl then 443 else 80
+  | Some p -> p
 
+let xeusessl = ref true
+let xedebug = ref false
+let xedebugonfail = ref false
+
+let stunnel_process = ref None
 let debug_channel = ref None
 let debug_file = ref None
 
 let error fmt = Printf.fprintf stderr fmt
-let debug fmt = 
-  let printer s = match !debug_channel with 
-    | Some c -> output_string c s 
+let debug fmt =
+  let printer s = match !debug_channel with
+    | Some c -> output_string c s
     | None -> () in
   Printf.kprintf printer fmt
 
@@ -49,12 +50,12 @@
 exception Usage
 
 let usage () =
-  if !xapicompatmode 
+  if !xapicompatmode
   then
     begin
       error "COMPATABILITY MODE\n";
       error "Usage: %s <cmd> [-h server] [-p port] ([-u username] [-pw password] or [-pwf <password file>]) <other arguments>\n" Sys.argv.(0);
-      error "\nA full list of commands can be obtained by running \n\t%s help -s <server> -p <port>\n" Sys.argv.(0)
+      error "\nA full list of commands can be obtained by running \n\t%s help -h <server> -p <port>\n" Sys.argv.(0)
     end
   else
     begin
@@ -68,36 +69,36 @@
 
 exception Http_parse_failure
 let hdrs = ["content-length"; "cookie"; "connection"; "transfer-encoding"; "authorization"; "location"]
-  
+
 let end_of_string s from =
   String.sub s from ((String.length s)-from)
-    
+
 let strip_cr r =
   if String.length r=0 then raise Http_parse_failure;
   let last_char = String.sub r ((String.length r)-1) 1 in
   if last_char <> "\r" then raise Http_parse_failure;
   String.sub r 0 ((String.length r)-1)
-    
+
 let rec read_rest_of_headers ic =
   try
     let r = input_line ic in
     let r = strip_cr r in
     if r="" then [] else
       begin
-	debug "read '%s'\n" r;
-	let hdr = List.find (fun s -> String.startswith (s^": ") (String.lowercase r)) hdrs in
-	let value = end_of_string r (String.length hdr + 2) in
-	(hdr,value)::read_rest_of_headers ic
+        debug "read '%s'\n" r;
+        let hdr = List.find (fun s -> String.startswith (s^": ") (String.lowercase r)) hdrs in
+        let value = end_of_string r (String.length hdr + 2) in
+        (hdr,value)::read_rest_of_headers ic
       end
   with
-    | Not_found -> read_rest_of_headers ic    
-    | _ -> []
-	
+  | Not_found -> read_rest_of_headers ic
+  | _ -> []
+
 let parse_url url =
   if String.startswith "https://"; url
   then
     let stripped = end_of_string url (String.length "https://";) in
-    let host, rest = 
+    let host, rest =
       let l =  String.split '/' stripped in
       List.hd l, List.tl l in
     (host,"/" ^ (String.concat "/" rest))
@@ -120,7 +121,7 @@
     exit 1
 
 
-let parse_port (x: string) = 
+let parse_port (x: string) =
   try
     let p = int_of_string x in
     if p < 0 || p > 65535 then failwith "illegal";
@@ -131,104 +132,135 @@
 
 (* Extract the arguments we're interested in. Return a list of the argumets we know *)
 (* nothing about. These will get passed straight into the server *)
-let parse_args args =
-  
+let parse_args =
+
   (* Set the key to the value. Return whether the key is one we know about *)
   (* compat mode is special as the argument is passed in two places. Once  *)
-  (* at the top of the message to the cli server in order to indicate that *) 
+  (* at the top of the message to the cli server in order to indicate that *)
   (* we need to use 'geneva style' parsing - that is, allow key = value as *)
   (* opposed to key=value. Secondly, the key then gets passed along with   *)
   (* all the others to the operations. So we need to register it's there,  *)
   (* but not strip it                                                      *)
+
+  let reserve_args = ref [] in
+
   let set_keyword (k,v) =
-    match k with
-	"server" -> xapiserver := v; true
-      | "port" -> xapiport := Some (parse_port v); true 
-      | "username" -> xapiuname := v; true
-      | "password" -> xapipword := v; true
-      | "passwordfile" -> xapipasswordfile := v; true
-      | "nossl"   -> usessl := not(bool_of_string v); true
-      | "compat" -> xapicompatmode := (try (bool_of_string v) with _ -> false); false (* dont strip it! *)
-      | _ -> false
-  in
+    try
+      (match k with
+       | "server" -> xapiserver := v
+       | "port" -> xapiport := Some (parse_port v)
+       | "username" -> xapiuname := v
+       | "password" -> xapipword := v
+       | "passwordfile" -> xapipasswordfile := v
+       | "nossl"   -> xeusessl := not(bool_of_string v)
+       | "compat" ->
+           xapicompatmode := (try (bool_of_string v) with _ -> false);
+           reserve_args := (k ^ "=" ^ v) :: !reserve_args
+       | "debug" -> xedebug := (try bool_of_string v with _ -> false)
+       | "debugonfail" -> xedebugonfail := (try bool_of_string v with _ -> false)
+       | _ -> raise Not_found);
+      true
+    with Not_found -> false in
 
-  let rec doit args = 
+  let parse_opt args =
     match args with
-      |	"--help"::_ 
-      | "-help"::_ ->
-	  raise Usage
-      | "-s"::server::xs ->
-	  xapiserver := server;
-	  doit xs
-      | "-p"::port::xs ->
-	  xapiport := Some (parse_port port);
-	  doit xs
-      | "-u"::uname::xs ->
-	  xapiuname := uname;
-	  doit xs
-      | "-pw"::pw::xs ->
-	  xapipword := pw;
-	  doit xs
-      | "--nossl"::xs ->
-	  usessl := false;
-	  doit xs
-      | "-pwf"::pwf::xs ->
-	  xapipasswordfile := pwf;
-	  doit xs
-      | "-h"::h::xs ->
-	  xapicompathost := h;
-	  doit xs
-      | x::xs ->
-	  (* we eat cmdline params if we know about them *)
-	  let eatit = 
-	    begin
-	      try
-		let eq = String.index x '=' in
-		let k = String.sub x 0 eq in
-		let v = String.sub x (eq+1) (String.length x - (eq+1)) in
-		set_keyword (k,v)
-	      with _ -> false 
-	    end
-	  in
-	  if eatit then doit xs else x::(doit xs)
-      | _ -> []
-  in
+    | "-s" :: server :: xs -> Some ("server", server, xs)
+    | "-p" :: port :: xs -> Some("port", port, xs)
+    | "-u" :: uname :: xs -> Some("username", uname, xs)
+    | "-pw" :: pw :: xs -> Some("password", pw, xs)
+    | "-pwf" :: pwf :: xs -> Some("passwordfile", pwf, xs)
+    | "--nossl" :: xs -> Some("nossl", "true", xs)
+    | "--compat" :: xs -> Some("compat", "true", xs)
+    | "--debug" :: xs -> Some("debug", "true", xs)
+    | "--debug-on-fail" :: xs -> Some("debugonfail", "true", xs)
+    | "-h" :: h :: xs -> Some("server", h, xs)
+    | _ -> None in
 
-  let defaults = Options.read_rc () in
-  ignore (List.map set_keyword defaults); (* Defaults from the fil ~/.xe *)
-  let newargs = doit args in
-  (if !xapipasswordfile <> "" then read_pwf ());
-  (if !xapicompatmode then xapiserver := !xapicompathost);
-  newargs
+  let parse_eql arg =
+    try
+      let eq = String.index arg '=' in
+      let k = String.sub arg 0 eq in
+      let v = String.sub arg (eq+1) (String.length arg - (eq+1)) in
+      Some (k,v)
+    with _ -> None in
 
-let open_tcp_ssl server = 
+  let rec process_args = function
+    | [] -> []
+    | args ->
+        match parse_opt args with
+        | Some(k, v, rest) ->
+            if set_keyword(k, v) then process_args rest else process_eql args
+        | None ->
+            process_eql args
+  and process_eql = function
+    | [] -> []
+    | arg :: args ->
+        match parse_eql arg with
+        | Some(k, v) when set_keyword(k,v) -> process_args args
+        | _ -> arg :: process_args args in
+
+  fun args ->
+    let rcs = Options.read_rc() in
+    let rcs_rest =
+      List.map (fun (k,v) -> k^"="^v)
+        (List.filter (fun (k, v) -> not (set_keyword (k,v))) rcs) in
+    let extras =
+      let extra_args = try Sys.getenv "XE_EXTRA_ARGS" with Not_found -> "" in
+      let l = ref [] and pos = ref 0 and i = ref 0 in
+      while !pos < String.length extra_args do
+        if extra_args.[!pos] = ',' then (incr pos; i := !pos)
+        else
+          if !i >= String.length extra_args
+            || extra_args.[!i] = ',' && extra_args.[!i-1] <> '\\' then
+              (let seg = String.sub extra_args !pos (!i - !pos) in
+               l := String.filter_chars seg ((<>) '\\') :: !l;
+               incr i; pos := !i)
+          else incr i
+      done;
+      List.rev !l  in
+    let extras_rest = process_args extras in
+    let help = ref false in
+    let args' = List.filter (fun s -> s<>"-help" && s <> "--help") args in
+    if List.length args' < List.length args then help := true;
+    let args_rest = process_args args in
+    if !help then raise Usage;
+    let () =
+      if !xapipasswordfile <> "" then read_pwf ();
+      if !xedebug then debug_channel := Some stderr;
+      if !xedebugonfail then begin
+        let tmpfile, tmpch = Filename.open_temp_file "xe_debug" "tmp" in
+        debug_file := Some tmpfile;
+        debug_channel := Some tmpch
+      end in
+    args_rest @ extras_rest @ rcs_rest @ !reserve_args
+
+let open_tcp_ssl server =
   let port = get_xapiport true in
   debug "Connecting via stunnel to [%s] port [%d]\n%!" server port;
   (* We don't bother closing fds since this requires our close_and_exec wrapper *)
-  let x = Stunnel.connect ~use_external_fd_wrapper:false 
-    ~write_to_log:(fun x -> debug "stunnel: %s\n%!" x) 
+  let x = Stunnel.connect ~use_external_fd_wrapper:false
+    ~write_to_log:(fun x -> debug "stunnel: %s\n%!" x)
     ~extended_diagnosis:(!debug_file <> None) server port in
   if !stunnel_process = None then stunnel_process := Some x;
   Unix.in_channel_of_descr x.Stunnel.fd, Unix.out_channel_of_descr x.Stunnel.fd
 
 let open_tcp server =
-    if !usessl && not(is_localhost server) then (* never use SSL on-host *)
-        open_tcp_ssl server
-    else (
-        let host = Unix.gethostbyname server in
-        let addr = host.Unix.h_addr_list.(0) in
-        Unix.open_connection (Unix.ADDR_INET (addr,get_xapiport false))
-    )
+  if !xeusessl && not(is_localhost server) then (* never use SSL on-host *)
+    open_tcp_ssl server
+  else (
+    let host = Unix.gethostbyname server in
+    let addr = host.Unix.h_addr_list.(0) in
+    Unix.open_connection (Unix.ADDR_INET (addr,get_xapiport false))
+  )
 
-let open_channels () = 
-    if is_localhost !xapiserver then (
-      try
-        Unix.open_connection (Unix.ADDR_UNIX "/var/xapi/xapi")
-      with _ ->
-        open_tcp !xapiserver
-    ) else
+let open_channels () =
+  if is_localhost !xapiserver then (
+    try
+      Unix.open_connection (Unix.ADDR_UNIX "/var/xapi/xapi")
+    with _ ->
       open_tcp !xapiserver
-
+  ) else
+    open_tcp !xapiserver
 
 let http_response_code x = match String.split ' ' x with
   | [ _; code; _ ] -> int_of_string code
@@ -241,20 +273,19 @@
 exception Stunnel_exit of int * Unix.process_status
 exception Unexpected_msg of message
 
-let attr = ref None 
-
+let attr = ref None
 
 let main_loop ifd ofd =
   (* Save the terminal state to restore it at exit *)
   (attr := try Some (Unix.tcgetattr Unix.stdin) with _ -> None);
-  at_exit (fun () -> 
+  at_exit (fun () ->
              match !attr with Some a -> Unix.tcsetattr Unix.stdin Unix.TCSANOW a | None -> ());
   (* Intially exchange version information *)
   let major', minor' = try unmarshal_protocol ifd with End_of_file -> raise Connect_failure in
   (* Be very conservative for the time-being *)
   let msg = Printf.sprintf "Server has protocol version %d.%d. Client has %d.%d" major' minor' major minor in
   debug "%s\n%!" msg;
-  if major' <> major || minor' <> minor 
+  if major' <> major || minor' <> minor
   then raise (Protocol_version_mismatch msg);
   marshal_protocol ofd;
 
@@ -265,18 +296,18 @@
     *)
     while (match Unix.select [ifd] [] [] 5.0 with
            | _ :: _, _, _ -> false
-           | _ -> 
+           | _ ->
                match !stunnel_process with
                | Some { Stunnel.pid = Stunnel.FEFork pid } -> begin
                    match Forkhelpers.waitpid_nohang pid with
                    | 0, _ -> true
                    | i, e -> raise (Stunnel_exit (i, e))
-                 end 
+                 end
                | Some {Stunnel.pid = Stunnel.StdFork pid} -> begin
                    match Unix.waitpid [Unix.WNOHANG] pid with
                    | 0, _ -> true
                    | i, e -> raise (Stunnel_exit (i, e))
-                 end 
+                 end
                | _ -> true) do ()
     done;
     let cmd = unmarshal ifd in
@@ -286,135 +317,135 @@
     | Command (PrintStderr x) -> Printf.fprintf stderr "%s\n%!" x
     | Command (Debug x) -> debug "debug from server: %s\n%!" x
     | Command (Load x) ->
-	      begin
-	        try
-	          let fd = Unix.openfile x [ Unix.O_RDONLY ] 0 in
-	          marshal ofd (Response OK);
-	          let length = (Unix.stat x).Unix.st_size in
-	          marshal ofd (Blob (Chunk (Int32.of_int length)));
-	          let buffer = String.make (1024 * 1024 * 10) '\000' in
-	          let left = ref length in
-	          while !left > 0 do
-		          let n = Unix.read fd buffer 0 (min (String.length buffer) !left) in
-		          really_write ofd buffer 0 n;
-		          left := !left - n
-	          done;
-	          marshal ofd (Blob End);
-	          Unix.close fd
-	        with 
-	        | e -> marshal ofd (Response Failed)
-	      end
+        begin
+          try
+            let fd = Unix.openfile x [ Unix.O_RDONLY ] 0 in
+            marshal ofd (Response OK);
+            let length = (Unix.stat x).Unix.st_size in
+            marshal ofd (Blob (Chunk (Int32.of_int length)));
+            let buffer = String.make (1024 * 1024 * 10) '\000' in
+            let left = ref length in
+            while !left > 0 do
+              let n = Unix.read fd buffer 0 (min (String.length buffer) !left) in
+              really_write ofd buffer 0 n;
+              left := !left - n
+            done;
+            marshal ofd (Blob End);
+            Unix.close fd
+          with
+          | e -> marshal ofd (Response Failed)
+        end
     | Command (HttpPut(filename, url)) ->
-	      begin
-	        try
-	          let rec doit url =
-		          let (server,path) = parse_url url in
-		          if not (Sys.file_exists filename) then
-			          raise (ClientSideError (Printf.sprintf "file '%s' does not exist" filename));
-		          let fd = Unix.openfile filename [ Unix.O_RDONLY ] 0 in
-		          let stat = Unix.LargeFile.fstat fd in
-		          let ic, oc = open_tcp server in
-		          debug "PUTting to path [%s]\n%!" path;
-		          Printf.fprintf oc "PUT %s HTTP/1.0\r\ncontent-length: %Ld\r\n\r\n" path stat.Unix.LargeFile.st_size;
-		          flush oc;
-		          let resultline = input_line ic in
-		          let headers = read_rest_of_headers ic in
-		          (* Get the result header immediately *)
-		          match http_response_code resultline with
-		          | 200 -> 
-		              let fd' = Unix.descr_of_out_channel oc in
-		              let bytes = Unixext.copy_file fd fd' in
-			            debug "Written %s bytes\n%!" (Int64.to_string bytes);
-			            Unix.close fd;
-			            Unix.shutdown fd' Unix.SHUTDOWN_SEND;
-			            marshal ofd (Response OK)
-		          | 302 ->
-		              let newloc = List.assoc "location" headers in
-		              doit newloc
-		          | _ -> failwith "Unhandled response code"		    
-	          in 
-		        doit url
-	        with
-	        | ClientSideError msg ->
+        begin
+          try
+            let rec doit url =
+              let (server,path) = parse_url url in
+              if not (Sys.file_exists filename) then
+                raise (ClientSideError (Printf.sprintf "file '%s' does not exist" filename));
+              let fd = Unix.openfile filename [ Unix.O_RDONLY ] 0 in
+              let stat = Unix.LargeFile.fstat fd in
+              let ic, oc = open_tcp server in
+              debug "PUTting to path [%s]\n%!" path;
+              Printf.fprintf oc "PUT %s HTTP/1.0\r\ncontent-length: %Ld\r\n\r\n" path stat.Unix.LargeFile.st_size;
+              flush oc;
+              let resultline = input_line ic in
+              let headers = read_rest_of_headers ic in
+              (* Get the result header immediately *)
+              match http_response_code resultline with
+              | 200 ->
+                  let fd' = Unix.descr_of_out_channel oc in
+                  let bytes = Unixext.copy_file fd fd' in
+                  debug "Written %s bytes\n%!" (Int64.to_string bytes);
+                  Unix.close fd;
+                  Unix.shutdown fd' Unix.SHUTDOWN_SEND;
+                  marshal ofd (Response OK)
+              | 302 ->
+                  let newloc = List.assoc "location" headers in
+                  doit newloc
+              | _ -> failwith "Unhandled response code"
+            in
+            doit url
+          with
+          | ClientSideError msg ->
               marshal ofd (Response Failed);
               Printf.fprintf stderr "Operation failed. Error: %s\n" msg;
               exit_code := Some 1
-	        | e ->
-		          debug "HttpPut failure: %s\n%!" (Printexc.to_string e);
-		          (* Assume the server will figure out what's wrong and tell us over
+          | e ->
+              debug "HttpPut failure: %s\n%!" (Printexc.to_string e);
+              (* Assume the server will figure out what's wrong and tell us over
                  the normal communication channel *)
-		          marshal ofd (Response Failed) 
-	      end
+              marshal ofd (Response Failed)
+        end
     | Command (HttpGet(filename, url)) ->
-	      begin
-	        try
-	          let rec doit url =
-		          let (server,path) = parse_url url in
-		          debug "Opening connection to server '%s' path '%s'\n%!" server path;
-		          let ic, oc = open_tcp server in
-		          Printf.fprintf oc "GET %s HTTP/1.0\r\n\r\n" path;
-		          flush oc;
-		          (* Get the result header immediately *)
-		          let resultline = input_line ic in
-		          debug "Got %s\n%!" resultline;
-		          match http_response_code resultline with
-		          | 200 -> 
-		              (* Copy from channel to the file descriptor *)
-		              let finished = ref false in
-		              while not(!finished) do
-			              finished := input_line ic = "\r";
-		              done;
-		              let buffer = String.make 65536 '\000' in
-		              let finished = ref false in
-		              let fd = 
-			              try
-		                  if filename = "" then
-		                    Unix.dup Unix.stdout
-		                  else
-		                    Unix.openfile filename [ Unix.O_WRONLY; Unix.O_CREAT; Unix.O_EXCL ] 0o600
-		                with 
-		                  Unix.Unix_error (a,b,c) ->
-			                  (* Note that this will close the connection to the export handler, causing the task to fail *)
-			                  raise (ClientSideError (Printf.sprintf "%s: %s, %s." (Unix.error_message a) b c))
-		              in
-		              while not(!finished) do
-			              let num = input ic buffer 0 (String.length buffer) in
-			              begin try
-			                really_write fd buffer 0 num;
-			              with
-			                Unix.Unix_error (a,b,c) ->
-			                  raise (ClientSideError (Printf.sprintf "%s: %s, %s." (Unix.error_message a) b c))
-			              end;
-			              finished := num = 0;
-		              done;
-		              Unix.close fd;
-		              (try close_in ic with _ -> ()); (* Nb. Unix.close_connection only requires the in_channel *)
-		              marshal ofd (Response OK)
-		          | 302 ->
-		              let headers = read_rest_of_headers ic in
-		              let newloc = List.assoc "location" headers in
-		              (try close_in ic with _ -> ()); (* Nb. Unix.close_connection only requires the in_channel *)
-		              doit newloc
-		          | _ -> failwith "Unhandled response code"		    
-	          in
-	          doit url
-	        with 
-	        | ClientSideError msg ->
+        begin
+          try
+            let rec doit url =
+              let (server,path) = parse_url url in
+              debug "Opening connection to server '%s' path '%s'\n%!" server path;
+              let ic, oc = open_tcp server in
+              Printf.fprintf oc "GET %s HTTP/1.0\r\n\r\n" path;
+              flush oc;
+              (* Get the result header immediately *)
+              let resultline = input_line ic in
+              debug "Got %s\n%!" resultline;
+              match http_response_code resultline with
+              | 200 ->
+                  (* Copy from channel to the file descriptor *)
+                  let finished = ref false in
+                  while not(!finished) do
+                    finished := input_line ic = "\r";
+                  done;
+                  let buffer = String.make 65536 '\000' in
+                  let finished = ref false in
+                  let fd =
+                    try
+                      if filename = "" then
+                        Unix.dup Unix.stdout
+                      else
+                        Unix.openfile filename [ Unix.O_WRONLY; Unix.O_CREAT; Unix.O_EXCL ] 0o600
+                    with
+                      Unix.Unix_error (a,b,c) ->
+                        (* Note that this will close the connection to the export handler, causing the task to fail *)
+                        raise (ClientSideError (Printf.sprintf "%s: %s, %s." (Unix.error_message a) b c))
+                  in
+                  while not(!finished) do
+                    let num = input ic buffer 0 (String.length buffer) in
+                    begin try
+                      really_write fd buffer 0 num;
+                    with
+                      Unix.Unix_error (a,b,c) ->
+                        raise (ClientSideError (Printf.sprintf "%s: %s, %s." (Unix.error_message a) b c))
+                    end;
+                    finished := num = 0;
+                  done;
+                  Unix.close fd;
+                  (try close_in ic with _ -> ()); (* Nb. Unix.close_connection only requires the in_channel *)
+                  marshal ofd (Response OK)
+              | 302 ->
+                  let headers = read_rest_of_headers ic in
+                  let newloc = List.assoc "location" headers in
+                  (try close_in ic with _ -> ()); (* Nb. Unix.close_connection only requires the in_channel *)
+                  doit newloc
+              | _ -> failwith "Unhandled response code"
+            in
+            doit url
+          with
+          | ClientSideError msg ->
               marshal ofd (Response Failed);
               Printf.fprintf stderr "Operation failed. Error: %s\n" msg;
               exit_code := Some 1
-	        | e ->
-		          debug "HttpGet failure: %s\n%!" (Printexc.to_string e);
-		          marshal ofd (Response Failed) 
-	      end
-    | Command Prompt -> 
-	      let data = input_line stdin in
-	      marshal ofd (Blob (Chunk (Int32.of_int (String.length data))));
-	      ignore (Unix.write ofd data 0 (String.length data));
-	      marshal ofd (Blob End)
+          | e ->
+              debug "HttpGet failure: %s\n%!" (Printexc.to_string e);
+              marshal ofd (Response Failed)
+        end
+    | Command Prompt ->
+        let data = input_line stdin in
+        marshal ofd (Blob (Chunk (Int32.of_int (String.length data))));
+        ignore (Unix.write ofd data 0 (String.length data));
+        marshal ofd (Blob End)
     | Command (Error(code, params)) ->
-	      error "Error code: %s\n" code;
-	      error "Error parameters: %s\n" (String.concat ", " params)
+        error "Error code: %s\n" code;
+        error "Error parameters: %s\n" (String.concat ", " params)
     | Command (Exit c) ->
         exit_code := Some c
     | x ->
@@ -428,78 +459,68 @@
     Sys.set_signal Sys.sigpipe Sys.Signal_ignore;
     Sys.set_signal Sys.sigint (Sys.Signal_handle (fun _ -> exit 1));
     Stunnel.init_stunnel_path();
-    let args = Array.to_list Sys.argv in
-    let args = 
-      if List.mem "-debug" args 
-      then (debug_channel := Some stderr; List.filter (fun x -> x <> "-debug") args) 
-      else args in
-    let args = 
-      if List.mem "-debug-on-fail" args
-      then begin
-        let tmpfile, tmpch = Filename.open_temp_file "xe_debug_info" "tmp" in
-        debug_file := Some tmpfile; debug_channel := Some tmpch;
-        List.filter (fun x -> x <> "-debug-on-fail") args
-      end else args in
+    let xe, args =
+      match Array.to_list Sys.argv with
+      | h :: t -> h, t
+      | _ -> assert false in
     if List.mem "-version" args then begin
-	    Printf.printf "ThinCLI protocol: %d.%d\n" major minor;
-	    exit 0
+      Printf.printf "ThinCLI protocol: %d.%d\n" major minor;
+      exit 0
     end;
 
-    if List.length args < 2 then (usage (); exit 0) else
+    let args = parse_args args in
+
+    if List.length args < 1 then raise Usage else
       begin
-	      let extra_args = try Sys.getenv "XE_EXTRA_ARGS" with _ -> "" in
-	      let split_extra = List.filter (fun s -> String.length s > 1) (String.split ',' extra_args) in    
-	      let cmd = List.nth args 1 in
-	      let args = parse_args (cmd :: split_extra @ (List.tl (List.tl args))) in
-	      let ic, oc = open_channels () in
+        let ic, oc = open_channels () in
+        Printf.fprintf oc "POST /cli HTTP/1.0\r\n";
+        let args = args @ [("username="^ !xapiuname);("password="^ !xapipword)] in
+        let args = if !xapicompatmode then "compat"::args else args in
+        let args = String.concat "\n" args in
+        Printf.fprintf oc "User-agent: xe-cli/Unix/%d.%d\r\n" major minor;
+        Printf.fprintf oc "content-length: %d\r\n\r\n" (String.length args);
+        Printf.fprintf oc "%s" args;
+        flush_all ();
 
-	      Printf.fprintf oc "POST /cli HTTP/1.0\r\n";
-	      let args = a...@[("username="^ !xapiuname);("password="^ !xapipword)] in
-	      let args = if !xapicompatmode then "compat"::args else args in
-	      let args = String.concat "\n" args in
-	      Printf.fprintf oc "User-agent: xe-cli/Unix/%d.%d\r\n" major minor;
-	      Printf.fprintf oc "content-length: %d\r\n\r\n" (String.length args);
-	      Printf.fprintf oc "%s" args;
-	      flush_all ();
-
-	      let in_fd = Unix.descr_of_in_channel ic
-	      and out_fd = Unix.descr_of_out_channel oc in
-	      exit_status := main_loop in_fd out_fd
+        let in_fd = Unix.descr_of_in_channel ic
+        and out_fd = Unix.descr_of_out_channel oc in
+        exit_status := main_loop in_fd out_fd
       end
   with
-  | Usage -> 
+  | Usage ->
+      exit_status := 0;
       usage ();
   | Not_a_cli_server ->
-	    error "Failed to contact a running XenServer management agent.\n";
-	    error "Try specifying a server name and port.\n";
-	    usage();
+      error "Failed to contact a running XenServer management agent.\n";
+      error "Try specifying a server name and port.\n";
+      usage();
   | Protocol_version_mismatch x ->
-	    error "Protocol version mismatch: %s.\n" x;
-	    error "Try specifying a server name and port on the command-line.\n";
-	    usage();
+      error "Protocol version mismatch: %s.\n" x;
+      error "Try specifying a server name and port on the command-line.\n";
+      usage();
   | Not_found ->
-	    error "Host '%s' not found.\n" !xapiserver;
+      error "Host '%s' not found.\n" !xapiserver;
   | Unix.Unix_error(err,fn,arg) ->
-	    error "Error: %s (calling %s %s)\n" (Unix.error_message err) fn arg
+      error "Error: %s (calling %s %s)\n" (Unix.error_message err) fn arg
   | Connect_failure ->
-	    error "Unable to contact server. Please check server and port settings.\n"
+      error "Unable to contact server. Please check server and port settings.\n"
   | Stunnel.Stunnel_binary_missing ->
       error "Please install the stunnel package or define the XE_STUNNEL environment variable to point to the binary.\n"
   | End_of_file ->
-	    error "Lost connection to the server.\n"
+      error "Lost connection to the server.\n"
   | Unexpected_msg m ->
       error "Unexpected message from server: %s" (string_of_message m)
   | Stunnel_exit (i, e) ->
-      error "Stunnel process %d %s.\n" i 
-        (match e with 
+      error "Stunnel process %d %s.\n" i
+        (match e with
          | Unix.WEXITED c -> "existed with exit code " ^ string_of_int c
          | Unix.WSIGNALED c -> "killed by signal " ^ string_of_int c
          | Unix.WSTOPPED c -> "stopped by signal " ^ string_of_int c)
   | e ->
-	    error "Unhandled exception\n%s\n" (Printexc.to_string e) in
+      error "Unhandled exception\n%s\n" (Printexc.to_string e) in
   begin match !stunnel_process with
-  | Some p -> 
-      if Sys.file_exists p.Stunnel.logfile then 
+  | Some p ->
+      if Sys.file_exists p.Stunnel.logfile then
         begin
           if !exit_status <> 0 then
             (debug "\nStunnel diagnosis:\n\n";
_______________________________________________
xen-api mailing list
[email protected]
http://lists.xensource.com/mailman/listinfo/xen-api

Reply via email to