Richard W.M. Jones
2017-Jun-19 15:39 UTC
[Libguestfs] [PATCH v7 00/29] Reimplement inspection in the daemon.
v6 was posted here: https://www.redhat.com/archives/libguestfs/2017-June/msg00103.html and this requires the utilities refactoring posted here: https://www.redhat.com/archives/libguestfs/2017-June/msg00169.html Inspection is now complete[*], although not very well tested. I'm intending to compare the output of many guests using old & new virt-inspector to see if I can find any differences. Rich. [*] Except that "name" labels on disks are ignored. This requires a bit of complicated work to fix because the names are not actually available inside the daemon. So far this only affects a test, it was not something that any of our tools used.
Richard W.M. Jones
2017-Jun-19 15:39 UTC
[Libguestfs] [PATCH v7 01/29] inspection: Deprecate APIs and remove support for inspecting installer CDs.
This just duplicated libosinfo information, and because it was never tested it didn't work most of the time. --- docs/C_SOURCE_FILES | 2 - generator/actions_inspection.ml | 67 --- generator/actions_inspection_deprecated.ml | 61 +++ inspector/Makefile.am | 11 +- inspector/example-debian-netinst-cd.xml | 23 - inspector/example-debian.xml | 1 - inspector/example-fedora-dvd.xml | 23 - inspector/example-fedora-netinst-cd.xml | 21 - inspector/example-fedora.xml | 1 - inspector/example-rhel-6-dvd.xml | 23 - inspector/example-rhel-6-netinst-cd.xml | 21 - inspector/example-rhel-6.xml | 1 - inspector/example-ubuntu-live-cd.xml | 23 - inspector/example-ubuntu.xml | 1 - inspector/example-windows-2003-x64-cd.xml | 24 -- inspector/example-windows-2003-x86-cd.xml | 24 -- inspector/example-windows-xp-cd.xml | 24 -- inspector/example-windows.xml | 1 - inspector/expected-archlinux.img.xml | 1 - inspector/expected-coreos.img.xml | 1 - inspector/expected-debian.img.xml | 1 - inspector/expected-fedora.img.xml | 1 - inspector/expected-ubuntu.img.xml | 1 - inspector/expected-windows.img.xml | 1 - inspector/inspector.c | 31 +- inspector/virt-inspector.pod | 22 - inspector/virt-inspector.rng | 15 - lib/Makefile.am | 3 - lib/guestfs-internal.h | 31 -- lib/guestfs.pod | 9 - lib/inspect-fs-cd.c | 606 -------------------------- lib/inspect-fs.c | 40 -- lib/osinfo.c | 655 ----------------------------- 33 files changed, 63 insertions(+), 1707 deletions(-) diff --git a/docs/C_SOURCE_FILES b/docs/C_SOURCE_FILES index 8ce6b5865..61cdbea38 100644 --- a/docs/C_SOURCE_FILES +++ b/docs/C_SOURCE_FILES @@ -304,7 +304,6 @@ lib/guid.c lib/handle.c lib/info.c lib/inspect-apps.c -lib/inspect-fs-cd.c lib/inspect-fs-unix.c lib/inspect-fs-windows.c lib/inspect-fs.c @@ -323,7 +322,6 @@ lib/listfs.c lib/lpj.c lib/match.c lib/mountable.c -lib/osinfo.c lib/private-data.c lib/proto.c lib/qemu.c diff --git a/generator/actions_inspection.ml b/generator/actions_inspection.ml index b7ea5a4de..cd8b9da18 100644 --- a/generator/actions_inspection.ml +++ b/generator/actions_inspection.ml @@ -566,73 +566,6 @@ string C<unknown> is returned. Please read L<guestfs(3)/INSPECTION> for more details." }; { defaults with - name = "inspect_get_format"; added = (1, 9, 4); - style = RString (RPlainString, "format"), [String (Mountable, "root")], []; - shortdesc = "get format of inspected operating system"; - longdesc = "\ -This returns the format of the inspected operating system. You -can use it to detect install images, live CDs and similar. - -Currently defined formats are: - -=over 4 - -=item \"installed\" - -This is an installed operating system. - -=item \"installer\" - -The disk image being inspected is not an installed operating system, -but a I<bootable> install disk, live CD, or similar. - -=item \"unknown\" - -The format of this disk image is not known. - -=back - -Future versions of libguestfs may return other strings here. -The caller should be prepared to handle any string. - -Please read L<guestfs(3)/INSPECTION> for more details." }; - - { defaults with - name = "inspect_is_live"; added = (1, 9, 4); - style = RBool "live", [String (Mountable, "root")], []; - shortdesc = "get live flag for install disk"; - longdesc = "\ -If C<guestfs_inspect_get_format> returns C<installer> (this -is an install disk), then this returns true if a live image -was detected on the disk. - -Please read L<guestfs(3)/INSPECTION> for more details." }; - - { defaults with - name = "inspect_is_netinst"; added = (1, 9, 4); - style = RBool "netinst", [String (Mountable, "root")], []; - shortdesc = "get netinst (network installer) flag for install disk"; - longdesc = "\ -If C<guestfs_inspect_get_format> returns C<installer> (this -is an install disk), then this returns true if the disk is -a network installer, ie. not a self-contained install CD but -one which is likely to require network access to complete -the install. - -Please read L<guestfs(3)/INSPECTION> for more details." }; - - { defaults with - name = "inspect_is_multipart"; added = (1, 9, 4); - style = RBool "multipart", [String (Mountable, "root")], []; - shortdesc = "get multipart flag for install disk"; - longdesc = "\ -If C<guestfs_inspect_get_format> returns C<installer> (this -is an install disk), then this returns true if the disk is -part of a set. - -Please read L<guestfs(3)/INSPECTION> for more details." }; - - { defaults with name = "inspect_get_product_variant"; added = (1, 9, 13); style = RString (RPlainString, "variant"), [String (Mountable, "root")], []; shortdesc = "get product variant of inspected operating system"; diff --git a/generator/actions_inspection_deprecated.ml b/generator/actions_inspection_deprecated.ml index 342b0c8cd..7c34cb1a8 100644 --- a/generator/actions_inspection_deprecated.ml +++ b/generator/actions_inspection_deprecated.ml @@ -121,4 +121,65 @@ If unavailable this is returned as an empty string C<\"\">. Please read L<guestfs(3)/INSPECTION> for more details." }; + { defaults with + name = "inspect_get_format"; added = (1, 9, 4); + style = RString (RPlainString, "format"), [String (Mountable, "root")], []; + deprecated_by = Deprecated_no_replacement; + shortdesc = "get format of inspected operating system"; + longdesc = "\ +Before libguestfs 1.38, there was some unreliable support for detecting +installer CDs. This API would return: + +=over 4 + +=item \"installed\" + +This is an installed operating system. + +=item \"installer\" + +The disk image being inspected is not an installed operating system, +but a I<bootable> install disk, live CD, or similar. + +=item \"unknown\" + +The format of this disk image is not known. + +=back + +In libguestfs E<ge> 1.38, this only returns C<installed>. +Use libosinfo directly to detect installer CDs. + +Please read L<guestfs(3)/INSPECTION> for more details." }; + + { defaults with + name = "inspect_is_live"; added = (1, 9, 4); + style = RBool "live", [String (Mountable, "root")], []; + deprecated_by = Deprecated_no_replacement; + shortdesc = "get live flag for install disk"; + longdesc = "\ +This is deprecated and always returns C<false>. + +Please read L<guestfs(3)/INSPECTION> for more details." }; + + { defaults with + name = "inspect_is_netinst"; added = (1, 9, 4); + style = RBool "netinst", [String (Mountable, "root")], []; + deprecated_by = Deprecated_no_replacement; + shortdesc = "get netinst (network installer) flag for install disk"; + longdesc = "\ +This is deprecated and always returns C<false>. + +Please read L<guestfs(3)/INSPECTION> for more details." }; + + { defaults with + name = "inspect_is_multipart"; added = (1, 9, 4); + style = RBool "multipart", [String (Mountable, "root")], []; + deprecated_by = Deprecated_no_replacement; + shortdesc = "get multipart flag for install disk"; + longdesc = "\ +This is deprecated and always returns C<false>. + +Please read L<guestfs(3)/INSPECTION> for more details." }; + ] diff --git a/inspector/Makefile.am b/inspector/Makefile.am index 9402270c9..cd82ae918 100644 --- a/inspector/Makefile.am +++ b/inspector/Makefile.am @@ -22,16 +22,7 @@ example_xml = \ example-fedora.xml \ example-rhel-6.xml \ example-ubuntu.xml \ - example-windows.xml \ - example-debian-netinst-cd.xml \ - example-fedora-dvd.xml \ - example-fedora-netinst-cd.xml \ - example-rhel-6-dvd.xml \ - example-rhel-6-netinst-cd.xml \ - example-ubuntu-live-cd.xml \ - example-windows-2003-x64-cd.xml \ - example-windows-2003-x86-cd.xml \ - example-windows-xp-cd.xml + example-windows.xml EXTRA_DIST = \ expected-debian.img.xml \ diff --git a/inspector/example-debian-netinst-cd.xml b/inspector/example-debian-netinst-cd.xml deleted file mode 100644 index 570a5bd66..000000000 --- a/inspector/example-debian-netinst-cd.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0"?> -<operatingsystems> - <operatingsystem> - <root>/dev/sda</root> - <name>linux</name> - <distro>debian</distro> - <product_name>Debian GNU/Linux 5.0.5 "Lenny" - Official amd64 NETINST Binary-1 20100627-10:37</product_name> - <major_version>5</major_version> - <minor_version>0</minor_version> - <format>installer</format> - <netinst/> - <mountpoints> - <mountpoint dev="/dev/sda">/</mountpoint> - </mountpoints> - <filesystems> - <filesystem dev="/dev/sda"> - <type>iso9660</type> - <label>Debian 5.0.5 amd64 Bin-1</label> - </filesystem> - </filesystems> - <applications/> - </operatingsystem> -</operatingsystems> diff --git a/inspector/example-debian.xml b/inspector/example-debian.xml index eb10d8567..22f391d80 100644 --- a/inspector/example-debian.xml +++ b/inspector/example-debian.xml @@ -11,7 +11,6 @@ <package_format>deb</package_format> <package_management>apt</package_management> <hostname>debian5x64.home.annexia.org</hostname> - <format>installed</format> <mountpoints> <mountpoint dev="/dev/debian5x64.home.annexia.org/root">/</mountpoint> <mountpoint dev="/dev/debian5x64.home.annexia.org/tmp">/tmp</mountpoint> diff --git a/inspector/example-fedora-dvd.xml b/inspector/example-fedora-dvd.xml deleted file mode 100644 index 16a04baa5..000000000 --- a/inspector/example-fedora-dvd.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0"?> -<operatingsystems> - <operatingsystem> - <root>/dev/sda</root> - <name>linux</name> - <arch>x86_64</arch> - <distro>fedora</distro> - <major_version>14</major_version> - <minor_version>0</minor_version> - <format>installer</format> - <multipart/> - <mountpoints> - <mountpoint dev="/dev/sda">/</mountpoint> - </mountpoints> - <filesystems> - <filesystem dev="/dev/sda"> - <type>iso9660</type> - <label>Fedora 14 x86_64 DVD</label> - </filesystem> - </filesystems> - <applications/> - </operatingsystem> -</operatingsystems> diff --git a/inspector/example-fedora-netinst-cd.xml b/inspector/example-fedora-netinst-cd.xml deleted file mode 100644 index 654fabbe3..000000000 --- a/inspector/example-fedora-netinst-cd.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0"?> -<operatingsystems> - <operatingsystem> - <root>/dev/sda</root> - <name>linux</name> - <distro>fedora</distro> - <major_version>14</major_version> - <minor_version>0</minor_version> - <format>installer</format> - <mountpoints> - <mountpoint dev="/dev/sda">/</mountpoint> - </mountpoints> - <filesystems> - <filesystem dev="/dev/sda"> - <type>iso9660</type> - <label>Fedora</label> - </filesystem> - </filesystems> - <applications/> - </operatingsystem> -</operatingsystems> diff --git a/inspector/example-fedora.xml b/inspector/example-fedora.xml index ca8f56b58..32c88adc5 100644 --- a/inspector/example-fedora.xml +++ b/inspector/example-fedora.xml @@ -11,7 +11,6 @@ <package_format>rpm</package_format> <package_management>yum</package_management> <hostname>f18x64homeannexiaorg</hostname> - <format>installed</format> <mountpoints> <mountpoint dev="/dev/fedora/root">/</mountpoint> <mountpoint dev="/dev/sda1">/boot</mountpoint> diff --git a/inspector/example-rhel-6-dvd.xml b/inspector/example-rhel-6-dvd.xml deleted file mode 100644 index 4004d5a73..000000000 --- a/inspector/example-rhel-6-dvd.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0"?> -<operatingsystems> - <operatingsystem> - <root>/dev/sda</root> - <name>linux</name> - <arch>x86_64</arch> - <distro>rhel</distro> - <major_version>6</major_version> - <minor_version>0</minor_version> - <format>installer</format> - <multipart/> - <mountpoints> - <mountpoint dev="/dev/sda">/</mountpoint> - </mountpoints> - <filesystems> - <filesystem dev="/dev/sda"> - <type>iso9660</type> - <label>RHEL_6.0 x86_64 Disc 1</label> - </filesystem> - </filesystems> - <applications/> - </operatingsystem> -</operatingsystems> diff --git a/inspector/example-rhel-6-netinst-cd.xml b/inspector/example-rhel-6-netinst-cd.xml deleted file mode 100644 index bcdeebe53..000000000 --- a/inspector/example-rhel-6-netinst-cd.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0"?> -<operatingsystems> - <operatingsystem> - <root>/dev/sda</root> - <name>linux</name> - <distro>rhel</distro> - <major_version>6</major_version> - <minor_version>0</minor_version> - <format>installer</format> - <mountpoints> - <mountpoint dev="/dev/sda">/</mountpoint> - </mountpoints> - <filesystems> - <filesystem dev="/dev/sda"> - <type>iso9660</type> - <label>RHEL_6.0 x86_64 boot</label> - </filesystem> - </filesystems> - <applications/> - </operatingsystem> -</operatingsystems> diff --git a/inspector/example-rhel-6.xml b/inspector/example-rhel-6.xml index 0d338cef6..f11402a0c 100644 --- a/inspector/example-rhel-6.xml +++ b/inspector/example-rhel-6.xml @@ -11,7 +11,6 @@ <package_format>rpm</package_format> <package_management>yum</package_management> <hostname>rhel6x32.home.annexia.org</hostname> - <format>installed</format> <mountpoints> <mountpoint dev="/dev/vg_rhel6x32/lv_root">/</mountpoint> <mountpoint dev="/dev/sda1">/boot</mountpoint> diff --git a/inspector/example-ubuntu-live-cd.xml b/inspector/example-ubuntu-live-cd.xml deleted file mode 100644 index ff5a1cedb..000000000 --- a/inspector/example-ubuntu-live-cd.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0"?> -<operatingsystems> - <operatingsystem> - <root>/dev/sda</root> - <name>linux</name> - <distro>ubuntu</distro> - <product_name>Ubuntu 10.10 "Maverick Meerkat" - Release amd64 (20101007)</product_name> - <major_version>10</major_version> - <minor_version>10</minor_version> - <format>installer</format> - <live/> - <mountpoints> - <mountpoint dev="/dev/sda">/</mountpoint> - </mountpoints> - <filesystems> - <filesystem dev="/dev/sda"> - <type>iso9660</type> - <label>Ubuntu 10.10 amd64</label> - </filesystem> - </filesystems> - <applications/> - </operatingsystem> -</operatingsystems> diff --git a/inspector/example-ubuntu.xml b/inspector/example-ubuntu.xml index 0519d3403..a1f78ea78 100644 --- a/inspector/example-ubuntu.xml +++ b/inspector/example-ubuntu.xml @@ -11,7 +11,6 @@ <package_format>deb</package_format> <package_management>apt</package_management> <hostname>ubuntu1304.home.annexia.org</hostname> - <format>installed</format> <mountpoints> <mountpoint dev="/dev/sda1">/</mountpoint> </mountpoints> diff --git a/inspector/example-windows-2003-x64-cd.xml b/inspector/example-windows-2003-x64-cd.xml deleted file mode 100644 index e74dcf457..000000000 --- a/inspector/example-windows-2003-x64-cd.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0"?> -<operatingsystems> - <operatingsystem> - <root>/dev/sda</root> - <name>windows</name> - <arch>x86_64</arch> - <distro>windows</distro> - <product_name>Windows Server 2003 Enterprise x64 Edition</product_name> - <major_version>5</major_version> - <minor_version>2</minor_version> - <windows_systemroot>\WINDOWS</windows_systemroot> - <format>installer</format> - <mountpoints> - <mountpoint dev="/dev/sda">/</mountpoint> - </mountpoints> - <filesystems> - <filesystem dev="/dev/sda"> - <type>iso9660</type> - <label>CRMEXFPP_EN</label> - </filesystem> - </filesystems> - <applications/> - </operatingsystem> -</operatingsystems> diff --git a/inspector/example-windows-2003-x86-cd.xml b/inspector/example-windows-2003-x86-cd.xml deleted file mode 100644 index d46859273..000000000 --- a/inspector/example-windows-2003-x86-cd.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0"?> -<operatingsystems> - <operatingsystem> - <root>/dev/sda</root> - <name>windows</name> - <arch>i386</arch> - <distro>windows</distro> - <product_name>Windows Server 2003, Enterprise</product_name> - <major_version>5</major_version> - <minor_version>2</minor_version> - <windows_systemroot>\WINDOWS</windows_systemroot> - <format>installer</format> - <mountpoints> - <mountpoint dev="/dev/sda">/</mountpoint> - </mountpoints> - <filesystems> - <filesystem dev="/dev/sda"> - <type>iso9660</type> - <label>CRMEFPP_EN</label> - </filesystem> - </filesystems> - <applications/> - </operatingsystem> -</operatingsystems> diff --git a/inspector/example-windows-xp-cd.xml b/inspector/example-windows-xp-cd.xml deleted file mode 100644 index 57ea235fb..000000000 --- a/inspector/example-windows-xp-cd.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0"?> -<operatingsystems> - <operatingsystem> - <root>/dev/sda</root> - <name>windows</name> - <arch>i386</arch> - <distro>windows</distro> - <product_name>Windows XP Professional</product_name> - <major_version>5</major_version> - <minor_version>1</minor_version> - <windows_systemroot>\WINDOWS</windows_systemroot> - <format>installer</format> - <mountpoints> - <mountpoint dev="/dev/sda">/</mountpoint> - </mountpoints> - <filesystems> - <filesystem dev="/dev/sda"> - <type>iso9660</type> - <label>GRTMPFPP_EN</label> - </filesystem> - </filesystems> - <applications/> - </operatingsystem> -</operatingsystems> diff --git a/inspector/example-windows.xml b/inspector/example-windows.xml index 776026a76..f269affaf 100644 --- a/inspector/example-windows.xml +++ b/inspector/example-windows.xml @@ -12,7 +12,6 @@ <windows_systemroot>/Windows</windows_systemroot> <windows_current_control_set>ControlSet001</windows_current_control_set> <hostname>WIN-6AOV5N85H2F</hostname> - <format>installed</format> <mountpoints> <mountpoint dev="/dev/sda2">/</mountpoint> </mountpoints> diff --git a/inspector/expected-archlinux.img.xml b/inspector/expected-archlinux.img.xml index 665ac30da..b6c3b9c92 100644 --- a/inspector/expected-archlinux.img.xml +++ b/inspector/expected-archlinux.img.xml @@ -10,7 +10,6 @@ <package_format>pacman</package_format> <package_management>pacman</package_management> <hostname>archlinux.test</hostname> - <format>installed</format> <mountpoints> <mountpoint dev="/dev/sda1">/</mountpoint> </mountpoints> diff --git a/inspector/expected-coreos.img.xml b/inspector/expected-coreos.img.xml index c819a0521..e4a5d1134 100644 --- a/inspector/expected-coreos.img.xml +++ b/inspector/expected-coreos.img.xml @@ -8,7 +8,6 @@ <major_version>899</major_version> <minor_version>13</minor_version> <hostname>coreos.invalid</hostname> - <format>installed</format> <mountpoints> <mountpoint dev="/dev/sda5">/</mountpoint> <mountpoint dev="/dev/sda3">/usr</mountpoint> diff --git a/inspector/expected-debian.img.xml b/inspector/expected-debian.img.xml index 6f1d9dfa0..37ecfa049 100644 --- a/inspector/expected-debian.img.xml +++ b/inspector/expected-debian.img.xml @@ -11,7 +11,6 @@ <package_format>deb</package_format> <package_management>apt</package_management> <hostname>debian.invalid</hostname> - <format>installed</format> <mountpoints> <mountpoint dev="/dev/debian/root">/</mountpoint> <mountpoint dev="/dev/debian/usr">/usr</mountpoint> diff --git a/inspector/expected-fedora.img.xml b/inspector/expected-fedora.img.xml index 5b6f1af6e..8d40e8cb7 100644 --- a/inspector/expected-fedora.img.xml +++ b/inspector/expected-fedora.img.xml @@ -11,7 +11,6 @@ <package_format>rpm</package_format> <package_management>yum</package_management> <hostname>fedora.invalid</hostname> - <format>installed</format> <mountpoints> <mountpoint dev="/dev/VG/Root">/</mountpoint> <mountpoint dev="/dev/sda1">/boot</mountpoint> diff --git a/inspector/expected-ubuntu.img.xml b/inspector/expected-ubuntu.img.xml index ff9d62226..c19c14cd5 100644 --- a/inspector/expected-ubuntu.img.xml +++ b/inspector/expected-ubuntu.img.xml @@ -11,7 +11,6 @@ <package_format>deb</package_format> <package_management>apt</package_management> <hostname>ubuntu.invalid</hostname> - <format>installed</format> <mountpoints> <mountpoint dev="/dev/sda2">/</mountpoint> <mountpoint dev="/dev/sda1">/boot</mountpoint> diff --git a/inspector/expected-windows.img.xml b/inspector/expected-windows.img.xml index ff787f188..26aab3261 100644 --- a/inspector/expected-windows.img.xml +++ b/inspector/expected-windows.img.xml @@ -12,7 +12,6 @@ <windows_systemroot>/Windows</windows_systemroot> <windows_current_control_set>ControlSet001</windows_current_control_set> <hostname>windows.invalid</hostname> - <format>installed</format> <mountpoints> <mountpoint dev="/dev/sda2">/</mountpoint> </mountpoints> diff --git a/inspector/inspector.c b/inspector/inspector.c index 104310d1f..3583c61df 100644 --- a/inspector/inspector.c +++ b/inspector/inspector.c @@ -343,7 +343,7 @@ static void output_root (xmlTextWriterPtr xo, char *root) { char *str; - int i, r; + int i; char buf[32]; char *canonical_root; size_t size; @@ -443,35 +443,6 @@ output_root (xmlTextWriterPtr xo, char *root) BAD_CAST str)); free (str); - str = guestfs_inspect_get_format (g, root); - if (!str) exit (EXIT_FAILURE); - if (STRNEQ (str, "unknown")) - XMLERROR (-1, - xmlTextWriterWriteElement (xo, BAD_CAST "format", - BAD_CAST str)); - free (str); - - r = guestfs_inspect_is_live (g, root); - if (r > 0) { - XMLERROR (-1, - xmlTextWriterStartElement (xo, BAD_CAST "live")); - XMLERROR (-1, xmlTextWriterEndElement (xo)); - } - - r = guestfs_inspect_is_netinst (g, root); - if (r > 0) { - XMLERROR (-1, - xmlTextWriterStartElement (xo, BAD_CAST "netinst")); - XMLERROR (-1, xmlTextWriterEndElement (xo)); - } - - r = guestfs_inspect_is_multipart (g, root); - if (r > 0) { - XMLERROR (-1, - xmlTextWriterStartElement (xo, BAD_CAST "multipart")); - XMLERROR (-1, xmlTextWriterEndElement (xo)); - } - output_mountpoints (xo, root); output_filesystems (xo, root); diff --git a/inspector/virt-inspector.pod b/inspector/virt-inspector.pod index 9d6fbda7c..1cea542c7 100644 --- a/inspector/virt-inspector.pod +++ b/inspector/virt-inspector.pod @@ -202,7 +202,6 @@ describe the operating system, its architecture, the descriptive <major_version>6</major_version> <minor_version>1</minor_version> <windows_systemroot>/Windows</windows_systemroot> - <format>installed</format> In brief, E<lt>nameE<gt> is the class of operating system (something like C<linux> or C<windows>), E<lt>distroE<gt> is the distribution @@ -330,27 +329,6 @@ the conversion back to a PNG file: base64 -i -d < icon.data > icon.png -=head2 INSPECTING INSTALL DISKS, LIVE CDs - -Virt-inspector can detect some operating system installers on -install disks, live CDs, bootable USB keys and more. - -In this case the E<lt>formatE<gt> tag will contain C<installer> -and other fields may be present to indicate a live CD, network -installer, or one part of a multipart CD. For example: - - <operatingsystems> - <operatingsystem> - <root>/dev/sda</root> - <name>linux</name> - <arch>i386</arch> - <distro>ubuntu</distro> - <product_name>Ubuntu 10.10 "Maverick Meerkat"</product_name> - <major_version>10</major_version> - <minor_version>10</minor_version> - <format>installer</format> - <live/> - =head1 XPATH QUERIES Virt-inspector includes built in support for running XPath queries. diff --git a/inspector/virt-inspector.rng b/inspector/virt-inspector.rng index dff46c53f..857a02766 100644 --- a/inspector/virt-inspector.rng +++ b/inspector/virt-inspector.rng @@ -38,10 +38,6 @@ <optional><ref name="ospackageformat"/></optional> <optional><ref name="ospackagemanagement"/></optional> <optional><element name="hostname"><text/></element></optional> - <optional><ref name="osformat"/></optional> - <optional><element name="live"><empty/></element></optional> - <optional><element name="netinst"><empty/></element></optional> - <optional><element name="multipart"><empty/></element></optional> <ref name="mountpoints"/> <ref name="filesystems"/> @@ -152,17 +148,6 @@ </element> </define> - <!-- the operating system format --> - <define name="osformat"> - <element name="format"> - <choice> - <value>installed</value> - <value>installer</value> - <!-- "unknown" is intentionally left out --> - </choice> - </element> - </define> - <!-- how filesystems are mounted on mount points --> <define name="mountpoints"> <element name="mountpoints"> diff --git a/lib/Makefile.am b/lib/Makefile.am index 9ee02bd71..bf3406b16 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -97,7 +97,6 @@ libguestfs_la_SOURCES = \ inspect.c \ inspect-apps.c \ inspect-fs.c \ - inspect-fs-cd.c \ inspect-fs-unix.c \ inspect-fs-windows.c \ inspect-icon.c \ @@ -113,7 +112,6 @@ libguestfs_la_SOURCES = \ lpj.c \ match.c \ mountable.c \ - osinfo.c \ private-data.c \ proto.c \ qemu.c \ @@ -135,7 +133,6 @@ libguestfs_la_SOURCES = \ libguestfs_la_CPPFLAGS = \ -DGUESTFS_WARN_DEPRECATED=1 \ -DGUESTFS_PRIVATE=1 \ - -DLIBOSINFO_DB_PATH='"$(datadir)/libosinfo/db"' \ -I$(top_srcdir)/common/errnostring -I$(top_builddir)/common/errnostring \ -I$(top_srcdir)/common/protocol -I$(top_builddir)/common/protocol \ -I$(top_srcdir)/common/qemuopts -I$(top_builddir)/common/qemuopts \ diff --git a/lib/guestfs-internal.h b/lib/guestfs-internal.h index 589968b29..81755177d 100644 --- a/lib/guestfs-internal.h +++ b/lib/guestfs-internal.h @@ -889,10 +889,6 @@ extern char *guestfs_int_case_sensitive_path_silently (guestfs_h *g, const char extern char * guestfs_int_get_windows_systemroot (guestfs_h *g); extern int guestfs_int_check_windows_root (guestfs_h *g, struct inspect_fs *fs, char *windows_systemroot); -/* inspect-fs-cd.c */ -extern int guestfs_int_check_installer_root (guestfs_h *g, struct inspect_fs *fs); -extern int guestfs_int_check_installer_iso (guestfs_h *g, struct inspect_fs *fs, const char *device); - /* dbdump.c */ typedef int (*guestfs_int_db_dump_callback) (guestfs_h *g, const unsigned char *key, size_t keylen, const unsigned char *value, size_t valuelen, void *opaque); extern int guestfs_int_read_db_dump (guestfs_h *g, const char *dumpfile, void *opaque, guestfs_int_db_dump_callback callback); @@ -910,33 +906,6 @@ extern void guestfs_int_free_fuse (guestfs_h *g); extern virConnectPtr guestfs_int_open_libvirt_connection (guestfs_h *g, const char *uri, unsigned int flags); #endif -/* osinfo.c */ -struct osinfo { - /* Data provided by libosinfo database. */ - enum inspect_os_type type; - enum inspect_os_distro distro; - char *product_name; - int major_version; - int minor_version; - char *arch; - int is_live_disk; - bool is_installer; - -#if 0 - /* Not yet available in libosinfo database. */ - char *product_variant; - int is_netinst_disk; - int is_multipart_disk; -#endif - - /* The regular expressions used to match ISOs. */ - pcre *re_system_id; - pcre *re_volume_id; - pcre *re_publisher_id; - pcre *re_application_id; -}; -extern int guestfs_int_osinfo_map (guestfs_h *g, const struct guestfs_isoinfo *isoinfo, const struct osinfo **osinfo_ret); - /* command.c */ struct command; typedef void (*cmd_stdout_callback) (guestfs_h *g, void *data, const char *line, size_t len); diff --git a/lib/guestfs.pod b/lib/guestfs.pod index f2a54a1fd..cd57cc337 100644 --- a/lib/guestfs.pod +++ b/lib/guestfs.pod @@ -939,20 +939,11 @@ documentation for that function for details). Libguestfs (since 1.9.4) can detect some install disks, install CDs, live CDs and more. -Call L</guestfs_inspect_get_format> to return the format of the -operating system, which currently can be C<installed> (a regular -operating system) or C<installer> (some sort of install disk). - Further information is available about the operating system that can be installed using the regular inspection APIs like L</guestfs_inspect_get_product_name>, L</guestfs_inspect_get_major_version> etc. -Some additional information specific to installer disks is also -available from the L</guestfs_inspect_is_live>, -L</guestfs_inspect_is_netinst> and L</guestfs_inspect_is_multipart> -calls. - =head2 SPECIAL CONSIDERATIONS FOR WINDOWS GUESTS Libguestfs can mount NTFS partitions. It does this using the diff --git a/lib/inspect-fs-cd.c b/lib/inspect-fs-cd.c deleted file mode 100644 index 1cff5606b..000000000 --- a/lib/inspect-fs-cd.c +++ /dev/null @@ -1,606 +0,0 @@ -/* libguestfs - * Copyright (C) 2010-2012 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 <libintl.h> -#include <inttypes.h> - -#ifdef HAVE_ENDIAN_H -#include <endian.h> -#endif - -#include "c-ctype.h" - -#include "guestfs.h" -#include "guestfs-internal.h" - -/* Debian/Ubuntu install disks are easy ... - * - * These files are added by the debian-cd program, and it is worth - * looking at the source code to determine exact values, in - * particular '/usr/share/debian-cd/tools/start_new_disc' - * - * XXX Architecture? We could parse it out of the product name - * string, but that seems quite hairy. We could look for the names - * of packages. Also note that some Debian install disks are - * multiarch. - */ -static int -check_debian_installer_root (guestfs_h *g, struct inspect_fs *fs) -{ - fs->product_name = guestfs_int_first_line_of_file (g, "/.disk/info"); - if (!fs->product_name) - return -1; - - fs->type = OS_TYPE_LINUX; - if (STRPREFIX (fs->product_name, "Ubuntu")) - fs->distro = OS_DISTRO_UBUNTU; - else if (STRPREFIX (fs->product_name, "Debian")) - fs->distro = OS_DISTRO_DEBIAN; - - (void) guestfs_int_parse_major_minor (g, fs); - - if (guestfs_is_file (g, "/.disk/cd_type") > 0) { - CLEANUP_FREE char *cd_type - guestfs_int_first_line_of_file (g, "/.disk/cd_type"); - if (!cd_type) - return -1; - - if (STRPREFIX (cd_type, "dvd/single") || - STRPREFIX (cd_type, "full_cd/single")) { - fs->is_multipart_disk = 0; - fs->is_netinst_disk = 0; - } - else if (STRPREFIX (cd_type, "dvd") || - STRPREFIX (cd_type, "full_cd")) { - fs->is_multipart_disk = 1; - fs->is_netinst_disk = 0; - } - else if (STRPREFIX (cd_type, "not_complete")) { - fs->is_multipart_disk = 0; - fs->is_netinst_disk = 1; - } - } - - return 0; -} - -/* Take string which must look like "key = value" and find the value. - * There may or may not be spaces before and after the equals sign. - * This function is used by both check_fedora_installer_root and - * check_w2k3_installer_root. - */ -static const char * -find_value (const char *kv) -{ - const char *p; - - p = strchr (kv, '='); - if (!p) - abort (); - - do { - ++p; - } while (c_isspace (*p)); - - return p; -} - -/* RHEL 5 has a DVD.iso and several CD-sized -discX-ftp.iso alternatives. - * - * The DVD.iso contains: - * /.treeinfo: - * [general] - * family = Red Hat Enterprise Linux Server - * timestamp = 1328200566.61 - * totaldiscs = 1 - * version = 5.8 - * discnum = 1 - * packagedir = Server - * arch = x86_64 - * [...] - * - * /.discinfo: - * 1328205744.315196 - * Red Hat Enterprise Linux Server 5.8 # product name - * x86_64 # arch - * 1 # disk number - * Server/base - * Server/RPMS - * Server/pixmaps - * - * The alternative CD-sized ISOs contain: - * - * disc1: - * /.treeinfo: - * [general] - * family = Red Hat Enterprise Linux Server - * timestamp = 1328200566.61 - * totaldiscs = 1 - * version = 5.8 - * discnum = 1 - * packagedir = Server - * arch = x86_64 - * [...] - * - * /.discinfo: - * 1328205744.315196 - * Red Hat Enterprise Linux Server 5.8 # product name - * x86_64 # arch - * 1 # disk number - * Server/base - * Server/RPMS - * Server/pixmaps - * - * discN (N > 1): - * /.discinfo: - * 1328205744.315196 - * Red Hat Enterprise Linux Server 5.8 # product name - * x86_64 # arch - * 2 # disk number - * Server/base - * Server/RPMS - * Server/pixmaps - */ - -/* Fedora CDs and DVD (not netinst). The /.treeinfo file contains - * an initial section somewhat like this: - * - * [general] - * version = 14 - * arch = x86_64 - * family = Fedora - * variant = Fedora - * discnum = 1 - * totaldiscs = 1 - */ -static int -check_fedora_installer_root (guestfs_h *g, struct inspect_fs *fs) -{ - char *str; - const char *v; - int r; - int discnum = 0, totaldiscs = 0; - - fs->type = OS_TYPE_LINUX; - - r = guestfs_int_first_egrep_of_file (g, "/.treeinfo", - "^family = Fedora$", 0, &str); - if (r == -1) - return -1; - if (r > 0) { - fs->distro = OS_DISTRO_FEDORA; - free (str); - } - - r = guestfs_int_first_egrep_of_file (g, "/.treeinfo", - "^family = Red Hat Enterprise Linux$", - 0, &str); - if (r == -1) - return -1; - if (r > 0) { - fs->distro = OS_DISTRO_RHEL; - free (str); - } - - r = guestfs_int_first_egrep_of_file (g, "/.treeinfo", - "^family = Oracle Linux Server$", - 0, &str); - if (r == -1) - return -1; - if (r > 0) { - fs->distro = OS_DISTRO_ORACLE_LINUX; - free (str); - } - - /* XXX should do major.minor before this */ - r = guestfs_int_first_egrep_of_file (g, "/.treeinfo", - "^version = [[:digit:]]+", 0, &str); - if (r == -1) - return -1; - if (r > 0) { - v = find_value (str); - fs->version.v_major = guestfs_int_parse_unsigned_int_ignore_trailing (g, v); - free (str); - if (fs->version.v_major == -1) - return -1; - } - - r = guestfs_int_first_egrep_of_file (g, "/.treeinfo", - "^arch = [-_[:alnum:]]+$", 0, &str); - if (r == -1) - return -1; - if (r > 0) { - v = find_value (str); - fs->arch = safe_strdup (g, v); - free (str); - } - - r = guestfs_int_first_egrep_of_file (g, "/.treeinfo", - "^discnum = [[:digit:]]+$", 0, &str); - if (r == -1) - return -1; - if (r > 0) { - v = find_value (str); - discnum = guestfs_int_parse_unsigned_int (g, v); - free (str); - if (discnum == -1) - return -1; - } - - r = guestfs_int_first_egrep_of_file (g, "/.treeinfo", - "^totaldiscs = [[:digit:]]+$", 0, &str); - if (r == -1) - return -1; - if (r > 0) { - v = find_value (str); - totaldiscs = guestfs_int_parse_unsigned_int (g, v); - free (str); - if (totaldiscs == -1) - return -1; - } - - fs->is_multipart_disk = totaldiscs > 1; - /* and what about discnum? */ - - return 0; -} - -/* Linux with /isolinux/isolinux.cfg. - * - * This file is not easily parsable so we have to do our best. - * Look for the "menu title" line which contains: - * menu title Welcome to Fedora 14! # since at least Fedora 10 - * menu title Welcome to Red Hat Enterprise Linux 6.0! - * menu title Welcome to RHEL6.2-20111117.0-Workstation-x! - */ -static int -check_isolinux_installer_root (guestfs_h *g, struct inspect_fs *fs) -{ - char *str; - int r; - - fs->type = OS_TYPE_LINUX; - - r = guestfs_int_first_egrep_of_file (g, "/isolinux/isolinux.cfg", - "^menu title Welcome to Fedora [[:digit:]]+", - 0, &str); - if (r == -1) - return -1; - if (r > 0) { - fs->distro = OS_DISTRO_FEDORA; - fs->version.v_major - guestfs_int_parse_unsigned_int_ignore_trailing (g, &str[29]); - free (str); - if (fs->version.v_major == -1) - return -1; - } - - /* XXX parse major.minor */ - r = guestfs_int_first_egrep_of_file (g, "/isolinux/isolinux.cfg", - "^menu title Welcome to Red Hat Enterprise Linux [[:digit:]]+", - 0, &str); - if (r == -1) - return -1; - if (r > 0) { - fs->distro = OS_DISTRO_RHEL; - fs->version.v_major - guestfs_int_parse_unsigned_int_ignore_trailing (g, &str[47]); - free (str); - if (fs->version.v_major == -1) - return -1; - } - - /* XXX parse major.minor */ - r = guestfs_int_first_egrep_of_file (g, "/isolinux/isolinux.cfg", - "^menu title Welcome to RHEL[[:digit:]]+", - 0, &str); - if (r == -1) - return -1; - if (r > 0) { - fs->distro = OS_DISTRO_RHEL; - fs->version.v_major - guestfs_int_parse_unsigned_int_ignore_trailing (g, &str[26]); - free (str); - if (fs->version.v_major == -1) - return -1; - } - - /* XXX parse major.minor */ - r = guestfs_int_first_egrep_of_file (g, "/isolinux/isolinux.cfg", - "^menu title Welcome to Oracle Linux Server [[:digit:]]+", - 0, &str); - if (r == -1) - return -1; - if (r > 0) { - fs->distro = OS_DISTRO_ORACLE_LINUX; - fs->version.v_major - guestfs_int_parse_unsigned_int_ignore_trailing (g, &str[42]); - free (str); - if (fs->version.v_major == -1) - return -1; - } - - return 0; -} - -/* Windows 2003 and similar versions. - * - * NB: txtsetup file contains Windows \r\n line endings, which guestfs_grep - * does not remove. We have to remove them by hand here. - */ -static void -trim_cr (char *str) -{ - const size_t n = strlen (str); - if (n > 0 && str[n-1] == '\r') - str[n-1] = '\0'; -} - -static void -trim_quot (char *str) -{ - const size_t n = strlen (str); - if (n > 0 && str[n-1] == '"') - str[n-1] = '\0'; -} - -static int -check_w2k3_installer_root (guestfs_h *g, struct inspect_fs *fs, - const char *txtsetup) -{ - char *str; - const char *v; - int r; - - fs->type = OS_TYPE_WINDOWS; - fs->distro = OS_DISTRO_WINDOWS; - - r = guestfs_int_first_egrep_of_file (g, txtsetup, - "^productname[[:space:]]*=[[:space:]]*\"", 1, &str); - if (r == -1) - return -1; - if (r > 0) { - trim_cr (str); - trim_quot (str); - v = find_value (str); - fs->product_name = safe_strdup (g, v+1); - free (str); - } - - r = guestfs_int_first_egrep_of_file (g, txtsetup, - "^majorversion[[:space:]]*=[[:space:]]*[[:digit:]]+", - 1, &str); - if (r == -1) - return -1; - if (r > 0) { - trim_cr (str); - v = find_value (str); - fs->version.v_major = guestfs_int_parse_unsigned_int_ignore_trailing (g, v); - free (str); - if (fs->version.v_major == -1) - return -1; - } - - r = guestfs_int_first_egrep_of_file (g, txtsetup, - "^minorversion[[:space:]]*=[[:space:]]*[[:digit:]]+", - 1, &str); - if (r == -1) - return -1; - if (r > 0) { - trim_cr (str); - v = find_value (str); - fs->version.v_minor = guestfs_int_parse_unsigned_int_ignore_trailing (g, v); - free (str); - if (fs->version.v_minor == -1) - return -1; - } - - /* This is the windows systemroot that would be chosen on - * installation by default, although not necessarily the one that - * the user will finally choose. - */ - r = guestfs_int_first_egrep_of_file (g, txtsetup, - "^defaultpath[[:space:]]*=[[:space:]]*", - 1, &str); - if (r == -1) - return -1; - if (r > 0) { - trim_cr (str); - v = find_value (str); - fs->windows_systemroot = safe_strdup (g, v); - free (str); - } - - return 0; -} - -/* Read the data from a product.id-like file. - * - * This is an old file, mostly used in Mandriva-based systems (still including - * Mageia). A very minimal documentation for it is: - * - https://wiki.mageia.org/en/Product_id - * - http://wiki.mandriva.com/en/Product_id (old URL, defunct) - */ -static int -check_product_id_installer_root (guestfs_h *g, struct inspect_fs *fs, - const char *filename) -{ - CLEANUP_FREE char *line = NULL; - const char *elem; - char *saveptr; - - fs->type = OS_TYPE_LINUX; - - line = guestfs_int_first_line_of_file (g, filename); - if (line == NULL) - return -1; - - elem = strtok_r (line, ",", &saveptr); - while (elem) { - const char *equal = strchr (elem, '='); - if (equal == NULL || equal == elem) - return -1; - - const char *value = equal + 1; - - if (STRPREFIX (elem, "distribution=")) { - if (STREQ (value, "Mageia")) - fs->distro = OS_DISTRO_MAGEIA; - } else if (STRPREFIX (elem, "version=")) { - if (guestfs_int_version_from_x_y_or_x (g, &fs->version, value) == -1) - return -1; - } else if (STRPREFIX (elem, "arch=")) { - fs->arch = safe_strdup (g, value); - } - - elem = strtok_r (NULL, ",", &saveptr); - } - - /* Not found. */ - return 0; -} - -/* The currently mounted device is very likely to be an installer. */ -int -guestfs_int_check_installer_root (guestfs_h *g, struct inspect_fs *fs) -{ - CLEANUP_FREE_STRING_LIST char **paths = NULL; - - /* The presence of certain files indicates a live CD. - * - * XXX Fedora netinst contains a ~120MB squashfs called - * /images/install.img. However this is not a live CD (unlike the - * Fedora live CDs which contain the same, but larger file). We - * need to unpack this and look inside to tell the difference. - */ - if (guestfs_is_file (g, "/casper/filesystem.squashfs") > 0 || - guestfs_is_file (g, "/live/filesystem.squashfs") > 0 || - guestfs_is_file (g, "/mfsroot.gz") > 0) - fs->is_live_disk = 1; - - /* Debian/Ubuntu. */ - if (guestfs_is_file (g, "/.disk/info") > 0) { - if (check_debian_installer_root (g, fs) == -1) - return -1; - } - - /* Fedora CDs and DVD (not netinst). */ - else if (guestfs_is_file (g, "/.treeinfo") > 0) { - if (check_fedora_installer_root (g, fs) == -1) - return -1; - } - - /* FreeDOS install CD. */ - else if (guestfs_is_file (g, "/freedos/freedos.ico") > 0 && - guestfs_is_file (g, "/setup.bat") > 0) { - fs->type = OS_TYPE_DOS; - fs->distro = OS_DISTRO_FREEDOS; - fs->arch = safe_strdup (g, "i386"); - } - - /* Linux with /isolinux/isolinux.cfg (note that non-Linux can use - * ISOLINUX too, eg. FreeDOS). - */ - else if (guestfs_is_file (g, "/isolinux/isolinux.cfg") > 0) { - if (check_isolinux_installer_root (g, fs) == -1) - return -1; - } - - /* FreeBSD with /boot/loader.rc. */ - else if (guestfs_is_file (g, "/boot/loader.rc") > 0) { - fs->type = OS_TYPE_FREEBSD; - } - - /* Windows 2003 64 bit */ - else if (guestfs_is_file (g, "/amd64/txtsetup.sif") > 0) { - fs->arch = safe_strdup (g, "x86_64"); - if (check_w2k3_installer_root (g, fs, "/amd64/txtsetup.sif") == -1) - return -1; - } - - /* Windows 2003 32 bit */ - else if (guestfs_is_file (g, "/i386/txtsetup.sif") > 0) { - fs->arch = safe_strdup (g, "i386"); - if (check_w2k3_installer_root (g, fs, "/i386/txtsetup.sif") == -1) - return -1; - } - - /* Linux with /{i586,x86_64,etc}/product.id (typically found in Mandriva - * and Mageia). Usually there should be just one around, so we use the - * first one found. - */ - paths = guestfs_glob_expand (g, "/*/product.id"); - if (paths == NULL) - return -1; - if (paths[0] != NULL) { - if (check_product_id_installer_root (g, fs, paths[0]) == -1) - return -1; - } - - return 0; -} - -/* This is called for whole block devices. See if the device is an - * ISO and we are able to read the ISO info from it. In that case, - * try using libosinfo to map from the volume ID and other strings - * directly to the operating system type. - */ -int -guestfs_int_check_installer_iso (guestfs_h *g, struct inspect_fs *fs, - const char *device) -{ - CLEANUP_FREE_ISOINFO struct guestfs_isoinfo *isoinfo = NULL; - const struct osinfo *osinfo; - int r; - - guestfs_push_error_handler (g, NULL, NULL); - isoinfo = guestfs_isoinfo_device (g, device); - guestfs_pop_error_handler (g); - if (!isoinfo) - return 0; - - r = guestfs_int_osinfo_map (g, isoinfo, &osinfo); - if (r == -1) /* Fatal error. */ - return -1; - if (r == 0) /* Could not locate any matching ISO. */ - return 0; - - /* Otherwise we matched an ISO, so fill in the fs fields. */ - fs->mountable = safe_strdup (g, device); - fs->role = OS_ROLE_ROOT; - if (osinfo->is_installer) - fs->format = OS_FORMAT_INSTALLER; - fs->type = osinfo->type; - fs->distro = osinfo->distro; - fs->product_name - osinfo->product_name ? safe_strdup (g, osinfo->product_name) : NULL; - guestfs_int_version_from_values (&fs->version, osinfo->major_version, - osinfo->minor_version, 0); - fs->arch = osinfo->arch ? safe_strdup (g, osinfo->arch) : NULL; - fs->is_live_disk = osinfo->is_live_disk; - - guestfs_int_check_package_format (g, fs); - guestfs_int_check_package_management (g, fs); - - return 1; -} diff --git a/lib/inspect-fs.c b/lib/inspect-fs.c index 2da73d310..e320b3e78 100644 --- a/lib/inspect-fs.c +++ b/lib/inspect-fs.c @@ -87,22 +87,6 @@ guestfs_int_check_for_filesystem_on (guestfs_h *g, const char *mountable) } } - if (whole_device) { - extend_fses (g); - fs = &g->fses[g->nr_fses-1]; - - r = guestfs_int_check_installer_iso (g, fs, m->im_device); - if (r == -1) { /* Fatal error. */ - g->nr_fses--; - return -1; - } - if (r > 0) /* Found something. */ - return 0; - - /* Didn't find anything. Fall through ... */ - g->nr_fses--; - } - /* Try mounting the device. As above, ignore errors. */ guestfs_push_error_handler (g, NULL, NULL); if (vfs_type && STREQ (vfs_type, "ufs")) { /* Hack for the *BSDs. */ @@ -290,30 +274,6 @@ check_filesystem (guestfs_h *g, const char *mountable, */ fs->arch = safe_strdup (g, "i386"); } - /* Install CD/disk? - * - * Note that we checked (above) for an install ISO, but there are - * other types of install image (eg. USB keys) which that check - * wouldn't have picked up. - * - * Skip these checks if it's not a whole device (eg. CD) or the - * first partition (eg. bootable USB key). - */ - else if ((whole_device || (partnum == 1 && nr_partitions == 1)) && - (guestfs_is_file (g, "/isolinux/isolinux.cfg") > 0 || - guestfs_is_dir (g, "/EFI/BOOT") > 0 || - guestfs_is_file (g, "/images/install.img") > 0 || - guestfs_is_dir (g, "/.disk") > 0 || - guestfs_is_file (g, "/.discinfo") > 0 || - guestfs_is_file (g, "/i386/txtsetup.sif") > 0 || - guestfs_is_file (g, "/amd64/txtsetup.sif") > 0 || - guestfs_is_file (g, "/freedos/freedos.ico") > 0 || - guestfs_is_file (g, "/boot/loader.rc") > 0)) { - fs->role = OS_ROLE_ROOT; - fs->format = OS_FORMAT_INSTALLER; - if (guestfs_int_check_installer_root (g, fs) == -1) - return -1; - } /* The above code should have set fs->type and fs->distro fields, so * we can now guess the package management system. diff --git a/lib/osinfo.c b/lib/osinfo.c deleted file mode 100644 index ea2a7659a..000000000 --- a/lib/osinfo.c +++ /dev/null @@ -1,655 +0,0 @@ -/* libguestfs - * Copyright (C) 2012 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 - */ - -/* Read libosinfo XML files to parse out just the - * os/media/iso/system-id and os/media/iso/volume-id fields, which we - * can then use to map install media to operating systems. - * - * Note some assumptions here: - * - * (1) Ignore the libosinfo library itself, since we don't care - * for GObject nonsense. The XML database contains all we need. - * - * (2) Ignore os/upgrades and os/derives-from fields. This is - * safe(-ish) since the media identifiers always change for every - * release of an OS. We can easily add support for this if it becomes - * necessary. - * - * (3) We have to do some translation of the distro names and versions - * stored in the libosinfo files and the standard names returned by - * libguestfs. - * - * (4) Media detection is only part of the story. We may still need - * to inspect inside the image. - * - * (5) We only read the XML database files (at most) once per process, - * and keep them cached. They are only read at all if someone tries - * to inspect a CD/DVD/ISO. - * - * XXX Currently the database is not freed when the program exits / - * library is unloaded, although we should probably do that. - */ - -#include <config.h> - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> -#include <dirent.h> -#include <assert.h> -#include <sys/types.h> -#include <libintl.h> -#include <sys/stat.h> - -#include <libxml/parser.h> -#include <libxml/xpath.h> - -#include "ignore-value.h" -#include "glthread/lock.h" -#include "c-ctype.h" - -#include "guestfs.h" -#include "guestfs-internal.h" - -gl_lock_define_initialized (static, osinfo_db_lock); -static ssize_t osinfo_db_size = 0; /* 0 = unread, -1 = error, >= 1 = #records */ -static struct osinfo *osinfo_db = NULL; - -static int read_osinfo_db (guestfs_h *g); -static void free_osinfo_db_entry (struct osinfo *); - -#define XMLSTREQ(a,b) (xmlStrEqual((a),(b)) == 1) - -/* Given one or more fields from the header of a CD/DVD/ISO, look up - * the media in the libosinfo database and return our best guess for - * the operating system. - * - * This returns: - * -1 => a fatal error ('error' has been called, caller must not ignore it) - * 0 => could not locate the OS - * 1 => matching OS found, the osinfo_ret pointer has been filled in - */ -int -guestfs_int_osinfo_map (guestfs_h *g, const struct guestfs_isoinfo *isoinfo, - const struct osinfo **osinfo_ret) -{ - size_t i; - - /* We only need to lock the database when reading it for the first time. */ - gl_lock_lock (osinfo_db_lock); - if (osinfo_db_size == 0) { - if (read_osinfo_db (g) == -1) { - gl_lock_unlock (osinfo_db_lock); - return -1; - } - } - gl_lock_unlock (osinfo_db_lock); - - if (osinfo_db_size <= 0) - return 0; - - /* Look in the database to see if we can find a match. */ - for (i = 0; i < (size_t) osinfo_db_size; ++i) { - if (osinfo_db[i].re_system_id) { - if (!isoinfo->iso_system_id || - !match (g, isoinfo->iso_system_id, osinfo_db[i].re_system_id)) - continue; - } - - if (osinfo_db[i].re_volume_id) { - if (!isoinfo->iso_volume_id || - !match (g, isoinfo->iso_volume_id, osinfo_db[i].re_volume_id)) - continue; - } - - if (osinfo_db[i].re_publisher_id) { - if (!isoinfo->iso_publisher_id || - !match (g, isoinfo->iso_publisher_id, osinfo_db[i].re_publisher_id)) - continue; - } - - if (osinfo_db[i].re_application_id) { - if (!isoinfo->iso_application_id || - !match (g, isoinfo->iso_application_id, osinfo_db[i].re_application_id)) - continue; - } - - debug (g, "osinfo: mapped disk to database entry %zu", i); - - if (osinfo_ret) - *osinfo_ret = &osinfo_db[i]; - return 1; - } - - debug (g, "osinfo: no mapping found"); - - return 0; -} - -/* Read the libosinfo XML database files. The lock is held while - * this is called. - * - * Returns: - * -1 => a fatal error ('error' has been called) - * 0 => OK - * - * Note that failure to find or parse the XML files is *not* a fatal - * error, since we should fall back silently if these are not - * available. Although we'll emit some debug if this happens. - * - * Try to use the shared osinfo database layout (and location) first: - * https://gitlab.com/libosinfo/libosinfo/blob/master/docs/database-layout.txt - */ -static int read_osinfo_db_xml (guestfs_h *g, const char *filename); - -static int read_osinfo_db_flat (guestfs_h *g, const char *directory); -static int read_osinfo_db_three_levels (guestfs_h *g, const char *directory); -static int read_osinfo_db_directory (guestfs_h *g, const char *directory); - -static int -read_osinfo_db (guestfs_h *g) -{ - int r; - size_t i; - - assert (osinfo_db_size == 0); - - /* (1) Try the shared osinfo directory, using either the - * $OSINFO_SYSTEM_DIR envvar or its default value. - */ - { - const char *path; - CLEANUP_FREE char *os_path = NULL; - - path = getenv ("OSINFO_SYSTEM_DIR"); - if (path == NULL) - path = "/usr/share/osinfo"; - os_path = safe_asprintf (g, "%s/os", path); - r = read_osinfo_db_three_levels (g, os_path); - } - if (r == -1) - goto error; - else if (r == 1) - return 0; - - /* (2) Try the libosinfo directory, using the newer three-directory - * layout ($LIBOSINFO_DB_PATH / "os" / $group-ID / [file.xml]). - */ - r = read_osinfo_db_three_levels (g, LIBOSINFO_DB_PATH "/os"); - if (r == -1) - goto error; - else if (r == 1) - return 0; - - /* (3) Try the libosinfo directory, using the old flat directory - * layout ($LIBOSINFO_DB_PATH / "oses" / [file.xml]). - */ - r = read_osinfo_db_flat (g, LIBOSINFO_DB_PATH "/oses"); - if (r == -1) - goto error; - else if (r == 1) - return 0; - - /* Nothing found. */ - return 0; - - error: - /* Fatal error: free any database entries which have been read, and - * mark the database as having a permanent error. - */ - if (osinfo_db_size > 0) { - for (i = 0; i < (size_t) osinfo_db_size; ++i) - free_osinfo_db_entry (&osinfo_db[i]); - } - free (osinfo_db); - osinfo_db = NULL; - osinfo_db_size = -1; - - return -1; -} - -static int -read_osinfo_db_flat (guestfs_h *g, const char *directory) -{ - debug (g, "osinfo: loading flat database from %s", directory); - - return read_osinfo_db_directory (g, directory); -} - -static int -read_osinfo_db_three_levels (guestfs_h *g, const char *directory) -{ - DIR *dir; - int r; - - dir = opendir (directory); - if (!dir) { - debug (g, "osinfo: %s: %s", directory, strerror (errno)); - return 0; /* This is not an error: RHBZ#948324. */ - } - - debug (g, "osinfo: loading 3-level-directories database from %s", directory); - - for (;;) { - struct dirent *d; - CLEANUP_FREE char *pathname = NULL; - struct stat sb; - - errno = 0; - d = readdir (dir); - if (!d) break; - - pathname = safe_asprintf (g, "%s/%s", directory, d->d_name); - - /* Iterate only on directories. */ - if (stat (pathname, &sb) == 0 && S_ISDIR (sb.st_mode)) { - r = read_osinfo_db_directory (g, pathname); - if (r == -1) - goto error; - } - } - - /* Check for failure in readdir. */ - if (errno != 0) { - perrorf (g, "readdir: %s", directory); - goto error; - } - - /* Close the directory handle. */ - r = closedir (dir); - dir = NULL; - if (r == -1) { - perrorf (g, "closedir: %s", directory); - goto error; - } - - return 1; - - error: - if (dir) - closedir (dir); - - return -1; -} - -static int -read_osinfo_db_directory (guestfs_h *g, const char *directory) -{ - DIR *dir; - int r; - - dir = opendir (directory); - if (!dir) { - debug (g, "osinfo: %s: %s", directory, strerror (errno)); - return 0; /* This is not an error: RHBZ#948324. */ - } - - for (;;) { - struct dirent *d; - - errno = 0; - d = readdir (dir); - if (!d) break; - - if (STRSUFFIX (d->d_name, ".xml")) { - CLEANUP_FREE char *pathname = NULL; - - pathname = safe_asprintf (g, "%s/%s", directory, d->d_name); - r = read_osinfo_db_xml (g, pathname); - if (r == -1) - goto error; - } - } - - /* Check for failure in readdir. */ - if (errno != 0) { - perrorf (g, "readdir: %s", directory); - goto error; - } - - /* Close the directory handle. */ - r = closedir (dir); - dir = NULL; - if (r == -1) { - perrorf (g, "closedir: %s", directory); - goto error; - } - - return 1; - - error: - if (dir) - closedir (dir); - - return -1; -} - -static int read_iso_node (guestfs_h *g, xmlNodePtr iso_node, struct osinfo *osinfo); -static int read_media_node (guestfs_h *g, xmlXPathContextPtr xpathCtx, xmlNodePtr media_node, struct osinfo *osinfo); -static int read_os_node (guestfs_h *g, xmlXPathContextPtr xpathCtx, xmlNodePtr os_node, struct osinfo *osinfo); - -/* Read a single XML file from pathname (which is a full path). - * Only memory allocation failures are fatal errors here. - */ -static int -read_osinfo_db_xml (guestfs_h *g, const char *pathname) -{ - CLEANUP_XMLFREEDOC xmlDocPtr doc = NULL; - CLEANUP_XMLXPATHFREECONTEXT xmlXPathContextPtr xpathCtx = NULL; - CLEANUP_XMLXPATHFREEOBJECT xmlXPathObjectPtr xpathObj = NULL; - xmlNodeSetPtr nodes; - xmlNodePtr iso_node, media_node, os_node; - struct osinfo *osinfo; - size_t i; - - doc = xmlReadFile (pathname, NULL, XML_PARSE_NONET); - if (doc == NULL) { - debug (g, "osinfo: unable to parse XML file %s", pathname); - return 0; - } - - xpathCtx = xmlXPathNewContext (doc); - if (xpathCtx == NULL) { - error (g, _("osinfo: unable to create new XPath context")); - return -1; - } - - /* Get all <iso> nodes at any depth, then use the parent pointers in - * order to work back up the tree. - */ - xpathObj = xmlXPathEvalExpression (BAD_CAST "/libosinfo/os/media/iso", - xpathCtx); - if (xpathObj == NULL) { - error (g, _("osinfo: %s: unable to evaluate XPath expression"), - pathname); - return -1; - } - - nodes = xpathObj->nodesetval; - - if (nodes != NULL) { - for (i = 0; i < (size_t) nodes->nodeNr; ++i) { - iso_node = nodes->nodeTab[i]; - assert (iso_node != NULL); - assert (STREQ ((const char *) iso_node->name, "iso")); - assert (iso_node->type == XML_ELEMENT_NODE); - - media_node = iso_node->parent; - assert (media_node != NULL); - assert (STREQ ((const char *) media_node->name, "media")); - assert (media_node->type == XML_ELEMENT_NODE); - - os_node = media_node->parent; - assert (os_node != NULL); - assert (STREQ ((const char *) os_node->name, "os")); - assert (os_node->type == XML_ELEMENT_NODE); - - /* Allocate an osinfo record. */ - osinfo_db_size++; - osinfo_db = safe_realloc (g, osinfo_db, - sizeof (struct osinfo) * osinfo_db_size); - osinfo = &osinfo_db[osinfo_db_size-1]; - memset (osinfo, 0, sizeof *osinfo); - - /* Read XML fields into the new osinfo record. */ - if (read_iso_node (g, iso_node, osinfo) == -1 || - read_media_node (g, xpathCtx, media_node, osinfo) == -1 || - read_os_node (g, xpathCtx, os_node, osinfo) == -1) { - free_osinfo_db_entry (osinfo); - osinfo_db_size--; - return -1; - } - -#if 0 - debug (g, "osinfo: %s: %s%s%s%s=> arch %s live %s installer %s product %s type %d distro %d version %d.%d", - pathname, - osinfo->re_system_id ? "<system-id/> " : "", - osinfo->re_volume_id ? "<volume-id/> " : "", - osinfo->re_publisher_id ? "<publisher-id/> " : "", - osinfo->re_application_id ? "<application-id/> " : "", - osinfo->arch ? osinfo->arch : "(none)", - osinfo->is_live_disk ? "true" : "false", - osinfo->is_installer ? "true" : "false", - osinfo->product_name ? osinfo->product_name : "(none)", - (int) osinfo->type, (int) osinfo->distro, - osinfo->major_version, osinfo->minor_version); -#endif - } - } - - return 0; -} - -static int compile_re (guestfs_h *g, xmlNodePtr child, pcre **re); - -/* Read the regular expressions under the <iso> node. libosinfo - * itself uses the glib function 'g_regex_match_simple'. That appears - * to implement PCRE, however I have not checked in detail. - */ -static int -read_iso_node (guestfs_h *g, xmlNodePtr iso_node, struct osinfo *osinfo) -{ - xmlNodePtr child; - - for (child = iso_node->children; child; child = child->next) { - if (STREQ ((const char *) child->name, "system-id")) { - if (compile_re (g, child, &osinfo->re_system_id) == -1) - return -1; - } - else if (STREQ ((const char *) child->name, "volume-id")) { - if (compile_re (g, child, &osinfo->re_volume_id) == -1) - return -1; - } - else if (STREQ ((const char *) child->name, "publisher-id")) { - if (compile_re (g, child, &osinfo->re_publisher_id) == -1) - return -1; - } - else if (STREQ ((const char *) child->name, "application-id")) { - if (compile_re (g, child, &osinfo->re_application_id) == -1) - return -1; - } - } - - return 0; -} - -static int -compile_re (guestfs_h *g, xmlNodePtr node, pcre **re) -{ - const char *err; - int offset; - CLEANUP_FREE char *content = (char *) xmlNodeGetContent (node); - - if (content) { - *re = pcre_compile (content, 0, &err, &offset, NULL); - if (*re == NULL) - debug (g, "osinfo: could not parse regular expression '%s': %s (ignored)", - content, err); - } - - return 0; -} - -/* Read the attributes of the <media/> node. */ -static int -read_media_node (guestfs_h *g, xmlXPathContextPtr xpathCtx, - xmlNodePtr media_node, struct osinfo *osinfo) -{ - osinfo->arch = (char *) xmlGetProp (media_node, BAD_CAST "arch"); - - osinfo->is_live_disk = 0; /* If no 'live' attr, defaults to false. */ - { - CLEANUP_XMLFREE xmlChar *content = NULL; - content = xmlGetProp (media_node, BAD_CAST "live"); - if (content) - osinfo->is_live_disk = XMLSTREQ (content, BAD_CAST "true"); - } - - osinfo->is_installer = true; /* If no 'installer' attr, defaults to true. */ - { - CLEANUP_XMLFREE xmlChar *content = NULL; - content = xmlGetProp (media_node, BAD_CAST "installer"); - if (content) - osinfo->is_installer = XMLSTREQ (content, BAD_CAST "true"); - } - - return 0; -} - -static int parse_version (guestfs_h *g, xmlNodePtr node, struct osinfo *osinfo); -static int parse_family (guestfs_h *g, xmlNodePtr node, struct osinfo *osinfo); -static int parse_distro (guestfs_h *g, xmlNodePtr node, struct osinfo *osinfo); - -/* Read some fields under the <os/> node. */ -static int -read_os_node (guestfs_h *g, xmlXPathContextPtr xpathCtx, - xmlNodePtr os_node, struct osinfo *osinfo) -{ - xmlNodePtr child; - - for (child = os_node->children; child; child = child->next) { - if (STREQ ((const char *) child->name, "name")) - osinfo->product_name = (char *) xmlNodeGetContent (child); - else if (STREQ ((const char *) child->name, "version")) { - if (parse_version (g, child, osinfo) == -1) - return -1; - } - else if (STREQ ((const char *) child->name, "family")) { - if (parse_family (g, child, osinfo) == -1) - return -1; - } - else if (STREQ ((const char *) child->name, "distro")) { - if (parse_distro (g, child, osinfo) == -1) - return -1; - } - } - - return 0; -} - -static int -parse_version (guestfs_h *g, xmlNodePtr node, struct osinfo *osinfo) -{ - CLEANUP_FREE char *content = NULL; - - content = (char *) xmlNodeGetContent (node); - /* We parse either "X.Y" or "X" as version strings, so try to parse - * only if the first character is a digit. - */ - if (content && c_isdigit (content[0])) { - struct version version; - const int res = guestfs_int_version_from_x_y_or_x (g, &version, content); - if (res < 0) - return -1; - else if (res > 0) { - osinfo->major_version = version.v_major; - osinfo->minor_version = version.v_minor; - } - } - - return 0; -} - -static int -parse_family (guestfs_h *g, xmlNodePtr node, struct osinfo *osinfo) -{ - CLEANUP_FREE char *content = NULL; - - osinfo->type = OS_TYPE_UNKNOWN; - - content = (char *) xmlNodeGetContent (node); - if (content) { - if (STREQ (content, "linux")) - osinfo->type = OS_TYPE_LINUX; - else if (STRPREFIX (content, "win")) - osinfo->type = OS_TYPE_WINDOWS; - else if (STREQ (content, "freebsd")) - osinfo->type = OS_TYPE_FREEBSD; - else if (STREQ (content, "netbsd")) - osinfo->type = OS_TYPE_NETBSD; - else if (STREQ (content, "msdos")) - osinfo->type = OS_TYPE_DOS; - else if (STREQ (content, "openbsd")) - osinfo->type = OS_TYPE_OPENBSD; - else - debug (g, "osinfo: warning: unknown <family> '%s'", content); - } - - return 0; -} - -static int -parse_distro (guestfs_h *g, xmlNodePtr node, struct osinfo *osinfo) -{ - CLEANUP_FREE char *content = NULL; - - osinfo->distro = OS_DISTRO_UNKNOWN; - - content = (char *) xmlNodeGetContent (node); - if (content) { - if (STREQ (content, "altlinux")) - osinfo->distro = OS_DISTRO_ALTLINUX; - else if (STREQ (content, "centos")) - osinfo->distro = OS_DISTRO_CENTOS; - else if (STREQ (content, "debian")) - osinfo->distro = OS_DISTRO_DEBIAN; - else if (STREQ (content, "fedora")) - osinfo->distro = OS_DISTRO_FEDORA; - else if (STREQ (content, "freebsd")) - osinfo->distro = OS_DISTRO_FREEBSD; - else if (STREQ (content, "mageia")) - osinfo->distro = OS_DISTRO_MAGEIA; - else if (STREQ (content, "mandriva")) - osinfo->distro = OS_DISTRO_MANDRIVA; - else if (STREQ (content, "netbsd")) - osinfo->distro = OS_DISTRO_NETBSD; - else if (STREQ (content, "openbsd")) - osinfo->distro = OS_DISTRO_OPENBSD; - else if (STREQ (content, "opensuse")) - osinfo->distro = OS_DISTRO_OPENSUSE; - else if (STREQ (content, "rhel")) - osinfo->distro = OS_DISTRO_RHEL; - else if (STREQ (content, "sled") || STREQ (content, "sles")) - osinfo->distro = OS_DISTRO_SLES; - else if (STREQ (content, "ubuntu")) - osinfo->distro = OS_DISTRO_UBUNTU; - else if (STRPREFIX (content, "win")) - osinfo->distro = OS_DISTRO_WINDOWS; - else - debug (g, "osinfo: warning: unknown <distro> '%s'", content); - } - - return 0; -} - -static void -free_osinfo_db_entry (struct osinfo *osinfo) -{ - free (osinfo->product_name); - free (osinfo->arch); - - if (osinfo->re_system_id) - pcre_free (osinfo->re_system_id); - if (osinfo->re_volume_id) - pcre_free (osinfo->re_volume_id); - if (osinfo->re_publisher_id) - pcre_free (osinfo->re_publisher_id); - if (osinfo->re_application_id) - pcre_free (osinfo->re_application_id); -} -- 2.13.0
Richard W.M. Jones
2017-Jun-19 15:39 UTC
[Libguestfs] [PATCH v7 02/29] 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. Note that the OCaml compiler (either ocamlc or ocamlopt) is now required even for building from tarballs. --- .gitignore | 6 +- Makefile.am | 2 +- daemon/Makefile.am | 103 +++++++++++++++++++++++-- daemon/chroot.ml | 85 +++++++++++++++++++++ daemon/chroot.mli | 35 +++++++++ daemon/daemon-c.c | 35 +++++++++ daemon/daemon.ml | 39 ++++++++++ daemon/guestfsd.c | 45 +++++++++++ daemon/sysroot-c.c | 37 +++++++++ daemon/sysroot.ml | 19 +++++ daemon/sysroot.mli | 22 ++++++ daemon/utils.ml | 156 ++++++++++++++++++++++++++++++++++++++ daemon/utils.mli | 65 ++++++++++++++++ docs/guestfs-building.pod | 10 ++- docs/guestfs-hacking.pod | 7 ++ generator/actions.ml | 5 ++ generator/actions.mli | 4 + generator/daemon.ml | 187 ++++++++++++++++++++++++++++++++++++++++++++++ generator/daemon.mli | 3 + generator/main.ml | 6 ++ generator/types.ml | 7 +- 21 files changed, 866 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 8afd06d0e..f37ca263a 100644 --- a/.gitignore +++ b/.gitignore @@ -164,20 +164,24 @@ 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/stamp-guestfsd.pod /daemon/structs-cleanups.c /daemon/structs-cleanups.h /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 9122d44ac..1b091044a 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/daemon/Makefile.am b/daemon/Makefile.am index db19594b8..c7375fb87 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -19,6 +19,7 @@ include $(top_srcdir)/subdir-rules.mk generator_built = \ actions.h \ + caml-stubs.c \ dispatch.c \ names.c \ lvm-tokenization.c \ @@ -31,13 +32,30 @@ generator_built = \ stubs-4.c \ stubs-5.c \ stubs-6.c \ - stubs.h + stubs.h \ + callbacks.ml \ + types.ml 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 = \ - $(BUILT_SOURCES) \ + $(generator_built) \ + $(SOURCES_MLI) $(SOURCES_ML) \ guestfsd.pod if INSTALL_DAEMON @@ -61,6 +79,7 @@ guestfsd_SOURCES = \ blkid.c \ blockdev.c \ btrfs.c \ + caml-stubs.c \ cap.c \ checksum.c \ cleanups.c \ @@ -71,6 +90,7 @@ guestfsd_SOURCES = \ copy.c \ cpio.c \ cpmv.c \ + daemon-c.c \ daemon.h \ dd.c \ debug.c \ @@ -161,6 +181,7 @@ guestfsd_SOURCES = \ swap.c \ sync.c \ syslinux.c \ + sysroot-c.c \ tar.c \ tsk.c \ truncate.c \ @@ -176,10 +197,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 +225,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 +250,69 @@ 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 \ + 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 +334,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..40dfa1dde --- /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 = prog) chroot + { 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..eda3a785f --- /dev/null +++ b/daemon/chroot.mli @@ -0,0 +1,35 @@ +(* 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 parmeter, forking, running the + function and marshalling the result or any exceptions. *) + +type t + +val create : ?name:string -> string -> t +(** Create a chroot handle. [?name] is an optional name used in + debugging and error messages. The string is the chroot + directory. *) + +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..da382bc35 --- /dev/null +++ b/daemon/daemon-c.c @@ -0,0 +1,35 @@ +/* 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/mlvalues.h> + +#include "daemon.h" + +extern value guestfs_int_daemon_get_verbose_flag (value unitv); + +/* NB: This is a "noalloc" call. */ +value +guestfs_int_daemon_get_verbose_flag (value unitv) +{ + return Val_bool (verbose); +} diff --git a/daemon/daemon.ml b/daemon/daemon.ml new file mode 100644 index 000000000..45bac029a --- /dev/null +++ b/daemon/daemon.ml @@ -0,0 +1,39 @@ +(* 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, after initialization but before accepting + * messages, 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 b3f40628b..05337b31c 100644 --- a/daemon/guestfsd.c +++ b/daemon/guestfsd.c @@ -56,6 +56,10 @@ #include <augeas.h> +#include <caml/callback.h> +#include <caml/mlvalues.h> +#include <caml/unixsupport.h> + #include "sockets.h" #include "c-ctype.h" #include "ignore-value.h" @@ -348,6 +352,9 @@ main (int argc, char *argv[]) */ udev_settle (); + /* Initialize the OCaml stubs. */ + caml_startup (argv); + /* Send the magic length message which indicates that * userspace is up inside the guest. */ @@ -1205,3 +1212,41 @@ cleanup_free_mountable (mountable_t *mountable) free (mountable->volume); } } + +/* Convert an OCaml exception to a reply_with_error_errno call + * as best we can. + */ +extern void ocaml_exn_to_reply_with_error (const char *func, value exn); + +void +ocaml_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 + */ + 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: unknown exception thrown: %s", + func, exn_name); +} 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..ecf0d7362 --- /dev/null +++ b/daemon/sysroot.ml @@ -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. + *) + +external sysroot : unit -> string = "guestfs_int_daemon_sysroot" diff --git a/daemon/sysroot.mli b/daemon/sysroot.mli new file mode 100644 index 000000000..88f976476 --- /dev/null +++ b/daemon/sysroot.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 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. *) diff --git a/daemon/utils.ml b/daemon/utils.ml new file mode 100644 index 000000000..7630a5534 --- /dev/null +++ b/daemon/utils.ml @@ -0,0 +1,156 @@ +(* 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 prog args + if verbose () then + eprintf "command: %s %s\n%!" + prog (String.concat " " 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; + dup2 stdout_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 + + 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 + let n = String.length stderr in + if n > 0 && stderr.[n-1] = '\n' then + String.sub stderr 0 (n-1) + else + stderr in + + (r, stdout, stderr) + +let command prog args + let r, stdout, stderr = commandr 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..57f703c6c --- /dev/null +++ b/daemon/utils.mli @@ -0,0 +1,65 @@ +(* 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 : 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. *) + +val commandr : 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/guestfs-building.pod b/docs/guestfs-building.pod index 0f9ed2893..4e1ff7df4 100644 --- a/docs/guestfs-building.pod +++ b/docs/guestfs-building.pod @@ -120,8 +120,7 @@ I<Required>. Part of Perl core. =item OCaml findlib -I<Required> if compiling from git. -Optional (but recommended) if compiling from tarball. +I<Required>. =item autoconf @@ -594,8 +593,11 @@ See L</USING A PREBUILT BINARY APPLIANCE> below. Disable specific language bindings, even if C<./configure> finds all the necessary libraries are installed so that they could be compiled. -Note that disabling OCaml or Perl will have the knock-on effect of -disabling large numbers of virt tools and parts of the test suite. +Note that disabling Perl will have the knock-on effect of disabling +parts of the test suite and some tools. + +Disabling OCaml only disables the bindings and several virt tools. +OCaml is required to build libguestfs. =item B<--disable-fuse> diff --git a/docs/guestfs-hacking.pod b/docs/guestfs-hacking.pod index bfbe4526d..25f4bfa94 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.37.15, 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/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..ac410b733 100644 --- a/generator/daemon.ml +++ b/generator/daemon.ml @@ -471,6 +471,193 @@ 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\" + +/* This is not declared in <daemon.h> because we don't want to + * include the OCaml headers (to get 'value') for the whole daemon. + */ +extern void ocaml_exn_to_reply_with_error (const char *func, value exn); + +"; + + 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 "/* 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 & %s_%s_BITMASK) == 0)\n" + f.c_optarg_prefix 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 (_, n) -> pr "caml_copy_string (%s)" n + | 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 " ocaml_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 -> assert false + | RInt _ -> assert false + | RInt64 _ -> assert false + | RBool _ -> assert false + | RConstString _ -> assert false + | RConstOptString _ -> assert false + | RString _ -> + 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" + | RStringList _ -> assert false + | RStruct _ -> assert false + | RStructList _ -> assert false + | RHashtable _ -> assert false + | RBufferOut _ -> assert false + ); + pr "}\n"; + pr "\n" + ) (actions |> impl_ocaml_functions |> sort) + let generate_daemon_dispatch () generate_header CStyle GPLv2plus; diff --git a/generator/daemon.mli b/generator/daemon.mli index ff008bf85..314a6da8f 100644 --- a/generator/daemon.mli +++ b/generator/daemon.mli @@ -19,6 +19,9 @@ 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 diff --git a/generator/main.ml b/generator/main.ml index 33fe2b2ee..a6c805e2e 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" 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.0
Richard W.M. Jones
2017-Jun-19 15:39 UTC
[Libguestfs] [PATCH v7 03/29] 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 | 80 ----------------------------------------------- daemon/file.ml | 60 +++++++++++++++++++++++++++++++++++ daemon/file.mli | 19 +++++++++++ generator/actions_core.ml | 1 + 5 files changed, 82 insertions(+), 80 deletions(-) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index c7375fb87..c11f51cef 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -256,6 +256,7 @@ guestfsd_CFLAGS = \ SOURCES_MLI = \ chroot.mli \ sysroot.mli \ + file.mli \ utils.mli SOURCES_ML = \ @@ -263,6 +264,7 @@ SOURCES_ML = \ utils.ml \ sysroot.ml \ chroot.ml \ + file.ml \ callbacks.ml \ daemon.ml diff --git a/daemon/file.c b/daemon/file.c index 84874dc6f..ee79eb507 100644 --- a/daemon/file.c +++ b/daemon/file.c @@ -30,7 +30,6 @@ #include "actions.h" #include "optgroups.h" -GUESTFSD_EXT_CMD(str_file, file); GUESTFSD_EXT_CMD(str_zcat, zcat); GUESTFSD_EXT_CMD(str_bzcat, bzcat); @@ -449,85 +448,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, str_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..557de764b --- /dev/null +++ b/daemon/file.ml @@ -0,0 +1,60 @@ +(* 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 sysroot = Sysroot.sysroot () in + let chroot = Chroot.create sysroot ~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 // 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.0
Richard W.M. Jones
2017-Jun-19 15:39 UTC
[Libguestfs] [PATCH v7 04/29] daemon: Reimplement ‘vfs_type’ API in OCaml.
This also implements support for String (Mountable, _) parameters. --- 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 + generator/daemon.ml | 38 ++++++++++++++++++++++++++++++++++++-- 8 files changed, 177 insertions(+), 8 deletions(-) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index c11f51cef..a9d7fb9bd 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -254,16 +254,20 @@ 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 = \ types.ml \ utils.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 1fe5ff93a..7757b5ad0 100644 --- a/daemon/blkid.c +++ b/daemon/blkid.c @@ -69,12 +69,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..3345f826e --- /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.trimr out + + | 2 -> (* means tag not found, we return "" *) + "" + + | _ -> + failwithf "blkid: %s: %s" 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"), [] diff --git a/generator/daemon.ml b/generator/daemon.ml index ac410b733..121634806 100644 --- a/generator/daemon.ml +++ b/generator/daemon.ml @@ -524,6 +524,35 @@ let generate_daemon_caml_stubs () */ extern void ocaml_exn_to_reply_with_error (const char *func, value exn); +/* Implement String (Mountable, _) parameter. */ +static value +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); +} + "; List.iter ( @@ -602,7 +631,11 @@ extern void ocaml_exn_to_reply_with_error (const char *func, value exn); | Bool n -> pr "Val_bool (%s)" n | Int n -> pr "Val_int (%s)" n | Int64 n -> pr "caml_copy_int64 (%s)" n - | String (_, n) -> pr "caml_copy_string (%s)" n + | String ((PlainString|Device|Dev_or_Path), n) -> + pr "caml_copy_string (%s)" n + | String (Mountable, n) -> + pr "copy_mountable (%s)" n + | String _ -> assert false | OptString _ -> assert false | StringList _ -> assert false | BufferIn _ -> assert false @@ -641,13 +674,14 @@ extern void ocaml_exn_to_reply_with_error (const char *func, value exn); | RBool _ -> assert false | RConstString _ -> assert false | RConstOptString _ -> assert false - | RString _ -> + | RString (RPlainString, _) -> 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 _ -> assert false | RStringList _ -> assert false | RStruct _ -> assert false | RStructList _ -> assert false -- 2.13.0
Richard W.M. Jones
2017-Jun-19 15:39 UTC
[Libguestfs] [PATCH v7 05/29] 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 | 3 - daemon/devsparts.c | 257 ---------------------------------------------- daemon/devsparts.ml | 109 ++++++++++++++++++++ daemon/devsparts.mli | 25 +++++ daemon/guestfsd.c | 75 -------------- daemon/utils.ml | 84 +++++++++++++++ daemon/utils.mli | 15 +++ generator/actions_core.ml | 5 + generator/daemon.ml | 32 +++++- 10 files changed, 268 insertions(+), 339 deletions(-) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index a9d7fb9bd..9e53aef7a 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -257,6 +257,7 @@ SOURCES_MLI = \ blkid.mli \ chroot.mli \ sysroot.mli \ + devsparts.mli \ file.mli \ mountable.mli \ utils.mli @@ -268,6 +269,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 be7a3bedc..0a92e6cee 100644 --- a/daemon/daemon.h +++ b/daemon/daemon.h @@ -130,9 +130,6 @@ extern void free_stringsbuf (struct stringsbuf *sb); 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 struct stringsbuf split_lines_sb (char *str); extern char **split_lines (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..e97ff1267 --- /dev/null +++ b/daemon/devsparts.ml @@ -0,0 +1,109 @@ +(* 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 close (openfile ("/dev/" ^ dev) [O_RDONLY; O_CLOEXEC] 0); 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 (fun dev -> "/dev/" ^ 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 (fun part -> "/dev/" ^ part) 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 05337b31c..9704094a6 100644 --- a/daemon/guestfsd.c +++ b/daemon/guestfsd.c @@ -630,81 +630,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 7630a5534..48f6b9c5c 100644 --- a/daemon/utils.ml +++ b/daemon/utils.ml @@ -129,6 +129,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 57f703c6c..a1f956be3 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"]]), []; diff --git a/generator/daemon.ml b/generator/daemon.ml index 121634806..3ffe91537 100644 --- a/generator/daemon.ml +++ b/generator/daemon.ml @@ -553,6 +553,26 @@ copy_mountable (const mountable_t *mountable) CAMLreturn (r); } +/* Implement RStringList. */ +static char ** +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 */ +} + "; List.iter ( @@ -669,12 +689,14 @@ copy_mountable (const mountable_t *mountable) (match ret with | RErr -> assert false - | RInt _ -> assert false + | RInt _ -> + pr " CAMLreturnT (int, Int_val (retv));\n" | RInt64 _ -> assert false - | RBool _ -> assert false + | RBool _ -> + pr " CAMLreturnT (int, Bool_val (retv));\n" | RConstString _ -> assert false | RConstOptString _ -> assert false - | RString (RPlainString, _) -> + | RString ((RPlainString|RDevice), _) -> pr " char *ret = strdup (String_val (retv));\n"; pr " if (ret == NULL) {\n"; pr " reply_with_perror (\"strdup\");\n"; @@ -682,7 +704,9 @@ copy_mountable (const mountable_t *mountable) pr " }\n"; pr " CAMLreturnT (char *, ret); /* caller frees */\n" | RString _ -> assert false - | RStringList _ -> assert false + | RStringList _ -> + pr " char **ret = return_string_list (retv);\n"; + pr " CAMLreturnT (char **, ret); /* caller frees */\n" | RStruct _ -> assert false | RStructList _ -> assert false | RHashtable _ -> assert false -- 2.13.0
Richard W.M. Jones
2017-Jun-19 15:39 UTC
[Libguestfs] [PATCH v7 06/29] daemon: Add unit tests of the ‘Utils’ module.
--- .gitignore | 1 + daemon/Makefile.am | 43 ++++++++++++++++++++++++++++++++++++++- daemon/daemon_utils_tests.ml | 48 ++++++++++++++++++++++++++++++++++++++++++++ daemon/dummy.c | 2 ++ docs/C_SOURCE_FILES | 1 + 5 files changed, 94 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f37ca263a..29596594a 100644 --- a/.gitignore +++ b/.gitignore @@ -168,6 +168,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 9e53aef7a..a7cd5d5c6 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -56,6 +56,7 @@ BUILT_SOURCES = \ EXTRA_DIST = \ $(generator_built) \ $(SOURCES_MLI) $(SOURCES_ML) \ + daemon_utils_tests.ml \ guestfsd.pod if INSTALL_DAEMON @@ -280,7 +281,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) @@ -307,6 +309,45 @@ camldaemon.o: $(OBJECTS) -linkpkg mlcutils.$(MLARCHIVE) mlstdutils.$(MLARCHIVE) \ $(OBJECTS) +# Unit tests. + +check_PROGRAMS = daemon_utils_tests +TESTS = daemon_utils_tests + +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 $@ + +check-valgrind: + $(MAKE) VG="@VG@" check + # OCaml dependencies. depend: .depend 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 61cdbea38..e7c457e92 100644 --- a/docs/C_SOURCE_FILES +++ b/docs/C_SOURCE_FILES @@ -93,6 +93,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.0
Richard W.M. Jones
2017-Jun-19 15:39 UTC
[Libguestfs] [PATCH v7 07/29] 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 | 44 ++++++++++++++++++++++++++++++++++++++++++++ daemon/is.mli | 21 +++++++++++++++++++++ generator/actions_core.ml | 3 +++ generator/daemon.ml | 7 ++++--- 6 files changed, 74 insertions(+), 44 deletions(-) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index a7cd5d5c6..2fffceffb 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -260,6 +260,7 @@ SOURCES_MLI = \ sysroot.mli \ devsparts.mli \ file.mli \ + is.mli \ mountable.mli \ utils.mli @@ -272,6 +273,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..b99215737 --- /dev/null +++ b/daemon/is.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 Printf +open Unix + +let rec is_file ?(followsymlinks = false) path + let sysroot = Sysroot.sysroot () in + let chroot = Chroot.create sysroot ~name:(sprintf "is_file: %s" path) in + Chroot.f chroot get_kind (path, followsymlinks) = Some S_REG + +and is_dir ?(followsymlinks = false) path + let sysroot = Sysroot.sysroot () in + let chroot = Chroot.create sysroot ~name:(sprintf "is_dir: %s" path) in + Chroot.f chroot get_kind (path, followsymlinks) = Some S_DIR + +and is_symlink path + let sysroot = Sysroot.sysroot () in + let chroot = Chroot.create sysroot ~name:(sprintf "is_symlink: %s" path) in + Chroot.f chroot get_kind (path, false) = Some S_LNK + +and get_kind (path, followsymlinks) + 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 *) 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"]]), []; diff --git a/generator/daemon.ml b/generator/daemon.ml index 3ffe91537..ef6086bfe 100644 --- a/generator/daemon.ml +++ b/generator/daemon.ml @@ -577,6 +577,7 @@ return_string_list (value retv) 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 @@ -625,8 +626,8 @@ return_string_list (value retv) let uc_n = String.uppercase_ascii n in (* optargs are all passed as [None|Some _] *) - pr " if ((optargs_bitmask & %s_%s_BITMASK) == 0)\n" - f.c_optarg_prefix uc_n; + 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 = "; @@ -651,7 +652,7 @@ return_string_list (value retv) | 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|Dev_or_Path), n) -> + | String ((PlainString|Device|Pathname|Dev_or_Path), n) -> pr "caml_copy_string (%s)" n | String (Mountable, n) -> pr "copy_mountable (%s)" n -- 2.13.0
Richard W.M. Jones
2017-Jun-19 15:39 UTC
[Libguestfs] [PATCH v7 08/29] daemon: Reimplement ‘readlink’ API in OCaml.
--- daemon/Makefile.am | 2 ++ daemon/link.c | 16 ---------------- daemon/link.ml | 25 +++++++++++++++++++++++++ daemon/link.mli | 19 +++++++++++++++++++ generator/actions_core.ml | 1 + 5 files changed, 47 insertions(+), 16 deletions(-) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 2fffceffb..8171985b4 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -261,6 +261,7 @@ SOURCES_MLI = \ devsparts.mli \ file.mli \ is.mli \ + link.mli \ mountable.mli \ utils.mli @@ -274,6 +275,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 3ce54fa37..dde61a1c2 100644 --- a/daemon/link.c +++ b/daemon/link.c @@ -32,22 +32,6 @@ GUESTFSD_EXT_CMD(str_ln, ln); -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..ba53fd6b5 --- /dev/null +++ b/daemon/link.ml @@ -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. + *) + +open Printf +open Unix + +let readlink path + let sysroot = Sysroot.sysroot () in + let chroot = Chroot.create sysroot ~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.0
Richard W.M. Jones
2017-Jun-19 15:39 UTC
[Libguestfs] [PATCH v7 09/29] 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 | 43 ++++++++++++++++++++ daemon/daemon.h | 6 --- daemon/mount.c | 99 ----------------------------------------------- daemon/mount.ml | 62 +++++++++++++++++++++++++++++ daemon/mount.mli | 22 +++++++++++ generator/actions_core.ml | 4 ++ generator/daemon.ml | 3 +- 8 files changed, 135 insertions(+), 106 deletions(-) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 8171985b4..d5703b595 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -262,6 +262,7 @@ SOURCES_MLI = \ file.mli \ is.mli \ link.mli \ + mount.mli \ mountable.mli \ utils.mli @@ -276,6 +277,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 5f1e5d1d0..4f52b71e8 100644 --- a/daemon/btrfs.c +++ b/daemon/btrfs.c @@ -37,6 +37,7 @@ GUESTFSD_EXT_CMD(str_btrfs, btrfs); GUESTFSD_EXT_CMD(str_btrfstune, btrfstune); GUESTFSD_EXT_CMD(str_btrfsck, btrfsck); GUESTFSD_EXT_CMD(str_mkfs_btrfs, mkfs.btrfs); +GUESTFSD_EXT_CMD(str_mount, mount); GUESTFSD_EXT_CMD(str_umount, umount); GUESTFSD_EXT_CMD(str_btrfsimage, btrfs-image); @@ -387,6 +388,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, + str_mount, "-o", options_plus ? options_plus : options, + "-t", vfstype, device, mp, NULL); + else + r = command (NULL, &error, + str_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 0a92e6cee..62e1211c8 100644 --- a/daemon/daemon.h +++ b/daemon/daemon.h @@ -94,12 +94,6 @@ extern void cleanup_free_stringsbuf (void *ptr); #define CLEANUP_FREE_STRINGSBUF #endif -/*-- in mount.c --*/ - -extern int mount_vfs_nochroot (const char *options, const char *vfstype, - const mountable_t *mountable, - const char *mp, const char *user_mp); - /* Growable strings buffer. */ struct stringsbuf { char **argv; diff --git a/daemon/mount.c b/daemon/mount.c index 0ad9626a7..962b86079 100644 --- a/daemon/mount.c +++ b/daemon/mount.c @@ -111,105 +111,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, - str_mount, "-o", options_plus ? options_plus : options, - "-t", vfstype, device, mp, NULL); - else - r = command (NULL, &error, - str_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..4bb74fb82 --- /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 () // 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 diff --git a/generator/daemon.ml b/generator/daemon.ml index ef6086bfe..fd01e5d8a 100644 --- a/generator/daemon.ml +++ b/generator/daemon.ml @@ -689,7 +689,8 @@ return_string_list (value retv) pr "\n"; (match ret with - | RErr -> assert false + | RErr -> + pr " CAMLreturnT (int, 0);\n" | RInt _ -> pr " CAMLreturnT (int, Int_val (retv));\n" | RInt64 _ -> assert false -- 2.13.0
Richard W.M. Jones
2017-Jun-19 15:39 UTC
[Libguestfs] [PATCH v7 10/29] 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 d5703b595..1035d7ea2 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -264,6 +264,7 @@ SOURCES_MLI = \ link.mli \ mount.mli \ mountable.mli \ + parted.mli \ utils.mli SOURCES_ML = \ @@ -278,6 +279,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 03e83cb32..a1e5c81cf 100644 --- a/daemon/parted.c +++ b/daemon/parted.c @@ -521,48 +521,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, str_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.0
Richard W.M. Jones
2017-Jun-19 15:39 UTC
[Libguestfs] [PATCH v7 11/29] daemon: Reimplement ‘case_sensitive_path’ API in OCaml.
--- daemon/Makefile.am | 2 + daemon/realpath.c | 187 ---------------------------------------------- daemon/realpath.ml | 83 ++++++++++++++++++++ daemon/realpath.mli | 19 +++++ generator/actions_core.ml | 1 + 5 files changed, 105 insertions(+), 187 deletions(-) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 1035d7ea2..d56c99123 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -265,6 +265,7 @@ SOURCES_MLI = \ mount.mli \ mountable.mli \ parted.mli \ + realpath.mli \ utils.mli SOURCES_ML = \ @@ -280,6 +281,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..cffe86322 --- /dev/null +++ b/daemon/realpath.ml @@ -0,0 +1,83 @@ +(* 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 sysroot = Sysroot.sysroot () in + let chroot = Chroot.create sysroot + ~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.0
Richard W.M. Jones
2017-Jun-19 15:39 UTC
[Libguestfs] [PATCH v7 12/29] 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 | 137 +++++++++++++++++ daemon/filearch.mli | 19 +++ docs/C_SOURCE_FILES | 4 +- 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 d56c99123..e86435c4c 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -260,6 +260,7 @@ SOURCES_MLI = \ sysroot.mli \ devsparts.mli \ file.mli \ + filearch.mli \ is.mli \ link.mli \ mount.mli \ @@ -277,6 +278,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..68ddd61ea --- /dev/null +++ b/daemon/filearch.ml @@ -0,0 +1,137 @@ +(* 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 + +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. Caller must free the result. + *) +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 sysroot = Sysroot.sysroot () in + + 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 = sprintf "/tmp/%s" (String.random8 ()) in + mkdir tmpdir 0o700; + + (* 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 // 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 e7c457e92..9562aed89 100644 --- a/docs/C_SOURCE_FILES +++ b/docs/C_SOURCE_FILES @@ -71,6 +71,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 @@ -81,6 +82,7 @@ daemon/compress.c daemon/copy.c daemon/cpio.c daemon/cpmv.c +daemon/daemon-c.c daemon/daemon.h daemon/dd.c daemon/debug-bmap.c @@ -172,6 +174,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 @@ -295,7 +298,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 bf3406b16..71dc25b9b 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 \ @@ -155,7 +154,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 0d8a924b6..1a38e8ed4 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -354,7 +354,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.0
Richard W.M. Jones
2017-Jun-19 15:39 UTC
[Libguestfs] [PATCH v7 13/29] daemon: Reimplement ‘list_ldm_(volumes|partitions)’ APIs in OCaml.
--- daemon/Makefile.am | 2 ++ daemon/ldm.c | 82 ----------------------------------------------- daemon/ldm.ml | 52 ++++++++++++++++++++++++++++++ daemon/ldm.mli | 20 ++++++++++++ generator/actions_core.ml | 2 ++ 5 files changed, 76 insertions(+), 82 deletions(-) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index e86435c4c..5d79dc830 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -262,6 +262,7 @@ SOURCES_MLI = \ file.mli \ filearch.mli \ is.mli \ + ldm.mli \ link.mli \ mount.mli \ mountable.mli \ @@ -280,6 +281,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 75418e8d3..5106e65f9 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> @@ -47,87 +46,6 @@ optgroup_ldm_available (void) return prog_exists (str_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..dc7b36f9c --- /dev/null +++ b/daemon/ldm.ml @@ -0,0 +1,52 @@ +(* 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_*. XXX We + * could tighten this up in future if ldmtool had a way to read these + * names back after they have been created. + *) +let list_ldm_volumes () + (* 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 "ldm_vol_") dir in + let dir = List.map ((^) "/dev/mapper/") dir in + List.sort compare dir + ) + +(* Same as above but /dev/mapper/ldm_part_*. *) +let list_ldm_partitions () + (* 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 "ldm_part_") 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.0
Richard W.M. Jones
2017-Jun-19 15:39 UTC
[Libguestfs] [PATCH v7 14/29] daemon: Reimplement ‘lvs’ API in OCaml.
--- daemon/Makefile.am | 2 + daemon/lvm.c | 151 ---------------------------------------------- daemon/lvm.ml | 92 ++++++++++++++++++++++++++++ daemon/lvm.mli | 19 ++++++ generator/actions_core.ml | 1 + 5 files changed, 114 insertions(+), 151 deletions(-) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 5d79dc830..abd45b744 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -264,6 +264,7 @@ SOURCES_MLI = \ is.mli \ ldm.mli \ link.mli \ + lvm.mli \ mount.mli \ mountable.mli \ parted.mli \ @@ -283,6 +284,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 5d12b009f..072bf53b4 100644 --- a/daemon/lvm.c +++ b/daemon/lvm.c @@ -103,89 +103,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) { @@ -222,74 +139,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, str_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, - str_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, - str_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..55421b628 --- /dev/null +++ b/daemon/lvm.ml @@ -0,0 +1,92 @@ +(* 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 + +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 = 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.0
Richard W.M. Jones
2017-Jun-19 15:39 UTC
[Libguestfs] [PATCH v7 15/29] 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 abd45b744..6ff71bb1f 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -265,6 +265,7 @@ SOURCES_MLI = \ ldm.mli \ link.mli \ lvm.mli \ + md.mli \ mount.mli \ mountable.mli \ parted.mli \ @@ -285,6 +286,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 64d98fae5..5c9ecd136 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" @@ -45,6 +46,35 @@ optgroup_mdadm_available (void) return prog_exists (str_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) { @@ -188,99 +218,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.0
Richard W.M. Jones
2017-Jun-19 15:39 UTC
[Libguestfs] [PATCH v7 16/29] daemon: Generate OCaml wrappers for optgroup_*_available functions.
It is sometimes useful to be able to call these from OCaml code. --- generator/daemon.ml | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/generator/daemon.ml b/generator/daemon.ml index fd01e5d8a..1d7461f8c 100644 --- a/generator/daemon.ml +++ b/generator/daemon.ml @@ -976,6 +976,10 @@ let generate_daemon_optgroups_c () generate_header CStyle GPLv2plus; pr "#include <config.h>\n"; + pr "#include <stdio.h>\n"; + pr "#include <stdlib.h>\n"; + pr "\n"; + pr "#include <caml/mlvalues.h>\n"; pr "\n"; pr "#include \"daemon.h\"\n"; pr "#include \"optgroups.h\"\n"; @@ -999,7 +1003,24 @@ 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 -> + if not (List.mem group optgroups_retired) then ( + 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_all let generate_daemon_optgroups_h () generate_header CStyle GPLv2plus; -- 2.13.0
Richard W.M. Jones
2017-Jun-19 15:39 UTC
[Libguestfs] [PATCH v7 17/29] daemon: Enable RStruct, RStructList for OCaml-implemented APIs.
--- .gitignore | 1 + daemon/Makefile.am | 1 + generator/OCaml.ml | 8 ++++ generator/OCaml.mli | 1 + generator/daemon.ml | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++- generator/main.ml | 2 + 6 files changed, 127 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 29596594a..8aea2cdb4 100644 --- a/.gitignore +++ b/.gitignore @@ -180,6 +180,7 @@ Makefile.in /daemon/stamp-guestfsd.pod /daemon/structs-cleanups.c /daemon/structs-cleanups.h +/daemon/structs.ml /daemon/stubs-?.c /daemon/stubs.h /daemon/types.ml diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 6ff71bb1f..4a818a7a9 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -275,6 +275,7 @@ SOURCES_MLI = \ SOURCES_ML = \ types.ml \ utils.ml \ + structs.ml \ sysroot.ml \ mountable.ml \ chroot.ml \ 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/daemon.ml b/generator/daemon.ml index 1d7461f8c..8cac5ccb1 100644 --- a/generator/daemon.ml +++ b/generator/daemon.ml @@ -575,6 +575,110 @@ return_string_list (value retv) "; + (* 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 @@ -709,8 +813,16 @@ return_string_list (value retv) | RStringList _ -> pr " char **ret = return_string_list (retv);\n"; pr " CAMLreturnT (char **, ret); /* caller frees */\n" - | RStruct _ -> assert false - | RStructList _ -> assert false + | 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 _ -> assert false | RBufferOut _ -> assert false ); diff --git a/generator/main.ml b/generator/main.ml index a6c805e2e..72f704b8e 100644 --- a/generator/main.ml +++ b/generator/main.ml @@ -191,6 +191,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; -- 2.13.0
Richard W.M. Jones
2017-Jun-19 15:39 UTC
[Libguestfs] [PATCH v7 18/29] 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 + generator/daemon.ml | 5 +- 6 files changed, 160 insertions(+), 177 deletions(-) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 4a818a7a9..459b5d7cc 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -256,6 +256,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 \ @@ -280,6 +281,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 4f52b71e8..d9043d53c 100644 --- a/daemon/btrfs.c +++ b/daemon/btrfs.c @@ -41,11 +41,6 @@ GUESTFSD_EXT_CMD(str_mount, mount); GUESTFSD_EXT_CMD(str_umount, umount); GUESTFSD_EXT_CMD(str_btrfsimage, btrfs-image); -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 @@ -483,137 +478,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, str_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) { @@ -649,45 +513,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, str_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..554212ccf --- /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 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 with_mounted mountable f + let tmpdir = sprintf "/tmp/%s" (String.random8 ()) 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 + + 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 () // mountable.m_device) + + | MountableDevice -> + protect ~finally ~f:( + fun () -> + mkdir tmpdir 0o700; + ignore (command "mount" [mountable.m_device; tmpdir]); + f tmpdir + ) + + | MountableBtrfsVol subvol -> + protect ~finally ~f:( + fun () -> + mkdir tmpdir 0o700; + ignore (command "mount" ["-o"; "subvol=" ^ subvol (* XXX quoting? *); + mountable.m_device; tmpdir]); + f tmpdir + ) + +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 + + (* 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. + *) + filter_map ( + fun line -> + if line = "" then None + else 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 + + Some { + 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 ( diff --git a/generator/daemon.ml b/generator/daemon.ml index 8cac5ccb1..83994e9d3 100644 --- a/generator/daemon.ml +++ b/generator/daemon.ml @@ -758,7 +758,7 @@ return_string_list (value retv) | Int64 n -> pr "caml_copy_int64 (%s)" n | String ((PlainString|Device|Pathname|Dev_or_Path), n) -> pr "caml_copy_string (%s)" n - | String (Mountable, n) -> + | String ((Mountable|Mountable_or_Path), n) -> pr "copy_mountable (%s)" n | String _ -> assert false | OptString _ -> assert false @@ -797,7 +797,8 @@ return_string_list (value retv) pr " CAMLreturnT (int, 0);\n" | RInt _ -> pr " CAMLreturnT (int, Int_val (retv));\n" - | RInt64 _ -> assert false + | RInt64 _ -> + pr " CAMLreturnT (int, Int64_val (retv));\n" | RBool _ -> pr " CAMLreturnT (int, Bool_val (retv));\n" | RConstString _ -> assert false -- 2.13.0
Richard W.M. Jones
2017-Jun-19 15:39 UTC
[Libguestfs] [PATCH v7 19/29] 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 | 3 + daemon/ldm.mli | 2 + daemon/listfs.ml | 156 +++++++++++++++++++++++++++++ daemon/listfs.mli | 19 ++++ daemon/lvm.ml | 3 + daemon/lvm.mli | 2 + docs/C_SOURCE_FILES | 1 - generator/actions_core.ml | 75 +++++++------- generator/daemon.ml | 59 ++++++++++- generator/proc_nr.ml | 1 + lib/MAX_PROC_NR | 2 +- lib/Makefile.am | 1 - lib/listfs.c | 246 ---------------------------------------------- 14 files changed, 285 insertions(+), 287 deletions(-) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 459b5d7cc..fbe4734cf 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -265,6 +265,7 @@ SOURCES_MLI = \ is.mli \ ldm.mli \ link.mli \ + listfs.mli \ lvm.mli \ md.mli \ mount.mli \ @@ -292,6 +293,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 dc7b36f9c..19cd03e83 100644 --- a/daemon/ldm.ml +++ b/daemon/ldm.ml @@ -20,6 +20,9 @@ open Std_utils open Utils +external available : unit -> bool + "guestfs_int_daemon_optgroup_lvm2_available" "noalloc" + (* All device mapper devices are 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. 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..df5404f81 --- /dev/null +++ b/daemon/listfs.ml @@ -0,0 +1,156 @@ +(* 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 = List.fold_left ( + fun devices part -> + let d = Devsparts.part_to_dev part in + List.filter ((<>) d) devices + ) devices partitions 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 55421b628..14f0a8578 100644 --- a/daemon/lvm.ml +++ b/daemon/lvm.ml @@ -22,6 +22,9 @@ open Std_utils open Utils +external available : unit -> bool + "guestfs_int_daemon_optgroup_lvm2_available" "noalloc" + let lvs_has_S_opt = lazy ( let out = command "lvm" ["lvs"; "--help"] in String.find out "-S" >= 0 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 9562aed89..71e350764 100644 --- a/docs/C_SOURCE_FILES +++ b/docs/C_SOURCE_FILES @@ -321,7 +321,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/daemon.ml b/generator/daemon.ml index 83994e9d3..66b625388 100644 --- a/generator/daemon.ml +++ b/generator/daemon.ml @@ -573,6 +573,58 @@ return_string_list (value retv) return take_stringsbuf (&ret); /* caller frees */ } +/* Implement RString (RMountable, _). */ +static char * +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 (RMountable, RPlainString, _). */ +static char ** +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 = 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 */ +} + "; (* Implement code for returning structs and struct lists. *) @@ -810,7 +862,9 @@ return_string_list (value retv) pr " CAMLreturnT (char *, NULL);\n"; pr " }\n"; pr " CAMLreturnT (char *, ret); /* caller frees */\n" - | RString _ -> assert false + | RString (RMountable, _) -> + pr " char *ret = return_string_mountable (retv);\n"; + pr " CAMLreturnT (char *, ret); /* caller frees */\n" | RStringList _ -> pr " char **ret = return_string_list (retv);\n"; pr " CAMLreturnT (char **, ret); /* caller frees */\n" @@ -824,6 +878,9 @@ return_string_list (value retv) pr " return_%s_list (retv);\n" typ; pr " /* caller frees */\n"; pr " CAMLreturnT (guestfs_int_%s_list *, ret);\n" typ + | RHashtable (RMountable, RPlainString, _) -> + pr " char **ret = return_hashtable_mountable_string (retv);\n"; + pr " CAMLreturnT (char **, ret); /* caller frees */\n" | RHashtable _ -> assert false | RBufferOut _ -> assert false ); 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 71dc25b9b..7eaff88ee 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -107,7 +107,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.0
Richard W.M. Jones
2017-Jun-19 15:39 UTC
[Libguestfs] [PATCH v7 20/29] daemon: Reimplement ‘part_list’ API in OCaml.
--- daemon/parted.c | 56 ----------------------------------------------- daemon/parted.ml | 51 ++++++++++++++++++++++++++++++++++++++++++ daemon/parted.mli | 8 +++++++ generator/actions_core.ml | 1 + 4 files changed, 60 insertions(+), 56 deletions(-) diff --git a/daemon/parted.c b/daemon/parted.c index a1e5c81cf..125aec60b 100644 --- a/daemon/parted.c +++ b/daemon/parted.c @@ -387,62 +387,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..37e1b42be 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,52 @@ let part_get_mbr_id device partnum (* It's printed in hex, possibly with a leading space. *) sscanf out " %x" identity + +let print_partition_table ~add_m_option device + udev_settle (); + + let args = ref [] in + if add_m_option then 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 ~add_m_option:true 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.0
Richard W.M. Jones
2017-Jun-19 15:39 UTC
[Libguestfs] [PATCH v7 21/29] 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 | 94 ----------------------------------------------- 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(+), 96 deletions(-) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index fbe4734cf..087f67258 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -108,7 +108,6 @@ guestfsd_SOURCES = \ ext2.c \ fallocate.c \ file.c \ - findfs.c \ fill.c \ find.c \ format.c \ @@ -262,6 +261,7 @@ SOURCES_MLI = \ devsparts.mli \ file.mli \ filearch.mli \ + findfs.mli \ is.mli \ ldm.mli \ link.mli \ @@ -290,6 +290,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 f44137038..000000000 --- a/daemon/findfs.c +++ /dev/null @@ -1,94 +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" - -GUESTFSD_EXT_CMD(str_findfs, findfs); - -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, str_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 14f0a8578..5dd01d6b2 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 @@ -93,3 +94,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 71e350764..5ec6f77bb 100644 --- a/docs/C_SOURCE_FILES +++ b/docs/C_SOURCE_FILES @@ -102,7 +102,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.0
Richard W.M. Jones
2017-Jun-19 15:39 UTC
[Libguestfs] [PATCH v7 22/29] 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 e97ff1267..273612516 100644 --- a/daemon/devsparts.ml +++ b/daemon/devsparts.ml @@ -85,6 +85,8 @@ and add_partitions dev let parts = List.filter (fun part -> String.is_prefix part dev) parts in List.map (fun part -> "/dev/" ^ part) 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.0
Richard W.M. Jones
2017-Jun-19 15:39 UTC
[Libguestfs] [PATCH v7 23/29] daemon: Reimplement ‘md_detail’ API in OCaml.
--- daemon/md.c | 66 ----------------------------------------------- daemon/md.ml | 37 ++++++++++++++++++++++++++ daemon/md.mli | 1 + generator/actions_core.ml | 1 + generator/daemon.ml | 27 +++++++++++++++++++ 5 files changed, 66 insertions(+), 66 deletions(-) diff --git a/daemon/md.c b/daemon/md.c index 5c9ecd136..549dd89fa 100644 --- a/daemon/md.c +++ b/daemon/md.c @@ -218,72 +218,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[] = { str_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..ba045b5f7 100644 --- a/daemon/md.ml +++ b/daemon/md.ml @@ -46,3 +46,40 @@ 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 + + (* 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 + *) + filter_map ( + fun line -> + (* Skip blank lines (shouldn't happen). *) + if line = "" then None + else ( + (* 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. *) + Some (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 = "\ diff --git a/generator/daemon.ml b/generator/daemon.ml index 66b625388..f20c87bea 100644 --- a/generator/daemon.ml +++ b/generator/daemon.ml @@ -597,6 +597,30 @@ return_string_mountable (value retv) } } +/* Implement RHashtable (RPlainString, RPlainString, _). */ +static char ** +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, _). */ static char ** return_hashtable_mountable_string (value retv) @@ -878,6 +902,9 @@ return_hashtable_mountable_string (value retv) 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 = return_hashtable_string_string (retv);\n"; + pr " CAMLreturnT (char **, ret); /* caller frees */\n" | RHashtable (RMountable, RPlainString, _) -> pr " char **ret = return_hashtable_mountable_string (retv);\n"; pr " CAMLreturnT (char **, ret); /* caller frees */\n" -- 2.13.0
Richard W.M. Jones
2017-Jun-19 15:39 UTC
[Libguestfs] [PATCH v7 24/29] daemon: Reimplement ‘realpath’ API in OCaml.
--- daemon/Makefile.am | 1 - daemon/realpath.c | 50 ----------------------------------------------- daemon/realpath.ml | 5 +++++ daemon/realpath.mli | 1 + docs/C_SOURCE_FILES | 1 - generator/actions_core.ml | 1 + 6 files changed, 7 insertions(+), 52 deletions(-) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 087f67258..5a5bc9855 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -153,7 +153,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 cffe86322..4b4971dd7 100644 --- a/daemon/realpath.ml +++ b/daemon/realpath.ml @@ -20,6 +20,11 @@ open Printf open Std_utils +let realpath path + let chroot = Chroot.create ~name:(sprintf "realpath: %s" path) + (Sysroot.sysroot ()) 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 5ec6f77bb..0b55c122f 100644 --- a/docs/C_SOURCE_FILES +++ b/docs/C_SOURCE_FILES @@ -145,7 +145,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.0
Richard W.M. Jones
2017-Jun-19 15:39 UTC
[Libguestfs] [PATCH v7 25/29] daemon: Implement command flag CommandFlagFoldStdoutOnStderr.
Used to handle broken commands like parted, sgdisk which print errors on stdout. --- daemon/utils.ml | 19 ++++++++++++++----- daemon/utils.mli | 11 +++++++++-- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/daemon/utils.ml b/daemon/utils.ml index 48f6b9c5c..808e575fd 100644 --- a/daemon/utils.ml +++ b/daemon/utils.ml @@ -25,9 +25,15 @@ let prog_exists prog try ignore (which prog); true with Executable_not_found _ -> false -let commandr prog args +type command_flag + CommandFlagFoldStdoutOnStderr + +let commandr ?(flags = []) prog args + let fold_stdout_on_stderr = List.mem CommandFlagFoldStdoutOnStderr flags in + if verbose () then - eprintf "command: %s %s\n%!" + eprintf "command:%s %s %s\n%!" + (if fold_stdout_on_stderr then " fold-stdout-on-stderr" else "") prog (String.concat " " args); let argv = Array.of_list (prog :: args) in @@ -43,7 +49,10 @@ let commandr prog args (* Child process. *) dup2 stdin_fd stdin; close stdin_fd; - dup2 stdout_fd stdout; + 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; @@ -91,8 +100,8 @@ let commandr prog args (r, stdout, stderr) -let command prog args - let r, stdout, stderr = commandr prog args in +let command ?flags prog args + let r, stdout, stderr = commandr ?flags prog args in if r <> 0 then failwithf "%s exited with status %d: %s" prog r stderr; stdout diff --git a/daemon/utils.mli b/daemon/utils.mli index a1f956be3..d3c8bdf4d 100644 --- a/daemon/utils.mli +++ b/daemon/utils.mli @@ -60,7 +60,14 @@ 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 : string -> string list -> string +type command_flag + CommandFlagFoldStdoutOnStderr + (** 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 command : ?flags:command_flag list -> 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. @@ -68,7 +75,7 @@ val command : string -> string list -> string On failure of the command, this throws an exception containing the stderr from the command. *) -val commandr : string -> string list -> (int * string * string) +val commandr : ?flags:command_flag list -> string -> string list -> (int * string * string) (** Run an external command without using the shell, and collect stdout and stderr separately. -- 2.13.0
Richard W.M. Jones
2017-Jun-19 15:39 UTC
[Libguestfs] [PATCH v7 26/29] 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 125aec60b..1c81cd968 100644 --- a/daemon/parted.c +++ b/daemon/parted.c @@ -348,45 +348,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) { @@ -557,126 +518,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, - str_sgdisk, device, "-i", partnum_str, NULL); - - if (r == -1) { - reply_with_error ("%s %s -i %s: %s", str_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", - str_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) { @@ -840,6 +681,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 37e1b42be..7c1e577dd 100644 --- a/daemon/parted.ml +++ b/daemon/parted.ml @@ -81,18 +81,18 @@ let print_partition_table ~add_m_option 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 ~add_m_option:true device in + let _, lines = print_partition_table ~add_m_option:true device in List.map ( fun line -> @@ -104,3 +104,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 ~add_m_option:true 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 ~flags:[CommandFlagFoldStdoutOnStderr] + "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.0
Richard W.M. Jones
2017-Jun-19 15:39 UTC
[Libguestfs] [PATCH v7 27/29] lib: Move implementation of ‘hivex_value_utf8’ to new file ‘lib/hivex.c’.
Just a code movement, no change. --- docs/C_SOURCE_FILES | 1 + lib/Makefile.am | 1 + lib/hivex.c | 111 +++++++++++++++++++++++++++++++++++++++++++++++ lib/inspect-fs-windows.c | 83 ----------------------------------- 4 files changed, 113 insertions(+), 83 deletions(-) diff --git a/docs/C_SOURCE_FILES b/docs/C_SOURCE_FILES index 0b55c122f..39dcf9035 100644 --- a/docs/C_SOURCE_FILES +++ b/docs/C_SOURCE_FILES @@ -303,6 +303,7 @@ lib/guestfs-internal.h lib/guestfs.h lib/guid.c lib/handle.c +lib/hivex.c lib/info.c lib/inspect-apps.c lib/inspect-fs-unix.c diff --git a/lib/Makefile.am b/lib/Makefile.am index 7eaff88ee..31568f933 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -92,6 +92,7 @@ libguestfs_la_SOURCES = \ fuse.c \ guid.c \ handle.c \ + hivex.c \ info.c \ inspect.c \ inspect-apps.c \ diff --git a/lib/hivex.c b/lib/hivex.c new file mode 100644 index 000000000..2d782e192 --- /dev/null +++ b/lib/hivex.c @@ -0,0 +1,111 @@ +/* libguestfs + * Copyright (C) 2010-2012 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 <errno.h> +#include <iconv.h> + +#include "guestfs.h" +#include "guestfs-internal.h" +#include "guestfs-internal-actions.h" + +/* Read the data from 'valueh', assume it is UTF16LE and convert it to + * UTF8. This is copied from hivex_value_string which doesn't work in + * the appliance because it uses iconv_open which doesn't work because + * we delete all the i18n databases. + */ +static char *utf16_to_utf8 (/* const */ char *input, size_t len); + +char * +guestfs_impl_hivex_value_utf8 (guestfs_h *g, int64_t valueh) +{ + char *ret; + size_t buflen; + + CLEANUP_FREE char *buf = guestfs_hivex_value_value (g, valueh, &buflen); + if (buf == NULL) + return NULL; + + ret = utf16_to_utf8 (buf, buflen); + if (ret == NULL) { + perrorf (g, "hivex: conversion of registry value to UTF8 failed"); + return NULL; + } + + return ret; +} + +static char * +utf16_to_utf8 (/* const */ char *input, size_t len) +{ + iconv_t ic = iconv_open ("UTF-8", "UTF-16LE"); + if (ic == (iconv_t) -1) + return NULL; + + /* iconv(3) has an insane interface ... */ + + /* Mostly UTF-8 will be smaller, so this is a good initial guess. */ + size_t outalloc = len; + + again:; + size_t inlen = len; + size_t outlen = outalloc; + char *out = malloc (outlen + 1); + if (out == NULL) { + int err = errno; + iconv_close (ic); + errno = err; + return NULL; + } + char *inp = input; + char *outp = out; + + const size_t r + iconv (ic, (ICONV_CONST char **) &inp, &inlen, &outp, &outlen); + if (r == (size_t) -1) { + if (errno == E2BIG) { + const int err = errno; + const size_t prev = outalloc; + /* Try again with a larger output buffer. */ + free (out); + outalloc *= 2; + if (outalloc < prev) { + iconv_close (ic); + errno = err; + return NULL; + } + goto again; + } + else { + /* Else some conversion failure, eg. EILSEQ, EINVAL. */ + const int err = errno; + iconv_close (ic); + free (out); + errno = err; + return NULL; + } + } + + *outp = '\0'; + iconv_close (ic); + + return out; +} diff --git a/lib/inspect-fs-windows.c b/lib/inspect-fs-windows.c index b14dc2e14..34f33c908 100644 --- a/lib/inspect-fs-windows.c +++ b/lib/inspect-fs-windows.c @@ -737,86 +737,3 @@ guestfs_int_case_sensitive_path_silently (guestfs_h *g, const char *path) return ret; } - -/* Read the data from 'valueh', assume it is UTF16LE and convert it to - * UTF8. This is copied from hivex_value_string which doesn't work in - * the appliance because it uses iconv_open which doesn't work because - * we delete all the i18n databases. - */ -static char *utf16_to_utf8 (/* const */ char *input, size_t len); - -char * -guestfs_impl_hivex_value_utf8 (guestfs_h *g, int64_t valueh) -{ - char *ret; - size_t buflen; - - CLEANUP_FREE char *buf = guestfs_hivex_value_value (g, valueh, &buflen); - if (buf == NULL) - return NULL; - - ret = utf16_to_utf8 (buf, buflen); - if (ret == NULL) { - perrorf (g, "hivex: conversion of registry value to UTF8 failed"); - return NULL; - } - - return ret; -} - -static char * -utf16_to_utf8 (/* const */ char *input, size_t len) -{ - iconv_t ic = iconv_open ("UTF-8", "UTF-16LE"); - if (ic == (iconv_t) -1) - return NULL; - - /* iconv(3) has an insane interface ... */ - - /* Mostly UTF-8 will be smaller, so this is a good initial guess. */ - size_t outalloc = len; - - again:; - size_t inlen = len; - size_t outlen = outalloc; - char *out = malloc (outlen + 1); - if (out == NULL) { - int err = errno; - iconv_close (ic); - errno = err; - return NULL; - } - char *inp = input; - char *outp = out; - - const size_t r - iconv (ic, (ICONV_CONST char **) &inp, &inlen, &outp, &outlen); - if (r == (size_t) -1) { - if (errno == E2BIG) { - const int err = errno; - const size_t prev = outalloc; - /* Try again with a larger output buffer. */ - free (out); - outalloc *= 2; - if (outalloc < prev) { - iconv_close (ic); - errno = err; - return NULL; - } - goto again; - } - else { - /* Else some conversion failure, eg. EILSEQ, EINVAL. */ - const int err = errno; - iconv_close (ic); - free (out); - errno = err; - return NULL; - } - } - - *outp = '\0'; - iconv_close (ic); - - return out; -} -- 2.13.0
Richard W.M. Jones
2017-Jun-19 15:39 UTC
[Libguestfs] [PATCH v7 28/29] 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 273612516..4d273f59e 100644 --- a/daemon/devsparts.ml +++ b/daemon/devsparts.ml @@ -109,3 +109,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.0
Richard W.M. Jones
2017-Jun-19 15:39 UTC
[Libguestfs] [PATCH v7 29/29] daemon: Reimplement most inspection APIs in the daemon, in OCaml.
Move the following APIs into the daemon, reimplemented in OCaml: * inspect_os * inspect_get_roots * inspect_get_mountpoints * inspect_get_filesystems * inspect_get_format [deprecated] * inspect_get_type * inspect_get_distro * inspect_get_package_format * inspect_get_package_management * inspect_get_product_name * inspect_get_product_variant * inspect_get_major_version * inspect_get_minor_version * inspect_get_arch * inspect_get_hostname * inspect_get_windows_systemroot * inspect_get_windows_software_hive * inspect_get_windows_system_hive * inspect_get_windows_current_control_set * inspect_get_drive_mappings * inspect_is_live [deprecated] * inspect_is_netinst [deprecated] * inspect_is_multipart [deprecated] The following inspection APIs have NOT been reimplemented in this commit: * inspect_list_applications [deprecated] * inspect_list_applications2 * inspect_get_icon This also embeds the ocaml-augeas library (upstream here: http://git.annexia.org/?p=ocaml-augeas.git;a=summary), but it's identical to the upstream version and should remain so. --- daemon/Makefile.am | 17 + daemon/augeas-c.c | 288 ++++ daemon/augeas.README | 8 + daemon/augeas.ml | 59 + daemon/augeas.mli | 95 ++ daemon/chroot.ml | 2 +- daemon/daemon_utils_tests.ml | 15 + daemon/inspect.ml | 397 +++++ daemon/inspect.mli | 41 + daemon/inspect_fs.ml | 336 +++++ daemon/inspect_fs.mli | 23 + daemon/inspect_fs_unix.ml | 797 ++++++++++ daemon/inspect_fs_unix.mli | 53 + daemon/inspect_fs_unix_fstab.ml | 518 +++++++ daemon/inspect_fs_unix_fstab.mli | 34 + daemon/inspect_fs_windows.ml | 498 +++++++ daemon/inspect_fs_windows.mli | 25 + daemon/inspect_types.ml | 317 ++++ daemon/inspect_types.mli | 168 +++ daemon/inspect_utils.ml | 175 +++ daemon/inspect_utils.mli | 51 + daemon/mount.ml | 61 + daemon/mount.mli | 2 + daemon/utils.ml | 100 ++ daemon/utils.mli | 12 + docs/C_SOURCE_FILES | 4 +- generator/actions.ml | 2 + generator/actions_inspection.ml | 394 ++--- generator/actions_inspection.mli | 1 + generator/actions_inspection_deprecated.ml | 7 + generator/actions_inspection_deprecated.mli | 1 + generator/daemon.ml | 63 +- generator/proc_nr.ml | 23 + lib/MAX_PROC_NR | 2 +- lib/Makefile.am | 3 - lib/guestfs-internal.h | 185 +-- lib/handle.c | 1 - lib/inspect-apps.c | 122 +- lib/inspect-fs-unix.c | 2158 --------------------------- lib/inspect-fs-windows.c | 739 --------- lib/inspect-fs.c | 758 ---------- lib/inspect-icon.c | 261 ++-- lib/inspect.c | 732 +-------- lib/version.c | 28 + 44 files changed, 4600 insertions(+), 4976 deletions(-) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 5a5bc9855..222e45073 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -75,6 +75,7 @@ guestfsd_SOURCES = \ actions.h \ available.c \ augeas.c \ + augeas-c.c \ base64.c \ blkdiscard.c \ blkid.c \ @@ -253,6 +254,7 @@ guestfsd_CFLAGS = \ # library and then linked to the daemon. See # https://caml.inria.fr/pub/docs/manual-ocaml/intfc.html SOURCES_MLI = \ + augeas.mli \ blkid.mli \ btrfs.mli \ chroot.mli \ @@ -261,6 +263,13 @@ SOURCES_MLI = \ file.mli \ filearch.mli \ findfs.mli \ + inspect.mli \ + inspect_fs.mli \ + inspect_fs_unix.mli \ + inspect_fs_unix_fstab.mli \ + inspect_fs_windows.mli \ + inspect_types.mli \ + inspect_utils.mli \ is.mli \ ldm.mli \ link.mli \ @@ -274,6 +283,7 @@ SOURCES_MLI = \ utils.mli SOURCES_ML = \ + augeas.ml \ types.ml \ utils.ml \ structs.ml \ @@ -295,6 +305,13 @@ SOURCES_ML = \ parted.ml \ listfs.ml \ realpath.ml \ + inspect_types.ml \ + inspect_utils.ml \ + inspect_fs_unix_fstab.ml \ + inspect_fs_unix.ml \ + inspect_fs_windows.ml \ + inspect_fs.ml \ + inspect.ml \ callbacks.ml \ daemon.ml diff --git a/daemon/augeas-c.c b/daemon/augeas-c.c new file mode 100644 index 000000000..c06bf92da --- /dev/null +++ b/daemon/augeas-c.c @@ -0,0 +1,288 @@ +/* Augeas OCaml bindings + * Copyright (C) 2008-2012 Red Hat Inc., Richard W.M. Jones + * + * 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 + * + * $Id: augeas_c.c,v 1.1 2008/05/06 10:48:20 rjones Exp $ + */ + +#include "config.h" + +#include <augeas.h> + +#include <caml/alloc.h> +#include <caml/memory.h> +#include <caml/mlvalues.h> +#include <caml/fail.h> +#include <caml/callback.h> +#include <caml/custom.h> + +typedef augeas *augeas_t; + +/* Raise an Augeas.Error exception. */ +static void +raise_error (const char *msg) +{ + caml_raise_with_string (*caml_named_value ("Augeas.Error"), msg); +} + +/* Map OCaml flags to C flags. */ +static int flag_map[] = { + /* AugSaveBackup */ AUG_SAVE_BACKUP, + /* AugSaveNewFile */ AUG_SAVE_NEWFILE, + /* AugTypeCheck */ AUG_TYPE_CHECK, + /* AugNoStdinc */ AUG_NO_STDINC, + /* AugSaveNoop */ AUG_SAVE_NOOP, + /* AugNoLoad */ AUG_NO_LOAD, +}; + +/* Wrap and unwrap augeas_t handles, with a finalizer. */ +#define Augeas_t_val(rv) (*(augeas_t *)Data_custom_val(rv)) + +static void +augeas_t_finalize (value tv) +{ + augeas_t t = Augeas_t_val (tv); + if (t) aug_close (t); +} + +static struct custom_operations custom_operations = { + (char *) "augeas_t_custom_operations", + augeas_t_finalize, + custom_compare_default, + custom_hash_default, + custom_serialize_default, + custom_deserialize_default +}; + +static value Val_augeas_t (augeas_t t) +{ + CAMLparam0 (); + CAMLlocal1 (rv); + /* We could choose these so that the GC can make better decisions. + * See 18.9.2 of the OCaml manual. + */ + const int used = 0; + const int max = 1; + + rv = caml_alloc_custom (&custom_operations, + sizeof (augeas_t), used, max); + Augeas_t_val(rv) = t; + + CAMLreturn (rv); +} + +#pragma GCC diagnostic ignored "-Wmissing-prototypes" + +/* val create : string -> string option -> flag list -> t */ +CAMLprim value +ocaml_augeas_create (value rootv, value loadpathv, value flagsv) +{ + CAMLparam1 (rootv); + char *root = String_val (rootv); + char *loadpath; + int flags = 0, i; + augeas_t t; + + /* Optional loadpath. */ + loadpath + loadpathv == Val_int (0) + ? NULL + : String_val (Field (loadpathv, 0)); + + /* Convert list of flags to C. */ + for (; flagsv != Val_int (0); flagsv = Field (flagsv, 1)) { + i = Int_val (Field (flagsv, 0)); + flags |= flag_map[i]; + } + + t = aug_init (root, loadpath, flags); + + if (t == NULL) + raise_error ("Augeas.create"); + + CAMLreturn (Val_augeas_t (t)); +} + +/* val close : t -> unit */ +CAMLprim value +ocaml_augeas_close (value tv) +{ + CAMLparam1 (tv); + augeas_t t = Augeas_t_val (tv); + + if (t) { + aug_close (t); + Augeas_t_val(tv) = NULL; /* So the finalizer doesn't double-free. */ + } + + CAMLreturn (Val_unit); +} + +/* val get : t -> path -> value option */ +CAMLprim value +ocaml_augeas_get (value tv, value pathv) +{ + CAMLparam2 (tv, pathv); + CAMLlocal2 (optv, v); + augeas_t t = Augeas_t_val (tv); + char *path = String_val (pathv); + const char *val; + int r; + + r = aug_get (t, path, &val); + if (r == 1) { /* Return Some val */ + v = caml_copy_string (val); + optv = caml_alloc (1, 0); + Field (optv, 0) = v; + } else if (r == 0) /* Return None */ + optv = Val_int (0); + else if (r == -1) /* Error or multiple matches */ + raise_error ("Augeas.get"); + else + failwith ("Augeas.get: bad return value"); + + CAMLreturn (optv); +} + +/* val exists : t -> path -> bool */ +CAMLprim value +ocaml_augeas_exists (value tv, value pathv) +{ + CAMLparam2 (tv, pathv); + CAMLlocal1 (v); + augeas_t t = Augeas_t_val (tv); + char *path = String_val (pathv); + int r; + + r = aug_get (t, path, NULL); + if (r == 1) /* Return true. */ + v = Val_int (1); + else if (r == 0) /* Return false */ + v = Val_int (0); + else if (r == -1) /* Error or multiple matches */ + raise_error ("Augeas.exists"); + else + failwith ("Augeas.exists: bad return value"); + + CAMLreturn (v); +} + +/* val insert : t -> ?before:bool -> path -> string -> unit */ +CAMLprim value +ocaml_augeas_insert (value tv, value beforev, value pathv, value labelv) +{ + CAMLparam4 (tv, beforev, pathv, labelv); + augeas_t t = Augeas_t_val (tv); + char *path = String_val (pathv); + char *label = String_val (labelv); + int before; + + before = beforev == Val_int (0) ? 0 : Int_val (Field (beforev, 0)); + + if (aug_insert (t, path, label, before) == -1) + raise_error ("Augeas.insert"); + + CAMLreturn (Val_unit); +} + +/* val rm : t -> path -> int */ +CAMLprim value +ocaml_augeas_rm (value tv, value pathv) +{ + CAMLparam2 (tv, pathv); + augeas_t t = Augeas_t_val (tv); + char *path = String_val (pathv); + int r; + + r = aug_rm (t, path); + if (r == -1) + raise_error ("Augeas.rm"); + + CAMLreturn (Val_int (r)); +} + +/* val matches : t -> path -> path list */ +CAMLprim value +ocaml_augeas_match (value tv, value pathv) +{ + CAMLparam2 (tv, pathv); + CAMLlocal3 (rv, v, cons); + augeas_t t = Augeas_t_val (tv); + char *path = String_val (pathv); + char **matches; + int r, i; + + r = aug_match (t, path, &matches); + if (r == -1) + raise_error ("Augeas.matches"); + + /* Copy the paths to a list. */ + rv = Val_int (0); + for (i = 0; i < r; ++i) { + v = caml_copy_string (matches[i]); + free (matches[i]); + cons = caml_alloc (2, 0); + Field (cons, 1) = rv; + Field (cons, 0) = v; + rv = cons; + } + + free (matches); + + CAMLreturn (rv); +} + +/* val count_matches : t -> path -> int */ +CAMLprim value +ocaml_augeas_count_matches (value tv, value pathv) +{ + CAMLparam2 (tv, pathv); + augeas_t t = Augeas_t_val (tv); + char *path = String_val (pathv); + int r; + + r = aug_match (t, path, NULL); + if (r == -1) + raise_error ("Augeas.count_matches"); + + CAMLreturn (Val_int (r)); +} + +/* val save : t -> unit */ +CAMLprim value +ocaml_augeas_save (value tv) +{ + CAMLparam1 (tv); + augeas_t t = Augeas_t_val (tv); + + if (aug_save (t) == -1) + raise_error ("Augeas.save"); + + CAMLreturn (Val_unit); +} + +/* val load : t -> unit */ +CAMLprim value +ocaml_augeas_load (value tv) +{ + CAMLparam1 (tv); + augeas_t t = Augeas_t_val (tv); + + if (aug_load (t) == -1) + raise_error ("Augeas.load"); + + CAMLreturn (Val_unit); +} diff --git a/daemon/augeas.README b/daemon/augeas.README new file mode 100644 index 000000000..938dfd255 --- /dev/null +++ b/daemon/augeas.README @@ -0,0 +1,8 @@ +The files augeas-c.c, augeas.ml and augeas.mli come from the +ocaml-augeas library: + + http://git.annexia.org/?p=ocaml-augeas.git + +which is released under a compatible license. We try to keep them +identical, so if you make changes to these files then you must also +submit the changes to ocaml-augeas, and vice versa. \ No newline at end of file diff --git a/daemon/augeas.ml b/daemon/augeas.ml new file mode 100644 index 000000000..f556df0f1 --- /dev/null +++ b/daemon/augeas.ml @@ -0,0 +1,59 @@ +(* Augeas OCaml bindings + * Copyright (C) 2008 Red Hat Inc., Richard W.M. Jones + * + * 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 + * + * $Id: augeas.ml,v 1.2 2008/05/06 10:48:20 rjones Exp $ + *) + +type t + +exception Error of string + +type flag + | AugSaveBackup + | AugSaveNewFile + | AugTypeCheck + | AugNoStdinc + | AugSaveNoop + | AugNoLoad + +type path = string + +type value = string + +external create : string -> string option -> flag list -> t + = "ocaml_augeas_create" +external close : t -> unit + = "ocaml_augeas_close" +external get : t -> path -> value option + = "ocaml_augeas_get" +external exists : t -> path -> bool + = "ocaml_augeas_exists" +external insert : t -> ?before:bool -> path -> string -> unit + = "ocaml_augeas_insert" +external rm : t -> path -> int + = "ocaml_augeas_rm" +external matches : t -> path -> path list + = "ocaml_augeas_match" +external count_matches : t -> path -> int + = "ocaml_augeas_count_matches" +external save : t -> unit + = "ocaml_augeas_save" +external load : t -> unit + = "ocaml_augeas_load" + +let () + Callback.register_exception "Augeas.Error" (Error "") diff --git a/daemon/augeas.mli b/daemon/augeas.mli new file mode 100644 index 000000000..64e824014 --- /dev/null +++ b/daemon/augeas.mli @@ -0,0 +1,95 @@ +(** Augeas OCaml bindings *) +(* Copyright (C) 2008 Red Hat Inc., Richard W.M. Jones + * + * 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 + * + * $Id: augeas.mli,v 1.2 2008/05/06 10:48:20 rjones Exp $ + *) + +type t + (** Augeas library handle. *) + +exception Error of string + (** This exception is thrown when the underlying Augeas library + returns an error. *) + +type flag + | AugSaveBackup (** Rename original with .augsave *) + | AugSaveNewFile (** Save changes to .augnew *) + | AugTypeCheck (** Type-check lenses *) + | AugNoStdinc + | AugSaveNoop + | AugNoLoad + (** Flags passed to the {!create} function. *) + +type path = string + (** A path expression. + + Note in future we may replace this with a type-safe path constructor. *) + +type value = string + (** A value. *) + +val create : string -> string option -> flag list -> t + (** [create root loadpath flags] creates an Augeas handle. + + [root] is a file system path describing the location + of the configuration files. + + [loadpath] is an optional colon-separated list of directories + which are searched for schema definitions. + + [flags] is a list of flags. *) + +val close : t -> unit + (** [close handle] closes the handle. + + You don't need to close handles explicitly with this function: + they will be finalized eventually by the garbage collector. + However calling this function frees up any resources used by the + underlying Augeas library immediately. + + Do not use the handle after closing it. *) + +val get : t -> path -> value option + (** [get t path] returns the value at [path], or [None] if there + is no value. *) + +val exists : t -> path -> bool + (** [exists t path] returns true iff there is a value at [path]. *) + +val insert : t -> ?before:bool -> path -> string -> unit + (** [insert t ?before path label] inserts [label] as a sibling + of [path]. By default it is inserted after [path], unless + [~before:true] is specified. *) + +val rm : t -> path -> int + (** [rm t path] removes all nodes matching [path]. + + Returns the number of nodes removed (which may be 0). *) + +val matches : t -> path -> path list + (** [matches t path] returns a list of path expressions + of all nodes matching [path]. *) + +val count_matches : t -> path -> int + (** [count_matches t path] counts the number of nodes matching + [path] but does not return them (see {!matches}). *) + +val save : t -> unit + (** [save t] saves all pending changes to disk. *) + +val load : t -> unit + (** [load t] loads files into the tree. *) diff --git a/daemon/chroot.ml b/daemon/chroot.ml index 40dfa1dde..0fddfcffa 100644 --- a/daemon/chroot.ml +++ b/daemon/chroot.ml @@ -32,7 +32,7 @@ let create ?(name = prog) chroot let f t func arg if verbose () then - eprintf "chroot: %s: running ‘%s’\n%!" t.chroot t.name; + eprintf "chroot: %s: running '%s'\n%!" t.chroot t.name; let rfd, wfd = pipe () in diff --git a/daemon/daemon_utils_tests.ml b/daemon/daemon_utils_tests.ml index 892509d89..b1f02de30 100644 --- a/daemon/daemon_utils_tests.ml +++ b/daemon/daemon_utils_tests.ml @@ -46,3 +46,18 @@ let () let () assert (proc_unmangle_path "\\040" = " "); assert (proc_unmangle_path "\\040\\040" = " ") + +(* Test unix_canonical_path. *) +let () + assert (unix_canonical_path "/" = "/"); + assert (unix_canonical_path "/usr" = "/usr"); + assert (unix_canonical_path "/usr/" = "/usr"); + assert (unix_canonical_path "/usr/local" = "/usr/local"); + assert (unix_canonical_path "///" = "/"); + assert (unix_canonical_path "///usr//local//" = "/usr/local"); + assert (unix_canonical_path "/usr///" = "/usr") + +(* Test utf16le_to_utf8. *) +let () + assert (utf16le_to_utf8 "\x57\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x77\x00\x73\x00" = "Windows"); + assert (utf16le_to_utf8 "\x57\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x77\x00\x73\x00\xae\x00" = "Windows\xc2\xae") diff --git a/daemon/inspect.ml b/daemon/inspect.ml new file mode 100644 index 000000000..eaad5119c --- /dev/null +++ b/daemon/inspect.ml @@ -0,0 +1,397 @@ +(* 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 +open Mountable +open Inspect_types + +let re_primary_partition = Str.regexp "^/dev/(h|s|v)d.[1234]$" + +let rec inspect_os () + Mount.umount_all (); + + (* Iterate over all detected filesystems. Inspect each one in turn. *) + let fses = Listfs.list_filesystems () in + + let fses + filter_map ( + fun (mountable, vfs_type) -> + Inspect_fs.check_for_filesystem_on mountable vfs_type + ) fses in + if verbose () then ( + eprintf "inspect_os: fses:\n"; + List.iter (fun fs -> eprintf "\t%s\n" (string_of_fs fs)) fses; + flush stderr + ); + + (* The OS inspection information for CoreOS are gathered by inspecting + * multiple filesystems. Gather all the inspected information in the + * inspect_fs struct of the root filesystem. + *) + 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 + *) + 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. + *) + let fses = collect_linux_inspection_info fses in + + (* Save what we found in a global variable. *) + Inspect_types.inspect_fses := fses; + + (* At this point we have, in the handle, a list of all filesystems + * found and data about each one. Now we assemble the list of + * filesystems which are root devices. + * + * Fall through to inspect_get_roots to do that. + *) + inspect_get_roots () + +(* Traverse through the filesystem list and find out if it contains + * the [/] and [/usr] filesystems of a CoreOS image. If this is the + * case, sum up all the collected information on the root fs. + *) +and collect_coreos_inspection_info fses + (* Split the list into CoreOS root(s), CoreOS usr(s), and + * everything else. + *) + let rec loop roots usrs others = function + | [] -> roots, usrs, others + | ({ role = RoleRoot { distro = Some DISTRO_COREOS } } as r) :: rest -> + loop (r::roots) usrs others rest + | ({ role = RoleUsr { distro = Some DISTRO_COREOS } } as u) :: rest -> + loop roots (u::usrs) others rest + | o :: rest -> + loop roots usrs (o::others) rest + in + let roots, usrs, others = loop [] [] [] fses in + + match roots with + (* If there are no CoreOS roots, then there's nothing to do. *) + | [] -> fses + (* If there are more than one CoreOS roots, we cannot inspect the guest. *) + | _::_::_ -> failwith "multiple CoreOS root filesystems found" + | [root] -> + match usrs with + (* If there are no CoreOS usr partitions, nothing to do. *) + | [] -> fses + | usrs -> + (* CoreOS is designed to contain 2 /usr partitions (USR-A, USR-B): + * https://coreos.com/docs/sdk-distributors/sdk/disk-partitions/ + * One is active and one passive. During the initial boot, the + * passive partition is empty and it gets filled up when an + * update is performed. Then, when the system reboots, the + * boot loader is instructed to boot from the passive partition. + * If both partitions are valid, we cannot determine which the + * active and which the passive is, unless we peep into the + * boot loader. As a workaround, we check the OS versions and + * pick the one with the higher version as active. + *) + let compare_versions u1 u2 + let v1 + match u1 with + | { role = RoleRoot { version = Some v } } -> v + | { role = RoleUsr { version = Some v } } -> v + | _ -> (0, 0) in + let v2 + match u2 with + | { role = RoleRoot { version = Some v } } -> v + | { role = RoleUsr { version = Some v } } -> v + | _ -> (0, 0) in + compare v2 v1 (* reverse order *) + in + let usrs = List.sort compare_versions usrs in + let usr = List.hd usrs in + + let root = merge usr root in + root :: others + +(* On *BSD systems, sometimes [/dev/sda[1234]] is a shadow of the + * real root filesystem that is probably [/dev/sda5] (see: + * [http://www.freebsd.org/doc/handbook/disk-organization.html]) + *) +and check_for_duplicated_bsd_root fses + try + let is_primary_partition = function + | { m_type = (MountablePath | MountableBtrfsVol _) } -> false + | { m_type = MountableDevice; m_device = d } -> + Str.string_match re_primary_partition d 0 + in + + (* Try to find a "BSD primary", if there is one. *) + let bsd_primary + List.find ( + function + | { fs_location = { mountable = mountable }; + role = RoleRoot { os_type = Some t } } -> + (t = OS_TYPE_FREEBSD || t = OS_TYPE_NETBSD || t = OS_TYPE_OPENBSD) + && is_primary_partition mountable + | _ -> false + ) fses in + + let bsd_primary_os_type + match bsd_primary with + | { role = RoleRoot { os_type = Some t } } -> t + | _ -> assert false in + + (* Try to find a shadow of the primary, and if it is found the + * primary is removed. + *) + let fses_without_bsd_primary = List.filter ((!=) bsd_primary) fses in + let shadow_exists + List.exists ( + function + | { role = RoleRoot { os_type = Some t } } -> + t = bsd_primary_os_type + | _ -> false + ) fses_without_bsd_primary in + if shadow_exists then fses_without_bsd_primary else fses + with + Not_found -> fses + +(* Traverse through the filesystem list and find out if it contains + * the [/] and [/usr] filesystems of a Linux image (but not CoreOS, + * for which there is a separate [collect_coreos_inspection_info]). + * + * If this is the case, sum up all the collected information on each + * root fs from the respective [/usr] filesystems. + *) +and collect_linux_inspection_info fses + List.map ( + function + | { role = RoleRoot { distro = Some d } } as root -> + if d <> DISTRO_COREOS then + collect_linux_inspection_info_for fses root + else + root + | fs -> fs + ) fses + +(* Traverse through the filesystems and find the /usr filesystem for + * the specified C<root>: if found, merge its basic inspection details + * to the root when they were set (i.e. because the /usr had os-release + * or other ways to identify the OS). + *) +and collect_linux_inspection_info_for fses root + let root_distro, root_fstab + match root with + | { role = RoleRoot { distro = Some d; fstab = f } } -> d, f + | _ -> assert false in + + try + let usr + List.find ( + function + | { role = RoleUsr { distro = d } } + when d = Some root_distro || d = None -> true + | _ -> false + ) fses in + + let usr_mountable = usr.fs_location.mountable in + + (* This checks that [usr] is found in the fstab of the root + * filesystem. If not, [Not_found] is thrown. + *) + ignore ( + List.find (fun (mountable, _) -> usr_mountable = mountable) root_fstab + ); + + merge usr root + with + Not_found -> root + +and inspect_get_roots () + let fses = !Inspect_types.inspect_fses in + + let roots + filter_map ( + fun fs -> try Some (root_of_fs fs) with Invalid_argument _ -> None + ) fses in + if verbose () then ( + eprintf "inspect_get_roots: roots:\n"; + List.iter (fun root -> eprintf "%s" (string_of_root root)) roots; + flush stderr + ); + + (* Only return the list of mountables, since subsequent calls will + * be used to retrieve the other information. + *) + List.map (fun { root_location = { mountable = m } } -> m) roots + +and root_of_fs + function + | { fs_location = location; role = RoleRoot data } -> + { root_location = location; inspection_data = data } + | { role = (RoleUsr _ | RoleSwap | RoleOther) } -> + invalid_arg "root_of_fs" + +and inspect_get_mountpoints root_mountable + let root = search_for_root root_mountable in + let fstab = root.inspection_data.fstab in + + (* If no fstab information (Windows) return just the root. *) + if fstab = [] then + [ "/", root_mountable ] + else ( + filter_map ( + fun (mountable, mp) -> + if String.length mp > 0 && mp.[0] = '/' then + Some (mp, mountable) + else + None + ) fstab + ) + +and inspect_get_filesystems root_mountable + let root = search_for_root root_mountable in + let fstab = root.inspection_data.fstab in + + (* If no fstab information (Windows) return just the root. *) + if fstab = [] then + [ root_mountable ] + else + List.map fst fstab + +and inspect_get_format root = "installed" + +and inspect_get_type root + let root = search_for_root root in + match root.inspection_data.os_type with + | Some v -> string_of_os_type v + | None -> "unknown" + +and inspect_get_distro root + let root = search_for_root root in + match root.inspection_data.distro with + | Some v -> string_of_distro v + | None -> "unknown" + +and inspect_get_package_format root + let root = search_for_root root in + match root.inspection_data.package_format with + | Some v -> string_of_package_format v + | None -> "unknown" + +and inspect_get_package_management root + let root = search_for_root root in + match root.inspection_data.package_management with + | Some v -> string_of_package_management v + | None -> "unknown" + +and inspect_get_product_name root + let root = search_for_root root in + match root.inspection_data.product_name with + | Some v -> v + | None -> "unknown" + +and inspect_get_product_variant root + let root = search_for_root root in + match root.inspection_data.product_variant with + | Some v -> v + | None -> "unknown" + +and inspect_get_major_version root + let root = search_for_root root in + match root.inspection_data.version with + | Some (major, _) -> major + | None -> 0 + +and inspect_get_minor_version root + let root = search_for_root root in + match root.inspection_data.version with + | Some (_, minor) -> minor + | None -> 0 + +and inspect_get_arch root + let root = search_for_root root in + match root.inspection_data.arch with + | Some v -> v + | None -> "unknown" + +and inspect_get_hostname root + let root = search_for_root root in + match root.inspection_data.hostname with + | Some v -> v + | None -> "unknown" + +and inspect_get_windows_systemroot root + let root = search_for_root root in + match root.inspection_data.windows_systemroot with + | Some v -> v + | None -> + failwith "not a Windows guest, or systemroot could not be determined" + +and inspect_get_windows_system_hive root + let root = search_for_root root in + match root.inspection_data.windows_system_hive with + | Some v -> v + | None -> + failwith "not a Windows guest, or system hive not found" + +and inspect_get_windows_software_hive root + let root = search_for_root root in + match root.inspection_data.windows_software_hive with + | Some v -> v + | None -> + failwith "not a Windows guest, or software hive not found" + +and inspect_get_windows_current_control_set root + let root = search_for_root root in + match root.inspection_data.windows_current_control_set with + | Some v -> v + | None -> + failwith "not a Windows guest, or CurrentControlSet could not be determined" + +and inspect_is_live root = false + +and inspect_is_netinst root = false + +and inspect_is_multipart root = false + +and inspect_get_drive_mappings root + let root = search_for_root root in + root.inspection_data.drive_mappings + +and search_for_root root + let fses = !Inspect_types.inspect_fses in + if fses = [] then + failwith "no inspection data: call guestfs_inspect_os first"; + + let root + try + List.find ( + function + | { fs_location = { mountable = m }; role = RoleRoot _ } -> root = m + | _ -> false + ) fses + with + Not_found -> + failwithf "%s: root device not found: only call this function with a root device previously returned by guestfs_inspect_os" + (Mountable.to_string root) in + + root_of_fs root diff --git a/daemon/inspect.mli b/daemon/inspect.mli new file mode 100644 index 000000000..29a1c1759 --- /dev/null +++ b/daemon/inspect.mli @@ -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. + *) + +val inspect_os : unit -> Mountable.t list +val inspect_get_roots : unit -> Mountable.t list +val inspect_get_mountpoints : Mountable.t -> (string * Mountable.t) list +val inspect_get_filesystems : Mountable.t -> Mountable.t list +val inspect_get_format : Mountable.t -> string +val inspect_get_type : Mountable.t -> string +val inspect_get_distro : Mountable.t -> string +val inspect_get_package_format : Mountable.t -> string +val inspect_get_package_management : Mountable.t -> string +val inspect_get_product_name : Mountable.t -> string +val inspect_get_product_variant : Mountable.t -> string +val inspect_get_major_version : Mountable.t -> int +val inspect_get_minor_version : Mountable.t -> int +val inspect_get_arch : Mountable.t -> string +val inspect_get_hostname : Mountable.t -> string +val inspect_get_windows_systemroot : Mountable.t -> string +val inspect_get_windows_software_hive : Mountable.t -> string +val inspect_get_windows_system_hive : Mountable.t -> string +val inspect_get_windows_current_control_set : Mountable.t -> string +val inspect_get_drive_mappings : Mountable.t -> (string * string) list +val inspect_is_live : Mountable.t -> bool +val inspect_is_netinst : Mountable.t -> bool +val inspect_is_multipart : Mountable.t -> bool diff --git a/daemon/inspect_fs.ml b/daemon/inspect_fs.ml new file mode 100644 index 000000000..ddf3cb350 --- /dev/null +++ b/daemon/inspect_fs.ml @@ -0,0 +1,336 @@ +(* 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 Mountable +open Inspect_types +open Inspect_utils + +let rec check_for_filesystem_on mountable vfs_type + if verbose () then + eprintf "check_for_filesystem_on: %s (%s)\n%!" + (Mountable.to_string mountable) vfs_type; + + let role + let is_swap = vfs_type = "swap" in + if is_swap then + Some RoleSwap + else ( + (* Try mounting the device. Ignore errors if we can't do this. *) + let mounted + if vfs_type = "ufs" then ( (* Hack for the *BSDs. *) + (* FreeBSD fs is a variant of ufs called ufs2 ... *) + try + Mount.mount_vfs (Some "ro,ufstype=ufs2") (Some "ufs") + mountable "/"; + true + with _ -> + (* while NetBSD and OpenBSD use another variant labeled 44bsd *) + try + Mount.mount_vfs (Some "ro,ufstype=44bsd") (Some "ufs") + mountable "/"; + true + with _ -> false + ) else ( + try Mount.mount_ro mountable "/"; + true + with _ -> false + ) in + if not mounted then None + else ( + let role = check_filesystem mountable in + Mount.umount_all (); + role + ) + ) in + + match role with + | None -> None + | Some role -> + Some { fs_location = { mountable = mountable; vfs_type = vfs_type }; + role = role } + +(* When this function is called, the filesystem is mounted on sysroot (). *) +and check_filesystem mountable + let role = ref `Other in + let data = ref null_inspection_data in + + (* Grub /boot? *) + if Is.is_file "/grub/menu.lst" || + Is.is_file "/grub/grub.conf" || + Is.is_file "/grub2/grub.cfg" then + () + (* FreeBSD root? *) + else if Is.is_dir "/etc" && + Is.is_dir "/bin" && + Is.is_file "/etc/freebsd-update.conf" && + Is.is_file "/etc/fstab" then ( + role := `Root; + data := Inspect_fs_unix.check_freebsd_root mountable !data + ) + (* NetBSD root? *) + else if Is.is_dir "/etc" && + Is.is_dir "/bin" && + Is.is_file "/netbsd" && + Is.is_file "/etc/fstab" && + Is.is_file "/etc/release" then ( + role := `Root; + data := Inspect_fs_unix.check_netbsd_root mountable !data; + ) + (* OpenBSD root? *) + else if Is.is_dir "/etc" && + Is.is_dir "/bin" && + Is.is_file "/bsd" && + Is.is_file "/etc/fstab" && + Is.is_file "/etc/motd" then ( + role := `Root; + data := Inspect_fs_unix.check_openbsd_root mountable !data; + ) + (* Hurd root? *) + else if Is.is_file "/hurd/console" && + Is.is_file "/hurd/hello" && + Is.is_file "/hurd/null" then ( + role := `Root; + data := Inspect_fs_unix.check_hurd_root mountable !data; + ) + (* Minix root? *) + else if Is.is_dir "/etc" && + Is.is_dir "/bin" && + Is.is_file "/service/vm" && + Is.is_file "/etc/fstab" && + Is.is_file "/etc/version" then ( + role := `Root; + data := Inspect_fs_unix.check_minix_root !data; + ) + (* Linux root? *) + else if Is.is_dir "/etc" && + (Is.is_dir "/bin" || + is_symlink_to "/bin" "usr/bin") && + (Is.is_file "/etc/fstab" || + Is.is_file "/etc/hosts") then ( + role := `Root; + data := Inspect_fs_unix.check_linux_root mountable !data; + ) + (* CoreOS root? *) + else if Is.is_dir "/etc" && + Is.is_dir "/root" && + Is.is_dir "/home" && + Is.is_dir "/usr" && + Is.is_file "/etc/coreos/update.conf" then ( + role := `Root; + data := Inspect_fs_unix.check_coreos_root mountable !data; + ) + (* Linux /usr/local? *) + else if Is.is_dir "/etc" && + Is.is_dir "/bin" && + Is.is_dir "/share" && + not (Is.is_dir "/local") && + not (Is.is_file "/etc/fstab") then + () + (* Linux /usr? *) + else if Is.is_dir "/etc" && + Is.is_dir "/bin" && + Is.is_dir "/share" && + Is.is_dir "/local" && + not (Is.is_file "/etc/fstab") then ( + role := `Usr; + data := Inspect_fs_unix.check_linux_usr !data; + ) + (* CoreOS /usr? *) + else if Is.is_dir "/bin" && + Is.is_dir "/share" && + Is.is_dir "/local" && + Is.is_dir "/share/coreos" then ( + role := `Usr; + data := Inspect_fs_unix.check_coreos_usr mountable !data; + ) + (* Linux /var? *) + else if Is.is_dir "/log" && + Is.is_dir "/run" && + Is.is_dir "/spool" then + () + (* Windows root? *) + else if Inspect_fs_windows.is_windows_systemroot () then ( + role := `Root; + data := Inspect_fs_windows.check_windows_root !data; + ) + (* Windows volume with installed applications (but not root)? *) + else if is_dir_nocase "/System Volume Information" && + is_dir_nocase "/Program Files" then + () + (* Windows volume (but not root)? *) + else if is_dir_nocase "/System Volume Information" then + () + (* FreeDOS? *) + else if is_dir_nocase "/FDOS" && + is_file_nocase "/FDOS/FREEDOS.BSS" then ( + role := `Root; + data := { !data with + os_type = Some OS_TYPE_DOS; + distro = Some DISTRO_FREEDOS; + (* FreeDOS is a mix of 16 and 32 bit, but + * assume it requires a 32 bit i386 processor. + *) + arch = Some "i386" } + ); + + (* The above code should have set [data.os_type] and [data.distro] + * fields, so we can now guess the package management system. + *) + let data = !data in + let data = { data with + package_format = check_package_format data; + package_management = check_package_management data } in + match !role with + | `Root -> Some (RoleRoot data) + | `Usr -> Some (RoleUsr data) + | `Other -> Some RoleOther + +and is_symlink_to file wanted_target + if not (Is.is_symlink file) then false + else Link.readlink file = wanted_target + +(* At the moment, package format and package management are just a + * simple function of the [distro] and [version[0]] fields, so these + * can never return an error. We might be cleverer in future. + *) +and check_package_format { distro = distro } + match distro with + | None -> None + | Some DISTRO_FEDORA + | Some DISTRO_MEEGO + | Some DISTRO_REDHAT_BASED + | Some DISTRO_RHEL + | Some DISTRO_MAGEIA + | Some DISTRO_MANDRIVA + | Some DISTRO_SUSE_BASED + | Some DISTRO_OPENSUSE + | Some DISTRO_SLES + | Some DISTRO_CENTOS + | Some DISTRO_SCIENTIFIC_LINUX + | Some DISTRO_ORACLE_LINUX + | Some DISTRO_ALTLINUX -> + Some PACKAGE_FORMAT_RPM + | Some DISTRO_DEBIAN + | Some DISTRO_UBUNTU + | Some DISTRO_LINUX_MINT -> + Some PACKAGE_FORMAT_DEB + | Some DISTRO_ARCHLINUX -> + Some PACKAGE_FORMAT_PACMAN + | Some DISTRO_GENTOO -> + Some PACKAGE_FORMAT_EBUILD + | Some DISTRO_PARDUS -> + Some PACKAGE_FORMAT_PISI + | Some DISTRO_ALPINE_LINUX -> + Some PACKAGE_FORMAT_APK + | Some DISTRO_VOID_LINUX -> + Some PACKAGE_FORMAT_XBPS + | Some DISTRO_SLACKWARE + | Some DISTRO_TTYLINUX + | Some DISTRO_COREOS + | Some DISTRO_WINDOWS + | Some DISTRO_BUILDROOT + | Some DISTRO_CIRROS + | Some DISTRO_FREEDOS + | Some DISTRO_FREEBSD + | Some DISTRO_NETBSD + | Some DISTRO_OPENBSD + | Some DISTRO_FRUGALWARE + | Some DISTRO_PLD_LINUX -> + None + +and check_package_management { distro = distro; version = version } + let major = match version with None -> 0 | Some (major, _) -> major in + match distro with + | None -> None + + | Some DISTRO_MEEGO -> + Some PACKAGE_MANAGEMENT_YUM + + | Some DISTRO_FEDORA -> + (* If Fedora >= 22 and dnf is installed, say "dnf". *) + if major >= 22 && Is.is_file ~followsymlinks:true "/usr/bin/dnf" then + Some PACKAGE_MANAGEMENT_DNF + else if major >= 1 then + Some PACKAGE_MANAGEMENT_YUM + else + (* Probably parsing the release file failed, see RHBZ#1332025. *) + None + + | Some DISTRO_REDHAT_BASED + | Some DISTRO_RHEL + | Some DISTRO_CENTOS + | Some DISTRO_SCIENTIFIC_LINUX + | Some DISTRO_ORACLE_LINUX -> + if major >= 8 then + Some PACKAGE_MANAGEMENT_DNF + else if major >= 5 then + Some PACKAGE_MANAGEMENT_YUM + else if major >= 2 then + Some PACKAGE_MANAGEMENT_UP2DATE + else + (* Probably parsing the release file failed, see RHBZ#1332025. *) + None + + | Some DISTRO_DEBIAN + | Some DISTRO_UBUNTU + | Some DISTRO_LINUX_MINT + | Some DISTRO_ALTLINUX -> + Some PACKAGE_MANAGEMENT_APT + + | Some DISTRO_ARCHLINUX -> + Some PACKAGE_MANAGEMENT_PACMAN + + | Some DISTRO_GENTOO -> + Some PACKAGE_MANAGEMENT_PORTAGE + + | Some DISTRO_PARDUS -> + Some PACKAGE_MANAGEMENT_PISI + + | Some DISTRO_MAGEIA + | Some DISTRO_MANDRIVA -> + Some PACKAGE_MANAGEMENT_URPMI + + | Some DISTRO_SUSE_BASED + | Some DISTRO_OPENSUSE + | Some DISTRO_SLES -> + Some PACKAGE_MANAGEMENT_ZYPPER + + | Some DISTRO_ALPINE_LINUX -> + Some PACKAGE_MANAGEMENT_APK + + | Some DISTRO_VOID_LINUX -> + Some PACKAGE_MANAGEMENT_XBPS; + + | Some DISTRO_SLACKWARE + | Some DISTRO_TTYLINUX + | Some DISTRO_COREOS + | Some DISTRO_WINDOWS + | Some DISTRO_BUILDROOT + | Some DISTRO_CIRROS + | Some DISTRO_FREEDOS + | Some DISTRO_FREEBSD + | Some DISTRO_NETBSD + | Some DISTRO_OPENBSD + | Some DISTRO_FRUGALWARE + | Some DISTRO_PLD_LINUX -> + None + diff --git a/daemon/inspect_fs.mli b/daemon/inspect_fs.mli new file mode 100644 index 000000000..53ea01587 --- /dev/null +++ b/daemon/inspect_fs.mli @@ -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. + *) + +val check_for_filesystem_on : Mountable.t -> string -> + Inspect_types.fs option +(** [check_for_filesystem_on cmdline mountable vfs_type] inspects + [mountable] looking for a single mountpoint from an operating + system. *) diff --git a/daemon/inspect_fs_unix.ml b/daemon/inspect_fs_unix.ml new file mode 100644 index 000000000..3c4d03ca7 --- /dev/null +++ b/daemon/inspect_fs_unix.ml @@ -0,0 +1,797 @@ +(* 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 C_utils +open Std_utils + +open Utils +open Inspect_types +open Inspect_utils + +let re_fedora = Str.regexp "Fedora release \\([0-9]+\\)" +let re_rhel_old = Str.regexp "Red Hat.*release \\([0-9]+\\).*Update \\([0-9]+\\)" +let re_rhel = Str.regexp "Red Hat.*release \\([0-9]+\\)\\.\\([0-9]+\\)" +let re_rhel_no_minor = Str.regexp "Red Hat.*release \\([0-9]+\\)" +let re_centos_old = Str.regexp "CentOS.*release \\([0-9]+\\).*Update \\([0-9]+\\)" +let re_centos = Str.regexp "CentOS.*release \\([0-9]+\\)\\.\\([0-9]+\\)" +let re_centos_no_minor = Str.regexp "CentOS.*release \\([0-9]+\\)" +let re_scientific_linux_old + Str.regexp "Scientific Linux.*release \\([0-9]+\\).*Update \\([0-9]+\\)" +let re_scientific_linux + Str.regexp "Scientific Linux.*release \\([0-9]+\\)\\.\\([0-9]+\\)" +let re_scientific_linux_no_minor + Str.regexp "Scientific Linux.*release \\([0-9]+\\)" +let re_oracle_linux_old + Str.regexp "Oracle Linux.*release \\([0-9]+\\).*Update \\([0-9]+\\)" +let re_oracle_linux + Str.regexp "Oracle Linux.*release \\([0-9]+\\)\\.\\([0-9]+\\)" +let re_oracle_linux_no_minor = Str.regexp "Oracle Linux.*release \\([0-9]+\\)" +let re_netbsd = Str.regexp "^NetBSD \\([0-9]+\\)\\.\\([0-9]+\\)" +let re_opensuse = Str.regexp "^\\(openSUSE|SuSE Linux|SUSE LINUX\\) " +let re_sles = Str.regexp "^SUSE \\(Linux|LINUX\\) Enterprise " +let re_nld = Str.regexp "^Novell Linux Desktop " +let re_sles_version = Str.regexp "^VERSION = \\([0-9]+\\)" +let re_sles_patchlevel = Str.regexp "^PATCHLEVEL = \\([0-9]+\\)" +let re_minix = Str.regexp "^\\([0-9]+\\)\\.\\([0-9]+\\)\\(\\.\\([0-9]+\\)\\)?" +let re_openbsd = Str.regexp "^OpenBSD \\([0-9]+|\\?\\)\\.\\([0-9]+|\\?\\)" +let re_frugalware = Str.regexp "Frugalware \\([0-9]+\\)\\.\\([0-9]+\\)" +let re_pldlinux = Str.regexp "\\([0-9]+\\)\\.\\([0-9]+\\) PLD Linux" + +let arch_binaries + [ "/bin/bash"; "/bin/ls"; "/bin/echo"; "/bin/rm"; "/bin/sh" ] + +let rec check_linux_root mountable data + let os_type = OS_TYPE_LINUX in + let data = { data with os_type = Some os_type } in + + let tests = [ + (* systemd distros include /etc/os-release which is reasonably + * standardized. This entry should be first. + *) + "/etc/os-release", parse_os_release; + (* LSB is also a reasonable standard. This entry should be second. *) + "/etc/lsb-release", parse_lsb_release; + + (* Now we enter the Wild West ... *) + + (* RHEL-based distros include a [/etc/redhat-release] file, hence their + * checks need to be performed before the Red-Hat one. + *) + "/etc/oracle-release", parse_generic ~rex:re_oracle_linux_old + DISTRO_ORACLE_LINUX; + "/etc/oracle-release", parse_generic ~rex:re_oracle_linux + DISTRO_ORACLE_LINUX; + "/etc/oracle-release", parse_generic ~rex:re_oracle_linux_no_minor + DISTRO_ORACLE_LINUX; + "/etc/centos-release", parse_generic ~rex:re_centos_old + DISTRO_CENTOS; + "/etc/centos-release", parse_generic ~rex:re_centos + DISTRO_CENTOS; + "/etc/centos-release", parse_generic ~rex:re_centos_no_minor + DISTRO_CENTOS; + "/etc/altlinux-release", parse_generic DISTRO_ALTLINUX; + "/etc/redhat-release", parse_generic ~rex:re_fedora + DISTRO_FEDORA; + "/etc/redhat-release", parse_generic ~rex:re_rhel_old + DISTRO_RHEL; + "/etc/redhat-release", parse_generic ~rex:re_rhel + DISTRO_RHEL; + "/etc/redhat-release", parse_generic ~rex:re_rhel_no_minor + DISTRO_RHEL; + "/etc/redhat-release", parse_generic ~rex:re_centos_old + DISTRO_CENTOS; + "/etc/redhat-release", parse_generic ~rex:re_centos + DISTRO_CENTOS; + "/etc/redhat-release", parse_generic ~rex:re_centos_no_minor + DISTRO_CENTOS; + "/etc/redhat-release", parse_generic ~rex:re_scientific_linux_old + DISTRO_SCIENTIFIC_LINUX; + "/etc/redhat-release", parse_generic ~rex:re_scientific_linux + DISTRO_SCIENTIFIC_LINUX; + "/etc/redhat-release", parse_generic ~rex:re_scientific_linux_no_minor + DISTRO_SCIENTIFIC_LINUX; + + (* If there's an /etc/redhat-release file, but nothing above + * matches, then it is a generic Red Hat-based distro. + *) + "/etc/redhat-release", parse_generic DISTRO_REDHAT_BASED; + "/etc/redhat-release", + (fun _ data -> { data with distro = Some DISTRO_REDHAT_BASED }); + + "/etc/debian_version", parse_generic DISTRO_DEBIAN; + "/etc/pardus-release", parse_generic DISTRO_PARDUS; + + (* /etc/arch-release file is empty and I can't see a way to + * determine the actual release or product string. + *) + "/etc/arch-release", + (fun _ data -> { data with distro = Some DISTRO_ARCHLINUX }); + + "/etc/gentoo-release", parse_generic DISTRO_GENTOO; + "/etc/meego-release", parse_generic DISTRO_MEEGO; + "/etc/slackware-version", parse_generic DISTRO_SLACKWARE; + "/etc/ttylinux-target", parse_generic DISTRO_TTYLINUX; + + "/etc/SuSE-release", parse_suse_release; + "/etc/SuSE-release", + (fun _ data -> { data with distro = Some DISTRO_SUSE_BASED }); + + "/etc/cirros/version", parse_generic DISTRO_CIRROS; + "/etc/br-version", + (fun release_file data -> + let distro + if Is.is_file ~followsymlinks:true "/usr/share/cirros/logo" then + DISTRO_CIRROS + else + DISTRO_BUILDROOT in + (* /etc/br-version has the format YYYY.MM[-git/hg/svn release] *) + parse_generic distro release_file data); + + "/etc/alpine-release", parse_generic DISTRO_ALPINE_LINUX; + "/etc/frugalware-release", parse_generic ~rex:re_frugalware + DISTRO_FRUGALWARE; + "/etc/pld-release", parse_generic ~rex:re_pldlinux + DISTRO_PLD_LINUX; + ] in + + let rec loop = function + | (release_file, parse_fun) :: tests -> + if verbose () then + eprintf "check_linux_root: checking %s\n%!" release_file; + (try + if not (Is.is_file ~followsymlinks:true release_file) then + raise Not_found; + parse_fun release_file data + with + Not_found -> loop tests) + | [] -> data + in + let data = loop tests in + + let data = { + data with + arch = check_architecture (); + fstab + Inspect_fs_unix_fstab.check_fstab ~mdadm_conf:true mountable os_type; + hostname = check_hostname_linux (); + } in + + data + +(* Parse a os-release file. + * + * Only few fields are parsed, falling back to the usual detection if we + * cannot read all of them. + * + * For the format of os-release, see also: + * http://www.freedesktop.org/software/systemd/man/os-release.html + *) +and parse_os_release release_file data + let chroot = Chroot.create ~name:"parse_os_release" (Sysroot.sysroot ()) in + let lines + Chroot.f chroot ( + fun () -> + if not (is_small_file release_file) then ( + eprintf "%s: not a regular file or too large\n" release_file; + raise Not_found + ); + read_whole_file release_file + ) () in + let lines = String.nsplit "\n" lines in + + let data = List.fold_left ( + fun data line -> + let line = String.trim line in + if line = "" || line.[0] = '#' then + data + else ( + let key, value = String.split "=" line in + let value + let n = String.length value in + if n >= 2 && value.[0] = '"' && value.[n-1] = '"' then + String.sub value 1 (n-2) + else + value in + if key = "ID" then ( + let distro = distro_of_os_release_id value in + match distro with + | Some _ as distro -> { data with distro = distro } + | None -> data + ) + else if key = "PRETTY_NAME" then + { data with product_name = Some value } + else if key = "VERSION_ID" then + parse_version_from_major_minor value data + else + data + ) + ) data lines in + + (* os-release in Debian and CentOS does not provide the full + * version number (VERSION_ID), just the major part of it. If + * we detect that situation then bail out and use the release + * files instead. + *) + (match data with + | { distro = Some (DISTRO_DEBIAN|DISTRO_CENTOS); version = Some (_, 0) } -> + raise Not_found + | _ -> () + ); + + data + +(* ID="fedora" => Some DISTRO_FEDORA *) +and distro_of_os_release_id = function + | "alpine" -> Some DISTRO_ALPINE_LINUX + | "altlinux" -> Some DISTRO_ALTLINUX + | "arch" -> Some DISTRO_ARCHLINUX + | "centos" -> Some DISTRO_CENTOS + | "coreos" -> Some DISTRO_COREOS + | "debian" -> Some DISTRO_DEBIAN + | "fedora" -> Some DISTRO_FEDORA + | "frugalware" -> Some DISTRO_FRUGALWARE + | "mageia" -> Some DISTRO_MAGEIA + | "opensuse" -> Some DISTRO_OPENSUSE + | "pld" -> Some DISTRO_PLD_LINUX + | "rhel" -> Some DISTRO_RHEL + | "sles" | "sled" -> Some DISTRO_SLES + | "ubuntu" -> Some DISTRO_UBUNTU + | "void" -> Some DISTRO_VOID_LINUX + | value -> + eprintf "/etc/os-release: unknown ID=%s\n" value; + None + +(* Ubuntu has /etc/lsb-release containing: + * DISTRIB_ID=Ubuntu # Distro + * DISTRIB_RELEASE=10.04 # Version + * DISTRIB_CODENAME=lucid + * DISTRIB_DESCRIPTION="Ubuntu 10.04.1 LTS" # Product name + * + * [Ubuntu-derived ...] Linux Mint was found to have this: + * DISTRIB_ID=LinuxMint + * DISTRIB_RELEASE=10 + * DISTRIB_CODENAME=julia + * DISTRIB_DESCRIPTION="Linux Mint 10 Julia" + * Linux Mint also has /etc/linuxmint/info with more information, + * but we can use the LSB file. + * + * Mandriva has: + * LSB_VERSION=lsb-4.0-amd64:lsb-4.0-noarch + * DISTRIB_ID=MandrivaLinux + * DISTRIB_RELEASE=2010.1 + * DISTRIB_CODENAME=Henry_Farman + * DISTRIB_DESCRIPTION="Mandriva Linux 2010.1" + * Mandriva also has a normal release file called /etc/mandriva-release. + * + * CoreOS has a /etc/lsb-release link to /usr/share/coreos/lsb-release containing: + * DISTRIB_ID=CoreOS + * DISTRIB_RELEASE=647.0.0 + * DISTRIB_CODENAME="Red Dog" + * DISTRIB_DESCRIPTION="CoreOS 647.0.0" + *) +and parse_lsb_release release_file data + let chroot = Chroot.create ~name:"parse_lsb_release" (Sysroot.sysroot ()) in + let lines + Chroot.f chroot ( + fun () -> + if not (is_small_file release_file) then ( + eprintf "%s: not a regular file or too large\n" release_file; + raise Not_found + ); + read_whole_file release_file + ) () in + let lines = String.nsplit "\n" lines in + + let data = List.fold_left ( + fun data line -> + if data.distro = None && line = "DISTRIB_ID=Ubuntu" then + { data with distro = Some DISTRO_UBUNTU } + else if data.distro = None && line = "DISTRIB_ID=LinuxMint" then + { data with distro = Some DISTRO_LINUX_MINT } + else if data.distro = None && line = "DISTRIB_ID=\"Mageia\"" then + { data with distro = Some DISTRO_MAGEIA } + else if data.distro = None && line = "DISTRIB_ID=CoreOS" then + { data with distro = Some DISTRO_COREOS } + else if String.is_prefix line "DISTRIB_RELEASE=" then ( + let line = String.sub line 16 (String.length line - 16) in + parse_version_from_major_minor line data + ) + else if String.is_prefix line "DISTRIB_DESCRIPTION=\"" || + String.is_prefix line "DISTRIB_DESCRIPTION='" then ( + let n = String.length line in + let product_name = String.sub line 21 (n-22) in + { data with product_name = Some product_name } + ) + else if String.is_prefix line "DISTRIB_DESCRIPTION=" then ( + let n = String.length line in + let product_name = String.sub line 20 (n-20) in + { data with product_name = Some product_name } + ) + else + data + ) data lines in + + data + +and parse_suse_release release_file data + let chroot = Chroot.create ~name:"parse_suse_release" (Sysroot.sysroot ()) in + let lines + Chroot.f chroot ( + fun () -> + if not (is_small_file release_file) then ( + eprintf "%s: not a regular file or too large\n" release_file; + raise Not_found + ); + read_whole_file release_file + ) () in + let lines = String.nsplit "\n" lines in + + if lines = [] then raise Not_found; + + (* First line is dist release name. *) + let product_name = List.hd lines in + let data = { + data with + product_name = Some product_name + } in + + (* Match SLES first because openSuSE regex overlaps some SLES + * release strings. + *) + if Str.string_match re_sles product_name 0 || + Str.string_match re_nld product_name 0 then ( + (* Second line contains version string. *) + let major + if List.length lines >= 2 then ( + let line = List.nth lines 1 in + if Str.string_match re_sles_version line 0 then + Some (int_of_string (Str.matched_group 1 line)) + else None + ) + else None in + + (* Third line contains service pack string. *) + let minor + if List.length lines >= 3 then ( + let line = List.nth lines 2 in + if Str.string_match re_sles_patchlevel line 0 then + Some (int_of_string (Str.matched_group 1 line)) + else None + ) + else None in + + let version + match major, minor with + | Some major, Some minor -> Some (major, minor) + | Some major, None -> Some (major, 0) + | None, Some _ | None, None -> None in + + { data with + distro = Some DISTRO_SLES; + version = version } + ) + else if Str.string_match re_opensuse product_name 0 then ( + (* Second line contains version string. *) + let data + if List.length lines >= 2 then ( + let line = List.nth lines 1 in + parse_version_from_major_minor line data + ) + else data in + + { data with distro = Some DISTRO_OPENSUSE } + ) + else + data + +(* Parse any generic /etc/x-release file. + * + * The optional regular expression which may match 0, 1 or 2 + * substrings, which are used as the major and minor numbers. + * + * The fixed distro is always set, and the product name is + * set to the first line of the release file. + *) +and parse_generic ?rex distro release_file data + let chroot = Chroot.create ~name:"parse_generic" (Sysroot.sysroot ()) in + let product_name + Chroot.f chroot ( + fun () -> + if not (is_small_file release_file) then ( + eprintf "%s: not a regular file or too large\n" release_file; + raise Not_found + ); + read_first_line_from_file release_file + ) () in + if product_name = "" then + raise Not_found; + + if verbose () then + eprintf "parse_generic: product_name = %s\n%!" product_name; + + let data + { data with product_name = Some product_name; + distro = Some distro } in + + match rex with + | Some rex -> + (* If ~rex was supplied, then it must match the release file, + * else the parsing fails. + *) + if not (Str.string_match rex product_name 0) then + raise Not_found; + + (* Although it's not documented, matched_group raises + * Invalid_argument if called with an unknown group number. + *) + let major + try Some (int_of_string (Str.matched_group 1 product_name)) + with Not_found | Invalid_argument _ | Failure _ -> None in + let minor + try Some (int_of_string (Str.matched_group 2 product_name)) + with Not_found | Invalid_argument _ | Failure _ -> None in + (match major, minor with + | None, None -> data + | None, Some _ -> data + | Some major, None -> { data with version = Some (major, 0) } + | Some major, Some minor -> { data with version = Some (major, minor) } + ) + + | None -> + (* However if no ~rex was supplied, then we make a best + * effort attempt to parse a version number, but don't + * fail if one cannot be found. + *) + parse_version_from_major_minor product_name data + +and check_architecture () + let rec loop = function + | [] -> None + | bin :: bins -> + (* Allow symlinks when checking the binaries:,so in case they are + * relative ones (which can be resolved within the same partition), + * then we can check the architecture of their target. + *) + if Is.is_file ~followsymlinks:true bin then ( + try + let resolved = Realpath.realpath bin in + let arch = Filearch.file_architecture resolved in + Some arch + with exn -> + if verbose () then + eprintf "check_architecture: %s: %s\n%!" bin + (Printexc.to_string exn); + loop bins + ) + else + loop bins + in + loop arch_binaries + +and check_hostname_linux () + (* Red Hat-derived would be in /etc/sysconfig/network or + * /etc/hostname (RHEL 7+, F18+). Debian-derived in the file + * /etc/hostname. Very old Debian and SUSE use /etc/HOSTNAME. + * It's best to just look for each of these files in turn, rather + * than try anything clever based on distro. + *) + let rec loop = function + | [] -> None + | filename :: rest -> + match check_hostname_from_file filename with + | Some hostname -> Some hostname + | None -> loop rest + in + let hostname = loop [ "/etc/HOSTNAME"; "/etc/hostname" ] in + match hostname with + | Some hostname -> Some hostname + | None -> + if Is.is_file "/etc/sysconfig/network" then + with_augeas ["/etc/sysconfig/network"] + check_hostname_from_sysconfig_network + else + None + +(* Parse the hostname where it is stored directly in a file. *) +and check_hostname_from_file filename + let chroot + let name = sprintf "check_hostname_linux_from_file: %s" filename in + Chroot.create ~name (Sysroot.sysroot ()) in + + Chroot.f chroot ( + fun () -> + if not (is_small_file filename) then ( + eprintf "%s: not a regular file or too large\n" filename; + None + ) + else + Some (read_first_line_from_file filename) + ) () + +(* Parse the hostname from /etc/sysconfig/network. This must be + * called from the 'with_augeas' wrapper. Note that F18+ and + * RHEL7+ use /etc/hostname just like Debian. + *) +and check_hostname_from_sysconfig_network aug + (* Errors here are not fatal (RHBZ#726739), since it could be + * just missing HOSTNAME field in the file. + *) + Augeas.get aug "/files/etc/sysconfig/network/HOSTNAME" + +(* The currently mounted device looks like a Linux /usr. *) +let check_linux_usr data + let data = { data with os_type = Some OS_TYPE_LINUX } in + + let data + if Is.is_file "/lib/os-release" ~followsymlinks:true then + parse_os_release "/lib/os-release" data + else + data in + + let data + match check_architecture () with + | None -> data + | (Some _) as arch -> { data with arch = arch } in + + data + +(* The currently mounted device is a CoreOS root. From this partition we can + * only determine the hostname. All immutable OS files are under a separate + * read-only /usr partition. + *) +let check_coreos_root mountable data + { + data with + os_type = Some OS_TYPE_LINUX; + distro = Some DISTRO_COREOS; + + (* Determine hostname. *) + hostname = check_hostname_linux (); + + (* CoreOS does not contain /etc/fstab to determine the mount points. + * Associate this filesystem with the "/" mount point. + *) + fstab = [ mountable, "/" ] + } + +(* The currently mounted device looks like a CoreOS /usr. In CoreOS + * the read-only /usr contains the OS version. The /etc/os-release is a + * link to /usr/share/coreos/os-release. + *) +let check_coreos_usr mountable data + let data = { data with os_type = Some OS_TYPE_LINUX; + distro = Some DISTRO_COREOS } in + + let data + if Is.is_file "/lib/os-release" ~followsymlinks:true then + parse_os_release "/lib/os-release" data + else if Is.is_file "/share/coreos/lsb-release" ~followsymlinks:true then + parse_lsb_release "/share/coreos/lsb-release" data + else + data in + + (* Determine the architecture. *) + let data + match check_architecture () with + | None -> data + | (Some _) as arch -> { data with arch = arch } in + + (* CoreOS does not contain /etc/fstab to determine the mount points. + * Associate this filesystem with the "/usr" mount point. + *) + let data = { data with fstab = [ mountable, "/usr" ] } in + + data + +let rec check_freebsd_root mountable data + let os_type = OS_TYPE_FREEBSD and distro = DISTRO_FREEBSD in + let data = { data with os_type = Some os_type; + distro = Some distro } in + + (* FreeBSD has no authoritative version file. The version number is + * in /etc/motd, which the system administrator might edit, but + * we'll use that anyway. + *) + let data + if Is.is_file "/etc/motd" ~followsymlinks:true then + parse_generic distro "/etc/motd" data + else + data in + + let data = { + data with + (* Determine the architecture. *) + arch = check_architecture (); + (* We already know /etc/fstab exists because it's part of the test + * in the caller. + *) + fstab = Inspect_fs_unix_fstab.check_fstab mountable os_type; + hostname = check_hostname_freebsd () + } in + + data + +(* Parse the hostname from /etc/rc.conf. On FreeBSD and NetBSD + * this file contains comments, blank lines and: + * hostname="freebsd8.example.com" + * ifconfig_re0="DHCP" + * keymap="uk.iso" + * sshd_enable="YES" + *) +and check_hostname_freebsd () + let chroot = Chroot.create ~name:"check_hostname_freebsd" + (Sysroot.sysroot ()) in + let filename = "/etc/rc.conf" in + + try + let lines + Chroot.f chroot ( + fun () -> + if not (is_small_file filename) then ( + eprintf "%s: not a regular file or too large\n" filename; + raise Not_found + ) + else ( + let lines = read_whole_file filename in + String.nsplit "\n" lines + ) + ) () in + let rec loop = function + | [] -> + raise Not_found + | line :: _ when String.is_prefix line "hostname=\"" || + String.is_prefix line "hostname='" -> + let len = String.length line - 10 - 1 in + String.sub line 10 len + | line :: _ when String.is_prefix line "hostname=" -> + let len = String.length line - 9 in + String.sub line 9 len + | _ :: lines -> + loop lines + in + let hostname = loop lines in + Some hostname + with + Not_found -> None + +let rec check_netbsd_root mountable data + let os_type = OS_TYPE_NETBSD and distro = DISTRO_NETBSD in + let data = { data with os_type = Some os_type; + distro = Some distro } in + + let data + if Is.is_file "/etc/release" ~followsymlinks:true then + parse_generic ~rex:re_netbsd distro "/etc/release" data + else + data in + + let data = { + data with + (* Determine the architecture. *) + arch = check_architecture (); + (* We already know /etc/fstab exists because it's part of the test + * in the caller. + *) + fstab = Inspect_fs_unix_fstab.check_fstab mountable os_type; + hostname = check_hostname_freebsd () + } in + + data + +and check_hostname_netbsd () = check_hostname_freebsd () + +let rec check_openbsd_root mountable data + let os_type = OS_TYPE_FREEBSD and distro = DISTRO_FREEBSD in + let data = { data with os_type = Some os_type; + distro = Some distro } in + + (* The first line of /etc/motd gets automatically updated at boot. *) + let data + if Is.is_file "/etc/motd" ~followsymlinks:true then + parse_generic distro "/etc/motd" data + else + data in + + (* Before the first boot, the first line will look like this: + * + * OpenBSD ?.? (UNKNOWN) + * + * The previous C code used to check for this case explicitly, + * but in this code, parse_generic should be unable to extract + * any version and so should return with [data.version = None]. + *) + + let data = { + data with + (* Determine the architecture. *) + arch = check_architecture (); + (* We already know /etc/fstab exists because it's part of the test + * in the caller. + *) + fstab = Inspect_fs_unix_fstab.check_fstab mountable os_type; + hostname = check_hostname_freebsd () + } in + + data + +and check_hostname_openbsd () + check_hostname_from_file "/etc/myname" + +(* The currently mounted device may be a Hurd root. Hurd has distros + * just like Linux. + *) +let rec check_hurd_root mountable data + let os_type = OS_TYPE_HURD in + let data = { data with os_type = Some os_type } in + + let data + if Is.is_file "/etc/debian_version" ~followsymlinks:true then ( + let distro = DISTRO_DEBIAN in + parse_generic distro "/etc/debian_version" data + ) + (* Arch Hurd also exists, but inconveniently it doesn't have + * the normal /etc/arch-release file. XXX + *) + else data in + + let data = { + data with + (* Determine the architecture. *) + arch = check_architecture (); + (* We already know /etc/fstab exists because it's part of the test + * in the caller. + *) + fstab = Inspect_fs_unix_fstab.check_fstab mountable os_type; + hostname = check_hostname_hurd () + } in + + data + +and check_hostname_hurd () = check_hostname_linux () + +let rec check_minix_root data + let os_type = OS_TYPE_MINIX in + let data = { data with os_type = Some os_type } in + + let data + if Is.is_file "/etc/version" ~followsymlinks:true then ( + let data + parse_generic ~rex:re_minix DISTRO_MEEGO (* XXX unset below *) + "/etc/version" data in + { data with distro = None } + ) + else + data in + + let data = { + data with + (* Determine the architecture. *) + arch = check_architecture (); + (* TODO: enable fstab inspection once resolve_fstab_device + * implements the proper mapping from the Minix device names + * to the appliance names. + *) + hostname = check_hostname_minix () + } in + + data + +and check_hostname_minix () + check_hostname_from_file "/etc/hostname.file" diff --git a/daemon/inspect_fs_unix.mli b/daemon/inspect_fs_unix.mli new file mode 100644 index 000000000..655c765b4 --- /dev/null +++ b/daemon/inspect_fs_unix.mli @@ -0,0 +1,53 @@ +(* 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 check_coreos_usr : Mountable.t -> Inspect_types.inspection_data -> + Inspect_types.inspection_data +(** Inspect the CoreOS [/usr] filesystem mounted on sysroot. *) + +val check_coreos_root : Mountable.t -> Inspect_types.inspection_data -> + Inspect_types.inspection_data +(** Inspect the CoreOS filesystem mounted on sysroot. *) + +val check_freebsd_root : Mountable.t -> Inspect_types.inspection_data -> + Inspect_types.inspection_data +(** Inspect the FreeBSD filesystem mounted on sysroot. *) + +val check_hurd_root : Mountable.t -> Inspect_types.inspection_data -> + Inspect_types.inspection_data +(** Inspect the Hurd filesystem mounted on sysroot. *) + +val check_linux_usr : Inspect_types.inspection_data -> + Inspect_types.inspection_data +(** Inspect the Linux [/usr] filesystem mounted on sysroot. *) + +val check_linux_root : Mountable.t -> Inspect_types.inspection_data -> + Inspect_types.inspection_data +(** Inspect the Linux filesystem mounted on sysroot. *) + +val check_minix_root : Inspect_types.inspection_data -> + Inspect_types.inspection_data +(** Inspect the Minix filesystem mounted on sysroot. *) + +val check_netbsd_root : Mountable.t -> Inspect_types.inspection_data -> + Inspect_types.inspection_data +(** Inspect the NetBSD filesystem mounted on sysroot. *) + +val check_openbsd_root : Mountable.t -> Inspect_types.inspection_data -> + Inspect_types.inspection_data +(** Inspect the OpenBSD filesystem mounted on sysroot. *) diff --git a/daemon/inspect_fs_unix_fstab.ml b/daemon/inspect_fs_unix_fstab.ml new file mode 100644 index 000000000..f9df08c3b --- /dev/null +++ b/daemon/inspect_fs_unix_fstab.ml @@ -0,0 +1,518 @@ +(* 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 C_utils +open Std_utils + +open Utils +open Inspect_types +open Inspect_utils + +let re_cciss = Str.regexp "^/dev/\\(cciss/c[0-9]+d[0-9]+\\)\\(p\\([0-9]+\\)\\)?$" +let re_diskbyid = Str.regexp "^/dev/disk/by-id/.*-part\\([0-9]+\\)$" +let re_freebsd_gpt = Str.regexp "^/dev/\\(ada{0,1}|vtbd\\)\\([0-9]+\\)p\\([0-9]+\\)$" +let re_freebsd_mbr = Str.regexp "^/dev/\\(ada{0,1}|vtbd\\)\\([0-9]+\\)s\\([0-9]+\\)\\([a-z]\\)$" +let re_hurd_dev = Str.regexp "^/dev/\\(h\\)d\\([0-9]+\\)s\\([0-9]+\\)$" +let re_mdN = Str.regexp "^/dev/md[0-9]+$" +let re_netbsd_dev = Str.regexp "^/dev/\\(l|s\\)d\\([0-9]\\)\\([a-z]\\)$" +let re_openbsd_dev = Str.regexp "^/dev/\\(s|w\\)d\\([0-9]\\)\\([a-z]\\)$" +let re_openbsd_duid = Str.regexp "^[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]\\.\\([a-z]\\)" +let re_xdev = Str.regexp "^/dev/\\(h|s|v|xv\\)d\\([a-z]+\\)\\([0-9]*\\)$" + +let rec check_fstab ?(mdadm_conf = false) (root_mountable : Mountable.t) + os_type + let configfiles + "/etc/fstab" :: if mdadm_conf then ["/etc/mdadm.conf"] else [] in + + with_augeas configfiles (check_fstab_aug mdadm_conf root_mountable os_type) + +and check_fstab_aug mdadm_conf root_mountable os_type aug + (* Generate a map of MD device paths listed in /etc/mdadm.conf + * to MD device paths in the guestfs appliance. + *) + 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 = Augeas.matches aug path in + filter_map (check_fstab_entry md_map root_mountable os_type aug) entries + +and check_fstab_entry md_map root_mountable os_type aug entry + if verbose () then + eprintf "check_fstab_entry: augeas path: %s\n%!" entry; + + let is_bsd + os_type = OS_TYPE_FREEBSD || + os_type = OS_TYPE_NETBSD || + os_type = OS_TYPE_OPENBSD in + + let spec = Augeas.get aug (entry ^ "/spec") in + let mp = Augeas.get aug (entry ^ "/file") in + let vfstype = Augeas.get aug (entry ^ "/vfstype") in + + match spec, mp, vfstype with + | None, _, _ | Some _, None, _ | Some _, Some _, None -> None + | Some spec, Some mp, Some vfstype -> + if verbose () then + eprintf "check_fstab_entry: spec=%s mp=%s vfstype=%s\n%!" + spec mp vfstype; + + (* Ignore /dev/fd (floppy disks) (RHBZ#642929) and CD-ROM drives. + * + * /dev/iso9660/FREEBSD_INSTALL can be found in FreeBSD's + * installation discs. + *) + if (String.is_prefix spec "/dev/fd" && + String.length spec >= 8 && Char.isdigit spec.[7]) || + (String.is_prefix spec "/dev/cd" && + String.length spec >= 8 && Char.isdigit spec.[7]) || + spec = "/dev/floppy" || + spec = "/dev/cdrom" || + String.is_prefix spec "/dev/iso9660/" then + None + else ( + (* Canonicalize the path, so "///usr//local//" -> "/usr/local" *) + let mp = unix_canonical_path mp in + + (* Ignore certain mountpoints. *) + if String.is_prefix mp "/dev/" || + mp = "/dev" || + String.is_prefix mp "/media/" || + String.is_prefix mp "/proc/" || + mp = "/proc" || + String.is_prefix mp "/selinux/" || + mp = "/selinux" || + String.is_prefix mp "/sys/" || + mp = "/sys" then + None + else ( + let mountable + (* Resolve UUID= and LABEL= to the actual device. *) + if String.is_prefix spec "UUID=" then ( + let uuid = String.sub spec 5 (String.length spec - 5) in + let uuid = shell_unquote uuid in + Some (Mountable.of_device (Findfs.findfs_uuid uuid)) + ) + else if String.is_prefix spec "LABEL=" then ( + let label = String.sub spec 6 (String.length spec - 6) in + let label = shell_unquote label in + Some (Mountable.of_device (Findfs.findfs_label label)) + ) + (* Resolve /dev/root to the current device. + * Do the same for the / partition of the *BSD + * systems, since the BSD -> Linux device + * translation is not straight forward. + *) + else if spec = "/dev/root" || (is_bsd && mp = "/") then + Some root_mountable + (* Resolve guest block device names. *) + else if String.is_prefix spec "/dev/" then + Some (resolve_fstab_device spec md_map os_type) + (* In OpenBSD's fstab you can specify partitions + * on a disk by appending a period and a partition + * letter to a Disklable Unique Identifier. The + * DUID is a 16 hex digit field found in the + * OpenBSD's altered BSD disklabel. For more info + * see here: + * http://www.openbsd.org/faq/faq14.html#intro + *) + else if Str.string_match re_openbsd_duid spec 0 then ( + let part = Str.matched_group 1 spec in + (* We cannot peep into disklabels, we can only + * assume that this is the first disk. + *) + let device = sprintf "/dev/sd0%s" part in + Some (resolve_fstab_device device md_map os_type) + ) + (* Ignore "/.swap" (Pardus) and pseudo-devices + * like "tmpfs". If we haven't resolved the device + * successfully by this point, just ignore it. + *) + else + None in + + match mountable with + | None -> None + | Some mountable -> + let mountable + if vfstype = "btrfs" then + get_btrfs_mountable aug entry mountable + else mountable in + + Some (mountable, mp) + ) + ) + +(* If an fstab entry corresponds to a btrfs filesystem, look for + * the 'subvol' option and if it is present then return a btrfs + * subvolume (else return the whole device). + *) +and get_btrfs_mountable aug entry mountable + let device + match mountable with + | { Mountable.m_type = Mountable.MountableDevice; m_device = device } -> + Some device + | { Mountable.m_type + (Mountable.MountablePath|Mountable.MountableBtrfsVol _) } -> + None in + + match device with + | None -> mountable + | Some device -> + let opts = Augeas.matches aug (entry ^ "/opt") in + let rec loop = function + | [] -> mountable (* no subvol, return whole device *) + | opt :: opts -> + let optname = Augeas.get aug opt in + match optname with + | None -> loop opts + | Some "subvol" -> + let subvol = Augeas.get aug (opt ^ "/value") in + (match subvol with + | None -> loop opts + | Some subvol -> + Mountable.of_btrfsvol device subvol + ) + | Some _ -> + loop opts + in + loop opts + +(* Get a map of md device names in mdadm.conf to their device names + * in the appliance. + *) +and map_md_devices aug + (* Get a map of md device uuids to their device names in the appliance. *) + let uuid_map = map_app_md_devices () in + + (* Nothing to do if there are no md devices. *) + if StringMap.is_empty uuid_map then StringMap.empty + else ( + (* Get all arrays listed in mdadm.conf. *) + let entries = Augeas.matches aug "/files/etc/mdadm.conf/array" in + + (* Log a debug entry if we've got md devices but nothing in mdadm.conf. *) + if verbose () && entries = [] then + eprintf "warning: appliance has MD devices, but augeas returned no array matches in /etc/mdadm.conf\n%!"; + + List.fold_left ( + fun md_map entry -> + try + (* Get device name and uuid for each array. *) + let dev = Augeas.get aug (entry ^ "/devicename") in + let uuid = Augeas.get aug (entry ^ "/uuid") in + let dev + match dev with None -> raise Not_found | Some dev -> dev in + let uuid + match uuid with None -> raise Not_found | Some uuid -> uuid in + + (* Parse the uuid into an md_uuid structure so we can look + * it up in the uuid_map. + *) + let uuid = parse_md_uuid uuid in + + let md = StringMap.find uuid uuid_map in + + (* If there's a corresponding uuid in the appliance, create + * a new entry in the transitive map. + *) + StringMap.add dev md md_map + with + (* No Augeas devicename or uuid node found, or could not parse + * uuid, or uuid not present in the uuid_map. + * + * This is not fatal, just ignore the entry. + *) + Not_found | Invalid_argument _ -> md_map + ) StringMap.empty entries + ) + +(* Create a mapping of uuids to appliance md device names. *) +and map_app_md_devices () + let mds = Md.list_md_devices () in + List.fold_left ( + fun map md -> + let detail = Md.md_detail md in + + try + (* Find the value of the "uuid" key. *) + let uuid = List.assoc "uuid" detail in + let uuid = parse_md_uuid uuid in + StringMap.add uuid md map + with + (* uuid not found, or could not be parsed - just ignore the entry *) + Not_found | Invalid_argument _ -> map + ) StringMap.empty mds + +(* Taken from parse_uuid in mdadm. + * + * Raises Invalid_argument if the input is not an MD UUID. + *) +and parse_md_uuid uuid + let len = String.length uuid in + let out = Bytes.create len in + let j = ref 0 in + + for i = 0 to len-1 do + let c = uuid.[i] in + if Char.isxdigit c then ( + Bytes.set out !j c; + incr j + ) + else if c = ':' || c = '.' || c = ' ' || c = '-' then + () + else + invalid_arg "parse_md_uuid: invalid character" + done; + + if !j <> 32 then + invalid_arg "parse_md_uuid: invalid length"; + + Bytes.sub_string out 0 !j + +(* Resolve block device name to the libguestfs device name, eg. + * /dev/xvdb1 => /dev/vdb1; and /dev/mapper/VG-LV => /dev/VG/LV. This + * assumes that disks were added in the same order as they appear to + * the real VM, which is a reasonable assumption to make. Return + * anything we don't recognize unchanged. + *) +and resolve_fstab_device spec md_map os_type + (* In any case where we didn't match a device pattern or there was + * another problem, return this default mountable derived from [spec]. + *) + let default = Mountable.of_device spec in + + if String.is_prefix spec "/dev/mapper" then ( + (* LVM2 does some strange munging on /dev/mapper paths for VGs and + * LVs which contain '-' character: + * + * ><fs> lvcreate LV--test VG--test 32 + * ><fs> debug ls /dev/mapper + * VG----test-LV----test + * + * This makes it impossible to reverse those paths directly, so + * we have implemented lvm_canonical_lv_name in the daemon. + *) + try + match Lvm.lv_canonical spec with + | None -> Mountable.of_device spec + | Some device -> Mountable.of_device device + with + (* Ignore devices that don't exist. (RHBZ#811872) *) + | Unix.Unix_error (Unix.ENOENT, _, _) -> default + ) + + else if Str.string_match re_xdev spec 0 then ( + let typ = Str.matched_group 1 spec + and disk = Str.matched_group 2 spec + and part = int_of_string (Str.matched_group 3 spec) in + resolve_xdev typ disk part default + ) + + else if Str.string_match re_cciss spec 0 then ( + let disk = Str.matched_group 1 spec + (* group 2 = optional p<NN>, group 3 = <NN> *) + and part + try Some (int_of_string (Str.matched_group 3 spec)) + with Not_found | Invalid_argument _ -> None in + resolve_cciss disk part default + ) + + else if Str.string_match re_mdN spec 0 then ( + try + Mountable.of_device (StringMap.find spec md_map) + with + | Not_found -> default + ) + + else if Str.string_match re_diskbyid spec 0 then ( + let part = int_of_string (Str.matched_group 1 spec) in + resolve_diskbyid part default + ) + + else if Str.string_match re_freebsd_gpt spec 0 then ( + (* group 1 (type) is not used *) + let disk = int_of_string (Str.matched_group 2 spec) + and part = int_of_string (Str.matched_group 3 spec) in + + (* If the FreeBSD disk contains GPT partitions, the translation to Linux + * device names is straight forward. Partitions on a virtio disk are + * prefixed with [vtbd]. IDE hard drives used to be prefixed with [ad] + * and now prefixed with [ada]. + *) + if disk >= 0 && disk <= 26 && part >= 0 && part <= 128 then ( + let dev = sprintf "/dev/sd%c%d" + (Char.chr (disk + Char.code 'a')) part in + Mountable.of_device dev + ) + else default + ) + + else if Str.string_match re_freebsd_mbr spec 0 then ( + (* group 1 (type) is not used *) + let disk = int_of_string (Str.matched_group 2 spec) + and slice = int_of_string (Str.matched_group 3 spec) + (* partition number counting from 0: *) + and part = Char.code (Str.matched_group 4 spec).[0] - Char.code 'a' in + + (* FreeBSD MBR disks are organized quite differently. See: + * http://www.freebsd.org/doc/handbook/disk-organization.html + * FreeBSD "partitions" are exposed as quasi-extended partitions + * numbered from 5 in Linux. I have no idea what happens when you + * have multiple "slices" (the FreeBSD term for MBR partitions). + *) + + (* Partition 'c' has the size of the enclosing slice. + * Not mapped under Linux. + *) + let part = if part > 2 then part - 1 else part in + + if disk >= 0 && disk <= 26 && + slice > 0 && slice <= 1 (* > 4 .. see comment above *) && + part >= 0 && part < 25 then ( + let dev = sprintf "/dev/sd%c%d" + (Char.chr (disk + Char.code 'a')) (part + 5) in + Mountable.of_device dev + ) + else default + ) + + else if os_type = OS_TYPE_NETBSD && + Str.string_match re_netbsd_dev spec 0 then ( + (* group 1 (type) is not used *) + let disk = int_of_string (Str.matched_group 2 spec) + (* partition number counting from 0: *) + and part = Char.code (Str.matched_group 3 spec).[0] - Char.code 'a' in + + (* Partition 'c' is the disklabel partition and 'd' the hard disk itself. + * Not mapped under Linux. + *) + let part = if part > 3 then part - 2 else part in + + if disk >= 0 && part >= 0 && part < 24 then ( + let dev = sprintf "/dev/sd%c%d" + (Char.chr (disk + Char.code 'a')) (part + 5) in + Mountable.of_device dev + ) + else default + ) + + else if os_type = OS_TYPE_OPENBSD && + Str.string_match re_openbsd_dev spec 0 then ( + (* group 1 (type) is not used *) + let disk = int_of_string (Str.matched_group 2 spec) + (* partition number counting from 0: *) + and part = Char.code (Str.matched_group 3 spec).[0] - Char.code 'a' in + + (* Partition 'c' is the hard disk itself. Not mapped under Linux. *) + let part = if part > 2 then part - 1 else part in + + (* In OpenBSD MAXPARTITIONS is defined to 16 for all architectures. *) + if disk >= 0 && part >= 0 && part < 15 then ( + let dev = sprintf "/dev/sd%c%d" + (Char.chr (disk + Char.code 'a')) (part + 5) in + Mountable.of_device dev + ) + else default + ) + + else if Str.string_match re_hurd_dev spec 0 then ( + let typ = Str.matched_group 1 spec + and disk = int_of_string (Str.matched_group 2 spec) + and part = int_of_string (Str.matched_group 3 spec) in + + (* Hurd disk devices are like /dev/hdNsM, where hdN is the + * N-th disk and M is the M-th partition on that disk. + * Turn the disk number into a letter-based identifier, so + * we can resolve it easily. + *) + let disk = sprintf "%c" (Char.chr (disk + Char.code 'a')) in + + resolve_xdev typ disk part default + ) + + else default + +(* type: (h|s|v|xv) + * disk: [a-z]+ + * part: \d* + *) +and resolve_xdev typ disk part default + let devices = Devsparts.list_devices () in + let devices = Array.of_list devices in + + (* XXX Check any hints we were passed for a non-heuristic mapping. + * The C code used hints here to map device names as known by + * the library user (eg. from metadata) to libguestfs devices here. + * However none of the libguestfs tools ever used this feature. + * Nevertheless we should reimplement it at some point because + * outside callers might require it, and it's a good idea in general. + *) + + (* Guess the appliance device name if we didn't find a matching hint. *) + let i = drive_index disk in + if i >= 0 && i < Array.length devices then ( + let dev = Array.get devices i in + let dev = dev ^ string_of_int part in + if is_partition dev then + Mountable.of_device dev + else + default + ) + else + default + +(* disk: (cciss/c\d+d\d+) + * part: (\d+)? + *) +and resolve_cciss disk part default + (* XXX Check any hints we were passed for a non-heuristic mapping. + * The C code used hints here to map device names as known by + * the library user (eg. from metadata) to libguestfs devices here. + * However none of the libguestfs tools ever used this feature. + * Nevertheless we should reimplement it at some point because + * outside callers might require it, and it's a good idea in general. + *) + + (* We don't try to guess mappings for cciss devices. *) + default + +(* For /dev/disk/by-id there is a limit to what we can do because + * original SCSI ID information has likely been lost. This + * heuristic will only work for guests that have a single block + * device. + * + * So the main task here is to make sure the assumptions above are + * true. + * + * XXX Use hints from virt-p2v if available. + * See also: https://bugzilla.redhat.com/show_bug.cgi?id=836573#c3 + *) +and resolve_diskbyid part default + let nr_devices = Devsparts.nr_devices () in + + (* If #devices isn't 1, give up trying to translate this fstab entry. *) + if nr_devices <> 1 then + default + else ( + (* Make the partition name and check it exists. *) + let dev = sprintf "/dev/sda%d" part in + if is_partition dev then Mountable.of_device dev + else default + ) diff --git a/daemon/inspect_fs_unix_fstab.mli b/daemon/inspect_fs_unix_fstab.mli new file mode 100644 index 000000000..3ce3aef05 --- /dev/null +++ b/daemon/inspect_fs_unix_fstab.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. + *) + +val check_fstab : ?mdadm_conf:bool -> Mountable.t -> Inspect_types.os_type -> + (Mountable.t * string) list +(** [check_fstab] examines the [/etc/fstab] file of a mounted root + filesystem, returning the list of devices and their mount points. + Various devices (like CD-ROMs) are ignored in the process, and + this function also knows how to map (eg) BSD device names into + Linux/libguestfs device names. + + [mdadm_conf] is true if you want to check [/etc/mdadm.conf] as well. + + [root_mountable] is the [Mountable.t] of the root filesystem. (Note + that the root filesystem must be mounted on sysroot before this + function is called.) + + [os_type] is the presumed operating system type of this root, and + is used to make some adjustments to fstab parsing. *) diff --git a/daemon/inspect_fs_windows.ml b/daemon/inspect_fs_windows.ml new file mode 100644 index 000000000..bb949be47 --- /dev/null +++ b/daemon/inspect_fs_windows.ml @@ -0,0 +1,498 @@ +(* 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 +open Inspect_types +open Inspect_utils + +(* Check a predefined list of common windows system root locations. *) +let systemroot_paths + [ "/windows"; "/winnt"; "/win32"; "/win"; "/reactos" ] + +let re_boot_ini_os + Str.regexp "^\\(multi|scsi\\)(\\([0-9]+\\))disk(\\([0-9]+\\))rdisk(\\([0-9]+\\))partition(\\([0-9]+\\))\\([^=]+\\)=" + +let rec check_windows_root data + let systemroot + match get_windows_systemroot () with + | None -> assert false (* Should never happen - see caller. *) + | Some systemroot -> systemroot in + + let data = { + data with os_type = Some OS_TYPE_WINDOWS; + distro = Some DISTRO_WINDOWS; + windows_systemroot = Some systemroot; + arch = Some (check_windows_arch systemroot) + } in + + (* Load further fields from the Windows registry. *) + check_windows_registry systemroot data + +and is_windows_systemroot () + get_windows_systemroot () <> None + +and get_windows_systemroot () + let rec loop = function + | [] -> None + | path :: paths -> + let path = case_sensitive_path_silently path in + match path with + | None -> loop paths + | Some path -> + if is_systemroot path then Some path + else loop paths + in + let systemroot = loop systemroot_paths in + + let systemroot + match systemroot with + | Some systemroot -> Some systemroot + | None -> + (* If the fs contains boot.ini, check it for non-standard + * systemroot locations. + *) + let boot_ini_path = case_sensitive_path_silently "/boot.ini" in + match boot_ini_path with + | None -> None + | Some boot_ini_path -> + get_windows_systemroot_from_boot_ini boot_ini_path in + + match systemroot with + | None -> None + | Some systemroot -> + if verbose () then + eprintf "get_windows_systemroot: windows %%SYSTEMROOT%% = %s\n%!" + systemroot; + Some systemroot + +and get_windows_systemroot_from_boot_ini boot_ini_path + let chroot + Chroot.create ~name:"get_windows_systemroot_from_boot_ini" + (Sysroot.sysroot ()) in + let lines + Chroot.f chroot ( + fun () -> + if not (is_small_file boot_ini_path) then ( + eprintf "%s: not a regular file or too large\n" boot_ini_path; + None + ) + else + Some (read_whole_file boot_ini_path) + ) () in + match lines with + | None -> None + | Some lines -> + let lines = String.nsplit "\n" lines in + + (* Find: + * [operating systems] + * followed by multiple lines starting with "multi" or "scsi". + *) + let rec loop = function + | [] -> None + | str :: rest when String.is_prefix str "[operating systems]" -> + let rec loop2 = function + | [] -> [] + | str :: rest when String.is_prefix str "multi(" || + String.is_prefix str "scsi(" -> + str :: loop2 rest + | _ -> [] + in + Some (loop2 rest) + | _ :: rest -> loop rest + in + match loop lines with + | None -> None + | Some oses -> + (* Rewrite multi|scsi lines, removing any which we cannot parse. *) + let oses + filter_map ( + fun line -> + if Str.string_match re_boot_ini_os line 0 then ( + let ctrlr_type = Str.matched_group 1 line + and ctrlr = int_of_string (Str.matched_group 2 line) + and disk = int_of_string (Str.matched_group 3 line) + and rdisk = int_of_string (Str.matched_group 4 line) + and part = int_of_string (Str.matched_group 5 line) + and path = Str.matched_group 6 line in + + (* Swap backslashes for forward slashes in the + * system root path. + *) + let path = String.replace_char path '\\' '/' in + + Some (ctrlr_type, ctrlr, disk, rdisk, part, path) + ) + else None + ) oses in + + (* The Windows system root may be on any disk. However, there + * are currently (at least) 2 practical problems preventing us + * from locating it on another disk: + * + * 1. We don't have enough metadata about the disks we were + * given to know if what controller they were on and what + * index they had. + * + * 2. The way inspection of filesystems currently works, we + * can't mark another filesystem, which we may have already + * inspected, to be inspected for a specific Windows system + * root. + * + * Solving 1 properly would require a new API at a minimum. We + * might be able to fudge something practical without this, + * though, e.g. by looking at the <partition>th partition of + * every disk for the specific windows root. + * + * Solving 2 would probably require a significant refactoring + * of the way filesystems are inspected. We should probably do + * this some time. + * + * For the moment, we ignore all partition information and + * assume the system root is on the current partition. In + * practice, this will normally be correct. + *) + + let rec loop = function + | [] -> None + | (_, _, _, _, _, path) :: rest -> + if is_systemroot path then Some path + else loop rest + in + loop oses + +(* Try to find Windows systemroot using some common locations. + * + * Notes: + * + * (1) We check for some directories inside to see if it is a real + * systemroot, and not just a directory that happens to have the same + * name. + * + * (2) If a Windows guest has multiple disks and applications are + * installed on those other disks, then those other disks will contain + * "/Program Files" and "/System Volume Information". Those would + * *not* be Windows root disks. (RHBZ#674130) + *) +and is_systemroot systemroot + is_dir_nocase (systemroot ^ "/system32") && + is_dir_nocase (systemroot ^ "/system32/config") && + is_file_nocase (systemroot ^ "/system32/cmd.exe") + +(* Return the architecture of the guest from cmd.exe. *) +and check_windows_arch systemroot + let cmd_exe = sprintf "%s/system32/cmd.exe" systemroot in + + (* Should exist because of previous check above in is_systemroot. *) + let cmd_exe = Realpath.case_sensitive_path cmd_exe in + + Filearch.file_architecture cmd_exe + +(* Read further fields from the Windows registry. *) +and check_windows_registry systemroot data + (* We know (from is_systemroot) that the config directory exists. *) + let software_hive = sprintf "%s/system32/config/software" systemroot in + let software_hive = Realpath.case_sensitive_path software_hive in + let software_hive + if Is.is_file software_hive then Some software_hive else None in + let data = { data with windows_software_hive = software_hive } in + + let system_hive = sprintf "%s/system32/config/system" systemroot in + let system_hive = Realpath.case_sensitive_path system_hive in + let system_hive + if Is.is_file system_hive then Some system_hive else None in + let data = { data with windows_system_hive = system_hive } in + + match software_hive, system_hive with + | None, _ | Some _, None -> data + | Some software_hive, Some system_hive -> + (* Check software hive. *) + let data = check_windows_software_registry software_hive data in + + (* Check system hive. *) + let data = check_windows_system_registry system_hive data in + + data + +(* At the moment, pull just the ProductName and version numbers from + * the registry. In future there is a case for making many more + * registry fields available to callers. + *) +and check_windows_software_registry software_hive data + with_hive (Sysroot.sysroot () // software_hive) ( + fun h root -> + try + let path = [ "Microsoft"; "Windows NT"; "CurrentVersion" ] in + let node = get_node h root path in + let values = Hivex.node_values h node in + let values = Array.to_list values in + (* Convert to a list of (key, value) to make the following easier. *) + let values = List.map (fun v -> Hivex.value_key h v, v) values in + + (* Look for ProductName key. *) + let data + try + let v = List.assoc "ProductName" values in + { data with product_name = Some (hivex_value_as_utf8 h v) } + with + Not_found -> data in + + (* Version is complicated. Use CurrentMajorVersionNumber and + * CurrentMinorVersionNumber if present. If they are not + * found, fall back on CurrentVersion. + *) + let data + try + let major_v = List.assoc "CurrentMajorVersionNumber" values + and minor_v = List.assoc "CurrentMinorVersionNumber" values in + let major = Int32.to_int (Hivex.value_dword h major_v) + and minor = Int32.to_int (Hivex.value_dword h minor_v) in + { data with version = Some (major, minor) } + with + Not_found -> + let v = List.assoc "CurrentVersion" values in + let v = hivex_value_as_utf8 h v in + parse_version_from_major_minor v data in + + (* InstallationType (product_variant). *) + let data + try + let v = List.assoc "InstallationType" values in + { data with product_variant = Some (hivex_value_as_utf8 h v) } + with + Not_found -> data in + + data + with + | Not_found -> + if verbose () then + eprintf "check_windows_software_registry: cannot locate HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\n%!"; + data + ) (* with_hive *) + +and check_windows_system_registry system_hive data + with_hive (Sysroot.sysroot () // system_hive) ( + fun h root -> + let data = get_drive_mappings h root data in + + let current_control_set = get_current_control_set h root in + let data + { data with windows_current_control_set = current_control_set } in + + match current_control_set with + | None -> data + | Some current_control_set -> + let hostname = get_hostname h root current_control_set in + let data = { data with hostname = hostname } in + data + ) (* with_hive *) + +(* Get the CurrentControlSet. *) +and get_current_control_set h root + try + let path = [ "Select" ] in + let node = get_node h root path in + let current_v = Hivex.node_get_value h node "Current" in + let current_control_set + sprintf "ControlSet%03ld" (Hivex.value_dword h current_v) in + Some current_control_set + with + | Not_found -> + if verbose () then + eprintf "check_windows_system_registry: cannot locate HKLM\\SYSTEM\\Select\n%!"; + None + +(* Get the drive mappings. + * This page explains the contents of HKLM\System\MountedDevices: + * http://www.goodells.net/multiboot/partsigs.shtml + *) +and get_drive_mappings h root data + let devices = lazy (Devsparts.list_devices ()) in + let partitions = lazy (Devsparts.list_partitions ()) in + try + let path = [ "MountedDevices" ] in + let node = get_node h root path in + let values = Hivex.node_values h node in + let values = Array.to_list values in + let values + filter_map ( + fun value -> + let key = Hivex.value_key h value in + let keylen = String.length key in + if keylen >= 14 && + String.lowercase_ascii (String.sub key 0 12) = "\\dosdevices\\" && + Char.isalpha key.[12] && key.[13] = ':' then ( + let drive_letter = String.sub key 12 1 in + + (* Get the binary value. Is it a fixed disk? *) + let (typ, blob) = Hivex.value_value h value in + let device + if typ = Hivex.REG_BINARY then ( + if String.length blob >= 24 && + String.is_prefix blob "DMIO:ID:" (* GPT *) then + map_registry_disk_blob_gpt (Lazy.force partitions) blob + else if String.length blob = 12 then + map_registry_disk_blob (Lazy.force devices) blob + else + None + ) + else None in + + match device with + | None -> None + | Some device -> Some (drive_letter, device) + ) + else + None + ) values in + + { data with drive_mappings = values } + + with + | Not_found -> + if verbose () then + eprintf "check_windows_system_registry: cannot find drive mappings\n%!"; + data + +(* Windows Registry HKLM\SYSTEM\MountedDevices uses a blob of data + * to store partitions. This blob is described here: + * http://www.goodells.net/multiboot/partsigs.shtml + * The following function maps this blob to a libguestfs partition + * name, if possible. + *) +and map_registry_disk_blob devices blob + try + (* First 4 bytes are the disk ID. Search all devices to find the + * disk with this disk ID. + *) + let diskid = String.sub blob 0 4 in + let device = List.find (fun dev -> pread dev 4 0x01b8 = diskid) devices in + + (* Next 8 bytes are the offset of the partition in bytes(!) given as + * a 64 bit little endian number. Luckily it's easy to get the + * partition byte offset from Parted.part_list. + *) + let offset = String.sub blob 4 8 in + let offset = int_of_le64 offset in + let partitions = Parted.part_list device in + let partition + List.find (fun { Parted.part_start = s } -> s = offset) partitions in + + (* Construct the full device name. *) + Some (sprintf "%s%ld" device partition.Parted.part_num) + with + | Not_found -> None + +(* Matches Windows registry HKLM\SYSYTEM\MountedDevices\DosDevices blob to + * to libguestfs GPT partition device. For GPT disks, the blob is made of + * "DMIO:ID:" prefix followed by the GPT partition GUID. + *) +and map_registry_disk_blob_gpt partitions blob + let blob_guid + String.lowercase_ascii (extract_guid_from_registry_blob blob) in + + try + let partition + List.find ( + fun part -> + let partnum = Devsparts.part_to_partnum part in + let device = Devsparts.part_to_dev part in + let typ = Parted.part_get_parttype device in + if typ <> "gpt" then false + else ( + let guid = Parted.part_get_gpt_guid device partnum in + String.lowercase_ascii guid = blob_guid + ) + ) partitions in + Some partition + with + | Not_found -> None + +(* Extracts the binary GUID stored in blob from Windows registry + * HKLM\SYSTYEM\MountedDevices\DosDevices value and converts it to a + * GUID string so that it can be matched against libguestfs partition + * device GPT GUID. + *) +and extract_guid_from_registry_blob blob + (* Copy relevant sections from blob to respective ints. + * Note we have to skip 8 byte "DMIO:ID:" prefix. + *) + let data1 = int_of_le32 (String.sub blob 8 4) + and data2 = int_of_le16 (String.sub blob 12 2) + and data3 = int_of_le16 (String.sub blob 14 2) + and data4 = int_of_be64 (String.sub blob 16 8) (* really big endian! *) in + + sprintf "%08Lx-%04Lx-%04Lx-%04Lx-%012Lx" + data1 data2 data3 + (Int64.shift_right data4 48) + (data4 &^ 0xffffffffffff_L) + +and pread device size offset + let fd = Unix.openfile device [Unix.O_RDONLY; Unix.O_CLOEXEC] 0 in + let ret + protect ~f:( + fun () -> + ignore (Unix.lseek fd offset Unix.SEEK_SET); + let ret = Bytes.create size in + if Unix.read fd ret 0 size < size then + failwithf "pread: %s: short read" device; + ret + ) ~finally:(fun () -> Unix.close fd) in + Bytes.to_string ret + +(* Get the hostname. *) +and get_hostname h root current_control_set + try + let path = [ current_control_set; "Services"; "Tcpip"; "Parameters" ] in + let node = get_node h root path in + let values = Hivex.node_values h node in + let values = Array.to_list values in + (* Convert to a list of (key, value) to make the following easier. *) + let values = List.map (fun v -> Hivex.value_key h v, v) values in + let hostname_v = List.assoc "Hostname" values in + Some (hivex_value_as_utf8 h hostname_v) + with + | Not_found -> + if verbose () then + eprintf "check_windows_system_registry: cannot locate HKLM\\SYSTEM\\%s\\Services\\Tcpip\\Parameters and/or Hostname key\n%!" current_control_set; + None + +(* Raises [Not_found] if the node is not found. *) +and get_node h node = function + | [] -> node + | x :: xs -> + let node = Hivex.node_get_child h node x in + get_node h node xs + +(* NB: This function DOES NOT test for the existence of the file. It + * will return non-NULL even if the file/directory does not exist. + * You have to call guestfs_is_file{,_opts} etc. + *) +and case_sensitive_path_silently path + try + Some (Realpath.case_sensitive_path path) + with + | exn -> + if verbose () then + eprintf "case_sensitive_path_silently: %s: %s\n%!" path + (Printexc.to_string exn); + None diff --git a/daemon/inspect_fs_windows.mli b/daemon/inspect_fs_windows.mli new file mode 100644 index 000000000..936d695c6 --- /dev/null +++ b/daemon/inspect_fs_windows.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 check_windows_root : Inspect_types.inspection_data -> + Inspect_types.inspection_data +(** Inspect the Windows [C:] filesystem mounted on sysroot. *) + +val is_windows_systemroot : unit -> bool +(** Decide if the filesystem mounted on sysroot looks like a + Windows [C:] filesystem. *) diff --git a/daemon/inspect_types.ml b/daemon/inspect_types.ml new file mode 100644 index 000000000..7695fa15b --- /dev/null +++ b/daemon/inspect_types.ml @@ -0,0 +1,317 @@ +(* 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 + +type fs = { + fs_location : location; + role : role; (** Special cases: root filesystem or /usr *) +} +and root = { + root_location : location; + inspection_data : inspection_data; +} +and location = { + mountable : Mountable.t; (** The device name or other mountable object.*) + vfs_type : string; (** Returned from [vfs_type] API. *) +} + +and role + | RoleRoot of inspection_data + | RoleUsr of inspection_data + | RoleSwap + | RoleOther +and inspection_data = { + os_type : os_type option; + distro : distro option; + package_format : package_format option; + package_management : package_management option; + product_name : string option; + product_variant : string option; + version : version option; + arch : string option; + hostname : string option; + fstab : fstab_entry list; + windows_systemroot : string option; + windows_software_hive : string option; + windows_system_hive : string option; + windows_current_control_set : string option; + drive_mappings : drive_mapping list; +} +and os_type + | OS_TYPE_DOS + | OS_TYPE_FREEBSD + | OS_TYPE_HURD + | OS_TYPE_LINUX + | OS_TYPE_MINIX + | OS_TYPE_NETBSD + | OS_TYPE_OPENBSD + | OS_TYPE_WINDOWS +and distro + | DISTRO_ALPINE_LINUX + | DISTRO_ALTLINUX + | DISTRO_ARCHLINUX + | DISTRO_BUILDROOT + | DISTRO_CENTOS + | DISTRO_CIRROS + | DISTRO_COREOS + | DISTRO_DEBIAN + | DISTRO_FEDORA + | DISTRO_FREEBSD + | DISTRO_FREEDOS + | DISTRO_FRUGALWARE + | DISTRO_GENTOO + | DISTRO_LINUX_MINT + | DISTRO_MAGEIA + | DISTRO_MANDRIVA + | DISTRO_MEEGO + | DISTRO_NETBSD + | DISTRO_OPENBSD + | DISTRO_OPENSUSE + | DISTRO_ORACLE_LINUX + | DISTRO_PARDUS + | DISTRO_PLD_LINUX + | DISTRO_REDHAT_BASED + | DISTRO_RHEL + | DISTRO_SCIENTIFIC_LINUX + | DISTRO_SLACKWARE + | DISTRO_SLES + | DISTRO_SUSE_BASED + | DISTRO_TTYLINUX + | DISTRO_UBUNTU + | DISTRO_VOID_LINUX + | DISTRO_WINDOWS +and package_format + | PACKAGE_FORMAT_APK + | PACKAGE_FORMAT_DEB + | PACKAGE_FORMAT_EBUILD + | PACKAGE_FORMAT_PACMAN + | PACKAGE_FORMAT_PISI + | PACKAGE_FORMAT_PKGSRC + | PACKAGE_FORMAT_RPM + | PACKAGE_FORMAT_XBPS +and package_management + | PACKAGE_MANAGEMENT_APK + | PACKAGE_MANAGEMENT_APT + | PACKAGE_MANAGEMENT_DNF + | PACKAGE_MANAGEMENT_PACMAN + | PACKAGE_MANAGEMENT_PISI + | PACKAGE_MANAGEMENT_PORTAGE + | PACKAGE_MANAGEMENT_UP2DATE + | PACKAGE_MANAGEMENT_URPMI + | PACKAGE_MANAGEMENT_XBPS + | PACKAGE_MANAGEMENT_YUM + | PACKAGE_MANAGEMENT_ZYPPER +and version = int * int +and fstab_entry = Mountable.t * string (* mountable, mountpoint *) +and drive_mapping = string * string (* drive name, device *) + +let rec string_of_fs { fs_location = location; role = role } + sprintf "fs: %s role: %s" + (string_of_location location) + (match role with + | RoleRoot _ -> "root" + | RoleUsr _ -> "usr" + | RoleSwap -> "swap" + | RoleOther -> "other") + +and string_of_location { mountable = mountable; vfs_type = vfs_type } + sprintf "%s (%s)" (Mountable.to_string mountable) vfs_type + +and string_of_root { root_location = location; + inspection_data = inspection_data } + sprintf "%s:\n%s" + (string_of_location location) + (string_of_inspection_data inspection_data) + +and string_of_inspection_data data + let b = Buffer.create 1024 in + let bpf fs = bprintf b fs in + may (fun v -> bpf "\ttype: %s\n" (string_of_os_type v)) + data.os_type; + may (fun v -> bpf "\tdistro: %s\n" (string_of_distro v)) + data.distro; + may (fun v -> bpf "\tpackage_format: %s\n" (string_of_package_format v)) + data.package_format; + may (fun v -> bpf "\tpackage_management: %s\n" (string_of_package_management v)) + data.package_management; + may (fun v -> bpf "\tproduct_name: %s\n" v) + data.product_name; + may (fun v -> bpf "\tproduct_variant: %s\n" v) + data.product_variant; + may (fun (major, minor) -> bpf "\tversion: %d.%d\n" major minor) + data.version; + may (fun v -> bpf "\tarch: %s\n" v) + data.arch; + may (fun v -> bpf "\thostname: %s\n" v) + data.hostname; + if data.fstab <> [] then ( + let v = List.map ( + fun (a, b) -> sprintf "(%s, %s)" (Mountable.to_string a) b + ) data.fstab in + bpf "\tfstab: [%s]\n" (String.concat ", " v) + ); + may (fun v -> bpf "\twindows_systemroot: %s\n" v) + data.windows_systemroot; + may (fun v -> bpf "\twindows_software_hive: %s\n" v) + data.windows_software_hive; + may (fun v -> bpf "\twindows_system_hive: %s\n" v) + data.windows_system_hive; + may (fun v -> bpf "\twindows_current_control_set: %s\n" v) + data.windows_current_control_set; + if data.drive_mappings <> [] then ( + let v + List.map (fun (a, b) -> sprintf "(%s, %s)" a b) data.drive_mappings in + bpf "\tdrive_mappings: [%s]\n" (String.concat ", " v) + ); + Buffer.contents b + +and string_of_os_type = function + | OS_TYPE_DOS -> "dos" + | OS_TYPE_FREEBSD -> "freebsd" + | OS_TYPE_HURD -> "hurd" + | OS_TYPE_LINUX -> "linux" + | OS_TYPE_MINIX -> "minix" + | OS_TYPE_NETBSD -> "netbsd" + | OS_TYPE_OPENBSD -> "openbsd" + | OS_TYPE_WINDOWS -> "windows" + +and string_of_distro = function + | DISTRO_ALPINE_LINUX -> "alpinelinux" + | DISTRO_ALTLINUX -> "altlinux" + | DISTRO_ARCHLINUX -> "archlinux" + | DISTRO_BUILDROOT -> "buildroot" + | DISTRO_CENTOS -> "centos" + | DISTRO_CIRROS -> "cirros" + | DISTRO_COREOS -> "coreos" + | DISTRO_DEBIAN -> "debian" + | DISTRO_FEDORA -> "fedora" + | DISTRO_FREEBSD -> "freebsd" + | DISTRO_FREEDOS -> "freedos" + | DISTRO_FRUGALWARE -> "frugalware" + | DISTRO_GENTOO -> "gentoo" + | DISTRO_LINUX_MINT -> "linuxmint" + | DISTRO_MAGEIA -> "mageia" + | DISTRO_MANDRIVA -> "mandriva" + | DISTRO_MEEGO -> "meego" + | DISTRO_NETBSD -> "netbsd" + | DISTRO_OPENBSD -> "openbsd" + | DISTRO_OPENSUSE -> "opensuse" + | DISTRO_ORACLE_LINUX -> "oraclelinux" + | DISTRO_PARDUS -> "pardus" + | DISTRO_PLD_LINUX -> "pldlinux" + | DISTRO_REDHAT_BASED -> "redhat-based" + | DISTRO_RHEL -> "rhel" + | DISTRO_SCIENTIFIC_LINUX -> "scientificlinux" + | DISTRO_SLACKWARE -> "slackware" + | DISTRO_SLES -> "sles" + | DISTRO_SUSE_BASED -> "suse-based" + | DISTRO_TTYLINUX -> "ttylinux" + | DISTRO_UBUNTU -> "ubuntu" + | DISTRO_VOID_LINUX -> "voidlinux" + | DISTRO_WINDOWS -> "windows" + +and string_of_package_format = function + | PACKAGE_FORMAT_APK -> "apk" + | PACKAGE_FORMAT_DEB -> "deb" + | PACKAGE_FORMAT_EBUILD -> "ebuild" + | PACKAGE_FORMAT_PACMAN -> "pacman" + | PACKAGE_FORMAT_PISI -> "pisi" + | PACKAGE_FORMAT_PKGSRC -> "pkgsrc" + | PACKAGE_FORMAT_RPM -> "rpm" + | PACKAGE_FORMAT_XBPS -> "xbps" + +and string_of_package_management = function + | PACKAGE_MANAGEMENT_APK -> "apk" + | PACKAGE_MANAGEMENT_APT -> "apt" + | PACKAGE_MANAGEMENT_DNF -> "dnf" + | PACKAGE_MANAGEMENT_PACMAN -> "pacman" + | PACKAGE_MANAGEMENT_PISI -> "pisi" + | PACKAGE_MANAGEMENT_PORTAGE -> "portage" + | PACKAGE_MANAGEMENT_UP2DATE -> "up2date" + | PACKAGE_MANAGEMENT_URPMI -> "urpmi" + | PACKAGE_MANAGEMENT_XBPS -> "xbps" + | PACKAGE_MANAGEMENT_YUM -> "yum" + | PACKAGE_MANAGEMENT_ZYPPER -> "zypper" + +let null_inspection_data = { + os_type = None; + distro = None; + package_format = None; + package_management = None; + product_name = None; + product_variant = None; + version = None; + arch = None; + hostname = None; + fstab = []; + windows_systemroot = None; + windows_software_hive = None; + windows_system_hive = None; + windows_current_control_set = None; + drive_mappings = []; +} + +let merge_inspection_data child parent + let merge child parent = if parent = None then child else parent in + + { os_type = merge child.os_type parent.os_type; + distro = merge child.distro parent.distro; + package_format = merge child.package_format parent.package_format; + package_management + merge child.package_management parent.package_management; + product_name = merge child.product_name parent.product_name; + product_variant = merge child.product_variant parent.product_variant; + version = merge child.version parent.version; + arch = merge child.arch parent.arch; + hostname = merge child.hostname parent.hostname; + fstab = child.fstab @ parent.fstab; + windows_systemroot + merge child.windows_systemroot parent.windows_systemroot; + windows_software_hive + merge child.windows_software_hive parent.windows_software_hive; + windows_system_hive + merge child.windows_system_hive parent.windows_system_hive; + windows_current_control_set + merge child.windows_current_control_set parent.windows_current_control_set; + + (* This is what the old C code did, but I doubt that it's correct. *) + drive_mappings = child.drive_mappings @ parent.drive_mappings; + } + +let merge child_fs parent_fs + let inspection_data_of_fs = function + | { role = RoleRoot data } + | { role = RoleUsr data } -> data + | { role = (RoleSwap|RoleOther) } -> assert false + in + + match parent_fs with + | { role = RoleRoot parent_data } -> + { parent_fs with + role = RoleRoot (merge_inspection_data (inspection_data_of_fs child_fs) + parent_data) } + | { role = RoleUsr parent_data } -> + { parent_fs with + role = RoleUsr (merge_inspection_data (inspection_data_of_fs child_fs) + parent_data) } + | { role = (RoleSwap|RoleOther) } -> parent_fs + +let inspect_fses = ref [] diff --git a/daemon/inspect_types.mli b/daemon/inspect_types.mli new file mode 100644 index 000000000..a9564b16b --- /dev/null +++ b/daemon/inspect_types.mli @@ -0,0 +1,168 @@ +(* 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 fs = { + fs_location : location; + role : role; (** Special cases: root filesystem or /usr *) +} +and root = { + root_location : location; + inspection_data : inspection_data; +} +and location = { + mountable : Mountable.t; (** The device name or other mountable object.*) + vfs_type : string; (** Returned from [vfs_type] API. *) +} + +and role + | RoleRoot of inspection_data + | RoleUsr of inspection_data + | RoleSwap + | RoleOther +and inspection_data = { + os_type : os_type option; + distro : distro option; + package_format : package_format option; + package_management : package_management option; + product_name : string option; + product_variant : string option; + version : version option; + arch : string option; + hostname : string option; + fstab : fstab_entry list; + windows_systemroot : string option; + windows_software_hive : string option; + windows_system_hive : string option; + windows_current_control_set : string option; + drive_mappings : drive_mapping list; +} +and os_type + | OS_TYPE_DOS + | OS_TYPE_FREEBSD + | OS_TYPE_HURD + | OS_TYPE_LINUX + | OS_TYPE_MINIX + | OS_TYPE_NETBSD + | OS_TYPE_OPENBSD + | OS_TYPE_WINDOWS +and distro + | DISTRO_ALPINE_LINUX + | DISTRO_ALTLINUX + | DISTRO_ARCHLINUX + | DISTRO_BUILDROOT + | DISTRO_CENTOS + | DISTRO_CIRROS + | DISTRO_COREOS + | DISTRO_DEBIAN + | DISTRO_FEDORA + | DISTRO_FREEBSD + | DISTRO_FREEDOS + | DISTRO_FRUGALWARE + | DISTRO_GENTOO + | DISTRO_LINUX_MINT + | DISTRO_MAGEIA + | DISTRO_MANDRIVA + | DISTRO_MEEGO + | DISTRO_NETBSD + | DISTRO_OPENBSD + | DISTRO_OPENSUSE + | DISTRO_ORACLE_LINUX + | DISTRO_PARDUS + | DISTRO_PLD_LINUX + | DISTRO_REDHAT_BASED + | DISTRO_RHEL + | DISTRO_SCIENTIFIC_LINUX + | DISTRO_SLACKWARE + | DISTRO_SLES + | DISTRO_SUSE_BASED + | DISTRO_TTYLINUX + | DISTRO_UBUNTU + | DISTRO_VOID_LINUX + | DISTRO_WINDOWS +and package_format + | PACKAGE_FORMAT_APK + | PACKAGE_FORMAT_DEB + | PACKAGE_FORMAT_EBUILD + | PACKAGE_FORMAT_PACMAN + | PACKAGE_FORMAT_PISI + | PACKAGE_FORMAT_PKGSRC + | PACKAGE_FORMAT_RPM + | PACKAGE_FORMAT_XBPS +and package_management + | PACKAGE_MANAGEMENT_APK + | PACKAGE_MANAGEMENT_APT + | PACKAGE_MANAGEMENT_DNF + | PACKAGE_MANAGEMENT_PACMAN + | PACKAGE_MANAGEMENT_PISI + | PACKAGE_MANAGEMENT_PORTAGE + | PACKAGE_MANAGEMENT_UP2DATE + | PACKAGE_MANAGEMENT_URPMI + | PACKAGE_MANAGEMENT_XBPS + | PACKAGE_MANAGEMENT_YUM + | PACKAGE_MANAGEMENT_ZYPPER +and version = int * int +and fstab_entry = Mountable.t * string (* mountable, mountpoint *) +and drive_mapping = string * string (* drive name, device *) + +val merge_inspection_data : inspection_data -> inspection_data -> inspection_data +(** [merge_inspection_data child parent] merges two sets of inspection + data into a single set. The parent inspection data fields, if + present, take precedence over the child inspection data fields. + + It's intended that you merge upwards, ie. + [merge_inspection_data usr root] *) + +val merge : fs -> fs -> fs +(** [merge child_fs parent_fs] merges two filesystems, + using [merge_inspection_data] to merge the inspection data of + the child into the parent. *) + +val string_of_fs : fs -> string +(** Convert [fs] into a single line string, for debugging only. *) + +val string_of_root : root -> string +(** Convert [root] into a multi-line string, for debugging only. *) + +val string_of_location : location -> string +(** Convert [location] into a string, for debugging only. *) + +val string_of_inspection_data : inspection_data -> string +(** Convert [inspection_data] into a multi-line string, for debugging only. *) + +val string_of_os_type : os_type -> string +(** Convert [os_type] to a string. + The string is part of the public API. *) + +val string_of_distro : distro -> string +(** Convert [distro] to a string. + The string is part of the public API. *) + +val string_of_package_format : package_format -> string +(** Convert [package_format] to a string. + The string is part of the public API. *) + +val string_of_package_management : package_management -> string +(** Convert [package_management] to a string. + The string is part of the public API. *) + +val null_inspection_data : inspection_data +(** {!inspection_data} structure with all fields set to [None]. *) + +val inspect_fses : fs list ref +(** The global list of filesystems found by the previous call to + inspect_os. *) diff --git a/daemon/inspect_utils.ml b/daemon/inspect_utils.ml new file mode 100644 index 000000000..b2388af53 --- /dev/null +++ b/daemon/inspect_utils.ml @@ -0,0 +1,175 @@ +(* 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 +open Inspect_types + +let max_augeas_file_size = 100 * 1000 + +let rec with_augeas configfiles f + let sysroot = Sysroot.sysroot () in + let chroot = Chroot.create (Sysroot.sysroot ()) in + + (* Security: + * + * The old C code had a few problems: It ignored non-regular-file + * objects (eg. devices), passing them to Augeas, so relying on + * Augeas to do the right thing. Also too-large regular files + * caused the whole inspection operation to fail. + * + * I have tried to improve this so that non-regular files and + * too large files are ignored (dropped from the configfiles list), + * so that Augeas won't touch them, but they also won't stop + * inspection. + *) + let safe_file file + Is.is_file ~followsymlinks:true file && ( + let size = (Chroot.f chroot Unix.stat file).Unix.st_size in + size <= max_augeas_file_size + ) + in + let configfiles = List.filter safe_file configfiles in + + let aug + Augeas.create sysroot None [Augeas.AugSaveNoop; Augeas.AugNoLoad] in + + protect + ~f:(fun () -> + (* Tell Augeas to only load configfiles and no other files. This + * prevents a rogue guest from performing a denial of service attack + * by having large, over-complicated configuration files which are + * unrelated to the task at hand. (Thanks Dominic Cleal). + * Note this requires Augeas >= 1.0.0 because of RHBZ#975412. + *) + let pathexpr = make_augeas_path_expression configfiles in + ignore (Augeas.rm aug pathexpr); + Augeas.load aug; + + (* Check that augeas did not get a parse error for any of the + * configfiles, otherwise we are silently missing information. + *) + let matches = Augeas.matches aug "/augeas/files//error" in + List.iter ( + fun match_ -> + List.iter ( + fun file -> + let errorpath = sprintf "/augeas/files%s/error" file in + if match_ = errorpath then ( + (* There's been an error - get the error details. *) + let get path + match Augeas.get aug (errorpath ^ path) with + | None -> "<missing>" + | Some v -> v + in + let message = get "message" in + let line = get "line" in + let charp = get "char" in + failwithf "%s:%s:%s: augeas parse failure: %s" + file line charp message + ) + ) configfiles + ) matches; + + f aug + ) + ~finally:( + fun () -> Augeas.close aug + ) + +(* Explained here: https://bugzilla.redhat.com/show_bug.cgi?id=975412#c0 *) +and make_augeas_path_expression files + let subexprs + List.map ( + fun file -> + (* v NB trailing '/' after filename *) + sprintf "\"%s/\" !~ regexp('^') + glob(incl) + regexp('/.*')" file + ) files in + let subexprs = String.concat " and " subexprs in + + let ret = sprintf "/augeas/load/*[ %s ]" subexprs in + if verbose () then + eprintf "augeas pathexpr = %s\n%!" ret; + + ret + +let is_file_nocase path + let path + try Some (Realpath.case_sensitive_path path) + with _ -> None in + match path with + | None -> false + | Some path -> Is.is_file path + +and is_dir_nocase path + let path + try Some (Realpath.case_sensitive_path path) + with _ -> None in + match path with + | None -> false + | Some path -> Is.is_dir path + +(* Rather hairy test for "is a partition", taken directly from + * the old C inspection code. + *) +let is_partition partition + try + let device = Devsparts.part_to_dev partition in + let i = Devsparts.device_index device in + true + with _ -> false + +(* Without non-greedy matching, it's difficult to write these regular + * expressions properly. We should really switch to using PCRE. XXX + *) +let re_major_minor = Str.regexp "[^0-9]*\\([0-9]+\\)\\.\\([0-9]+\\)" +let re_major_no_minor = Str.regexp "[^0-9]*\\([0-9]+\\)" + +let parse_version_from_major_minor str data + if Str.string_match re_major_minor str 0 || + Str.string_match re_major_no_minor str 0 then ( + let major + try Some (int_of_string (Str.matched_group 1 str)) + with Not_found | Invalid_argument _ | Failure _ -> None in + let minor + try Some (int_of_string (Str.matched_group 2 str)) + with Not_found | Invalid_argument _ | Failure _ -> None in + match major, minor with + | None, None -> data + | None, Some _ -> data + | Some major, None -> { data with version = Some (major, 0) } + | Some major, Some minor -> { data with version = Some (major, minor) } + ) + else ( + eprintf "parse_version_from_major_minor: cannot parse version from '%s'\n" + str; + data + ) + +let with_hive hive_filename f + let flags = [ Hivex.OPEN_UNSAFE ] in + let flags = if verbose () then Hivex.OPEN_VERBOSE :: flags else flags in + let h = Hivex.open_file hive_filename flags in + protect ~f:(fun () -> f h (Hivex.root h)) ~finally:(fun () -> Hivex.close h) + +let hivex_value_as_utf8 h value + utf16le_to_utf8 (snd (Hivex.value_value h value)) diff --git a/daemon/inspect_utils.mli b/daemon/inspect_utils.mli new file mode 100644 index 000000000..96363c011 --- /dev/null +++ b/daemon/inspect_utils.mli @@ -0,0 +1,51 @@ +(* 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 with_augeas : string list -> (Augeas.t -> 'a) -> 'a +(** Open an Augeas handle, parse only 'configfiles' (these + files must exist), and then call 'f' with the Augeas handle. + + As a security measure, this bails if any file is too large for + a reasonable configuration file. After the call to 'f' the + Augeas handle is closed. *) + +val is_file_nocase : string -> bool +val is_dir_nocase : string -> bool +(** With a filesystem mounted under sysroot, check if [path] is + a file or directory under that sysroot. The [path] is + checked case-insensitively. *) + +val is_partition : string -> bool +(** Return true if the device is a partition. *) + +val parse_version_from_major_minor : string -> Inspect_types.inspection_data -> + Inspect_types.inspection_data +(** Make a best effort attempt to parse either X or X.Y from a string, + usually the product_name string. *) + +val with_hive : string -> (Hivex.t -> Hivex.node -> 'a) -> 'a +(** Open a Windows registry "hive", and call the function on the + handle and root node. + + After the call to the function, the hive is always closed. + + The hive is opened readonly. *) + +val hivex_value_as_utf8 : Hivex.t -> Hivex.value -> string +(** Convert a Hivex value which we interpret as UTF-16LE to UTF-8. + The type field stored in the registry is ignored. *) diff --git a/daemon/mount.ml b/daemon/mount.ml index 4bb74fb82..40c81be0e 100644 --- a/daemon/mount.ml +++ b/daemon/mount.ml @@ -60,3 +60,64 @@ let mount_vfs options vfs mountable mountpoint let mount = mount_vfs None None let mount_ro = mount_vfs (Some "ro") None let mount_options options = mount_vfs (Some options) None + +(* Unmount everything mounted under /sysroot. + * + * We have to unmount in the correct order, so we sort the paths by + * longest first to ensure that child paths are unmounted by parent + * paths. + * + * This call is more important than it appears at first, because it + * is widely used by both test and production code in order to + * get back to a known state (nothing mounted, everything synchronized). + *) +let rec umount_all () + (* This is called from internal_autosync and generally as a cleanup + * function, and since the umount will definitely fail if any + * handles are open, we may as well close them. + *) + (* XXX + aug_finalize (); + hivex_finalize (); + journal_finalize (); + *) + + let sysroot = Sysroot.sysroot () in + let sysroot_len = String.length sysroot in + + let info = read_whole_file "/proc/self/mountinfo" in + let info = String.nsplit "\n" info in + + let mps = ref [] in + List.iter ( + fun line -> + let line = String.nsplit " " line in + (* The field of interest is the 5th field. Whitespace is escaped + * with octal sequences like \040 (for space). + * See fs/seq_file.c:mangle_path. + *) + if List.length line >= 5 then ( + let mp = List.nth line 4 in + let mp = proc_unmangle_path mp in + + (* Allow a mount directory like "/sysroot" or "/sysroot/..." *) + if (sysroot_len > 0 && String.is_prefix mp sysroot) || + (String.is_prefix mp sysroot && + String.length mp > sysroot_len && + mp.[sysroot_len] = '/') then + push_front mp mps + ) + ) info; + + let mps = !mps in + let mps = List.sort compare_longest_first mps in + + (* Unmount them. *) + List.iter ( + fun mp -> ignore (command "umount" [mp]) + ) mps + +and compare_longest_first s1 s2 + let n1 = String.length s1 in + let n2 = String.length s2 in + n2 - n1 diff --git a/daemon/mount.mli b/daemon/mount.mli index e43d97c42..abf538521 100644 --- a/daemon/mount.mli +++ b/daemon/mount.mli @@ -20,3 +20,5 @@ 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 + +val umount_all : unit -> unit diff --git a/daemon/utils.ml b/daemon/utils.ml index 808e575fd..e53b4bf02 100644 --- a/daemon/utils.ml +++ b/daemon/utils.ml @@ -247,3 +247,103 @@ let proc_unmangle_path path let is_small_file path is_regular_file path && (stat path).st_size <= 2 * 1048 * 1024 + +let unix_canonical_path path + let is_absolute = String.length path > 0 && path.[0] = '/' in + let path = String.nsplit "/" path in + let path = List.filter ((<>) "") path in + (if is_absolute then "/" else "") ^ String.concat "/" path + +(* Note that we cannot use iconv here because inside the appliance + * all i18n databases are deleted. For the same reason we cannot + * use functions like hivex_value_string, as they also use iconv + * internally. + * + * https://en.wikipedia.org/wiki/UTF-16 + * Also inspired by functions in glib's glib/gutf8.c + *) +let rec utf16le_to_utf8 instr + (* If the length is odd and the last character is ASCII NUL, just + * drop that. (If it's not ASCII NUL, then there's an error) + *) + let len = String.length instr in + let instr + if len mod 1 = 1 then ( + if instr.[len-1] = '\000' then String.sub instr 0 (len-1) + else invalid_arg "input is not a valid UTF16-LE string: length is odd" + ) else instr in + + (* The length should now be even. If the last two bytes are + * '\0\0' then assume it's a NUL-terminated string from the + * Windows registry and drop both characters. + *) + let len = String.length instr in + let instr + if len >= 2 && instr.[len-2] = '\000' && instr.[len-1] = '\000' then + String.sub instr 0 (len-2) + else instr in + + let outbuf = Buffer.create len in + + (* Encode a wide character as UTF-8 and write to outbuf. + * Basically this is g_unichar_to_utf8 implemented in OCaml. + *) + let encode_utf8 c + let first, len + if c < 0x80 then + (0, 1) + else if c < 0x800 then + (0xc0, 2) + else if c < 0x10000 then + (0xe0, 3) + else if c < 0x200000 then + (0xf0, 4) + else if c < 0x4000000 then + (0xf8, 5) + else + (0xfc, 6) in + let rec loop i c + if i = 0 then Buffer.add_char outbuf (Char.chr (c lor first)) + else if i > 0 then ( + loop (i-1) (c lsr 6); + Buffer.add_char outbuf (Char.chr ((c land 0x3f) lor 0x80)) + ) + in + loop (len-1) c + in + + (* Loop over the input UTF16-LE characters. *) + let is_high_surrogate c = c >= 0xd800 && c < 0xdc00 + and is_low_surrogate c = c >= 0xdc00 && c < 0xe000 + and surrogate_value highc lowc + 0x1_0000 + (highc - 0xd800) * 0x400 + lowc - 0xdc00 + in + + let len = String.length instr in + let rec loop i + if i+1 >= len then () + else ( + let c = Char.code instr.[i] + (Char.code instr.[i+1] lsl 8) in + + let wc, skip + (* High surrogate - must come first. *) + if is_high_surrogate c then ( + if i+3 >= len then + invalid_arg "input is not a valid UTF16-LE string: high surrogate at end of string"; + let lowc = Char.code instr.[i+2] + (Char.code instr.[i+3] lsl 8) in + if not (is_low_surrogate lowc) then + invalid_arg "input is not a valid UTF16-LE string: high surrogate not followed by low surrogate"; + (surrogate_value c lowc, 4) + ) + else if is_low_surrogate c then + invalid_arg "input is not a valid UTF16-LE string: unexpected low surrogate" + else + (c, 2) in + + encode_utf8 wc; + loop (i+skip) + ) + in + loop 0; + + Buffer.contents outbuf diff --git a/daemon/utils.mli b/daemon/utils.mli index d3c8bdf4d..94a77de01 100644 --- a/daemon/utils.mli +++ b/daemon/utils.mli @@ -85,3 +85,15 @@ val commandr : ?flags:command_flag list -> string -> string list -> (int * strin val is_small_file : string -> bool (** Return true if the path is a small regular file. *) + +val unix_canonical_path : string -> string +(** Canonicalize a Unix path, so "///usr//local//" -> "/usr/local" + + The path is modified in place because the result is always + the same length or shorter than the argument passed. *) + +val utf16le_to_utf8 : string -> string +(** Convert a UTF16-LE string to UTF-8. + + This uses a simple internal implementation since we cannot use + iconv inside the daemon. *) diff --git a/docs/C_SOURCE_FILES b/docs/C_SOURCE_FILES index 39dcf9035..e90aac0f0 100644 --- a/docs/C_SOURCE_FILES +++ b/docs/C_SOURCE_FILES @@ -64,6 +64,7 @@ customize/perl_edit-c.c daemon/9p.c daemon/acl.c daemon/actions.h +daemon/augeas-c.c daemon/augeas.c daemon/available.c daemon/base64.c @@ -306,9 +307,6 @@ lib/handle.c lib/hivex.c lib/info.c lib/inspect-apps.c -lib/inspect-fs-unix.c -lib/inspect-fs-windows.c -lib/inspect-fs.c lib/inspect-icon.c lib/inspect.c lib/journal.c diff --git a/generator/actions.ml b/generator/actions.ml index 75742397a..515096881 100644 --- a/generator/actions.ml +++ b/generator/actions.ml @@ -51,6 +51,8 @@ let daemon_functions Actions_core_deprecated.daemon_functions @ Actions_debug.daemon_functions @ Actions_hivex.daemon_functions @ + Actions_inspection.daemon_functions @ + Actions_inspection_deprecated.daemon_functions @ Actions_tsk.daemon_functions @ Actions_yara.daemon_functions diff --git a/generator/actions_inspection.ml b/generator/actions_inspection.ml index cd8b9da18..d67d00833 100644 --- a/generator/actions_inspection.ml +++ b/generator/actions_inspection.ml @@ -22,10 +22,11 @@ open Types (* Inspection APIs. *) -let non_daemon_functions = [ +let daemon_functions = [ { defaults with name = "inspect_os"; added = (1, 5, 3); style = RStringList (RMountable, "roots"), [], []; + impl = OCaml "Inspect.inspect_os"; shortdesc = "inspect disk and return list of operating systems found"; longdesc = "\ This function uses other libguestfs functions and certain @@ -61,8 +62,24 @@ Please read L<guestfs(3)/INSPECTION> for more details. See also C<guestfs_list_filesystems>." }; { defaults with + name = "inspect_get_roots"; added = (1, 7, 3); + style = RStringList (RMountable, "roots"), [], []; + impl = OCaml "Inspect.inspect_get_roots"; + shortdesc = "return list of operating systems found by last inspection"; + longdesc = "\ +This function is a convenient way to get the list of root +devices, as returned from a previous call to C<guestfs_inspect_os>, +but without redoing the whole inspection process. + +This returns an empty list if either no root devices were +found or the caller has not called C<guestfs_inspect_os>. + +Please read L<guestfs(3)/INSPECTION> for more details." }; + + { defaults with name = "inspect_get_type"; added = (1, 5, 3); style = RString (RPlainString, "name"), [String (Mountable, "root")], []; + impl = OCaml "Inspect.inspect_get_type"; shortdesc = "get type of inspected operating system"; longdesc = "\ This returns the type of the inspected operating system. @@ -116,6 +133,7 @@ Please read L<guestfs(3)/INSPECTION> for more details." }; { defaults with name = "inspect_get_arch"; added = (1, 5, 3); style = RString (RPlainString, "arch"), [String (Mountable, "root")], []; + impl = OCaml "Inspect.inspect_get_arch"; shortdesc = "get architecture of inspected operating system"; longdesc = "\ This returns the architecture of the inspected operating system. @@ -130,6 +148,7 @@ Please read L<guestfs(3)/INSPECTION> for more details." }; { defaults with name = "inspect_get_distro"; added = (1, 5, 3); style = RString (RPlainString, "distro"), [String (Mountable, "root")], []; + impl = OCaml "Inspect.inspect_get_distro"; shortdesc = "get distro of inspected operating system"; longdesc = "\ This returns the distro (distribution) of the inspected operating @@ -286,6 +305,7 @@ Please read L<guestfs(3)/INSPECTION> for more details." }; { defaults with name = "inspect_get_major_version"; added = (1, 5, 3); style = RInt "major", [String (Mountable, "root")], []; + impl = OCaml "Inspect.inspect_get_major_version"; shortdesc = "get major version of inspected operating system"; longdesc = "\ This returns the major version number of the inspected operating @@ -305,6 +325,7 @@ Please read L<guestfs(3)/INSPECTION> for more details." }; { defaults with name = "inspect_get_minor_version"; added = (1, 5, 3); style = RInt "minor", [String (Mountable, "root")], []; + impl = OCaml "Inspect.inspect_get_minor_version"; shortdesc = "get minor version of inspected operating system"; longdesc = "\ This returns the minor version number of the inspected operating @@ -318,6 +339,7 @@ See also C<guestfs_inspect_get_major_version>." }; { defaults with name = "inspect_get_product_name"; added = (1, 5, 3); style = RString (RPlainString, "product"), [String (Mountable, "root")], []; + impl = OCaml "Inspect.inspect_get_product_name"; shortdesc = "get product name of inspected operating system"; longdesc = "\ This returns the product name of the inspected operating @@ -331,8 +353,164 @@ string C<unknown> is returned. Please read L<guestfs(3)/INSPECTION> for more details." }; { defaults with + name = "inspect_get_windows_systemroot"; added = (1, 5, 25); + style = RString (RPlainString, "systemroot"), [String (Mountable, "root")], []; + impl = OCaml "Inspect.inspect_get_windows_systemroot"; + shortdesc = "get Windows systemroot of inspected operating system"; + longdesc = "\ +This returns the Windows systemroot of the inspected guest. +The systemroot is a directory path such as F</WINDOWS>. + +This call assumes that the guest is Windows and that the +systemroot could be determined by inspection. If this is not +the case then an error is returned. + +Please read L<guestfs(3)/INSPECTION> for more details." }; + + { defaults with + name = "inspect_get_package_format"; added = (1, 7, 5); + style = RString (RPlainString, "packageformat"), [String (Mountable, "root")], []; + impl = OCaml "Inspect.inspect_get_package_format"; + shortdesc = "get package format used by the operating system"; + longdesc = "\ +This function and C<guestfs_inspect_get_package_management> return +the package format and package management tool used by the +inspected operating system. For example for Fedora these +functions would return C<rpm> (package format), and +C<yum> or C<dnf> (package management). + +This returns the string C<unknown> if we could not determine the +package format I<or> if the operating system does not have +a real packaging system (eg. Windows). + +Possible strings include: +C<rpm>, C<deb>, C<ebuild>, C<pisi>, C<pacman>, C<pkgsrc>, C<apk>, +C<xbps>. +Future versions of libguestfs may return other strings. + +Please read L<guestfs(3)/INSPECTION> for more details." }; + + { defaults with + name = "inspect_get_package_management"; added = (1, 7, 5); + style = RString (RPlainString, "packagemanagement"), [String (Mountable, "root")], []; + impl = OCaml "Inspect.inspect_get_package_management"; + shortdesc = "get package management tool used by the operating system"; + longdesc = "\ +C<guestfs_inspect_get_package_format> and this function return +the package format and package management tool used by the +inspected operating system. For example for Fedora these +functions would return C<rpm> (package format), and +C<yum> or C<dnf> (package management). + +This returns the string C<unknown> if we could not determine the +package management tool I<or> if the operating system does not have +a real packaging system (eg. Windows). + +Possible strings include: C<yum>, C<dnf>, C<up2date>, +C<apt> (for all Debian derivatives), +C<portage>, C<pisi>, C<pacman>, C<urpmi>, C<zypper>, C<apk>, C<xbps>. +Future versions of libguestfs may return other strings. + +Please read L<guestfs(3)/INSPECTION> for more details." }; + + { defaults with + name = "inspect_get_hostname"; added = (1, 7, 9); + style = RString (RPlainString, "hostname"), [String (Mountable, "root")], []; + impl = OCaml "Inspect.inspect_get_hostname"; + shortdesc = "get hostname of the operating system"; + longdesc = "\ +This function returns the hostname of the operating system +as found by inspection of the guest’s configuration files. + +If the hostname could not be determined, then the +string C<unknown> is returned. + +Please read L<guestfs(3)/INSPECTION> for more details." }; + + { defaults with + name = "inspect_get_product_variant"; added = (1, 9, 13); + style = RString (RPlainString, "variant"), [String (Mountable, "root")], []; + impl = OCaml "Inspect.inspect_get_product_variant"; + shortdesc = "get product variant of inspected operating system"; + longdesc = "\ +This returns the product variant of the inspected operating +system. + +For Windows guests, this returns the contents of the Registry key +C<HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion> +C<InstallationType> which is usually a string such as +C<Client> or C<Server> (other values are possible). This +can be used to distinguish consumer and enterprise versions +of Windows that have the same version number (for example, +Windows 7 and Windows 2008 Server are both version 6.1, +but the former is C<Client> and the latter is C<Server>). + +For enterprise Linux guests, in future we intend this to return +the product variant such as C<Desktop>, C<Server> and so on. But +this is not implemented at present. + +If the product variant could not be determined, then the +string C<unknown> is returned. + +Please read L<guestfs(3)/INSPECTION> for more details. +See also C<guestfs_inspect_get_product_name>, +C<guestfs_inspect_get_major_version>." }; + + { defaults with + name = "inspect_get_windows_current_control_set"; added = (1, 9, 17); + style = RString (RPlainString, "controlset"), [String (Mountable, "root")], []; + impl = OCaml "Inspect.inspect_get_windows_current_control_set"; + shortdesc = "get Windows CurrentControlSet of inspected operating system"; + longdesc = "\ +This returns the Windows CurrentControlSet of the inspected guest. +The CurrentControlSet is a registry key name such as C<ControlSet001>. + +This call assumes that the guest is Windows and that the +Registry could be examined by inspection. If this is not +the case then an error is returned. + +Please read L<guestfs(3)/INSPECTION> for more details." }; + + { defaults with + name = "inspect_get_windows_software_hive"; added = (1, 35, 26); + style = RString (RPlainString, "path"), [String (Mountable, "root")], []; + impl = OCaml "Inspect.inspect_get_windows_software_hive"; + shortdesc = "get the path of the Windows software hive"; + longdesc = "\ +This returns the path to the hive (binary Windows Registry file) +corresponding to HKLM\\SOFTWARE. + +This call assumes that the guest is Windows and that the guest +has a software hive file with the right name. If this is not the +case then an error is returned. This call does not check that the +hive is a valid Windows Registry hive. + +You can use C<guestfs_hivex_open> to read or write to the hive. + +Please read L<guestfs(3)/INSPECTION> for more details." }; + + { defaults with + name = "inspect_get_windows_system_hive"; added = (1, 35, 26); + style = RString (RPlainString, "path"), [String (Mountable, "root")], []; + impl = OCaml "Inspect.inspect_get_windows_system_hive"; + shortdesc = "get the path of the Windows system hive"; + longdesc = "\ +This returns the path to the hive (binary Windows Registry file) +corresponding to HKLM\\SYSTEM. + +This call assumes that the guest is Windows and that the guest +has a system hive file with the right name. If this is not the +case then an error is returned. This call does not check that the +hive is a valid Windows Registry hive. + +You can use C<guestfs_hivex_open> to read or write to the hive. + +Please read L<guestfs(3)/INSPECTION> for more details." }; + + { defaults with name = "inspect_get_mountpoints"; added = (1, 5, 3); style = RHashtable (RPlainString, RMountable, "mountpoints"), [String (Mountable, "root")], []; + impl = OCaml "Inspect.inspect_get_mountpoints"; shortdesc = "get mountpoints of inspected operating system"; longdesc = "\ This returns a hash of where we think the filesystems @@ -364,6 +542,7 @@ See also C<guestfs_inspect_get_filesystems>." }; { defaults with name = "inspect_get_filesystems"; added = (1, 5, 3); style = RStringList (RMountable, "filesystems"), [String (Mountable, "root")], []; + impl = OCaml "Inspect.inspect_get_filesystems"; shortdesc = "get filesystems associated with inspected operating system"; longdesc = "\ This returns a list of all the filesystems that we think @@ -378,77 +557,43 @@ Please read L<guestfs(3)/INSPECTION> for more details. See also C<guestfs_inspect_get_mountpoints>." }; { defaults with - name = "inspect_get_windows_systemroot"; added = (1, 5, 25); - style = RString (RPlainString, "systemroot"), [String (Mountable, "root")], []; - shortdesc = "get Windows systemroot of inspected operating system"; + name = "inspect_get_drive_mappings"; added = (1, 9, 17); + style = RHashtable (RPlainString, RDevice, "drives"), [String (Mountable, "root")], []; + impl = OCaml "Inspect.inspect_get_drive_mappings"; + shortdesc = "get drive letter mappings"; longdesc = "\ -This returns the Windows systemroot of the inspected guest. -The systemroot is a directory path such as F</WINDOWS>. +This call is useful for Windows which uses a primitive system +of assigning drive letters (like F<C:\\>) to partitions. +This inspection API examines the Windows Registry to find out +how disks/partitions are mapped to drive letters, and returns +a hash table as in the example below: -This call assumes that the guest is Windows and that the -systemroot could be determined by inspection. If this is not -the case then an error is returned. + C => /dev/vda2 + E => /dev/vdb1 + F => /dev/vdc1 -Please read L<guestfs(3)/INSPECTION> for more details." }; +Note that keys are drive letters. For Windows, the key is +case insensitive and just contains the drive letter, without +the customary colon separator character. - { defaults with - name = "inspect_get_roots"; added = (1, 7, 3); - style = RStringList (RMountable, "roots"), [], []; - shortdesc = "return list of operating systems found by last inspection"; - longdesc = "\ -This function is a convenient way to get the list of root -devices, as returned from a previous call to C<guestfs_inspect_os>, -but without redoing the whole inspection process. - -This returns an empty list if either no root devices were -found or the caller has not called C<guestfs_inspect_os>. - -Please read L<guestfs(3)/INSPECTION> for more details." }; - - { defaults with - name = "inspect_get_package_format"; added = (1, 7, 5); - style = RString (RPlainString, "packageformat"), [String (Mountable, "root")], []; - shortdesc = "get package format used by the operating system"; - longdesc = "\ -This function and C<guestfs_inspect_get_package_management> return -the package format and package management tool used by the -inspected operating system. For example for Fedora these -functions would return C<rpm> (package format), and -C<yum> or C<dnf> (package management). +In future we may support other operating systems that also used drive +letters, but the keys for those might not be case insensitive +and might be longer than 1 character. For example in OS-9, +hard drives were named C<h0>, C<h1> etc. -This returns the string C<unknown> if we could not determine the -package format I<or> if the operating system does not have -a real packaging system (eg. Windows). - -Possible strings include: -C<rpm>, C<deb>, C<ebuild>, C<pisi>, C<pacman>, C<pkgsrc>, C<apk>, -C<xbps>. -Future versions of libguestfs may return other strings. - -Please read L<guestfs(3)/INSPECTION> for more details." }; - - { defaults with - name = "inspect_get_package_management"; added = (1, 7, 5); - style = RString (RPlainString, "packagemanagement"), [String (Mountable, "root")], []; - shortdesc = "get package management tool used by the operating system"; - longdesc = "\ -C<guestfs_inspect_get_package_format> and this function return -the package format and package management tool used by the -inspected operating system. For example for Fedora these -functions would return C<rpm> (package format), and -C<yum> or C<dnf> (package management). +For Windows guests, currently only hard drive mappings are +returned. Removable disks (eg. DVD-ROMs) are ignored. -This returns the string C<unknown> if we could not determine the -package management tool I<or> if the operating system does not have -a real packaging system (eg. Windows). +For guests that do not use drive mappings, or if the drive mappings +could not be determined, this returns an empty hash table. -Possible strings include: C<yum>, C<dnf>, C<up2date>, -C<apt> (for all Debian derivatives), -C<portage>, C<pisi>, C<pacman>, C<urpmi>, C<zypper>, C<apk>, C<xbps>. -Future versions of libguestfs may return other strings. +Please read L<guestfs(3)/INSPECTION> for more details. +See also C<guestfs_inspect_get_mountpoints>, +C<guestfs_inspect_get_filesystems>." }; -Please read L<guestfs(3)/INSPECTION> for more details." }; +] +let non_daemon_functions = [ { defaults with name = "inspect_list_applications2"; added = (1, 19, 56); style = RStructList ("applications2", "application2"), [String (Mountable, "root")], []; @@ -553,95 +698,6 @@ If unavailable this is returned as an empty string C<\"\">. Please read L<guestfs(3)/INSPECTION> for more details." }; { defaults with - name = "inspect_get_hostname"; added = (1, 7, 9); - style = RString (RPlainString, "hostname"), [String (Mountable, "root")], []; - shortdesc = "get hostname of the operating system"; - longdesc = "\ -This function returns the hostname of the operating system -as found by inspection of the guest’s configuration files. - -If the hostname could not be determined, then the -string C<unknown> is returned. - -Please read L<guestfs(3)/INSPECTION> for more details." }; - - { defaults with - name = "inspect_get_product_variant"; added = (1, 9, 13); - style = RString (RPlainString, "variant"), [String (Mountable, "root")], []; - shortdesc = "get product variant of inspected operating system"; - longdesc = "\ -This returns the product variant of the inspected operating -system. - -For Windows guests, this returns the contents of the Registry key -C<HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion> -C<InstallationType> which is usually a string such as -C<Client> or C<Server> (other values are possible). This -can be used to distinguish consumer and enterprise versions -of Windows that have the same version number (for example, -Windows 7 and Windows 2008 Server are both version 6.1, -but the former is C<Client> and the latter is C<Server>). - -For enterprise Linux guests, in future we intend this to return -the product variant such as C<Desktop>, C<Server> and so on. But -this is not implemented at present. - -If the product variant could not be determined, then the -string C<unknown> is returned. - -Please read L<guestfs(3)/INSPECTION> for more details. -See also C<guestfs_inspect_get_product_name>, -C<guestfs_inspect_get_major_version>." }; - - { defaults with - name = "inspect_get_windows_current_control_set"; added = (1, 9, 17); - style = RString (RPlainString, "controlset"), [String (Mountable, "root")], []; - shortdesc = "get Windows CurrentControlSet of inspected operating system"; - longdesc = "\ -This returns the Windows CurrentControlSet of the inspected guest. -The CurrentControlSet is a registry key name such as C<ControlSet001>. - -This call assumes that the guest is Windows and that the -Registry could be examined by inspection. If this is not -the case then an error is returned. - -Please read L<guestfs(3)/INSPECTION> for more details." }; - - { defaults with - name = "inspect_get_drive_mappings"; added = (1, 9, 17); - style = RHashtable (RPlainString, RDevice, "drives"), [String (Mountable, "root")], []; - shortdesc = "get drive letter mappings"; - longdesc = "\ -This call is useful for Windows which uses a primitive system -of assigning drive letters (like F<C:\\>) to partitions. -This inspection API examines the Windows Registry to find out -how disks/partitions are mapped to drive letters, and returns -a hash table as in the example below: - - C => /dev/vda2 - E => /dev/vdb1 - F => /dev/vdc1 - -Note that keys are drive letters. For Windows, the key is -case insensitive and just contains the drive letter, without -the customary colon separator character. - -In future we may support other operating systems that also used drive -letters, but the keys for those might not be case insensitive -and might be longer than 1 character. For example in OS-9, -hard drives were named C<h0>, C<h1> etc. - -For Windows guests, currently only hard drive mappings are -returned. Removable disks (eg. DVD-ROMs) are ignored. - -For guests that do not use drive mappings, or if the drive mappings -could not be determined, this returns an empty hash table. - -Please read L<guestfs(3)/INSPECTION> for more details. -See also C<guestfs_inspect_get_mountpoints>, -C<guestfs_inspect_get_filesystems>." }; - - { defaults with name = "inspect_get_icon"; added = (1, 11, 12); style = RBufferOut "icon", [String (Mountable, "root")], [OBool "favicon"; OBool "highquality"]; shortdesc = "get the icon corresponding to this operating system"; @@ -706,38 +762,4 @@ advice before using trademarks in applications. =back" }; - { defaults with - name = "inspect_get_windows_software_hive"; added = (1, 35, 26); - style = RString (RPlainString, "path"), [String (Mountable, "root")], []; - shortdesc = "get the path of the Windows software hive"; - longdesc = "\ -This returns the path to the hive (binary Windows Registry file) -corresponding to HKLM\\SOFTWARE. - -This call assumes that the guest is Windows and that the guest -has a software hive file with the right name. If this is not the -case then an error is returned. This call does not check that the -hive is a valid Windows Registry hive. - -You can use C<guestfs_hivex_open> to read or write to the hive. - -Please read L<guestfs(3)/INSPECTION> for more details." }; - - { defaults with - name = "inspect_get_windows_system_hive"; added = (1, 35, 26); - style = RString (RPlainString, "path"), [String (Mountable, "root")], []; - shortdesc = "get the path of the Windows system hive"; - longdesc = "\ -This returns the path to the hive (binary Windows Registry file) -corresponding to HKLM\\SYSTEM. - -This call assumes that the guest is Windows and that the guest -has a system hive file with the right name. If this is not the -case then an error is returned. This call does not check that the -hive is a valid Windows Registry hive. - -You can use C<guestfs_hivex_open> to read or write to the hive. - -Please read L<guestfs(3)/INSPECTION> for more details." }; - ] diff --git a/generator/actions_inspection.mli b/generator/actions_inspection.mli index 327f7aa4f..06b8116c4 100644 --- a/generator/actions_inspection.mli +++ b/generator/actions_inspection.mli @@ -19,3 +19,4 @@ (* Please read generator/README first. *) val non_daemon_functions : Types.action list +val daemon_functions : Types.action list diff --git a/generator/actions_inspection_deprecated.ml b/generator/actions_inspection_deprecated.ml index 7c34cb1a8..c3e76ff7b 100644 --- a/generator/actions_inspection_deprecated.ml +++ b/generator/actions_inspection_deprecated.ml @@ -121,9 +121,13 @@ If unavailable this is returned as an empty string C<\"\">. Please read L<guestfs(3)/INSPECTION> for more details." }; +] + +let daemon_functions = [ { defaults with name = "inspect_get_format"; added = (1, 9, 4); style = RString (RPlainString, "format"), [String (Mountable, "root")], []; + impl = OCaml "Inspect.inspect_get_format"; deprecated_by = Deprecated_no_replacement; shortdesc = "get format of inspected operating system"; longdesc = "\ @@ -155,6 +159,7 @@ Please read L<guestfs(3)/INSPECTION> for more details." }; { defaults with name = "inspect_is_live"; added = (1, 9, 4); style = RBool "live", [String (Mountable, "root")], []; + impl = OCaml "Inspect.inspect_is_live"; deprecated_by = Deprecated_no_replacement; shortdesc = "get live flag for install disk"; longdesc = "\ @@ -165,6 +170,7 @@ Please read L<guestfs(3)/INSPECTION> for more details." }; { defaults with name = "inspect_is_netinst"; added = (1, 9, 4); style = RBool "netinst", [String (Mountable, "root")], []; + impl = OCaml "Inspect.inspect_is_netinst"; deprecated_by = Deprecated_no_replacement; shortdesc = "get netinst (network installer) flag for install disk"; longdesc = "\ @@ -175,6 +181,7 @@ Please read L<guestfs(3)/INSPECTION> for more details." }; { defaults with name = "inspect_is_multipart"; added = (1, 9, 4); style = RBool "multipart", [String (Mountable, "root")], []; + impl = OCaml "Inspect.inspect_is_multipart"; deprecated_by = Deprecated_no_replacement; shortdesc = "get multipart flag for install disk"; longdesc = "\ diff --git a/generator/actions_inspection_deprecated.mli b/generator/actions_inspection_deprecated.mli index 327f7aa4f..06b8116c4 100644 --- a/generator/actions_inspection_deprecated.mli +++ b/generator/actions_inspection_deprecated.mli @@ -19,3 +19,4 @@ (* Please read generator/README first. *) val non_daemon_functions : Types.action list +val daemon_functions : Types.action list diff --git a/generator/daemon.ml b/generator/daemon.ml index f20c87bea..b8d0a3a88 100644 --- a/generator/daemon.ml +++ b/generator/daemon.ml @@ -597,6 +597,30 @@ return_string_mountable (value retv) } } +/* Implement RStringList (RMountable, _). */ +static char ** +return_string_mountable_list (value retv) +{ + CLEANUP_FREE_STRINGSBUF DECLARE_STRINGSBUF (ret); + value v; + char *m; + + while (retv != Val_int (0)) { + v = Field (retv, 0); + m = return_string_mountable (v); + if (m == NULL) + return NULL; + if (add_string_nodup (&ret, m) == -1) + return NULL; + retv = Field (retv, 1); + } + + if (end_stringsbuf (&ret) == -1) + return NULL; + + return take_stringsbuf (&ret); /* caller frees */ +} + /* Implement RHashtable (RPlainString, RPlainString, _). */ static char ** return_hashtable_string_string (value retv) @@ -649,6 +673,34 @@ return_hashtable_mountable_string (value retv) return take_stringsbuf (&ret); /* caller frees */ } +/* Implement RHashtable (RPlainString, RMountable, _). */ +static char ** +return_hashtable_string_mountable (value retv) +{ + CLEANUP_FREE_STRINGSBUF DECLARE_STRINGSBUF (ret); + value sv, v, mv; + char *m; + + while (retv != Val_int (0)) { + v = Field (retv, 0); /* (string, Mountable.t) */ + sv = Field (v, 0); /* string */ + if (add_string (&ret, String_val (sv)) == -1) + return NULL; + mv = Field (v, 1); /* Mountable.t */ + m = return_string_mountable (mv); + if (m == NULL) + return NULL; + if (add_string_nodup (&ret, m) == -1) + return NULL; + retv = Field (retv, 1); + } + + if (end_stringsbuf (&ret) == -1) + return NULL; + + return take_stringsbuf (&ret); /* caller frees */ +} + "; (* Implement code for returning structs and struct lists. *) @@ -889,9 +941,12 @@ return_hashtable_mountable_string (value retv) | RString (RMountable, _) -> pr " char *ret = return_string_mountable (retv);\n"; pr " CAMLreturnT (char *, ret); /* caller frees */\n" - | RStringList _ -> + | RStringList ((RPlainString|RDevice), _) -> pr " char **ret = return_string_list (retv);\n"; pr " CAMLreturnT (char **, ret); /* caller frees */\n" + | RStringList (RMountable, _) -> + pr " char **ret = return_string_mountable_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; @@ -902,12 +957,16 @@ return_hashtable_mountable_string (value retv) pr " return_%s_list (retv);\n" typ; pr " /* caller frees */\n"; pr " CAMLreturnT (guestfs_int_%s_list *, ret);\n" typ - | RHashtable (RPlainString, RPlainString, _) -> + | RHashtable (RPlainString, RPlainString, _) + | RHashtable (RPlainString, RDevice, _) -> pr " char **ret = return_hashtable_string_string (retv);\n"; pr " CAMLreturnT (char **, ret); /* caller frees */\n" | RHashtable (RMountable, RPlainString, _) -> pr " char **ret = return_hashtable_mountable_string (retv);\n"; pr " CAMLreturnT (char **, ret); /* caller frees */\n" + | RHashtable (RPlainString, RMountable, _) -> + pr " char **ret = return_hashtable_string_mountable (retv);\n"; + pr " CAMLreturnT (char **, ret); /* caller frees */\n" | RHashtable _ -> assert false | RBufferOut _ -> assert false ); diff --git a/generator/proc_nr.ml b/generator/proc_nr.ml index dec02f5fa..0a41f9c24 100644 --- a/generator/proc_nr.ml +++ b/generator/proc_nr.ml @@ -484,6 +484,29 @@ let proc_nr = [ 474, "internal_yara_scan"; 475, "file_architecture"; 476, "list_filesystems"; +477, "inspect_os"; +478, "inspect_get_roots"; +479, "inspect_get_format"; +480, "inspect_get_type"; +481, "inspect_get_distro"; +482, "inspect_get_package_format"; +483, "inspect_get_package_management"; +484, "inspect_get_product_name"; +485, "inspect_get_product_variant"; +486, "inspect_get_major_version"; +487, "inspect_get_minor_version"; +488, "inspect_get_arch"; +489, "inspect_get_hostname"; +490, "inspect_get_windows_systemroot"; +491, "inspect_get_windows_software_hive"; +492, "inspect_get_windows_system_hive"; +493, "inspect_get_windows_current_control_set"; +494, "inspect_is_live"; +495, "inspect_is_netinst"; +496, "inspect_is_multipart"; +497, "inspect_get_mountpoints"; +498, "inspect_get_filesystems"; +499, "inspect_get_drive_mappings"; ] (* 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 b86395733..761fcd3ac 100644 --- a/lib/MAX_PROC_NR +++ b/lib/MAX_PROC_NR @@ -1 +1 @@ -476 +499 diff --git a/lib/Makefile.am b/lib/Makefile.am index 31568f933..80adebe69 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -96,9 +96,6 @@ libguestfs_la_SOURCES = \ info.c \ inspect.c \ inspect-apps.c \ - inspect-fs.c \ - inspect-fs-unix.c \ - inspect-fs-windows.c \ inspect-icon.c \ journal.c \ launch.c \ diff --git a/lib/guestfs-internal.h b/lib/guestfs-internal.h index 81755177d..1fa621cf0 100644 --- a/lib/guestfs-internal.h +++ b/lib/guestfs-internal.h @@ -116,21 +116,6 @@ /* Some limits on what the inspection code will read, for safety. */ -/* Small text configuration files. - * - * The upper limit is for general files that we grep or download. The - * largest such file is probably "txtsetup.sif" from Windows CDs - * (~500K). This number has to be larger than any legitimate file and - * smaller than the protocol message size. - * - * The lower limit is for files parsed by Augeas on the daemon side, - * where Augeas is running in reduced memory and can potentially - * create a lot of metadata so we really need to be careful about - * those. - */ -#define MAX_SMALL_FILE_SIZE (2 * 1000 * 1000) -#define MAX_AUGEAS_FILE_SIZE (100 * 1000) - /* Maximum RPM or dpkg database we will download to /tmp. RPM * 'Packages' database can get very large: 70 MB is roughly the * standard size for a new Fedora install, and after lots of package @@ -470,12 +455,6 @@ struct guestfs_h { struct event *events; size_t nr_events; - /* Information gathered by inspect_os. Must be freed by calling - * guestfs_int_free_inspect_info. - */ - struct inspect_fs *fses; - size_t nr_fses; - /* Private data area. */ struct hash_table *pda; struct pda_entry *pda_next; @@ -536,133 +515,6 @@ struct version { int v_micro; }; -/* Per-filesystem data stored for inspect_os. */ -enum inspect_os_format { - OS_FORMAT_UNKNOWN = 0, - OS_FORMAT_INSTALLED, - OS_FORMAT_INSTALLER, - /* in future: supplemental disks */ -}; - -enum inspect_os_type { - OS_TYPE_UNKNOWN = 0, - OS_TYPE_LINUX, - OS_TYPE_WINDOWS, - OS_TYPE_FREEBSD, - OS_TYPE_NETBSD, - OS_TYPE_HURD, - OS_TYPE_DOS, - OS_TYPE_OPENBSD, - OS_TYPE_MINIX, -}; - -enum inspect_os_distro { - OS_DISTRO_UNKNOWN = 0, - OS_DISTRO_DEBIAN, - OS_DISTRO_FEDORA, - OS_DISTRO_REDHAT_BASED, - OS_DISTRO_RHEL, - OS_DISTRO_WINDOWS, - OS_DISTRO_PARDUS, - OS_DISTRO_ARCHLINUX, - OS_DISTRO_GENTOO, - OS_DISTRO_UBUNTU, - OS_DISTRO_MEEGO, - OS_DISTRO_LINUX_MINT, - OS_DISTRO_MANDRIVA, - OS_DISTRO_SLACKWARE, - OS_DISTRO_CENTOS, - OS_DISTRO_SCIENTIFIC_LINUX, - OS_DISTRO_TTYLINUX, - OS_DISTRO_MAGEIA, - OS_DISTRO_OPENSUSE, - OS_DISTRO_BUILDROOT, - OS_DISTRO_CIRROS, - OS_DISTRO_FREEDOS, - OS_DISTRO_SUSE_BASED, - OS_DISTRO_SLES, - OS_DISTRO_OPENBSD, - OS_DISTRO_ORACLE_LINUX, - OS_DISTRO_FREEBSD, - OS_DISTRO_NETBSD, - OS_DISTRO_COREOS, - OS_DISTRO_ALPINE_LINUX, - OS_DISTRO_ALTLINUX, - OS_DISTRO_FRUGALWARE, - OS_DISTRO_PLD_LINUX, - OS_DISTRO_VOID_LINUX, -}; - -enum inspect_os_package_format { - OS_PACKAGE_FORMAT_UNKNOWN = 0, - OS_PACKAGE_FORMAT_RPM, - OS_PACKAGE_FORMAT_DEB, - OS_PACKAGE_FORMAT_PACMAN, - OS_PACKAGE_FORMAT_EBUILD, - OS_PACKAGE_FORMAT_PISI, - OS_PACKAGE_FORMAT_PKGSRC, - OS_PACKAGE_FORMAT_APK, - OS_PACKAGE_FORMAT_XBPS, -}; - -enum inspect_os_package_management { - OS_PACKAGE_MANAGEMENT_UNKNOWN = 0, - OS_PACKAGE_MANAGEMENT_YUM, - OS_PACKAGE_MANAGEMENT_UP2DATE, - OS_PACKAGE_MANAGEMENT_APT, - OS_PACKAGE_MANAGEMENT_PACMAN, - OS_PACKAGE_MANAGEMENT_PORTAGE, - OS_PACKAGE_MANAGEMENT_PISI, - OS_PACKAGE_MANAGEMENT_URPMI, - OS_PACKAGE_MANAGEMENT_ZYPPER, - OS_PACKAGE_MANAGEMENT_DNF, - OS_PACKAGE_MANAGEMENT_APK, - OS_PACKAGE_MANAGEMENT_XBPS, -}; - -enum inspect_os_role { - OS_ROLE_UNKNOWN = 0, - OS_ROLE_ROOT, - OS_ROLE_USR, -}; - -/** - * The inspection code maintains one of these structures per mountable - * filesystem found in the disk image. The struct (or structs) which - * have the C<role> attribute set to C<OS_ROLE_ROOT> are inspection roots, - * each corresponding to a single guest. Note that a filesystem can be - * shared between multiple guests. - */ -struct inspect_fs { - enum inspect_os_role role; - char *mountable; - enum inspect_os_type type; - enum inspect_os_distro distro; - enum inspect_os_package_format package_format; - enum inspect_os_package_management package_management; - char *product_name; - char *product_variant; - struct version version; - char *arch; - char *hostname; - char *windows_systemroot; - char *windows_software_hive; - char *windows_system_hive; - char *windows_current_control_set; - char **drive_mappings; - enum inspect_os_format format; - int is_live_disk; - int is_netinst_disk; - int is_multipart_disk; - struct inspect_fstab_entry *fstab; - size_t nr_fstab; -}; - -struct inspect_fstab_entry { - char *mountable; - char *mountpoint; -}; - struct guestfs_message_header; struct guestfs_message_error; struct guestfs_progress; @@ -854,40 +706,7 @@ extern int guestfs_int_set_backend (guestfs_h *g, const char *method); } while (0) /* inspect.c */ -extern void guestfs_int_free_inspect_info (guestfs_h *g); -extern char *guestfs_int_download_to_tmp (guestfs_h *g, struct inspect_fs *fs, const char *filename, const char *basename, uint64_t max_size); -extern struct inspect_fs *guestfs_int_search_for_root (guestfs_h *g, const char *root); -extern int guestfs_int_is_partition (guestfs_h *g, const char *partition); - -/* inspect-fs.c */ -extern int guestfs_int_is_file_nocase (guestfs_h *g, const char *); -extern int guestfs_int_is_dir_nocase (guestfs_h *g, const char *); -extern int guestfs_int_check_for_filesystem_on (guestfs_h *g, - const char *mountable); -extern int guestfs_int_parse_unsigned_int (guestfs_h *g, const char *str); -extern int guestfs_int_parse_unsigned_int_ignore_trailing (guestfs_h *g, const char *str); -extern int guestfs_int_parse_major_minor (guestfs_h *g, struct inspect_fs *fs); -extern char *guestfs_int_first_line_of_file (guestfs_h *g, const char *filename); -extern int guestfs_int_first_egrep_of_file (guestfs_h *g, const char *filename, const char *eregex, int iflag, char **ret); -extern void guestfs_int_check_package_format (guestfs_h *g, struct inspect_fs *fs); -extern void guestfs_int_check_package_management (guestfs_h *g, struct inspect_fs *fs); -extern void guestfs_int_merge_fs_inspections (guestfs_h *g, struct inspect_fs *dst, struct inspect_fs *src); - -/* inspect-fs-unix.c */ -extern int guestfs_int_check_linux_root (guestfs_h *g, struct inspect_fs *fs); -extern int guestfs_int_check_linux_usr (guestfs_h *g, struct inspect_fs *fs); -extern int guestfs_int_check_freebsd_root (guestfs_h *g, struct inspect_fs *fs); -extern int guestfs_int_check_netbsd_root (guestfs_h *g, struct inspect_fs *fs); -extern int guestfs_int_check_openbsd_root (guestfs_h *g, struct inspect_fs *fs); -extern int guestfs_int_check_hurd_root (guestfs_h *g, struct inspect_fs *fs); -extern int guestfs_int_check_minix_root (guestfs_h *g, struct inspect_fs *fs); -extern int guestfs_int_check_coreos_root (guestfs_h *g, struct inspect_fs *fs); -extern int guestfs_int_check_coreos_usr (guestfs_h *g, struct inspect_fs *fs); - -/* inspect-fs-windows.c */ -extern char *guestfs_int_case_sensitive_path_silently (guestfs_h *g, const char *); -extern char * guestfs_int_get_windows_systemroot (guestfs_h *g); -extern int guestfs_int_check_windows_root (guestfs_h *g, struct inspect_fs *fs, char *windows_systemroot); +extern char *guestfs_int_download_to_tmp (guestfs_h *g, const char *filename, const char *basename, uint64_t max_size); /* dbdump.c */ typedef int (*guestfs_int_db_dump_callback) (guestfs_h *g, const unsigned char *key, size_t keylen, const unsigned char *value, size_t valuelen, void *opaque); @@ -969,6 +788,8 @@ struct rusage; extern int guestfs_int_wait4 (guestfs_h *g, pid_t pid, int *status, struct rusage *rusage, const char *errmsg); /* version.c */ +extern int guestfs_int_parse_unsigned_int (guestfs_h *g, const char *str); +extern int guestfs_int_parse_unsigned_int_ignore_trailing (guestfs_h *g, const char *str); extern void guestfs_int_version_from_libvirt (struct version *v, int vernum); extern void guestfs_int_version_from_values (struct version *v, int maj, int min, int mic); extern int guestfs_int_version_from_x_y (guestfs_h *g, struct version *v, const char *str); diff --git a/lib/handle.c b/lib/handle.c index 91f5f755d..57fda24b1 100644 --- a/lib/handle.c +++ b/lib/handle.c @@ -369,7 +369,6 @@ guestfs_close (guestfs_h *g) guestfs_int_free_fuse (g); #endif - guestfs_int_free_inspect_info (g); guestfs_int_free_drives (g); for (hp = g->hv_params; hp; hp = hp_next) { diff --git a/lib/inspect-apps.c b/lib/inspect-apps.c index 25192340c..2a0e2beba 100644 --- a/lib/inspect-apps.c +++ b/lib/inspect-apps.c @@ -46,12 +46,12 @@ #include "structs-cleanups.h" #ifdef DB_DUMP -static struct guestfs_application2_list *list_applications_rpm (guestfs_h *g, struct inspect_fs *fs); +static struct guestfs_application2_list *list_applications_rpm (guestfs_h *g, const char *root); #endif -static struct guestfs_application2_list *list_applications_deb (guestfs_h *g, struct inspect_fs *fs); -static struct guestfs_application2_list *list_applications_pacman (guestfs_h *g, struct inspect_fs *fs); -static struct guestfs_application2_list *list_applications_apk (guestfs_h *g, struct inspect_fs *fs); -static struct guestfs_application2_list *list_applications_windows (guestfs_h *g, struct inspect_fs *fs); +static struct guestfs_application2_list *list_applications_deb (guestfs_h *g, const char *root); +static struct guestfs_application2_list *list_applications_pacman (guestfs_h *g, const char *root); +static struct guestfs_application2_list *list_applications_apk (guestfs_h *g, const char *root); +static struct guestfs_application2_list *list_applications_windows (guestfs_h *g, const char *root); static void add_application (guestfs_h *g, struct guestfs_application2_list *, const char *name, const char *display_name, int32_t epoch, const char *version, const char *release, const char *arch, const char *install_path, const char *publisher, const char *url, const char *source, const char *summary, const char *description); static void sort_applications (struct guestfs_application2_list *); @@ -109,68 +109,45 @@ struct guestfs_application2_list * guestfs_impl_inspect_list_applications2 (guestfs_h *g, const char *root) { struct guestfs_application2_list *ret = NULL; - struct inspect_fs *fs = guestfs_int_search_for_root (g, root); - if (!fs) + CLEANUP_FREE char *type = NULL; + CLEANUP_FREE char *package_format = NULL; + + type = guestfs_inspect_get_type (g, root); + if (!type) + return NULL; + package_format = guestfs_inspect_get_package_format (g, root); + if (!package_format) return NULL; - /* Presently we can only list applications for installed disks. It - * is possible in future to get lists of packages from installers. - */ - if (fs->format == OS_FORMAT_INSTALLED) { - switch (fs->type) { - case OS_TYPE_LINUX: - case OS_TYPE_HURD: - switch (fs->package_format) { - case OS_PACKAGE_FORMAT_RPM: + if (STREQ (type, "linux") || STREQ (type, "hurd")) { + if (STREQ (package_format, "rpm")) { #ifdef DB_DUMP - ret = list_applications_rpm (g, fs); - if (ret == NULL) - return NULL; + ret = list_applications_rpm (g, root); + if (ret == NULL) + return NULL; #endif - break; - - case OS_PACKAGE_FORMAT_DEB: - ret = list_applications_deb (g, fs); - if (ret == NULL) - return NULL; - break; - - case OS_PACKAGE_FORMAT_PACMAN: - ret = list_applications_pacman (g, fs); - if (ret == NULL) - return NULL; - break; - - case OS_PACKAGE_FORMAT_APK: - ret = list_applications_apk (g, fs); - if (ret == NULL) - return NULL; - break; - - case OS_PACKAGE_FORMAT_EBUILD: - case OS_PACKAGE_FORMAT_PISI: - case OS_PACKAGE_FORMAT_PKGSRC: - case OS_PACKAGE_FORMAT_XBPS: - case OS_PACKAGE_FORMAT_UNKNOWN: - ; /* nothing */ - } - break; - - case OS_TYPE_WINDOWS: - ret = list_applications_windows (g, fs); + } + else if (STREQ (package_format, "deb")) { + ret = list_applications_deb (g, root); + if (ret == NULL) + return NULL; + } + else if (STREQ (package_format, "pacman")) { + ret = list_applications_pacman (g, root); + if (ret == NULL) + return NULL; + } + else if (STREQ (package_format, "apk")) { + ret = list_applications_apk (g, root); if (ret == NULL) return NULL; - break; - - case OS_TYPE_FREEBSD: - case OS_TYPE_MINIX: - case OS_TYPE_NETBSD: - case OS_TYPE_DOS: - case OS_TYPE_OPENBSD: - case OS_TYPE_UNKNOWN: - ; /* nothing */ } } + else if (STREQ (type, "windows")) { + ret = list_applications_windows (g, root); + if (ret == NULL) + return NULL; + } if (ret == NULL) { /* Don't know how to do inspection. Not an error, return an @@ -386,20 +363,20 @@ read_package (guestfs_h *g, #pragma GCC diagnostic pop static struct guestfs_application2_list * -list_applications_rpm (guestfs_h *g, struct inspect_fs *fs) +list_applications_rpm (guestfs_h *g, const char *root) { CLEANUP_FREE char *Name = NULL, *Packages = NULL; struct rpm_names_list list = { .names = NULL, .len = 0 }; struct guestfs_application2_list *apps = NULL; struct read_package_data data; - Name = guestfs_int_download_to_tmp (g, fs, + Name = guestfs_int_download_to_tmp (g, "/var/lib/rpm/Name", "rpm_Name", MAX_PKG_DB_SIZE); if (Name == NULL) goto error; - Packages = guestfs_int_download_to_tmp (g, fs, + Packages = guestfs_int_download_to_tmp (g, "/var/lib/rpm/Packages", "rpm_Packages", MAX_PKG_DB_SIZE); if (Packages == NULL) @@ -437,7 +414,7 @@ list_applications_rpm (guestfs_h *g, struct inspect_fs *fs) #endif /* defined DB_DUMP */ static struct guestfs_application2_list * -list_applications_deb (guestfs_h *g, struct inspect_fs *fs) +list_applications_deb (guestfs_h *g, const char *root) { CLEANUP_FREE char *status = NULL; struct guestfs_application2_list *apps = NULL, *ret = NULL; @@ -452,7 +429,7 @@ list_applications_deb (guestfs_h *g, struct inspect_fs *fs) char **continuation_field = NULL; size_t continuation_field_len = 0; - status = guestfs_int_download_to_tmp (g, fs, "/var/lib/dpkg/status", "status", + status = guestfs_int_download_to_tmp (g, "/var/lib/dpkg/status", "status", MAX_PKG_DB_SIZE); if (status == NULL) return NULL; @@ -594,7 +571,7 @@ list_applications_deb (guestfs_h *g, struct inspect_fs *fs) } static struct guestfs_application2_list * -list_applications_pacman (guestfs_h *g, struct inspect_fs *fs) +list_applications_pacman (guestfs_h *g, const char *root) { CLEANUP_FREE char *desc_file = NULL, *fname = NULL, *line = NULL; CLEANUP_FREE_DIRENT_LIST struct guestfs_dirent_list *local_db = NULL; @@ -628,7 +605,7 @@ list_applications_pacman (guestfs_h *g, struct inspect_fs *fs) fname = safe_malloc (g, strlen (curr->name) + path_len + 1); sprintf (fname, "/var/lib/pacman/local/%s/desc", curr->name); free (desc_file); - desc_file = guestfs_int_download_to_tmp (g, fs, fname, curr->name, 8192); + desc_file = guestfs_int_download_to_tmp (g, fname, curr->name, 8192); /* The desc files are small (4K). If the desc file does not exist or is * larger than the 8K limit we've used, the database is probably corrupted, @@ -725,7 +702,7 @@ list_applications_pacman (guestfs_h *g, struct inspect_fs *fs) } static struct guestfs_application2_list * -list_applications_apk (guestfs_h *g, struct inspect_fs *fs) +list_applications_apk (guestfs_h *g, const char *root) { CLEANUP_FREE char *installed = NULL, *line = NULL; struct guestfs_application2_list *apps = NULL, *ret = NULL; @@ -736,7 +713,7 @@ list_applications_apk (guestfs_h *g, struct inspect_fs *fs) CLEANUP_FREE char *name = NULL, *version = NULL, *release = NULL, *arch = NULL, *url = NULL, *description = NULL; - installed = guestfs_int_download_to_tmp (g, fs, "/lib/apk/db/installed", + installed = guestfs_int_download_to_tmp (g, "/lib/apk/db/installed", "installed", MAX_PKG_DB_SIZE); if (installed == NULL) return NULL; @@ -843,14 +820,15 @@ list_applications_apk (guestfs_h *g, struct inspect_fs *fs) static void list_applications_windows_from_path (guestfs_h *g, struct guestfs_application2_list *apps, const char **path, size_t path_len); static struct guestfs_application2_list * -list_applications_windows (guestfs_h *g, struct inspect_fs *fs) +list_applications_windows (guestfs_h *g, const char *root) { struct guestfs_application2_list *ret = NULL; - - if (!fs->windows_software_hive) + CLEANUP_FREE char *software_hive + guestfs_inspect_get_windows_software_hive (g, root); + if (!software_hive) return NULL; - if (guestfs_hivex_open (g, fs->windows_software_hive, + if (guestfs_hivex_open (g, software_hive, GUESTFS_HIVEX_OPEN_VERBOSE, g->verbose, GUESTFS_HIVEX_OPEN_UNSAFE, 1, -1) == -1) diff --git a/lib/inspect-fs-unix.c b/lib/inspect-fs-unix.c deleted file mode 100644 index 9b6bfbf38..000000000 --- a/lib/inspect-fs-unix.c +++ /dev/null @@ -1,2158 +0,0 @@ -/* libguestfs - * Copyright (C) 2010-2012 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 <libintl.h> -#include <errno.h> - -#ifdef HAVE_ENDIAN_H -#include <endian.h> -#endif - -#include "c-ctype.h" -#include "ignore-value.h" -#include "hash-pjw.h" - -#include "guestfs.h" -#include "guestfs-internal.h" - -COMPILE_REGEXP (re_fedora, "Fedora release (\\d+)", 0) -COMPILE_REGEXP (re_rhel_old, "Red Hat.*release (\\d+).*Update (\\d+)", 0) -COMPILE_REGEXP (re_rhel, "Red Hat.*release (\\d+)\\.(\\d+)", 0) -COMPILE_REGEXP (re_rhel_no_minor, "Red Hat.*release (\\d+)", 0) -COMPILE_REGEXP (re_centos_old, "CentOS.*release (\\d+).*Update (\\d+)", 0) -COMPILE_REGEXP (re_centos, "CentOS.*release (\\d+)\\.(\\d+)", 0) -COMPILE_REGEXP (re_centos_no_minor, "CentOS.*release (\\d+)", 0) -COMPILE_REGEXP (re_scientific_linux_old, - "Scientific Linux.*release (\\d+).*Update (\\d+)", 0) -COMPILE_REGEXP (re_scientific_linux, - "Scientific Linux.*release (\\d+)\\.(\\d+)", 0) -COMPILE_REGEXP (re_scientific_linux_no_minor, - "Scientific Linux.*release (\\d+)", 0) -COMPILE_REGEXP (re_oracle_linux_old, - "Oracle Linux.*release (\\d+).*Update (\\d+)", 0) -COMPILE_REGEXP (re_oracle_linux, - "Oracle Linux.*release (\\d+)\\.(\\d+)", 0) -COMPILE_REGEXP (re_oracle_linux_no_minor, "Oracle Linux.*release (\\d+)", 0) -COMPILE_REGEXP (re_xdev, "^/dev/(h|s|v|xv)d([a-z]+)(\\d*)$", 0) -COMPILE_REGEXP (re_cciss, "^/dev/(cciss/c\\d+d\\d+)(?:p(\\d+))?$", 0) -COMPILE_REGEXP (re_mdN, "^(/dev/md\\d+)$", 0) -COMPILE_REGEXP (re_freebsd_mbr, - "^/dev/(ada{0,1}|vtbd)(\\d+)s(\\d+)([a-z])$", 0) -COMPILE_REGEXP (re_freebsd_gpt, "^/dev/(ada{0,1}|vtbd)(\\d+)p(\\d+)$", 0) -COMPILE_REGEXP (re_diskbyid, "^/dev/disk/by-id/.*-part(\\d+)$", 0) -COMPILE_REGEXP (re_netbsd, "^NetBSD (\\d+)\\.(\\d+)", 0) -COMPILE_REGEXP (re_opensuse, "^(openSUSE|SuSE Linux|SUSE LINUX) ", 0) -COMPILE_REGEXP (re_sles, "^SUSE (Linux|LINUX) Enterprise ", 0) -COMPILE_REGEXP (re_nld, "^Novell Linux Desktop ", 0) -COMPILE_REGEXP (re_opensuse_version, "^VERSION = (\\d+)\\.(\\d+)", 0) -COMPILE_REGEXP (re_sles_version, "^VERSION = (\\d+)", 0) -COMPILE_REGEXP (re_sles_patchlevel, "^PATCHLEVEL = (\\d+)", 0) -COMPILE_REGEXP (re_minix, "^(\\d+)\\.(\\d+)(\\.(\\d+))?", 0) -COMPILE_REGEXP (re_hurd_dev, "^/dev/(h)d(\\d+)s(\\d+)$", 0) -COMPILE_REGEXP (re_openbsd, "^OpenBSD (\\d+|\\?)\\.(\\d+|\\?)", 0) -COMPILE_REGEXP (re_openbsd_duid, "^[0-9a-f]{16}\\.[a-z]", 0) -COMPILE_REGEXP (re_openbsd_dev, "^/dev/(s|w)d([0-9])([a-z])$", 0) -COMPILE_REGEXP (re_netbsd_dev, "^/dev/(l|s)d([0-9])([a-z])$", 0) -COMPILE_REGEXP (re_altlinux, " (?:(\\d+)(?:\\.(\\d+)(?:[\\.\\d]+)?)?)\\s+\\((?:[^)]+)\\)$", 0) -COMPILE_REGEXP (re_frugalware, "Frugalware (\\d+)\\.(\\d+)", 0) -COMPILE_REGEXP (re_pldlinux, "(\\d+)\\.(\\d+) PLD Linux", 0) - -static void check_architecture (guestfs_h *g, struct inspect_fs *fs); -static int check_hostname_unix (guestfs_h *g, struct inspect_fs *fs); -static int check_hostname_redhat (guestfs_h *g, struct inspect_fs *fs); -static int check_hostname_freebsd (guestfs_h *g, struct inspect_fs *fs); -static int check_fstab (guestfs_h *g, struct inspect_fs *fs); -static void add_fstab_entry (guestfs_h *g, struct inspect_fs *fs, - const char *mountable, const char *mp); -static char *resolve_fstab_device (guestfs_h *g, const char *spec, - Hash_table *md_map, - enum inspect_os_type os_type); -static int inspect_with_augeas (guestfs_h *g, struct inspect_fs *fs, const char **configfiles, int (*f) (guestfs_h *, struct inspect_fs *)); -static void canonical_mountpoint (char *mp); - -/* Hash structure for uuid->path lookups */ -typedef struct md_uuid { - uint32_t uuid[4]; - char *path; -} md_uuid; - -static size_t uuid_hash(const void *x, size_t table_size); -static bool uuid_cmp(const void *x, const void *y); -static void md_uuid_free(void *x); - -static int parse_uuid(const char *str, uint32_t *uuid); - -/* Hash structure for path(mdadm)->path(appliance) lookup */ -typedef struct { - char *mdadm; - char *app; -} mdadm_app; - -static size_t mdadm_app_hash(const void *x, size_t table_size); -static bool mdadm_app_cmp(const void *x, const void *y); -static void mdadm_app_free(void *x); - -static ssize_t map_app_md_devices (guestfs_h *g, Hash_table **map); -static int map_md_devices(guestfs_h *g, Hash_table **map); - -/* Set fs->product_name to the first line of the release file. */ -static int -parse_release_file (guestfs_h *g, struct inspect_fs *fs, - const char *release_filename) -{ - fs->product_name = guestfs_int_first_line_of_file (g, release_filename); - if (fs->product_name == NULL) - return -1; - if (STREQ (fs->product_name, "")) { - free (fs->product_name); - fs->product_name = NULL; - error (g, _("release file %s is empty or malformed"), release_filename); - return -1; - } - return 0; -} - -/* Parse a os-release file. - * - * Only few fields are parsed, falling back to the usual detection if we - * cannot read all of them. - * - * For the format of os-release, see also: - * http://www.freedesktop.org/software/systemd/man/os-release.html - */ -static int -parse_os_release (guestfs_h *g, struct inspect_fs *fs, const char *filename) -{ - int64_t size; - CLEANUP_FREE_STRING_LIST char **lines = NULL; - size_t i; - enum inspect_os_distro distro = OS_DISTRO_UNKNOWN; - CLEANUP_FREE char *product_name = NULL; - struct version version; - guestfs_int_version_from_values (&version, -1, -1, 0); - - /* Don't trust guestfs_read_lines not to break with very large files. - * Check the file size is something reasonable first. - */ - size = guestfs_filesize (g, filename); - if (size == -1) - /* guestfs_filesize failed and has already set error in handle */ - return -1; - if (size > MAX_SMALL_FILE_SIZE) { - error (g, _("size of %s is unreasonably large (%" PRIi64 " bytes)"), - filename, size); - return -1; - } - - lines = guestfs_read_lines (g, filename); - if (lines == NULL) - return -1; - - for (i = 0; lines[i] != NULL; ++i) { - const char *line = lines[i]; - const char *value; - size_t value_len; - - if (line[0] == '#') - continue; - - value = strchr (line, '='); - if (value == NULL) - continue; - - ++value; - value_len = strlen (line) - (value - line); - if (value_len > 1 && value[0] == '"' && value[value_len-1] == '"') { - ++value; - value_len -= 2; - } - -#define VALUE_IS(a) STREQLEN(value, a, value_len) - if (STRPREFIX (line, "ID=")) { - if (VALUE_IS ("alpine")) - distro = OS_DISTRO_ALPINE_LINUX; - else if (VALUE_IS ("altlinux")) - distro = OS_DISTRO_ALTLINUX; - else if (VALUE_IS ("arch")) - distro = OS_DISTRO_ARCHLINUX; - else if (VALUE_IS ("centos")) - distro = OS_DISTRO_CENTOS; - else if (VALUE_IS ("coreos")) - distro = OS_DISTRO_COREOS; - else if (VALUE_IS ("debian")) - distro = OS_DISTRO_DEBIAN; - else if (VALUE_IS ("fedora")) - distro = OS_DISTRO_FEDORA; - else if (VALUE_IS ("frugalware")) - distro = OS_DISTRO_FRUGALWARE; - else if (VALUE_IS ("mageia")) - distro = OS_DISTRO_MAGEIA; - else if (VALUE_IS ("opensuse")) - distro = OS_DISTRO_OPENSUSE; - else if (VALUE_IS ("pld")) - distro = OS_DISTRO_PLD_LINUX; - else if (VALUE_IS ("rhel")) - distro = OS_DISTRO_RHEL; - else if (VALUE_IS ("sles") || VALUE_IS ("sled")) - distro = OS_DISTRO_SLES; - else if (VALUE_IS ("ubuntu")) - distro = OS_DISTRO_UBUNTU; - else if (VALUE_IS ("void")) - distro = OS_DISTRO_VOID_LINUX; - } else if (STRPREFIX (line, "PRETTY_NAME=")) { - free (product_name); - product_name = safe_strndup (g, value, value_len); - } else if (STRPREFIX (line, "VERSION_ID=")) { - CLEANUP_FREE char *buf - safe_asprintf (g, "%.*s", (int) value_len, value); - if (guestfs_int_version_from_x_y_or_x (g, &version, buf) == -1) - return -1; - } -#undef VALUE_IS - } - - /* If we haven't got all the fields, exit right away. */ - if (distro == OS_DISTRO_UNKNOWN || product_name == NULL) - return 0; - - if (version.v_major == -1 || version.v_minor == -1) { - /* Void Linux has no VERSION_ID (yet), but since it's a rolling - * distro and has no other version/release-like file. */ - if (distro == OS_DISTRO_VOID_LINUX) - version_init_null (&version); - else - return 0; - } - - /* Apparently, os-release in Debian and CentOS does not provide the full - * version number in VERSION_ID, but just the "major" part of it. - * Hence, if version.v_minor is 0, act as there was no information in - * os-release, which will continue the inspection using the release files - * as done previously. - */ - if ((distro == OS_DISTRO_DEBIAN || distro == OS_DISTRO_CENTOS) && - version.v_minor == 0) - return 0; - - /* We got everything, so set the fields and report the inspection - * was successful. - */ - fs->distro = distro; - fs->product_name = product_name; - product_name = NULL; - fs->version = version; - - return 1; -} - -/* Ubuntu has /etc/lsb-release containing: - * DISTRIB_ID=Ubuntu # Distro - * DISTRIB_RELEASE=10.04 # Version - * DISTRIB_CODENAME=lucid - * DISTRIB_DESCRIPTION="Ubuntu 10.04.1 LTS" # Product name - * - * [Ubuntu-derived ...] Linux Mint was found to have this: - * DISTRIB_ID=LinuxMint - * DISTRIB_RELEASE=10 - * DISTRIB_CODENAME=julia - * DISTRIB_DESCRIPTION="Linux Mint 10 Julia" - * Linux Mint also has /etc/linuxmint/info with more information, - * but we can use the LSB file. - * - * Mandriva has: - * LSB_VERSION=lsb-4.0-amd64:lsb-4.0-noarch - * DISTRIB_ID=MandrivaLinux - * DISTRIB_RELEASE=2010.1 - * DISTRIB_CODENAME=Henry_Farman - * DISTRIB_DESCRIPTION="Mandriva Linux 2010.1" - * Mandriva also has a normal release file called /etc/mandriva-release. - * - * CoreOS has a /etc/lsb-release link to /usr/share/coreos/lsb-release containing: - * DISTRIB_ID=CoreOS - * DISTRIB_RELEASE=647.0.0 - * DISTRIB_CODENAME="Red Dog" - * DISTRIB_DESCRIPTION="CoreOS 647.0.0" - */ -static int -parse_lsb_release (guestfs_h *g, struct inspect_fs *fs, const char *filename) -{ - int64_t size; - CLEANUP_FREE_STRING_LIST char **lines = NULL; - size_t i; - int r = 0; - - /* Don't trust guestfs_head_n not to break with very large files. - * Check the file size is something reasonable first. - */ - size = guestfs_filesize (g, filename); - if (size == -1) - /* guestfs_filesize failed and has already set error in handle */ - return -1; - if (size > MAX_SMALL_FILE_SIZE) { - error (g, _("size of %s is unreasonably large (%" PRIi64 " bytes)"), - filename, size); - return -1; - } - - lines = guestfs_head_n (g, 10, filename); - if (lines == NULL) - return -1; - - for (i = 0; lines[i] != NULL; ++i) { - if (fs->distro == 0 && - STREQ (lines[i], "DISTRIB_ID=Ubuntu")) { - fs->distro = OS_DISTRO_UBUNTU; - r = 1; - } - else if (fs->distro == 0 && - STREQ (lines[i], "DISTRIB_ID=LinuxMint")) { - fs->distro = OS_DISTRO_LINUX_MINT; - r = 1; - } - else if (fs->distro == 0 && - STREQ (lines[i], "DISTRIB_ID=MandrivaLinux")) { - fs->distro = OS_DISTRO_MANDRIVA; - r = 1; - } - else if (fs->distro == 0 && - STREQ (lines[i], "DISTRIB_ID=\"Mageia\"")) { - fs->distro = OS_DISTRO_MAGEIA; - r = 1; - } - else if (fs->distro == 0 && - STREQ (lines[i], "DISTRIB_ID=CoreOS")) { - fs->distro = OS_DISTRO_COREOS; - r = 1; - } - else if (STRPREFIX (lines[i], "DISTRIB_RELEASE=")) { - if (guestfs_int_version_from_x_y_or_x (g, &fs->version, &lines[i][16]) == -1) - return -1; - } - else if (fs->product_name == NULL && - (STRPREFIX (lines[i], "DISTRIB_DESCRIPTION=\"") || - STRPREFIX (lines[i], "DISTRIB_DESCRIPTION='"))) { - const size_t len = strlen (lines[i]) - 21 - 1; - fs->product_name = safe_strndup (g, &lines[i][21], len); - r = 1; - } - else if (fs->product_name == NULL && - STRPREFIX (lines[i], "DISTRIB_DESCRIPTION=")) { - const size_t len = strlen (lines[i]) - 20; - fs->product_name = safe_strndup (g, &lines[i][20], len); - r = 1; - } - } - - /* The unnecessary construct in the next line is required to - * workaround -Wstrict-overflow warning in gcc 4.5.1. - */ - return r ? 1 : 0; -} - -static int -parse_suse_release (guestfs_h *g, struct inspect_fs *fs, const char *filename) -{ - int64_t size; - CLEANUP_FREE_STRING_LIST char **lines = NULL; - int r = -1; - - /* Don't trust guestfs_head_n not to break with very large files. - * Check the file size is something reasonable first. - */ - size = guestfs_filesize (g, filename); - if (size == -1) - /* guestfs_filesize failed and has already set error in handle */ - return -1; - if (size > MAX_SMALL_FILE_SIZE) { - error (g, _("size of %s is unreasonably large (%" PRIi64 " bytes)"), - filename, size); - return -1; - } - - lines = guestfs_head_n (g, 10, filename); - if (lines == NULL) - return -1; - - if (lines[0] == NULL) - goto out; - - /* First line is dist release name */ - fs->product_name = safe_strdup (g, lines[0]); - - /* Match SLES first because openSuSE regex overlaps some SLES release strings */ - if (match (g, fs->product_name, re_sles) || match (g, fs->product_name, re_nld)) { - char *major, *minor; - - fs->distro = OS_DISTRO_SLES; - - /* Second line contains version string */ - if (lines[1] == NULL) - goto out; - major = match1 (g, lines[1], re_sles_version); - if (major == NULL) - goto out; - fs->version.v_major = guestfs_int_parse_unsigned_int (g, major); - free (major); - if (fs->version.v_major == -1) - goto out; - - /* Third line contains service pack string */ - if (lines[2] == NULL) - goto out; - minor = match1 (g, lines[2], re_sles_patchlevel); - if (minor == NULL) - goto out; - fs->version.v_minor = guestfs_int_parse_unsigned_int (g, minor); - free (minor); - if (fs->version.v_minor == -1) - goto out; - } - else if (match (g, fs->product_name, re_opensuse)) { - fs->distro = OS_DISTRO_OPENSUSE; - - /* Second line contains version string */ - if (lines[1] == NULL) - goto out; - if (guestfs_int_version_from_x_y_re (g, &fs->version, lines[1], - re_opensuse_version) == -1) - goto out; - } - - r = 0; - - out: - return r; -} - -/* The currently mounted device is known to be a Linux root. Try to - * determine from this the distro, version, etc. Also parse - * /etc/fstab to determine the arrangement of mountpoints and - * associated devices. - */ -int -guestfs_int_check_linux_root (guestfs_h *g, struct inspect_fs *fs) -{ - int r; - char *major, *minor; - - fs->type = OS_TYPE_LINUX; - - if (guestfs_is_file_opts (g, "/etc/os-release", - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) > 0) { - r = parse_os_release (g, fs, "/etc/os-release"); - if (r == -1) /* error */ - return -1; - if (r == 1) /* ok - detected the release from this file */ - goto skip_release_checks; - } - - if (guestfs_is_file_opts (g, "/etc/lsb-release", - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) > 0) { - r = parse_lsb_release (g, fs, "/etc/lsb-release"); - if (r == -1) /* error */ - return -1; - if (r == 1) /* ok - detected the release from this file */ - goto skip_release_checks; - } - - /* RHEL-based distros include a "/etc/redhat-release" file, hence their - * checks need to be performed before the Red-Hat one. - */ - if (guestfs_is_file_opts (g, "/etc/oracle-release", - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) > 0) { - - fs->distro = OS_DISTRO_ORACLE_LINUX; - - if (parse_release_file (g, fs, "/etc/oracle-release") == -1) - return -1; - - if (match2 (g, fs->product_name, re_oracle_linux_old, &major, &minor) || - match2 (g, fs->product_name, re_oracle_linux, &major, &minor)) { - fs->version.v_major = guestfs_int_parse_unsigned_int (g, major); - free (major); - if (fs->version.v_major == -1) { - free (minor); - return -1; - } - fs->version.v_minor = guestfs_int_parse_unsigned_int (g, minor); - free (minor); - if (fs->version.v_minor == -1) - return -1; - } else if ((major = match1 (g, fs->product_name, re_oracle_linux_no_minor)) != NULL) { - fs->version.v_major = guestfs_int_parse_unsigned_int (g, major); - free (major); - if (fs->version.v_major == -1) - return -1; - fs->version.v_minor = 0; - } - } - else if (guestfs_is_file_opts (g, "/etc/centos-release", - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) > 0) { - fs->distro = OS_DISTRO_CENTOS; - - if (parse_release_file (g, fs, "/etc/centos-release") == -1) - return -1; - - if (match2 (g, fs->product_name, re_centos_old, &major, &minor) || - match2 (g, fs->product_name, re_centos, &major, &minor)) { - fs->version.v_major = guestfs_int_parse_unsigned_int (g, major); - free (major); - if (fs->version.v_major == -1) { - free (minor); - return -1; - } - fs->version.v_minor = guestfs_int_parse_unsigned_int (g, minor); - free (minor); - if (fs->version.v_minor == -1) - return -1; - } - else if ((major = match1 (g, fs->product_name, re_centos_no_minor)) != NULL) { - fs->version.v_major = guestfs_int_parse_unsigned_int (g, major); - free (major); - if (fs->version.v_major == -1) - return -1; - fs->version.v_minor = 0; - } - } - else if (guestfs_is_file_opts (g, "/etc/altlinux-release", - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) > 0) { - fs->distro = OS_DISTRO_ALTLINUX; - - if (parse_release_file (g, fs, "/etc/altlinux-release") == -1) - return -1; - - if (guestfs_int_version_from_x_y_re (g, &fs->version, fs->product_name, - re_altlinux) == -1) - return -1; - } - else if (guestfs_is_file_opts (g, "/etc/redhat-release", - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) > 0) { - fs->distro = OS_DISTRO_REDHAT_BASED; /* Something generic Red Hat-like. */ - - if (parse_release_file (g, fs, "/etc/redhat-release") == -1) - return -1; - - if ((major = match1 (g, fs->product_name, re_fedora)) != NULL) { - fs->distro = OS_DISTRO_FEDORA; - fs->version.v_major = guestfs_int_parse_unsigned_int (g, major); - free (major); - if (fs->version.v_major == -1) - return -1; - } - else if (match2 (g, fs->product_name, re_rhel_old, &major, &minor) || - match2 (g, fs->product_name, re_rhel, &major, &minor)) { - fs->distro = OS_DISTRO_RHEL; - fs->version.v_major = guestfs_int_parse_unsigned_int (g, major); - free (major); - if (fs->version.v_major == -1) { - free (minor); - return -1; - } - fs->version.v_minor = guestfs_int_parse_unsigned_int (g, minor); - free (minor); - if (fs->version.v_minor == -1) - return -1; - } - else if ((major = match1 (g, fs->product_name, re_rhel_no_minor)) != NULL) { - fs->distro = OS_DISTRO_RHEL; - fs->version.v_major = guestfs_int_parse_unsigned_int (g, major); - free (major); - if (fs->version.v_major == -1) - return -1; - fs->version.v_minor = 0; - } - else if (match2 (g, fs->product_name, re_centos_old, &major, &minor) || - match2 (g, fs->product_name, re_centos, &major, &minor)) { - fs->distro = OS_DISTRO_CENTOS; - fs->version.v_major = guestfs_int_parse_unsigned_int (g, major); - free (major); - if (fs->version.v_major == -1) { - free (minor); - return -1; - } - fs->version.v_minor = guestfs_int_parse_unsigned_int (g, minor); - free (minor); - if (fs->version.v_minor == -1) - return -1; - } - else if ((major = match1 (g, fs->product_name, re_centos_no_minor)) != NULL) { - fs->distro = OS_DISTRO_CENTOS; - fs->version.v_major = guestfs_int_parse_unsigned_int (g, major); - free (major); - if (fs->version.v_major == -1) - return -1; - fs->version.v_minor = 0; - } - else if (match2 (g, fs->product_name, re_scientific_linux_old, &major, &minor) || - match2 (g, fs->product_name, re_scientific_linux, &major, &minor)) { - fs->distro = OS_DISTRO_SCIENTIFIC_LINUX; - fs->version.v_major = guestfs_int_parse_unsigned_int (g, major); - free (major); - if (fs->version.v_major == -1) { - free (minor); - return -1; - } - fs->version.v_minor = guestfs_int_parse_unsigned_int (g, minor); - free (minor); - if (fs->version.v_minor == -1) - return -1; - } - else if ((major = match1 (g, fs->product_name, re_scientific_linux_no_minor)) != NULL) { - fs->distro = OS_DISTRO_SCIENTIFIC_LINUX; - fs->version.v_major = guestfs_int_parse_unsigned_int (g, major); - free (major); - if (fs->version.v_major == -1) - return -1; - fs->version.v_minor = 0; - } - } - else if (guestfs_is_file_opts (g, "/etc/debian_version", - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) > 0) { - fs->distro = OS_DISTRO_DEBIAN; - - if (parse_release_file (g, fs, "/etc/debian_version") == -1) - return -1; - - if (guestfs_int_parse_major_minor (g, fs) == -1) - return -1; - } - else if (guestfs_is_file_opts (g, "/etc/pardus-release", - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) > 0) { - fs->distro = OS_DISTRO_PARDUS; - - if (parse_release_file (g, fs, "/etc/pardus-release") == -1) - return -1; - - if (guestfs_int_parse_major_minor (g, fs) == -1) - return -1; - } - else if (guestfs_is_file_opts (g, "/etc/arch-release", - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) > 0) { - fs->distro = OS_DISTRO_ARCHLINUX; - - /* /etc/arch-release file is empty and I can't see a way to - * determine the actual release or product string. - */ - } - else if (guestfs_is_file_opts (g, "/etc/gentoo-release", - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) > 0) { - fs->distro = OS_DISTRO_GENTOO; - - if (parse_release_file (g, fs, "/etc/gentoo-release") == -1) - return -1; - - if (guestfs_int_parse_major_minor (g, fs) == -1) - return -1; - } - else if (guestfs_is_file_opts (g, "/etc/meego-release", - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) > 0) { - fs->distro = OS_DISTRO_MEEGO; - - if (parse_release_file (g, fs, "/etc/meego-release") == -1) - return -1; - - if (guestfs_int_parse_major_minor (g, fs) == -1) - return -1; - } - else if (guestfs_is_file_opts (g, "/etc/slackware-version", - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) > 0) { - fs->distro = OS_DISTRO_SLACKWARE; - - if (parse_release_file (g, fs, "/etc/slackware-version") == -1) - return -1; - - if (guestfs_int_parse_major_minor (g, fs) == -1) - return -1; - } - else if (guestfs_is_file_opts (g, "/etc/ttylinux-target", - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) > 0) { - fs->distro = OS_DISTRO_TTYLINUX; - - if (parse_release_file (g, fs, "/etc/ttylinux-target") == -1) - return -1; - - if (guestfs_int_parse_major_minor (g, fs) == -1) - return -1; - } - else if (guestfs_is_file_opts (g, "/etc/SuSE-release", - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) > 0) { - fs->distro = OS_DISTRO_SUSE_BASED; - - if (parse_suse_release (g, fs, "/etc/SuSE-release") == -1) - return -1; - - } - /* CirrOS versions providing a own version file. - */ - else if (guestfs_is_file_opts (g, "/etc/cirros/version", - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) > 0) { - fs->distro = OS_DISTRO_CIRROS; - - if (parse_release_file (g, fs, "/etc/cirros/version") == -1) - return -1; - - if (guestfs_int_parse_major_minor (g, fs) == -1) - return -1; - } - /* Buildroot (http://buildroot.net) is an embedded Linux distro - * toolkit. It is used by specific distros such as Cirros. - */ - else if (guestfs_is_file_opts (g, "/etc/br-version", - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) > 0) { - if (guestfs_is_file_opts (g, "/usr/share/cirros/logo", - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) > 0) - fs->distro = OS_DISTRO_CIRROS; - else - fs->distro = OS_DISTRO_BUILDROOT; - - /* /etc/br-version has the format YYYY.MM[-git/hg/svn release] */ - if (parse_release_file (g, fs, "/etc/br-version") == -1) - return -1; - - if (guestfs_int_parse_major_minor (g, fs) == -1) - return -1; - } - else if (guestfs_is_file_opts (g, "/etc/alpine-release", - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) > 0) { - fs->distro = OS_DISTRO_ALPINE_LINUX; - - if (parse_release_file (g, fs, "/etc/alpine-release") == -1) - return -1; - - if (guestfs_int_parse_major_minor (g, fs) == -1) - return -1; - } - else if (guestfs_is_file_opts (g, "/etc/frugalware-release", - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) > 0) { - fs->distro = OS_DISTRO_FRUGALWARE; - - if (parse_release_file (g, fs, "/etc/frugalware-release") == -1) - return -1; - - if (guestfs_int_version_from_x_y_re (g, &fs->version, fs->product_name, - re_frugalware) == -1) - return -1; - } - else if (guestfs_is_file_opts (g, "/etc/pld-release", - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) > 0) { - fs->distro = OS_DISTRO_PLD_LINUX; - - if (parse_release_file (g, fs, "/etc/pld-release") == -1) - return -1; - - if (guestfs_int_version_from_x_y_re (g, &fs->version, fs->product_name, - re_pldlinux) == -1) - return -1; - } - - skip_release_checks:; - - /* Determine the architecture. */ - check_architecture (g, fs); - - /* We already know /etc/fstab exists because it's part of the test - * for Linux root above. We must now parse this file to determine - * which filesystems are used by the operating system and how they - * are mounted. - */ - const char *configfiles[] = { "/etc/fstab", "/etc/mdadm.conf", NULL }; - if (inspect_with_augeas (g, fs, configfiles, check_fstab) == -1) - return -1; - - /* Determine hostname. */ - if (check_hostname_unix (g, fs) == -1) - return -1; - - return 0; -} - -/* The currently mounted device looks like a Linux /usr. */ -int -guestfs_int_check_linux_usr (guestfs_h *g, struct inspect_fs *fs) -{ - int r; - - fs->type = OS_TYPE_LINUX; - fs->role = OS_ROLE_USR; - - if (guestfs_is_file_opts (g, "/lib/os-release", - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) > 0) { - r = parse_os_release (g, fs, "/lib/os-release"); - if (r == -1) /* error */ - return -1; - if (r == 1) /* ok - detected the release from this file */ - goto skip_release_checks; - } - - skip_release_checks:; - - /* Determine the architecture. */ - check_architecture (g, fs); - - return 0; -} - -/* The currently mounted device is known to be a FreeBSD root. */ -int -guestfs_int_check_freebsd_root (guestfs_h *g, struct inspect_fs *fs) -{ - fs->type = OS_TYPE_FREEBSD; - fs->distro = OS_DISTRO_FREEBSD; - - /* FreeBSD has no authoritative version file. The version number is - * in /etc/motd, which the system administrator might edit, but - * we'll use that anyway. - */ - - if (guestfs_is_file_opts (g, "/etc/motd", - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) > 0) { - if (parse_release_file (g, fs, "/etc/motd") == -1) - return -1; - - if (guestfs_int_parse_major_minor (g, fs) == -1) - return -1; - } - - /* Determine the architecture. */ - check_architecture (g, fs); - - /* We already know /etc/fstab exists because it's part of the test above. */ - const char *configfiles[] = { "/etc/fstab", NULL }; - if (inspect_with_augeas (g, fs, configfiles, check_fstab) == -1) - return -1; - - /* Determine hostname. */ - if (check_hostname_unix (g, fs) == -1) - return -1; - - return 0; -} - -/* The currently mounted device is maybe to be a *BSD root. */ -int -guestfs_int_check_netbsd_root (guestfs_h *g, struct inspect_fs *fs) -{ - - if (guestfs_is_file_opts (g, "/etc/release", - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) > 0) { - int result; - if (parse_release_file (g, fs, "/etc/release") == -1) - return -1; - - result = guestfs_int_version_from_x_y_re (g, &fs->version, - fs->product_name, re_netbsd); - if (result == -1) { - return -1; - } else if (result == 1) { - fs->type = OS_TYPE_NETBSD; - fs->distro = OS_DISTRO_NETBSD; - } - } else { - return -1; - } - - /* Determine the architecture. */ - check_architecture (g, fs); - - /* We already know /etc/fstab exists because it's part of the test above. */ - const char *configfiles[] = { "/etc/fstab", NULL }; - if (inspect_with_augeas (g, fs, configfiles, check_fstab) == -1) - return -1; - - /* Determine hostname. */ - if (check_hostname_unix (g, fs) == -1) - return -1; - - return 0; -} - -/* The currently mounted device may be an OpenBSD root. */ -int -guestfs_int_check_openbsd_root (guestfs_h *g, struct inspect_fs *fs) -{ - if (guestfs_is_file_opts (g, "/etc/motd", - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) > 0) { - CLEANUP_FREE char *major = NULL, *minor = NULL; - - /* The first line of this file gets automatically updated at boot. */ - if (parse_release_file (g, fs, "/etc/motd") == -1) - return -1; - - if (match2 (g, fs->product_name, re_openbsd, &major, &minor)) { - fs->type = OS_TYPE_OPENBSD; - fs->distro = OS_DISTRO_OPENBSD; - - /* Before the first boot, the first line will look like this: - * - * OpenBSD ?.? (UNKNOWN) - */ - if ((fs->product_name[8] != '?') && (fs->product_name[10] != '?')) { - fs->version.v_major = guestfs_int_parse_unsigned_int (g, major); - if (fs->version.v_major == -1) - return -1; - - fs->version.v_minor = guestfs_int_parse_unsigned_int (g, minor); - if (fs->version.v_minor == -1) - return -1; - } - } - } else { - return -1; - } - - /* Determine the architecture. */ - check_architecture (g, fs); - - /* We already know /etc/fstab exists because it's part of the test above. */ - const char *configfiles[] = { "/etc/fstab", NULL }; - if (inspect_with_augeas (g, fs, configfiles, check_fstab) == -1) - return -1; - - /* Determine hostname. */ - if (check_hostname_unix (g, fs) == -1) - return -1; - - return 0; -} - -/* The currently mounted device may be a Hurd root. Hurd has distros - * just like Linux. - */ -int -guestfs_int_check_hurd_root (guestfs_h *g, struct inspect_fs *fs) -{ - fs->type = OS_TYPE_HURD; - - if (guestfs_is_file_opts (g, "/etc/debian_version", - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) > 0) { - fs->distro = OS_DISTRO_DEBIAN; - - if (parse_release_file (g, fs, "/etc/debian_version") == -1) - return -1; - - if (guestfs_int_parse_major_minor (g, fs) == -1) - return -1; - } - - /* Arch Hurd also exists, but inconveniently it doesn't have - * the normal /etc/arch-release file. XXX - */ - - /* Determine the architecture. */ - check_architecture (g, fs); - - if (guestfs_is_file (g, "/etc/fstab") > 0) { - const char *configfiles[] = { "/etc/fstab", NULL }; - if (inspect_with_augeas (g, fs, configfiles, check_fstab) == -1) - return -1; - } - - /* Determine hostname. */ - if (check_hostname_unix (g, fs) == -1) - return -1; - - return 0; -} - -/* The currently mounted device is maybe to be a Minix root. */ -int -guestfs_int_check_minix_root (guestfs_h *g, struct inspect_fs *fs) -{ - fs->type = OS_TYPE_MINIX; - - if (guestfs_is_file_opts (g, "/etc/version", - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) > 0) { - if (parse_release_file (g, fs, "/etc/version") == -1) - return -1; - - if (guestfs_int_version_from_x_y_re (g, &fs->version, fs->product_name, - re_minix) == -1) - return -1; - } else { - return -1; - } - - /* Determine the architecture. */ - check_architecture (g, fs); - - /* TODO: enable fstab inspection once resolve_fstab_device implements - * the proper mapping from the Minix device names to the appliance names - */ - - /* Determine hostname. */ - if (check_hostname_unix (g, fs) == -1) - return -1; - - return 0; -} - -/* The currently mounted device is a CoreOS root. From this partition we can - * only determine the hostname. All immutable OS files are under a separate - * read-only /usr partition. - */ -int -guestfs_int_check_coreos_root (guestfs_h *g, struct inspect_fs *fs) -{ - fs->type = OS_TYPE_LINUX; - fs->distro = OS_DISTRO_COREOS; - - /* Determine hostname. */ - if (check_hostname_unix (g, fs) == -1) - return -1; - - /* CoreOS does not contain /etc/fstab to determine the mount points. - * Associate this filesystem with the "/" mount point. - */ - add_fstab_entry (g, fs, fs->mountable, "/"); - - return 0; -} - -/* The currently mounted device looks like a CoreOS /usr. In CoreOS - * the read-only /usr contains the OS version. The /etc/os-release is a - * link to /usr/share/coreos/os-release. - */ -int -guestfs_int_check_coreos_usr (guestfs_h *g, struct inspect_fs *fs) -{ - int r; - - fs->type = OS_TYPE_LINUX; - fs->distro = OS_DISTRO_COREOS; - fs->role = OS_ROLE_USR; - - if (guestfs_is_file_opts (g, "/lib/os-release", - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) > 0) { - r = parse_os_release (g, fs, "/lib/os-release"); - if (r == -1) /* error */ - return -1; - if (r == 1) /* ok - detected the release from this file */ - goto skip_release_checks; - } - - if (guestfs_is_file_opts (g, "/share/coreos/lsb-release", - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) > 0) { - r = parse_lsb_release (g, fs, "/share/coreos/lsb-release"); - if (r == -1) /* error */ - return -1; - } - - skip_release_checks:; - - /* Determine the architecture. */ - check_architecture (g, fs); - - /* CoreOS does not contain /etc/fstab to determine the mount points. - * Associate this filesystem with the "/usr" mount point. - */ - add_fstab_entry (g, fs, fs->mountable, "/usr"); - - return 0; -} - -static void -check_architecture (guestfs_h *g, struct inspect_fs *fs) -{ - const char *binaries[] - { "/bin/bash", "/bin/ls", "/bin/echo", "/bin/rm", "/bin/sh" }; - size_t i; - char *arch = NULL; - - for (i = 0; i < sizeof binaries / sizeof binaries[0]; ++i) { - /* Allow symlinks when checking the binaries:,so in case they are - * relative ones (which can be resolved within the same partition), - * then we can check the architecture of their target. - */ - if (guestfs_is_file_opts (g, binaries[i], - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) > 0) { - CLEANUP_FREE char *resolved = NULL; - - /* Ignore errors from realpath and file_architecture calls. */ - guestfs_push_error_handler (g, NULL, NULL); - resolved = guestfs_realpath (g, binaries[i]); - /* If is_file above succeeded realpath should too, but better - * be safe than sorry. - */ - if (resolved) - arch = guestfs_file_architecture (g, resolved); - guestfs_pop_error_handler (g); - - if (arch) { - /* String will be owned by handle, freed by - * guestfs_int_free_inspect_info. - */ - fs->arch = arch; - break; - } - } - } -} - -/* Try several methods to determine the hostname from a Linux or - * FreeBSD guest. Note that type and distro have been set, so we can - * use that information to direct the search. - */ -static int -check_hostname_unix (guestfs_h *g, struct inspect_fs *fs) -{ - switch (fs->type) { - case OS_TYPE_LINUX: - case OS_TYPE_HURD: - /* Red Hat-derived would be in /etc/sysconfig/network or - * /etc/hostname (RHEL 7+, F18+). Debian-derived in the file - * /etc/hostname. Very old Debian and SUSE use /etc/HOSTNAME. - * It's best to just look for each of these files in turn, rather - * than try anything clever based on distro. - */ - if (guestfs_is_file (g, "/etc/HOSTNAME")) { - fs->hostname = guestfs_int_first_line_of_file (g, "/etc/HOSTNAME"); - if (fs->hostname == NULL) - return -1; - if (STREQ (fs->hostname, "")) { - free (fs->hostname); - fs->hostname = NULL; - } - } - - if (!fs->hostname && guestfs_is_file (g, "/etc/hostname")) { - fs->hostname = guestfs_int_first_line_of_file (g, "/etc/hostname"); - if (fs->hostname == NULL) - return -1; - if (STREQ (fs->hostname, "")) { - free (fs->hostname); - fs->hostname = NULL; - } - } - - if (!fs->hostname && guestfs_is_file (g, "/etc/sysconfig/network")) { - const char *configfiles[] = { "/etc/sysconfig/network", NULL }; - if (inspect_with_augeas (g, fs, configfiles, - check_hostname_redhat) == -1) - return -1; - } - break; - - case OS_TYPE_FREEBSD: - case OS_TYPE_NETBSD: - /* /etc/rc.conf contains the hostname, but there is no Augeas lens - * for this file. - */ - if (guestfs_is_file (g, "/etc/rc.conf")) { - if (check_hostname_freebsd (g, fs) == -1) - return -1; - } - break; - - case OS_TYPE_OPENBSD: - if (guestfs_is_file (g, "/etc/myname")) { - fs->hostname = guestfs_int_first_line_of_file (g, "/etc/myname"); - if (fs->hostname == NULL) - return -1; - if (STREQ (fs->hostname, "")) { - free (fs->hostname); - fs->hostname = NULL; - } - } - break; - - case OS_TYPE_MINIX: - if (guestfs_is_file (g, "/etc/hostname.file")) { - fs->hostname = guestfs_int_first_line_of_file (g, "/etc/hostname.file"); - if (fs->hostname == NULL) - return -1; - if (STREQ (fs->hostname, "")) { - free (fs->hostname); - fs->hostname = NULL; - } - } - break; - - case OS_TYPE_WINDOWS: /* not here, see check_windows_system_registry */ - case OS_TYPE_DOS: - case OS_TYPE_UNKNOWN: - /* nothing */; - } - - return 0; -} - -/* Parse the hostname from /etc/sysconfig/network. This must be - * called from the inspect_with_augeas wrapper. Note that F18+ and - * RHEL7+ use /etc/hostname just like Debian. - */ -static int -check_hostname_redhat (guestfs_h *g, struct inspect_fs *fs) -{ - char *hostname; - - /* Errors here are not fatal (RHBZ#726739), since it could be - * just missing HOSTNAME field in the file. - */ - guestfs_push_error_handler (g, NULL, NULL); - hostname = guestfs_aug_get (g, "/files/etc/sysconfig/network/HOSTNAME"); - guestfs_pop_error_handler (g); - - /* This is freed by guestfs_int_free_inspect_info. Note that hostname - * could be NULL because we ignored errors above. - */ - fs->hostname = hostname; - return 0; -} - -/* Parse the hostname from /etc/rc.conf. On FreeBSD this file - * contains comments, blank lines and: - * hostname="freebsd8.example.com" - * ifconfig_re0="DHCP" - * keymap="uk.iso" - * sshd_enable="YES" - */ -static int -check_hostname_freebsd (guestfs_h *g, struct inspect_fs *fs) -{ - const char *filename = "/etc/rc.conf"; - int64_t size; - CLEANUP_FREE_STRING_LIST char **lines = NULL; - size_t i; - - /* Don't trust guestfs_read_lines not to break with very large files. - * Check the file size is something reasonable first. - */ - size = guestfs_filesize (g, filename); - if (size == -1) - /* guestfs_filesize failed and has already set error in handle */ - return -1; - if (size > MAX_SMALL_FILE_SIZE) { - error (g, _("size of %s is unreasonably large (%" PRIi64 " bytes)"), - filename, size); - return -1; - } - - lines = guestfs_read_lines (g, filename); - if (lines == NULL) - return -1; - - for (i = 0; lines[i] != NULL; ++i) { - if (STRPREFIX (lines[i], "hostname=\"") || - STRPREFIX (lines[i], "hostname='")) { - const size_t len = strlen (lines[i]) - 10 - 1; - fs->hostname = safe_strndup (g, &lines[i][10], len); - break; - } else if (STRPREFIX (lines[i], "hostname=")) { - const size_t len = strlen (lines[i]) - 9; - fs->hostname = safe_strndup (g, &lines[i][9], len); - break; - } - } - - return 0; -} - -static int -check_fstab (guestfs_h *g, struct inspect_fs *fs) -{ - CLEANUP_FREE_STRING_LIST char **entries = NULL; - char **entry; - char augpath[256]; - CLEANUP_HASH_FREE Hash_table *md_map = NULL; - bool is_bsd = (fs->type == OS_TYPE_FREEBSD || - fs->type == OS_TYPE_NETBSD || - fs->type == OS_TYPE_OPENBSD); - - /* Generate a map of MD device paths listed in /etc/mdadm.conf to MD device - * paths in the guestfs appliance */ - if (map_md_devices (g, &md_map) == -1) return -1; - - entries = guestfs_aug_match (g, "/files/etc/fstab/*[label() != '#comment']"); - if (entries == NULL) - return -1; - - for (entry = entries; *entry != NULL; entry++) { - CLEANUP_FREE char *spec = NULL; - CLEANUP_FREE char *mp = NULL; - CLEANUP_FREE char *mountable = NULL; - CLEANUP_FREE char *vfstype = NULL; - - snprintf (augpath, sizeof augpath, "%s/spec", *entry); - spec = guestfs_aug_get (g, augpath); - if (spec == NULL) - return -1; - - /* Ignore /dev/fd (floppy disks) (RHBZ#642929) and CD-ROM drives. - * - * /dev/iso9660/FREEBSD_INSTALL can be found in FreeBSDs installation - * discs. - */ - if ((STRPREFIX (spec, "/dev/fd") && c_isdigit (spec[7])) || - (STRPREFIX (spec, "/dev/cd") && c_isdigit (spec[7])) || - STREQ (spec, "/dev/floppy") || - STREQ (spec, "/dev/cdrom") || - STRPREFIX (spec, "/dev/iso9660/")) - continue; - - snprintf (augpath, sizeof augpath, "%s/file", *entry); - mp = guestfs_aug_get (g, augpath); - if (mp == NULL) - return -1; - - /* Canonicalize the path, so "///usr//local//" -> "/usr/local" */ - canonical_mountpoint (mp); - - /* Ignore certain mountpoints. */ - if (STRPREFIX (mp, "/dev/") || - STREQ (mp, "/dev") || - STRPREFIX (mp, "/media/") || - STRPREFIX (mp, "/proc/") || - STREQ (mp, "/proc") || - STRPREFIX (mp, "/selinux/") || - STREQ (mp, "/selinux") || - STRPREFIX (mp, "/sys/") || - STREQ (mp, "/sys")) - continue; - - /* Resolve UUID= and LABEL= to the actual device. */ - if (STRPREFIX (spec, "UUID=")) { - CLEANUP_FREE char *s = guestfs_int_shell_unquote (&spec[5]); - if (s == NULL) { perrorf (g, "guestfs_int_shell_unquote"); return -1; } - mountable = guestfs_findfs_uuid (g, s); - } - else if (STRPREFIX (spec, "LABEL=")) { - CLEANUP_FREE char *s = guestfs_int_shell_unquote (&spec[6]); - if (s == NULL) { perrorf (g, "guestfs_int_shell_unquote"); return -1; } - mountable = guestfs_findfs_label (g, s); - } - /* Ignore "/.swap" (Pardus) and pseudo-devices like "tmpfs". */ - else if (STREQ (spec, "/dev/root") || (is_bsd && STREQ (mp, "/"))) - /* Resolve /dev/root to the current device. - * Do the same for the / partition of the *BSD systems, since the - * BSD -> Linux device translation is not straight forward. - */ - mountable = safe_strdup (g, fs->mountable); - else if (STRPREFIX (spec, "/dev/")) - /* Resolve guest block device names. */ - mountable = resolve_fstab_device (g, spec, md_map, fs->type); - else if (match (g, spec, re_openbsd_duid)) { - /* In OpenBSD's fstab you can specify partitions on a disk by appending a - * period and a partition letter to a Disklable Unique Identifier. The - * DUID is a 16 hex digit field found in the OpenBSD's altered BSD - * disklabel. For more info see here: - * http://www.openbsd.org/faq/faq14.html#intro - */ - char device[10]; /* /dev/sd[0-9][a-z] */ - char part = spec[17]; - - /* We cannot peep into disklables, we can only assume that this is the - * first disk. - */ - snprintf(device, 10, "%s%c", "/dev/sd0", part); - mountable = resolve_fstab_device (g, device, md_map, fs->type); - } - - /* If we haven't resolved the device successfully by this point, - * we don't care, just ignore it. - */ - if (mountable == NULL) - continue; - - snprintf (augpath, sizeof augpath, "%s/vfstype", *entry); - vfstype = guestfs_aug_get (g, augpath); - if (vfstype == NULL) return -1; - - if (STREQ (vfstype, "btrfs")) { - size_t i; - - snprintf (augpath, sizeof augpath, "%s/opt", *entry); - CLEANUP_FREE_STRING_LIST char **opts = guestfs_aug_match (g, augpath); - if (opts == NULL) return -1; - - for (i = 0; opts[i] != NULL; ++i) { - CLEANUP_FREE char *optname = NULL, *optvalue = NULL, *subvol = NULL; - char *old_mountable; - - optname = guestfs_aug_get (g, opts[i]); - if (optname == NULL) return -1; - - if (STREQ (optname, "subvol")) { - optvalue = safe_asprintf (g, "%s/value", opts[i]); - - subvol = guestfs_aug_get (g, optvalue); - if (subvol == NULL) return -1; - - old_mountable = mountable; - mountable = safe_asprintf (g, "btrfsvol:%s/%s", mountable, subvol); - free (old_mountable); - } - } - } - - add_fstab_entry (g, fs, mountable, mp); - } - - return 0; -} - -/* Add a filesystem and possibly a mountpoint entry for - * the root filesystem 'fs'. - * - * 'spec' is the fstab spec field, which might be a device name or a - * pseudodevice or 'UUID=...' or 'LABEL=...'. - * - * 'mp' is the mount point, which could also be 'swap' or 'none'. - */ -static void -add_fstab_entry (guestfs_h *g, struct inspect_fs *fs, - const char *mountable, const char *mountpoint) -{ - /* Add this to the fstab entry in 'fs'. - * Note these are further filtered by guestfs_inspect_get_mountpoints - * and guestfs_inspect_get_filesystems. - */ - const size_t n = fs->nr_fstab + 1; - struct inspect_fstab_entry *p; - - p = safe_realloc (g, fs->fstab, n * sizeof (struct inspect_fstab_entry)); - - fs->fstab = p; - fs->nr_fstab = n; - - /* These are owned by the handle and freed by guestfs_int_free_inspect_info. */ - fs->fstab[n-1].mountable = safe_strdup (g, mountable); - fs->fstab[n-1].mountpoint = safe_strdup (g, mountpoint); - - debug (g, "fstab: mountable=%s mountpoint=%s", mountable, mountpoint); -} - -/* Compute a uuid hash as a simple xor of of its 4 32bit components */ -static size_t -uuid_hash(const void *x, size_t table_size) -{ - const md_uuid *a = x; - size_t h, i; - - h = a->uuid[0]; - for (i = 1; i < 4; i++) { - h ^= a->uuid[i]; - } - - return h % table_size; -} - -static bool -uuid_cmp(const void *x, const void *y) -{ - const md_uuid *a = x; - const md_uuid *b = y; - size_t i; - - for (i = 0; i < 1; i++) { - if (a->uuid[i] != b->uuid[i]) return 0; - } - - return 1; -} - -static void -md_uuid_free(void *x) -{ - md_uuid *a = x; - free(a->path); - free(a); -} - -/* Taken from parse_uuid in mdadm */ -static int -parse_uuid (const char *str, uint32_t *uuid) -{ - size_t hit = 0; /* number of Hex digIT */ - char c; - size_t i; - int n; - - for (i = 0; i < 4; i++) - uuid[i] = 0; - - while ((c = *str++)) { - if (c >= '0' && c <= '9') - n = c - '0'; - else if (c >= 'a' && c <= 'f') - n = 10 + c - 'a'; - else if (c >= 'A' && c <= 'F') - n = 10 + c - 'A'; - else if (strchr (":. -", c)) - continue; - else - return -1; - - if (hit < 32) { - uuid[hit / 8] <<= 4; - uuid[hit / 8] += n; - } - hit++; - } - if (hit == 32) return 0; - - return -1; -} - -/* Create a mapping of uuids to appliance md device names */ -static ssize_t -map_app_md_devices (guestfs_h *g, Hash_table **map) -{ - CLEANUP_FREE_STRING_LIST char **mds = NULL; - size_t n = 0; - char **md; - - /* A hash mapping uuids to md device names */ - *map = hash_initialize(16, NULL, uuid_hash, uuid_cmp, md_uuid_free); - if (*map == NULL) g->abort_cb(); - - mds = guestfs_list_md_devices(g); - if (mds == NULL) goto error; - - for (md = mds; *md != NULL; md++) { - char **i; - CLEANUP_FREE_STRING_LIST char **detail = guestfs_md_detail (g, *md); - if (detail == NULL) goto error; - - /* Iterate over keys until we find uuid */ - for (i = detail; *i != NULL; i += 2) { - if (STREQ(*i, "uuid")) break; - } - - /* We found it */ - if (*i) { - md_uuid *entry; - - /* Next item is the uuid value */ - i++; - - entry = safe_malloc(g, sizeof(md_uuid)); - entry->path = safe_strdup(g, *md); - - if (parse_uuid(*i, entry->uuid) == -1) { - /* Invalid UUID is weird, but not fatal. */ - debug(g, "inspect-os: guestfs_md_detail returned invalid " - "uuid for %s: %s", *md, *i); - md_uuid_free(entry); - continue; - } - - const void *matched = NULL; - switch (hash_insert_if_absent(*map, entry, &matched)) { - case -1: - g->abort_cb(); - - case 0: - /* Duplicate uuid in for md device is weird, but not fatal. */ - debug(g, "inspect-os: md devices %s and %s have the same uuid", - ((md_uuid *)matched)->path, entry->path); - md_uuid_free(entry); - break; - - default: - n++; - } - } - } - - return n; - - error: - hash_free (*map); *map = NULL; - - return -1; -} - -static size_t -mdadm_app_hash(const void *x, size_t table_size) -{ - const mdadm_app *a = x; - return hash_pjw(a->mdadm, table_size); -} - -static bool -mdadm_app_cmp(const void *x, const void *y) -{ - const mdadm_app *a = x; - const mdadm_app *b = y; - - return STREQ (a->mdadm, b->mdadm); -} - -static void -mdadm_app_free(void *x) -{ - mdadm_app *a = x; - free(a->mdadm); - free(a->app); - free(a); -} - -/* Get a map of md device names in mdadm.conf to their device names in the - * appliance */ -static int -map_md_devices(guestfs_h *g, Hash_table **map) -{ - CLEANUP_HASH_FREE Hash_table *app_map = NULL; - CLEANUP_FREE_STRING_LIST char **matches = NULL; - ssize_t n_app_md_devices; - - *map = NULL; - - /* Get a map of md device uuids to their device names in the appliance */ - n_app_md_devices = map_app_md_devices (g, &app_map); - if (n_app_md_devices == -1) goto error; - - /* Nothing to do if there are no md devices */ - if (n_app_md_devices == 0) - return 0; - - /* Get all arrays listed in mdadm.conf */ - matches = guestfs_aug_match(g, "/files/etc/mdadm.conf/array"); - if (!matches) goto error; - - /* Log a debug message if we've got md devices, but nothing in mdadm.conf */ - if (matches[0] == NULL) { - debug(g, "Appliance has MD devices, but augeas returned no array matches " - "in mdadm.conf"); - return 0; - } - - *map = hash_initialize(16, NULL, mdadm_app_hash, mdadm_app_cmp, - mdadm_app_free); - if (!*map) g->abort_cb(); - - for (char **m = matches; *m != NULL; m++) { - /* Get device name and uuid for each array */ - CLEANUP_FREE char *dev_path = safe_asprintf (g, "%s/devicename", *m); - char *dev = guestfs_aug_get (g, dev_path); - if (!dev) goto error; - - CLEANUP_FREE char *uuid_path = safe_asprintf (g, "%s/uuid", *m); - CLEANUP_FREE char *uuid = guestfs_aug_get (g, uuid_path); - if (!uuid) { - free (dev); - continue; - } - - /* Parse the uuid into an md_uuid structure so we can look it up in the - * uuid->appliance device map */ - md_uuid mdadm; - mdadm.path = dev; - if (parse_uuid(uuid, mdadm.uuid) == -1) { - /* Invalid uuid. Weird, but not fatal. */ - debug(g, "inspect-os: mdadm.conf contains invalid uuid for %s: %s", - dev, uuid); - free (dev); - continue; - } - - /* If there's a corresponding uuid in the appliance, create a new - * entry in the transitive map */ - md_uuid *app = hash_lookup(app_map, &mdadm); - if (app) { - mdadm_app *entry = safe_malloc(g, sizeof(mdadm_app)); - entry->mdadm = dev; - entry->app = safe_strdup(g, app->path); - - switch (hash_insert_if_absent(*map, entry, NULL)) { - case -1: - g->abort_cb(); - - case 0: - /* Duplicate uuid in for md device is weird, but not fatal. */ - debug(g, "inspect-os: mdadm.conf contains multiple entries for %s", - app->path); - mdadm_app_free(entry); - continue; - } - } else - free (dev); - } - - return 0; - - error: - if (*map) hash_free (*map); - - return -1; -} - -static int -resolve_fstab_device_xdev (guestfs_h *g, const char *type, const char *disk, - const char *part, char **device_ret) -{ - CLEANUP_FREE char *name = NULL; - char *device; - CLEANUP_FREE_STRING_LIST char **devices = NULL; - size_t i, count; - struct drive *drv; - const char *p; - - /* type: (h|s|v|xv) - * disk: ([a-z]+) - * part: (\d*) - */ - - devices = guestfs_list_devices (g); - if (devices == NULL) - return -1; - - /* Check any hints we were passed for a non-heuristic mapping */ - name = safe_asprintf (g, "%sd%s", type, disk); - ITER_DRIVES (g, i, drv) { - if (drv->name && STREQ (drv->name, name)) { - device = safe_asprintf (g, "%s%s", devices[i], part); - if (!guestfs_int_is_partition (g, device)) { - free (device); - return 0; - } - *device_ret = device; - break; - } - } - - /* Guess the appliance device name if we didn't find a matching hint */ - if (!*device_ret) { - /* Count how many disks the libguestfs appliance has */ - for (count = 0; devices[count] != NULL; count++) - ; - - /* Calculate the numerical index of the disk */ - i = disk[0] - 'a'; - for (p = disk + 1; *p != '\0'; p++) { - i += 1; i *= 26; - i += *p - 'a'; - } - - /* Check the index makes sense wrt the number of disks the appliance has. - * If it does, map it to an appliance disk. - */ - if (i < count) { - device = safe_asprintf (g, "%s%s", devices[i], part); - if (!guestfs_int_is_partition (g, device)) { - free (device); - return 0; - } - *device_ret = device; - } - } - - return 0; -} - -static int -resolve_fstab_device_cciss (guestfs_h *g, const char *disk, const char *part, - char **device_ret) -{ - char *device; - CLEANUP_FREE_STRING_LIST char **devices = NULL; - size_t i; - struct drive *drv; - - /* disk: (cciss/c\d+d\d+) - * part: (\d+)? - */ - - devices = guestfs_list_devices (g); - if (devices == NULL) - return -1; - - /* Check any hints we were passed for a non-heuristic mapping */ - ITER_DRIVES (g, i, drv) { - if (drv->name && STREQ (drv->name, disk)) { - if (part) { - device = safe_asprintf (g, "%s%s", devices[i], part); - if (!guestfs_int_is_partition (g, device)) { - free (device); - return 0; - } - *device_ret = device; - } - else - *device_ret = safe_strdup (g, devices[i]); - break; - } - } - - /* We don't try to guess mappings for cciss devices */ - return 0; -} - -static int -resolve_fstab_device_diskbyid (guestfs_h *g, const char *part, - char **device_ret) -{ - int nr_devices; - char *device; - - /* For /dev/disk/by-id there is a limit to what we can do because - * original SCSI ID information has likely been lost. This - * heuristic will only work for guests that have a single block - * device. - * - * So the main task here is to make sure the assumptions above are - * true. - * - * XXX Use hints from virt-p2v if available. - * See also: https://bugzilla.redhat.com/show_bug.cgi?id=836573#c3 - */ - - nr_devices = guestfs_nr_devices (g); - if (nr_devices == -1) - return -1; - - /* If #devices isn't 1, give up trying to translate this fstab entry. */ - if (nr_devices != 1) - return 0; - - /* Make the partition name and check it exists. */ - device = safe_asprintf (g, "/dev/sda%s", part); - if (!guestfs_int_is_partition (g, device)) { - free (device); - return 0; - } - - *device_ret = device; - return 0; -} - -/* Resolve block device name to the libguestfs device name, eg. - * /dev/xvdb1 => /dev/vdb1; and /dev/mapper/VG-LV => /dev/VG/LV. This - * assumes that disks were added in the same order as they appear to - * the real VM, which is a reasonable assumption to make. Return - * anything we don't recognize unchanged. - */ -static char * -resolve_fstab_device (guestfs_h *g, const char *spec, Hash_table *md_map, - enum inspect_os_type os_type) -{ - char *device = NULL; - char *type, *slice, *disk, *part; - int r; - - if (STRPREFIX (spec, "/dev/mapper/")) { - /* LVM2 does some strange munging on /dev/mapper paths for VGs and - * LVs which contain '-' character: - * - * ><fs> lvcreate LV--test VG--test 32 - * ><fs> debug ls /dev/mapper - * VG----test-LV----test - * - * This makes it impossible to reverse those paths directly, so - * we have implemented lvm_canonical_lv_name in the daemon. - */ - guestfs_push_error_handler (g, NULL, NULL); - device = guestfs_lvm_canonical_lv_name (g, spec); - guestfs_pop_error_handler (g); - if (device == NULL) { - if (guestfs_last_errno (g) == ENOENT) { - /* Ignore devices that don't exist. (RHBZ#811872) */ - } else { - guestfs_int_error_errno (g, guestfs_last_errno (g), "%s", guestfs_last_error (g)); - return NULL; - } - } - } - else if (match3 (g, spec, re_xdev, &type, &disk, &part)) { - r = resolve_fstab_device_xdev (g, type, disk, part, &device); - free (type); - free (disk); - free (part); - if (r == -1) - return NULL; - } - else if (match2 (g, spec, re_cciss, &disk, &part)) { - r = resolve_fstab_device_cciss (g, disk, part, &device); - free (disk); - free (part); - if (r == -1) - return NULL; - } - else if (md_map && (disk = match1 (g, spec, re_mdN)) != NULL) { - mdadm_app entry; - entry.mdadm = disk; - - mdadm_app *app = hash_lookup (md_map, &entry); - if (app) device = safe_strdup (g, app->app); - - free (disk); - } - else if (match3 (g, spec, re_freebsd_gpt, &type, &disk, &part)) { - /* If the FreeBSD disk contains GPT partitions, the translation to Linux - * device names is straight forward. Partitions on a virtio disk are - * prefixed with vtbd. IDE hard drives used to be prefixed with ad and now - * are with ada. - */ - const int disk_i = guestfs_int_parse_unsigned_int (g, disk); - const int part_i = guestfs_int_parse_unsigned_int (g, part); - free (type); - free (disk); - free (part); - - if (disk_i != -1 && disk_i <= 26 && part_i > 0 && part_i <= 128) - device = safe_asprintf (g, "/dev/sd%c%d", disk_i + 'a', part_i); - } - else if (match4 (g, spec, re_freebsd_mbr, &type, &disk, &slice, &part)) { - /* FreeBSD disks are organized quite differently. See: - * http://www.freebsd.org/doc/handbook/disk-organization.html - * FreeBSD "partitions" are exposed as quasi-extended partitions - * numbered from 5 in Linux. I have no idea what happens when you - * have multiple "slices" (the FreeBSD term for MBR partitions). - */ - const int disk_i = guestfs_int_parse_unsigned_int (g, disk); - const int slice_i = guestfs_int_parse_unsigned_int (g, slice); - int part_i = part[0] - 'a' /* counting from 0 */; - free (type); - free (disk); - free (slice); - free (part); - - if (part_i > 2) - /* Partition 'c' has the size of the enclosing slice. Not mapped under Linux. */ - part_i -= 1; - - if (disk_i != -1 && disk_i <= 26 && - slice_i > 0 && slice_i <= 1 /* > 4 .. see comment above */ && - part_i >= 0 && part_i < 25) { - device = safe_asprintf (g, "/dev/sd%c%d", disk_i + 'a', part_i + 5); - } - } - else if ((os_type == OS_TYPE_NETBSD) && - match3 (g, spec, re_netbsd_dev, &type, &disk, &part)) { - const int disk_i = guestfs_int_parse_unsigned_int (g, disk); - int part_i = part[0] - 'a'; /* counting from 0 */ - free (type); - free (disk); - free (part); - - if (part_i > 3) - /* Partition 'c' is the disklabel partition and 'd' the hard disk itself. - * Not mapped under Linux. - */ - part_i -= 2; - - if (disk_i != -1 && part_i >= 0 && part_i < 24) - device = safe_asprintf (g, "/dev/sd%c%d", disk_i + 'a', part_i + 5); - } - else if ((os_type == OS_TYPE_OPENBSD) && - match3 (g, spec, re_openbsd_dev, &type, &disk, &part)) { - const int disk_i = guestfs_int_parse_unsigned_int (g, disk); - int part_i = part[0] - 'a'; /* counting from 0 */ - free (type); - free (disk); - free (part); - - if (part_i > 2) - /* Partition 'c' is the hard disk itself. Not mapped under Linux */ - part_i -= 1; - - /* In OpenBSD MAXPARTITIONS is defined to 16 for all architectures */ - if (disk_i != -1 && part_i >= 0 && part_i < 15) - device = safe_asprintf (g, "/dev/sd%c%d", disk_i + 'a', part_i + 5); - } - else if ((part = match1 (g, spec, re_diskbyid)) != NULL) { - r = resolve_fstab_device_diskbyid (g, part, &device); - free (part); - if (r == -1) - return NULL; - } - else if (match3 (g, spec, re_hurd_dev, &type, &disk, &part)) { - /* Hurd disk devices are like /dev/hdNsM, where hdN is the - * N-th disk and M is the M-th partition on that disk. - * Turn the disk number into a letter-based identifier, so - * we can resolve it easily. - */ - const int disk_i = guestfs_int_parse_unsigned_int (g, disk); - const char disk_as_letter[2] = { disk_i + 'a', 0 }; - r = resolve_fstab_device_xdev (g, type, disk_as_letter, part, &device); - free (type); - free (disk); - free (part); - if (r == -1) - return NULL; - } - - /* Didn't match device pattern, return original spec unchanged. */ - if (device == NULL) - device = safe_strdup (g, spec); - - return device; -} - -static char *make_augeas_path_expression (guestfs_h *g, const char **configfiles); - -/* Call 'f' with Augeas opened and having parsed 'configfiles' (these - * files must exist). As a security measure, this bails if any file - * is too large for a reasonable configuration file. After the call - * to 'f' the Augeas handle is closed. - */ -static int -inspect_with_augeas (guestfs_h *g, struct inspect_fs *fs, - const char **configfiles, - int (*f) (guestfs_h *, struct inspect_fs *)) -{ - size_t i; - int64_t size; - int r; - CLEANUP_FREE char *pathexpr = NULL; - CLEANUP_FREE_STRING_LIST char **matches = NULL; - char **match; - - /* Security: Refuse to do this if a config file is too large. */ - for (i = 0; configfiles[i] != NULL; ++i) { - if (guestfs_is_file_opts (g, configfiles[i], - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) == 0) - continue; - - size = guestfs_filesize (g, configfiles[i]); - if (size == -1) - /* guestfs_filesize failed and has already set error in handle */ - return -1; - if (size > MAX_AUGEAS_FILE_SIZE) { - error (g, _("size of %s is unreasonably large (%" PRIi64 " bytes)"), - configfiles[i], size); - return -1; - } - } - - if (guestfs_aug_init (g, "/", 16|32 /* AUG_SAVE_NOOP|AUG_NO_LOAD */) == -1) - return -1; - - r = -1; - - /* Tell Augeas to only load configfiles and no other files. This - * prevents a rogue guest from performing a denial of service attack - * by having large, over-complicated configuration files which are - * unrelated to the task at hand. (Thanks Dominic Cleal). - * Note this requires Augeas >= 1.0.0 because of RHBZ#975412. - */ - pathexpr = make_augeas_path_expression (g, configfiles); - if (guestfs_aug_rm (g, pathexpr) == -1) - goto out; - - if (guestfs_aug_load (g) == -1) - goto out; - - /* Check that augeas did not get a parse error for any of the configfiles, - * otherwise we are silently missing information. - */ - matches = guestfs_aug_match (g, "/augeas/files//error"); - for (match = matches; *match != NULL; ++match) { - for (i = 0; configfiles[i] != NULL; ++i) { - CLEANUP_FREE char *errorpath - safe_asprintf (g, "/augeas/files%s/error", configfiles[i]); - - if (STREQ (*match, errorpath)) { - /* Get the various error details. */ - guestfs_push_error_handler (g, NULL, NULL); - CLEANUP_FREE char *messagepath - safe_asprintf (g, "%s/message", errorpath); - CLEANUP_FREE char *message = guestfs_aug_get (g, messagepath); - CLEANUP_FREE char *linepath - safe_asprintf (g, "%s/line", errorpath); - CLEANUP_FREE char *line = guestfs_aug_get (g, linepath); - CLEANUP_FREE char *charpath - safe_asprintf (g, "%s/char", errorpath); - CLEANUP_FREE char *charp = guestfs_aug_get (g, charpath); - guestfs_pop_error_handler (g); - - error (g, _("%s:%s:%s: augeas parse failure: %s"), - configfiles[i], - line ? : "<none>", - charp ? : "<none>", - message ? : "<none>"); - goto out; - } - } - } - - r = f (g, fs); - - out: - guestfs_aug_close (g); - - return r; -} - -/* Explained here: https://bugzilla.redhat.com/show_bug.cgi?id=975412#c0 */ -static char * -make_augeas_path_expression (guestfs_h *g, const char **configfiles) -{ - size_t i; - size_t nr_files; - CLEANUP_FREE_STRING_LIST char **subexprs = NULL; - CLEANUP_FREE char *subexpr = NULL; - char *ret; - - nr_files = guestfs_int_count_strings ((char **) configfiles); - subexprs = safe_malloc (g, sizeof (char *) * (nr_files + 1)); - - for (i = 0; i < nr_files; ++i) { - subexprs[i] = /* v NB trailing '/' after filename */ - safe_asprintf (g, "\"%s/\" !~ regexp('^') + glob(incl) + regexp('/.*')", - configfiles[i]); - } - subexprs[nr_files] = NULL; - - subexpr = guestfs_int_join_strings (" and ", subexprs); - if (subexpr == NULL) - g->abort_cb (); - - ret = safe_asprintf (g, "/augeas/load/*[ %s ]", subexpr); - debug (g, "augeas pathexpr = %s", ret); - return ret; -} - -/* Canonicalize the path, so "///usr//local//" -> "/usr/local" - * - * The path is modified in place because the result is always - * the same length or shorter than the argument passed. - */ -static void -canonical_mountpoint (char *s) -{ - size_t len = strlen (s); - char *orig = s; - - s = strchr (s, '/'); - while (s != NULL && *s != 0) { - char *pos = s + 1; - char *p = pos; - /* Find how many consecutive slashes are there after the one found, - * and shift the characters after them accordingly. */ - while (*p == '/') - ++p; - if (p - pos > 0) { - memmove (pos, p, len - (p - orig) + 1); - len -= p - pos; - } - - s = strchr (pos, '/'); - } - /* Ignore the trailing slash, but avoid removing it for "/". */ - if (len > 1 && orig[len-1] == '/') - --len; - orig[len] = 0; -} diff --git a/lib/inspect-fs-windows.c b/lib/inspect-fs-windows.c deleted file mode 100644 index 34f33c908..000000000 --- a/lib/inspect-fs-windows.c +++ /dev/null @@ -1,739 +0,0 @@ -/* libguestfs - * Copyright (C) 2010-2012 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 <stdbool.h> -#include <unistd.h> -#include <string.h> -#include <errno.h> -#include <iconv.h> -#include <inttypes.h> - -#ifdef HAVE_ENDIAN_H -#include <endian.h> -#endif -#ifdef HAVE_SYS_ENDIAN_H -#include <sys/endian.h> -#endif - -#if defined __APPLE__ && defined __MACH__ -#include <libkern/OSByteOrder.h> -#define le32toh(x) OSSwapLittleToHostInt32(x) -#define le64toh(x) OSSwapLittleToHostInt64(x) -#endif - -#include <pcre.h> - -#include "c-ctype.h" -#include "ignore-value.h" - -#include "guestfs.h" -#include "guestfs-internal.h" -#include "guestfs-internal-actions.h" -#include "structs-cleanups.h" - -COMPILE_REGEXP (re_windows_version, "^(\\d+)\\.(\\d+)", 0) -COMPILE_REGEXP (re_boot_ini_os_header, "^\\[operating systems\\]\\s*$", 0) -COMPILE_REGEXP (re_boot_ini_os, - "^(multi|scsi)\\((\\d+)\\)disk\\((\\d+)\\)rdisk\\((\\d+)\\)partition\\((\\d+)\\)([^=]+)=", 0) - -static int check_windows_arch (guestfs_h *g, struct inspect_fs *fs); -static int check_windows_registry_paths (guestfs_h *g, struct inspect_fs *fs); -static int check_windows_software_registry (guestfs_h *g, struct inspect_fs *fs); -static int check_windows_system_registry (guestfs_h *g, struct inspect_fs *fs); -static char *map_registry_disk_blob (guestfs_h *g, const void *blob); -static char *map_registry_disk_blob_gpt (guestfs_h *g, const void *blob); -static char *extract_guid_from_registry_blob (guestfs_h *g, const void *blob); - -/* XXX Handling of boot.ini in the Perl version was pretty broken. It - * essentially didn't do anything for modern Windows guests. - * Therefore I've omitted all that code. - */ - -/* Try to find Windows systemroot using some common locations. - * - * Notes: - * - * (1) We check for some directories inside to see if it is a real - * systemroot, and not just a directory that happens to have the same - * name. - * - * (2) If a Windows guest has multiple disks and applications are - * installed on those other disks, then those other disks will contain - * "/Program Files" and "/System Volume Information". Those would - * *not* be Windows root disks. (RHBZ#674130) - */ - -static int -is_systemroot (guestfs_h *const g, const char *systemroot) -{ - CLEANUP_FREE char *path1 = NULL, *path2 = NULL, *path3 = NULL; - - path1 = safe_asprintf (g, "%s/system32", systemroot); - if (!guestfs_int_is_dir_nocase (g, path1)) - return 0; - - path2 = safe_asprintf (g, "%s/system32/config", systemroot); - if (!guestfs_int_is_dir_nocase (g, path2)) - return 0; - - path3 = safe_asprintf (g, "%s/system32/cmd.exe", systemroot); - if (!guestfs_int_is_file_nocase (g, path3)) - return 0; - - return 1; -} - -char * -guestfs_int_get_windows_systemroot (guestfs_h *g) -{ - /* Check a predefined list of common windows system root locations */ - static const char *systemroots[] - { "/windows", "/winnt", "/win32", "/win", "/reactos", NULL }; - - for (size_t i = 0; i < sizeof systemroots / sizeof systemroots[0]; ++i) { - char *systemroot - guestfs_int_case_sensitive_path_silently (g, systemroots[i]); - if (!systemroot) - continue; - - if (is_systemroot (g, systemroot)) { - debug (g, "windows %%SYSTEMROOT%% = %s", systemroot); - - return systemroot; - } else { - free (systemroot); - } - } - - /* If the fs contains boot.ini, check it for non-standard - * systemroot locations */ - CLEANUP_FREE char *boot_ini_path - guestfs_int_case_sensitive_path_silently (g, "/boot.ini"); - if (boot_ini_path && guestfs_is_file (g, boot_ini_path) > 0) { - CLEANUP_FREE_STRING_LIST char **boot_ini - guestfs_read_lines (g, boot_ini_path); - if (!boot_ini) { - debug (g, "error reading %s", boot_ini_path); - return NULL; - } - - int found_os = 0; - for (char **i = boot_ini; *i != NULL; i++) { - CLEANUP_FREE char *controller_type = NULL; - CLEANUP_FREE char *controller = NULL; - CLEANUP_FREE char *disk = NULL; - CLEANUP_FREE char *rdisk = NULL; - CLEANUP_FREE char *partition = NULL; - CLEANUP_FREE char *path = NULL; - - char *line = *i; - - if (!found_os) { - if (match (g, line, re_boot_ini_os_header)) { - found_os = 1; - continue; - } - } - - /* See http://support.microsoft.com/kb/102873 for a discussion - * of what this line means */ - if (match6 (g, line, re_boot_ini_os, &controller_type, - &controller, &disk, &rdisk, &partition, &path)) - { - /* The Windows system root may be on any disk. However, there - * are currently (at least) 2 practical problems preventing us - * from locating it on another disk: - * - * 1. We don't have enough metadata about the disks we were - * given to know if what controller they were on and what - * index they had. - * - * 2. The way inspection of filesystems currently works, we - * can't mark another filesystem, which we may have already - * inspected, to be inspected for a specific Windows system - * root. - * - * Solving 1 properly would require a new API at a minimum. We - * might be able to fudge something practical without this, - * though, e.g. by looking at the <partition>th partition of - * every disk for the specific windows root. - * - * Solving 2 would probably require a significant refactoring - * of the way filesystems are inspected. We should probably do - * this some time. - * - * For the moment, we ignore all partition information and - * assume the system root is on the current partition. In - * practice, this will normally be correct. - */ - - /* Swap backslashes for forward slashes in the system root - * path */ - for (char *j = path; *j != '\0'; j++) { - if (*j == '\\') *j = '/'; - } - - char *systemroot = guestfs_int_case_sensitive_path_silently (g, path); - if (systemroot && is_systemroot (g, systemroot)) { - debug (g, "windows %%SYSTEMROOT%% = %s", systemroot); - - return systemroot; - } else { - free (systemroot); - } - } - } - } - - return NULL; /* not found */ -} - -int -guestfs_int_check_windows_root (guestfs_h *g, struct inspect_fs *fs, - char *const systemroot) -{ - fs->type = OS_TYPE_WINDOWS; - fs->distro = OS_DISTRO_WINDOWS; - - /* Freed by guestfs_int_free_inspect_info. */ - fs->windows_systemroot = systemroot; - - if (check_windows_arch (g, fs) == -1) - return -1; - - /* Get system and software registry paths. */ - if (check_windows_registry_paths (g, fs) == -1) - return -1; - - /* Product name and version. */ - if (check_windows_software_registry (g, fs) == -1) - return -1; - - /* Hostname. */ - if (check_windows_system_registry (g, fs) == -1) - return -1; - - return 0; -} - -static int -check_windows_arch (guestfs_h *g, struct inspect_fs *fs) -{ - CLEANUP_FREE char *cmd_exe - safe_asprintf (g, "%s/system32/cmd.exe", fs->windows_systemroot); - - /* Should exist because of previous check above in get_windows_systemroot. */ - CLEANUP_FREE char *cmd_exe_path = guestfs_case_sensitive_path (g, cmd_exe); - if (!cmd_exe_path) - return -1; - - char *arch = guestfs_file_architecture (g, cmd_exe_path); - if (!arch) - return -1; - - fs->arch = arch; /* freed by guestfs_int_free_inspect_info */ - - return 0; -} - -static int -check_windows_registry_paths (guestfs_h *g, struct inspect_fs *fs) -{ - int r; - CLEANUP_FREE char *software = NULL, *system = NULL; - - if (!fs->windows_systemroot) - return 0; - - software = safe_asprintf (g, "%s/system32/config/software", - fs->windows_systemroot); - - fs->windows_software_hive = guestfs_case_sensitive_path (g, software); - if (!fs->windows_software_hive) - return -1; - - r = guestfs_is_file (g, fs->windows_software_hive); - if (r == -1) { - free (fs->windows_software_hive); - fs->windows_software_hive = NULL; - return -1; - } - - if (r == 0) { /* doesn't exist, or not a file */ - free (fs->windows_software_hive); - fs->windows_software_hive = NULL; - /*FALLTHROUGH*/ - } - - system = safe_asprintf (g, "%s/system32/config/system", - fs->windows_systemroot); - - fs->windows_system_hive = guestfs_case_sensitive_path (g, system); - if (!fs->windows_system_hive) - return -1; - - r = guestfs_is_file (g, fs->windows_system_hive); - if (r == -1) { - free (fs->windows_system_hive); - fs->windows_system_hive = NULL; - return -1; - } - - if (r == 0) { /* doesn't exist, or not a file */ - free (fs->windows_system_hive); - fs->windows_system_hive = NULL; - /*FALLTHROUGH*/ - } - - return 0; -} - -/* At the moment, pull just the ProductName and version numbers from - * the registry. In future there is a case for making many more - * registry fields available to callers. - */ -static int -check_windows_software_registry (guestfs_h *g, struct inspect_fs *fs) -{ - int ret = -1; - int64_t node; - const char *hivepath[] - { "Microsoft", "Windows NT", "CurrentVersion" }; - size_t i; - CLEANUP_FREE_HIVEX_VALUE_LIST struct guestfs_hivex_value_list *values = NULL; - bool ignore_currentversion = false; - - /* If the software hive doesn't exist, just accept that we cannot - * find product_name etc. - */ - if (!fs->windows_software_hive) - return 0; - - if (guestfs_hivex_open (g, fs->windows_software_hive, - GUESTFS_HIVEX_OPEN_VERBOSE, g->verbose, - GUESTFS_HIVEX_OPEN_UNSAFE, 1, - -1) == -1) - return -1; - - node = guestfs_hivex_root (g); - for (i = 0; node > 0 && i < sizeof hivepath / sizeof hivepath[0]; ++i) - node = guestfs_hivex_node_get_child (g, node, hivepath[i]); - - if (node == -1) - goto out; - - if (node == 0) { - perrorf (g, "hivex: cannot locate HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"); - goto out; - } - - values = guestfs_hivex_node_values (g, node); - - for (i = 0; i < values->len; ++i) { - const int64_t value = values->val[i].hivex_value_h; - CLEANUP_FREE char *key = guestfs_hivex_value_key (g, value); - if (key == NULL) - goto out; - - if (STRCASEEQ (key, "ProductName")) { - fs->product_name = guestfs_hivex_value_utf8 (g, value); - if (!fs->product_name) - goto out; - } - else if (STRCASEEQ (key, "CurrentMajorVersionNumber")) { - size_t vsize; - const int64_t vtype = guestfs_hivex_value_type (g, value); - CLEANUP_FREE char *vbuf = guestfs_hivex_value_value (g, value, &vsize); - - if (vbuf == NULL) - goto out; - if (vtype != 4 || vsize != 4) { - error (g, "hivex: expected CurrentVersion\\%s to be a DWORD field", - "CurrentMajorVersionNumber"); - goto out; - } - - fs->version.v_major = le32toh (*(int32_t *)vbuf); - - /* Ignore CurrentVersion if we see it after this key. */ - ignore_currentversion = true; - } - else if (STRCASEEQ (key, "CurrentMinorVersionNumber")) { - size_t vsize; - const int64_t vtype = guestfs_hivex_value_type (g, value); - CLEANUP_FREE char *vbuf = guestfs_hivex_value_value (g, value, &vsize); - - if (vbuf == NULL) - goto out; - if (vtype != 4 || vsize != 4) { - error (g, "hivex: expected CurrentVersion\\%s to be a DWORD field", - "CurrentMinorVersionNumber"); - goto out; - } - - fs->version.v_minor = le32toh (*(int32_t *)vbuf); - - /* Ignore CurrentVersion if we see it after this key. */ - ignore_currentversion = true; - } - else if (!ignore_currentversion && STRCASEEQ (key, "CurrentVersion")) { - CLEANUP_FREE char *version = guestfs_hivex_value_utf8 (g, value); - if (!version) - goto out; - if (guestfs_int_version_from_x_y_re (g, &fs->version, version, - re_windows_version) == -1) - goto out; - } - else if (STRCASEEQ (key, "InstallationType")) { - fs->product_variant = guestfs_hivex_value_utf8 (g, value); - if (!fs->product_variant) - goto out; - } - } - - ret = 0; - - out: - guestfs_hivex_close (g); - - return ret; -} - -static int -check_windows_system_registry (guestfs_h *g, struct inspect_fs *fs) -{ - static const char gpt_prefix[] = "DMIO:ID:"; - int ret = -1; - int64_t root, node, value; - CLEANUP_FREE_HIVEX_VALUE_LIST struct guestfs_hivex_value_list *values = NULL; - CLEANUP_FREE_HIVEX_VALUE_LIST struct guestfs_hivex_value_list *values2 = NULL; - int32_t dword; - size_t i, count; - CLEANUP_FREE void *buf = NULL; - size_t buflen; - const char *hivepath[] - { NULL /* current control set */, "Services", "Tcpip", "Parameters" }; - - /* If the system hive doesn't exist, just accept that we cannot - * find hostname etc. - */ - if (!fs->windows_system_hive) - return 0; - - if (guestfs_hivex_open (g, fs->windows_system_hive, - GUESTFS_HIVEX_OPEN_VERBOSE, g->verbose, - GUESTFS_HIVEX_OPEN_UNSAFE, 1, - -1) == -1) - goto out; - - root = guestfs_hivex_root (g); - if (root == 0) - goto out; - - /* Get the CurrentControlSet. */ - node = guestfs_hivex_node_get_child (g, root, "Select"); - if (node == -1) - goto out; - - if (node == 0) { - error (g, "hivex: could not locate HKLM\\SYSTEM\\Select"); - goto out; - } - - value = guestfs_hivex_node_get_value (g, node, "Current"); - if (value == -1) - goto out; - - if (value == 0) { - error (g, "hivex: HKLM\\System\\Select Default entry not found"); - goto out; - } - - /* XXX Should check the type. */ - buf = guestfs_hivex_value_value (g, value, &buflen); - if (buflen != 4) { - error (g, "hivex: HKLM\\System\\Select\\Current expected to be DWORD"); - goto out; - } - dword = le32toh (*(int32_t *)buf); - fs->windows_current_control_set = safe_asprintf (g, "ControlSet%03d", dword); - - /* Get the drive mappings. - * This page explains the contents of HKLM\System\MountedDevices: - * http://www.goodells.net/multiboot/partsigs.shtml - */ - node = guestfs_hivex_node_get_child (g, root, "MountedDevices"); - if (node == -1) - goto out; - - if (node == 0) - /* Not found: skip getting drive letter mappings (RHBZ#803664). */ - goto skip_drive_letter_mappings; - - values = guestfs_hivex_node_values (g, node); - - /* Count how many DOS drive letter mappings there are. This doesn't - * ignore removable devices, so it overestimates, but that doesn't - * matter because it just means we'll allocate a few bytes extra. - */ - for (i = count = 0; i < values->len; ++i) { - CLEANUP_FREE char *key - guestfs_hivex_value_key (g, values->val[i].hivex_value_h); - if (key == NULL) - goto out; - if (STRCASEEQLEN (key, "\\DosDevices\\", 12) && - c_isalpha (key[12]) && key[13] == ':') - count++; - } - - fs->drive_mappings = safe_calloc (g, 2*count + 1, sizeof (char *)); - - for (i = count = 0; i < values->len; ++i) { - const int64_t v = values->val[i].hivex_value_h; - CLEANUP_FREE char *key = guestfs_hivex_value_key (g, v); - if (key == NULL) - goto out; - if (STRCASEEQLEN (key, "\\DosDevices\\", 12) && - c_isalpha (key[12]) && key[13] == ':') { - /* Get the binary value. Is it a fixed disk? */ - CLEANUP_FREE char *blob = NULL; - char *device; - int64_t type; - bool is_gpt; - size_t len; - - type = guestfs_hivex_value_type (g, v); - blob = guestfs_hivex_value_value (g, v, &len); - is_gpt = memcmp (blob, gpt_prefix, 8) == 0; - if (blob != NULL && type == 3 && (len == 12 || is_gpt)) { - /* Try to map the blob to a known disk and partition. */ - if (is_gpt) - device = map_registry_disk_blob_gpt (g, blob); - else - device = map_registry_disk_blob (g, blob); - - if (device != NULL) { - fs->drive_mappings[count++] = safe_strndup (g, &key[12], 1); - fs->drive_mappings[count++] = device; - } - } - } - } - - skip_drive_letter_mappings:; - /* Get the hostname. */ - hivepath[0] = fs->windows_current_control_set; - for (node = root, i = 0; - node > 0 && i < sizeof hivepath / sizeof hivepath[0]; - ++i) { - node = guestfs_hivex_node_get_child (g, node, hivepath[i]); - } - - if (node == -1) - goto out; - - if (node == 0) { - perrorf (g, "hivex: cannot locate HKLM\\SYSTEM\\%s\\Services\\Tcpip\\Parameters", - fs->windows_current_control_set); - goto out; - } - - values2 = guestfs_hivex_node_values (g, node); - if (values2 == NULL) - goto out; - - for (i = 0; i < values2->len; ++i) { - const int64_t v = values2->val[i].hivex_value_h; - CLEANUP_FREE char *key = guestfs_hivex_value_key (g, v); - if (key == NULL) - goto out; - - if (STRCASEEQ (key, "Hostname")) { - fs->hostname = guestfs_hivex_value_utf8 (g, v); - if (!fs->hostname) - goto out; - } - /* many other interesting fields here ... */ - } - - ret = 0; - - out: - guestfs_hivex_close (g); - - return ret; -} - -/* Windows Registry HKLM\SYSTEM\MountedDevices uses a blob of data - * to store partitions. This blob is described here: - * http://www.goodells.net/multiboot/partsigs.shtml - * The following function maps this blob to a libguestfs partition - * name, if possible. - */ -static char * -map_registry_disk_blob (guestfs_h *g, const void *blob) -{ - CLEANUP_FREE_STRING_LIST char **devices = NULL; - CLEANUP_FREE_PARTITION_LIST struct guestfs_partition_list *partitions = NULL; - size_t i, j, len; - uint64_t part_offset; - - /* First 4 bytes are the disk ID. Search all devices to find the - * disk with this disk ID. - */ - devices = guestfs_list_devices (g); - if (devices == NULL) - return NULL; - - for (i = 0; devices[i] != NULL; ++i) { - /* Read the disk ID. */ - CLEANUP_FREE char *diskid - guestfs_pread_device (g, devices[i], 4, 0x01b8, &len); - if (diskid == NULL) - continue; - if (len < 4) - continue; - if (memcmp (diskid, blob, 4) == 0) /* found it */ - goto found_disk; - } - return NULL; - - found_disk: - /* Next 8 bytes are the offset of the partition in bytes(!) given as - * a 64 bit little endian number. Luckily it's easy to get the - * partition byte offset from guestfs_part_list. - */ - memcpy (&part_offset, (char *) blob + 4, sizeof (part_offset)); - part_offset = le64toh (part_offset); - - partitions = guestfs_part_list (g, devices[i]); - if (partitions == NULL) - return NULL; - - for (j = 0; j < partitions->len; ++j) { - if (partitions->val[j].part_start == part_offset) /* found it */ - goto found_partition; - } - return NULL; - - found_partition: - /* Construct the full device name. */ - return safe_asprintf (g, "%s%d", devices[i], partitions->val[j].part_num); -} - -/* Matches Windows registry HKLM\SYSYTEM\MountedDevices\DosDevices blob to - * to libguestfs GPT partition device. For GPT disks, the blob is made of - * "DMIO:ID:" prefix followed by the GPT partition GUID. - */ -static char * -map_registry_disk_blob_gpt (guestfs_h *g, const void *blob) -{ - CLEANUP_FREE_STRING_LIST char **parts = NULL; - CLEANUP_FREE char *blob_guid = extract_guid_from_registry_blob (g, blob); - size_t i; - - parts = guestfs_list_partitions (g); - if (parts == NULL) - return NULL; - - for (i = 0; parts[i] != NULL; ++i) { - CLEANUP_FREE char *fs_guid = NULL; - int partnum; - CLEANUP_FREE char *device = NULL; - CLEANUP_FREE char *type = NULL; - - partnum = guestfs_part_to_partnum (g, parts[i]); - if (partnum == -1) - continue; - - device = guestfs_part_to_dev (g, parts[i]); - if (device == NULL) - continue; - - type = guestfs_part_get_parttype (g, device); - if (type == NULL) - continue; - - if (STRCASENEQ (type, "gpt")) - continue; - - /* get the GPT parition GUID from the partition block device */ - fs_guid = guestfs_part_get_gpt_guid (g, device, partnum); - if (fs_guid == NULL) - continue; - - /* if both GUIDs match, we have found the mapping for our device */ - if (STRCASEEQ (fs_guid, blob_guid)) - return safe_strdup (g, parts[i]); - } - - return NULL; -} - -/* Extracts the binary GUID stored in blob from Windows registry - * HKLM\SYSTYEM\MountedDevices\DosDevices value and converts it to a - * GUID string so that it can be matched against libguestfs partition - * device GPT GUID. - */ -static char * -extract_guid_from_registry_blob (guestfs_h *g, const void *blob) -{ - char guid_bytes[16]; - uint32_t data1; - uint16_t data2, data3; - uint64_t data4; - - /* get the GUID bytes from blob (skip 8 byte "DMIO:ID:" prefix) */ - memcpy (&guid_bytes, (char *) blob + 8, sizeof (guid_bytes)); - - /* copy relevant sections from blob to respective ints */ - memcpy (&data1, guid_bytes, sizeof (data1)); - memcpy (&data2, guid_bytes + 4, sizeof (data2)); - memcpy (&data3, guid_bytes + 6, sizeof (data3)); - memcpy (&data4, guid_bytes + 8, sizeof (data4)); - - /* ensure proper endianness */ - data1 = le32toh (data1); - data2 = le16toh (data2); - data3 = le16toh (data3); - data4 = be64toh (data4); - - return safe_asprintf (g, - "%08" PRIX32 "-%04" PRIX16 "-%04" PRIX16 "-%04" PRIX64 "-%012" PRIX64, - data1, data2, data3, data4 >> 48, data4 & 0xffffffffffff); -} - -/* NB: This function DOES NOT test for the existence of the file. It - * will return non-NULL even if the file/directory does not exist. - * You have to call guestfs_is_file{,_opts} etc. - */ -char * -guestfs_int_case_sensitive_path_silently (guestfs_h *g, const char *path) -{ - char *ret; - - guestfs_push_error_handler (g, NULL, NULL); - ret = guestfs_case_sensitive_path (g, path); - guestfs_pop_error_handler (g); - - return ret; -} diff --git a/lib/inspect-fs.c b/lib/inspect-fs.c deleted file mode 100644 index e320b3e78..000000000 --- a/lib/inspect-fs.c +++ /dev/null @@ -1,758 +0,0 @@ -/* libguestfs - * Copyright (C) 2010-2012 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 <unistd.h> -#include <string.h> -#include <libintl.h> - -#ifdef HAVE_ENDIAN_H -#include <endian.h> -#endif - -#include <pcre.h> - -#include "ignore-value.h" -#include "xstrtol.h" - -#include "guestfs.h" -#include "guestfs-internal.h" -#include "structs-cleanups.h" - -static int check_filesystem (guestfs_h *g, const char *mountable, - const struct guestfs_internal_mountable *m, - int whole_device); -static void extend_fses (guestfs_h *g); -static int get_partition_context (guestfs_h *g, const char *partition, int *partnum_ret, int *nr_partitions_ret); -static int is_symlink_to (guestfs_h *g, const char *file, const char *wanted_target); - -/* Find out if 'device' contains a filesystem. If it does, add - * another entry in g->fses. - */ -int -guestfs_int_check_for_filesystem_on (guestfs_h *g, const char *mountable) -{ - CLEANUP_FREE char *vfs_type = NULL; - int is_swap, r; - struct inspect_fs *fs; - CLEANUP_FREE_INTERNAL_MOUNTABLE struct guestfs_internal_mountable *m = NULL; - int whole_device = 0; - - /* Get vfs-type in order to check if it's a Linux(?) swap device. - * If there's an error we should ignore it, so to do that we have to - * temporarily replace the error handler with a null one. - */ - guestfs_push_error_handler (g, NULL, NULL); - vfs_type = guestfs_vfs_type (g, mountable); - guestfs_pop_error_handler (g); - - is_swap = vfs_type && STREQ (vfs_type, "swap"); - debug (g, "check_for_filesystem_on: %s (%s)", - mountable, vfs_type ? vfs_type : "failed to get vfs type"); - - if (is_swap) { - extend_fses (g); - fs = &g->fses[g->nr_fses-1]; - fs->mountable = safe_strdup (g, mountable); - return 0; - } - - m = guestfs_internal_parse_mountable (g, mountable); - if (m == NULL) - return -1; - - /* If it's a whole device, see if it is an install ISO. */ - if (m->im_type == MOUNTABLE_DEVICE) { - whole_device = guestfs_is_whole_device (g, m->im_device); - if (whole_device == -1) { - return -1; - } - } - - /* Try mounting the device. As above, ignore errors. */ - guestfs_push_error_handler (g, NULL, NULL); - if (vfs_type && STREQ (vfs_type, "ufs")) { /* Hack for the *BSDs. */ - /* FreeBSD fs is a variant of ufs called ufs2 ... */ - r = guestfs_mount_vfs (g, "ro,ufstype=ufs2", "ufs", mountable, "/"); - if (r == -1) - /* while NetBSD and OpenBSD use another variant labeled 44bsd */ - r = guestfs_mount_vfs (g, "ro,ufstype=44bsd", "ufs", mountable, "/"); - } else { - r = guestfs_mount_ro (g, mountable, "/"); - } - guestfs_pop_error_handler (g); - if (r == -1) - return 0; - - /* Do the rest of the checks. */ - r = check_filesystem (g, mountable, m, whole_device); - - /* Unmount the filesystem. */ - if (guestfs_umount_all (g) == -1) - return -1; - - return r; -} - -static int -check_filesystem (guestfs_h *g, const char *mountable, - const struct guestfs_internal_mountable *m, - int whole_device) -{ - int partnum = -1, nr_partitions = -1; - /* Not CLEANUP_FREE, as it will be cleaned up with inspection info */ - char *windows_systemroot = NULL; - - extend_fses (g); - - if (!whole_device && m->im_type == MOUNTABLE_DEVICE && - guestfs_int_is_partition (g, m->im_device)) { - if (get_partition_context (g, m->im_device, - &partnum, &nr_partitions) == -1) - return -1; - } - - struct inspect_fs *fs = &g->fses[g->nr_fses-1]; - - fs->mountable = safe_strdup (g, mountable); - - /* Optimize some of the tests by avoiding multiple tests of the same thing. */ - const int is_dir_etc = guestfs_is_dir (g, "/etc") > 0; - const int is_dir_bin = guestfs_is_dir (g, "/bin") > 0; - const int is_dir_share = guestfs_is_dir (g, "/share") > 0; - - /* Grub /boot? */ - if (guestfs_is_file (g, "/grub/menu.lst") > 0 || - guestfs_is_file (g, "/grub/grub.conf") > 0 || - guestfs_is_file (g, "/grub2/grub.cfg") > 0) - ; - /* FreeBSD root? */ - else if (is_dir_etc && - is_dir_bin && - guestfs_is_file (g, "/etc/freebsd-update.conf") > 0 && - guestfs_is_file (g, "/etc/fstab") > 0) { - fs->role = OS_ROLE_ROOT; - fs->format = OS_FORMAT_INSTALLED; - if (guestfs_int_check_freebsd_root (g, fs) == -1) - return -1; - } - /* NetBSD root? */ - else if (is_dir_etc && - is_dir_bin && - guestfs_is_file (g, "/netbsd") > 0 && - guestfs_is_file (g, "/etc/fstab") > 0 && - guestfs_is_file (g, "/etc/release") > 0) { - fs->role = OS_ROLE_ROOT; - fs->format = OS_FORMAT_INSTALLED; - if (guestfs_int_check_netbsd_root (g, fs) == -1) - return -1; - } - /* OpenBSD root? */ - else if (is_dir_etc && - is_dir_bin && - guestfs_is_file (g, "/bsd") > 0 && - guestfs_is_file (g, "/etc/fstab") > 0 && - guestfs_is_file (g, "/etc/motd") > 0) { - fs->role = OS_ROLE_ROOT; - fs->format = OS_FORMAT_INSTALLED; - if (guestfs_int_check_openbsd_root (g, fs) == -1) - return -1; - } - /* Hurd root? */ - else if (guestfs_is_file (g, "/hurd/console") > 0 && - guestfs_is_file (g, "/hurd/hello") > 0 && - guestfs_is_file (g, "/hurd/null") > 0) { - fs->role = OS_ROLE_ROOT; - fs->format = OS_FORMAT_INSTALLED; /* XXX could be more specific */ - if (guestfs_int_check_hurd_root (g, fs) == -1) - return -1; - } - /* Minix root? */ - else if (is_dir_etc && - is_dir_bin && - guestfs_is_file (g, "/service/vm") > 0 && - guestfs_is_file (g, "/etc/fstab") > 0 && - guestfs_is_file (g, "/etc/version") > 0) { - fs->role = OS_ROLE_ROOT; - fs->format = OS_FORMAT_INSTALLED; - if (guestfs_int_check_minix_root (g, fs) == -1) - return -1; - } - /* Linux root? */ - else if (is_dir_etc && - (is_dir_bin || - is_symlink_to (g, "/bin", "usr/bin") > 0) && - (guestfs_is_file (g, "/etc/fstab") > 0 || - guestfs_is_file (g, "/etc/hosts") > 0)) { - fs->role = OS_ROLE_ROOT; - fs->format = OS_FORMAT_INSTALLED; - if (guestfs_int_check_linux_root (g, fs) == -1) - return -1; - } - /* CoreOS root? */ - else if (is_dir_etc && - guestfs_is_dir (g, "/root") > 0 && - guestfs_is_dir (g, "/home") > 0 && - guestfs_is_dir (g, "/usr") > 0 && - guestfs_is_file (g, "/etc/coreos/update.conf") > 0) { - fs->role = OS_ROLE_ROOT; - fs->format = OS_FORMAT_INSTALLED; - if (guestfs_int_check_coreos_root (g, fs) == -1) - return -1; - } - /* Linux /usr/local? */ - else if (is_dir_etc && - is_dir_bin && - is_dir_share && - guestfs_is_dir (g, "/local") == 0 && - guestfs_is_file (g, "/etc/fstab") == 0) - ; - /* Linux /usr? */ - else if (is_dir_etc && - is_dir_bin && - is_dir_share && - guestfs_is_dir (g, "/local") > 0 && - guestfs_is_file (g, "/etc/fstab") == 0) { - if (guestfs_int_check_linux_usr (g, fs) == -1) - return -1; - } - /* CoreOS /usr? */ - else if (is_dir_bin && - is_dir_share && - guestfs_is_dir (g, "/local") > 0 && - guestfs_is_dir (g, "/share/coreos") > 0) { - if (guestfs_int_check_coreos_usr (g, fs) == -1) - return -1; - } - /* Linux /var? */ - else if (guestfs_is_dir (g, "/log") > 0 && - guestfs_is_dir (g, "/run") > 0 && - guestfs_is_dir (g, "/spool") > 0) - ; - /* Windows root? */ - else if ((windows_systemroot = guestfs_int_get_windows_systemroot (g)) != NULL) - { - fs->role = OS_ROLE_ROOT; - fs->format = OS_FORMAT_INSTALLED; - if (guestfs_int_check_windows_root (g, fs, windows_systemroot) == -1) - return -1; - } - /* Windows volume with installed applications (but not root)? */ - else if (guestfs_int_is_dir_nocase (g, "/System Volume Information") > 0 && - guestfs_int_is_dir_nocase (g, "/Program Files") > 0) - ; - /* Windows volume (but not root)? */ - else if (guestfs_int_is_dir_nocase (g, "/System Volume Information") > 0) - ; - /* FreeDOS? */ - else if (guestfs_int_is_dir_nocase (g, "/FDOS") > 0 && - guestfs_int_is_file_nocase (g, "/FDOS/FREEDOS.BSS") > 0) { - fs->role = OS_ROLE_ROOT; - fs->format = OS_FORMAT_INSTALLED; - fs->type = OS_TYPE_DOS; - fs->distro = OS_DISTRO_FREEDOS; - /* FreeDOS is a mix of 16 and 32 bit, but assume it requires a - * 32 bit i386 processor. - */ - fs->arch = safe_strdup (g, "i386"); - } - - /* The above code should have set fs->type and fs->distro fields, so - * we can now guess the package management system. - */ - guestfs_int_check_package_format (g, fs); - guestfs_int_check_package_management (g, fs); - - return 0; -} - -static void -extend_fses (guestfs_h *g) -{ - const size_t n = g->nr_fses + 1; - struct inspect_fs *p; - - p = safe_realloc (g, g->fses, n * sizeof (struct inspect_fs)); - - g->fses = p; - g->nr_fses = n; - - memset (&g->fses[n-1], 0, sizeof (struct inspect_fs)); -} - -/* Given a partition (eg. /dev/sda2) then return the partition number - * (eg. 2) and the total number of other partitions. - */ -static int -get_partition_context (guestfs_h *g, const char *partition, - int *partnum_ret, int *nr_partitions_ret) -{ - int partnum, nr_partitions; - CLEANUP_FREE char *device = NULL; - CLEANUP_FREE_PARTITION_LIST struct guestfs_partition_list *partitions = NULL; - - partnum = guestfs_part_to_partnum (g, partition); - if (partnum == -1) - return -1; - - device = guestfs_part_to_dev (g, partition); - if (device == NULL) - return -1; - - partitions = guestfs_part_list (g, device); - if (partitions == NULL) - return -1; - - nr_partitions = partitions->len; - - *partnum_ret = partnum; - *nr_partitions_ret = nr_partitions; - return 0; -} - -static int -is_symlink_to (guestfs_h *g, const char *file, const char *wanted_target) -{ - CLEANUP_FREE char *target = NULL; - - if (guestfs_is_symlink (g, file) == 0) - return 0; - - target = guestfs_readlink (g, file); - /* This should not fail, but play safe. */ - if (target == NULL) - return 0; - - return STREQ (target, wanted_target); -} - -int -guestfs_int_is_file_nocase (guestfs_h *g, const char *path) -{ - CLEANUP_FREE char *p = NULL; - int r; - - p = guestfs_int_case_sensitive_path_silently (g, path); - if (!p) - return 0; - r = guestfs_is_file (g, p); - return r > 0; -} - -int -guestfs_int_is_dir_nocase (guestfs_h *g, const char *path) -{ - CLEANUP_FREE char *p = NULL; - int r; - - p = guestfs_int_case_sensitive_path_silently (g, path); - if (!p) - return 0; - r = guestfs_is_dir (g, p); - return r > 0; -} - -/* Parse small, unsigned ints, as used in version numbers. */ -int -guestfs_int_parse_unsigned_int (guestfs_h *g, const char *str) -{ - long ret; - const int r = xstrtol (str, NULL, 10, &ret, ""); - if (r != LONGINT_OK) { - error (g, _("could not parse integer in version number: %s"), str); - return -1; - } - return ret; -} - -/* Like parse_unsigned_int, but ignore trailing stuff. */ -int -guestfs_int_parse_unsigned_int_ignore_trailing (guestfs_h *g, const char *str) -{ - long ret; - const int r = xstrtol (str, NULL, 10, &ret, NULL); - if (r != LONGINT_OK) { - error (g, _("could not parse integer in version number: %s"), str); - return -1; - } - return ret; -} - -/* Parse generic MAJOR.MINOR from the fs->product_name string. */ -int -guestfs_int_parse_major_minor (guestfs_h *g, struct inspect_fs *fs) -{ - if (guestfs_int_version_from_x_y (g, &fs->version, fs->product_name) == -1) - return -1; - - return 0; -} - -/* At the moment, package format and package management is just a - * simple function of the distro and version.v_major fields, so these - * can never return an error. We might be cleverer in future. - */ -void -guestfs_int_check_package_format (guestfs_h *g, struct inspect_fs *fs) -{ - switch (fs->distro) { - case OS_DISTRO_FEDORA: - case OS_DISTRO_MEEGO: - case OS_DISTRO_REDHAT_BASED: - case OS_DISTRO_RHEL: - case OS_DISTRO_MAGEIA: - case OS_DISTRO_MANDRIVA: - case OS_DISTRO_SUSE_BASED: - case OS_DISTRO_OPENSUSE: - case OS_DISTRO_SLES: - case OS_DISTRO_CENTOS: - case OS_DISTRO_SCIENTIFIC_LINUX: - case OS_DISTRO_ORACLE_LINUX: - case OS_DISTRO_ALTLINUX: - fs->package_format = OS_PACKAGE_FORMAT_RPM; - break; - - case OS_DISTRO_DEBIAN: - case OS_DISTRO_UBUNTU: - case OS_DISTRO_LINUX_MINT: - fs->package_format = OS_PACKAGE_FORMAT_DEB; - break; - - case OS_DISTRO_ARCHLINUX: - fs->package_format = OS_PACKAGE_FORMAT_PACMAN; - break; - case OS_DISTRO_GENTOO: - fs->package_format = OS_PACKAGE_FORMAT_EBUILD; - break; - case OS_DISTRO_PARDUS: - fs->package_format = OS_PACKAGE_FORMAT_PISI; - break; - - case OS_DISTRO_ALPINE_LINUX: - fs->package_format = OS_PACKAGE_FORMAT_APK; - break; - - case OS_DISTRO_VOID_LINUX: - fs->package_format = OS_PACKAGE_FORMAT_XBPS; - break; - - case OS_DISTRO_SLACKWARE: - case OS_DISTRO_TTYLINUX: - case OS_DISTRO_COREOS: - case OS_DISTRO_WINDOWS: - case OS_DISTRO_BUILDROOT: - case OS_DISTRO_CIRROS: - case OS_DISTRO_FREEDOS: - case OS_DISTRO_FREEBSD: - case OS_DISTRO_NETBSD: - case OS_DISTRO_OPENBSD: - case OS_DISTRO_FRUGALWARE: - case OS_DISTRO_PLD_LINUX: - case OS_DISTRO_UNKNOWN: - fs->package_format = OS_PACKAGE_FORMAT_UNKNOWN; - break; - } -} - -void -guestfs_int_check_package_management (guestfs_h *g, struct inspect_fs *fs) -{ - switch (fs->distro) { - case OS_DISTRO_MEEGO: - fs->package_management = OS_PACKAGE_MANAGEMENT_YUM; - break; - - case OS_DISTRO_FEDORA: - /* If Fedora >= 22 and dnf is installed, say "dnf". */ - if (guestfs_int_version_ge (&fs->version, 22, 0, 0) && - guestfs_is_file_opts (g, "/usr/bin/dnf", - GUESTFS_IS_FILE_OPTS_FOLLOWSYMLINKS, 1, -1) > 0) - fs->package_management = OS_PACKAGE_MANAGEMENT_DNF; - else if (guestfs_int_version_ge (&fs->version, 1, 0, 0)) - fs->package_management = OS_PACKAGE_MANAGEMENT_YUM; - else - /* Probably parsing the release file failed, see RHBZ#1332025. */ - fs->package_management = OS_PACKAGE_MANAGEMENT_UNKNOWN; - break; - - case OS_DISTRO_REDHAT_BASED: - case OS_DISTRO_RHEL: - case OS_DISTRO_CENTOS: - case OS_DISTRO_SCIENTIFIC_LINUX: - case OS_DISTRO_ORACLE_LINUX: - if (guestfs_int_version_ge (&fs->version, 5, 0, 0)) - fs->package_management = OS_PACKAGE_MANAGEMENT_YUM; - else if (guestfs_int_version_ge (&fs->version, 2, 0, 0)) - fs->package_management = OS_PACKAGE_MANAGEMENT_UP2DATE; - else - /* Probably parsing the release file failed, see RHBZ#1332025. */ - fs->package_management = OS_PACKAGE_MANAGEMENT_UNKNOWN; - break; - - case OS_DISTRO_DEBIAN: - case OS_DISTRO_UBUNTU: - case OS_DISTRO_LINUX_MINT: - case OS_DISTRO_ALTLINUX: - fs->package_management = OS_PACKAGE_MANAGEMENT_APT; - break; - - case OS_DISTRO_ARCHLINUX: - fs->package_management = OS_PACKAGE_MANAGEMENT_PACMAN; - break; - case OS_DISTRO_GENTOO: - fs->package_management = OS_PACKAGE_MANAGEMENT_PORTAGE; - break; - case OS_DISTRO_PARDUS: - fs->package_management = OS_PACKAGE_MANAGEMENT_PISI; - break; - case OS_DISTRO_MAGEIA: - case OS_DISTRO_MANDRIVA: - fs->package_management = OS_PACKAGE_MANAGEMENT_URPMI; - break; - - case OS_DISTRO_SUSE_BASED: - case OS_DISTRO_OPENSUSE: - case OS_DISTRO_SLES: - fs->package_management = OS_PACKAGE_MANAGEMENT_ZYPPER; - break; - - case OS_DISTRO_ALPINE_LINUX: - fs->package_management = OS_PACKAGE_MANAGEMENT_APK; - break; - - case OS_DISTRO_VOID_LINUX: - fs->package_management = OS_PACKAGE_MANAGEMENT_XBPS; - break; - - case OS_DISTRO_SLACKWARE: - case OS_DISTRO_TTYLINUX: - case OS_DISTRO_COREOS: - case OS_DISTRO_WINDOWS: - case OS_DISTRO_BUILDROOT: - case OS_DISTRO_CIRROS: - case OS_DISTRO_FREEDOS: - case OS_DISTRO_FREEBSD: - case OS_DISTRO_NETBSD: - case OS_DISTRO_OPENBSD: - case OS_DISTRO_FRUGALWARE: - case OS_DISTRO_PLD_LINUX: - case OS_DISTRO_UNKNOWN: - fs->package_management = OS_PACKAGE_MANAGEMENT_UNKNOWN; - break; - } -} - -/* Get the first line of a small file, without any trailing newline - * character. - * - * NOTE: If the file is completely empty or begins with a '\n' - * character, this returns an empty string (not NULL). The caller - * will usually need to check for this case. - */ -char * -guestfs_int_first_line_of_file (guestfs_h *g, const char *filename) -{ - char **lines = NULL; /* sic: not CLEANUP_FREE_STRING_LIST */ - int64_t size; - char *ret; - - /* Don't trust guestfs_head_n not to break with very large files. - * Check the file size is something reasonable first. - */ - size = guestfs_filesize (g, filename); - if (size == -1) - /* guestfs_filesize failed and has already set error in handle */ - return NULL; - if (size > MAX_SMALL_FILE_SIZE) { - error (g, _("size of %s is unreasonably large (%" PRIi64 " bytes)"), - filename, size); - return NULL; - } - - lines = guestfs_head_n (g, 1, filename); - if (lines == NULL) - return NULL; - if (lines[0] == NULL) { - guestfs_int_free_string_list (lines); - /* Empty file: Return an empty string as explained above. */ - return safe_strdup (g, ""); - } - /* lines[1] should be NULL because of '1' argument above ... */ - - ret = lines[0]; /* caller frees */ - - free (lines); - - return ret; -} - -/* Get the first matching line (using egrep [-i]) of a small file, - * without any trailing newline character. - * - * Returns: 1 = returned a line (in *ret) - * 0 = no match - * -1 = error - */ -int -guestfs_int_first_egrep_of_file (guestfs_h *g, const char *filename, - const char *eregex, int iflag, char **ret) -{ - char **lines; - int64_t size; - size_t i; - struct guestfs_grep_opts_argv optargs; - - /* Don't trust guestfs_grep not to break with very large files. - * Check the file size is something reasonable first. - */ - size = guestfs_filesize (g, filename); - if (size == -1) - /* guestfs_filesize failed and has already set error in handle */ - return -1; - if (size > MAX_SMALL_FILE_SIZE) { - error (g, _("size of %s is unreasonably large (%" PRIi64 " bytes)"), - filename, size); - return -1; - } - - optargs.bitmask = GUESTFS_GREP_OPTS_EXTENDED_BITMASK; - optargs.extended = 1; - if (iflag) { - optargs.bitmask |= GUESTFS_GREP_OPTS_INSENSITIVE_BITMASK; - optargs.insensitive = 1; - } - lines = guestfs_grep_opts_argv (g, eregex, filename, &optargs); - if (lines == NULL) - return -1; - if (lines[0] == NULL) { - guestfs_int_free_string_list (lines); - return 0; - } - - *ret = lines[0]; /* caller frees */ - - /* free up any other matches and the array itself */ - for (i = 1; lines[i] != NULL; ++i) - free (lines[i]); - free (lines); - - return 1; -} - -/* Merge the missing OS inspection information found on the src inspect_fs into - * the ones of the dst inspect_fs. This function is useful if the inspection - * information for an OS are gathered by inspecting multiple filesystems. - */ -void -guestfs_int_merge_fs_inspections (guestfs_h *g, struct inspect_fs *dst, struct inspect_fs *src) -{ - size_t n, i, old; - struct inspect_fstab_entry *fstab = NULL; - char ** mappings = NULL; - - if (dst->type == 0) - dst->type = src->type; - - if (dst->distro == 0) - dst->distro = src->distro; - - if (dst->package_format == 0) - dst->package_format = src->package_format; - - if (dst->package_management == 0) - dst->package_management = src->package_management; - - if (dst->product_name == NULL) { - dst->product_name = src->product_name; - src->product_name = NULL; - } - - if (dst->product_variant == NULL) { - dst->product_variant= src->product_variant; - src->product_variant = NULL; - } - - if (version_is_null (&dst->version)) - dst->version = src->version; - - if (dst->arch == NULL) { - dst->arch = src->arch; - src->arch = NULL; - } - - if (dst->hostname == NULL) { - dst->hostname = src->hostname; - src->hostname = NULL; - } - - if (dst->windows_systemroot == NULL) { - dst->windows_systemroot = src->windows_systemroot; - src->windows_systemroot = NULL; - } - - if (dst->windows_current_control_set == NULL) { - dst->windows_current_control_set = src->windows_current_control_set; - src->windows_current_control_set = NULL; - } - - if (src->drive_mappings != NULL) { - if (dst->drive_mappings == NULL) { - /* Adopt the drive mappings of src */ - dst->drive_mappings = src->drive_mappings; - src->drive_mappings = NULL; - } else { - n = 0; - for (; dst->drive_mappings[n] != NULL; n++) - ; - old = n; - for (; src->drive_mappings[n] != NULL; n++) - ; - - /* Merge the src mappings to dst */ - mappings = safe_realloc (g, dst->drive_mappings,(n + 1) * sizeof (char *)); - - for (i = old; i < n; i++) - mappings[i] = src->drive_mappings[i - old]; - - mappings[n] = NULL; - dst->drive_mappings = mappings; - - free(src->drive_mappings); - src->drive_mappings = NULL; - } - } - - if (src->nr_fstab > 0) { - n = dst->nr_fstab + src->nr_fstab; - fstab = safe_realloc (g, dst->fstab, n * sizeof (struct inspect_fstab_entry)); - - for (i = 0; i < src->nr_fstab; i++) { - fstab[dst->nr_fstab + i].mountable = src->fstab[i].mountable; - fstab[dst->nr_fstab + i].mountpoint = src->fstab[i].mountpoint; - } - free(src->fstab); - src->fstab = NULL; - src->nr_fstab = 0; - - dst->fstab = fstab; - dst->nr_fstab = n; - } -} diff --git a/lib/inspect-icon.c b/lib/inspect-icon.c index 89c232f5b..f4f5f0660 100644 --- a/lib/inspect-icon.c +++ b/lib/inspect-icon.c @@ -51,22 +51,24 @@ * An icon was found. 'ret' points to the icon buffer, and *size_r * is the size. */ -static char *icon_favicon (guestfs_h *g, struct inspect_fs *fs, size_t *size_r); -static char *icon_fedora (guestfs_h *g, struct inspect_fs *fs, size_t *size_r); -static char *icon_rhel (guestfs_h *g, struct inspect_fs *fs, size_t *size_r); -static char *icon_debian (guestfs_h *g, struct inspect_fs *fs, size_t *size_r); -static char *icon_ubuntu (guestfs_h *g, struct inspect_fs *fs, size_t *size_r); -static char *icon_mageia (guestfs_h *g, struct inspect_fs *fs, size_t *size_r); -static char *icon_opensuse (guestfs_h *g, struct inspect_fs *fs, size_t *size_r); +static char *icon_favicon (guestfs_h *g, const char *type, size_t *size_r); +static char *icon_fedora (guestfs_h *g, size_t *size_r); +static char *icon_rhel (guestfs_h *g, int major, size_t *size_r); +static char *icon_debian (guestfs_h *g, size_t *size_r); +static char *icon_ubuntu (guestfs_h *g, size_t *size_r); +static char *icon_mageia (guestfs_h *g, size_t *size_r); +static char *icon_opensuse (guestfs_h *g, size_t *size_r); #if CAN_DO_CIRROS -static char *icon_cirros (guestfs_h *g, struct inspect_fs *fs, size_t *size_r); +static char *icon_cirros (guestfs_h *g, size_t *size_r); #endif -static char *icon_voidlinux (guestfs_h *g, struct inspect_fs *fs, size_t *size_r); -static char *icon_altlinux (guestfs_h *g, struct inspect_fs *fs, size_t *size_r); +static char *icon_voidlinux (guestfs_h *g, size_t *size_r); +static char *icon_altlinux (guestfs_h *g, size_t *size_r); #if CAN_DO_WINDOWS -static char *icon_windows (guestfs_h *g, struct inspect_fs *fs, size_t *size_r); +static char *icon_windows (guestfs_h *g, const char *root, size_t *size_r); #endif +static char *case_sensitive_path_silently (guestfs_h *g, const char *path); + /* Dummy static object. */ static char *NOT_FOUND = (char *) "not_found"; @@ -82,13 +84,17 @@ char * guestfs_impl_inspect_get_icon (guestfs_h *g, const char *root, size_t *size_r, const struct guestfs_inspect_get_icon_argv *optargs) { - struct inspect_fs *fs; char *r = NOT_FOUND; int favicon, highquality; size_t size; + CLEANUP_FREE char *type = NULL; + CLEANUP_FREE char *distro = NULL; - fs = guestfs_int_search_for_root (g, root); - if (!fs) + type = guestfs_inspect_get_type (g, root); + if (!type) + return NULL; + distro = guestfs_inspect_get_distro (g, root); + if (!distro) return NULL; /* Get optargs, or defaults. */ @@ -106,7 +112,7 @@ guestfs_impl_inspect_get_icon (guestfs_h *g, const char *root, size_t *size_r, /* Try looking for a favicon first. */ if (favicon) { - r = icon_favicon (g, fs, &size); + r = icon_favicon (g, type, &size); if (!r) return NULL; @@ -120,96 +126,52 @@ guestfs_impl_inspect_get_icon (guestfs_h *g, const char *root, size_t *size_r, /* Favicon failed, so let's try a method based on the detected operating * system. */ - switch (fs->type) { - case OS_TYPE_LINUX: - case OS_TYPE_HURD: - switch (fs->distro) { - case OS_DISTRO_FEDORA: - r = icon_fedora (g, fs, &size); - break; - - case OS_DISTRO_RHEL: - case OS_DISTRO_REDHAT_BASED: - case OS_DISTRO_CENTOS: - case OS_DISTRO_SCIENTIFIC_LINUX: - case OS_DISTRO_ORACLE_LINUX: - r = icon_rhel (g, fs, &size); - break; - - case OS_DISTRO_DEBIAN: - r = icon_debian (g, fs, &size); - break; - - case OS_DISTRO_UBUNTU: + if (STREQ (type, "linux") || STREQ (type, "hurd")) { + if (STREQ (distro, "fedora")) { + r = icon_fedora (g, &size); + } + else if (STREQ (distro, "rhel") || + STREQ (distro, "redhat-based") || + STREQ (distro, "centos") || + STREQ (distro, "scientificlinux") || + STREQ (distro, "oraclelinux")) { + r = icon_rhel (g, guestfs_inspect_get_major_version (g, root), &size); + } + else if (STREQ (distro, "debian")) { + r = icon_debian (g, &size); + } + else if (STREQ (distro, "ubuntu")) { if (!highquality) - r = icon_ubuntu (g, fs, &size); - break; - - case OS_DISTRO_MAGEIA: - r = icon_mageia (g, fs, &size); - break; - - case OS_DISTRO_SUSE_BASED: - case OS_DISTRO_OPENSUSE: - case OS_DISTRO_SLES: - r = icon_opensuse (g, fs, &size); - break; - - case OS_DISTRO_CIRROS: + r = icon_ubuntu (g, &size); + } + else if (STREQ (distro, "mageia")) { + r = icon_mageia (g, &size); + } + else if (STREQ (distro, "suse-based") || + STREQ (distro, "opensuse") || + STREQ (distro, "sles")) { + r = icon_opensuse (g, &size); + } + else if (STREQ (distro, "cirros")) { #if CAN_DO_CIRROS - r = icon_cirros (g, fs, &size); + r = icon_cirros (g, &size); #endif - break; - - case OS_DISTRO_VOID_LINUX: - r = icon_voidlinux (g, fs, &size); - break; - - case OS_DISTRO_ALTLINUX: - r = icon_altlinux (g, fs, &size); - break; - - /* These are just to keep gcc warnings happy. */ - case OS_DISTRO_ARCHLINUX: - case OS_DISTRO_BUILDROOT: - case OS_DISTRO_COREOS: - case OS_DISTRO_FREEDOS: - case OS_DISTRO_GENTOO: - case OS_DISTRO_LINUX_MINT: - case OS_DISTRO_MANDRIVA: - case OS_DISTRO_MEEGO: - case OS_DISTRO_PARDUS: - case OS_DISTRO_SLACKWARE: - case OS_DISTRO_TTYLINUX: - case OS_DISTRO_WINDOWS: - case OS_DISTRO_FREEBSD: - case OS_DISTRO_NETBSD: - case OS_DISTRO_OPENBSD: - case OS_DISTRO_ALPINE_LINUX: - case OS_DISTRO_FRUGALWARE: - case OS_DISTRO_PLD_LINUX: - case OS_DISTRO_UNKNOWN: - ; /* nothing */ } - break; - - case OS_TYPE_WINDOWS: + else if (STREQ (distro, "voidlinux")) { + r = icon_voidlinux (g, &size); + } + else if (STREQ (distro, "altlinux")) { + r = icon_altlinux (g, &size); + } + } + else if (STREQ (type, "windows")) { #if CAN_DO_WINDOWS /* We don't know how to get high quality icons from a Windows guest, * so disable this if high quality was specified. */ if (!highquality) - r = icon_windows (g, fs, &size); + r = icon_windows (g, root, &size); #endif - break; - - case OS_TYPE_FREEBSD: - case OS_TYPE_NETBSD: - case OS_TYPE_DOS: - case OS_TYPE_OPENBSD: - case OS_TYPE_MINIX: - case OS_TYPE_UNKNOWN: - ; /* nothing */ } if (r == NOT_FOUND) { @@ -229,8 +191,7 @@ guestfs_impl_inspect_get_icon (guestfs_h *g, const char *root, size_t *size_r, * If it is, download and return it. */ static char * -get_png (guestfs_h *g, struct inspect_fs *fs, const char *filename, - size_t *size_r, size_t max_size) +get_png (guestfs_h *g, const char *filename, size_t *size_r, size_t max_size) { char *ret; CLEANUP_FREE char *real = NULL; @@ -270,7 +231,7 @@ get_png (guestfs_h *g, struct inspect_fs *fs, const char *filename, if (max_size == 0) max_size = 4 * w * h; - local = guestfs_int_download_to_tmp (g, fs, real, "icon", max_size); + local = guestfs_int_download_to_tmp (g, real, "icon", max_size); if (!local) return NOT_FOUND; @@ -285,20 +246,20 @@ get_png (guestfs_h *g, struct inspect_fs *fs, const char *filename, * it has a reasonable size and format. */ static char * -icon_favicon (guestfs_h *g, struct inspect_fs *fs, size_t *size_r) +icon_favicon (guestfs_h *g, const char *type, size_t *size_r) { char *ret; char *filename = safe_strdup (g, "/etc/favicon.png"); - if (fs->type == OS_TYPE_WINDOWS) { - char *f = guestfs_int_case_sensitive_path_silently (g, filename); + if (STREQ (type, "windows")) { + char *f = case_sensitive_path_silently (g, filename); if (f) { free (filename); filename = f; } } - ret = get_png (g, fs, filename, size_r, 0); + ret = get_png (g, filename, size_r, 0); free (filename); return ret; } @@ -309,9 +270,9 @@ icon_favicon (guestfs_h *g, struct inspect_fs *fs, size_t *size_r) #define FEDORA_ICON "/usr/share/icons/hicolor/96x96/apps/fedora-logo-icon.png" static char * -icon_fedora (guestfs_h *g, struct inspect_fs *fs, size_t *size_r) +icon_fedora (guestfs_h *g, size_t *size_r) { - return get_png (g, fs, FEDORA_ICON, size_r, 0); + return get_png (g, FEDORA_ICON, size_r, 0); } /* RHEL 3, 4: @@ -330,28 +291,28 @@ icon_fedora (guestfs_h *g, struct inspect_fs *fs, size_t *size_r) * RHEL clones have different sizes. */ static char * -icon_rhel (guestfs_h *g, struct inspect_fs *fs, size_t *size_r) +icon_rhel (guestfs_h *g, int major, size_t *size_r) { const char *shadowman; - if (!guestfs_int_version_ge (&fs->version, 7, 0, 0)) + if (major < 7) shadowman = "/usr/share/pixmaps/redhat/shadowman-transparent.png"; else shadowman = "/usr/share/pixmaps/fedora-logo-sprite.png"; - return get_png (g, fs, shadowman, size_r, 102400); + return get_png (g, shadowman, size_r, 102400); } #define DEBIAN_ICON "/usr/share/pixmaps/debian-logo.png" static char * -icon_debian (guestfs_h *g, struct inspect_fs *fs, size_t *size_r) +icon_debian (guestfs_h *g, size_t *size_r) { - return get_png (g, fs, DEBIAN_ICON, size_r, 2048); + return get_png (g, DEBIAN_ICON, size_r, 2048); } static char * -icon_ubuntu (guestfs_h *g, struct inspect_fs *fs, size_t *size_r) +icon_ubuntu (guestfs_h *g, size_t *size_r) { const char *icons[] = { "/usr/share/icons/gnome/24x24/places/ubuntu-logo.png", @@ -366,7 +327,7 @@ icon_ubuntu (guestfs_h *g, struct inspect_fs *fs, size_t *size_r) char *ret; for (i = 0; icons[i] != NULL; ++i) { - ret = get_png (g, fs, icons[i], size_r, 2048); + ret = get_png (g, icons[i], size_r, 2048); if (ret == NULL) return NULL; if (ret != NOT_FOUND) @@ -378,17 +339,17 @@ icon_ubuntu (guestfs_h *g, struct inspect_fs *fs, size_t *size_r) #define MAGEIA_ICON "/usr/share/icons/mageia.png" static char * -icon_mageia (guestfs_h *g, struct inspect_fs *fs, size_t *size_r) +icon_mageia (guestfs_h *g, size_t *size_r) { - return get_png (g, fs, MAGEIA_ICON, size_r, 2048); + return get_png (g, MAGEIA_ICON, size_r, 2048); } #define OPENSUSE_ICON "/usr/share/icons/hicolor/24x24/apps/distributor.png" static char * -icon_opensuse (guestfs_h *g, struct inspect_fs *fs, size_t *size_r) +icon_opensuse (guestfs_h *g, size_t *size_r) { - return get_png (g, fs, OPENSUSE_ICON, size_r, 2048); + return get_png (g, OPENSUSE_ICON, size_r, 2048); } #if CAN_DO_CIRROS @@ -397,7 +358,7 @@ icon_opensuse (guestfs_h *g, struct inspect_fs *fs, size_t *size_r) #define CIRROS_LOGO "/usr/share/cirros/logo" static char * -icon_cirros (guestfs_h *g, struct inspect_fs *fs, size_t *size_r) +icon_cirros (guestfs_h *g, size_t *size_r) { char *ret; CLEANUP_FREE char *type = NULL; @@ -421,7 +382,7 @@ icon_cirros (guestfs_h *g, struct inspect_fs *fs, size_t *size_r) if (!STRPREFIX (type, "ASCII text")) return NOT_FOUND; - local = guestfs_int_download_to_tmp (g, fs, CIRROS_LOGO, "icon", 1024); + local = guestfs_int_download_to_tmp (g, CIRROS_LOGO, "icon", 1024); if (!local) return NOT_FOUND; @@ -450,17 +411,17 @@ icon_cirros (guestfs_h *g, struct inspect_fs *fs, size_t *size_r) #define VOIDLINUX_ICON "/usr/share/void-artwork/void-logo.png" static char * -icon_voidlinux (guestfs_h *g, struct inspect_fs *fs, size_t *size_r) +icon_voidlinux (guestfs_h *g, size_t *size_r) { - return get_png (g, fs, VOIDLINUX_ICON, size_r, 20480); + return get_png (g, VOIDLINUX_ICON, size_r, 20480); } #define ALTLINUX_ICON "/usr/share/icons/hicolor/48x48/apps/altlinux.png" static char * -icon_altlinux (guestfs_h *g, struct inspect_fs *fs, size_t *size_r) +icon_altlinux (guestfs_h *g, size_t *size_r) { - return get_png (g, fs, ALTLINUX_ICON, size_r, 20480); + return get_png (g, ALTLINUX_ICON, size_r, 20480); } #if CAN_DO_WINDOWS @@ -481,7 +442,7 @@ icon_altlinux (guestfs_h *g, struct inspect_fs *fs, size_t *size_r) */ static char * -icon_windows_xp (guestfs_h *g, struct inspect_fs *fs, size_t *size_r) +icon_windows_xp (guestfs_h *g, const char *systemroot, size_t *size_r) { CLEANUP_FREE char *filename = NULL; CLEANUP_FREE char *filename_case = NULL; @@ -492,7 +453,7 @@ icon_windows_xp (guestfs_h *g, struct inspect_fs *fs, size_t *size_r) char *ret; /* Download %systemroot%\explorer.exe */ - filename = safe_asprintf (g, "%s/explorer.exe", fs->windows_systemroot); + filename = safe_asprintf (g, "%s/explorer.exe", systemroot); filename_case = guestfs_case_sensitive_path (g, filename); if (filename_case == NULL) return NULL; @@ -505,7 +466,7 @@ icon_windows_xp (guestfs_h *g, struct inspect_fs *fs, size_t *size_r) if (r == 0) return NOT_FOUND; - filename_downloaded = guestfs_int_download_to_tmp (g, fs, filename_case, + filename_downloaded = guestfs_int_download_to_tmp (g, filename_case, "explorer.exe", MAX_WINDOWS_EXPLORER_SIZE); if (filename_downloaded == NULL) @@ -543,7 +504,7 @@ static const char *win7_explorer[] = { }; static char * -icon_windows_7 (guestfs_h *g, struct inspect_fs *fs, size_t *size_r) +icon_windows_7 (guestfs_h *g, const char *systemroot, size_t *size_r) { size_t i; CLEANUP_FREE char *filename_case = NULL; @@ -556,11 +517,10 @@ icon_windows_7 (guestfs_h *g, struct inspect_fs *fs, size_t *size_r) for (i = 0; win7_explorer[i] != NULL; ++i) { CLEANUP_FREE char *filename = NULL; - filename = safe_asprintf (g, "%s/%s", - fs->windows_systemroot, win7_explorer[i]); + filename = safe_asprintf (g, "%s/%s", systemroot, win7_explorer[i]); free (filename_case); - filename_case = guestfs_int_case_sensitive_path_silently (g, filename); + filename_case = case_sensitive_path_silently (g, filename); if (filename_case == NULL) continue; @@ -575,7 +535,7 @@ icon_windows_7 (guestfs_h *g, struct inspect_fs *fs, size_t *size_r) if (win7_explorer[i] == NULL) return NOT_FOUND; - filename_downloaded = guestfs_int_download_to_tmp (g, fs, filename_case, + filename_downloaded = guestfs_int_download_to_tmp (g, filename_case, "explorer.exe", MAX_WINDOWS_EXPLORER_SIZE); if (filename_downloaded == NULL) @@ -609,14 +569,14 @@ icon_windows_7 (guestfs_h *g, struct inspect_fs *fs, size_t *size_r) * - /Windows/System32/slui.exe --type=14 group icon #2 */ static char * -icon_windows_8 (guestfs_h *g, struct inspect_fs *fs, size_t *size_r) +icon_windows_8 (guestfs_h *g, size_t *size_r) { CLEANUP_FREE char *filename_case = NULL; CLEANUP_FREE char *filename_downloaded = NULL; int r; char *ret; - filename_case = guestfs_int_case_sensitive_path_silently + filename_case = case_sensitive_path_silently (g, "/ProgramData/Microsoft/Windows Live/WLive48x48.png"); if (filename_case == NULL) return NOT_FOUND; /* Not an error since a parent dir might not exist. */ @@ -629,7 +589,7 @@ icon_windows_8 (guestfs_h *g, struct inspect_fs *fs, size_t *size_r) if (r == 0) return NOT_FOUND; - filename_downloaded = guestfs_int_download_to_tmp (g, fs, filename_case, + filename_downloaded = guestfs_int_download_to_tmp (g, filename_case, "wlive48x48.png", 8192); if (filename_downloaded == NULL) return NOT_FOUND; @@ -641,25 +601,46 @@ icon_windows_8 (guestfs_h *g, struct inspect_fs *fs, size_t *size_r) } static char * -icon_windows (guestfs_h *g, struct inspect_fs *fs, size_t *size_r) +icon_windows (guestfs_h *g, const char *root, size_t *size_r) { - if (fs->windows_systemroot == NULL) + CLEANUP_FREE char *systemroot + guestfs_inspect_get_windows_systemroot (g, root); + int major = guestfs_inspect_get_major_version (g, root); + int minor = guestfs_inspect_get_minor_version (g, root); + + if (systemroot == NULL) return NOT_FOUND; /* Windows XP. */ - if (fs->version.v_major == 5 && fs->version.v_minor == 1) - return icon_windows_xp (g, fs, size_r); + if (major == 5 && minor == 1) + return icon_windows_xp (g, systemroot, size_r); /* Windows 7. */ - else if (fs->version.v_major == 6 && fs->version.v_minor == 1) - return icon_windows_7 (g, fs, size_r); + else if (major == 6 && minor == 1) + return icon_windows_7 (g, systemroot, size_r); /* Windows 8. */ - else if (fs->version.v_major == 6 && fs->version.v_minor == 2) - return icon_windows_8 (g, fs, size_r); + else if (major == 6 && minor == 2) + return icon_windows_8 (g, size_r); /* Not (yet) a supported version of Windows. */ else return NOT_FOUND; } #endif /* CAN_DO_WINDOWS */ + +/* NB: This function DOES NOT test for the existence of the file. It + * will return non-NULL even if the file/directory does not exist. + * You have to call guestfs_is_file{,_opts} etc. + */ +static char * +case_sensitive_path_silently (guestfs_h *g, const char *path) +{ + char *ret; + + guestfs_push_error_handler (g, NULL, NULL); + ret = guestfs_case_sensitive_path (g, path); + guestfs_pop_error_handler (g); + + return ret; +} diff --git a/lib/inspect.c b/lib/inspect.c index 1cc0942f1..f2d64b61e 100644 --- a/lib/inspect.c +++ b/lib/inspect.c @@ -43,688 +43,6 @@ #include "guestfs-internal.h" #include "guestfs-internal-actions.h" -COMPILE_REGEXP (re_primary_partition, "^/dev/(?:h|s|v)d.[1234]$", 0) - -static void check_for_duplicated_bsd_root (guestfs_h *g); -static void collect_coreos_inspection_info (guestfs_h *g); -static void collect_linux_inspection_info (guestfs_h *g); -static void collect_linux_inspection_info_for (guestfs_h *g, struct inspect_fs *root); - -/** - * The main inspection API. - */ -char ** -guestfs_impl_inspect_os (guestfs_h *g) -{ - CLEANUP_FREE_STRING_LIST char **fses = NULL; - char **fs, **ret; - - /* Remove any information previously stored in the handle. */ - guestfs_int_free_inspect_info (g); - - if (guestfs_umount_all (g) == -1) - return NULL; - - /* Iterate over all detected filesystems. Inspect each one in turn - * and add that information to the handle. - */ - - fses = guestfs_list_filesystems (g); - if (fses == NULL) return NULL; - - for (fs = fses; *fs; fs += 2) { - if (guestfs_int_check_for_filesystem_on (g, *fs)) { - guestfs_int_free_inspect_info (g); - return NULL; - } - } - - /* The OS inspection information for CoreOS are gathered by inspecting - * multiple filesystems. Gather all the inspected information in the - * inspect_fs struct of the root filesystem. - */ - collect_coreos_inspection_info (g); - - /* Check if the same filesystem was listed twice as root in g->fses. - * This may happen for the *BSD root partition where an MBR partition - * is a shadow of the real root partition probably /dev/sda5 - */ - check_for_duplicated_bsd_root (g); - - /* For Linux guests with a separate /usr filesyste, merge some of the - * inspected information in that partition to the inspect_fs struct - * of the root filesystem. - */ - collect_linux_inspection_info (g); - - /* At this point we have, in the handle, a list of all filesystems - * found and data about each one. Now we assemble the list of - * filesystems which are root devices and return that to the user. - * Fall through to guestfs_inspect_get_roots to do that. - */ - ret = guestfs_inspect_get_roots (g); - if (ret == NULL) - guestfs_int_free_inspect_info (g); - return ret; -} - -/** - * Traverse through the filesystem list and find out if it contains - * the C</> and C</usr> filesystems of a CoreOS image. If this is the - * case, sum up all the collected information on the root fs. - */ -static void -collect_coreos_inspection_info (guestfs_h *g) -{ - size_t i; - struct inspect_fs *root = NULL, *usr = NULL; - - for (i = 0; i < g->nr_fses; ++i) { - struct inspect_fs *fs = &g->fses[i]; - - if (fs->distro == OS_DISTRO_COREOS && fs->role == OS_ROLE_ROOT) - root = fs; - } - - if (root == NULL) - return; - - for (i = 0; i < g->nr_fses; ++i) { - struct inspect_fs *fs = &g->fses[i]; - - if (fs->distro != OS_DISTRO_COREOS || fs->role != OS_ROLE_USR) - continue; - - /* CoreOS is designed to contain 2 /usr partitions (USR-A, USR-B): - * https://coreos.com/docs/sdk-distributors/sdk/disk-partitions/ - * One is active and one passive. During the initial boot, the passive - * partition is empty and it gets filled up when an update is performed. - * Then, when the system reboots, the boot loader is instructed to boot - * from the passive partition. If both partitions are valid, we cannot - * determine which the active and which the passive is, unless we peep into - * the boot loader. As a workaround, we check the OS versions and pick the - * one with the higher version as active. - */ - if (usr && guestfs_int_version_cmp_ge (&usr->version, &fs->version)) - continue; - - usr = fs; - } - - if (usr == NULL) - return; - - guestfs_int_merge_fs_inspections (g, root, usr); -} - -/** - * Traverse through the filesystems and find the /usr filesystem for - * the specified C<root>: if found, merge its basic inspection details - * to the root when they were set (i.e. because the /usr had os-release - * or other ways to identify the OS). - */ -static void -collect_linux_inspection_info_for (guestfs_h *g, struct inspect_fs *root) -{ - size_t i; - struct inspect_fs *usr = NULL; - - for (i = 0; i < g->nr_fses; ++i) { - struct inspect_fs *fs = &g->fses[i]; - size_t j; - - if (!(fs->distro == root->distro || fs->distro == OS_DISTRO_UNKNOWN) || - fs->role != OS_ROLE_USR) - continue; - - for (j = 0; j < root->nr_fstab; ++j) { - if (STREQ (fs->mountable, root->fstab[j].mountable)) { - usr = fs; - goto got_usr; - } - } - } - - assert (usr == NULL); - return; - - got_usr: - /* If the version information in /usr is not null, then most probably - * there was an os-release file there, so reset what is in root - * and pick the results from /usr. - */ - if (!version_is_null (&usr->version)) { - root->distro = OS_DISTRO_UNKNOWN; - free (root->product_name); - root->product_name = NULL; - } - - guestfs_int_merge_fs_inspections (g, root, usr); -} - -/** - * Traverse through the filesystem list and find out if it contains - * the C</> and C</usr> filesystems of a Linux image (but not CoreOS, - * for which there is a separate C<collect_coreos_inspection_info>). - * If this is the case, sum up all the collected information on each - * root fs from the respective /usr filesystems. - */ -static void -collect_linux_inspection_info (guestfs_h *g) -{ - size_t i; - - for (i = 0; i < g->nr_fses; ++i) { - struct inspect_fs *fs = &g->fses[i]; - - if (fs->distro != OS_DISTRO_COREOS && fs->role == OS_ROLE_ROOT) - collect_linux_inspection_info_for (g, fs); - } -} - -/** - * On *BSD systems, sometimes F</dev/sda[1234]> is a shadow of the - * real root filesystem that is probably F</dev/sda5> (see: - * L<http://www.freebsd.org/doc/handbook/disk-organization.html>) - */ -static void -check_for_duplicated_bsd_root (guestfs_h *g) -{ - size_t i; - struct inspect_fs *bsd_primary = NULL; - - for (i = 0; i < g->nr_fses; ++i) { - bool is_bsd; - struct inspect_fs *fs = &g->fses[i]; - - is_bsd - fs->type == OS_TYPE_FREEBSD || - fs->type == OS_TYPE_NETBSD || - fs->type == OS_TYPE_OPENBSD; - - if (fs->role == OS_ROLE_ROOT && is_bsd && - match (g, fs->mountable, re_primary_partition)) { - bsd_primary = fs; - continue; - } - - if (fs->role == OS_ROLE_ROOT && bsd_primary && - bsd_primary->type == fs->type) { - /* remove the root role from the bsd_primary */ - bsd_primary->role = OS_ROLE_UNKNOWN; - bsd_primary->format = OS_FORMAT_UNKNOWN; - return; - } - } -} - -static int -compare_strings (const void *vp1, const void *vp2) -{ - const char *s1 = * (char * const *) vp1; - const char *s2 = * (char * const *) vp2; - - return strcmp (s1, s2); -} - -char ** -guestfs_impl_inspect_get_roots (guestfs_h *g) -{ - size_t i; - DECLARE_STRINGSBUF (ret); - - /* NB. Doesn't matter if g->nr_fses == 0. We just return an empty - * list in this case. - */ - for (i = 0; i < g->nr_fses; ++i) { - if (g->fses[i].role == OS_ROLE_ROOT) - guestfs_int_add_string (g, &ret, g->fses[i].mountable); - } - guestfs_int_end_stringsbuf (g, &ret); - - qsort (ret.argv, ret.size-1, sizeof (char *), compare_strings); - - return ret.argv; -} - -char * -guestfs_impl_inspect_get_type (guestfs_h *g, const char *root) -{ - struct inspect_fs *fs = guestfs_int_search_for_root (g, root); - char *ret = NULL; - - if (!fs) - return NULL; - - switch (fs->type) { - case OS_TYPE_DOS: ret = safe_strdup (g, "dos"); break; - case OS_TYPE_FREEBSD: ret = safe_strdup (g, "freebsd"); break; - case OS_TYPE_HURD: ret = safe_strdup (g, "hurd"); break; - case OS_TYPE_LINUX: ret = safe_strdup (g, "linux"); break; - case OS_TYPE_MINIX: ret = safe_strdup (g, "minix"); break; - case OS_TYPE_NETBSD: ret = safe_strdup (g, "netbsd"); break; - case OS_TYPE_OPENBSD: ret = safe_strdup (g, "openbsd"); break; - case OS_TYPE_WINDOWS: ret = safe_strdup (g, "windows"); break; - case OS_TYPE_UNKNOWN: ret = safe_strdup (g, "unknown"); break; - } - - if (ret == NULL) - abort (); - - return ret; -} - -char * -guestfs_impl_inspect_get_arch (guestfs_h *g, const char *root) -{ - struct inspect_fs *fs = guestfs_int_search_for_root (g, root); - if (!fs) - return NULL; - - return safe_strdup (g, fs->arch ? : "unknown"); -} - -char * -guestfs_impl_inspect_get_distro (guestfs_h *g, const char *root) -{ - struct inspect_fs *fs = guestfs_int_search_for_root (g, root); - char *ret = NULL; - - if (!fs) - return NULL; - - switch (fs->distro) { - case OS_DISTRO_ALPINE_LINUX: ret = safe_strdup (g, "alpinelinux"); break; - case OS_DISTRO_ALTLINUX: ret = safe_strdup (g, "altlinux"); break; - case OS_DISTRO_ARCHLINUX: ret = safe_strdup (g, "archlinux"); break; - case OS_DISTRO_BUILDROOT: ret = safe_strdup (g, "buildroot"); break; - case OS_DISTRO_CENTOS: ret = safe_strdup (g, "centos"); break; - case OS_DISTRO_CIRROS: ret = safe_strdup (g, "cirros"); break; - case OS_DISTRO_COREOS: ret = safe_strdup (g, "coreos"); break; - case OS_DISTRO_DEBIAN: ret = safe_strdup (g, "debian"); break; - case OS_DISTRO_FEDORA: ret = safe_strdup (g, "fedora"); break; - case OS_DISTRO_FREEBSD: ret = safe_strdup (g, "freebsd"); break; - case OS_DISTRO_FREEDOS: ret = safe_strdup (g, "freedos"); break; - case OS_DISTRO_FRUGALWARE: ret = safe_strdup (g, "frugalware"); break; - case OS_DISTRO_GENTOO: ret = safe_strdup (g, "gentoo"); break; - case OS_DISTRO_LINUX_MINT: ret = safe_strdup (g, "linuxmint"); break; - case OS_DISTRO_MAGEIA: ret = safe_strdup (g, "mageia"); break; - case OS_DISTRO_MANDRIVA: ret = safe_strdup (g, "mandriva"); break; - case OS_DISTRO_MEEGO: ret = safe_strdup (g, "meego"); break; - case OS_DISTRO_NETBSD: ret = safe_strdup (g, "netbsd"); break; - case OS_DISTRO_OPENBSD: ret = safe_strdup (g, "openbsd"); break; - case OS_DISTRO_OPENSUSE: ret = safe_strdup (g, "opensuse"); break; - case OS_DISTRO_ORACLE_LINUX: ret = safe_strdup (g, "oraclelinux"); break; - case OS_DISTRO_PARDUS: ret = safe_strdup (g, "pardus"); break; - case OS_DISTRO_PLD_LINUX: ret = safe_strdup (g, "pldlinux"); break; - case OS_DISTRO_REDHAT_BASED: ret = safe_strdup (g, "redhat-based"); break; - case OS_DISTRO_RHEL: ret = safe_strdup (g, "rhel"); break; - case OS_DISTRO_SCIENTIFIC_LINUX: ret = safe_strdup (g, "scientificlinux"); break; - case OS_DISTRO_SLACKWARE: ret = safe_strdup (g, "slackware"); break; - case OS_DISTRO_SLES: ret = safe_strdup (g, "sles"); break; - case OS_DISTRO_SUSE_BASED: ret = safe_strdup (g, "suse-based"); break; - case OS_DISTRO_TTYLINUX: ret = safe_strdup (g, "ttylinux"); break; - case OS_DISTRO_WINDOWS: ret = safe_strdup (g, "windows"); break; - case OS_DISTRO_UBUNTU: ret = safe_strdup (g, "ubuntu"); break; - case OS_DISTRO_VOID_LINUX: ret = safe_strdup (g, "voidlinux"); break; - case OS_DISTRO_UNKNOWN: ret = safe_strdup (g, "unknown"); break; - } - - if (ret == NULL) - abort (); - - return ret; -} - -int -guestfs_impl_inspect_get_major_version (guestfs_h *g, const char *root) -{ - struct inspect_fs *fs = guestfs_int_search_for_root (g, root); - if (!fs) - return -1; - - return fs->version.v_major; -} - -int -guestfs_impl_inspect_get_minor_version (guestfs_h *g, const char *root) -{ - struct inspect_fs *fs = guestfs_int_search_for_root (g, root); - if (!fs) - return -1; - - return fs->version.v_minor; -} - -char * -guestfs_impl_inspect_get_product_name (guestfs_h *g, const char *root) -{ - struct inspect_fs *fs = guestfs_int_search_for_root (g, root); - if (!fs) - return NULL; - - return safe_strdup (g, fs->product_name ? : "unknown"); -} - -char * -guestfs_impl_inspect_get_product_variant (guestfs_h *g, const char *root) -{ - struct inspect_fs *fs = guestfs_int_search_for_root (g, root); - if (!fs) - return NULL; - - return safe_strdup (g, fs->product_variant ? : "unknown"); -} - -char * -guestfs_impl_inspect_get_windows_systemroot (guestfs_h *g, const char *root) -{ - struct inspect_fs *fs = guestfs_int_search_for_root (g, root); - if (!fs) - return NULL; - - if (!fs->windows_systemroot) { - error (g, _("not a Windows guest, or systemroot could not be determined")); - return NULL; - } - - return safe_strdup (g, fs->windows_systemroot); -} - -char * -guestfs_impl_inspect_get_windows_software_hive (guestfs_h *g, const char *root) -{ - struct inspect_fs *fs = guestfs_int_search_for_root (g, root); - if (!fs) - return NULL; - - if (!fs->windows_software_hive) { - error (g, _("not a Windows guest, or software hive not found")); - return NULL; - } - - return safe_strdup (g, fs->windows_software_hive); -} - -char * -guestfs_impl_inspect_get_windows_system_hive (guestfs_h *g, const char *root) -{ - struct inspect_fs *fs = guestfs_int_search_for_root (g, root); - if (!fs) - return NULL; - - if (!fs->windows_system_hive) { - error (g, _("not a Windows guest, or system hive not found")); - return NULL; - } - - return safe_strdup (g, fs->windows_system_hive); -} - -char * -guestfs_impl_inspect_get_windows_current_control_set (guestfs_h *g, - const char *root) -{ - struct inspect_fs *fs = guestfs_int_search_for_root (g, root); - if (!fs) - return NULL; - - if (!fs->windows_current_control_set) { - error (g, _("not a Windows guest, or CurrentControlSet could not be determined")); - return NULL; - } - - return safe_strdup (g, fs->windows_current_control_set); -} - -char * -guestfs_impl_inspect_get_format (guestfs_h *g, const char *root) -{ - char *ret = NULL; - struct inspect_fs *fs = guestfs_int_search_for_root (g, root); - if (!fs) - return NULL; - - switch (fs->format) { - case OS_FORMAT_INSTALLED: ret = safe_strdup (g, "installed"); break; - case OS_FORMAT_INSTALLER: ret = safe_strdup (g, "installer"); break; - case OS_FORMAT_UNKNOWN: ret = safe_strdup (g, "unknown"); break; - } - - if (ret == NULL) - abort (); - - return ret; -} - -int -guestfs_impl_inspect_is_live (guestfs_h *g, const char *root) -{ - struct inspect_fs *fs = guestfs_int_search_for_root (g, root); - if (!fs) - return -1; - - return fs->is_live_disk; -} - -int -guestfs_impl_inspect_is_netinst (guestfs_h *g, const char *root) -{ - struct inspect_fs *fs = guestfs_int_search_for_root (g, root); - if (!fs) - return -1; - - return fs->is_netinst_disk; -} - -int -guestfs_impl_inspect_is_multipart (guestfs_h *g, const char *root) -{ - struct inspect_fs *fs = guestfs_int_search_for_root (g, root); - if (!fs) - return -1; - - return fs->is_multipart_disk; -} - -char ** -guestfs_impl_inspect_get_mountpoints (guestfs_h *g, const char *root) -{ - char **ret; - size_t i, count, nr; - struct inspect_fs *fs; - - fs = guestfs_int_search_for_root (g, root); - if (!fs) - return NULL; - -#define CRITERION(fs, i) fs->fstab[i].mountpoint[0] == '/' - - nr = fs->nr_fstab; - - if (nr == 0) - count = 1; - else { - count = 0; - for (i = 0; i < nr; ++i) - if (CRITERION (fs, i)) - count++; - } - - /* Hashtables have 2N+1 entries. */ - ret = calloc (2*count+1, sizeof (char *)); - if (ret == NULL) { - perrorf (g, "calloc"); - return NULL; - } - - /* If no fstab information (Windows) return just the root. */ - if (nr == 0) { - ret[0] = safe_strdup (g, "/"); - ret[1] = safe_strdup (g, root); - ret[2] = NULL; - return ret; - } - - count = 0; - for (i = 0; i < nr; ++i) - if (CRITERION (fs, i)) { - ret[2*count] = safe_strdup (g, fs->fstab[i].mountpoint); - ret[2*count+1] = safe_strdup (g, fs->fstab[i].mountable); - count++; - } -#undef CRITERION - - return ret; -} - -char ** -guestfs_impl_inspect_get_filesystems (guestfs_h *g, const char *root) -{ - char **ret; - size_t i, nr; - struct inspect_fs *fs = guestfs_int_search_for_root (g, root); - - if (!fs) - return NULL; - - nr = fs->nr_fstab; - ret = calloc (nr == 0 ? 2 : nr+1, sizeof (char *)); - if (ret == NULL) { - perrorf (g, "calloc"); - return NULL; - } - - /* If no fstab information (Windows) return just the root. */ - if (nr == 0) { - ret[0] = safe_strdup (g, root); - ret[1] = NULL; - return ret; - } - - for (i = 0; i < nr; ++i) - ret[i] = safe_strdup (g, fs->fstab[i].mountable); - - return ret; -} - -char ** -guestfs_impl_inspect_get_drive_mappings (guestfs_h *g, const char *root) -{ - DECLARE_STRINGSBUF (ret); - size_t i; - struct inspect_fs *fs; - - fs = guestfs_int_search_for_root (g, root); - if (!fs) - return NULL; - - if (fs->drive_mappings) { - for (i = 0; fs->drive_mappings[i] != NULL; ++i) - guestfs_int_add_string (g, &ret, fs->drive_mappings[i]); - } - - guestfs_int_end_stringsbuf (g, &ret); - return ret.argv; -} - -char * -guestfs_impl_inspect_get_package_format (guestfs_h *g, const char *root) -{ - char *ret = NULL; - struct inspect_fs *fs = guestfs_int_search_for_root (g, root); - if (!fs) - return NULL; - - switch (fs->package_format) { - case OS_PACKAGE_FORMAT_RPM: ret = safe_strdup (g, "rpm"); break; - case OS_PACKAGE_FORMAT_DEB: ret = safe_strdup (g, "deb"); break; - case OS_PACKAGE_FORMAT_PACMAN: ret = safe_strdup (g, "pacman"); break; - case OS_PACKAGE_FORMAT_EBUILD: ret = safe_strdup (g, "ebuild"); break; - case OS_PACKAGE_FORMAT_PISI: ret = safe_strdup (g, "pisi"); break; - case OS_PACKAGE_FORMAT_PKGSRC: ret = safe_strdup (g, "pkgsrc"); break; - case OS_PACKAGE_FORMAT_APK: ret = safe_strdup (g, "apk"); break; - case OS_PACKAGE_FORMAT_XBPS: ret = safe_strdup (g, "xbps"); break; - case OS_PACKAGE_FORMAT_UNKNOWN: - ret = safe_strdup (g, "unknown"); - break; - } - - if (ret == NULL) - abort (); - - return ret; -} - -char * -guestfs_impl_inspect_get_package_management (guestfs_h *g, const char *root) -{ - char *ret = NULL; - struct inspect_fs *fs = guestfs_int_search_for_root (g, root); - if (!fs) - return NULL; - - switch (fs->package_management) { - case OS_PACKAGE_MANAGEMENT_APK: ret = safe_strdup (g, "apk"); break; - case OS_PACKAGE_MANAGEMENT_APT: ret = safe_strdup (g, "apt"); break; - case OS_PACKAGE_MANAGEMENT_DNF: ret = safe_strdup (g, "dnf"); break; - case OS_PACKAGE_MANAGEMENT_PACMAN: ret = safe_strdup (g, "pacman"); break; - case OS_PACKAGE_MANAGEMENT_PISI: ret = safe_strdup (g, "pisi"); break; - case OS_PACKAGE_MANAGEMENT_PORTAGE: ret = safe_strdup (g, "portage"); break; - case OS_PACKAGE_MANAGEMENT_UP2DATE: ret = safe_strdup (g, "up2date"); break; - case OS_PACKAGE_MANAGEMENT_URPMI: ret = safe_strdup (g, "urpmi"); break; - case OS_PACKAGE_MANAGEMENT_XBPS: ret = safe_strdup (g, "xbps"); break; - case OS_PACKAGE_MANAGEMENT_YUM: ret = safe_strdup (g, "yum"); break; - case OS_PACKAGE_MANAGEMENT_ZYPPER: ret = safe_strdup (g, "zypper"); break; - case OS_PACKAGE_MANAGEMENT_UNKNOWN: - ret = safe_strdup (g, "unknown"); - break; - } - - if (ret == NULL) - abort (); - - return ret; -} - -char * -guestfs_impl_inspect_get_hostname (guestfs_h *g, const char *root) -{ - struct inspect_fs *fs = guestfs_int_search_for_root (g, root); - if (!fs) - return NULL; - - return safe_strdup (g, fs->hostname ? : "unknown"); -} - -void -guestfs_int_free_inspect_info (guestfs_h *g) -{ - size_t i, j; - - for (i = 0; i < g->nr_fses; ++i) { - free (g->fses[i].mountable); - free (g->fses[i].product_name); - free (g->fses[i].product_variant); - free (g->fses[i].arch); - free (g->fses[i].hostname); - free (g->fses[i].windows_systemroot); - free (g->fses[i].windows_software_hive); - free (g->fses[i].windows_system_hive); - free (g->fses[i].windows_current_control_set); - for (j = 0; j < g->fses[i].nr_fstab; ++j) { - free (g->fses[i].fstab[j].mountable); - free (g->fses[i].fstab[j].mountpoint); - } - free (g->fses[i].fstab); - if (g->fses[i].drive_mappings) - guestfs_int_free_string_list (g->fses[i].drive_mappings); - } - free (g->fses); - g->nr_fses = 0; - g->fses = NULL; -} - /** * Download a guest file to a local temporary file. The file is * cached in the temporary directory, and is not downloaded again. @@ -740,7 +58,7 @@ guestfs_int_free_inspect_info (guestfs_h *g) * handle the case of multiple roots. */ char * -guestfs_int_download_to_tmp (guestfs_h *g, struct inspect_fs *fs, +guestfs_int_download_to_tmp (guestfs_h *g, const char *filename, const char *basename, uint64_t max_size) { @@ -749,10 +67,7 @@ guestfs_int_download_to_tmp (guestfs_h *g, struct inspect_fs *fs, char devfd[32]; int64_t size; - /* Make the basename unique by prefixing it with the fs number. - * This also ensures there is one cache per filesystem. - */ - if (asprintf (&r, "%s/%td-%s", g->tmpdir, fs - g->fses, basename) == -1) { + if (asprintf (&r, "%s/%s", g->tmpdir, basename) == -1) { perrorf (g, "asprintf"); return NULL; } @@ -798,46 +113,3 @@ guestfs_int_download_to_tmp (guestfs_h *g, struct inspect_fs *fs, free (r); return NULL; } - -struct inspect_fs * -guestfs_int_search_for_root (guestfs_h *g, const char *root) -{ - size_t i; - - if (g->nr_fses == 0) { - error (g, _("no inspection data: call guestfs_inspect_os first")); - return NULL; - } - - for (i = 0; i < g->nr_fses; ++i) { - struct inspect_fs *fs = &g->fses[i]; - if (fs->role == OS_ROLE_ROOT && STREQ (root, fs->mountable)) - return fs; - } - - error (g, _("%s: root device not found: only call this function with a root device previously returned by guestfs_inspect_os"), - root); - return NULL; -} - -int -guestfs_int_is_partition (guestfs_h *g, const char *partition) -{ - CLEANUP_FREE char *device = NULL; - - guestfs_push_error_handler (g, NULL, NULL); - - if ((device = guestfs_part_to_dev (g, partition)) == NULL) { - guestfs_pop_error_handler (g); - return 0; - } - - if (guestfs_device_index (g, device) == -1) { - guestfs_pop_error_handler (g); - return 0; - } - - guestfs_pop_error_handler (g); - - return 1; -} diff --git a/lib/version.c b/lib/version.c index 60ffe1e89..86bb0b1f0 100644 --- a/lib/version.c +++ b/lib/version.c @@ -24,14 +24,42 @@ #include <string.h> #include <unistd.h> +#include <libintl.h> #include "ignore-value.h" +#include "xstrtol.h" #include "guestfs.h" #include "guestfs-internal.h" COMPILE_REGEXP (re_major_minor, "(\\d+)\\.(\\d+)", 0) +/* Parse small, unsigned ints, as used in version numbers. */ +int +guestfs_int_parse_unsigned_int (guestfs_h *g, const char *str) +{ + long ret; + const int r = xstrtol (str, NULL, 10, &ret, ""); + if (r != LONGINT_OK) { + error (g, _("could not parse integer in version number: %s"), str); + return -1; + } + return ret; +} + +/* Like parse_unsigned_int, but ignore trailing stuff. */ +int +guestfs_int_parse_unsigned_int_ignore_trailing (guestfs_h *g, const char *str) +{ + long ret; + const int r = xstrtol (str, NULL, 10, &ret, NULL); + if (r != LONGINT_OK) { + error (g, _("could not parse integer in version number: %s"), str); + return -1; + } + return ret; +} + static int version_from_x_y_or_x (guestfs_h *g, struct version *v, const char *str, const pcre *re, bool allow_only_x); void -- 2.13.0
Apparently Analagous Threads
- [PATCH v12 00/11] Reimplement inspection in the daemon.
- [PATCH v10 00/10] Reimplement inspection in the daemon.
- [PATCH v9 00/11] Reimplement inspection in the daemon.
- [PATCH v11 00/10] Reimplement inspection in the daemon.
- [PATCH v8 00/42] Refactor utilities and reimplement inspection.