Richard W.M. Jones
2017-Jul-17 16:55 UTC
[Libguestfs] [PATCH v9 00/11] Reimplement inspection in the daemon.
This depends on the patch series "[PATCH 00/27] Reimplement many daemon APIs in OCaml." (https://www.redhat.com/archives/libguestfs/2017-July/msg00098.html) v8 was posted here: https://www.redhat.com/archives/libguestfs/2017-June/msg00274.html v9: - I split up the mega-patch into a more reviewable series of smaller, incremental patches. There are some other changes vs v8, but they are insignificant cleanups and one bug fix. Rich.
Richard W.M. Jones
2017-Jul-17 16:55 UTC
[Libguestfs] [PATCH v9 01/11] 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 | 607 -------------------------- lib/inspect-fs.c | 40 -- lib/osinfo.c | 655 ----------------------------- 33 files changed, 63 insertions(+), 1708 deletions(-) diff --git a/docs/C_SOURCE_FILES b/docs/C_SOURCE_FILES index 3601c3d5d..a36163d6d 100644 --- a/docs/C_SOURCE_FILES +++ b/docs/C_SOURCE_FILES @@ -307,7 +307,6 @@ lib/handle.c lib/hivex.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 @@ -325,7 +324,6 @@ lib/libvirt-is-version.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 cab03107f..83b33ac8d 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 \ @@ -112,7 +111,6 @@ libguestfs_la_SOURCES = \ lpj.c \ match.c \ mountable.c \ - osinfo.c \ private-data.c \ proto.c \ qemu.c \ @@ -134,7 +132,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 ff430c6e4..ab4eaf7e3 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 b87210e65..917fb61a7 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 c9a4e219e..000000000 --- a/lib/inspect-fs-cd.c +++ /dev/null @@ -1,607 +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" -#include "structs-cleanups.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.2
Richard W.M. Jones
2017-Jul-17 16:55 UTC
[Libguestfs] [PATCH v9 02/11] daemon: Embed the ocaml-augeas library in the daemon.
This commit embeds the ocaml-augeas library (upstream here: http://git.annexia.org/?p=ocaml-augeas.git;a=summary). It's identical to the upstream version and should remain so. We can work towards using system ocaml-augeas, when it's more widely available. --- daemon/Makefile.am | 3 + daemon/augeas-c.c | 288 +++++++++++++++++++++++++++++++++++++++++++++++++++ daemon/augeas.README | 8 ++ daemon/augeas.ml | 59 +++++++++++ daemon/augeas.mli | 95 +++++++++++++++++ daemon/guestfsd.c | 2 + docs/C_SOURCE_FILES | 1 + 7 files changed, 456 insertions(+) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 8921f6239..9b7952879 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 \ @@ -274,6 +276,7 @@ SOURCES_MLI = \ utils.mli SOURCES_ML = \ + augeas.ml \ types.ml \ utils.ml \ structs.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/guestfsd.c b/daemon/guestfsd.c index dbc8fef45..e8f797868 100644 --- a/daemon/guestfsd.c +++ b/daemon/guestfsd.c @@ -1176,6 +1176,8 @@ ocaml_exn_to_reply_with_error (const char *func, value exn) 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 if (STREQ (exn_name, "Augeas.Error")) + reply_with_error ("augeas error: %s", String_val (Field (exn, 1))); else reply_with_error ("internal error: %s: unhandled exception thrown: %s", func, exn_name); diff --git a/docs/C_SOURCE_FILES b/docs/C_SOURCE_FILES index a36163d6d..bb9cc2941 100644 --- a/docs/C_SOURCE_FILES +++ b/docs/C_SOURCE_FILES @@ -65,6 +65,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 -- 2.13.2
Richard W.M. Jones
2017-Jul-17 16:55 UTC
[Libguestfs] [PATCH v9 03/11] daemon: utils: New functions unix_canonical_path, utf16le_to_utf8 and tests.
These utility functions will be used in the OCaml inspection code. --- daemon/daemon_utils_tests.ml | 15 +++++++ daemon/utils.ml | 100 +++++++++++++++++++++++++++++++++++++++++++ daemon/utils.mli | 12 ++++++ 3 files changed, 127 insertions(+) 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/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. *) -- 2.13.2
Richard W.M. Jones
2017-Jul-17 16:55 UTC
[Libguestfs] [PATCH v9 04/11] daemon: Implement umount_all in OCaml.
Unlike previous ‘daemon: Reimplement ...’ patches, this does not reimplement the umount_all API completely (yet, but this implementation could be completed in future and then replace the C one). However it is necessary to have a version of umount_all which we can call from the OCaml inspection code. --- daemon/mount.ml | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ daemon/mount.mli | 2 ++ 2 files changed, 63 insertions(+) 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 -- 2.13.2
Richard W.M. Jones
2017-Jul-17 16:55 UTC
[Libguestfs] [PATCH v9 05/11] generator: daemon: Implement RStringList (RMountable, _) and RHashtable (RPlainString, RMountable, _).
Implement returning these two types from OCaml daemon functions. --- generator/daemon.ml | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 2 deletions(-) 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 ); -- 2.13.2
Richard W.M. Jones
2017-Jul-17 16:55 UTC
[Libguestfs] [PATCH v9 06/11] lib: Move guestfs_int_parse_unsigned_int* functions.
Just code motion, no functional change. --- lib/guestfs-internal.h | 4 ++-- lib/inspect-fs.c | 26 -------------------------- lib/inspect.c | 27 +++++++++++++++++++++++++++ 3 files changed, 29 insertions(+), 28 deletions(-) diff --git a/lib/guestfs-internal.h b/lib/guestfs-internal.h index ab4eaf7e3..6dbaba9bd 100644 --- a/lib/guestfs-internal.h +++ b/lib/guestfs-internal.h @@ -856,6 +856,8 @@ extern int guestfs_int_set_backend (guestfs_h *g, const char *method); /* 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 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 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); @@ -864,8 +866,6 @@ 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); diff --git a/lib/inspect-fs.c b/lib/inspect-fs.c index e320b3e78..54f9a281a 100644 --- a/lib/inspect-fs.c +++ b/lib/inspect-fs.c @@ -370,32 +370,6 @@ guestfs_int_is_dir_nocase (guestfs_h *g, const char *path) 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) diff --git a/lib/inspect.c b/lib/inspect.c index 1cc0942f1..37d175873 100644 --- a/lib/inspect.c +++ b/lib/inspect.c @@ -38,6 +38,7 @@ #endif #include "ignore-value.h" +#include "xstrtol.h" #include "guestfs.h" #include "guestfs-internal.h" @@ -799,6 +800,32 @@ guestfs_int_download_to_tmp (guestfs_h *g, struct inspect_fs *fs, return NULL; } +/* 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; +} + struct inspect_fs * guestfs_int_search_for_root (guestfs_h *g, const char *root) { -- 2.13.2
Richard W.M. Jones
2017-Jul-17 16:55 UTC
[Libguestfs] [PATCH v9 07/11] lib: inspect: Remove ‘fs’ parameter from ‘guestfs_int_download_to_tmp’.
After we move inspection code to the daemon, the library will no longer have access to ‘struct inspect_fs’, and so we won't be able to prefix downloads with the "root filesystem number". Just remove this prefix (it's internal only). However it does mean that this function can no longer cache downloaded files. --- lib/guestfs-internal.h | 2 +- lib/inspect-apps.c | 10 +++++----- lib/inspect-icon.c | 10 +++++----- lib/inspect.c | 11 ++--------- 4 files changed, 13 insertions(+), 20 deletions(-) diff --git a/lib/guestfs-internal.h b/lib/guestfs-internal.h index 6dbaba9bd..585dac706 100644 --- a/lib/guestfs-internal.h +++ b/lib/guestfs-internal.h @@ -855,7 +855,7 @@ extern int guestfs_int_set_backend (guestfs_h *g, const char *method); /* 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 char *guestfs_int_download_to_tmp (guestfs_h *g, const char *filename, const char *basename, uint64_t max_size); 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 struct inspect_fs *guestfs_int_search_for_root (guestfs_h *g, const char *root); diff --git a/lib/inspect-apps.c b/lib/inspect-apps.c index 25192340c..3196a9d0b 100644 --- a/lib/inspect-apps.c +++ b/lib/inspect-apps.c @@ -393,13 +393,13 @@ list_applications_rpm (guestfs_h *g, struct inspect_fs *fs) 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) @@ -452,7 +452,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; @@ -628,7 +628,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, @@ -736,7 +736,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; diff --git a/lib/inspect-icon.c b/lib/inspect-icon.c index 89c232f5b..89f5da082 100644 --- a/lib/inspect-icon.c +++ b/lib/inspect-icon.c @@ -270,7 +270,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; @@ -421,7 +421,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; @@ -505,7 +505,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) @@ -575,7 +575,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) @@ -629,7 +629,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; diff --git a/lib/inspect.c b/lib/inspect.c index 37d175873..f4ad192a9 100644 --- a/lib/inspect.c +++ b/lib/inspect.c @@ -741,7 +741,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) { @@ -750,18 +750,11 @@ 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; } - /* If the file has already been downloaded, return. */ - if (access (r, R_OK) == 0) - return r; - /* Check size of remote file. */ size = guestfs_filesize (g, filename); if (size == -1) -- 2.13.2
Richard W.M. Jones
2017-Jul-17 16:55 UTC
[Libguestfs] [PATCH v9 08/11] daemon: Implement inspection types and utility functions.
Define the types which will be used to communicate between the different parts of the inspection code. The main types are: fs corresponds to ‘struct inspect_fs’ in C code root no direct correspondance with the C code, but in the C code, ‘inspect_fs’ was overloaded to store roots inspection_data the inspection data which is incrementally collected about each filesystem as we perform inspection steps Other types have simple and obvious correspondances with the equivalent C code. Add some utility function which will be used by inspection. Note that this commit has no effect on its own, it just links extra dead code into the daemon. --- daemon/Makefile.am | 4 + daemon/inspect_types.ml | 314 +++++++++++++++++++++++++++++++++++++++++++++++ daemon/inspect_types.mli | 182 +++++++++++++++++++++++++++ daemon/inspect_utils.ml | 193 +++++++++++++++++++++++++++++ daemon/inspect_utils.mli | 57 +++++++++ 5 files changed, 750 insertions(+) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 9b7952879..c92eba9cd 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -263,6 +263,8 @@ SOURCES_MLI = \ file.mli \ filearch.mli \ findfs.mli \ + inspect_types.mli \ + inspect_utils.mli \ is.mli \ ldm.mli \ link.mli \ @@ -298,6 +300,8 @@ SOURCES_ML = \ parted.ml \ listfs.ml \ realpath.ml \ + inspect_types.ml \ + inspect_utils.ml \ callbacks.ml \ daemon.ml diff --git a/daemon/inspect_types.ml b/daemon/inspect_types.ml new file mode 100644 index 000000000..4570349ba --- /dev/null +++ b/daemon/inspect_types.ml @@ -0,0 +1,314 @@ +(* 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 = { + mutable os_type : os_type option; + mutable distro : distro option; + mutable package_format : package_format option; + mutable package_management : package_management option; + mutable product_name : string option; + mutable product_variant : string option; + mutable version : version option; + mutable arch : string option; + mutable hostname : string option; + mutable fstab : fstab_entry list; + mutable windows_systemroot : string option; + mutable windows_software_hive : string option; + mutable windows_system_hive : string option; + mutable windows_current_control_set : string option; + mutable 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\n" + (string_of_location location) + (match role with + | RoleRoot data -> "root\n" ^ string_of_inspection_data data + | RoleUsr data -> "usr\n" ^ string_of_inspection_data data + | 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 " type: %s\n" (string_of_os_type v)) + data.os_type; + may (fun v -> bpf " distro: %s\n" (string_of_distro v)) + data.distro; + may (fun v -> bpf " package_format: %s\n" (string_of_package_format v)) + data.package_format; + may (fun v -> bpf " package_management: %s\n" (string_of_package_management v)) + data.package_management; + may (fun v -> bpf " product_name: %s\n" v) + data.product_name; + may (fun v -> bpf " product_variant: %s\n" v) + data.product_variant; + may (fun (major, minor) -> bpf " version: %d.%d\n" major minor) + data.version; + may (fun v -> bpf " arch: %s\n" v) + data.arch; + may (fun v -> bpf " hostname: %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 " fstab: [%s]\n" (String.concat ", " v) + ); + may (fun v -> bpf " windows_systemroot: %s\n" v) + data.windows_systemroot; + may (fun v -> bpf " windows_software_hive: %s\n" v) + data.windows_software_hive; + may (fun v -> bpf " windows_system_hive: %s\n" v) + data.windows_system_hive; + may (fun v -> bpf " windows_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 " drive_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 null_inspection_data () = { null_inspection_data with os_type = None } + +let merge_inspection_data child parent + let merge child parent = if parent = None then child else parent in + + parent.os_type <- merge child.os_type parent.os_type; + parent.distro <- merge child.distro parent.distro; + parent.package_format <- merge child.package_format parent.package_format; + parent.package_management <- + merge child.package_management parent.package_management; + parent.product_name <- merge child.product_name parent.product_name; + parent.product_variant <- merge child.product_variant parent.product_variant; + parent.version <- merge child.version parent.version; + parent.arch <- merge child.arch parent.arch; + parent.hostname <- merge child.hostname parent.hostname; + parent.fstab <- child.fstab @ parent.fstab; + parent.windows_systemroot <- + merge child.windows_systemroot parent.windows_systemroot; + parent.windows_software_hive <- + merge child.windows_software_hive parent.windows_software_hive; + parent.windows_system_hive <- + merge child.windows_system_hive parent.windows_system_hive; + parent.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. *) + parent.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) } -> null_inspection_data () + in + + match parent_fs with + | { role = RoleRoot parent_data } -> + merge_inspection_data (inspection_data_of_fs child_fs) parent_data + | { role = RoleUsr parent_data } -> + merge_inspection_data (inspection_data_of_fs child_fs) parent_data + | { role = (RoleSwap|RoleOther) } -> + () + +let inspect_fses = ref [] diff --git a/daemon/inspect_types.mli b/daemon/inspect_types.mli new file mode 100644 index 000000000..5c2151e14 --- /dev/null +++ b/daemon/inspect_types.mli @@ -0,0 +1,182 @@ +(* 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 *) +} +(** A single filesystem. *) + +and root = { + root_location : location; + inspection_data : inspection_data; +} +(** A root (as in "inspect_get_roots"). *) + +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 +(** During inspection, single filesystems are assigned a role which is + one of root, /usr, swap or other. *) + +and inspection_data = { + mutable os_type : os_type option; + mutable distro : distro option; + mutable package_format : package_format option; + mutable package_management : package_management option; + mutable product_name : string option; + mutable product_variant : string option; + mutable version : version option; + mutable arch : string option; + mutable hostname : string option; + mutable fstab : fstab_entry list; + mutable windows_systemroot : string option; + mutable windows_software_hive : string option; + mutable windows_system_hive : string option; + mutable windows_current_control_set : string option; + mutable drive_mappings : drive_mapping list; +} +(** During inspection, this data is collected incrementally for each + filesystem. At the end of inspection, inspection data is merged + into the root. *) + +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 -> unit +(** [merge_inspection_data child parent] merges two sets of inspection + data into the parent. 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 -> unit +(** [merge child_fs parent_fs] merges two filesystems, + using [merge_inspection_data] to merge the inspection data of + the child into the parent. (Nothing else is merged, only + the inspection data). *) + +val string_of_fs : fs -> string +(** Convert [fs] into a multi-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 : unit -> inspection_data +(** {!inspection_data} structure with all fields set to [None]. + This is a function: since we mutate this structure, we want + a fresh structure each time (so we're not mutating a common copy). *) + +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..9f66bbfea --- /dev/null +++ b/daemon/inspect_utils.ml @@ -0,0 +1,193 @@ +(* 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 ?name configfiles f + let sysroot = Sysroot.sysroot () in + let name + match name with + | None -> sprintf "with_augeas: %s" (String.concat " " configfiles) + | Some name -> name in + let chroot = Chroot.create ~name 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 (aug_rm_noerrors 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 = aug_matches_noerrors 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 aug_get_noerrors 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 + +and aug_get_noerrors aug path + try Augeas.get aug path + with Augeas.Error _ -> None + +and aug_matches_noerrors aug path + try Augeas.matches aug path + with Augeas.Error _ -> [] + +and aug_rm_noerrors aug path + try Augeas.rm aug path + with Augeas.Error _ -> 0 + +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. XXX fix function and callers + *) +let is_partition partition + try + let device = Devsparts.part_to_dev partition in + ignore (Devsparts.device_index device); + 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 verbose () then + eprintf "parse_version_from_major_minor: parsing '%s'\n%!" str; + + 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 + | None, Some _ -> () + | Some major, None -> data.version <- Some (major, 0) + | Some major, Some minor -> data.version <- Some (major, minor) + ) + else ( + eprintf "parse_version_from_major_minor: cannot parse version from '%s'\n" + str + ) + +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..74e196b52 --- /dev/null +++ b/daemon/inspect_utils.mli @@ -0,0 +1,57 @@ +(* 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 : ?name:string -> 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 aug_get_noerrors : Augeas.t -> string -> string option +val aug_matches_noerrors : Augeas.t -> string -> string list +val aug_rm_noerrors : Augeas.t -> string -> int +(** When inspecting a guest, we don't particularly care if Augeas + calls fail. These functions wrap {!Augeas.get}, {!Augeas.matches} + and {!Augeas.rm} returning null content if there is an error. *) + +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 -> unit +(** 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. *) -- 2.13.2
Richard W.M. Jones
2017-Jul-17 16:55 UTC
[Libguestfs] [PATCH v9 09/11] daemon: Implement inspection of Linux and other Unix-like operating systems.
This is essentially a line-for-line translation of the C inspection code. --- daemon/Makefile.am | 8 + daemon/inspect.ml | 396 ++++++++++++++++++++ daemon/inspect.mli | 41 ++ daemon/inspect_fs.ml | 363 ++++++++++++++++++ daemon/inspect_fs.mli | 23 ++ daemon/inspect_fs_unix.ml | 789 +++++++++++++++++++++++++++++++++++++++ daemon/inspect_fs_unix.mli | 44 +++ daemon/inspect_fs_unix_fstab.ml | 537 ++++++++++++++++++++++++++ daemon/inspect_fs_unix_fstab.mli | 34 ++ 9 files changed, 2235 insertions(+) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index c92eba9cd..624f7db44 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -263,6 +263,10 @@ SOURCES_MLI = \ file.mli \ filearch.mli \ findfs.mli \ + inspect.mli \ + inspect_fs.mli \ + inspect_fs_unix.mli \ + inspect_fs_unix_fstab.mli \ inspect_types.mli \ inspect_utils.mli \ is.mli \ @@ -302,6 +306,10 @@ SOURCES_ML = \ realpath.ml \ inspect_types.ml \ inspect_utils.ml \ + inspect_fs_unix_fstab.ml \ + inspect_fs_unix.ml \ + inspect_fs.ml \ + inspect.ml \ callbacks.ml \ daemon.ml diff --git a/daemon/inspect.ml b/daemon/inspect.ml new file mode 100644 index 000000000..88a3d93d6 --- /dev/null +++ b/daemon/inspect.ml @@ -0,0 +1,396 @@ +(* 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 "%s" (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 = RoleUsr { version = Some v } } -> v + | _ -> (0, 0) in + let v2 + match u2 with + | { 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 + + merge usr root; + 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; + 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..9153e68a5 --- /dev/null +++ b/daemon/inspect_fs.ml @@ -0,0 +1,363 @@ +(* 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 + (* The following struct is mutated in place by callees. However we + * need to make a copy of the object here so we don't mutate the + * null_inspection_data struct! + *) + let data = null_inspection_data () in + + let debug_matching what + if verbose () then + eprintf "check_filesystem: %s matched %s\n%!" + (Mountable.to_string mountable) what + in + + (* Grub /boot? *) + if Is.is_file "/grub/menu.lst" || + Is.is_file "/grub/grub.conf" || + Is.is_file "/grub2/grub.cfg" then ( + debug_matching "Grub /boot"; + () + ) + (* 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 ( + debug_matching "FreeBSD root"; + role := `Root; + 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 ( + debug_matching "NetBSD root"; + role := `Root; + 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 ( + debug_matching "OpenBSD root"; + role := `Root; + 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 ( + debug_matching "Hurd root"; + role := `Root; + 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 ( + debug_matching "Minix root"; + role := `Root; + 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 ( + debug_matching "Linux root"; + role := `Root; + 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 ( + debug_matching "CoreOS root"; + role := `Root; + 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 ( + debug_matching "Linux /usr/local"; + () + ) + (* 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 ( + debug_matching "Linux /usr"; + role := `Usr; + 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 ( + debug_matching "CoreOS /usr"; + role := `Usr; + 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 ( + debug_matching "Linux /var"; + () + ) + (* Windows volume with installed applications (but not root)? *) + else if is_dir_nocase "/System Volume Information" && + is_dir_nocase "/Program Files" then ( + debug_matching "Windows volume with installed applications"; + () + ) + (* Windows volume (but not root)? *) + else if is_dir_nocase "/System Volume Information" then ( + debug_matching "Windows volume without installed applications"; + () + ) + (* FreeDOS? *) + else if is_dir_nocase "/FDOS" && + is_file_nocase "/FDOS/FREEDOS.BSS" then ( + debug_matching "FreeDOS"; + role := `Root; + data.os_type <- Some OS_TYPE_DOS; + data.distro <- Some DISTRO_FREEDOS; + (* FreeDOS is a mix of 16 and 32 bit, but + * assume it requires a 32 bit i386 processor. + *) + data.arch <- Some "i386" + ) + (* None of the above. *) + else ( + debug_matching "no known OS partition" + ); + + (* The above code should have set [data.os_type] and [data.distro] + * fields, so we can now guess the package management system. + *) + data.package_format <- check_package_format data; + data.package_management <- check_package_management data; + + 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..0c171b76a --- /dev/null +++ b/daemon/inspect_fs_unix.ml @@ -0,0 +1,789 @@ +(* 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" ] + +(* 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 + *) +let rec 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; + None + ) + else + Some (read_whole_file release_file) + ) () in + + match lines with + | None -> false + | Some lines -> + let lines = String.nsplit "\n" lines in + + List.iter ( + fun line -> + let line = String.trim line in + if line = "" || line.[0] = '#' then + () + 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.distro <- distro + | None -> () + ) + else if key = "PRETTY_NAME" then + data.product_name <- Some value + else if key = "VERSION_ID" then + parse_version_from_major_minor value data + ) + ) lines; + + (* If we haven't got all the fields, exit right away. *) + if data.distro = None || data.product_name = None then + false + else ( + (* 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) } -> + false + | _ -> true + ) + +(* 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; + None + ) + else + Some (read_whole_file release_file) + ) () in + + match lines with + | None -> false + | Some lines -> + let lines = String.nsplit "\n" lines in + + (* Some distros (eg. RHEL 3) have a bare lsb-release file that might + * just contain the LSB_VERSION field and nothing else. In that case + * we must bail out (return false). + *) + let ok = ref false in + + List.iter ( + fun line -> + if verbose () then + eprintf "parse_lsb_release: parsing: %s\n%!" line; + + if data.distro = None && line = "DISTRIB_ID=Ubuntu" then ( + ok := true; + data. distro <- Some DISTRO_UBUNTU + ) + else if data.distro = None && line = "DISTRIB_ID=LinuxMint" then ( + ok := true; + data.distro <- Some DISTRO_LINUX_MINT + ) + else if data.distro = None && line = "DISTRIB_ID=\"Mageia\"" then ( + ok := true; + data.distro <- Some DISTRO_MAGEIA + ) + else if data.distro = None && line = "DISTRIB_ID=CoreOS" then ( + ok := true; + data.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 ( + ok := true; + let n = String.length line in + let product_name = String.sub line 21 (n-22) in + data.product_name <- Some product_name + ) + else if String.is_prefix line "DISTRIB_DESCRIPTION=" then ( + ok := true; + let n = String.length line in + let product_name = String.sub line 20 (n-20) in + data.product_name <- Some product_name + ) + ) lines; + + !ok + +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; + None + ) + else + Some (read_whole_file release_file) + ) () in + + match lines with + | None -> false + | Some lines -> + let lines = String.nsplit "\n" lines in + + if lines = [] then false + else ( + (* First line is dist release name. *) + let product_name = List.hd lines in + data.product_name <- Some product_name; + + (* 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.distro <- Some DISTRO_SLES; + data.version <- version + ) + else if Str.string_match re_opensuse product_name 0 then ( + (* Second line contains version string. *) + if List.length lines >= 2 then ( + let line = List.nth lines 1 in + parse_version_from_major_minor line data + ); + + data.distro <- Some DISTRO_OPENSUSE + ); + + true + ) + +(* 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; + "" + ) + else + read_first_line_from_file release_file + ) () in + if product_name = "" then + false + else ( + if verbose () then + eprintf "parse_generic: product_name = %s\n%!" product_name; + + data.product_name <- Some product_name; + data.distro <- Some distro; + + (match rex with + | Some rex -> + (* If ~rex was supplied, then it must match the release file, + * else the parsing fails. + *) + if Str.string_match rex product_name 0 then ( + (* 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 -> () + | None, Some _ -> () + | Some major, None -> data.version <- Some (major, 0) + | Some major, Some minor -> data.version <- Some (major, minor) + ); + true + ) + else + false (* ... else the parsing fails. *) + + | 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; + true + ) + ) + +(* The list of release file tests that we run for Linux root filesystems. + * This is processed in order. + * + * For each test, first we check if the named release file exists. + * If so, the parse function is called. If not, we go on to the next + * test. + * + * Each parse function should return true or false. If a parse function + * returns true, then we finish, else if it returns false then we continue + * to the next test. + *) +type parse_function = string -> inspection_data -> bool + +let linux_root_tests : (string * parse_function) list = [ + (* 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.distro <- Some DISTRO_REDHAT_BASED; true); + + "/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.distro <- Some DISTRO_ARCHLINUX; true); + + "/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.distro <- Some DISTRO_SUSE_BASED; true); + + "/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; +] + +let rec check_linux_root mountable data + let os_type = OS_TYPE_LINUX in + data.os_type <- Some os_type; + + let rec loop = function + | (release_file, parse_fun) :: tests -> + if verbose () then + eprintf "check_linux_root: checking %s\n%!" release_file; + if Is.is_file ~followsymlinks:true release_file then ( + if parse_fun release_file data then () (* true => finished *) + else loop tests + ) else loop tests + | [] -> () + in + loop linux_root_tests; + + data.arch <- check_architecture (); + data.fstab <- + Inspect_fs_unix_fstab.check_fstab ~mdadm_conf:true mountable os_type; + data.hostname <- check_hostname_linux () + +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 _) as hostname -> hostname + | None -> + if Is.is_file "/etc/sysconfig/network" then + with_augeas ~name:"check_hostname_from_sysconfig_network" + ["/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_from_file: %s" filename in + Chroot.create ~name (Sysroot.sysroot ()) in + + let hostname + 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) + ) () in + + match hostname with + | None | Some "" -> None + | (Some _) as hostname -> hostname + +(* 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. + *) + aug_get_noerrors aug "/files/etc/sysconfig/network/HOSTNAME" + +(* The currently mounted device looks like a Linux /usr. *) +let check_linux_usr data + data.os_type <- Some OS_TYPE_LINUX; + + if Is.is_file "/lib/os-release" ~followsymlinks:true then + ignore (parse_os_release "/lib/os-release" data); + + (match check_architecture () with + | None -> () + | (Some _) as arch -> data.arch <- arch + ) + +(* 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.os_type <- Some OS_TYPE_LINUX; + data.distro <- Some DISTRO_COREOS; + + (* Determine hostname. *) + data.hostname <- check_hostname_linux (); + + (* CoreOS does not contain /etc/fstab to determine the mount points. + * Associate this filesystem with the "/" mount point. + *) + data.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 + data.os_type <- Some OS_TYPE_LINUX; + data.distro <- Some DISTRO_COREOS; + + if Is.is_file "/lib/os-release" ~followsymlinks:true then + ignore (parse_os_release "/lib/os-release" data) + else if Is.is_file "/share/coreos/lsb-release" ~followsymlinks:true then + ignore (parse_lsb_release "/share/coreos/lsb-release" data); + + (* Determine the architecture. *) + (match check_architecture () with + | None -> () + | (Some _) as arch -> data.arch <- arch + ); + + (* CoreOS does not contain /etc/fstab to determine the mount points. + * Associate this filesystem with the "/usr" mount point. + *) + data.fstab <- [ mountable, "/usr" ] + +let rec check_freebsd_root mountable data + let os_type = OS_TYPE_FREEBSD and distro = DISTRO_FREEBSD in + data.os_type <- Some os_type; + data.distro <- Some distro; + + (* 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 Is.is_file "/etc/motd" ~followsymlinks:true then + ignore (parse_generic distro "/etc/motd" data); + + (* Determine the architecture. *) + data.arch <- check_architecture (); + (* We already know /etc/fstab exists because it's part of the test + * in the caller. + *) + data.fstab <- Inspect_fs_unix_fstab.check_fstab mountable os_type; + data.hostname <- check_hostname_freebsd () + +(* 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 + data.os_type <- Some os_type; + data.distro <- Some distro; + + if Is.is_file "/etc/release" ~followsymlinks:true then + ignore (parse_generic ~rex:re_netbsd distro "/etc/release" data); + + (* Determine the architecture. *) + data.arch <- check_architecture (); + (* We already know /etc/fstab exists because it's part of the test + * in the caller. + *) + data.fstab <- Inspect_fs_unix_fstab.check_fstab mountable os_type; + data.hostname <- check_hostname_freebsd () + +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 + data.os_type <- Some os_type; + data.distro <- Some distro; + + (* The first line of /etc/motd gets automatically updated at boot. *) + if Is.is_file "/etc/motd" ~followsymlinks:true then + ignore (parse_generic distro "/etc/motd" data); + + (* 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]. + *) + + (* Determine the architecture. *) + data.arch <- check_architecture (); + (* We already know /etc/fstab exists because it's part of the test + * in the caller. + *) + data.fstab <- Inspect_fs_unix_fstab.check_fstab mountable os_type; + data.hostname <- check_hostname_freebsd () + +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 + data.os_type <- Some os_type; + + if Is.is_file "/etc/debian_version" ~followsymlinks:true then ( + let distro = DISTRO_DEBIAN in + ignore (parse_generic distro "/etc/debian_version" data) + ); + (* Arch Hurd also exists, but inconveniently it doesn't have + * the normal /etc/arch-release file. XXX + *) + + (* Determine the architecture. *) + data.arch <- check_architecture (); + (* We already know /etc/fstab exists because it's part of the test + * in the caller. + *) + data.fstab <- Inspect_fs_unix_fstab.check_fstab mountable os_type; + data.hostname <- check_hostname_hurd () + +and check_hostname_hurd () = check_hostname_linux () + +let rec check_minix_root data + let os_type = OS_TYPE_MINIX in + data.os_type <- Some os_type; + + if Is.is_file "/etc/version" ~followsymlinks:true then ( + ignore (parse_generic ~rex:re_minix DISTRO_MEEGO (* XXX unset below *) + "/etc/version" data); + data.distro <- None + ); + + (* Determine the architecture. *) + data.arch <- check_architecture (); + (* TODO: enable fstab inspection once resolve_fstab_device + * implements the proper mapping from the Minix device names + * to the appliance names. + *) + data.hostname <- check_hostname_minix () + +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..af58e5dcc --- /dev/null +++ b/daemon/inspect_fs_unix.mli @@ -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. + *) + +val check_coreos_usr : Mountable.t -> Inspect_types.inspection_data -> unit +(** Inspect the CoreOS [/usr] filesystem mounted on sysroot. *) + +val check_coreos_root : Mountable.t -> Inspect_types.inspection_data -> unit +(** Inspect the CoreOS filesystem mounted on sysroot. *) + +val check_freebsd_root : Mountable.t -> Inspect_types.inspection_data -> unit +(** Inspect the FreeBSD filesystem mounted on sysroot. *) + +val check_hurd_root : Mountable.t -> Inspect_types.inspection_data -> unit +(** Inspect the Hurd filesystem mounted on sysroot. *) + +val check_linux_usr : Inspect_types.inspection_data -> unit +(** Inspect the Linux [/usr] filesystem mounted on sysroot. *) + +val check_linux_root : Mountable.t -> Inspect_types.inspection_data -> unit +(** Inspect the Linux filesystem mounted on sysroot. *) + +val check_minix_root : Inspect_types.inspection_data -> unit +(** Inspect the Minix filesystem mounted on sysroot. *) + +val check_netbsd_root : Mountable.t -> Inspect_types.inspection_data -> unit +(** Inspect the NetBSD filesystem mounted on sysroot. *) + +val check_openbsd_root : Mountable.t -> Inspect_types.inspection_data -> unit +(** 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..f103eb304 --- /dev/null +++ b/daemon/inspect_fs_unix_fstab.ml @@ -0,0 +1,537 @@ +(* 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 ~name:"check_fstab_aug" + 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 = aug_matches_noerrors 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 = aug_get_noerrors aug (entry ^ "/spec") in + let mp = aug_get_noerrors aug (entry ^ "/file") in + let vfstype = aug_get_noerrors 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 = aug_matches_noerrors aug (entry ^ "/opt") in + let rec loop = function + | [] -> mountable (* no subvol, return whole device *) + | opt :: opts -> + let optname = aug_get_noerrors aug opt in + match optname with + | None -> loop opts + | Some "subvol" -> + let subvol = aug_get_noerrors 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 = aug_matches_noerrors 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 = aug_get_noerrors aug (entry ^ "/devicename") in + let uuid = aug_get_noerrors 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 + + let debug_matching what + if verbose () then + eprintf "resolve_fstab_device: %s matched %s\n%!" spec what + in + + if String.is_prefix spec "/dev/mapper" then ( + debug_matching "/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. + *) + 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 ( + debug_matching "xdev"; + 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 ( + debug_matching "cciss"; + 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 ( + debug_matching "md<N>"; + try + Mountable.of_device (StringMap.find spec md_map) + with + | Not_found -> default + ) + + else if Str.string_match re_diskbyid spec 0 then ( + debug_matching "diskbyid"; + 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 ( + debug_matching "FreeBSD GPT"; + (* 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 ( + debug_matching "FreeBSD MBR"; + (* 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 ( + debug_matching "NetBSD"; + (* 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 ( + debug_matching "OpenBSD"; + (* 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 ( + debug_matching "Hurd"; + 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 ( + debug_matching "no known device scheme"; + 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. *) -- 2.13.2
Richard W.M. Jones
2017-Jul-17 16:55 UTC
[Libguestfs] [PATCH v9 10/11] daemon: Implement inspection of Windows.
Mostly a line-for-line translation of the C inspection code. --- daemon/Makefile.am | 2 + daemon/inspect_fs.ml | 6 + daemon/inspect_fs_windows.ml | 493 ++++++++++++++++++++++++++++++++++++++++++ daemon/inspect_fs_windows.mli | 24 ++ 4 files changed, 525 insertions(+) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 624f7db44..1ea046383 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -267,6 +267,7 @@ SOURCES_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 \ @@ -308,6 +309,7 @@ SOURCES_ML = \ inspect_utils.ml \ inspect_fs_unix_fstab.ml \ inspect_fs_unix.ml \ + inspect_fs_windows.ml \ inspect_fs.ml \ inspect.ml \ callbacks.ml \ diff --git a/daemon/inspect_fs.ml b/daemon/inspect_fs.ml index 9153e68a5..10a15827b 100644 --- a/daemon/inspect_fs.ml +++ b/daemon/inspect_fs.ml @@ -192,6 +192,12 @@ and check_filesystem mountable debug_matching "Linux /var"; () ) + (* Windows root? *) + else if Inspect_fs_windows.is_windows_systemroot () then ( + debug_matching "Windows root"; + role := `Root; + 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 ( diff --git a/daemon/inspect_fs_windows.ml b/daemon/inspect_fs_windows.ml new file mode 100644 index 000000000..313babd0f --- /dev/null +++ b/daemon/inspect_fs_windows.ml @@ -0,0 +1,493 @@ +(* 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 + + data.os_type <- Some OS_TYPE_WINDOWS; + data.distro <- Some DISTRO_WINDOWS; + data.windows_systemroot <- Some systemroot; + data.arch <- Some (check_windows_arch systemroot); + + (* 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 + data.windows_software_hive <- software_hive; + + 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 + data.windows_system_hive <- system_hive; + + match software_hive, system_hive with + | None, _ | Some _, None -> () + | Some software_hive, Some system_hive -> + (* Check software hive. *) + check_windows_software_registry software_hive data; + + (* Check system hive. *) + check_windows_system_registry system_hive 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. *) + (try + let v = List.assoc "ProductName" values in + data.product_name <- Some (hivex_value_as_utf8 h v) + with + Not_found -> () + ); + + (* Version is complicated. Use CurrentMajorVersionNumber and + * CurrentMinorVersionNumber if present. If they are not + * found, fall back on CurrentVersion. + *) + (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.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 + ); + + (* InstallationType (product_variant). *) + (try + let v = List.assoc "InstallationType" values in + data.product_variant <- Some (hivex_value_as_utf8 h v) + with + Not_found -> () + ); + with + | Not_found -> + if verbose () then + eprintf "check_windows_software_registry: cannot locate HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\n%!" + ) (* with_hive *) + +and check_windows_system_registry system_hive data + with_hive (Sysroot.sysroot () // system_hive) ( + fun h root -> + get_drive_mappings h root data; + + let current_control_set = get_current_control_set h root in + data.windows_current_control_set <- current_control_set; + + match current_control_set with + | None -> () + | Some current_control_set -> + let hostname = get_hostname h root current_control_set in + data.hostname <- hostname + ) (* 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.drive_mappings <- values + + with + | Not_found -> + if verbose () then + eprintf "check_windows_system_registry: cannot find drive mappings\n%!" + +(* 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 + (* The blob_guid is returned as a lowercase hex string. *) + let blob_guid = extract_guid_from_registry_blob blob in + + if verbose () then + eprintf "map_registry_disk_blob_gpt: searching for GUID %s\n%!" + blob_guid; + + 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 + + (* Must be lowercase hex. *) + sprintf "%08Lx-%04Lx-%04Lx-%04Lx-%012Lx" + data1 data2 data3 + (Int64.shift_right_logical 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..a7dcd76ba --- /dev/null +++ b/daemon/inspect_fs_windows.mli @@ -0,0 +1,24 @@ +(* guestfs-inspection + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +val check_windows_root : Inspect_types.inspection_data -> unit +(** 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. *) -- 2.13.2
Richard W.M. Jones
2017-Jul-17 16:55 UTC
[Libguestfs] [PATCH v9 11/11] Remove inspection from the C library and switch to daemon/OCaml implementation.
--- docs/C_SOURCE_FILES | 3 - 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/proc_nr.ml | 23 + lib/MAX_PROC_NR | 2 +- lib/Makefile.am | 3 - lib/guestfs-internal.h | 179 --- lib/handle.c | 1 - lib/inspect-apps.c | 112 +- lib/inspect-fs-unix.c | 2158 --------------------------- lib/inspect-fs-windows.c | 739 --------- lib/inspect-fs.c | 732 --------- lib/inspect-icon.c | 251 ++-- lib/inspect.c | 730 --------- 17 files changed, 404 insertions(+), 4934 deletions(-) diff --git a/docs/C_SOURCE_FILES b/docs/C_SOURCE_FILES index bb9cc2941..01cb835c5 100644 --- a/docs/C_SOURCE_FILES +++ b/docs/C_SOURCE_FILES @@ -308,9 +308,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/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 83b33ac8d..185828cc7 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 585dac706..185a13df4 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,9 @@ 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, const char *filename, const char *basename, uint64_t max_size); 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 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_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); /* 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); 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 3196a9d0b..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,7 +363,7 @@ 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 }; @@ -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; @@ -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; @@ -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; @@ -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 54f9a281a..000000000 --- a/lib/inspect-fs.c +++ /dev/null @@ -1,732 +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 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 89f5da082..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; @@ -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; @@ -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; @@ -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; @@ -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. */ @@ -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 f4ad192a9..501074316 100644 --- a/lib/inspect.c +++ b/lib/inspect.c @@ -16,11 +16,6 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -/** - * This file, and the other C<lib/inspect*.c> files, handle - * inspection. See L<guestfs(3)/INSPECTION>. - */ - #include <config.h> #include <stdio.h> @@ -44,688 +39,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. @@ -818,46 +131,3 @@ guestfs_int_parse_unsigned_int_ignore_trailing (guestfs_h *g, const char *str) } return ret; } - -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; -} -- 2.13.2
Pino Toscano
2017-Jul-20 15:16 UTC
Re: [Libguestfs] [PATCH v9 04/11] daemon: Implement umount_all in OCaml.
On Monday, 17 July 2017 18:55:24 CEST Richard W.M. Jones wrote:> Unlike previous ‘daemon: Reimplement ...’ patches, this does not > reimplement the umount_all API completely (yet, but this > implementation could be completed in future and then replace the C > one). However it is necessary to have a version of umount_all which > we can call from the OCaml inspection code. > ---Wouldn't it be easier to wrap the C implementation, for now? This way there is no functionality mismatch, and it can be used in both C and OCaml snippets. -- Pino Toscano
Maybe Matching Threads
- [PATCH v9 04/11] daemon: Implement umount_all in OCaml.
- [PATCH v12 05/11] daemon: Implement umount_all in OCaml.
- [PATCH 0/3]: daemon: Reimplement ‘file’ API in OCaml.
- [PATCH v2 4/5] daemon: move Mount.umount_all to new Mount_utils module
- [PATCH v2 0/4] Switch augeas APIs to OCaml