+
+#if defined(HAVE_SYS_FILE_H) && defined(HAVE_FLOCK)
+int
+lock_wim(WIMStruct *wim, int fd)
+{
+ int ret = 0;
+ if (fd != -1 && !wim->wim_locked) {
+ ret = flock(fd, LOCK_EX | LOCK_NB);
+ if (ret != 0) {
+ if (errno == EWOULDBLOCK) {
+ ERROR("`%"TS"' is already being modified or has been "
+ "mounted read-write\n"
+ " by another process!", wim->filename);
+ ret = WIMLIB_ERR_ALREADY_LOCKED;
+ } else {
+ WARNING_WITH_ERRNO("Failed to lock `%"TS"'",
+ wim->filename);
+ ret = 0;
+ }
+ } else {
+ wim->wim_locked = 1;
+ }
+ }
+ return ret;
+}
+#endif
+
+/*
+ * write_pipable_wim():
+ *
+ * Perform the intermediate stages of creating a "pipable" WIM (i.e. a WIM
+ * capable of being applied from a pipe).
+ *
+ * Pipable WIMs are a wimlib-specific modification of the WIM format such that
+ * images can be applied from them sequentially when the file data is sent over
+ * a pipe. In addition, a pipable WIM can be written sequentially to a pipe.
+ * The modifications made to the WIM format for pipable WIMs are:
+ *
+ * - Magic characters in header are "WLPWM\0\0\0" (wimlib pipable WIM) instead
+ * of "MSWIM\0\0\0". This lets wimlib know that the WIM is pipable and also
+ * stops other software from trying to read the file as a normal WIM.
+ *
+ * - The header at the beginning of the file does not contain all the normal
+ * information; in particular it will have all 0's for the lookup table and
+ * XML data resource entries. This is because this information cannot be
+ * determined until the lookup table and XML data have been written.
+ * Consequently, wimlib will write the full header at the very end of the
+ * file. The header at the end, however, is only used when reading the WIM
+ * from a seekable file (not a pipe).
+ *
+ * - An extra copy of the XML data is placed directly after the header. This
+ * allows image names and sizes to be determined at an appropriate time when
+ * reading the WIM from a pipe. This copy of the XML data is ignored if the
+ * WIM is read from a seekable file (not a pipe).
+ *
+ * - The format of resources, or streams, has been modified to allow them to be
+ * used before the "lookup table" has been read. Each stream is prefixed with
+ * a `struct pwm_stream_hdr' that is basically an abbreviated form of `struct
+ * wim_lookup_table_entry_disk' that only contains the SHA1 message digest,
+ * uncompressed stream size, and flags that indicate whether the stream is
+ * compressed. The data of uncompressed streams then follows literally, while
+ * the data of compressed streams follows in a modified format. Compressed
+ * streams do not begin with a chunk table, since the chunk table cannot be
+ * written until all chunks have been compressed. Instead, each compressed
+ * chunk is prefixed by a `struct pwm_chunk_hdr' that gives its size.
+ * Furthermore, the chunk table is written at the end of the resource instead
+ * of the start. Note: chunk offsets are given in the chunk table as if the
+ * `struct pwm_chunk_hdr's were not present; also, the chunk table is only
+ * used if the WIM is being read from a seekable file (not a pipe).
+ *
+ * - Metadata resources always come before other file resources (streams).
+ * (This does not by itself constitute an incompatibility with normal WIMs,
+ * since this is valid in normal WIMs.)
+ *
+ * - At least up to the end of the file resources, all components must be packed
+ * as tightly as possible; there cannot be any "holes" in the WIM. (This does
+ * not by itself consititute an incompatibility with normal WIMs, since this
+ * is valid in normal WIMs.)
+ *
+ * Note: the lookup table, XML data, and header at the end are not used when
+ * applying from a pipe. They exist to support functionality such as image
+ * application and export when the WIM is *not* read from a pipe.
+ *
+ * Layout of pipable WIM:
+ *
+ * ---------+----------+--------------------+----------------+--------------+-----------+--------+
+ * | Header | XML data | Metadata resources | File resources | Lookup table | XML data | Header |
+ * ---------+----------+--------------------+----------------+--------------+-----------+--------+
+ *
+ * Layout of normal WIM:
+ *
+ * +--------+-----------------------------+-------------------------+
+ * | Header | File and metadata resources | Lookup table | XML data |
+ * +--------+-----------------------------+-------------------------+
+ *
+ * An optional integrity table can follow the final XML data in both normal and
+ * pipable WIMs. However, due to implementation details, wimlib currently can
+ * only include an integrity table in a pipable WIM when writing it to a
+ * seekable file (not a pipe).
+ *
+ * Do note that since pipable WIMs are not supported by Microsoft's software,
+ * wimlib does not create them unless explicitly requested (with
+ * WIMLIB_WRITE_FLAG_PIPABLE) and as stated above they use different magic
+ * characters to identify the file.
+ */
+static int
+write_pipable_wim(WIMStruct *wim, int image, int write_flags,
+ unsigned num_threads, wimlib_progress_func_t progress_func,
+ struct list_head *stream_list_override,
+ struct list_head *lookup_table_list_ret)
+{
+ int ret;
+ struct wim_reshdr xml_reshdr;
+
+ WARNING("Creating a pipable WIM, which will "
+ "be incompatible\n"
+ " with Microsoft's software (wimgapi/imagex/Dism).");
+
+ /* At this point, the header at the beginning of the file has already
+ * been written. */
+
+ /* For efficiency, when wimlib adds an image to the WIM with
+ * wimlib_add_image(), the SHA1 message digests of files is not
+ * calculated; instead, they are calculated while the files are being
+ * written. However, this does not work when writing a pipable WIM,
+ * since when writing a stream to a pipable WIM, its SHA1 message digest
+ * needs to be known before the stream data is written. Therefore,
+ * before getting much farther, we need to pre-calculate the SHA1
+ * message digests of all streams that will be written. */
+ ret = wim_checksum_unhashed_streams(wim);
+ if (ret)
+ return ret;
+
+ /* Write extra copy of the XML data. */
+ ret = write_wim_xml_data(wim, image, WIM_TOTALBYTES_OMIT,
+ &xml_reshdr,
+ WRITE_RESOURCE_FLAG_PIPABLE);
+ if (ret)
+ return ret;
+
+ /* Write metadata resources for the image(s) being included in the
+ * output WIM. */
+ ret = write_wim_metadata_resources(wim, image, write_flags,
+ progress_func);
+ if (ret)
+ return ret;
+
+ /* Write streams needed for the image(s) being included in the output
+ * WIM, or streams needed for the split WIM part. */
+ return write_wim_streams(wim, image, write_flags, num_threads,
+ progress_func, stream_list_override,
+ lookup_table_list_ret);
+
+ /* The lookup table, XML data, and header at end are handled by
+ * finish_write(). */
+}
+
+/* Write a standalone WIM or split WIM (SWM) part to a new file or to a file
+ * descriptor. */
+int
+write_wim_part(WIMStruct *wim,
+ const void *path_or_fd,
+ int image,
+ int write_flags,
+ unsigned num_threads,
+ wimlib_progress_func_t progress_func,
+ unsigned part_number,
+ unsigned total_parts,
+ struct list_head *stream_list_override,
+ const u8 *guid)
+{
+ int ret;
+ struct wim_header hdr_save;
+ struct list_head lookup_table_list;
+
+ if (total_parts == 1)
+ DEBUG("Writing standalone WIM.");
+ else
+ DEBUG("Writing split WIM part %u/%u", part_number, total_parts);
+ if (image == WIMLIB_ALL_IMAGES)
+ DEBUG("Including all images.");
+ else
+ DEBUG("Including image %d only.", image);
+ if (write_flags & WIMLIB_WRITE_FLAG_FILE_DESCRIPTOR)
+ DEBUG("File descriptor: %d", *(const int*)path_or_fd);
+ else
+ DEBUG("Path: \"%"TS"\"", (const tchar*)path_or_fd);
+ DEBUG("Write flags: 0x%08x", write_flags);
+
+ if (write_flags & WIMLIB_WRITE_FLAG_CHECK_INTEGRITY)
+ DEBUG("\tCHECK_INTEGRITY");
+
+ if (write_flags & WIMLIB_WRITE_FLAG_REBUILD)
+ DEBUG("\tREBUILD");
+
+ if (write_flags & WIMLIB_WRITE_FLAG_RECOMPRESS)
+ DEBUG("\tRECOMPRESS");
+
+ if (write_flags & WIMLIB_WRITE_FLAG_FSYNC)
+ DEBUG("\tFSYNC");
+
+ if (write_flags & WIMLIB_WRITE_FLAG_SOFT_DELETE)
+ DEBUG("\tFSYNC");
+
+ if (write_flags & WIMLIB_WRITE_FLAG_IGNORE_READONLY_FLAG)
+ DEBUG("\tIGNORE_READONLY_FLAG");
+
+ if (write_flags & WIMLIB_WRITE_FLAG_PIPABLE)
+ DEBUG("\tPIPABLE");
+
+ if (write_flags & WIMLIB_WRITE_FLAG_NOT_PIPABLE)
+ DEBUG("\tNOT_PIPABLE");
+
+ if (write_flags & WIMLIB_WRITE_FLAG_PACK_STREAMS)
+ DEBUG("\tPACK_STREAMS");
+
+ if (write_flags & WIMLIB_WRITE_FLAG_NO_PACK_STREAMS)
+ DEBUG("\tNO_PACK_STREAMS");
+
+ if (write_flags & WIMLIB_WRITE_FLAG_FILE_DESCRIPTOR)
+ DEBUG("\tFILE_DESCRIPTOR");
+
+ if (write_flags & WIMLIB_WRITE_FLAG_NO_METADATA)
+ DEBUG("\tNO_METADATA");
+
+ if (write_flags & WIMLIB_WRITE_FLAG_USE_EXISTING_TOTALBYTES)
+ DEBUG("\tUSE_EXISTING_TOTALBYTES");
+
+ if (num_threads == 0)
+ DEBUG("Number of threads: autodetect");
+ else
+ DEBUG("Number of threads: %u", num_threads);
+ DEBUG("Progress function: %s", (progress_func ? "yes" : "no"));
+ DEBUG("Stream list: %s", (stream_list_override ? "specified" : "autodetect"));
+ DEBUG("GUID: %s", ((guid || wim->guid_set_explicitly) ?
+ "specified" : "generate new"));
+
+ /* Internally, this is always called with a valid part number and total
+ * parts. */
+ wimlib_assert(total_parts >= 1);
+ wimlib_assert(part_number >= 1 && part_number <= total_parts);
+
+ /* A valid image (or all images) must be specified. */
+ if (image != WIMLIB_ALL_IMAGES &&
+ (image < 1 || image > wim->hdr.image_count))
+ return WIMLIB_ERR_INVALID_IMAGE;
+
+ /* If we need to write metadata resources, make sure the ::WIMStruct has
+ * the needed information attached (e.g. is not a resource-only WIM,
+ * such as a non-first part of a split WIM). */
+ if (!wim_has_metadata(wim) &&
+ !(write_flags & WIMLIB_WRITE_FLAG_NO_METADATA))
+ return WIMLIB_ERR_METADATA_NOT_FOUND;
+
+ /* Check for contradictory flags. */
+ if ((write_flags & (WIMLIB_WRITE_FLAG_CHECK_INTEGRITY |
+ WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY))
+ == (WIMLIB_WRITE_FLAG_CHECK_INTEGRITY |
+ WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY))
+ return WIMLIB_ERR_INVALID_PARAM;
+
+ if ((write_flags & (WIMLIB_WRITE_FLAG_PIPABLE |
+ WIMLIB_WRITE_FLAG_NOT_PIPABLE))
+ == (WIMLIB_WRITE_FLAG_PIPABLE |
+ WIMLIB_WRITE_FLAG_NOT_PIPABLE))
+ return WIMLIB_ERR_INVALID_PARAM;
+
+ if ((write_flags & (WIMLIB_WRITE_FLAG_PACK_STREAMS |
+ WIMLIB_WRITE_FLAG_NO_PACK_STREAMS))
+ == (WIMLIB_WRITE_FLAG_PACK_STREAMS |
+ WIMLIB_WRITE_FLAG_NO_PACK_STREAMS))
+ return WIMLIB_ERR_INVALID_PARAM;
+
+ /* Save previous header, then start initializing the new one. */
+ memcpy(&hdr_save, &wim->hdr, sizeof(struct wim_header));
+
+ /* Set default integrity, pipable, and packed stream flags. */
+ if (!(write_flags & (WIMLIB_WRITE_FLAG_PIPABLE |
+ WIMLIB_WRITE_FLAG_NOT_PIPABLE)))
+ if (wim_is_pipable(wim)) {
+ DEBUG("WIM is pipable; default to PIPABLE.");
+ write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
+ }
+
+ if (!(write_flags & (WIMLIB_WRITE_FLAG_CHECK_INTEGRITY |
+ WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY)))
+ if (wim_has_integrity_table(wim)) {
+ DEBUG("Integrity table present; default to CHECK_INTEGRITY.");
+ write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
+ }
+
+ if (!(write_flags & (WIMLIB_WRITE_FLAG_PACK_STREAMS |
+ WIMLIB_WRITE_FLAG_NO_PACK_STREAMS)))
+ if (wim->hdr.wim_version == WIM_VERSION_PACKED_STREAMS) {
+ DEBUG("WIM version 3584; default to PACK_STREAMS.");
+ write_flags |= WIMLIB_WRITE_FLAG_PACK_STREAMS;
+ }
+
+ if ((write_flags & (WIMLIB_WRITE_FLAG_PIPABLE |
+ WIMLIB_WRITE_FLAG_PACK_STREAMS))
+ == (WIMLIB_WRITE_FLAG_PIPABLE |
+ WIMLIB_WRITE_FLAG_PACK_STREAMS))
+ {
+ ERROR("Cannot specify both PIPABLE and PACK_STREAMS!");
+ return WIMLIB_ERR_INVALID_PARAM;
+ }
+
+ /* Set appropriate magic number. */
+ if (write_flags & WIMLIB_WRITE_FLAG_PIPABLE)
+ wim->hdr.magic = PWM_MAGIC;
+ else
+ wim->hdr.magic = WIM_MAGIC;
+
+ /* Set appropriate version number. */
+ if ((write_flags & WIMLIB_WRITE_FLAG_PACK_STREAMS) ||
+ wim->out_compression_type == WIMLIB_COMPRESSION_TYPE_LZMS)
+ wim->hdr.wim_version = WIM_VERSION_PACKED_STREAMS;
+ else
+ wim->hdr.wim_version = WIM_VERSION_DEFAULT;
+
+ /* Clear header flags that will be set automatically. */
+ wim->hdr.flags &= ~(WIM_HDR_FLAG_METADATA_ONLY |
+ WIM_HDR_FLAG_RESOURCE_ONLY |
+ WIM_HDR_FLAG_SPANNED |
+ WIM_HDR_FLAG_WRITE_IN_PROGRESS);
+
+ /* Set SPANNED header flag if writing part of a split WIM. */
+ if (total_parts != 1)
+ wim->hdr.flags |= WIM_HDR_FLAG_SPANNED;
+
+ /* Set part number and total parts of split WIM. This will be 1 and 1
+ * if the WIM is standalone. */
+ wim->hdr.part_number = part_number;
+ wim->hdr.total_parts = total_parts;
+
+ /* Set compression type if different. */
+ if (wim->compression_type != wim->out_compression_type) {
+ ret = set_wim_hdr_cflags(wim->out_compression_type, &wim->hdr);
+ wimlib_assert(ret == 0);
+ }
+
+ /* Set chunk size if different. */
+ wim->hdr.chunk_size = wim->out_chunk_size;
+
+ /* Use GUID if specified; otherwise generate a new one. */
+ if (guid)
+ memcpy(wim->hdr.guid, guid, WIMLIB_GUID_LEN);
+ else if (!wim->guid_set_explicitly)
+ randomize_byte_array(wim->hdr.guid, WIMLIB_GUID_LEN);
+
+ /* Clear references to resources that have not been written yet. */
+ zero_reshdr(&wim->hdr.lookup_table_reshdr);
+ zero_reshdr(&wim->hdr.xml_data_reshdr);
+ zero_reshdr(&wim->hdr.boot_metadata_reshdr);
+ zero_reshdr(&wim->hdr.integrity_table_reshdr);
+
+ /* Set image count and boot index correctly for single image writes. */
+ if (image != WIMLIB_ALL_IMAGES) {
+ wim->hdr.image_count = 1;
+ if (wim->hdr.boot_idx == image)
+ wim->hdr.boot_idx = 1;
+ else
+ wim->hdr.boot_idx = 0;
+ }
+
+ /* Split WIMs can't be bootable. */
+ if (total_parts != 1)
+ wim->hdr.boot_idx = 0;
+
+ /* Initialize output file descriptor. */
+ if (write_flags & WIMLIB_WRITE_FLAG_FILE_DESCRIPTOR) {
+ /* File descriptor was explicitly provided. Return error if
+ * file descriptor is not seekable, unless writing a pipable WIM
+ * was requested. */
+ wim->out_fd.fd = *(const int*)path_or_fd;
+ wim->out_fd.offset = 0;
+ if (!filedes_is_seekable(&wim->out_fd)) {
+ ret = WIMLIB_ERR_INVALID_PARAM;
+ if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE))
+ goto out_restore_hdr;
+ if (write_flags & WIMLIB_WRITE_FLAG_CHECK_INTEGRITY) {
+ ERROR("Can't include integrity check when "
+ "writing pipable WIM to pipe!");
+ goto out_restore_hdr;
+ }
+ }
+
+ } else {
+ /* Filename of WIM to write was provided; open file descriptor
+ * to it. */
+ ret = open_wim_writable(wim, (const tchar*)path_or_fd,
+ O_TRUNC | O_CREAT | O_RDWR);
+ if (ret)
+ goto out_restore_hdr;
+ }
+
+ /* Write initial header. This is merely a "dummy" header since it
+ * doesn't have all the information yet, so it will be overwritten later
+ * (unless writing a pipable WIM). */
+ if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE))
+ wim->hdr.flags |= WIM_HDR_FLAG_WRITE_IN_PROGRESS;
+ ret = write_wim_header(&wim->hdr, &wim->out_fd);
+ wim->hdr.flags &= ~WIM_HDR_FLAG_WRITE_IN_PROGRESS;
+ if (ret)
+ goto out_restore_hdr;
+
+ /* Write metadata resources and streams. */
+ if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
+ /* Default case: create a normal (non-pipable) WIM. */
+ ret = write_wim_streams(wim, image, write_flags, num_threads,
+ progress_func, stream_list_override,
+ &lookup_table_list);
+ if (ret)
+ goto out_restore_hdr;
+
+ ret = write_wim_metadata_resources(wim, image, write_flags,
+ progress_func);
+ if (ret)
+ goto out_restore_hdr;
+ } else {
+ /* Non-default case: create pipable WIM. */
+ ret = write_pipable_wim(wim, image, write_flags, num_threads,
+ progress_func, stream_list_override,
+ &lookup_table_list);
+ if (ret)
+ goto out_restore_hdr;
+ write_flags |= WIMLIB_WRITE_FLAG_HEADER_AT_END;
+ }
+
+
+ /* Write lookup table, XML data, and (optional) integrity table. */
+ ret = finish_write(wim, image, write_flags, progress_func,
+ &lookup_table_list);
+out_restore_hdr:
+ memcpy(&wim->hdr, &hdr_save, sizeof(struct wim_header));
+ (void)close_wim_writable(wim, write_flags);
+ DEBUG("ret=%d", ret);
+ return ret;
+}
+
+/* Write a standalone WIM to a file or file descriptor. */
+static int
+write_standalone_wim(WIMStruct *wim, const void *path_or_fd,
+ int image, int write_flags, unsigned num_threads,
+ wimlib_progress_func_t progress_func)
+{
+ return write_wim_part(wim, path_or_fd, image, write_flags,
+ num_threads, progress_func, 1, 1, NULL, NULL);
+}
+
+/* API function documented in wimlib.h */
+WIMLIBAPI int
+wimlib_write(WIMStruct *wim, const tchar *path,
+ int image, int write_flags, unsigned num_threads,
+ wimlib_progress_func_t progress_func)
+{
+ if (!path)
+ return WIMLIB_ERR_INVALID_PARAM;
+
+ write_flags &= WIMLIB_WRITE_MASK_PUBLIC;
+
+ return write_standalone_wim(wim, path, image, write_flags,
+ num_threads, progress_func);
+}
+
+/* API function documented in wimlib.h */
+WIMLIBAPI int
+wimlib_write_to_fd(WIMStruct *wim, int fd,
+ int image, int write_flags, unsigned num_threads,
+ wimlib_progress_func_t progress_func)
+{
+ if (fd < 0)
+ return WIMLIB_ERR_INVALID_PARAM;
+
+ write_flags &= WIMLIB_WRITE_MASK_PUBLIC;
+ write_flags |= WIMLIB_WRITE_FLAG_FILE_DESCRIPTOR;
+
+ return write_standalone_wim(wim, &fd, image, write_flags,
+ num_threads, progress_func);
+}
+
+static bool
+any_images_modified(WIMStruct *wim)
+{
+ for (int i = 0; i < wim->hdr.image_count; i++)
+ if (wim->image_metadata[i]->modified)
+ return true;
+ return false;
+}
+
+static int
+check_resource_offset(struct wim_lookup_table_entry *lte, void *_wim)
+{
+ const WIMStruct *wim = _wim;
+ off_t end_offset = *(const off_t*)wim->private;
+
+ if (lte->resource_location == RESOURCE_IN_WIM && lte->rspec->wim == wim &&
+ lte->rspec->offset_in_wim + lte->rspec->size_in_wim > end_offset)
+ return WIMLIB_ERR_RESOURCE_ORDER;
+ return 0;
+}
+
+/* Make sure no file or metadata resources are located after the XML data (or
+ * integrity table if present)--- otherwise we can't safely overwrite the WIM in
+ * place and we return WIMLIB_ERR_RESOURCE_ORDER. */
+static int
+check_resource_offsets(WIMStruct *wim, off_t end_offset)
+{
+ int ret;
+ unsigned i;
+
+ wim->private = &end_offset;
+ ret = for_lookup_table_entry(wim->lookup_table, check_resource_offset, wim);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < wim->hdr.image_count; i++) {
+ ret = check_resource_offset(wim->image_metadata[i]->metadata_lte, wim);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+/*
+ * Overwrite a WIM, possibly appending streams to it.
+ *
+ * A WIM looks like (or is supposed to look like) the following:
+ *
+ * Header (212 bytes)
+ * Streams and metadata resources (variable size)
+ * Lookup table (variable size)
+ * XML data (variable size)
+ * Integrity table (optional) (variable size)
+ *
+ * If we are not adding any streams or metadata resources, the lookup table is
+ * unchanged--- so we only need to overwrite the XML data, integrity table, and
+ * header. This operation is potentially unsafe if the program is abruptly
+ * terminated while the XML data or integrity table are being overwritten, but
+ * before the new header has been written. To partially alleviate this problem,
+ * a special flag (WIMLIB_WRITE_FLAG_CHECKPOINT_AFTER_XML) is passed to
+ * finish_write() to cause a temporary WIM header to be written after the XML
+ * data has been written. This may prevent the WIM from becoming corrupted if
+ * the program is terminated while the integrity table is being calculated (but
+ * no guarantees, due to write re-ordering...).
+ *
+ * If we are adding new streams or images (metadata resources), the lookup table
+ * needs to be changed, and those streams need to be written. In this case, we
+ * try to perform a safe update of the WIM file by writing the streams *after*
+ * the end of the previous WIM, then writing the new lookup table, XML data, and
+ * (optionally) integrity table following the new streams. This will produce a
+ * layout like the following:
+ *
+ * Header (212 bytes)
+ * (OLD) Streams and metadata resources (variable size)
+ * (OLD) Lookup table (variable size)
+ * (OLD) XML data (variable size)
+ * (OLD) Integrity table (optional) (variable size)
+ * (NEW) Streams and metadata resources (variable size)
+ * (NEW) Lookup table (variable size)
+ * (NEW) XML data (variable size)
+ * (NEW) Integrity table (optional) (variable size)
+ *
+ * At all points, the WIM is valid as nothing points to the new data yet. Then,
+ * the header is overwritten to point to the new lookup table, XML data, and
+ * integrity table, to produce the following layout:
+ *
+ * Header (212 bytes)
+ * Streams and metadata resources (variable size)
+ * Nothing (variable size)
+ * More Streams and metadata resources (variable size)
+ * Lookup table (variable size)
+ * XML data (variable size)
+ * Integrity table (optional) (variable size)
+ *
+ * This method allows an image to be appended to a large WIM very quickly, and
+ * is is crash-safe except in the case of write re-ordering, but the
+ * disadvantage is that a small hole is left in the WIM where the old lookup
+ * table, xml data, and integrity table were. (These usually only take up a
+ * small amount of space compared to the streams, however.)
+ */
+static int
+overwrite_wim_inplace(WIMStruct *wim, int write_flags,
+ unsigned num_threads,
+ wimlib_progress_func_t progress_func)
+{
+ int ret;
+ off_t old_wim_end;
+ u64 old_lookup_table_end, old_xml_begin, old_xml_end;
+ struct wim_header hdr_save;
+ struct list_head stream_list;
+ struct list_head lookup_table_list;
+ struct filter_context filter_ctx;
+
+ DEBUG("Overwriting `%"TS"' in-place", wim->filename);
+
+ /* Save original header so it can be restored in case of error */
+ memcpy(&hdr_save, &wim->hdr, sizeof(struct wim_header));
+
+ /* Set default integrity flag. */
+ if (!(write_flags & (WIMLIB_WRITE_FLAG_CHECK_INTEGRITY |
+ WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY)))
+ if (wim_has_integrity_table(wim))
+ write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
+
+ /* Set default packed flag. */
+ if (!(write_flags & (WIMLIB_WRITE_FLAG_PACK_STREAMS |
+ WIMLIB_WRITE_FLAG_NO_PACK_STREAMS)))
+ {
+ #if 0
+ if (wim->hdr.wim_version == WIM_VERSION_PACKED_STREAMS)
+ write_flags |= WIMLIB_WRITE_FLAG_PACK_STREAMS;
+ #endif
+ /* wimlib allows multiple packs in a single WIM, but they don't
+ * seem to be compatible with WIMGAPI. Write new streams
+ * unpacked. */
+ } else if (write_flags & WIMLIB_WRITE_FLAG_PACK_STREAMS) {
+ wim->hdr.wim_version = WIM_VERSION_PACKED_STREAMS;
+ }
+
+ /* Set additional flags for overwrite. */
+ write_flags |= WIMLIB_WRITE_FLAG_OVERWRITE |
+ WIMLIB_WRITE_FLAG_STREAMS_OK;
+
+ /* Make sure that the integrity table (if present) is after the XML
+ * data, and that there are no stream resources, metadata resources, or
+ * lookup tables after the XML data. Otherwise, these data would be
+ * overwritten. */
+ old_xml_begin = wim->hdr.xml_data_reshdr.offset_in_wim;
+ old_xml_end = old_xml_begin + wim->hdr.xml_data_reshdr.size_in_wim;
+ old_lookup_table_end = wim->hdr.lookup_table_reshdr.offset_in_wim +
+ wim->hdr.lookup_table_reshdr.size_in_wim;
+ if (wim->hdr.integrity_table_reshdr.offset_in_wim != 0 &&
+ wim->hdr.integrity_table_reshdr.offset_in_wim < old_xml_end) {
+ WARNING("Didn't expect the integrity table to be before the XML data");
+ ret = WIMLIB_ERR_RESOURCE_ORDER;
+ goto out_restore_memory_hdr;
+ }
+
+ if (old_lookup_table_end > old_xml_begin) {
+ WARNING("Didn't expect the lookup table to be after the XML data");
+ ret = WIMLIB_ERR_RESOURCE_ORDER;
+ goto out_restore_memory_hdr;
+ }
+
+ /* Set @old_wim_end, which indicates the point beyond which we don't
+ * allow any file and metadata resources to appear without returning
+ * WIMLIB_ERR_RESOURCE_ORDER (due to the fact that we would otherwise
+ * overwrite these resources). */
+ if (!wim->deletion_occurred && !any_images_modified(wim)) {
+ /* If no images have been modified and no images have been
+ * deleted, a new lookup table does not need to be written. We
+ * shall write the new XML data and optional integrity table
+ * immediately after the lookup table. Note that this may
+ * overwrite an existing integrity table. */
+ DEBUG("Skipping writing lookup table "
+ "(no images modified or deleted)");
+ old_wim_end = old_lookup_table_end;
+ write_flags |= WIMLIB_WRITE_FLAG_NO_LOOKUP_TABLE |
+ WIMLIB_WRITE_FLAG_CHECKPOINT_AFTER_XML;
+ } else if (wim->hdr.integrity_table_reshdr.offset_in_wim != 0) {
+ /* Old WIM has an integrity table; begin writing new streams
+ * after it. */
+ old_wim_end = wim->hdr.integrity_table_reshdr.offset_in_wim +
+ wim->hdr.integrity_table_reshdr.size_in_wim;
+ } else {
+ /* No existing integrity table; begin writing new streams after
+ * the old XML data. */
+ old_wim_end = old_xml_end;
+ }
+
+ ret = check_resource_offsets(wim, old_wim_end);
+ if (ret)
+ goto out_restore_memory_hdr;
+
+ ret = prepare_stream_list_for_write(wim, WIMLIB_ALL_IMAGES, write_flags,
+ &stream_list, &lookup_table_list,
+ &filter_ctx);
+ if (ret)
+ goto out_restore_memory_hdr;
+
+ ret = open_wim_writable(wim, wim->filename, O_RDWR);
+ if (ret)
+ goto out_restore_memory_hdr;
+
+ ret = lock_wim(wim, wim->out_fd.fd);
+ if (ret)
+ goto out_close_wim;
+
+ /* Set WIM_HDR_FLAG_WRITE_IN_PROGRESS flag in header. */
+ wim->hdr.flags |= WIM_HDR_FLAG_WRITE_IN_PROGRESS;
+ ret = write_wim_header_flags(wim->hdr.flags, &wim->out_fd);
+ if (ret) {
+ ERROR_WITH_ERRNO("Error updating WIM header flags");
+ goto out_unlock_wim;
+ }
+
+ if (filedes_seek(&wim->out_fd, old_wim_end) == -1) {
+ ERROR_WITH_ERRNO("Can't seek to end of WIM");
+ ret = WIMLIB_ERR_WRITE;
+ goto out_restore_physical_hdr;
+ }
+
+ ret = wim_write_stream_list(wim,
+ &stream_list,
+ write_flags,
+ num_threads,
+ &filter_ctx,
+ progress_func);
+ if (ret)
+ goto out_truncate;
+
+ ret = write_wim_metadata_resources(wim, WIMLIB_ALL_IMAGES,
+ write_flags, progress_func);
+ if (ret)
+ goto out_truncate;
+
+ write_flags |= WIMLIB_WRITE_FLAG_REUSE_INTEGRITY_TABLE;
+ ret = finish_write(wim, WIMLIB_ALL_IMAGES, write_flags,
+ progress_func, &lookup_table_list);
+ if (ret)
+ goto out_truncate;
+
+ wim->wim_locked = 0;
+ return 0;
+
+out_truncate:
+ if (!(write_flags & WIMLIB_WRITE_FLAG_NO_LOOKUP_TABLE)) {
+ WARNING("Truncating `%"TS"' to its original size (%"PRIu64" bytes)",
+ wim->filename, old_wim_end);
+ /* Return value of ftruncate() is ignored because this is
+ * already an error path. */
+ (void)ftruncate(wim->out_fd.fd, old_wim_end);
+ }
+out_restore_physical_hdr:
+ (void)write_wim_header_flags(hdr_save.flags, &wim->out_fd);
+out_unlock_wim:
+ wim->wim_locked = 0;
+out_close_wim:
+ (void)close_wim_writable(wim, write_flags);
+out_restore_memory_hdr:
+ memcpy(&wim->hdr, &hdr_save, sizeof(struct wim_header));
+ return ret;
+}
+
+static int
+overwrite_wim_via_tmpfile(WIMStruct *wim, int write_flags,
+ unsigned num_threads,
+ wimlib_progress_func_t progress_func)
+{
+ size_t wim_name_len;
+ int ret;
+
+ DEBUG("Overwriting `%"TS"' via a temporary file", wim->filename);
+
+ /* Write the WIM to a temporary file in the same directory as the
+ * original WIM. */
+ wim_name_len = tstrlen(wim->filename);
+ tchar tmpfile[wim_name_len + 10];
+ tmemcpy(tmpfile, wim->filename, wim_name_len);
+ randomize_char_array_with_alnum(tmpfile + wim_name_len, 9);
+ tmpfile[wim_name_len + 9] = T('\0');
+
+ ret = wimlib_write(wim, tmpfile, WIMLIB_ALL_IMAGES,
+ write_flags | WIMLIB_WRITE_FLAG_FSYNC,
+ num_threads, progress_func);
+ if (ret) {
+ tunlink(tmpfile);
+ return ret;
+ }
+
+ close_wim(wim);
+
+ /* Rename the new WIM file to the original WIM file. Note: on Windows
+ * this actually calls win32_rename_replacement(), not _wrename(), so
+ * that removing the existing destination file can be handled. */
+ DEBUG("Renaming `%"TS"' to `%"TS"'", tmpfile, wim->filename);
+ ret = trename(tmpfile, wim->filename);
+ if (ret) {
+ ERROR_WITH_ERRNO("Failed to rename `%"TS"' to `%"TS"'",
+ tmpfile, wim->filename);
+ #ifdef __WIN32__
+ if (ret < 0)
+ #endif
+ {
+ tunlink(tmpfile);
+ }
+ return WIMLIB_ERR_RENAME;
+ }
+
+ if (progress_func) {
+ union wimlib_progress_info progress;
+ progress.rename.from = tmpfile;
+ progress.rename.to = wim->filename;
+ progress_func(WIMLIB_PROGRESS_MSG_RENAME, &progress);
+ }
+ return 0;
+}
+
+/* Determine if the specified WIM file may be updated by appending in-place
+ * rather than writing and replacing it with an entirely new file. */
+static bool
+can_overwrite_wim_inplace(const WIMStruct *wim, int write_flags)
+{
+ /* REBUILD flag forces full rebuild. */
+ if (write_flags & WIMLIB_WRITE_FLAG_REBUILD)
+ return false;
+
+ /* Deletions cause full rebuild by default. */
+ if (wim->deletion_occurred && !(write_flags & WIMLIB_WRITE_FLAG_SOFT_DELETE))
+ return false;
+
+ /* Pipable WIMs cannot be updated in place, nor can a non-pipable WIM be
+ * turned into a pipable WIM in-place. */
+ if (wim_is_pipable(wim) || (write_flags & WIMLIB_WRITE_FLAG_PIPABLE))
+ return false;
+
+ /* wimlib allows multiple packs in a single WIM, but they don't seem to
+ * be compatible with WIMGAPI, so force all streams to be repacked if
+ * the WIM already may have contained a pack and PACK_STREAMS was
+ * requested. */
+ if (write_flags & WIMLIB_WRITE_FLAG_PACK_STREAMS &&
+ wim->hdr.wim_version == WIM_VERSION_PACKED_STREAMS)
+ return false;
+
+ /* The default compression type and compression chunk size selected for
+ * the output WIM must be the same as those currently used for the WIM.
+ */
+ if (wim->compression_type != wim->out_compression_type)
+ return false;
+ if (wim->chunk_size != wim->out_chunk_size)
+ return false;
+
+ return true;
+}
+
+/* API function documented in wimlib.h */
+WIMLIBAPI int
+wimlib_overwrite(WIMStruct *wim, int write_flags,
+ unsigned num_threads,
+ wimlib_progress_func_t progress_func)
+{
+ int ret;
+ u32 orig_hdr_flags;
+
+ write_flags &= WIMLIB_WRITE_MASK_PUBLIC;
+
+ if (write_flags & WIMLIB_WRITE_FLAG_FILE_DESCRIPTOR)
+ return WIMLIB_ERR_INVALID_PARAM;
+
+ if (!wim->filename)
+ return WIMLIB_ERR_NO_FILENAME;
+
+ orig_hdr_flags = wim->hdr.flags;
+ if (write_flags & WIMLIB_WRITE_FLAG_IGNORE_READONLY_FLAG)
+ wim->hdr.flags &= ~WIM_HDR_FLAG_READONLY;
+ ret = can_modify_wim(wim);
+ wim->hdr.flags = orig_hdr_flags;
+ if (ret)
+ return ret;
+
+ if (can_overwrite_wim_inplace(wim, write_flags)) {
+ ret = overwrite_wim_inplace(wim, write_flags, num_threads,
+ progress_func);
+ if (ret != WIMLIB_ERR_RESOURCE_ORDER)
+ return ret;
+ WARNING("Falling back to re-building entire WIM");
+ }
+ return overwrite_wim_via_tmpfile(wim, write_flags, num_threads,
+ progress_func);
+}