2008-02-20 15:45:59 +00:00
|
|
|
/*
|
|
|
|
* storage_backend_fs.c: storage backend for FS and directory handling
|
|
|
|
*
|
2009-02-03 13:08:59 +00:00
|
|
|
* Copyright (C) 2007-2009 Red Hat, Inc.
|
2008-02-20 15:45:59 +00:00
|
|
|
* 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 <string.h>
|
|
|
|
|
2008-08-27 20:05:58 +00:00
|
|
|
#include <libxml/parser.h>
|
|
|
|
#include <libxml/tree.h>
|
|
|
|
#include <libxml/xpath.h>
|
|
|
|
|
2008-11-04 22:30:33 +00:00
|
|
|
#include "virterror_internal.h"
|
2008-02-20 15:45:59 +00:00
|
|
|
#include "storage_backend_fs.h"
|
|
|
|
#include "storage_conf.h"
|
|
|
|
#include "util.h"
|
2008-06-06 11:09:57 +00:00
|
|
|
#include "memory.h"
|
2008-08-27 20:05:58 +00:00
|
|
|
#include "xml.h"
|
2008-02-20 15:45:59 +00:00
|
|
|
|
2008-12-17 18:08:42 +00:00
|
|
|
enum lv_endian {
|
|
|
|
LV_LITTLE_ENDIAN = 1, /* 1234 */
|
|
|
|
LV_BIG_ENDIAN /* 4321 */
|
|
|
|
};
|
2008-02-20 15:45:59 +00:00
|
|
|
|
2009-01-27 18:30:03 +00:00
|
|
|
enum {
|
|
|
|
BACKING_STORE_OK,
|
|
|
|
BACKING_STORE_INVALID,
|
|
|
|
BACKING_STORE_ERROR,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int cowGetBackingStore(virConnectPtr, char **,
|
|
|
|
const unsigned char *, size_t);
|
|
|
|
static int qcowXGetBackingStore(virConnectPtr, char **,
|
|
|
|
const unsigned char *, size_t);
|
|
|
|
static int vmdk4GetBackingStore(virConnectPtr, char **,
|
|
|
|
const unsigned char *, size_t);
|
|
|
|
|
2008-02-20 15:45:59 +00:00
|
|
|
/* Either 'magic' or 'extension' *must* be provided */
|
2008-05-09 08:06:50 +00:00
|
|
|
struct FileTypeInfo {
|
2008-02-20 15:45:59 +00:00
|
|
|
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 */
|
2008-12-17 18:08:42 +00:00
|
|
|
enum lv_endian endian; /* Endianness of file format */
|
2008-02-20 15:45:59 +00:00
|
|
|
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 */
|
2009-01-27 18:30:03 +00:00
|
|
|
/* Store a COW base image path (possibly relative),
|
|
|
|
* or NULL if there is no COW base image, to RES;
|
|
|
|
* return BACKING_STORE_* */
|
|
|
|
int (*getBackingStore)(virConnectPtr conn, char **res,
|
|
|
|
const unsigned char *buf, size_t buf_size);
|
2008-05-09 08:06:50 +00:00
|
|
|
};
|
2009-02-03 13:08:59 +00:00
|
|
|
struct FileTypeInfo const fileTypeInfo[] = {
|
2008-02-20 15:45:59 +00:00
|
|
|
/* Bochs */
|
|
|
|
/* XXX Untested
|
2009-05-04 15:44:44 +00:00
|
|
|
{ VIR_STORAGE_VOL_FILE_BOCHS, "Bochs Virtual HD Image", NULL,
|
2008-12-17 18:08:42 +00:00
|
|
|
LV_LITTLE_ENDIAN, 64, 0x20000,
|
2009-01-27 18:30:03 +00:00
|
|
|
32+16+16+4+4+4+4+4, 8, 1, NULL },*/
|
2008-02-20 15:45:59 +00:00
|
|
|
/* 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,
|
2008-12-17 18:08:42 +00:00
|
|
|
LV_LITTLE_ENDIAN, -1, 0,
|
2009-01-27 18:30:03 +00:00
|
|
|
-1, 0, 0, NULL }, */
|
2008-02-20 15:45:59 +00:00
|
|
|
/* Cow */
|
2008-11-17 11:19:33 +00:00
|
|
|
{ VIR_STORAGE_VOL_FILE_COW, "OOOM", NULL,
|
2008-12-17 18:08:42 +00:00
|
|
|
LV_BIG_ENDIAN, 4, 2,
|
2009-01-27 18:30:03 +00:00
|
|
|
4+4+1024+4, 8, 1, cowGetBackingStore },
|
2008-02-20 15:45:59 +00:00
|
|
|
/* DMG */
|
|
|
|
/* XXX QEMU says there's no magic for dmg, but we should check... */
|
2008-11-17 11:19:33 +00:00
|
|
|
{ VIR_STORAGE_VOL_FILE_DMG, NULL, ".dmg",
|
2008-02-20 15:45:59 +00:00
|
|
|
0, -1, 0,
|
2009-01-27 18:30:03 +00:00
|
|
|
-1, 0, 0, NULL },
|
2008-02-20 15:45:59 +00:00
|
|
|
/* XXX there's probably some magic for iso we can validate too... */
|
2008-11-17 11:19:33 +00:00
|
|
|
{ VIR_STORAGE_VOL_FILE_ISO, NULL, ".iso",
|
2008-02-20 15:45:59 +00:00
|
|
|
0, -1, 0,
|
2009-01-27 18:30:03 +00:00
|
|
|
-1, 0, 0, NULL },
|
2008-02-20 15:45:59 +00:00
|
|
|
/* Parallels */
|
|
|
|
/* XXX Untested
|
2008-11-17 11:19:33 +00:00
|
|
|
{ VIR_STORAGE_VOL_FILE_PARALLELS, "WithoutFreeSpace", NULL,
|
2008-12-17 18:08:42 +00:00
|
|
|
LV_LITTLE_ENDIAN, 16, 2,
|
2009-01-27 18:30:03 +00:00
|
|
|
16+4+4+4+4, 4, 512, NULL },
|
2008-02-20 15:45:59 +00:00
|
|
|
*/
|
|
|
|
/* QCow */
|
2008-11-17 11:19:33 +00:00
|
|
|
{ VIR_STORAGE_VOL_FILE_QCOW, "QFI", NULL,
|
2008-12-17 18:08:42 +00:00
|
|
|
LV_BIG_ENDIAN, 4, 1,
|
2009-01-27 18:30:03 +00:00
|
|
|
4+4+8+4+4, 8, 1, qcowXGetBackingStore },
|
2008-02-20 15:45:59 +00:00
|
|
|
/* QCow 2 */
|
2008-11-17 11:19:33 +00:00
|
|
|
{ VIR_STORAGE_VOL_FILE_QCOW2, "QFI", NULL,
|
2008-12-17 18:08:42 +00:00
|
|
|
LV_BIG_ENDIAN, 4, 2,
|
2009-01-27 18:30:03 +00:00
|
|
|
4+4+8+4+4, 8, 1, qcowXGetBackingStore },
|
2008-02-20 15:45:59 +00:00
|
|
|
/* VMDK 3 */
|
|
|
|
/* XXX Untested
|
2008-11-17 11:19:33 +00:00
|
|
|
{ VIR_STORAGE_VOL_FILE_VMDK, "COWD", NULL,
|
2008-12-17 18:08:42 +00:00
|
|
|
LV_LITTLE_ENDIAN, 4, 1,
|
2009-01-27 18:30:03 +00:00
|
|
|
4+4+4, 4, 512, NULL },
|
2008-02-20 15:45:59 +00:00
|
|
|
*/
|
|
|
|
/* VMDK 4 */
|
2008-11-17 11:19:33 +00:00
|
|
|
{ VIR_STORAGE_VOL_FILE_VMDK, "KDMV", NULL,
|
2008-12-17 18:08:42 +00:00
|
|
|
LV_LITTLE_ENDIAN, 4, 1,
|
2009-01-27 18:30:03 +00:00
|
|
|
4+4+4, 8, 512, vmdk4GetBackingStore },
|
2008-02-20 15:45:59 +00:00
|
|
|
/* Connectix / VirtualPC */
|
|
|
|
/* XXX Untested
|
2008-11-17 11:19:33 +00:00
|
|
|
{ VIR_STORAGE_VOL_FILE_VPC, "conectix", NULL,
|
2008-12-17 18:08:42 +00:00
|
|
|
LV_BIG_ENDIAN, -1, 0,
|
2009-01-27 18:30:03 +00:00
|
|
|
-1, 0, 0, NULL},
|
2008-02-20 15:45:59 +00:00
|
|
|
*/
|
|
|
|
};
|
|
|
|
|
2009-01-20 17:13:33 +00:00
|
|
|
#define VIR_FROM_THIS VIR_FROM_STORAGE
|
2008-02-20 15:45:59 +00:00
|
|
|
|
2009-01-27 18:30:03 +00:00
|
|
|
static int
|
|
|
|
cowGetBackingStore(virConnectPtr conn,
|
|
|
|
char **res,
|
|
|
|
const unsigned char *buf,
|
|
|
|
size_t buf_size)
|
|
|
|
{
|
|
|
|
#define COW_FILENAME_MAXLEN 1024
|
|
|
|
*res = NULL;
|
|
|
|
if (buf_size < 4+4+ COW_FILENAME_MAXLEN)
|
|
|
|
return BACKING_STORE_INVALID;
|
|
|
|
if (buf[4+4] == '\0') /* cow_header_v2.backing_file[0] */
|
|
|
|
return BACKING_STORE_OK;
|
|
|
|
|
|
|
|
*res = strndup ((const char*)buf + 4+4, COW_FILENAME_MAXLEN);
|
|
|
|
if (*res == NULL) {
|
|
|
|
virReportOOMError(conn);
|
|
|
|
return BACKING_STORE_ERROR;
|
|
|
|
}
|
|
|
|
return BACKING_STORE_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
qcowXGetBackingStore(virConnectPtr conn,
|
|
|
|
char **res,
|
|
|
|
const unsigned char *buf,
|
|
|
|
size_t buf_size)
|
|
|
|
{
|
|
|
|
unsigned long long offset;
|
|
|
|
unsigned long size;
|
|
|
|
|
|
|
|
*res = NULL;
|
|
|
|
if (buf_size < 4+4+8+4)
|
|
|
|
return BACKING_STORE_INVALID;
|
|
|
|
offset = (((unsigned long long)buf[4+4] << 56)
|
|
|
|
| ((unsigned long long)buf[4+4+1] << 48)
|
|
|
|
| ((unsigned long long)buf[4+4+2] << 40)
|
|
|
|
| ((unsigned long long)buf[4+4+3] << 32)
|
|
|
|
| ((unsigned long long)buf[4+4+4] << 24)
|
|
|
|
| ((unsigned long long)buf[4+4+5] << 16)
|
|
|
|
| ((unsigned long long)buf[4+4+6] << 8)
|
|
|
|
| buf[4+4+7]); /* QCowHeader.backing_file_offset */
|
|
|
|
if (offset > buf_size)
|
|
|
|
return BACKING_STORE_INVALID;
|
|
|
|
size = ((buf[4+4+8] << 24)
|
|
|
|
| (buf[4+4+8+1] << 16)
|
|
|
|
| (buf[4+4+8+2] << 8)
|
|
|
|
| buf[4+4+8+3]); /* QCowHeader.backing_file_size */
|
|
|
|
if (size == 0)
|
|
|
|
return BACKING_STORE_OK;
|
|
|
|
if (offset + size > buf_size || offset + size < offset)
|
|
|
|
return BACKING_STORE_INVALID;
|
|
|
|
if (size + 1 == 0)
|
|
|
|
return BACKING_STORE_INVALID;
|
|
|
|
if (VIR_ALLOC_N(*res, size + 1) < 0) {
|
2009-01-29 12:10:32 +00:00
|
|
|
virReportOOMError(conn);
|
2009-01-27 18:30:03 +00:00
|
|
|
return BACKING_STORE_ERROR;
|
|
|
|
}
|
|
|
|
memcpy(*res, buf + offset, size);
|
|
|
|
(*res)[size] = '\0';
|
|
|
|
return BACKING_STORE_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
vmdk4GetBackingStore(virConnectPtr conn,
|
|
|
|
char **res,
|
|
|
|
const unsigned char *buf,
|
|
|
|
size_t buf_size)
|
|
|
|
{
|
|
|
|
static const char prefix[] = "parentFileNameHint=\"";
|
|
|
|
|
|
|
|
char desc[20*512 + 1], *start, *end;
|
|
|
|
size_t len;
|
|
|
|
|
|
|
|
*res = NULL;
|
|
|
|
|
|
|
|
if (buf_size <= 0x200)
|
|
|
|
return BACKING_STORE_INVALID;
|
|
|
|
len = buf_size - 0x200;
|
|
|
|
if (len > sizeof(desc) - 1)
|
|
|
|
len = sizeof(desc) - 1;
|
|
|
|
memcpy(desc, buf + 0x200, len);
|
|
|
|
desc[len] = '\0';
|
|
|
|
start = strstr(desc, prefix);
|
|
|
|
if (start == NULL)
|
|
|
|
return BACKING_STORE_OK;
|
|
|
|
start += strlen(prefix);
|
|
|
|
end = strchr(start, '"');
|
|
|
|
if (end == NULL)
|
|
|
|
return BACKING_STORE_INVALID;
|
|
|
|
if (end == start)
|
|
|
|
return BACKING_STORE_OK;
|
|
|
|
*end = '\0';
|
|
|
|
*res = strdup(start);
|
|
|
|
if (*res == NULL) {
|
2009-01-29 12:10:32 +00:00
|
|
|
virReportOOMError(conn);
|
2009-01-27 18:30:03 +00:00
|
|
|
return BACKING_STORE_ERROR;
|
|
|
|
}
|
|
|
|
return BACKING_STORE_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return an absolute path corresponding to PATH, which is absolute or relative
|
|
|
|
* to the directory containing BASE_FILE, or NULL on error
|
|
|
|
*/
|
|
|
|
static char *absolutePathFromBaseFile(const char *base_file, const char *path)
|
|
|
|
{
|
|
|
|
size_t base_size, path_size;
|
|
|
|
char *res, *p;
|
|
|
|
|
|
|
|
if (*path == '/')
|
|
|
|
return strdup(path);
|
|
|
|
|
|
|
|
base_size = strlen(base_file) + 1;
|
|
|
|
path_size = strlen(path) + 1;
|
|
|
|
if (VIR_ALLOC_N(res, base_size - 1 + path_size) < 0)
|
|
|
|
return NULL;
|
|
|
|
memcpy(res, base_file, base_size);
|
|
|
|
p = strrchr(res, '/');
|
|
|
|
if (p != NULL)
|
|
|
|
p++;
|
|
|
|
else
|
|
|
|
p = res;
|
|
|
|
memcpy(p, path, path_size);
|
|
|
|
if (VIR_REALLOC_N(res, (p + path_size) - res) < 0) {
|
|
|
|
/* Ignore failure */
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2008-02-20 15:45:59 +00:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Probe the header of a file to determine what type of disk image
|
|
|
|
* it is, and info about its capacity if available.
|
|
|
|
*/
|
2009-01-27 18:30:03 +00:00
|
|
|
static int virStorageBackendProbeTarget(virConnectPtr conn,
|
|
|
|
virStorageVolTargetPtr target,
|
|
|
|
char **backingStore,
|
|
|
|
unsigned long long *allocation,
|
|
|
|
unsigned long long *capacity) {
|
2008-02-20 15:45:59 +00:00
|
|
|
int fd;
|
2009-01-27 18:30:03 +00:00
|
|
|
unsigned char head[20*512]; /* vmdk4GetBackingStore needs this much. */
|
2008-02-20 15:45:59 +00:00
|
|
|
int len, i, ret;
|
|
|
|
|
2009-01-27 18:30:03 +00:00
|
|
|
if (backingStore)
|
|
|
|
*backingStore = NULL;
|
|
|
|
|
|
|
|
if ((fd = open(target->path, O_RDONLY)) < 0) {
|
2009-01-20 17:13:33 +00:00
|
|
|
virReportSystemError(conn, errno,
|
|
|
|
_("cannot open volume '%s'"),
|
2009-01-27 18:30:03 +00:00
|
|
|
target->path);
|
2008-02-20 15:45:59 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2009-01-27 18:30:03 +00:00
|
|
|
if ((ret = virStorageBackendUpdateVolTargetInfoFD(conn, target, fd,
|
|
|
|
allocation,
|
|
|
|
capacity)) < 0) {
|
2008-02-20 15:45:59 +00:00
|
|
|
close(fd);
|
|
|
|
return ret; /* Take care to propagate ret, it is not always -1 */
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((len = read(fd, head, sizeof(head))) < 0) {
|
2009-01-20 17:13:33 +00:00
|
|
|
virReportSystemError(conn, errno,
|
|
|
|
_("cannot read header '%s'"),
|
2009-01-27 18:30:03 +00:00
|
|
|
target->path);
|
2008-02-20 15:45:59 +00:00
|
|
|
close(fd);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
close(fd);
|
|
|
|
|
|
|
|
/* First check file magic */
|
2008-10-28 17:43:24 +00:00
|
|
|
for (i = 0 ; i < ARRAY_CARDINALITY(fileTypeInfo) ; i++) {
|
2008-02-20 15:45:59 +00:00
|
|
|
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;
|
|
|
|
|
2008-12-17 18:08:42 +00:00
|
|
|
if (fileTypeInfo[i].endian == LV_LITTLE_ENDIAN) {
|
2008-02-20 15:45:59 +00:00
|
|
|
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 */
|
2009-01-27 18:30:03 +00:00
|
|
|
if (fileTypeInfo[i].sizeOffset != -1 && capacity) {
|
2008-12-17 18:08:42 +00:00
|
|
|
if (fileTypeInfo[i].endian == LV_LITTLE_ENDIAN) {
|
2009-01-27 18:30:03 +00:00
|
|
|
*capacity =
|
2008-02-20 15:45:59 +00:00
|
|
|
((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 {
|
2009-01-27 18:30:03 +00:00
|
|
|
*capacity =
|
2008-02-20 15:45:59 +00:00
|
|
|
((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 */
|
2009-01-27 18:30:03 +00:00
|
|
|
if (*capacity > (ULLONG_MAX / fileTypeInfo[i].sizeMultiplier))
|
2008-02-20 15:45:59 +00:00
|
|
|
continue;
|
2009-01-27 18:30:03 +00:00
|
|
|
*capacity *= fileTypeInfo[i].sizeMultiplier;
|
2008-02-20 15:45:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Validation passed, we know the file format now */
|
2009-01-27 18:30:03 +00:00
|
|
|
target->format = fileTypeInfo[i].type;
|
|
|
|
if (fileTypeInfo[i].getBackingStore != NULL && backingStore) {
|
|
|
|
char *base;
|
|
|
|
|
|
|
|
switch (fileTypeInfo[i].getBackingStore(conn, &base, head, len)) {
|
|
|
|
case BACKING_STORE_OK:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BACKING_STORE_INVALID:
|
|
|
|
continue;
|
|
|
|
|
|
|
|
case BACKING_STORE_ERROR:
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (base != NULL) {
|
|
|
|
*backingStore
|
|
|
|
= absolutePathFromBaseFile(target->path, base);
|
|
|
|
VIR_FREE(base);
|
|
|
|
if (*backingStore == NULL) {
|
2009-01-29 12:10:32 +00:00
|
|
|
virReportOOMError(conn);
|
2009-01-27 18:30:03 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2008-02-20 15:45:59 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* No magic, so check file extension */
|
2008-10-28 17:43:24 +00:00
|
|
|
for (i = 0 ; i < ARRAY_CARDINALITY(fileTypeInfo) ; i++) {
|
2008-02-20 15:45:59 +00:00
|
|
|
if (fileTypeInfo[i].extension == NULL)
|
|
|
|
continue;
|
|
|
|
|
2009-01-27 18:30:03 +00:00
|
|
|
if (!virFileHasSuffix(target->path, fileTypeInfo[i].extension))
|
2008-02-20 15:45:59 +00:00
|
|
|
continue;
|
|
|
|
|
2009-01-27 18:30:03 +00:00
|
|
|
target->format = fileTypeInfo[i].type;
|
2008-02-20 15:45:59 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* All fails, so call it a raw file */
|
2009-01-27 18:30:03 +00:00
|
|
|
target->format = VIR_STORAGE_VOL_FILE_RAW;
|
2008-02-20 15:45:59 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if WITH_STORAGE_FS
|
2009-01-06 08:43:44 +00:00
|
|
|
|
|
|
|
#include <mntent.h>
|
|
|
|
|
2008-08-27 20:05:58 +00:00
|
|
|
struct _virNetfsDiscoverState {
|
|
|
|
const char *host;
|
2008-11-04 21:54:21 +00:00
|
|
|
virStoragePoolSourceList list;
|
2008-08-27 20:05:58 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
typedef struct _virNetfsDiscoverState virNetfsDiscoverState;
|
|
|
|
|
|
|
|
static int
|
|
|
|
virStorageBackendFileSystemNetFindPoolSourcesFunc(virConnectPtr conn ATTRIBUTE_UNUSED,
|
|
|
|
virStoragePoolObjPtr pool ATTRIBUTE_UNUSED,
|
|
|
|
char **const groups,
|
|
|
|
void *data)
|
|
|
|
{
|
|
|
|
virNetfsDiscoverState *state = data;
|
|
|
|
const char *name, *path;
|
2008-11-04 21:54:21 +00:00
|
|
|
virStoragePoolSource *src;
|
2008-08-27 20:05:58 +00:00
|
|
|
|
|
|
|
path = groups[0];
|
|
|
|
|
|
|
|
name = strrchr(path, '/');
|
|
|
|
if (name == NULL) {
|
|
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("invalid netfs path (no /): %s"), path);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
name += 1;
|
|
|
|
if (*name == '\0') {
|
|
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("invalid netfs path (ends in /): %s"), path);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2008-11-04 21:54:21 +00:00
|
|
|
if (VIR_REALLOC_N(state->list.sources, state->list.nsources+1) < 0) {
|
2009-01-20 17:13:33 +00:00
|
|
|
virReportOOMError(conn);
|
2008-08-27 20:05:58 +00:00
|
|
|
return -1;
|
|
|
|
}
|
2008-11-04 21:54:21 +00:00
|
|
|
memset(state->list.sources + state->list.nsources, 0, sizeof(*state->list.sources));
|
2008-08-27 20:05:58 +00:00
|
|
|
|
2008-11-04 21:54:21 +00:00
|
|
|
src = state->list.sources + state->list.nsources++;
|
|
|
|
if (!(src->host.name = strdup(state->host)) ||
|
|
|
|
!(src->dir = strdup(path)))
|
2008-08-27 20:05:58 +00:00
|
|
|
return -1;
|
2008-11-04 21:54:21 +00:00
|
|
|
src->format = VIR_STORAGE_POOL_NETFS_NFS;
|
2008-08-27 20:05:58 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *
|
|
|
|
virStorageBackendFileSystemNetFindPoolSources(virConnectPtr conn,
|
|
|
|
const char *srcSpec,
|
|
|
|
unsigned int flags ATTRIBUTE_UNUSED)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* # 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
|
|
|
|
};
|
|
|
|
xmlDocPtr doc = NULL;
|
|
|
|
xmlXPathContextPtr xpath_ctxt = NULL;
|
2008-11-04 21:54:21 +00:00
|
|
|
virNetfsDiscoverState state = {
|
|
|
|
.host = NULL,
|
|
|
|
.list = {
|
|
|
|
.type = VIR_STORAGE_POOL_NETFS,
|
|
|
|
.nsources = 0,
|
|
|
|
.sources = NULL
|
|
|
|
}
|
|
|
|
};
|
2008-08-27 20:05:58 +00:00
|
|
|
const char *prog[] = { SHOWMOUNT, "--no-headers", "--exports", NULL, NULL };
|
|
|
|
int exitstatus;
|
|
|
|
char *retval = NULL;
|
2008-11-04 21:54:21 +00:00
|
|
|
unsigned int i;
|
2008-08-27 20:05:58 +00:00
|
|
|
|
|
|
|
doc = xmlReadDoc((const xmlChar *)srcSpec, "srcSpec.xml", NULL,
|
|
|
|
XML_PARSE_NOENT | XML_PARSE_NONET |
|
|
|
|
XML_PARSE_NOERROR | XML_PARSE_NOWARNING);
|
|
|
|
if (doc == NULL) {
|
|
|
|
virStorageReportError(conn, VIR_ERR_XML_ERROR, "%s", _("bad <source> spec"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
xpath_ctxt = xmlXPathNewContext(doc);
|
|
|
|
if (xpath_ctxt == NULL) {
|
2009-01-20 17:13:33 +00:00
|
|
|
virReportOOMError(conn);
|
2008-08-27 20:05:58 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
state.host = virXPathString(conn, "string(/source/host/@name)", xpath_ctxt);
|
|
|
|
if (!state.host || !state.host[0]) {
|
|
|
|
virStorageReportError(conn, VIR_ERR_XML_ERROR, "%s",
|
|
|
|
_("missing <host> in <source> spec"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
prog[3] = state.host;
|
|
|
|
|
|
|
|
if (virStorageBackendRunProgRegex(conn, NULL, prog, 1, regexes, vars,
|
|
|
|
virStorageBackendFileSystemNetFindPoolSourcesFunc,
|
|
|
|
&state, &exitstatus) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
|
2008-11-04 21:54:21 +00:00
|
|
|
retval = virStoragePoolSourceListFormat(conn, &state.list);
|
2008-08-27 20:05:58 +00:00
|
|
|
if (retval == NULL) {
|
2009-01-20 17:13:33 +00:00
|
|
|
virReportOOMError(conn);
|
2008-08-27 20:05:58 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
cleanup:
|
2008-11-04 21:54:21 +00:00
|
|
|
for (i = 0; i < state.list.nsources; i++)
|
|
|
|
virStoragePoolSourceFree(&state.list.sources[i]);
|
|
|
|
|
|
|
|
VIR_FREE(state.list.sources);
|
|
|
|
VIR_FREE(state.host);
|
|
|
|
|
2008-08-27 20:05:58 +00:00
|
|
|
xmlFreeDoc(doc);
|
|
|
|
xmlXPathFreeContext(xpath_ctxt);
|
|
|
|
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-02-20 15:45:59 +00:00
|
|
|
/**
|
|
|
|
* @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;
|
2009-01-22 19:41:48 +00:00
|
|
|
struct mntent ent;
|
|
|
|
char buf[1024];
|
2008-02-20 15:45:59 +00:00
|
|
|
|
|
|
|
if ((mtab = fopen(_PATH_MOUNTED, "r")) == NULL) {
|
2009-01-20 17:13:33 +00:00
|
|
|
virReportSystemError(conn, errno,
|
|
|
|
_("cannot read mount list '%s'"),
|
|
|
|
_PATH_MOUNTED);
|
2008-02-20 15:45:59 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2009-01-22 19:41:48 +00:00
|
|
|
while ((getmntent_r(mtab, &ent, buf, sizeof(buf))) != NULL) {
|
|
|
|
if (STREQ(ent.mnt_dir, pool->def->target.path)) {
|
2008-02-20 15:45:59 +00:00
|
|
|
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;
|
2009-07-21 15:49:20 +00:00
|
|
|
char *options;
|
2008-07-17 15:20:28 +00:00
|
|
|
const char **mntargv;
|
|
|
|
|
|
|
|
/* 'mount -t auto' doesn't seem to auto determine nfs (or cifs),
|
|
|
|
* while plain 'mount' does. We have to craft separate argvs to
|
|
|
|
* accommodate this */
|
|
|
|
int netauto = (pool->def->type == VIR_STORAGE_POOL_NETFS &&
|
|
|
|
pool->def->source.format == VIR_STORAGE_POOL_NETFS_AUTO);
|
2009-07-21 15:49:20 +00:00
|
|
|
int glusterfs = (pool->def->type == VIR_STORAGE_POOL_NETFS &&
|
|
|
|
pool->def->source.format == VIR_STORAGE_POOL_NETFS_GLUSTERFS);
|
|
|
|
|
|
|
|
int option_index;
|
2008-07-17 15:20:28 +00:00
|
|
|
int source_index;
|
|
|
|
|
|
|
|
const char *netfs_auto_argv[] = {
|
|
|
|
MOUNT,
|
|
|
|
NULL, /* source path */
|
|
|
|
pool->def->target.path,
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
const char *fs_argv[] = {
|
2008-02-20 15:45:59 +00:00
|
|
|
MOUNT,
|
|
|
|
"-t",
|
|
|
|
pool->def->type == VIR_STORAGE_POOL_FS ?
|
2008-11-17 11:19:33 +00:00
|
|
|
virStoragePoolFormatFileSystemTypeToString(pool->def->source.format) :
|
|
|
|
virStoragePoolFormatFileSystemNetTypeToString(pool->def->source.format),
|
2008-07-17 15:20:28 +00:00
|
|
|
NULL, /* Fill in shortly - careful not to add extra fields
|
|
|
|
before this */
|
2008-02-20 15:45:59 +00:00
|
|
|
pool->def->target.path,
|
|
|
|
NULL,
|
|
|
|
};
|
2008-07-17 15:20:28 +00:00
|
|
|
|
2009-07-21 15:49:20 +00:00
|
|
|
const char *glusterfs_argv[] = {
|
|
|
|
MOUNT,
|
|
|
|
"-t",
|
|
|
|
pool->def->type == VIR_STORAGE_POOL_FS ?
|
|
|
|
virStoragePoolFormatFileSystemTypeToString(pool->def->source.format) :
|
|
|
|
virStoragePoolFormatFileSystemNetTypeToString(pool->def->source.format),
|
|
|
|
NULL,
|
|
|
|
"-o",
|
|
|
|
NULL,
|
|
|
|
pool->def->target.path,
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
2008-07-17 15:20:28 +00:00
|
|
|
if (netauto) {
|
|
|
|
mntargv = netfs_auto_argv;
|
|
|
|
source_index = 1;
|
2009-07-21 15:49:20 +00:00
|
|
|
} else if (glusterfs) {
|
|
|
|
mntargv = glusterfs_argv;
|
|
|
|
source_index = 3;
|
|
|
|
option_index = 5;
|
2008-07-17 15:20:28 +00:00
|
|
|
} else {
|
|
|
|
mntargv = fs_argv;
|
|
|
|
source_index = 3;
|
|
|
|
}
|
|
|
|
|
2008-02-20 15:45:59 +00:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (pool->def->type == VIR_STORAGE_POOL_NETFS) {
|
|
|
|
if (pool->def->source.host.name == NULL) {
|
|
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
2008-02-22 16:26:13 +00:00
|
|
|
"%s", _("missing source host"));
|
2008-02-20 15:45:59 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (pool->def->source.dir == NULL) {
|
|
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
2008-02-22 16:26:13 +00:00
|
|
|
"%s", _("missing source path"));
|
2008-02-20 15:45:59 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (pool->def->source.ndevice != 1) {
|
|
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
2008-02-22 16:26:13 +00:00
|
|
|
"%s", _("missing source device"));
|
2008-02-20 15:45:59 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-03-03 23:06:02 +00:00
|
|
|
/* Short-circuit if already mounted */
|
2008-02-20 15:45:59 +00:00
|
|
|
if ((ret = virStorageBackendFileSystemIsMounted(conn, pool)) != 0) {
|
|
|
|
if (ret < 0)
|
|
|
|
return -1;
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pool->def->type == VIR_STORAGE_POOL_NETFS) {
|
2009-07-21 15:49:20 +00:00
|
|
|
if (pool->def->source.format = VIR_STORAGE_POOL_NETFS_GLUSTERFS) {
|
|
|
|
if (virAsprintf(&options, "direct-io-mode=1") == -1) {
|
|
|
|
virReportOOMError(conn);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
2009-05-19 13:15:50 +00:00
|
|
|
if (virAsprintf(&src, "%s:%s",
|
|
|
|
pool->def->source.host.name,
|
|
|
|
pool->def->source.dir) == -1) {
|
2009-01-20 17:13:33 +00:00
|
|
|
virReportOOMError(conn);
|
2008-06-06 11:09:57 +00:00
|
|
|
return -1;
|
|
|
|
}
|
2009-05-19 13:15:50 +00:00
|
|
|
|
2008-02-20 15:45:59 +00:00
|
|
|
} else {
|
2008-06-06 11:09:57 +00:00
|
|
|
if ((src = strdup(pool->def->source.devices[0].path)) == NULL) {
|
2009-01-20 17:13:33 +00:00
|
|
|
virReportOOMError(conn);
|
2008-06-06 11:09:57 +00:00
|
|
|
return -1;
|
|
|
|
}
|
2008-02-20 15:45:59 +00:00
|
|
|
}
|
2008-07-17 15:20:28 +00:00
|
|
|
mntargv[source_index] = src;
|
2008-02-20 15:45:59 +00:00
|
|
|
|
2009-07-21 15:49:20 +00:00
|
|
|
if (glusterfs) {
|
|
|
|
mntargv[option_index] = options;
|
|
|
|
}
|
|
|
|
|
2008-08-08 15:43:38 +00:00
|
|
|
if (virRun(conn, mntargv, NULL) < 0) {
|
2008-06-06 11:09:57 +00:00
|
|
|
VIR_FREE(src);
|
2008-02-20 15:45:59 +00:00
|
|
|
return -1;
|
|
|
|
}
|
2008-06-06 11:09:57 +00:00
|
|
|
VIR_FREE(src);
|
2008-02-20 15:45:59 +00:00
|
|
|
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,
|
2008-02-22 16:26:13 +00:00
|
|
|
"%s", _("missing source host"));
|
2008-02-20 15:45:59 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (pool->def->source.dir == NULL) {
|
|
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
2008-02-22 16:26:13 +00:00
|
|
|
"%s", _("missing source dir"));
|
2008-02-20 15:45:59 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (pool->def->source.ndevice != 1) {
|
|
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
2008-02-22 16:26:13 +00:00
|
|
|
"%s", _("missing source device"));
|
2008-02-20 15:45:59 +00:00
|
|
|
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;
|
|
|
|
|
2008-08-08 15:43:38 +00:00
|
|
|
if (virRun(conn, mntargv, NULL) < 0) {
|
2008-02-20 15:45:59 +00:00
|
|
|
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)
|
|
|
|
{
|
2009-01-20 17:13:33 +00:00
|
|
|
int err;
|
|
|
|
if ((err = virFileMakePath(pool->def->target.path)) < 0) {
|
|
|
|
virReportSystemError(conn, err,
|
|
|
|
_("cannot create path '%s'"),
|
|
|
|
pool->def->target.path);
|
2008-02-20 15:45:59 +00:00
|
|
|
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;
|
2008-10-10 15:13:28 +00:00
|
|
|
virStorageVolDefPtr vol = NULL;
|
2008-02-20 15:45:59 +00:00
|
|
|
|
|
|
|
if (!(dir = opendir(pool->def->target.path))) {
|
2009-01-20 17:13:33 +00:00
|
|
|
virReportSystemError(conn, errno,
|
|
|
|
_("cannot open path '%s'"),
|
|
|
|
pool->def->target.path);
|
2008-02-20 15:45:59 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
while ((ent = readdir(dir)) != NULL) {
|
|
|
|
int ret;
|
2009-01-27 18:30:03 +00:00
|
|
|
char *backingStore;
|
2008-02-20 15:45:59 +00:00
|
|
|
|
2008-10-10 15:13:28 +00:00
|
|
|
if (VIR_ALLOC(vol) < 0)
|
|
|
|
goto no_memory;
|
2008-02-20 15:45:59 +00:00
|
|
|
|
2008-10-10 15:13:28 +00:00
|
|
|
if ((vol->name = strdup(ent->d_name)) == NULL)
|
|
|
|
goto no_memory;
|
2008-02-20 15:45:59 +00:00
|
|
|
|
2008-11-17 11:19:33 +00:00
|
|
|
vol->type = VIR_STORAGE_VOL_FILE;
|
|
|
|
vol->target.format = VIR_STORAGE_VOL_FILE_RAW; /* Real value is filled in during probe */
|
2009-05-19 13:15:50 +00:00
|
|
|
if (virAsprintf(&vol->target.path, "%s/%s",
|
|
|
|
pool->def->target.path,
|
|
|
|
vol->name) == -1)
|
2008-10-10 15:13:28 +00:00
|
|
|
goto no_memory;
|
|
|
|
|
|
|
|
if ((vol->key = strdup(vol->target.path)) == NULL)
|
|
|
|
goto no_memory;
|
2008-02-20 15:45:59 +00:00
|
|
|
|
2009-01-27 18:30:03 +00:00
|
|
|
if ((ret = virStorageBackendProbeTarget(conn,
|
|
|
|
&vol->target,
|
|
|
|
&backingStore,
|
|
|
|
&vol->allocation,
|
|
|
|
&vol->capacity) < 0)) {
|
2008-02-20 15:45:59 +00:00
|
|
|
if (ret == -1)
|
2009-05-12 19:59:46 +00:00
|
|
|
goto cleanup;
|
2008-10-10 15:13:28 +00:00
|
|
|
else {
|
2008-02-20 15:45:59 +00:00
|
|
|
/* Silently ignore non-regular files,
|
|
|
|
* eg '.' '..', 'lost+found' */
|
2008-10-10 15:13:28 +00:00
|
|
|
virStorageVolDefFree(vol);
|
|
|
|
vol = NULL;
|
2008-02-20 15:45:59 +00:00
|
|
|
continue;
|
2008-10-10 15:13:28 +00:00
|
|
|
}
|
2008-02-20 15:45:59 +00:00
|
|
|
}
|
|
|
|
|
2009-01-27 18:30:03 +00:00
|
|
|
if (backingStore != NULL) {
|
|
|
|
if (vol->target.format == VIR_STORAGE_VOL_FILE_QCOW2 &&
|
|
|
|
STRPREFIX("fmt:", backingStore)) {
|
|
|
|
char *fmtstr = backingStore + 4;
|
|
|
|
char *path = strchr(fmtstr, ':');
|
|
|
|
if (!path) {
|
|
|
|
VIR_FREE(backingStore);
|
|
|
|
} else {
|
|
|
|
*path = '\0';
|
|
|
|
if ((vol->backingStore.format =
|
|
|
|
virStorageVolFormatFileSystemTypeFromString(fmtstr)) < 0) {
|
|
|
|
VIR_FREE(backingStore);
|
|
|
|
} else {
|
|
|
|
memmove(backingStore, path, strlen(path) + 1);
|
|
|
|
vol->backingStore.path = backingStore;
|
|
|
|
|
|
|
|
if (virStorageBackendUpdateVolTargetInfo(conn,
|
|
|
|
&vol->backingStore,
|
|
|
|
NULL,
|
|
|
|
NULL) < 0)
|
|
|
|
VIR_FREE(vol->backingStore);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
vol->backingStore.path = backingStore;
|
|
|
|
|
|
|
|
if ((ret = virStorageBackendProbeTarget(conn,
|
|
|
|
&vol->backingStore,
|
|
|
|
NULL, NULL, NULL)) < 0) {
|
|
|
|
if (ret == -1)
|
2009-05-12 19:59:46 +00:00
|
|
|
goto cleanup;
|
2009-01-27 18:30:03 +00:00
|
|
|
else {
|
|
|
|
/* Silently ignore non-regular files,
|
|
|
|
* eg '.' '..', 'lost+found' */
|
|
|
|
VIR_FREE(vol->backingStore);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2008-10-10 15:13:28 +00:00
|
|
|
if (VIR_REALLOC_N(pool->volumes.objs,
|
|
|
|
pool->volumes.count+1) < 0)
|
|
|
|
goto no_memory;
|
|
|
|
pool->volumes.objs[pool->volumes.count++] = vol;
|
|
|
|
vol = NULL;
|
2008-02-20 15:45:59 +00:00
|
|
|
}
|
|
|
|
closedir(dir);
|
|
|
|
|
|
|
|
|
|
|
|
if (statvfs(pool->def->target.path, &sb) < 0) {
|
2009-01-20 17:13:33 +00:00
|
|
|
virReportSystemError(conn, errno,
|
|
|
|
_("cannot statvfs path '%s'"),
|
|
|
|
pool->def->target.path);
|
2008-02-20 15:45:59 +00:00
|
|
|
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;
|
|
|
|
|
2008-10-10 15:13:28 +00:00
|
|
|
no_memory:
|
2009-01-20 17:13:33 +00:00
|
|
|
virReportOOMError(conn);
|
2008-10-10 15:13:28 +00:00
|
|
|
/* fallthrough */
|
|
|
|
|
2008-02-20 15:45:59 +00:00
|
|
|
cleanup:
|
|
|
|
closedir(dir);
|
2008-10-10 15:13:28 +00:00
|
|
|
virStorageVolDefFree(vol);
|
2008-02-20 15:45:59 +00:00
|
|
|
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) {
|
2009-01-20 17:13:33 +00:00
|
|
|
virReportSystemError(conn, errno,
|
|
|
|
_("cannot unlink path '%s'"),
|
|
|
|
pool->def->target.path);
|
2008-02-20 15:45:59 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2009-04-17 19:12:37 +00:00
|
|
|
* Set up a volume definition to be added to a pool's volume list, but
|
|
|
|
* don't do any file creation or allocation. By separating the two processes,
|
|
|
|
* we allow allocation progress reporting (by polling the volume's 'info'
|
|
|
|
* function), and can drop the parent pool lock during the (slow) allocation.
|
2008-02-20 15:45:59 +00:00
|
|
|
*/
|
|
|
|
static int
|
|
|
|
virStorageBackendFileSystemVolCreate(virConnectPtr conn,
|
|
|
|
virStoragePoolObjPtr pool,
|
|
|
|
virStorageVolDefPtr vol)
|
|
|
|
{
|
|
|
|
|
2009-05-19 13:15:50 +00:00
|
|
|
vol->type = VIR_STORAGE_VOL_FILE;
|
|
|
|
|
|
|
|
if (virAsprintf(&vol->target.path, "%s/%s",
|
|
|
|
pool->def->target.path,
|
|
|
|
vol->name) == -1) {
|
2009-01-20 17:13:33 +00:00
|
|
|
virReportOOMError(conn);
|
2008-02-20 15:45:59 +00:00
|
|
|
return -1;
|
|
|
|
}
|
2009-05-19 13:15:50 +00:00
|
|
|
|
2008-02-20 15:45:59 +00:00
|
|
|
vol->key = strdup(vol->target.path);
|
|
|
|
if (vol->key == NULL) {
|
2009-01-20 17:13:33 +00:00
|
|
|
virReportOOMError(conn);
|
2008-02-20 15:45:59 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2009-04-17 19:12:37 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-05-12 20:27:17 +00:00
|
|
|
static int createFileDir(virConnectPtr conn,
|
2009-05-19 13:43:48 +00:00
|
|
|
virStorageVolDefPtr vol,
|
2009-07-16 16:27:07 +00:00
|
|
|
virStorageVolDefPtr inputvol,
|
|
|
|
unsigned int flags ATTRIBUTE_UNUSED) {
|
2009-05-19 13:43:48 +00:00
|
|
|
if (inputvol) {
|
|
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
|
|
|
"%s",
|
|
|
|
_("cannot copy from volume to a directory volume"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2009-05-12 20:27:17 +00:00
|
|
|
if (mkdir(vol->target.path, vol->target.perms.mode) < 0) {
|
|
|
|
virReportSystemError(conn, errno,
|
|
|
|
_("cannot create path '%s'"),
|
|
|
|
vol->target.path);
|
|
|
|
return -1;
|
|
|
|
}
|
2008-02-20 15:45:59 +00:00
|
|
|
|
2009-05-12 20:27:17 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2008-02-20 15:45:59 +00:00
|
|
|
|
2009-05-12 20:27:17 +00:00
|
|
|
static int
|
2009-05-19 13:43:48 +00:00
|
|
|
_virStorageBackendFileSystemVolBuild(virConnectPtr conn,
|
|
|
|
virStorageVolDefPtr vol,
|
|
|
|
virStorageVolDefPtr inputvol)
|
2009-05-12 20:27:17 +00:00
|
|
|
{
|
|
|
|
int fd;
|
2009-07-16 16:27:07 +00:00
|
|
|
virStorageBackendBuildVolFrom create_func;
|
2009-06-25 21:28:23 +00:00
|
|
|
int tool_type;
|
2009-05-12 20:27:17 +00:00
|
|
|
|
2009-06-25 21:28:23 +00:00
|
|
|
if (inputvol) {
|
2009-07-16 16:09:26 +00:00
|
|
|
create_func = virStorageBackendGetBuildVolFromFunction(conn, vol,
|
|
|
|
inputvol);
|
2009-06-25 21:28:23 +00:00
|
|
|
if (!create_func)
|
|
|
|
return -1;
|
|
|
|
} else if (vol->target.format == VIR_STORAGE_VOL_FILE_RAW) {
|
2009-07-16 16:09:26 +00:00
|
|
|
create_func = virStorageBackendCreateRaw;
|
2009-05-12 20:27:17 +00:00
|
|
|
} else if (vol->target.format == VIR_STORAGE_VOL_FILE_DIR) {
|
|
|
|
create_func = createFileDir;
|
2009-07-16 16:09:26 +00:00
|
|
|
} else if ((tool_type = virStorageBackendFindFSImageTool(NULL)) != -1) {
|
|
|
|
create_func = virStorageBackendFSImageToolTypeToFunc(conn, tool_type);
|
|
|
|
|
|
|
|
if (!create_func)
|
2009-06-25 21:28:23 +00:00
|
|
|
return -1;
|
2009-06-11 13:18:56 +00:00
|
|
|
} else {
|
2008-02-20 15:45:59 +00:00
|
|
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
2008-02-22 16:26:13 +00:00
|
|
|
"%s", _("creation of non-raw images "
|
|
|
|
"is not supported without qemu-img"));
|
2008-02-20 15:45:59 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2009-07-16 16:27:07 +00:00
|
|
|
if (create_func(conn, vol, inputvol, 0) < 0)
|
2009-05-12 20:27:17 +00:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
if ((fd = open(vol->target.path, O_RDONLY)) < 0) {
|
|
|
|
virReportSystemError(conn, errno,
|
|
|
|
_("cannot read path '%s'"),
|
|
|
|
vol->target.path);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2008-02-20 15:45:59 +00:00
|
|
|
/* We can only chown/grp if root */
|
|
|
|
if (getuid() == 0) {
|
|
|
|
if (fchown(fd, vol->target.perms.uid, vol->target.perms.gid) < 0) {
|
2009-01-20 17:13:33 +00:00
|
|
|
virReportSystemError(conn, errno,
|
|
|
|
_("cannot set file owner '%s'"),
|
|
|
|
vol->target.path);
|
2008-02-20 15:45:59 +00:00
|
|
|
close(fd);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (fchmod(fd, vol->target.perms.mode) < 0) {
|
2009-01-20 17:13:33 +00:00
|
|
|
virReportSystemError(conn, errno,
|
|
|
|
_("cannot set file mode '%s'"),
|
|
|
|
vol->target.path);
|
2008-02-20 15:45:59 +00:00
|
|
|
close(fd);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Refresh allocation / permissions info, but not capacity */
|
2009-01-27 18:30:03 +00:00
|
|
|
if (virStorageBackendUpdateVolTargetInfoFD(conn, &vol->target, fd,
|
|
|
|
&vol->allocation,
|
|
|
|
NULL) < 0) {
|
2008-02-20 15:45:59 +00:00
|
|
|
close(fd);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (close(fd) < 0) {
|
2009-01-20 17:13:33 +00:00
|
|
|
virReportSystemError(conn, errno,
|
|
|
|
_("cannot close file '%s'"),
|
|
|
|
vol->target.path);
|
2008-02-20 15:45:59 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-05-19 13:43:48 +00:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
virStorageBackendFileSystemVolBuild(virConnectPtr conn,
|
|
|
|
virStorageVolDefPtr vol) {
|
|
|
|
return _virStorageBackendFileSystemVolBuild(conn, vol, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Create a storage vol using 'inputvol' as input
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
virStorageBackendFileSystemVolBuildFrom(virConnectPtr conn,
|
|
|
|
virStorageVolDefPtr vol,
|
|
|
|
virStorageVolDefPtr inputvol,
|
|
|
|
unsigned int flags ATTRIBUTE_UNUSED) {
|
|
|
|
return _virStorageBackendFileSystemVolBuild(conn, vol, inputvol);
|
|
|
|
}
|
2008-02-20 15:45:59 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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) {
|
2009-01-20 17:13:33 +00:00
|
|
|
virReportSystemError(conn, errno,
|
|
|
|
_("cannot unlink file '%s'"),
|
|
|
|
vol->target.path);
|
2008-02-20 15:45:59 +00:00
|
|
|
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,
|
2009-04-17 19:12:37 +00:00
|
|
|
.buildVol = virStorageBackendFileSystemVolBuild,
|
2009-05-19 13:43:48 +00:00
|
|
|
.buildVolFrom = virStorageBackendFileSystemVolBuildFrom,
|
2008-02-20 15:45:59 +00:00
|
|
|
.createVol = virStorageBackendFileSystemVolCreate,
|
|
|
|
.refreshVol = virStorageBackendFileSystemVolRefresh,
|
|
|
|
.deleteVol = virStorageBackendFileSystemVolDelete,
|
|
|
|
};
|
|
|
|
|
|
|
|
#if WITH_STORAGE_FS
|
|
|
|
virStorageBackend virStorageBackendFileSystem = {
|
|
|
|
.type = VIR_STORAGE_POOL_FS,
|
|
|
|
|
|
|
|
.buildPool = virStorageBackendFileSystemBuild,
|
|
|
|
.startPool = virStorageBackendFileSystemStart,
|
|
|
|
.refreshPool = virStorageBackendFileSystemRefresh,
|
|
|
|
.stopPool = virStorageBackendFileSystemStop,
|
|
|
|
.deletePool = virStorageBackendFileSystemDelete,
|
2009-04-17 19:12:37 +00:00
|
|
|
.buildVol = virStorageBackendFileSystemVolBuild,
|
2009-05-19 13:43:48 +00:00
|
|
|
.buildVolFrom = virStorageBackendFileSystemVolBuildFrom,
|
2008-02-20 15:45:59 +00:00
|
|
|
.createVol = virStorageBackendFileSystemVolCreate,
|
|
|
|
.refreshVol = virStorageBackendFileSystemVolRefresh,
|
|
|
|
.deleteVol = virStorageBackendFileSystemVolDelete,
|
|
|
|
};
|
|
|
|
virStorageBackend virStorageBackendNetFileSystem = {
|
|
|
|
.type = VIR_STORAGE_POOL_NETFS,
|
|
|
|
|
|
|
|
.buildPool = virStorageBackendFileSystemBuild,
|
|
|
|
.startPool = virStorageBackendFileSystemStart,
|
2008-08-27 20:05:58 +00:00
|
|
|
.findPoolSources = virStorageBackendFileSystemNetFindPoolSources,
|
2008-02-20 15:45:59 +00:00
|
|
|
.refreshPool = virStorageBackendFileSystemRefresh,
|
|
|
|
.stopPool = virStorageBackendFileSystemStop,
|
|
|
|
.deletePool = virStorageBackendFileSystemDelete,
|
2009-04-17 19:12:37 +00:00
|
|
|
.buildVol = virStorageBackendFileSystemVolBuild,
|
2009-05-19 13:43:48 +00:00
|
|
|
.buildVolFrom = virStorageBackendFileSystemVolBuildFrom,
|
2008-02-20 15:45:59 +00:00
|
|
|
.createVol = virStorageBackendFileSystemVolCreate,
|
|
|
|
.refreshVol = virStorageBackendFileSystemVolRefresh,
|
|
|
|
.deleteVol = virStorageBackendFileSystemVolDelete,
|
|
|
|
};
|
|
|
|
#endif /* WITH_STORAGE_FS */
|