/* * vircgroup.c: methods for managing control cgroups * * Copyright (C) 2010-2015 Red Hat, Inc. * Copyright IBM Corp. 2008 * * 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 #ifdef __linux__ # include # include # include # include # include # include # include # include # include #endif /* __linux__ */ #define LIBVIRT_VIRCGROUPPRIV_H_ALLOW #include "vircgrouppriv.h" #include "virutil.h" #include "viralloc.h" #include "vircgroupbackend.h" #include "virerror.h" #include "virlog.h" #include "virfile.h" #include "virgdbus.h" #include "virhash.h" #include "virstring.h" #include "virsystemd.h" #include "virtypedparam.h" #include "virhostcpu.h" #include "virthread.h" VIR_LOG_INIT("util.cgroup"); #define VIR_FROM_THIS VIR_FROM_CGROUP #define CGROUP_NB_TOTAL_CPU_STAT_PARAM 3 #define CGROUP_NB_PER_CPU_STAT_PARAM 1 VIR_ENUM_IMPL(virCgroupController, VIR_CGROUP_CONTROLLER_LAST, "cpu", "cpuacct", "cpuset", "memory", "devices", "freezer", "blkio", "net_cls", "perf_event", "name=systemd", ); /** * virCgroupGetDevicePermsString: * * @perms: Bitwise or of VIR_CGROUP_DEVICE permission bits * * Returns string corresponding to the appropriate bits set. */ const char * virCgroupGetDevicePermsString(int perms) { if (perms & VIR_CGROUP_DEVICE_READ) { if (perms & VIR_CGROUP_DEVICE_WRITE) { if (perms & VIR_CGROUP_DEVICE_MKNOD) return "rwm"; else return "rw"; } else { if (perms & VIR_CGROUP_DEVICE_MKNOD) return "rm"; else return "r"; } } else { if (perms & VIR_CGROUP_DEVICE_WRITE) { if (perms & VIR_CGROUP_DEVICE_MKNOD) return "wm"; else return "w"; } else { if (perms & VIR_CGROUP_DEVICE_MKNOD) return "m"; else return ""; } } } #ifdef __linux__ bool virCgroupAvailable(void) { size_t i; virCgroupBackend **backends = virCgroupBackendGetAll(); if (!backends) return false; for (i = 0; i < VIR_CGROUP_BACKEND_TYPE_LAST; i++) { if (backends[i] && backends[i]->available()) return true; } return false; } static int virCgroupPartitionNeedsEscaping(const char *path) { FILE *fp = NULL; int ret = 0; g_autofree char *line = NULL; size_t buflen; /* If it starts with 'cgroup.' or a '_' of any * of the controller names from /proc/cgroups, * then we must prefix a '_' */ if (STRPREFIX(path, "cgroup.")) return 1; if (path[0] == '_' || path[0] == '.') return 1; if (!(fp = fopen("/proc/cgroups", "r"))) { /* The API contract is that we return ENXIO * if cgroups are not available on a host */ if (errno == ENOENT) errno = ENXIO; virReportSystemError(errno, "%s", _("Cannot open /proc/cgroups")); return -1; } /* * Data looks like this: * #subsys_name hierarchy num_cgroups enabled * cpuset 2 4 1 * cpu 3 48 1 * cpuacct 3 48 1 * memory 4 4 1 * devices 5 4 1 * freezer 6 4 1 * net_cls 7 1 1 */ while (getline(&line, &buflen, fp) > 0) { char *tmp; size_t len; if (STRPREFIX(line, "#subsys_name")) continue; tmp = strchr(line, ' '); if (tmp) { *tmp = '\0'; len = tmp - line; } else { len = strlen(line); } if (STRPREFIX(path, line) && path[len] == '.') { ret = 1; goto cleanup; } } if (ferror(fp)) { virReportSystemError(errno, "%s", _("Error while reading /proc/cgroups")); goto cleanup; } cleanup: VIR_FORCE_FCLOSE(fp); return ret; } int virCgroupPartitionEscape(char **path) { int rc; char *newstr = NULL; if ((rc = virCgroupPartitionNeedsEscaping(*path)) <= 0) return rc; newstr = g_strdup_printf("_%s", *path); VIR_FREE(*path); *path = newstr; return 0; } static int virCgroupSetBackends(virCgroup *group) { virCgroupBackend **backends = virCgroupBackendGetAll(); bool backendAvailable = false; size_t i; if (!backends) return -1; for (i = 0; i < VIR_CGROUP_BACKEND_TYPE_LAST; i++) { if (backends[i] && backends[i]->available()) { group->backends[i] = backends[i]; backendAvailable = true; } } if (!backendAvailable) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("no cgroup backend available")); return -1; } return 0; } static int virCgroupCopyMounts(virCgroup *group, virCgroup *parent) { size_t i; for (i = 0; i < VIR_CGROUP_BACKEND_TYPE_LAST; i++) { if (group->backends[i] && group->backends[i]->copyMounts(group, parent) < 0) { return -1; } } return 0; } /* * Process /proc/mounts figuring out what controllers are * mounted and where */ static int virCgroupDetectMounts(virCgroup *group) { FILE *mounts = NULL; struct mntent entry; char buf[CGROUP_MAX_VAL]; int ret = -1; size_t i; mounts = fopen("/proc/mounts", "r"); if (mounts == NULL) { virReportSystemError(errno, "%s", _("Unable to open /proc/mounts")); return -1; } while (getmntent_r(mounts, &entry, buf, sizeof(buf)) != NULL) { for (i = 0; i < VIR_CGROUP_BACKEND_TYPE_LAST; i++) { if (group->backends[i] && group->backends[i]->detectMounts(group, entry.mnt_type, entry.mnt_opts, entry.mnt_dir) < 0) { goto cleanup; } } } ret = 0; cleanup: VIR_FORCE_FCLOSE(mounts); return ret; } static int virCgroupCopyPlacement(virCgroup *group, const char *path, virCgroup *parent) { size_t i; for (i = 0; i < VIR_CGROUP_BACKEND_TYPE_LAST; i++) { if (group->backends[i] && group->backends[i]->copyPlacement(group, path, parent) < 0) { return -1; } } return 0; } /* * virCgroupDetectPlacement: * @group: the group to process * @path: the relative path to append, not starting with '/' * * Process /proc/self/cgroup figuring out what cgroup * sub-path the current process is assigned to. ie not * necessarily in the root. The contents of this file * looks like * * 9:perf_event:/ * 8:blkio:/ * 7:net_cls:/ * 6:freezer:/ * 5:devices:/ * 4:memory:/ * 3:cpuacct,cpu:/ * 2:cpuset:/ * 1:name=systemd:/user/berrange/2 * * It then appends @path to each detected path. */ static int virCgroupDetectPlacement(virCgroup *group, pid_t pid, const char *path) { FILE *mapping = NULL; char line[1024]; int ret = -1; g_autofree char *procfile = NULL; VIR_DEBUG("Detecting placement for pid %lld path %s", (long long) pid, path); if (pid == -1) { procfile = g_strdup("/proc/self/cgroup"); } else { procfile = g_strdup_printf("/proc/%lld/cgroup", (long long)pid); } mapping = fopen(procfile, "r"); if (mapping == NULL) { virReportSystemError(errno, _("Unable to open '%s'"), procfile); goto cleanup; } while (fgets(line, sizeof(line), mapping) != NULL) { size_t i; char *controllers = strchr(line, ':'); char *selfpath = controllers ? strchr(controllers + 1, ':') : NULL; char *nl = selfpath ? strchr(selfpath, '\n') : NULL; if (!controllers || !selfpath) continue; if (nl) *nl = '\0'; *selfpath = '\0'; controllers++; selfpath++; for (i = 0; i < VIR_CGROUP_BACKEND_TYPE_LAST; i++) { if (group->backends[i] && group->backends[i]->detectPlacement(group, path, controllers, selfpath) < 0) { goto cleanup; } } } ret = 0; cleanup: VIR_FORCE_FCLOSE(mapping); return ret; } static int virCgroupSetPlacement(virCgroup *group, const char *path) { size_t i; for (i = 0; i < VIR_CGROUP_BACKEND_TYPE_LAST; i++) { if (group->backends[i] && group->backends[i]->setPlacement(group, path) < 0) { return -1; } } return 0; } static int virCgroupValidatePlacement(virCgroup *group, pid_t pid) { size_t i; for (i = 0; i < VIR_CGROUP_BACKEND_TYPE_LAST; i++) { if (group->backends[i] && group->backends[i]->validatePlacement(group, pid) < 0) { return -1; } } return 0; } static int virCgroupDetectControllers(virCgroup *group, int controllers, virCgroup *parent) { size_t i; int controllersAvailable = 0; for (i = 0; i < VIR_CGROUP_BACKEND_TYPE_LAST; i++) { int rc; if (!group->backends[i]) continue; rc = group->backends[i]->detectControllers(group, controllers, parent, controllersAvailable); if (rc < 0) return -1; controllersAvailable |= rc; } /* Check that at least 1 controller is available */ if (controllersAvailable == 0) { virReportSystemError(ENXIO, "%s", _("At least one cgroup controller is required")); return -1; } return 0; } char * virCgroupGetBlockDevString(const char *path) { struct stat sb; if (stat(path, &sb) < 0) { virReportSystemError(errno, _("Path '%s' is not accessible"), path); return NULL; } if (!S_ISBLK(sb.st_mode)) { virReportSystemError(EINVAL, _("Path '%s' must be a block device"), path); return NULL; } /* Automatically append space after the string since all callers * use it anyway */ return g_strdup_printf("%d:%d ", major(sb.st_rdev), minor(sb.st_rdev)); } int virCgroupSetValueDBus(const char *unitName, const char *key, GVariant *value) { GDBusConnection *conn; g_autoptr(GVariant) message = NULL; GVariantBuilder builder; GVariant *props = NULL; g_variant_builder_init(&builder, G_VARIANT_TYPE("a(sv)")); g_variant_builder_add(&builder, "(sv)", key, value); props = g_variant_builder_end(&builder); message = g_variant_new("(sb@a(sv))", unitName, true, props); if (!(conn = virGDBusGetSystemBus())) return -1; return virGDBusCallMethod(conn, NULL, NULL, NULL, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "SetUnitProperties", message); } int virCgroupSetValueRaw(const char *path, const char *value) { char *tmp; VIR_DEBUG("Set value '%s' to '%s'", path, value); if (virFileWriteStr(path, value, 0) < 0) { if (errno == EINVAL && (tmp = strrchr(path, '/'))) { virReportSystemError(errno, _("Invalid value '%s' for '%s'"), value, tmp + 1); return -1; } virReportSystemError(errno, _("Unable to write to '%s'"), path); return -1; } return 0; } int virCgroupGetValueRaw(const char *path, char **value) { int rc; *value = NULL; VIR_DEBUG("Get value %s", path); if ((rc = virFileReadAll(path, 1024*1024, value)) < 0) { virReportSystemError(errno, _("Unable to read from '%s'"), path); return -1; } /* Terminated with '\n' has sometimes harmful effects to the caller */ if (rc > 0 && (*value)[rc - 1] == '\n') (*value)[rc - 1] = '\0'; return 0; } int virCgroupSetValueStr(virCgroup *group, int controller, const char *key, const char *value) { g_autofree char *keypath = NULL; if (virCgroupPathOfController(group, controller, key, &keypath) < 0) return -1; return virCgroupSetValueRaw(keypath, value); } int virCgroupGetValueStr(virCgroup *group, int controller, const char *key, char **value) { g_autofree char *keypath = NULL; if (virCgroupPathOfController(group, controller, key, &keypath) < 0) return -1; return virCgroupGetValueRaw(keypath, value); } int virCgroupGetValueForBlkDev(const char *str, const char *path, char **value) { g_autofree char *prefix = NULL; g_auto(GStrv) lines = NULL; GStrv tmp; if (!(prefix = virCgroupGetBlockDevString(path))) return -1; if (!(lines = g_strsplit(str, "\n", -1))) return -1; for (tmp = lines; *tmp; tmp++) { if ((*value = g_strdup(STRSKIP(*tmp, prefix)))) break; } return 0; } int virCgroupSetValueU64(virCgroup *group, int controller, const char *key, unsigned long long int value) { g_autofree char *strval = NULL; strval = g_strdup_printf("%llu", value); return virCgroupSetValueStr(group, controller, key, strval); } int virCgroupSetValueI64(virCgroup *group, int controller, const char *key, long long int value) { g_autofree char *strval = NULL; strval = g_strdup_printf("%lld", value); return virCgroupSetValueStr(group, controller, key, strval); } int virCgroupGetValueI64(virCgroup *group, int controller, const char *key, long long int *value) { g_autofree char *strval = NULL; if (virCgroupGetValueStr(group, controller, key, &strval) < 0) return -1; if (virStrToLong_ll(strval, NULL, 10, value) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Unable to parse '%s' as an integer"), strval); return -1; } return 0; } int virCgroupGetValueU64(virCgroup *group, int controller, const char *key, unsigned long long int *value) { g_autofree char *strval = NULL; if (virCgroupGetValueStr(group, controller, key, &strval) < 0) return -1; if (virStrToLong_ull(strval, NULL, 10, value) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Unable to parse '%s' as an integer"), strval); return -1; } return 0; } static int virCgroupMakeGroup(virCgroup *parent, virCgroup *group, bool create, pid_t pid, unsigned int flags) { size_t i; for (i = 0; i < VIR_CGROUP_BACKEND_TYPE_LAST; i++) { if (group->backends[i] && group->backends[i]->makeGroup(parent, group, create, pid, flags) < 0) { virCgroupRemove(group); return -1; } } return 0; } static bool virCgroupExists(virCgroup *group) { size_t i; for (i = 0; i < VIR_CGROUP_BACKEND_TYPE_LAST; i++) { if (group->backends[i] && !group->backends[i]->exists(group)) { return false; } } return true; } /** * virCgroupNew: * @path: path for the new group * @controllers: bitmask of controllers to activate * * Create a new cgroup storing it in @group. * * Returns 0 on success, -1 on error */ int virCgroupNew(const char *path, int controllers, virCgroup **group) { g_autoptr(virCgroup) newGroup = NULL; VIR_DEBUG("path=%s controllers=%d group=%p", path, controllers, group); *group = NULL; newGroup = g_new0(virCgroup, 1); if (virCgroupSetBackends(newGroup) < 0) return -1; if (virCgroupDetectMounts(newGroup) < 0) return -1; if (virCgroupSetPlacement(newGroup, path) < 0) return -1; /* ... but use /proc/cgroups to fill in the rest */ if (virCgroupDetectPlacement(newGroup, -1, path) < 0) return -1; /* Check that for every mounted controller, we found our placement */ if (virCgroupValidatePlacement(newGroup, -1) < 0) return -1; if (virCgroupDetectControllers(newGroup, controllers, NULL) < 0) return -1; *group = g_steal_pointer(&newGroup); return 0; } static int virCgroupAddTaskInternal(virCgroup *group, pid_t pid, unsigned int flags) { size_t i; virCgroup *parent = virCgroupGetNested(group); for (i = 0; i < VIR_CGROUP_BACKEND_TYPE_LAST; i++) { if (parent->backends[i] && parent->backends[i]->addTask(parent, pid, flags) < 0) { return -1; } } return 0; } /** * virCgroupAddProcess: * * @group: The cgroup to add a process to * @pid: The pid of the process to add * * Will add the process to all controllers, except the * systemd unit controller. * * Returns: 0 on success, -1 on error */ int virCgroupAddProcess(virCgroup *group, pid_t pid) { return virCgroupAddTaskInternal(group, pid, VIR_CGROUP_TASK_PROCESS); } /** * virCgroupAddMachineProcess: * * @group: The cgroup to add a process to * @pid: The pid of the process to add * * Will add the process to all controllers, including the * systemd unit controller. * * Returns: 0 on success, -1 on error */ int virCgroupAddMachineProcess(virCgroup *group, pid_t pid) { return virCgroupAddTaskInternal(group, pid, VIR_CGROUP_TASK_PROCESS | VIR_CGROUP_TASK_SYSTEMD); } /** * virCgroupAddThread: * * @group: The cgroup to add a thread to * @pid: The pid of the thread to add * * Will add the thread to all controllers, except the * systemd unit controller. * * Returns: 0 on success, -1 on error */ int virCgroupAddThread(virCgroup *group, pid_t pid) { return virCgroupAddTaskInternal(group, pid, VIR_CGROUP_TASK_THREAD); } static int virCgroupSetPartitionSuffix(const char *path, char **res) { g_auto(GStrv) tokens = NULL; size_t i; int ret = -1; if (!(tokens = g_strsplit(path, "/", 0))) return ret; for (i = 0; tokens[i] != NULL; i++) { /* Special case the 3 top level fixed dirs * NB i == 0 is "", since we have leading '/' */ if (i == 1 && (STREQ(tokens[i], "machine") || STREQ(tokens[i], "system") || STREQ(tokens[i], "user"))) { continue; } /* If there is no suffix set already, then * add ".partition" */ if (STRNEQ(tokens[i], "") && !strchr(tokens[i], '.')) { g_autofree char *oldtoken = tokens[i]; tokens[i] = g_strdup_printf("%s.partition", oldtoken); } if (virCgroupPartitionEscape(&(tokens[i])) < 0) goto cleanup; } if (!(*res = g_strjoinv("/", tokens))) goto cleanup; ret = 0; cleanup: return ret; } static int virCgroupNewFromParent(virCgroup *parent, const char *path, int controllers, virCgroup **group) { g_autoptr(virCgroup) new = g_new0(virCgroup, 1); VIR_DEBUG("parent=%p path=%s controllers=%d group=%p", parent, path, controllers, group); if (virCgroupSetBackends(new) < 0) return -1; if (virCgroupCopyMounts(new, parent) < 0) return -1; if (virCgroupCopyPlacement(new, path, parent) < 0) return -1; if (virCgroupDetectPlacement(new, -1, path) < 0) return -1; if (virCgroupValidatePlacement(new, -1) < 0) return -1; if (virCgroupDetectControllers(new, controllers, parent) < 0) return -1; *group = g_steal_pointer(&new); return 0; } /** * virCgroupNewPartition: * @path: path for the partition * @create: true to create the cgroup tree * @controllers: mask of controllers to create * * Creates a new cgroup to represent the resource * partition path identified by @path. * * Returns 0 on success, -1 on failure */ int virCgroupNewPartition(const char *path, bool create, int controllers, virCgroup **group) { g_autofree char *newPath = NULL; g_autoptr(virCgroup) parent = NULL; g_autoptr(virCgroup) newGroup = NULL; char *partition = NULL; VIR_DEBUG("path=%s create=%d controllers=%x", path, create, controllers); *group = NULL; if (!g_path_is_absolute(path)) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Partition path '%s' must start with '/'"), path); return -1; } if (virCgroupSetPartitionSuffix(path, &newPath) < 0) return -1; if (STRNEQ(newPath, "/")) { char *tmp; const char *parentPath; tmp = strrchr(newPath, '/'); *tmp = '\0'; if (tmp == newPath) { parentPath = "/"; } else { parentPath = newPath; } if (virCgroupNew(parentPath, controllers, &parent) < 0) return -1; partition = tmp + 1; } else { partition = newPath; } if (virCgroupNewFromParent(parent, partition, controllers, &newGroup) < 0) return -1; if (parent) { if (virCgroupMakeGroup(parent, newGroup, create, -1, VIR_CGROUP_NONE) < 0) return -1; } *group = g_steal_pointer(&newGroup); return 0; } static int virCgroupNewNested(virCgroup *parent, int controllers, bool create, pid_t pid, virCgroup **nested) { g_autoptr(virCgroup) new = NULL; if (virCgroupNewFromParent(parent, "libvirt", controllers, &new) < 0) return -1; if (create) { if (virCgroupMakeGroup(parent, new, create, pid, VIR_CGROUP_NONE) < 0) return -1; } *nested = g_steal_pointer(&new); return 0; } /** * virCgroupNewSelf: * * @group: Pointer to returned virCgroup * * * Obtain a cgroup representing the config of the * current process * * Returns 0 on success, or -1 on error */ int virCgroupNewSelf(virCgroup **group) { return virCgroupNewDetect(-1, -1, group); } /** * virCgroupNewDomainPartition: * * @partition: partition holding the domain * @driver: name of the driver * @name: name of the domain * @group: Pointer to returned virCgroup * * * Returns 0 on success, or -1 on error */ int virCgroupNewDomainPartition(virCgroup *partition, const char *driver, const char *name, virCgroup **group) { g_autofree char *grpname = NULL; g_autoptr(virCgroup) newGroup = NULL; grpname = g_strdup_printf("%s.libvirt-%s", name, driver); if (virCgroupPartitionEscape(&grpname) < 0) return -1; if (virCgroupNewFromParent(partition, grpname, -1, &newGroup) < 0) return -1; /* * Create a cgroup with memory.use_hierarchy enabled to * surely account memory usage of lxc with ns subsystem * enabled. (To be exact, memory and ns subsystems are * enabled at the same time.) * * The reason why doing it here, not a upper group, say * a group for driver, is to avoid overhead to track * cumulative usage that we don't need. */ if (virCgroupMakeGroup(partition, newGroup, true, -1, VIR_CGROUP_MEM_HIERACHY) < 0) { return -1; } *group = g_steal_pointer(&newGroup); return 0; } /** * virCgroupNewThread: * * @domain: group for the domain * @name: enum to generate the name for the new thread * @id: id of the vcpu or iothread * @create: true to create if not already existing * @group: Pointer to returned virCgroup * * * Returns 0 on success, or -1 on error */ int virCgroupNewThread(virCgroup *domain, virCgroupThreadName nameval, int id, bool create, virCgroup **group) { g_autofree char *name = NULL; g_autoptr(virCgroup) newGroup = NULL; virCgroup *parent = NULL; int controllers; *group = NULL; switch (nameval) { case VIR_CGROUP_THREAD_VCPU: name = g_strdup_printf("vcpu%d", id); break; case VIR_CGROUP_THREAD_EMULATOR: name = g_strdup("emulator"); break; case VIR_CGROUP_THREAD_IOTHREAD: name = g_strdup_printf("iothread%d", id); break; case VIR_CGROUP_THREAD_LAST: virReportError(VIR_ERR_INTERNAL_ERROR, _("unexpected name value %d"), nameval); return -1; } controllers = ((1 << VIR_CGROUP_CONTROLLER_CPU) | (1 << VIR_CGROUP_CONTROLLER_CPUACCT) | (1 << VIR_CGROUP_CONTROLLER_CPUSET)); parent = virCgroupGetNested(domain); if (virCgroupNewFromParent(parent, name, controllers, &newGroup) < 0) return -1; if (virCgroupMakeGroup(parent, newGroup, create, -1, VIR_CGROUP_THREAD) < 0) return -1; *group = g_steal_pointer(&newGroup); return 0; } int virCgroupNewDetect(pid_t pid, int controllers, virCgroup **group) { g_autoptr(virCgroup) new = g_new0(virCgroup, 1); VIR_DEBUG("pid=%lld controllers=%d group=%p", (long long) pid, controllers, group); if (virCgroupSetBackends(new) < 0) return -1; if (virCgroupDetectMounts(new) < 0) return -1; if (virCgroupDetectPlacement(new, pid, "") < 0) return -1; if (virCgroupValidatePlacement(new, pid) < 0) return -1; if (virCgroupDetectControllers(new, controllers, NULL) < 0) return -1; *group = g_steal_pointer(&new); return 0; } /* * Returns 0 on success (but @group may be NULL), -1 on fatal error */ int virCgroupNewDetectMachine(const char *name, const char *drivername, pid_t pid, int controllers, char *machinename, virCgroup **group) { size_t i; g_autoptr(virCgroup) newGroup = NULL; g_autoptr(virCgroup) nested = NULL; *group = NULL; if (virCgroupNewDetect(pid, controllers, &newGroup) < 0) { if (virCgroupNewIgnoreError()) return 0; return -1; } for (i = 0; i < VIR_CGROUP_BACKEND_TYPE_LAST; i++) { if (newGroup->backends[i] && !newGroup->backends[i]->validateMachineGroup(newGroup, name, drivername, machinename)) { VIR_DEBUG("Failed to validate machine name for '%s' driver '%s'", name, drivername); return 0; } } newGroup->unitName = virSystemdGetMachineUnitByPID(pid); if (virSystemdHasMachined() == 0 && !newGroup->unitName) return -1; if (virCgroupNewNested(newGroup, controllers, false, -1, &nested) < 0) return -1; if (virCgroupExists(nested)) newGroup->nested = g_steal_pointer(&nested); *group = g_steal_pointer(&newGroup); return 0; } static int virCgroupEnableMissingControllers(char *path, int controllers, virCgroup **group) { g_autoptr(virCgroup) parent = NULL; g_auto(GStrv) tokens = g_strsplit(path, "/", 0); size_t i; if (virCgroupNew("/", controllers, &parent) < 0) return -1; /* Skip the first token as it is empty string. */ for (i = 1; tokens[i]; i++) { g_autoptr(virCgroup) tmp = NULL; if (virCgroupNewFromParent(parent, tokens[i], controllers, &tmp) < 0) return -1; if (virCgroupMakeGroup(parent, tmp, true, -1, VIR_CGROUP_SYSTEMD) < 0) return -1; virCgroupFree(parent); parent = g_steal_pointer(&tmp); } *group = g_steal_pointer(&parent); return 0; } /* * Returns 0 on success, -1 on fatal error, -2 on systemd not available */ static int virCgroupNewMachineSystemd(const char *name, const char *drivername, const unsigned char *uuid, const char *rootdir, pid_t pidleader, bool isContainer, size_t nnicindexes, int *nicindexes, const char *partition, int controllers, unsigned int maxthreads, virCgroup **group) { int rv; g_autoptr(virCgroup) init = NULL; g_autoptr(virCgroup) newGroup = NULL; g_autoptr(virCgroup) nested = NULL; g_autofree char *path = NULL; size_t i; VIR_DEBUG("Trying to setup machine '%s' via systemd", name); if ((rv = virSystemdCreateMachine(name, drivername, uuid, rootdir, pidleader, isContainer, nnicindexes, nicindexes, partition, maxthreads)) < 0) return rv; if (controllers != -1) controllers |= (1 << VIR_CGROUP_CONTROLLER_SYSTEMD); VIR_DEBUG("Detecting systemd placement"); if (virCgroupNewDetect(pidleader, controllers, &init) < 0) return -1; for (i = 0; i < VIR_CGROUP_BACKEND_TYPE_LAST; i++) { if (init->backends[i] && (path = init->backends[i]->stealPlacement(init))) { break; } } if (!path || STREQ(path, "/") || !g_path_is_absolute(path)) { VIR_DEBUG("Systemd didn't setup its controller, path=%s", NULLSTR(path)); return -2; } if (virCgroupEnableMissingControllers(path, controllers, &newGroup) < 0) return -1; newGroup->unitName = virSystemdGetMachineUnitByPID(pidleader); if (!newGroup->unitName) return -1; if (virCgroupNewNested(newGroup, controllers, true, pidleader, &nested) < 0) return -1; newGroup->nested = g_steal_pointer(&nested); if (virCgroupAddProcess(newGroup, pidleader) < 0) { virErrorPtr saved; virErrorPreserveLast(&saved); virCgroupRemove(newGroup); virErrorRestore(&saved); return 0; } *group = g_steal_pointer(&newGroup); return 0; } /* * Returns 0 on success, -1 on fatal error */ int virCgroupTerminateMachine(const char *name) { return virSystemdTerminateMachine(name); } static int virCgroupNewMachineManual(const char *name, const char *drivername, pid_t pidleader, const char *partition, int controllers, virCgroup **group) { g_autoptr(virCgroup) parent = NULL; g_autoptr(virCgroup) newGroup = NULL; VIR_DEBUG("Fallback to non-systemd setup"); if (virCgroupNewPartition(partition, STREQ(partition, "/machine"), controllers, &parent) < 0) { if (virCgroupNewIgnoreError()) return 0; return -1; } if (virCgroupNewDomainPartition(parent, drivername, name, &newGroup) < 0) return -1; if (virCgroupAddProcess(newGroup, pidleader) < 0) { virErrorPtr saved; virErrorPreserveLast(&saved); virCgroupRemove(newGroup); virErrorRestore(&saved); } *group = g_steal_pointer(&newGroup); return 0; } int virCgroupNewMachine(const char *name, const char *drivername, const unsigned char *uuid, const char *rootdir, pid_t pidleader, bool isContainer, size_t nnicindexes, int *nicindexes, const char *partition, int controllers, unsigned int maxthreads, virCgroup **group) { int rv; *group = NULL; if ((rv = virCgroupNewMachineSystemd(name, drivername, uuid, rootdir, pidleader, isContainer, nnicindexes, nicindexes, partition, controllers, maxthreads, group)) == 0) return 0; if (rv == -1) return -1; return virCgroupNewMachineManual(name, drivername, pidleader, partition, controllers, group); } bool virCgroupNewIgnoreError(void) { if (virLastErrorIsSystemErrno(ENXIO) || virLastErrorIsSystemErrno(EPERM) || virLastErrorIsSystemErrno(EACCES)) { virResetLastError(); VIR_DEBUG("No cgroups present/configured/accessible, ignoring error"); return true; } return false; } /** * virCgroupHasController: query whether a cgroup controller is present * * @cgroup: The group structure to be queried, or NULL * @controller: cgroup subsystem id * * Returns true if a cgroup controller is mounted and is associated * with this cgroup object. */ bool virCgroupHasController(virCgroup *cgroup, int controller) { size_t i; if (!cgroup) return false; if (controller < 0 || controller >= VIR_CGROUP_CONTROLLER_LAST) return false; for (i = 0; i < VIR_CGROUP_BACKEND_TYPE_LAST; i++) { if (cgroup->backends[i] && cgroup->backends[i]->hasController(cgroup, controller)) { return true; } } return false; } static int virCgroupGetAnyController(virCgroup *cgroup) { size_t i; for (i = 0; i < VIR_CGROUP_BACKEND_TYPE_LAST; i++) { if (!cgroup->backends[i]) continue; return cgroup->backends[i]->getAnyController(cgroup); } virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Unable to get any controller")); return -1; } int virCgroupPathOfController(virCgroup *group, unsigned int controller, const char *key, char **path) { if (controller >= VIR_CGROUP_CONTROLLER_LAST) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Invalid controller id '%d'"), controller); return -1; } VIR_CGROUP_BACKEND_CALL(group, controller, pathOfController, -1, controller, key, path); } /** * virCgroupGetBlkioIoServiced: * * @group: The cgroup to get throughput for * @bytes_read: Pointer to returned bytes read * @bytes_write: Pointer to returned bytes written * @requests_read: Pointer to returned read io ops * @requests_write: Pointer to returned write io ops * * Returns: 0 on success, -1 on error */ int virCgroupGetBlkioIoServiced(virCgroup *group, long long *bytes_read, long long *bytes_write, long long *requests_read, long long *requests_write) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_BLKIO, getBlkioIoServiced, -1, bytes_read, bytes_write, requests_read, requests_write); } /** * virCgroupGetBlkioIoDeviceServiced: * * @group: The cgroup to get throughput for * @path: The device to get throughput for * @bytes_read: Pointer to returned bytes read * @bytes_write: Pointer to returned bytes written * @requests_read: Pointer to returned read io ops * @requests_write: Pointer to returned write io ops * * Returns: 0 on success, -1 on error */ int virCgroupGetBlkioIoDeviceServiced(virCgroup *group, const char *path, long long *bytes_read, long long *bytes_write, long long *requests_read, long long *requests_write) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_BLKIO, getBlkioIoDeviceServiced, -1, path, bytes_read, bytes_write, requests_read, requests_write); } /** * virCgroupSetBlkioWeight: * * @group: The cgroup to change io weight for * @weight: The Weight for this cgroup * * Returns: 0 on success, -1 on error */ int virCgroupSetBlkioWeight(virCgroup *group, unsigned int weight) { VIR_CGROUP_BACKEND_CALL(group, VIR_CGROUP_CONTROLLER_BLKIO, setBlkioWeight, -1, weight); } /** * virCgroupGetBlkioWeight: * * @group: The cgroup to get weight for * @Weight: Pointer to returned weight * * Returns: 0 on success, -1 on error */ int virCgroupGetBlkioWeight(virCgroup *group, unsigned int *weight) { VIR_CGROUP_BACKEND_CALL(group, VIR_CGROUP_CONTROLLER_BLKIO, getBlkioWeight, -1, weight); } /** * virCgroupSetBlkioDeviceReadIops: * @group: The cgroup to change block io setting for * @path: The path of device * @riops: The new device read iops throttle, or 0 to clear * * Returns: 0 on success, -1 on error */ static int virCgroupSetBlkioDeviceReadIops(virCgroup *group, const char *path, unsigned int riops) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_BLKIO, setBlkioDeviceReadIops, -1, path, riops); } /** * virCgroupSetBlkioDeviceWriteIops: * @group: The cgroup to change block io setting for * @path: The path of device * @wiops: The new device write iops throttle, or 0 to clear * * Returns: 0 on success, -1 on error */ static int virCgroupSetBlkioDeviceWriteIops(virCgroup *group, const char *path, unsigned int wiops) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_BLKIO, setBlkioDeviceWriteIops, -1, path, wiops); } /** * virCgroupSetBlkioDeviceReadBps: * @group: The cgroup to change block io setting for * @path: The path of device * @rbps: The new device read bps throttle, or 0 to clear * * Returns: 0 on success, -1 on error */ static int virCgroupSetBlkioDeviceReadBps(virCgroup *group, const char *path, unsigned long long rbps) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_BLKIO, setBlkioDeviceReadBps, -1, path, rbps); } /** * virCgroupSetBlkioDeviceWriteBps: * @group: The cgroup to change block io setting for * @path: The path of device * @wbps: The new device write bps throttle, or 0 to clear * * Returns: 0 on success, -1 on error */ static int virCgroupSetBlkioDeviceWriteBps(virCgroup *group, const char *path, unsigned long long wbps) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_BLKIO, setBlkioDeviceWriteBps, -1, path, wbps); } /** * virCgroupSetBlkioDeviceWeight: * @group: The cgroup to change block io setting for * @path: The path of device * @weight: The new device weight (100-1000), * (10-1000) after kernel 2.6.39, or 0 to clear * * Returns: 0 on success, -1 on error */ static int virCgroupSetBlkioDeviceWeight(virCgroup *group, const char *path, unsigned int weight) { VIR_CGROUP_BACKEND_CALL(group, VIR_CGROUP_CONTROLLER_BLKIO, setBlkioDeviceWeight, -1, path, weight); } /** * virCgroupGetBlkioDeviceReadIops: * @group: The cgroup to gather block io setting for * @path: The path of device * @riops: Returned device read iops throttle, 0 if there is none * * Returns: 0 on success, -1 on error */ static int virCgroupGetBlkioDeviceReadIops(virCgroup *group, const char *path, unsigned int *riops) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_BLKIO, getBlkioDeviceReadIops, -1, path, riops); } /** * virCgroupGetBlkioDeviceWriteIops: * @group: The cgroup to gather block io setting for * @path: The path of device * @wiops: Returned device write iops throttle, 0 if there is none * * Returns: 0 on success, -1 on error */ static int virCgroupGetBlkioDeviceWriteIops(virCgroup *group, const char *path, unsigned int *wiops) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_BLKIO, getBlkioDeviceWriteIops, -1, path, wiops); } /** * virCgroupGetBlkioDeviceReadBps: * @group: The cgroup to gather block io setting for * @path: The path of device * @rbps: Returned device read bps throttle, 0 if there is none * * Returns: 0 on success, -1 on error */ static int virCgroupGetBlkioDeviceReadBps(virCgroup *group, const char *path, unsigned long long *rbps) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_BLKIO, getBlkioDeviceReadBps, -1, path, rbps); } /** * virCgroupGetBlkioDeviceWriteBps: * @group: The cgroup to gather block io setting for * @path: The path of device * @wbps: Returned device write bps throttle, 0 if there is none * * Returns: 0 on success, -1 on error */ static int virCgroupGetBlkioDeviceWriteBps(virCgroup *group, const char *path, unsigned long long *wbps) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_BLKIO, getBlkioDeviceWriteBps, -1, path, wbps); } /** * virCgroupGetBlkioDeviceWeight: * @group: The cgroup to gather block io setting for * @path: The path of device * @weight: Returned device weight, 0 if there is none * * Returns: 0 on success, -1 on error */ static int virCgroupGetBlkioDeviceWeight(virCgroup *group, const char *path, unsigned int *weight) { VIR_CGROUP_BACKEND_CALL(group, VIR_CGROUP_CONTROLLER_BLKIO, getBlkioDeviceWeight, -1, path, weight); } /** * virCgroupSetMemory: * * @group: The cgroup to change memory for * @kb: The memory amount in kilobytes * * Returns: 0 on success */ int virCgroupSetMemory(virCgroup *group, unsigned long long kb) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_MEMORY, setMemory, -1, kb); } /** * virCgroupGetMemoryStat: * * @group: The cgroup to change memory for * @cache: page cache memory in KiB * @activeAnon: anonymous and swap cache memory in KiB * @inactiveAnon: anonymous and swap cache memory in KiB * @activeFile: file-backed memory in KiB * @inactiveFile: file-backed memory in KiB * @unevictable: memory that cannot be reclaimed KiB * * Returns: 0 on success, -1 on error */ int virCgroupGetMemoryStat(virCgroup *group, unsigned long long *cache, unsigned long long *activeAnon, unsigned long long *inactiveAnon, unsigned long long *activeFile, unsigned long long *inactiveFile, unsigned long long *unevictable) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_MEMORY, getMemoryStat, -1, cache, activeAnon, inactiveAnon, activeFile, inactiveFile, unevictable); } /** * virCgroupGetMemoryUsage: * * @group: The cgroup to change memory for * @kb: Pointer to returned used memory in kilobytes * * Returns: 0 on success */ int virCgroupGetMemoryUsage(virCgroup *group, unsigned long *kb) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_MEMORY, getMemoryUsage, -1, kb); } /** * virCgroupSetMemoryHardLimit: * * @group: The cgroup to change memory hard limit for * @kb: The memory amount in kilobytes * * Returns: 0 on success */ int virCgroupSetMemoryHardLimit(virCgroup *group, unsigned long long kb) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_MEMORY, setMemoryHardLimit, -1, kb); } /** * virCgroupGetMemoryHardLimit: * * @group: The cgroup to get the memory hard limit for * @kb: The memory amount in kilobytes * * Returns: 0 on success */ int virCgroupGetMemoryHardLimit(virCgroup *group, unsigned long long *kb) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_MEMORY, getMemoryHardLimit, -1, kb); } /** * virCgroupSetMemorySoftLimit: * * @group: The cgroup to change memory soft limit for * @kb: The memory amount in kilobytes * * Returns: 0 on success */ int virCgroupSetMemorySoftLimit(virCgroup *group, unsigned long long kb) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_MEMORY, setMemorySoftLimit, -1, kb); } /** * virCgroupGetMemorySoftLimit: * * @group: The cgroup to get the memory soft limit for * @kb: The memory amount in kilobytes * * Returns: 0 on success */ int virCgroupGetMemorySoftLimit(virCgroup *group, unsigned long long *kb) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_MEMORY, getMemorySoftLimit, -1, kb); } /** * virCgroupSetMemSwapHardLimit: * * @group: The cgroup to change mem+swap hard limit for * @kb: The mem+swap amount in kilobytes * * Returns: 0 on success */ int virCgroupSetMemSwapHardLimit(virCgroup *group, unsigned long long kb) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_MEMORY, setMemSwapHardLimit, -1, kb); } /** * virCgroupGetMemSwapHardLimit: * * @group: The cgroup to get mem+swap hard limit for * @kb: The mem+swap amount in kilobytes * * Returns: 0 on success */ int virCgroupGetMemSwapHardLimit(virCgroup *group, unsigned long long *kb) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_MEMORY, getMemSwapHardLimit, -1, kb); } /** * virCgroupGetMemSwapUsage: * * @group: The cgroup to get mem+swap usage for * @kb: The mem+swap amount in kilobytes * * Returns: 0 on success */ int virCgroupGetMemSwapUsage(virCgroup *group, unsigned long long *kb) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_MEMORY, getMemSwapUsage, -1, kb); } /** * virCgroupSetCpusetMems: * * @group: The cgroup to set cpuset.mems for * @mems: the numa nodes to set * * Returns: 0 on success */ int virCgroupSetCpusetMems(virCgroup *group, const char *mems) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_CPUSET, setCpusetMems, -1, mems); } /** * virCgroupGetCpusetMems: * * @group: The cgroup to get cpuset.mems for * @mems: the numa nodes to get * * Returns: 0 on success */ int virCgroupGetCpusetMems(virCgroup *group, char **mems) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_CPUSET, getCpusetMems, -1, mems); } /** * virCgroupSetCpusetMemoryMigrate: * * @group: The cgroup to set cpuset.memory_migrate for * @migrate: Whether to migrate the memory on change or not * * Returns: 0 on success */ int virCgroupSetCpusetMemoryMigrate(virCgroup *group, bool migrate) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_CPUSET, setCpusetMemoryMigrate, -1, migrate); } /** * virCgroupGetCpusetMemoryMigrate: * * @group: The cgroup to get cpuset.memory_migrate for * @migrate: Migration setting * * Returns: 0 on success */ int virCgroupGetCpusetMemoryMigrate(virCgroup *group, bool *migrate) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_CPUSET, getCpusetMemoryMigrate, -1, migrate); } /** * virCgroupSetCpusetCpus: * * @group: The cgroup to set cpuset.cpus for * @cpus: the cpus to set * * Returns: 0 on success */ int virCgroupSetCpusetCpus(virCgroup *group, const char *cpus) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_CPUSET, setCpusetCpus, -1, cpus); } /** * virCgroupGetCpusetCpus: * * @group: The cgroup to get cpuset.cpus for * @cpus: the cpus to get * * Returns: 0 on success */ int virCgroupGetCpusetCpus(virCgroup *group, char **cpus) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_CPUSET, getCpusetCpus, -1, cpus); } /** * virCgroupDenyAllDevices: * * @group: The cgroup to deny all permissions, for all devices * * Returns: 0 on success */ int virCgroupDenyAllDevices(virCgroup *group) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_DEVICES, denyAllDevices, -1); } /** * virCgroupAllowAllDevices: * * Allows the permission for all devices by setting lines similar * to these ones (obviously the 'm' permission is an example): * * 'b *:* m' * 'c *:* m' * * @group: The cgroup to allow devices for * @perms: Bitwise or of VIR_CGROUP_DEVICE permission bits to allow * * Returns: 0 on success */ int virCgroupAllowAllDevices(virCgroup *group, int perms) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_DEVICES, allowAllDevices, -1, perms); } /** * virCgroupAllowDevice: * * @group: The cgroup to allow a device for * @type: The device type (i.e., 'c' or 'b') * @major: The major number of the device, a negative value means '*' * @minor: The minor number of the device, a negative value means '*' * @perms: Bitwise or of VIR_CGROUP_DEVICE permission bits to allow * * Returns: 0 on success */ int virCgroupAllowDevice(virCgroup *group, char type, int major, int minor, int perms) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_DEVICES, allowDevice, -1, type, major, minor, perms); } /** * virCgroupAllowDevicePath: * * @group: The cgroup to allow the device for * @path: the device to allow * @perms: Bitwise or of VIR_CGROUP_DEVICE permission bits to allow * @ignoreEacces: Ignore lack of permission (mostly for NFS mounts) * * Queries the type of device and its major/minor number, and * adds that to the cgroup ACL * * Returns: 0 on success, 1 if path exists but is not a device or is not * accessible, or * -1 on error */ int virCgroupAllowDevicePath(virCgroup *group, const char *path, int perms, bool ignoreEacces) { struct stat sb; virCgroup *parent = virCgroupGetNested(group); if (stat(path, &sb) < 0) { if (errno == EACCES && ignoreEacces) return 1; virReportSystemError(errno, _("Path '%s' is not accessible"), path); return -1; } if (!S_ISCHR(sb.st_mode) && !S_ISBLK(sb.st_mode)) return 1; VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_DEVICES, allowDevice, -1, S_ISCHR(sb.st_mode) ? 'c' : 'b', major(sb.st_rdev), minor(sb.st_rdev), perms); } /** * virCgroupDenyDevice: * * @group: The cgroup to deny a device for * @type: The device type (i.e., 'c' or 'b') * @major: The major number of the device, a negative value means '*' * @minor: The minor number of the device, a negative value means '*' * @perms: Bitwise or of VIR_CGROUP_DEVICE permission bits to deny * * Returns: 0 on success */ int virCgroupDenyDevice(virCgroup *group, char type, int major, int minor, int perms) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_DEVICES, denyDevice, -1, type, major, minor, perms); } /** * virCgroupDenyDevicePath: * * @group: The cgroup to deny the device for * @path: the device to deny * @perms: Bitwise or of VIR_CGROUP_DEVICE permission bits to allow * @ignoreEacces: Ignore lack of permission (mostly for NFS mounts) * * Queries the type of device and its major/minor number, and * removes it from the cgroup ACL * * Returns: 0 on success, 1 if path exists but is not a device or is not * accessible, or -1 on error. */ int virCgroupDenyDevicePath(virCgroup *group, const char *path, int perms, bool ignoreEacces) { struct stat sb; virCgroup *parent = virCgroupGetNested(group); if (stat(path, &sb) < 0) { if (errno == EACCES && ignoreEacces) return 1; virReportSystemError(errno, _("Path '%s' is not accessible"), path); return -1; } if (!S_ISCHR(sb.st_mode) && !S_ISBLK(sb.st_mode)) return 1; VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_DEVICES, denyDevice, -1, S_ISCHR(sb.st_mode) ? 'c' : 'b', major(sb.st_rdev), minor(sb.st_rdev), perms); } /* This function gets the sums of cpu time consumed by all vcpus. * For example, if there are 4 physical cpus, and 2 vcpus in a domain, * then for each vcpu, the cpuacct.usage_percpu looks like this: * t0 t1 t2 t3 * and we have 2 groups of such data: * v\p 0 1 2 3 * 0 t00 t01 t02 t03 * 1 t10 t11 t12 t13 * for each pcpu, the sum is cpu time consumed by all vcpus. * s0 = t00 + t10 * s1 = t01 + t11 * s2 = t02 + t12 * s3 = t03 + t13 */ static int virCgroupGetPercpuVcpuSum(virCgroup *group, virBitmap *guestvcpus, unsigned long long *sum_cpu_time, size_t nsum, virBitmap *cpumap) { ssize_t i = -1; while ((i = virBitmapNextSetBit(guestvcpus, i)) >= 0) { g_autofree char *buf = NULL; g_autoptr(virCgroup) group_vcpu = NULL; char *pos; unsigned long long tmp; ssize_t j; if (virCgroupNewThread(group, VIR_CGROUP_THREAD_VCPU, i, false, &group_vcpu) < 0) return -1; if (virCgroupGetCpuacctPercpuUsage(group_vcpu, &buf) < 0) return -1; pos = buf; for (j = virBitmapNextSetBit(cpumap, -1); j >= 0 && j < nsum; j = virBitmapNextSetBit(cpumap, j)) { if (virStrToLong_ull(pos, &pos, 10, &tmp) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("cpuacct parse error")); return -1; } sum_cpu_time[j] += tmp; } } return 0; } /** * virCgroupGetPercpuStats: * @cgroup: cgroup data structure * @params: typed parameter array where data is returned * @nparams: cardinality of @params * @start_cpu: offset of physical CPU to get data for * @ncpus: number of physical CPUs to get data for * @nvcpupids: number of vCPU threads for a domain (actual number of vcpus) * * This function is the worker that retrieves data in the appropriate format * for the terribly designed 'virDomainGetCPUStats' API. Sharing semantics with * the API, this function has two modes of operation depending on magic settings * of the input arguments. Please refer to docs of 'virDomainGetCPUStats' for * the usage patterns of the similarly named arguments. * * @nvcpupids determines the count of active vcpu threads for the vm. If the * threads could not be detected the percpu data is skipped. * * Please DON'T use this function anywhere else. */ int virCgroupGetPercpuStats(virCgroup *group, virTypedParameterPtr params, unsigned int nparams, int start_cpu, unsigned int ncpus, virBitmap *guestvcpus) { int ret = -1; size_t i; int need_cpus, total_cpus; char *pos; g_autofree char *buf = NULL; g_autofree unsigned long long *sum_cpu_time = NULL; virTypedParameterPtr ent; int param_idx; unsigned long long cpu_time; virBitmap *cpumap = NULL; /* return the number of supported params */ if (nparams == 0 && ncpus != 0) { if (!guestvcpus) return CGROUP_NB_PER_CPU_STAT_PARAM; else return CGROUP_NB_PER_CPU_STAT_PARAM + 1; } /* To parse account file, we need to know how many cpus are present. */ if (!(cpumap = virHostCPUGetPresentBitmap())) return -1; total_cpus = virBitmapSize(cpumap); /* return total number of cpus */ if (ncpus == 0) { ret = total_cpus; goto cleanup; } if (start_cpu >= total_cpus) { virReportError(VIR_ERR_INVALID_ARG, _("start_cpu %d larger than maximum of %d"), start_cpu, total_cpus - 1); goto cleanup; } /* we get percpu cputime accounting info. */ if (virCgroupGetCpuacctPercpuUsage(group, &buf)) goto cleanup; pos = buf; /* return percpu cputime in index 0 */ param_idx = 0; /* number of cpus to compute */ need_cpus = MIN(total_cpus, start_cpu + ncpus); for (i = 0; i < need_cpus; i++) { if (!virBitmapIsBitSet(cpumap, i)) { cpu_time = 0; } else if (virStrToLong_ull(pos, &pos, 10, &cpu_time) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("cpuacct parse error")); goto cleanup; } if (i < start_cpu) continue; ent = ¶ms[(i - start_cpu) * nparams + param_idx]; if (virTypedParameterAssign(ent, VIR_DOMAIN_CPU_STATS_CPUTIME, VIR_TYPED_PARAM_ULLONG, cpu_time) < 0) goto cleanup; } /* return percpu vcputime in index 1 */ param_idx = 1; if (guestvcpus && param_idx < nparams) { sum_cpu_time = g_new0(unsigned long long, need_cpus); if (virCgroupGetPercpuVcpuSum(group, guestvcpus, sum_cpu_time, need_cpus, cpumap) < 0) goto cleanup; for (i = start_cpu; i < need_cpus; i++) { int idx = (i - start_cpu) * nparams + param_idx; if (virTypedParameterAssign(¶ms[idx], VIR_DOMAIN_CPU_STATS_VCPUTIME, VIR_TYPED_PARAM_ULLONG, sum_cpu_time[i]) < 0) goto cleanup; } param_idx++; } ret = param_idx; cleanup: virBitmapFree(cpumap); return ret; } int virCgroupGetDomainTotalCpuStats(virCgroup *group, virTypedParameterPtr params, int nparams) { unsigned long long cpu_time; int ret; if (nparams == 0) /* return supported number of params */ return CGROUP_NB_TOTAL_CPU_STAT_PARAM; /* entry 0 is cputime */ ret = virCgroupGetCpuacctUsage(group, &cpu_time); if (ret < 0) { virReportSystemError(-ret, "%s", _("unable to get cpu account")); return -1; } if (virTypedParameterAssign(¶ms[0], VIR_DOMAIN_CPU_STATS_CPUTIME, VIR_TYPED_PARAM_ULLONG, cpu_time) < 0) return -1; if (nparams > 1) { unsigned long long user; unsigned long long sys; ret = virCgroupGetCpuacctStat(group, &user, &sys); if (ret < 0) { virReportSystemError(-ret, "%s", _("unable to get cpu account")); return -1; } if (virTypedParameterAssign(¶ms[1], VIR_DOMAIN_CPU_STATS_USERTIME, VIR_TYPED_PARAM_ULLONG, user) < 0) return -1; if (nparams > 2 && virTypedParameterAssign(¶ms[2], VIR_DOMAIN_CPU_STATS_SYSTEMTIME, VIR_TYPED_PARAM_ULLONG, sys) < 0) return -1; if (nparams > CGROUP_NB_TOTAL_CPU_STAT_PARAM) nparams = CGROUP_NB_TOTAL_CPU_STAT_PARAM; } return nparams; } int virCgroupSetCpuShares(virCgroup *group, unsigned long long shares) { VIR_CGROUP_BACKEND_CALL(group, VIR_CGROUP_CONTROLLER_CPU, setCpuShares, -1, shares); } int virCgroupGetCpuShares(virCgroup *group, unsigned long long *shares) { VIR_CGROUP_BACKEND_CALL(group, VIR_CGROUP_CONTROLLER_CPU, getCpuShares, -1, shares); } /** * virCgroupSetCpuCfsPeriod: * * @group: The cgroup to change cpu.cfs_period_us for * @cfs_period: The bandwidth period in usecs * * Returns: 0 on success */ int virCgroupSetCpuCfsPeriod(virCgroup *group, unsigned long long cfs_period) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_CPU, setCpuCfsPeriod, -1, cfs_period); } /** * virCgroupGetCpuCfsPeriod: * * @group: The cgroup to get cpu.cfs_period_us for * @cfs_period: Pointer to the returned bandwidth period in usecs * * Returns: 0 on success */ int virCgroupGetCpuCfsPeriod(virCgroup *group, unsigned long long *cfs_period) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_CPU, getCpuCfsPeriod, -1, cfs_period); } /** * virCgroupSetCpuCfsQuota: * * @group: The cgroup to change cpu.cfs_quota_us for * @cfs_quota: the cpu bandwidth (in usecs) that this tg will be allowed to * consume over period * * Returns: 0 on success */ int virCgroupSetCpuCfsQuota(virCgroup *group, long long cfs_quota) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_CPU, setCpuCfsQuota, -1, cfs_quota); } int virCgroupGetCpuacctPercpuUsage(virCgroup *group, char **usage) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_CPUACCT, getCpuacctPercpuUsage, -1, usage); } int virCgroupRemoveRecursively(char *grppath) { g_autoptr(DIR) grpdir = NULL; struct dirent *ent; int rc = 0; int direrr; if (virDirOpenQuiet(&grpdir, grppath) < 0) { if (errno == ENOENT) return 0; rc = -errno; VIR_ERROR(_("Unable to open %s (%d)"), grppath, errno); return rc; } /* This is best-effort cleanup: we want to log failures with just * VIR_ERROR instead of normal virReportError */ while ((direrr = virDirRead(grpdir, &ent, NULL)) > 0) { g_autofree char *path = NULL; if (ent->d_type != DT_DIR) continue; path = g_strdup_printf("%s/%s", grppath, ent->d_name); rc = virCgroupRemoveRecursively(path); if (rc != 0) break; } if (direrr < 0) { rc = -errno; VIR_ERROR(_("Failed to readdir for %s (%d)"), grppath, errno); } VIR_DEBUG("Removing cgroup %s", grppath); if (rmdir(grppath) != 0 && errno != ENOENT) { rc = -errno; VIR_ERROR(_("Unable to remove %s (%d)"), grppath, errno); } return rc; } /** * virCgroupRemove: * * @group: The group to be removed * * It first removes all child groups recursively * in depth first order and then removes @group * because the presence of the child groups * prevents removing @group. * * Returns: 0 on success */ int virCgroupRemove(virCgroup *group) { size_t i; for (i = 0; i < VIR_CGROUP_BACKEND_TYPE_LAST; i++) { if (group->backends[i]) { int rc = group->backends[i]->remove(group); if (rc < 0) return rc; } } return 0; } /* * Returns 1 if some PIDs are killed, 0 if none are killed, or -1 on error */ static int virCgroupKillInternal(virCgroup *group, int signum, GHashTable *pids, int controller, const char *taskFile) { int ret = -1; bool killedAny = false; g_autofree char *keypath = NULL; bool done = false; FILE *fp = NULL; VIR_DEBUG("group=%p signum=%d pids=%p", group, signum, pids); if (virCgroupPathOfController(group, controller, taskFile, &keypath) < 0) return -1; /* PIDs may be forking as we kill them, so loop * until there are no new PIDs found */ while (!done) { done = true; if (!(fp = fopen(keypath, "r"))) { if (errno == ENOENT) { VIR_DEBUG("No file %s, assuming done", keypath); killedAny = false; goto done; } virReportSystemError(errno, _("Failed to read %s"), keypath); goto cleanup; } else { while (!feof(fp)) { g_autofree long long *pid_value = g_new0(long long, 1); if (fscanf(fp, "%lld", pid_value) != 1) { if (feof(fp)) break; virReportSystemError(errno, _("Failed to read %s"), keypath); goto cleanup; } if (g_hash_table_lookup(pids, pid_value)) continue; VIR_DEBUG("pid=%lld", *pid_value); /* Cgroups is a Linux concept, so this cast is safe. */ if (kill((pid_t)*pid_value, signum) < 0) { if (errno != ESRCH) { virReportSystemError(errno, _("Failed to kill process %lld"), *pid_value); goto cleanup; } /* Leave RC == 0 since we didn't kill one */ } else { killedAny = true; done = false; } g_hash_table_add(pids, g_steal_pointer(&pid_value)); } VIR_FORCE_FCLOSE(fp); } } done: ret = killedAny ? 1 : 0; cleanup: VIR_FORCE_FCLOSE(fp); return ret; } int virCgroupKillRecursiveInternal(virCgroup *group, int signum, GHashTable *pids, const char *taskFile, bool dormdir) { int rc; int controller; bool killedAny = false; g_autofree char *keypath = NULL; g_autoptr(DIR) dp = NULL; struct dirent *ent; int direrr; VIR_DEBUG("group=%p signum=%d pids=%p taskFile=%s dormdir=%d", group, signum, pids, taskFile, dormdir); if ((controller = virCgroupGetAnyController(group)) < 0) return -1; if (virCgroupPathOfController(group, controller, "", &keypath) < 0) return -1; if ((rc = virCgroupKillInternal(group, signum, pids, controller, taskFile)) < 0) { return -1; } if (rc == 1) killedAny = true; VIR_DEBUG("Iterate over children of %s (killedAny=%d)", keypath, killedAny); if ((rc = virDirOpenIfExists(&dp, keypath)) < 0) return -1; if (rc == 0) { VIR_DEBUG("Path %s does not exist, assuming done", keypath); return 0; } while ((direrr = virDirRead(dp, &ent, keypath)) > 0) { g_autoptr(virCgroup) subgroup = NULL; if (ent->d_type != DT_DIR) continue; VIR_DEBUG("Process subdir %s", ent->d_name); if (virCgroupNewFromParent(group, ent->d_name, -1, &subgroup) < 0) return -1; if ((rc = virCgroupKillRecursiveInternal(subgroup, signum, pids, taskFile, true)) < 0) return -1; if (rc == 1) killedAny = true; if (dormdir) virCgroupRemove(subgroup); } if (direrr < 0) return -1; return killedAny ? 1 : 0; } int virCgroupKillRecursive(virCgroup *group, int signum) { int rc; bool success = false; size_t i; bool backendAvailable = false; virCgroupBackend **backends = virCgroupBackendGetAll(); g_autoptr(GHashTable) pids = g_hash_table_new_full(g_int64_hash, g_int64_equal, g_free, NULL); VIR_DEBUG("group=%p signum=%d", group, signum); for (i = 0; i < VIR_CGROUP_BACKEND_TYPE_LAST; i++) { if (backends && backends[i] && backends[i]->available()) { backendAvailable = true; if ((rc = backends[i]->killRecursive(group, signum, pids)) < 0) return -1; if (rc > 0) success = true; } } if (success) return 1; if (!backends || !backendAvailable) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("no cgroup backend available")); return -1; } return 0; } int virCgroupKillPainfully(virCgroup *group) { size_t i; int ret; VIR_DEBUG("cgroup=%p", group); for (i = 0; i < 15; i++) { int signum; if (i == 0) signum = SIGTERM; else if (i == 8) signum = SIGKILL; else signum = 0; /* Just check for existence */ ret = virCgroupKillRecursive(group, signum); VIR_DEBUG("Iteration %zu rc=%d", i, ret); /* If ret == -1 we hit error, if 0 we ran out of PIDs */ if (ret <= 0) break; g_usleep(200 * 1000); } VIR_DEBUG("Complete %d", ret); return ret; } /** * virCgroupGetCpuCfsQuota: * * @group: The cgroup to get cpu.cfs_quota_us for * @cfs_quota: Pointer to the returned cpu bandwidth (in usecs) that this tg * will be allowed to consume over period * * Returns: 0 on success */ int virCgroupGetCpuCfsQuota(virCgroup *group, long long *cfs_quota) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_CPU, getCpuCfsQuota, -1, cfs_quota); } int virCgroupGetCpuacctUsage(virCgroup *group, unsigned long long *usage) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_CPUACCT, getCpuacctUsage, -1, usage); } int virCgroupGetCpuacctStat(virCgroup *group, unsigned long long *user, unsigned long long *sys) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_CPUACCT, getCpuacctStat, -1, user, sys); } int virCgroupSetFreezerState(virCgroup *group, const char *state) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_FREEZER, setFreezerState, -1, state); } int virCgroupGetFreezerState(virCgroup *group, char **state) { virCgroup *parent = virCgroupGetNested(group); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_FREEZER, getFreezerState, -1, state); } int virCgroupBindMount(virCgroup *group, const char *oldroot, const char *mountopts) { size_t i; virCgroup *parent = virCgroupGetNested(group); for (i = 0; i < VIR_CGROUP_BACKEND_TYPE_LAST; i++) { if (parent->backends[i] && parent->backends[i]->bindMount(parent, oldroot, mountopts) < 0) { return -1; } } return 0; } int virCgroupSetOwner(virCgroup *cgroup, uid_t uid, gid_t gid, int controllers) { size_t i; virCgroup *parent = virCgroupGetNested(cgroup); for (i = 0; i < VIR_CGROUP_BACKEND_TYPE_LAST; i++) { if (parent->backends[i] && parent->backends[i]->setOwner(parent, uid, gid, controllers) < 0) { return -1; } } return 0; } /** * virCgroupSupportsCpuBW(): * Check whether the host supports CFS bandwidth. * * Return true when CFS bandwidth is supported, * false when CFS bandwidth is not supported. */ bool virCgroupSupportsCpuBW(virCgroup *cgroup) { virCgroup *parent = virCgroupGetNested(cgroup); VIR_CGROUP_BACKEND_CALL(parent, VIR_CGROUP_CONTROLLER_CPU, supportsCpuBW, false); } int virCgroupHasEmptyTasks(virCgroup *cgroup, int controller) { size_t i; virCgroup *parent = virCgroupGetNested(cgroup); for (i = 0; i < VIR_CGROUP_BACKEND_TYPE_LAST; i++) { if (parent->backends[i]) { int rc = parent->backends[i]->hasEmptyTasks(parent, controller); if (rc <= 0) return rc; } } return 1; } bool virCgroupControllerAvailable(int controller) { g_autoptr(virCgroup) cgroup = NULL; /* Don't use virCgroupNewSelf() - we might be running in a slice that * doesn't have @controller but the domain (for which we will later use * virCgroupHasController()) might have it. */ if (virCgroupNew("/", -1, &cgroup) < 0) return false; return virCgroupHasController(cgroup, controller); } /** * virCgroupGetInode: * * @cgroup: the cgroup to get inode for * * Get the @cgroup inode and return its value to the caller. * * Returns inode on success, -1 on error with error message reported. */ int virCgroupGetInode(virCgroup *cgroup) { struct stat st; int controller = virCgroupGetAnyController(cgroup); g_autofree char *path = NULL; if (controller < 0) return -1; if (virCgroupPathOfController(cgroup, controller, "", &path) < 0) return -1; if (stat(path, &st) < 0) { virReportSystemError(errno, _("failed to get stat for '%s'"), path); return -1; } return st.st_ino; } #else /* !__linux__ */ bool virCgroupAvailable(void) { return false; } int virCgroupNewPartition(const char *path G_GNUC_UNUSED, bool create G_GNUC_UNUSED, int controllers G_GNUC_UNUSED, virCgroup **group G_GNUC_UNUSED) { virReportSystemError(ENXIO, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupNew(const char *path G_GNUC_UNUSED, int controllers G_GNUC_UNUSED, virCgroup **group G_GNUC_UNUSED) { virReportSystemError(ENXIO, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupNewSelf(virCgroup **group G_GNUC_UNUSED) { virReportSystemError(ENXIO, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupNewDomainPartition(virCgroup *partition G_GNUC_UNUSED, const char *driver G_GNUC_UNUSED, const char *name G_GNUC_UNUSED, virCgroup **group G_GNUC_UNUSED) { virReportSystemError(ENXIO, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupNewThread(virCgroup *domain G_GNUC_UNUSED, virCgroupThreadName nameval G_GNUC_UNUSED, int id G_GNUC_UNUSED, bool create G_GNUC_UNUSED, virCgroup **group G_GNUC_UNUSED) { virReportSystemError(ENXIO, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupNewDetect(pid_t pid G_GNUC_UNUSED, int controllers G_GNUC_UNUSED, virCgroup **group G_GNUC_UNUSED) { virReportSystemError(ENXIO, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupNewDetectMachine(const char *name G_GNUC_UNUSED, const char *drivername G_GNUC_UNUSED, pid_t pid G_GNUC_UNUSED, int controllers G_GNUC_UNUSED, char *machinename G_GNUC_UNUSED, virCgroup **group G_GNUC_UNUSED) { virReportSystemError(ENXIO, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupTerminateMachine(const char *name G_GNUC_UNUSED) { virReportSystemError(ENXIO, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupNewMachine(const char *name G_GNUC_UNUSED, const char *drivername G_GNUC_UNUSED, const unsigned char *uuid G_GNUC_UNUSED, const char *rootdir G_GNUC_UNUSED, pid_t pidleader G_GNUC_UNUSED, bool isContainer G_GNUC_UNUSED, size_t nnicindexes G_GNUC_UNUSED, int *nicindexes G_GNUC_UNUSED, const char *partition G_GNUC_UNUSED, int controllers G_GNUC_UNUSED, unsigned int maxthreads G_GNUC_UNUSED, virCgroup **group G_GNUC_UNUSED) { virReportSystemError(ENXIO, "%s", _("Control groups not supported on this platform")); return -1; } bool virCgroupNewIgnoreError(void) { VIR_DEBUG("No cgroups present/configured/accessible, ignoring error"); return true; } bool virCgroupHasController(virCgroup *cgroup G_GNUC_UNUSED, int controller G_GNUC_UNUSED) { return false; } int virCgroupPathOfController(virCgroup *group G_GNUC_UNUSED, unsigned int controller G_GNUC_UNUSED, const char *key G_GNUC_UNUSED, char **path G_GNUC_UNUSED) { virReportSystemError(ENXIO, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupAddProcess(virCgroup *group G_GNUC_UNUSED, pid_t pid G_GNUC_UNUSED) { virReportSystemError(ENXIO, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupAddMachineProcess(virCgroup *group G_GNUC_UNUSED, pid_t pid G_GNUC_UNUSED) { virReportSystemError(ENXIO, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupAddThread(virCgroup *group G_GNUC_UNUSED, pid_t pid G_GNUC_UNUSED) { virReportSystemError(ENXIO, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupGetBlkioIoServiced(virCgroup *group G_GNUC_UNUSED, long long *bytes_read G_GNUC_UNUSED, long long *bytes_write G_GNUC_UNUSED, long long *requests_read G_GNUC_UNUSED, long long *requests_write G_GNUC_UNUSED) { virReportSystemError(ENXIO, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupGetBlkioIoDeviceServiced(virCgroup *group G_GNUC_UNUSED, const char *path G_GNUC_UNUSED, long long *bytes_read G_GNUC_UNUSED, long long *bytes_write G_GNUC_UNUSED, long long *requests_read G_GNUC_UNUSED, long long *requests_write G_GNUC_UNUSED) { virReportSystemError(ENXIO, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupSetBlkioWeight(virCgroup *group G_GNUC_UNUSED, unsigned int weight G_GNUC_UNUSED) { virReportSystemError(ENXIO, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupGetBlkioWeight(virCgroup *group G_GNUC_UNUSED, unsigned int *weight G_GNUC_UNUSED) { virReportSystemError(ENXIO, "%s", _("Control groups not supported on this platform")); return -1; } static int virCgroupSetBlkioDeviceWeight(virCgroup *group G_GNUC_UNUSED, const char *path G_GNUC_UNUSED, unsigned int weight G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } static int virCgroupSetBlkioDeviceReadIops(virCgroup *group G_GNUC_UNUSED, const char *path G_GNUC_UNUSED, unsigned int riops G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } static int virCgroupSetBlkioDeviceWriteIops(virCgroup *group G_GNUC_UNUSED, const char *path G_GNUC_UNUSED, unsigned int wiops G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } static int virCgroupSetBlkioDeviceReadBps(virCgroup *group G_GNUC_UNUSED, const char *path G_GNUC_UNUSED, unsigned long long rbps G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } static int virCgroupSetBlkioDeviceWriteBps(virCgroup *group G_GNUC_UNUSED, const char *path G_GNUC_UNUSED, unsigned long long wbps G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } static int virCgroupGetBlkioDeviceWeight(virCgroup *group G_GNUC_UNUSED, const char *path G_GNUC_UNUSED, unsigned int *weight G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } static int virCgroupGetBlkioDeviceReadIops(virCgroup *group G_GNUC_UNUSED, const char *path G_GNUC_UNUSED, unsigned int *riops G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } static int virCgroupGetBlkioDeviceWriteIops(virCgroup *group G_GNUC_UNUSED, const char *path G_GNUC_UNUSED, unsigned int *wiops G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } static int virCgroupGetBlkioDeviceReadBps(virCgroup *group G_GNUC_UNUSED, const char *path G_GNUC_UNUSED, unsigned long long *rbps G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } static int virCgroupGetBlkioDeviceWriteBps(virCgroup *group G_GNUC_UNUSED, const char *path G_GNUC_UNUSED, unsigned long long *wbps G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupSetMemory(virCgroup *group G_GNUC_UNUSED, unsigned long long kb G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupGetMemoryStat(virCgroup *group G_GNUC_UNUSED, unsigned long long *cache G_GNUC_UNUSED, unsigned long long *activeAnon G_GNUC_UNUSED, unsigned long long *inactiveAnon G_GNUC_UNUSED, unsigned long long *activeFile G_GNUC_UNUSED, unsigned long long *inactiveFile G_GNUC_UNUSED, unsigned long long *unevictable G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupGetMemoryUsage(virCgroup *group G_GNUC_UNUSED, unsigned long *kb G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupSetMemoryHardLimit(virCgroup *group G_GNUC_UNUSED, unsigned long long kb G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupGetMemoryHardLimit(virCgroup *group G_GNUC_UNUSED, unsigned long long *kb G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupSetMemorySoftLimit(virCgroup *group G_GNUC_UNUSED, unsigned long long kb G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupGetMemorySoftLimit(virCgroup *group G_GNUC_UNUSED, unsigned long long *kb G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupSetMemSwapHardLimit(virCgroup *group G_GNUC_UNUSED, unsigned long long kb G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupGetMemSwapHardLimit(virCgroup *group G_GNUC_UNUSED, unsigned long long *kb G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupGetMemSwapUsage(virCgroup *group G_GNUC_UNUSED, unsigned long long *kb G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupSetCpusetMems(virCgroup *group G_GNUC_UNUSED, const char *mems G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupGetCpusetMems(virCgroup *group G_GNUC_UNUSED, char **mems G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupSetCpusetMemoryMigrate(virCgroup *group G_GNUC_UNUSED, bool migrate G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupGetCpusetMemoryMigrate(virCgroup *group G_GNUC_UNUSED, bool *migrate G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupSetCpusetCpus(virCgroup *group G_GNUC_UNUSED, const char *cpus G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupGetCpusetCpus(virCgroup *group G_GNUC_UNUSED, char **cpus G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupAllowAllDevices(virCgroup *group G_GNUC_UNUSED, int perms G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupDenyAllDevices(virCgroup *group G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupAllowDevice(virCgroup *group G_GNUC_UNUSED, char type G_GNUC_UNUSED, int major G_GNUC_UNUSED, int minor G_GNUC_UNUSED, int perms G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupAllowDevicePath(virCgroup *group G_GNUC_UNUSED, const char *path G_GNUC_UNUSED, int perms G_GNUC_UNUSED, bool ignoreEaccess G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupDenyDevice(virCgroup *group G_GNUC_UNUSED, char type G_GNUC_UNUSED, int major G_GNUC_UNUSED, int minor G_GNUC_UNUSED, int perms G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupDenyDevicePath(virCgroup *group G_GNUC_UNUSED, const char *path G_GNUC_UNUSED, int perms G_GNUC_UNUSED, bool ignoreEacces G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupSetCpuShares(virCgroup *group G_GNUC_UNUSED, unsigned long long shares G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupGetCpuShares(virCgroup *group G_GNUC_UNUSED, unsigned long long *shares G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupSetCpuCfsPeriod(virCgroup *group G_GNUC_UNUSED, unsigned long long cfs_period G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupGetCpuCfsPeriod(virCgroup *group G_GNUC_UNUSED, unsigned long long *cfs_period G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupSetCpuCfsQuota(virCgroup *group G_GNUC_UNUSED, long long cfs_quota G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupRemove(virCgroup *group G_GNUC_UNUSED) { virReportSystemError(ENXIO, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupKillRecursive(virCgroup *group G_GNUC_UNUSED, int signum G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupKillPainfully(virCgroup *group G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupGetCpuCfsQuota(virCgroup *group G_GNUC_UNUSED, long long *cfs_quota G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupGetCpuacctUsage(virCgroup *group G_GNUC_UNUSED, unsigned long long *usage G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupGetCpuacctPercpuUsage(virCgroup *group G_GNUC_UNUSED, char **usage G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupGetCpuacctStat(virCgroup *group G_GNUC_UNUSED, unsigned long long *user G_GNUC_UNUSED, unsigned long long *sys G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupGetDomainTotalCpuStats(virCgroup *group G_GNUC_UNUSED, virTypedParameterPtr params G_GNUC_UNUSED, int nparams G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupSetFreezerState(virCgroup *group G_GNUC_UNUSED, const char *state G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupGetFreezerState(virCgroup *group G_GNUC_UNUSED, char **state G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupBindMount(virCgroup *group G_GNUC_UNUSED, const char *oldroot G_GNUC_UNUSED, const char *mountopts G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } bool virCgroupSupportsCpuBW(virCgroup *cgroup G_GNUC_UNUSED) { VIR_DEBUG("Control groups not supported on this platform"); return false; } int virCgroupGetPercpuStats(virCgroup *group G_GNUC_UNUSED, virTypedParameterPtr params G_GNUC_UNUSED, unsigned int nparams G_GNUC_UNUSED, int start_cpu G_GNUC_UNUSED, unsigned int ncpus G_GNUC_UNUSED, virBitmap *guestvcpus G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupSetOwner(virCgroup *cgroup G_GNUC_UNUSED, uid_t uid G_GNUC_UNUSED, gid_t gid G_GNUC_UNUSED, int controllers G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } int virCgroupHasEmptyTasks(virCgroup *cgroup G_GNUC_UNUSED, int controller G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } bool virCgroupControllerAvailable(int controller G_GNUC_UNUSED) { return false; } int virCgroupGetInode(virCgroup *cgroup G_GNUC_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Control groups not supported on this platform")); return -1; } #endif /* !__linux__ */ /** * virCgroupFree: * * @group: The group structure to free */ void virCgroupFree(virCgroup *group) { size_t i; if (group == NULL) return; for (i = 0; i < VIR_CGROUP_CONTROLLER_LAST; i++) { g_free(group->legacy[i].mountPoint); g_free(group->legacy[i].linkPoint); g_free(group->legacy[i].placement); } g_free(group->unified.mountPoint); g_free(group->unified.placement); g_free(group->unitName); virCgroupFree(group->nested); g_free(group); } int virCgroupDelThread(virCgroup *cgroup, virCgroupThreadName nameval, int idx) { g_autoptr(virCgroup) new_cgroup = NULL; virCgroup *parent = NULL; if (cgroup) { parent = virCgroupGetNested(cgroup); if (virCgroupNewThread(parent, nameval, idx, false, &new_cgroup) < 0) return -1; /* Remove the offlined cgroup */ virCgroupRemove(new_cgroup); } return 0; } /** * Calls virCgroupSetBlkioDeviceWeight() to set up blkio device weight, * then retrieves the actual value set by the kernel with * virCgroupGetBlkioDeviceWeight() in the same @weight pointer. */ int virCgroupSetupBlkioDeviceWeight(virCgroup *cgroup, const char *path, unsigned int *weight) { if (virCgroupSetBlkioDeviceWeight(cgroup, path, *weight) < 0 || virCgroupGetBlkioDeviceWeight(cgroup, path, weight) < 0) return -1; return 0; } /** * Calls virCgroupSetBlkioDeviceReadIops() to set up blkio device riops, * then retrieves the actual value set by the kernel with * virCgroupGetBlkioDeviceReadIops() in the same @riops pointer. */ int virCgroupSetupBlkioDeviceReadIops(virCgroup *cgroup, const char *path, unsigned int *riops) { if (virCgroupSetBlkioDeviceReadIops(cgroup, path, *riops) < 0 || virCgroupGetBlkioDeviceReadIops(cgroup, path, riops) < 0) return -1; return 0; } /** * Calls virCgroupSetBlkioDeviceWriteIops() to set up blkio device wiops, * then retrieves the actual value set by the kernel with * virCgroupGetBlkioDeviceWriteIops() in the same @wiops pointer. */ int virCgroupSetupBlkioDeviceWriteIops(virCgroup *cgroup, const char *path, unsigned int *wiops) { if (virCgroupSetBlkioDeviceWriteIops(cgroup, path, *wiops) < 0 || virCgroupGetBlkioDeviceWriteIops(cgroup, path, wiops) < 0) return -1; return 0; } /** * Calls virCgroupSetBlkioDeviceReadBps() to set up blkio device rbps, * then retrieves the actual value set by the kernel with * virCgroupGetBlkioDeviceReadBps() in the same @rbps pointer. */ int virCgroupSetupBlkioDeviceReadBps(virCgroup *cgroup, const char *path, unsigned long long *rbps) { if (virCgroupSetBlkioDeviceReadBps(cgroup, path, *rbps) < 0 || virCgroupGetBlkioDeviceReadBps(cgroup, path, rbps) < 0) return -1; return 0; } /** * Calls virCgroupSetBlkioDeviceWriteBps() to set up blkio device wbps, * then retrieves the actual value set by the kernel with * virCgroupGetBlkioDeviceWriteBps() in the same @wbps pointer. */ int virCgroupSetupBlkioDeviceWriteBps(virCgroup *cgroup, const char *path, unsigned long long *wbps) { if (virCgroupSetBlkioDeviceWriteBps(cgroup, path, *wbps) < 0 || virCgroupGetBlkioDeviceWriteBps(cgroup, path, wbps) < 0) return -1; return 0; } int virCgroupSetupCpusetCpus(virCgroup *cgroup, virBitmap *cpumask) { g_autofree char *new_cpus = NULL; if (!(new_cpus = virBitmapFormat(cpumask))) return -1; if (virCgroupSetCpusetCpus(cgroup, new_cpus) < 0) return -1; return 0; } int virCgroupSetupCpuPeriodQuota(virCgroup *cgroup, unsigned long long period, long long quota) { unsigned long long old_period; if (period == 0 && quota == 0) return 0; if (period) { /* get old period, and we can rollback if set quota failed */ if (virCgroupGetCpuCfsPeriod(cgroup, &old_period) < 0) return -1; if (virCgroupSetCpuCfsPeriod(cgroup, period) < 0) return -1; } if (quota && virCgroupSetCpuCfsQuota(cgroup, quota) < 0) goto error; return 0; error: if (period) { virErrorPtr saved; virErrorPreserveLast(&saved); ignore_value(virCgroupSetCpuCfsPeriod(cgroup, old_period)); virErrorRestore(&saved); } return -1; } int virCgroupGetCpuPeriodQuota(virCgroup *cgroup, unsigned long long *period, long long *quota) { if (virCgroupGetCpuCfsPeriod(cgroup, period) < 0) return -1; if (virCgroupGetCpuCfsQuota(cgroup, quota) < 0) return -1; return 0; }