6b21015a0aedf6e7b5000cc962213870f74d74ee
[wimlib] / src / template.c
1 /*
2  * template.c
3  *
4  * API to reference a template image to optimize later writing of a WIM file.
5  */
6
7 /*
8  * Copyright (C) 2013 Eric Biggers
9  *
10  * This file is part of wimlib, a library for working with WIM files.
11  *
12  * wimlib is free software; you can redistribute it and/or modify it under the
13  * terms of the GNU General Public License as published by the Free
14  * Software Foundation; either version 3 of the License, or (at your option)
15  * any later version.
16  *
17  * wimlib is distributed in the hope that it will be useful, but WITHOUT ANY
18  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
19  * A PARTICULAR PURPOSE. See the GNU General Public License for more
20  * details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with wimlib; if not, see http://www.gnu.org/licenses/.
24  */
25
26 #ifdef HAVE_CONFIG_H
27 #  include "config.h"
28 #endif
29
30 #include "wimlib.h"
31 #include "wimlib/dentry.h"
32 #include "wimlib/error.h"
33 #include "wimlib/lookup_table.h"
34 #include "wimlib/metadata.h"
35 #include "wimlib/util.h"
36
37 /* Returns %true iff the metadata of @inode and @template_inode are reasonably
38  * consistent with them being the same, unmodified file.  */
39 static bool
40 inode_metadata_consistent(const struct wim_inode *inode,
41                           const struct wim_inode *template_inode,
42                           const struct wim_lookup_table *template_lookup_table)
43 {
44         /* Must have exact same creation time and last write time.  */
45         if (inode->i_creation_time != template_inode->i_creation_time ||
46             inode->i_last_write_time != template_inode->i_last_write_time)
47                 return false;
48
49         /* Last access time may have stayed the same or increased, but certainly
50          * shouldn't have decreased.  */
51         if (inode->i_last_access_time < template_inode->i_last_access_time)
52                 return false;
53
54         /* Must have same number of alternate data stream entries.  */
55         if (inode->i_num_ads != template_inode->i_num_ads)
56                 return false;
57
58         /* If the stream entries for the inode are for some reason not resolved,
59          * then the hashes are already available and the point of this function
60          * is defeated.  */
61         if (!inode->i_resolved)
62                 return false;
63
64         /* Iterate through each stream and do some more checks.  */
65         for (unsigned i = 0; i <= inode->i_num_ads; i++) {
66                 const struct wim_lookup_table_entry *lte, *template_lte;
67
68                 lte = inode_stream_lte_resolved(inode, i);
69                 template_lte = inode_stream_lte(template_inode, i,
70                                                 template_lookup_table);
71
72                 /* Compare stream sizes.  */
73                 if (lte && template_lte) {
74                         if (lte->size != template_lte->size)
75                                 return false;
76
77                         /* If hash happens to be available, compare with template.  */
78                         if (!lte->unhashed && !template_lte->unhashed &&
79                             !hashes_equal(lte->hash, template_lte->hash))
80                                 return false;
81
82                 } else if (lte && lte->size) {
83                         return false;
84                 } else if (template_lte && template_lte->size) {
85                         return false;
86                 }
87         }
88
89         /* All right, barring a full checksum and given that the inodes share a
90          * path and the user isn't trying to trick us, these inodes most likely
91          * refer to the same file.  */
92         return true;
93 }
94
95 /**
96  * Given an inode @inode that has been determined to be "the same" as another
97  * inode @template_inode in either the same WIM or another WIM, retrieve some
98  * useful stream information (e.g. checksums) from @template_inode.
99  *
100  * This assumes that the streams for @inode have been resolved (to point
101  * directly to the appropriate `struct wim_lookup_table_entry's)  but do not
102  * necessarily have checksum information filled in.
103  */
104 static int
105 inode_copy_checksums(struct wim_inode *inode,
106                      struct wim_inode *template_inode,
107                      WIMStruct *wim,
108                      WIMStruct *template_wim)
109 {
110         for (unsigned i = 0; i <= inode->i_num_ads; i++) {
111                 struct wim_lookup_table_entry *lte, *template_lte;
112                 struct wim_lookup_table_entry *replace_lte;
113
114                 lte = inode_stream_lte_resolved(inode, i);
115                 template_lte = inode_stream_lte(template_inode, i,
116                                                 template_wim->lookup_table);
117
118                 /* Only take action if both entries exist, the entry for @inode
119                  * has no checksum calculated, but the entry for @template_inode
120                  * does.  */
121                 if (lte == NULL || template_lte == NULL ||
122                     !lte->unhashed || template_lte->unhashed)
123                         continue;
124
125                 wimlib_assert(lte->refcnt == inode->i_nlink);
126
127                 /* If the WIM of the template image is the same as the WIM of
128                  * the new image, then @template_lte can be used directly.
129                  *
130                  * Otherwise, look for a stream with the same hash in the WIM of
131                  * the new image.  If found, use it; otherwise re-use the entry
132                  * being discarded, filling in the hash.  */
133
134                 if (wim == template_wim)
135                         replace_lte = template_lte;
136                 else
137                         replace_lte = lookup_stream(wim->lookup_table,
138                                                     template_lte->hash);
139
140                 list_del(&lte->unhashed_list);
141                 if (replace_lte) {
142                         free_lookup_table_entry(lte);
143                 } else {
144                         copy_hash(lte->hash, template_lte->hash);
145                         lte->unhashed = 0;
146                         lookup_table_insert(wim->lookup_table, lte);
147                         lte->refcnt = 0;
148                         replace_lte = lte;
149                 }
150
151                 if (i == 0)
152                         inode->i_lte = replace_lte;
153                 else
154                         inode->i_ads_entries[i - 1].lte = replace_lte;
155
156                 replace_lte->refcnt += inode->i_nlink;
157         }
158         return 0;
159 }
160
161 struct reference_template_args {
162         WIMStruct *wim;
163         WIMStruct *template_wim;
164 };
165
166 static int
167 dentry_reference_template(struct wim_dentry *dentry, void *_args)
168 {
169         int ret;
170         struct wim_dentry *template_dentry;
171         struct wim_inode *inode, *template_inode;
172         struct reference_template_args *args = _args;
173         WIMStruct *wim = args->wim;
174         WIMStruct *template_wim = args->template_wim;
175
176         if (dentry->d_inode->i_visited)
177                 return 0;
178
179         ret = calculate_dentry_full_path(dentry);
180         if (ret)
181                 return ret;
182
183         template_dentry = get_dentry(template_wim, dentry->_full_path,
184                                      WIMLIB_CASE_SENSITIVE);
185         if (template_dentry == NULL) {
186                 DEBUG("\"%"TS"\": newly added file", dentry->_full_path);
187                 return 0;
188         }
189
190         inode = dentry->d_inode;
191         template_inode = template_dentry->d_inode;
192
193         if (inode_metadata_consistent(inode, template_inode,
194                                       template_wim->lookup_table)) {
195                 /*DEBUG("\"%"TS"\": No change detected", dentry->_full_path);*/
196                 ret = inode_copy_checksums(inode, template_inode,
197                                            wim, template_wim);
198                 inode->i_visited = 1;
199         } else {
200                 DEBUG("\"%"TS"\": change detected!", dentry->_full_path);
201                 ret = 0;
202         }
203         return ret;
204 }
205
206 /* API function documented in wimlib.h  */
207 WIMLIBAPI int
208 wimlib_reference_template_image(WIMStruct *wim, int new_image,
209                                 WIMStruct *template_wim, int template_image,
210                                 int flags, wimlib_progress_func_t progress_func)
211 {
212         int ret;
213         struct wim_image_metadata *new_imd;
214
215         if (flags != 0)
216                 return WIMLIB_ERR_INVALID_PARAM;
217
218         if (wim == NULL || template_wim == NULL)
219                 return WIMLIB_ERR_INVALID_PARAM;
220
221         if (wim == template_wim && new_image == template_image)
222                 return WIMLIB_ERR_INVALID_PARAM;
223
224         if (new_image < 1 || new_image > wim->hdr.image_count)
225                 return WIMLIB_ERR_INVALID_IMAGE;
226
227         if (!wim_has_metadata(wim))
228                 return WIMLIB_ERR_METADATA_NOT_FOUND;
229
230         new_imd = wim->image_metadata[new_image - 1];
231         if (!new_imd->modified)
232                 return WIMLIB_ERR_INVALID_PARAM;
233
234         ret = select_wim_image(template_wim, template_image);
235         if (ret)
236                 return ret;
237
238         struct reference_template_args args = {
239                 .wim = wim,
240                 .template_wim = template_wim,
241         };
242
243         ret = for_dentry_in_tree(new_imd->root_dentry,
244                                  dentry_reference_template, &args);
245         dentry_tree_clear_inode_visited(new_imd->root_dentry);
246         return ret;
247 }