When block streaming an image, if a base name is passed in that is a relative name, but not accessible from the top-level snapshot, then the relative name is stored incorrectly in the image file.
For instance, given a snapshot case of: /tmp/a/base.raw /tmp/a/snap1.qcow2 /tmp/b/snap2.qcow2 if they are all chained with relative filenames, like so: base(bak:"") <- snap1(bak:"base.raw") <- snap2(bak:"../a/snap1.qcow2") Then the merged top-layer will point to an inaccessible path for the base file: base(bak:"") <- snap2(bak:"base.raw") This patch checks for a relative path for a basename, and fixes it so that it is stored in the top-layer image relative to the top-layer image. Signed-off-by: Jeff Cody <jc...@redhat.com> --- block.c | 5 +++ block.h | 1 + block/stream.c | 12 +++++++- cutils.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ qemu-common.h | 2 + 5 files changed, 111 insertions(+), 1 deletions(-) diff --git a/block.c b/block.c index 42f0953..98a28ee 100644 --- a/block.c +++ b/block.c @@ -206,6 +206,11 @@ static int path_has_protocol(const char *path) return strchr(path, ':') != NULL; } +bool bdrv_has_protocol(BlockDriverState *bs) +{ + return(bs->drv && bs->drv->protocol_name); +} + int path_is_absolute(const char *path) { const char *p; diff --git a/block.h b/block.h index 415bb17..f2d6de2 100644 --- a/block.h +++ b/block.h @@ -340,6 +340,7 @@ void bdrv_disable_copy_on_read(BlockDriverState *bs); void bdrv_set_in_use(BlockDriverState *bs, int in_use); int bdrv_in_use(BlockDriverState *bs); +bool bdrv_has_protocol(BlockDriverState *bs); enum BlockAcctType { BDRV_ACCT_READ, diff --git a/block/stream.c b/block/stream.c index 5c939c7..0335c78 100644 --- a/block/stream.c +++ b/block/stream.c @@ -172,6 +172,7 @@ static void coroutine_fn stream_run(void *opaque) int ret = 0; int n; void *buf; + char *base_filename = NULL; s->common.len = bdrv_getlength(bs); if (s->common.len < 0) { @@ -240,13 +241,22 @@ retry: if (sector_num == end && ret == 0) { const char *base_id = NULL; if (base) { - base_id = s->backing_file_id; + /* fix up relative paths, if any */ + if (!bdrv_has_protocol(bs) && + !path_is_absolute(s->backing_file_id)) { + base_filename = path_find_relative(bs->filename, + s->base->filename); + base_id = base_filename; + } else { + base_id = s->backing_file_id; + } } ret = bdrv_change_backing_file(bs, base_id, NULL); close_unused_images(bs, base, base_id); } qemu_vfree(buf); + g_free(base_filename); block_job_complete(&s->common, ret); } diff --git a/cutils.c b/cutils.c index dd82375..62fc0c6 100644 --- a/cutils.c +++ b/cutils.c @@ -553,6 +553,98 @@ int qemu_sendv(int sockfd, struct iovec *iov, int len, int iov_offset) return do_sendv_recvv(sockfd, iov, len, iov_offset, 1); } + +static inline bool is_path_separator(char c) +{ +#ifdef _WIN32 + return (c == '/' || c == '\\'); +#else + return (c == '/'); +#endif +} + +/* + * Returns a newly allocated string containing the relative path to access + * backing, from current. Caller is responsible for freeing this string. + * + * On error, returns the absolute canonical path of backing, if available; NULL + * otherwise. + + * Note: it is expected that both 'current' and 'backing' are strings + * containing a path (relative or absolute) to a file, not just a directory. + * E.g., bs->filename, bs->backing_file. + */ +char *path_find_relative(char *current, char *backing) +{ + char *src = NULL; + char *dest = NULL; + char *src_tmp = NULL; + char *rel_backing = NULL; + char relpath[PATH_MAX] = {0}; + int common_idx = 0; + int src_bytes_max = 0; + + + src = qemu_file_get_full_path(current, 0, NULL); + dest = qemu_file_get_full_path(backing, 0, NULL); + + if (src == NULL || dest == NULL) { + goto free_and_exit; + } + + src_tmp = g_strdup(src); + src_bytes_max = strlen(src_tmp)+1; + + g_strlcpy(src_tmp, dirname(src), src_bytes_max); + g_strlcpy(src, src_tmp, src_bytes_max); + + for (;;) { + char c; + if (!strncmp(src_tmp, dest, strlen(src_tmp))) { + /* common_idx is the marker of the first unique character of + * src_tmp and dest + */ + common_idx = strlen(src_tmp); + c = '/'; + if (common_idx > 1) { + c = dest[common_idx]; + /* dirname() will return '/' for base directory, if we get + * that far. That is the only dirname() result that will end + * with a '/' or '\'. Account for this by moving the index + * marker forward. + */ + common_idx++; + } + if (common_idx < strlen(dest)) { + /* we are looking for the commonality of directory paths, + * not just characters - the common part must denote a full + * directory. Look for directory separator. + */ + if (is_path_separator(c)) { + /* success */ + rel_backing = g_strconcat(relpath, &dest[common_idx], NULL); + break; + } + } else { + rel_backing = g_strdup(dest); + break; + } + } else if (strlen(src_tmp) <= 1) { + break; + } + g_strlcpy(src_tmp, dirname(src), src_bytes_max); + g_strlcpy(src, src_tmp, src_bytes_max); + g_strlcat(relpath, "../", sizeof(relpath)); + } + +free_and_exit: + g_free(src_tmp); + free(src); + free(dest); + return rel_backing; +} + + /** * g_get_full_path: * @program: a file name in the GLib file name encoding diff --git a/qemu-common.h b/qemu-common.h index 3c2af73..88ada73 100644 --- a/qemu-common.h +++ b/qemu-common.h @@ -360,6 +360,8 @@ gchar *qemu_file_get_full_path(const gchar *path, QEMUFileFullPathFlags flags, GError **error); +char *path_find_relative(char *current, char *backing); + #define QEMU_FILE_TYPE_BIOS 0 #define QEMU_FILE_TYPE_KEYMAP 1 char *qemu_find_file(int type, const char *name); -- 1.7.9.rc2.1.g69204