]> wimlib.net Git - wimlib/blob - src/ntfs-apply.c
fcf141f5b89e3cfdea469aa70cdda66ecd9d2689
[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 #include <ntfs-3g/endians.h>
32 #include <ntfs-3g/types.h>
33
34 #include "wimlib_internal.h"
35 #include "buffer_io.h"
36 #include "dentry.h"
37 #include "lookup_table.h"
38
39 #include <ntfs-3g/attrib.h>
40 #include <ntfs-3g/security.h> /* security.h before xattrs.h */
41 #include <ntfs-3g/reparse.h>
42 #include <ntfs-3g/xattrs.h>
43 #include <string.h>
44
45 static int extract_wim_chunk_to_ntfs_attr(const u8 *buf, size_t len,
46                                           u64 offset, void *arg)
47 {
48         ntfs_attr *na = arg;
49         if (ntfs_attr_pwrite(na, offset, len, buf) == len) {
50                 return 0;
51         } else {
52                 ERROR_WITH_ERRNO("Error extracting WIM resource to NTFS attribute");
53                 return WIMLIB_ERR_WRITE;
54         }
55 }
56
57 /*
58  * Extracts a WIM resource to a NTFS attribute.
59  */
60 static int
61 extract_wim_resource_to_ntfs_attr(const struct wim_lookup_table_entry *lte,
62                                   ntfs_attr *na)
63 {
64         return extract_wim_resource(lte, wim_resource_size(lte),
65                                     extract_wim_chunk_to_ntfs_attr, na);
66 }
67
68 /* Writes the data streams of a WIM inode to the data attributes of a NTFS
69  * inode.
70  *
71  * @ni:      The NTFS inode to which the streams are to be extracted.
72  *
73  * @dentry:  The WIM dentry being extracted.  The @d_inode member points to the
74  *           corresponding WIM inode that contains the streams being extracted.
75  *           The WIM dentry itself is only needed to provide a file path for
76  *           better error messages.
77  *
78  * @progress_info:  Progress information for the image application.  The number
79  *                  of extracted bytes will be incremented by the uncompressed
80  *                  size of each stream extracted.
81  *
82  * Returns 0 on success, nonzero on failure.
83  */
84 static int write_ntfs_data_streams(ntfs_inode *ni, const struct wim_dentry *dentry,
85                                    union wimlib_progress_info *progress_info)
86 {
87         int ret = 0;
88         unsigned stream_idx = 0;
89         ntfschar *stream_name = AT_UNNAMED;
90         u32 stream_name_len = 0;
91         const struct wim_inode *inode = dentry->d_inode;
92         struct wim_lookup_table_entry *lte;
93
94         DEBUG("Writing %u NTFS data stream%s for `%s'",
95               inode->i_num_ads + 1,
96               (inode->i_num_ads == 0 ? "" : "s"),
97               dentry->full_path_utf8);
98
99         lte = inode->i_lte;
100         while (1) {
101                 if (stream_name_len) {
102                         /* Create an empty named stream. */
103                         ret = ntfs_attr_add(ni, AT_DATA, stream_name,
104                                             stream_name_len, NULL, 0);
105                         if (ret != 0) {
106                                 ERROR_WITH_ERRNO("Failed to create name data "
107                                                  "stream for extracted file "
108                                                  "`%s'",
109                                                  dentry->full_path_utf8);
110                                 ret = WIMLIB_ERR_NTFS_3G;
111                                 break;
112
113                         }
114                 }
115
116                 /* If there's no lookup table entry, it's an empty stream.
117                  * Otherwise, open the attribute and extract the data. */
118                 if (lte) {
119                         ntfs_attr *na;
120
121                         na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len);
122                         if (!na) {
123                                 ERROR_WITH_ERRNO("Failed to open a data stream of "
124                                                  "extracted file `%s'",
125                                                  dentry->full_path_utf8);
126                                 ret = WIMLIB_ERR_NTFS_3G;
127                                 break;
128                         }
129
130                         /* The WIM lookup table entry provides the stream
131                          * length, so the NTFS attribute should be resized to
132                          * this length before starting to extract the data. */
133                         ret = ntfs_attr_truncate_solid(na, wim_resource_size(lte));
134                         if (ret != 0) {
135                                 ntfs_attr_close(na);
136                                 break;
137                         }
138
139                         /* Actually extract the stream */
140                         ret = extract_wim_resource_to_ntfs_attr(lte, na);
141
142                         /* Close the attribute */
143                         ntfs_attr_close(na);
144                         if (ret != 0)
145                                 break;
146
147                         /* Record the number of bytes of uncompressed data that
148                          * have been extracted. */
149                         progress_info->extract.completed_bytes += wim_resource_size(lte);
150                 }
151                 if (stream_idx == inode->i_num_ads) /* Has the last stream been extracted? */
152                         break;
153
154                 /* Get the name and lookup table entry for the next stream. */
155                 stream_name = (ntfschar*)inode->i_ads_entries[stream_idx].stream_name;
156                 stream_name_len = inode->i_ads_entries[stream_idx].stream_name_len / 2;
157                 lte = inode->i_ads_entries[stream_idx].lte;
158                 stream_idx++;
159         }
160         return ret;
161 }
162
163 /* Open the NTFS inode that corresponds to the parent of a WIM dentry.  Returns
164  * the opened inode, or NULL on failure. */
165 static ntfs_inode *dentry_open_parent_ni(const struct wim_dentry *dentry,
166                                          ntfs_volume *vol)
167 {
168         char *p;
169         const char *dir_name;
170         ntfs_inode *dir_ni;
171         char orig;
172
173         p = dentry->full_path_utf8 + dentry->full_path_utf8_len;
174         do {
175                 p--;
176         } while (*p != '/');
177
178         orig = *p;
179         *p = '\0';
180         dir_name = dentry->full_path_utf8;
181         dir_ni = ntfs_pathname_to_inode(vol, NULL, dir_name);
182         if (!dir_ni) {
183                 ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
184                                  dir_name);
185         }
186         *p = orig;
187         return dir_ni;
188 }
189
190 /*
191  * Makes a NTFS hard link.
192  *
193  * The hard link is named @from_dentry->file_name and is located under the
194  * directory specified by @dir_ni, and it is made to point to the previously
195  * extracted file located at @inode->i_extracted_file.
196  *
197  * Or, in other words, this adds a new name @from_dentry->full_path_utf8 to an
198  * existing NTFS inode which already has a name @inode->i_extracted_file.
199  *
200  * The new name is made in the POSIX namespace (this is the behavior of
201  * ntfs_link()).
202  *
203  * Return 0 on success, nonzero on failure.  dir_ni is closed either way.
204  */
205 static int apply_ntfs_hardlink(const struct wim_dentry *from_dentry,
206                                const struct wim_inode *inode,
207                                ntfs_inode *dir_ni)
208 {
209         int ret;
210         ntfs_inode *to_ni;
211         ntfs_volume *vol;
212
213         vol = dir_ni->vol;
214         ret = ntfs_inode_close(dir_ni);
215         if (ret != 0) {
216                 ERROR_WITH_ERRNO("Error closing directory");
217                 return WIMLIB_ERR_NTFS_3G;
218         }
219
220         DEBUG("Extracting NTFS hard link `%s' => `%s'",
221               from_dentry->full_path_utf8, inode->i_extracted_file);
222
223         to_ni = ntfs_pathname_to_inode(vol, NULL, inode->i_extracted_file);
224         if (!to_ni) {
225                 ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
226                                  inode->i_extracted_file);
227                 return WIMLIB_ERR_NTFS_3G;
228         }
229
230         dir_ni = dentry_open_parent_ni(from_dentry, vol);
231         if (!dir_ni) {
232                 ntfs_inode_close(to_ni);
233                 return WIMLIB_ERR_NTFS_3G;
234         }
235
236         ret = ntfs_link(to_ni, dir_ni,
237                         (ntfschar*)from_dentry->file_name,
238                         from_dentry->file_name_len / 2);
239         ret |= ntfs_inode_close(dir_ni);
240         ret |= ntfs_inode_close(to_ni);
241         if (ret) {
242                 ERROR_WITH_ERRNO("Could not create hard link `%s' => `%s'",
243                                  from_dentry->full_path_utf8,
244                                  inode->i_extracted_file);
245                 ret = WIMLIB_ERR_NTFS_3G;
246         }
247         return ret;
248 }
249
250 /* Transfers file attributes and possibly a security descriptor from a WIM inode
251  * to a NTFS inode.
252  *
253  * @ni:      The NTFS inode to apply the metadata to.
254  * @dir_ni:  The NTFS inode for a directory containing @ni.
255  * @dentry:  The WIM dentry whose inode contains the metadata to apply.
256  * @w:       The WIMStruct for the WIM, through which the table of security
257  *              descriptors can be accessed.
258  *
259  * Returns 0 on success, nonzero on failure.
260  */
261 static int
262 apply_file_attributes_and_security_data(ntfs_inode *ni,
263                                         ntfs_inode *dir_ni,
264                                         const struct wim_dentry *dentry,
265                                         const WIMStruct *w)
266 {
267         int ret;
268         struct SECURITY_CONTEXT ctx;
269         u32 attributes_le32;
270         const struct wim_inode *inode;
271
272         inode = dentry->d_inode;
273
274         DEBUG("Setting NTFS file attributes on `%s' to %#"PRIx32,
275               dentry->full_path_utf8, inode->i_attributes);
276
277         attributes_le32 = cpu_to_le32(inode->i_attributes);
278         memset(&ctx, 0, sizeof(ctx));
279         ctx.vol = ni->vol;
280         ret = ntfs_xattr_system_setxattr(&ctx, XATTR_NTFS_ATTRIB,
281                                          ni, dir_ni,
282                                          (const char*)&attributes_le32,
283                                          sizeof(u32), 0);
284         if (ret != 0) {
285                 ERROR("Failed to set NTFS file attributes on `%s'",
286                        dentry->full_path_utf8);
287                 return WIMLIB_ERR_NTFS_3G;
288         }
289         if (inode->i_security_id != -1) {
290                 const char *desc;
291                 const struct wim_security_data *sd;
292
293                 sd = wim_const_security_data(w);
294                 wimlib_assert(inode->i_security_id < sd->num_entries);
295                 desc = (const char *)sd->descriptors[inode->i_security_id];
296                 DEBUG("Applying security descriptor %d to `%s'",
297                       inode->i_security_id, dentry->full_path_utf8);
298
299                 ret = ntfs_xattr_system_setxattr(&ctx, XATTR_NTFS_ACL,
300                                                  ni, dir_ni, desc,
301                                                  sd->sizes[inode->i_security_id], 0);
302
303                 if (ret != 0) {
304                         ERROR_WITH_ERRNO("Failed to set security data on `%s'",
305                                         dentry->full_path_utf8);
306                         return WIMLIB_ERR_NTFS_3G;
307                 }
308         }
309         return 0;
310 }
311
312 /*
313  * Transfers the reparse data from a WIM inode (which must represent a reparse
314  * point) to a NTFS inode.
315  */
316 static int apply_reparse_data(ntfs_inode *ni, const struct wim_dentry *dentry,
317                               union wimlib_progress_info *progress_info)
318 {
319         struct wim_lookup_table_entry *lte;
320         int ret = 0;
321
322         lte = inode_unnamed_lte_resolved(dentry->d_inode);
323
324         DEBUG("Applying reparse data to `%s'", dentry->full_path_utf8);
325
326         if (!lte) {
327                 ERROR("Could not find reparse data for `%s'",
328                       dentry->full_path_utf8);
329                 return WIMLIB_ERR_INVALID_DENTRY;
330         }
331
332         if (wim_resource_size(lte) >= 0xffff) {
333                 ERROR("Reparse data of `%s' is too long (%"PRIu64" bytes)",
334                       dentry->full_path_utf8, wim_resource_size(lte));
335                 return WIMLIB_ERR_INVALID_DENTRY;
336         }
337
338         u8 reparse_data_buf[8 + wim_resource_size(lte)];
339         u8 *p = reparse_data_buf;
340         p = put_u32(p, dentry->d_inode->i_reparse_tag); /* ReparseTag */
341         DEBUG("ReparseTag = %#x", dentry->d_inode->i_reparse_tag);
342         p = put_u16(p, wim_resource_size(lte)); /* ReparseDataLength */
343         p = put_u16(p, 0); /* Reserved */
344
345         ret = read_full_wim_resource(lte, p, 0);
346         if (ret != 0)
347                 return ret;
348
349         ret = ntfs_set_ntfs_reparse_data(ni, (char*)reparse_data_buf,
350                                          wim_resource_size(lte) + 8, 0);
351         if (ret != 0) {
352                 ERROR_WITH_ERRNO("Failed to set NTFS reparse data on `%s'",
353                                  dentry->full_path_utf8);
354                 return WIMLIB_ERR_NTFS_3G;
355         }
356         progress_info->extract.completed_bytes += wim_resource_size(lte);
357         return 0;
358 }
359
360 /*
361  * Applies a WIM dentry to a NTFS filesystem.
362  *
363  * @dentry:  The WIM dentry to apply
364  * @dir_ni:  The NTFS inode for the parent directory
365  *
366  * @return:  0 on success; nonzero on failure.
367  */
368 static int do_apply_dentry_ntfs(struct wim_dentry *dentry, ntfs_inode *dir_ni,
369                                 struct apply_args *args)
370 {
371         int ret = 0;
372         mode_t type;
373         ntfs_inode *ni = NULL;
374         struct wim_inode *inode = dentry->d_inode;
375         dentry->is_extracted = 1;
376
377         if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) {
378                 type = S_IFDIR;
379         } else {
380                 type = S_IFREG;
381                 if (inode->i_nlink > 1) {
382                         /* Inode has multiple dentries referencing it. */
383                         if (inode->i_extracted_file) {
384                                 /* Already extracted another dentry in the hard
385                                  * link group.  Make a hard link instead of
386                                  * extracting the file data. */
387                                 ret = apply_ntfs_hardlink(dentry, inode, dir_ni);
388                                 /* dir_ni was closed */
389                                 goto out;
390                         } else {
391                                 /* None of the dentries of this inode have been
392                                  * extracted yet, so go ahead and extract the
393                                  * first one. */
394                                 FREE(inode->i_extracted_file);
395                                 inode->i_extracted_file = STRDUP(dentry->full_path_utf8);
396                                 if (!inode->i_extracted_file) {
397                                         ret = WIMLIB_ERR_NOMEM;
398                                         goto out_close_dir_ni;
399                                 }
400                         }
401                 }
402         }
403
404         /* Create a NTFS directory or file.
405          *
406          * Note: For symbolic links that are not directory junctions, S_IFREG is
407          * passed here, since the reparse data and file attributes are set
408          * later. */
409         ni = ntfs_create(dir_ni, 0, (ntfschar*)dentry->file_name,
410                          dentry->file_name_len / 2, type);
411
412         if (!ni) {
413                 ERROR_WITH_ERRNO("Could not create NTFS inode for `%s'",
414                                  dentry->full_path_utf8);
415                 ret = WIMLIB_ERR_NTFS_3G;
416                 goto out_close_dir_ni;
417         }
418
419         /* Write the data streams, unless this is a directory or reparse point
420          * */
421         if (!(inode->i_attributes & (FILE_ATTRIBUTE_REPARSE_POINT |
422                                    FILE_ATTRIBUTE_DIRECTORY))) {
423                 ret = write_ntfs_data_streams(ni, dentry, &args->progress);
424                 if (ret != 0)
425                         goto out_close_dir_ni;
426         }
427
428
429         ret = apply_file_attributes_and_security_data(ni, dir_ni, dentry,
430                                                       args->w);
431         if (ret != 0)
432                 goto out_close_dir_ni;
433
434         if (inode->i_attributes & FILE_ATTR_REPARSE_POINT) {
435                 ret = apply_reparse_data(ni, dentry, &args->progress);
436                 if (ret != 0)
437                         goto out_close_dir_ni;
438         }
439
440         /* Set DOS (short) name if given */
441         if (dentry->short_name_len != 0) {
442                 char *short_name_utf8;
443                 size_t short_name_utf8_len;
444                 ret = utf16_to_utf8(dentry->short_name,
445                                     dentry->short_name_len,
446                                     &short_name_utf8,
447                                     &short_name_utf8_len);
448                 if (ret != 0)
449                         goto out_close_dir_ni;
450
451                 DEBUG("Setting short (DOS) name of `%s' to %s",
452                       dentry->full_path_utf8, short_name_utf8);
453
454                 ret = ntfs_set_ntfs_dos_name(ni, dir_ni, short_name_utf8,
455                                              short_name_utf8_len, 0);
456                 FREE(short_name_utf8);
457                 if (ret != 0) {
458                         ERROR_WITH_ERRNO("Could not set DOS (short) name for `%s'",
459                                          dentry->full_path_utf8);
460                         ret = WIMLIB_ERR_NTFS_3G;
461                 }
462                 /* inodes have been closed by ntfs_set_ntfs_dos_name(). */
463                 goto out;
464         }
465 out_close_dir_ni:
466         if (dir_ni) {
467                 if (ni) {
468                         if (ntfs_inode_close_in_dir(ni, dir_ni)) {
469                                 if (ret == 0)
470                                         ret = WIMLIB_ERR_NTFS_3G;
471                                 ERROR_WITH_ERRNO("Failed to close inode for `%s'",
472                                                  dentry->full_path_utf8);
473                         }
474                 }
475                 if (ntfs_inode_close(dir_ni)) {
476                         if (ret == 0)
477                                 ret = WIMLIB_ERR_NTFS_3G;
478                         ERROR_WITH_ERRNO("Failed to close inode of directory "
479                                          "containing `%s'", dentry->full_path_utf8);
480                 }
481         }
482 out:
483         return ret;
484 }
485
486 static int apply_root_dentry_ntfs(const struct wim_dentry *dentry,
487                                   ntfs_volume *vol, const WIMStruct *w)
488 {
489         ntfs_inode *ni;
490         int ret = 0;
491
492         ni = ntfs_pathname_to_inode(vol, NULL, "/");
493         if (!ni) {
494                 ERROR_WITH_ERRNO("Could not find root NTFS inode");
495                 return WIMLIB_ERR_NTFS_3G;
496         }
497         ret = apply_file_attributes_and_security_data(ni, ni, dentry, w);
498         if (ntfs_inode_close(ni) != 0) {
499                 ERROR_WITH_ERRNO("Failed to close NTFS inode for root "
500                                  "directory");
501                 ret = WIMLIB_ERR_NTFS_3G;
502         }
503         return ret;
504 }
505
506 /* Applies a WIM dentry to the NTFS volume */
507 int apply_dentry_ntfs(struct wim_dentry *dentry, void *arg)
508 {
509         struct apply_args *args = arg;
510         ntfs_volume *vol = args->vol;
511         WIMStruct *w = args->w;
512         struct wim_dentry *orig_dentry;
513         struct wim_dentry *other;
514         int ret;
515
516         /* Treat the root dentry specially. */
517         if (dentry_is_root(dentry))
518                 return apply_root_dentry_ntfs(dentry, vol, w);
519
520         /* NTFS filename namespaces need careful consideration.  A name for a
521          * NTFS file may be in either the POSIX, Win32, DOS, or Win32+DOS
522          * namespaces.  A NTFS file (a.k.a. inode) may have multiple names in
523          * multiple directories (i.e. hard links); however, a NTFS file can have
524          * at most 1 DOS name total.  Furthermore, a Win32 name is always
525          * associated with a DOS name (either as a Win32+DOS name, or a Win32
526          * name and a DOS name separately), which implies that a NTFS file can
527          * have at most 1 Win32 name.
528          *
529          * A WIM dentry just contains a "long name", which wimlib makes sure is
530          * non-empty, and a "short name", which may be empty.  So, wimlib must
531          * map these to the correct NTFS names.  wimlib collects all WIM
532          * dentries that map to the same NTFS inode and factors out the common
533          * information into a 'struct wim_inode', so this should make the
534          * mapping a little more obvious.  As a NTFS file can have at most 1 DOS
535          * name, a WIM inode cannot have more than 1 dentry with a non-empty
536          * short name, and this is checked in the verify_inode() function in
537          * verify.c.  Furthermore, a WIM dentry, if any, that has a DOS name
538          * must have a long name that corresponds to a Win32 name or Win32+DOS
539          * name.
540          *
541          * WIM dentries that have a long name but no associated short name are
542          * assumed to be in the POSIX namespace.
543          *
544          * So, given a WIM inode that is to map to a NTFS inode, we must apply
545          * the Win32 and DOS or Win32+DOS names, if they exist, then any
546          * additional (POSIX) names.  A caveat when actually doing this:  as
547          * confirmed by the libntfs-3g authors, ntfs_set_ntfs_dos_name() is only
548          * guaranteed to associate a DOS name with the appropriate long name if
549          * it's called when that long name is the only one in existence for that
550          * file.  So, this implies that the correct ordering of function calls
551          * to extract a NTFS file are:
552          *
553          *      if (file has a DOS name) {
554          *              - Call ntfs_create() to create long name associated with
555          *              the DOS name (this initially creates a POSIX name)
556          *              - Call ntfs_set_ntfs_dos_name() to associate a DOS name
557          *              with the long name just created.  This either changes
558          *              the POSIX name to Win32+DOS, or changes the POSIX name
559          *              to Win32 and creates a separate DOS name.
560          *      } else {
561          *              - Call ntfs_create() to create the first link to the
562          *              file in the POSIX namespace
563          *      }
564          *      - Call ntfs_link() to create the other names of the file, in the
565          *      POSIX namespace.
566          */
567 again:
568         orig_dentry = NULL;
569         if (!dentry->d_inode->i_dos_name_extracted &&
570             dentry->short_name_len == 0)
571         {
572                 inode_for_each_dentry(other, dentry->d_inode) {
573                         if (other->short_name_len != 0) {
574                                 orig_dentry = dentry;
575                                 dentry = other;
576                                 break;
577                         }
578                 }
579         }
580         dentry->d_inode->i_dos_name_extracted = 1;
581         ntfs_inode *dir_ni = dentry_open_parent_ni(dentry, vol);
582         if (dir_ni) {
583                 ret = do_apply_dentry_ntfs(dentry, dir_ni, arg);
584                 if (ret == 0 && orig_dentry != NULL) {
585                         dentry = orig_dentry;
586                         goto again;
587                 }
588         } else {
589                 ret = WIMLIB_ERR_NTFS_3G;
590         }
591         return ret;
592 }
593
594 /* Transfers the 100-nanosecond precision timestamps from a WIM dentry to a NTFS
595  * inode */
596 int apply_dentry_timestamps_ntfs(struct wim_dentry *dentry, void *arg)
597 {
598         struct apply_args *args = arg;
599         ntfs_volume *vol = args->vol;
600         u8 *p;
601         u8 buf[24];
602         ntfs_inode *ni;
603         int ret;
604
605         DEBUG("Setting timestamps on `%s'", dentry->full_path_utf8);
606
607         ni = ntfs_pathname_to_inode(vol, NULL, dentry->full_path_utf8);
608         if (!ni) {
609                 ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
610                                  dentry->full_path_utf8);
611                 return WIMLIB_ERR_NTFS_3G;
612         }
613
614         p = buf;
615         p = put_u64(p, dentry->d_inode->i_creation_time);
616         p = put_u64(p, dentry->d_inode->i_last_write_time);
617         p = put_u64(p, dentry->d_inode->i_last_access_time);
618         ret = ntfs_inode_set_times(ni, (const char*)buf, 3 * sizeof(u64), 0);
619         if (ret != 0) {
620                 ERROR_WITH_ERRNO("Failed to set NTFS timestamps on `%s'",
621                                  dentry->full_path_utf8);
622                 ret = WIMLIB_ERR_NTFS_3G;
623         }
624
625         if (ntfs_inode_close(ni) != 0) {
626                 if (ret == 0)
627                         ret = WIMLIB_ERR_NTFS_3G;
628                 ERROR_WITH_ERRNO("Failed to close NTFS inode for `%s'",
629                                  dentry->full_path_utf8);
630         }
631         return ret;
632 }