f2a79fd0f79cec0d97931fcfc65890df7d801821
[wimlib] / src / ntfs-apply.c
1 /*
2  * ntfs-apply.c
3  *
4  * Apply a WIM image to a NTFS volume, restoring everything we can, including
5  * security data and alternate data streams.  There should be no loss of
6  * information.
7  */
8
9 /*
10  * Copyright (C) 2012 Eric Biggers
11  *
12  * This file is part of wimlib, a library for working with WIM files.
13  *
14  * wimlib is free software; you can redistribute it and/or modify it under the
15  * terms of the GNU Lesser General Public License as published by the Free
16  * Software Foundation; either version 2.1 of the License, or (at your option)
17  * any later version.
18  *
19  * wimlib is distributed in the hope that it will be useful, but WITHOUT ANY
20  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
21  * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
22  * details.
23  *
24  * You should have received a copy of the GNU Lesser General Public License
25  * along with wimlib; if not, see http://www.gnu.org/licenses/.
26  */
27
28 #include "config.h"
29 #include "wimlib_internal.h"
30
31
32 #ifdef WITH_NTFS_3G
33 #include "dentry.h"
34 #include "lookup_table.h"
35 #include "io.h"
36 #include <ntfs-3g/layout.h>
37 #include <ntfs-3g/acls.h>
38 #include <ntfs-3g/attrib.h>
39 #include <ntfs-3g/misc.h>
40 #include <ntfs-3g/reparse.h>
41 #include <ntfs-3g/security.h>
42 #include <ntfs-3g/volume.h>
43 #include <stdlib.h>
44 #include <unistd.h>
45
46 struct ntfs_apply_args {
47         ntfs_volume *vol;
48         int extract_flags;
49         WIMStruct *w;
50 };
51
52 extern int _ntfs_set_file_security(ntfs_volume *vol, ntfs_inode *ni,
53                                    u32 selection, const char *attr);
54 extern int _ntfs_set_file_attributes(ntfs_inode *ni, s32 attrib);
55
56 /* 
57  * Extracts a WIM resource to a NTFS attribute.
58  */
59 static int
60 extract_wim_resource_to_ntfs_attr(const struct lookup_table_entry *lte,
61                                   ntfs_attr *na)
62 {
63         u64 bytes_remaining = wim_resource_size(lte);
64         char buf[min(WIM_CHUNK_SIZE, bytes_remaining)];
65         u64 offset = 0;
66         int ret = 0;
67         u8 hash[SHA1_HASH_SIZE];
68
69         SHA_CTX ctx;
70         sha1_init(&ctx);
71
72         while (bytes_remaining) {
73                 u64 to_read = min(bytes_remaining, WIM_CHUNK_SIZE);
74                 ret = read_wim_resource(lte, buf, to_read, offset, false);
75                 if (ret != 0)
76                         break;
77                 sha1_update(&ctx, buf, to_read);
78                 if (ntfs_attr_pwrite(na, offset, to_read, buf) != to_read) {
79                         ERROR_WITH_ERRNO("Error extracting WIM resource");
80                         return WIMLIB_ERR_WRITE;
81                 }
82                 bytes_remaining -= to_read;
83                 offset += to_read;
84         }
85         sha1_final(hash, &ctx);
86         if (!hashes_equal(hash, lte->hash)) {
87                 ERROR("Invalid checksum on a WIM resource "
88                       "(detected when extracting to NTFS stream file)");
89                 ERROR("The following WIM resource is invalid:");
90                 print_lookup_table_entry(lte);
91                 return WIMLIB_ERR_INVALID_RESOURCE_HASH;
92         }
93         return 0;
94 }
95
96 /* Writes the data streams to a NTFS file
97  *
98  * @ni:      The NTFS inode for the file.
99  * @dentry:  The directory entry in the WIM file.
100  * @w:       The WIMStruct for the WIM containing the image we are applying.
101  *
102  * Returns 0 on success, nonzero on failure.
103  */
104 static int write_ntfs_data_streams(ntfs_inode *ni, const struct dentry *dentry,
105                                    WIMStruct *w)
106 {
107         int ret = 0;
108         unsigned stream_idx = 0;
109         ntfschar *stream_name = AT_UNNAMED;
110         u32 stream_name_len = 0;
111
112         DEBUG("Writing NTFS data streams for `%s'", dentry->full_path_utf8);
113
114         while (1) {
115                 struct lookup_table_entry *lte;
116                 ntfs_attr *na;
117
118                 lte = dentry_stream_lte(dentry, 0, w->lookup_table);
119                 na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len);
120                 if (!na) {
121                         ERROR_WITH_ERRNO("Failed to open a data stream of "
122                                          "extracted file `%s'",
123                                          dentry->full_path_utf8);
124                         ret = WIMLIB_ERR_NTFS_3G;
125                         break;
126                 }
127                 if (lte && wim_resource_size(lte) != 0)
128                         ret = extract_wim_resource_to_ntfs_attr(lte, na);
129                 ntfs_attr_close(na);
130                 if (ret != 0)
131                         break;
132                 if (stream_idx == dentry->num_ads)
133                         break;
134                 stream_name = (ntfschar*)dentry->ads_entries[stream_idx].stream_name;
135                 stream_name_len = dentry->ads_entries[stream_idx].stream_name_len / 2;
136                 stream_idx++;
137         }
138         return ret;
139 }
140
141 /*
142  * Makes a NTFS hard link
143  *
144  * It is named @from_dentry->file_name and is located under the directory
145  * specified by @dir_ni, and it is made to point to the previously extracted
146  * file located at @to_dentry->extracted_file.
147  *
148  * Return 0 on success, nonzero on failure.
149  */
150 static int wim_apply_hardlink_ntfs(const struct dentry *from_dentry,
151                                    const struct dentry *to_dentry,
152                                    ntfs_inode *dir_ni)
153 {
154         int ret;
155         ntfs_inode *to_ni;
156
157         DEBUG("Extracting NTFS hard link `%s' => `%s'",
158               from_dentry->full_path_utf8, to_dentry->extracted_file);
159
160         to_ni = ntfs_pathname_to_inode(dir_ni->vol, NULL,
161                                        to_dentry->extracted_file);
162         if (!to_ni) {
163                 ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
164                                  to_dentry->extracted_file);
165                 return WIMLIB_ERR_NTFS_3G;
166         }
167         ret = ntfs_link(to_ni, dir_ni,
168                         (ntfschar*)from_dentry->file_name,
169                         from_dentry->file_name_len / 2);
170         if (ret != 0) {
171                 ERROR_WITH_ERRNO("Could not create hard link `%s' => `%s'",
172                                  from_dentry->full_path_utf8,
173                                  to_dentry->extracted_file);
174                 ret = WIMLIB_ERR_NTFS_3G;
175         }
176         if (ntfs_inode_close(to_ni) != 0) {
177                 ERROR_WITH_ERRNO("Failed to close NTFS inode for `%s'",
178                                  to_dentry->extracted_file);
179                 ret = WIMLIB_ERR_NTFS_3G;
180         }
181         return ret;
182 }
183
184 static int
185 apply_file_attributes_and_security_data(ntfs_inode *ni,
186                                         const struct dentry *dentry,
187                                         const WIMStruct *w)
188 {
189         DEBUG("Setting NTFS file attributes on `%s' to %#"PRIx32,
190               dentry->full_path_utf8, dentry->attributes);
191         if (!_ntfs_set_file_attributes(ni, dentry->attributes)) {
192                 ERROR("Failed to set NTFS file attributes on `%s'",
193                        dentry->full_path_utf8);
194                 return WIMLIB_ERR_NTFS_3G;
195         }
196
197         if (dentry->security_id != -1) {
198                 const struct wim_security_data *sd;
199                 
200                 sd = wim_const_security_data(w);
201                 wimlib_assert(dentry->security_id < sd->num_entries);
202                 DEBUG("Applying security descriptor %d to `%s'",
203                       dentry->security_id, dentry->full_path_utf8);
204                 u32 selection = OWNER_SECURITY_INFORMATION |
205                                 GROUP_SECURITY_INFORMATION |
206                                 DACL_SECURITY_INFORMATION  |
207                                 SACL_SECURITY_INFORMATION;
208                                 
209                 if (!_ntfs_set_file_security(ni->vol, ni, selection,
210                                              sd->descriptors[dentry->security_id]))
211                 {
212                         ERROR_WITH_ERRNO("Failed to set security data on `%s'",
213                                         dentry->full_path_utf8);
214                         return WIMLIB_ERR_NTFS_3G;
215                 }
216         }
217         return 0;
218 }
219
220 static int apply_reparse_data(ntfs_inode *ni, const struct dentry *dentry,
221                               const WIMStruct *w)
222 {
223         struct lookup_table_entry *lte;
224         int ret = 0;
225
226         wimlib_assert(dentry->attributes & FILE_ATTRIBUTE_REPARSE_POINT);
227
228         lte = dentry_first_lte(dentry, w->lookup_table);
229
230         if (!lte) {
231                 ERROR("Could not find reparse data for `%s'",
232                       dentry->full_path_utf8);
233                 return WIMLIB_ERR_INVALID_DENTRY;
234         }
235
236         if (wim_resource_size(lte) >= 0xffff) {
237                 ERROR("Reparse data of `%s' is too long (%lu bytes)",
238                       dentry->full_path_utf8, wim_resource_size(lte));
239                 return WIMLIB_ERR_INVALID_DENTRY;
240         }
241
242         char reparse_data_buf[8 + wim_resource_size(lte)];
243         char *p = reparse_data_buf;
244         p = put_u32(p, dentry->reparse_tag); /* ReparseTag */
245         p = put_u16(p, wim_resource_size(lte)); /* ReparseDataLength */
246         p = put_u16(p, 0); /* Reserved */
247
248         ret = read_full_wim_resource(lte, p);
249         if (ret != 0)
250                 return ret;
251
252         ret = ntfs_set_ntfs_reparse_data(ni, reparse_data_buf,
253                                          wim_resource_size(lte) + 8, 0);
254         if (ret != 0) {
255                 ERROR_WITH_ERRNO("Failed to set NTFS reparse data on `%s'",
256                                  dentry->full_path_utf8);
257                 return WIMLIB_ERR_NTFS_3G;
258         }
259         return 0;
260 }
261
262 /* 
263  * Applies a WIM dentry to a NTFS filesystem.
264  *
265  * @dentry:  The WIM dentry to apply
266  * @dir_ni:  The NTFS inode for the parent directory
267  * @w:       The WIMStruct for the WIM containing the image we are applying.
268  *
269  * @return:  0 on success; nonzero on failure.
270  */
271 static int do_wim_apply_dentry_ntfs(struct dentry *dentry, ntfs_inode *dir_ni,
272                                     WIMStruct *w)
273 {
274         ntfs_inode *ni;
275         int ret = 0;
276         mode_t type;
277
278         if (dentry->attributes & FILE_ATTRIBUTE_DIRECTORY) {
279                 type = S_IFDIR;
280         } else {
281                 type = S_IFREG;
282                 const struct list_head *head = &dentry->link_group_list;
283                 if (head->next != head) {
284                         /* This dentry is one of a hard link set of at least 2
285                          * dentries.  If one of the other dentries has already
286                          * been extracted, make a hard link to the file
287                          * corresponding to this already-extracted directory.
288                          * Otherwise, extract the file, and set the
289                          * dentry->extracted_file field so that other dentries
290                          * in the hard link group can link to it. */
291                         struct dentry *other;
292                         list_for_each_entry(other, head, link_group_list) {
293                                 if (other->extracted_file) {
294                                         return wim_apply_hardlink_ntfs(
295                                                         dentry, other, dir_ni);
296                                 }
297                         }
298                 }
299                 FREE(dentry->extracted_file);
300                 dentry->extracted_file = STRDUP(dentry->full_path_utf8);
301                 if (!dentry->extracted_file) {
302                         ERROR("Failed to allocate memory for filename");
303                         return WIMLIB_ERR_NOMEM;
304                 }
305         }
306
307         /* 
308          * Create a directory or file.
309          *
310          * Note: if it's a reparse point (such as a symbolic link), we still
311          * pass S_IFREG here, since we manually set the reparse data later.
312          */
313         ni = ntfs_create(dir_ni, 0, (ntfschar*)dentry->file_name,
314                          dentry->file_name_len / 2, type);
315
316         if (!ni) {
317                 ERROR_WITH_ERRNO("Could not create NTFS object for `%s'",
318                                  dentry->full_path_utf8);
319                 ret = WIMLIB_ERR_NTFS_3G;
320                 goto out;
321         }
322
323         /* Write the data streams, unless this is a directory or reparse point
324          * */
325         if (!dentry_is_directory(dentry) &&
326              !(dentry->attributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
327                 ret = write_ntfs_data_streams(ni, dentry, w);
328                 if (ret != 0)
329                         goto out;
330         }
331
332
333         ret = apply_file_attributes_and_security_data(ni, dentry, w);
334         if (ret != 0)
335                 goto out;
336
337         if (dentry->attributes & FILE_ATTR_REPARSE_POINT) {
338                 ret = apply_reparse_data(ni, dentry, w);
339                 if (ret != 0)
340                         goto out;
341         }
342
343         if (ntfs_inode_close_in_dir(ni, dir_ni) != 0) {
344                 ERROR_WITH_ERRNO("Failed to close new inode");
345                 ret = WIMLIB_ERR_NTFS_3G;
346                 goto out;
347         }
348 out:
349         return ret;
350 }
351
352 static int wim_apply_root_dentry_ntfs(const struct dentry *dentry,
353                                       ntfs_volume *vol,
354                                       const WIMStruct *w)
355 {
356         ntfs_inode *ni;
357         int ret = 0;
358
359         wimlib_assert(dentry_is_directory(dentry));
360         ni = ntfs_pathname_to_inode(vol, NULL, "/");
361         if (!ni) {
362                 ERROR_WITH_ERRNO("Could not find root NTFS inode");
363                 return WIMLIB_ERR_NTFS_3G;
364         }
365         ret = apply_file_attributes_and_security_data(ni, dentry, w);
366         if (ntfs_inode_close(ni) != 0) {
367                 ERROR_WITH_ERRNO("Failed to close NTFS inode for root "
368                                  "directory");
369                 ret = WIMLIB_ERR_NTFS_3G;
370         }
371         return ret;
372 }
373
374 /* Applies a WIM dentry to the NTFS volume */
375 static int wim_apply_dentry_ntfs(struct dentry *dentry, void *arg)
376 {
377         struct ntfs_apply_args *args = arg;
378         ntfs_volume *vol             = args->vol;
379         int extract_flags            = args->extract_flags;
380         WIMStruct *w                 = args->w;
381         ntfs_inode *dir_ni;
382         int ret;
383         char *p;
384         char orig;
385         const char *dir_name;
386
387         wimlib_assert(dentry->full_path_utf8);
388
389         DEBUG("Applying dentry `%s' to NTFS", dentry->full_path_utf8);
390
391         if (extract_flags & WIMLIB_EXTRACT_FLAG_VERBOSE)
392                 puts(dentry->full_path_utf8);
393
394         if (dentry_is_root(dentry))
395                 return wim_apply_root_dentry_ntfs(dentry, vol, w);
396
397         p = dentry->full_path_utf8 + dentry->full_path_utf8_len;
398         do {
399                 p--;
400         } while (*p != '/');
401
402         orig = *p;
403         *p = '\0';
404         dir_name = dentry->full_path_utf8;
405
406         dir_ni = ntfs_pathname_to_inode(vol, NULL, dir_name);
407         *p = orig;
408         if (!dir_ni) {
409                 ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
410                                  dir_name);
411                 return WIMLIB_ERR_NTFS_3G;
412         }
413         DEBUG("Found NTFS inode for `%s'", dir_name);
414
415         ret = do_wim_apply_dentry_ntfs(dentry, dir_ni, w);
416
417         if (ntfs_inode_close(dir_ni) != 0) {
418                 if (ret == 0)
419                         ret = WIMLIB_ERR_NTFS_3G;
420                 ERROR_WITH_ERRNO("Failed to close directory inode");
421         }
422         return ret;
423
424 }
425
426 static int do_wim_apply_image_ntfs(WIMStruct *w, const char *device, int extract_flags)
427 {
428         ntfs_volume *vol;
429         int ret;
430         
431         vol = ntfs_mount(device, 0);
432         if (!vol) {
433                 ERROR_WITH_ERRNO("Failed to mount NTFS volume `%s'", device);
434                 return WIMLIB_ERR_NTFS_3G;
435         }
436         struct ntfs_apply_args args = {
437                 .vol           = vol,
438                 .extract_flags = extract_flags,
439                 .w             = w,
440         };
441         ret = for_dentry_in_tree(wim_root_dentry(w), wim_apply_dentry_ntfs,
442                                  &args);
443         if (ntfs_umount(vol, FALSE) != 0) {
444                 ERROR_WITH_ERRNO("Failed to unmount NTFS volume `%s'", device);
445                 if (ret == 0)
446                         ret = WIMLIB_ERR_NTFS_3G;
447         }
448         return ret;
449 }
450
451
452 /* 
453  * API entry point for applying a WIM image to a NTFS volume.
454  *
455  * Please note that this is a NTFS *volume* and not a directory.  The intention
456  * is that the volume contain an empty filesystem, and the WIM image contain a
457  * full filesystem to be applied to the volume.
458  */
459 WIMLIBAPI int wimlib_apply_image_to_ntfs_volume(WIMStruct *w, int image,
460                                                 const char *device, int flags)
461 {
462         int ret;
463
464         if (!device)
465                 return WIMLIB_ERR_INVALID_PARAM;
466         if (image == WIM_ALL_IMAGES) {
467                 ERROR("Can only apply a single image when applying "
468                       "directly to a NTFS volume");
469                 return WIMLIB_ERR_INVALID_PARAM;
470         }
471         if (flags & (WIMLIB_EXTRACT_FLAG_SYMLINK | WIMLIB_EXTRACT_FLAG_HARDLINK)) {
472                 ERROR("Cannot specify symlink or hardlink flags when applying ");
473                 ERROR("directly to a NTFS volume");
474                 return WIMLIB_ERR_INVALID_PARAM;
475         }
476         ret = wimlib_select_image(w, image);
477         if (ret != 0)
478                 return ret;
479
480 #if 0
481         if (getuid() != 0) {
482                 ERROR("We are not root, but NTFS-3g requires root privileges to set arbitrary");
483                 ERROR("security data on the NTFS filesystem.  Please run this program as root");
484                 ERROR("if you want to extract a WIM image while preserving NTFS-specific");
485                 ERROR("information.");
486
487                 return WIMLIB_ERR_NOT_ROOT;
488         }
489 #endif
490         return do_wim_apply_image_ntfs(w, device, flags);
491 }
492
493 #else /* WITH_NTFS_3G */
494 WIMLIBAPI int wimlib_apply_image_to_ntfs_volume(WIMStruct *w, int image,
495                                                 const char *device, int flags)
496 {
497         ERROR("wimlib was compiled without support for NTFS-3g, so");
498         ERROR("we cannot apply a WIM image directly to a NTFS volume");
499         return WIMLIB_ERR_UNSUPPORTED;
500 }
501 #endif /* WITH_NTFS_3G */