]> wimlib.net Git - wimlib/blob - src/ntfs-apply.c
Allow in-place overwrites when unmounting read-write mounted WIM
[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                 short_name_utf8 = utf16_to_utf8(dentry->short_name,
465                                                 dentry->short_name_len,
466                                                 &short_name_utf8_len);
467                 if (!short_name_utf8) {
468                         ERROR("Out of memory");
469                         ret = WIMLIB_ERR_NOMEM;
470                         goto out_close_dir_ni;
471                 }
472
473                 if (is_hardlink) {
474                         char *p;
475                         char orig;
476                         const char *dir_name;
477
478                         /* ntfs_set_ntfs_dos_name() closes the inodes in the
479                          * wrong order if we have applied a hard link.   Close
480                          * them ourselves, then re-open then. */
481                         if (ntfs_inode_close(dir_ni) != 0) {
482                                 if (ret == 0)
483                                         ret = WIMLIB_ERR_NTFS_3G;
484                                 ERROR_WITH_ERRNO("Failed to close directory inode");
485                         }
486                         if (ntfs_inode_close(ni) != 0) {
487                                 if (ret == 0)
488                                         ret = WIMLIB_ERR_NTFS_3G;
489                                 ERROR_WITH_ERRNO("Failed to close hard link target inode");
490                         }
491                         p = dentry->full_path_utf8 + dentry->full_path_utf8_len;
492                         do {
493                                 p--;
494                         } while (*p != '/');
495
496                         orig = *p;
497                         *p = '\0';
498                         dir_name = dentry->full_path_utf8;
499
500                         dir_ni = ntfs_pathname_to_inode(vol, NULL, dir_name);
501                         *p = orig;
502                         if (!dir_ni) {
503                                 ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
504                                                  dir_name);
505                                 return WIMLIB_ERR_NTFS_3G;
506                         }
507                         ni = ntfs_pathname_to_inode(vol, dir_ni,
508                                                     dentry->file_name_utf8);
509                         if (!ni) {
510                                 ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
511                                                  dir_name);
512                                 return WIMLIB_ERR_NTFS_3G;
513                         }
514                 }
515
516                 DEBUG("Setting short (DOS) name of `%s' to %s",
517                       dentry->full_path_utf8, short_name_utf8);
518
519                 ret = ntfs_set_ntfs_dos_name(ni, dir_ni, short_name_utf8,
520                                              short_name_utf8_len, 0);
521                 FREE(short_name_utf8);
522                 if (ret != 0) {
523                         ERROR_WITH_ERRNO("Could not set DOS (short) name for `%s'",
524                                          dentry->full_path_utf8);
525                         ret = WIMLIB_ERR_NTFS_3G;
526                 }
527                 /* inodes have been closed by ntfs_set_ntfs_dos_name(). */
528                 return ret;
529         }
530
531 out_close_dir_ni:
532         if (ntfs_inode_close(dir_ni) != 0) {
533                 if (ret == 0)
534                         ret = WIMLIB_ERR_NTFS_3G;
535                 ERROR_WITH_ERRNO("Failed to close directory inode");
536         }
537         if (ni && ntfs_inode_close(ni) != 0) {
538                 if (ret == 0)
539                         ret = WIMLIB_ERR_NTFS_3G;
540                 ERROR_WITH_ERRNO("Failed to close inode");
541         }
542         return ret;
543 }
544
545 static int apply_root_dentry_ntfs(const struct dentry *dentry,
546                                   ntfs_volume *vol, const WIMStruct *w)
547 {
548         ntfs_inode *ni;
549         int ret = 0;
550
551         wimlib_assert(dentry_is_directory(dentry));
552         ni = ntfs_pathname_to_inode(vol, NULL, "/");
553         if (!ni) {
554                 ERROR_WITH_ERRNO("Could not find root NTFS inode");
555                 return WIMLIB_ERR_NTFS_3G;
556         }
557         ret = apply_file_attributes_and_security_data(ni, ni, dentry, w);
558         if (ntfs_inode_close(ni) != 0) {
559                 ERROR_WITH_ERRNO("Failed to close NTFS inode for root "
560                                  "directory");
561                 ret = WIMLIB_ERR_NTFS_3G;
562         }
563         return ret;
564 }
565
566 /* Applies a WIM dentry to the NTFS volume */
567 int apply_dentry_ntfs(struct dentry *dentry, void *arg)
568 {
569         struct apply_args *args = arg;
570         ntfs_volume *vol             = args->vol;
571         int extract_flags            = args->extract_flags;
572         WIMStruct *w                 = args->w;
573         ntfs_inode *dir_ni;
574         char *p;
575         char orig;
576         const char *dir_name;
577
578         if (dentry->is_extracted)
579                 return 0;
580
581         if (extract_flags & WIMLIB_EXTRACT_FLAG_NO_STREAMS)
582                 if (inode_unnamed_lte_resolved(dentry->d_inode))
583                         return 0;
584
585         DEBUG("Applying dentry `%s' to NTFS", dentry->full_path_utf8);
586
587         if ((extract_flags & WIMLIB_EXTRACT_FLAG_VERBOSE) &&
588              args->progress_func)
589         {
590                 args->progress.extract.cur_path = dentry->full_path_utf8;
591                 args->progress_func(WIMLIB_PROGRESS_MSG_EXTRACT_DENTRY,
592                                     &args->progress);
593         }
594
595         if (dentry_is_root(dentry))
596                 return apply_root_dentry_ntfs(dentry, vol, w);
597
598         p = dentry->full_path_utf8 + dentry->full_path_utf8_len;
599         do {
600                 p--;
601         } while (*p != '/');
602
603         orig = *p;
604         *p = '\0';
605         dir_name = dentry->full_path_utf8;
606
607         dir_ni = ntfs_pathname_to_inode(vol, NULL, dir_name);
608         *p = orig;
609         if (!dir_ni) {
610                 ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
611                                  dir_name);
612                 return WIMLIB_ERR_NTFS_3G;
613         }
614         return do_apply_dentry_ntfs(dentry, dir_ni, arg);
615 }
616
617 int apply_dentry_timestamps_ntfs(struct dentry *dentry, void *arg)
618 {
619         struct apply_args *args = arg;
620         ntfs_volume *vol = args->vol;
621         u8 *p;
622         u8 buf[24];
623         ntfs_inode *ni;
624         int ret = 0;
625
626         DEBUG("Setting timestamps on `%s'", dentry->full_path_utf8);
627
628         ni = ntfs_pathname_to_inode(vol, NULL, dentry->full_path_utf8);
629         if (!ni) {
630                 ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
631                                  dentry->full_path_utf8);
632                 return WIMLIB_ERR_NTFS_3G;
633         }
634
635         p = buf;
636         p = put_u64(p, dentry->d_inode->creation_time);
637         p = put_u64(p, dentry->d_inode->last_write_time);
638         p = put_u64(p, dentry->d_inode->last_access_time);
639         ret = ntfs_inode_set_times(ni, (const char*)buf, 3 * sizeof(u64), 0);
640         if (ret != 0) {
641                 ERROR_WITH_ERRNO("Failed to set NTFS timestamps on `%s'",
642                                  dentry->full_path_utf8);
643                 ret = WIMLIB_ERR_NTFS_3G;
644         }
645
646         if (ntfs_inode_close(ni) != 0) {
647                 if (ret == 0)
648                         ret = WIMLIB_ERR_NTFS_3G;
649                 ERROR_WITH_ERRNO("Failed to close NTFS inode for `%s'",
650                                  dentry->full_path_utf8);
651         }
652         return ret;
653 }