Don't stash strings in the handle.
[libguestfs.git] / src / generator.ml
index 5b6e1eb..7edfe67 100755 (executable)
@@ -305,9 +305,6 @@ configure script.
 You can also override this by setting the C<LIBGUESTFS_QEMU>
 environment variable.
 
-The string C<qemu> is stashed in the libguestfs handle, so the caller
-must make sure it remains valid for the lifetime of the handle.
-
 Setting C<qemu> to C<NULL> restores the default qemu binary.");
 
   ("get_qemu", (RConstString "qemu", []), -1, [],
@@ -328,9 +325,6 @@ Set the path that libguestfs searches for kernel and initrd.img.
 The default is C<$libdir/guestfs> unless overridden by setting
 C<LIBGUESTFS_PATH> environment variable.
 
-The string C<path> is stashed in the libguestfs handle, so the caller
-must make sure it remains valid for the lifetime of the handle.
-
 Setting C<path> to C<NULL> restores the default path.");
 
   ("get_path", (RConstString "path", []), -1, [],
@@ -342,6 +336,28 @@ Return the current search path.
 This is always non-NULL.  If it wasn't set already, then this will
 return the default path.");
 
+  ("set_append", (RErr, [String "append"]), -1, [FishAlias "append"],
+   [],
+   "add options to kernel command line",
+   "\
+This function is used to add additional options to the
+guest kernel command line.
+
+The default is C<NULL> unless overridden by setting
+C<LIBGUESTFS_APPEND> environment variable.
+
+Setting C<append> to C<NULL> means I<no> additional options
+are passed (libguestfs always adds a few of its own).");
+
+  ("get_append", (RConstString "append", []), -1, [],
+   [],
+   "get the additional kernel options",
+   "\
+Return the additional kernel options which are added to the
+guest kernel command line.
+
+If C<NULL> then no options are added.");
+
   ("set_autosync", (RErr, [Bool "autosync"]), -1, [FishAlias "autosync"],
    [],
    "set autosync mode",
@@ -1148,8 +1164,55 @@ The exact command which runs is C<file -bsL path>.  Note in
 particular that the filename is not prepended to the output
 (the C<-b> option).");
 
-  ("command", (RString "output", [StringList "arguments"]), 50, [],
-   [], (* XXX how to test? *)
+  ("command", (RString "output", [StringList "arguments"]), 50, [ProtocolLimitWarning],
+   [InitBasicFS, TestOutput (
+      [["upload"; "test-command"; "/test-command"];
+       ["chmod"; "493"; "/test-command"];
+       ["command"; "/test-command 1"]], "Result1");
+    InitBasicFS, TestOutput (
+      [["upload"; "test-command"; "/test-command"];
+       ["chmod"; "493"; "/test-command"];
+       ["command"; "/test-command 2"]], "Result2\n");
+    InitBasicFS, TestOutput (
+      [["upload"; "test-command"; "/test-command"];
+       ["chmod"; "493"; "/test-command"];
+       ["command"; "/test-command 3"]], "\nResult3");
+    InitBasicFS, TestOutput (
+      [["upload"; "test-command"; "/test-command"];
+       ["chmod"; "493"; "/test-command"];
+       ["command"; "/test-command 4"]], "\nResult4\n");
+    InitBasicFS, TestOutput (
+      [["upload"; "test-command"; "/test-command"];
+       ["chmod"; "493"; "/test-command"];
+       ["command"; "/test-command 5"]], "\nResult5\n\n");
+    InitBasicFS, TestOutput (
+      [["upload"; "test-command"; "/test-command"];
+       ["chmod"; "493"; "/test-command"];
+       ["command"; "/test-command 6"]], "\n\nResult6\n\n");
+    InitBasicFS, TestOutput (
+      [["upload"; "test-command"; "/test-command"];
+       ["chmod"; "493"; "/test-command"];
+       ["command"; "/test-command 7"]], "");
+    InitBasicFS, TestOutput (
+      [["upload"; "test-command"; "/test-command"];
+       ["chmod"; "493"; "/test-command"];
+       ["command"; "/test-command 8"]], "\n");
+    InitBasicFS, TestOutput (
+      [["upload"; "test-command"; "/test-command"];
+       ["chmod"; "493"; "/test-command"];
+       ["command"; "/test-command 9"]], "\n\n");
+    InitBasicFS, TestOutput (
+      [["upload"; "test-command"; "/test-command"];
+       ["chmod"; "493"; "/test-command"];
+       ["command"; "/test-command 10"]], "Result10-1\nResult10-2\n");
+    InitBasicFS, TestOutput (
+      [["upload"; "test-command"; "/test-command"];
+       ["chmod"; "493"; "/test-command"];
+       ["command"; "/test-command 11"]], "Result11-1\nResult11-2");
+    InitBasicFS, TestLastFail (
+      [["upload"; "test-command"; "/test-command"];
+       ["chmod"; "493"; "/test-command"];
+       ["command"; "/test-command"]])],
    "run a command from the guest filesystem",
    "\
 This call runs a command from the guest filesystem.  The
@@ -1162,6 +1225,13 @@ The first element is the name of the program to run.
 Subsequent elements are parameters.  The list must be
 non-empty (ie. must contain a program name).
 
+The return value is anything printed to I<stdout> by
+the command.
+
+If the command returns a non-zero exit status, then
+this function returns an error message.  The error message
+string is the content of I<stderr> from the command.
+
 The C<$PATH> environment variable will contain at least
 C</usr/bin> and C</bin>.  If you require a program from
 another location, you should provide the full path in the
@@ -1173,8 +1243,51 @@ correct places.  It is the caller's responsibility to ensure
 all filesystems that are needed are mounted at the right
 locations.");
 
-  ("command_lines", (RStringList "lines", [StringList "arguments"]), 51, [],
-   [], (* XXX how to test? *)
+  ("command_lines", (RStringList "lines", [StringList "arguments"]), 51, [ProtocolLimitWarning],
+   [InitBasicFS, TestOutputList (
+      [["upload"; "test-command"; "/test-command"];
+       ["chmod"; "493"; "/test-command"];
+       ["command_lines"; "/test-command 1"]], ["Result1"]);
+    InitBasicFS, TestOutputList (
+      [["upload"; "test-command"; "/test-command"];
+       ["chmod"; "493"; "/test-command"];
+       ["command_lines"; "/test-command 2"]], ["Result2"]);
+    InitBasicFS, TestOutputList (
+      [["upload"; "test-command"; "/test-command"];
+       ["chmod"; "493"; "/test-command"];
+       ["command_lines"; "/test-command 3"]], ["";"Result3"]);
+    InitBasicFS, TestOutputList (
+      [["upload"; "test-command"; "/test-command"];
+       ["chmod"; "493"; "/test-command"];
+       ["command_lines"; "/test-command 4"]], ["";"Result4"]);
+    InitBasicFS, TestOutputList (
+      [["upload"; "test-command"; "/test-command"];
+       ["chmod"; "493"; "/test-command"];
+       ["command_lines"; "/test-command 5"]], ["";"Result5";""]);
+    InitBasicFS, TestOutputList (
+      [["upload"; "test-command"; "/test-command"];
+       ["chmod"; "493"; "/test-command"];
+       ["command_lines"; "/test-command 6"]], ["";"";"Result6";""]);
+    InitBasicFS, TestOutputList (
+      [["upload"; "test-command"; "/test-command"];
+       ["chmod"; "493"; "/test-command"];
+       ["command_lines"; "/test-command 7"]], []);
+    InitBasicFS, TestOutputList (
+      [["upload"; "test-command"; "/test-command"];
+       ["chmod"; "493"; "/test-command"];
+       ["command_lines"; "/test-command 8"]], [""]);
+    InitBasicFS, TestOutputList (
+      [["upload"; "test-command"; "/test-command"];
+       ["chmod"; "493"; "/test-command"];
+       ["command_lines"; "/test-command 9"]], ["";""]);
+    InitBasicFS, TestOutputList (
+      [["upload"; "test-command"; "/test-command"];
+       ["chmod"; "493"; "/test-command"];
+       ["command_lines"; "/test-command 10"]], ["Result10-1";"Result10-2"]);
+    InitBasicFS, TestOutputList (
+      [["upload"; "test-command"; "/test-command"];
+       ["chmod"; "493"; "/test-command"];
+       ["command_lines"; "/test-command 11"]], ["Result11-1";"Result11-2"])],
    "run a command, returning lines",
    "\
 This is the same as C<guestfs_command>, but splits the
@@ -1820,7 +1933,10 @@ The external L<cmp(1)> program is used for the comparison.");
   ("strings", (RStringList "stringsout", [String "path"]), 94, [ProtocolLimitWarning],
    [InitBasicFS, TestOutputList (
       [["write_file"; "/new"; "hello\nworld\n"; "0"];
-       ["strings"; "/new"]], ["hello"; "world"])],
+       ["strings"; "/new"]], ["hello"; "world"]);
+    InitBasicFS, TestOutputList (
+      [["touch"; "/new"];
+       ["strings"; "/new"]], [])],
    "print the printable strings in a file",
    "\
 This runs the L<strings(1)> command on a file and returns
@@ -2214,14 +2330,15 @@ let chan = ref stdout
 let pr fs = ksprintf (output_string !chan) fs
 
 (* Generate a header block in a number of standard styles. *)
-type comment_style = CStyle | HashStyle | OCamlStyle
+type comment_style = CStyle | HashStyle | OCamlStyle | HaskellStyle
 type license = GPLv2 | LGPLv2
 
 let generate_header comment license =
   let c = match comment with
     | CStyle ->     pr "/* "; " *"
     | HashStyle ->  pr "# ";  "#"
-    | OCamlStyle -> pr "(* "; " *" in
+    | OCamlStyle -> pr "(* "; " *"
+    | HaskellStyle -> pr "{- "; "  " in
   pr "libguestfs generated file\n";
   pr "%s WARNING: THIS FILE IS GENERATED BY 'src/generator.ml'.\n" c;
   pr "%s ANY CHANGES YOU MAKE TO THIS FILE WILL BE LOST.\n" c;
@@ -2263,6 +2380,7 @@ let generate_header comment license =
    | CStyle -> pr " */\n"
    | HashStyle -> ()
    | OCamlStyle -> pr " *)\n"
+   | HaskellStyle -> pr "-}\n"
   );
   pr "\n"
 
@@ -4860,7 +4978,7 @@ XS_unpack_charPtrPtr (SV *arg) {
     croak (\"array reference expected\");
 
   av = (AV *)SvRV (arg);
-  ret = malloc (av_len (av) + 1 + 1);
+  ret = malloc ((av_len (av) + 1 + 1) * sizeof (char *));
   if (!ret)
     croak (\"malloc failed\");
 
@@ -6528,6 +6646,207 @@ and generate_java_lvm_return typ jtyp cols =
   pr "  guestfs_free_lvm_%s_list (r);\n" typ;
   pr "  return jr;\n"
 
+and generate_haskell_hs () =
+  generate_header HaskellStyle LGPLv2;
+
+  (* XXX We only know how to generate partial FFI for Haskell
+   * at the moment.  Please help out!
+   *)
+  let can_generate style =
+    let check_no_bad_args =
+      List.for_all (function Bool _ | Int _ -> false | _ -> true)
+    in
+    match style with
+    | RErr, args -> check_no_bad_args args
+    | RBool _, _
+    | RInt _, _
+    | RInt64 _, _
+    | RConstString _, _
+    | RString _, _
+    | RStringList _, _
+    | RIntBool _, _
+    | RPVList _, _
+    | RVGList _, _
+    | RLVList _, _
+    | RStat _, _
+    | RStatVFS _, _
+    | RHashtable _, _ -> false in
+
+  pr "\
+{-# INCLUDE <guestfs.h> #-}
+{-# LANGUAGE ForeignFunctionInterface #-}
+
+module Guestfs (
+  create";
+
+  (* List out the names of the actions we want to export. *)
+  List.iter (
+    fun (name, style, _, _, _, _, _) ->
+      if can_generate style then pr ",\n  %s" name
+  ) all_functions;
+
+  pr "
+  ) where
+import Foreign
+import Foreign.C
+import IO
+import Control.Exception
+import Data.Typeable
+
+data GuestfsS = GuestfsS            -- represents the opaque C struct
+type GuestfsP = Ptr GuestfsS        -- guestfs_h *
+type GuestfsH = ForeignPtr GuestfsS -- guestfs_h * with attached finalizer
+
+-- XXX define properly later XXX
+data PV = PV
+data VG = VG
+data LV = LV
+data IntBool = IntBool
+data Stat = Stat
+data StatVFS = StatVFS
+data Hashtable = Hashtable
+
+foreign import ccall unsafe \"guestfs_create\" c_create
+  :: IO GuestfsP
+foreign import ccall unsafe \"&guestfs_close\" c_close
+  :: FunPtr (GuestfsP -> IO ())
+foreign import ccall unsafe \"guestfs_set_error_handler\" c_set_error_handler
+  :: GuestfsP -> Ptr CInt -> Ptr CInt -> IO ()
+
+create :: IO GuestfsH
+create = do
+  p <- c_create
+  c_set_error_handler p nullPtr nullPtr
+  h <- newForeignPtr c_close p
+  return h
+
+foreign import ccall unsafe \"guestfs_last_error\" c_last_error
+  :: GuestfsP -> IO CString
+
+-- last_error :: GuestfsH -> IO (Maybe String)
+-- last_error h = do
+--   str <- withForeignPtr h (\\p -> c_last_error p)
+--   maybePeek peekCString str
+
+last_error :: GuestfsH -> IO (String)
+last_error h = do
+  str <- withForeignPtr h (\\p -> c_last_error p)
+  if (str == nullPtr)
+    then return \"no error\"
+    else peekCString str
+
+";
+
+  (* Generate wrappers for each foreign function. *)
+  List.iter (
+    fun (name, style, _, _, _, _, _) ->
+      if can_generate style then (
+       pr "foreign import ccall unsafe \"guestfs_%s\" c_%s\n" name name;
+       pr "  :: ";
+       generate_haskell_prototype ~handle:"GuestfsP" style;
+       pr "\n";
+       pr "\n";
+       pr "%s :: " name;
+       generate_haskell_prototype ~handle:"GuestfsH" ~hs:true style;
+       pr "\n";
+       pr "%s %s = do\n" name
+         (String.concat " " ("h" :: List.map name_of_argt (snd style)));
+       pr "  r <- ";
+       List.iter (
+         function
+         | FileIn n
+         | FileOut n
+         | String n -> pr "withCString %s $ \\%s -> " n n
+         | OptString n -> pr "maybeWith withCString %s $ \\%s -> " n n
+         | StringList n -> pr "withMany withCString %s $ \\%s -> withArray0 nullPtr %s $ \\%s -> " n n n n
+         | Bool n ->
+             (* XXX this doesn't work *)
+             pr "      let\n";
+             pr "        %s = case %s of\n" n n;
+             pr "          False -> 0\n";
+             pr "          True -> 1\n";
+             pr "      in fromIntegral %s $ \\%s ->\n" n n
+         | Int n -> pr "fromIntegral %s $ \\%s -> " n n
+       ) (snd style);
+       pr "withForeignPtr h (\\p -> c_%s %s)\n" name
+         (String.concat " " ("p" :: List.map name_of_argt (snd style)));
+       (match fst style with
+        | RErr | RInt _ | RInt64 _ | RBool _ ->
+            pr "  if (r == -1)\n";
+            pr "    then do\n";
+            pr "      err <- last_error h\n";
+            pr "      fail err\n";
+        | RConstString _ | RString _ | RStringList _ | RIntBool _
+        | RPVList _ | RVGList _ | RLVList _ | RStat _ | RStatVFS _
+        | RHashtable _ ->
+            pr "  if (r == nullPtr)\n";
+            pr "    then do\n";
+            pr "      err <- last_error h\n";
+            pr "      fail err\n";
+       );
+       (match fst style with
+        | RErr ->
+            pr "    else return ()\n"
+        | RInt _ ->
+            pr "    else return (fromIntegral r)\n"
+        | RInt64 _ ->
+            pr "    else return (fromIntegral r)\n"
+        | RBool _ ->
+            pr "    else return (toBool r)\n"
+        | RConstString _
+        | RString _
+        | RStringList _
+        | RIntBool _
+        | RPVList _
+        | RVGList _
+        | RLVList _
+        | RStat _
+        | RStatVFS _
+        | RHashtable _ ->
+            pr "    else return ()\n" (* XXXXXXXXXXXXXXXXXXXX *)
+       );
+       pr "\n";
+      )
+  ) all_functions
+
+and generate_haskell_prototype ~handle ?(hs = false) style =
+  pr "%s -> " handle;
+  let string = if hs then "String" else "CString" in
+  let int = if hs then "Int" else "CInt" in
+  let bool = if hs then "Bool" else "CInt" in
+  let int64 = if hs then "Integer" else "Int64" in
+  List.iter (
+    fun arg ->
+      (match arg with
+       | String _ -> pr "%s" string
+       | OptString _ -> if hs then pr "Maybe String" else pr "CString"
+       | StringList _ -> if hs then pr "[String]" else pr "Ptr CString"
+       | Bool _ -> pr "%s" bool
+       | Int _ -> pr "%s" int
+       | FileIn _ -> pr "%s" string
+       | FileOut _ -> pr "%s" string
+      );
+      pr " -> ";
+  ) (snd style);
+  pr "IO (";
+  (match fst style with
+   | RErr -> if not hs then pr "CInt"
+   | RInt _ -> pr "%s" int
+   | RInt64 _ -> pr "%s" int64
+   | RBool _ -> pr "%s" bool
+   | RConstString _ -> pr "%s" string
+   | RString _ -> pr "%s" string
+   | RStringList _ -> pr "[%s]" string
+   | RIntBool _ -> pr "IntBool"
+   | RPVList _ -> pr "[PV]"
+   | RVGList _ -> pr "[VG]"
+   | RLVList _ -> pr "[LV]"
+   | RStat _ -> pr "Stat"
+   | RStatVFS _ -> pr "StatVFS"
+   | RHashtable _ -> pr "Hashtable"
+  );
+  pr ")"
+
 let output_to filename =
   let filename_new = filename ^ ".new" in
   chan := open_out filename_new;
@@ -6668,3 +6987,7 @@ Run it from the top source directory using the command
   let close = output_to "java/com_redhat_et_libguestfs_GuestFS.c" in
   generate_java_c ();
   close ();
+
+  let close = output_to "haskell/Guestfs.hs" in
+  generate_haskell_hs ();
+  close ();