From 91ba045392c18b7e12add95158cd2ab67c1505ae Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Fri, 21 Feb 2014 23:47:13 -0600 Subject: [PATCH] Support committing changed mounted WIM image as new image --- NEWS | 5 ++ doc/imagex-mount.1.in | 5 ++ include/wimlib.h | 5 ++ include/wimlib/lookup_table.h | 10 ++- programs/imagex.c | 16 ++++ src/mount_image.c | 145 +++++++++++++++++++++++++++++++--- 6 files changed, 174 insertions(+), 12 deletions(-) diff --git a/NEWS b/NEWS index c75cae13..bd50c589 100644 --- a/NEWS +++ b/NEWS @@ -6,6 +6,11 @@ Version 1.6.2 (BETA): Fixed build failure on Mac OS X. + wimunmount now provides the '--new-image' option to cause changes to a + read-write mounted image to be committed as a new image rather than as + an update of the same image. (The corresponding new library flag is + WIMLIB_UNMOUNT_FLAG_NEW_IMAGE.) + The LZMS compression chunk size, or "dictionary size", may now be up to 1 GiB (2^30). diff --git a/doc/imagex-mount.1.in b/doc/imagex-mount.1.in index fc288436..5fb88dc1 100644 --- a/doc/imagex-mount.1.in +++ b/doc/imagex-mount.1.in @@ -162,6 +162,11 @@ where the filesystem is detached immediately even if it is still busy. However, even with this option, \fB@IMAGEX_PROGNAME@ unmount\fR still waits for the filesystem to become unbusy; \fB--lazy\fR will only stop the unmount from immediately failing. +.TP +\fB--new-image\fR +In combination with \fB--commit\fR for a read-write mounted image, causes the +modified image to be committed as a new, unnamed image appended to the WIM +archive. The original image will be unmodified. .SH IMPLEMENTATION DETAILS Since a WIM is an archive and not a filesystem, \fB@IMAGEX_PROGNAME@ mountrw\fR creates a temporary staging directory to contain files that are created or modified. This diff --git a/include/wimlib.h b/include/wimlib.h index 22b6b0d3..22df79c5 100644 --- a/include/wimlib.h +++ b/include/wimlib.h @@ -1628,6 +1628,11 @@ typedef int (*wimlib_iterate_lookup_table_callback_t)(const struct wimlib_resour /** Do a "lazy" unmount (detach filesystem immediately, even if busy). */ #define WIMLIB_UNMOUNT_FLAG_LAZY 0x00000010 +/** In combination with ::WIMLIB_UNMOUNT_FLAG_COMMIT for a read-write mounted + * image, causes the modified image to be committed as a new, unnamed image + * appended to the archive. The original image will be unmodified. */ +#define WIMLIB_UNMOUNT_FLAG_NEW_IMAGE 0x00000020 + /** @} */ /** @ingroup G_modifying_wims * @{ */ diff --git a/include/wimlib/lookup_table.h b/include/wimlib/lookup_table.h index a2feebf7..a169e393 100644 --- a/include/wimlib/lookup_table.h +++ b/include/wimlib/lookup_table.h @@ -136,7 +136,12 @@ struct wim_lookup_table_entry { * be extracted. * * During image export, this is set to the number of references of this - * stream that originated from the source WIM. */ + * stream that originated from the source WIM. + * + * When mounting a WIM image read-write, this is set to the number of + * extra references to this stream preemptively taken to allow later + * saving the modified image as a new image and leaving the original + * image alone. */ u32 out_refcnt; #ifdef WITH_FUSE @@ -225,6 +230,9 @@ struct wim_lookup_table_entry { /* Links streams being exported. */ struct list_head export_stream_list; + + /* Links original list of streams in the read-write mounted image. */ + struct list_head orig_stream_list; }; /* Links streams that are still unhashed after being been added to a diff --git a/programs/imagex.c b/programs/imagex.c index c1d436e1..c0a9f8c2 100644 --- a/programs/imagex.c +++ b/programs/imagex.c @@ -167,6 +167,7 @@ enum { IMAGEX_LAZY_OPTION, IMAGEX_LOOKUP_TABLE_OPTION, IMAGEX_METADATA_OPTION, + IMAGEX_NEW_IMAGE_OPTION, IMAGEX_NOCHECK_OPTION, IMAGEX_NORPFIX_OPTION, IMAGEX_NOT_PIPABLE_OPTION, @@ -361,6 +362,7 @@ static const struct option unmount_options[] = { {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION}, {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION}, {T("lazy"), no_argument, NULL, IMAGEX_LAZY_OPTION}, + {T("new-image"), no_argument, NULL, IMAGEX_NEW_IMAGE_OPTION}, {NULL, 0, NULL, 0}, }; @@ -3755,6 +3757,9 @@ imagex_unmount(int argc, tchar **argv, int cmd) case IMAGEX_LAZY_OPTION: unmount_flags |= WIMLIB_UNMOUNT_FLAG_LAZY; break; + case IMAGEX_NEW_IMAGE_OPTION: + unmount_flags |= WIMLIB_UNMOUNT_FLAG_NEW_IMAGE; + break; default: goto out_usage; } @@ -3764,6 +3769,15 @@ imagex_unmount(int argc, tchar **argv, int cmd) if (argc != 1) goto out_usage; + if (unmount_flags & WIMLIB_UNMOUNT_FLAG_NEW_IMAGE) { + if (!(unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT)) { + imagex_error(T("--new-image is meaningless " + "without --commit also specified!")); + goto out_err; + } + imagex_printf(T("Committing changes as new image...\n")); + } + ret = wimlib_unmount_image(argv[0], unmount_flags, imagex_progress_func); if (ret) @@ -3773,6 +3787,7 @@ out: out_usage: usage(CMD_UNMOUNT, stderr); +out_err: ret = -1; goto out; } @@ -4113,6 +4128,7 @@ T( [CMD_UNMOUNT] = T( " %"TS" DIRECTORY [--commit] [--check] [--rebuild] [--lazy]\n" +" [--new-image]\n" ), #endif [CMD_UPDATE] = diff --git a/src/mount_image.c b/src/mount_image.c index d7165466..30ee82d2 100644 --- a/src/mount_image.c +++ b/src/mount_image.c @@ -114,6 +114,10 @@ struct wimfs_context { /* List of inodes in the mounted image */ struct list_head *image_inode_list; + /* Original list of streams in the mounted image, linked by + * mount_orig_stream_list. */ + struct list_head orig_stream_list; + /* Name and message queue descriptors for message queues between the * filesystem daemon process and the unmount process. These are used * when the filesystem is unmounted and the process running @@ -186,7 +190,6 @@ alloc_wimfs_fd(struct wim_inode *inode, { static const u16 fds_per_alloc = 8; static const u16 max_fds = 0xffff; - int ret; DEBUG("Allocating fd for stream ID %u from inode %#"PRIx64" " "(open = %u, allocated = %u)", @@ -1101,7 +1104,9 @@ send_unmount_request_msg(mqd_t mq, int unmount_flags, u8 want_progress_messages) DEBUG("Sending unmount request msg"); struct msg_unmount_request msg = { .hdr = { - .min_version = WIMLIB_MAKEVERSION(1, 2, 1), + .min_version = ((unmount_flags & WIMLIB_UNMOUNT_FLAG_NEW_IMAGE) ? + WIMLIB_MAKEVERSION(1, 6, 1) : + WIMLIB_MAKEVERSION(1, 2, 1)), .cur_version = WIMLIB_VERSION_CODE, .msg_type = MSG_TYPE_UNMOUNT_REQUEST, .msg_size = sizeof(msg), @@ -1181,6 +1186,78 @@ unmount_progress_func(enum wimlib_progress_msg msg, return 0; } +static void +release_extra_refcnts(struct wimfs_context *ctx) +{ + struct list_head *list = &ctx->orig_stream_list; + struct wim_lookup_table *lookup_table = ctx->wim->lookup_table; + struct wim_lookup_table_entry *lte, *tmp; + + list_for_each_entry_safe(lte, tmp, list, orig_stream_list) + while (lte->out_refcnt--) + lte_decrement_refcnt(lte, lookup_table); +} + +/* Moves the currently selected image, which may have been modified, to a new + * index, and sets the original index to refer to a reset (unmodified) copy of + * the image. */ +static int +renew_current_image(struct wimfs_context *ctx) +{ + WIMStruct *wim = ctx->wim; + int ret; + int idx = wim->current_image - 1; + struct wim_image_metadata *imd = wim->image_metadata[idx]; + struct wim_image_metadata *replace_imd; + struct wim_lookup_table_entry *new_lte; + + if (imd->metadata_lte->resource_location != RESOURCE_IN_WIM) { + ERROR("Can't reset modified image that doesn't yet " + "exist in the on-disk WIM file!"); + return WIMLIB_ERR_METADATA_NOT_FOUND; + } + + /* Create 'replace_imd' structure to use for the reset original, + * unmodified image. */ + replace_imd = new_image_metadata(); + if (!replace_imd) + return WIMLIB_ERR_NOMEM; + + /* Create new stream reference for the modified image's metadata + * resource, which doesn't exist yet. */ + ret = WIMLIB_ERR_NOMEM; + new_lte = new_lookup_table_entry(); + if (!new_lte) + goto err_put_replace_imd; + new_lte->flags = WIM_RESHDR_FLAG_METADATA; + new_lte->unhashed = 1; + + /* Make the image being moved available at a new index. Increments the + * WIM's image count, but does not increment the reference count of the + * 'struct image_metadata'. */ + ret = append_image_metadata(wim, imd); + if (ret) + goto err_free_new_lte; + + ret = xml_add_image(wim, T("")); + if (ret) + goto err_undo_append; + + replace_imd->metadata_lte = imd->metadata_lte; + imd->metadata_lte = new_lte; + wim->image_metadata[idx] = replace_imd; + wim->current_image = wim->hdr.image_count; + return 0; + +err_undo_append: + wim->hdr.image_count--; +err_free_new_lte: + free_lookup_table_entry(new_lte); +err_put_replace_imd: + put_image_metadata(replace_imd, NULL); + return ret; +} + static int msg_unmount_request_handler(const void *_msg, void *_handler_ctx) { @@ -1215,6 +1292,18 @@ msg_unmount_request_handler(const void *_msg, void *_handler_ctx) if (wimfs_ctx->mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) { if (unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT) { + + if (unmount_flags & WIMLIB_UNMOUNT_FLAG_NEW_IMAGE) { + ret = renew_current_image(wimfs_ctx); + if (ret) { + status = ret; + goto out; + } + } else { + release_extra_refcnts(wimfs_ctx); + } + INIT_LIST_HEAD(&wimfs_ctx->orig_stream_list); + int write_flags = 0; if (unmount_flags & WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY) write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY; @@ -2525,16 +2614,47 @@ wimlib_mount_image(WIMStruct *wim, int image, const char *dir, } #endif - /* Mark dentry tree as modified if read-write mount. */ - if (mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) + /* Assign inode numbers. Also, if a read-write mount was requested, + * mark the dentry tree as modified, and add each streams referenced by + * files in the image to a list and preemptively double the number of + * references to each. The latter is done to allow implementing the + * WIMLIB_UNMOUNT_FLAG_NEW_IMAGE semantics. */ + ctx.next_ino = 1; + INIT_LIST_HEAD(&ctx.orig_stream_list); + if (mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) { imd->modified = 1; + image_for_each_inode(inode, imd) { + inode->i_ino = ctx.next_ino++; + for (unsigned i = 0; i <= inode->i_num_ads; i++) { + struct wim_lookup_table_entry *lte; + + lte = inode_stream_lte(inode, i, wim->lookup_table); + if (lte) { + lte->orig_stream_list = (struct list_head){NULL, NULL}; + lte->out_refcnt = 0; + } + } + } + image_for_each_inode(inode, imd) { + for (unsigned i = 0; i <= inode->i_num_ads; i++) { + struct wim_lookup_table_entry *lte; + + lte = inode_stream_lte(inode, i, + wim->lookup_table); + if (lte) { + if (lte->out_refcnt == 0) + list_add(<e->orig_stream_list, + &ctx.orig_stream_list); + lte->out_refcnt += inode->i_nlink; + lte->refcnt += inode->i_nlink; + } + } + } + } else { + image_for_each_inode(inode, imd) + inode->i_ino = ctx.next_ino++; + } - /* Resolve the lookup table entries for every inode in the image, and - * assign inode numbers */ - DEBUG("Resolving lookup table entries and assigning inode numbers"); - ctx.next_ino = 1; - image_for_each_inode(inode, imd) - inode->i_ino = ctx.next_ino++; DEBUG("(next_ino = %"PRIu64")", ctx.next_ino); DEBUG("Calling fuse_main()"); @@ -2556,6 +2676,8 @@ wimlib_mount_image(WIMStruct *wim, int image, const char *dir, close_message_queues(&ctx); } + release_extra_refcnts(&ctx); + /* Try to delete the staging directory if a deletion wasn't yet * attempted due to an earlier error */ if (ctx.staging_dir_name) @@ -2581,7 +2703,8 @@ wimlib_unmount_image(const char *dir, int unmount_flags, WIMLIB_UNMOUNT_FLAG_COMMIT | WIMLIB_UNMOUNT_FLAG_REBUILD | WIMLIB_UNMOUNT_FLAG_RECOMPRESS | - WIMLIB_UNMOUNT_FLAG_LAZY)) + WIMLIB_UNMOUNT_FLAG_LAZY | + WIMLIB_UNMOUNT_FLAG_NEW_IMAGE)) return WIMLIB_ERR_INVALID_PARAM; init_wimfs_context(&wimfs_ctx); -- 2.43.0