#include "qapi/error.h"
#include "qemu/cutils.h"
#include "qemu/error-report.h"
-#include "qemu/timer.h"
-#include "qemu/log.h"
#include "block/block_int.h"
#include "qemu/module.h"
#include "trace.h"
#define MAX_BLOCKSIZE 4096
+/* Posix file locking bytes. Libvirt takes byte 0, we start from higher bytes,
+ * leaving a few more bytes for its future use. */
+#define RAW_LOCK_PERM_BASE 100
+#define RAW_LOCK_SHARED_BASE 200
+
typedef struct BDRVRawState {
int fd;
+ int lock_fd;
+ bool use_lock;
int type;
int open_flags;
size_t buf_align;
+ /* The current permissions. */
+ uint64_t perm;
+ uint64_t shared_perm;
+
#ifdef CONFIG_XFS
bool is_xfs:1;
#endif
static void raw_parse_filename(const char *filename, QDict *options,
Error **errp)
{
- /* The filename does not have to be prefixed by the protocol name, since
- * "file" is the default protocol; therefore, the return value of this
- * function call can be ignored. */
- strstart(filename, "file:", &filename);
-
- qdict_put_obj(options, "filename", QOBJECT(qstring_from_str(filename)));
+ bdrv_parse_filename_strip_prefix(filename, "file:", options);
}
static QemuOptsList raw_runtime_opts = {
.type = QEMU_OPT_STRING,
.help = "host AIO implementation (threads, native)",
},
+ {
+ .name = "locking",
+ .type = QEMU_OPT_STRING,
+ .help = "file locking mode (on/off/auto, default: auto)",
+ },
{ /* end of list */ }
},
};
BlockdevAioOptions aio, aio_default;
int fd, ret;
struct stat st;
+ OnOffAuto locking;
opts = qemu_opts_create(&raw_runtime_opts, NULL, 0, &error_abort);
qemu_opts_absorb_qdict(opts, options, &local_err);
}
s->use_linux_aio = (aio == BLOCKDEV_AIO_OPTIONS_NATIVE);
+ locking = qapi_enum_parse(OnOffAuto_lookup, qemu_opt_get(opts, "locking"),
+ ON_OFF_AUTO__MAX, ON_OFF_AUTO_AUTO, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ ret = -EINVAL;
+ goto fail;
+ }
+ switch (locking) {
+ case ON_OFF_AUTO_ON:
+ s->use_lock = true;
+#ifndef F_OFD_SETLK
+ fprintf(stderr,
+ "File lock requested but OFD locking syscall is unavailable, "
+ "falling back to POSIX file locks.\n"
+ "Due to the implementation, locks can be lost unexpectedly.\n");
+#endif
+ break;
+ case ON_OFF_AUTO_OFF:
+ s->use_lock = false;
+ break;
+ case ON_OFF_AUTO_AUTO:
+#ifdef F_OFD_SETLK
+ s->use_lock = true;
+#else
+ s->use_lock = false;
+#endif
+ break;
+ default:
+ abort();
+ }
+
s->open_flags = open_flags;
raw_parse_flags(bdrv_flags, &s->open_flags);
}
s->fd = fd;
+ s->lock_fd = -1;
+ if (s->use_lock) {
+ fd = qemu_open(filename, s->open_flags);
+ if (fd < 0) {
+ ret = -errno;
+ error_setg_errno(errp, errno, "Could not open '%s' for locking",
+ filename);
+ qemu_close(s->fd);
+ goto fail;
+ }
+ s->lock_fd = fd;
+ }
+ s->perm = 0;
+ s->shared_perm = BLK_PERM_ALL;
+
#ifdef CONFIG_LINUX_AIO
/* Currently Linux does AIO only for files opened with O_DIRECT */
if (s->use_linux_aio && !(s->open_flags & O_DIRECT)) {
return raw_open_common(bs, options, flags, 0, errp);
}
+typedef enum {
+ RAW_PL_PREPARE,
+ RAW_PL_COMMIT,
+ RAW_PL_ABORT,
+} RawPermLockOp;
+
+#define PERM_FOREACH(i) \
+ for ((i) = 0; (1ULL << (i)) <= BLK_PERM_ALL; i++)
+
+/* Lock bytes indicated by @perm_lock_bits and @shared_perm_lock_bits in the
+ * file; if @unlock == true, also unlock the unneeded bytes.
+ * @shared_perm_lock_bits is the mask of all permissions that are NOT shared.
+ */
+static int raw_apply_lock_bytes(BDRVRawState *s,
+ uint64_t perm_lock_bits,
+ uint64_t shared_perm_lock_bits,
+ bool unlock, Error **errp)
+{
+ int ret;
+ int i;
+
+ PERM_FOREACH(i) {
+ int off = RAW_LOCK_PERM_BASE + i;
+ if (perm_lock_bits & (1ULL << i)) {
+ ret = qemu_lock_fd(s->lock_fd, off, 1, false);
+ if (ret) {
+ error_setg(errp, "Failed to lock byte %d", off);
+ return ret;
+ }
+ } else if (unlock) {
+ ret = qemu_unlock_fd(s->lock_fd, off, 1);
+ if (ret) {
+ error_setg(errp, "Failed to unlock byte %d", off);
+ return ret;
+ }
+ }
+ }
+ PERM_FOREACH(i) {
+ int off = RAW_LOCK_SHARED_BASE + i;
+ if (shared_perm_lock_bits & (1ULL << i)) {
+ ret = qemu_lock_fd(s->lock_fd, off, 1, false);
+ if (ret) {
+ error_setg(errp, "Failed to lock byte %d", off);
+ return ret;
+ }
+ } else if (unlock) {
+ ret = qemu_unlock_fd(s->lock_fd, off, 1);
+ if (ret) {
+ error_setg(errp, "Failed to unlock byte %d", off);
+ return ret;
+ }
+ }
+ }
+ return 0;
+}
+
+/* Check "unshared" bytes implied by @perm and ~@shared_perm in the file. */
+static int raw_check_lock_bytes(BDRVRawState *s,
+ uint64_t perm, uint64_t shared_perm,
+ Error **errp)
+{
+ int ret;
+ int i;
+
+ PERM_FOREACH(i) {
+ int off = RAW_LOCK_SHARED_BASE + i;
+ uint64_t p = 1ULL << i;
+ if (perm & p) {
+ ret = qemu_lock_fd_test(s->lock_fd, off, 1, true);
+ if (ret) {
+ char *perm_name = bdrv_perm_names(p);
+ error_setg(errp,
+ "Failed to get \"%s\" lock",
+ perm_name);
+ g_free(perm_name);
+ error_append_hint(errp,
+ "Is another process using the image?\n");
+ return ret;
+ }
+ }
+ }
+ PERM_FOREACH(i) {
+ int off = RAW_LOCK_PERM_BASE + i;
+ uint64_t p = 1ULL << i;
+ if (!(shared_perm & p)) {
+ ret = qemu_lock_fd_test(s->lock_fd, off, 1, true);
+ if (ret) {
+ char *perm_name = bdrv_perm_names(p);
+ error_setg(errp,
+ "Failed to get shared \"%s\" lock",
+ perm_name);
+ g_free(perm_name);
+ error_append_hint(errp,
+ "Is another process using the image?\n");
+ return ret;
+ }
+ }
+ }
+ return 0;
+}
+
+static int raw_handle_perm_lock(BlockDriverState *bs,
+ RawPermLockOp op,
+ uint64_t new_perm, uint64_t new_shared,
+ Error **errp)
+{
+ BDRVRawState *s = bs->opaque;
+ int ret = 0;
+ Error *local_err = NULL;
+
+ if (!s->use_lock) {
+ return 0;
+ }
+
+ if (bdrv_get_flags(bs) & BDRV_O_INACTIVE) {
+ return 0;
+ }
+
+ assert(s->lock_fd > 0);
+
+ switch (op) {
+ case RAW_PL_PREPARE:
+ ret = raw_apply_lock_bytes(s, s->perm | new_perm,
+ ~s->shared_perm | ~new_shared,
+ false, errp);
+ if (!ret) {
+ ret = raw_check_lock_bytes(s, new_perm, new_shared, errp);
+ if (!ret) {
+ return 0;
+ }
+ }
+ op = RAW_PL_ABORT;
+ /* fall through to unlock bytes. */
+ case RAW_PL_ABORT:
+ raw_apply_lock_bytes(s, s->perm, ~s->shared_perm, true, &local_err);
+ if (local_err) {
+ /* Theoretically the above call only unlocks bytes and it cannot
+ * fail. Something weird happened, report it.
+ */
+ error_report_err(local_err);
+ }
+ break;
+ case RAW_PL_COMMIT:
+ raw_apply_lock_bytes(s, new_perm, ~new_shared, true, &local_err);
+ if (local_err) {
+ /* Theoretically the above call only unlocks bytes and it cannot
+ * fail. Something weird happened, report it.
+ */
+ error_report_err(local_err);
+ }
+ break;
+ }
+ return ret;
+}
+
static int raw_reopen_prepare(BDRVReopenState *state,
BlockReopenQueue *queue, Error **errp)
{
static int paio_submit_co(BlockDriverState *bs, int fd,
int64_t offset, QEMUIOVector *qiov,
- int count, int type)
+ int bytes, int type)
{
RawPosixAIOData *acb = g_new(RawPosixAIOData, 1);
ThreadPool *pool;
acb->aio_type = type;
acb->aio_fildes = fd;
- acb->aio_nbytes = count;
+ acb->aio_nbytes = bytes;
acb->aio_offset = offset;
if (qiov) {
acb->aio_iov = qiov->iov;
acb->aio_niov = qiov->niov;
- assert(qiov->size == count);
+ assert(qiov->size == bytes);
}
- trace_paio_submit_co(offset, count, type);
+ trace_paio_submit_co(offset, bytes, type);
pool = aio_get_thread_pool(bdrv_get_aio_context(bs));
return thread_pool_submit_co(pool, aio_worker, acb);
}
static BlockAIOCB *paio_submit(BlockDriverState *bs, int fd,
- int64_t offset, QEMUIOVector *qiov, int count,
+ int64_t offset, QEMUIOVector *qiov, int bytes,
BlockCompletionFunc *cb, void *opaque, int type)
{
RawPosixAIOData *acb = g_new(RawPosixAIOData, 1);
acb->aio_type = type;
acb->aio_fildes = fd;
- acb->aio_nbytes = count;
+ acb->aio_nbytes = bytes;
acb->aio_offset = offset;
if (qiov) {
assert(qiov->size == acb->aio_nbytes);
}
- trace_paio_submit(acb, opaque, offset, count, type);
+ trace_paio_submit(acb, opaque, offset, bytes, type);
pool = aio_get_thread_pool(bdrv_get_aio_context(bs));
return thread_pool_submit_aio(pool, aio_worker, acb, cb, opaque);
}
qemu_close(s->fd);
s->fd = -1;
}
+ if (s->lock_fd >= 0) {
+ qemu_close(s->lock_fd);
+ s->lock_fd = -1;
+ }
}
-static int raw_truncate(BlockDriverState *bs, int64_t offset)
+/**
+ * Truncates the given regular file @fd to @offset and, when growing, fills the
+ * new space according to @prealloc.
+ *
+ * Returns: 0 on success, -errno on failure.
+ */
+static int raw_regular_truncate(int fd, int64_t offset, PreallocMode prealloc,
+ Error **errp)
+{
+ int result = 0;
+ int64_t current_length = 0;
+ char *buf = NULL;
+ struct stat st;
+
+ if (fstat(fd, &st) < 0) {
+ result = -errno;
+ error_setg_errno(errp, -result, "Could not stat file");
+ return result;
+ }
+
+ current_length = st.st_size;
+ if (current_length > offset && prealloc != PREALLOC_MODE_OFF) {
+ error_setg(errp, "Cannot use preallocation for shrinking files");
+ return -ENOTSUP;
+ }
+
+ switch (prealloc) {
+#ifdef CONFIG_POSIX_FALLOCATE
+ case PREALLOC_MODE_FALLOC:
+ /*
+ * Truncating before posix_fallocate() makes it about twice slower on
+ * file systems that do not support fallocate(), trying to check if a
+ * block is allocated before allocating it, so don't do that here.
+ */
+ result = -posix_fallocate(fd, current_length, offset - current_length);
+ if (result != 0) {
+ /* posix_fallocate() doesn't set errno. */
+ error_setg_errno(errp, -result,
+ "Could not preallocate new data");
+ }
+ goto out;
+#endif
+ case PREALLOC_MODE_FULL:
+ {
+ int64_t num = 0, left = offset - current_length;
+
+ /*
+ * Knowing the final size from the beginning could allow the file
+ * system driver to do less allocations and possibly avoid
+ * fragmentation of the file.
+ */
+ if (ftruncate(fd, offset) != 0) {
+ result = -errno;
+ error_setg_errno(errp, -result, "Could not resize file");
+ goto out;
+ }
+
+ buf = g_malloc0(65536);
+
+ result = lseek(fd, current_length, SEEK_SET);
+ if (result < 0) {
+ result = -errno;
+ error_setg_errno(errp, -result,
+ "Failed to seek to the old end of file");
+ goto out;
+ }
+
+ while (left > 0) {
+ num = MIN(left, 65536);
+ result = write(fd, buf, num);
+ if (result < 0) {
+ result = -errno;
+ error_setg_errno(errp, -result,
+ "Could not write zeros for preallocation");
+ goto out;
+ }
+ left -= result;
+ }
+ if (result >= 0) {
+ result = fsync(fd);
+ if (result < 0) {
+ result = -errno;
+ error_setg_errno(errp, -result,
+ "Could not flush file to disk");
+ goto out;
+ }
+ }
+ goto out;
+ }
+ case PREALLOC_MODE_OFF:
+ if (ftruncate(fd, offset) != 0) {
+ result = -errno;
+ error_setg_errno(errp, -result, "Could not resize file");
+ }
+ return result;
+ default:
+ result = -ENOTSUP;
+ error_setg(errp, "Unsupported preallocation mode: %s",
+ PreallocMode_lookup[prealloc]);
+ return result;
+ }
+
+out:
+ if (result < 0) {
+ if (ftruncate(fd, current_length) < 0) {
+ error_report("Failed to restore old file length: %s",
+ strerror(errno));
+ }
+ }
+
+ g_free(buf);
+ return result;
+}
+
+static int raw_truncate(BlockDriverState *bs, int64_t offset,
+ PreallocMode prealloc, Error **errp)
{
BDRVRawState *s = bs->opaque;
struct stat st;
+ int ret;
if (fstat(s->fd, &st)) {
- return -errno;
+ ret = -errno;
+ error_setg_errno(errp, -ret, "Failed to fstat() the file");
+ return ret;
}
if (S_ISREG(st.st_mode)) {
- if (ftruncate(s->fd, offset) < 0) {
- return -errno;
+ return raw_regular_truncate(s->fd, offset, prealloc, errp);
+ }
+
+ if (prealloc != PREALLOC_MODE_OFF) {
+ error_setg(errp, "Preallocation mode '%s' unsupported for this "
+ "non-regular file", PreallocMode_lookup[prealloc]);
+ return -ENOTSUP;
+ }
+
+ if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) {
+ if (offset > raw_getlength(bs)) {
+ error_setg(errp, "Cannot grow device files");
+ return -EINVAL;
}
- } else if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) {
- if (offset > raw_getlength(bs)) {
- return -EINVAL;
- }
} else {
+ error_setg(errp, "Resizing this file is not supported");
return -ENOTSUP;
}
#endif
}
- switch (prealloc) {
-#ifdef CONFIG_POSIX_FALLOCATE
- case PREALLOC_MODE_FALLOC:
- /*
- * Truncating before posix_fallocate() makes it about twice slower on
- * file systems that do not support fallocate(), trying to check if a
- * block is allocated before allocating it, so don't do that here.
- */
- result = -posix_fallocate(fd, 0, total_size);
- if (result != 0) {
- /* posix_fallocate() doesn't set errno. */
- error_setg_errno(errp, -result,
- "Could not preallocate data for the new file");
- }
- break;
-#endif
- case PREALLOC_MODE_FULL:
- {
- /*
- * Knowing the final size from the beginning could allow the file
- * system driver to do less allocations and possibly avoid
- * fragmentation of the file.
- */
- if (ftruncate(fd, total_size) != 0) {
- result = -errno;
- error_setg_errno(errp, -result, "Could not resize file");
- goto out_close;
- }
-
- int64_t num = 0, left = total_size;
- buf = g_malloc0(65536);
-
- while (left > 0) {
- num = MIN(left, 65536);
- result = write(fd, buf, num);
- if (result < 0) {
- result = -errno;
- error_setg_errno(errp, -result,
- "Could not write to the new file");
- break;
- }
- left -= result;
- }
- if (result >= 0) {
- result = fsync(fd);
- if (result < 0) {
- result = -errno;
- error_setg_errno(errp, -result,
- "Could not flush new file to disk");
- }
- }
- g_free(buf);
- break;
- }
- case PREALLOC_MODE_OFF:
- if (ftruncate(fd, total_size) != 0) {
- result = -errno;
- error_setg_errno(errp, -result, "Could not resize file");
- }
- break;
- default:
- result = -EINVAL;
- error_setg(errp, "Unsupported preallocation mode: %s",
- PreallocMode_lookup[prealloc]);
- break;
+ result = raw_regular_truncate(fd, total_size, prealloc, errp);
+ if (result < 0) {
+ goto out_close;
}
out_close:
}
static coroutine_fn BlockAIOCB *raw_aio_pdiscard(BlockDriverState *bs,
- int64_t offset, int count,
+ int64_t offset, int bytes,
BlockCompletionFunc *cb, void *opaque)
{
BDRVRawState *s = bs->opaque;
- return paio_submit(bs, s->fd, offset, NULL, count,
+ return paio_submit(bs, s->fd, offset, NULL, bytes,
cb, opaque, QEMU_AIO_DISCARD);
}
static int coroutine_fn raw_co_pwrite_zeroes(
BlockDriverState *bs, int64_t offset,
- int count, BdrvRequestFlags flags)
+ int bytes, BdrvRequestFlags flags)
{
BDRVRawState *s = bs->opaque;
if (!(flags & BDRV_REQ_MAY_UNMAP)) {
- return paio_submit_co(bs, s->fd, offset, NULL, count,
+ return paio_submit_co(bs, s->fd, offset, NULL, bytes,
QEMU_AIO_WRITE_ZEROES);
} else if (s->discard_zeroes) {
- return paio_submit_co(bs, s->fd, offset, NULL, count,
+ return paio_submit_co(bs, s->fd, offset, NULL, bytes,
QEMU_AIO_DISCARD);
}
return -ENOTSUP;
}
};
+static int raw_check_perm(BlockDriverState *bs, uint64_t perm, uint64_t shared,
+ Error **errp)
+{
+ return raw_handle_perm_lock(bs, RAW_PL_PREPARE, perm, shared, errp);
+}
+
+static void raw_set_perm(BlockDriverState *bs, uint64_t perm, uint64_t shared)
+{
+ BDRVRawState *s = bs->opaque;
+ raw_handle_perm_lock(bs, RAW_PL_COMMIT, perm, shared, NULL);
+ s->perm = perm;
+ s->shared_perm = shared;
+}
+
+static void raw_abort_perm_update(BlockDriverState *bs)
+{
+ raw_handle_perm_lock(bs, RAW_PL_ABORT, 0, 0, NULL);
+}
+
BlockDriver bdrv_file = {
.format_name = "file",
.protocol_name = "file",
.bdrv_get_info = raw_get_info,
.bdrv_get_allocated_file_size
= raw_get_allocated_file_size,
-
+ .bdrv_check_perm = raw_check_perm,
+ .bdrv_set_perm = raw_set_perm,
+ .bdrv_abort_perm_update = raw_abort_perm_update,
.create_opts = &raw_create_opts,
};
static void hdev_parse_filename(const char *filename, QDict *options,
Error **errp)
{
- /* The prefix is optional, just as for "file". */
- strstart(filename, "host_device:", &filename);
-
- qdict_put_obj(options, "filename", QOBJECT(qstring_from_str(filename)));
+ bdrv_parse_filename_strip_prefix(filename, "host_device:", options);
}
static bool hdev_is_sg(BlockDriverState *bs)
int ret;
#if defined(__APPLE__) && defined(__MACH__)
+ /*
+ * Caution: while qdict_get_str() is fine, getting non-string types
+ * would require more care. When @options come from -blockdev or
+ * blockdev_add, its members are typed according to the QAPI
+ * schema, but when they come from -drive, they're all QString.
+ */
const char *filename = qdict_get_str(options, "filename");
char bsd_path[MAXPATHLEN] = "";
bool error_occurred = false;
goto hdev_open_Mac_error;
}
- qdict_put(options, "filename", qstring_from_str(bsd_path));
+ qdict_put_str(options, "filename", bsd_path);
hdev_open_Mac_error:
g_free(mediaType);
}
static coroutine_fn BlockAIOCB *hdev_aio_pdiscard(BlockDriverState *bs,
- int64_t offset, int count,
+ int64_t offset, int bytes,
BlockCompletionFunc *cb, void *opaque)
{
BDRVRawState *s = bs->opaque;
if (fd_open(bs) < 0) {
return NULL;
}
- return paio_submit(bs, s->fd, offset, NULL, count,
+ return paio_submit(bs, s->fd, offset, NULL, bytes,
cb, opaque, QEMU_AIO_DISCARD|QEMU_AIO_BLKDEV);
}
static coroutine_fn int hdev_co_pwrite_zeroes(BlockDriverState *bs,
- int64_t offset, int count, BdrvRequestFlags flags)
+ int64_t offset, int bytes, BdrvRequestFlags flags)
{
BDRVRawState *s = bs->opaque;
int rc;
return rc;
}
if (!(flags & BDRV_REQ_MAY_UNMAP)) {
- return paio_submit_co(bs, s->fd, offset, NULL, count,
+ return paio_submit_co(bs, s->fd, offset, NULL, bytes,
QEMU_AIO_WRITE_ZEROES|QEMU_AIO_BLKDEV);
} else if (s->discard_zeroes) {
- return paio_submit_co(bs, s->fd, offset, NULL, count,
+ return paio_submit_co(bs, s->fd, offset, NULL, bytes,
QEMU_AIO_DISCARD|QEMU_AIO_BLKDEV);
}
return -ENOTSUP;
.bdrv_get_info = raw_get_info,
.bdrv_get_allocated_file_size
= raw_get_allocated_file_size,
+ .bdrv_check_perm = raw_check_perm,
+ .bdrv_set_perm = raw_set_perm,
+ .bdrv_abort_perm_update = raw_abort_perm_update,
.bdrv_probe_blocksizes = hdev_probe_blocksizes,
.bdrv_probe_geometry = hdev_probe_geometry,
static void cdrom_parse_filename(const char *filename, QDict *options,
Error **errp)
{
- /* The prefix is optional, just as for "file". */
- strstart(filename, "host_cdrom:", &filename);
-
- qdict_put_obj(options, "filename", QOBJECT(qstring_from_str(filename)));
+ bdrv_parse_filename_strip_prefix(filename, "host_cdrom:", options);
}
#endif