]> wimlib.net Git - wimlib/blobdiff - src/export_image.c
wimlib_export_image(): improve duplicate image detection
[wimlib] / src / export_image.c
index e5b7e42954c727cb8881b8704779678960302bbb..da638322ee06c32d19217986653be498eea4e63d 100644 (file)
  */
 
 /*
- * Copyright (C) 2012 Eric Biggers
+ * Copyright (C) 2012, 2013, 2014, 2015 Eric Biggers
  *
- * This file is part of wimlib, a library for working with WIM files.
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option) any
+ * later version.
  *
- * wimlib is free software; you can redistribute it and/or modify it under the
- * terms of the GNU General Public License as published by the Free
- * Software Foundation; either version 3 of the License, or (at your option)
- * any later version.
- *
- * wimlib is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
  * details.
  *
- * You should have received a copy of the GNU General Public License
- * along with wimlib; if not, see http://www.gnu.org/licenses/.
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this file; if not, see http://www.gnu.org/licenses/.
  */
 
-#include "wimlib_internal.h"
-#include "dentry.h"
-#include "lookup_table.h"
-#include "xml.h"
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include "wimlib.h"
+#include "wimlib/blob_table.h"
+#include "wimlib/error.h"
+#include "wimlib/inode.h"
+#include "wimlib/metadata.h"
+#include "wimlib/xml.h"
 
-static int inode_allocate_needed_ltes(struct wim_inode *inode,
-                                     struct wim_lookup_table *src_lookup_table,
-                                     struct wim_lookup_table *dest_lookup_table,
-                                     struct list_head *lte_list_head)
+static int
+blob_set_not_exported(struct blob_descriptor *blob, void *_ignore)
 {
-       struct wim_lookup_table_entry *src_lte, *dest_lte;
-       unsigned i;
+       blob->out_refcnt = 0;
+       blob->was_exported = 0;
+       return 0;
+}
 
-       inode_unresolve_ltes(inode);
-       for (i = 0; i <= inode->i_num_ads; i++) {
-               src_lte = inode_stream_lte_unresolved(inode, i,
-                                                     src_lookup_table);
-               if (src_lte && src_lte->out_refcnt == 0) {
-                       src_lte->out_refcnt = 1;
-                       dest_lte = inode_stream_lte_unresolved(inode, i,
-                                                              dest_lookup_table);
-                       if (!dest_lte) {
-                               dest_lte = clone_lookup_table_entry(src_lte);
-                               if (!dest_lte)
-                                       return WIMLIB_ERR_NOMEM;
-                               list_add_tail(&dest_lte->staging_list, lte_list_head);
-                       }
-               }
+static int
+blob_rollback_export(struct blob_descriptor *blob, void *_blob_table)
+{
+       struct blob_table *blob_table = _blob_table;
+
+       blob->refcnt -= blob->out_refcnt;
+       if (blob->was_exported) {
+               blob_table_unlink(blob_table, blob);
+               free_blob_descriptor(blob);
        }
        return 0;
 }
 
-static void inode_move_ltes_to_table(struct wim_inode *inode,
-                                    struct wim_lookup_table *src_lookup_table,
-                                    struct wim_lookup_table *dest_lookup_table,
-                                    struct list_head *lte_list_head)
+static int
+inode_export_blobs(struct wim_inode *inode, struct blob_table *src_blob_table,
+                  struct blob_table *dest_blob_table, bool gift)
 {
-       struct wim_lookup_table_entry *src_lte, *dest_lte;
        unsigned i;
-       struct wim_dentry *dentry;
-
-       inode_for_each_dentry(dentry, inode)
-               dentry->refcnt++;
-
-       for (i = 0; i <= inode->i_num_ads; i++) {
-               src_lte = inode_stream_lte_unresolved(inode, i, src_lookup_table);
-               if (src_lte) {
-                       dest_lte = inode_stream_lte_unresolved(inode, i,
-                                                              dest_lookup_table);
-                       if (!dest_lte) {
-                               struct list_head *next;
-
-                               wimlib_assert(!list_empty(lte_list_head));
-                               next = lte_list_head->next;
-                               list_del(next);
-                               dest_lte = container_of(next,
-                                                       struct wim_lookup_table_entry,
-                                                       staging_list);
-                               dest_lte->part_number = 1;
-                               dest_lte->refcnt = 0;
-                               wimlib_assert(hashes_equal(dest_lte->hash, src_lte->hash));
-                               lookup_table_insert(dest_lookup_table, dest_lte);
+       const u8 *hash;
+       struct blob_descriptor *src_blob, *dest_blob;
+
+       for (i = 0; i < inode->i_num_streams; i++) {
+
+               /* Retrieve SHA-1 message digest of blob to export.  */
+               hash = stream_hash(&inode->i_streams[i]);
+               if (is_zero_hash(hash))  /* Empty stream?  */
+                       continue;
+
+               /* Search for the blob (via SHA-1 message digest) in the
+                * destination WIM.  */
+               dest_blob = lookup_blob(dest_blob_table, hash);
+               if (!dest_blob) {
+                       /* Blob not yet present in destination WIM.  Search for
+                        * it in the source WIM, then export it into the
+                        * destination WIM.  */
+                       src_blob = stream_blob(&inode->i_streams[i],
+                                              src_blob_table);
+                       if (!src_blob)
+                               return blob_not_found_error(inode, hash);
+
+                       if (gift) {
+                               dest_blob = src_blob;
+                               blob_table_unlink(src_blob_table, src_blob);
+                       } else {
+                               dest_blob = clone_blob_descriptor(src_blob);
+                               if (!dest_blob)
+                                       return WIMLIB_ERR_NOMEM;
                        }
-                       dest_lte->refcnt += inode->i_nlink;
+                       dest_blob->refcnt = 0;
+                       dest_blob->out_refcnt = 0;
+                       dest_blob->was_exported = 1;
+                       blob_table_insert(dest_blob_table, dest_blob);
                }
+
+               /* Blob is present in destination WIM (either pre-existing,
+                * already exported, or just exported above).  Increment its
+                * reference count appropriately.   Note: we use 'refcnt' for
+                * the raw reference count, but 'out_refcnt' for references
+                * arising just from the export operation; this is used to roll
+                * back a failed export if needed.  */
+               dest_blob->refcnt += inode->i_nlink;
+               dest_blob->out_refcnt += inode->i_nlink;
        }
+       return 0;
 }
 
-/*
- * Copies an image, or all the images, from a WIM file, into another WIM file.
- */
-WIMLIBAPI int wimlib_export_image(WIMStruct *src_wim,
-                                 int src_image,
-                                 WIMStruct *dest_wim,
-                                 const char *dest_name,
-                                 const char *dest_description,
-                                 int export_flags,
-                                 WIMStruct **additional_swms,
-                                 unsigned num_additional_swms,
-                                 wimlib_progress_func_t progress_func)
+/* API function documented in wimlib.h  */
+WIMLIBAPI int
+wimlib_export_image(WIMStruct *src_wim,
+                   int src_image,
+                   WIMStruct *dest_wim,
+                   const tchar *dest_name,
+                   const tchar *dest_description,
+                   int export_flags)
 {
        int ret;
-       struct wim_security_data *sd;
-       struct wim_lookup_table *joined_tab, *src_wim_tab_save;
-       struct wim_image_metadata *src_imd;
-       struct hlist_node *cur_node;
-       struct list_head lte_list_head;
-       struct wim_inode *inode;
-
-       if (dest_wim->hdr.total_parts != 1) {
-               ERROR("Exporting an image to a split WIM is "
-                     "unsupported");
-               return WIMLIB_ERR_SPLIT_UNSUPPORTED;
-       }
+       int start_src_image;
+       int end_src_image;
+       int orig_dest_image_count;
+       int image;
+       bool all_images = (src_image == WIMLIB_ALL_IMAGES);
 
-       if (src_image == WIMLIB_ALL_IMAGES) {
-               if (src_wim->hdr.image_count > 1) {
-
-                       /* multi-image export. */
-
-                       if ((export_flags & WIMLIB_EXPORT_FLAG_BOOT) &&
-                             (src_wim->hdr.boot_idx == 0))
-                       {
-                               /* Specifying the boot flag on a multi-image
-                                * source WIM makes the boot index default to
-                                * the bootable image in the source WIM.  It is
-                                * an error if there is no such bootable image.
-                                * */
-                               ERROR("Cannot specify `boot' flag when "
-                                     "exporting multiple images from a WIM "
-                                     "with no bootable images");
-                               return WIMLIB_ERR_INVALID_PARAM;
-                       }
-                       if (dest_name || dest_description) {
-                               ERROR("Image name or image description was "
-                                     "specified, but we are exporting "
-                                     "multiple images");
-                               return WIMLIB_ERR_INVALID_PARAM;
-                       }
-                       for (int i = 1; i <= src_wim->hdr.image_count; i++) {
-                               int new_flags = export_flags;
-
-                               if (i != src_wim->hdr.boot_idx)
-                                       new_flags &= ~WIMLIB_EXPORT_FLAG_BOOT;
-
-                               ret = wimlib_export_image(src_wim, i, dest_wim,
-                                                         NULL, NULL,
-                                                         new_flags,
-                                                         additional_swms,
-                                                         num_additional_swms,
-                                                         progress_func);
-                               if (ret != 0)
-                                       return ret;
-                       }
-                       return 0;
-               } else if (src_wim->hdr.image_count == 1) {
-                       src_image = 1;
-               } else {
-                       return 0;
+       /* Check for sane parameters.  */
+       if (export_flags & ~(WIMLIB_EXPORT_FLAG_BOOT |
+                            WIMLIB_EXPORT_FLAG_NO_NAMES |
+                            WIMLIB_EXPORT_FLAG_NO_DESCRIPTIONS |
+                            WIMLIB_EXPORT_FLAG_GIFT |
+                            WIMLIB_EXPORT_FLAG_WIMBOOT))
+               return WIMLIB_ERR_INVALID_PARAM;
+
+       if (!src_wim || !dest_wim)
+               return WIMLIB_ERR_INVALID_PARAM;
+
+       if (!wim_has_metadata(src_wim) || !wim_has_metadata(dest_wim))
+               return WIMLIB_ERR_METADATA_NOT_FOUND;
+
+       if (all_images) {
+               /* Multi-image export.  */
+               if ((!(export_flags & WIMLIB_EXPORT_FLAG_NO_NAMES) &&
+                       dest_name) ||
+                   (!(export_flags & WIMLIB_EXPORT_FLAG_NO_DESCRIPTIONS) &&
+                       dest_description))
+               {
+                       ERROR("Image name and description must be "
+                             "left NULL for multi-image export");
+                       return WIMLIB_ERR_INVALID_PARAM;
                }
+               start_src_image = 1;
+               end_src_image = src_wim->hdr.image_count;
+       } else {
+               start_src_image = src_image;
+               end_src_image = src_image;
        }
+       orig_dest_image_count = dest_wim->hdr.image_count;
 
-       if (!dest_name) {
-               dest_name = wimlib_get_image_name(src_wim, src_image);
-               DEBUG("Using name `%s' for source image %d",
-                     dest_name, src_image);
+       /* We don't yet support having a single WIMStruct contain duplicate
+        * 'image_metadata' structures, so we must forbid this from happening.
+        * A duplication is possible if 'src_wim == dest_wim', if the same image
+        * is exported to the same destination WIMStruct multiple times, or if
+        * an image is exported in an A => B => A manner.  */
+       for (src_image = start_src_image;
+            src_image <= end_src_image; src_image++)
+       {
+               const struct wim_image_metadata *src_imd =
+                               src_wim->image_metadata[src_image - 1];
+               for (int i = 0; i < dest_wim->hdr.image_count; i++)
+                       if (dest_wim->image_metadata[i] == src_imd)
+                               return WIMLIB_ERR_DUPLICATE_EXPORTED_IMAGE;
        }
 
-       if (!dest_description) {
-               dest_description = wimlib_get_image_description(src_wim,
-                                                               src_image);
-               DEBUG("Using description `%s' for source image %d",
-                     dest_description, src_image);
-       }
+       /* Blob checksums must be known before proceeding.  */
+       ret = wim_checksum_unhashed_blobs(src_wim);
+       if (ret)
+               return ret;
+       ret = wim_checksum_unhashed_blobs(dest_wim);
+       if (ret)
+               return ret;
 
-       DEBUG("Exporting image %d from `%s'", src_image, src_wim->filename);
+       /* Enable rollbacks  */
+       for_blob_in_table(dest_wim->blob_table, blob_set_not_exported, NULL);
 
-       if (wimlib_image_name_in_use(dest_wim, dest_name)) {
-               ERROR("There is already an image named `%s' in the "
-                     "destination WIM", dest_name);
-               return WIMLIB_ERR_IMAGE_NAME_COLLISION;
-       }
+       /* Export each requested image.  */
+       for (src_image = start_src_image;
+            src_image <= end_src_image;
+            src_image++)
+       {
+               const tchar *next_dest_name, *next_dest_description;
+               struct wim_image_metadata *src_imd;
+               struct wim_inode *inode;
 
-       ret = verify_swm_set(src_wim, additional_swms, num_additional_swms);
-       if (ret != 0)
-               return ret;
+               /* Determine destination image name and description.  */
 
-       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;
-       }
+               if (export_flags & WIMLIB_EXPORT_FLAG_NO_NAMES)
+                       next_dest_name = NULL;
+               else if (dest_name)
+                       next_dest_name = dest_name;
+               else
+                       next_dest_name = wimlib_get_image_name(src_wim, src_image);
 
-       ret = select_wim_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;
-       }
+               if (export_flags & WIMLIB_EXPORT_FLAG_NO_DESCRIPTIONS)
+                       next_dest_description = NULL;
+               else if (dest_description)
+                       next_dest_description = dest_description;
+               else
+                       next_dest_description = wimlib_get_image_description(src_wim, src_image);
 
-       /* Pre-allocate the new lookup table entries that will be needed.  This
-        * way, it's not possible to run out of memory part-way through
-        * modifying the lookup table of the destination WIM. */
-       INIT_LIST_HEAD(&lte_list_head);
-       for_lookup_table_entry(src_wim->lookup_table, lte_zero_out_refcnt, NULL);
-       src_imd = wim_get_current_image_metadata(src_wim);
-
-       hlist_for_each_entry(inode, cur_node, &src_imd->inode_list, i_hlist) {
-               ret = inode_allocate_needed_ltes(inode,
-                                                src_wim->lookup_table,
-                                                dest_wim->lookup_table,
-                                                &lte_list_head);
-               if (ret != 0)
-                       goto out_free_ltes;
-       }
+               /* Check for name conflict.  */
+               if (wimlib_image_name_in_use(dest_wim, next_dest_name)) {
+                       ERROR("There is already an image named \"%"TS"\" "
+                             "in the destination WIM", next_dest_name);
+                       ret = WIMLIB_ERR_IMAGE_NAME_COLLISION;
+                       goto out_rollback;
+               }
 
-       ret = xml_export_image(src_wim->wim_info, src_image,
-                              &dest_wim->wim_info, dest_name,
-                              dest_description);
-       if (ret != 0)
-               goto out_free_ltes;
-
-       sd = src_imd->security_data;
-       ret = add_new_dentry_tree(dest_wim, src_imd->root_dentry, sd);
-       if (ret != 0)
-               goto out_xml_delete_image;
-
-       dest_wim->image_metadata[
-               dest_wim->hdr.image_count - 1].inode_list = src_imd->inode_list;
-       if (src_imd->inode_list.first)
-               src_imd->inode_list.first->pprev = NULL;
-
-       /* All memory allocations have been taken care of, so it's no longer
-        * possible for this function to fail.  Go ahead and increment the
-        * reference counts of the dentry tree and security data, then update
-        * the lookup table of the destination WIM and the boot index, if
-        * needed. */
-       sd->refcnt++;
-       hlist_for_each_entry(inode, cur_node, &src_imd->inode_list, i_hlist) {
-               inode_move_ltes_to_table(inode,
-                                        src_wim->lookup_table,
-                                        dest_wim->lookup_table,
-                                        &lte_list_head);
+               /* Load metadata for source image into memory.  */
+               ret = select_wim_image(src_wim, src_image);
+               if (ret)
+                       goto out_rollback;
+
+               src_imd = wim_get_current_image_metadata(src_wim);
+
+               /* Iterate through inodes in the source image and export their
+                * blobs into the destination WIM.  */
+               image_for_each_inode(inode, src_imd) {
+                       ret = inode_export_blobs(inode,
+                                                src_wim->blob_table,
+                                                dest_wim->blob_table,
+                                                export_flags & WIMLIB_EXPORT_FLAG_GIFT);
+                       if (ret)
+                               goto out_rollback;
+               }
+
+               /* Export XML information into the destination WIM.  */
+               ret = xml_export_image(src_wim->xml_info, src_image,
+                                      dest_wim->xml_info, next_dest_name,
+                                      next_dest_description,
+                                      export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT);
+               if (ret)
+                       goto out_rollback;
+
+               /* Reference the source image metadata from the destination WIM.
+                */
+               ret = append_image_metadata(dest_wim, src_imd);
+               if (ret)
+                       goto out_rollback;
+               src_imd->refcnt++;
        }
 
-       if (export_flags & WIMLIB_EXPORT_FLAG_BOOT)
-               wimlib_set_boot_idx(dest_wim, dest_wim->hdr.image_count);
-       ret = 0;
-       goto out;
+       /* Image export complete.  Finish by setting any needed special metadata
+        * on the destination WIM.  */
+
+       if (src_wim->hdr.flags & WIM_HDR_FLAG_RP_FIX)
+               dest_wim->hdr.flags |= WIM_HDR_FLAG_RP_FIX;
 
-out_xml_delete_image:
-       xml_delete_image(&dest_wim->wim_info, dest_wim->hdr.image_count);
-out_free_ltes:
+       for (src_image = start_src_image;
+            src_image <= end_src_image;
+            src_image++)
        {
-               struct wim_lookup_table_entry *lte, *tmp;
-               list_for_each_entry_safe(lte, tmp, &lte_list_head, staging_list)
-                       free_lookup_table_entry(lte);
+               int dst_image = orig_dest_image_count + 1 +
+                               (src_image - start_src_image);
+
+               if ((export_flags & WIMLIB_EXPORT_FLAG_BOOT) &&
+                   (!all_images || src_image == src_wim->hdr.boot_idx))
+                       dest_wim->hdr.boot_idx = dst_image;
        }
 
-out:
-       if (num_additional_swms) {
-               free_lookup_table(src_wim->lookup_table);
-               src_wim->lookup_table = src_wim_tab_save;
+       return 0;
+
+out_rollback:
+       while ((image = xml_get_image_count(dest_wim->xml_info))
+              > orig_dest_image_count)
+       {
+               xml_delete_image(dest_wim->xml_info, image);
+       }
+       while (dest_wim->hdr.image_count > orig_dest_image_count)
+       {
+               put_image_metadata(dest_wim->image_metadata[
+                                       --dest_wim->hdr.image_count], NULL);
        }
+       for_blob_in_table(dest_wim->blob_table, blob_rollback_export,
+                         dest_wim->blob_table);
        return ret;
 }