From 4211c7a258debd236017a19c70965bc1b3658edb Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Mon, 22 Jun 2009 07:49:50 +0100 Subject: [PATCH] Generated code for 'sh' and 'sh-lines' commands. --- capitests/tests.c | 2 + daemon/actions.h | 2 + daemon/stubs.c | 61 ++++++++++ fish/cmds.c | 52 +++++++- fish/completion.c | 2 + guestfish-actions.pod | 32 ++++- guestfs-actions.pod | 41 ++++++- java/com/redhat/et/libguestfs/GuestFS.java | 54 ++++++++- java/com_redhat_et_libguestfs_GuestFS.c | 54 +++++++++ ocaml/guestfs.ml | 2 + ocaml/guestfs.mli | 6 + ocaml/guestfs_c_actions.c | 50 ++++++++ perl/Guestfs.xs | 34 ++++++ perl/lib/Sys/Guestfs.pm | 28 ++++- python/guestfs-py.c | 52 ++++++++ python/guestfs.py | 32 ++++- ruby/ext/guestfs/_guestfs.c | 56 +++++++++ src/guestfs-actions.c | 189 +++++++++++++++++++++++++++++ src/guestfs-actions.h | 2 + src/guestfs_protocol.c | 41 +++++++ src/guestfs_protocol.h | 35 +++++- src/guestfs_protocol.x | 18 +++ 22 files changed, 837 insertions(+), 8 deletions(-) diff --git a/capitests/tests.c b/capitests/tests.c index 0e42cb4..e5466b5 100644 --- a/capitests/tests.c +++ b/capitests/tests.c @@ -148,6 +148,8 @@ static void no_test_warnings (void) fprintf (stderr, "warning: \"guestfs_vg_activate\" has no tests\n"); fprintf (stderr, "warning: \"guestfs_resize2fs\" has no tests\n"); fprintf (stderr, "warning: \"guestfs_e2fsck_f\" has no tests\n"); + fprintf (stderr, "warning: \"guestfs_sh\" has no tests\n"); + fprintf (stderr, "warning: \"guestfs_sh_lines\" has no tests\n"); } static int test_ntfs_3g_probe_0_skip (void) diff --git a/daemon/actions.h b/daemon/actions.h index c8e285c..a76498f 100644 --- a/daemon/actions.h +++ b/daemon/actions.h @@ -131,3 +131,5 @@ extern char **do_find (char *directory); extern int do_e2fsck_f (char *device); extern int do_sleep (int secs); extern int do_ntfs_3g_probe (int rw, char *device); +extern char *do_sh (char *command); +extern char **do_sh_lines (char *command); diff --git a/daemon/stubs.c b/daemon/stubs.c index 7842640..dcdc51c 100644 --- a/daemon/stubs.c +++ b/daemon/stubs.c @@ -2771,6 +2771,61 @@ done: xdr_free ((xdrproc_t) xdr_guestfs_ntfs_3g_probe_args, (char *) &args); } +static void sh_stub (XDR *xdr_in) +{ + char *r; + struct guestfs_sh_args args; + char *command; + + memset (&args, 0, sizeof args); + + if (!xdr_guestfs_sh_args (xdr_in, &args)) { + reply_with_error ("%s: daemon failed to decode procedure arguments", "sh"); + return; + } + command = args.command; + + r = do_sh (command); + if (r == NULL) + /* do_sh has already called reply_with_error */ + goto done; + + struct guestfs_sh_ret ret; + ret.output = r; + reply ((xdrproc_t) &xdr_guestfs_sh_ret, (char *) &ret); + free (r); +done: + xdr_free ((xdrproc_t) xdr_guestfs_sh_args, (char *) &args); +} + +static void sh_lines_stub (XDR *xdr_in) +{ + char **r; + struct guestfs_sh_lines_args args; + char *command; + + memset (&args, 0, sizeof args); + + if (!xdr_guestfs_sh_lines_args (xdr_in, &args)) { + reply_with_error ("%s: daemon failed to decode procedure arguments", "sh_lines"); + return; + } + command = args.command; + + r = do_sh_lines (command); + if (r == NULL) + /* do_sh_lines has already called reply_with_error */ + goto done; + + struct guestfs_sh_lines_ret ret; + ret.lines.lines_len = count_strings (r); + ret.lines.lines_val = r; + reply ((xdrproc_t) &xdr_guestfs_sh_lines_ret, (char *) &ret); + free_strings (r); +done: + xdr_free ((xdrproc_t) xdr_guestfs_sh_lines_args, (char *) &args); +} + void dispatch_incoming_message (XDR *xdr_in) { switch (proc_nr) { @@ -3104,6 +3159,12 @@ void dispatch_incoming_message (XDR *xdr_in) case GUESTFS_PROC_NTFS_3G_PROBE: ntfs_3g_probe_stub (xdr_in); break; + case GUESTFS_PROC_SH: + sh_stub (xdr_in); + break; + case GUESTFS_PROC_SH_LINES: + sh_lines_stub (xdr_in); + break; default: reply_with_error ("dispatch_incoming_message: unknown procedure number %d", proc_nr); } diff --git a/fish/cmds.c b/fish/cmds.c index 334d715..fe1ef92 100644 --- a/fish/cmds.c +++ b/fish/cmds.c @@ -138,6 +138,8 @@ void list_commands (void) printf ("%-20s %s\n", "sfdisk-disk-geometry", "display the disk geometry from the partition table"); printf ("%-20s %s\n", "sfdisk-kernel-geometry", "display the kernel geometry"); printf ("%-20s %s\n", "sfdisk-l", "display the partition table"); + printf ("%-20s %s\n", "sh", "run a command via the shell"); + printf ("%-20s %s\n", "sh-lines", "run a command via the shell returning lines"); printf ("%-20s %s\n", "sleep", "sleep for some seconds"); printf ("%-20s %s\n", "stat", "get file information"); printf ("%-20s %s\n", "statvfs", "get file system statistics"); @@ -378,10 +380,10 @@ void display_command (const char *cmd) pod2text ("file - determine file type", " file \n\nThis call uses the standard L command to determine\nthe type or contents of the file. This also works on devices,\nfor example to find out whether a partition contains a filesystem.\n\nThe exact command which runs is C. Note in\nparticular that the filename is not prepended to the output\n(the C<-b> option)."); else if (strcasecmp (cmd, "command") == 0) - pod2text ("command - run a command from the guest filesystem", " command \n\nThis call runs a command from the guest filesystem. The\nfilesystem must be mounted, and must contain a compatible\noperating system (ie. something Linux, with the same\nor compatible processor architecture).\n\nThe single parameter is an argv-style list of arguments.\nThe first element is the name of the program to run.\nSubsequent elements are parameters. The list must be\nnon-empty (ie. must contain a program name).\n\nThe return value is anything printed to I by\nthe command.\n\nIf the command returns a non-zero exit status, then\nthis function returns an error message. The error message\nstring is the content of I from the command.\n\nThe C<$PATH> environment variable will contain at least\nC and C. If you require a program from\nanother location, you should provide the full path in the\nfirst parameter.\n\nShared libraries and data files required by the program\nmust be available on filesystems which are mounted in the\ncorrect places. It is the caller's responsibility to ensure\nall filesystems that are needed are mounted at the right\nlocations.\n\nBecause of the message protocol, there is a transfer limit \nof somewhere between 2MB and 4MB. To transfer large files you should use\nFTP."); + pod2text ("command - run a command from the guest filesystem", " command \n\nThis call runs a command from the guest filesystem. The\nfilesystem must be mounted, and must contain a compatible\noperating system (ie. something Linux, with the same\nor compatible processor architecture).\n\nThe single parameter is an argv-style list of arguments.\nThe first element is the name of the program to run.\nSubsequent elements are parameters. The list must be\nnon-empty (ie. must contain a program name). Note that\nthe command runs directly, and is I invoked via\nthe shell (see C).\n\nThe return value is anything printed to I by\nthe command.\n\nIf the command returns a non-zero exit status, then\nthis function returns an error message. The error message\nstring is the content of I from the command.\n\nThe C<$PATH> environment variable will contain at least\nC and C. If you require a program from\nanother location, you should provide the full path in the\nfirst parameter.\n\nShared libraries and data files required by the program\nmust be available on filesystems which are mounted in the\ncorrect places. It is the caller's responsibility to ensure\nall filesystems that are needed are mounted at the right\nlocations.\n\nBecause of the message protocol, there is a transfer limit \nof somewhere between 2MB and 4MB. To transfer large files you should use\nFTP."); else if (strcasecmp (cmd, "command_lines") == 0 || strcasecmp (cmd, "command-lines") == 0) - pod2text ("command-lines - run a command, returning lines", " command-lines \n\nThis is the same as C, but splits the\nresult into a list of lines.\n\nBecause of the message protocol, there is a transfer limit \nof somewhere between 2MB and 4MB. To transfer large files you should use\nFTP."); + pod2text ("command-lines - run a command, returning lines", " command-lines \n\nThis is the same as C, but splits the\nresult into a list of lines.\n\nSee also: C\n\nBecause of the message protocol, there is a transfer limit \nof somewhere between 2MB and 4MB. To transfer large files you should use\nFTP."); else if (strcasecmp (cmd, "stat") == 0) pod2text ("stat - get file information", " stat \n\nReturns file information for the given C.\n\nThis is the same as the C system call."); @@ -560,6 +562,12 @@ void display_command (const char *cmd) if (strcasecmp (cmd, "ntfs_3g_probe") == 0 || strcasecmp (cmd, "ntfs-3g-probe") == 0) pod2text ("ntfs-3g-probe - probe NTFS volume", " ntfs-3g-probe \n\nThis command runs the L command which probes\nan NTFS C for mountability. (Not all NTFS volumes can\nbe mounted read-write, and some cannot be mounted at all).\n\nC is a boolean flag. Set it to true if you want to test\nif the volume can be mounted read-write. Set it to false if\nyou want to test if the volume can be mounted read-only.\n\nThe return value is an integer which C<0> if the operation\nwould succeed, or some non-zero value documented in the\nL manual page."); else + if (strcasecmp (cmd, "sh") == 0) + pod2text ("sh - run a command via the shell", " sh \n\nThis call runs a command from the guest filesystem via the\nguest's C.\n\nThis is like C, but passes the command to:\n\n /bin/sh -c \"command\"\n\nDepending on the guest's shell, this usually results in\nwildcards being expanded, shell expressions being interpolated\nand so on.\n\nAll the provisos about C apply to this call."); + else + if (strcasecmp (cmd, "sh_lines") == 0 || strcasecmp (cmd, "sh-lines") == 0) + pod2text ("sh-lines - run a command via the shell returning lines", " sh-lines \n\nThis is the same as C, but splits the result\ninto a list of lines.\n\nSee also: C"); + else display_builtin_command (cmd); } @@ -2738,6 +2746,40 @@ static int run_ntfs_3g_probe (const char *cmd, int argc, char *argv[]) return 0; } +static int run_sh (const char *cmd, int argc, char *argv[]) +{ + char *r; + const char *command; + if (argc != 1) { + fprintf (stderr, "%s should have 1 parameter(s)\n", cmd); + fprintf (stderr, "type 'help %s' for help on %s\n", cmd, cmd); + return -1; + } + command = argv[0]; + r = guestfs_sh (g, command); + if (r == NULL) return -1; + printf ("%s\n", r); + free (r); + return 0; +} + +static int run_sh_lines (const char *cmd, int argc, char *argv[]) +{ + char **r; + const char *command; + if (argc != 1) { + fprintf (stderr, "%s should have 1 parameter(s)\n", cmd); + fprintf (stderr, "type 'help %s' for help on %s\n", cmd, cmd); + return -1; + } + command = argv[0]; + r = guestfs_sh_lines (g, command); + if (r == NULL) return -1; + print_strings (r); + free_strings (r); + return 0; +} + int run_action (const char *cmd, int argc, char *argv[]) { if (strcasecmp (cmd, "launch") == 0 || strcasecmp (cmd, "run") == 0) @@ -3133,6 +3175,12 @@ int run_action (const char *cmd, int argc, char *argv[]) if (strcasecmp (cmd, "ntfs_3g_probe") == 0 || strcasecmp (cmd, "ntfs-3g-probe") == 0) return run_ntfs_3g_probe (cmd, argc, argv); else + if (strcasecmp (cmd, "sh") == 0) + return run_sh (cmd, argc, argv); + else + if (strcasecmp (cmd, "sh_lines") == 0 || strcasecmp (cmd, "sh-lines") == 0) + return run_sh_lines (cmd, argc, argv); + else { fprintf (stderr, "%s: unknown command\n", cmd); return -1; diff --git a/fish/completion.c b/fish/completion.c index 43c4f64..4ac0fad 100644 --- a/fish/completion.c +++ b/fish/completion.c @@ -177,6 +177,8 @@ static const char *const commands[] = { "e2fsck-f", "sleep", "ntfs-3g-probe", + "sh", + "sh-lines", NULL }; diff --git a/guestfish-actions.pod b/guestfish-actions.pod index ee3f641..3217d92 100644 --- a/guestfish-actions.pod +++ b/guestfish-actions.pod @@ -401,7 +401,9 @@ or compatible processor architecture). The single parameter is an argv-style list of arguments. 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). +non-empty (ie. must contain a program name). Note that +the command runs directly, and is I invoked via +the shell (see C). The return value is anything printed to I by the command. @@ -432,6 +434,8 @@ FTP. This is the same as C, but splits the result into a list of lines. +See also: C + Because of the message protocol, there is a transfer limit of somewhere between 2MB and 4MB. To transfer large files you should use FTP. @@ -1226,6 +1230,32 @@ This displays the partition table on C, in the human-readable output of the L command. It is not intended to be parsed. +=head2 sh + + sh command + +This call runs a command from the guest filesystem via the +guest's C. + +This is like C, but passes the command to: + + /bin/sh -c "command" + +Depending on the guest's shell, this usually results in +wildcards being expanded, shell expressions being interpolated +and so on. + +All the provisos about C apply to this call. + +=head2 sh-lines + + sh-lines command + +This is the same as C, but splits the result +into a list of lines. + +See also: C + =head2 sleep sleep secs diff --git a/guestfs-actions.pod b/guestfs-actions.pod index f37ec50..93a9fbb 100644 --- a/guestfs-actions.pod +++ b/guestfs-actions.pod @@ -511,7 +511,9 @@ or compatible processor architecture). The single parameter is an argv-style list of arguments. 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). +non-empty (ie. must contain a program name). Note that +the command runs directly, and is I invoked via +the shell (see C). The return value is anything printed to I by the command. @@ -546,6 +548,8 @@ FTP. This is the same as C, but splits the result into a list of lines. +See also: C + This function returns a NULL-terminated array of strings (like L), or NULL if there was an error. I. @@ -1649,6 +1653,41 @@ not intended to be parsed. This function returns a string, or NULL on error. I. +=head2 guestfs_sh + + char *guestfs_sh (guestfs_h *handle, + const char *command); + +This call runs a command from the guest filesystem via the +guest's C. + +This is like C, but passes the command to: + + /bin/sh -c "command" + +Depending on the guest's shell, this usually results in +wildcards being expanded, shell expressions being interpolated +and so on. + +All the provisos about C apply to this call. + +This function returns a string, or NULL on error. +I. + +=head2 guestfs_sh_lines + + char **guestfs_sh_lines (guestfs_h *handle, + const char *command); + +This is the same as C, but splits the result +into a list of lines. + +See also: C + +This function returns a NULL-terminated array of strings +(like L), or NULL if there was an error. +I. + =head2 guestfs_sleep int guestfs_sleep (guestfs_h *handle, diff --git a/java/com/redhat/et/libguestfs/GuestFS.java b/java/com/redhat/et/libguestfs/GuestFS.java index d29ae54..4075420 100644 --- a/java/com/redhat/et/libguestfs/GuestFS.java +++ b/java/com/redhat/et/libguestfs/GuestFS.java @@ -1998,7 +1998,9 @@ public HashMap test0rhashtableerr () * The single parameter is an argv-style list of arguments. * 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). + * non-empty (ie. must contain a program name). Note that + * the command runs directly, and is *not* invoked via the + * shell (see "g.sh"). *

* The return value is anything printed to *stdout* by the * command. @@ -2040,6 +2042,8 @@ public HashMap test0rhashtableerr () * This is the same as "g.command", but splits the result * into a list of lines. *

+ * See also: "g.sh_lines" + *

* Because of the message protocol, there is a transfer * limit of somewhere between 2MB and 4MB. To transfer * large files you should use FTP. @@ -3383,4 +3387,52 @@ public HashMap test0rhashtableerr () private native int _ntfs_3g_probe (long g, boolean rw, String device) throws LibGuestFSException; + /** + * run a command via the shell + *

+ * This call runs a command from the guest filesystem via + * the guest's "/bin/sh". + *

+ * This is like "g.command", but passes the command to: + *

+ * /bin/sh -c "command" + *

+ * Depending on the guest's shell, this usually results in + * wildcards being expanded, shell expressions being + * interpolated and so on. + *

+ * All the provisos about "g.command" apply to this call. + *

+ * @throws LibGuestFSException + */ + public String sh (String command) + throws LibGuestFSException + { + if (g == 0) + throw new LibGuestFSException ("sh: handle is closed"); + return _sh (g, command); + } + private native String _sh (long g, String command) + throws LibGuestFSException; + + /** + * run a command via the shell returning lines + *

+ * This is the same as "g.sh", but splits the result into a + * list of lines. + *

+ * See also: "g.command_lines" + *

+ * @throws LibGuestFSException + */ + public String[] sh_lines (String command) + throws LibGuestFSException + { + if (g == 0) + throw new LibGuestFSException ("sh_lines: handle is closed"); + return _sh_lines (g, command); + } + private native String[] _sh_lines (long g, String command) + throws LibGuestFSException; + } diff --git a/java/com_redhat_et_libguestfs_GuestFS.c b/java/com_redhat_et_libguestfs_GuestFS.c index 91e3cf1..631e48f 100644 --- a/java/com_redhat_et_libguestfs_GuestFS.c +++ b/java/com_redhat_et_libguestfs_GuestFS.c @@ -3986,3 +3986,57 @@ Java_com_redhat_et_libguestfs_GuestFS__1ntfs_13g_1probe return (jint) r; } +JNIEXPORT jstring JNICALL +Java_com_redhat_et_libguestfs_GuestFS__1sh + (JNIEnv *env, jobject obj, jlong jg, jstring jcommand) +{ + guestfs_h *g = (guestfs_h *) (long) jg; + jstring jr; + char *r; + const char *command; + + command = (*env)->GetStringUTFChars (env, jcommand, NULL); + r = guestfs_sh (g, command); + (*env)->ReleaseStringUTFChars (env, jcommand, command); + if (r == NULL) { + throw_exception (env, guestfs_last_error (g)); + return NULL; + } + jr = (*env)->NewStringUTF (env, r); + free (r); + return jr; +} + +JNIEXPORT jobjectArray JNICALL +Java_com_redhat_et_libguestfs_GuestFS__1sh_1lines + (JNIEnv *env, jobject obj, jlong jg, jstring jcommand) +{ + guestfs_h *g = (guestfs_h *) (long) jg; + jobjectArray jr; + int r_len; + jclass cl; + jstring jstr; + char **r; + const char *command; + int i; + + command = (*env)->GetStringUTFChars (env, jcommand, NULL); + r = guestfs_sh_lines (g, command); + (*env)->ReleaseStringUTFChars (env, jcommand, command); + if (r == NULL) { + throw_exception (env, guestfs_last_error (g)); + return NULL; + } + for (r_len = 0; r[r_len] != NULL; ++r_len) ; + cl = (*env)->FindClass (env, "java/lang/String"); + jstr = (*env)->NewStringUTF (env, ""); + jr = (*env)->NewObjectArray (env, r_len, cl, jstr); + for (i = 0; i < r_len; ++i) { + jstr = (*env)->NewStringUTF (env, r[i]); + (*env)->SetObjectArrayElement (env, jr, i, jstr); + free (r[i]); + } + free (r); + return jr; +} + diff --git a/ocaml/guestfs.ml b/ocaml/guestfs.ml index fb43214..f102459 100644 --- a/ocaml/guestfs.ml +++ b/ocaml/guestfs.ml @@ -277,3 +277,5 @@ external find : t -> string -> string array = "ocaml_guestfs_find" external e2fsck_f : t -> string -> unit = "ocaml_guestfs_e2fsck_f" external sleep : t -> int -> unit = "ocaml_guestfs_sleep" external ntfs_3g_probe : t -> bool -> string -> int = "ocaml_guestfs_ntfs_3g_probe" +external sh : t -> string -> string = "ocaml_guestfs_sh" +external sh_lines : t -> string -> string array = "ocaml_guestfs_sh_lines" diff --git a/ocaml/guestfs.mli b/ocaml/guestfs.mli index 859774a..8983a16 100644 --- a/ocaml/guestfs.mli +++ b/ocaml/guestfs.mli @@ -610,3 +610,9 @@ val sleep : t -> int -> unit val ntfs_3g_probe : t -> bool -> string -> int (** probe NTFS volume *) +val sh : t -> string -> string +(** run a command via the shell *) + +val sh_lines : t -> string -> string array +(** run a command via the shell returning lines *) + diff --git a/ocaml/guestfs_c_actions.c b/ocaml/guestfs_c_actions.c index 45c9883..e4dc509 100644 --- a/ocaml/guestfs_c_actions.c +++ b/ocaml/guestfs_c_actions.c @@ -4198,3 +4198,53 @@ ocaml_guestfs_ntfs_3g_probe (value gv, value rwv, value devicev) CAMLreturn (rv); } +CAMLprim value +ocaml_guestfs_sh (value gv, value commandv) +{ + CAMLparam2 (gv, commandv); + CAMLlocal1 (rv); + + guestfs_h *g = Guestfs_val (gv); + if (g == NULL) + caml_failwith ("sh: used handle after closing it"); + + const char *command = String_val (commandv); + char *r; + + caml_enter_blocking_section (); + r = guestfs_sh (g, command); + caml_leave_blocking_section (); + if (r == NULL) + ocaml_guestfs_raise_error (g, "sh"); + + rv = caml_copy_string (r); + free (r); + CAMLreturn (rv); +} + +CAMLprim value +ocaml_guestfs_sh_lines (value gv, value commandv) +{ + CAMLparam2 (gv, commandv); + CAMLlocal1 (rv); + + guestfs_h *g = Guestfs_val (gv); + if (g == NULL) + caml_failwith ("sh_lines: used handle after closing it"); + + const char *command = String_val (commandv); + int i; + char **r; + + caml_enter_blocking_section (); + r = guestfs_sh_lines (g, command); + caml_leave_blocking_section (); + if (r == NULL) + ocaml_guestfs_raise_error (g, "sh_lines"); + + rv = caml_copy_string_array ((const char **) r); + for (i = 0; r[i] != NULL; ++i) free (r[i]); + free (r); + CAMLreturn (rv); +} + diff --git a/perl/Guestfs.xs b/perl/Guestfs.xs index c26faa1..8a6c4bb 100644 --- a/perl/Guestfs.xs +++ b/perl/Guestfs.xs @@ -2549,3 +2549,37 @@ PREINIT: OUTPUT: RETVAL +SV * +sh (g, command) + guestfs_h *g; + char *command; +PREINIT: + char *output; + CODE: + output = guestfs_sh (g, command); + if (output == NULL) + croak ("sh: %s", guestfs_last_error (g)); + RETVAL = newSVpv (output, 0); + free (output); + OUTPUT: + RETVAL + +void +sh_lines (g, command) + guestfs_h *g; + char *command; +PREINIT: + char **lines; + int i, n; + PPCODE: + lines = guestfs_sh_lines (g, command); + if (lines == NULL) + croak ("sh_lines: %s", guestfs_last_error (g)); + for (n = 0; lines[n] != NULL; ++n) /**/; + EXTEND (SP, n); + for (i = 0; i < n; ++i) { + PUSHs (sv_2mortal (newSVpv (lines[i], 0))); + free (lines[i]); + } + free (lines); + diff --git a/perl/lib/Sys/Guestfs.pm b/perl/lib/Sys/Guestfs.pm index 05adfcb..9329b76 100644 --- a/perl/lib/Sys/Guestfs.pm +++ b/perl/lib/Sys/Guestfs.pm @@ -432,7 +432,9 @@ or compatible processor architecture). The single parameter is an argv-style list of arguments. 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). +non-empty (ie. must contain a program name). Note that +the command runs directly, and is I invoked via +the shell (see C<$h-Esh>). The return value is anything printed to I by the command. @@ -461,6 +463,8 @@ FTP. This is the same as C<$h-Ecommand>, but splits the result into a list of lines. +See also: C<$h-Esh_lines> + Because of the message protocol, there is a transfer limit of somewhere between 2MB and 4MB. To transfer large files you should use FTP. @@ -1125,6 +1129,28 @@ This displays the partition table on C, in the human-readable output of the L command. It is not intended to be parsed. +=item $output = $h->sh ($command); + +This call runs a command from the guest filesystem via the +guest's C. + +This is like C<$h-Ecommand>, but passes the command to: + + /bin/sh -c "command" + +Depending on the guest's shell, this usually results in +wildcards being expanded, shell expressions being interpolated +and so on. + +All the provisos about C<$h-Ecommand> apply to this call. + +=item @lines = $h->sh_lines ($command); + +This is the same as C<$h-Esh>, but splits the result +into a list of lines. + +See also: C<$h-Ecommand_lines> + =item $h->sleep ($secs); Sleep for C seconds. diff --git a/python/guestfs-py.c b/python/guestfs-py.c index 8e90d76..f5bc109 100644 --- a/python/guestfs-py.c +++ b/python/guestfs-py.c @@ -4456,6 +4456,56 @@ py_guestfs_ntfs_3g_probe (PyObject *self, PyObject *args) return py_r; } +static PyObject * +py_guestfs_sh (PyObject *self, PyObject *args) +{ + PyObject *py_g; + guestfs_h *g; + PyObject *py_r; + char *r; + const char *command; + + if (!PyArg_ParseTuple (args, (char *) "Os:guestfs_sh", + &py_g, &command)) + return NULL; + g = get_handle (py_g); + + r = guestfs_sh (g, command); + if (r == NULL) { + PyErr_SetString (PyExc_RuntimeError, guestfs_last_error (g)); + return NULL; + } + + py_r = PyString_FromString (r); + free (r); + return py_r; +} + +static PyObject * +py_guestfs_sh_lines (PyObject *self, PyObject *args) +{ + PyObject *py_g; + guestfs_h *g; + PyObject *py_r; + char **r; + const char *command; + + if (!PyArg_ParseTuple (args, (char *) "Os:guestfs_sh_lines", + &py_g, &command)) + return NULL; + g = get_handle (py_g); + + r = guestfs_sh_lines (g, command); + if (r == NULL) { + PyErr_SetString (PyExc_RuntimeError, guestfs_last_error (g)); + return NULL; + } + + py_r = put_string_list (r); + free_strings (r); + return py_r; +} + static PyMethodDef methods[] = { { (char *) "create", py_guestfs_create, METH_VARARGS, NULL }, { (char *) "close", py_guestfs_close, METH_VARARGS, NULL }, @@ -4621,6 +4671,8 @@ static PyMethodDef methods[] = { { (char *) "e2fsck_f", py_guestfs_e2fsck_f, METH_VARARGS, NULL }, { (char *) "sleep", py_guestfs_sleep, METH_VARARGS, NULL }, { (char *) "ntfs_3g_probe", py_guestfs_ntfs_3g_probe, METH_VARARGS, NULL }, + { (char *) "sh", py_guestfs_sh, METH_VARARGS, NULL }, + { (char *) "sh_lines", py_guestfs_sh_lines, METH_VARARGS, NULL }, { NULL, NULL, 0, NULL } }; diff --git a/python/guestfs.py b/python/guestfs.py index 2600ff2..c9658cd 100644 --- a/python/guestfs.py +++ b/python/guestfs.py @@ -947,7 +947,9 @@ class GuestFS: The single parameter is an argv-style list of arguments. 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). + non-empty (ie. must contain a program name). Note that + the command runs directly, and is *not* invoked via the + shell (see "g.sh"). The return value is anything printed to *stdout* by the command. @@ -977,6 +979,8 @@ class GuestFS: u"""This is the same as "g.command", but splits the result into a list of lines. + See also: "g.sh_lines" + This function returns a list of strings. Because of the message protocol, there is a transfer @@ -1621,3 +1625,29 @@ class GuestFS: """ return libguestfsmod.ntfs_3g_probe (self._o, rw, device) + def sh (self, command): + u"""This call runs a command from the guest filesystem via + the guest's "/bin/sh". + + This is like "g.command", but passes the command to: + + /bin/sh -c "command" + + Depending on the guest's shell, this usually results in + wildcards being expanded, shell expressions being + interpolated and so on. + + All the provisos about "g.command" apply to this call. + """ + return libguestfsmod.sh (self._o, command) + + def sh_lines (self, command): + u"""This is the same as "g.sh", but splits the result into a + list of lines. + + See also: "g.command_lines" + + This function returns a list of strings. + """ + return libguestfsmod.sh_lines (self._o, command) + diff --git a/ruby/ext/guestfs/_guestfs.c b/ruby/ext/guestfs/_guestfs.c index 38256dc..81e3691 100644 --- a/ruby/ext/guestfs/_guestfs.c +++ b/ruby/ext/guestfs/_guestfs.c @@ -4003,6 +4003,58 @@ static VALUE ruby_guestfs_ntfs_3g_probe (VALUE gv, VALUE rwv, VALUE devicev) return INT2NUM (r); } +static VALUE ruby_guestfs_sh (VALUE gv, VALUE commandv) +{ + guestfs_h *g; + Data_Get_Struct (gv, guestfs_h, g); + if (!g) + rb_raise (rb_eArgError, "%s: used handle after closing it", "sh"); + + const char *command = StringValueCStr (commandv); + if (!command) + rb_raise (rb_eTypeError, "expected string for parameter %s of %s", + "command", "sh"); + + char *r; + + r = guestfs_sh (g, command); + if (r == NULL) + rb_raise (e_Error, "%s", guestfs_last_error (g)); + + VALUE rv = rb_str_new2 (r); + free (r); + return rv; +} + +static VALUE ruby_guestfs_sh_lines (VALUE gv, VALUE commandv) +{ + guestfs_h *g; + Data_Get_Struct (gv, guestfs_h, g); + if (!g) + rb_raise (rb_eArgError, "%s: used handle after closing it", "sh_lines"); + + const char *command = StringValueCStr (commandv); + if (!command) + rb_raise (rb_eTypeError, "expected string for parameter %s of %s", + "command", "sh_lines"); + + char **r; + + r = guestfs_sh_lines (g, command); + if (r == NULL) + rb_raise (e_Error, "%s", guestfs_last_error (g)); + + int i, len = 0; + for (i = 0; r[i] != NULL; ++i) len++; + VALUE rv = rb_ary_new2 (len); + for (i = 0; r[i] != NULL; ++i) { + rb_ary_push (rv, rb_str_new2 (r[i])); + free (r[i]); + } + free (r); + return rv; +} + /* Initialize the module. */ void Init__guestfs () { @@ -4337,4 +4389,8 @@ void Init__guestfs () ruby_guestfs_sleep, 1); rb_define_method (c_guestfs, "ntfs_3g_probe", ruby_guestfs_ntfs_3g_probe, 2); + rb_define_method (c_guestfs, "sh", + ruby_guestfs_sh, 1); + rb_define_method (c_guestfs, "sh_lines", + ruby_guestfs_sh_lines, 1); } diff --git a/src/guestfs-actions.c b/src/guestfs-actions.c index ecaa566..f36fcf0 100644 --- a/src/guestfs-actions.c +++ b/src/guestfs-actions.c @@ -10074,3 +10074,192 @@ int guestfs_ntfs_3g_probe (guestfs_h *g, return ctx.ret.status; } +struct sh_ctx { + /* This flag is set by the callbacks, so we know we've done + * the callbacks as expected, and in the right sequence. + * 0 = not called, 1 = reply_cb called. + */ + int cb_sequence; + struct guestfs_message_header hdr; + struct guestfs_message_error err; + struct guestfs_sh_ret ret; +}; + +static void sh_reply_cb (guestfs_h *g, void *data, XDR *xdr) +{ + guestfs_main_loop *ml = guestfs_get_main_loop (g); + struct sh_ctx *ctx = (struct sh_ctx *) data; + + /* This should definitely not happen. */ + if (ctx->cb_sequence != 0) { + ctx->cb_sequence = 9999; + error (g, "%s: internal error: reply callback called twice", "guestfs_sh"); + return; + } + + ml->main_loop_quit (ml, g); + + if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) { + error (g, "%s: failed to parse reply header", "guestfs_sh"); + return; + } + if (ctx->hdr.status == GUESTFS_STATUS_ERROR) { + if (!xdr_guestfs_message_error (xdr, &ctx->err)) { + error (g, "%s: failed to parse reply error", "guestfs_sh"); + return; + } + goto done; + } + if (!xdr_guestfs_sh_ret (xdr, &ctx->ret)) { + error (g, "%s: failed to parse reply", "guestfs_sh"); + return; + } + done: + ctx->cb_sequence = 1; +} + +char *guestfs_sh (guestfs_h *g, + const char *command) +{ + struct guestfs_sh_args args; + struct sh_ctx ctx; + guestfs_main_loop *ml = guestfs_get_main_loop (g); + int serial; + + if (check_state (g, "guestfs_sh") == -1) return NULL; + guestfs_set_busy (g); + + memset (&ctx, 0, sizeof ctx); + + args.command = (char *) command; + serial = guestfs__send_sync (g, GUESTFS_PROC_SH, + (xdrproc_t) xdr_guestfs_sh_args, (char *) &args); + if (serial == -1) { + guestfs_end_busy (g); + return NULL; + } + + guestfs__switch_to_receiving (g); + ctx.cb_sequence = 0; + guestfs_set_reply_callback (g, sh_reply_cb, &ctx); + (void) ml->main_loop_run (ml, g); + guestfs_set_reply_callback (g, NULL, NULL); + if (ctx.cb_sequence != 1) { + error (g, "%s reply failed, see earlier error messages", "guestfs_sh"); + guestfs_end_busy (g); + return NULL; + } + + if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_SH, serial) == -1) { + guestfs_end_busy (g); + return NULL; + } + + if (ctx.hdr.status == GUESTFS_STATUS_ERROR) { + error (g, "%s", ctx.err.error_message); + free (ctx.err.error_message); + guestfs_end_busy (g); + return NULL; + } + + guestfs_end_busy (g); + return ctx.ret.output; /* caller will free */ +} + +struct sh_lines_ctx { + /* This flag is set by the callbacks, so we know we've done + * the callbacks as expected, and in the right sequence. + * 0 = not called, 1 = reply_cb called. + */ + int cb_sequence; + struct guestfs_message_header hdr; + struct guestfs_message_error err; + struct guestfs_sh_lines_ret ret; +}; + +static void sh_lines_reply_cb (guestfs_h *g, void *data, XDR *xdr) +{ + guestfs_main_loop *ml = guestfs_get_main_loop (g); + struct sh_lines_ctx *ctx = (struct sh_lines_ctx *) data; + + /* This should definitely not happen. */ + if (ctx->cb_sequence != 0) { + ctx->cb_sequence = 9999; + error (g, "%s: internal error: reply callback called twice", "guestfs_sh_lines"); + return; + } + + ml->main_loop_quit (ml, g); + + if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) { + error (g, "%s: failed to parse reply header", "guestfs_sh_lines"); + return; + } + if (ctx->hdr.status == GUESTFS_STATUS_ERROR) { + if (!xdr_guestfs_message_error (xdr, &ctx->err)) { + error (g, "%s: failed to parse reply error", "guestfs_sh_lines"); + return; + } + goto done; + } + if (!xdr_guestfs_sh_lines_ret (xdr, &ctx->ret)) { + error (g, "%s: failed to parse reply", "guestfs_sh_lines"); + return; + } + done: + ctx->cb_sequence = 1; +} + +char **guestfs_sh_lines (guestfs_h *g, + const char *command) +{ + struct guestfs_sh_lines_args args; + struct sh_lines_ctx ctx; + guestfs_main_loop *ml = guestfs_get_main_loop (g); + int serial; + + if (check_state (g, "guestfs_sh_lines") == -1) return NULL; + guestfs_set_busy (g); + + memset (&ctx, 0, sizeof ctx); + + args.command = (char *) command; + serial = guestfs__send_sync (g, GUESTFS_PROC_SH_LINES, + (xdrproc_t) xdr_guestfs_sh_lines_args, (char *) &args); + if (serial == -1) { + guestfs_end_busy (g); + return NULL; + } + + guestfs__switch_to_receiving (g); + ctx.cb_sequence = 0; + guestfs_set_reply_callback (g, sh_lines_reply_cb, &ctx); + (void) ml->main_loop_run (ml, g); + guestfs_set_reply_callback (g, NULL, NULL); + if (ctx.cb_sequence != 1) { + error (g, "%s reply failed, see earlier error messages", "guestfs_sh_lines"); + guestfs_end_busy (g); + return NULL; + } + + if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_SH_LINES, serial) == -1) { + guestfs_end_busy (g); + return NULL; + } + + if (ctx.hdr.status == GUESTFS_STATUS_ERROR) { + error (g, "%s", ctx.err.error_message); + free (ctx.err.error_message); + guestfs_end_busy (g); + return NULL; + } + + guestfs_end_busy (g); + /* caller will free this, but we need to add a NULL entry */ + ctx.ret.lines.lines_val = + safe_realloc (g, ctx.ret.lines.lines_val, + sizeof (char *) * (ctx.ret.lines.lines_len + 1)); + ctx.ret.lines.lines_val[ctx.ret.lines.lines_len] = NULL; + return ctx.ret.lines.lines_val; +} + diff --git a/src/guestfs-actions.h b/src/guestfs-actions.h index 7f8f0de..fe9967f 100644 --- a/src/guestfs-actions.h +++ b/src/guestfs-actions.h @@ -181,3 +181,5 @@ extern char **guestfs_find (guestfs_h *handle, const char *directory); extern int guestfs_e2fsck_f (guestfs_h *handle, const char *device); extern int guestfs_sleep (guestfs_h *handle, int secs); extern int guestfs_ntfs_3g_probe (guestfs_h *handle, int rw, const char *device); +extern char *guestfs_sh (guestfs_h *handle, const char *command); +extern char **guestfs_sh_lines (guestfs_h *handle, const char *command); diff --git a/src/guestfs_protocol.c b/src/guestfs_protocol.c index fc88d05..96951d9 100644 --- a/src/guestfs_protocol.c +++ b/src/guestfs_protocol.c @@ -1871,6 +1871,47 @@ xdr_guestfs_ntfs_3g_probe_ret (XDR *xdrs, guestfs_ntfs_3g_probe_ret *objp) } bool_t +xdr_guestfs_sh_args (XDR *xdrs, guestfs_sh_args *objp) +{ + register int32_t *buf; + + if (!xdr_string (xdrs, &objp->command, ~0)) + return FALSE; + return TRUE; +} + +bool_t +xdr_guestfs_sh_ret (XDR *xdrs, guestfs_sh_ret *objp) +{ + register int32_t *buf; + + if (!xdr_string (xdrs, &objp->output, ~0)) + return FALSE; + return TRUE; +} + +bool_t +xdr_guestfs_sh_lines_args (XDR *xdrs, guestfs_sh_lines_args *objp) +{ + register int32_t *buf; + + if (!xdr_string (xdrs, &objp->command, ~0)) + return FALSE; + return TRUE; +} + +bool_t +xdr_guestfs_sh_lines_ret (XDR *xdrs, guestfs_sh_lines_ret *objp) +{ + register int32_t *buf; + + if (!xdr_array (xdrs, (char **)&objp->lines.lines_val, (u_int *) &objp->lines.lines_len, ~0, + sizeof (str), (xdrproc_t) xdr_str)) + return FALSE; + return TRUE; +} + +bool_t xdr_guestfs_procedure (XDR *xdrs, guestfs_procedure *objp) { register int32_t *buf; diff --git a/src/guestfs_protocol.h b/src/guestfs_protocol.h index 9f3bd8d..a9863cc 100644 --- a/src/guestfs_protocol.h +++ b/src/guestfs_protocol.h @@ -946,6 +946,29 @@ struct guestfs_ntfs_3g_probe_ret { }; typedef struct guestfs_ntfs_3g_probe_ret guestfs_ntfs_3g_probe_ret; +struct guestfs_sh_args { + char *command; +}; +typedef struct guestfs_sh_args guestfs_sh_args; + +struct guestfs_sh_ret { + char *output; +}; +typedef struct guestfs_sh_ret guestfs_sh_ret; + +struct guestfs_sh_lines_args { + char *command; +}; +typedef struct guestfs_sh_lines_args guestfs_sh_lines_args; + +struct guestfs_sh_lines_ret { + struct { + u_int lines_len; + str *lines_val; + } lines; +}; +typedef struct guestfs_sh_lines_ret guestfs_sh_lines_ret; + enum guestfs_procedure { GUESTFS_PROC_MOUNT = 1, GUESTFS_PROC_SYNC = 2, @@ -1057,7 +1080,9 @@ enum guestfs_procedure { GUESTFS_PROC_E2FSCK_F = 108, GUESTFS_PROC_SLEEP = 109, GUESTFS_PROC_NTFS_3G_PROBE = 110, - GUESTFS_PROC_NR_PROCS = 110 + 1, + GUESTFS_PROC_SH = 111, + GUESTFS_PROC_SH_LINES = 112, + GUESTFS_PROC_NR_PROCS = 112 + 1, }; typedef enum guestfs_procedure guestfs_procedure; #define GUESTFS_MESSAGE_MAX 4194304 @@ -1258,6 +1283,10 @@ extern bool_t xdr_guestfs_e2fsck_f_args (XDR *, guestfs_e2fsck_f_args*); extern bool_t xdr_guestfs_sleep_args (XDR *, guestfs_sleep_args*); extern bool_t xdr_guestfs_ntfs_3g_probe_args (XDR *, guestfs_ntfs_3g_probe_args*); extern bool_t xdr_guestfs_ntfs_3g_probe_ret (XDR *, guestfs_ntfs_3g_probe_ret*); +extern bool_t xdr_guestfs_sh_args (XDR *, guestfs_sh_args*); +extern bool_t xdr_guestfs_sh_ret (XDR *, guestfs_sh_ret*); +extern bool_t xdr_guestfs_sh_lines_args (XDR *, guestfs_sh_lines_args*); +extern bool_t xdr_guestfs_sh_lines_ret (XDR *, guestfs_sh_lines_ret*); extern bool_t xdr_guestfs_procedure (XDR *, guestfs_procedure*); extern bool_t xdr_guestfs_message_direction (XDR *, guestfs_message_direction*); extern bool_t xdr_guestfs_message_status (XDR *, guestfs_message_status*); @@ -1417,6 +1446,10 @@ extern bool_t xdr_guestfs_e2fsck_f_args (); extern bool_t xdr_guestfs_sleep_args (); extern bool_t xdr_guestfs_ntfs_3g_probe_args (); extern bool_t xdr_guestfs_ntfs_3g_probe_ret (); +extern bool_t xdr_guestfs_sh_args (); +extern bool_t xdr_guestfs_sh_ret (); +extern bool_t xdr_guestfs_sh_lines_args (); +extern bool_t xdr_guestfs_sh_lines_ret (); extern bool_t xdr_guestfs_procedure (); extern bool_t xdr_guestfs_message_direction (); extern bool_t xdr_guestfs_message_status (); diff --git a/src/guestfs_protocol.x b/src/guestfs_protocol.x index cc514e7..94d2e84 100644 --- a/src/guestfs_protocol.x +++ b/src/guestfs_protocol.x @@ -732,6 +732,22 @@ struct guestfs_ntfs_3g_probe_ret { int status; }; +struct guestfs_sh_args { + string command<>; +}; + +struct guestfs_sh_ret { + string output<>; +}; + +struct guestfs_sh_lines_args { + string command<>; +}; + +struct guestfs_sh_lines_ret { + str lines<>; +}; + enum guestfs_procedure { GUESTFS_PROC_MOUNT = 1, GUESTFS_PROC_SYNC = 2, @@ -843,6 +859,8 @@ enum guestfs_procedure { GUESTFS_PROC_E2FSCK_F = 108, GUESTFS_PROC_SLEEP = 109, GUESTFS_PROC_NTFS_3G_PROBE = 110, + GUESTFS_PROC_SH = 111, + GUESTFS_PROC_SH_LINES = 112, GUESTFS_PROC_NR_PROCS }; -- 1.8.3.1