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