Export from split WIM
authorEric Biggers <ebiggers3@gmail.com>
Sat, 1 Sep 2012 03:04:46 +0000 (22:04 -0500)
committerEric Biggers <ebiggers3@gmail.com>
Sat, 1 Sep 2012 03:04:46 +0000 (22:04 -0500)
doc/imagex-export.1.in
programs/imagex.c
src/join.c
src/lookup_table.h
src/modify.c
src/wimlib.h

index d455e9d140a32194ab4effb295de50e15e0d6804..e80a5d13b67529058f886d2c0f0dbc37ea88523d 100644 (file)
@@ -29,6 +29,10 @@ If given, \fIDEST_IMAGE_DESCRIPTION\fR specifies the description to give the
 image being exported to \fIDEST_WIMFILE\fR.  The default is its description in
 \fISRC_WIMFILE\fR.
 
+\fBimagex export\fR supports exporting images from stand-alone WIMs as well as
+from split WIMs.  However, you cannot export an image to a split WIM.  See
+\fBSPLIT WIMS\fR.
+
 .SH OPTIONS
 .TP 6
 \fB--boot\fR
@@ -56,16 +60,50 @@ compression type must be the same as that of \fIDEST_WIMFILE\fR.
 You may also specify the actual names of the compression algorithms, "XPRESS"
 and "LZX", instead of "fast" and "maximum", respectively.
 
-.SH NOTES
+.TP
+\fB--ref\fR="\fIGLOB\fR"
+File glob of additional split WIM parts that are part of the split WIM being
+exported.  See \fBSPLIT_WIMS\fR.
+
+.SH SPLIT WIMS
+
+You may use \fBimagex export\fR to export images from a split WIM.  The
+\fISRC_WIMFILE\fR argument is used to specify the first part of the split WIM, and
+the \fB--refs\fR="\fIGLOB\fR" option is used to provide a shell-style file glob
+that specifies the additional parts of the split WIM.  \fIGLOB\fR is expected to
+be a single string on the command line, so \fIGLOB\fR must be quoted so that it
+is protected against shell expansion.  \fIGLOB\fR must expand to all parts of
+the split WIM, except optionally the first part which may either omitted or
+included in the glob (but the first part MUST be specified as \fISRC_WIMFILE\fR as
+well).
+
+Here's an example.  The names for the split WIMs usually go something like:
+       
+.RS
+.PP
+.nf
+mywim.swm
+mywim2.swm
+mywim3.swm
+mywim4.swm
+mywim5.swm
+\. ... etc.
+.RE
 
-\fBimagex export\fR does not yet support split WIMs.
+To export the first image of this split WIM to a new or existing WIM file
+"other.wim", run:
+.PP
+.RS
+imagex export mywim.swm 1 other.wim --ref="mywim*.swm"
+.RE
+.PP
 
 .SH EXAMPLES
-Export the second image of 'boot.wim' to the new WIM file 'image2.wim', and
+Export the second image of 'boot.wim' to the new WIM file 'new.wim', and
 change the compression type to maximum, if it wasn't maximum already:
 .RS
 .PP
-image export boot.wim 2 image2.wim --compress=maximum
+image export boot.wim 2 new.wim --compress=maximum
 .RE
 .PP
 
index 07381a9bf5752bf2f2642ceeb1e31e2b05920cee..fac692f054028fd8f32e0e70fd89249fcf311f82 100644 (file)
@@ -81,7 +81,7 @@ static const char *usage_strings[] = {
 "    imagex export SRC_WIMFILE (SRC_IMAGE_NUM | SRC_IMAGE_NAME | all ) \n"
 "                  DEST_WIMFILE [DEST_IMAGE_NAME]\n"
 "                  [DEST_IMAGE_DESCRIPTION] [--boot] [--check]\n"
-"                  [--compress=TYPE]\n",
+"                  [--compress=TYPE] [--ref=\"GLOB\"]\n",
 [INFO] = 
 "    imagex info WIMFILE [IMAGE_NUM | IMAGE_NAME] [NEW_NAME]\n"
 "                [NEW_DESC] [--boot] [--check] [--header] [--lookup-table]\n"
@@ -143,6 +143,7 @@ static const struct option export_options[] = {
        {"boot",       no_argument,       NULL, 'b'},
        {"check",      no_argument,       NULL, 'c'},
        {"compress",   required_argument, NULL, 'x'},
+       {"ref",        required_argument, NULL, 'r'},
        {NULL, 0, NULL, 0},
 };
 
@@ -851,6 +852,9 @@ static int imagex_export(int argc, const char **argv)
        int image;
        struct stat stbuf;
        bool wim_is_new;
+       const char *swm_glob = NULL;
+       WIMStruct **additional_swms = NULL;
+       unsigned num_additional_swms = 0;
 
        for_opt(c, export_options) {
                switch (c) {
@@ -867,6 +871,9 @@ static int imagex_export(int argc, const char **argv)
                                return -1;
                        compression_type_specified = true;
                        break;
+               case 'r':
+                       swm_glob = optarg;
+                       break;
                default:
                        usage(EXPORT);
                        return -1;
@@ -883,7 +890,8 @@ static int imagex_export(int argc, const char **argv)
        dest_wimfile          = argv[2];
        dest_name             = (argc >= 4) ? argv[3] : NULL;
        dest_desc             = (argc >= 5) ? argv[4] : NULL;
-       ret = wimlib_open_wim(src_wimfile, open_flags, &src_w);
+       ret = wimlib_open_wim(src_wimfile,
+                             open_flags | WIMLIB_OPEN_FLAG_SPLIT_OK, &src_w);
        if (ret != 0)
                return ret;
 
@@ -896,11 +904,11 @@ static int imagex_export(int argc, const char **argv)
                if (!S_ISREG(stbuf.st_mode) && !S_ISLNK(stbuf.st_mode)) {
                        imagex_error("`%s' is not a regular file",
                                        dest_wimfile);
-                       goto done;
+                       goto out;
                }
                ret = wimlib_open_wim(dest_wimfile, open_flags, &dest_w);
                if (ret != 0)
-                       goto done;
+                       goto out;
 
                if (compression_type_specified && compression_type != 
                                wimlib_get_compression_type(dest_w)) {
@@ -908,7 +916,7 @@ static int imagex_export(int argc, const char **argv)
                                     "not the same as that used in the "
                                     "destination WIM");
                        ret = -1;
-                       goto done;
+                       goto out;
                }
                compression_type = wimlib_get_compression_type(dest_w);
        } else {
@@ -917,23 +925,32 @@ static int imagex_export(int argc, const char **argv)
                if (errno == ENOENT) {
                        ret = wimlib_create_new_wim(compression_type, &dest_w);
                        if (ret != 0)
-                               goto done;
+                               goto out;
                } else {
                        imagex_error_with_errno("Cannot stat file `%s'",
                                                dest_wimfile);
-                       goto done;
+                       goto out;
                }
        }
 
        image = wimlib_resolve_image(src_w, src_image_num_or_name);
        ret = verify_image_exists(image);
        if (ret != 0)
-               goto done;
+               goto out;
+
+       if (swm_glob) {
+               ret = open_swms_from_glob(swm_glob, src_wimfile, open_flags,
+                                         &additional_swms,
+                                         &num_additional_swms);
+               if (ret != 0)
+                       goto out;
+       }
 
        ret = wimlib_export_image(src_w, image, dest_w, dest_name, dest_desc, 
-                                 export_flags);
+                                 export_flags, additional_swms,
+                                 num_additional_swms);
        if (ret != 0)
-               goto done;
+               goto out;
 
 
        if (wim_is_new)
@@ -941,9 +958,12 @@ static int imagex_export(int argc, const char **argv)
                                   write_flags);
        else
                ret = wimlib_overwrite(dest_w, write_flags);
-done:
+out:
        wimlib_free(src_w);
        wimlib_free(dest_w);
+       if (additional_swms)
+               for (unsigned i = 0; i < num_additional_swms; i++)
+                       wimlib_free(additional_swms[i]);
        return ret;
 }
 
index 07cbfa971c92622b08eb72df6b39e9d26e18524a..a1639e3627bb8641b9a87394b8c1bf13f3e8afe7 100644 (file)
@@ -138,6 +138,21 @@ int verify_swm_set(WIMStruct *w, WIMStruct **additional_swms,
        return 0;
 }
 
+/* 
+ * Joins lookup tables from the parts of a split WIM.
+ *
+ * @w specifies the first part, while @additional_swms and @num_additional_swms
+ * specify an array of points to the WIMStruct's for additional split WIM parts.
+ *
+ * On success, 0 is returned on a pointer to the joined lookup table is returned
+ * in @table_ret.
+ *
+ * The reason we join the lookup tables is so:
+ *     - We only have to search one lookup table to find the location of a
+ *     resource in the entire split WIM.
+ *     - Each lookup table entry will have a pointer to its split WIM part (and
+ *     a part number field, although we don't really use it).
+ */
 int new_joined_lookup_table(WIMStruct *w,
                            WIMStruct **additional_swms,
                            unsigned num_additional_swms,
index f0ee25b78f2d89761352afaa4860a39e1f783312..6fc5ae50c3f5f37439ca191f01d4fc3ef3ba0105 100644 (file)
@@ -41,55 +41,97 @@ struct ntfs_location {
  *
  * It is used to find data streams for files in the WIM. 
  *
- * The lookup_table_entry for a given dentry in the WIM is found using the SHA1
- * message digest field. 
+ * Metadata resources and reparse point data buffers will also have lookup table
+ * entries associated with the data.
+ *
+ * The lookup_table_entry for a given dentry or alternate stream entry in the
+ * WIM is found using the SHA1 message digest field. 
  */
 struct lookup_table_entry {
 
        /* List of lookup table entries in this hash bucket */
        struct hlist_node hash_list;
 
-       /* @resource_entry is read from the lookup table in the WIM
-        * file; it says where to find the file resource in the WIM
-        * file, and whether it is compressed or not. */
+       /* Location and size of the stream in the WIM, whether it is compressed
+        * or not, and whether it's a metadata resource or not.  This is an
+        * on-disk field. */
        struct resource_entry resource_entry;
 
-       /* Currently ignored; set to 1 in new lookup table entries. */
+       /* Specifies which part of the split WIM the resource is located in.
+        * This is on on-disk field.
+        *
+        * In stand-alone WIMs, this must be 1.
+        *
+        * In split WIMs, every split WIM part has its own lookup table, and in
+        * read_lookup_table() it's currently expected that the part number of
+        * each lookup table entry in a split WIM part's lookup table is the
+        * same as the part number of that split WIM part.  So this makes this
+        * field redundant since we store a pointer to the corresponding
+        * WIMStruct in the lookup table entry anyway.
+        */
        u16 part_number;
 
-       /* If %true, this lookup table entry corresponds to a symbolic link
-        * reparse buffer.  @symlink_reparse_data_buf will give the target of
-        * the symbolic link. */
+       /* An enumerated type that identifies where the stream corresponding to
+        * this lookup table entry is actually located.
+        *
+        * Obviously if we open a WIM and read its lookup table, the location is
+        * set to RESOURCE_IN_WIM since all the streams will initially be
+        * located in the WIM.  However, to deal with problems such as image
+        * capture and image mount, we allow the actual location of the stream
+        * to be somewhere else, such as an external file.
+        */
        enum {
+               /* The lookup table entry does not correspond to a stream (this
+                * state should exist only temporarily) */
                RESOURCE_NONEXISTENT = 0,
+
+               /* The stream resource is located in a WIM file.  The WIMStruct
+                * for the WIM file will be pointed to by the @wim member. */
                RESOURCE_IN_WIM,
+
+               /* The stream resource is located in an external file.  The
+                * name of the file will be provided by @file_on_disk member.
+                * In addition, if @file_on_disk_fp is not NULL, it will be an
+                * open FILE * to the file. */
                RESOURCE_IN_FILE_ON_DISK,
+
+               /* The stream resource is located in an external file in the
+                * staging directory for a read-write mount.  */
                RESOURCE_IN_STAGING_FILE,
+
+               /* The stream resource is directly attached in an in-memory
+                * buffer pointed to by @attached_buffer. */
                RESOURCE_IN_ATTACHED_BUFFER,
+
+               /* The stream resource is located in an NTFS volume.  It is
+                * identified by volume, filename, data stream name, and by
+                * whether it is a reparse point or not. @ntfs_loc points to a
+                * structure containing this information. */
                RESOURCE_IN_NTFS_VOLUME,
        } resource_location;
 
-       /* Number of times this lookup table entry is referenced by dentries. */
+       /* (On-disk field)
+        * Number of times this lookup table entry is referenced by dentries.
+        * Unfortunately, this field is not always set correctly in Microsoft's
+        * WIMs, so we have no choice but to fix it if more references to the
+        * lookup table entry are found than stated here. */
        u32 refcnt;
 
        union {
-               /* SHA1 hash of the file resource pointed to by this lookup
-                * table entry */
+               /* (On-disk field) SHA1 message digest of the stream referenced
+                * by this lookup table entry */
                u8  hash[SHA1_HASH_SIZE];
 
-               /* First 4 or 8 bytes of the SHA1 hash, used for inserting the
-                * entry into the hash table.  Since the SHA1 hashes can be
-                * considered random, we don't really need the full 20 byte hash
-                * just to insert the entry in a hash table. */
+               /* First 4 or 8 bytes of the SHA1 message digest, used for
+                * inserting the entry into the hash table.  Since the SHA1
+                * message digest can be considered random, we don't really need
+                * the full 20 byte hash just to insert the entry in a hash
+                * table. */
                size_t hash_short;
        };
 
-       /* If @file_on_disk != NULL, the file resource indicated by this lookup
-        * table entry is not in the WIM file, but rather a file on disk; this
-        * occurs for files that are added to the WIM.  In that case,
-        * file_on_disk is the name of the file in the outside filesystem.  
-        * It will not be compressed, and its size will be given by
-        * resource_entry.size and resource_entry.original_size. */
+       /* Pointers to somewhere where the stream is actually located.  See the
+        * comments for the @resource_location field above. */
        union {
                WIMStruct *wim;
                char *file_on_disk;
@@ -100,14 +142,25 @@ struct lookup_table_entry {
        #endif
        };
        union {
+               /* Temporary field for creating a singly linked list.  Shouldn't
+                * really be here */
                struct lookup_table_entry *next_lte_in_swm;
+
+               /* @file_on_disk_fp and @attr are both used to cache file/stream
+                * handles so we don't have re-open them on every read */
                FILE *file_on_disk_fp;
        #ifdef WITH_NTFS_3G
                struct _ntfs_attr *attr;
        #endif
        };
 #ifdef WITH_FUSE
-       /* File descriptors table for this data stream */
+       /* File descriptors table for this data stream.  This is used if the WIM
+        * is mounted.  Basically, each time a file is open()ed, a new file
+        * descriptor is added here, and each time a file is close()ed, the file
+        * descriptor is gotten rid of.  If the stream is opened for writing, it
+        * will be extracted to the staging directory and there will be an
+        * actual native file descriptor associated with each "wimlib file
+        * descriptor". */
        u16 num_opened_fds;
        u16 num_allocated_fds;
        struct wimlib_fd **fds;
@@ -115,22 +168,31 @@ struct lookup_table_entry {
 
        /* When a WIM file is written, out_refcnt starts at 0 and is incremented
         * whenever the file resource pointed to by this lookup table entry
-        * needs to be written.  Naturally, the file resource only need to be
-        * written when out_refcnt is 0.  Incrementing it further is needed to
-        * find the correct reference count to write to the lookup table in the
-        * output file, which may be less than the regular refcnt if not all
-        * images in the WIM file are written. 
-        *
-        * output_resource_entry is the struct resource_entry for the position of the
-        * file resource when written to the output file. */
+        * needs to be written.  The file resource only need to be written when
+        * out_refcnt is nonzero, since otherwise it is not referenced by any
+        * dentries. */
        u32 out_refcnt;
+
        union {
+               /* When a WIM file is written, @output_resource_entry is filled
+                * in with the resource entry for the output WIM.  This will not
+                * necessarily be the same as the @resource_entry since:
+                *      - The stream may have a different offset in the new WIM
+                *      - The stream may have a different compressed size in the
+                *      new WIM if the compression type changed
+                */
                struct resource_entry output_resource_entry;
+
+               /* This field is used for the special hardlink or symlink image
+                * application mode.   In these mode, all identical files are
+                * linked together, and @extracted_file will be set to the
+                * filename of the first extracted file containing this stream.
+                * */
                char *extracted_file;
        };
 
        /* Circular linked list of streams that share the same lookup table
-        * entry
+        * entry.
         * 
         * This list of streams may include streams from different hard link
         * sets that happen to be the same.  */
index 5223b81912c5abde8943a9d2c85023ca814029cd..8519881d2b95f613ae2f06a71bd6e35d79ffbc22 100644 (file)
@@ -267,16 +267,12 @@ static int add_lte_to_dest_wim(struct dentry *dentry, void *arg)
                if (dest_lte) {
                        dest_lte->refcnt++;
                } else {
-                       dest_lte = new_lookup_table_entry();
+                       dest_lte = MALLOC(sizeof(struct lookup_table_entry));
                        if (!dest_lte)
                                return WIMLIB_ERR_NOMEM;
-                       dest_lte->resource_location = RESOURCE_IN_WIM;
-                       dest_lte->wim = src_wim;
-                       memcpy(&dest_lte->resource_entry, 
-                              &src_lte->resource_entry, 
-                              sizeof(struct resource_entry));
-                       copy_hash(dest_lte->hash,
-                                 dentry_stream_hash_unresolved(dentry, i));
+                       memcpy(dest_lte, src_lte, sizeof(struct lookup_table_entry));
+                       dest_lte->part_number = 1;
+                       dest_lte->refcnt = 1;
                        lookup_table_insert(dest_wim->lookup_table, dest_lte);
                }
        }
@@ -356,19 +352,22 @@ WIMLIBAPI int wimlib_export_image(WIMStruct *src_wim,
                                  WIMStruct *dest_wim, 
                                  const char *dest_name, 
                                  const char *dest_description, 
-                                 int flags)
+                                 int flags,
+                                 WIMStruct **additional_swms,
+                                 unsigned num_additional_swms)
 {
        int i;
        int ret;
        struct dentry *root;
        struct wim_pair wims;
        struct wim_security_data *sd;
+       struct lookup_table *joined_tab, *src_wim_tab_save;
 
        if (!src_wim || !dest_wim)
                return WIMLIB_ERR_INVALID_PARAM;
 
-       if (src_wim->hdr.total_parts != 1 || src_wim->hdr.total_parts != 1) {
-               ERROR("Exporting an image to or from a split WIM is "
+       if (dest_wim->hdr.total_parts != 1) {
+               ERROR("Exporting an image to a split WIM is "
                      "unsupported");
                return WIMLIB_ERR_SPLIT_UNSUPPORTED;
        }
@@ -406,7 +405,9 @@ WIMLIBAPI int wimlib_export_image(WIMStruct *src_wim,
                                ret = wimlib_export_image(src_wim, i, dest_wim, 
                                                          NULL,
                                                          dest_description,
-                                                         export_flags);
+                                                         export_flags,
+                                                         additional_swms,
+                                                         num_additional_swms);
                                if (ret != 0)
                                        return ret;
                        }
@@ -416,13 +417,6 @@ WIMLIBAPI int wimlib_export_image(WIMStruct *src_wim,
                }
        }
 
-       ret = wimlib_select_image(src_wim, src_image);
-       if (ret != 0) {
-               ERROR("Could not select image %d from the WIM `%s' "
-                     "to export it", src_image, src_wim->filename);
-               return ret;
-       }
-
        if (!dest_name) {
                dest_name = wimlib_get_image_name(src_wim, src_image);
                DEBUG("Using name `%s' for source image %d",
@@ -437,12 +431,32 @@ WIMLIBAPI int wimlib_export_image(WIMStruct *src_wim,
                return WIMLIB_ERR_IMAGE_NAME_COLLISION;
        }
 
+       ret = verify_swm_set(src_wim, additional_swms, num_additional_swms);
+       if (ret != 0)
+               return ret;
+
+       if (num_additional_swms) {
+               ret = new_joined_lookup_table(src_wim, additional_swms,
+                                             num_additional_swms,
+                                             &joined_tab);
+               if (ret != 0)
+                       return ret;
+               src_wim_tab_save = src_wim->lookup_table;
+               src_wim->lookup_table = joined_tab;
+       }
+
+       ret = wimlib_select_image(src_wim, src_image);
+       if (ret != 0) {
+               ERROR("Could not select image %d from the WIM `%s' "
+                     "to export it", src_image, src_wim->filename);
+               goto out;
+       }
 
        /* Cleaning up here on failure would be hard.  For example, we could
         * fail to allocate memory in add_lte_to_dest_wim(),
         * leaving the lookup table entries in the destination WIM in an
         * inconsistent state.  Until these issues can be resolved,
-        * wimlib_export_image() is documented as leaving dest_wim is an
+        * wimlib_export_image() is documented as leaving dest_wim in an
         * indeterminate state.  */
        root = wim_root_dentry(src_wim);
        sd = wim_security_data(src_wim);
@@ -451,10 +465,10 @@ WIMLIBAPI int wimlib_export_image(WIMStruct *src_wim,
        wims.dest_wim = dest_wim;
        ret = for_dentry_in_tree(root, add_lte_to_dest_wim, &wims);
        if (ret != 0)
-               return ret;
+               goto out;
        ret = add_new_dentry_tree(dest_wim, root, sd);
        if (ret != 0)
-               return ret;
+               goto out;
        sd->refcnt++;
 
        if (flags & WIMLIB_EXPORT_FLAG_BOOT) {
@@ -462,8 +476,14 @@ WIMLIBAPI int wimlib_export_image(WIMStruct *src_wim,
                dest_wim->hdr.boot_idx = dest_wim->hdr.image_count;
        }
 
-       return xml_export_image(src_wim->wim_info, src_image, &dest_wim->wim_info,
-                               dest_name, dest_description);
+       ret = xml_export_image(src_wim->wim_info, src_image, &dest_wim->wim_info,
+                              dest_name, dest_description);
+out:
+       if (num_additional_swms) {
+               free_lookup_table(src_wim->lookup_table);
+               src_wim->lookup_table = src_wim_tab_save;
+       }
+       return ret;
 }
 
 /* 
index 94a0f9d994595956fc428b4691f615d0d579e002..05681b3a5c7b8420463174d7f1a27d24296679bb 100644 (file)
@@ -514,8 +514,8 @@ extern int wimlib_delete_image(WIMStruct *wim, int image);
  * Copies an image, or all the images, from a WIM file, into another WIM file.
  *
  * @param src_wim
- *     Pointer to the ::WIMStruct for a WIM file that contains the image(s)
- *     being exported.
+ *     Pointer to the ::WIMStruct for a stand-alone WIM or part 1 of a split
+ *     WIM that contains the image(s) being exported.
  * @param src_image
  *     The image to export from @a src_wim.  Can be the number of an image, or
  *     ::WIM_ALL_IMAGES to export all images.
@@ -541,6 +541,16 @@ extern int wimlib_delete_image(WIMStruct *wim, int image);
  *     ::WIMLIB_EXPORT_FLAG_BOOT is valid only if one of the exported images is
  *     currently marked as bootable in @a src_wim; if that is the case, then
  *     that image is marked as bootable in the destination WIM.
+ * @param additional_swms
+ *     Array of pointers to the ::WIMStruct for each additional part in the
+ *     split WIM.  Ignored if @a num_additional_swms is 0.  The pointers do not
+ *     need to be in any particular order, but they must include all parts of
+ *     the split WIM other than the first part, which must be provided in the
+ *     @a wim parameter.
+ * @param num_additional_swms
+ *     Number of additional WIM parts provided in the @a additional_swms array.
+ *     This number should be one less than the total number of parts in the
+ *     split WIM.  Set to 0 if the WIM is a standalone WIM.
  *
  * @return 0 on success; nonzero on error.  On error, @dest_wim is left in an
  * indeterminate state and should be freed with wimlib_free().
@@ -570,13 +580,20 @@ extern int wimlib_delete_image(WIMStruct *wim, int image);
  *     Failed to allocate needed memory.
  * @retval ::WIMLIB_ERR_READ
  *     Could not read the metadata resource for @a src_image from @a src_wim.
+ * @retval ::WIMLIB_ERR_SPLIT_INVALID
+ *     The source WIM is a split WIM, but the parts specified do not form a
+ *     complete split WIM because they do not include all the parts of the
+ *     original WIM, there are duplicate parts, or not all the parts have the
+ *     same GUID and compression type.
  * @retval ::WIMLIB_ERR_SPLIT_UNSUPPORTED
- *     @a src_wim or @a dest_wim is part of a split WIM.  Exporting an image
- *     from or to a split WIM is unsupported.
+ *     @a dest_wim is part of a split WIM.  Exporting an image to a split WIM
+ *     is unsupported.
  */
 extern int wimlib_export_image(WIMStruct *src_wim, int src_image, 
                               WIMStruct *dest_wim, const char *dest_name, 
-                              const char *dest_description, int flags);
+                              const char *dest_description, int flags,
+                              WIMStruct **additional_swms,
+                              unsigned num_additional_swms);
 
 /**
  * Extracts an image, or all images, from a standalone or split WIM file.