ntfs-apply.c: Tweak NTFS inode closing some more
[wimlib] / src / ntfs-apply.c
1 /*
2  * ntfs-apply.c
3  *
4  * Apply a WIM image to a NTFS volume.  Restore as much information as possible,
5  * including security data, file attributes, DOS names, and alternate data
6  * streams.
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 General Public License as published by the Free
16  * Software Foundation; either version 3 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 General Public License for more
22  * details.
23  *
24  * You should have received a copy of the GNU General Public License
25  * along with wimlib; if not, see http://www.gnu.org/licenses/.
26  */
27
28
29 #include "config.h"
30
31
32 #include <ntfs-3g/endians.h>
33 #include <ntfs-3g/types.h>
34
35 #include "wimlib_internal.h"
36 #include "dentry.h"
37 #include "lookup_table.h"
38 #include "buffer_io.h"
39 #include <ntfs-3g/layout.h>
40 #include <ntfs-3g/acls.h>
41 #include <ntfs-3g/attrib.h>
42 #include <ntfs-3g/security.h> /* security.h before xattrs.h */
43 #include <ntfs-3g/xattrs.h>
44 #include <ntfs-3g/reparse.h>
45 #include <stdlib.h>
46 #include <unistd.h>
47
48 static int extract_wim_chunk_to_ntfs_attr(const u8 *buf, size_t len,
49                                           u64 offset, void *arg)
50 {
51         ntfs_attr *na = arg;
52         if (ntfs_attr_pwrite(na, offset, len, buf) == len) {
53                 return 0;
54         } else {
55                 ERROR_WITH_ERRNO("Error extracting WIM resource to NTFS attribute");
56                 return WIMLIB_ERR_WRITE;
57         }
58 }
59
60 /*
61  * Extracts a WIM resource to a NTFS attribute.
62  */
63 static int
64 extract_wim_resource_to_ntfs_attr(const struct lookup_table_entry *lte,
65                                   ntfs_attr *na)
66 {
67         return extract_wim_resource(lte, wim_resource_size(lte),
68                                     extract_wim_chunk_to_ntfs_attr, na);
69 }
70
71 /* Writes the data streams to a NTFS file
72  *
73  * @ni:      The NTFS inode for the file.
74  * @inode:   The WIM dentry that has an inode containing the streams.
75  *
76  * Returns 0 on success, nonzero on failure.
77  */
78 static int write_ntfs_data_streams(ntfs_inode *ni, const struct dentry *dentry,
79                                    union wimlib_progress_info *progress_info)
80 {
81         int ret = 0;
82         unsigned stream_idx = 0;
83         ntfschar *stream_name = AT_UNNAMED;
84         u32 stream_name_len = 0;
85         const struct inode *inode = dentry->d_inode;
86
87         DEBUG("Writing %u NTFS data stream%s for `%s'",
88               inode->num_ads + 1,
89               (inode->num_ads == 0 ? "" : "s"),
90               dentry->full_path_utf8);
91
92         while (1) {
93                 struct lookup_table_entry *lte;
94
95                 lte = inode_stream_lte_resolved(inode, stream_idx);
96
97                 if (stream_name_len) {
98                         /* Create an empty named stream. */
99                         ret = ntfs_attr_add(ni, AT_DATA, stream_name,
100                                             stream_name_len, NULL, 0);
101                         if (ret != 0) {
102                                 ERROR_WITH_ERRNO("Failed to create name data "
103                                                  "stream for extracted file "
104                                                  "`%s'",
105                                                  dentry->full_path_utf8);
106                                 ret = WIMLIB_ERR_NTFS_3G;
107                                 break;
108
109                         }
110                 }
111                 /* If there's no lookup table entry, it's an empty stream.
112                  * Otherwise, we must open the attribute and extract the data.
113                  * */
114                 if (lte) {
115                         ntfs_attr *na;
116
117                         na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len);
118                         if (!na) {
119                                 ERROR_WITH_ERRNO("Failed to open a data stream of "
120                                                  "extracted file `%s'",
121                                                  dentry->full_path_utf8);
122                                 ret = WIMLIB_ERR_NTFS_3G;
123                                 break;
124                         }
125                         ret = ntfs_attr_truncate_solid(na, wim_resource_size(lte));
126                         if (ret != 0) {
127                                 ntfs_attr_close(na);
128                                 break;
129                         }
130
131                         ret = extract_wim_resource_to_ntfs_attr(lte, na);
132                         ntfs_attr_close(na);
133                         if (ret != 0)
134                                 break;
135                         progress_info->extract.completed_bytes += wim_resource_size(lte);
136                 }
137                 if (stream_idx == inode->num_ads)
138                         break;
139                 stream_name = (ntfschar*)inode->ads_entries[stream_idx].stream_name;
140                 stream_name_len = inode->ads_entries[stream_idx].stream_name_len / 2;
141                 stream_idx++;
142         }
143         return ret;
144 }
145
146 /* Open the NTFS inode that corresponds to the parent of a WIM dentry. */
147 static ntfs_inode *dentry_open_parent_ni(const struct dentry *dentry,
148                                          ntfs_volume *vol)
149 {
150         char *p;
151         const char *dir_name;
152         ntfs_inode *dir_ni;
153         char orig;
154
155         p = dentry->full_path_utf8 + dentry->full_path_utf8_len;
156         do {
157                 p--;
158         } while (*p != '/');
159
160         orig = *p;
161         *p = '\0';
162         dir_name = dentry->full_path_utf8;
163         dir_ni = ntfs_pathname_to_inode(vol, NULL, dir_name);
164         if (!dir_ni) {
165                 ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
166                                  dir_name);
167         }
168         *p = orig;
169         return dir_ni;
170 }
171
172 /*
173  * Makes a NTFS hard link
174  *
175  * It is named @from_dentry->file_name and is located under the directory
176  * specified by @dir_ni, and it is made to point to the previously extracted
177  * file located at @inode->extracted_file.
178  *
179  * Return 0 on success, nonzero on failure.
180  */
181 static int apply_ntfs_hardlink(const struct dentry *from_dentry,
182                                const struct inode *inode,
183                                ntfs_inode **dir_ni_p)
184 {
185         int ret;
186         ntfs_inode *to_ni;
187         ntfs_inode *dir_ni;
188         ntfs_volume *vol;
189
190         dir_ni = *dir_ni_p;
191         vol = dir_ni->vol;
192         ret = ntfs_inode_close(dir_ni);
193         *dir_ni_p = NULL;
194         if (ret != 0) {
195                 ERROR_WITH_ERRNO("Error closing directory");
196                 return WIMLIB_ERR_NTFS_3G;
197         }
198
199         DEBUG("Extracting NTFS hard link `%s' => `%s'",
200               from_dentry->full_path_utf8, inode->extracted_file);
201
202         to_ni = ntfs_pathname_to_inode(vol, NULL, inode->extracted_file);
203         if (!to_ni) {
204                 ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
205                                  inode->extracted_file);
206                 return WIMLIB_ERR_NTFS_3G;
207         }
208
209         dir_ni = dentry_open_parent_ni(from_dentry, vol);
210         if (!dir_ni) {
211                 ntfs_inode_close(to_ni);
212                 return WIMLIB_ERR_NTFS_3G;
213         }
214
215         *dir_ni_p = dir_ni;
216
217         ret = ntfs_link(to_ni, dir_ni,
218                         (ntfschar*)from_dentry->file_name,
219                         from_dentry->file_name_len / 2);
220         if (ntfs_inode_close_in_dir(to_ni, dir_ni) || ret != 0) {
221                 ERROR_WITH_ERRNO("Could not create hard link `%s' => `%s'",
222                                  from_dentry->full_path_utf8,
223                                  inode->extracted_file);
224                 ret = WIMLIB_ERR_NTFS_3G;
225         }
226         return ret;
227 }
228
229 static int
230 apply_file_attributes_and_security_data(ntfs_inode *ni,
231                                         ntfs_inode *dir_ni,
232                                         const struct dentry *dentry,
233                                         const WIMStruct *w)
234 {
235         DEBUG("Setting NTFS file attributes on `%s' to %#"PRIx32,
236               dentry->full_path_utf8, dentry->d_inode->attributes);
237         int ret;
238         struct SECURITY_CONTEXT ctx;
239         u32 attributes_le32;
240         attributes_le32 = cpu_to_le32(dentry->d_inode->attributes);
241         memset(&ctx, 0, sizeof(ctx));
242         ctx.vol = ni->vol;
243         ret = ntfs_xattr_system_setxattr(&ctx, XATTR_NTFS_ATTRIB,
244                                          ni, dir_ni,
245                                          (const char*)&attributes_le32,
246                                          sizeof(u32), 0);
247         if (ret != 0) {
248                 ERROR("Failed to set NTFS file attributes on `%s'",
249                        dentry->full_path_utf8);
250                 return WIMLIB_ERR_NTFS_3G;
251         }
252         if (dentry->d_inode->security_id != -1) {
253                 const struct wim_security_data *sd;
254                 const char *descriptor;
255
256                 sd = wim_const_security_data(w);
257                 wimlib_assert(dentry->d_inode->security_id < sd->num_entries);
258                 descriptor = (const char *)sd->descriptors[dentry->d_inode->security_id];
259                 DEBUG("Applying security descriptor %d to `%s'",
260                       dentry->d_inode->security_id, dentry->full_path_utf8);
261
262                 ret = ntfs_xattr_system_setxattr(&ctx, XATTR_NTFS_ACL,
263                                                  ni, dir_ni, descriptor,
264                                                  sd->sizes[dentry->d_inode->security_id], 0);
265
266                 if (ret != 0) {
267                         ERROR_WITH_ERRNO("Failed to set security data on `%s'",
268                                         dentry->full_path_utf8);
269                         return WIMLIB_ERR_NTFS_3G;
270                 }
271         }
272         return 0;
273 }
274
275 static int apply_reparse_data(ntfs_inode *ni, const struct dentry *dentry,
276                               union wimlib_progress_info *progress_info)
277 {
278         struct lookup_table_entry *lte;
279         int ret = 0;
280
281         lte = inode_unnamed_lte_resolved(dentry->d_inode);
282
283         DEBUG("Applying reparse data to `%s'", dentry->full_path_utf8);
284
285         if (!lte) {
286                 ERROR("Could not find reparse data for `%s'",
287                       dentry->full_path_utf8);
288                 return WIMLIB_ERR_INVALID_DENTRY;
289         }
290
291         if (wim_resource_size(lte) >= 0xffff) {
292                 ERROR("Reparse data of `%s' is too long (%"PRIu64" bytes)",
293                       dentry->full_path_utf8, wim_resource_size(lte));
294                 return WIMLIB_ERR_INVALID_DENTRY;
295         }
296
297         u8 reparse_data_buf[8 + wim_resource_size(lte)];
298         u8 *p = reparse_data_buf;
299         p = put_u32(p, dentry->d_inode->reparse_tag); /* ReparseTag */
300         p = put_u16(p, wim_resource_size(lte)); /* ReparseDataLength */
301         p = put_u16(p, 0); /* Reserved */
302
303         ret = read_full_wim_resource(lte, p, 0);
304         if (ret != 0)
305                 return ret;
306
307         ret = ntfs_set_ntfs_reparse_data(ni, (char*)reparse_data_buf,
308                                          wim_resource_size(lte) + 8, 0);
309         if (ret != 0) {
310                 ERROR_WITH_ERRNO("Failed to set NTFS reparse data on `%s'",
311                                  dentry->full_path_utf8);
312                 return WIMLIB_ERR_NTFS_3G;
313         }
314         progress_info->extract.completed_bytes += wim_resource_size(lte);
315         return 0;
316 }
317
318 static int do_apply_dentry_ntfs(struct dentry *dentry, ntfs_inode *dir_ni,
319                                 struct apply_args *args);
320
321 /*
322  * If @dentry is part of a hard link group, search for hard-linked dentries in
323  * the same directory that have a nonempty DOS (short) filename.  There should
324  * be exactly 0 or 1 such dentries.  If there is 1, extract that dentry first,
325  * so that the DOS name is correctly associated with the corresponding long name
326  * in the Win32 namespace, and not any of the additional names in the POSIX
327  * namespace created from hard links.
328  */
329 static int preapply_dentry_with_dos_name(struct dentry *dentry,
330                                          ntfs_inode **dir_ni_p,
331                                          struct apply_args *args)
332 {
333         struct dentry *other;
334         struct dentry *dentry_with_dos_name;
335
336         dentry_with_dos_name = NULL;
337         inode_for_each_dentry(other, dentry->d_inode) {
338                 if (other != dentry && (dentry->parent == other->parent)
339                     && other->short_name_len)
340                 {
341                         if (dentry_with_dos_name) {
342                                 ERROR("Found multiple DOS names for file `%s' "
343                                       "in the same directory",
344                                       dentry_with_dos_name->full_path_utf8);
345                                 return WIMLIB_ERR_INVALID_DENTRY;
346                         }
347                         dentry_with_dos_name = other;
348                 }
349         }
350         /* If there's a dentry with a DOS name, extract it first */
351         if (dentry_with_dos_name && !dentry_with_dos_name->is_extracted) {
352                 char *p;
353                 const char *dir_name;
354                 char orig;
355                 int ret;
356                 ntfs_volume *vol = (*dir_ni_p)->vol;
357
358                 DEBUG("pre-applying DOS name `%s'",
359                       dentry_with_dos_name->full_path_utf8);
360                 ret = do_apply_dentry_ntfs(dentry_with_dos_name,
361                                            *dir_ni_p, args);
362                 if (ret != 0)
363                         return ret;
364
365                 *dir_ni_p = dentry_open_parent_ni(dentry, vol);
366                 if (!*dir_ni_p)
367                         return WIMLIB_ERR_NTFS_3G;
368         }
369         return 0;
370 }
371
372 /*
373  * Applies a WIM dentry to a NTFS filesystem.
374  *
375  * @dentry:  The WIM dentry to apply
376  * @dir_ni:  The NTFS inode for the parent directory
377  *
378  * @return:  0 on success; nonzero on failure.
379  */
380 static int do_apply_dentry_ntfs(struct dentry *dentry, ntfs_inode *dir_ni,
381                                 struct apply_args *args)
382 {
383         int ret = 0;
384         mode_t type;
385         ntfs_inode *ni = NULL;
386         bool is_hardlink = false;
387         ntfs_volume *vol = dir_ni->vol;
388         struct inode *inode = dentry->d_inode;
389         dentry->is_extracted = 1;
390
391         if (inode->attributes & FILE_ATTRIBUTE_DIRECTORY) {
392                 type = S_IFDIR;
393         } else {
394                 /* If this dentry is hard-linked to any other dentries in the
395                  * same directory, make sure to apply the one (if any) with a
396                  * DOS name first.  Otherwise, NTFS-3g might not assign the file
397                  * names correctly. */
398                 if (dentry->short_name_len == 0) {
399                         ret = preapply_dentry_with_dos_name(dentry,
400                                                             &dir_ni, args);
401                         if (ret != 0)
402                                 return ret;
403                 }
404
405                 type = S_IFREG;
406
407                 if (inode->link_count > 1) {
408                         /* Already extracted another dentry in the hard link
409                          * group.  Make a hard link instead of extracting the
410                          * file data. */
411                         if (inode->extracted_file) {
412                                 ret = apply_ntfs_hardlink(dentry, inode,
413                                                           &dir_ni);
414                                 is_hardlink = true;
415                                 if (ret)
416                                         goto out_close_dir_ni;
417                                 else
418                                         goto out_set_dos_name;
419                         }
420                         /* Can't make a hard link; extract the file itself */
421                         FREE(inode->extracted_file);
422                         inode->extracted_file = STRDUP(dentry->full_path_utf8);
423                         if (!inode->extracted_file) {
424                                 ret = WIMLIB_ERR_NOMEM;
425                                 goto out_close_dir_ni;
426                         }
427                 }
428         }
429
430         /*
431          * Create a directory or file.
432          *
433          * Note: For symbolic links that are not directory junctions, pass
434          * S_IFREG here, since we manually set the reparse data later.
435          */
436         ni = ntfs_create(dir_ni, 0, (ntfschar*)dentry->file_name,
437                          dentry->file_name_len / 2, type);
438
439         if (!ni) {
440                 ERROR_WITH_ERRNO("Could not create NTFS object for `%s'",
441                                  dentry->full_path_utf8);
442                 ret = WIMLIB_ERR_NTFS_3G;
443                 goto out_close_dir_ni;
444         }
445
446         /* Write the data streams, unless this is a directory or reparse point
447          * */
448         if (!(inode->attributes & (FILE_ATTRIBUTE_REPARSE_POINT |
449                                    FILE_ATTRIBUTE_DIRECTORY))) {
450                 ret = write_ntfs_data_streams(ni, dentry, &args->progress);
451                 if (ret != 0)
452                         goto out_close_dir_ni;
453         }
454
455
456         ret = apply_file_attributes_and_security_data(ni, dir_ni,
457                                                       dentry, args->w);
458         if (ret != 0)
459                 goto out_close_dir_ni;
460
461         if (inode->attributes & FILE_ATTR_REPARSE_POINT) {
462                 ret = apply_reparse_data(ni, dentry, &args->progress);
463                 if (ret != 0)
464                         goto out_close_dir_ni;
465         }
466
467 out_set_dos_name:
468         /* Set DOS (short) name if given */
469         if (dentry->short_name_len != 0) {
470
471                 char *short_name_utf8;
472                 size_t short_name_utf8_len;
473                 ret = utf16_to_utf8(dentry->short_name,
474                                     dentry->short_name_len,
475                                     &short_name_utf8,
476                                     &short_name_utf8_len);
477                 if (ret != 0)
478                         goto out_close_dir_ni;
479
480                 if (is_hardlink) {
481                         wimlib_assert(ni == NULL);
482                         ni = ntfs_pathname_to_inode(vol, dir_ni,
483                                                     dentry->file_name_utf8);
484                         if (!ni) {
485                                 ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
486                                                  dentry->full_path_utf8);
487                                 ret = WIMLIB_ERR_NTFS_3G;
488                                 goto out_close_dir_ni;
489                         }
490                 }
491
492                 wimlib_assert(ni != NULL);
493
494                 DEBUG("Setting short (DOS) name of `%s' to %s",
495                       dentry->full_path_utf8, short_name_utf8);
496
497                 ret = ntfs_set_ntfs_dos_name(ni, dir_ni, short_name_utf8,
498                                              short_name_utf8_len, 0);
499                 FREE(short_name_utf8);
500                 if (ret != 0) {
501                         ERROR_WITH_ERRNO("Could not set DOS (short) name for `%s'",
502                                          dentry->full_path_utf8);
503                         ret = WIMLIB_ERR_NTFS_3G;
504                 }
505                 /* inodes have been closed by ntfs_set_ntfs_dos_name(). */
506                 return ret;
507         }
508
509 out_close_dir_ni:
510         if (dir_ni) {
511                 if (ni) {
512                         if (ntfs_inode_close_in_dir(ni, dir_ni)) {
513                                 if (ret == 0)
514                                         ret = WIMLIB_ERR_NTFS_3G;
515                                 ERROR_WITH_ERRNO("Failed to close inode for `%s'",
516                                                  dentry->full_path_utf8);
517                         }
518                 }
519                 if (ntfs_inode_close(dir_ni)) {
520                         if (ret == 0)
521                                 ret = WIMLIB_ERR_NTFS_3G;
522                         ERROR_WITH_ERRNO("Failed to close directory inode");
523                 }
524         } else {
525                 wimlib_assert(ni == NULL);
526         }
527         return ret;
528 }
529
530 static int apply_root_dentry_ntfs(const struct dentry *dentry,
531                                   ntfs_volume *vol, const WIMStruct *w)
532 {
533         ntfs_inode *ni;
534         int ret = 0;
535
536         wimlib_assert(dentry_is_directory(dentry));
537         ni = ntfs_pathname_to_inode(vol, NULL, "/");
538         if (!ni) {
539                 ERROR_WITH_ERRNO("Could not find root NTFS inode");
540                 return WIMLIB_ERR_NTFS_3G;
541         }
542         ret = apply_file_attributes_and_security_data(ni, ni, dentry, w);
543         if (ntfs_inode_close(ni) != 0) {
544                 ERROR_WITH_ERRNO("Failed to close NTFS inode for root "
545                                  "directory");
546                 ret = WIMLIB_ERR_NTFS_3G;
547         }
548         return ret;
549 }
550
551 /* Applies a WIM dentry to the NTFS volume */
552 int apply_dentry_ntfs(struct dentry *dentry, void *arg)
553 {
554         struct apply_args *args = arg;
555         ntfs_volume *vol = args->vol;
556         WIMStruct *w = args->w;
557         ntfs_inode *dir_ni;
558
559         if (dentry_is_root(dentry))
560                 return apply_root_dentry_ntfs(dentry, vol, w);
561
562         dir_ni = dentry_open_parent_ni(dentry, vol);
563         if (dir_ni)
564                 return do_apply_dentry_ntfs(dentry, dir_ni, arg);
565         else
566                 return WIMLIB_ERR_NTFS_3G;
567 }
568
569 int apply_dentry_timestamps_ntfs(struct dentry *dentry, void *arg)
570 {
571         struct apply_args *args = arg;
572         ntfs_volume *vol = args->vol;
573         u8 *p;
574         u8 buf[24];
575         ntfs_inode *ni;
576         int ret = 0;
577
578         DEBUG("Setting timestamps on `%s'", dentry->full_path_utf8);
579
580         ni = ntfs_pathname_to_inode(vol, NULL, dentry->full_path_utf8);
581         if (!ni) {
582                 ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
583                                  dentry->full_path_utf8);
584                 return WIMLIB_ERR_NTFS_3G;
585         }
586
587         p = buf;
588         p = put_u64(p, dentry->d_inode->creation_time);
589         p = put_u64(p, dentry->d_inode->last_write_time);
590         p = put_u64(p, dentry->d_inode->last_access_time);
591         ret = ntfs_inode_set_times(ni, (const char*)buf, 3 * sizeof(u64), 0);
592         if (ret != 0) {
593                 ERROR_WITH_ERRNO("Failed to set NTFS timestamps on `%s'",
594                                  dentry->full_path_utf8);
595                 ret = WIMLIB_ERR_NTFS_3G;
596         }
597
598         if (ntfs_inode_close(ni) != 0) {
599                 if (ret == 0)
600                         ret = WIMLIB_ERR_NTFS_3G;
601                 ERROR_WITH_ERRNO("Failed to close NTFS inode for `%s'",
602                                  dentry->full_path_utf8);
603         }
604         return ret;
605 }