summaryrefslogtreecommitdiffstats
path: root/erlang
diff options
context:
space:
mode:
authorRichard W.M. Jones <rjones@redhat.com>2011-09-20 18:03:58 +0100
committerRichard W.M. Jones <rjones@redhat.com>2011-09-21 15:21:58 +0100
commit84763d7fca3668c62ee3fe53d0e00a5a672f687b (patch)
tree823b91a69e995438e4af670099408d3285a02176 /erlang
parent917f947590c92318fee2545ba88245d0de012e31 (diff)
downloadlibguestfs-84763d7fca3668c62ee3fe53d0e00a5a672f687b.tar.gz
libguestfs-84763d7fca3668c62ee3fe53d0e00a5a672f687b.tar.xz
libguestfs-84763d7fca3668c62ee3fe53d0e00a5a672f687b.zip
Add Erlang bindings.
Diffstat (limited to 'erlang')
-rw-r--r--erlang/Makefile.am54
-rw-r--r--erlang/README53
-rw-r--r--erlang/erl-guestfs-proto.c278
-rw-r--r--erlang/examples/LICENSE2
-rw-r--r--erlang/examples/Makefile.am39
-rwxr-xr-xerlang/examples/create_disk.erl65
-rw-r--r--erlang/examples/guestfs-erlang.pod133
-rwxr-xr-xerlang/examples/inspect_vm.erl79
8 files changed, 703 insertions, 0 deletions
diff --git a/erlang/Makefile.am b/erlang/Makefile.am
new file mode 100644
index 00000000..933e6026
--- /dev/null
+++ b/erlang/Makefile.am
@@ -0,0 +1,54 @@
+# libguestfs Erlang bindings
+# Copyright (C) 2011 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+include $(top_srcdir)/subdir-rules.mk
+
+generator_built = \
+ guestfs.erl \
+ erl-guestfs.c
+
+EXTRA_DIST = \
+ $(generator_built) \
+ README
+
+if HAVE_ERLANG
+
+erlang_bindir = $(libdir)/erlang/lib/$(PACKAGE_NAME)-$(PACKAGE_VERSION)/ebin
+
+erlang_bin_DATA = guestfs.beam
+
+guestfs.beam: guestfs.erl
+ $(ERLC) +debug_info guestfs.erl
+
+bin_PROGRAMS = erl-guestfs
+
+erl_guestfs_SOURCES = erl-guestfs.c erl-guestfs-proto.c
+
+erl_guestfs_CFLAGS = \
+ -I$(top_srcdir)/src -I$(top_builddir)/src \
+ -I$(srcdir)/../gnulib/lib -I../gnulib/lib \
+ -I$(ERL_INTERFACEDIR)/include \
+ $(WARN_CFLAGS) $(WERROR_CFLAGS)
+
+erl_guestfs_LDADD = \
+ $(ERL_INTERFACEDIR)/lib/liberl_interface.a \
+ $(ERL_INTERFACEDIR)/lib/libei.a \
+ -lpthread \
+ $(top_builddir)/src/libguestfs.la \
+ ../gnulib/lib/libgnu.la
+
+endif
diff --git a/erlang/README b/erlang/README
new file mode 100644
index 00000000..4d710a96
--- /dev/null
+++ b/erlang/README
@@ -0,0 +1,53 @@
+REAMDE for the Erlang bindings to libguestfs
+----------------------------------------------------------------------
+
+To get started, take a look at the man page guestfs-erlang(3) and the
+example programs.
+
+Note that to run the examples, the "erl-guestfs" binary must be on the
+path. To run the examples without installing, do:
+
+ cd erlang
+ PATH=.:$PATH ../run ./examples/create_disk.erl
+ PATH=.:$PATH ../run ./examples/inspect_vm.erl /path/to/vm_disk.img
+
+To simplify the implementation we currently don't support events or
+user cancellation. However it would be pretty simple to add both of
+these. Patches welcome!
+
+Implementation notes
+----------------------------------------------------------------------
+
+These bindings are done using a port that launches an external
+program, following this example:
+http://www.erlang.org/doc/tutorial/erl_interface.html
+
+The reason for this is that the libguestfs API is synchronous and
+calls may take a long time. If we used a linked-in driver then that
+would require us to start a POSIX thread in the Erlang interpreter and
+manage concurrency issues. Using an external process per handle
+simplifies the implementation and makes it much less likely to break
+the Erlang interpreter.
+
+The external C program is called "erl-guestfs". It is normally
+installed in $(bindir), eg. /usr/bin/erl-guestfs.
+
+You need to make sure that the Erlang code and erl-guestfs are the
+same version. The protocol used between the Erlang code (guestfs.erl)
+and erl-guestfs might change in future versions.
+
+There is not really any type checking done in the erl-guestfs binary,
+which means you can get undefined behaviour if you send incorrect
+argument types. Patches welcome to improve this situation.
+
+Licensing
+----------------------------------------------------------------------
+
+Because the C program runs in a separate process, it is licensed as
+GPLv2+. The Erlang part which "links" into the Erlang interpreter is
+licensed as LGPLv2+. We believe this means there is no impediment to
+using libguestfs from closed source Erlang programs.
+
+The example programs are under a separate, very permissive license,
+which basically allows you to do what you want with them. See
+erlang/examples/LICENSE.
diff --git a/erlang/erl-guestfs-proto.c b/erlang/erl-guestfs-proto.c
new file mode 100644
index 00000000..d1eb48b9
--- /dev/null
+++ b/erlang/erl-guestfs-proto.c
@@ -0,0 +1,278 @@
+/* libguestfs Erlang bindings.
+ * Copyright (C) 2011 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <erl_interface.h>
+#include <ei.h>
+
+#include "error.h"
+#include "full-read.h"
+#include "full-write.h"
+
+#include "guestfs.h"
+
+guestfs_h *g;
+
+extern ETERM *dispatch (ETERM *message);
+extern int atom_equals (ETERM *atom, const char *name);
+extern ETERM *make_error (const char *funname);
+extern ETERM *unknown_optarg (const char *funname, ETERM *optargname);
+extern ETERM *unknown_function (ETERM *fun);
+extern ETERM *make_string_list (char **r);
+extern ETERM *make_table (char **r);
+extern ETERM *make_bool (int r);
+extern char **get_string_list (ETERM *term);
+extern int get_bool (ETERM *term);
+extern void free_strings (char **r);
+
+/* This stops things getting out of hand, but also lets us detect
+ * protocol problems quickly.
+ */
+#define MAX_MESSAGE_SIZE (32*1024*1024)
+
+static unsigned char *read_message (void);
+static void write_reply (ETERM *);
+
+int
+main (void)
+{
+ unsigned char *buf;
+ ETERM *ret, *message;
+
+ erl_init (NULL, 0);
+
+ /* This process has a single libguestfs handle. If the Erlang
+ * system creates more than one handle, then more than one of these
+ * processes will be running.
+ */
+ g = guestfs_create ();
+ if (g == NULL)
+ error (EXIT_FAILURE, 0, "could not create guestfs handle");
+
+ guestfs_set_error_handler (g, NULL, NULL);
+
+ while ((buf = read_message ()) != NULL) {
+ message = erl_decode (buf);
+ free (buf);
+
+ ret = dispatch (message);
+ erl_free_term (message);
+
+ write_reply (ret);
+ erl_free_term (ret);
+ }
+
+ guestfs_close (g);
+
+ exit (EXIT_SUCCESS);
+}
+
+/* The Erlang port always sends the length of the buffer as 4
+ * bytes in network byte order, followed by the message buffer.
+ */
+static unsigned char *
+read_message (void)
+{
+ unsigned char buf[4];
+ size_t size;
+ unsigned char *r;
+
+ errno = 0;
+ if (full_read (0, buf, 4) != 4) {
+ if (errno == 0) /* ok - closed connection normally */
+ return NULL;
+ else
+ error (EXIT_FAILURE, errno, "read message size");
+ }
+
+ size = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3];
+
+ if (size > MAX_MESSAGE_SIZE)
+ error (EXIT_FAILURE, 0, "message larger than MAX_MESSAGE_SIZE");
+
+ r = malloc (size);
+ if (r == NULL)
+ error (EXIT_FAILURE, errno, "malloc");
+
+ if (full_read (0, r, size) != size)
+ error (EXIT_FAILURE, errno, "read message content");
+
+ return r;
+}
+
+static void
+write_reply (ETERM *term)
+{
+ size_t size;
+ unsigned char sbuf[4];
+ unsigned char *buf;
+
+ size = erl_term_len (term);
+
+ buf = malloc (size);
+ if (buf == NULL)
+ error (EXIT_FAILURE, errno, "malloc");
+
+ erl_encode (term, buf);
+
+ sbuf[0] = (size >> 24) & 0xff;
+ sbuf[1] = (size >> 16) & 0xff;
+ sbuf[2] = (size >> 8) & 0xff;
+ sbuf[3] = size & 0xff;
+
+ if (full_write (1, sbuf, 4) != 4)
+ error (EXIT_FAILURE, errno, "write message size");
+
+ if (full_write (1, buf, size) != size)
+ error (EXIT_FAILURE, errno, "write message content");
+
+ free (buf);
+}
+
+/* Note that all published Erlang code/examples etc uses strncmp in
+ * a buggy way. This is the right way to do it.
+ */
+int
+atom_equals (ETERM *atom, const char *name)
+{
+ size_t namelen = strlen (name);
+ size_t atomlen = ERL_ATOM_SIZE (atom);
+ if (namelen != atomlen) return 0;
+ return strncmp (ERL_ATOM_PTR (atom), name, atomlen) == 0;
+}
+
+ETERM *
+make_error (const char *funname)
+{
+ ETERM *error = erl_mk_atom ("error");
+ ETERM *msg = erl_mk_string (guestfs_last_error (g));
+ ETERM *num = erl_mk_int (guestfs_last_errno (g));
+ ETERM *t[3] = { error, msg, num };
+ return erl_mk_tuple (t, 3);
+}
+
+ETERM *
+unknown_function (ETERM *fun)
+{
+ ETERM *unknown = erl_mk_atom ("unknown");
+ ETERM *funcopy = erl_copy_term (fun);
+ ETERM *t[2] = { unknown, funcopy };
+ return erl_mk_tuple (t, 2);
+}
+
+ETERM *
+unknown_optarg (const char *funname, ETERM *optargname)
+{
+ ETERM *unknownarg = erl_mk_atom ("unknownarg");
+ ETERM *copy = erl_copy_term (optargname);
+ ETERM *t[2] = { unknownarg, copy };
+ return erl_mk_tuple (t, 2);
+}
+
+ETERM *
+make_string_list (char **r)
+{
+ size_t i, size;
+
+ for (size = 0; r[size] != NULL; ++size)
+ ;
+
+ ETERM *t[size];
+
+ for (i = 0; r[i] != NULL; ++i)
+ t[i] = erl_mk_string (r[i]);
+
+ return erl_mk_list (t, size);
+}
+
+/* Make a hash table. The number of elements returned by the C
+ * function is always even.
+ */
+ETERM *
+make_table (char **r)
+{
+ size_t i, size;
+
+ for (size = 0; r[size] != NULL; ++size)
+ ;
+
+ ETERM *t[size/2];
+ ETERM *a[2];
+
+ for (i = 0; r[i] != NULL; i += 2) {
+ a[0] = erl_mk_string (r[i]);
+ a[1] = erl_mk_string (r[i+1]);
+ t[i/2] = erl_mk_tuple (a, 2);
+ }
+
+ return erl_mk_list (t, size/2);
+}
+
+ETERM *
+make_bool (int r)
+{
+ if (r)
+ return erl_mk_atom ("true");
+ else
+ return erl_mk_atom ("false");
+}
+
+char **
+get_string_list (ETERM *term)
+{
+ ETERM *t;
+ size_t i, size;
+ char **r;
+
+ for (size = 0, t = term; !ERL_IS_EMPTY_LIST (t);
+ size++, t = ERL_CONS_TAIL (t))
+ ;
+
+ r = malloc ((size+1) * sizeof (char *));
+ if (r == NULL)
+ error (EXIT_FAILURE, errno, "malloc");
+
+ for (i = 0, t = term; !ERL_IS_EMPTY_LIST (t); i++, t = ERL_CONS_TAIL (t))
+ r[i] = erl_iolist_to_string (ERL_CONS_HEAD (t));
+ r[size] = NULL;
+
+ return r;
+}
+
+int
+get_bool (ETERM *term)
+{
+ if (atom_equals (term, "true"))
+ return 1;
+ else
+ return 0;
+}
+
+void
+free_strings (char **r)
+{
+ size_t i;
+
+ for (i = 0; r[i] != NULL; ++i)
+ free (r[i]);
+ free (r);
+}
diff --git a/erlang/examples/LICENSE b/erlang/examples/LICENSE
new file mode 100644
index 00000000..555f04d2
--- /dev/null
+++ b/erlang/examples/LICENSE
@@ -0,0 +1,2 @@
+All the examples in the erlang/examples/ subdirectory may be freely
+copied without any restrictions.
diff --git a/erlang/examples/Makefile.am b/erlang/examples/Makefile.am
new file mode 100644
index 00000000..9fa82bb3
--- /dev/null
+++ b/erlang/examples/Makefile.am
@@ -0,0 +1,39 @@
+# libguestfs Erlang examples
+# Copyright (C) 2011 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+EXTRA_DIST = \
+ LICENSE \
+ create_disk.erl \
+ inspect_vm.erl \
+ guestfs-erlang.pod
+
+CLEANFILES = stamp-guestfs-erlang.pod
+
+man_MANS = guestfs-erlang.3
+noinst_DATA = $(top_builddir)/html/guestfs-erlang.3.html
+
+guestfs-erlang.3 $(top_builddir)/html/guestfs-erlang.3.html: stamp-guestfs-erlang.pod
+
+stamp-guestfs-erlang.pod: guestfs-erlang.pod create_disk.erl inspect_vm.erl
+ $(top_builddir)/podwrapper.sh \
+ --section 3 \
+ --man guestfs-erlang.3 \
+ --html $(top_builddir)/html/guestfs-erlang.3.html \
+ --verbatim $(srcdir)/create_disk.erl:@EXAMPLE1@ \
+ --verbatim $(srcdir)/inspect_vm.erl:@EXAMPLE2@ \
+ $<
+ touch $@
diff --git a/erlang/examples/create_disk.erl b/erlang/examples/create_disk.erl
new file mode 100755
index 00000000..231c3989
--- /dev/null
+++ b/erlang/examples/create_disk.erl
@@ -0,0 +1,65 @@
+#!/usr/bin/env escript
+%%! -smp enable -sname create_disk debug verbose
+% Example showing how to create a disk image.
+
+main(_) ->
+ Output = "disk.img",
+
+ {ok, G} = guestfs:create(),
+
+ % Create a raw-format sparse disk image, 512 MB in size.
+ {ok, File} = file:open(Output, [raw, write, binary]),
+ {ok, _} = file:position(File, 512 * 1024 * 1024 - 1),
+ ok = file:write(File, " "),
+ ok = file:close(File),
+
+ % Set the trace flag so that we can see each libguestfs call.
+ ok = guestfs:set_trace(G, true),
+
+ % Set the autosync flag so that the disk will be synchronized
+ % automatically when the libguestfs handle is closed.
+ ok = guestfs:set_autosync(G, true),
+
+ % Attach the disk image to libguestfs.
+ ok = guestfs:add_drive_opts(G, Output,
+ [{format, "raw"}, {readonly, false}]),
+
+ % Run the libguestfs back-end.
+ ok = guestfs:launch(G),
+
+ % Get the list of devices. Because we only added one drive
+ % above, we expect that this list should contain a single
+ % element.
+ [Device] = guestfs:list_devices(G),
+
+ % Partition the disk as one single MBR partition.
+ ok = guestfs:part_disk(G, Device, "mbr"),
+
+ % Get the list of partitions. We expect a single element, which
+ % is the partition we have just created.
+ [Partition] = guestfs:list_partitions(G),
+
+ % Create a filesystem on the partition.
+ ok = guestfs:mkfs(G, "ext4", Partition),
+
+ % Now mount the filesystem so that we can add files. *)
+ ok = guestfs:mount_options(G, "", Partition, "/"),
+
+ % Create some files and directories. *)
+ ok = guestfs:touch(G, "/empty"),
+ Message = "Hello, world\n",
+ ok = guestfs:write(G, "/hello", Message),
+ ok = guestfs:mkdir(G, "/foo"),
+
+ % This one uploads the local file /etc/resolv.conf into
+ % the disk image.
+ ok = guestfs:upload(G, "/etc/resolv.conf", "/foo/resolv.conf"),
+
+ % Because 'autosync' was set (above) we can just close the handle
+ % and the disk contents will be synchronized. You can also do
+ % this manually by calling guestfs:umount_all and guestfs:sync.
+ %
+ % Note also that handles are automatically closed if they are
+ % reaped by the garbage collector. You only need to call close
+ % if you want to close the handle right away.
+ ok = guestfs:close(G).
diff --git a/erlang/examples/guestfs-erlang.pod b/erlang/examples/guestfs-erlang.pod
new file mode 100644
index 00000000..8721318b
--- /dev/null
+++ b/erlang/examples/guestfs-erlang.pod
@@ -0,0 +1,133 @@
+=encoding utf8
+
+=head1 NAME
+
+guestfs-erlang - How to use libguestfs from Erlang
+
+=head1 SYNOPSIS
+
+ {ok, G} = guestfs:create(),
+ ok = guestfs:add_drive_opts(G, Disk,
+ [{format, "raw"}, {readonly, true}]),
+ ok = guestfs:launch(G),
+ [Device] = guestfs:list_devices(G),
+ ok = guestfs:close(G).
+
+=head1 DESCRIPTION
+
+This manual page documents how to call libguestfs from the Erlang
+programming language. This page just documents the differences from
+the C API and gives some examples. If you are not familiar with using
+libguestfs, you also need to read L<guestfs(3)>.
+
+=head2 OPENING AND CLOSING THE HANDLE
+
+The Erlang bindings are implemented using an external program called
+C<erl-guestfs>. This program must be on the current PATH, or else you
+should specify the full path to the program:
+
+ {ok, G} = guestfs:create().
+
+ {ok, G} = guestfs:create("/path/to/erl-guestfs").
+
+C<G> is the libguestfs handle which you should pass to other
+functions.
+
+To close the handle:
+
+ ok = guestfs:close(G).
+
+=head2 FUNCTIONS WITH OPTIONAL ARGUMENTS
+
+For functions that take optional arguments, the first arguments are
+the non-optional ones. The last argument is a list of tuples
+supplying the remaining optional arguments.
+
+ ok = guestfs:add_drive_opts(G, Disk,
+ [{format, "raw"}, {readonly, true}]).
+
+If the last argument would be an empty list, you can also omit it:
+
+ ok = guestfs:add_drive_opts(G, Disk).
+
+=head2 RETURN VALUES AND ERRORS
+
+On success, most functions return a C<Result> term (which could be a
+list, string, tuple etc.). If there is nothing for the function to
+return, then the atom C<ok> is returned.
+
+On error, you would see one of the following tuples:
+
+=over 4
+
+=item C<{error, Msg, Errno}>
+
+This indicates an ordinary error from the function.
+
+C<Msg> is the error message (string) and C<Errno> is the Unix error
+(integer).
+
+C<Errno> can be zero. See L<guestfs(3)/guestfs_last_errno>.
+
+=item C<{unknown, Function}>
+
+This indicates that the function you called is not known. Generally
+this means you are mixing C<erl-guestfs> from another version of
+libguestfs, which you should not do.
+
+C<Function> is the name of the unknown function.
+
+=item C<{unknownarg, Arg}>
+
+This indicates that you called a function with optional arguments,
+with an unknown argument name.
+
+C<Arg> is the name of the unknown argument.
+
+=back
+
+=head1 EXAMPLE 1: CREATE A DISK IMAGE
+
+@EXAMPLE1@
+
+=head1 EXAMPLE 2: INSPECT A VIRTUAL MACHINE DISK IMAGE
+
+@EXAMPLE2@
+
+=head1 SEE ALSO
+
+L<guestfs(3)>,
+L<guestfs-examples(3)>,
+L<guestfs-java(3)>,
+L<guestfs-ocaml(3)>,
+L<guestfs-perl(3)>,
+L<guestfs-python(3)>,
+L<guestfs-recipes(1)>,
+L<guestfs-ruby(3)>,
+L<http://www.erlang.org/>.
+L<http://libguestfs.org/>.
+
+=head1 AUTHORS
+
+Richard W.M. Jones (C<rjones at redhat dot com>)
+
+=head1 COPYRIGHT
+
+Copyright (C) 2011 Red Hat Inc. L<http://libguestfs.org/>
+
+The examples in this manual page may be freely copied, modified and
+distributed without any restrictions.
+
+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
diff --git a/erlang/examples/inspect_vm.erl b/erlang/examples/inspect_vm.erl
new file mode 100755
index 00000000..87d751ca
--- /dev/null
+++ b/erlang/examples/inspect_vm.erl
@@ -0,0 +1,79 @@
+#!/usr/bin/env escript
+%%! -smp enable -sname inspect_vm debug verbose
+% Example showing how to inspect a virtual machine disk.
+
+main([Disk]) ->
+ {ok, G} = guestfs:create(),
+
+ % Attach the disk image read-only to libguestfs.
+ ok = guestfs:add_drive_opts(G, Disk, [{readonly, true}]),
+
+ % Run the libguestfs back-end.
+ ok = guestfs:launch(G),
+
+ % Ask libguestfs to inspect for operating systems.
+ case guestfs:inspect_os(G) of
+ [] ->
+ io:fwrite("inspect_vm: no operating systems found~n"),
+ exit(no_operating_system);
+ Roots ->
+ list_os(G, Roots)
+ end.
+
+list_os(_, []) ->
+ ok;
+list_os(G, [Root|Roots]) ->
+ io:fwrite("Root device: ~s~n", [Root]),
+
+ % Print basic information about the operating system.
+ Product_name = guestfs:inspect_get_product_name(G, Root),
+ io:fwrite(" Product name: ~s~n", [Product_name]),
+ Major = guestfs:inspect_get_major_version(G, Root),
+ Minor = guestfs:inspect_get_minor_version(G, Root),
+ io:fwrite(" Version: ~w.~w~n", [Major, Minor]),
+ Type = guestfs:inspect_get_type(G, Root),
+ io:fwrite(" Type: ~s~n", [Type]),
+ Distro = guestfs:inspect_get_distro(G, Root),
+ io:fwrite(" Distro: ~s~n", [Distro]),
+
+ % Mount up the disks, like guestfish -i.
+ Mps = sort_mps(guestfs:inspect_get_mountpoints(G, Root)),
+ mount_mps(G, Mps),
+
+ % If /etc/issue.net file exists, print up to 3 lines. *)
+ Filename = "/etc/issue.net",
+ Is_file = guestfs:is_file(G, Filename),
+ if Is_file ->
+ io:fwrite("--- ~s ---~n", [Filename]),
+ Lines = guestfs:head_n(G, 3, Filename),
+ write_lines(Lines);
+ true -> ok
+ end,
+
+ % Unmount everything.
+ ok = guestfs:umount_all(G),
+
+ list_os(G, Roots).
+
+% Sort keys by length, shortest first, so that we end up
+% mounting the filesystems in the correct order.
+sort_mps(Mps) ->
+ Cmp = fun ({A,_}, {B,_}) ->
+ length(A) =< length(B) end,
+ lists:sort(Cmp, Mps).
+
+mount_mps(_, []) ->
+ ok;
+mount_mps(G, [{Mp, Dev}|Mps]) ->
+ case guestfs:mount_ro(G, Dev, Mp) of
+ ok -> ok;
+ { error, Msg, _ } ->
+ io:fwrite("~s (ignored)~n", [Msg])
+ end,
+ mount_mps(G, Mps).
+
+write_lines([]) ->
+ ok;
+write_lines([Line|Lines]) ->
+ io:fwrite("~s~n", [Line]),
+ write_lines(Lines).