/* * virt-host-validate-common.c: Sanity check helper APIs * * Copyright (C) 2012, 2014 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.1 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, see * . * */ #include #include #include #include #include #include #ifdef HAVE_MNTENT_H # include #endif /* HAVE_MNTENT_H */ #include #include "virutil.h" #include "viralloc.h" #include "virfile.h" #include "virt-host-validate-common.h" #include "virstring.h" #define VIR_FROM_THIS VIR_FROM_NONE static bool quiet; void virHostMsgSetQuiet(bool quietFlag) { quiet = quietFlag; } void virHostMsgCheck(const char *prefix, const char *format, ...) { va_list args; char *msg; if (quiet) return; va_start(args, format); if (virVasprintf(&msg, format, args) < 0) { perror("malloc"); abort(); } va_end(args); fprintf(stdout, _("%6s: Checking %-60s: "), prefix, msg); VIR_FREE(msg); } static bool virHostMsgWantEscape(void) { static bool detectTty = true; static bool wantEscape; if (detectTty) { if (isatty(STDOUT_FILENO)) wantEscape = true; detectTty = false; } return wantEscape; } void virHostMsgPass(void) { if (quiet) return; if (virHostMsgWantEscape()) fprintf(stdout, "\033[32m%s\033[0m\n", _("PASS")); else fprintf(stdout, "%s\n", _("PASS")); } static const char * failMessages[] = { N_("FAIL"), N_("WARN"), N_("NOTE"), }; verify(ARRAY_CARDINALITY(failMessages) == VIR_HOST_VALIDATE_LAST); static const char *failEscapeCodes[] = { "\033[31m", "\033[33m", "\033[34m", }; verify(ARRAY_CARDINALITY(failEscapeCodes) == VIR_HOST_VALIDATE_LAST); void virHostMsgFail(virHostValidateLevel level, const char *format, ...) { va_list args; char *msg; if (quiet) return; va_start(args, format); if (virVasprintf(&msg, format, args) < 0) { perror("malloc"); abort(); } va_end(args); if (virHostMsgWantEscape()) fprintf(stdout, "%s%s\033[0m (%s)\n", failEscapeCodes[level], _(failMessages[level]), msg); else fprintf(stdout, "%s (%s)\n", _(failMessages[level]), msg); VIR_FREE(msg); } int virHostValidateDeviceExists(const char *hvname, const char *dev_name, virHostValidateLevel level, const char *hint) { virHostMsgCheck(hvname, "if device %s exists", dev_name); if (access(dev_name, F_OK) < 0) { virHostMsgFail(level, "%s", hint); return -1; } virHostMsgPass(); return 0; } int virHostValidateDeviceAccessible(const char *hvname, const char *dev_name, virHostValidateLevel level, const char *hint) { virHostMsgCheck(hvname, "if device %s is accessible", dev_name); if (access(dev_name, R_OK|W_OK) < 0) { virHostMsgFail(level, "%s", hint); return -1; } virHostMsgPass(); return 0; } int virHostValidateNamespace(const char *hvname, const char *ns_name, virHostValidateLevel level, const char *hint) { virHostMsgCheck(hvname, "for namespace %s", ns_name); char nspath[100]; snprintf(nspath, sizeof(nspath), "/proc/self/ns/%s", ns_name); if (access(nspath, F_OK) < 0) { virHostMsgFail(level, "%s", hint); return -1; } virHostMsgPass(); return 0; } bool virHostValidateHasCPUFlag(const char *name) { FILE *fp = fopen("/proc/cpuinfo", "r"); bool ret = false; if (!fp) return false; do { char line[1024]; if (!fgets(line, sizeof(line), fp)) break; if (strstr(line, name)) { ret = true; break; } } while (1); VIR_FORCE_FCLOSE(fp); return ret; } int virHostValidateLinuxKernel(const char *hvname, int version, virHostValidateLevel level, const char *hint) { struct utsname uts; unsigned long thisversion; uname(&uts); virHostMsgCheck(hvname, _("for Linux >= %d.%d.%d"), ((version >> 16) & 0xff), ((version >> 8) & 0xff), (version & 0xff)); if (STRNEQ(uts.sysname, "Linux")) { virHostMsgFail(level, "%s", hint); return -1; } if (virParseVersionString(uts.release, &thisversion, true) < 0) { virHostMsgFail(level, "%s", hint); return -1; } if (thisversion < version) { virHostMsgFail(level, "%s", hint); return -1; } else { virHostMsgPass(); return 0; } } static int virHostValidateCGroupSupport(const char *hvname, const char *cg_name, virHostValidateLevel level, const char *config_name) { virHostMsgCheck(hvname, "for cgroup '%s' controller support", cg_name); FILE *fp = fopen("/proc/self/cgroup", "r"); size_t len = 0; char *line = NULL; ssize_t ret; bool matched = false; if (!fp) goto error; while ((ret = getline(&line, &len, fp)) >= 0 && !matched) { char *tmp = strstr(line, cg_name); if (!tmp) continue; tmp += strlen(cg_name); if (*tmp != ',' && *tmp != ':') continue; matched = true; } VIR_FREE(line); VIR_FORCE_FCLOSE(fp); if (!matched) goto error; virHostMsgPass(); return 0; error: VIR_FREE(line); virHostMsgFail(level, "Enable CONFIG_%s in kernel Kconfig file", config_name); return -1; } #ifdef HAVE_MNTENT_H static int virHostValidateCGroupMount(const char *hvname, const char *cg_name, virHostValidateLevel level) { virHostMsgCheck(hvname, "for cgroup '%s' controller mount-point", cg_name); FILE *fp = setmntent("/proc/mounts", "r"); struct mntent ent; char mntbuf[1024]; bool matched = false; if (!fp) goto error; while (getmntent_r(fp, &ent, mntbuf, sizeof(mntbuf)) && !matched) { char *tmp = strstr(ent.mnt_opts, cg_name); if (!tmp) continue; tmp += strlen(cg_name); if (*tmp != ',' && *tmp != '\0') continue; matched = true; } endmntent(fp); if (!matched) goto error; virHostMsgPass(); return 0; error: virHostMsgFail(level, "Mount '%s' cgroup controller (suggested at /sys/fs/cgroup/%s)", cg_name, cg_name); return -1; } #else /* ! HAVE_MNTENT_H */ static int virHostValidateCGroupMount(const char *hvname, const char *cg_name, virHostValidateLevel level) { virHostMsgCheck(hvname, "for cgroup '%s' controller mount-point", cg_name); virHostMsgFail(level, "%s", "This platform does not support cgroups"); return -1; } #endif /* ! HAVE_MNTENT_H */ int virHostValidateCGroupController(const char *hvname, const char *cg_name, virHostValidateLevel level, const char *config_name) { if (virHostValidateCGroupSupport(hvname, cg_name, level, config_name) < 0) return -1; if (virHostValidateCGroupMount(hvname, cg_name, level) < 0) return -1; return 0; } int virHostValidateIOMMU(const char *hvname, virHostValidateLevel level) { struct stat sb; const char *bootarg = NULL; bool isAMD = false, isIntel = false; if (virHostValidateHasCPUFlag("vmx")) isIntel = true; else if (virHostValidateHasCPUFlag("svm")) isAMD = true; else /* XXX PPC/ARM/etc support */ return 0; virHostMsgCheck(hvname, "%s", _("for device assignment IOMMU support")); if (isIntel) { if (access("/sys/firmware/acpi/tables/DMAR", F_OK) == 0) { virHostMsgPass(); bootarg = "intel_iommu=on"; } else { virHostMsgFail(level, "No ACPI DMAR table found, IOMMU either " "disabled in BIOS or not supported by this " "hardware platform"); return -1; } } else if (isAMD) { if (access("/sys/firmware/acpi/tables/IVRS", F_OK) == 0) { virHostMsgPass(); bootarg = "iommu=pt iommu=1"; } else { virHostMsgFail(level, "No ACPI IVRS table found, IOMMU either " "disabled in BIOS or not supported by this " "hardware platform"); return -1; } } else { virHostMsgFail(level, "Unknown if this platform has IOMMU support"); return -1; } /* We can only check on newer kernels with iommu groups & vfio */ if (stat("/sys/kernel/iommu_groups", &sb) < 0) return 0; if (!S_ISDIR(sb.st_mode)) return 0; virHostMsgCheck(hvname, "%s", _("if IOMMU is enabled by kernel")); if (sb.st_nlink <= 2) { virHostMsgFail(level, "IOMMU appears to be disabled in kernel. " "Add %s to kernel cmdline arguments", bootarg); return -1; } virHostMsgPass(); return 0; }