Compare commits

...

17 Commits

Author SHA1 Message Date
Richard W.M. Jones
d9f4ab8dc6 daemon: inspect: Remove duplicate root mountpoints in /etc/fstab
A customer case was found where /etc/fstab contained multiple root
mountpoints, something like:

  LABEL=System / xfs ...
  LABEL=Boot /boot ext2 ...
  LABEL=System / xfs ...

This causes libguestfs and virt-v2v to fail.  Either (on RHEL 9) we
try to mount the second instance of / which gives an error.  Or (on
upstream kernels) we are able to mount the second instance but then
libguestfs gets confused when trying to unmount them.

In this case as the mounted devices are the same we can just delete
the duplicate.  It's also possible that there could be multiple
non-identical root mountpoints, in which case we have to pick one, and
this code arbitrarily picks the first[*] (but emits a warning).

We don't do anything for non-root mountpoints.

Update common submodule to add 'List.same' function from mlstdutils.

[*] Which one is "the first" depends on what version of ocaml-augeas
we are using.  ocaml-augeas version 0.6 Augeas.matches function
returns entries in reverse order (compared to augeas itself).  This is
fixed in version 0.7:
http://git.annexia.org/?p=ocaml-augeas.git;a=commitdiff;h=b703b92e3d26690aa6f7b822132049ce5435983e

Fixes: https://issues.redhat.com/browse/RHEL-90168
(cherry picked from commit 5441d3dd0c)
2025-05-20 08:42:43 +01:00
Richard W.M. Jones
8c060f1d03 daemon/fstrim.c: Run the fstrim command twice
In upstream kernels, for XFS, you may have to run fstrim twice for it
to be effective.

Fixes: https://issues.redhat.com/browse/RHEL-88508
Related: https://issues.redhat.com/browse/RHEL-88450
Thanks: Eric Sandeen
(cherry picked from commit ca87485eea)
2025-04-30 11:23:30 +01:00
Richard W.M. Jones
5e18522de2 daemon/fstrim.c: Issue sync_disks after fstrim
Thanks: Eric Sandeen
(cherry picked from commit e127edcafc)
2025-04-30 11:23:21 +01:00
Richard W.M. Jones
1b3d8b2f1f daemon: inspect: Resolve Ubuntu 22+ /dev/disk/by-id/dm-uuid-LVM-... in fstab
Linux + LVM supports device names like /dev/disk/by-id/dm-uuid-LVM-
followed by two concatenated UUIDs, firstly for the volume group and
secondly for the logical volume.  We can reverse those to get the
device name (/dev/VG/LV).

fstab entries look like:

  # / was on /dev/vg0/lv-0 during curtin installation
  /dev/disk/by-id/dm-uuid-LVM-OzFWT6NHkstr1hcmrWRRMDGPn9xdZj1YOOycQ533186x288FdU6UubU3OlnWJz6D / ext4 defaults 0 1
  # /usr was on /dev/vg0/lv-1 during curtin installation
  /dev/disk/by-id/dm-uuid-LVM-OzFWT6NHkstr1hcmrWRRMDGPn9xdZj1YZu53m4ZssZ8Jeb3I14RAJwIj5YlHIb9P /usr ext4 defaults 0 1

The upshot of this fix is that we are now able to correctly inspect
and run virt-v2v on Ubuntu 22+ guests with split /usr.  In particular,
we correctly map /etc/fstab entries like the above to LV device names,
which means that /usr merging now works correctly.

Reported-by: Jaroslav Spanko
Thanks: Daniel Berrange
Fixes: https://issues.redhat.com/browse/RHEL-87493
(cherry picked from commit e43ca19129)
(cherry picked from commit 180293338e)
2025-04-29 08:49:22 +01:00
Richard W.M. Jones
44def29794 daemon: Rewrite {pvs,vgs,lvs}-full APIs in OCaml
These were previously written in very convoluted C which had to deal
with parsing the crazy output of the "lvm" command.  In fact the
parsing was so complex that it was generated by the generator.  It's
easier to do this in OCaml.

These are basically legacy APIs.  They cannot be expanded and LVM
already supports many more fields.  We should replace these with APIs
for getting single named fields from LVM.

(cherry picked from commit a73f248369)
(cherry picked from commit 80b2fcb243)
2025-04-29 08:49:15 +01:00
Richard W.M. Jones
fa0d2e8027 Update common submodule
Richard W.M. Jones (1):
      mlstdutils: Implement String.implode

(cherry picked from commit c7930f2140)
2025-04-29 08:49:08 +01:00
Richard W.M. Jones
08a23b0d1f generator: Fix implementation of FUUID for OCaml functions
This was implemented wrongly.  In the XDR protocol, UUIDs are fixed
buffers of length 32.  We can just use memcpy to copy from the OCaml
string to the UUID, but we have to ensure the string length returned
by OCaml is correct (if not we just assert, it's an internal error).

(It didn't even compile before, so we know it was never used).

(cherry picked from commit bcd6b3ec3a)
(cherry picked from commit 1b64c54b8a)
2025-04-29 08:48:56 +01:00
Richard W.M. Jones
cd0091f421 daemon: inspect: Resolve Ubuntu 22+ /dev/disk/by-uuid/ in fstab
Ubuntu 22= uses /dev/disk/by-uuid/ followed by a filesystem UUID in
fstab entries.  Resolve these to mountables.

A typical fstab entry looks like this:

  # /boot was on /dev/vda2 during curtin installation
  /dev/disk/by-uuid/b4e56462-5a64-4272-b76d-f5e58bd8f128 /boot ext4 defaults 0 1

The comment is generated by the installer and appears in the fstab.
This entry would be translated to /dev/sda2.

(cherry picked from commit 7a1ffd744b)
(cherry picked from commit 0d466cb25e)
2025-04-29 08:48:51 +01:00
Richard W.M. Jones
f09cfd0e8b generator: Use new FDevice type for the pvs-full pv_name field
Remove the code which did explicit reverse device name translation,
and use the generator's code instead.

(cherry picked from commit 5a16d1120f)
(cherry picked from commit b306532e7a)
2025-04-29 08:48:45 +01:00
Richard W.M. Jones
c163e8a4f1 generator: Implement struct FDevice type
This acts just like FString except that we do reverse device name
translation on it.  The only use is in the 'pvs-full' API where we
will use it (in a subsequent commit) to reverse translate the pv_name
field (a device name) before returning it from the daemon.

Compare this to the 'pvs' API which also returns a list of device
names, but using the generator's 'RStructList (RDevice,...)'  return
type, where RDevice is similarly reverse translated.

Note in the library-side bindings, because the name has already been
translated in the daemon, we just treat it exactly the same as
FString.  The vast majority of this patch is this mechanical change.

(cherry picked from commit 0ff73a42c7)
(cherry picked from commit 4e27b259c1)
2025-04-29 08:48:39 +01:00
Richard W.M. Jones
734b3874f6 daemon: inspect: Add some debugging of /usr merging
(cherry picked from commit 2d1e894130)
(cherry picked from commit 68cecb6475)
2025-04-29 08:48:33 +01:00
Richard W.M. Jones
9a5892b8df daemon: Fix loongarch64 detection on RHEL 9
$ rpm -q file
file-5.39-16.el9.x86_64
$ file ./test-data/binaries/bin-loongarch64-dynamic
./test-data/binaries/bin-loongarch64-dynamic: ELF 64-bit LSB pie executable, *unknown arch 0x102* version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-loongarch-lp64d.so.1, BuildID[sha1]=7622a1a70bf6e697851ac3790557e1ca686459b5, for GNU/Linux 5.19.0, stripped

Updates: commit 729d6d55ea
(cherry picked from commit 4176b2043f)
2025-03-11 13:43:56 +00:00
Richard W.M. Jones
19c4d1c8b9 lib: Print kernel utsname in debug output
Useful for debugging problems caused by the host kernel.  In
particular we were looking at a problem with passt creating a user
namespace but didn't know what exact kernel was being used.

(cherry picked from commit 31fa712aa0)
2025-03-11 12:45:50 +00:00
Richard W.M. Jones
42ae34115f daemon: New command_out and sh_out APIs
These APIs allow you to capture output from guest commands that
generate more output than the protocol limit allows.

Thanks: Nijin Ashok
Fixes: https://issues.redhat.com/browse/RHEL-80159
(cherry picked from commit 47ac4871b2)
2025-02-24 16:01:19 +00:00
Richard W.M. Jones
0212c0fe22 RHEL: Reject use of libguestfs-winsupport features except for virt-* tools (RHBZ#1240276).
Fix the tests: it doesn't let us use guestfish for arbitrary Windows
edits.
2024-10-30 10:29:50 +00:00
Richard W.M. Jones
1b6dd045a7 RHEL: Disable unsupported remote drive protocols (RHBZ#962113).
This disables support for unsupported remote drive protocols:

 * ftp
 * ftps
 * http
 * https
 * iscsi
 * ssh

Note 'nbd' is not disabled, and of course 'file' works.
2024-10-30 10:29:50 +00:00
Richard W.M. Jones
a5cc27fc95 website: Fix link to latest development version
Fixes: commit 0edaea8f91
2024-10-30 10:29:50 +00:00
50 changed files with 628 additions and 640 deletions

3
.gitignore vendored
View File

@@ -97,7 +97,7 @@ Makefile.in
/daemon/listfs.mli
/daemon/lvm.mli
/daemon/lvm_dm.mli
/daemon/lvm-tokenization.c
/daemon/lvm_full.mli
/daemon/md.mli
/daemon/mount.mli
/daemon/names.c
@@ -423,6 +423,7 @@ Makefile.in
/tests/disks/test-add-disks
/tests/disks/test-qemu-drive-libvirt.xml
/tests/events/test-libvirt-auth-callbacks
/tests/large-command/test-large-command
/tests/mount-local/test-parallel-mount-local
/tests/mountable/test-internal-parse-mountable
/tests/parallel/test-parallel

2
common

Submodule common updated: e9eea65a49...9053edae24

View File

@@ -22,7 +22,6 @@ BUILT_SOURCES = \
caml-stubs.c \
dispatch.c \
names.c \
lvm-tokenization.c \
structs-cleanups.c \
structs-cleanups.h \
stubs-0.c \
@@ -52,6 +51,7 @@ generator_built = \
listfs.mli \
lvm.mli \
lvm_dm.mli \
lvm_full.mli \
md.mli \
mount.mli \
optgroups.ml \
@@ -152,7 +152,6 @@ guestfsd_SOURCES = \
luks.c \
lvm.c \
lvm-filter.c \
lvm-tokenization.c \
md.c \
mkfs.c \
mknod.c \
@@ -298,6 +297,7 @@ SOURCES_MLI = \
listfs.mli \
lvm.mli \
lvm_dm.mli \
lvm_full.mli \
lvm_utils.mli \
md.mli \
mount.mli \
@@ -333,6 +333,7 @@ SOURCES_ML = \
ldm.ml \
link.ml \
lvm.ml \
lvm_full.ml \
lvm_utils.ml \
lvm_dm.ml \
findfs.ml \

View File

@@ -100,6 +100,8 @@ and canonical_elf_arch bits endianness elf_arch =
)
else if substr "LoongArch" then
sprintf "loongarch%s" bits
else if substr "*unknown arch 0x102*" then (* file command on RHEL 9 *)
sprintf "loongarch%s" bits
else
elf_arch

View File

@@ -101,8 +101,19 @@ do_fstrim (const char *path,
ADD_ARG (argv, i, buf);
ADD_ARG (argv, i, NULL);
/* Run the command twice to workaround
* https://issues.redhat.com/browse/RHEL-88450
*/
r = commandv (&out, &err, argv);
if (r == -1) goto error;
if (verbose)
fprintf (stderr, "%s\n", out);
free (out); out = NULL;
free (err); err = NULL;
r = commandv (&out, &err, argv);
if (r == -1) {
error:
/* If the error is about the kernel operation not being supported
* for this filesystem type, then return errno ENOTSUP here.
*/
@@ -116,5 +127,13 @@ do_fstrim (const char *path,
if (verbose)
fprintf (stderr, "%s\n", out);
/* Sync the disks again. In practice we always call fstrim
* expecting that afterwards the results are visible in the qemu
* devices backing the guest. Depending on the Linux filesystem,
* fstrim may issue asynch discard requests, so it's not necessarily
* true that everything has been written out before this point.
*/
sync_disks ();
return 0;
}

View File

@@ -47,18 +47,21 @@ let rec inspect_os () =
* multiple filesystems. Gather all the inspected information in the
* inspect_fs struct of the root filesystem.
*)
eprintf "inspect_os: collect_coreos_inspection_info\n%!";
let fses = collect_coreos_inspection_info fses in
(* Check if the same filesystem was listed twice as root in fses.
* This may happen for the *BSD root partition where an MBR partition
* is a shadow of the real root partition probably /dev/sda5
*)
eprintf "inspect_os: check_for_duplicated_bsd_root\n%!";
let fses = check_for_duplicated_bsd_root fses in
(* For Linux guests with a separate /usr filesystem, merge some of the
* inspected information in that partition to the inspect_fs struct
* of the root filesystem.
*)
eprintf "inspect_os: collect_linux_inspection_info\n%!";
let fses = collect_linux_inspection_info fses in
(* Save what we found in a global variable. *)
@@ -194,6 +197,9 @@ and collect_linux_inspection_info fses =
* or other ways to identify the OS).
*)
and collect_linux_inspection_info_for fses root =
eprintf "inspect_os: collect_linux_inspection_info_for %s\n"
(string_of_location root.fs_location);
let root_fstab =
match root with
| { role = RoleRoot { fstab = f } } -> f
@@ -207,14 +213,21 @@ and collect_linux_inspection_info_for fses root =
(* This checks that this usr is found in the fstab of
* the root filesystem.
*)
eprintf "inspect_os: checking if %s found in fstab of this root\n"
(string_of_location usr_mp);
List.exists (
fun (mountable, _) ->
eprintf "inspect_os: collect_linux_inspection_info_for: \
compare %s = %s\n"
(Mountable.to_string usr_mp.mountable)
(Mountable.to_string mountable);
usr_mp.mountable = mountable
) root_fstab
| _ -> false
) fses in
eprintf "collect_linux_inspection_info_for: merging:\n%sinto:\n%s"
eprintf "inspect_os: collect_linux_inspection_info_for: merging:\n\
%sinto:\n%s"
(string_of_fs usr) (string_of_fs root);
merge usr root;
root

View File

@@ -27,6 +27,7 @@ open Inspect_utils
let re_cciss = PCRE.compile "^/dev/(cciss/c\\d+d\\d+)(?:p(\\d+))?$"
let re_diskbyid = PCRE.compile "^/dev/disk/by-id/.*-part(\\d+)$"
let re_dmuuid = PCRE.compile "^/dev/disk/by-id/dm-uuid-LVM-([0-9a-zA-Z]{32})([0-9a-zA-Z]{32})$"
let re_freebsd_gpt = PCRE.compile "^/dev/(ada{0,1}|vtbd)(\\d+)p(\\d+)$"
let re_freebsd_mbr = PCRE.compile "^/dev/(ada{0,1}|vtbd)(\\d+)s(\\d+)([a-z])$"
let re_hurd_dev = PCRE.compile "^/dev/(h)d(\\d+)s(\\d+)$"
@@ -52,8 +53,10 @@ and check_fstab_aug mdadm_conf root_mountable os_type aug =
let md_map = if mdadm_conf then map_md_devices aug else StringMap.empty in
let path = "/files/etc/fstab/*[label() != '#comment']" in
let entries = aug_matches_noerrors aug path in
List.filter_map (check_fstab_entry md_map root_mountable os_type aug) entries
path |>
aug_matches_noerrors aug |>
List.filter_map (check_fstab_entry md_map root_mountable os_type aug) |>
remove_duplicate_root_mountpoints
and check_fstab_entry md_map root_mountable os_type aug entry =
with_return (fun {return} ->
@@ -394,6 +397,39 @@ and resolve_fstab_device spec md_map os_type =
resolve_diskbyid part default
)
(* Ubuntu 22+ uses /dev/disk/by-uuid/ followed by a UUID. *)
else if String.is_prefix spec "/dev/disk/by-uuid/" then (
debug_matching "diskbyuuid";
let uuid = String.sub spec 18 (String.length spec - 18) in
try
(* Try a filesystem UUID. Unclear if this could be a partition UUID
* as well, but in the Ubuntu guest I tried it was an fs UUID XXX.
*)
Mountable.of_device (Findfs.findfs_uuid uuid)
with
Failure _ -> default
)
(* Ubuntu 22+ uses /dev/disk/by-id/dm-uuid-LVM-... followed by a
* double UUID which identifies an LV. The first part of the UUID
* is the VG UUID. The second part is the LV UUID.
*)
else if PCRE.matches re_dmuuid spec then (
debug_matching "dmuuid";
let vg_uuid_spec = PCRE.sub 1 and lv_uuid_spec = PCRE.sub 2 in
try
(* Get the list of all VGs and LVs. *)
let vgs = Lvm_full.vgs_full () and lvs = Lvm_full.lvs_full () in
(* Find one VG & LV (hopefully) that matches the UUIDs. *)
let vg =
List.find (fun { Structs.vg_uuid } -> vg_uuid = vg_uuid_spec) vgs
and lv =
List.find (fun { Structs.lv_uuid } -> lv_uuid = lv_uuid_spec) lvs in
Mountable.of_device (sprintf "/dev/%s/%s" vg.vg_name lv.lv_name)
with
Failure _ | Not_found -> default
)
else if PCRE.matches re_freebsd_gpt spec then (
debug_matching "FreeBSD GPT";
(* group 1 (type) is not used *)
@@ -570,3 +606,28 @@ and resolve_diskbyid part default =
if is_partition dev then Mountable.of_device dev
else default
)
(* Remove duplicate root mountpoints if they are identical. If
* there are multiple non-identical roots we pick the first and
* emit a warning (RHEL-90168).
*)
and remove_duplicate_root_mountpoints (entries : fstab_entry list) =
let root_entries, non_root_entries =
List.partition (function (_, "/") -> true | _ -> false) entries in
(* If there is one root entry (the normal case) return the list unmodified. *)
if List.length root_entries <= 1 then entries
else (
(* If they are not the same, issue a warning. *)
if not (List.same root_entries) then
eprintf "check_fstab: multiple, non-identical root mountpoints found \
in the /etc/fstab of this guest, picking the first. The \
root entries were: [%s]\n"
(String.concat "; "
(List.map (fun (mountable, mp) ->
sprintf "%s -> %s" (Mountable.to_string mountable) mp)
root_entries)
);
(* Choose the first root entry and return it. *)
List.hd root_entries :: non_root_entries
)

View File

@@ -139,55 +139,6 @@ do_vgs (void)
return convert_lvm_output (out, NULL);
}
/* 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.
*/
guestfs_int_lvm_pv_list *
do_pvs_full (void)
{
guestfs_int_lvm_pv_list *r;
size_t i;
char *din, *dout;
r = parse_command_line_pvs ();
if (r == NULL)
/* parse_command_line_pvs has already called reply_with_error */
return NULL;
/* The pv_name fields contain device names which must be reverse
* translated. The problem here is that the generator does not have
* a "FMountable" field type in types.mli.
*/
for (i = 0; i < r->guestfs_int_lvm_pv_list_len; ++i) {
din = r->guestfs_int_lvm_pv_list_val[i].pv_name;
if (din) {
dout = reverse_device_name_translation (din);
if (!dout) {
/* reverse_device_name_translation has already called reply_with_error*/
/* XXX memory leak here */
return NULL;
}
r->guestfs_int_lvm_pv_list_val[i].pv_name = dout;
free (din);
}
}
return r;
}
guestfs_int_lvm_vg_list *
do_vgs_full (void)
{
return parse_command_line_vgs ();
}
guestfs_int_lvm_lv_list *
do_lvs_full (void)
{
return parse_command_line_lvs ();
}
int
do_pvcreate (const char *device)
{

221
daemon/lvm_full.ml Normal file
View File

@@ -0,0 +1,221 @@
(* guestfs-inspection
* Copyright (C) 2009-2025 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 implements the complicated lvs-full, vgs-full and pvs-full APIs
*
* XXX Deprecate these APIs are replace with APIs for getting single
* named fields from LVM. That will be slower but far more flexible
* and extensible.
*)
open Unix
open Printf
open Std_utils
open Utils
(* LVM UUIDs are basically 32 byte strings with '-' inserted.
* Remove the '-' characters and check it's the right length.
*)
let parse_uuid uuid =
let uuid' =
uuid |> String.explode |> List.filter ((<>) '-') |> String.implode in
if String.length uuid' <> 32 then
failwithf "lvm-full: parse_uuid: unexpected UUID format: %S" uuid;
uuid'
(* Parse the percent fields. These can be empty. *)
let parse_percent pc = if pc = "" then None else Some (float_of_string pc)
(* XXX These must match generator/structs.ml *)
let lvm_pv_cols = [
"pv_name"; (* FString *)
"pv_uuid"; (* FUUID *)
"pv_fmt"; (* FString *)
"pv_size"; (* FBytes *)
"dev_size"; (* FBytes *)
"pv_free"; (* FBytes *)
"pv_used"; (* FBytes *)
"pv_attr"; (* FString (* XXX *) *)
"pv_pe_count"; (* FInt64 *)
"pv_pe_alloc_count"; (* FInt64 *)
"pv_tags"; (* FString *)
"pe_start"; (* FBytes *)
"pv_mda_count"; (* FInt64 *)
"pv_mda_free"; (* FBytes *)
]
let tokenize_pvs = function
| [ pv_name; pv_uuid; pv_fmt; pv_size; dev_size; pv_free;
pv_used; pv_attr; pv_pe_count; pv_pe_alloc_count; pv_tags;
pe_start; pv_mda_count; pv_mda_free ] ->
{ Structs.pv_name = pv_name;
pv_uuid = parse_uuid pv_uuid;
pv_fmt = pv_fmt;
pv_size = Int64.of_string pv_size;
dev_size = Int64.of_string dev_size;
pv_free = Int64.of_string pv_free;
pv_used = Int64.of_string pv_used;
pv_attr = pv_attr;
pv_pe_count = Int64.of_string pv_pe_count;
pv_pe_alloc_count = Int64.of_string pv_pe_alloc_count;
pv_tags = pv_tags;
pe_start = Int64.of_string pe_start;
pv_mda_count = Int64.of_string pv_mda_count;
pv_mda_free = Int64.of_string pv_mda_free }
| fields ->
failwithf "pvs-full: tokenize_pvs: unexpected number of fields: %d"
(List.length fields)
(* XXX These must match generator/structs.ml *)
let lvm_vg_cols = [
"vg_name"; (* FString *)
"vg_uuid"; (* FUUID *)
"vg_fmt"; (* FString *)
"vg_attr"; (* FString (* XXX *) *)
"vg_size"; (* FBytes *)
"vg_free"; (* FBytes *)
"vg_sysid"; (* FString *)
"vg_extent_size"; (* FBytes *)
"vg_extent_count"; (* FInt64 *)
"vg_free_count"; (* FInt64 *)
"max_lv"; (* FInt64 *)
"max_pv"; (* FInt64 *)
"pv_count"; (* FInt64 *)
"lv_count"; (* FInt64 *)
"snap_count"; (* FInt64 *)
"vg_seqno"; (* FInt64 *)
"vg_tags"; (* FString *)
"vg_mda_count"; (* FInt64 *)
"vg_mda_free"; (* FBytes *)
]
let tokenize_vgs = function
| [ vg_name; vg_uuid; vg_fmt; vg_attr; vg_size; vg_free; vg_sysid;
vg_extent_size; vg_extent_count; vg_free_count; max_lv;
max_pv; pv_count; lv_count; snap_count; vg_seqno; vg_tags;
vg_mda_count; vg_mda_free ] ->
{ Structs.vg_name = vg_name;
vg_uuid = parse_uuid vg_uuid;
vg_fmt = vg_fmt;
vg_attr = vg_attr;
vg_size = Int64.of_string vg_size;
vg_free = Int64.of_string vg_free;
vg_sysid = vg_sysid;
vg_extent_size = Int64.of_string vg_extent_size;
vg_extent_count = Int64.of_string vg_extent_count;
vg_free_count = Int64.of_string vg_free_count;
max_lv = Int64.of_string max_lv;
max_pv = Int64.of_string max_pv;
pv_count = Int64.of_string pv_count;
lv_count = Int64.of_string lv_count;
snap_count = Int64.of_string snap_count;
vg_seqno = Int64.of_string vg_seqno;
vg_tags = vg_tags;
vg_mda_count = Int64.of_string vg_mda_count;
vg_mda_free = Int64.of_string vg_mda_free }
| fields ->
failwithf "pvs-full: tokenize_vgs: unexpected number of fields: %d"
(List.length fields)
(* XXX These must match generator/structs.ml *)
let lvm_lv_cols = [
"lv_name"; (* FString *)
"lv_uuid"; (* FUUID *)
"lv_attr"; (* FString (* XXX *) *)
"lv_major"; (* FInt64 *)
"lv_minor"; (* FInt64 *)
"lv_kernel_major"; (* FInt64 *)
"lv_kernel_minor"; (* FInt64 *)
"lv_size"; (* FBytes *)
"seg_count"; (* FInt64 *)
"origin"; (* FString *)
"snap_percent"; (* FOptPercent *)
"copy_percent"; (* FOptPercent *)
"move_pv"; (* FString *)
"lv_tags"; (* FString *)
"mirror_log"; (* FString *)
"modules"; (* FString *)
]
let tokenize_lvs = function
| [ lv_name; lv_uuid; lv_attr; lv_major; lv_minor; lv_kernel_major;
lv_kernel_minor; lv_size; seg_count; origin; snap_percent;
copy_percent; move_pv; lv_tags; mirror_log; modules ] ->
{ Structs.lv_name = lv_name;
lv_uuid = parse_uuid lv_uuid;
lv_attr = lv_attr;
lv_major = Int64.of_string lv_major;
lv_minor = Int64.of_string lv_minor;
lv_kernel_major = Int64.of_string lv_kernel_major;
lv_kernel_minor = Int64.of_string lv_kernel_minor;
lv_size = Int64.of_string lv_size;
seg_count = Int64.of_string seg_count;
origin = origin;
snap_percent = parse_percent snap_percent;
copy_percent = parse_percent copy_percent;
move_pv = move_pv;
lv_tags = lv_tags;
mirror_log = mirror_log;
modules = modules }
| fields ->
failwithf "pvs-full: tokenize_vgs: unexpected number of fields: %d"
(List.length fields)
let rec pvs_full () =
let out = run_lvm_command "pvs" lvm_pv_cols in
let lines = trim_and_split out in
let pvs = List.map tokenize_pvs lines in
pvs
and vgs_full () =
let out = run_lvm_command "vgs" lvm_vg_cols in
let lines = trim_and_split out in
let vgs = List.map tokenize_vgs lines in
vgs
and lvs_full () =
let out = run_lvm_command "lvs" lvm_lv_cols in
let lines = trim_and_split out in
let lvs = List.map tokenize_lvs lines in
lvs
and run_lvm_command typ cols =
let cols = String.concat "," cols in
let cmd = [ typ; "-o"; cols;
"--unbuffered"; "--noheadings"; "--nosuffix";
"--separator"; "\r"; "--units"; "b" ] in
command "lvm" cmd
and trim_and_split out =
(* Split the output into lines. *)
let lines = String.nsplit "\n" out in
(* LVM puts leading whitespace on each line so remove that. *)
let lines = List.map String.triml lines in
(* Ignore any blank lines. *)
let lines = List.filter ((<>) "") lines in
(* Split each line into fields. *)
let lines = List.map (String.nsplit "\r") lines in
lines

View File

@@ -294,6 +294,40 @@ do_command_lines (char *const *argv)
return lines; /* Caller frees. */
}
/* Has one FileOut parameter. */
int
do_command_out (char *const *argv)
{
/* We could in theory spool the command to output as it is running,
* but error handling mid-command, and progress bars would not work
* if we did that. If we encounter a case where this is a problem,
* another approach would be to save the output in a temporary file.
*/
CLEANUP_FREE char *out = NULL;
size_t i, n;
out = do_command (argv);
if (out == NULL)
return -1;
/* Send the reply message. We know that we're not going to fail now
* (except for client cancellation).
*/
reply (NULL, NULL);
n = strlen (out);
for (i = 0; i < n; i += GUESTFS_MAX_CHUNK_SIZE) {
if (send_file_write (out+i, MIN (GUESTFS_MAX_CHUNK_SIZE, n-i)) < 0)
return -1;
notify_progress (i, n);
}
if (send_file_end (0))
return -1;
return 0;
}
char *
do_sh (const char *cmd)
{
@@ -309,3 +343,11 @@ do_sh_lines (const char *cmd)
return do_command_lines ((char **) argv);
}
int
do_sh_out (const char *cmd)
{
const char *argv[] = { "/bin/sh", "-c", cmd, NULL };
return do_command_out ((char **) argv);
}

View File

@@ -111,7 +111,6 @@ daemon/link.c
daemon/ls.c
daemon/luks.c
daemon/lvm-filter.c
daemon/lvm-tokenization.c
daemon/lvm.c
daemon/md.c
daemon/mkfs.c

View File

@@ -109,26 +109,6 @@ image. To exit, type C<exit>.
If you get an error, try enabling debugging (add C<-v> to the command
line). Also make sure that L<libguestfs-test-tool(1)> succeeds.
=head2 Try to open a remote guest image with guestfish.
You may also have to disable libvirt by setting this:
export LIBGUESTFS_BACKEND=direct
If you have a disk image available over HTTP/FTP, try to open it.
guestfish --ro -i --format=raw -a http://www.example.com/disk.img
For SSH you will need to make sure that ssh-agent is set up so you
don't need a password to log in to the remote machine. Then a command
similar to this should work:
guestfish --ro -i --format=raw \
-a ssh://remote.example.com/path/to/disk.img
If you get an error, try enabling debugging (add C<-v> to the command
line). Also make sure that L<libguestfs-test-tool(1)> succeeds.
=head2 Run virt-alignment-scan on all your guests.
Run L<virt-alignment-scan(1)> on guests or disk images:

View File

@@ -131,9 +131,9 @@ To list what is available do:
=head2 Remote drives
Access a remote disk using ssh:
Access a remote disk using NBD:
guestfish -a ssh://example.com/path/to/disk.img
guestfish -a nbd://example.com
=head2 Remote control
@@ -1129,12 +1129,12 @@ L<guestfs(3)/REMOTE STORAGE>>.
On the command line, you can use the I<-a> option to add network
block devices using a URI-style format, for example:
guestfish -a ssh://root@example.com/disk.img
guestfish -a nbd://example.com
URIs I<cannot> be used with the L</add> command. The equivalent
command using the API directly is:
><fs> add /disk.img protocol:ssh server:tcp:example.com username:root
><fs> add /disk.img protocol:nbd server:tcp:example.com
The possible I<-a URI> formats are described below.
@@ -1144,28 +1144,6 @@ The possible I<-a URI> formats are described below.
Add the local disk image (or device) called F<disk.img>.
=head2 B<-a ftp://[user@]example.com[:port]/disk.img>
=head2 B<-a ftps://[user@]example.com[:port]/disk.img>
=head2 B<-a http://[user@]example.com[:port]/disk.img>
=head2 B<-a https://[user@]example.com[:port]/disk.img>
Add a disk located on a remote FTP or HTTP server.
The equivalent API command would be:
><fs> add /disk.img protocol:(ftp|...) server:tcp:example.com
=head2 B<-a iscsi://example.com[:port]/target-iqn-name[/lun]>
Add a disk located on an iSCSI server.
The equivalent API command would be:
><fs> add target-iqn-name/lun protocol:iscsi server:tcp:example.com
=head2 B<-a nbd://example.com[:port]>
=head2 B<-a nbd://example.com[:port]/exportname>
@@ -1200,23 +1178,13 @@ The equivalent API command would be:
><fs> add pool/disk protocol:rbd server:tcp:example.com:port
=head2 B<-a ssh://[user@]example.com[:port]/disk.img>
Add a disk image located on a remote server, accessed using the Secure
Shell (ssh) SFTP protocol. SFTP is supported out of the box by all
major SSH servers.
The equivalent API command would be:
><fs> add /disk protocol:ssh server:tcp:example.com [username:user]
Note that the URIs follow the syntax of
L<RFC 3986|https://tools.ietf.org/html/rfc3986>: in particular, there
are restrictions on the allowed characters for the various components
of the URI. Characters such as C<:>, C<@>, and C</> B<must> be
percent-encoded:
$ guestfish -a ssh://user:pass%40word@example.com/disk.img
$ guestfish -a rbd://user:pass%40word@example.com[:port]/pool/disk
In this case, the password is C<pass@word>.

View File

@@ -40,10 +40,6 @@ function fail ()
$VG guestfish -x -a file://$abs_builddir/test-add-uri.img </dev/null >test-add-uri.out 2>&1
grep -sq 'add_drive ".*/test-add-uri.img"' test-add-uri.out || fail
# curl
$VG guestfish -x -a ftp://user@example.com/disk.img </dev/null >test-add-uri.out 2>&1
grep -sq 'add_drive "/disk.img" "protocol:ftp" "server:tcp:example.com" "username:user"' test-add-uri.out || fail
# NBD
$VG guestfish -x -a nbd://example.com </dev/null >test-add-uri.out 2>&1
grep -sq 'add_drive "" "protocol:nbd" "server:tcp:example.com"' test-add-uri.out || fail
@@ -63,22 +59,5 @@ grep -sq 'add_drive "pool/disk" "protocol:rbd" "server:tcp:example.com:6789"' te
$VG guestfish -x -a rbd:///pool/disk </dev/null >test-add-uri.out 2>&1
grep -sq 'add_drive "pool/disk" "protocol:rbd"' test-add-uri.out || fail
# ssh
$VG guestfish -x -a ssh://example.com/disk.img </dev/null >test-add-uri.out 2>&1
grep -sq 'add_drive "/disk.img" "protocol:ssh" "server:tcp:example.com"' test-add-uri.out || fail
$VG guestfish -x -a ssh://user@example.com/disk.img </dev/null >test-add-uri.out 2>&1
grep -sq 'add_drive "/disk.img" "protocol:ssh" "server:tcp:example.com" "username:user"' test-add-uri.out || fail
$VG guestfish -x -a ssh://user@example.com:2000/disk.img </dev/null >test-add-uri.out 2>&1
grep -sq 'add_drive "/disk.img" "protocol:ssh" "server:tcp:example.com:2000" "username:user"' test-add-uri.out || fail
# iSCSI
$VG guestfish -x -a iscsi://example.com/iqn.2015-12.com.libguestfs:test1/0 </dev/null >test-add-uri.out 2>&1
grep -sq 'add_drive "iqn.2015-12.com.libguestfs:test1/0" "protocol:iscsi" "server:tcp:example.com"' test-add-uri.out || fail
$VG guestfish -x -a iscsi://user:password@example.com/iqn.2015-12.com.libguestfs:test2/0 </dev/null >test-add-uri.out 2>&1
grep -sq 'add_drive "iqn.2015-12.com.libguestfs:test2/0" "protocol:iscsi" "server:tcp:example.com" "username:user" "secret:password"' test-add-uri.out || fail
rm test-add-uri.out
rm test-add-uri.img

View File

@@ -206,7 +206,7 @@ let generate_gobject_struct_header filename typ cols () =
pr " * @%s: An unsigned 64-bit integer\n" n
| n, FInt64 ->
pr " * @%s: A signed 64-bit integer\n" n
| n, FString ->
| n, (FString|FDevice) ->
pr " * @%s: A NULL-terminated string\n" n
| n, FBuffer ->
pr " * @%s: A GByteArray\n" n
@@ -231,7 +231,7 @@ let generate_gobject_struct_header filename typ cols () =
pr " guint64 %s;\n" n
| n, FInt64 ->
pr " gint64 %s;\n" n
| n, FString ->
| n, (FString|FDevice) ->
pr " gchar *%s;\n" n
| n, FBuffer ->
pr " GByteArray *%s;\n" n
@@ -1228,7 +1228,7 @@ guestfs_session_close (GuestfsSession *session, GError **err)
| n, FUUID ->
pr "%smemcpy (%s%s, %s%s, sizeof (%s%s));\n"
indent dst n src n dst n
| n, FString ->
| n, (FString|FDevice) ->
pr "%sif (%s%s) %s%s = g_strdup (%s%s);\n"
indent src n dst n src n
| n, FBuffer ->

View File

@@ -512,7 +512,7 @@ copy_table (char * const * argv)
List.iteri (
fun i col ->
(match col with
| name, FString ->
| name, (FString|FDevice) ->
pr " v = caml_copy_string (%s->%s);\n" typ name
| name, FBuffer ->
pr " v = caml_alloc_initialized_string (%s->%s_len, %s->%s);\n"
@@ -839,9 +839,7 @@ and generate_ocaml_structure_decls () =
pr "type %s = {\n" typ;
List.iter (
function
| name, FString -> pr " %s : string;\n" name
| name, FBuffer -> pr " %s : string;\n" name
| name, FUUID -> pr " %s : string;\n" name
| name, (FString|FDevice|FBuffer|FUUID) -> pr " %s : string;\n" name
| name, (FBytes|FInt64|FUInt64) -> pr " %s : int64;\n" name
| name, (FInt32|FUInt32) -> pr " %s : int32;\n" name
| name, FChar -> pr " %s : char;\n" name

View File

@@ -66,7 +66,7 @@ let generate_xdr () =
pr "struct guestfs_int_%s {\n" typ;
List.iter (function
| name, FChar -> pr " char %s;\n" name
| name, FString -> pr " string %s<>;\n" name
| name, (FString|FDevice) -> pr " string %s<>;\n" name
| name, FBuffer -> pr " opaque %s<>;\n" name
| name, FUUID -> pr " opaque %s[32];\n" name
| name, FInt32 -> pr " int %s;\n" name

View File

@@ -350,22 +350,6 @@ F<filename> is interpreted as a local file or device.
This is the default if the optional protocol parameter
is omitted.
=item C<protocol = \"ftp\"|\"ftps\"|\"http\"|\"https\">
Connect to a remote FTP or HTTP server.
The C<server> parameter must also be supplied - see below.
See also: L<guestfs(3)/FTP AND HTTP>
=item C<protocol = \"iscsi\">
Connect to the iSCSI server.
The C<server> parameter must also be supplied - see below.
The C<username> parameter may be supplied. See below.
The C<secret> parameter may be supplied. See below.
See also: L<guestfs(3)/ISCSI>.
=item C<protocol = \"nbd\">
Connect to the Network Block Device server.
@@ -382,15 +366,6 @@ The C<secret> parameter may be supplied. See below.
See also: L<guestfs(3)/CEPH>.
=item C<protocol = \"ssh\">
Connect to the Secure Shell (ssh) server.
The C<server> parameter must be supplied.
The C<username> parameter may be supplied. See below.
See also: L<guestfs(3)/SSH>.
=back
=item C<server>
@@ -401,11 +376,8 @@ is a list of server(s).
Protocol Number of servers required
-------- --------------------------
file List must be empty or param not used at all
ftp|ftps|http|https Exactly one
iscsi Exactly one
nbd Exactly one
rbd Zero or more
ssh Exactly one
Each list element is a string specifying a server. The string must be
in one of the following formats:
@@ -421,10 +393,10 @@ for the protocol is used (see F</etc/services>).
=item C<username>
For the C<ftp>, C<ftps>, C<http>, C<https>, C<iscsi>, C<rbd> and C<ssh>
protocols, this specifies the remote username.
For the C<rbd>
protocol, this specifies the remote username.
If not given, then the local username is used for C<ssh>, and no authentication
If not given, then no authentication
is attempted for ceph. But note this sometimes may give unexpected results, for
example if using the libvirt backend and if the libvirt backend is configured to
start the qemu appliance as a special user such as C<qemu.qemu>. If in doubt,
@@ -1795,6 +1767,7 @@ See also C<guestfs_lvs_full>, C<guestfs_list_filesystems>." };
{ defaults with
name = "pvs_full"; added = (0, 0, 4);
style = RStructList ("physvols", "lvm_pv"), [], [];
impl = OCaml "Lvm_full.pvs_full";
optional = Some "lvm2";
shortdesc = "list the LVM physical volumes (PVs)";
longdesc = "\
@@ -1804,6 +1777,7 @@ of the L<pvs(8)> command. The \"full\" version includes all fields." };
{ defaults with
name = "vgs_full"; added = (0, 0, 4);
style = RStructList ("volgroups", "lvm_vg"), [], [];
impl = OCaml "Lvm_full.vgs_full";
optional = Some "lvm2";
shortdesc = "list the LVM volume groups (VGs)";
longdesc = "\
@@ -1813,6 +1787,7 @@ of the L<vgs(8)> command. The \"full\" version includes all fields." };
{ defaults with
name = "lvs_full"; added = (0, 0, 4);
style = RStructList ("logvols", "lvm_lv"), [], [];
impl = OCaml "Lvm_full.lvs_full";
optional = Some "lvm2";
shortdesc = "list the LVM logical volumes (LVs)";
longdesc = "\
@@ -2380,6 +2355,19 @@ result into a list of lines.
See also: C<guestfs_sh_lines>" };
{ defaults with
name = "command_out"; added = (1, 55, 6);
style = RErr, [StringList (PlainString, "arguments"); String (FileOut, "output")], [];
progress = true; cancellable = true;
test_excuse = "there is a separate test in the tests directory";
shortdesc = "run a command from the guest filesystem";
longdesc = "\
This is the same as C<guestfs_command>, but streams the output
back, handling the case where the output from the command is
larger than the protocol limit.
See also: C<guestfs_sh_out>" };
{ defaults with
name = "statvfs"; added = (1, 9, 2);
style = RStruct ("statbuf", "statvfs"), [String (Pathname, "path")], [];
@@ -3489,6 +3477,18 @@ into a list of lines.
See also: C<guestfs_command_lines>" };
{ defaults with
name = "sh_out"; added = (1, 55, 6);
style = RErr, [String (PlainString, "command"); String (FileOut, "output")], [];
test_excuse = "there is a separate test in the tests directory";
shortdesc = "run a command via the shell";
longdesc = "\
This is the same as C<guestfs_sh>, but streams the output
back, handling the case where the output from the command is
larger than the protocol limit.
See also: C<guestfs_command_out>" };
{ defaults with
name = "glob_expand"; added = (1, 0, 50);
(* Use Pathname here, and hence ABS_PATH (pattern,...) in

View File

@@ -352,7 +352,7 @@ and generate_structs_pod () =
| name, FInt32 -> pr " int32_t %s;\n" name
| name, (FUInt64|FBytes) -> pr " uint64_t %s;\n" name
| name, FInt64 -> pr " int64_t %s;\n" name
| name, FString -> pr " char *%s;\n" name
| name, (FString|FDevice) -> pr " char *%s;\n" name
| name, FBuffer ->
pr " /* The next two fields describe a byte array. */\n";
pr " uint32_t %s_len;\n" name;
@@ -609,7 +609,7 @@ extern GUESTFS_DLL_PUBLIC void *guestfs_next_private (guestfs_h *g, const char *
List.iter (
function
| name, FChar -> pr " char %s;\n" name
| name, FString -> pr " char *%s;\n" name
| name, (FString|FDevice) -> pr " char *%s;\n" name
| name, FBuffer ->
pr " uint32_t %s_len;\n" name;
pr " char *%s;\n" name
@@ -916,7 +916,7 @@ and generate_client_structs_compare () =
fun { s_name = typ; s_cols = cols } ->
let has_nonnumeric_cols =
let nonnumeric = function
| _,(FString|FUUID|FBuffer) -> true
| _,(FString|FDevice|FUUID|FBuffer) -> true
| _,(FChar|FUInt32|FInt32|FUInt64|FBytes|FInt64|FOptPercent) -> false
in
List.exists nonnumeric cols in
@@ -932,7 +932,7 @@ and generate_client_structs_compare () =
);
List.iter (
function
| name, FString ->
| name, (FString|FDevice) ->
pr " r = strcmp (s1->%s, s2->%s);\n" name name;
pr " if (r != 0) return r;\n"
| name, FBuffer ->
@@ -1001,7 +1001,7 @@ and generate_client_structs_copy () =
fun { s_name = typ; s_cols = cols } ->
let has_boxed_cols =
let boxed = function
| _,(FString|FBuffer) -> true
| _,(FString|FDevice|FBuffer) -> true
| _,(FChar|FUUID|FUInt32|FInt32|FUInt64|FBytes|FInt64|FOptPercent) ->
false
in
@@ -1014,8 +1014,7 @@ and generate_client_structs_copy () =
pr "{\n";
List.iter (
function
| name, FString
| name, FBuffer -> pr " free (s->%s);\n" name
| name, (FString|FDevice|FBuffer) -> pr " free (s->%s);\n" name
| _, FChar
| _, FUUID
| _, FUInt32
@@ -1038,8 +1037,7 @@ and generate_client_structs_copy () =
pr "\n";
List.iter (
function
| name, FString
| name, FBuffer -> pr " out->%s = NULL;\n" name
| name, (FString|FDevice|FBuffer) -> pr " out->%s = NULL;\n" name
| _, FChar
| _, FUUID
| _, FUInt32
@@ -1051,7 +1049,7 @@ and generate_client_structs_copy () =
) cols;
List.iter (
function
| name, FString ->
| name, (FString|FDevice) ->
pr " out->%s = strdup (inp->%s);\n" name name;
pr " if (out->%s == NULL) goto error;\n" name
| name, FBuffer ->
@@ -1234,7 +1232,7 @@ and generate_client_structs_print_c () =
);
List.iter (
function
| name, FString ->
| name, (FString|FDevice) ->
pr " fprintf (dest, \"%%s%s: %%s%%s\", indent, %s->%s, linesep);\n"
name typ name
| name, FUUID ->
@@ -1836,6 +1834,22 @@ and generate_client_actions actions () =
check_args_validity c_name style;
trace_call name c_name style;
(* RHEL *)
if name = "mount" || name = "mount_ro" || name = "mount_options" ||
name = "mount_vfs" then (
pr " if (g->program && !STRPREFIX (g->program, \"virt-\")) {\n";
pr " CLEANUP_FREE char *vfs_type = guestfs_vfs_type (g, mountable);\n";
pr " if (vfs_type && STREQ (vfs_type, \"ntfs\")) {\n";
pr " error (g, \"mount: unsupported filesystem type\");\n";
pr " if (trace_flag)\n";
pr " guestfs_int_trace (g, \"%%s = %%s (error)\",\n";
pr " \"%s\", \"-1\");\n" name;
pr " return %s;\n" (string_of_errcode errcode);
pr " }\n";
pr " }\n";
pr "\n";
);
(* Calculate the total size of all FileIn arguments to pass
* as a progress bar hint.
*)

View File

@@ -119,7 +119,7 @@ namespace Guestfs
List.iter (
function
| name, FChar -> pr " char %s;\n" name
| name, FString -> pr " string %s;\n" name
| name, (FString | FDevice) -> pr " string %s;\n" name
| name, FBuffer ->
pr " uint %s_len;\n" name;
pr " string %s;\n" name

View File

@@ -442,15 +442,37 @@ let generate_daemon_stubs actions () =
pr " ret.%s.%s_val = r;\n" n n;
pr " reply ((xdrproc_t) &xdr_guestfs_%s_ret, (char *) &ret);\n"
name
| RStruct (n, _) ->
| RStruct (n, typ) ->
(* XXX RStruct containing an FDevice field would require
* reverse device name translation. That is not implemented.
* See also RStructList immediately below this.
*)
let cols = (Structs.lookup_struct typ).s_cols in
assert (not (List.exists
(function (_, FDevice) -> true | _ -> false) cols));
pr " struct guestfs_%s_ret ret;\n" name;
pr " ret.%s = *r;\n" n;
pr " reply ((xdrproc_t) xdr_guestfs_%s_ret, (char *) &ret);\n"
name;
pr " xdr_free ((xdrproc_t) xdr_guestfs_%s_ret, (char *) &ret);\n"
name
| RStructList (n, _) ->
| RStructList (n, typ) ->
pr " struct guestfs_%s_ret ret;\n" name;
let cols = (Structs.lookup_struct typ).s_cols in
List.iter (
function
| (fname, FDevice) ->
pr " for (size_t i = 0; i < r->guestfs_int_%s_list_len; ++i) {\n"
typ;
pr " char *field = r->guestfs_int_%s_list_val[i].%s;\n"
typ fname;
pr " char *rr = reverse_device_name_translation (field);\n";
pr " if (!rr) abort ();\n";
pr " free (field);\n";
pr " r->guestfs_int_%s_list_val[i].%s = rr;\n" typ fname;
pr " }\n";
| _ -> ()
) cols;
pr " ret.%s = *r;\n" n;
pr " reply ((xdrproc_t) xdr_guestfs_%s_ret, (char *) &ret);\n"
name;
@@ -584,6 +606,7 @@ let generate_daemon_caml_stubs () =
#include <string.h>
#include <inttypes.h>
#include <errno.h>
#include <assert.h>
#include <caml/alloc.h>
#include <caml/callback.h>
@@ -619,9 +642,12 @@ let generate_daemon_caml_stubs () =
fun i ->
pr " v = Field (retv, %d);\n" i;
function
| n, (FString|FUUID) ->
| n, (FString|FDevice) ->
pr " ret->%s = strdup (String_val (v));\n" n;
pr " if (ret->%s == NULL) return NULL;\n" n
| n, FUUID ->
pr " assert (caml_string_length (v) == sizeof ret->%s);\n" n;
pr " memcpy (ret->%s, String_val (v), sizeof ret->%s);\n" n n
| n, FBuffer ->
pr " ret->%s_len = caml_string_length (v);\n" n;
pr " ret->%s = strdup (String_val (v));\n" n;
@@ -925,196 +951,6 @@ let generate_daemon_dispatch () =
pr "}\n";
pr "\n"
let generate_daemon_lvm_tokenization () =
generate_header CStyle GPLv2plus;
pr "\
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <errno.h>
#include <rpc/types.h>
#include <rpc/xdr.h>
#include \"daemon.h\"
#include \"c-ctype.h\"
#include \"guestfs_protocol.h\"
#include \"actions.h\"
#include \"optgroups.h\"
";
(* LVM columns and tokenization functions. *)
(* XXX This generates crap code. We should rethink how we
* do this parsing.
*)
List.iter (
function
| typ, cols ->
pr "static const char lvm_%s_cols[] = \"%s\";\n"
typ (String.concat "," (List.map fst cols));
pr "\n";
pr "static int lvm_tokenize_%s (char *str, guestfs_int_lvm_%s *r)\n" typ typ;
pr "{\n";
pr " char *tok, *p, *next;\n";
pr " size_t i, j;\n";
pr "\n";
(*
pr " fprintf (stderr, \"%%s: <<%%s>>\\n\", __func__, str);\n";
pr "\n";
*)
pr " if (!str) {\n";
pr " fprintf (stderr, \"%%s: failed: passed a NULL string\\n\", __func__);\n";
pr " return -1;\n";
pr " }\n";
pr " if (!*str || c_isspace (*str)) {\n";
pr " fprintf (stderr, \"%%s: failed: passed a empty string or one beginning with whitespace\\n\", __func__);\n";
pr " return -1;\n";
pr " }\n";
pr " tok = str;\n";
List.iter (
fun (name, coltype) ->
pr " if (!tok) {\n";
pr " fprintf (stderr, \"%%s: failed: string finished early, around token %%s\\n\", __func__, \"%s\");\n" name;
pr " return -1;\n";
pr " }\n";
pr " p = strchrnul (tok, '\\r');\n";
pr " if (*p) next = p+1; else next = NULL;\n";
pr " *p = '\\0';\n";
(match coltype with
| FString ->
pr " r->%s = strdup (tok);\n" name;
pr " if (r->%s == NULL) {\n" name;
pr " perror (\"strdup\");\n";
pr " return -1;\n";
pr " }\n"
| FUUID ->
pr " for (i = j = 0; i < 32; ++j) {\n";
pr " if (tok[j] == '\\0') {\n";
pr " fprintf (stderr, \"%%s: failed to parse UUID from '%%s'\\n\", __func__, tok);\n";
pr " return -1;\n";
pr " } else if (tok[j] != '-')\n";
pr " r->%s[i++] = tok[j];\n" name;
pr " }\n";
| FBytes ->
pr " if (sscanf (tok, \"%%\" SCNi64, &r->%s) != 1) {\n" name;
pr " fprintf (stderr, \"%%s: failed to parse size '%%s' from token %%s\\n\", __func__, tok, \"%s\");\n" name;
pr " return -1;\n";
pr " }\n";
| FInt64 ->
pr " if (sscanf (tok, \"%%\" SCNi64, &r->%s) != 1) {\n" name;
pr " fprintf (stderr, \"%%s: failed to parse int '%%s' from token %%s\\n\", __func__, tok, \"%s\");\n" name;
pr " return -1;\n";
pr " }\n";
| FOptPercent ->
pr " if (tok[0] == '\\0')\n";
pr " r->%s = -1;\n" name;
pr " else if (sscanf (tok, \"%%f\", &r->%s) != 1) {\n" name;
pr " fprintf (stderr, \"%%s: failed to parse float '%%s' from token %%s\\n\", __func__, tok, \"%s\");\n" name;
pr " return -1;\n";
pr " }\n";
| FBuffer | FInt32 | FUInt32 | FUInt64 | FChar ->
assert false (* can never be an LVM column *)
);
pr " tok = next;\n";
) cols;
pr " if (tok != NULL) {\n";
pr " fprintf (stderr, \"%%s: failed: extra tokens at end of string\\n\", __func__);\n";
pr " return -1;\n";
pr " }\n";
pr " return 0;\n";
pr "}\n";
pr "\n";
pr "guestfs_int_lvm_%s_list *\n" typ;
pr "parse_command_line_%ss (void)\n" typ;
pr "{\n";
pr " char *out, *err;\n";
pr " char *p, *pend;\n";
pr " int r, i;\n";
pr " guestfs_int_lvm_%s_list *ret;\n" typ;
pr " void *newp;\n";
pr "\n";
pr " ret = malloc (sizeof *ret);\n";
pr " if (!ret) {\n";
pr " reply_with_perror (\"malloc\");\n";
pr " return NULL;\n";
pr " }\n";
pr "\n";
pr " ret->guestfs_int_lvm_%s_list_len = 0;\n" typ;
pr " ret->guestfs_int_lvm_%s_list_val = NULL;\n" typ;
pr "\n";
pr " r = command (&out, &err,\n";
pr " \"lvm\", \"%ss\",\n" typ;
pr " \"-o\", lvm_%s_cols, \"--unbuffered\", \"--noheadings\",\n" typ;
pr " \"--nosuffix\", \"--separator\", \"\\r\", \"--units\", \"b\", NULL);\n";
pr " if (r == -1) {\n";
pr " reply_with_error (\"%%s\", err);\n";
pr " free (out);\n";
pr " free (err);\n";
pr " free (ret);\n";
pr " return NULL;\n";
pr " }\n";
pr "\n";
pr " free (err);\n";
pr "\n";
pr " /* Tokenize each line of the output. */\n";
pr " p = out;\n";
pr " i = 0;\n";
pr " while (p) {\n";
pr " pend = strchr (p, '\\n'); /* Get the next line of output. */\n";
pr " if (pend) {\n";
pr " *pend = '\\0';\n";
pr " pend++;\n";
pr " }\n";
pr "\n";
pr " while (*p && c_isspace (*p)) /* Skip any leading whitespace. */\n";
pr " p++;\n";
pr "\n";
pr " if (!*p) { /* Empty line? Skip it. */\n";
pr " p = pend;\n";
pr " continue;\n";
pr " }\n";
pr "\n";
pr " /* Allocate some space to store this next entry. */\n";
pr " newp = realloc (ret->guestfs_int_lvm_%s_list_val,\n" typ;
pr " sizeof (guestfs_int_lvm_%s) * (i+1));\n" typ;
pr " if (newp == NULL) {\n";
pr " reply_with_perror (\"realloc\");\n";
pr " free (ret->guestfs_int_lvm_%s_list_val);\n" typ;
pr " free (ret);\n";
pr " free (out);\n";
pr " return NULL;\n";
pr " }\n";
pr " ret->guestfs_int_lvm_%s_list_val = newp;\n" typ;
pr "\n";
pr " /* Tokenize the next entry. */\n";
pr " r = lvm_tokenize_%s (p, &ret->guestfs_int_lvm_%s_list_val[i]);\n" typ typ;
pr " if (r == -1) {\n";
pr " reply_with_error (\"failed to parse output of '%ss' command\");\n" typ;
pr " free (ret->guestfs_int_lvm_%s_list_val);\n" typ;
pr " free (ret);\n";
pr " free (out);\n";
pr " return NULL;\n";
pr " }\n";
pr "\n";
pr " ++i;\n";
pr " p = pend;\n";
pr " }\n";
pr "\n";
pr " ret->guestfs_int_lvm_%s_list_len = i;\n" typ;
pr "\n";
pr " free (out);\n";
pr " return ret;\n";
pr "}\n"
) ["pv", lvm_pv_cols; "vg", lvm_vg_cols; "lv", lvm_lv_cols]
(* Generate a list of function names, for debugging in the daemon.. *)
let generate_daemon_names () =
generate_header CStyle GPLv2plus;

View File

@@ -23,7 +23,6 @@ val generate_daemon_caml_stubs : unit -> unit
val generate_daemon_caml_callbacks_ml : unit -> unit
val generate_daemon_caml_interface : string -> 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

View File

@@ -286,7 +286,7 @@ and generate_erlang_structs () =
List.iteri (
fun i col ->
(match col with
| name, FString ->
| name, (FString|FDevice) ->
pr " if (ei_x_encode_string (buff, %s->%s) != 0) return -1;\n" typ name
| name, FBuffer ->
pr " if (ei_x_encode_string_len (buff, %s->%s, %s->%s_len) != 0) return -1;\n"

View File

@@ -248,7 +248,7 @@ func return_hashtable (argv **C.char) map[string]string {
let n = String.capitalize_ascii n in
match field with
| FChar -> pr " %s byte\n" n
| FString -> pr " %s string\n" n
| FString | FDevice -> pr " %s string\n" n
| FBuffer -> pr " %s []byte\n" n
| FUInt32 -> pr " %s uint32\n" n
| FInt32 -> pr " %s int32\n" n
@@ -267,7 +267,7 @@ func return_hashtable (argv **C.char) map[string]string {
let gon = String.capitalize_ascii n in
match field with
| FChar -> pr " r.%s = byte (c.%s)\n" gon n
| FString -> pr " r.%s = C.GoString (c.%s)\n" gon n
| FString | FDevice -> pr " r.%s = C.GoString (c.%s)\n" gon n
| FBuffer ->
pr " r.%s = C.GoBytes (unsafe.Pointer (c.%s), C.int (c.%s_len))\n"
gon n n

View File

@@ -560,6 +560,7 @@ public class %s {
List.iter (
function
| name, FString
| name, FDevice
| name, FUUID
| name, FBuffer -> pr " public String %s;\n" name
| name, (FBytes|FUInt64|FInt64) -> pr " public long %s;\n" name
@@ -947,7 +948,7 @@ and generate_java_struct_return typ jtyp cols =
pr " jr = (*env)->AllocObject (env, cl);\n";
List.iter (
function
| name, FString ->
| name, (FString|FDevice) ->
pr " fl = (*env)->GetFieldID (env, cl, \"%s\", \"Ljava/lang/String;\");\n" name;
pr " (*env)->SetObjectField (env, jr, fl, (*env)->NewStringUTF (env, r->%s));\n" name;
| name, FUUID ->
@@ -997,7 +998,7 @@ and generate_java_struct_list_return typ jtyp cols =
fun (name, ftyp) ->
(* Get the field ID in 'fl'. *)
let java_field_type = match ftyp with
| FString | FUUID | FBuffer -> "Ljava/lang/String;"
| FString | FDevice | FUUID | FBuffer -> "Ljava/lang/String;"
| FBytes | FUInt64 | FInt64 -> "J"
| FUInt32 | FInt32 -> "I"
| FOptPercent -> "F"
@@ -1007,7 +1008,7 @@ and generate_java_struct_list_return typ jtyp cols =
(* Assign the value to this field. *)
match ftyp with
| FString ->
| FString | FDevice ->
pr " (*env)->SetObjectField (env, jfl, fl,\n";
pr " (*env)->NewStringUTF (env, r->val[i].%s));\n" name;
| FUUID ->

View File

@@ -824,7 +824,7 @@ push_event (lua_State *L, uint64_t event)
(match field with
| FChar ->
pr " lua_pushlstring (L, &v->%s, 1);\n" n
| FString ->
| FString | FDevice ->
pr " lua_pushstring (L, v->%s);\n" n
| FBuffer ->
pr " lua_pushlstring (L, v->%s, v->%s_len);\n" n n

View File

@@ -147,8 +147,6 @@ Run it from the top source directory using the command
Daemon.generate_daemon_optgroups_ml;
output_to "daemon/optgroups.mli"
Daemon.generate_daemon_optgroups_mli;
output_to "daemon/lvm-tokenization.c"
Daemon.generate_daemon_lvm_tokenization;
output_to "daemon/structs-cleanups.c"
Daemon.generate_daemon_structs_cleanups_c;
output_to "daemon/structs-cleanups.h"

View File

@@ -607,7 +607,7 @@ and generate_perl_struct_list_code typ cols name style =
pr " hv = newHV ();\n";
List.iter (
function
| name, FString ->
| name, (FString|FDevice) ->
pr " (void) hv_store (hv, \"%s\", %d, newSVpv (r->val[i].%s, 0), 0);\n"
name (String.length name) name
| name, FUUID ->
@@ -645,7 +645,7 @@ and generate_perl_struct_code typ cols name style =
pr " PUSHs (sv_2mortal (newSVpv (\"%s\", 0)));\n" name;
match col with
| name, FString ->
| name, (FString|FDevice) ->
pr " PUSHs (sv_2mortal (newSVpv (r->%s, 0)));\n"
name
| name, FBuffer ->

View File

@@ -616,7 +616,7 @@ and generate_php_struct_code typ cols =
pr " array_init (return_value);\n";
List.iter (
function
| name, FString ->
| name, (FString|FDevice) ->
pr " guestfs_add_assoc_string (return_value, \"%s\", r->%s, 1);\n" name name
| name, FBuffer ->
pr " guestfs_add_assoc_stringl (return_value, \"%s\", r->%s, r->%s_len, 1);\n"
@@ -650,7 +650,7 @@ and generate_php_struct_list_code typ cols =
pr " array_init (z_elem);\n";
List.iter (
function
| name, FString ->
| name, (FString|FDevice) ->
pr " guestfs_add_assoc_string (z_elem, \"%s\", r->val[c].%s, 1);\n"
name name
| name, FBuffer ->

View File

@@ -518,6 +518,8 @@ let proc_nr = [
513, "inspect_get_build_id";
514, "findfs_partuuid";
515, "findfs_partlabel";
516, "command_out";
517, "sh_out";
]
(* End of list. If adding a new entry, add it at the end of the list

View File

@@ -168,7 +168,7 @@ and generate_python_structs () =
pr " return NULL;\n";
List.iter (
function
| name, FString ->
| name, (FString|FDevice) ->
pr " value = guestfs_int_py_fromstring (%s->%s);\n" typ name;
pr " if (value == NULL)\n";
pr " goto err;\n";

View File

@@ -526,7 +526,7 @@ and generate_ruby_struct_code typ cols =
pr " volatile VALUE rv = rb_hash_new ();\n";
List.iter (
function
| name, FString ->
| name, (FString|FDevice) ->
pr " rb_hash_aset (rv, rb_str_new2 (\"%s\"), rb_str_new2 (r->%s));\n" name name
| name, FBuffer ->
pr " rb_hash_aset (rv, rb_str_new2 (\"%s\"), rb_str_new (r->%s, r->%s_len));\n" name name name
@@ -556,7 +556,7 @@ and generate_ruby_struct_list_code typ cols =
pr " volatile VALUE hv = rb_hash_new ();\n";
List.iter (
function
| name, FString ->
| name, (FString|FDevice) ->
pr " rb_hash_aset (hv, rb_str_new2 (\"%s\"), rb_str_new2 (r->val[i].%s));\n" name name
| name, FBuffer ->
pr " rb_hash_aset (hv, rb_str_new2 (\"%s\"), rb_str_new (r->val[i].%s, r->val[i].%s_len));\n" name name name

View File

@@ -115,7 +115,7 @@ extern \"C\" {
List.iter (
function
| n, FChar -> pr " pub %s: i8,\n" n
| n, FString -> pr " pub %s: String,\n" n
| n, (FString|FDevice) -> pr " pub %s: String,\n" n
| n, FBuffer -> pr " pub %s: Vec<u8>,\n" n
| n, FUInt32 -> pr " pub %s: u32,\n" n
| n, FInt32 -> pr " pub %s: i32,\n" n
@@ -130,7 +130,7 @@ extern \"C\" {
List.iter (
function
| n, FChar -> pr " %s: c_char,\n" n
| n, FString -> pr " %s: *const c_char,\n" n
| n, (FString|FDevice) -> pr " %s: *const c_char,\n" n
| n, FBuffer ->
pr " %s_len: usize,\n" n;
pr " %s: *const c_char,\n" n;
@@ -154,7 +154,7 @@ extern \"C\" {
match x with
| n, FChar ->
pr "%s: (*raw).%s as i8,\n" n n;
| n, FString ->
| n, (FString|FDevice) ->
pr "%s: char_ptr_to_string((*raw).%s)?,\n" n n;
| n, FBuffer ->
pr "%s: slice::from_raw_parts((*raw).%s as *const u8, (*raw).%s_len).to_vec(),\n" n n n

View File

@@ -31,11 +31,9 @@ type struc = {
s_unused : unit; (* Silences warning 23 when using 'defaults with ...' *)
}
(* Because we generate extra parsing code for LVM command line tools,
* we have to pull out the LVM columns separately here.
*)
(* XXX These must match daemon/lvm_full.ml *)
let lvm_pv_cols = [
"pv_name", FString;
"pv_name", FDevice;
"pv_uuid", FUUID;
"pv_fmt", FString;
"pv_size", FBytes;

View File

@@ -34,13 +34,6 @@ type struc = {
val structs : struc list
(** List of structures. *)
val lvm_pv_cols : cols
val lvm_vg_cols : cols
val lvm_lv_cols : cols
(** These are exported to the daemon code generator where they are
used to generate code for parsing the output of commands like
[lvs]. One day replace this with liblvm API calls. *)
val lookup_struct : string -> struc
(** Lookup a struct by name. *)

View File

@@ -215,6 +215,7 @@ let defaults = { name = "";
type field =
| FChar (* C 'char' (really, a 7 bit byte). *)
| FString (* nul-terminated ASCII string, NOT NULL. *)
| FDevice (* device name, needs reverse transl. *)
| FBuffer (* opaque buffer of bytes, (char *, int) pair *)
| FUInt32
| FInt32

View File

@@ -413,6 +413,7 @@ val defaults : action
type field =
| FChar (** C 'char' (really, a 7 bit byte). *)
| FString (** nul-terminated ASCII string, NOT NULL. *)
| FDevice (** device name, needs reverse transl. *)
| FBuffer (** opaque buffer of bytes, (char *, int) pair*)
| FUInt32
| FInt32

View File

@@ -1 +1 @@
515
517

View File

@@ -166,34 +166,6 @@ create_drive_non_file (guestfs_h *g,
return drv;
}
static struct drive *
create_drive_curl (guestfs_h *g,
const struct drive_create_data *data)
{
if (data->nr_servers != 1) {
error (g, _("curl: you must specify exactly one server"));
return NULL;
}
if (data->servers[0].transport != drive_transport_none &&
data->servers[0].transport != drive_transport_tcp) {
error (g, _("curl: only tcp transport is supported"));
return NULL;
}
if (STREQ (data->exportname, "")) {
error (g, _("curl: pathname should not be an empty string"));
return NULL;
}
if (data->exportname[0] != '/') {
error (g, _("curl: pathname must begin with a '/'"));
return NULL;
}
return create_drive_non_file (g, data);
}
static int
nbd_port (void)
{
@@ -261,67 +233,6 @@ create_drive_rbd (guestfs_h *g,
return create_drive_non_file (g, data);
}
static struct drive *
create_drive_ssh (guestfs_h *g,
const struct drive_create_data *data)
{
if (data->nr_servers != 1) {
error (g, _("ssh: you must specify exactly one server"));
return NULL;
}
if (data->servers[0].transport != drive_transport_none &&
data->servers[0].transport != drive_transport_tcp) {
error (g, _("ssh: only tcp transport is supported"));
return NULL;
}
if (STREQ (data->exportname, "")) {
error (g, _("ssh: pathname should not be an empty string"));
return NULL;
}
if (data->exportname[0] != '/') {
error (g, _("ssh: pathname must begin with a '/'"));
return NULL;
}
if (data->username && STREQ (data->username, "")) {
error (g, _("ssh: username should not be an empty string"));
return NULL;
}
return create_drive_non_file (g, data);
}
static struct drive *
create_drive_iscsi (guestfs_h *g,
const struct drive_create_data *data)
{
if (data->nr_servers != 1) {
error (g, _("iscsi: you must specify exactly one server"));
return NULL;
}
if (data->servers[0].transport != drive_transport_none &&
data->servers[0].transport != drive_transport_tcp) {
error (g, _("iscsi: only tcp transport is supported"));
return NULL;
}
if (STREQ (data->exportname, "")) {
error (g, _("iscsi: target name should not be an empty string"));
return NULL;
}
if (data->exportname[0] == '/') {
error (g, _("iscsi: target string must not begin with a '/'"));
return NULL;
}
return create_drive_non_file (g, data);
}
/**
* Create the special F</dev/null> drive.
*
@@ -768,26 +679,6 @@ guestfs_impl_add_drive_opts (guestfs_h *g, const char *filename,
drv = create_drive_file (g, &data);
}
}
else if (STREQ (protocol, "ftp")) {
data.protocol = drive_protocol_ftp;
drv = create_drive_curl (g, &data);
}
else if (STREQ (protocol, "ftps")) {
data.protocol = drive_protocol_ftps;
drv = create_drive_curl (g, &data);
}
else if (STREQ (protocol, "http")) {
data.protocol = drive_protocol_http;
drv = create_drive_curl (g, &data);
}
else if (STREQ (protocol, "https")) {
data.protocol = drive_protocol_https;
drv = create_drive_curl (g, &data);
}
else if (STREQ (protocol, "iscsi")) {
data.protocol = drive_protocol_iscsi;
drv = create_drive_iscsi (g, &data);
}
else if (STREQ (protocol, "nbd")) {
data.protocol = drive_protocol_nbd;
drv = create_drive_nbd (g, &data);
@@ -796,10 +687,6 @@ guestfs_impl_add_drive_opts (guestfs_h *g, const char *filename,
data.protocol = drive_protocol_rbd;
drv = create_drive_rbd (g, &data);
}
else if (STREQ (protocol, "ssh")) {
data.protocol = drive_protocol_ssh;
drv = create_drive_ssh (g, &data);
}
else {
error (g, _("unknown protocol %s"), protocol);
drv = NULL; /*FALLTHROUGH*/

View File

@@ -723,51 +723,6 @@ a qcow2 backing file specification, libvirt does not construct an
ephemeral secret object from those, for Ceph authentication. Refer to
L<https://bugzilla.redhat.com/2033247>.
=head3 FTP AND HTTP
Libguestfs can access remote disks over FTP, FTPS, HTTP or HTTPS
protocols.
To do this, set the optional C<protocol> and C<server> parameters of
L</guestfs_add_drive_opts> like this:
char **servers = { "www.example.org", NULL };
guestfs_add_drive_opts (g, "/disk.img",
GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw",
GUESTFS_ADD_DRIVE_OPTS_PROTOCOL, "http",
GUESTFS_ADD_DRIVE_OPTS_SERVER, servers,
-1);
The C<protocol> can be one of C<"ftp">, C<"ftps">, C<"http">,
or C<"https">.
C<servers> (the C<server> parameter) is a list which must have a
single element. The single element is a string defining the web
or FTP server. The format of this string is documented in
L</guestfs_add_drive_opts>.
=head3 GLUSTER
Glusterfs support was removed in libguestfs 1.54 (2024).
=head3 ISCSI
Libguestfs can access iSCSI disks remotely.
To do this, set the optional C<protocol> and C<server> parameters like
this:
char **server = { "iscsi.example.org:3000", NULL };
guestfs_add_drive_opts (g, "target-iqn-name/lun",
GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw",
GUESTFS_ADD_DRIVE_OPTS_PROTOCOL, "iscsi",
GUESTFS_ADD_DRIVE_OPTS_SERVER, server,
-1);
The C<server> parameter is a list which must have a single element.
The single element is a string defining the iSCSI server. The format
of this string is documented in L</guestfs_add_drive_opts>.
=head3 NETWORK BLOCK DEVICE
Libguestfs can access Network Block Device (NBD) disks remotely.
@@ -830,28 +785,6 @@ L<https://bugs.launchpad.net/qemu/+bug/1155677>
=back
=head3 SHEEPDOG
Sheepdog support was removed in libguestfs 1.54 (2024).
=head3 SSH
Libguestfs can access disks over a Secure Shell (SSH) connection.
To do this, set the C<protocol> and C<server> and (optionally)
C<username> parameters of L</guestfs_add_drive_opts> like this:
char **server = { "remote.example.com", NULL };
guestfs_add_drive_opts (g, "/path/to/disk.img",
GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw",
GUESTFS_ADD_DRIVE_OPTS_PROTOCOL, "ssh",
GUESTFS_ADD_DRIVE_OPTS_SERVER, server,
GUESTFS_ADD_DRIVE_OPTS_USERNAME, "remoteuser",
-1);
The format of the server string is documented in
L</guestfs_add_drive_opts>.
=head2 INSPECTION
Libguestfs has APIs for inspecting an unknown disk image to find out

View File

@@ -36,6 +36,7 @@
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <sys/wait.h>
#include <errno.h>
#include <assert.h>
@@ -93,6 +94,7 @@ guestfs_impl_launch (guestfs_h *g)
struct backend *b;
CLEANUP_FREE char *backend = guestfs_get_backend (g);
int mask;
struct utsname utsname;
debug (g, "launch: program=%s", g->program);
if (STRNEQ (g->identifier, ""))
@@ -109,6 +111,10 @@ guestfs_impl_launch (guestfs_h *g)
if (mask >= 0)
debug (g, "launch: umask=0%03o", (unsigned) mask);
debug (g, "launch: euid=%ju", (uintmax_t) geteuid ());
if (uname (&utsname) == 0)
debug (g, "launch: host: %s %s %s %s %s",
utsname.sysname, utsname.nodename, utsname.release,
utsname.version, utsname.machine);
}
/* Launch the appliance. */

View File

@@ -90,7 +90,6 @@ daemon/link.c
daemon/ls.c
daemon/luks.c
daemon/lvm-filter.c
daemon/lvm-tokenization.c
daemon/lvm.c
daemon/md.c
daemon/mkfs.c

View File

@@ -37,6 +37,7 @@ fi
# Create a disk image.
guestfish <<EOF
set-program virt-testing
sparse windows.img-t 512M
run

View File

@@ -440,6 +440,16 @@ EXTRA_DIST += http/test-http.py
TESTS += journal/test-journal.pl
EXTRA_DIST += journal/test-journal.pl
# This binary must be statically linked. It is used for testing
# the "guestfs_command_out" function.
large_command_test_large_command_SOURCES = large-command/test-large-command.c
large_command_test_large_command_LDFLAGS = -all-static
check_PROGRAMS += large-command/test-large-command
TESTS += large-command/test-large-command.sh
EXTRA_DIST += large-command/test-large-command.sh
TESTS += \
luks/test-luks.sh \
luks/test-luks-list.sh \

View File

@@ -96,6 +96,8 @@ main (int argc, char *argv[])
if (g == NULL)
error (EXIT_FAILURE, 0, "failed to create handle");
guestfs_set_program (g, "virt-testing");
if (guestfs_add_drive_scratch (g, 1024*1024*1024, -1) == -1)
exit (EXIT_FAILURE);

View File

@@ -65,13 +65,6 @@ check_output
grep -sq -- '-drive file=rbd:abc-def/ghi-jkl:auth_supported=none,' "$DEBUG_QEMU_FILE" || fail ceph2
rm "$DEBUG_QEMU_FILE"
# iSCSI.
$guestfish -d iscsi run ||:
check_output
grep -sq -- '-drive file=iscsi://1.2.3.4:1234/iqn.2003-01.org.linux-iscsi.fedora' "$DEBUG_QEMU_FILE" || fail iscsi
rm "$DEBUG_QEMU_FILE"
# NBD.
$guestfish -d nbd run ||:

View File

@@ -62,35 +62,6 @@ check_output
grep -sq -- '-drive file=rbd:abc-def/ghi-jkl:auth_supported=none,' "$DEBUG_QEMU_FILE" || fail
rm "$DEBUG_QEMU_FILE"
# HTTP.
guestfish <<EOF ||:
add "/disk.img" "format:raw" "protocol:http" "server:www.example.com"
run
EOF
check_output
grep -sq -- '-drive file=http://www.example.com/disk.img,' "$DEBUG_QEMU_FILE" || fail
rm "$DEBUG_QEMU_FILE"
# iSCSI.
guestfish <<EOF ||:
add "target-iqn-name/lun" "format:raw" "protocol:iscsi" "server:www.example.com:3000"
run
EOF
check_output
grep -sq -- '-drive file=iscsi://www.example.com:3000/target-iqn-name/lun,' "$DEBUG_QEMU_FILE" || fail
rm "$DEBUG_QEMU_FILE"
guestfish <<EOF ||:
add "target-iqn-name/lun" "format:raw" "protocol:iscsi" "server:www.example.com:3000" \
"username:user" "secret:pass"
run
EOF
check_output
grep -sq -- '-drive file=iscsi://user%pass@www.example.com:3000/target-iqn-name/lun,' "$DEBUG_QEMU_FILE" || fail
rm "$DEBUG_QEMU_FILE"
# NBD.
guestfish <<EOF ||:
@@ -108,14 +79,3 @@ EOF
check_output
grep -sq -- '-drive file=nbd:unix:/socket,' "$DEBUG_QEMU_FILE" || fail
rm "$DEBUG_QEMU_FILE"
# SSH.
guestfish <<EOF ||:
add "/disk.img" "format:raw" "protocol:ssh" "server:example.com" \
"username:rich"
run
EOF
check_output
grep -sq -- '-drive file=ssh://rich@example.com/disk.img,' "$DEBUG_QEMU_FILE" || fail
rm "$DEBUG_QEMU_FILE"

View File

@@ -0,0 +1,46 @@
/* libguestfs
* Copyright (C) 2009-2025 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 program, which must be statically linked, is used to test the
* guestfs_command_out and guestfs_sh_out functions.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <error.h>
#define STREQ(a,b) (strcmp((a),(b)) == 0)
int
main (int argc, char *argv[])
{
size_t n, i;
if (argc > 1) {
if (sscanf (argv[1], "%zu", &n) != 1)
error (EXIT_FAILURE, 0, "could not parse parameter: %s", argv[1]);
for (i = 0; i < n; ++i)
putchar ('x');
} else
error (EXIT_FAILURE, 0, "missing parameter");
exit (EXIT_SUCCESS);
}

View File

@@ -0,0 +1,73 @@
#!/bin/bash -
# libguestfs
# Copyright (C) 2025 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.
# Test command-out. We can't easily test sh-out without having a
# shell (which requires a full guest), however the code path for both
# is essentially identical.
set -e
$TEST_FUNCTIONS
skip_if_skipped
skip_unless stat --version
# Binary must exist and must be linked statically.
bin=large-command/test-large-command
skip_unless test -x $bin
skip_unless bash -c " ldd $bin |& grep -sq 'not a dynamic executable' "
disk=large-command/test.img
rm -f $disk
out1=large-command/test.out1
out2=large-command/test.out2
out3=large-command/test.out3
out4=large-command/test.out4
# Must be larger than protocol size, currently 4MB.
size=$((10 * 1024 * 1024))
guestfish -x -N $disk=fs -m /dev/sda1 <<EOF
upload $bin /test-large-command
chmod 0755 /test-large-command
command-out "/test-large-command $size" $out1
# Check smaller sizes work as well.
command-out "/test-large-command 0" $out2
command-out "/test-large-command 1" $out3
command-out "/test-large-command 80" $out4
EOF
ls -l $out1 $out2 $out3 $out4
cat $out2
cat $out3
cat $out4
# Check the sizes are correct.
test "$( stat -c '%s' $out1 )" -eq $size
test "$( stat -c '%s' $out2 )" -eq 0
test "$( stat -c '%s' $out3 )" -eq 1
test "$( stat -c '%s' $out4 )" -eq 80
# Check the content is correct, for the smaller files.
test `cat $out3` = "x"
test `cat $out4` = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
rm $disk $out1 $out2 $out3 $out4

View File

@@ -183,7 +183,7 @@ git clone <a href="https://github.com/libguestfs/libguestfs">https://github.com/
<p class="latest">
<em><small>
<!--
LATEST-URL: http://libguestfs.org/download/1.53-development/libguestfs-@PACKAGE_VERSION@.tar.gz
LATEST-URL: http://libguestfs.org/download/1.55-development/libguestfs-@PACKAGE_VERSION@.tar.gz
LATEST-VERSION: @PACKAGE_VERSION@
-->
<a href="download/1.55-development/">Latest development version: <strong>@PACKAGE_VERSION@</strong></a> (released <strong>@RELEASE_DATE@</strong>).<br/>