Richard W.M. Jones
2017-Jul-27 19:43 UTC
[Libguestfs] [PATCH v3 00/23] Reimplement many daemon APIs in OCaml.
I think this fixes everything mentioned: - Added the Optgroups module as suggested. - Remove command temporary files. - Replace command ~flags with ?fold_stdout_on_stderr. - Nest _with_mounted function. - Rebase & retest. Rich.
Richard W.M. Jones
2017-Jul-27 19:43 UTC
[Libguestfs] [PATCH v3 01/23] daemon: Allow parts of the daemon and APIs to be written in OCaml.
This change allows parts of the daemon to be written in the OCaml programming language. I am using the ‘Main Program in C’ method along with ‘-output-obj’ to create an object file from the OCaml code / runtime, as described here: https://caml.inria.fr/pub/docs/manual-ocaml/intfc.html Furthermore, change the generator to allow individual APIs to be implemented in OCaml. This is picked by setting: impl = OCaml <ocaml_function>; The generator creates ‘do_function’ (the same one you would have to write by hand in C), with the function calling the named ‘ocaml_function’ and dealing with marshalling/unmarshalling the OCaml parameters. --- .gitignore | 8 +- Makefile.am | 2 +- common/mlutils/Makefile.am | 4 - daemon/Makefile.am | 122 +++++++++++++--- daemon/chroot.ml | 85 +++++++++++ daemon/chroot.mli | 38 +++++ daemon/daemon-c.c | 203 ++++++++++++++++++++++++++ daemon/daemon-c.h | 38 +++++ daemon/daemon.ml | 41 ++++++ daemon/guestfsd.c | 8 ++ daemon/sysroot-c.c | 37 +++++ daemon/sysroot.ml | 23 +++ daemon/sysroot.mli | 25 ++++ daemon/utils.ml | 158 ++++++++++++++++++++ daemon/utils.mli | 72 ++++++++++ docs/C_SOURCE_FILES | 4 + docs/guestfs-hacking.pod | 7 + generator/OCaml.ml | 8 ++ generator/OCaml.mli | 1 + generator/actions.ml | 5 + generator/actions.mli | 4 + generator/daemon.ml | 351 ++++++++++++++++++++++++++++++++++++++++++++- generator/daemon.mli | 4 + generator/main.ml | 10 ++ generator/types.ml | 7 +- 25 files changed, 1235 insertions(+), 30 deletions(-) diff --git a/.gitignore b/.gitignore index bbd9284c6..de50f7381 100644 --- a/.gitignore +++ b/.gitignore @@ -165,20 +165,26 @@ Makefile.in /customize/test-settings-*.sh /customize/virt-customize /customize/virt-customize.1 +/daemon/.depend /daemon/actions.h +/daemon/callbacks.ml +/daemon/caml-stubs.c /daemon/dispatch.c /daemon/guestfsd /daemon/guestfsd.8 /daemon/guestfsd.exe +/daemon/lvm-tokenization.c /daemon/names.c /daemon/optgroups.c /daemon/optgroups.h -/daemon/lvm-tokenization.c +/daemon/optgroups.ml /daemon/stamp-guestfsd.pod /daemon/structs-cleanups.c /daemon/structs-cleanups.h +/daemon/structs.ml /daemon/stubs-?.c /daemon/stubs.h +/daemon/types.ml /depcomp /df/stamp-virt-df.pod /df/virt-df diff --git a/Makefile.am b/Makefile.am index a411b0b7b..84b00393d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -44,6 +44,7 @@ SUBDIRS += common/structs SUBDIRS += lib docs examples po # The daemon and the appliance. +SUBDIRS += common/mlutils if ENABLE_DAEMON SUBDIRS += daemon SUBDIRS += tests/daemon @@ -155,7 +156,6 @@ SUBDIRS += csharp # OCaml tools. Note 'common/ml*', 'mllib' and 'customize' contain # shared code used by other OCaml tools, so these must come first. if HAVE_OCAML -SUBDIRS += common/mlutils SUBDIRS += common/mlprogress SUBDIRS += common/mlvisit SUBDIRS += common/mlxml diff --git a/common/mlutils/Makefile.am b/common/mlutils/Makefile.am index 94b2187eb..f29ffc062 100644 --- a/common/mlutils/Makefile.am +++ b/common/mlutils/Makefile.am @@ -35,8 +35,6 @@ SOURCES_C = \ c_utils-c.c \ unix_utils-c.c -if HAVE_OCAML - # We pretend that we're building a C library. automake handles the # compilation of the C sources for us. At the end we take the C # objects and OCaml objects and link them into the OCaml library. @@ -150,6 +148,4 @@ depend: .depend -include .depend -endif - .PHONY: depend docs diff --git a/daemon/Makefile.am b/daemon/Makefile.am index eedf09d52..e5a4986e3 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -17,27 +17,31 @@ include $(top_srcdir)/subdir-rules.mk -generator_built = \ - actions.h \ - dispatch.c \ - names.c \ - lvm-tokenization.c \ - structs-cleanups.c \ - structs-cleanups.h \ - stubs-0.c \ - stubs-1.c \ - stubs-2.c \ - stubs-3.c \ - stubs-4.c \ - stubs-5.c \ - stubs-6.c \ - stubs.h - BUILT_SOURCES = \ - $(generator_built) + actions.h \ + caml-stubs.c \ + dispatch.c \ + names.c \ + lvm-tokenization.c \ + structs-cleanups.c \ + structs-cleanups.h \ + stubs-0.c \ + stubs-1.c \ + stubs-2.c \ + stubs-3.c \ + stubs-4.c \ + stubs-5.c \ + stubs-6.c \ + stubs.h -EXTRA_DIST = \ +generator_built = \ $(BUILT_SOURCES) \ + callbacks.ml \ + types.ml + +EXTRA_DIST = \ + $(generator_built) \ + $(SOURCES_MLI) $(SOURCES_ML) \ guestfsd.pod if INSTALL_DAEMON @@ -61,6 +65,7 @@ guestfsd_SOURCES = \ blkid.c \ blockdev.c \ btrfs.c \ + caml-stubs.c \ cap.c \ checksum.c \ cleanups.c \ @@ -71,6 +76,8 @@ guestfsd_SOURCES = \ copy.c \ cpio.c \ cpmv.c \ + daemon-c.c \ + daemon-c.h \ daemon.h \ dd.c \ debug.c \ @@ -161,6 +168,7 @@ guestfsd_SOURCES = \ swap.c \ sync.c \ syslinux.c \ + sysroot-c.c \ tar.c \ tsk.c \ truncate.c \ @@ -176,10 +184,16 @@ guestfsd_SOURCES = \ zero.c \ zerofree.c +guestfsd_LDFLAGS = \ + -L$(shell $(OCAMLC) -where) \ + -L$(shell $(OCAMLC) -where)/hivex \ + -L../common/mlutils \ + -L../common/mlstdutils guestfsd_LDADD = \ ../common/errnostring/liberrnostring.la \ ../common/protocol/libprotocol.la \ ../common/utils/libutils.la \ + camldaemon.o \ $(ACL_LIBS) \ $(CAP_LIBS) \ $(YAJL_LIBS) \ @@ -198,9 +212,12 @@ guestfsd_LDADD = \ $(PCRE_LIBS) \ $(TSK_LIBS) \ $(RPC_LIBS) \ - $(YARA_LIBS) + $(YARA_LIBS) \ + $(OCAML_LIBS) guestfsd_CPPFLAGS = \ + -I$(shell $(OCAMLC) -where) \ + -I$(shell $(OCAMLC) -where)/hivex \ -I$(top_srcdir)/gnulib/lib \ -I$(top_builddir)/gnulib/lib \ -I$(top_srcdir)/lib \ @@ -220,6 +237,71 @@ guestfsd_CFLAGS = \ $(YAJL_CFLAGS) \ $(PCRE_CFLAGS) +# Parts of the daemon are written in OCaml. These are linked into a +# library and then linked to the daemon. See +# https://caml.inria.fr/pub/docs/manual-ocaml/intfc.html +SOURCES_MLI = \ + chroot.mli \ + sysroot.mli \ + utils.mli + +SOURCES_ML = \ + types.ml \ + utils.ml \ + structs.ml \ + optgroups.ml \ + sysroot.ml \ + chroot.ml \ + callbacks.ml \ + daemon.ml + +BOBJECTS = $(SOURCES_ML:.ml=.cmo) +XOBJECTS = $(BOBJECTS:.cmo=.cmx) + +OCAMLPACKAGES = \ + -package str,unix,hivex \ + -I $(top_srcdir)/common/mlstdutils \ + -I $(top_srcdir)/common/mlutils + +OCAMLFLAGS = $(OCAML_FLAGS) $(OCAML_WARN_FLAGS) + +if !HAVE_OCAMLOPT +OBJECTS = $(BOBJECTS) +CAMLRUN = camlrun +else +OBJECTS = $(XOBJECTS) +CAMLRUN = asmrun +endif +OCAML_LIBS = \ + -lmlcutils \ + -lmlstdutils \ + -lmlhivex \ + -lcamlstr \ + -lunix \ + -l$(CAMLRUN) -ldl -lm + +CLEANFILES += camldaemon.o + +camldaemon.o: $(OBJECTS) + $(OCAMLFIND) $(BEST) -output-obj -o $@ \ + $(OCAMLFLAGS) $(OCAMLPACKAGES) \ + -linkpkg mlcutils.$(MLARCHIVE) mlstdutils.$(MLARCHIVE) \ + $(OBJECTS) + +# OCaml dependencies. +depend: .depend + +.depend: $(wildcard $(abs_srcdir)/*.mli) $(wildcard $(abs_srcdir)/*.ml) + rm -f $@ $@-t + $(OCAMLFIND) ocamldep -I $(abs_srcdir) -I $(abs_top_builddir)/common/mlstdutils -I $(abs_top_builddir)/common/mlutils $^ | \ + $(SED) 's/ *$$//' | \ + $(SED) -e :a -e '/ *\\$$/N; s/ *\\\n */ /; ta' | \ + $(SED) -e 's,$(abs_srcdir)/,$(builddir)/,g' | \ + sort > $@-t + mv $@-t $@ + +-include .depend + # Manual pages and HTML files for the website. if INSTALL_DAEMON man_MANS = guestfsd.8 @@ -241,4 +323,4 @@ stamp-guestfsd.pod: guestfsd.pod $< touch $@ -.PHONY: force +.PHONY: depend force diff --git a/daemon/chroot.ml b/daemon/chroot.ml new file mode 100644 index 000000000..3364cd20b --- /dev/null +++ b/daemon/chroot.ml @@ -0,0 +1,85 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +open Printf +open Unix + +open Std_utils +open Unix_utils + +type t = { + name : string; + chroot : string; +} + +let create ?(name = "<unnamed>") ?(chroot = Sysroot.sysroot ()) () + { name = name; chroot = chroot } + +let f t func arg + if verbose () then + eprintf "chroot: %s: running '%s'\n%!" t.chroot t.name; + + let rfd, wfd = pipe () in + + let pid = fork () in + if pid = 0 then ( + (* Child. *) + close rfd; + + chdir t.chroot; + chroot t.chroot; + + let ret + try Either (func arg) + with exn -> Or exn in + + try + let chan = out_channel_of_descr wfd in + output_value chan ret; + Pervasives.flush chan; + Exit._exit 0 + with + exn -> + prerr_endline (Printexc.to_string exn); + Exit._exit 1 + ); + + (* Parent. *) + close wfd; + + let _, status = waitpid [] pid in + (match status with + | WEXITED 0 -> () + | WEXITED i -> + close rfd; + failwithf "chroot ‘%s’ exited with non-zero error %d" t.name i + | WSIGNALED i -> + close rfd; + failwithf "chroot ‘%s’ killed by signal %d" t.name i + | WSTOPPED i -> + close rfd; + failwithf "chroot ‘%s’ stopped by signal %d" t.name i + ); + + let chan = in_channel_of_descr rfd in + let ret = input_value chan in + close_in chan; + + match ret with + | Either ret -> ret + | Or exn -> raise exn diff --git a/daemon/chroot.mli b/daemon/chroot.mli new file mode 100644 index 000000000..33f53ba18 --- /dev/null +++ b/daemon/chroot.mli @@ -0,0 +1,38 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +(** This is a generic module for running functions in a chroot. + The function runs in a forked subprocess too so that we can + restore the root afterwards. + + It handles passing the parameter, forking, running the + function and marshalling the result or any exceptions. *) + +type t + +val create : ?name:string -> ?chroot:string -> unit -> t +(** Create a chroot handle. + + [?name] is an optional name used in debugging and error messages. + + [?chroot] is the optional chroot directory. This parameter + defaults to [Sysroot.sysroot ()]. *) + +val f : t -> ('a -> 'b) -> 'a -> 'b +(** Run a function in the chroot, returning the result or re-raising + any exception thrown. *) diff --git a/daemon/daemon-c.c b/daemon/daemon-c.c new file mode 100644 index 000000000..cbb3d8918 --- /dev/null +++ b/daemon/daemon-c.c @@ -0,0 +1,203 @@ +/* guestfs-inspection + * Copyright (C) 2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> + +#include <caml/alloc.h> +#include <caml/mlvalues.h> +#include <caml/memory.h> +#include <caml/unixsupport.h> + +#include "daemon.h" +#include "daemon-c.h" + +/* Convert an OCaml exception to a reply_with_error_errno call + * as best we can. + */ +void +guestfs_int_daemon_exn_to_reply_with_error (const char *func, value exn) +{ + const char *exn_name; + + /* This is not the official way to do this, but I could not get the + * official way to work, and this way does work. See + * http://caml.inria.fr/pub/ml-archives/caml-list/2006/05/097f63cfb39a80418f95c70c3c520aa8.en.html + * http://caml.inria.fr/pub/ml-archives/caml-list/2009/06/797e2f797f57b8ea2a2c0e431a2df312.en.html + */ + if (Tag_val (Field (exn, 0)) == String_tag) + /* For End_of_file and a few other constant exceptions. */ + exn_name = String_val (Field (exn, 0)); + else + /* For most exceptions. */ + exn_name = String_val (Field (Field (exn, 0), 0)); + + if (verbose) + fprintf (stderr, "ocaml_exn: '%s' raised '%s' exception\n", + func, exn_name); + + if (STREQ (exn_name, "Unix.Unix_error")) { + int errcode = code_of_unix_error (Field (exn, 1)); + reply_with_perror_errno (errcode, "%s: %s", + String_val (Field (exn, 2)), + String_val (Field (exn, 3))); + } + else if (STREQ (exn_name, "Failure")) + reply_with_error ("%s", String_val (Field (exn, 1))); + else if (STREQ (exn_name, "Sys_error")) + reply_with_error ("%s", String_val (Field (exn, 1))); + else if (STREQ (exn_name, "Invalid_argument")) + reply_with_error ("invalid argument: %s", String_val (Field (exn, 1))); + else + reply_with_error ("internal error: %s: unhandled exception thrown: %s", + func, exn_name); +} + +/* NB: This is a "noalloc" call. */ +value +guestfs_int_daemon_get_verbose_flag (value unitv) +{ + return Val_bool (verbose); +} + +/* Implement String (Mountable, _) parameter. */ +value +guestfs_int_daemon_copy_mountable (const mountable_t *mountable) +{ + CAMLparam0 (); + CAMLlocal4 (r, typev, devicev, volumev); + + switch (mountable->type) { + case MOUNTABLE_DEVICE: + typev = Val_int (0); /* MountableDevice */ + break; + case MOUNTABLE_PATH: + typev = Val_int (1); /* MountablePath */ + break; + case MOUNTABLE_BTRFSVOL: + volumev = caml_copy_string (mountable->volume); + typev = caml_alloc (1, 0); /* MountableBtrfsVol */ + Store_field (typev, 0, volumev); + } + + devicev = caml_copy_string (mountable->device); + + r = caml_alloc_tuple (2); + Store_field (r, 0, typev); + Store_field (r, 1, devicev); + + CAMLreturn (r); +} + +/* Implement RStringList. */ +char ** +guestfs_int_daemon_return_string_list (value retv) +{ + CLEANUP_FREE_STRINGSBUF DECLARE_STRINGSBUF (ret); + value v; + + while (retv != Val_int (0)) { + v = Field (retv, 0); + if (add_string (&ret, String_val (v)) == -1) + return NULL; + retv = Field (retv, 1); + } + + if (end_stringsbuf (&ret) == -1) + return NULL; + + return take_stringsbuf (&ret); /* caller frees */ +} + +/* Implement RString (RMountable, _). */ +char * +guestfs_int_daemon_return_string_mountable (value retv) +{ + value typev = Field (retv, 0); + value devicev = Field (retv, 1); + value subvolv; + char *ret; + + if (Is_long (typev)) { /* MountableDevice or MountablePath */ + ret = strdup (String_val (devicev)); + if (ret == NULL) + reply_with_perror ("strdup"); + return ret; + } + else { /* MountableBtrfsVol of subvol */ + subvolv = Field (typev, 0); + if (asprintf (&ret, "btrfsvol:%s/%s", + String_val (devicev), String_val (subvolv)) == -1) + reply_with_perror ("asprintf"); + return ret; + } +} + +/* Implement RHashtable (RPlainString, RPlainString, _). */ +char ** +guestfs_int_daemon_return_hashtable_string_string (value retv) +{ + CLEANUP_FREE_STRINGSBUF DECLARE_STRINGSBUF (ret); + value v, sv; + + while (retv != Val_int (0)) { + v = Field (retv, 0); /* (string, string) */ + sv = Field (v, 0); /* string */ + if (add_string (&ret, String_val (sv)) == -1) + return NULL; + sv = Field (v, 1); /* string */ + if (add_string (&ret, String_val (sv)) == -1) + return NULL; + retv = Field (retv, 1); + } + + if (end_stringsbuf (&ret) == -1) + return NULL; + + return take_stringsbuf (&ret); /* caller frees */ +} + +/* Implement RHashtable (RMountable, RPlainString, _). */ +char ** +guestfs_int_daemon_return_hashtable_mountable_string (value retv) +{ + CLEANUP_FREE_STRINGSBUF DECLARE_STRINGSBUF (ret); + value v, mv, sv; + char *m; + + while (retv != Val_int (0)) { + v = Field (retv, 0); /* (Mountable.t, string) */ + mv = Field (v, 0); /* Mountable.t */ + m = guestfs_int_daemon_return_string_mountable (mv); + if (m == NULL) + return NULL; + if (add_string_nodup (&ret, m) == -1) + return NULL; + sv = Field (v, 1); /* string */ + if (add_string (&ret, String_val (sv)) == -1) + return NULL; + retv = Field (retv, 1); + } + + if (end_stringsbuf (&ret) == -1) + return NULL; + + return take_stringsbuf (&ret); /* caller frees */ +} diff --git a/daemon/daemon-c.h b/daemon/daemon-c.h new file mode 100644 index 000000000..1b9f102ff --- /dev/null +++ b/daemon/daemon-c.h @@ -0,0 +1,38 @@ +/* guestfs-inspection + * Copyright (C) 2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* This file is separate from <daemon.h> because we don't want to + * include the OCaml headers (to get 'value') for the whole daemon. + */ + +#ifndef GUESTFSD_DAEMON_C_H +#define GUESTFSD_DAEMON_C_H + +#include "daemon.h" + +#include <caml/mlvalues.h> + +extern value guestfs_int_daemon_get_verbose_flag (value unitv); +extern void guestfs_int_daemon_exn_to_reply_with_error (const char *func, value exn); +extern value guestfs_int_daemon_copy_mountable (const mountable_t *mountable); +extern char **guestfs_int_daemon_return_string_list (value retv); +extern char *guestfs_int_daemon_return_string_mountable (value retv); +extern char **guestfs_int_daemon_return_hashtable_string_string (value retv); +extern char **guestfs_int_daemon_return_hashtable_mountable_string (value retv); + +#endif /* GUESTFSD_DAEMON_C_H */ diff --git a/daemon/daemon.ml b/daemon/daemon.ml new file mode 100644 index 000000000..bf486344f --- /dev/null +++ b/daemon/daemon.ml @@ -0,0 +1,41 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +open Printf + +external get_verbose_flag : unit -> bool + "guestfs_int_daemon_get_verbose_flag" "noalloc" + +(* When guestfsd starts up, early on (after parsing the command line + * but not much else), it calls 'caml_startup' which runs all + * initialization code in the OCaml modules, including this one. + * + * Therefore this is where we can place OCaml initialization code + * for the daemon. + *) +let () + (* Connect the guestfsd [-v] (verbose) flag into 'verbose ()' + * used in OCaml code to print debugging messages. + *) + if get_verbose_flag () then ( + Std_utils.set_verbose (); + eprintf "OCaml daemon loaded\n%!" + ); + + (* Register the callbacks which are used to call OCaml code from C. *) + Callbacks.init_callbacks () diff --git a/daemon/guestfsd.c b/daemon/guestfsd.c index 8489500c6..90fc1058a 100644 --- a/daemon/guestfsd.c +++ b/daemon/guestfsd.c @@ -56,6 +56,8 @@ #include <augeas.h> +#include <caml/callback.h> /* for caml_startup */ + #include "sockets.h" #include "c-ctype.h" #include "ignore-value.h" @@ -231,6 +233,12 @@ main (int argc, char *argv[]) exit (EXIT_FAILURE); } + /* Initialize the OCaml stubs. This must be done after the + * ‘verbose’ flag is set from the command line since the OCaml + * initialization code depends on that. + */ + caml_startup (argv); + #ifndef WIN32 /* Make sure SIGPIPE doesn't kill us. */ struct sigaction sa; diff --git a/daemon/sysroot-c.c b/daemon/sysroot-c.c new file mode 100644 index 000000000..ad31d36ee --- /dev/null +++ b/daemon/sysroot-c.c @@ -0,0 +1,37 @@ +/* guestfs-inspection + * Copyright (C) 2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> + +#include <caml/alloc.h> +#include <caml/fail.h> +#include <caml/memory.h> +#include <caml/mlvalues.h> + +#include "daemon.h" + +extern value guestfs_int_daemon_sysroot (value unitv); + +value +guestfs_int_daemon_sysroot (value unitv) +{ + return caml_copy_string (sysroot); +} diff --git a/daemon/sysroot.ml b/daemon/sysroot.ml new file mode 100644 index 000000000..262890952 --- /dev/null +++ b/daemon/sysroot.ml @@ -0,0 +1,23 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +open Std_utils + +external sysroot : unit -> string = "guestfs_int_daemon_sysroot" + +let sysroot_path path = sysroot () // path diff --git a/daemon/sysroot.mli b/daemon/sysroot.mli new file mode 100644 index 000000000..f99ab0d54 --- /dev/null +++ b/daemon/sysroot.mli @@ -0,0 +1,25 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +val sysroot : unit -> string +(** Return the current sysroot path where filesystems are mounted. + This comes from the daemon command line ([-r] option) or a built + in default. *) + +val sysroot_path : string -> string +(** Equivalent to calling [sysroot () // path] *) diff --git a/daemon/utils.ml b/daemon/utils.ml new file mode 100644 index 000000000..f2c1ea328 --- /dev/null +++ b/daemon/utils.ml @@ -0,0 +1,158 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +open Unix +open Printf + +open Std_utils + +let prog_exists prog + try ignore (which prog); true + with Executable_not_found _ -> false + +let commandr ?(fold_stdout_on_stderr = false) prog args + if verbose () then + eprintf "command: %s %s\n%!" + (if fold_stdout_on_stderr then " fold-stdout-on-stderr" else "") + (stringify_args (prog :: args)); + + let argv = Array.of_list (prog :: args) in + + let stdout_file, stdout_chan = Filename.open_temp_file "cmd" ".out" in + let stderr_file, stderr_chan = Filename.open_temp_file "cmd" ".err" in + let stdout_fd = descr_of_out_channel stdout_chan in + let stderr_fd = descr_of_out_channel stderr_chan in + let stdin_fd = openfile "/dev/null" [O_RDONLY] 0 in + + let pid = fork () in + if pid = 0 then ( + (* Child process. *) + dup2 stdin_fd stdin; + close stdin_fd; + if not fold_stdout_on_stderr then + dup2 stdout_fd stdout + else + dup2 stderr_fd stdout; + close stdout_fd; + dup2 stderr_fd stderr; + close stderr_fd; + + execvp prog argv + ); + + (* Parent process. *) + close stdin_fd; + close stdout_fd; + close stderr_fd; + let _, status = waitpid [] pid in + let r + match status with + | WEXITED i -> i + | WSIGNALED i -> + failwithf "external command ‘%s’ killed by signal %d" prog i + | WSTOPPED i -> + failwithf "external command ‘%s’ stopped by signal %d" prog i in + + if verbose () then + eprintf "command: %s returned %d\n" prog r; + + let stdout = read_whole_file stdout_file in + let stderr = read_whole_file stderr_file in + + (try unlink stdout_file with _ -> ()); + (try unlink stderr_file with _ -> ()); + + if verbose () then ( + if stdout <> "" then ( + eprintf "command: %s: stdout:\n%s%!" prog stdout; + if not (String.is_suffix stdout "\n") then eprintf "\n%!" + ); + if stderr <> "" then ( + eprintf "command: %s: stderr:\n%s%!" prog stderr; + if not (String.is_suffix stderr "\n") then eprintf "\n%!" + ) + ); + + (* Strip trailing \n from stderr but NOT from stdout. *) + let stderr = String.chomp stderr in + + (r, stdout, stderr) + +let command ?fold_stdout_on_stderr prog args + let r, stdout, stderr = commandr ?fold_stdout_on_stderr prog args in + if r <> 0 then + failwithf "%s exited with status %d: %s" prog r stderr; + stdout + +let udev_settle ?filename () + let args = ref [] in + if verbose () then + push_back args "--debug"; + push_back args "settle"; + (match filename with + | None -> () + | Some filename -> + push_back args "-E"; + push_back args filename + ); + let args = !args in + let r, _, err = commandr "udevadm" args in + if r <> 0 then + eprintf "udevadm settle: %s\n" err + +let root_device = lazy ((stat "/").st_dev) + +let is_root_device_stat statbuf + statbuf.st_rdev = Lazy.force root_device + +let is_root_device device + udev_settle ~filename:device (); + try + let statbuf = stat device in + is_root_device_stat statbuf + with + Unix_error (err, func, arg) -> + eprintf "is_root_device: %s: %s: %s: %s\n" + device func arg (error_message err); + false + +let proc_unmangle_path path + let n = String.length path in + let b = Buffer.create n in + let rec loop i + if i < n-3 && path.[i] = '\\' then ( + let to_int c = Char.code c - Char.code '0' in + let v + (to_int path.[i+1] lsl 6) lor + (to_int path.[i+2] lsl 3) lor + to_int path.[i+3] in + Buffer.add_char b (Char.chr v); + loop (i+4) + ) + else if i < n then ( + Buffer.add_char b path.[i]; + loop (i+1) + ) + else + Buffer.contents b + in + loop 0 + +let is_small_file path + is_regular_file path && + (stat path).st_size <= 2 * 1048 * 1024 diff --git a/daemon/utils.mli b/daemon/utils.mli new file mode 100644 index 000000000..b475ae3b8 --- /dev/null +++ b/daemon/utils.mli @@ -0,0 +1,72 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +val prog_exists : string -> bool +(** Return true iff the program is found on [$PATH]. *) + +val udev_settle : ?filename:string -> unit -> unit +(** + * LVM and other commands aren't synchronous, especially when udev is + * involved. eg. You can create or remove some device, but the + * [/dev] device node won't appear until some time later. This means + * that you get an error if you run one command followed by another. + * + * Use [udevadm settle] after certain commands, but don't be too + * fussed if it fails. + * + * The optional [?filename] passes the [udevadm settle -E filename] + * option, which means udevadm stops waiting as soon as the named + * file is created (or if it exists at the start). + *) + +val is_root_device : string -> bool +(** Return true if this is the root (appliance) device. *) + +val is_root_device_stat : Unix.stats -> bool +(** As for {!is_root_device} but operates on a statbuf instead of + a device name. *) + +val proc_unmangle_path : string -> string +(** Reverse kernel path escaping done in fs/seq_file.c:mangle_path. + This is inconsistently used for /proc fields. *) + +val command : ?fold_stdout_on_stderr:bool -> string -> string list -> string +(** Run an external command without using the shell, and collect + stdout and stderr separately. Returns stdout if the command + runs successfully. + + On failure of the command, this throws an exception containing + the stderr from the command. + + [?fold_stdout_on_stderr] (default: false) + + For broken external commands that send error messages to stdout + (hello, parted) but that don't have any useful stdout information, + use this flag to capture the error messages in the [stderr] + buffer. Nothing will be captured on stdout if you use this flag. *) + +val commandr : ?fold_stdout_on_stderr:bool -> string -> string list -> (int * string * string) +(** Run an external command without using the shell, and collect + stdout and stderr separately. + + Returns [status, stdout, stderr]. As with the C function in + [daemon/command.c], this strips the trailing [\n] from stderr, + but {b not} from stdout. *) + +val is_small_file : string -> bool +(** Return true if the path is a small regular file. *) diff --git a/docs/C_SOURCE_FILES b/docs/C_SOURCE_FILES index a3ac13b7c..7bb6d5143 100644 --- a/docs/C_SOURCE_FILES +++ b/docs/C_SOURCE_FILES @@ -72,6 +72,7 @@ daemon/blkdiscard.c daemon/blkid.c daemon/blockdev.c daemon/btrfs.c +daemon/caml-stubs.c daemon/cap.c daemon/checksum.c daemon/cleanups.c @@ -82,6 +83,8 @@ daemon/compress.c daemon/copy.c daemon/cpio.c daemon/cpmv.c +daemon/daemon-c.c +daemon/daemon-c.h daemon/daemon.h daemon/dd.c daemon/debug-bmap.c @@ -172,6 +175,7 @@ daemon/stubs.h daemon/swap.c daemon/sync.c daemon/syslinux.c +daemon/sysroot-c.c daemon/tar.c daemon/truncate.c daemon/tsk.c diff --git a/docs/guestfs-hacking.pod b/docs/guestfs-hacking.pod index bfbe4526d..61a49e872 100644 --- a/docs/guestfs-hacking.pod +++ b/docs/guestfs-hacking.pod @@ -416,6 +416,13 @@ in the C<lib/> directory. In either case, use another function as an example of what to do. +=item 3. + +As an alternative to step 2: Since libguestfs 1.38, daemon actions +can be implemented in OCaml. You have to set the C<impl = OCaml ...> +flag in the generator. Take a look at F<daemon/file.ml> for an +example. + =back After making these changes, use C<make> to compile. diff --git a/generator/OCaml.ml b/generator/OCaml.ml index 53f105198..853b41bb3 100644 --- a/generator/OCaml.ml +++ b/generator/OCaml.ml @@ -888,3 +888,11 @@ and generate_ocaml_function_type ?(extra_unit = false) (ret, args, optargs) | RStructList (_, typ) -> pr "%s array" typ | RHashtable _ -> pr "(string * string) list" ) + +(* Structure definitions (again). These are used in the daemon, + * but it's convenient to generate them here. + *) +and generate_ocaml_daemon_structs () + generate_header OCamlStyle GPLv2plus; + + generate_ocaml_structure_decls () diff --git a/generator/OCaml.mli b/generator/OCaml.mli index 4e79a5b5a..a36fbe02f 100644 --- a/generator/OCaml.mli +++ b/generator/OCaml.mli @@ -20,3 +20,4 @@ val generate_ocaml_c : unit -> unit val generate_ocaml_c_errnos : unit -> unit val generate_ocaml_ml : unit -> unit val generate_ocaml_mli : unit -> unit +val generate_ocaml_daemon_structs : unit -> unit diff --git a/generator/actions.ml b/generator/actions.ml index a9b3b5906..75742397a 100644 --- a/generator/actions.ml +++ b/generator/actions.ml @@ -185,6 +185,11 @@ let is_fish { visibility = v; style = (_, args, _) } not (List.exists (function Pointer _ -> true | _ -> false) args) let fish_functions = List.filter is_fish +let is_ocaml_function = function + | { impl = OCaml _ } -> true + | { impl = C } -> false +let impl_ocaml_functions = List.filter is_ocaml_function + (* In some places we want the functions to be displayed sorted * alphabetically, so this is useful: *) diff --git a/generator/actions.mli b/generator/actions.mli index 0d326b609..82217cbdc 100644 --- a/generator/actions.mli +++ b/generator/actions.mli @@ -40,6 +40,10 @@ val internal_functions : Types.action list -> Types.action list val fish_functions : Types.action list -> Types.action list (** Filter {!actions}, returning only functions in guestfish. *) +val impl_ocaml_functions : Types.action list -> Types.action list +(** Filter {!actions}, returning only functions implemented + in OCaml (in the daemon). *) + val documented_functions : Types.action list -> Types.action list (** Filter {!actions}, returning only functions requiring documentation. *) diff --git a/generator/daemon.ml b/generator/daemon.ml index 2ae462864..b4d4cfe8e 100644 --- a/generator/daemon.ml +++ b/generator/daemon.ml @@ -471,6 +471,324 @@ let generate_daemon_stubs actions () pr "}\n\n"; ) (actions |> daemon_functions |> sort) +let generate_daemon_caml_types_ml () + generate_header OCamlStyle GPLv2plus + +let generate_daemon_caml_callbacks_ml () + generate_header OCamlStyle GPLv2plus; + + if actions |> impl_ocaml_functions <> [] then ( + pr "let init_callbacks () =\n"; + pr " (* Initialize callbacks to OCaml code. *)\n"; + List.iter ( + fun ({ name = name; style = ret, args, optargs } as f) -> + let ocaml_function + match f.impl with + | OCaml f -> f + | C -> assert false in + + pr " Callback.register %S %s;\n" ocaml_function ocaml_function + ) (actions |> impl_ocaml_functions |> sort) + ) + else + pr "let init_callbacks () = ()\n" + +(* Generate stubs for the functions implemented in OCaml. + * Basically we implement the do_<name> function here, and + * have it call out to OCaml code. + *) +let generate_daemon_caml_stubs () + generate_header CStyle GPLv2plus; + + pr "\ +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <inttypes.h> +#include <errno.h> + +#include <caml/alloc.h> +#include <caml/callback.h> +#include <caml/fail.h> +#include <caml/memory.h> +#include <caml/mlvalues.h> + +#include \"daemon.h\" +#include \"actions.h\" +#include \"daemon-c.h\" + +"; + + (* Implement code for returning structs and struct lists. *) + let emit_return_struct typ + let struc = Structs.lookup_struct typ in + pr "/* Implement RStruct (%S, _). */\n" typ; + pr "static guestfs_int_%s *\n" typ; + pr "return_%s (value retv)\n" typ; + pr "{\n"; + pr " guestfs_int_%s *ret;\n" typ; + pr " value v;\n"; + pr "\n"; + pr " ret = malloc (sizeof (*ret));\n"; + pr " if (ret == NULL) {\n"; + pr " reply_with_perror (\"malloc\");\n"; + pr " return NULL;\n"; + pr " }\n"; + pr "\n"; + iteri ( + fun i -> + pr " v = Field (retv, %d);\n" i; + function + | n, (FString|FUUID) -> + pr " ret->%s = strdup (String_val (v));\n" n; + pr " if (ret->%s == NULL) return NULL;\n" n + | n, FBuffer -> + pr " ret->%s_len = caml_string_length (v);\n" n; + pr " ret->%s = strdup (String_val (v));\n" n; + pr " if (ret->%s == NULL) return NULL;\n" n + | n, (FBytes|FInt64|FUInt64) -> + pr " ret->%s = Int64_val (v);\n" n + | n, (FInt32|FUInt32) -> + pr " ret->%s = Int32_val (v);\n" n + | n, FOptPercent -> + pr " if (v == Val_int (0)) /* None */\n"; + pr " ret->%s = -1;\n" n; + pr " else {\n"; + pr " v = Field (v, 0);\n"; + pr " ret->%s = Double_val (v);\n" n; + pr " }\n" + | n, FChar -> + pr " ret->%s = Int_val (v);\n" n + ) struc.s_cols; + pr "\n"; + pr " return ret;\n"; + pr "}\n"; + pr "\n" + + and emit_return_struct_list typ + pr "/* Implement RStructList (%S, _). */\n" typ; + pr "static guestfs_int_%s_list *\n" typ; + pr "return_%s_list (value retv)\n" typ; + pr "{\n"; + pr " guestfs_int_%s_list *ret;\n" typ; + pr " guestfs_int_%s *r;\n" typ; + pr " size_t i, len;\n"; + pr " value v, rv;\n"; + pr "\n"; + pr " /* Count the number of elements in the list. */\n"; + pr " rv = retv;\n"; + pr " len = 0;\n"; + pr " while (rv != Val_int (0)) {\n"; + pr " len++;\n"; + pr " rv = Field (rv, 1);\n"; + pr " }\n"; + pr "\n"; + pr " ret = malloc (sizeof *ret);\n"; + pr " if (ret == NULL) {\n"; + pr " reply_with_perror (\"malloc\");\n"; + pr " return NULL;\n"; + pr " }\n"; + pr " ret->guestfs_int_%s_list_len = len;\n" typ; + pr " ret->guestfs_int_%s_list_val =\n" typ; + pr " calloc (len, sizeof (guestfs_int_%s));\n" typ; + pr " if (ret->guestfs_int_%s_list_val == NULL) {\n" typ; + pr " reply_with_perror (\"calloc\");\n"; + pr " free (ret);\n"; + pr " return NULL;\n"; + pr " }\n"; + pr "\n"; + pr " rv = retv;\n"; + pr " for (i = 0; i < len; ++i) {\n"; + pr " v = Field (rv, 0);\n"; + pr " r = return_%s (v);\n" typ; + pr " if (r == NULL)\n"; + pr " return NULL; /* XXX leaks memory along this error path */\n"; + pr " memcpy (&ret->guestfs_int_%s_list_val[i], r, sizeof (*r));\n" typ; + pr " free (r);\n"; + pr " rv = Field (rv, 1);\n"; + pr " }\n"; + pr "\n"; + pr " return ret;\n"; + pr "}\n"; + pr "\n"; + in + + List.iter ( + function + | typ, RStructOnly -> + emit_return_struct typ + | typ, (RStructListOnly | RStructAndList) -> + emit_return_struct typ; + emit_return_struct_list typ + ) (rstructs_used_by (actions |> impl_ocaml_functions)); + + (* Implement the wrapper functions. *) + List.iter ( + fun ({ name = name; style = ret, args, optargs } as f) -> + let uc_name = String.uppercase_ascii name in + let ocaml_function + match f.impl with + | OCaml f -> f + | C -> assert false in + + pr "/* Wrapper for OCaml function ‘%s’. */\n" ocaml_function; + + let args_do_function = args @ args_of_optargs optargs in + let args_do_function + List.filter (function + | String ((FileIn|FileOut), _) -> false | _ -> true) + args_do_function in + let style = ret, args_do_function, [] in + generate_prototype ~extern:false ~semicolon:false + ~single_line:false ~newline:false + ~in_daemon:true ~prefix:"do_" + name style; + pr "\n"; + + let add_unit_arg + let args = List.filter + (function + | String ((FileIn|FileOut), _) -> false | _ -> true) + args in + args = [] in + let nr_args = List.length args_do_function in + + pr "{\n"; + pr " static value *cb = NULL;\n"; + pr " CAMLparam0 ();\n"; + pr " CAMLlocal2 (v, retv);\n"; + pr " CAMLlocalN (args, %d);\n" + (nr_args + if add_unit_arg then 1 else 0); + pr "\n"; + pr " if (cb == NULL)\n"; + pr " cb = caml_named_value (\"%s\");\n" ocaml_function; + pr "\n"; + + (* Construct the actual call, but note that we want to pass + * the optional arguments first in the list. + *) + let i = ref 0 in + List.iter ( + fun optarg -> + let n = name_of_optargt optarg in + let uc_n = String.uppercase_ascii n in + + (* optargs are all passed as [None|Some _] *) + pr " if ((optargs_bitmask & GUESTFS_%s_%s_BITMASK) == 0)\n" + uc_name uc_n; + pr " args[%d] = Val_int (0); /* None */\n" !i; + pr " else {\n"; + pr " v = "; + (match optarg with + | OBool _ -> + pr "Val_bool (%s)" n; + | OInt _ -> assert false + | OInt64 _ -> assert false + | OString _ -> assert false + | OStringList _ -> assert false + ); + pr ";\n"; + pr " args[%d] = caml_alloc (1, 0);\n" !i; + pr " Store_field (args[%d], 0, v);\n" !i; + pr " }\n"; + incr i + ) optargs; + List.iter ( + fun arg -> + pr " args[%d] = " !i; + (match arg with + | Bool n -> pr "Val_bool (%s)" n + | Int n -> pr "Val_int (%s)" n + | Int64 n -> pr "caml_copy_int64 (%s)" n + | String ((PlainString|Device|Pathname|Dev_or_Path), n) -> + pr "caml_copy_string (%s)" n + | String ((Mountable|Mountable_or_Path), n) -> + pr "guestfs_int_daemon_copy_mountable (%s)" n + | String _ -> assert false + | OptString _ -> assert false + | StringList _ -> assert false + | BufferIn _ -> assert false + | Pointer _ -> assert false + ); + pr ";\n"; + incr i + ) args; + assert (!i = nr_args); + + (* If there are no non-optional arguments, we add a unit arg. *) + if add_unit_arg then + pr " args[%d] = Val_unit;\n" !i; + + pr " retv = caml_callbackN_exn (*cb, %d, args);\n" + (nr_args + if add_unit_arg then 1 else 0); + pr "\n"; + pr " if (Is_exception_result (retv)) {\n"; + pr " retv = Extract_exception (retv);\n"; + pr " guestfs_int_daemon_exn_to_reply_with_error (%S, retv);\n" name; + (match errcode_of_ret ret with + | `CannotReturnError -> + pr " CAMLreturn0;\n" + | `ErrorIsMinusOne -> + pr " CAMLreturnT (int, -1);\n" + | `ErrorIsNULL -> + pr " CAMLreturnT (void *, NULL);\n" + ); + pr " }\n"; + pr "\n"; + + (match ret with + | RErr -> + pr " CAMLreturnT (int, 0);\n" + | RInt _ -> + pr " CAMLreturnT (int, Int_val (retv));\n" + | RInt64 _ -> + pr " CAMLreturnT (int, Int64_val (retv));\n" + | RBool _ -> + pr " CAMLreturnT (int, Bool_val (retv));\n" + | RConstString _ -> assert false + | RConstOptString _ -> assert false + | RString ((RPlainString|RDevice), _) -> + pr " char *ret = strdup (String_val (retv));\n"; + pr " if (ret == NULL) {\n"; + pr " reply_with_perror (\"strdup\");\n"; + pr " CAMLreturnT (char *, NULL);\n"; + pr " }\n"; + pr " CAMLreturnT (char *, ret); /* caller frees */\n" + | RString (RMountable, _) -> + pr " char *ret =\n"; + pr " guestfs_int_daemon_return_string_mountable (retv);\n"; + pr " CAMLreturnT (char *, ret); /* caller frees */\n" + | RStringList _ -> + pr " char **ret = guestfs_int_daemon_return_string_list (retv);\n"; + pr " CAMLreturnT (char **, ret); /* caller frees */\n" + | RStruct (_, typ) -> + pr " guestfs_int_%s *ret =\n" typ; + pr " return_%s (retv);\n" typ; + pr " /* caller frees */\n"; + pr " CAMLreturnT (guestfs_int_%s *, ret);\n" typ + | RStructList (_, typ) -> + pr " guestfs_int_%s_list *ret =\n" typ; + pr " return_%s_list (retv);\n" typ; + pr " /* caller frees */\n"; + pr " CAMLreturnT (guestfs_int_%s_list *, ret);\n" typ + | RHashtable (RPlainString, RPlainString, _) -> + pr " char **ret =\n"; + pr " guestfs_int_daemon_return_hashtable_string_string (retv);\n"; + pr " CAMLreturnT (char **, ret); /* caller frees */\n" + | RHashtable (RMountable, RPlainString, _) -> + pr " char **ret =\n"; + pr " guestfs_int_daemon_return_hashtable_mountable_string (retv);\n"; + pr " CAMLreturnT (char **, ret); /* caller frees */\n" + | RHashtable _ -> assert false + | RBufferOut _ -> assert false + ); + pr "}\n"; + pr "\n" + ) (actions |> impl_ocaml_functions |> sort) + let generate_daemon_dispatch () generate_header CStyle GPLv2plus; @@ -730,6 +1048,8 @@ let generate_daemon_optgroups_c () pr "#include <config.h>\n"; pr "\n"; + pr "#include <caml/mlvalues.h>\n"; + pr "\n"; pr "#include \"daemon.h\"\n"; pr "#include \"optgroups.h\"\n"; pr "\n"; @@ -752,7 +1072,22 @@ let generate_daemon_optgroups_c () pr " { \"%s\", optgroup_%s_available },\n" group group ) optgroups_names_all; pr " { NULL, NULL }\n"; - pr "};\n" + pr "};\n"; + pr "\n"; + pr "/* Wrappers so these functions can be called from OCaml code. */\n"; + List.iter ( + fun group -> + pr "extern value guestfs_int_daemon_optgroup_%s_available (value);\n" + group; + pr "\n"; + pr "/* NB: This is a \"noalloc\" call. */\n"; + pr "value\n"; + pr "guestfs_int_daemon_optgroup_%s_available (value unitv)\n" group; + pr "{\n"; + pr " return Val_bool (optgroup_%s_available ());\n" group; + pr "}\n"; + pr "\n" + ) optgroups_names let generate_daemon_optgroups_h () generate_header CStyle GPLv2plus; @@ -801,8 +1136,18 @@ let generate_daemon_optgroups_h () pr "#endif /* GUESTFSD_OPTGROUPS_H */\n" +(* Generate optgroup available functions in OCaml. *) +let generate_daemon_optgroups_ml () + generate_header OCamlStyle GPLv2plus; + + List.iter ( + fun group -> + pr "external %s_available : unit -> bool =\n" group; + pr " \"guestfs_int_daemon_optgroup_%s_available\" \"noalloc\"\n" group + ) optgroups_names + (* Generate structs-cleanups.c file. *) -and generate_daemon_structs_cleanups_c () +let generate_daemon_structs_cleanups_c () generate_header CStyle GPLv2plus; pr "\ @@ -852,7 +1197,7 @@ and generate_daemon_structs_cleanups_c () ) structs (* Generate structs-cleanups.h file. *) -and generate_daemon_structs_cleanups_h () +let generate_daemon_structs_cleanups_h () generate_header CStyle GPLv2plus; pr "\ diff --git a/generator/daemon.mli b/generator/daemon.mli index ff008bf85..602f36a86 100644 --- a/generator/daemon.mli +++ b/generator/daemon.mli @@ -19,10 +19,14 @@ val generate_daemon_actions_h : unit -> unit val generate_daemon_stubs_h : unit -> unit val generate_daemon_stubs : Types.action list -> unit -> unit +val generate_daemon_caml_stubs : unit -> unit +val generate_daemon_caml_callbacks_ml : unit -> unit +val generate_daemon_caml_types_ml : unit -> unit val generate_daemon_dispatch : unit -> unit val generate_daemon_lvm_tokenization : unit -> unit val generate_daemon_names : unit -> unit val generate_daemon_optgroups_c : unit -> unit val generate_daemon_optgroups_h : unit -> unit +val generate_daemon_optgroups_ml : unit -> unit val generate_daemon_structs_cleanups_c : unit -> unit val generate_daemon_structs_cleanups_h : unit -> unit diff --git a/generator/main.ml b/generator/main.ml index c8890de6a..9d795b15a 100644 --- a/generator/main.ml +++ b/generator/main.ml @@ -133,6 +133,12 @@ Run it from the top source directory using the command Daemon.generate_daemon_stubs_h; output_to_subset "daemon/stubs-%d.c" Daemon.generate_daemon_stubs; + output_to "daemon/caml-stubs.c" + Daemon.generate_daemon_caml_stubs; + output_to "daemon/callbacks.ml" + Daemon.generate_daemon_caml_callbacks_ml; + output_to "daemon/types.ml" + Daemon.generate_daemon_caml_types_ml; output_to "daemon/dispatch.c" Daemon.generate_daemon_dispatch; output_to "daemon/names.c" @@ -141,6 +147,8 @@ Run it from the top source directory using the command Daemon.generate_daemon_optgroups_c; output_to "daemon/optgroups.h" Daemon.generate_daemon_optgroups_h; + output_to "daemon/optgroups.ml" + Daemon.generate_daemon_optgroups_ml; output_to "daemon/lvm-tokenization.c" Daemon.generate_daemon_lvm_tokenization; output_to "daemon/structs-cleanups.c" @@ -185,6 +193,8 @@ Run it from the top source directory using the command OCaml.generate_ocaml_c; output_to "ocaml/guestfs-c-errnos.c" OCaml.generate_ocaml_c_errnos; + output_to "daemon/structs.ml" + OCaml.generate_ocaml_daemon_structs; output_to "ocaml/bindtests.ml" Bindtests.generate_ocaml_bindtests; diff --git a/generator/types.ml b/generator/types.ml index 740bc7750..fb6c3bc06 100644 --- a/generator/types.ml +++ b/generator/types.ml @@ -379,11 +379,16 @@ type deprecated_by | Replaced_by of string (* replaced by another function *) | Deprecated_no_replacement (* deprecated with no replacement *) +type impl + | C (* implemented in C by "do_<name>" *) + | OCaml of string (* implemented in OCaml by named function *) + (* Type of an action as declared in Actions module. *) type action = { name : string; (* name, not including "guestfs_" *) added : version; (* which version was the API first added *) style : style; (* args and return value *) + impl : impl; (* implementation language (C or OCaml) *) proc_nr : int option; (* proc number, None for non-daemon *) tests : c_api_tests; (* C API tests *) test_excuse : string; (* if there's no tests ... *) @@ -439,7 +444,7 @@ type action = { *) let defaults = { name = ""; added = (-1,-1,-1); - style = RErr, [], []; proc_nr = None; + style = RErr, [], []; impl = C; proc_nr = None; tests = []; test_excuse = ""; shortdesc = ""; longdesc = ""; protocol_limit_warning = false; fish_alias = []; -- 2.13.2
Richard W.M. Jones
2017-Jul-27 19:43 UTC
[Libguestfs] [PATCH v3 02/23] daemon: Reimplement ‘file’ API in OCaml.
‘file’ is a small, self-contained API which runs a single command, so it's a good test case for reimplementing APIs. --- daemon/Makefile.am | 2 ++ daemon/file.c | 79 ----------------------------------------------- daemon/file.ml | 59 +++++++++++++++++++++++++++++++++++ daemon/file.mli | 19 ++++++++++++ generator/actions_core.ml | 1 + 5 files changed, 81 insertions(+), 79 deletions(-) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index e5a4986e3..8cf5d77ce 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -243,6 +243,7 @@ guestfsd_CFLAGS = \ SOURCES_MLI = \ chroot.mli \ sysroot.mli \ + file.mli \ utils.mli SOURCES_ML = \ @@ -252,6 +253,7 @@ SOURCES_ML = \ optgroups.ml \ sysroot.ml \ chroot.ml \ + file.ml \ callbacks.ml \ daemon.ml diff --git a/daemon/file.c b/daemon/file.c index da4218c45..2d687a960 100644 --- a/daemon/file.c +++ b/daemon/file.c @@ -445,85 +445,6 @@ do_pwrite_device (const char *device, const char *content, size_t size, return pwrite_fd (fd, content, size, offset, device, 1); } -/* This runs the 'file' command. */ -char * -do_file (const char *path) -{ - CLEANUP_FREE char *buf = NULL; - const char *display_path = path; - const int is_dev = STRPREFIX (path, "/dev/"); - struct stat statbuf; - - if (!is_dev) { - buf = sysroot_path (path); - if (!buf) { - reply_with_perror ("malloc"); - return NULL; - } - path = buf; - - /* For non-dev, check this is a regular file, else just return the - * file type as a string (RHBZ#582484). - */ - if (lstat (path, &statbuf) == -1) { - reply_with_perror ("lstat: %s", display_path); - return NULL; - } - - if (! S_ISREG (statbuf.st_mode)) { - char *ret; - - if (S_ISDIR (statbuf.st_mode)) - ret = strdup ("directory"); - else if (S_ISCHR (statbuf.st_mode)) - ret = strdup ("character device"); - else if (S_ISBLK (statbuf.st_mode)) - ret = strdup ("block device"); - else if (S_ISFIFO (statbuf.st_mode)) - ret = strdup ("FIFO"); - else if (S_ISLNK (statbuf.st_mode)) - ret = strdup ("symbolic link"); - else if (S_ISSOCK (statbuf.st_mode)) - ret = strdup ("socket"); - else - ret = strdup ("unknown, not regular file"); - - if (ret == NULL) - reply_with_perror ("strdup"); - return ret; - } - } - - /* Which flags to use? For /dev paths, follow links because - * /dev/VG/LV is a symbolic link. - */ - const char *flags = is_dev ? "-zbsL" : "-zb"; - - char *out; - CLEANUP_FREE char *err = NULL; - int r = command (&out, &err, "file", flags, path, NULL); - - if (r == -1) { - free (out); - reply_with_error ("%s: %s", display_path, err); - return NULL; - } - - /* We need to remove the trailing \n from output of file(1). */ - size_t len = strlen (out); - if (len > 0 && out[len-1] == '\n') - out[--len] = '\0'; - - /* Some upstream versions of file add a space at the end of the - * output. This is fixed in the Fedora version, but we might as - * well fix it here too. (RHBZ#928995). - */ - if (len > 0 && out[len-1] == ' ') - out[--len] = '\0'; - - return out; /* caller frees */ -} - /* zcat | file */ char * do_zfile (const char *method, const char *path) diff --git a/daemon/file.ml b/daemon/file.ml new file mode 100644 index 000000000..b45bd9019 --- /dev/null +++ b/daemon/file.ml @@ -0,0 +1,59 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +open Unix +open Printf + +open Std_utils + +open Utils + +(* This runs the [file] command. *) +let file path + let is_dev = String.is_prefix path "/dev/" in + + (* For non-dev, check this is a regular file, else just return the + * file type as a string (RHBZ#582484). + *) + if not is_dev then ( + let chroot = Chroot.create ~name:(sprintf "file: %s" path) () in + + let statbuf = Chroot.f chroot lstat path in + match statbuf.st_kind with + | S_DIR -> "directory" + | S_CHR -> "character device" + | S_BLK -> "block device" + | S_FIFO -> "FIFO" + | S_LNK -> "symbolic link" + | S_SOCK -> "socket" + | S_REG -> + (* Regular file, so now run [file] on it. *) + let out = command "file" ["-zb"; Sysroot.sysroot_path path] in + + (* We need to remove the trailing \n from output of file(1). + * + * Some upstream versions of file add a space at the end of the + * output. This is fixed in the Fedora version, but we might as + * well fix it here too. (RHBZ#928995). + *) + String.trimr out + ) + else (* it's a device *) ( + let out = command "file" ["-zbsL"; path] in + String.trimr out + ) diff --git a/daemon/file.mli b/daemon/file.mli new file mode 100644 index 000000000..bd49bad0b --- /dev/null +++ b/daemon/file.mli @@ -0,0 +1,19 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +val file : string -> string diff --git a/generator/actions_core.ml b/generator/actions_core.ml index 0e667eff1..26ed1274e 100644 --- a/generator/actions_core.ml +++ b/generator/actions_core.ml @@ -2321,6 +2321,7 @@ and physical volumes." }; { defaults with name = "file"; added = (1, 9, 1); style = RString (RPlainString, "description"), [String (Dev_or_Path, "path")], []; + impl = OCaml "File.file"; tests = [ InitISOFS, Always, TestResultString ( [["file"; "/empty"]], "empty"), []; -- 2.13.2
Richard W.M. Jones
2017-Jul-27 19:43 UTC
[Libguestfs] [PATCH v3 03/23] daemon: Reimplement ‘vfs_type’ API in OCaml.
--- daemon/Makefile.am | 4 ++++ daemon/blkid.c | 6 ------ daemon/blkid.ml | 40 ++++++++++++++++++++++++++++++++++++++++ daemon/blkid.mli | 19 +++++++++++++++++++ daemon/mountable.ml | 43 +++++++++++++++++++++++++++++++++++++++++++ daemon/mountable.mli | 34 ++++++++++++++++++++++++++++++++++ generator/actions_core.ml | 1 + 7 files changed, 141 insertions(+), 6 deletions(-) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 8cf5d77ce..cf0e2c503 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -241,9 +241,11 @@ guestfsd_CFLAGS = \ # library and then linked to the daemon. See # https://caml.inria.fr/pub/docs/manual-ocaml/intfc.html SOURCES_MLI = \ + blkid.mli \ chroot.mli \ sysroot.mli \ file.mli \ + mountable.mli \ utils.mli SOURCES_ML = \ @@ -252,7 +254,9 @@ SOURCES_ML = \ structs.ml \ optgroups.ml \ sysroot.ml \ + mountable.ml \ chroot.ml \ + blkid.ml \ file.ml \ callbacks.ml \ daemon.ml diff --git a/daemon/blkid.c b/daemon/blkid.c index d9858d5c8..fea322f5e 100644 --- a/daemon/blkid.c +++ b/daemon/blkid.c @@ -67,12 +67,6 @@ get_blkid_tag (const char *device, const char *tag) } char * -do_vfs_type (const mountable_t *mountable) -{ - return get_blkid_tag (mountable->device, "TYPE"); -} - -char * do_vfs_label (const mountable_t *mountable) { CLEANUP_FREE char *type = do_vfs_type (mountable); diff --git a/daemon/blkid.ml b/daemon/blkid.ml new file mode 100644 index 000000000..69baa8e8d --- /dev/null +++ b/daemon/blkid.ml @@ -0,0 +1,40 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +open Std_utils + +open Utils + +let rec vfs_type { Mountable.m_device = device } + get_blkid_tag device "TYPE" + +and get_blkid_tag device tag + let r, out, err + commandr "blkid" + [(* Adding -c option kills all caching, even on RHEL 5. *) + "-c"; "/dev/null"; + "-o"; "value"; "-s"; tag; device] in + match r with + | 0 -> (* success *) + String.chomp out + + | 2 -> (* means tag not found, we return "" *) + "" + + | _ -> + failwithf "blkid: %s: %s: %s" device tag err diff --git a/daemon/blkid.mli b/daemon/blkid.mli new file mode 100644 index 000000000..59a86ac2c --- /dev/null +++ b/daemon/blkid.mli @@ -0,0 +1,19 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +val vfs_type : Mountable.t -> string diff --git a/daemon/mountable.ml b/daemon/mountable.ml new file mode 100644 index 000000000..96dffb80b --- /dev/null +++ b/daemon/mountable.ml @@ -0,0 +1,43 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +open Printf + +type t = { + m_type : mountable_type; + m_device : string; +} +and mountable_type + | MountableDevice + | MountablePath + | MountableBtrfsVol of string (* volume *) + +let to_string { m_type = t; m_device = device } + match t with + | MountableDevice | MountablePath -> device + | MountableBtrfsVol volume -> + sprintf "btrfsvol:%s/%s" device volume + +let of_device device + { m_type = MountableDevice; m_device = device } + +let of_path path + { m_type = MountablePath; m_device = path } + +let of_btrfsvol device volume + { m_type = MountableBtrfsVol volume; m_device = device } diff --git a/daemon/mountable.mli b/daemon/mountable.mli new file mode 100644 index 000000000..52f1ad45b --- /dev/null +++ b/daemon/mountable.mli @@ -0,0 +1,34 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +type t = { + m_type : mountable_type; + m_device : string; +} +and mountable_type + | MountableDevice + | MountablePath + | MountableBtrfsVol of string (* volume *) + +val to_string : t -> string +(** Convert the mountable back to the string used in the public API. *) + +val of_device : string -> t +val of_path : string -> t +val of_btrfsvol : string -> string -> t +(** Create a mountable from various objects. *) diff --git a/generator/actions_core.ml b/generator/actions_core.ml index 26ed1274e..a6eb2c273 100644 --- a/generator/actions_core.ml +++ b/generator/actions_core.ml @@ -4872,6 +4872,7 @@ See also C<guestfs_realpath>." }; { defaults with name = "vfs_type"; added = (1, 0, 75); style = RString (RPlainString, "fstype"), [String (Mountable, "mountable")], []; + impl = OCaml "Blkid.vfs_type"; tests = [ InitScratchFS, Always, TestResultString ( [["vfs_type"; "/dev/sdb1"]], "ext2"), [] -- 2.13.2
Richard W.M. Jones
2017-Jul-27 19:43 UTC
[Libguestfs] [PATCH v3 04/23] daemon: Reimplement several devsparts APIs in OCaml.
The reimplemented APIs are: * list_devices * list_partitions * part_to_dev * part_to_partnum * is_whole_device --- daemon/Makefile.am | 2 + daemon/daemon.h | 2 - daemon/devsparts.c | 257 ---------------------------------------------- daemon/devsparts.ml | 113 ++++++++++++++++++++ daemon/devsparts.mli | 25 +++++ daemon/guestfsd.c | 75 -------------- daemon/utils.ml | 84 +++++++++++++++ daemon/utils.mli | 15 +++ generator/actions_core.ml | 5 + 9 files changed, 244 insertions(+), 334 deletions(-) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index cf0e2c503..2cdf20c07 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -244,6 +244,7 @@ SOURCES_MLI = \ blkid.mli \ chroot.mli \ sysroot.mli \ + devsparts.mli \ file.mli \ mountable.mli \ utils.mli @@ -257,6 +258,7 @@ SOURCES_ML = \ mountable.ml \ chroot.ml \ blkid.ml \ + devsparts.ml \ file.ml \ callbacks.ml \ daemon.ml diff --git a/daemon/daemon.h b/daemon/daemon.h index a68a79e12..4acc9c9fe 100644 --- a/daemon/daemon.h +++ b/daemon/daemon.h @@ -67,8 +67,6 @@ extern int xread (int sock, void *buf, size_t len) __attribute__((__warn_unused_result__)); extern void sort_strings (char **argv, size_t len); extern void free_stringslen (char **argv, size_t len); -extern void sort_device_names (char **argv, size_t len); -extern int compare_device_names (const char *a, const char *b); extern char **take_stringsbuf (struct stringsbuf *sb); extern void free_stringsbuf (struct stringsbuf *sb); extern struct stringsbuf split_lines_sb (char *str); diff --git a/daemon/devsparts.c b/daemon/devsparts.c index 82467b92f..1aacb8e16 100644 --- a/daemon/devsparts.c +++ b/daemon/devsparts.c @@ -33,263 +33,6 @@ #include "daemon.h" #include "actions.h" -typedef int (*block_dev_func_t) (const char *dev, struct stringsbuf *r); - -/* Execute a given function for each discovered block device */ -static char ** -foreach_block_device (block_dev_func_t func, bool return_md) -{ - CLEANUP_FREE_STRINGSBUF DECLARE_STRINGSBUF (r); - DIR *dir; - int err = 0; - struct dirent *d; - int fd; - - dir = opendir ("/sys/block"); - if (!dir) { - reply_with_perror ("opendir: /sys/block"); - return NULL; - } - - for (;;) { - errno = 0; - d = readdir (dir); - if (!d) break; - - if (STREQLEN (d->d_name, "sd", 2) || - STREQLEN (d->d_name, "hd", 2) || - STREQLEN (d->d_name, "ubd", 3) || - STREQLEN (d->d_name, "vd", 2) || - STREQLEN (d->d_name, "sr", 2) || - (return_md && - STREQLEN (d->d_name, "md", 2) && c_isdigit (d->d_name[2]))) { - CLEANUP_FREE char *dev_path = NULL; - if (asprintf (&dev_path, "/dev/%s", d->d_name) == -1) { - reply_with_perror ("asprintf"); - closedir (dir); - return NULL; - } - - /* Ignore the root device. */ - if (is_root_device (dev_path)) - continue; - - /* RHBZ#514505: Some versions of qemu <= 0.10 add a - * CD-ROM device even though we didn't request it. Try to - * detect this by seeing if the device contains media. - */ - fd = open (dev_path, O_RDONLY|O_CLOEXEC); - if (fd == -1) { - perror (dev_path); - continue; - } - close (fd); - - /* Call the map function for this device */ - if ((*func)(d->d_name, &r) != 0) { - err = 1; - break; - } - } - } - - /* Check readdir didn't fail */ - if (errno != 0) { - reply_with_perror ("readdir: /sys/block"); - closedir (dir); - return NULL; - } - - /* Close the directory handle */ - if (closedir (dir) == -1) { - reply_with_perror ("closedir: /sys/block"); - return NULL; - } - - if (err) - return NULL; - - /* Sort the devices. */ - if (r.size > 0) - sort_device_names (r.argv, r.size); - - /* NULL terminate the list */ - if (end_stringsbuf (&r) == -1) { - return NULL; - } - - return take_stringsbuf (&r); -} - -/* Add a device to the list of devices */ -static int -add_device (const char *device, struct stringsbuf *r) -{ - char *dev_path; - - if (asprintf (&dev_path, "/dev/%s", device) == -1) { - reply_with_perror ("asprintf"); - return -1; - } - - if (add_string_nodup (r, dev_path) == -1) - return -1; - - return 0; -} - -char ** -do_list_devices (void) -{ - /* For backwards compatibility, don't return MD devices in the list - * returned by guestfs_list_devices. This is because most API users - * expect that this list is effectively the same as the list of - * devices added by guestfs_add_drive. - * - * Also, MD devices are special devices - unlike the devices exposed - * by QEMU, and there is a special API for them, - * guestfs_list_md_devices. - */ - return foreach_block_device (add_device, false); -} - -static int -add_partitions (const char *device, struct stringsbuf *r) -{ - CLEANUP_FREE char *devdir = NULL; - - /* Open the device's directory under /sys/block */ - if (asprintf (&devdir, "/sys/block/%s", device) == -1) { - reply_with_perror ("asprintf"); - return -1; - } - - DIR *dir = opendir (devdir); - if (!dir) { - reply_with_perror ("opendir: %s", devdir); - return -1; - } - - /* Look in /sys/block/<device>/ for entries starting with <device> - * e.g. /sys/block/sda/sda1 - */ - errno = 0; - struct dirent *d; - while ((d = readdir (dir)) != NULL) { - if (STREQLEN (d->d_name, device, strlen (device))) { - CLEANUP_FREE char *part = NULL; - if (asprintf (&part, "/dev/%s", d->d_name) == -1) { - perror ("asprintf"); - closedir (dir); - return -1; - } - - if (add_string (r, part) == -1) { - closedir (dir); - return -1; - } - } - } - - /* Check if readdir failed */ - if (0 != errno) { - reply_with_perror ("readdir: %s", devdir); - closedir (dir); - return -1; - } - - /* Close the directory handle */ - if (closedir (dir) == -1) { - reply_with_perror ("closedir: /sys/block/%s", device); - return -1; - } - - return 0; -} - -char ** -do_list_partitions (void) -{ - return foreach_block_device (add_partitions, true); -} - -char * -do_part_to_dev (const char *part) -{ - int err = 1; - size_t n = strlen (part); - - while (n >= 1 && c_isdigit (part[n-1])) { - err = 0; - n--; - } - - if (err) { - reply_with_error ("device name is not a partition"); - return NULL; - } - - /* Deal with <device>p<N> partition names such as /dev/md0p1. */ - if (part[n-1] == 'p') - n--; - - char *r = strndup (part, n); - if (r == NULL) { - reply_with_perror ("strdup"); - return NULL; - } - - return r; -} - -int -do_part_to_partnum (const char *part) -{ - int err = 1; - size_t n = strlen (part); - - while (n >= 1 && c_isdigit (part[n-1])) { - err = 0; - n--; - } - - if (err) { - reply_with_error ("device name is not a partition"); - return -1; - } - - int r; - if (sscanf (&part[n], "%d", &r) != 1) { - reply_with_error ("could not parse number"); - return -1; - } - - return r; -} - -int -do_is_whole_device (const char *device) -{ - /* A 'whole' block device will have a symlink to the device in its - * /sys/block directory */ - CLEANUP_FREE char *devpath = NULL; - if (asprintf (&devpath, "/sys/block/%s/device", - device + strlen ("/dev/")) == -1) { - reply_with_perror ("asprintf"); - return -1; - } - - struct stat statbuf; - if (stat (devpath, &statbuf) == -1) { - if (errno == ENOENT || errno == ENOTDIR) return 0; - - reply_with_perror ("stat"); - return -1; - } - - return 1; -} - int do_device_index (const char *device) { diff --git a/daemon/devsparts.ml b/daemon/devsparts.ml new file mode 100644 index 000000000..43093aba8 --- /dev/null +++ b/daemon/devsparts.ml @@ -0,0 +1,113 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +open Printf +open Unix + +open Std_utils + +open Utils + +let map_block_devices ~return_md f + let devs = Sys.readdir "/sys/block" in + let devs = Array.to_list devs in + let devs = List.filter ( + fun dev -> + String.is_prefix dev "sd" || + String.is_prefix dev "hd" || + String.is_prefix dev "ubd" || + String.is_prefix dev "vd" || + String.is_prefix dev "sr" || + (return_md && String.is_prefix dev "md" && + String.length dev >= 3 && Char.isdigit dev.[2]) + ) devs in + + (* Ignore the root device. *) + let devs + List.filter (fun dev -> not (is_root_device ("/dev/" ^ dev))) devs in + + (* RHBZ#514505: Some versions of qemu <= 0.10 add a + * CD-ROM device even though we didn't request it. Try to + * detect this by seeing if the device contains media. + *) + let devs + List.filter ( + fun dev -> + try + let fd = openfile ("/dev/" ^ dev) [O_RDONLY] 0 in + Unix.set_close_on_exec fd; (* XXX *) + close fd; + true + with _ -> false + ) devs in + + (* Call the map function for the devices left in the list. *) + List.map f devs + +let list_devices () + (* For backwards compatibility, don't return MD devices in the list + * returned by guestfs_list_devices. This is because most API users + * expect that this list is effectively the same as the list of + * devices added by guestfs_add_drive. + * + * Also, MD devices are special devices - unlike the devices exposed + * by QEMU, and there is a special API for them, + * guestfs_list_md_devices. + *) + let devices + map_block_devices ~return_md:false ((^) "/dev/") in + sort_device_names devices + +let rec list_partitions () + let partitions = map_block_devices ~return_md:true add_partitions in + let partitions = List.flatten partitions in + sort_device_names partitions + +and add_partitions dev + (* Open the device's directory under /sys/block *) + let parts = Sys.readdir ("/sys/block/" ^ dev) in + let parts = Array.to_list parts in + + (* Look in /sys/block/<device>/ for entries starting with + * <device>, eg. /sys/block/sda/sda1. + *) + let parts = List.filter (fun part -> String.is_prefix part dev) parts in + List.map ((^) "/dev/") parts + +let part_to_dev part + let dev, part = split_device_partition part in + if part = 0 then + failwithf "device name is not a partition"; + "/dev/" ^ dev + +let part_to_partnum part + let _, part = split_device_partition part in + if part = 0 then + failwithf "device name is not a partition"; + part + +let is_whole_device device + (* A 'whole' block device will have a symlink to the device in its + * /sys/block directory + *) + assert (String.is_prefix device "/dev/"); + let device = String.sub device 5 (String.length device - 5) in + let devpath = sprintf "/sys/block/%s/device" device in + + try ignore (stat devpath); true + with Unix_error ((ENOENT|ENOTDIR), _, _) -> false diff --git a/daemon/devsparts.mli b/daemon/devsparts.mli new file mode 100644 index 000000000..4dfaa86e6 --- /dev/null +++ b/daemon/devsparts.mli @@ -0,0 +1,25 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +val list_devices : unit -> string list +val list_partitions : unit -> string list + +val part_to_dev : string -> string +val part_to_partnum : string -> int + +val is_whole_device : string -> bool diff --git a/daemon/guestfsd.c b/daemon/guestfsd.c index 90fc1058a..2ceaccbee 100644 --- a/daemon/guestfsd.c +++ b/daemon/guestfsd.c @@ -628,81 +628,6 @@ free_stringslen (char **argv, size_t len) } /** - * Compare device names (including partition numbers if present). - * - * L<https://rwmj.wordpress.com/2011/01/09/how-are-linux-drives-named-beyond-drive-26-devsdz/> - */ -int -compare_device_names (const char *a, const char *b) -{ - size_t alen, blen; - int r; - int a_partnum, b_partnum; - - /* Skip /dev/ prefix if present. */ - if (STRPREFIX (a, "/dev/")) - a += 5; - if (STRPREFIX (b, "/dev/")) - b += 5; - - /* Skip sd/hd/ubd/vd. */ - alen = strcspn (a, "d"); - blen = strcspn (b, "d"); - assert (alen > 0 && alen <= 2); - assert (blen > 0 && blen <= 2); - a += alen + 1; - b += blen + 1; - - /* Get device name part, that is, just 'a', 'ab' etc. */ - alen = strcspn (a, "0123456789"); - blen = strcspn (b, "0123456789"); - - /* If device name part is longer, it is always greater, eg. - * "/dev/sdz" < "/dev/sdaa". - */ - if (alen != blen) - return alen - blen; - - /* Device name parts are the same length, so do a regular compare. */ - r = strncmp (a, b, alen); - if (r != 0) - return r; - - /* Compare partitions numbers. */ - a += alen; - b += alen; - - /* If no partition numbers, bail -- the devices are the same. This - * can happen in one peculiar case: where you have a mix of devices - * with different interfaces (eg. /dev/sda and /dev/vda). - * (RHBZ#858128). - */ - if (!*a && !*b) - return 0; - - r = sscanf (a, "%d", &a_partnum); - assert (r == 1); - r = sscanf (b, "%d", &b_partnum); - assert (r == 1); - - return a_partnum - b_partnum; -} - -static int -compare_device_names_vp (const void *vp1, const void *vp2) -{ - char * const *p1 = (char * const *) vp1; - char * const *p2 = (char * const *) vp2; - return compare_device_names (*p1, *p2); -} - -void -sort_device_names (char **argv, size_t len) -{ - qsort (argv, len, sizeof (char *), compare_device_names_vp); -} - -/** * Split an output string into a NULL-terminated list of lines, * wrapped into a stringsbuf. * diff --git a/daemon/utils.ml b/daemon/utils.ml index f2c1ea328..b459a2314 100644 --- a/daemon/utils.ml +++ b/daemon/utils.ml @@ -131,6 +131,90 @@ let is_root_device device device func arg (error_message err); false +(* XXX This function is copied from C, but is misconceived. It + * cannot by design work for devices like /dev/md0. It would be + * better if it checked for the existence of devices and partitions + * in /sys/block so we know what the kernel thinks is a device or + * partition. The same applies to APIs such as part_to_partnum + * and part_to_dev which rely on this function. + *) +let split_device_partition dev + (* Skip /dev/ prefix if present. *) + let dev + if String.is_prefix dev "/dev/" then + String.sub dev 5 (String.length dev - 5) + else dev in + + (* Find the partition number (if present). *) + let dev, part + let n = String.length dev in + let i = ref n in + while !i >= 1 && Char.isdigit dev.[!i-1] do + decr i + done; + let i = !i in + if i = n then + dev, 0 (* no partition number, whole device *) + else + String.sub dev 0 i, int_of_string (String.sub dev i (n-i)) in + + (* Deal with device names like /dev/md0p1. *) + (* XXX This function is buggy (as was the old C function) when + * presented with a whole device like /dev/md0. + *) + let dev + let n = String.length dev in + if n < 2 || dev.[n-1] <> 'p' || not (Char.isdigit dev.[n-2]) then + dev + else ( + let i = ref (n-1) in + while !i >= 0 && Char.isdigit dev.[!i] do + decr i; + done; + let i = !i in + String.sub dev 0 i + ) in + + dev, part + +let rec sort_device_names devs + List.sort compare_device_names devs + +and compare_device_names a b + (* This takes the device name like "/dev/sda1" and returns ("sda", 1). *) + let dev_a, part_a = split_device_partition a + and dev_b, part_b = split_device_partition b in + + (* Skip "sd|hd|ubd..." so that /dev/sda and /dev/vda sort together. + * (This is what the old C function did, but it's not clear if it + * is still relevant. XXX) + *) + let skip_prefix dev + let n = String.length dev in + if n >= 2 && dev.[1] = 'd' then + String.sub dev 2 (String.length dev - 2) + else if n >= 3 && dev.[2] = 'd' then + String.sub dev 3 (String.length dev - 3) + else + dev in + let dev_a = skip_prefix dev_a + and dev_b = skip_prefix dev_b in + + (* If device name part is longer, it is always greater, eg. + * "/dev/sdz" < "/dev/sdaa". + *) + let r = compare (String.length dev_a) (String.length dev_b) in + if r <> 0 then r + else ( + (* Device name parts are the same length, so do a regular compare. *) + let r = compare dev_a dev_b in + if r <> 0 then r + else ( + (* Device names are identical, so compare partition numbers. *) + compare part_a part_b + ) + ) + let proc_unmangle_path path let n = String.length path in let b = Buffer.create n in diff --git a/daemon/utils.mli b/daemon/utils.mli index b475ae3b8..16569f018 100644 --- a/daemon/utils.mli +++ b/daemon/utils.mli @@ -41,6 +41,21 @@ val is_root_device_stat : Unix.stats -> bool (** As for {!is_root_device} but operates on a statbuf instead of a device name. *) +val split_device_partition : string -> string * int +(** Split a device name like [/dev/sda1] into a device name and + partition number, eg. ["sda", 1]. + + The [/dev/] prefix is skipped and removed, if present. + + If the partition number is not present (a whole device), 0 is returned. + + This function splits [/dev/md0p1] to ["md0", 1]. *) + +val sort_device_names : string list -> string list +(** Sort device names correctly so that /dev/sdaa appears after /dev/sdz. + This also deals with partition numbers, and works whether or not + [/dev/] is present. *) + val proc_unmangle_path : string -> string (** Reverse kernel path escaping done in fs/seq_file.c:mangle_path. This is inconsistently used for /proc fields. *) diff --git a/generator/actions_core.ml b/generator/actions_core.ml index a6eb2c273..94391288f 100644 --- a/generator/actions_core.ml +++ b/generator/actions_core.ml @@ -1817,6 +1817,7 @@ is I<not> intended that you try to parse the output string." }; { defaults with name = "list_devices"; added = (0, 0, 4); style = RStringList (RDevice, "devices"), [], []; + impl = OCaml "Devsparts.list_devices"; tests = [ InitEmpty, Always, TestResult ( [["list_devices"]], @@ -1833,6 +1834,7 @@ See also C<guestfs_list_filesystems>." }; { defaults with name = "list_partitions"; added = (0, 0, 4); style = RStringList (RDevice, "partitions"), [], []; + impl = OCaml "Devsparts.list_partitions"; tests = [ InitBasicFS, Always, TestResult ( [["list_partitions"]], @@ -6086,6 +6088,7 @@ See also C<guestfs_stat>." }; { defaults with name = "part_to_dev"; added = (1, 5, 15); style = RString (RDevice, "device"), [String (Device, "partition")], []; + impl = OCaml "Devsparts.part_to_dev"; tests = [ InitPartition, Always, TestResultDevice ( [["part_to_dev"; "/dev/sda1"]], "/dev/sda"), []; @@ -6533,6 +6536,7 @@ as in C<guestfs_compress_out>." }; { defaults with name = "part_to_partnum"; added = (1, 13, 25); style = RInt "partnum", [String (Device, "partition")], []; + impl = OCaml "Devsparts.part_to_partnum"; tests = [ InitPartition, Always, TestResult ( [["part_to_partnum"; "/dev/sda1"]], "ret == 1"), []; @@ -8480,6 +8484,7 @@ you are better to use C<guestfs_mv> instead." }; { defaults with name = "is_whole_device"; added = (1, 21, 9); style = RBool "flag", [String (Device, "device")], []; + impl = OCaml "Devsparts.is_whole_device"; tests = [ InitEmpty, Always, TestResultTrue ( [["is_whole_device"; "/dev/sda"]]), []; -- 2.13.2
Richard W.M. Jones
2017-Jul-27 19:43 UTC
[Libguestfs] [PATCH v3 05/23] daemon: Add unit tests of the ‘Utils’ module.
--- .gitignore | 1 + daemon/Makefile.am | 45 ++++++++++++++++++++++++++++++++++++++++- daemon/daemon_utils_tests.ml | 48 ++++++++++++++++++++++++++++++++++++++++++++ daemon/dummy.c | 2 ++ docs/C_SOURCE_FILES | 1 + 5 files changed, 96 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index de50f7381..4699933d3 100644 --- a/.gitignore +++ b/.gitignore @@ -169,6 +169,7 @@ Makefile.in /daemon/actions.h /daemon/callbacks.ml /daemon/caml-stubs.c +/daemon/daemon_utils_tests /daemon/dispatch.c /daemon/guestfsd /daemon/guestfsd.8 diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 2cdf20c07..f1b395725 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -42,6 +42,7 @@ generator_built = \ EXTRA_DIST = \ $(generator_built) \ $(SOURCES_MLI) $(SOURCES_ML) \ + daemon_utils_tests.ml \ guestfsd.pod if INSTALL_DAEMON @@ -269,7 +270,8 @@ XOBJECTS = $(BOBJECTS:.cmo=.cmx) OCAMLPACKAGES = \ -package str,unix,hivex \ -I $(top_srcdir)/common/mlstdutils \ - -I $(top_srcdir)/common/mlutils + -I $(top_srcdir)/common/mlutils \ + -I $(top_builddir)/common/utils/.libs OCAMLFLAGS = $(OCAML_FLAGS) $(OCAML_WARN_FLAGS) @@ -310,6 +312,47 @@ depend: .depend -include .depend +# Tests. + +check_PROGRAMS = daemon_utils_tests +TESTS_ENVIRONMENT = $(top_builddir)/run --test +TESTS = \ + daemon_utils_tests + +check-valgrind: + $(MAKE) VG="@VG@" check + +daemon_utils_tests_SOURCES = dummy.c +daemon_utils_tests_CPPFLAGS = \ + -I. \ + -I$(top_builddir) \ + -I$(shell $(OCAMLC) -where) \ + -I$(top_srcdir)/lib +daemon_utils_tests_BOBJECTS = \ + utils.cmo \ + daemon_utils_tests.cmo +daemon_utils_tests_XOBJECTS = $(daemon_utils_tests_BOBJECTS:.cmo=.cmx) + +if !HAVE_OCAMLOPT +daemon_utils_tests_THEOBJECTS = $(daemon_utils_tests_BOBJECTS) +else +daemon_utils_tests_THEOBJECTS = $(daemon_utils_tests_XOBJECTS) +endif + +OCAMLLINKFLAGS = \ + mlcutils.$(MLARCHIVE) \ + mlstdutils.$(MLARCHIVE) \ + $(LINK_CUSTOM_OCAMLC_ONLY) + +daemon_utils_tests_DEPENDENCIES = \ + $(daemon_utils_tests_THEOBJECTS) \ + $(top_srcdir)/ocaml-link.sh +daemon_utils_tests_LINK = \ + $(top_srcdir)/ocaml-link.sh -- \ + $(OCAMLFIND) $(BEST) $(OCAMLFLAGS) $(OCAMLLINKFLAGS) \ + $(OCAMLPACKAGES) \ + $(daemon_utils_tests_THEOBJECTS) -o $@ + # Manual pages and HTML files for the website. if INSTALL_DAEMON man_MANS = guestfsd.8 diff --git a/daemon/daemon_utils_tests.ml b/daemon/daemon_utils_tests.ml new file mode 100644 index 000000000..892509d89 --- /dev/null +++ b/daemon/daemon_utils_tests.ml @@ -0,0 +1,48 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +open Unix +open Printf + +open Utils + +(* Test prog_exists. *) +let () + assert (prog_exists "ls"); + assert (prog_exists "true") + +(* Test command, commandr. *) +let () + ignore (command "true" []); + + let r, _, _ = commandr "false" [] in + assert (r = 1) + +(* Test split_device_partition. *) +let () + assert (split_device_partition "/dev/sda1" = ("sda", 1)); + assert (split_device_partition "/dev/sdb" = ("sdb", 0)); + assert (split_device_partition "/dev/ubda9" = ("ubda", 9)); + assert (split_device_partition "/dev/md0p1" = ("md0", 1)) + (* XXX The function is buggy: + assert (split_device_partition "/dev/md0" = ("md0", 0)) *) + +(* Test proc_unmangle_path. *) +let () + assert (proc_unmangle_path "\\040" = " "); + assert (proc_unmangle_path "\\040\\040" = " ") diff --git a/daemon/dummy.c b/daemon/dummy.c new file mode 100644 index 000000000..ebab6198c --- /dev/null +++ b/daemon/dummy.c @@ -0,0 +1,2 @@ +/* Dummy source, to be used for OCaml-based tools with no C sources. */ +enum { foo = 1 }; diff --git a/docs/C_SOURCE_FILES b/docs/C_SOURCE_FILES index 7bb6d5143..5f76499f7 100644 --- a/docs/C_SOURCE_FILES +++ b/docs/C_SOURCE_FILES @@ -97,6 +97,7 @@ daemon/dispatch.c daemon/dmesg.c daemon/dropcaches.c daemon/du.c +daemon/dummy.c daemon/echo-daemon.c daemon/ext2.c daemon/fallocate.c -- 2.13.2
Richard W.M. Jones
2017-Jul-27 19:43 UTC
[Libguestfs] [PATCH v3 06/23] daemon: Reimplement ‘is_dir’, ‘is_file’ and ‘is_symlink’ APIs in OCaml.
This also demonstrates usage of optional arguments. --- daemon/Makefile.am | 2 ++ daemon/is.c | 41 ----------------------------------------- daemon/is.ml | 42 ++++++++++++++++++++++++++++++++++++++++++ daemon/is.mli | 21 +++++++++++++++++++++ generator/actions_core.ml | 3 +++ 5 files changed, 68 insertions(+), 41 deletions(-) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index f1b395725..090a80329 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -247,6 +247,7 @@ SOURCES_MLI = \ sysroot.mli \ devsparts.mli \ file.mli \ + is.mli \ mountable.mli \ utils.mli @@ -261,6 +262,7 @@ SOURCES_ML = \ blkid.ml \ devsparts.ml \ file.ml \ + is.ml \ callbacks.ml \ daemon.ml diff --git a/daemon/is.c b/daemon/is.c index 4d5e911c2..a91dab32b 100644 --- a/daemon/is.c +++ b/daemon/is.c @@ -39,36 +39,6 @@ do_exists (const char *path) /* Takes optional arguments, consult optargs_bitmask. */ int -do_is_file (const char *path, int followsymlinks) -{ - mode_t mode; - int r; - - if (!(optargs_bitmask & GUESTFS_IS_FILE_FOLLOWSYMLINKS_BITMASK)) - followsymlinks = 0; - - r = get_mode (path, &mode, followsymlinks); - if (r <= 0) return r; - return S_ISREG (mode); -} - -/* Takes optional arguments, consult optargs_bitmask. */ -int -do_is_dir (const char *path, int followsymlinks) -{ - mode_t mode; - int r; - - if (!(optargs_bitmask & GUESTFS_IS_DIR_FOLLOWSYMLINKS_BITMASK)) - followsymlinks = 0; - - r = get_mode (path, &mode, followsymlinks); - if (r <= 0) return r; - return S_ISDIR (mode); -} - -/* Takes optional arguments, consult optargs_bitmask. */ -int do_is_chardev (const char *path, int followsymlinks) { mode_t mode; @@ -112,17 +82,6 @@ do_is_fifo (const char *path, int followsymlinks) return S_ISFIFO (mode); } -int -do_is_symlink (const char *path) -{ - mode_t mode; - int r; - - r = get_mode (path, &mode, 0); - if (r <= 0) return r; - return S_ISLNK (mode); -} - /* Takes optional arguments, consult optargs_bitmask. */ int do_is_socket (const char *path, int followsymlinks) diff --git a/daemon/is.ml b/daemon/is.ml new file mode 100644 index 000000000..4929f48b3 --- /dev/null +++ b/daemon/is.ml @@ -0,0 +1,42 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +open Printf +open Unix + +let rec is_file ?(followsymlinks = false) path + is "is_file" S_REG followsymlinks path +and is_dir ?(followsymlinks = false) path + is "is_dir" S_DIR followsymlinks path +and is_symlink path + is "is_symlink" S_LNK false path + +and is func expected_kind followsymlinks path + let chroot = Chroot.create ~name:(sprintf "%s: %s" func path) () in + let kind + Chroot.f chroot ( + fun () -> + let statfun = if followsymlinks then stat else lstat in + try + let statbuf = statfun path in + Some statbuf.st_kind + with + Unix_error ((ENOENT|ENOTDIR), _, _) -> + None (* File doesn't exist => return None *) + ) () in + kind = Some expected_kind diff --git a/daemon/is.mli b/daemon/is.mli new file mode 100644 index 000000000..20622c39f --- /dev/null +++ b/daemon/is.mli @@ -0,0 +1,21 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +val is_file : ?followsymlinks:bool -> string -> bool +val is_dir : ?followsymlinks:bool -> string -> bool +val is_symlink : string -> bool diff --git a/generator/actions_core.ml b/generator/actions_core.ml index 94391288f..421f3ac6b 100644 --- a/generator/actions_core.ml +++ b/generator/actions_core.ml @@ -2114,6 +2114,7 @@ See also C<guestfs_is_file>, C<guestfs_is_dir>, C<guestfs_stat>." }; { defaults with name = "is_file"; added = (0, 0, 8); style = RBool "fileflag", [String (Pathname, "path")], [OBool "followsymlinks"]; + impl = OCaml "Is.is_file"; once_had_no_optargs = true; tests = [ InitISOFS, Always, TestResultTrue ( @@ -2138,6 +2139,7 @@ See also C<guestfs_stat>." }; { defaults with name = "is_dir"; added = (0, 0, 8); style = RBool "dirflag", [String (Pathname, "path")], [OBool "followsymlinks"]; + impl = OCaml "Is.is_dir"; once_had_no_optargs = true; tests = [ InitISOFS, Always, TestResultFalse ( @@ -6052,6 +6054,7 @@ See also C<guestfs_stat>." }; { defaults with name = "is_symlink"; added = (1, 5, 10); style = RBool "flag", [String (Pathname, "path")], []; + impl = OCaml "Is.is_symlink"; tests = [ InitISOFS, Always, TestResultFalse ( [["is_symlink"; "/directory"]]), []; -- 2.13.2
Richard W.M. Jones
2017-Jul-27 19:43 UTC
[Libguestfs] [PATCH v3 07/23] daemon: Reimplement ‘readlink’ API in OCaml.
--- daemon/Makefile.am | 2 ++ daemon/link.c | 16 ---------------- daemon/link.ml | 24 ++++++++++++++++++++++++ daemon/link.mli | 19 +++++++++++++++++++ generator/actions_core.ml | 1 + 5 files changed, 46 insertions(+), 16 deletions(-) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 090a80329..ea9a5fc8f 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -248,6 +248,7 @@ SOURCES_MLI = \ devsparts.mli \ file.mli \ is.mli \ + link.mli \ mountable.mli \ utils.mli @@ -263,6 +264,7 @@ SOURCES_ML = \ devsparts.ml \ file.ml \ is.ml \ + link.ml \ callbacks.ml \ daemon.ml diff --git a/daemon/link.c b/daemon/link.c index 0878fd32b..53f4719ed 100644 --- a/daemon/link.c +++ b/daemon/link.c @@ -30,22 +30,6 @@ #include "daemon.h" #include "actions.h" -char * -do_readlink (const char *path) -{ - char *link; - - CHROOT_IN; - link = areadlink (path); - CHROOT_OUT; - if (link == NULL) { - reply_with_perror ("%s", path); - return NULL; - } - - return link; /* caller frees */ -} - char ** do_internal_readlinklist (const char *path, char *const *names) { diff --git a/daemon/link.ml b/daemon/link.ml new file mode 100644 index 000000000..e80e24aa5 --- /dev/null +++ b/daemon/link.ml @@ -0,0 +1,24 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +open Printf +open Unix + +let readlink path + let chroot = Chroot.create ~name:(sprintf "readlink: %s" path) () in + Chroot.f chroot readlink path diff --git a/daemon/link.mli b/daemon/link.mli new file mode 100644 index 000000000..6ca0283b4 --- /dev/null +++ b/daemon/link.mli @@ -0,0 +1,19 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +val readlink : string -> string diff --git a/generator/actions_core.ml b/generator/actions_core.ml index 421f3ac6b..7d6755fdc 100644 --- a/generator/actions_core.ml +++ b/generator/actions_core.ml @@ -4489,6 +4489,7 @@ The I<-f> option removes the link (C<linkname>) if it exists already." }; { defaults with name = "readlink"; added = (1, 0, 66); style = RString (RPlainString, "link"), [String (Pathname, "path")], []; + impl = OCaml "Link.readlink"; shortdesc = "read the target of a symbolic link"; longdesc = "\ This command reads the target of a symbolic link." }; -- 2.13.2
Richard W.M. Jones
2017-Jul-27 19:43 UTC
[Libguestfs] [PATCH v3 08/23] daemon: Reimplement ‘mount’, ‘mount_ro’, ‘mount_options’, ‘mount_vfs’ APIs in OCaml.
Some of the oldest and most core APIs, reimplemented. This also moves the strange ‘mount_vfs_nochroot’ function into btrfs.c. --- daemon/Makefile.am | 2 + daemon/btrfs.c | 42 ++++++++++++++++++++ daemon/daemon.h | 3 -- daemon/mount.c | 99 ----------------------------------------------- daemon/mount.ml | 62 +++++++++++++++++++++++++++++ daemon/mount.mli | 22 +++++++++++ generator/actions_core.ml | 4 ++ 7 files changed, 132 insertions(+), 102 deletions(-) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index ea9a5fc8f..3b527e10f 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -249,6 +249,7 @@ SOURCES_MLI = \ file.mli \ is.mli \ link.mli \ + mount.mli \ mountable.mli \ utils.mli @@ -265,6 +266,7 @@ SOURCES_ML = \ file.ml \ is.ml \ link.ml \ + mount.ml \ callbacks.ml \ daemon.ml diff --git a/daemon/btrfs.c b/daemon/btrfs.c index 8fd327c09..00c9b3c75 100644 --- a/daemon/btrfs.c +++ b/daemon/btrfs.c @@ -380,6 +380,48 @@ do_btrfs_subvolume_create (const char *dest, const char *qgroupid) return 0; } +static int +mount_vfs_nochroot (const char *options, const char *vfstype, + const mountable_t *mountable, + const char *mp, const char *user_mp) +{ + CLEANUP_FREE char *options_plus = NULL; + const char *device = mountable->device; + if (mountable->type == MOUNTABLE_BTRFSVOL) { + if (options && strlen (options) > 0) { + if (asprintf (&options_plus, "subvol=%s,%s", + mountable->volume, options) == -1) { + reply_with_perror ("asprintf"); + return -1; + } + } + else { + if (asprintf (&options_plus, "subvol=%s", mountable->volume) == -1) { + reply_with_perror ("asprintf"); + return -1; + } + } + } + + CLEANUP_FREE char *error = NULL; + int r; + if (vfstype) + r = command (NULL, &error, + "mount", "-o", options_plus ? options_plus : options, + "-t", vfstype, device, mp, NULL); + else + r = command (NULL, &error, + "mount", "-o", options_plus ? options_plus : options, + device, mp, NULL); + if (r == -1) { + reply_with_error ("%s on %s (options: '%s'): %s", + device, user_mp, options, error); + return -1; + } + + return 0; +} + static char * mount (const mountable_t *fs) { diff --git a/daemon/daemon.h b/daemon/daemon.h index 4acc9c9fe..0f7ead258 100644 --- a/daemon/daemon.h +++ b/daemon/daemon.h @@ -109,9 +109,6 @@ extern void cleanup_free_stringsbuf (void *ptr); #endif /* mount.c */ -extern int mount_vfs_nochroot (const char *options, const char *vfstype, - const mountable_t *mountable, - const char *mp, const char *user_mp); extern int is_root_mounted (void); extern int is_device_mounted (const char *device); diff --git a/daemon/mount.c b/daemon/mount.c index f9dfaff70..bf58bd527 100644 --- a/daemon/mount.c +++ b/daemon/mount.c @@ -108,105 +108,6 @@ is_device_mounted (const char *device) return 0; } -/* The "simple mount" call offers no complex options, you can just - * mount a device on a mountpoint. The variations like mount_ro, - * mount_options and mount_vfs let you set progressively more things. - * - * It's tempting to try a direct mount(2) syscall, but that doesn't - * do any autodetection, so we are better off calling out to - * /bin/mount. - */ - -int -do_mount_vfs (const char *options, const char *vfstype, - const mountable_t *mountable, const char *mountpoint) -{ - CLEANUP_FREE char *mp = NULL; - struct stat statbuf; - - ABS_PATH (mountpoint, 0, return -1); - - mp = sysroot_path (mountpoint); - if (!mp) { - reply_with_perror ("malloc"); - return -1; - } - - /* Check the mountpoint exists and is a directory. */ - if (stat (mp, &statbuf) == -1) { - reply_with_perror ("mount: %s", mountpoint); - return -1; - } - if (!S_ISDIR (statbuf.st_mode)) { - reply_with_perror ("mount: %s: mount point is not a directory", mountpoint); - return -1; - } - - return mount_vfs_nochroot (options, vfstype, mountable, mp, mountpoint); -} - -int -mount_vfs_nochroot (const char *options, const char *vfstype, - const mountable_t *mountable, - const char *mp, const char *user_mp) -{ - CLEANUP_FREE char *options_plus = NULL; - const char *device = mountable->device; - if (mountable->type == MOUNTABLE_BTRFSVOL) { - if (options && strlen (options) > 0) { - if (asprintf (&options_plus, "subvol=%s,%s", - mountable->volume, options) == -1) { - reply_with_perror ("asprintf"); - return -1; - } - } - - else { - if (asprintf (&options_plus, "subvol=%s", mountable->volume) == -1) { - reply_with_perror ("asprintf"); - return -1; - } - } - } - - CLEANUP_FREE char *error = NULL; - int r; - if (vfstype) - r = command (NULL, &error, - "mount", "-o", options_plus ? options_plus : options, - "-t", vfstype, device, mp, NULL); - else - r = command (NULL, &error, - "mount", "-o", options_plus ? options_plus : options, - device, mp, NULL); - if (r == -1) { - reply_with_error ("%s on %s (options: '%s'): %s", - device, user_mp, options, error); - return -1; - } - - return 0; -} - -int -do_mount (const mountable_t *mountable, const char *mountpoint) -{ - return do_mount_vfs ("", NULL, mountable, mountpoint); -} - -int -do_mount_ro (const mountable_t *mountable, const char *mountpoint) -{ - return do_mount_vfs ("ro", NULL, mountable, mountpoint); -} - -int -do_mount_options (const char *options, const mountable_t *mountable, - const char *mountpoint) -{ - return do_mount_vfs (options, NULL, mountable, mountpoint); -} - /* Takes optional arguments, consult optargs_bitmask. */ int do_umount (const char *pathordevice, diff --git a/daemon/mount.ml b/daemon/mount.ml new file mode 100644 index 000000000..3391ffc11 --- /dev/null +++ b/daemon/mount.ml @@ -0,0 +1,62 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +open Std_utils + +open Mountable +open Utils + +let mount_vfs options vfs mountable mountpoint + let mp = Sysroot.sysroot_path mountpoint in + + (* Check the mountpoint exists and is a directory. *) + if not (is_directory mp) then + failwithf "mount: %s: mount point is not a directory" mountpoint; + + let args = ref [] in + + (* -o options *) + (match options, mountable.m_type with + | (None | Some ""), (MountableDevice | MountablePath) -> () + | Some options, (MountableDevice | MountablePath) -> + push_back args "-o"; + push_back args options + | (None | Some ""), MountableBtrfsVol subvol -> + push_back args "-o"; + push_back args ("subvol=" ^ subvol) + | Some options, MountableBtrfsVol subvol -> + push_back args "-o"; + push_back args ("subvol=" ^ subvol ^ "," ^ options) + ); + + (* -t vfs *) + (match vfs with + | None | Some "" -> () + | Some t -> + push_back args "-t"; + push_back args t + ); + + push_back args mountable.m_device; + push_back args mp; + + ignore (command "mount" !args) + +let mount = mount_vfs None None +let mount_ro = mount_vfs (Some "ro") None +let mount_options options = mount_vfs (Some options) None diff --git a/daemon/mount.mli b/daemon/mount.mli new file mode 100644 index 000000000..e43d97c42 --- /dev/null +++ b/daemon/mount.mli @@ -0,0 +1,22 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +val mount : Mountable.t -> string -> unit +val mount_ro : Mountable.t -> string -> unit +val mount_options : string -> Mountable.t -> string -> unit +val mount_vfs : string option -> string option -> Mountable.t -> string -> unit diff --git a/generator/actions_core.ml b/generator/actions_core.ml index 7d6755fdc..f33bc5320 100644 --- a/generator/actions_core.ml +++ b/generator/actions_core.ml @@ -1739,6 +1739,7 @@ let daemon_functions = [ { defaults with name = "mount"; added = (0, 0, 3); style = RErr, [String (Mountable, "mountable"); String (PlainString, "mountpoint")], []; + impl = OCaml "Mount.mount"; tests = [ InitEmpty, Always, TestResultString ( [["part_disk"; "/dev/sda"; "mbr"]; @@ -2922,6 +2923,7 @@ If set to true, POSIX ACLs are saved in the output tar. { defaults with name = "mount_ro"; added = (1, 0, 10); style = RErr, [String (Mountable, "mountable"); String (PlainString, "mountpoint")], []; + impl = OCaml "Mount.mount_ro"; tests = [ InitBasicFS, Always, TestLastFail ( [["umount"; "/"; "false"; "false"]; @@ -2941,6 +2943,7 @@ mounts the filesystem with the read-only (I<-o ro>) flag." }; { defaults with name = "mount_options"; added = (1, 0, 10); style = RErr, [String (PlainString, "options"); String (Mountable, "mountable"); String (PlainString, "mountpoint")], []; + impl = OCaml "Mount.mount_options"; shortdesc = "mount a guest disk with mount options"; longdesc = "\ This is the same as the C<guestfs_mount> command, but it @@ -2954,6 +2957,7 @@ the filesystem uses)." }; { defaults with name = "mount_vfs"; added = (1, 0, 10); style = RErr, [String (PlainString, "options"); String (PlainString, "vfstype"); String (Mountable, "mountable"); String (PlainString, "mountpoint")], []; + impl = OCaml "Mount.mount_vfs"; shortdesc = "mount a guest disk with mount options and vfstype"; longdesc = "\ This is the same as the C<guestfs_mount> command, but it -- 2.13.2
Richard W.M. Jones
2017-Jul-27 19:43 UTC
[Libguestfs] [PATCH v3 09/23] daemon: Reimplement ‘part_get_mbr_id’ API in OCaml.
--- daemon/Makefile.am | 2 ++ daemon/parted.c | 42 ------------------------------------ daemon/parted.ml | 55 +++++++++++++++++++++++++++++++++++++++++++++++ daemon/parted.mli | 19 ++++++++++++++++ generator/actions_core.ml | 1 + 5 files changed, 77 insertions(+), 42 deletions(-) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 3b527e10f..195b2e0a7 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -251,6 +251,7 @@ SOURCES_MLI = \ link.mli \ mount.mli \ mountable.mli \ + parted.mli \ utils.mli SOURCES_ML = \ @@ -267,6 +268,7 @@ SOURCES_ML = \ is.ml \ link.ml \ mount.ml \ + parted.ml \ callbacks.ml \ daemon.ml diff --git a/daemon/parted.c b/daemon/parted.c index 7446bc93e..b788ed72a 100644 --- a/daemon/parted.c +++ b/daemon/parted.c @@ -517,48 +517,6 @@ test_sfdisk_has_part_type (void) return tested; } -/* Currently we use sfdisk for getting and setting the ID byte. In - * future, extend parted to provide this functionality. As a result - * of using sfdisk, this won't work for non-MBR-style partitions, but - * that limitation is noted in the documentation and we can extend it - * later without breaking the ABI. - */ -int -do_part_get_mbr_id (const char *device, int partnum) -{ - if (partnum <= 0) { - reply_with_error ("partition number must be >= 1"); - return -1; - } - - const char *param = test_sfdisk_has_part_type () ? "--part-type" : "--print-id"; - - char partnum_str[16]; - snprintf (partnum_str, sizeof partnum_str, "%d", partnum); - - CLEANUP_FREE char *out = NULL, *err = NULL; - int r; - - udev_settle (); - - r = command (&out, &err, "sfdisk", param, device, partnum_str, NULL); - if (r == -1) { - reply_with_error ("sfdisk %s: %s", param, err); - return -1; - } - - udev_settle (); - - /* It's printed in hex ... */ - unsigned id; - if (sscanf (out, "%x", &id) != 1) { - reply_with_error ("sfdisk --print-id: cannot parse output: %s", out); - return -1; - } - - return id; -} - int do_part_set_mbr_id (const char *device, int partnum, int idbyte) { diff --git a/daemon/parted.ml b/daemon/parted.ml new file mode 100644 index 000000000..6be41cf66 --- /dev/null +++ b/daemon/parted.ml @@ -0,0 +1,55 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +open Scanf + +open Std_utils + +open Utils + +(* Test if [sfdisk] is recent enough to have [--part-type], to be used + * instead of [--print-id] and [--change-id]. + *) +let test_sfdisk_has_part_type = lazy ( + let out = command "sfdisk" ["--help"] in + String.find out "--part-type" >= 0 +) + +(* Currently we use sfdisk for getting and setting the ID byte. In + * future, extend parted to provide this functionality. As a result + * of using sfdisk, this won't work for non-MBR-style partitions, but + * that limitation is noted in the documentation and we can extend it + * later without breaking the ABI. + *) +let part_get_mbr_id device partnum + if partnum <= 0 then + failwith "partition number must be >= 1"; + + let param + if Lazy.force test_sfdisk_has_part_type then + "--part-type" + else + "--print-id" in + + udev_settle (); + let out + command "sfdisk" [param; device; string_of_int partnum] in + udev_settle (); + + (* It's printed in hex, possibly with a leading space. *) + sscanf out " %x" identity diff --git a/daemon/parted.mli b/daemon/parted.mli new file mode 100644 index 000000000..33eb6d30d --- /dev/null +++ b/daemon/parted.mli @@ -0,0 +1,19 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +val part_get_mbr_id : string -> int -> int diff --git a/generator/actions_core.ml b/generator/actions_core.ml index f33bc5320..4bf0c7b70 100644 --- a/generator/actions_core.ml +++ b/generator/actions_core.ml @@ -5513,6 +5513,7 @@ See also C<guestfs_part_set_bootable>." }; { defaults with name = "part_get_mbr_id"; added = (1, 3, 2); style = RInt "idbyte", [String (Device, "device"); Int "partnum"], []; + impl = OCaml "Parted.part_get_mbr_id"; fish_output = Some FishOutputHexadecimal; tests = [ InitEmpty, Always, TestResult ( -- 2.13.2
Richard W.M. Jones
2017-Jul-27 19:43 UTC
[Libguestfs] [PATCH v3 10/23] daemon: Reimplement ‘case_sensitive_path’ API in OCaml.
--- daemon/Makefile.am | 2 + daemon/realpath.c | 187 ---------------------------------------------- daemon/realpath.ml | 82 ++++++++++++++++++++ daemon/realpath.mli | 19 +++++ generator/actions_core.ml | 1 + 5 files changed, 104 insertions(+), 187 deletions(-) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 195b2e0a7..9927bb47c 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -252,6 +252,7 @@ SOURCES_MLI = \ mount.mli \ mountable.mli \ parted.mli \ + realpath.mli \ utils.mli SOURCES_ML = \ @@ -269,6 +270,7 @@ SOURCES_ML = \ link.ml \ mount.ml \ parted.ml \ + realpath.ml \ callbacks.ml \ daemon.ml diff --git a/daemon/realpath.c b/daemon/realpath.c index 24ab133e2..f9d22d28d 100644 --- a/daemon/realpath.c +++ b/daemon/realpath.c @@ -48,190 +48,3 @@ do_realpath (const char *path) return ret; /* caller frees */ } - -static int find_path_element (int fd_cwd, int is_end, const char *name, char **name_ret); - -char * -do_case_sensitive_path (const char *path) -{ - size_t next; - int fd_cwd, fd2, err, is_end; - char *ret; - - ret = strdup ("/"); - if (ret == NULL) { - reply_with_perror ("strdup"); - return NULL; - } - next = 1; /* next position in 'ret' buffer */ - - /* 'fd_cwd' here is a surrogate for the current working directory, so - * that we don't have to actually call chdir(2). - */ - fd_cwd = open (sysroot, O_RDONLY|O_DIRECTORY|O_CLOEXEC); - if (fd_cwd == -1) { - reply_with_perror ("%s", sysroot); - goto error; - } - - /* First character is a '/'. Take each subsequent path element - * and follow it. - */ - while (*path) { - char *t; - size_t i, len; - CLEANUP_FREE char *name_in = NULL, *name_out = NULL; - - i = strcspn (path, "/"); - if (i == 0) { - path++; - continue; - } - - if ((i == 1 && path[0] == '.') || - (i == 2 && path[0] == '.' && path[1] == '.')) { - reply_with_error ("path contained . or .. elements"); - goto error; - } - - name_in = strndup (path, i); - if (name_in == NULL) { - reply_with_perror ("strdup"); - goto error; - } - - /* Skip to next element in path (for the next loop iteration). */ - path += i; - is_end = *path == 0; - - /* Read the current directory looking (case insensitively) for - * this element of the path. This replaces 'name' with the - * correct case version. - */ - if (find_path_element (fd_cwd, is_end, name_in, &name_out) == -1) - goto error; - len = strlen (name_out); - - /* Add the real name of this path element to the return value. */ - if (next > 1) - ret[next++] = '/'; - - t = realloc (ret, next+len+1); - if (t == NULL) { - reply_with_perror ("realloc"); - goto error; - } - ret = t; - - strcpy (&ret[next], name_out); - next += len; - - /* Is it a directory? Try going into it. */ - fd2 = openat (fd_cwd, name_out, O_RDONLY|O_DIRECTORY|O_CLOEXEC); - err = errno; - close (fd_cwd); - fd_cwd = fd2; - errno = err; - if (fd_cwd == -1) { - /* Some errors are OK provided we've reached the end of the path. */ - if (is_end && (errno == ENOTDIR || errno == ENOENT)) - break; - - reply_with_perror ("openat: %s", name_out); - goto error; - } - } - - if (fd_cwd >= 0) - close (fd_cwd); - - return ret; /* caller frees */ - - error: - if (fd_cwd >= 0) - close (fd_cwd); - free (ret); - - return NULL; -} - -/* 'fd_cwd' is a file descriptor pointing to an open directory. - * 'name' is the path element to search for. 'is_end' is a flag - * indicating if this is the last path element. - * - * We search the directory looking for a path element that case - * insensitively matches 'name', returning the actual name in '*name_ret'. - * - * If this is successful, return 0. If it fails, reply with an error - * and return -1. - */ -static int -find_path_element (int fd_cwd, int is_end, const char *name, char **name_ret) -{ - int fd2; - DIR *dir; - struct dirent *d; - - fd2 = dup_cloexec (fd_cwd); /* because closedir will close it */ - if (fd2 == -1) { - reply_with_perror ("dup"); - return -1; - } - dir = fdopendir (fd2); - if (dir == NULL) { - reply_with_perror ("opendir"); - close (fd2); - return -1; - } - - for (;;) { - errno = 0; - d = readdir (dir); - if (d == NULL) - break; - if (STRCASEEQ (d->d_name, name)) - break; - } - - if (d == NULL && errno != 0) { - reply_with_perror ("readdir"); - closedir (dir); - return -1; - } - - if (d == NULL && is_end) { - /* Last path element: return it as-is, assuming that the user will - * create a new file or directory (RHBZ#840115). - */ - closedir (dir); - *name_ret = strdup (name); - if (*name_ret == NULL) { - reply_with_perror ("strdup"); - return -1; - } - return 0; - } - - if (d == NULL) { - reply_with_error ("%s: no file or directory found with this name", name); - closedir (dir); - return -1; - } - - *name_ret = strdup (d->d_name); - if (*name_ret == NULL) { - reply_with_perror ("strdup"); - closedir (dir); - return -1; - } - - /* NB: closedir frees the structure associated with 'd', so we must - * do this last. - */ - if (closedir (dir) == -1) { - reply_with_perror ("closedir"); - return -1; - } - - return 0; -} diff --git a/daemon/realpath.ml b/daemon/realpath.ml new file mode 100644 index 000000000..e499786a1 --- /dev/null +++ b/daemon/realpath.ml @@ -0,0 +1,82 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +open Printf + +open Std_utils + +(* The infamous case_sensitive_path function, which works around + * the bug in ntfs-3g that all paths are case sensitive even though + * the underlying filesystem is case insensitive. + *) +let rec case_sensitive_path path + let elems = String.nsplit "/" path in + + (* The caller ensures that the first element of [path] is [/], + * and therefore the first element of the split list must be + * empty. + *) + assert (List.length elems > 0); + assert (List.hd elems = ""); + let elems = List.tl elems in + + let chroot + Chroot.create ~name:(sprintf "case_sensitive_path: %s" path) () in + + (* Now we iterate down the tree starting at the sysroot. *) + let elems + Chroot.f chroot ( + fun () -> + let rec loop = function + | [] -> [] + | [ "."|".." ] -> + failwithf "path contains \".\" or \"..\" elements" + | "" :: elems -> + (* For compatibility with C implementation, we ignore + * "//" in the middle of the path. + *) + loop elems + | [ file ] -> + (* If it's the final element, it's allowed to be missing. *) + (match find_path_element file with + | None -> [ file ] (* return the original *) + | Some file -> [ file ] + ); + | elem :: elems -> + (match find_path_element elem with + | None -> + failwithf "%s: not found" elem + | Some elem -> + (* This will fail intentionally if not a directory. *) + Unix.chdir elem; + elem :: loop elems + ) + in + loop elems + ) () in + + (* Reconstruct the case sensitive path. *) + "/" ^ String.concat "/" elems + +and find_path_element name + let dir = Sys.readdir "." in + let dir = Array.to_list dir in + let lc_name = String.lowercase_ascii name in + let cmp n = String.lowercase_ascii n = lc_name in + try Some (List.find cmp dir) + with Not_found -> None diff --git a/daemon/realpath.mli b/daemon/realpath.mli new file mode 100644 index 000000000..371e619fc --- /dev/null +++ b/daemon/realpath.mli @@ -0,0 +1,19 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +val case_sensitive_path : string -> string diff --git a/generator/actions_core.ml b/generator/actions_core.ml index 4bf0c7b70..54d0a6ca8 100644 --- a/generator/actions_core.ml +++ b/generator/actions_core.ml @@ -4797,6 +4797,7 @@ The result list is not sorted. { defaults with name = "case_sensitive_path"; added = (1, 0, 75); style = RString (RPlainString, "rpath"), [String (Pathname, "path")], []; + impl = OCaml "Realpath.case_sensitive_path"; tests = [ InitISOFS, Always, TestResultString ( [["case_sensitive_path"; "/DIRECTORY"]], "/directory"), []; -- 2.13.2
Richard W.M. Jones
2017-Jul-27 19:43 UTC
[Libguestfs] [PATCH v3 11/23] daemon: Reimplement ‘file_architecture’ API in OCaml.
The previously library-side ‘file_architecture’ API is reimplemented in the daemon, in OCaml. There are some significant differences compared to the C implementation: - The C code used libmagic. That is replaced by calling the ‘file’ command (because that is simpler than using the library). - The C code had extra cases to deal with compressed files. This is not necessary since the ‘file’ command supports the ‘-z’ option which transparently looks inside compressed content (this is a consequence of the change above). This commit demonstrates a number of techniques which will be useful for moving inspection code to the daemon: - Moving an API from the C library to the OCaml daemon. - Calling from one OCaml API inside the daemon to another (from ‘Filearch.file_architecture’ to ‘File.file’). This can be done and is done with C daemon APIs but correct reply_with_error handling is more difficult in C. - Use of Str for regular expression matching within the appliance. --- daemon/Makefile.am | 2 + daemon/filearch.ml | 140 +++++++++++++++++ daemon/filearch.mli | 19 +++ docs/C_SOURCE_FILES | 1 - generator/actions_core.ml | 377 +++++++++++++++++++++++----------------------- generator/proc_nr.ml | 1 + lib/MAX_PROC_NR | 2 +- lib/Makefile.am | 3 +- lib/filearch.c | 362 -------------------------------------------- po/POTFILES | 1 - 10 files changed, 353 insertions(+), 555 deletions(-) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 9927bb47c..31cb01b85 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -247,6 +247,7 @@ SOURCES_MLI = \ sysroot.mli \ devsparts.mli \ file.mli \ + filearch.mli \ is.mli \ link.mli \ mount.mli \ @@ -266,6 +267,7 @@ SOURCES_ML = \ blkid.ml \ devsparts.ml \ file.ml \ + filearch.ml \ is.ml \ link.ml \ mount.ml \ diff --git a/daemon/filearch.ml b/daemon/filearch.ml new file mode 100644 index 000000000..505d8c78e --- /dev/null +++ b/daemon/filearch.ml @@ -0,0 +1,140 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +open Unix +open Printf + +open Std_utils +open Unix_utils + +open Utils + +let re_file_elf + Str.regexp ".*ELF \\([0-9]+\\)-bit \\(MSB\\|LSB\\).*\\(executable\\|shared object\\|relocatable\\), \\([^,]+\\)," + +let re_file_elf_ppc64 = Str.regexp ".*64.*PowerPC" + +let initrd_binaries = [ + "bin/ls"; + "bin/rm"; + "bin/modprobe"; + "sbin/modprobe"; + "bin/sh"; + "bin/bash"; + "bin/dash"; + "bin/nash"; +] + +let rec file_architecture orig_path + (* Get the output of the "file" command. Note that because this + * is running in the daemon, LANG=C so it's in English. + *) + let magic = File.file orig_path in + file_architecture_of_magic magic orig_path orig_path + +and file_architecture_of_magic magic orig_path path + if Str.string_match re_file_elf magic 0 then ( + let bits = Str.matched_group 1 magic in + let endianness = Str.matched_group 2 magic in + let elf_arch = Str.matched_group 4 magic in + canonical_elf_arch bits endianness elf_arch + ) + else if String.find magic "PE32 executable" >= 0 then + "i386" + else if String.find magic "PE32+ executable" >= 0 then + "x86_64" + else if String.find magic "cpio archive" >= 0 then + cpio_arch magic orig_path path + else + failwithf "unknown architecture: %s" path + +(* Convert output from 'file' command on ELF files to the canonical + * architecture string. + *) +and canonical_elf_arch bits endianness elf_arch + let substr s = String.find elf_arch s >= 0 in + if substr "Intel 80386" || substr "Intel 80486" then + "i386" + else if substr "x86-64" || substr "AMD x86-64" then + "x86_64" + else if substr "SPARC32" then + "sparc" + else if substr "SPARC V9" then + "sparc64" + else if substr "IA-64" then + "ia64" + else if Str.string_match re_file_elf_ppc64 elf_arch 0 then ( + match endianness with + | "MSB" -> "ppc64" + | "LSB" -> "ppc64le" + | _ -> failwithf "unknown endianness '%s'" endianness + ) + else if substr "PowerPC" then + "ppc" + else if substr "ARM aarch64" then + "aarch64" + else if substr "ARM" then + "arm" + else if substr "UCB RISC-V" then + sprintf "riscv%s" bits + else if substr "IBM S/390" then ( + match bits with + | "32" -> "s390" + | "64" -> "s390x" + | _ -> failwithf "unknown S/390 bit size: %s" bits + ) + else + elf_arch + +and cpio_arch magic orig_path path + let zcat + if String.find magic "gzip" >= 0 then "zcat" + else if String.find magic "bzip2" >= 0 then "bzcat" + else if String.find magic "XZ compressed" >= 0 then "xzcat" + else "cat" in + + let tmpdir = Mkdtemp.temp_dir "filearch" in + let finally () = ignore (Sys.command (sprintf "rm -rf %s" (quote tmpdir))) in + + protect ~finally ~f:( + fun () -> + (* Construct a command to extract named binaries from the initrd file. *) + let cmd + sprintf "cd %s && %s %s | cpio --quiet -id %s" + tmpdir zcat (quote (Sysroot.sysroot_path path)) + (String.concat " " (List.map quote initrd_binaries)) in + if verbose () then eprintf "%s\n%!" cmd; + if Sys.command cmd <> 0 then + failwith "cpio command failed"; + + (* See if any of the binaries were present in the output. *) + let rec loop = function + | bin :: bins -> + let bin_path = tmpdir // bin in + if is_regular_file bin_path then ( + let out = command "file" ["-zb"; bin_path] in + file_architecture_of_magic out orig_path bin_path + ) + else + loop bins + | [] -> + failwithf "could not determine architecture of cpio archive: %s" + path + in + loop initrd_binaries + ) diff --git a/daemon/filearch.mli b/daemon/filearch.mli new file mode 100644 index 000000000..c4630225b --- /dev/null +++ b/daemon/filearch.mli @@ -0,0 +1,19 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +val file_architecture : string -> string diff --git a/docs/C_SOURCE_FILES b/docs/C_SOURCE_FILES index 5f76499f7..7ac6716cd 100644 --- a/docs/C_SOURCE_FILES +++ b/docs/C_SOURCE_FILES @@ -300,7 +300,6 @@ lib/errors.c lib/event-string.c lib/events.c lib/file.c -lib/filearch.c lib/fuse.c lib/guestfs-internal-actions.h lib/guestfs-internal-all.h diff --git a/generator/actions_core.ml b/generator/actions_core.ml index 54d0a6ca8..bfd96589e 100644 --- a/generator/actions_core.ml +++ b/generator/actions_core.ml @@ -183,194 +183,6 @@ making this an unreliable way to test for features. Use C<guestfs_available> or C<guestfs_feature_available> instead." }; { defaults with - name = "file_architecture"; added = (1, 5, 3); - style = RString (RPlainString, "arch"), [String (Pathname, "filename")], []; - tests = [ - InitISOFS, Always, TestResultString ( - [["file_architecture"; "/bin-aarch64-dynamic"]], "aarch64"), []; - InitISOFS, Always, TestResultString ( - [["file_architecture"; "/bin-armv7-dynamic"]], "arm"), []; - InitISOFS, Always, TestResultString ( - [["file_architecture"; "/bin-i586-dynamic"]], "i386"), []; - InitISOFS, Always, TestResultString ( - [["file_architecture"; "/bin-ppc64-dynamic"]], "ppc64"), []; - InitISOFS, Always, TestResultString ( - [["file_architecture"; "/bin-ppc64le-dynamic"]], "ppc64le"), []; - InitISOFS, Always, TestResultString ( - [["file_architecture"; "/bin-riscv64-dynamic"]], "riscv64"), []; - InitISOFS, Always, TestResultString ( - [["file_architecture"; "/bin-s390x-dynamic"]], "s390x"), []; - InitISOFS, Always, TestResultString ( - [["file_architecture"; "/bin-sparc-dynamic"]], "sparc"), []; - InitISOFS, Always, TestResultString ( - [["file_architecture"; "/bin-win32.exe"]], "i386"), []; - InitISOFS, Always, TestResultString ( - [["file_architecture"; "/bin-win64.exe"]], "x86_64"), []; - InitISOFS, Always, TestResultString ( - [["file_architecture"; "/bin-x86_64-dynamic"]], "x86_64"), []; - InitISOFS, Always, TestResultString ( - [["file_architecture"; "/lib-aarch64.so"]], "aarch64"), []; - InitISOFS, Always, TestResultString ( - [["file_architecture"; "/lib-armv7.so"]], "arm"), []; - InitISOFS, Always, TestResultString ( - [["file_architecture"; "/lib-i586.so"]], "i386"), []; - InitISOFS, Always, TestResultString ( - [["file_architecture"; "/lib-ppc64.so"]], "ppc64"), []; - InitISOFS, Always, TestResultString ( - [["file_architecture"; "/lib-ppc64le.so"]], "ppc64le"), []; - InitISOFS, Always, TestResultString ( - [["file_architecture"; "/lib-riscv64.so"]], "riscv64"), []; - InitISOFS, Always, TestResultString ( - [["file_architecture"; "/lib-s390x.so"]], "s390x"), []; - InitISOFS, Always, TestResultString ( - [["file_architecture"; "/lib-sparc.so"]], "sparc"), []; - InitISOFS, Always, TestResultString ( - [["file_architecture"; "/lib-win32.dll"]], "i386"), []; - InitISOFS, Always, TestResultString ( - [["file_architecture"; "/lib-win64.dll"]], "x86_64"), []; - InitISOFS, Always, TestResultString ( - [["file_architecture"; "/lib-x86_64.so"]], "x86_64"), []; - InitISOFS, Always, TestResultString ( - [["file_architecture"; "/initrd-x86_64.img"]], "x86_64"), []; - InitISOFS, Always, TestResultString ( - [["file_architecture"; "/initrd-x86_64.img.gz"]], "x86_64"), []; - InitISOFS, Always, TestResultString ( - [["file_architecture"; "/bin-x86_64-dynamic.gz"]], "x86_64"), []; - InitISOFS, Always, TestResultString ( - [["file_architecture"; "/lib-i586.so.xz"]], "i386"), []; - ]; - shortdesc = "detect the architecture of a binary file"; - longdesc = "\ -This detects the architecture of the binary F<filename>, -and returns it if known. - -Currently defined architectures are: - -=over 4 - -=item \"aarch64\" - -64 bit ARM. - -=item \"arm\" - -32 bit ARM. - -=item \"i386\" - -This string is returned for all 32 bit i386, i486, i586, i686 binaries -irrespective of the precise processor requirements of the binary. - -=item \"ia64\" - -Intel Itanium. - -=item \"ppc\" - -32 bit Power PC. - -=item \"ppc64\" - -64 bit Power PC (big endian). - -=item \"ppc64le\" - -64 bit Power PC (little endian). - -=item \"riscv32\" - -=item \"riscv64\" - -=item \"riscv128\" - -RISC-V 32-, 64- or 128-bit variants. - -=item \"s390\" - -31 bit IBM S/390. - -=item \"s390x\" - -64 bit IBM S/390. - -=item \"sparc\" - -32 bit SPARC. - -=item \"sparc64\" - -64 bit SPARC V9 and above. - -=item \"x86_64\" - -64 bit x86-64. - -=back - -Libguestfs may return other architecture strings in future. - -The function works on at least the following types of files: - -=over 4 - -=item * - -many types of Un*x and Linux binary - -=item * - -many types of Un*x and Linux shared library - -=item * - -Windows Win32 and Win64 binaries - -=item * - -Windows Win32 and Win64 DLLs - -Win32 binaries and DLLs return C<i386>. - -Win64 binaries and DLLs return C<x86_64>. - -=item * - -Linux kernel modules - -=item * - -Linux new-style initrd images - -=item * - -some non-x86 Linux vmlinuz kernels - -=back - -What it can't do currently: - -=over 4 - -=item * - -static libraries (libfoo.a) - -=item * - -Linux old-style initrd as compressed ext2 filesystem (RHEL 3) - -=item * - -x86 Linux vmlinuz kernels - -x86 vmlinuz images (bzImage format) consist of a mix of 16-, 32- and -compressed code, and are horribly hard to unpack. If you want to find -the architecture of a kernel, use the architecture of the associated -initrd or kernel module(s) instead. - -=back" }; - - { defaults with name = "mountable_device"; added = (1, 33, 15); style = RString (RDevice, "device"), [String (Mountable, "mountable")], []; shortdesc = "extract the device part of a mountable"; @@ -9628,4 +9440,193 @@ wildcards. Please note that this API may fail when used to compress directories with large files, such as the resulting squashfs will be over 3GB big." }; + { defaults with + name = "file_architecture"; added = (1, 5, 3); + style = RString (RPlainString, "arch"), [String (Pathname, "filename")], []; + impl = OCaml "Filearch.file_architecture"; + tests = [ + InitISOFS, Always, TestResultString ( + [["file_architecture"; "/bin-aarch64-dynamic"]], "aarch64"), []; + InitISOFS, Always, TestResultString ( + [["file_architecture"; "/bin-armv7-dynamic"]], "arm"), []; + InitISOFS, Always, TestResultString ( + [["file_architecture"; "/bin-i586-dynamic"]], "i386"), []; + InitISOFS, Always, TestResultString ( + [["file_architecture"; "/bin-ppc64-dynamic"]], "ppc64"), []; + InitISOFS, Always, TestResultString ( + [["file_architecture"; "/bin-ppc64le-dynamic"]], "ppc64le"), []; + InitISOFS, Always, TestResultString ( + [["file_architecture"; "/bin-riscv64-dynamic"]], "riscv64"), []; + InitISOFS, Always, TestResultString ( + [["file_architecture"; "/bin-s390x-dynamic"]], "s390x"), []; + InitISOFS, Always, TestResultString ( + [["file_architecture"; "/bin-sparc-dynamic"]], "sparc"), []; + InitISOFS, Always, TestResultString ( + [["file_architecture"; "/bin-win32.exe"]], "i386"), []; + InitISOFS, Always, TestResultString ( + [["file_architecture"; "/bin-win64.exe"]], "x86_64"), []; + InitISOFS, Always, TestResultString ( + [["file_architecture"; "/bin-x86_64-dynamic"]], "x86_64"), []; + InitISOFS, Always, TestResultString ( + [["file_architecture"; "/lib-aarch64.so"]], "aarch64"), []; + InitISOFS, Always, TestResultString ( + [["file_architecture"; "/lib-armv7.so"]], "arm"), []; + InitISOFS, Always, TestResultString ( + [["file_architecture"; "/lib-i586.so"]], "i386"), []; + InitISOFS, Always, TestResultString ( + [["file_architecture"; "/lib-ppc64.so"]], "ppc64"), []; + InitISOFS, Always, TestResultString ( + [["file_architecture"; "/lib-ppc64le.so"]], "ppc64le"), []; + InitISOFS, Always, TestResultString ( + [["file_architecture"; "/lib-riscv64.so"]], "riscv64"), []; + InitISOFS, Always, TestResultString ( + [["file_architecture"; "/lib-s390x.so"]], "s390x"), []; + InitISOFS, Always, TestResultString ( + [["file_architecture"; "/lib-sparc.so"]], "sparc"), []; + InitISOFS, Always, TestResultString ( + [["file_architecture"; "/lib-win32.dll"]], "i386"), []; + InitISOFS, Always, TestResultString ( + [["file_architecture"; "/lib-win64.dll"]], "x86_64"), []; + InitISOFS, Always, TestResultString ( + [["file_architecture"; "/lib-x86_64.so"]], "x86_64"), []; + InitISOFS, Always, TestResultString ( + [["file_architecture"; "/initrd-x86_64.img"]], "x86_64"), []; + InitISOFS, Always, TestResultString ( + [["file_architecture"; "/initrd-x86_64.img.gz"]], "x86_64"), []; + InitISOFS, Always, TestResultString ( + [["file_architecture"; "/bin-x86_64-dynamic.gz"]], "x86_64"), []; + InitISOFS, Always, TestResultString ( + [["file_architecture"; "/lib-i586.so.xz"]], "i386"), []; + ]; + shortdesc = "detect the architecture of a binary file"; + longdesc = "\ +This detects the architecture of the binary F<filename>, +and returns it if known. + +Currently defined architectures are: + +=over 4 + +=item \"aarch64\" + +64 bit ARM. + +=item \"arm\" + +32 bit ARM. + +=item \"i386\" + +This string is returned for all 32 bit i386, i486, i586, i686 binaries +irrespective of the precise processor requirements of the binary. + +=item \"ia64\" + +Intel Itanium. + +=item \"ppc\" + +32 bit Power PC. + +=item \"ppc64\" + +64 bit Power PC (big endian). + +=item \"ppc64le\" + +64 bit Power PC (little endian). + +=item \"riscv32\" + +=item \"riscv64\" + +=item \"riscv128\" + +RISC-V 32-, 64- or 128-bit variants. + +=item \"s390\" + +31 bit IBM S/390. + +=item \"s390x\" + +64 bit IBM S/390. + +=item \"sparc\" + +32 bit SPARC. + +=item \"sparc64\" + +64 bit SPARC V9 and above. + +=item \"x86_64\" + +64 bit x86-64. + +=back + +Libguestfs may return other architecture strings in future. + +The function works on at least the following types of files: + +=over 4 + +=item * + +many types of Un*x and Linux binary + +=item * + +many types of Un*x and Linux shared library + +=item * + +Windows Win32 and Win64 binaries + +=item * + +Windows Win32 and Win64 DLLs + +Win32 binaries and DLLs return C<i386>. + +Win64 binaries and DLLs return C<x86_64>. + +=item * + +Linux kernel modules + +=item * + +Linux new-style initrd images + +=item * + +some non-x86 Linux vmlinuz kernels + +=back + +What it can't do currently: + +=over 4 + +=item * + +static libraries (libfoo.a) + +=item * + +Linux old-style initrd as compressed ext2 filesystem (RHEL 3) + +=item * + +x86 Linux vmlinuz kernels + +x86 vmlinuz images (bzImage format) consist of a mix of 16-, 32- and +compressed code, and are horribly hard to unpack. If you want to find +the architecture of a kernel, use the architecture of the associated +initrd or kernel module(s) instead. + +=back" }; + ] diff --git a/generator/proc_nr.ml b/generator/proc_nr.ml index c7619638a..1b0feae87 100644 --- a/generator/proc_nr.ml +++ b/generator/proc_nr.ml @@ -482,6 +482,7 @@ let proc_nr = [ 472, "yara_load"; 473, "yara_destroy"; 474, "internal_yara_scan"; +475, "file_architecture"; ] (* End of list. If adding a new entry, add it at the end of the list diff --git a/lib/MAX_PROC_NR b/lib/MAX_PROC_NR index 5f3bb9813..7573eff88 100644 --- a/lib/MAX_PROC_NR +++ b/lib/MAX_PROC_NR @@ -1 +1 @@ -474 +475 diff --git a/lib/Makefile.am b/lib/Makefile.am index 5a1443b62..fab8c4a45 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -89,7 +89,6 @@ libguestfs_la_SOURCES = \ event-string.c \ events.c \ file.c \ - filearch.c \ fuse.c \ guid.c \ handle.c \ @@ -159,7 +158,7 @@ libguestfs_la_LIBADD = \ ../common/qemuopts/libqemuopts.la \ ../common/structs/libstructs.la \ ../common/utils/libutils.la \ - $(PCRE_LIBS) $(MAGIC_LIBS) \ + $(PCRE_LIBS) \ $(LIBVIRT_LIBS) $(LIBXML2_LIBS) \ $(SELINUX_LIBS) \ $(YAJL_LIBS) \ diff --git a/lib/filearch.c b/lib/filearch.c deleted file mode 100644 index e1d3daeef..000000000 --- a/lib/filearch.c +++ /dev/null @@ -1,362 +0,0 @@ -/* libguestfs - * Copyright (C) 2010 Red Hat Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include <config.h> - -#include <stdio.h> -#include <stdlib.h> -#include <inttypes.h> -#include <unistd.h> -#include <string.h> -#include <sys/stat.h> -#include <sys/wait.h> -#include <libintl.h> - -#include <magic.h> - -#include "ignore-value.h" - -#include "guestfs.h" -#include "guestfs-internal.h" -#include "guestfs-internal-actions.h" - -# ifdef HAVE_ATTRIBUTE_CLEANUP -# define CLEANUP_MAGIC_T_FREE __attribute__((cleanup(cleanup_magic_t_free))) - -static void -cleanup_magic_t_free (void *ptr) -{ - magic_t m = *(magic_t *) ptr; - - if (m) - magic_close (m); -} - -# else -# define CLEANUP_MAGIC_T_FREE -# endif - -COMPILE_REGEXP (re_file_elf, - "ELF (\\d+)-bit (MSB|LSB).*(?:executable|shared object|relocatable), (.+?),", 0) -COMPILE_REGEXP (re_elf_ppc64, ".*64.*PowerPC", 0) - -/* Convert output from 'file' command on ELF files to the canonical - * architecture string. Caller must free the result. - */ -static char * -canonical_elf_arch (guestfs_h *g, - const char *bits, const char *endianness, - const char *elf_arch) -{ - const char *r; - char *ret; - - if (strstr (elf_arch, "Intel 80386") || - strstr (elf_arch, "Intel 80486")) - r = "i386"; - else if (strstr (elf_arch, "x86-64") || - strstr (elf_arch, "AMD x86-64")) - r = "x86_64"; - else if (strstr (elf_arch, "SPARC32")) - r = "sparc"; - else if (strstr (elf_arch, "SPARC V9")) - r = "sparc64"; - else if (strstr (elf_arch, "IA-64")) - r = "ia64"; - else if (match (g, elf_arch, re_elf_ppc64)) { - if (strstr (endianness, "MSB")) - r = "ppc64"; - else if (strstr (endianness, "LSB")) - r = "ppc64le"; - else { - error (g, "file_architecture: unknown endianness '%s'", endianness); - return NULL; - } - } - else if (strstr (elf_arch, "PowerPC")) - r = "ppc"; - else if (strstr (elf_arch, "ARM aarch64")) - r = "aarch64"; - else if (strstr (elf_arch, "ARM")) - r = "arm"; - else if (strstr (elf_arch, "UCB RISC-V")) { - ret = safe_asprintf (g, "riscv%s", bits); - goto no_strdup; - } - else if (strstr (elf_arch, "IBM S/390")) { - if (STREQ (bits, "32")) - r = "s390"; - else if (STREQ (bits, "64")) - r = "s390x"; - else { - error (g, "file_architecture: unknown S/390 bit size: %s", bits); - return NULL; - } - } - else - r = elf_arch; - - ret = safe_strdup (g, r); - no_strdup: - return ret; -} - -static int -is_regular_file (const char *filename) -{ - struct stat statbuf; - - return lstat (filename, &statbuf) == 0 && S_ISREG (statbuf.st_mode); -} - -static char * -magic_for_file (guestfs_h *g, const char *filename, bool *loading_ok, - bool *matched) -{ - int flags; - CLEANUP_MAGIC_T_FREE magic_t m = NULL; - const char *line; - CLEANUP_FREE char *bits = NULL; - CLEANUP_FREE char *elf_arch = NULL; - CLEANUP_FREE char *endianness = NULL; - - flags = g->verbose ? MAGIC_DEBUG : 0; - flags |= MAGIC_ERROR | MAGIC_RAW; - - if (loading_ok) - *loading_ok = false; - if (matched) - *matched = false; - - m = magic_open (flags); - if (m == NULL) { - perrorf (g, "magic_open"); - return NULL; - } - - if (magic_load (m, NULL) == -1) { - perrorf (g, "magic_load: default magic database file"); - return NULL; - } - - line = magic_file (m, filename); - if (line == NULL) { - perrorf (g, "magic_file: %s", filename); - return NULL; - } - - if (loading_ok) - *loading_ok = true; - - if (!match3 (g, line, re_file_elf, &bits, &endianness, &elf_arch)) { - error (g, "no re_file_elf match in '%s'", line); - return NULL; - } - - if (matched) - *matched = true; - - return canonical_elf_arch (g, bits, endianness, elf_arch); -} - -/* Download and uncompress the cpio file to find binaries within. */ -static const char *initrd_binaries[] = { - "bin/ls", - "bin/rm", - "bin/modprobe", - "sbin/modprobe", - "bin/sh", - "bin/bash", - "bin/dash", - "bin/nash", - NULL -}; - -static char * -cpio_arch (guestfs_h *g, const char *file, const char *path) -{ - CLEANUP_FREE char *tmpdir = guestfs_get_tmpdir (g), *dir = NULL; - CLEANUP_FREE char *initrd = NULL; - CLEANUP_CMD_CLOSE struct command *cmd = guestfs_int_new_command (g); - char *ret = NULL; - const char *method; - int64_t size; - int r; - size_t i; - - if (asprintf (&dir, "%s/libguestfsXXXXXX", tmpdir) == -1) { - perrorf (g, "asprintf"); - return NULL; - } - - if (strstr (file, "gzip")) - method = "zcat"; - else if (strstr (file, "bzip2")) - method = "bzcat"; - else - method = "cat"; - - /* Security: Refuse to download initrd if it is huge. */ - size = guestfs_filesize (g, path); - if (size == -1 || size > 100000000) { - error (g, _("size of %s unreasonable (%" PRIi64 " bytes)"), - path, size); - goto out; - } - - if (mkdtemp (dir) == NULL) { - perrorf (g, "mkdtemp"); - goto out; - } - - initrd = safe_asprintf (g, "%s/initrd", dir); - if (guestfs_download (g, path, initrd) == -1) - goto out; - - /* Construct a command to extract named binaries from the initrd file. */ - guestfs_int_cmd_add_string_unquoted (cmd, "cd "); - guestfs_int_cmd_add_string_quoted (cmd, dir); - guestfs_int_cmd_add_string_unquoted (cmd, " && "); - guestfs_int_cmd_add_string_unquoted (cmd, method); - guestfs_int_cmd_add_string_unquoted (cmd, " initrd | cpio --quiet -id"); - for (i = 0; initrd_binaries[i] != NULL; ++i) { - guestfs_int_cmd_add_string_unquoted (cmd, " "); - guestfs_int_cmd_add_string_quoted (cmd, initrd_binaries[i]); - } - - r = guestfs_int_cmd_run (cmd); - if (r == -1) - goto out; - if (!WIFEXITED (r) || WEXITSTATUS (r) != 0) { - guestfs_int_external_command_failed (g, r, "cpio", path); - goto out; - } - - for (i = 0; initrd_binaries[i] != NULL; ++i) { - CLEANUP_FREE char *bin - safe_asprintf (g, "%s/%s", dir, initrd_binaries[i]); - - if (is_regular_file (bin)) { - bool loading_ok, matched; - - ret = magic_for_file (g, bin, &loading_ok, &matched); - if (!loading_ok || matched) - goto out; - } - } - error (g, "file_architecture: could not determine architecture of cpio archive"); - - out: - guestfs_int_recursive_remove_dir (g, dir); - - return ret; -} - -static char * -compressed_file_arch (guestfs_h *g, const char *path, const char *method) -{ - CLEANUP_FREE char *tmpdir = guestfs_get_tmpdir (g), *dir = NULL; - CLEANUP_FREE char *tempfile = NULL, *tempfile_extracted = NULL; - CLEANUP_CMD_CLOSE struct command *cmd = guestfs_int_new_command (g); - char *ret = NULL; - int64_t size; - int r; - bool matched; - - if (asprintf (&dir, "%s/libguestfsXXXXXX", tmpdir) == -1) { - perrorf (g, "asprintf"); - return NULL; - } - - /* Security: Refuse to download file if it is huge. */ - size = guestfs_filesize (g, path); - if (size == -1 || size > 10000000) { - error (g, _("size of %s unreasonable (%" PRIi64 " bytes)"), - path, size); - goto out; - } - - if (mkdtemp (dir) == NULL) { - perrorf (g, "mkdtemp"); - goto out; - } - - tempfile = safe_asprintf (g, "%s/file", dir); - if (guestfs_download (g, path, tempfile) == -1) - goto out; - - tempfile_extracted = safe_asprintf (g, "%s/file_extracted", dir); - - /* Construct a command to extract named binaries from the initrd file. */ - guestfs_int_cmd_add_string_unquoted (cmd, method); - guestfs_int_cmd_add_string_unquoted (cmd, " "); - guestfs_int_cmd_add_string_quoted (cmd, tempfile); - guestfs_int_cmd_add_string_unquoted (cmd, " > "); - guestfs_int_cmd_add_string_quoted (cmd, tempfile_extracted); - - r = guestfs_int_cmd_run (cmd); - if (r == -1) - goto out; - if (!WIFEXITED (r) || WEXITSTATUS (r) != 0) { - guestfs_int_external_command_failed (g, r, method, path); - goto out; - } - - ret = magic_for_file (g, tempfile_extracted, NULL, &matched); - if (!matched) - error (g, "file_architecture: could not determine architecture of compressed file"); - - out: - guestfs_int_recursive_remove_dir (g, dir); - - return ret; -} - -char * -guestfs_impl_file_architecture (guestfs_h *g, const char *path) -{ - CLEANUP_FREE char *file = NULL; - CLEANUP_FREE char *bits = NULL; - CLEANUP_FREE char *elf_arch = NULL; - CLEANUP_FREE char *endianness = NULL; - char *ret = NULL; - - /* Get the output of the "file" command. Note that because this - * runs in the daemon, LANG=C so it's in English. - */ - file = guestfs_file (g, path); - if (file == NULL) - return NULL; - - if ((match3 (g, file, re_file_elf, &bits, &endianness, &elf_arch)) != 0) - ret = canonical_elf_arch (g, bits, endianness, elf_arch); - else if (strstr (file, "PE32 executable")) - ret = safe_strdup (g, "i386"); - else if (strstr (file, "PE32+ executable")) - ret = safe_strdup (g, "x86_64"); - else if (strstr (file, "cpio archive")) - ret = cpio_arch (g, file, path); - else if (strstr (file, "gzip compressed data")) - ret = compressed_file_arch (g, path, "zcat"); - else if (strstr (file, "XZ compressed data")) - ret = compressed_file_arch (g, path, "xzcat"); - else - error (g, "file_architecture: unknown architecture: %s", path); - - return ret; /* caller frees */ -} diff --git a/po/POTFILES b/po/POTFILES index 429c63abf..456cbe73b 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -363,7 +363,6 @@ lib/errors.c lib/event-string.c lib/events.c lib/file.c -lib/filearch.c lib/fuse.c lib/guid.c lib/handle.c -- 2.13.2
Richard W.M. Jones
2017-Jul-27 19:43 UTC
[Libguestfs] [PATCH v3 12/23] daemon: Reimplement ‘list_ldm_(volumes|partitions)’ APIs in OCaml.
--- daemon/Makefile.am | 2 ++ daemon/ldm.c | 82 ----------------------------------------------- daemon/ldm.ml | 44 +++++++++++++++++++++++++ daemon/ldm.mli | 20 ++++++++++++ generator/actions_core.ml | 2 ++ 5 files changed, 68 insertions(+), 82 deletions(-) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 31cb01b85..f5049516b 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -249,6 +249,7 @@ SOURCES_MLI = \ file.mli \ filearch.mli \ is.mli \ + ldm.mli \ link.mli \ mount.mli \ mountable.mli \ @@ -269,6 +270,7 @@ SOURCES_ML = \ file.ml \ filearch.ml \ is.ml \ + ldm.ml \ link.ml \ mount.ml \ parted.ml \ diff --git a/daemon/ldm.c b/daemon/ldm.c index 609a3e1ec..1bab28989 100644 --- a/daemon/ldm.c +++ b/daemon/ldm.c @@ -23,7 +23,6 @@ #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> -#include <glob.h> #include <string.h> #include <yajl/yajl_tree.h> @@ -45,87 +44,6 @@ optgroup_ldm_available (void) return prog_exists ("ldmtool"); } -static int -glob_errfunc (const char *epath, int eerrno) -{ - fprintf (stderr, "glob: failure reading %s: %s\n", epath, strerror (eerrno)); - return 1; -} - -static char ** -get_devices (const char *pattern) -{ - CLEANUP_FREE_STRINGSBUF DECLARE_STRINGSBUF (ret); - glob_t devs; - int err; - size_t i; - - memset (&devs, 0, sizeof devs); - - err = glob (pattern, GLOB_ERR, glob_errfunc, &devs); - if (err == GLOB_NOSPACE) { - reply_with_error ("glob: returned GLOB_NOSPACE: " - "rerun with LIBGUESTFS_DEBUG=1"); - goto error; - } else if (err == GLOB_ABORTED) { - reply_with_error ("glob: returned GLOB_ABORTED: " - "rerun with LIBGUESTFS_DEBUG=1"); - goto error; - } - - for (i = 0; i < devs.gl_pathc; ++i) { - if (add_string (&ret, devs.gl_pathv[i]) == -1) - goto error; - } - - if (end_stringsbuf (&ret) == -1) goto error; - - globfree (&devs); - return take_stringsbuf (&ret); - - error: - globfree (&devs); - - return NULL; -} - -/* All device mapper devices called /dev/mapper/ldm_vol_*. XXX We - * could tighten this up in future if ldmtool had a way to read these - * names back after they have been created. - */ -char ** -do_list_ldm_volumes (void) -{ - struct stat buf; - - /* If /dev/mapper doesn't exist at all, don't give an error. */ - if (stat ("/dev/mapper", &buf) == -1) { - if (errno == ENOENT) - return empty_list (); - reply_with_perror ("/dev/mapper"); - return NULL; - } - - return get_devices ("/dev/mapper/ldm_vol_*"); -} - -/* Same as above but /dev/mapper/ldm_part_*. See comment above. */ -char ** -do_list_ldm_partitions (void) -{ - struct stat buf; - - /* If /dev/mapper doesn't exist at all, don't give an error. */ - if (stat ("/dev/mapper", &buf) == -1) { - if (errno == ENOENT) - return empty_list (); - reply_with_perror ("/dev/mapper"); - return NULL; - } - - return get_devices ("/dev/mapper/ldm_part_*"); -} - int do_ldmtool_create_all (void) { diff --git a/daemon/ldm.ml b/daemon/ldm.ml new file mode 100644 index 000000000..f943e3cfd --- /dev/null +++ b/daemon/ldm.ml @@ -0,0 +1,44 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +open Std_utils + +open Utils + +(* All device mapper devices are called /dev/mapper/ldm_vol_* + * or /dev/mapper/ldm_part_*. + * + * XXX We could tighten this up in future if ldmtool had a way + * to read these names back after they have been created. + *) +let rec list_ldm_volumes () = list "ldm_vol_" + +and list_ldm_partitions () = list "ldm_part_" + +and list prefix + (* If /dev/mapper doesn't exist at all, don't give an error. *) + if not (is_directory "/dev/mapper") then + [] + else ( + let dir = Sys.readdir "/dev/mapper" in + let dir = Array.to_list dir in + let dir + List.filter (fun d -> String.is_prefix d prefix) dir in + let dir = List.map ((^) "/dev/mapper/") dir in + List.sort compare dir + ) diff --git a/daemon/ldm.mli b/daemon/ldm.mli new file mode 100644 index 000000000..789abb0b3 --- /dev/null +++ b/daemon/ldm.mli @@ -0,0 +1,20 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +val list_ldm_volumes : unit -> string list +val list_ldm_partitions : unit -> string list diff --git a/generator/actions_core.ml b/generator/actions_core.ml index bfd96589e..331a5feb1 100644 --- a/generator/actions_core.ml +++ b/generator/actions_core.ml @@ -8114,6 +8114,7 @@ The capabilities set C<cap> should be passed in text form { defaults with name = "list_ldm_volumes"; added = (1, 20, 0); style = RStringList (RDevice, "devices"), [], []; + impl = OCaml "Ldm.list_ldm_volumes"; optional = Some "ldm"; shortdesc = "list all Windows dynamic disk volumes"; longdesc = "\ @@ -8124,6 +8125,7 @@ device names." }; { defaults with name = "list_ldm_partitions"; added = (1, 20, 0); style = RStringList (RDevice, "devices"), [], []; + impl = OCaml "Ldm.list_ldm_partitions"; optional = Some "ldm"; shortdesc = "list all Windows dynamic disk partitions"; longdesc = "\ -- 2.13.2
Richard W.M. Jones
2017-Jul-27 19:43 UTC
[Libguestfs] [PATCH v3 13/23] daemon: Reimplement ‘lvs’ API in OCaml.
--- daemon/Makefile.am | 2 + daemon/lvm.c | 151 ---------------------------------------------- daemon/lvm.ml | 98 ++++++++++++++++++++++++++++++ daemon/lvm.mli | 19 ++++++ generator/actions_core.ml | 1 + 5 files changed, 120 insertions(+), 151 deletions(-) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index f5049516b..2e723bfc8 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -251,6 +251,7 @@ SOURCES_MLI = \ is.mli \ ldm.mli \ link.mli \ + lvm.mli \ mount.mli \ mountable.mli \ parted.mli \ @@ -272,6 +273,7 @@ SOURCES_ML = \ is.ml \ ldm.ml \ link.ml \ + lvm.ml \ mount.ml \ parted.ml \ realpath.ml \ diff --git a/daemon/lvm.c b/daemon/lvm.c index af2582448..4026c4f92 100644 --- a/daemon/lvm.c +++ b/daemon/lvm.c @@ -101,89 +101,6 @@ convert_lvm_output (char *out, const char *prefix) return take_stringsbuf (&ret); } -/* Filter a colon-separated output of - * lvs -o lv_attr,vg_name,lv_name - * removing thin layouts, and building the device path as we expect it. - * - * This is used only when lvm has no -S. - */ -static char ** -filter_convert_old_lvs_output (char *out) -{ - char *p, *pend; - CLEANUP_FREE_STRINGSBUF DECLARE_STRINGSBUF (ret); - - p = out; - while (p) { - size_t len; - char *saveptr; - char *lv_attr, *vg_name, *lv_name; - - pend = strchr (p, '\n'); /* Get the next line of output. */ - if (pend) { - *pend = '\0'; - pend++; - } - - while (*p && c_isspace (*p)) /* Skip any leading whitespace. */ - p++; - - /* Sigh, skip trailing whitespace too. "pvs", I'm looking at you. */ - len = strlen (p)-1; - while (*p && c_isspace (p[len])) - p[len--] = '\0'; - - if (!*p) { /* Empty line? Skip it. */ - skip_line: - p = pend; - continue; - } - - lv_attr = strtok_r (p, ":", &saveptr); - if (!lv_attr) - goto skip_line; - - vg_name = strtok_r (NULL, ":", &saveptr); - if (!vg_name) - goto skip_line; - - lv_name = strtok_r (NULL, ":", &saveptr); - if (!lv_name) - goto skip_line; - - /* Ignore thin layouts (RHBZ#1278878). */ - if (lv_attr[0] == 't') - goto skip_line; - - /* Ignore activationskip (RHBZ#1306666). */ - if (strlen (lv_attr) >= 10 && lv_attr[9] == 'k') - goto skip_line; - - /* Ignore "unknown device" message (RHBZ#1054761). */ - if (STRNEQ (p, "unknown device")) { - char buf[256]; - - snprintf (buf, sizeof buf, "/dev/%s/%s", vg_name, lv_name); - if (add_string (&ret, buf) == -1) { - free (out); - return NULL; - } - } - - p = pend; - } - - free (out); - - if (ret.size > 0) - sort_strings (ret.argv, ret.size); - - if (end_stringsbuf (&ret) == -1) - return NULL; - - return take_stringsbuf (&ret); -} - char ** do_pvs (void) { @@ -220,74 +137,6 @@ do_vgs (void) return convert_lvm_output (out, NULL); } -/* Check whether lvs has -S to filter its output. - * It is available only in lvm2 >= 2.02.107. - */ -static int -test_lvs_has_S_opt (void) -{ - static int result = -1; - if (result != -1) - return result; - - CLEANUP_FREE char *out = NULL; - CLEANUP_FREE char *err = NULL; - - int r = command (&out, &err, "lvm", "lvs", "--help", NULL); - if (r == -1) { - reply_with_error ("lvm lvs --help: %s", err); - return -1; - } - - if (strstr (out, "-S") == NULL) - result = 0; - else - result = 1; - - return result; -} - -char ** -do_lvs (void) -{ - char *out; - CLEANUP_FREE char *err = NULL; - int r; - const int has_S = test_lvs_has_S_opt (); - - if (has_S < 0) - return NULL; - - if (has_S > 0) { - r = command (&out, &err, - "lvm", "lvs", - "-o", "vg_name,lv_name", - "-S", "lv_role=public && lv_skip_activation!=yes", - "--noheadings", - "--separator", "/", NULL); - if (r == -1) { - reply_with_error ("%s", err); - free (out); - return NULL; - } - - return convert_lvm_output (out, "/dev/"); - } else { - r = command (&out, &err, - "lvm", "lvs", - "-o", "lv_attr,vg_name,lv_name", - "--noheadings", - "--separator", ":", NULL); - if (r == -1) { - reply_with_error ("%s", err); - free (out); - return NULL; - } - - return filter_convert_old_lvs_output (out); - } -} - /* These were so complex to implement that I ended up auto-generating * the code. That code is in stubs.c, and it is generated as usual * by generator.ml. diff --git a/daemon/lvm.ml b/daemon/lvm.ml new file mode 100644 index 000000000..4a9156dad --- /dev/null +++ b/daemon/lvm.ml @@ -0,0 +1,98 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +open Printf + +open Std_utils + +open Utils + +(* Check whether lvs has -S to filter its output. + * It is available only in lvm2 >= 2.02.107. + *) +let lvs_has_S_opt = lazy ( + let out = command "lvm" ["lvs"; "--help"] in + String.find out "-S" >= 0 +) + +let rec lvs () + let has_S = Lazy.force lvs_has_S_opt in + if has_S then ( + let out = command "lvm" ["lvs"; + "-o"; "vg_name,lv_name"; + "-S"; "lv_role=public && lv_skip_activation!=yes"; + "--noheadings"; + "--separator"; "/"] in + convert_lvm_output ~prefix:"/dev/" out + ) + else ( + let out = command "lvm" ["lvs"; + "-o"; "lv_attr,vg_name,lv_name"; + "--noheadings"; + "--separator"; ":"] in + filter_convert_old_lvs_output out + ) + +and convert_lvm_output ?prefix out + let lines = String.nsplit "\n" out in + + (* Skip leading and trailing ("pvs", I'm looking at you) whitespace. *) + let lines = List.map String.trim lines in + + (* Skip empty lines. *) + let lines = List.filter ((<>) "") lines in + + (* Ignore "unknown device" message (RHBZ#1054761). *) + let lines = List.filter ((<>) "unknown device") lines in + + (* Add a prefix? *) + let lines + match prefix with + | None -> lines + | Some prefix -> List.map ((^) prefix) lines in + + (* Sort and return. *) + List.sort compare lines + +(* Filter a colon-separated output of + * lvs -o lv_attr,vg_name,lv_name + * removing thin layouts, and building the device path as we expect it. + * + * This is used only when lvm has no -S. + *) +and filter_convert_old_lvs_output out + let lines = String.nsplit "\n" out in + let lines = List.map String.trim lines in + let lines = List.filter ((<>) "") lines in + let lines = List.filter ((<>) "unknown device") lines in + + let lines = filter_map ( + fun line -> + match String.nsplit ":" line with + | [ lv_attr; vg_name; lv_name ] -> + (* Ignore thin layouts (RHBZ#1278878). *) + if String.length lv_attr > 0 && lv_attr.[0] = 't' then None + (* Ignore activationskip (RHBZ#1306666). *) + else if String.length lv_attr > 9 && lv_attr.[9] = 'k' then None + else + Some (sprintf "/dev/%s/%s" vg_name lv_name) + | _ -> + None + ) lines in + + List.sort compare lines diff --git a/daemon/lvm.mli b/daemon/lvm.mli new file mode 100644 index 000000000..f254728cb --- /dev/null +++ b/daemon/lvm.mli @@ -0,0 +1,19 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +val lvs : unit -> string list diff --git a/generator/actions_core.ml b/generator/actions_core.ml index 331a5feb1..f6f006eee 100644 --- a/generator/actions_core.ml +++ b/generator/actions_core.ml @@ -1732,6 +1732,7 @@ See also C<guestfs_vgs_full>." }; { defaults with name = "lvs"; added = (0, 0, 4); style = RStringList (RDevice, "logvols"), [], []; + impl = OCaml "Lvm.lvs"; optional = Some "lvm2"; tests = [ InitBasicFSonLVM, Always, TestResult ( -- 2.13.2
Richard W.M. Jones
2017-Jul-27 19:43 UTC
[Libguestfs] [PATCH v3 14/23] daemon: Reimplement ‘list_md_devices’ API in OCaml.
--- daemon/Makefile.am | 2 + daemon/md.c | 125 ++++++++++++---------------------------------- daemon/md.ml | 48 ++++++++++++++++++ daemon/md.mli | 19 +++++++ generator/actions_core.ml | 1 + 5 files changed, 101 insertions(+), 94 deletions(-) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 2e723bfc8..873f6b394 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -252,6 +252,7 @@ SOURCES_MLI = \ ldm.mli \ link.mli \ lvm.mli \ + md.mli \ mount.mli \ mountable.mli \ parted.mli \ @@ -274,6 +275,7 @@ SOURCES_ML = \ ldm.ml \ link.ml \ lvm.ml \ + md.ml \ mount.ml \ parted.ml \ realpath.ml \ diff --git a/daemon/md.c b/daemon/md.c index 246e38150..55461b95a 100644 --- a/daemon/md.c +++ b/daemon/md.c @@ -24,7 +24,6 @@ #include <inttypes.h> #include <unistd.h> #include <fcntl.h> -#include <glob.h> #ifdef HAVE_LINUX_RAID_MD_U_H #include <sys/ioctl.h> @@ -32,6 +31,8 @@ #include <linux/raid/md_u.h> #endif /* HAVE_LINUX_RAID_MD_U_H */ +#include <caml/mlvalues.h> + #include "daemon.h" #include "actions.h" #include "optgroups.h" @@ -43,6 +44,35 @@ optgroup_mdadm_available (void) return prog_exists ("mdadm"); } +/* Check if 'dev' is a real RAID device, because in the case where md + * is linked directly into the kernel (not a module), /dev/md0 is + * sometimes created. This is called from OCaml function + * Md.list_md_devices. + */ +extern value guestfs_int_daemon_is_raid_device (value devicev); + +/* NB: This is a "noalloc" call. */ +value +guestfs_int_daemon_is_raid_device (value devv) +{ + const char *dev = String_val (devv); + int ret = 1; + +#if defined(HAVE_LINUX_RAID_MD_U_H) && defined(GET_ARRAY_INFO) + int fd; + mdu_array_info_t array; + + fd = open (dev, O_RDONLY); + if (fd >= 0) { + if (ioctl (fd, GET_ARRAY_INFO, &array) == -1 && errno == ENODEV) + ret = 0; + close (fd); + } +#endif + + return Val_bool (ret); +} + static size_t count_bits (uint64_t bitmap) { @@ -186,99 +216,6 @@ do_md_create (const char *name, char *const *devices, #pragma GCC diagnostic pop #endif -static int -glob_errfunc (const char *epath, int eerrno) -{ - fprintf (stderr, "glob: failure reading %s: %s\n", epath, strerror (eerrno)); - return 1; -} - -/* Check if 'dev' is a real RAID device, because in the case where md - * is linked directly into the kernel (not a module), /dev/md0 is - * sometimes created. - */ -static int -is_raid_device (const char *dev) -{ - int ret = 1; - -#if defined(HAVE_LINUX_RAID_MD_U_H) && defined(GET_ARRAY_INFO) - int fd; - mdu_array_info_t array; - - fd = open (dev, O_RDONLY); - if (fd >= 0) { - if (ioctl (fd, GET_ARRAY_INFO, &array) == -1 && errno == ENODEV) - ret = 0; - close (fd); - } -#endif - - return ret; -} - -char ** -do_list_md_devices (void) -{ - CLEANUP_FREE_STRINGSBUF DECLARE_STRINGSBUF (ret); - glob_t mds; - - memset (&mds, 0, sizeof mds); - -#define PREFIX "/sys/block/md" -#define SUFFIX "/md" - - /* Look for directories under /sys/block matching md[0-9]* - * As an additional check, we also make sure they have a md subdirectory. - */ - const int err = glob (PREFIX "[0-9]*" SUFFIX, GLOB_ERR, glob_errfunc, &mds); - if (err == GLOB_NOSPACE) { - reply_with_error ("glob: returned GLOB_NOSPACE: " - "rerun with LIBGUESTFS_DEBUG=1"); - goto error; - } else if (err == GLOB_ABORTED) { - reply_with_error ("glob: returned GLOB_ABORTED: " - "rerun with LIBGUESTFS_DEBUG=1"); - goto error; - } - - for (size_t i = 0; i < mds.gl_pathc; i++) { - size_t len; - char *dev, *n; - - len = strlen (mds.gl_pathv[i]) - strlen (PREFIX) - strlen (SUFFIX); - -#define DEV "/dev/md" - dev = malloc (strlen (DEV) + len + 1); - if (NULL == dev) { - reply_with_perror ("malloc"); - goto error; - } - - n = dev; - n = mempcpy (n, DEV, strlen (DEV)); - n = mempcpy (n, &mds.gl_pathv[i][strlen (PREFIX)], len); - *n = '\0'; - - if (!is_raid_device (dev)) { - free (dev); - continue; - } - - if (add_string_nodup (&ret, dev) == -1) goto error; - } - - if (end_stringsbuf (&ret) == -1) goto error; - globfree (&mds); - - return take_stringsbuf (&ret); - - error: - globfree (&mds); - - return NULL; -} - char ** do_md_detail (const char *md) { diff --git a/daemon/md.ml b/daemon/md.ml new file mode 100644 index 000000000..caf87cf8f --- /dev/null +++ b/daemon/md.ml @@ -0,0 +1,48 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +open Printf + +open Std_utils + +open Utils + +external is_raid_device : string -> bool + "guestfs_int_daemon_is_raid_device" "noalloc" + +let re_md = Str.regexp "^md[0-9]+$" + +let list_md_devices () + (* Look for directories under /sys/block matching md[0-9]+ + * As an additional check, we also make sure they have a md subdirectory. + *) + let devs = Sys.readdir "/sys/block" in + let devs = Array.to_list devs in + let devs = List.filter (fun d -> Str.string_match re_md d 0) devs in + let devs = List.filter ( + fun d -> is_directory (sprintf "/sys/block/%s/md" d) + ) devs in + + (* Construct the equivalent /dev/md[0-9]+ device names. *) + let devs = List.map ((^) "/dev/") devs in + + (* Check they are really RAID devices. *) + let devs = List.filter is_raid_device devs in + + (* Return the list sorted. *) + sort_device_names devs diff --git a/daemon/md.mli b/daemon/md.mli new file mode 100644 index 000000000..56b6ea65e --- /dev/null +++ b/daemon/md.mli @@ -0,0 +1,19 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +val list_md_devices : unit -> string list diff --git a/generator/actions_core.ml b/generator/actions_core.ml index f6f006eee..140ba6c1b 100644 --- a/generator/actions_core.ml +++ b/generator/actions_core.ml @@ -6632,6 +6632,7 @@ If not set, this defaults to C<raid1>. { defaults with name = "list_md_devices"; added = (1, 15, 4); style = RStringList (RDevice, "devices"), [], []; + impl = OCaml "Md.list_md_devices"; shortdesc = "list Linux md (RAID) devices"; longdesc = "\ List all Linux md devices." }; -- 2.13.2
Richard W.M. Jones
2017-Jul-27 19:43 UTC
[Libguestfs] [PATCH v3 15/23] daemon: Reimplement ‘btrfs_subvolume_list’ and ‘btrfs_subvolume_get_default’ in OCaml.
--- daemon/Makefile.am | 2 + daemon/btrfs.c | 175 ---------------------------------------------- daemon/btrfs.ml | 127 +++++++++++++++++++++++++++++++++ daemon/btrfs.mli | 26 +++++++ generator/actions_core.ml | 2 + 5 files changed, 157 insertions(+), 175 deletions(-) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 873f6b394..9971a45a7 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -243,6 +243,7 @@ guestfsd_CFLAGS = \ # https://caml.inria.fr/pub/docs/manual-ocaml/intfc.html SOURCES_MLI = \ blkid.mli \ + btrfs.mli \ chroot.mli \ sysroot.mli \ devsparts.mli \ @@ -268,6 +269,7 @@ SOURCES_ML = \ mountable.ml \ chroot.ml \ blkid.ml \ + btrfs.ml \ devsparts.ml \ file.ml \ filearch.ml \ diff --git a/daemon/btrfs.c b/daemon/btrfs.c index 00c9b3c75..f11902b21 100644 --- a/daemon/btrfs.c +++ b/daemon/btrfs.c @@ -33,11 +33,6 @@ #include "c-ctype.h" #include "ignore-value.h" -COMPILE_REGEXP (re_btrfs_subvolume_list, - "ID\\s+(\\d+).*\\s" - "top level\\s+(\\d+).*\\s" - "path\\s(.*)", - 0) COMPILE_REGEXP (re_btrfs_balance_status, "Balance on '.*' is (.*)", 0) int @@ -475,137 +470,6 @@ umount (char *fs_buf, const mountable_t *fs) return 0; } -guestfs_int_btrfssubvolume_list * -do_btrfs_subvolume_list (const mountable_t *fs) -{ - CLEANUP_FREE_STRING_LIST char **lines = NULL; - size_t i = 0; - const size_t MAX_ARGS = 64; - const char *argv[MAX_ARGS]; - - /* Execute 'btrfs subvolume list <fs>', and split the output into lines */ - { - char *fs_buf = mount (fs); - - if (!fs_buf) - return NULL; - - ADD_ARG (argv, i, "btrfs"); - ADD_ARG (argv, i, "subvolume"); - ADD_ARG (argv, i, "list"); - ADD_ARG (argv, i, fs_buf); - ADD_ARG (argv, i, NULL); - - CLEANUP_FREE char *out = NULL, *errout = NULL; - int r = commandv (&out, &errout, argv); - - if (umount (fs_buf, fs) != 0) - return NULL; - - if (r == -1) { - CLEANUP_FREE char *fs_desc = mountable_to_string (fs); - if (fs_desc == NULL) { - fprintf (stderr, "malloc: %m"); - } - reply_with_error ("%s: %s", fs_desc ? fs_desc : "malloc", errout); - return NULL; - } - - lines = split_lines (out); - if (!lines) return NULL; - } - - /* Output is: - * - * ID 256 gen 30 top level 5 path test1 - * ID 257 gen 30 top level 5 path dir/test2 - * ID 258 gen 30 top level 5 path test3 - * - * "ID <n>" is the subvolume ID. - * "gen <n>" is the generation when the root was created or last - * updated. - * "top level <n>" is the top level subvolume ID. - * "path <str>" is the subvolume path, relative to the top of the - * filesystem. - * - * Note that the order that each of the above is fixed, but - * different versions of btrfs may display different sets of data. - * Specifically, older versions of btrfs do not display gen. - */ - - guestfs_int_btrfssubvolume_list *ret = NULL; - - const size_t nr_subvolumes = guestfs_int_count_strings (lines); - - ret = malloc (sizeof *ret); - if (!ret) { - reply_with_perror ("malloc"); - return NULL; - } - - ret->guestfs_int_btrfssubvolume_list_len = nr_subvolumes; - ret->guestfs_int_btrfssubvolume_list_val - calloc (nr_subvolumes, sizeof (struct guestfs_int_btrfssubvolume)); - if (ret->guestfs_int_btrfssubvolume_list_val == NULL) { - reply_with_perror ("calloc"); - goto error; - } - - for (i = 0; i < nr_subvolumes; ++i) { - /* To avoid allocations, reuse the 'line' buffer to store the - * path. Thus we don't need to free 'line', since it will be - * freed by the calling (XDR) code. - */ - char *line = lines[i]; -#define N_MATCHES 4 - int ovector[N_MATCHES * 3]; - - if (pcre_exec (re_btrfs_subvolume_list, NULL, line, strlen (line), 0, 0, - ovector, N_MATCHES * 3) < 0) -#undef N_MATCHES - { - unexpected_output: - reply_with_error ("unexpected output from 'btrfs subvolume list' command: %s", line); - goto error; - } - - struct guestfs_int_btrfssubvolume *this - &ret->guestfs_int_btrfssubvolume_list_val[i]; - -#if __WORDSIZE == 64 -#define XSTRTOU64 xstrtoul -#else -#define XSTRTOU64 xstrtoull -#endif - - if (XSTRTOU64 (line + ovector[2], NULL, 10, - &this->btrfssubvolume_id, NULL) != LONGINT_OK) - goto unexpected_output; - if (XSTRTOU64 (line + ovector[4], NULL, 10, - &this->btrfssubvolume_top_level_id, NULL) != LONGINT_OK) - goto unexpected_output; - -#undef XSTRTOU64 - - this->btrfssubvolume_path - strndup (line + ovector[6], ovector[7] - ovector[6]); - if (this->btrfssubvolume_path == NULL) - goto error; - } - - return ret; - - error: - if (ret->guestfs_int_btrfssubvolume_list_val) { - for (i = 0; i < nr_subvolumes; ++i) - free (ret->guestfs_int_btrfssubvolume_list_val[i].btrfssubvolume_path); - free (ret->guestfs_int_btrfssubvolume_list_val); - } - free (ret); - - return NULL; -} - int do_btrfs_subvolume_set_default (int64_t id, const char *fs) { @@ -641,45 +505,6 @@ do_btrfs_subvolume_set_default (int64_t id, const char *fs) return 0; } -int64_t -do_btrfs_subvolume_get_default (const mountable_t *fs) -{ - const size_t MAX_ARGS = 64; - const char *argv[MAX_ARGS]; - size_t i = 0; - char *fs_buf = NULL; - CLEANUP_FREE char *err = NULL; - CLEANUP_FREE char *out = NULL; - int r; - int64_t ret = -1; - - fs_buf = mount (fs); - if (fs_buf == NULL) - goto error; - - ADD_ARG (argv, i, "btrfs"); - ADD_ARG (argv, i, "subvolume"); - ADD_ARG (argv, i, "get-default"); - ADD_ARG (argv, i, fs_buf); - ADD_ARG (argv, i, NULL); - - r = commandv (&out, &err, argv); - if (r == -1) { - reply_with_error ("%s: %s", fs_buf, err); - goto error; - } - if (sscanf (out, "ID %" SCNi64, &ret) != 1) { - reply_with_error ("%s: could not parse subvolume id: %s", argv[0], out); - ret = -1; - goto error; - } - - error: - if (fs_buf && umount (fs_buf, fs) != 0) - return -1; - return ret; -} - int do_btrfs_filesystem_sync (const char *fs) { diff --git a/daemon/btrfs.ml b/daemon/btrfs.ml new file mode 100644 index 000000000..fc02abefa --- /dev/null +++ b/daemon/btrfs.ml @@ -0,0 +1,127 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +open Printf +open Scanf +open Unix + +open Std_utils +open Unix_utils + +open Mountable +open Utils + +include Structs + +(* In order to examine subvolumes, quota and other things, the btrfs + * filesystem has to be mounted. However we're passed a mountable + * in these cases, so we must mount the filesystem. But we cannot + * mount it under the sysroot, as something else might be mounted + * there so this function mounts the filesystem on a temporary + * directory and ensures it is always unmounted afterwards. + *) +let rec with_mounted mountable f + let _with_mounted mount_cmd f + let tmpdir = Mkdtemp.temp_dir "btrfs" in + + (* This is the cleanup function which is called to unmount and + * remove the temporary directory. This is called on error and + * ordinary exit paths. + *) + let finally () + ignore (Sys.command (sprintf "umount %s" (quote tmpdir))); + rmdir tmpdir + in + + protect ~finally ~f:(fun () -> mount_cmd tmpdir; f tmpdir) + in + + match mountable.m_type with + | MountablePath -> + (* This corner-case happens for Mountable_or_Path parameters, where + * a path was supplied by the caller. The path (the m_device + * field) is relative to the sysroot. + *) + f (Sysroot.sysroot_path mountable.m_device) + + | MountableDevice -> + let cmd tmpdir + ignore (command "mount" [mountable.m_device; tmpdir]) in + _with_mounted cmd f + + | MountableBtrfsVol subvol -> + let cmd tmpdir + ignore (command "mount" ["-o"; "subvol=" ^ subvol (* XXX quoting? *); + mountable.m_device; tmpdir]) in + _with_mounted cmd f + +let re_btrfs_subvolume_list + Str.regexp ("ID[ \t]+\\([0-9]+\\).*[ \t]" ^ + "top level[ \t]+\\([0-9]+\\).*[ \t]" ^ + "path[ \t]+\\(.*\\)") + +let btrfs_subvolume_list mountable + (* Execute 'btrfs subvolume list <fs>', and split the output into lines *) + let lines + with_mounted mountable ( + fun mp -> command "btrfs" ["subvolume"; "list"; mp] + ) in + let lines = String.nsplit "\n" lines in + let lines = List.filter ((<>) "") lines in + + (* Output is: + * + * ID 256 gen 30 top level 5 path test1 + * ID 257 gen 30 top level 5 path dir/test2 + * ID 258 gen 30 top level 5 path test3 + * + * "ID <n>" is the subvolume ID. + * "gen <n>" is the generation when the root was created or last + * updated. + * "top level <n>" is the top level subvolume ID. + * "path <str>" is the subvolume path, relative to the top of the + * filesystem. + * + * Note that the order that each of the above is fixed, but + * different versions of btrfs may display different sets of data. + * Specifically, older versions of btrfs do not display gen. + *) + List.map ( + fun line -> + if Str.string_match re_btrfs_subvolume_list line 0 then ( + let id = Int64.of_string (Str.matched_group 1 line) + and top_level_id = Int64.of_string (Str.matched_group 2 line) + and path = Str.matched_group 3 line in + + { + btrfssubvolume_id = id; + btrfssubvolume_top_level_id = top_level_id; + btrfssubvolume_path = path + } + ) + else + failwithf "unexpected output from 'btrfs subvolume list' command: %s" + line + ) lines + +let btrfs_subvolume_get_default mountable + let out + with_mounted mountable ( + fun mp -> command "btrfs" ["subvolume"; "get-default"; mp] + ) in + sscanf out "ID %Ld" identity diff --git a/daemon/btrfs.mli b/daemon/btrfs.mli new file mode 100644 index 000000000..55a38e42d --- /dev/null +++ b/daemon/btrfs.mli @@ -0,0 +1,26 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +type btrfssubvolume = { + btrfssubvolume_id : int64; + btrfssubvolume_top_level_id : int64; + btrfssubvolume_path : string; +} + +val btrfs_subvolume_list : Mountable.t -> btrfssubvolume list +val btrfs_subvolume_get_default : Mountable.t -> int64 diff --git a/generator/actions_core.ml b/generator/actions_core.ml index 140ba6c1b..bd3c21d3b 100644 --- a/generator/actions_core.ml +++ b/generator/actions_core.ml @@ -7304,6 +7304,7 @@ created subvolume will be added to." }; { defaults with name = "btrfs_subvolume_list"; added = (1, 17, 35); style = RStructList ("subvolumes", "btrfssubvolume"), [String (Mountable_or_Path, "fs")], []; + impl = OCaml "Btrfs.btrfs_subvolume_list"; optional = Some "btrfs"; camel_name = "BTRFSSubvolumeList"; test_excuse = "tested in tests/btrfs"; shortdesc = "list btrfs snapshots and subvolumes"; @@ -8783,6 +8784,7 @@ This uses the L<blockdev(8)> command." }; { defaults with name = "btrfs_subvolume_get_default"; added = (1, 29, 17); style = RInt64 "id", [String (Mountable_or_Path, "fs")], []; + impl = OCaml "Btrfs.btrfs_subvolume_get_default"; optional = Some "btrfs"; camel_name = "BTRFSSubvolumeGetDefault"; tests = [ InitPartition, Always, TestResult ( -- 2.13.2
Richard W.M. Jones
2017-Jul-27 19:43 UTC
[Libguestfs] [PATCH v3 16/23] daemon: Reimplement ‘list_filesystems’ API in the daemon, in OCaml.
Move the list_filesystems API into the daemon, reimplementing it in OCaml. Since this API makes many other API calls, it runs a lot faster in the daemon. --- daemon/Makefile.am | 2 + daemon/ldm.ml | 2 + daemon/ldm.mli | 2 + daemon/listfs.ml | 159 ++++++++++++++++++++++++++++++ daemon/listfs.mli | 19 ++++ daemon/lvm.ml | 2 + daemon/lvm.mli | 2 + docs/C_SOURCE_FILES | 1 - generator/actions_core.ml | 75 +++++++------- generator/proc_nr.ml | 1 + lib/MAX_PROC_NR | 2 +- lib/Makefile.am | 1 - lib/listfs.c | 246 ---------------------------------------------- 13 files changed, 228 insertions(+), 286 deletions(-) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 9971a45a7..e3517ca15 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -252,6 +252,7 @@ SOURCES_MLI = \ is.mli \ ldm.mli \ link.mli \ + listfs.mli \ lvm.mli \ md.mli \ mount.mli \ @@ -280,6 +281,7 @@ SOURCES_ML = \ md.ml \ mount.ml \ parted.ml \ + listfs.ml \ realpath.ml \ callbacks.ml \ daemon.ml diff --git a/daemon/ldm.ml b/daemon/ldm.ml index f943e3cfd..9720a6c8a 100644 --- a/daemon/ldm.ml +++ b/daemon/ldm.ml @@ -20,6 +20,8 @@ open Std_utils open Utils +let available = Optgroups.ldm_available + (* All device mapper devices are called /dev/mapper/ldm_vol_* * or /dev/mapper/ldm_part_*. * diff --git a/daemon/ldm.mli b/daemon/ldm.mli index 789abb0b3..e6edfabd8 100644 --- a/daemon/ldm.mli +++ b/daemon/ldm.mli @@ -16,5 +16,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *) +val available : unit -> bool + val list_ldm_volumes : unit -> string list val list_ldm_partitions : unit -> string list diff --git a/daemon/listfs.ml b/daemon/listfs.ml new file mode 100644 index 000000000..610a1ea78 --- /dev/null +++ b/daemon/listfs.ml @@ -0,0 +1,159 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +open Printf + +open Std_utils + +let rec list_filesystems () + let has_lvm2 = Lvm.available () in + let has_ldm = Ldm.available () in + + let devices = Devsparts.list_devices () in + let partitions = Devsparts.list_partitions () in + let mds = Md.list_md_devices () in + + (* Look to see if any devices directly contain filesystems + * (RHBZ#590167). However vfs-type will fail to tell us anything + * useful about devices which just contain partitions, so we also + * get the list of partitions and exclude the corresponding devices + * by using part-to-dev. + *) + let devices_containing_partitions = List.fold_left ( + fun set part -> + StringSet.add (Devsparts.part_to_dev part) set + ) StringSet.empty partitions in + let devices = List.filter ( + fun dev -> + not (StringSet.mem dev devices_containing_partitions) + ) devices in + + (* Use vfs-type to check for filesystems on devices. *) + let ret = filter_map check_with_vfs_type devices in + + (* Use vfs-type to check for filesystems on partitions, but + * ignore MBR partition type 42 used by LDM. + *) + let ret + ret @ + filter_map ( + fun part -> + if not has_ldm || not (is_mbr_partition_type_42 part) then + check_with_vfs_type part + else + None (* ignore type 42 *) + ) partitions in + + (* Use vfs-type to check for filesystems on md devices. *) + let ret = ret @ filter_map check_with_vfs_type mds in + + (* LVM. *) + let ret + if has_lvm2 then ( + let lvs = Lvm.lvs () in + (* Use vfs-type to check for filesystems on LVs. *) + ret @ filter_map check_with_vfs_type lvs + ) + else ret in + + (* LDM. *) + let ret + if has_ldm then ( + let ldmvols = Ldm.list_ldm_volumes () in + let ldmparts = Ldm.list_ldm_partitions () in + (* Use vfs-type to check for filesystems on Windows dynamic disks. *) + ret @ + filter_map check_with_vfs_type ldmvols @ + filter_map check_with_vfs_type ldmparts + ) + else ret in + + List.flatten ret + +(* Use vfs-type to check for a filesystem of some sort of [device]. + * Returns [Some [device, vfs_type; ...]] if found (there may be + * multiple devices found in the case of btrfs), else [None] if nothing + * is found. + *) +and check_with_vfs_type device + let mountable = Mountable.of_device device in + let vfs_type + try Blkid.vfs_type mountable + with exn -> + if verbose () then + eprintf "check_with_vfs_type: %s: %s\n" + device (Printexc.to_string exn); + "" in + + if vfs_type = "" then + Some [mountable, "unknown"] + + (* Ignore all "*_member" strings. In libblkid these are returned + * for things which are members of some RAID or LVM set, most + * importantly "LVM2_member" which is a PV. + *) + else if String.is_suffix vfs_type "_member" then + None + + (* Ignore LUKS-encrypted partitions. These are also containers, as above. *) + else if vfs_type = "crypto_LUKS" then + None + + (* A single btrfs device can turn into many volumes. *) + else if vfs_type = "btrfs" then ( + let vols = Btrfs.btrfs_subvolume_list mountable in + + (* Filter out the default subvolume. You can access that by + * simply mounting the whole device, so we will add the whole + * device at the beginning of the returned list instead. + *) + let default_volume = Btrfs.btrfs_subvolume_get_default mountable in + let vols + List.filter ( + fun { Btrfs.btrfssubvolume_id = id } -> id <> default_volume + ) vols in + + Some ( + (mountable, vfs_type) (* whole device = default volume *) + :: List.map ( + fun { Btrfs.btrfssubvolume_path = path } -> + let mountable = Mountable.of_btrfsvol device path in + (mountable, "btrfs") + ) vols + ) + ) + + else + Some [mountable, vfs_type] + +(* We should ignore partitions that have MBR type byte 0x42, because + * these are members of a Windows dynamic disk group. Trying to read + * them will cause errors (RHBZ#887520). Assuming that libguestfs was + * compiled with ldm support, we'll get the filesystems on these later. + *) +and is_mbr_partition_type_42 partition + try + let partnum = Devsparts.part_to_partnum partition in + let device = Devsparts.part_to_dev partition in + let mbr_id = Parted.part_get_mbr_id device partnum in + mbr_id = 0x42 + with exn -> + if verbose () then + eprintf "is_mbr_partition_type_42: %s: %s\n" + partition (Printexc.to_string exn); + false diff --git a/daemon/listfs.mli b/daemon/listfs.mli new file mode 100644 index 000000000..69958da77 --- /dev/null +++ b/daemon/listfs.mli @@ -0,0 +1,19 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +val list_filesystems : unit -> (Mountable.t * string) list diff --git a/daemon/lvm.ml b/daemon/lvm.ml index 4a9156dad..0d1dc55b5 100644 --- a/daemon/lvm.ml +++ b/daemon/lvm.ml @@ -22,6 +22,8 @@ open Std_utils open Utils +let available = Optgroups.lvm2_available + (* Check whether lvs has -S to filter its output. * It is available only in lvm2 >= 2.02.107. *) diff --git a/daemon/lvm.mli b/daemon/lvm.mli index f254728cb..1cf61ecfb 100644 --- a/daemon/lvm.mli +++ b/daemon/lvm.mli @@ -16,4 +16,6 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *) +val available : unit -> bool + val lvs : unit -> string list diff --git a/docs/C_SOURCE_FILES b/docs/C_SOURCE_FILES index 7ac6716cd..b4f085699 100644 --- a/docs/C_SOURCE_FILES +++ b/docs/C_SOURCE_FILES @@ -325,7 +325,6 @@ lib/launch.c lib/libvirt-auth.c lib/libvirt-domain.c lib/libvirt-is-version.c -lib/listfs.c lib/lpj.c lib/match.c lib/mountable.c diff --git a/generator/actions_core.ml b/generator/actions_core.ml index bd3c21d3b..d5946b3f5 100644 --- a/generator/actions_core.ml +++ b/generator/actions_core.ml @@ -209,43 +209,6 @@ If the mountable does not represent a btrfs subvolume, then this function fails and the C<errno> is set to C<EINVAL>." }; { defaults with - name = "list_filesystems"; added = (1, 5, 15); - style = RHashtable (RMountable, RPlainString, "fses"), [], []; - shortdesc = "list filesystems"; - longdesc = "\ -This inspection command looks for filesystems on partitions, -block devices and logical volumes, returning a list of C<mountables> -containing filesystems and their type. - -The return value is a hash, where the keys are the devices -containing filesystems, and the values are the filesystem types. -For example: - - \"/dev/sda1\" => \"ntfs\" - \"/dev/sda2\" => \"ext2\" - \"/dev/vg_guest/lv_root\" => \"ext4\" - \"/dev/vg_guest/lv_swap\" => \"swap\" - -The key is not necessarily a block device. It may also be an opaque -‘mountable’ string which can be passed to C<guestfs_mount>. - -The value can have the special value \"unknown\", meaning the -content of the device is undetermined or empty. -\"swap\" means a Linux swap partition. - -This command runs other libguestfs commands, which might include -C<guestfs_mount> and C<guestfs_umount>, and therefore you should -use this soon after launch and only when nothing is mounted. - -Not all of the filesystems returned will be mountable. In -particular, swap partitions are returned in the list. Also -this command does not check that each filesystem -found is valid and mountable, and some filesystems might -be mountable but require special options. Filesystems may -not all belong to a single logical operating system -(use C<guestfs_inspect_os> to look for OSes)." }; - - { defaults with name = "add_drive"; added = (0, 0, 3); style = RErr, [String (PlainString, "filename")], [OBool "readonly"; OString "format"; OString "iface"; OString "name"; OString "label"; OString "protocol"; OStringList "server"; OString "username"; OString "secret"; OString "cachemode"; OString "discard"; OBool "copyonread"]; once_had_no_optargs = true; @@ -9635,4 +9598,42 @@ initrd or kernel module(s) instead. =back" }; + { defaults with + name = "list_filesystems"; added = (1, 5, 15); + style = RHashtable (RMountable, RPlainString, "fses"), [], []; + impl = OCaml "Listfs.list_filesystems"; + shortdesc = "list filesystems"; + longdesc = "\ +This inspection command looks for filesystems on partitions, +block devices and logical volumes, returning a list of C<mountables> +containing filesystems and their type. + +The return value is a hash, where the keys are the devices +containing filesystems, and the values are the filesystem types. +For example: + + \"/dev/sda1\" => \"ntfs\" + \"/dev/sda2\" => \"ext2\" + \"/dev/vg_guest/lv_root\" => \"ext4\" + \"/dev/vg_guest/lv_swap\" => \"swap\" + +The key is not necessarily a block device. It may also be an opaque +‘mountable’ string which can be passed to C<guestfs_mount>. + +The value can have the special value \"unknown\", meaning the +content of the device is undetermined or empty. +\"swap\" means a Linux swap partition. + +This command runs other libguestfs commands, which might include +C<guestfs_mount> and C<guestfs_umount>, and therefore you should +use this soon after launch and only when nothing is mounted. + +Not all of the filesystems returned will be mountable. In +particular, swap partitions are returned in the list. Also +this command does not check that each filesystem +found is valid and mountable, and some filesystems might +be mountable but require special options. Filesystems may +not all belong to a single logical operating system +(use C<guestfs_inspect_os> to look for OSes)." }; + ] diff --git a/generator/proc_nr.ml b/generator/proc_nr.ml index 1b0feae87..dec02f5fa 100644 --- a/generator/proc_nr.ml +++ b/generator/proc_nr.ml @@ -483,6 +483,7 @@ let proc_nr = [ 473, "yara_destroy"; 474, "internal_yara_scan"; 475, "file_architecture"; +476, "list_filesystems"; ] (* End of list. If adding a new entry, add it at the end of the list diff --git a/lib/MAX_PROC_NR b/lib/MAX_PROC_NR index 7573eff88..b86395733 100644 --- a/lib/MAX_PROC_NR +++ b/lib/MAX_PROC_NR @@ -1 +1 @@ -475 +476 diff --git a/lib/Makefile.am b/lib/Makefile.am index fab8c4a45..cab03107f 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -109,7 +109,6 @@ libguestfs_la_SOURCES = \ launch-unix.c \ libvirt-auth.c \ libvirt-domain.c \ - listfs.c \ lpj.c \ match.c \ mountable.c \ diff --git a/lib/listfs.c b/lib/listfs.c deleted file mode 100644 index 60aff3305..000000000 --- a/lib/listfs.c +++ /dev/null @@ -1,246 +0,0 @@ -/* libguestfs - * Copyright (C) 2010 Red Hat Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include <config.h> - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include "guestfs.h" -#include "guestfs-internal.h" -#include "guestfs-internal-actions.h" -#include "structs-cleanups.h" - -/* List filesystems. - * - * The current implementation just uses guestfs_vfs_type and doesn't - * try mounting anything, but we reserve the right in future to try - * mounting filesystems. - */ - -static void remove_from_list (char **list, const char *item); -static int check_with_vfs_type (guestfs_h *g, const char *dev, struct stringsbuf *sb); -static int is_mbr_partition_type_42 (guestfs_h *g, const char *partition); - -char ** -guestfs_impl_list_filesystems (guestfs_h *g) -{ - size_t i; - DECLARE_STRINGSBUF (ret); - - const char *lvm2[] = { "lvm2", NULL }; - const int has_lvm2 = guestfs_feature_available (g, (char **) lvm2); - const char *ldm[] = { "ldm", NULL }; - const int has_ldm = guestfs_feature_available (g, (char **) ldm); - - CLEANUP_FREE_STRING_LIST char **devices = NULL; - CLEANUP_FREE_STRING_LIST char **partitions = NULL; - CLEANUP_FREE_STRING_LIST char **mds = NULL; - CLEANUP_FREE_STRING_LIST char **lvs = NULL; - CLEANUP_FREE_STRING_LIST char **ldmvols = NULL; - CLEANUP_FREE_STRING_LIST char **ldmparts = NULL; - - /* Look to see if any devices directly contain filesystems - * (RHBZ#590167). However vfs-type will fail to tell us anything - * useful about devices which just contain partitions, so we also - * get the list of partitions and exclude the corresponding devices - * by using part-to-dev. - */ - devices = guestfs_list_devices (g); - if (devices == NULL) goto error; - partitions = guestfs_list_partitions (g); - if (partitions == NULL) goto error; - mds = guestfs_list_md_devices (g); - if (mds == NULL) goto error; - - for (i = 0; partitions[i] != NULL; ++i) { - CLEANUP_FREE char *dev = guestfs_part_to_dev (g, partitions[i]); - if (dev) - remove_from_list (devices, dev); - } - - /* Use vfs-type to check for filesystems on devices. */ - for (i = 0; devices[i] != NULL; ++i) - if (check_with_vfs_type (g, devices[i], &ret) == -1) - goto error; - - /* Use vfs-type to check for filesystems on partitions. */ - for (i = 0; partitions[i] != NULL; ++i) { - if (has_ldm == 0 || ! is_mbr_partition_type_42 (g, partitions[i])) { - if (check_with_vfs_type (g, partitions[i], &ret) == -1) - goto error; - } - } - - /* Use vfs-type to check for filesystems on md devices. */ - for (i = 0; mds[i] != NULL; ++i) - if (check_with_vfs_type (g, mds[i], &ret) == -1) - goto error; - - if (has_lvm2 > 0) { - /* Use vfs-type to check for filesystems on LVs. */ - lvs = guestfs_lvs (g); - if (lvs == NULL) goto error; - - for (i = 0; lvs[i] != NULL; ++i) - if (check_with_vfs_type (g, lvs[i], &ret) == -1) - goto error; - } - - if (has_ldm > 0) { - /* Use vfs-type to check for filesystems on Windows dynamic disks. */ - ldmvols = guestfs_list_ldm_volumes (g); - if (ldmvols == NULL) goto error; - - for (i = 0; ldmvols[i] != NULL; ++i) - if (check_with_vfs_type (g, ldmvols[i], &ret) == -1) - goto error; - - ldmparts = guestfs_list_ldm_partitions (g); - if (ldmparts == NULL) goto error; - - for (i = 0; ldmparts[i] != NULL; ++i) - if (check_with_vfs_type (g, ldmparts[i], &ret) == -1) - goto error; - } - - /* Finish off the list and return it. */ - guestfs_int_end_stringsbuf (g, &ret); - return ret.argv; - - error: - guestfs_int_free_stringsbuf (&ret); - return NULL; -} - -/* If 'item' occurs in 'list', remove and free it. */ -static void -remove_from_list (char **list, const char *item) -{ - size_t i; - - for (i = 0; list[i] != NULL; ++i) - if (STREQ (list[i], item)) { - free (list[i]); - for (; list[i+1] != NULL; ++i) - list[i] = list[i+1]; - list[i] = NULL; - return; - } -} - -/* Use vfs-type to look for a filesystem of some sort on 'dev'. - * Apart from some types which we ignore, add the result to the - * 'ret' string list. - */ -static int -check_with_vfs_type (guestfs_h *g, const char *device, struct stringsbuf *sb) -{ - const char *v; - CLEANUP_FREE char *vfs_type = NULL; - - guestfs_push_error_handler (g, NULL, NULL); - vfs_type = guestfs_vfs_type (g, device); - guestfs_pop_error_handler (g); - - if (!vfs_type) - v = "unknown"; - else if (STREQ (vfs_type, "")) - v = "unknown"; - else if (STREQ (vfs_type, "btrfs")) { - CLEANUP_FREE_BTRFSSUBVOLUME_LIST struct guestfs_btrfssubvolume_list *vols - guestfs_btrfs_subvolume_list (g, device); - - if (vols == NULL) - return -1; - - const int64_t default_volume - guestfs_btrfs_subvolume_get_default (g, device); - - for (size_t i = 0; i < vols->len; i++) { - struct guestfs_btrfssubvolume *this = &vols->val[i]; - - /* Ignore the default subvolume. We get it by simply mounting - * the whole device of this btrfs filesystem. - */ - if (this->btrfssubvolume_id == (uint64_t) default_volume) - continue; - - guestfs_int_add_sprintf (g, sb, - "btrfsvol:%s/%s", - device, this->btrfssubvolume_path); - guestfs_int_add_string (g, sb, "btrfs"); - } - - v = vfs_type; - } - else { - /* Ignore all "*_member" strings. In libblkid these are returned - * for things which are members of some RAID or LVM set, most - * importantly "LVM2_member" which is a PV. - */ - const size_t n = strlen (vfs_type); - if (n >= 7 && STREQ (&vfs_type[n-7], "_member")) - return 0; - - /* Ignore LUKS-encrypted partitions. These are also containers. */ - if (STREQ (vfs_type, "crypto_LUKS")) - return 0; - - v = vfs_type; - } - - guestfs_int_add_string (g, sb, device); - guestfs_int_add_string (g, sb, v); - - return 0; -} - -/* We should ignore partitions that have MBR type byte 0x42, because - * these are members of a Windows dynamic disk group. Trying to read - * them will cause errors (RHBZ#887520). Assuming that libguestfs was - * compiled with ldm support, we'll get the filesystems on these later. - */ -static int -is_mbr_partition_type_42 (guestfs_h *g, const char *partition) -{ - CLEANUP_FREE char *device = NULL; - int partnum; - int mbr_id; - int ret = 0; - - guestfs_push_error_handler (g, NULL, NULL); - - partnum = guestfs_part_to_partnum (g, partition); - if (partnum == -1) - goto out; - - device = guestfs_part_to_dev (g, partition); - if (device == NULL) - goto out; - - mbr_id = guestfs_part_get_mbr_id (g, device, partnum); - - ret = mbr_id == 0x42; - - out: - guestfs_pop_error_handler (g); - - return ret; -} -- 2.13.2
Richard W.M. Jones
2017-Jul-27 19:43 UTC
[Libguestfs] [PATCH v3 17/23] daemon: Reimplement ‘part_list’ API in OCaml.
--- daemon/parted.c | 56 ----------------------------------------------- daemon/parted.ml | 56 +++++++++++++++++++++++++++++++++++++++++++++++ daemon/parted.mli | 8 +++++++ generator/actions_core.ml | 1 + 4 files changed, 65 insertions(+), 56 deletions(-) diff --git a/daemon/parted.c b/daemon/parted.c index b788ed72a..3ad1ba147 100644 --- a/daemon/parted.c +++ b/daemon/parted.c @@ -383,62 +383,6 @@ do_part_get_parttype (const char *device) return r; } -guestfs_int_partition_list * -do_part_list (const char *device) -{ - CLEANUP_FREE char *out = print_partition_table (device, true); - if (!out) - return NULL; - - CLEANUP_FREE_STRING_LIST char **lines = split_lines (out); - - if (!lines) - return NULL; - - guestfs_int_partition_list *r; - - /* lines[0] is "BYT;", lines[1] is the device line which we ignore, - * lines[2..] are the partitions themselves. Count how many. - */ - size_t nr_rows = 0, row; - for (row = 2; lines[row] != NULL; ++row) - ++nr_rows; - - r = malloc (sizeof *r); - if (r == NULL) { - reply_with_perror ("malloc"); - return NULL; - } - r->guestfs_int_partition_list_len = nr_rows; - r->guestfs_int_partition_list_val - malloc (nr_rows * sizeof (guestfs_int_partition)); - if (r->guestfs_int_partition_list_val == NULL) { - reply_with_perror ("malloc"); - goto error2; - } - - /* Now parse the lines. */ - size_t i; - for (i = 0, row = 2; lines[row] != NULL; ++i, ++row) { - if (sscanf (lines[row], "%d:%" SCNi64 "B:%" SCNi64 "B:%" SCNi64 "B", - &r->guestfs_int_partition_list_val[i].part_num, - &r->guestfs_int_partition_list_val[i].part_start, - &r->guestfs_int_partition_list_val[i].part_end, - &r->guestfs_int_partition_list_val[i].part_size) != 4) { - reply_with_error ("could not parse row from output of parted print command: %s", lines[row]); - goto error3; - } - } - - return r; - - error3: - free (r->guestfs_int_partition_list_val); - error2: - free (r); - return NULL; -} - int do_part_get_bootable (const char *device, int partnum) { diff --git a/daemon/parted.ml b/daemon/parted.ml index 6be41cf66..da31ab5c6 100644 --- a/daemon/parted.ml +++ b/daemon/parted.ml @@ -22,6 +22,8 @@ open Std_utils open Utils +include Structs + (* Test if [sfdisk] is recent enough to have [--part-type], to be used * instead of [--print-id] and [--change-id]. *) @@ -53,3 +55,57 @@ let part_get_mbr_id device partnum (* It's printed in hex, possibly with a leading space. *) sscanf out " %x" identity + +(* This is not equivalent to print_partition_table in the C code, as + * it only deals with the ‘-m’ option output, and it partially parses + * that. If we convert other functions that don't use the ‘-m’ version + * we'll have to refactor this. XXX + *) +let print_partition_table_machine_readable device + udev_settle (); + + let args = ref [] in + push_back args "-m"; + push_back args "-s"; + push_back args "--"; + push_back args device; + push_back args "unit"; + push_back args "b"; + push_back args "print"; + + let out + try command "parted" !args + with + (* Translate "unrecognised disk label" into an errno code. *) + Failure str when String.find str "unrecognised disk label" >= 0 -> + raise (Unix.Unix_error (Unix.EINVAL, "parted", device ^ ": " ^ str)) in + + udev_settle (); + + (* Split the output into lines. *) + let out = String.trim out in + let lines = String.nsplit "\n" out in + + (* lines[0] is "BYT;", lines[1] is the device line which we ignore, + * lines[2..] are the partitions themselves. + *) + match lines with + | "BYT;" :: _ :: lines -> lines + | [] | [_] -> + failwith "too few rows of output from 'parted print' command" + | _ -> + failwith "did not see 'BYT;' magic value in 'parted print' command" + +let part_list device + let lines = print_partition_table_machine_readable device in + + List.map ( + fun line -> + try sscanf line "%d:%LdB:%LdB:%LdB" + (fun num start end_ size -> + { part_num = Int32.of_int num; + part_start = start; part_end = end_; part_size = size }) + with Scan_failure err -> + failwithf "could not parse row from output of 'parted print' command: %s: %s" + line err + ) lines diff --git a/daemon/parted.mli b/daemon/parted.mli index 33eb6d30d..057d7e8c7 100644 --- a/daemon/parted.mli +++ b/daemon/parted.mli @@ -16,4 +16,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *) +type partition = { + part_num : int32; + part_start : int64; + part_end : int64; + part_size : int64; +} + val part_get_mbr_id : string -> int -> int +val part_list : string -> partition list diff --git a/generator/actions_core.ml b/generator/actions_core.ml index d5946b3f5..b1e2559e0 100644 --- a/generator/actions_core.ml +++ b/generator/actions_core.ml @@ -5039,6 +5039,7 @@ table. This works on C<gpt> but not on C<mbr> partitions." }; { defaults with name = "part_list"; added = (1, 0, 78); style = RStructList ("partitions", "partition"), [String (Device, "device")], []; + impl = OCaml "Parted.part_list"; tests = [] (* XXX Add a regression test for this. *); shortdesc = "list partitions on a device"; longdesc = "\ -- 2.13.2
Richard W.M. Jones
2017-Jul-27 19:43 UTC
[Libguestfs] [PATCH v3 18/23] daemon: Reimplement ‘findfs_uuid’ and ‘findfs_label’ APIs in OCaml.
This also reimplements the lv_canonical function in OCaml. We cannot call the original C function because it calls reply_with_perror which would break the OCaml bindings. --- daemon/Makefile.am | 3 +- daemon/findfs.c | 92 ----------------------------------------------- daemon/findfs.ml | 56 +++++++++++++++++++++++++++++ daemon/findfs.mli | 20 +++++++++++ daemon/lvm.ml | 28 +++++++++++++++ daemon/lvm.mli | 10 ++++++ docs/C_SOURCE_FILES | 1 - generator/actions_core.ml | 2 ++ 8 files changed, 118 insertions(+), 94 deletions(-) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index e3517ca15..eb8bbb152 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -95,7 +95,6 @@ guestfsd_SOURCES = \ ext2.c \ fallocate.c \ file.c \ - findfs.c \ fill.c \ find.c \ format.c \ @@ -249,6 +248,7 @@ SOURCES_MLI = \ devsparts.mli \ file.mli \ filearch.mli \ + findfs.mli \ is.mli \ ldm.mli \ link.mli \ @@ -278,6 +278,7 @@ SOURCES_ML = \ ldm.ml \ link.ml \ lvm.ml \ + findfs.ml \ md.ml \ mount.ml \ parted.ml \ diff --git a/daemon/findfs.c b/daemon/findfs.c deleted file mode 100644 index f44a60088..000000000 --- a/daemon/findfs.c +++ /dev/null @@ -1,92 +0,0 @@ -/* libguestfs - the guestfsd daemon - * Copyright (C) 2010 Red Hat Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include <config.h> - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include "daemon.h" -#include "actions.h" - -static char * -findfs (const char *tag, const char *label_or_uuid) -{ - char *out; - CLEANUP_FREE char *err = NULL; - CLEANUP_FREE char *arg = NULL; - int r; - size_t len; - - /* Kill the cache file, forcing blkid to reread values from the - * original filesystems. In blkid there is a '-p' option which is - * supposed to do this, but (a) it doesn't work and (b) that option - * is not supported in RHEL 5. - */ - unlink ("/etc/blkid/blkid.tab"); - unlink ("/run/blkid/blkid.tab"); - - if (asprintf (&arg, "%s=%s", tag, label_or_uuid) == -1) { - reply_with_perror ("asprintf"); - return NULL; - } - - r = command (&out, &err, "findfs", arg, NULL); - if (r == -1) { - reply_with_error ("%s", err); - free (out); - return NULL; - } - - /* Trim trailing \n if present. */ - len = strlen (out); - if (len > 0 && out[len-1] == '\n') - out[len-1] = '\0'; - - if (STRPREFIX (out, "/dev/mapper/") || STRPREFIX (out, "/dev/dm-")) { - char *canonical; - r = lv_canonical (out, &canonical); - if (r == -1) { - free (out); - return NULL; - } - if (r == 1) { - free (out); - out = canonical; - } - /* Ignore the case where r == 0. /dev/mapper does not correspond - * to an LV, so the best we can do is just return it as-is. - */ - } - - return out; /* caller frees */ -} - -char * -do_findfs_uuid (const char *uuid) -{ - return findfs ("UUID", uuid); -} - -char * -do_findfs_label (const char *label) -{ - return findfs ("LABEL", label); -} diff --git a/daemon/findfs.ml b/daemon/findfs.ml new file mode 100644 index 000000000..8acb72928 --- /dev/null +++ b/daemon/findfs.ml @@ -0,0 +1,56 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +open Printf +open Unix + +open Std_utils + +open Utils + +let rec findfs_uuid uuid + findfs "UUID" uuid +and findfs_label label + findfs "LABEL"label + +and findfs tag str + (* Kill the cache file, forcing blkid to reread values from the + * original filesystems. In blkid there is a '-p' option which is + * supposed to do this, but (a) it doesn't work and (b) that option + * is not supported in RHEL 5. + *) + (try unlink "/etc/blkid/blkid.tab" with Unix_error _ -> ()); + (try unlink "/run/blkid/blkid.tab" with Unix_error _ -> ()); + + let out = command "findfs" [ sprintf "%s=%s" tag str ] in + + (* Trim trailing \n if present. *) + let out = String.trim out in + + if String.is_prefix out "/dev/mapper/" || + String.is_prefix out "/dev/dm-" then ( + match Lvm.lv_canonical out with + | None -> + (* Ignore the case where 'out' doesn't appear to be an LV. + * The best we can do is return the original as-is. + *) + out + | Some out -> out + ) + else + out diff --git a/daemon/findfs.mli b/daemon/findfs.mli new file mode 100644 index 000000000..acef0395c --- /dev/null +++ b/daemon/findfs.mli @@ -0,0 +1,20 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +val findfs_uuid : string -> string +val findfs_label : string -> string diff --git a/daemon/lvm.ml b/daemon/lvm.ml index 0d1dc55b5..4210c2fb3 100644 --- a/daemon/lvm.ml +++ b/daemon/lvm.ml @@ -16,6 +16,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *) +open Unix open Printf open Std_utils @@ -98,3 +99,30 @@ and filter_convert_old_lvs_output out ) lines in List.sort compare lines + +(* Convert a non-canonical LV path like /dev/mapper/vg-lv or /dev/dm-0 + * to a canonical one. + * + * This is harder than it should be. A LV device like /dev/VG/LV is + * really a symlink to a device-mapper device like /dev/dm-0. However + * at the device-mapper (kernel) level, nothing is really known about + * LVM (a userspace concept). Therefore we use a convoluted method to + * determine this, by listing out known LVs and checking whether the + * rdev (major/minor) of the device we are passed matches any of them. + * + * Note use of 'stat' instead of 'lstat' so that symlinks are fully + * resolved. + *) +let lv_canonical device + let stat1 = stat device in + let lvs = lvs () in + try + Some ( + List.find ( + fun lv -> + let stat2 = stat lv in + stat1.st_rdev = stat2.st_rdev + ) lvs + ) + with + | Not_found -> None diff --git a/daemon/lvm.mli b/daemon/lvm.mli index 1cf61ecfb..7cde16ebb 100644 --- a/daemon/lvm.mli +++ b/daemon/lvm.mli @@ -19,3 +19,13 @@ val available : unit -> bool val lvs : unit -> string list + +val lv_canonical : string -> string option +(** Convert a non-canonical LV path like /dev/mapper/vg-lv or /dev/dm-0 + to a canonical one. + + On error this raises an exception. There are two possible non-error + return cases: + + Some lv = conversion was successful, returning the canonical LV + None = input path was not an LV, it could not be made canonical *) diff --git a/docs/C_SOURCE_FILES b/docs/C_SOURCE_FILES index b4f085699..d720f43ff 100644 --- a/docs/C_SOURCE_FILES +++ b/docs/C_SOURCE_FILES @@ -104,7 +104,6 @@ daemon/fallocate.c daemon/file.c daemon/fill.c daemon/find.c -daemon/findfs.c daemon/format.c daemon/fs-min-size.c daemon/fsck.c diff --git a/generator/actions_core.ml b/generator/actions_core.ml index b1e2559e0..0a967f76d 100644 --- a/generator/actions_core.ml +++ b/generator/actions_core.ml @@ -5746,6 +5746,7 @@ returns true iff this is the case." }; { defaults with name = "findfs_uuid"; added = (1, 5, 3); style = RString (RDevice, "device"), [String (PlainString, "uuid")], []; + impl = OCaml "Findfs.findfs_uuid"; shortdesc = "find a filesystem by UUID"; longdesc = "\ This command searches the filesystems and returns the one @@ -5757,6 +5758,7 @@ To find the UUID of a filesystem, use C<guestfs_vfs_uuid>." }; { defaults with name = "findfs_label"; added = (1, 5, 3); style = RString (RDevice, "device"), [String (PlainString, "label")], []; + impl = OCaml "Findfs.findfs_label"; shortdesc = "find a filesystem by label"; longdesc = "\ This command searches the filesystems and returns the one -- 2.13.2
Richard W.M. Jones
2017-Jul-27 19:43 UTC
[Libguestfs] [PATCH v3 19/23] daemon: Reimplement ‘nr_devices’ API in OCaml.
--- daemon/devsparts.c | 15 --------------- daemon/devsparts.ml | 2 ++ daemon/devsparts.mli | 2 ++ generator/actions_core.ml | 1 + 4 files changed, 5 insertions(+), 15 deletions(-) diff --git a/daemon/devsparts.c b/daemon/devsparts.c index 1aacb8e16..12e779326 100644 --- a/daemon/devsparts.c +++ b/daemon/devsparts.c @@ -54,21 +54,6 @@ do_device_index (const char *device) return ret; } -int -do_nr_devices (void) -{ - size_t i; - CLEANUP_FREE_STRING_LIST char **devices = do_list_devices (); - - if (devices == NULL) - return -1; - - for (i = 0; devices[i] != NULL; ++i) - ; - - return (int) i; -} - #define GUESTFSDIR "/dev/disk/guestfs" char ** diff --git a/daemon/devsparts.ml b/daemon/devsparts.ml index 43093aba8..86963899f 100644 --- a/daemon/devsparts.ml +++ b/daemon/devsparts.ml @@ -89,6 +89,8 @@ and add_partitions dev let parts = List.filter (fun part -> String.is_prefix part dev) parts in List.map ((^) "/dev/") parts +let nr_devices () = List.length (list_devices ()) + let part_to_dev part let dev, part = split_device_partition part in if part = 0 then diff --git a/daemon/devsparts.mli b/daemon/devsparts.mli index 4dfaa86e6..8be47e752 100644 --- a/daemon/devsparts.mli +++ b/daemon/devsparts.mli @@ -19,6 +19,8 @@ val list_devices : unit -> string list val list_partitions : unit -> string list +val nr_devices : unit -> int + val part_to_dev : string -> string val part_to_partnum : string -> int diff --git a/generator/actions_core.ml b/generator/actions_core.ml index 0a967f76d..db1411ff8 100644 --- a/generator/actions_core.ml +++ b/generator/actions_core.ml @@ -7432,6 +7432,7 @@ See also C<guestfs_list_devices>, C<guestfs_part_to_dev>." }; { defaults with name = "nr_devices"; added = (1, 19, 15); + impl = OCaml "Devsparts.nr_devices"; style = RInt "nrdisks", [], []; tests = [ InitEmpty, Always, TestResult ( -- 2.13.2
Richard W.M. Jones
2017-Jul-27 19:43 UTC
[Libguestfs] [PATCH v3 20/23] daemon: Reimplement ‘md_detail’ API in OCaml.
--- daemon/md.c | 66 ----------------------------------------------- daemon/md.ml | 34 ++++++++++++++++++++++++ daemon/md.mli | 1 + generator/actions_core.ml | 1 + 4 files changed, 36 insertions(+), 66 deletions(-) diff --git a/daemon/md.c b/daemon/md.c index 55461b95a..973418285 100644 --- a/daemon/md.c +++ b/daemon/md.c @@ -216,72 +216,6 @@ do_md_create (const char *name, char *const *devices, #pragma GCC diagnostic pop #endif -char ** -do_md_detail (const char *md) -{ - size_t i; - int r; - - CLEANUP_FREE char *out = NULL, *err = NULL; - CLEANUP_FREE_STRING_LIST char **lines = NULL; - - CLEANUP_FREE_STRINGSBUF DECLARE_STRINGSBUF (ret); - - const char *mdadm[] = { "mdadm", "-D", "--export", md, NULL }; - r = commandv (&out, &err, mdadm); - if (r == -1) { - reply_with_error ("%s", err); - return NULL; - } - - /* Split the command output into lines */ - lines = split_lines (out); - if (lines == NULL) - return NULL; - - /* Parse the output of mdadm -D --export: - * MD_LEVEL=raid1 - * MD_DEVICES=2 - * MD_METADATA=1.0 - * MD_UUID=cfa81b59:b6cfbd53:3f02085b:58f4a2e1 - * MD_NAME=localhost.localdomain:0 - */ - for (i = 0; lines[i] != NULL; ++i) { - char *line = lines[i]; - - /* Skip blank lines (shouldn't happen) */ - if (line[0] == '\0') continue; - - /* Split the line in 2 at the equals sign */ - char *eq = strchr (line, '='); - if (eq) { - *eq = '\0'; eq++; - - /* Remove the MD_ prefix from the key and translate the remainder to lower - * case */ - if (STRPREFIX (line, "MD_")) { - line += 3; - for (char *j = line; *j != '\0'; j++) { - *j = c_tolower (*j); - } - } - - /* Add the key/value pair to the output */ - if (add_string (&ret, line) == -1 || - add_string (&ret, eq) == -1) return NULL; - } else { - /* Ignore lines with no equals sign (shouldn't happen). Log to stderr so - * it will show up in LIBGUESTFS_DEBUG. */ - fprintf (stderr, "md-detail: unexpected mdadm output ignored: %s", line); - } - } - - if (end_stringsbuf (&ret) == -1) - return NULL; - - return take_stringsbuf (&ret); -} - int do_md_stop (const char *md) { diff --git a/daemon/md.ml b/daemon/md.ml index caf87cf8f..1fd00ebb8 100644 --- a/daemon/md.ml +++ b/daemon/md.ml @@ -46,3 +46,37 @@ let list_md_devices () (* Return the list sorted. *) sort_device_names devs + +let md_detail md + let out = command "mdadm" ["-D"; "--export"; md] in + + (* Split the command output into lines. *) + let out = String.trim out in + let lines = String.nsplit "\n" out in + let lines = List.filter ((<>) "") lines in + + (* Parse the output of mdadm -D --export: + * MD_LEVEL=raid1 + * MD_DEVICES=2 + * MD_METADATA=1.0 + * MD_UUID=cfa81b59:b6cfbd53:3f02085b:58f4a2e1 + * MD_NAME=localhost.localdomain:0 + *) + List.map ( + fun line -> + (* Split the line at the equals sign. *) + let key, value = String.split "=" line in + + (* Remove the MD_ prefix from the key and translate the + * remainder to lower case. + *) + let key + if String.is_prefix key "MD_" then + String.sub key 3 (String.length key - 3) + else + key in + let key = String.lowercase_ascii key in + + (* Add the key/value pair to the output. *) + (key, value) + ) lines diff --git a/daemon/md.mli b/daemon/md.mli index 56b6ea65e..8f0c79a7f 100644 --- a/daemon/md.mli +++ b/daemon/md.mli @@ -17,3 +17,4 @@ *) val list_md_devices : unit -> string list +val md_detail : string -> (string * string) list diff --git a/generator/actions_core.ml b/generator/actions_core.ml index db1411ff8..070a1c641 100644 --- a/generator/actions_core.ml +++ b/generator/actions_core.ml @@ -6606,6 +6606,7 @@ List all Linux md devices." }; { defaults with name = "md_detail"; added = (1, 15, 6); style = RHashtable (RPlainString, RPlainString, "info"), [String (Device, "md")], []; + impl = OCaml "Md.md_detail"; optional = Some "mdadm"; shortdesc = "obtain metadata for an MD device"; longdesc = "\ -- 2.13.2
Richard W.M. Jones
2017-Jul-27 19:43 UTC
[Libguestfs] [PATCH v3 21/23] daemon: Reimplement ‘realpath’ API in OCaml.
--- daemon/Makefile.am | 1 - daemon/realpath.c | 50 ----------------------------------------------- daemon/realpath.ml | 4 ++++ daemon/realpath.mli | 1 + docs/C_SOURCE_FILES | 1 - generator/actions_core.ml | 1 + 6 files changed, 6 insertions(+), 52 deletions(-) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index eb8bbb152..1f7cb2277 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -140,7 +140,6 @@ guestfsd_SOURCES = \ pingdaemon.c \ proto.c \ readdir.c \ - realpath.c \ rename.c \ rsync.c \ scrub.c \ diff --git a/daemon/realpath.c b/daemon/realpath.c deleted file mode 100644 index f9d22d28d..000000000 --- a/daemon/realpath.c +++ /dev/null @@ -1,50 +0,0 @@ -/* libguestfs - the guestfsd daemon - * Copyright (C) 2009-2017 Red Hat Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include <config.h> - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> -#include <fcntl.h> -#include <limits.h> -#include <sys/types.h> -#include <dirent.h> - -#include "cloexec.h" - -#include "daemon.h" -#include "optgroups.h" -#include "actions.h" - -char * -do_realpath (const char *path) -{ - char *ret; - - CHROOT_IN; - ret = realpath (path, NULL); - CHROOT_OUT; - if (ret == NULL) { - reply_with_perror ("%s", path); - return NULL; - } - - return ret; /* caller frees */ -} diff --git a/daemon/realpath.ml b/daemon/realpath.ml index e499786a1..8f83a7ad9 100644 --- a/daemon/realpath.ml +++ b/daemon/realpath.ml @@ -20,6 +20,10 @@ open Printf open Std_utils +let realpath path + let chroot = Chroot.create ~name:(sprintf "realpath: %s" path) () in + Chroot.f chroot Unix_utils.Realpath.realpath path + (* The infamous case_sensitive_path function, which works around * the bug in ntfs-3g that all paths are case sensitive even though * the underlying filesystem is case insensitive. diff --git a/daemon/realpath.mli b/daemon/realpath.mli index 371e619fc..3da53c461 100644 --- a/daemon/realpath.mli +++ b/daemon/realpath.mli @@ -16,4 +16,5 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *) +val realpath : string -> string val case_sensitive_path : string -> string diff --git a/docs/C_SOURCE_FILES b/docs/C_SOURCE_FILES index d720f43ff..e47469a6a 100644 --- a/docs/C_SOURCE_FILES +++ b/docs/C_SOURCE_FILES @@ -147,7 +147,6 @@ daemon/parted.c daemon/pingdaemon.c daemon/proto.c daemon/readdir.c -daemon/realpath.c daemon/rename.c daemon/rsync.c daemon/scrub.c diff --git a/generator/actions_core.ml b/generator/actions_core.ml index 070a1c641..4ec83d22d 100644 --- a/generator/actions_core.ml +++ b/generator/actions_core.ml @@ -4197,6 +4197,7 @@ compress- or gzip-compressed. { defaults with name = "realpath"; added = (1, 0, 66); style = RString (RPlainString, "rpath"), [String (Pathname, "path")], []; + impl = OCaml "Realpath.realpath"; tests = [ InitISOFS, Always, TestResultString ( [["realpath"; "/../directory"]], "/directory"), [] -- 2.13.2
Richard W.M. Jones
2017-Jul-27 19:43 UTC
[Libguestfs] [PATCH v3 22/23] daemon: Reimplement ‘part_get_parttype’, ‘part_get_gpt_type’, ‘part_get_gpt_guid’ APIs in OCaml.
--- daemon/parted.c | 176 +++++----------------------------------------- daemon/parted.ml | 74 ++++++++++++++++++- daemon/parted.mli | 5 ++ generator/actions_core.ml | 3 + 4 files changed, 96 insertions(+), 162 deletions(-) diff --git a/daemon/parted.c b/daemon/parted.c index 3ad1ba147..72e1b8420 100644 --- a/daemon/parted.c +++ b/daemon/parted.c @@ -344,45 +344,6 @@ print_partition_table (const char *device, bool add_m_option) return out; } -char * -do_part_get_parttype (const char *device) -{ - CLEANUP_FREE char *out = print_partition_table (device, true); - if (!out) - return NULL; - - CLEANUP_FREE_STRING_LIST char **lines = split_lines (out); - if (!lines) - return NULL; - - if (lines[0] == NULL || STRNEQ (lines[0], "BYT;")) { - reply_with_error ("unknown signature, expected \"BYT;\" as first line of the output: %s", - lines[0] ? lines[0] : "(signature was null)"); - return NULL; - } - - if (lines[1] == NULL) { - reply_with_error ("parted didn't return a line describing the device"); - return NULL; - } - - /* lines[1] is something like: - * "/dev/sda:1953525168s:scsi:512:512:msdos:ATA Hitachi HDT72101;" - */ - char *r = get_table_field (lines[1], 5); - if (r == NULL) - return NULL; - - /* If "loop" return an error (RHBZ#634246). */ - if (STREQ (r, "loop")) { - free (r); - reply_with_error ("not a partitioned device"); - return NULL; - } - - return r; -} - int do_part_get_bootable (const char *device, int partnum) { @@ -553,126 +514,6 @@ do_part_set_gpt_guid (const char *device, int partnum, const char *guid) return 0; } -static char * -sgdisk_info_extract_field (const char *device, int partnum, const char *field, - char *(*extract) (const char *path)) -{ - if (partnum <= 0) { - reply_with_error ("partition number must be >= 1"); - return NULL; - } - - CLEANUP_FREE char *partnum_str = NULL; - if (asprintf (&partnum_str, "%i", partnum) == -1) { - reply_with_perror ("asprintf"); - return NULL; - } - - udev_settle (); - - CLEANUP_FREE char *err = NULL; - int r = commandf (NULL, &err, COMMAND_FLAG_FOLD_STDOUT_ON_STDERR, - "sgdisk", device, "-i", partnum_str, NULL); - - if (r == -1) { - reply_with_error ("%s %s -i %s: %s", "sgdisk", device, partnum_str, err); - return NULL; - } - - udev_settle (); - - CLEANUP_FREE_STRING_LIST char **lines = split_lines (err); - if (lines == NULL) { - reply_with_error ("'%s %s -i %i' returned no output", - "sgdisk", device, partnum); - return NULL; - } - - const int fieldlen = strlen (field); - - /* Parse the output of sgdisk -i: - * Partition GUID code: 21686148-6449-6E6F-744E-656564454649 (BIOS boot partition) - * Partition unique GUID: 19AEC5FE-D63A-4A15-9D37-6FCBFB873DC0 - * First sector: 2048 (at 1024.0 KiB) - * Last sector: 411647 (at 201.0 MiB) - * Partition size: 409600 sectors (200.0 MiB) - * Attribute flags: 0000000000000000 - * Partition name: 'EFI System Partition' - */ - for (char **i = lines; *i != NULL; i++) { - char *line = *i; - - /* Skip blank lines */ - if (line[0] == '\0') continue; - - /* Split the line in 2 at the colon */ - char *colon = strchr (line, ':'); - if (colon) { - if (colon - line == fieldlen && - memcmp (line, field, fieldlen) == 0) - { - /* The value starts after the colon */ - char *value = colon + 1; - - /* Skip any leading whitespace */ - value += strspn (value, " \t"); - - /* Extract the actual information from the field. */ - char *ret = extract (value); - if (ret == NULL) { - /* The extraction function already sends the error. */ - return NULL; - } - - return ret; - } - } else { - /* Ignore lines with no colon. Log to stderr so it will show up in - * LIBGUESTFS_DEBUG. */ - if (verbose) { - fprintf (stderr, "get-gpt-type: unexpected sgdisk output ignored: %s\n", - line); - } - } - } - - /* If we got here it means we didn't find the field */ - reply_with_error ("sgdisk output did not contain '%s'. " - "See LIBGUESTFS_DEBUG output for more details", field); - return NULL; -} - -static char * -extract_uuid (const char *value) -{ - /* The value contains only valid GUID characters */ - const size_t value_len = strspn (value, "-0123456789ABCDEF"); - - char *ret = malloc (value_len + 1); - if (ret == NULL) { - reply_with_perror ("malloc"); - return NULL; - } - - memcpy (ret, value, value_len); - ret[value_len] = '\0'; - return ret; -} - -char * -do_part_get_gpt_type (const char *device, int partnum) -{ - return sgdisk_info_extract_field (device, partnum, - "Partition GUID code", extract_uuid); -} - -char * -do_part_get_gpt_guid (const char *device, int partnum) -{ - return sgdisk_info_extract_field (device, partnum, - "Partition unique GUID", extract_uuid); -} - char * do_part_get_name (const char *device, int partnum) { @@ -836,6 +677,23 @@ do_part_get_mbr_part_type (const char *device, int partnum) return NULL; } +static char * +extract_uuid (const char *value) +{ + /* The value contains only valid GUID characters */ + const size_t value_len = strspn (value, "-0123456789ABCDEF"); + + char *ret = malloc (value_len + 1); + if (ret == NULL) { + reply_with_perror ("malloc"); + return NULL; + } + + memcpy (ret, value, value_len); + ret[value_len] = '\0'; + return ret; +} + char * do_part_get_disk_guid (const char *device) { diff --git a/daemon/parted.ml b/daemon/parted.ml index da31ab5c6..2e8e744d0 100644 --- a/daemon/parted.ml +++ b/daemon/parted.ml @@ -86,18 +86,18 @@ let print_partition_table_machine_readable device let out = String.trim out in let lines = String.nsplit "\n" out in - (* lines[0] is "BYT;", lines[1] is the device line which we ignore, + (* lines[0] is "BYT;", lines[1] is the device line, * lines[2..] are the partitions themselves. *) match lines with - | "BYT;" :: _ :: lines -> lines + | "BYT;" :: device_line :: lines -> device_line, lines | [] | [_] -> failwith "too few rows of output from 'parted print' command" | _ -> failwith "did not see 'BYT;' magic value in 'parted print' command" let part_list device - let lines = print_partition_table_machine_readable device in + let _, lines = print_partition_table_machine_readable device in List.map ( fun line -> @@ -109,3 +109,71 @@ let part_list device failwithf "could not parse row from output of 'parted print' command: %s: %s" line err ) lines + +let part_get_parttype device + let device_line, _ = print_partition_table_machine_readable device in + + (* device_line is something like: + * "/dev/sda:1953525168s:scsi:512:512:msdos:ATA Hitachi HDT72101;" + *) + let fields = String.nsplit ":" device_line in + match fields with + | _::_::_::_::_::"loop"::_ -> (* If "loop" return an error (RHBZ#634246). *) + failwithf "%s: not a partitioned device" device + | _::_::_::_::_::ret::_ -> ret + | _ -> + failwithf "%s: cannot parse the output of parted" device + +let rec part_get_gpt_type device partnum + sgdisk_info_extract_uuid_field device partnum "Partition GUID code" +and part_get_gpt_guid device partnum + sgdisk_info_extract_uuid_field device partnum "Partition unique GUID" + +and sgdisk_info_extract_uuid_field device partnum field + if partnum <= 0 then failwith "partition number must be >= 1"; + + udev_settle (); + + let r, _, err + commandr ~fold_stdout_on_stderr:true + "sgdisk" [ device; "-i"; string_of_int partnum ] in + if r <> 0 then + failwithf "sgdisk: %s" err; + + udev_settle (); + + let err = String.trim err in + let lines = String.nsplit "\n" err in + + (* Parse the output of sgdisk -i: + * Partition GUID code: 21686148-6449-6E6F-744E-656564454649 (BIOS boot partition) + * Partition unique GUID: 19AEC5FE-D63A-4A15-9D37-6FCBFB873DC0 + * First sector: 2048 (at 1024.0 KiB) + * Last sector: 411647 (at 201.0 MiB) + * Partition size: 409600 sectors (200.0 MiB) + * Attribute flags: 0000000000000000 + * Partition name: 'EFI System Partition' + *) + let field_len = String.length field in + let rec loop = function + | [] -> + failwithf "%s: sgdisk output did not contain '%s'" device field + | line :: _ when String.is_prefix line field && + String.length line >= field_len + 2 && + line.[field_len] = ':' -> + let value + String.sub line (field_len+1) (String.length line - field_len - 1) in + + (* Skip any whitespace after the colon. *) + let value = String.triml value in + + (* Extract the UUID. *) + extract_uuid value + + | _ :: lines -> loop lines + in + loop lines + +and extract_uuid value + (* The value contains only valid GUID characters. *) + String.sub value 0 (String.span value "-0123456789ABCDEF") diff --git a/daemon/parted.mli b/daemon/parted.mli index 057d7e8c7..5a77a8779 100644 --- a/daemon/parted.mli +++ b/daemon/parted.mli @@ -25,3 +25,8 @@ type partition = { val part_get_mbr_id : string -> int -> int val part_list : string -> partition list + +val part_get_parttype : string -> string + +val part_get_gpt_type : string -> int -> string +val part_get_gpt_guid : string -> int -> string diff --git a/generator/actions_core.ml b/generator/actions_core.ml index 4ec83d22d..c3421133e 100644 --- a/generator/actions_core.ml +++ b/generator/actions_core.ml @@ -5073,6 +5073,7 @@ Size of the partition in bytes. { defaults with name = "part_get_parttype"; added = (1, 0, 78); style = RString (RPlainString, "parttype"), [String (Device, "device")], []; + impl = OCaml "Parted.part_get_parttype"; tests = [ InitEmpty, Always, TestResultString ( [["part_disk"; "/dev/sda"; "gpt"]; @@ -8247,6 +8248,7 @@ for a useful list of type GUIDs." }; { defaults with name = "part_get_gpt_type"; added = (1, 21, 1); style = RString (RPlainString, "guid"), [String (Device, "device"); Int "partnum"], []; + impl = OCaml "Parted.part_get_gpt_type"; optional = Some "gdisk"; tests = [ InitGPT, Always, TestResultString ( @@ -9067,6 +9069,7 @@ valid GUID." }; { defaults with name = "part_get_gpt_guid"; added = (1, 29, 25); style = RString (RPlainString, "guid"), [String (Device, "device"); Int "partnum"], []; + impl = OCaml "Parted.part_get_gpt_guid"; optional = Some "gdisk"; tests = [ InitGPT, Always, TestResultString ( -- 2.13.2
Richard W.M. Jones
2017-Jul-27 19:43 UTC
[Libguestfs] [PATCH v3 23/23] daemon: Reimplement ‘device_index’ API in OCaml.
--- daemon/devsparts.c | 21 --------------------- daemon/devsparts.ml | 11 +++++++++++ daemon/devsparts.mli | 6 ++---- generator/actions_core.ml | 1 + 4 files changed, 14 insertions(+), 25 deletions(-) diff --git a/daemon/devsparts.c b/daemon/devsparts.c index 12e779326..7c65be1dc 100644 --- a/daemon/devsparts.c +++ b/daemon/devsparts.c @@ -33,27 +33,6 @@ #include "daemon.h" #include "actions.h" -int -do_device_index (const char *device) -{ - size_t i; - int ret = -1; - CLEANUP_FREE_STRING_LIST char **devices = do_list_devices (); - - if (devices == NULL) - return -1; - - for (i = 0; devices[i] != NULL; ++i) { - if (STREQ (device, devices[i])) - ret = (int) i; - } - - if (ret == -1) - reply_with_error ("device not found"); - - return ret; -} - #define GUESTFSDIR "/dev/disk/guestfs" char ** diff --git a/daemon/devsparts.ml b/daemon/devsparts.ml index 86963899f..9f9751b40 100644 --- a/daemon/devsparts.ml +++ b/daemon/devsparts.ml @@ -113,3 +113,14 @@ let is_whole_device device try ignore (stat devpath); true with Unix_error ((ENOENT|ENOTDIR), _, _) -> false + +let device_index device + (* This is the algorithm which was used by the C version. Why + * can't we use drive_index from C_utils? XXX + *) + let rec loop i = function + | [] -> failwithf "%s: device not found" device + | dev :: devices when dev = device -> i + | _ :: devices -> loop (i+1) devices + in + loop 0 (list_devices ()) diff --git a/daemon/devsparts.mli b/daemon/devsparts.mli index 8be47e752..4afb36bec 100644 --- a/daemon/devsparts.mli +++ b/daemon/devsparts.mli @@ -18,10 +18,8 @@ val list_devices : unit -> string list val list_partitions : unit -> string list - -val nr_devices : unit -> int - val part_to_dev : string -> string val part_to_partnum : string -> int - val is_whole_device : string -> bool +val nr_devices : unit -> int +val device_index : string -> int diff --git a/generator/actions_core.ml b/generator/actions_core.ml index c3421133e..ea0735676 100644 --- a/generator/actions_core.ml +++ b/generator/actions_core.ml @@ -7419,6 +7419,7 @@ instead of, or after calling C<guestfs_zero_free_space>." }; { defaults with name = "device_index"; added = (1, 19, 7); style = RInt "index", [String (Device, "device")], []; + impl = OCaml "Devsparts.device_index"; tests = [ InitEmpty, Always, TestResult ( [["device_index"; "/dev/sda"]], "ret == 0"), [] -- 2.13.2
Reasonably Related Threads
- [PATCH v2 00/23] Reimplement many daemon APIs in OCaml.
- [PATCH v3 00/19] Allow APIs to be implemented in OCaml.
- [PATCH 00/27] Reimplement many daemon APIs in OCaml.
- [PATCH v2 00/12] Allow APIs to be implemented in OCaml.
- [PATCH v7 00/29] Reimplement inspection in the daemon.