mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-11-02 11:21:12 +00:00
e497deb89e
* .x-sc_avoid_write: Exempt src/util-lib.c, too. * Makefile.maint (sc_unmarked_diagnostics): Filter out false positives. * src/storage_backend_fs.c (virStorageBackendFileSystemVolCreate): Use safewrite, not write. * src/storage_backend_logical.c (virStorageBackendLogicalBuildPool): Likewise.
1125 lines
36 KiB
C
1125 lines
36 KiB
C
/*
|
|
* storage_backend_fs.c: storage backend for FS and directory handling
|
|
*
|
|
* Copyright (C) 2007-2008 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, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
* Author: Daniel P. Berrange <berrange@redhat.com>
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <sys/statvfs.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <stdio.h>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <endian.h>
|
|
#include <byteswap.h>
|
|
#include <mntent.h>
|
|
#include <string.h>
|
|
|
|
#include "storage_backend_fs.h"
|
|
#include "storage_conf.h"
|
|
#include "util.h"
|
|
#include "config.h"
|
|
|
|
|
|
enum {
|
|
VIR_STORAGE_POOL_FS_AUTO = 0,
|
|
VIR_STORAGE_POOL_FS_EXT2,
|
|
VIR_STORAGE_POOL_FS_EXT3,
|
|
VIR_STORAGE_POOL_FS_EXT4,
|
|
VIR_STORAGE_POOL_FS_UFS,
|
|
VIR_STORAGE_POOL_FS_ISO,
|
|
VIR_STORAGE_POOL_FS_UDF,
|
|
VIR_STORAGE_POOL_FS_GFS,
|
|
VIR_STORAGE_POOL_FS_GFS2,
|
|
VIR_STORAGE_POOL_FS_VFAT,
|
|
VIR_STORAGE_POOL_FS_HFSPLUS,
|
|
VIR_STORAGE_POOL_FS_XFS,
|
|
};
|
|
|
|
enum {
|
|
VIR_STORAGE_POOL_NETFS_AUTO = 0,
|
|
VIR_STORAGE_POOL_NETFS_NFS,
|
|
};
|
|
|
|
|
|
|
|
enum {
|
|
VIR_STORAGE_VOL_RAW,
|
|
VIR_STORAGE_VOL_DIR,
|
|
VIR_STORAGE_VOL_BOCHS,
|
|
VIR_STORAGE_VOL_CLOOP,
|
|
VIR_STORAGE_VOL_COW,
|
|
VIR_STORAGE_VOL_DMG,
|
|
VIR_STORAGE_VOL_ISO,
|
|
VIR_STORAGE_VOL_QCOW,
|
|
VIR_STORAGE_VOL_QCOW2,
|
|
VIR_STORAGE_VOL_VMDK,
|
|
VIR_STORAGE_VOL_VPC,
|
|
};
|
|
|
|
/* Either 'magic' or 'extension' *must* be provided */
|
|
struct {
|
|
int type; /* One of the constants above */
|
|
const char *magic; /* Optional string of file magic
|
|
* to check at head of file */
|
|
const char *extension; /* Optional file extension to check */
|
|
int endian; /* Endianness of file format */
|
|
int versionOffset; /* Byte offset from start of file
|
|
* where we find version number,
|
|
* -1 to skip version test */
|
|
int versionNumber; /* Version number to validate */
|
|
int sizeOffset; /* Byte offset from start of file
|
|
* where we find capacity info,
|
|
* -1 to use st_size as capacity */
|
|
int sizeBytes; /* Number of bytes for size field */
|
|
int sizeMultiplier; /* A scaling factor if size is not in bytes */
|
|
} fileTypeInfo[] = {
|
|
/* Bochs */
|
|
/* XXX Untested
|
|
{ VIR_STORAGE_VOL_BOCHS, "Bochs Virtual HD Image", NULL,
|
|
__LITTLE_ENDIAN, 64, 0x20000,
|
|
32+16+16+4+4+4+4+4, 8, 1 },*/
|
|
/* CLoop */
|
|
/* XXX Untested
|
|
{ VIR_STORAGE_VOL_CLOOP, "#!/bin/sh\n#V2.0 Format\nmodprobe cloop file=$0 && mount -r -t iso9660 /dev/cloop $1\n", NULL,
|
|
__LITTLE_ENDIAN, -1, 0,
|
|
-1, 0, 0 }, */
|
|
/* Cow */
|
|
{ VIR_STORAGE_VOL_COW, "OOOM", NULL,
|
|
__BIG_ENDIAN, 4, 2,
|
|
4+4+1024+4, 8, 1 },
|
|
/* DMG */
|
|
/* XXX QEMU says there's no magic for dmg, but we should check... */
|
|
{ VIR_STORAGE_VOL_DMG, NULL, ".dmg",
|
|
0, -1, 0,
|
|
-1, 0, 0 },
|
|
/* XXX there's probably some magic for iso we can validate too... */
|
|
{ VIR_STORAGE_VOL_ISO, NULL, ".iso",
|
|
0, -1, 0,
|
|
-1, 0, 0 },
|
|
/* Parallels */
|
|
/* XXX Untested
|
|
{ VIR_STORAGE_VOL_PARALLELS, "WithoutFreeSpace", NULL,
|
|
__LITTLE_ENDIAN, 16, 2,
|
|
16+4+4+4+4, 4, 512 },
|
|
*/
|
|
/* QCow */
|
|
{ VIR_STORAGE_VOL_QCOW, "QFI", NULL,
|
|
__BIG_ENDIAN, 4, 1,
|
|
4+4+8+4+4, 8, 1 },
|
|
/* QCow 2 */
|
|
{ VIR_STORAGE_VOL_QCOW2, "QFI", NULL,
|
|
__BIG_ENDIAN, 4, 2,
|
|
4+4+8+4+4, 8, 1 },
|
|
/* VMDK 3 */
|
|
/* XXX Untested
|
|
{ VIR_STORAGE_VOL_VMDK, "COWD", NULL,
|
|
__LITTLE_ENDIAN, 4, 1,
|
|
4+4+4, 4, 512 },
|
|
*/
|
|
/* VMDK 4 */
|
|
{ VIR_STORAGE_VOL_VMDK, "KDMV", NULL,
|
|
__LITTLE_ENDIAN, 4, 1,
|
|
4+4+4, 8, 512 },
|
|
/* Connectix / VirtualPC */
|
|
/* XXX Untested
|
|
{ VIR_STORAGE_VOL_VPC, "conectix", NULL,
|
|
__BIG_ENDIAN, -1, 0,
|
|
-1, 0, 0},
|
|
*/
|
|
};
|
|
|
|
|
|
|
|
|
|
static int
|
|
virStorageBackendFileSystemVolFormatFromString(virConnectPtr conn,
|
|
const char *format) {
|
|
if (format == NULL)
|
|
return VIR_STORAGE_VOL_RAW;
|
|
|
|
if (STREQ(format, "raw"))
|
|
return VIR_STORAGE_VOL_RAW;
|
|
if (STREQ(format, "dir"))
|
|
return VIR_STORAGE_VOL_DIR;
|
|
if (STREQ(format, "bochs"))
|
|
return VIR_STORAGE_VOL_BOCHS;
|
|
if (STREQ(format, "cow"))
|
|
return VIR_STORAGE_VOL_COW;
|
|
if (STREQ(format, "cloop"))
|
|
return VIR_STORAGE_VOL_CLOOP;
|
|
if (STREQ(format, "dmg"))
|
|
return VIR_STORAGE_VOL_DMG;
|
|
if (STREQ(format, "iso"))
|
|
return VIR_STORAGE_VOL_ISO;
|
|
if (STREQ(format, "qcow"))
|
|
return VIR_STORAGE_VOL_QCOW;
|
|
if (STREQ(format, "qcow2"))
|
|
return VIR_STORAGE_VOL_QCOW2;
|
|
if (STREQ(format, "vmdk"))
|
|
return VIR_STORAGE_VOL_VMDK;
|
|
if (STREQ(format, "vpc"))
|
|
return VIR_STORAGE_VOL_VPC;
|
|
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
_("unsupported volume format %s"), format);
|
|
return -1;
|
|
}
|
|
|
|
static const char *
|
|
virStorageBackendFileSystemVolFormatToString(virConnectPtr conn,
|
|
int format) {
|
|
switch (format) {
|
|
case VIR_STORAGE_VOL_RAW:
|
|
return "raw";
|
|
case VIR_STORAGE_VOL_DIR:
|
|
return "dir";
|
|
case VIR_STORAGE_VOL_BOCHS:
|
|
return "bochs";
|
|
case VIR_STORAGE_VOL_CLOOP:
|
|
return "cloop";
|
|
case VIR_STORAGE_VOL_COW:
|
|
return "cow";
|
|
case VIR_STORAGE_VOL_DMG:
|
|
return "dmg";
|
|
case VIR_STORAGE_VOL_ISO:
|
|
return "iso";
|
|
case VIR_STORAGE_VOL_QCOW:
|
|
return "qcow";
|
|
case VIR_STORAGE_VOL_QCOW2:
|
|
return "qcow2";
|
|
case VIR_STORAGE_VOL_VMDK:
|
|
return "vmdk";
|
|
case VIR_STORAGE_VOL_VPC:
|
|
return "vpc";
|
|
}
|
|
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
_("unsupported volume format %d"), format);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static int
|
|
virStorageBackendFileSystemPoolFormatFromString(virConnectPtr conn,
|
|
const char *format) {
|
|
if (format == NULL)
|
|
return VIR_STORAGE_POOL_FS_AUTO;
|
|
|
|
if (STREQ(format, "auto"))
|
|
return VIR_STORAGE_POOL_FS_AUTO;
|
|
if (STREQ(format, "ext2"))
|
|
return VIR_STORAGE_POOL_FS_EXT2;
|
|
if (STREQ(format, "ext3"))
|
|
return VIR_STORAGE_POOL_FS_EXT3;
|
|
if (STREQ(format, "ext4"))
|
|
return VIR_STORAGE_POOL_FS_EXT4;
|
|
if (STREQ(format, "ufs"))
|
|
return VIR_STORAGE_POOL_FS_UFS;
|
|
if (STREQ(format, "iso9660"))
|
|
return VIR_STORAGE_POOL_FS_ISO;
|
|
if (STREQ(format, "udf"))
|
|
return VIR_STORAGE_POOL_FS_UDF;
|
|
if (STREQ(format, "gfs"))
|
|
return VIR_STORAGE_POOL_FS_GFS;
|
|
if (STREQ(format, "gfs2"))
|
|
return VIR_STORAGE_POOL_FS_GFS2;
|
|
if (STREQ(format, "vfat"))
|
|
return VIR_STORAGE_POOL_FS_VFAT;
|
|
if (STREQ(format, "hfs+"))
|
|
return VIR_STORAGE_POOL_FS_HFSPLUS;
|
|
if (STREQ(format, "xfs"))
|
|
return VIR_STORAGE_POOL_FS_XFS;
|
|
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
_("unsupported volume format %s"), format);
|
|
return -1;
|
|
}
|
|
|
|
static const char *
|
|
virStorageBackendFileSystemPoolFormatToString(virConnectPtr conn,
|
|
int format) {
|
|
switch (format) {
|
|
case VIR_STORAGE_POOL_FS_AUTO:
|
|
return "auto";
|
|
case VIR_STORAGE_POOL_FS_EXT2:
|
|
return "ext2";
|
|
case VIR_STORAGE_POOL_FS_EXT3:
|
|
return "ext3";
|
|
case VIR_STORAGE_POOL_FS_EXT4:
|
|
return "ext4";
|
|
case VIR_STORAGE_POOL_FS_UFS:
|
|
return "ufs";
|
|
case VIR_STORAGE_POOL_FS_ISO:
|
|
return "iso";
|
|
case VIR_STORAGE_POOL_FS_UDF:
|
|
return "udf";
|
|
case VIR_STORAGE_POOL_FS_GFS:
|
|
return "gfs";
|
|
case VIR_STORAGE_POOL_FS_GFS2:
|
|
return "gfs2";
|
|
case VIR_STORAGE_POOL_FS_VFAT:
|
|
return "vfat";
|
|
case VIR_STORAGE_POOL_FS_HFSPLUS:
|
|
return "hfs+";
|
|
case VIR_STORAGE_POOL_FS_XFS:
|
|
return "xfs";
|
|
}
|
|
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
_("unsupported volume format %d"), format);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static int
|
|
virStorageBackendFileSystemNetPoolFormatFromString(virConnectPtr conn,
|
|
const char *format) {
|
|
if (format == NULL)
|
|
return VIR_STORAGE_POOL_NETFS_AUTO;
|
|
|
|
if (STREQ(format, "auto"))
|
|
return VIR_STORAGE_POOL_NETFS_AUTO;
|
|
if (STREQ(format, "nfs"))
|
|
return VIR_STORAGE_POOL_NETFS_NFS;
|
|
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
_("unsupported volume format %s"), format);
|
|
return -1;
|
|
}
|
|
|
|
static const char *
|
|
virStorageBackendFileSystemNetPoolFormatToString(virConnectPtr conn,
|
|
int format) {
|
|
switch (format) {
|
|
case VIR_STORAGE_POOL_NETFS_AUTO:
|
|
return "auto";
|
|
case VIR_STORAGE_POOL_NETFS_NFS:
|
|
return "nfs";
|
|
}
|
|
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
_("unsupported volume format %d"), format);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* Probe the header of a file to determine what type of disk image
|
|
* it is, and info about its capacity if available.
|
|
*/
|
|
static int virStorageBackendProbeFile(virConnectPtr conn,
|
|
virStorageVolDefPtr def) {
|
|
int fd;
|
|
char head[4096];
|
|
int len, i, ret;
|
|
|
|
if ((fd = open(def->target.path, O_RDONLY)) < 0) {
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot open volume '%s': %s"),
|
|
def->target.path, strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
if ((ret = virStorageBackendUpdateVolInfoFD(conn, def, fd, 1)) < 0) {
|
|
close(fd);
|
|
return ret; /* Take care to propagate ret, it is not always -1 */
|
|
}
|
|
|
|
if ((len = read(fd, head, sizeof(head))) < 0) {
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot read header '%s': %s"),
|
|
def->target.path, strerror(errno));
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
close(fd);
|
|
|
|
/* First check file magic */
|
|
for (i = 0 ; i < sizeof(fileTypeInfo)/sizeof(fileTypeInfo[0]) ; i++) {
|
|
int mlen;
|
|
if (fileTypeInfo[i].magic == NULL)
|
|
continue;
|
|
|
|
/* Validate magic data */
|
|
mlen = strlen(fileTypeInfo[i].magic);
|
|
if (mlen > len)
|
|
continue;
|
|
if (memcmp(head, fileTypeInfo[i].magic, mlen) != 0)
|
|
continue;
|
|
|
|
/* Validate version number info */
|
|
if (fileTypeInfo[i].versionNumber != -1) {
|
|
int version;
|
|
|
|
if (fileTypeInfo[i].endian == __LITTLE_ENDIAN) {
|
|
version = (head[fileTypeInfo[i].versionOffset+3] << 24) |
|
|
(head[fileTypeInfo[i].versionOffset+2] << 16) |
|
|
(head[fileTypeInfo[i].versionOffset+1] << 8) |
|
|
head[fileTypeInfo[i].versionOffset];
|
|
} else {
|
|
version = (head[fileTypeInfo[i].versionOffset] << 24) |
|
|
(head[fileTypeInfo[i].versionOffset+1] << 16) |
|
|
(head[fileTypeInfo[i].versionOffset+2] << 8) |
|
|
head[fileTypeInfo[i].versionOffset+3];
|
|
}
|
|
if (version != fileTypeInfo[i].versionNumber)
|
|
continue;
|
|
}
|
|
|
|
/* Optionally extract capacity from file */
|
|
if (fileTypeInfo[i].sizeOffset != -1) {
|
|
if (fileTypeInfo[i].endian == __LITTLE_ENDIAN) {
|
|
def->capacity =
|
|
((unsigned long long)head[fileTypeInfo[i].sizeOffset+7] << 56) |
|
|
((unsigned long long)head[fileTypeInfo[i].sizeOffset+6] << 48) |
|
|
((unsigned long long)head[fileTypeInfo[i].sizeOffset+5] << 40) |
|
|
((unsigned long long)head[fileTypeInfo[i].sizeOffset+4] << 32) |
|
|
((unsigned long long)head[fileTypeInfo[i].sizeOffset+3] << 24) |
|
|
((unsigned long long)head[fileTypeInfo[i].sizeOffset+2] << 16) |
|
|
((unsigned long long)head[fileTypeInfo[i].sizeOffset+1] << 8) |
|
|
((unsigned long long)head[fileTypeInfo[i].sizeOffset]);
|
|
} else {
|
|
def->capacity =
|
|
((unsigned long long)head[fileTypeInfo[i].sizeOffset] << 56) |
|
|
((unsigned long long)head[fileTypeInfo[i].sizeOffset+1] << 48) |
|
|
((unsigned long long)head[fileTypeInfo[i].sizeOffset+2] << 40) |
|
|
((unsigned long long)head[fileTypeInfo[i].sizeOffset+3] << 32) |
|
|
((unsigned long long)head[fileTypeInfo[i].sizeOffset+4] << 24) |
|
|
((unsigned long long)head[fileTypeInfo[i].sizeOffset+5] << 16) |
|
|
((unsigned long long)head[fileTypeInfo[i].sizeOffset+6] << 8) |
|
|
((unsigned long long)head[fileTypeInfo[i].sizeOffset+7]);
|
|
}
|
|
/* Avoid unlikely, but theoretically possible overflow */
|
|
if (def->capacity > (ULLONG_MAX / fileTypeInfo[i].sizeMultiplier))
|
|
continue;
|
|
def->capacity *= fileTypeInfo[i].sizeMultiplier;
|
|
}
|
|
|
|
/* Validation passed, we know the file format now */
|
|
def->target.format = fileTypeInfo[i].type;
|
|
return 0;
|
|
}
|
|
|
|
/* No magic, so check file extension */
|
|
for (i = 0 ; i < sizeof(fileTypeInfo)/sizeof(fileTypeInfo[0]) ; i++) {
|
|
if (fileTypeInfo[i].extension == NULL)
|
|
continue;
|
|
|
|
if (!virFileHasSuffix(def->target.path, fileTypeInfo[i].extension))
|
|
continue;
|
|
|
|
def->target.format = fileTypeInfo[i].type;
|
|
return 0;
|
|
}
|
|
|
|
/* All fails, so call it a raw file */
|
|
def->target.format = VIR_STORAGE_VOL_RAW;
|
|
return 0;
|
|
}
|
|
|
|
#if WITH_STORAGE_FS
|
|
/**
|
|
* @conn connection to report errors against
|
|
* @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(virConnectPtr conn,
|
|
virStoragePoolObjPtr pool) {
|
|
FILE *mtab;
|
|
struct mntent *ent;
|
|
|
|
if ((mtab = fopen(_PATH_MOUNTED, "r")) == NULL) {
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot read %s: %s"),
|
|
_PATH_MOUNTED, strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
while ((ent = getmntent(mtab)) != NULL) {
|
|
if (STREQ(ent->mnt_dir, pool->def->target.path)) {
|
|
fclose(mtab);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
fclose(mtab);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @conn connection to report errors against
|
|
* @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(virConnectPtr conn,
|
|
virStoragePoolObjPtr pool) {
|
|
char *src;
|
|
const char *mntargv[] = {
|
|
MOUNT,
|
|
"-t",
|
|
pool->def->type == VIR_STORAGE_POOL_FS ?
|
|
virStorageBackendFileSystemPoolFormatToString(conn,
|
|
pool->def->source.format) :
|
|
virStorageBackendFileSystemNetPoolFormatToString(conn,
|
|
pool->def->source.format),
|
|
NULL, /* Fill in shortly - careful not to add extra fields before this */
|
|
pool->def->target.path,
|
|
NULL,
|
|
};
|
|
int ret;
|
|
|
|
if (pool->def->type == VIR_STORAGE_POOL_NETFS) {
|
|
if (pool->def->source.host.name == NULL) {
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("missing source host"));
|
|
return -1;
|
|
}
|
|
if (pool->def->source.dir == NULL) {
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("missing source path"));
|
|
return -1;
|
|
}
|
|
} else {
|
|
if (pool->def->source.ndevice != 1) {
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("missing source device"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* Short-circuit is already mounted */
|
|
if ((ret = virStorageBackendFileSystemIsMounted(conn, pool)) != 0) {
|
|
if (ret < 0)
|
|
return -1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
if (pool->def->type == VIR_STORAGE_POOL_NETFS) {
|
|
src = malloc(strlen(pool->def->source.host.name) +
|
|
1 + strlen(pool->def->source.dir) + 1);
|
|
strcpy(src, pool->def->source.host.name);
|
|
strcat(src, ":");
|
|
strcat(src, pool->def->source.dir);
|
|
} else {
|
|
src = strdup(pool->def->source.devices[0].path);
|
|
}
|
|
if (src == NULL) {
|
|
virStorageReportError(conn, VIR_ERR_NO_MEMORY, "%s", _("source"));
|
|
return -1;
|
|
}
|
|
mntargv[3] = src;
|
|
|
|
if (virRun(conn, (char**)mntargv, NULL) < 0) {
|
|
free(src);
|
|
return -1;
|
|
}
|
|
free(src);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @conn connection to report errors against
|
|
* @pool storage pool to unmount
|
|
*
|
|
* 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
|
|
virStorageBackendFileSystemUnmount(virConnectPtr conn,
|
|
virStoragePoolObjPtr pool) {
|
|
const char *mntargv[3];
|
|
int ret;
|
|
|
|
if (pool->def->type == VIR_STORAGE_POOL_NETFS) {
|
|
if (pool->def->source.host.name == NULL) {
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("missing source host"));
|
|
return -1;
|
|
}
|
|
if (pool->def->source.dir == NULL) {
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("missing source dir"));
|
|
return -1;
|
|
}
|
|
} else {
|
|
if (pool->def->source.ndevice != 1) {
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("missing source device"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* Short-circuit if already unmounted */
|
|
if ((ret = virStorageBackendFileSystemIsMounted(conn, pool)) != 1) {
|
|
if (ret < 0)
|
|
return -1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
mntargv[0] = UMOUNT;
|
|
mntargv[1] = pool->def->target.path;
|
|
mntargv[2] = NULL;
|
|
|
|
if (virRun(conn, (char**)mntargv, NULL) < 0) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif /* WITH_STORAGE_FS */
|
|
|
|
|
|
/**
|
|
* @conn connection to report errors against
|
|
* @pool storage pool to start
|
|
*
|
|
* Starts a directory or FS based storage pool.
|
|
*
|
|
* - If it is a FS based pool, mounts the unlying source device on the pool
|
|
*
|
|
* Returns 0 on success, -1 on error
|
|
*/
|
|
#if WITH_STORAGE_FS
|
|
static int
|
|
virStorageBackendFileSystemStart(virConnectPtr conn,
|
|
virStoragePoolObjPtr pool)
|
|
{
|
|
if (pool->def->type != VIR_STORAGE_POOL_DIR &&
|
|
virStorageBackendFileSystemMount(conn, pool) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
#endif /* WITH_STORAGE_FS */
|
|
|
|
|
|
/**
|
|
* @conn connection to report errors against
|
|
* @pool storage pool to build
|
|
*
|
|
* Build a directory or FS based storage pool.
|
|
*
|
|
* - If it is a FS based pool, mounts the unlying source device on the pool
|
|
*
|
|
* Returns 0 on success, -1 on error
|
|
*/
|
|
static int
|
|
virStorageBackendFileSystemBuild(virConnectPtr conn,
|
|
virStoragePoolObjPtr pool,
|
|
unsigned int flags ATTRIBUTE_UNUSED)
|
|
{
|
|
if (virFileMakePath(pool->def->target.path) < 0) {
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot create path '%s': %s"),
|
|
pool->def->target.path, strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Iterate over the pool's directory and enumerate all disk images
|
|
* within it. This is non-recursive.
|
|
*/
|
|
static int
|
|
virStorageBackendFileSystemRefresh(virConnectPtr conn,
|
|
virStoragePoolObjPtr pool)
|
|
{
|
|
DIR *dir;
|
|
struct dirent *ent;
|
|
struct statvfs sb;
|
|
|
|
if (!(dir = opendir(pool->def->target.path))) {
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot open path '%s': %s"),
|
|
pool->def->target.path, strerror(errno));
|
|
goto cleanup;
|
|
}
|
|
|
|
while ((ent = readdir(dir)) != NULL) {
|
|
virStorageVolDefPtr vol;
|
|
int ret;
|
|
|
|
vol = calloc(1, sizeof(virStorageVolDef));
|
|
if (vol == NULL) {
|
|
virStorageReportError(conn, VIR_ERR_NO_MEMORY,
|
|
"%s", _("volume"));
|
|
goto cleanup;
|
|
}
|
|
|
|
vol->name = strdup(ent->d_name);
|
|
if (vol->name == NULL) {
|
|
free(vol);
|
|
virStorageReportError(conn, VIR_ERR_NO_MEMORY,
|
|
"%s", _("volume name"));
|
|
goto cleanup;
|
|
}
|
|
|
|
vol->target.format = VIR_STORAGE_VOL_RAW; /* Real value is filled in during probe */
|
|
vol->target.path = malloc(strlen(pool->def->target.path) +
|
|
1 + strlen(vol->name) + 1);
|
|
if (vol->target.path == NULL) {
|
|
free(vol->target.path);
|
|
free(vol);
|
|
virStorageReportError(conn, VIR_ERR_NO_MEMORY,
|
|
"%s", _("volume name"));
|
|
goto cleanup;
|
|
}
|
|
strcpy(vol->target.path, pool->def->target.path);
|
|
strcat(vol->target.path, "/");
|
|
strcat(vol->target.path, vol->name);
|
|
if ((vol->key = strdup(vol->target.path)) == NULL) {
|
|
free(vol->name);
|
|
free(vol->target.path);
|
|
free(vol);
|
|
virStorageReportError(conn, VIR_ERR_NO_MEMORY,
|
|
"%s", _("volume key"));
|
|
goto cleanup;
|
|
}
|
|
|
|
if ((ret = virStorageBackendProbeFile(conn, vol) < 0)) {
|
|
free(vol->key);
|
|
free(vol->name);
|
|
free(vol->target.path);
|
|
free(vol);
|
|
if (ret == -1)
|
|
goto cleanup;
|
|
else
|
|
/* Silently ignore non-regular files,
|
|
* eg '.' '..', 'lost+found' */
|
|
continue;
|
|
}
|
|
|
|
vol->next = pool->volumes;
|
|
pool->volumes = vol;
|
|
pool->nvolumes++;
|
|
continue;
|
|
}
|
|
closedir(dir);
|
|
|
|
|
|
if (statvfs(pool->def->target.path, &sb) < 0) {
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot statvfs path '%s': %s"),
|
|
pool->def->target.path, strerror(errno));
|
|
return -1;
|
|
}
|
|
pool->def->capacity = ((unsigned long long)sb.f_frsize *
|
|
(unsigned long long)sb.f_blocks);
|
|
pool->def->available = ((unsigned long long)sb.f_bfree *
|
|
(unsigned long long)sb.f_bsize);
|
|
pool->def->allocation = pool->def->capacity - pool->def->available;
|
|
|
|
return 0;
|
|
|
|
cleanup:
|
|
closedir(dir);
|
|
virStoragePoolObjClearVols(pool);
|
|
return -1;
|
|
}
|
|
|
|
|
|
/**
|
|
* @conn connection to report errors against
|
|
* @pool storage pool to start
|
|
*
|
|
* Stops a directory or FS based storage pool.
|
|
*
|
|
* - If it is a FS based pool, unmounts the unlying source device on the pool
|
|
* - Releases all cached data about volumes
|
|
*/
|
|
#if WITH_STORAGE_FS
|
|
static int
|
|
virStorageBackendFileSystemStop(virConnectPtr conn,
|
|
virStoragePoolObjPtr pool)
|
|
{
|
|
if (pool->def->type != VIR_STORAGE_POOL_DIR &&
|
|
virStorageBackendFileSystemUnmount(conn, pool) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
#endif /* WITH_STORAGE_FS */
|
|
|
|
|
|
/**
|
|
* @conn connection to report errors against
|
|
* @pool storage pool to build
|
|
*
|
|
* Build a directory or FS based storage pool.
|
|
*
|
|
* - If it is a FS based pool, mounts the unlying source device on the pool
|
|
*
|
|
* Returns 0 on success, -1 on error
|
|
*/
|
|
static int
|
|
virStorageBackendFileSystemDelete(virConnectPtr conn,
|
|
virStoragePoolObjPtr pool,
|
|
unsigned int flags ATTRIBUTE_UNUSED)
|
|
{
|
|
/* XXX delete all vols first ? */
|
|
|
|
if (unlink(pool->def->target.path) < 0) {
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot unlink path '%s': %s"),
|
|
pool->def->target.path, strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Allocate a new file as a volume. This is either done directly
|
|
* for raw/sparse files, or by calling qemu-img/qcow-create for
|
|
* special kinds of files
|
|
*/
|
|
static int
|
|
virStorageBackendFileSystemVolCreate(virConnectPtr conn,
|
|
virStoragePoolObjPtr pool,
|
|
virStorageVolDefPtr vol)
|
|
{
|
|
int fd;
|
|
|
|
vol->target.path = malloc(strlen(pool->def->target.path) +
|
|
1 + strlen(vol->name) + 1);
|
|
if (vol->target.path == NULL) {
|
|
virStorageReportError(conn, VIR_ERR_NO_MEMORY, "%s", _("target"));
|
|
return -1;
|
|
}
|
|
strcpy(vol->target.path, pool->def->target.path);
|
|
strcat(vol->target.path, "/");
|
|
strcat(vol->target.path, vol->name);
|
|
vol->key = strdup(vol->target.path);
|
|
if (vol->key == NULL) {
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("storage vol key"));
|
|
return -1;
|
|
}
|
|
|
|
if (vol->target.format == VIR_STORAGE_VOL_RAW) {
|
|
if ((fd = open(vol->target.path, O_RDWR | O_CREAT | O_EXCL,
|
|
vol->target.perms.mode)) < 0) {
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot create path '%s': %s"),
|
|
vol->target.path, strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
/* Pre-allocate any data if requested */
|
|
/* XXX slooooooooooooooooow.
|
|
* Need to add in progress bars & bg thread somehow */
|
|
if (vol->allocation) {
|
|
unsigned long long remain = vol->allocation;
|
|
static const char const zeros[4096];
|
|
while (remain) {
|
|
int bytes = sizeof(zeros);
|
|
if (bytes > remain)
|
|
bytes = remain;
|
|
if ((bytes = safewrite(fd, zeros, bytes)) < 0) {
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot fill file '%s': %s"),
|
|
vol->target.path, strerror(errno));
|
|
unlink(vol->target.path);
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
remain -= bytes;
|
|
}
|
|
}
|
|
|
|
/* Now seek to final size, possibly making the file sparse */
|
|
if (ftruncate(fd, vol->capacity) < 0) {
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot extend file '%s': %s"),
|
|
vol->target.path, strerror(errno));
|
|
unlink(vol->target.path);
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
} else if (vol->target.format == VIR_STORAGE_VOL_DIR) {
|
|
if (mkdir(vol->target.path, vol->target.perms.mode) < 0) {
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot create path '%s': %s"),
|
|
vol->target.path, strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
if ((fd = open(vol->target.path, O_RDWR)) < 0) {
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot read path '%s': %s"),
|
|
vol->target.path, strerror(errno));
|
|
return -1;
|
|
}
|
|
} else {
|
|
#if HAVE_QEMU_IMG
|
|
const char *type;
|
|
char size[100];
|
|
const char *imgargv[7];
|
|
|
|
if ((type = virStorageBackendFileSystemVolFormatToString(conn,
|
|
vol->target.format)) == NULL) {
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
_("unknown storage vol type %d"),
|
|
vol->target.format);
|
|
return -1;
|
|
}
|
|
|
|
/* Size in KB */
|
|
snprintf(size, sizeof(size), "%llu", vol->capacity/1024);
|
|
|
|
imgargv[0] = QEMU_IMG;
|
|
imgargv[1] = "create";
|
|
imgargv[2] = "-f";
|
|
imgargv[3] = type;
|
|
imgargv[4] = vol->target.path;
|
|
imgargv[5] = size;
|
|
imgargv[6] = NULL;
|
|
|
|
if (virRun(conn, (char **)imgargv, NULL) < 0) {
|
|
unlink(vol->target.path);
|
|
return -1;
|
|
}
|
|
|
|
if ((fd = open(vol->target.path, O_RDONLY)) < 0) {
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot read path '%s': %s"),
|
|
vol->target.path, strerror(errno));
|
|
unlink(vol->target.path);
|
|
return -1;
|
|
}
|
|
#elif HAVE_QCOW_CREATE
|
|
/*
|
|
* Xen removed the fully-functional qemu-img, and replaced it
|
|
* with a partially functional qcow-create. Go figure ??!?
|
|
*/
|
|
char size[100];
|
|
const char *imgargv[4];
|
|
|
|
if (vol->target.format != VIR_STORAGE_VOL_QCOW2) {
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
_("unsupported storage vol type %d"),
|
|
vol->target.format);
|
|
return -1;
|
|
}
|
|
|
|
/* Size in MB - yes different units to qemu-img :-( */
|
|
snprintf(size, sizeof(size), "%llu", vol->capacity/1024/1024);
|
|
|
|
imgargv[0] = QCOW_CREATE;
|
|
imgargv[1] = size;
|
|
imgargv[2] = vol->target.path;
|
|
imgargv[3] = NULL;
|
|
|
|
if (virRun(conn, (char **)imgargv, NULL) < 0) {
|
|
unlink(vol->target.path);
|
|
return -1;
|
|
}
|
|
|
|
if ((fd = open(vol->target.path, O_RDONLY)) < 0) {
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot read path '%s': %s"),
|
|
vol->target.path, strerror(errno));
|
|
unlink(vol->target.path);
|
|
return -1;
|
|
}
|
|
#else
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("creation of non-raw images "
|
|
"is not supported without qemu-img"));
|
|
return -1;
|
|
#endif
|
|
}
|
|
|
|
/* We can only chown/grp if root */
|
|
if (getuid() == 0) {
|
|
if (fchown(fd, vol->target.perms.uid, vol->target.perms.gid) < 0) {
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot set file owner '%s': %s"),
|
|
vol->target.path, strerror(errno));
|
|
unlink(vol->target.path);
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
}
|
|
if (fchmod(fd, vol->target.perms.mode) < 0) {
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot set file mode '%s': %s"),
|
|
vol->target.path, strerror(errno));
|
|
unlink(vol->target.path);
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
/* Refresh allocation / permissions info, but not capacity */
|
|
if (virStorageBackendUpdateVolInfoFD(conn, vol, fd, 0) < 0) {
|
|
unlink(vol->target.path);
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
if (close(fd) < 0) {
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot close file '%s': %s"),
|
|
vol->target.path, strerror(errno));
|
|
unlink(vol->target.path);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Remove a volume - just unlinks for now
|
|
*/
|
|
static int
|
|
virStorageBackendFileSystemVolDelete(virConnectPtr conn,
|
|
virStoragePoolObjPtr pool ATTRIBUTE_UNUSED,
|
|
virStorageVolDefPtr vol,
|
|
unsigned int flags ATTRIBUTE_UNUSED)
|
|
{
|
|
if (unlink(vol->target.path) < 0) {
|
|
/* Silently ignore failures where the vol has already gone away */
|
|
if (errno != ENOENT) {
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot unlink file '%s': %s"),
|
|
vol->target.path, strerror(errno));
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Update info about a volume's capacity/allocation
|
|
*/
|
|
static int
|
|
virStorageBackendFileSystemVolRefresh(virConnectPtr conn,
|
|
virStoragePoolObjPtr pool ATTRIBUTE_UNUSED,
|
|
virStorageVolDefPtr vol)
|
|
{
|
|
/* Refresh allocation / permissions info in case its changed */
|
|
return virStorageBackendUpdateVolInfo(conn, vol, 0);
|
|
}
|
|
|
|
virStorageBackend virStorageBackendDirectory = {
|
|
.type = VIR_STORAGE_POOL_DIR,
|
|
|
|
.buildPool = virStorageBackendFileSystemBuild,
|
|
.refreshPool = virStorageBackendFileSystemRefresh,
|
|
.deletePool = virStorageBackendFileSystemDelete,
|
|
.createVol = virStorageBackendFileSystemVolCreate,
|
|
.refreshVol = virStorageBackendFileSystemVolRefresh,
|
|
.deleteVol = virStorageBackendFileSystemVolDelete,
|
|
|
|
.volOptions = {
|
|
.formatFromString = virStorageBackendFileSystemVolFormatFromString,
|
|
.formatToString = virStorageBackendFileSystemVolFormatToString,
|
|
},
|
|
.volType = VIR_STORAGE_VOL_FILE,
|
|
};
|
|
|
|
#if WITH_STORAGE_FS
|
|
virStorageBackend virStorageBackendFileSystem = {
|
|
.type = VIR_STORAGE_POOL_FS,
|
|
|
|
.buildPool = virStorageBackendFileSystemBuild,
|
|
.startPool = virStorageBackendFileSystemStart,
|
|
.refreshPool = virStorageBackendFileSystemRefresh,
|
|
.stopPool = virStorageBackendFileSystemStop,
|
|
.deletePool = virStorageBackendFileSystemDelete,
|
|
.createVol = virStorageBackendFileSystemVolCreate,
|
|
.refreshVol = virStorageBackendFileSystemVolRefresh,
|
|
.deleteVol = virStorageBackendFileSystemVolDelete,
|
|
|
|
.poolOptions = {
|
|
.flags = (VIR_STORAGE_BACKEND_POOL_SOURCE_DEVICE),
|
|
.formatFromString = virStorageBackendFileSystemPoolFormatFromString,
|
|
.formatToString = virStorageBackendFileSystemPoolFormatToString,
|
|
},
|
|
.volOptions = {
|
|
.formatFromString = virStorageBackendFileSystemVolFormatFromString,
|
|
.formatToString = virStorageBackendFileSystemVolFormatToString,
|
|
},
|
|
.volType = VIR_STORAGE_VOL_FILE,
|
|
};
|
|
virStorageBackend virStorageBackendNetFileSystem = {
|
|
.type = VIR_STORAGE_POOL_NETFS,
|
|
|
|
.buildPool = virStorageBackendFileSystemBuild,
|
|
.startPool = virStorageBackendFileSystemStart,
|
|
.refreshPool = virStorageBackendFileSystemRefresh,
|
|
.stopPool = virStorageBackendFileSystemStop,
|
|
.deletePool = virStorageBackendFileSystemDelete,
|
|
.createVol = virStorageBackendFileSystemVolCreate,
|
|
.refreshVol = virStorageBackendFileSystemVolRefresh,
|
|
.deleteVol = virStorageBackendFileSystemVolDelete,
|
|
|
|
.poolOptions = {
|
|
.flags = (VIR_STORAGE_BACKEND_POOL_SOURCE_HOST |
|
|
VIR_STORAGE_BACKEND_POOL_SOURCE_DIR),
|
|
.formatFromString = virStorageBackendFileSystemNetPoolFormatFromString,
|
|
.formatToString = virStorageBackendFileSystemNetPoolFormatToString,
|
|
},
|
|
.volOptions = {
|
|
.formatFromString = virStorageBackendFileSystemVolFormatFromString,
|
|
.formatToString = virStorageBackendFileSystemVolFormatToString,
|
|
},
|
|
.volType = VIR_STORAGE_VOL_FILE,
|
|
};
|
|
#endif /* WITH_STORAGE_FS */
|
|
|
|
/*
|
|
* vim: set tabstop=4:
|
|
* vim: set shiftwidth=4:
|
|
* vim: set expandtab:
|
|
*/
|
|
/*
|
|
* Local variables:
|
|
* indent-tabs-mode: nil
|
|
* c-indent-level: 4
|
|
* c-basic-offset: 4
|
|
* tab-width: 4
|
|
* End:
|
|
*/
|