wimlib_export_image(): improve duplicate image detection
[wimlib] / src / export_image.c
1 /*
2  * export_image.c
3  */
4
5 /*
6  * Copyright (C) 2012, 2013, 2014, 2015 Eric Biggers
7  *
8  * This file is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU Lesser General Public License as published by the Free
10  * Software Foundation; either version 3 of the License, or (at your option) any
11  * later version.
12  *
13  * This file is distributed in the hope that it will be useful, but WITHOUT
14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this file; if not, see http://www.gnu.org/licenses/.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #  include "config.h"
24 #endif
25
26 #include "wimlib.h"
27 #include "wimlib/blob_table.h"
28 #include "wimlib/error.h"
29 #include "wimlib/inode.h"
30 #include "wimlib/metadata.h"
31 #include "wimlib/xml.h"
32
33 static int
34 blob_set_not_exported(struct blob_descriptor *blob, void *_ignore)
35 {
36         blob->out_refcnt = 0;
37         blob->was_exported = 0;
38         return 0;
39 }
40
41 static int
42 blob_rollback_export(struct blob_descriptor *blob, void *_blob_table)
43 {
44         struct blob_table *blob_table = _blob_table;
45
46         blob->refcnt -= blob->out_refcnt;
47         if (blob->was_exported) {
48                 blob_table_unlink(blob_table, blob);
49                 free_blob_descriptor(blob);
50         }
51         return 0;
52 }
53
54 static int
55 inode_export_blobs(struct wim_inode *inode, struct blob_table *src_blob_table,
56                    struct blob_table *dest_blob_table, bool gift)
57 {
58         unsigned i;
59         const u8 *hash;
60         struct blob_descriptor *src_blob, *dest_blob;
61
62         for (i = 0; i < inode->i_num_streams; i++) {
63
64                 /* Retrieve SHA-1 message digest of blob to export.  */
65                 hash = stream_hash(&inode->i_streams[i]);
66                 if (is_zero_hash(hash))  /* Empty stream?  */
67                         continue;
68
69                 /* Search for the blob (via SHA-1 message digest) in the
70                  * destination WIM.  */
71                 dest_blob = lookup_blob(dest_blob_table, hash);
72                 if (!dest_blob) {
73                         /* Blob not yet present in destination WIM.  Search for
74                          * it in the source WIM, then export it into the
75                          * destination WIM.  */
76                         src_blob = stream_blob(&inode->i_streams[i],
77                                                src_blob_table);
78                         if (!src_blob)
79                                 return blob_not_found_error(inode, hash);
80
81                         if (gift) {
82                                 dest_blob = src_blob;
83                                 blob_table_unlink(src_blob_table, src_blob);
84                         } else {
85                                 dest_blob = clone_blob_descriptor(src_blob);
86                                 if (!dest_blob)
87                                         return WIMLIB_ERR_NOMEM;
88                         }
89                         dest_blob->refcnt = 0;
90                         dest_blob->out_refcnt = 0;
91                         dest_blob->was_exported = 1;
92                         blob_table_insert(dest_blob_table, dest_blob);
93                 }
94
95                 /* Blob is present in destination WIM (either pre-existing,
96                  * already exported, or just exported above).  Increment its
97                  * reference count appropriately.   Note: we use 'refcnt' for
98                  * the raw reference count, but 'out_refcnt' for references
99                  * arising just from the export operation; this is used to roll
100                  * back a failed export if needed.  */
101                 dest_blob->refcnt += inode->i_nlink;
102                 dest_blob->out_refcnt += inode->i_nlink;
103         }
104         return 0;
105 }
106
107 /* API function documented in wimlib.h  */
108 WIMLIBAPI int
109 wimlib_export_image(WIMStruct *src_wim,
110                     int src_image,
111                     WIMStruct *dest_wim,
112                     const tchar *dest_name,
113                     const tchar *dest_description,
114                     int export_flags)
115 {
116         int ret;
117         int start_src_image;
118         int end_src_image;
119         int orig_dest_image_count;
120         int image;
121         bool all_images = (src_image == WIMLIB_ALL_IMAGES);
122
123         /* Check for sane parameters.  */
124         if (export_flags & ~(WIMLIB_EXPORT_FLAG_BOOT |
125                              WIMLIB_EXPORT_FLAG_NO_NAMES |
126                              WIMLIB_EXPORT_FLAG_NO_DESCRIPTIONS |
127                              WIMLIB_EXPORT_FLAG_GIFT |
128                              WIMLIB_EXPORT_FLAG_WIMBOOT))
129                 return WIMLIB_ERR_INVALID_PARAM;
130
131         if (!src_wim || !dest_wim)
132                 return WIMLIB_ERR_INVALID_PARAM;
133
134         if (!wim_has_metadata(src_wim) || !wim_has_metadata(dest_wim))
135                 return WIMLIB_ERR_METADATA_NOT_FOUND;
136
137         if (all_images) {
138                 /* Multi-image export.  */
139                 if ((!(export_flags & WIMLIB_EXPORT_FLAG_NO_NAMES) &&
140                         dest_name) ||
141                     (!(export_flags & WIMLIB_EXPORT_FLAG_NO_DESCRIPTIONS) &&
142                         dest_description))
143                 {
144                         ERROR("Image name and description must be "
145                               "left NULL for multi-image export");
146                         return WIMLIB_ERR_INVALID_PARAM;
147                 }
148                 start_src_image = 1;
149                 end_src_image = src_wim->hdr.image_count;
150         } else {
151                 start_src_image = src_image;
152                 end_src_image = src_image;
153         }
154         orig_dest_image_count = dest_wim->hdr.image_count;
155
156         /* We don't yet support having a single WIMStruct contain duplicate
157          * 'image_metadata' structures, so we must forbid this from happening.
158          * A duplication is possible if 'src_wim == dest_wim', if the same image
159          * is exported to the same destination WIMStruct multiple times, or if
160          * an image is exported in an A => B => A manner.  */
161         for (src_image = start_src_image;
162              src_image <= end_src_image; src_image++)
163         {
164                 const struct wim_image_metadata *src_imd =
165                                 src_wim->image_metadata[src_image - 1];
166                 for (int i = 0; i < dest_wim->hdr.image_count; i++)
167                         if (dest_wim->image_metadata[i] == src_imd)
168                                 return WIMLIB_ERR_DUPLICATE_EXPORTED_IMAGE;
169         }
170
171         /* Blob checksums must be known before proceeding.  */
172         ret = wim_checksum_unhashed_blobs(src_wim);
173         if (ret)
174                 return ret;
175         ret = wim_checksum_unhashed_blobs(dest_wim);
176         if (ret)
177                 return ret;
178
179         /* Enable rollbacks  */
180         for_blob_in_table(dest_wim->blob_table, blob_set_not_exported, NULL);
181
182         /* Export each requested image.  */
183         for (src_image = start_src_image;
184              src_image <= end_src_image;
185              src_image++)
186         {
187                 const tchar *next_dest_name, *next_dest_description;
188                 struct wim_image_metadata *src_imd;
189                 struct wim_inode *inode;
190
191                 /* Determine destination image name and description.  */
192
193                 if (export_flags & WIMLIB_EXPORT_FLAG_NO_NAMES)
194                         next_dest_name = NULL;
195                 else if (dest_name)
196                         next_dest_name = dest_name;
197                 else
198                         next_dest_name = wimlib_get_image_name(src_wim, src_image);
199
200                 if (export_flags & WIMLIB_EXPORT_FLAG_NO_DESCRIPTIONS)
201                         next_dest_description = NULL;
202                 else if (dest_description)
203                         next_dest_description = dest_description;
204                 else
205                         next_dest_description = wimlib_get_image_description(src_wim, src_image);
206
207                 /* Check for name conflict.  */
208                 if (wimlib_image_name_in_use(dest_wim, next_dest_name)) {
209                         ERROR("There is already an image named \"%"TS"\" "
210                               "in the destination WIM", next_dest_name);
211                         ret = WIMLIB_ERR_IMAGE_NAME_COLLISION;
212                         goto out_rollback;
213                 }
214
215                 /* Load metadata for source image into memory.  */
216                 ret = select_wim_image(src_wim, src_image);
217                 if (ret)
218                         goto out_rollback;
219
220                 src_imd = wim_get_current_image_metadata(src_wim);
221
222                 /* Iterate through inodes in the source image and export their
223                  * blobs into the destination WIM.  */
224                 image_for_each_inode(inode, src_imd) {
225                         ret = inode_export_blobs(inode,
226                                                  src_wim->blob_table,
227                                                  dest_wim->blob_table,
228                                                  export_flags & WIMLIB_EXPORT_FLAG_GIFT);
229                         if (ret)
230                                 goto out_rollback;
231                 }
232
233                 /* Export XML information into the destination WIM.  */
234                 ret = xml_export_image(src_wim->xml_info, src_image,
235                                        dest_wim->xml_info, next_dest_name,
236                                        next_dest_description,
237                                        export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT);
238                 if (ret)
239                         goto out_rollback;
240
241                 /* Reference the source image metadata from the destination WIM.
242                  */
243                 ret = append_image_metadata(dest_wim, src_imd);
244                 if (ret)
245                         goto out_rollback;
246                 src_imd->refcnt++;
247         }
248
249         /* Image export complete.  Finish by setting any needed special metadata
250          * on the destination WIM.  */
251
252         if (src_wim->hdr.flags & WIM_HDR_FLAG_RP_FIX)
253                 dest_wim->hdr.flags |= WIM_HDR_FLAG_RP_FIX;
254
255         for (src_image = start_src_image;
256              src_image <= end_src_image;
257              src_image++)
258         {
259                 int dst_image = orig_dest_image_count + 1 +
260                                 (src_image - start_src_image);
261
262                 if ((export_flags & WIMLIB_EXPORT_FLAG_BOOT) &&
263                     (!all_images || src_image == src_wim->hdr.boot_idx))
264                         dest_wim->hdr.boot_idx = dst_image;
265         }
266
267         return 0;
268
269 out_rollback:
270         while ((image = xml_get_image_count(dest_wim->xml_info))
271                > orig_dest_image_count)
272         {
273                 xml_delete_image(dest_wim->xml_info, image);
274         }
275         while (dest_wim->hdr.image_count > orig_dest_image_count)
276         {
277                 put_image_metadata(dest_wim->image_metadata[
278                                         --dest_wim->hdr.image_count], NULL);
279         }
280         for_blob_in_table(dest_wim->blob_table, blob_rollback_export,
281                           dest_wim->blob_table);
282         return ret;
283 }