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