1fd6d93d3264cef4d1ce8b3e019abb400010c113
[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 static u64
37 stream_size(const struct wim_inode_stream *strm,
38             const struct blob_table *blob_table)
39 {
40         const struct blob_descriptor *blob;
41
42         blob = stream_blob(strm, blob_table);
43         if (!blob)
44                 return 0;
45         return blob->size;
46 }
47
48 /* Returns %true iff the metadata of @inode and @template_inode are reasonably
49  * consistent with them being the same, unmodified file.  */
50 static bool
51 inode_metadata_consistent(const struct wim_inode *inode,
52                           const struct wim_inode *template_inode,
53                           const struct blob_table *blob_table,
54                           const struct blob_table *template_blob_table)
55 {
56         /* Must have exact same creation time and last write time.  */
57         if (inode->i_creation_time != template_inode->i_creation_time ||
58             inode->i_last_write_time != template_inode->i_last_write_time)
59                 return false;
60
61         /* Last access time may have stayed the same or increased, but certainly
62          * shouldn't have decreased.  */
63         if (inode->i_last_access_time < template_inode->i_last_access_time)
64                 return false;
65
66         /* All stream sizes must match.  */
67         for (unsigned i = 0; i < inode->i_num_streams; i++) {
68                 const struct wim_inode_stream *strm, *template_strm;
69
70                 strm = &inode->i_streams[i];
71                 template_strm = inode_get_stream(template_inode,
72                                                  strm->stream_type,
73                                                  strm->stream_name);
74                 if (!template_strm)
75                         return false;
76
77                 if (stream_size(strm, blob_table) !=
78                     stream_size(template_strm, template_blob_table))
79                         return false;
80         }
81
82         return true;
83 }
84
85 /**
86  * Given an inode @inode that has been determined to be "the same" as another
87  * inode @template_inode in either the same WIM or another WIM, copy stream
88  * checksums from @template_inode to @inode.
89  */
90 static void
91 inode_copy_checksums(struct wim_inode *inode,
92                      struct wim_inode *template_inode,
93                      struct blob_table *blob_table,
94                      struct blob_table *template_blob_table)
95 {
96         for (unsigned i = 0; i < inode->i_num_streams; i++) {
97                 const struct wim_inode_stream *strm, *template_strm;
98                 struct blob_descriptor *blob, *template_blob, **back_ptr;
99
100                 strm = &inode->i_streams[i];
101                 template_strm = inode_get_stream(template_inode,
102                                                  strm->stream_type,
103                                                  strm->stream_name);
104
105                 blob = stream_blob(strm, blob_table);
106                 template_blob = stream_blob(template_strm, template_blob_table);
107
108                 /* To copy hashes: both blobs must exist, the blob for @inode
109                  * must be unhashed, and the blob for @template_inode must be
110                  * hashed.  */
111                 if (!blob || !template_blob ||
112                     !blob->unhashed || template_blob->unhashed)
113                         continue;
114
115                 back_ptr = retrieve_pointer_to_unhashed_blob(blob);
116                 copy_hash(blob->hash, template_blob->hash);
117                 if (after_blob_hashed(blob, back_ptr, blob_table) != blob)
118                         free_blob_descriptor(blob);
119         }
120 }
121
122 struct reference_template_args {
123         WIMStruct *wim;
124         WIMStruct *template_wim;
125 };
126
127 static int
128 dentry_reference_template(struct wim_dentry *dentry, void *_args)
129 {
130         int ret;
131         struct wim_dentry *template_dentry;
132         struct wim_inode *inode, *template_inode;
133         struct reference_template_args *args = _args;
134         WIMStruct *wim = args->wim;
135         WIMStruct *template_wim = args->template_wim;
136
137         if (dentry->d_inode->i_visited)
138                 return 0;
139
140         ret = calculate_dentry_full_path(dentry);
141         if (ret)
142                 return ret;
143
144         template_dentry = get_dentry(template_wim, dentry->_full_path,
145                                      WIMLIB_CASE_SENSITIVE);
146         if (template_dentry == NULL) {
147                 DEBUG("\"%"TS"\": newly added file", dentry->_full_path);
148                 return 0;
149         }
150
151         inode = dentry->d_inode;
152         template_inode = template_dentry->d_inode;
153
154         if (inode_metadata_consistent(inode, template_inode, wim->blob_table,
155                                       template_wim->blob_table))
156         {
157                 DEBUG("\"%"TS"\": No change detected", dentry->_full_path);
158                 inode_copy_checksums(inode, template_inode, wim->blob_table,
159                                      template_wim->blob_table);
160                 inode->i_visited = 1;
161         } else {
162                 DEBUG("\"%"TS"\": change detected!", dentry->_full_path);
163         }
164         return 0;
165 }
166
167 /* API function documented in wimlib.h  */
168 WIMLIBAPI int
169 wimlib_reference_template_image(WIMStruct *wim, int new_image,
170                                 WIMStruct *template_wim, int template_image,
171                                 int flags)
172 {
173         int ret;
174         struct wim_image_metadata *new_imd;
175
176         if (flags != 0)
177                 return WIMLIB_ERR_INVALID_PARAM;
178
179         if (wim == NULL || template_wim == NULL)
180                 return WIMLIB_ERR_INVALID_PARAM;
181
182         if (wim == template_wim && new_image == template_image)
183                 return WIMLIB_ERR_INVALID_PARAM;
184
185         if (new_image < 1 || new_image > wim->hdr.image_count)
186                 return WIMLIB_ERR_INVALID_IMAGE;
187
188         if (!wim_has_metadata(wim))
189                 return WIMLIB_ERR_METADATA_NOT_FOUND;
190
191         new_imd = wim->image_metadata[new_image - 1];
192         if (!new_imd->modified)
193                 return WIMLIB_ERR_INVALID_PARAM;
194
195         ret = select_wim_image(template_wim, template_image);
196         if (ret)
197                 return ret;
198
199         struct reference_template_args args = {
200                 .wim = wim,
201                 .template_wim = template_wim,
202         };
203
204         ret = for_dentry_in_tree(new_imd->root_dentry,
205                                  dentry_reference_template, &args);
206         dentry_tree_clear_inode_visited(new_imd->root_dentry);
207         return ret;
208 }