mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-21 22:53:37 +00:00
545 lines
14 KiB
C
545 lines
14 KiB
C
/* 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>
|
|
|
|
#ifdef HAVE_ENDIAN_H
|
|
#include <endian.h>
|
|
#endif
|
|
|
|
#include "c-ctype.h"
|
|
|
|
#include "guestfs.h"
|
|
#include "guestfs-internal.h"
|
|
|
|
/* Debian/Ubuntu install disks are easy ...
|
|
*
|
|
* These files are added by the debian-cd program, and it is worth
|
|
* looking at the source code to determine exact values, in
|
|
* particular '/usr/share/debian-cd/tools/start_new_disc'
|
|
*
|
|
* XXX Architecture? We could parse it out of the product name
|
|
* string, but that seems quite hairy. We could look for the names
|
|
* of packages. Also note that some Debian install disks are
|
|
* multiarch.
|
|
*/
|
|
static int
|
|
check_debian_installer_root (guestfs_h *g, struct inspect_fs *fs)
|
|
{
|
|
fs->product_name = guestfs_int_first_line_of_file (g, "/.disk/info");
|
|
if (!fs->product_name)
|
|
return -1;
|
|
|
|
fs->type = OS_TYPE_LINUX;
|
|
if (STRPREFIX (fs->product_name, "Ubuntu"))
|
|
fs->distro = OS_DISTRO_UBUNTU;
|
|
else if (STRPREFIX (fs->product_name, "Debian"))
|
|
fs->distro = OS_DISTRO_DEBIAN;
|
|
|
|
(void) guestfs_int_parse_major_minor (g, fs);
|
|
|
|
if (guestfs_is_file (g, "/.disk/cd_type") > 0) {
|
|
CLEANUP_FREE char *cd_type =
|
|
guestfs_int_first_line_of_file (g, "/.disk/cd_type");
|
|
if (!cd_type)
|
|
return -1;
|
|
|
|
if (STRPREFIX (cd_type, "dvd/single") ||
|
|
STRPREFIX (cd_type, "full_cd/single")) {
|
|
fs->is_multipart_disk = 0;
|
|
fs->is_netinst_disk = 0;
|
|
}
|
|
else if (STRPREFIX (cd_type, "dvd") ||
|
|
STRPREFIX (cd_type, "full_cd")) {
|
|
fs->is_multipart_disk = 1;
|
|
fs->is_netinst_disk = 0;
|
|
}
|
|
else if (STRPREFIX (cd_type, "not_complete")) {
|
|
fs->is_multipart_disk = 0;
|
|
fs->is_netinst_disk = 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Take string which must look like "key = value" and find the value.
|
|
* There may or may not be spaces before and after the equals sign.
|
|
* This function is used by both check_fedora_installer_root and
|
|
* check_w2k3_installer_root.
|
|
*/
|
|
static const char *
|
|
find_value (const char *kv)
|
|
{
|
|
const char *p;
|
|
|
|
p = strchr (kv, '=');
|
|
if (!p)
|
|
abort ();
|
|
|
|
do {
|
|
++p;
|
|
} while (c_isspace (*p));
|
|
|
|
return p;
|
|
}
|
|
|
|
/* RHEL 5 has a DVD.iso and several CD-sized -discX-ftp.iso alternatives.
|
|
*
|
|
* The DVD.iso contains:
|
|
* /.treeinfo:
|
|
* [general]
|
|
* family = Red Hat Enterprise Linux Server
|
|
* timestamp = 1328200566.61
|
|
* totaldiscs = 1
|
|
* version = 5.8
|
|
* discnum = 1
|
|
* packagedir = Server
|
|
* arch = x86_64
|
|
* [...]
|
|
*
|
|
* /.discinfo:
|
|
* 1328205744.315196
|
|
* Red Hat Enterprise Linux Server 5.8 # product name
|
|
* x86_64 # arch
|
|
* 1 # disk number
|
|
* Server/base
|
|
* Server/RPMS
|
|
* Server/pixmaps
|
|
*
|
|
* The alternative CD-sized ISOs contain:
|
|
*
|
|
* disc1:
|
|
* /.treeinfo:
|
|
* [general]
|
|
* family = Red Hat Enterprise Linux Server
|
|
* timestamp = 1328200566.61
|
|
* totaldiscs = 1
|
|
* version = 5.8
|
|
* discnum = 1
|
|
* packagedir = Server
|
|
* arch = x86_64
|
|
* [...]
|
|
*
|
|
* /.discinfo:
|
|
* 1328205744.315196
|
|
* Red Hat Enterprise Linux Server 5.8 # product name
|
|
* x86_64 # arch
|
|
* 1 # disk number
|
|
* Server/base
|
|
* Server/RPMS
|
|
* Server/pixmaps
|
|
*
|
|
* discN (N > 1):
|
|
* /.discinfo:
|
|
* 1328205744.315196
|
|
* Red Hat Enterprise Linux Server 5.8 # product name
|
|
* x86_64 # arch
|
|
* 2 # disk number
|
|
* Server/base
|
|
* Server/RPMS
|
|
* Server/pixmaps
|
|
*/
|
|
|
|
/* Fedora CDs and DVD (not netinst). The /.treeinfo file contains
|
|
* an initial section somewhat like this:
|
|
*
|
|
* [general]
|
|
* version = 14
|
|
* arch = x86_64
|
|
* family = Fedora
|
|
* variant = Fedora
|
|
* discnum = 1
|
|
* totaldiscs = 1
|
|
*/
|
|
static int
|
|
check_fedora_installer_root (guestfs_h *g, struct inspect_fs *fs)
|
|
{
|
|
char *str;
|
|
const char *v;
|
|
int r;
|
|
int discnum = 0, totaldiscs = 0;
|
|
|
|
fs->type = OS_TYPE_LINUX;
|
|
|
|
r = guestfs_int_first_egrep_of_file (g, "/.treeinfo",
|
|
"^family = Fedora$", 0, &str);
|
|
if (r == -1)
|
|
return -1;
|
|
if (r > 0) {
|
|
fs->distro = OS_DISTRO_FEDORA;
|
|
free (str);
|
|
}
|
|
|
|
r = guestfs_int_first_egrep_of_file (g, "/.treeinfo",
|
|
"^family = Red Hat Enterprise Linux$",
|
|
0, &str);
|
|
if (r == -1)
|
|
return -1;
|
|
if (r > 0) {
|
|
fs->distro = OS_DISTRO_RHEL;
|
|
free (str);
|
|
}
|
|
|
|
r = guestfs_int_first_egrep_of_file (g, "/.treeinfo",
|
|
"^family = Oracle Linux Server$",
|
|
0, &str);
|
|
if (r == -1)
|
|
return -1;
|
|
if (r > 0) {
|
|
fs->distro = OS_DISTRO_ORACLE_LINUX;
|
|
free (str);
|
|
}
|
|
|
|
/* XXX should do major.minor before this */
|
|
r = guestfs_int_first_egrep_of_file (g, "/.treeinfo",
|
|
"^version = [[:digit:]]+", 0, &str);
|
|
if (r == -1)
|
|
return -1;
|
|
if (r > 0) {
|
|
v = find_value (str);
|
|
fs->version.v_major = guestfs_int_parse_unsigned_int_ignore_trailing (g, v);
|
|
free (str);
|
|
if (fs->version.v_major == -1)
|
|
return -1;
|
|
}
|
|
|
|
r = guestfs_int_first_egrep_of_file (g, "/.treeinfo",
|
|
"^arch = [-_[:alnum:]]+$", 0, &str);
|
|
if (r == -1)
|
|
return -1;
|
|
if (r > 0) {
|
|
v = find_value (str);
|
|
fs->arch = safe_strdup (g, v);
|
|
free (str);
|
|
}
|
|
|
|
r = guestfs_int_first_egrep_of_file (g, "/.treeinfo",
|
|
"^discnum = [[:digit:]]+$", 0, &str);
|
|
if (r == -1)
|
|
return -1;
|
|
if (r > 0) {
|
|
v = find_value (str);
|
|
discnum = guestfs_int_parse_unsigned_int (g, v);
|
|
free (str);
|
|
if (discnum == -1)
|
|
return -1;
|
|
}
|
|
|
|
r = guestfs_int_first_egrep_of_file (g, "/.treeinfo",
|
|
"^totaldiscs = [[:digit:]]+$", 0, &str);
|
|
if (r == -1)
|
|
return -1;
|
|
if (r > 0) {
|
|
v = find_value (str);
|
|
totaldiscs = guestfs_int_parse_unsigned_int (g, v);
|
|
free (str);
|
|
if (totaldiscs == -1)
|
|
return -1;
|
|
}
|
|
|
|
fs->is_multipart_disk = totaldiscs > 1;
|
|
/* and what about discnum? */
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Linux with /isolinux/isolinux.cfg.
|
|
*
|
|
* This file is not easily parsable so we have to do our best.
|
|
* Look for the "menu title" line which contains:
|
|
* menu title Welcome to Fedora 14! # since at least Fedora 10
|
|
* menu title Welcome to Red Hat Enterprise Linux 6.0!
|
|
* menu title Welcome to RHEL6.2-20111117.0-Workstation-x!
|
|
*/
|
|
static int
|
|
check_isolinux_installer_root (guestfs_h *g, struct inspect_fs *fs)
|
|
{
|
|
char *str;
|
|
int r;
|
|
|
|
fs->type = OS_TYPE_LINUX;
|
|
|
|
r = guestfs_int_first_egrep_of_file (g, "/isolinux/isolinux.cfg",
|
|
"^menu title Welcome to Fedora [[:digit:]]+",
|
|
0, &str);
|
|
if (r == -1)
|
|
return -1;
|
|
if (r > 0) {
|
|
fs->distro = OS_DISTRO_FEDORA;
|
|
fs->version.v_major =
|
|
guestfs_int_parse_unsigned_int_ignore_trailing (g, &str[29]);
|
|
free (str);
|
|
if (fs->version.v_major == -1)
|
|
return -1;
|
|
}
|
|
|
|
/* XXX parse major.minor */
|
|
r = guestfs_int_first_egrep_of_file (g, "/isolinux/isolinux.cfg",
|
|
"^menu title Welcome to Red Hat Enterprise Linux [[:digit:]]+",
|
|
0, &str);
|
|
if (r == -1)
|
|
return -1;
|
|
if (r > 0) {
|
|
fs->distro = OS_DISTRO_RHEL;
|
|
fs->version.v_major =
|
|
guestfs_int_parse_unsigned_int_ignore_trailing (g, &str[47]);
|
|
free (str);
|
|
if (fs->version.v_major == -1)
|
|
return -1;
|
|
}
|
|
|
|
/* XXX parse major.minor */
|
|
r = guestfs_int_first_egrep_of_file (g, "/isolinux/isolinux.cfg",
|
|
"^menu title Welcome to RHEL[[:digit:]]+",
|
|
0, &str);
|
|
if (r == -1)
|
|
return -1;
|
|
if (r > 0) {
|
|
fs->distro = OS_DISTRO_RHEL;
|
|
fs->version.v_major =
|
|
guestfs_int_parse_unsigned_int_ignore_trailing (g, &str[26]);
|
|
free (str);
|
|
if (fs->version.v_major == -1)
|
|
return -1;
|
|
}
|
|
|
|
/* XXX parse major.minor */
|
|
r = guestfs_int_first_egrep_of_file (g, "/isolinux/isolinux.cfg",
|
|
"^menu title Welcome to Oracle Linux Server [[:digit:]]+",
|
|
0, &str);
|
|
if (r == -1)
|
|
return -1;
|
|
if (r > 0) {
|
|
fs->distro = OS_DISTRO_ORACLE_LINUX;
|
|
fs->version.v_major =
|
|
guestfs_int_parse_unsigned_int_ignore_trailing (g, &str[42]);
|
|
free (str);
|
|
if (fs->version.v_major == -1)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Windows 2003 and similar versions.
|
|
*
|
|
* NB: txtsetup file contains Windows \r\n line endings, which guestfs_grep
|
|
* does not remove. We have to remove them by hand here.
|
|
*/
|
|
static void
|
|
trim_cr (char *str)
|
|
{
|
|
const size_t n = strlen (str);
|
|
if (n > 0 && str[n-1] == '\r')
|
|
str[n-1] = '\0';
|
|
}
|
|
|
|
static void
|
|
trim_quot (char *str)
|
|
{
|
|
const size_t n = strlen (str);
|
|
if (n > 0 && str[n-1] == '"')
|
|
str[n-1] = '\0';
|
|
}
|
|
|
|
static int
|
|
check_w2k3_installer_root (guestfs_h *g, struct inspect_fs *fs,
|
|
const char *txtsetup)
|
|
{
|
|
char *str;
|
|
const char *v;
|
|
int r;
|
|
|
|
fs->type = OS_TYPE_WINDOWS;
|
|
fs->distro = OS_DISTRO_WINDOWS;
|
|
|
|
r = guestfs_int_first_egrep_of_file (g, txtsetup,
|
|
"^productname[[:space:]]*=[[:space:]]*\"", 1, &str);
|
|
if (r == -1)
|
|
return -1;
|
|
if (r > 0) {
|
|
trim_cr (str);
|
|
trim_quot (str);
|
|
v = find_value (str);
|
|
fs->product_name = safe_strdup (g, v+1);
|
|
free (str);
|
|
}
|
|
|
|
r = guestfs_int_first_egrep_of_file (g, txtsetup,
|
|
"^majorversion[[:space:]]*=[[:space:]]*[[:digit:]]+",
|
|
1, &str);
|
|
if (r == -1)
|
|
return -1;
|
|
if (r > 0) {
|
|
trim_cr (str);
|
|
v = find_value (str);
|
|
fs->version.v_major = guestfs_int_parse_unsigned_int_ignore_trailing (g, v);
|
|
free (str);
|
|
if (fs->version.v_major == -1)
|
|
return -1;
|
|
}
|
|
|
|
r = guestfs_int_first_egrep_of_file (g, txtsetup,
|
|
"^minorversion[[:space:]]*=[[:space:]]*[[:digit:]]+",
|
|
1, &str);
|
|
if (r == -1)
|
|
return -1;
|
|
if (r > 0) {
|
|
trim_cr (str);
|
|
v = find_value (str);
|
|
fs->version.v_minor = guestfs_int_parse_unsigned_int_ignore_trailing (g, v);
|
|
free (str);
|
|
if (fs->version.v_minor == -1)
|
|
return -1;
|
|
}
|
|
|
|
/* This is the windows systemroot that would be chosen on
|
|
* installation by default, although not necessarily the one that
|
|
* the user will finally choose.
|
|
*/
|
|
r = guestfs_int_first_egrep_of_file (g, txtsetup,
|
|
"^defaultpath[[:space:]]*=[[:space:]]*",
|
|
1, &str);
|
|
if (r == -1)
|
|
return -1;
|
|
if (r > 0) {
|
|
trim_cr (str);
|
|
v = find_value (str);
|
|
fs->windows_systemroot = safe_strdup (g, v);
|
|
free (str);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* The currently mounted device is very likely to be an installer. */
|
|
int
|
|
guestfs_int_check_installer_root (guestfs_h *g, struct inspect_fs *fs)
|
|
{
|
|
/* 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;
|
|
}
|
|
|
|
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;
|
|
}
|