Extract NTFS junction points correctly
[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                 if (!_ntfs_set_file_security(ni->vol, ni, ~0,
205                                              sd->descriptors[dentry->security_id]))
206                 {
207                         ERROR_WITH_ERRNO("Failed to set security data on `%s'",
208                                         dentry->full_path_utf8);
209                         return WIMLIB_ERR_NTFS_3G;
210                 }
211         }
212         return 0;
213 }
214
215 static int apply_reparse_data(ntfs_inode *ni, const struct dentry *dentry,
216                               const WIMStruct *w)
217 {
218         struct lookup_table_entry *lte;
219         int ret = 0;
220
221         wimlib_assert(dentry->attributes & FILE_ATTRIBUTE_REPARSE_POINT);
222
223         lte = dentry_first_lte(dentry, w->lookup_table);
224
225         if (!lte) {
226                 ERROR("Could not find reparse data for `%s'",
227                       dentry->full_path_utf8);
228                 return WIMLIB_ERR_INVALID_DENTRY;
229         }
230
231         if (wim_resource_size(lte) >= 0xffff) {
232                 ERROR("Reparse data of `%s' is too long (%lu bytes)",
233                       dentry->full_path_utf8, wim_resource_size(lte));
234                 return WIMLIB_ERR_INVALID_DENTRY;
235         }
236
237         char reparse_data_buf[8 + wim_resource_size(lte)];
238         char *p = reparse_data_buf;
239         p = put_u32(p, dentry->reparse_tag); /* ReparseTag */
240         p = put_u16(p, wim_resource_size(lte)); /* ReparseDataLength */
241         p = put_u16(p, 0); /* Reserved */
242
243         ret = read_full_wim_resource(lte, p);
244         if (ret != 0)
245                 return ret;
246
247         ret = ntfs_set_ntfs_reparse_data(ni, reparse_data_buf,
248                                          wim_resource_size(lte) + 8, 0);
249         if (ret != 0) {
250                 ERROR_WITH_ERRNO("Failed to set NTFS reparse data on `%s'",
251                                  dentry->full_path_utf8);
252                 return WIMLIB_ERR_NTFS_3G;
253         }
254         return 0;
255 }
256
257 /* 
258  * Applies a WIM dentry to a NTFS filesystem.
259  *
260  * @dentry:  The WIM dentry to apply
261  * @dir_ni:  The NTFS inode for the parent directory
262  * @w:       The WIMStruct for the WIM containing the image we are applying.
263  *
264  * @return:  0 on success; nonzero on failure.
265  */
266 static int do_wim_apply_dentry_ntfs(struct dentry *dentry, ntfs_inode *dir_ni,
267                                     WIMStruct *w)
268 {
269         ntfs_inode *ni;
270         int ret = 0;
271         mode_t type;
272
273         if (dentry->attributes & FILE_ATTRIBUTE_DIRECTORY) {
274                 type = S_IFDIR;
275         } else {
276                 type = S_IFREG;
277                 const struct list_head *head = &dentry->link_group_list;
278                 if (head->next != head) {
279                         /* This dentry is one of a hard link set of at least 2
280                          * dentries.  If one of the other dentries has already
281                          * been extracted, make a hard link to the file
282                          * corresponding to this already-extracted directory.
283                          * Otherwise, extract the file, and set the
284                          * dentry->extracted_file field so that other dentries
285                          * in the hard link group can link to it. */
286                         struct dentry *other;
287                         list_for_each_entry(other, head, link_group_list) {
288                                 if (other->extracted_file) {
289                                         return wim_apply_hardlink_ntfs(
290                                                         dentry, other, dir_ni);
291                                 }
292                         }
293                 }
294                 FREE(dentry->extracted_file);
295                 dentry->extracted_file = STRDUP(dentry->full_path_utf8);
296                 if (!dentry->extracted_file) {
297                         ERROR("Failed to allocate memory for filename");
298                         return WIMLIB_ERR_NOMEM;
299                 }
300         }
301
302         /* 
303          * Create a directory or file.
304          *
305          * Note: if it's a reparse point (such as a symbolic link), we still
306          * pass S_IFREG here, since we manually set the reparse data later.
307          */
308         ni = ntfs_create(dir_ni, 0, (ntfschar*)dentry->file_name,
309                          dentry->file_name_len / 2, type);
310
311         if (!ni) {
312                 ERROR_WITH_ERRNO("Could not create NTFS object for `%s'",
313                                  dentry->full_path_utf8);
314                 ret = WIMLIB_ERR_NTFS_3G;
315                 goto out;
316         }
317
318         /* Write the data streams, unless this is a directory or reparse point
319          * */
320         if (!dentry_is_directory(dentry) &&
321              !(dentry->attributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
322                 ret = write_ntfs_data_streams(ni, dentry, w);
323                 if (ret != 0)
324                         goto out;
325         }
326
327
328         ret = apply_file_attributes_and_security_data(ni, dentry, w);
329         if (ret != 0)
330                 goto out;
331
332         if (dentry->attributes & FILE_ATTR_REPARSE_POINT) {
333                 ret = apply_reparse_data(ni, dentry, w);
334                 if (ret != 0)
335                         goto out;
336         }
337
338         if (ntfs_inode_close_in_dir(ni, dir_ni) != 0) {
339                 ERROR_WITH_ERRNO("Failed to close new inode");
340                 ret = WIMLIB_ERR_NTFS_3G;
341                 goto out;
342         }
343 out:
344         return ret;
345 }
346
347 static int wim_apply_root_dentry_ntfs(const struct dentry *dentry,
348                                       ntfs_volume *vol,
349                                       const WIMStruct *w)
350 {
351         ntfs_inode *ni;
352         int ret = 0;
353
354         wimlib_assert(dentry_is_directory(dentry));
355         ni = ntfs_pathname_to_inode(vol, NULL, "/");
356         if (!ni) {
357                 ERROR_WITH_ERRNO("Could not find root NTFS inode");
358                 return WIMLIB_ERR_NTFS_3G;
359         }
360         ret = apply_file_attributes_and_security_data(ni, dentry, w);
361         if (ntfs_inode_close(ni) != 0) {
362                 ERROR_WITH_ERRNO("Failed to close NTFS inode for root "
363                                  "directory");
364                 ret = WIMLIB_ERR_NTFS_3G;
365         }
366         return ret;
367 }
368
369 /* Applies a WIM dentry to the NTFS volume */
370 static int wim_apply_dentry_ntfs(struct dentry *dentry, void *arg)
371 {
372         struct ntfs_apply_args *args = arg;
373         ntfs_volume *vol             = args->vol;
374         int extract_flags            = args->extract_flags;
375         WIMStruct *w                 = args->w;
376         ntfs_inode *dir_ni;
377         int ret;
378         char *p;
379         char orig;
380         const char *dir_name;
381
382         wimlib_assert(dentry->full_path_utf8);
383
384         DEBUG("Applying dentry `%s' to NTFS", dentry->full_path_utf8);
385
386         if (extract_flags & WIMLIB_EXTRACT_FLAG_VERBOSE)
387                 puts(dentry->full_path_utf8);
388
389         if (dentry_is_root(dentry))
390                 return wim_apply_root_dentry_ntfs(dentry, vol, w);
391
392         p = dentry->full_path_utf8 + dentry->full_path_utf8_len;
393         do {
394                 p--;
395         } while (*p != '/');
396
397         orig = *p;
398         *p = '\0';
399         dir_name = dentry->full_path_utf8;
400
401         dir_ni = ntfs_pathname_to_inode(vol, NULL, dir_name);
402         *p = orig;
403         if (!dir_ni) {
404                 ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
405                                  dir_name);
406                 return WIMLIB_ERR_NTFS_3G;
407         }
408         DEBUG("Found NTFS inode for `%s'", dir_name);
409
410         ret = do_wim_apply_dentry_ntfs(dentry, dir_ni, w);
411
412         if (ntfs_inode_close(dir_ni) != 0) {
413                 if (ret == 0)
414                         ret = WIMLIB_ERR_NTFS_3G;
415                 ERROR_WITH_ERRNO("Failed to close directory inode");
416         }
417         return ret;
418
419 }
420
421 static int do_wim_apply_image_ntfs(WIMStruct *w, const char *device, int extract_flags)
422 {
423         ntfs_volume *vol;
424         int ret;
425         
426         vol = ntfs_mount(device, 0);
427         if (!vol) {
428                 ERROR_WITH_ERRNO("Failed to mount NTFS volume `%s'", device);
429                 return WIMLIB_ERR_NTFS_3G;
430         }
431         struct ntfs_apply_args args = {
432                 .vol           = vol,
433                 .extract_flags = extract_flags,
434                 .w             = w,
435         };
436         ret = for_dentry_in_tree(wim_root_dentry(w), wim_apply_dentry_ntfs,
437                                  &args);
438         if (ntfs_umount(vol, FALSE) != 0) {
439                 ERROR_WITH_ERRNO("Failed to unmount NTFS volume `%s'", device);
440                 if (ret == 0)
441                         ret = WIMLIB_ERR_NTFS_3G;
442         }
443         return ret;
444 }
445
446
447 /* 
448  * API entry point for applying a WIM image to a NTFS volume.
449  *
450  * Please note that this is a NTFS *volume* and not a directory.  The intention
451  * is that the volume contain an empty filesystem, and the WIM image contain a
452  * full filesystem to be applied to the volume.
453  */
454 WIMLIBAPI int wimlib_apply_image_to_ntfs_volume(WIMStruct *w, int image,
455                                                 const char *device, int flags)
456 {
457         int ret;
458
459         if (!device)
460                 return WIMLIB_ERR_INVALID_PARAM;
461         if (image == WIM_ALL_IMAGES) {
462                 ERROR("Can only apply a single image when applying "
463                       "directly to a NTFS volume");
464                 return WIMLIB_ERR_INVALID_PARAM;
465         }
466         if (flags & (WIMLIB_EXTRACT_FLAG_SYMLINK | WIMLIB_EXTRACT_FLAG_HARDLINK)) {
467                 ERROR("Cannot specify symlink or hardlink flags when applying ");
468                 ERROR("directly to a NTFS volume");
469                 return WIMLIB_ERR_INVALID_PARAM;
470         }
471         ret = wimlib_select_image(w, image);
472         if (ret != 0)
473                 return ret;
474
475 #if 0
476         if (getuid() != 0) {
477                 ERROR("We are not root, but NTFS-3g requires root privileges to set arbitrary");
478                 ERROR("security data on the NTFS filesystem.  Please run this program as root");
479                 ERROR("if you want to extract a WIM image while preserving NTFS-specific");
480                 ERROR("information.");
481
482                 return WIMLIB_ERR_NOT_ROOT;
483         }
484 #endif
485         return do_wim_apply_image_ntfs(w, device, flags);
486 }
487
488 #else /* WITH_NTFS_3G */
489 WIMLIBAPI int wimlib_apply_image_to_ntfs_volume(WIMStruct *w, int image,
490                                                 const char *device, int flags)
491 {
492         ERROR("wimlib was compiled without support for NTFS-3g, so");
493         ERROR("we cannot apply a WIM image directly to a NTFS volume");
494         return WIMLIB_ERR_UNSUPPORTED;
495 }
496 #endif /* WITH_NTFS_3G */