/* * storage_backend_fs.c: storage backend for FS and directory handling * * Copyright (C) 2007-2015 Red Hat, Inc. * Copyright (C) 2007-2008 Daniel P. Berrange * * 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 "virerror.h" #include "storage_backend_fs.h" #include "storage_util.h" #include "storage_conf.h" #include "vircommand.h" #include "viralloc.h" #include "virfile.h" #include "virlog.h" #include "virstring.h" #define VIR_FROM_THIS VIR_FROM_STORAGE VIR_LOG_INIT("storage.storage_backend_fs"); #if WITH_STORAGE_FS # include # include struct _virNetfsDiscoverState { const char *host; virStoragePoolSourceList list; }; typedef struct _virNetfsDiscoverState virNetfsDiscoverState; static int virStorageBackendFileSystemNetFindPoolSourcesFunc(char **const groups, void *data) { virNetfsDiscoverState *state = data; const char *name, *path; virStoragePoolSource *src = NULL; int ret = -1; path = groups[0]; if (!(name = strrchr(path, '/'))) { virReportError(VIR_ERR_INTERNAL_ERROR, _("invalid netfs path (no /): %s"), path); goto cleanup; } name += 1; if (*name == '\0') { virReportError(VIR_ERR_INTERNAL_ERROR, _("invalid netfs path (ends in /): %s"), path); goto cleanup; } if (!(src = virStoragePoolSourceListNewSource(&state->list))) goto cleanup; if (VIR_ALLOC_N(src->hosts, 1) < 0) goto cleanup; src->nhost = 1; src->hosts[0].name = g_strdup(state->host); src->dir = g_strdup(path); src->format = VIR_STORAGE_POOL_NETFS_NFS; ret = 0; cleanup: return ret; } static int virStorageBackendFileSystemNetFindNFSPoolSources(virNetfsDiscoverState *state) { /* * # showmount --no-headers -e HOSTNAME * /tmp * * /A dir demo1.foo.bar,demo2.foo.bar * * Extract directory name (including possible interior spaces ...). */ const char *regexes[] = { "^(/.*\\S) +\\S+$" }; int vars[] = { 1 }; g_autoptr(virCommand) cmd = NULL; cmd = virCommandNewArgList(SHOWMOUNT, "--no-headers", "--exports", state->host, NULL); return virCommandRunRegex(cmd, 1, regexes, vars, virStorageBackendFileSystemNetFindPoolSourcesFunc, state, NULL, NULL); } static char * virStorageBackendFileSystemNetFindPoolSources(const char *srcSpec, unsigned int flags) { virNetfsDiscoverState state = { .host = NULL, .list = { .type = VIR_STORAGE_POOL_NETFS, .nsources = 0, .sources = NULL } }; char *ret = NULL; size_t i; int retNFS = -1; int retGluster = 0; g_autoptr(virStoragePoolSource) source = NULL; virCheckFlags(0, NULL); if (!srcSpec) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("hostname must be specified for netfs sources")); return NULL; } if (!(source = virStoragePoolDefParseSourceString(srcSpec, VIR_STORAGE_POOL_NETFS))) return NULL; if (source->nhost != 1) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Expected exactly 1 host for the storage pool")); goto cleanup; } state.host = source->hosts[0].name; retNFS = virStorageBackendFileSystemNetFindNFSPoolSources(&state); retGluster = virStorageBackendFindGlusterPoolSources(state.host, VIR_STORAGE_POOL_NETFS, &state.list, false); if (retGluster < 0) goto cleanup; /* If both fail, then we won't return an empty list - return an error */ if (retNFS < 0 && retGluster == 0) { virReportError(VIR_ERR_OPERATION_FAILED, _("no storage pools were found on host '%s'"), state.host); goto cleanup; } if (!(ret = virStoragePoolSourceListFormat(&state.list))) goto cleanup; cleanup: for (i = 0; i < state.list.nsources; i++) virStoragePoolSourceClear(&state.list.sources[i]); VIR_FREE(state.list.sources); return ret; } /** * @pool storage pool to check FS types * * Determine if storage pool FS types are properly set up * * Return 0 if everything's OK, -1 on error */ static int virStorageBackendFileSystemIsValid(virStoragePoolObjPtr pool) { virStoragePoolDefPtr def = virStoragePoolObjGetDef(pool); if (def->type == VIR_STORAGE_POOL_NETFS) { if (def->source.nhost != 1) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("expected exactly 1 host for the storage pool")); return -1; } if (def->source.hosts[0].name == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("missing source host")); return -1; } if (def->source.dir == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("missing source path")); return -1; } } else { if (def->source.ndevice != 1) { if (def->source.ndevice == 0) virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("missing source device")); else virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("expected exactly 1 device for the " "storage pool")); return -1; } } return 0; } /** * @pool storage pool to check for status * * Determine if a storage pool is already mounted * * Return 0 if not mounted, 1 if mounted, -1 on error */ static int virStorageBackendFileSystemIsMounted(virStoragePoolObjPtr pool) { int ret = -1; virStoragePoolDefPtr def = virStoragePoolObjGetDef(pool); FILE *mtab; struct mntent ent; char buf[1024]; int rc1, rc2; g_autofree char *src = NULL; if ((mtab = fopen(_PATH_MOUNTED, "r")) == NULL) { virReportSystemError(errno, _("cannot read mount list '%s'"), _PATH_MOUNTED); goto cleanup; } while ((getmntent_r(mtab, &ent, buf, sizeof(buf))) != NULL) { if (!(src = virStorageBackendFileSystemGetPoolSource(pool))) goto cleanup; /* compare both mount destinations and sources to be sure the mounted * FS pool is really the one we're looking for */ if ((rc1 = virFileComparePaths(ent.mnt_dir, def->target.path)) < 0 || (rc2 = virFileComparePaths(ent.mnt_fsname, src)) < 0) goto cleanup; if (rc1 && rc2) { ret = 1; goto cleanup; } VIR_FREE(src); } ret = 0; cleanup: VIR_FORCE_FCLOSE(mtab); return ret; } /** * @pool storage pool to mount * * Ensure that a FS storage pool is mounted on its target location. * If already mounted, this is a no-op * * Returns 0 if successfully mounted, -1 on error */ static int virStorageBackendFileSystemMount(virStoragePoolObjPtr pool) { virStoragePoolDefPtr def = virStoragePoolObjGetDef(pool); int rc; g_autofree char *src = NULL; g_autoptr(virCommand) cmd = NULL; if (virStorageBackendFileSystemIsValid(pool) < 0) return -1; if ((rc = virStorageBackendFileSystemIsMounted(pool)) < 0) return -1; /* Short-circuit if already mounted */ if (rc == 1) { VIR_INFO("Target '%s' is already mounted", def->target.path); return 0; } if (!(src = virStorageBackendFileSystemGetPoolSource(pool))) return -1; cmd = virStorageBackendFileSystemMountCmd(MOUNT, def, src); /* Mounting a shared FS might take a long time. Don't hold * the pool locked meanwhile. */ virObjectUnlock(pool); rc = virCommandRun(cmd, NULL); virObjectLock(pool); return rc; } /** * @pool storage pool to start * * Starts a directory or FS based storage pool. The underlying source * device will be mounted for FS based pools. * * Returns 0 on success, -1 on error */ static int virStorageBackendFileSystemStart(virStoragePoolObjPtr pool) { virStoragePoolDefPtr def = virStoragePoolObjGetDef(pool); if (def->type != VIR_STORAGE_POOL_DIR && virStorageBackendFileSystemMount(pool) < 0) return -1; return 0; } /** * @pool storage pool to unmount * * Stops a file storage pool. The underlying source device is unmounted * for FS based pools. Any cached data about volumes is released. * * Ensure that a FS storage pool is not mounted on its target location. * If already unmounted, this is a no-op. * * Returns 0 if successfully unmounted, -1 on error */ static int virStorageBackendFileSystemStop(virStoragePoolObjPtr pool) { virStoragePoolDefPtr def = virStoragePoolObjGetDef(pool); int rc; g_autoptr(virCommand) cmd = NULL; if (virStorageBackendFileSystemIsValid(pool) < 0) return -1; /* Short-circuit if already unmounted */ if ((rc = virStorageBackendFileSystemIsMounted(pool)) != 1) return rc; cmd = virCommandNewArgList(UMOUNT, def->target.path, NULL); return virCommandRun(cmd, NULL); } #endif /* WITH_STORAGE_FS */ static int virStorageBackendFileSystemCheck(virStoragePoolObjPtr pool, bool *isActive) { virStoragePoolDefPtr def = virStoragePoolObjGetDef(pool); if (def->type == VIR_STORAGE_POOL_DIR) { *isActive = virFileExists(def->target.path); #if WITH_STORAGE_FS } else { int ret; *isActive = false; if (virStorageBackendFileSystemIsValid(pool) < 0) return -1; if ((ret = virStorageBackendFileSystemIsMounted(pool)) != 0) { if (ret < 0) return -1; *isActive = true; } #endif /* WITH_STORAGE_FS */ } return 0; } /* some platforms don't support mkfs */ #ifdef MKFS static int virStorageBackendExecuteMKFS(const char *device, const char *format) { g_autoptr(virCommand) cmd = NULL; cmd = virCommandNewArgList(MKFS, "-t", format, NULL); /* use the force, otherwise mkfs.xfs won't overwrite existing fs. * Similarly mkfs.ext2, mkfs.ext3, and mkfs.ext4 require supplying -F * and mkfs.vfat uses -I */ if (STREQ(format, "xfs")) virCommandAddArg(cmd, "-f"); else if (STREQ(format, "ext2") || STREQ(format, "ext3") || STREQ(format, "ext4")) virCommandAddArg(cmd, "-F"); else if (STREQ(format, "vfat")) virCommandAddArg(cmd, "-I"); virCommandAddArg(cmd, device); if (virCommandRun(cmd, NULL) < 0) return -1; return 0; } #else /* #ifdef MKFS */ static int virStorageBackendExecuteMKFS(const char *device G_GNUC_UNUSED, const char *format G_GNUC_UNUSED) { virReportError(VIR_ERR_INTERNAL_ERROR, _("mkfs is not supported on this platform: " "Failed to make filesystem of " "type '%s' on device '%s'"), format, device); return -1; } #endif /* #ifdef MKFS */ static int virStorageBackendMakeFileSystem(virStoragePoolObjPtr pool, unsigned int flags) { virStoragePoolDefPtr def = virStoragePoolObjGetDef(pool); const char *device = NULL, *format = NULL; bool ok_to_mkfs = false; int ret = -1; if (def->source.devices == NULL) { virReportError(VIR_ERR_OPERATION_INVALID, _("No source device specified when formatting pool '%s'"), def->name); return -1; } device = def->source.devices[0].path; format = virStoragePoolFormatFileSystemTypeToString(def->source.format); VIR_DEBUG("source device: '%s' format: '%s'", device, format); virObjectUnlock(pool); if (!virFileExists(device)) { virReportError(VIR_ERR_OPERATION_INVALID, _("Source device does not exist when formatting pool '%s'"), def->name); goto error; } if (flags & VIR_STORAGE_POOL_BUILD_OVERWRITE) { ok_to_mkfs = true; } else if (flags & VIR_STORAGE_POOL_BUILD_NO_OVERWRITE && virStorageBackendDeviceIsEmpty(device, format, true)) { ok_to_mkfs = true; } if (ok_to_mkfs) ret = virStorageBackendExecuteMKFS(device, format); error: virObjectLock(pool); return ret; } /** * @pool storage pool to build * @flags controls the pool formatting behaviour * * Build a directory or FS based storage pool. * * If no flag is set, it only makes the directory. * * If VIR_STORAGE_POOL_BUILD_NO_OVERWRITE set, it probes to determine if * any filesystem already exists on the target device, returning an error * if one exists. If no filesystem already exists, use mkfs to format the * target device. * * If VIR_STORAGE_POOL_BUILD_OVERWRITE is set, mkfs is always executed and * any existing data on the target device is overwritten unconditionally. * * The underlying source device is mounted for FS based pools. * * Returns 0 on success, -1 on error */ static int virStorageBackendFileSystemBuild(virStoragePoolObjPtr pool, unsigned int flags) { virCheckFlags(VIR_STORAGE_POOL_BUILD_OVERWRITE | VIR_STORAGE_POOL_BUILD_NO_OVERWRITE, -1); VIR_EXCLUSIVE_FLAGS_RET(VIR_STORAGE_POOL_BUILD_OVERWRITE, VIR_STORAGE_POOL_BUILD_NO_OVERWRITE, -1); if (virStorageBackendBuildLocal(pool) < 0) return -1; if (flags != 0) return virStorageBackendMakeFileSystem(pool, flags); return 0; } #if WITH_STORAGE_FS /* Backend XML Namespace handling for fs or netfs specific mount options to * be added to the mount -o {options_list} command line that are not otherwise * supplied by supported XML. The XML will use the format, such as: * * * * * * * and the or is required to have a * "xmlns:fs='%s'" attribute using the STORAGE_POOL_FS_NAMESPACE_HREF */ static void virStoragePoolDefFSNamespaceFree(void *nsdata) { virStoragePoolFSMountOptionsDefPtr cmdopts = nsdata; size_t i; if (!cmdopts) return; for (i = 0; i < cmdopts->noptions; i++) VIR_FREE(cmdopts->options[i]); VIR_FREE(cmdopts->options); VIR_FREE(cmdopts); } static int virStoragePoolDefFSNamespaceParse(xmlXPathContextPtr ctxt, void **data) { virStoragePoolFSMountOptionsDefPtr cmdopts = NULL; int nnodes; size_t i; int ret = -1; g_autofree xmlNodePtr *nodes = NULL; nnodes = virXPathNodeSet("./fs:mount_opts/fs:option", ctxt, &nodes); if (nnodes < 0) return -1; if (nnodes == 0) return 0; if (VIR_ALLOC(cmdopts) < 0 || VIR_ALLOC_N(cmdopts->options, nnodes) < 0) goto cleanup; for (i = 0; i < nnodes; i++) { if (!(cmdopts->options[cmdopts->noptions] = virXMLPropString(nodes[i], "name"))) { virReportError(VIR_ERR_XML_ERROR, "%s", _("no fs mount option name specified")); goto cleanup; } cmdopts->noptions++; } *data = g_steal_pointer(&cmdopts); ret = 0; cleanup: virStoragePoolDefFSNamespaceFree(cmdopts); return ret; } static int virStoragePoolDefFSNamespaceFormatXML(virBufferPtr buf, void *nsdata) { size_t i; virStoragePoolFSMountOptionsDefPtr def = nsdata; if (!def) return 0; virBufferAddLit(buf, "\n"); virBufferAdjustIndent(buf, 2); for (i = 0; i < def->noptions; i++) virBufferEscapeString(buf, "\n", def->options[i]); virBufferAdjustIndent(buf, -2); virBufferAddLit(buf, "\n"); return 0; } #endif /* WITH_STORAGE_FS */ virStorageBackend virStorageBackendDirectory = { .type = VIR_STORAGE_POOL_DIR, .buildPool = virStorageBackendFileSystemBuild, .checkPool = virStorageBackendFileSystemCheck, .refreshPool = virStorageBackendRefreshLocal, .deletePool = virStorageBackendDeleteLocal, .buildVol = virStorageBackendVolBuildLocal, .buildVolFrom = virStorageBackendVolBuildFromLocal, .createVol = virStorageBackendVolCreateLocal, .refreshVol = virStorageBackendVolRefreshLocal, .deleteVol = virStorageBackendVolDeleteLocal, .resizeVol = virStorageBackendVolResizeLocal, .uploadVol = virStorageBackendVolUploadLocal, .downloadVol = virStorageBackendVolDownloadLocal, .wipeVol = virStorageBackendVolWipeLocal, }; #if WITH_STORAGE_FS virStorageBackend virStorageBackendFileSystem = { .type = VIR_STORAGE_POOL_FS, .buildPool = virStorageBackendFileSystemBuild, .checkPool = virStorageBackendFileSystemCheck, .startPool = virStorageBackendFileSystemStart, .refreshPool = virStorageBackendRefreshLocal, .stopPool = virStorageBackendFileSystemStop, .deletePool = virStorageBackendDeleteLocal, .buildVol = virStorageBackendVolBuildLocal, .buildVolFrom = virStorageBackendVolBuildFromLocal, .createVol = virStorageBackendVolCreateLocal, .refreshVol = virStorageBackendVolRefreshLocal, .deleteVol = virStorageBackendVolDeleteLocal, .resizeVol = virStorageBackendVolResizeLocal, .uploadVol = virStorageBackendVolUploadLocal, .downloadVol = virStorageBackendVolDownloadLocal, .wipeVol = virStorageBackendVolWipeLocal, }; virStorageBackend virStorageBackendNetFileSystem = { .type = VIR_STORAGE_POOL_NETFS, .buildPool = virStorageBackendFileSystemBuild, .checkPool = virStorageBackendFileSystemCheck, .startPool = virStorageBackendFileSystemStart, .findPoolSources = virStorageBackendFileSystemNetFindPoolSources, .refreshPool = virStorageBackendRefreshLocal, .stopPool = virStorageBackendFileSystemStop, .deletePool = virStorageBackendDeleteLocal, .buildVol = virStorageBackendVolBuildLocal, .buildVolFrom = virStorageBackendVolBuildFromLocal, .createVol = virStorageBackendVolCreateLocal, .refreshVol = virStorageBackendVolRefreshLocal, .deleteVol = virStorageBackendVolDeleteLocal, .resizeVol = virStorageBackendVolResizeLocal, .uploadVol = virStorageBackendVolUploadLocal, .downloadVol = virStorageBackendVolDownloadLocal, .wipeVol = virStorageBackendVolWipeLocal, }; static virXMLNamespace virStoragePoolFSXMLNamespace = { .parse = virStoragePoolDefFSNamespaceParse, .free = virStoragePoolDefFSNamespaceFree, .format = virStoragePoolDefFSNamespaceFormatXML, .prefix = "fs", .uri = "http://libvirt.org/schemas/storagepool/fs/1.0", }; #endif /* WITH_STORAGE_FS */ int virStorageBackendFsRegister(void) { if (virStorageBackendRegister(&virStorageBackendDirectory) < 0) return -1; #if WITH_STORAGE_FS if (virStorageBackendRegister(&virStorageBackendFileSystem) < 0) return -1; if (virStorageBackendNamespaceInit(VIR_STORAGE_POOL_FS, &virStoragePoolFSXMLNamespace) < 0) return -1; if (virStorageBackendRegister(&virStorageBackendNetFileSystem) < 0) return -1; if (virStorageBackendNamespaceInit(VIR_STORAGE_POOL_NETFS, &virStoragePoolFSXMLNamespace) < 0) return -1; #endif /* WITH_STORAGE_FS */ return 0; }