]> wimlib.net Git - wimlib/blob - src/ntfs-apply.c
Cleanup
[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         p = put_u16(p, wim_resource_size(lte)); /* ReparseDataLength */
342         p = put_u16(p, 0); /* Reserved */
343
344         ret = read_full_wim_resource(lte, p, 0);
345         if (ret != 0)
346                 return ret;
347
348         ret = ntfs_set_ntfs_reparse_data(ni, (char*)reparse_data_buf,
349                                          wim_resource_size(lte) + 8, 0);
350         if (ret != 0) {
351                 ERROR_WITH_ERRNO("Failed to set NTFS reparse data on `%s'",
352                                  dentry->full_path_utf8);
353                 return WIMLIB_ERR_NTFS_3G;
354         }
355         progress_info->extract.completed_bytes += wim_resource_size(lte);
356         return 0;
357 }
358
359 /*
360  * Applies a WIM dentry to a NTFS filesystem.
361  *
362  * @dentry:  The WIM dentry to apply
363  * @dir_ni:  The NTFS inode for the parent directory
364  *
365  * @return:  0 on success; nonzero on failure.
366  */
367 static int do_apply_dentry_ntfs(struct wim_dentry *dentry, ntfs_inode *dir_ni,
368                                 struct apply_args *args)
369 {
370         int ret = 0;
371         mode_t type;
372         ntfs_inode *ni = NULL;
373         struct wim_inode *inode = dentry->d_inode;
374         dentry->is_extracted = 1;
375
376         if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) {
377                 type = S_IFDIR;
378         } else {
379                 type = S_IFREG;
380                 if (inode->i_nlink > 1) {
381                         /* Inode has multiple dentries referencing it. */
382                         if (inode->i_extracted_file) {
383                                 /* Already extracted another dentry in the hard
384                                  * link group.  Make a hard link instead of
385                                  * extracting the file data. */
386                                 ret = apply_ntfs_hardlink(dentry, inode, dir_ni);
387                                 /* dir_ni was closed */
388                                 goto out;
389                         } else {
390                                 /* None of the dentries of this inode have been
391                                  * extracted yet, so go ahead and extract the
392                                  * first one. */
393                                 FREE(inode->i_extracted_file);
394                                 inode->i_extracted_file = STRDUP(dentry->full_path_utf8);
395                                 if (!inode->i_extracted_file) {
396                                         ret = WIMLIB_ERR_NOMEM;
397                                         goto out_close_dir_ni;
398                                 }
399                         }
400                 }
401         }
402
403         /* Create a NTFS directory or file.
404          *
405          * Note: For symbolic links that are not directory junctions, S_IFREG is
406          * passed here, since the reparse data and file attributes are set
407          * later. */
408         ni = ntfs_create(dir_ni, 0, (ntfschar*)dentry->file_name,
409                          dentry->file_name_len / 2, type);
410
411         if (!ni) {
412                 ERROR_WITH_ERRNO("Could not create NTFS inode for `%s'",
413                                  dentry->full_path_utf8);
414                 ret = WIMLIB_ERR_NTFS_3G;
415                 goto out_close_dir_ni;
416         }
417
418         /* Write the data streams, unless this is a directory or reparse point
419          * */
420         if (!(inode->i_attributes & (FILE_ATTRIBUTE_REPARSE_POINT |
421                                    FILE_ATTRIBUTE_DIRECTORY))) {
422                 ret = write_ntfs_data_streams(ni, dentry, &args->progress);
423                 if (ret != 0)
424                         goto out_close_dir_ni;
425         }
426
427
428         ret = apply_file_attributes_and_security_data(ni, dir_ni, dentry,
429                                                       args->w);
430         if (ret != 0)
431                 goto out_close_dir_ni;
432
433         if (inode->i_attributes & FILE_ATTR_REPARSE_POINT) {
434                 ret = apply_reparse_data(ni, dentry, &args->progress);
435                 if (ret != 0)
436                         goto out_close_dir_ni;
437         }
438
439         /* Set DOS (short) name if given */
440         if (dentry->short_name_len != 0) {
441                 char *short_name_utf8;
442                 size_t short_name_utf8_len;
443                 ret = utf16_to_utf8(dentry->short_name,
444                                     dentry->short_name_len,
445                                     &short_name_utf8,
446                                     &short_name_utf8_len);
447                 if (ret != 0)
448                         goto out_close_dir_ni;
449
450                 DEBUG("Setting short (DOS) name of `%s' to %s",
451                       dentry->full_path_utf8, short_name_utf8);
452
453                 ret = ntfs_set_ntfs_dos_name(ni, dir_ni, short_name_utf8,
454                                              short_name_utf8_len, 0);
455                 FREE(short_name_utf8);
456                 if (ret != 0) {
457                         ERROR_WITH_ERRNO("Could not set DOS (short) name for `%s'",
458                                          dentry->full_path_utf8);
459                         ret = WIMLIB_ERR_NTFS_3G;
460                 }
461                 /* inodes have been closed by ntfs_set_ntfs_dos_name(). */
462                 goto out;
463         }
464 out_close_dir_ni:
465         if (dir_ni) {
466                 if (ni) {
467                         if (ntfs_inode_close_in_dir(ni, dir_ni)) {
468                                 if (ret == 0)
469                                         ret = WIMLIB_ERR_NTFS_3G;
470                                 ERROR_WITH_ERRNO("Failed to close inode for `%s'",
471                                                  dentry->full_path_utf8);
472                         }
473                 }
474                 if (ntfs_inode_close(dir_ni)) {
475                         if (ret == 0)
476                                 ret = WIMLIB_ERR_NTFS_3G;
477                         ERROR_WITH_ERRNO("Failed to close inode of directory "
478                                          "containing `%s'", dentry->full_path_utf8);
479                 }
480         }
481 out:
482         return ret;
483 }
484
485 static int apply_root_dentry_ntfs(const struct wim_dentry *dentry,
486                                   ntfs_volume *vol, const WIMStruct *w)
487 {
488         ntfs_inode *ni;
489         int ret = 0;
490
491         ni = ntfs_pathname_to_inode(vol, NULL, "/");
492         if (!ni) {
493                 ERROR_WITH_ERRNO("Could not find root NTFS inode");
494                 return WIMLIB_ERR_NTFS_3G;
495         }
496         ret = apply_file_attributes_and_security_data(ni, ni, dentry, w);
497         if (ntfs_inode_close(ni) != 0) {
498                 ERROR_WITH_ERRNO("Failed to close NTFS inode for root "
499                                  "directory");
500                 ret = WIMLIB_ERR_NTFS_3G;
501         }
502         return ret;
503 }
504
505 /* Applies a WIM dentry to the NTFS volume */
506 int apply_dentry_ntfs(struct wim_dentry *dentry, void *arg)
507 {
508         struct apply_args *args = arg;
509         ntfs_volume *vol = args->vol;
510         WIMStruct *w = args->w;
511         struct wim_dentry *orig_dentry;
512         struct wim_dentry *other;
513         int ret;
514
515         /* Treat the root dentry specially. */
516         if (dentry_is_root(dentry))
517                 return apply_root_dentry_ntfs(dentry, vol, w);
518
519         /* NTFS filename namespaces need careful consideration.  A name for a
520          * NTFS file may be in either the POSIX, Win32, DOS, or Win32+DOS
521          * namespaces.  A NTFS file (a.k.a. inode) may have multiple names in
522          * multiple directories (i.e. hard links); however, a NTFS file can have
523          * at most 1 DOS name total.  Furthermore, a Win32 name is always
524          * associated with a DOS name (either as a Win32+DOS name, or a Win32
525          * name and a DOS name separately), which implies that a NTFS file can
526          * have at most 1 Win32 name.
527          *
528          * A WIM dentry just contains a "long name", which wimlib makes sure is
529          * non-empty, and a "short name", which may be empty.  So, wimlib must
530          * map these to the correct NTFS names.  wimlib collects all WIM
531          * dentries that map to the same NTFS inode and factors out the common
532          * information into a 'struct wim_inode', so this should make the
533          * mapping a little more obvious.  As a NTFS file can have at most 1 DOS
534          * name, a WIM inode cannot have more than 1 dentry with a non-empty
535          * short name, and this is checked in the verify_inode() function in
536          * verify.c.  Furthermore, a WIM dentry, if any, that has a DOS name
537          * must have a long name that corresponds to a Win32 name or Win32+DOS
538          * name.
539          *
540          * WIM dentries that have a long name but no associated short name are
541          * assumed to be in the POSIX namespace.
542          *
543          * So, given a WIM inode that is to map to a NTFS inode, we must apply
544          * the Win32 and DOS or Win32+DOS names, if they exist, then any
545          * additional (POSIX) names.  A caveat when actually doing this:  as
546          * confirmed by the libntfs-3g authors, ntfs_set_ntfs_dos_name() is only
547          * guaranteed to associate a DOS name with the appropriate long name if
548          * it's called when that long name is the only one in existence for that
549          * file.  So, this implies that the correct ordering of function calls
550          * to extract a NTFS file are:
551          *
552          *      if (file has a DOS name) {
553          *              - Call ntfs_create() to create long name associated with
554          *              the DOS name (this initially creates a POSIX name)
555          *              - Call ntfs_set_ntfs_dos_name() to associate a DOS name
556          *              with the long name just created.  This either changes
557          *              the POSIX name to Win32+DOS, or changes the POSIX name
558          *              to Win32 and creates a separate DOS name.
559          *      } else {
560          *              - Call ntfs_create() to create the first link to the
561          *              file in the POSIX namespace
562          *      }
563          *      - Call ntfs_link() to create the other names of the file, in the
564          *      POSIX namespace.
565          */
566 again:
567         orig_dentry = NULL;
568         if (!dentry->d_inode->i_dos_name_extracted &&
569             dentry->short_name_len == 0)
570         {
571                 inode_for_each_dentry(other, dentry->d_inode) {
572                         if (other->short_name_len != 0) {
573                                 orig_dentry = dentry;
574                                 dentry = other;
575                                 break;
576                         }
577                 }
578         }
579         dentry->d_inode->i_dos_name_extracted = 1;
580         ntfs_inode *dir_ni = dentry_open_parent_ni(dentry, vol);
581         if (dir_ni) {
582                 ret = do_apply_dentry_ntfs(dentry, dir_ni, arg);
583                 if (ret == 0 && orig_dentry != NULL) {
584                         dentry = orig_dentry;
585                         goto again;
586                 }
587         } else {
588                 ret = WIMLIB_ERR_NTFS_3G;
589         }
590         return ret;
591 }
592
593 /* Transfers the 100-nanosecond precision timestamps from a WIM dentry to a NTFS
594  * inode */
595 int apply_dentry_timestamps_ntfs(struct wim_dentry *dentry, void *arg)
596 {
597         struct apply_args *args = arg;
598         ntfs_volume *vol = args->vol;
599         u8 *p;
600         u8 buf[24];
601         ntfs_inode *ni;
602         int ret;
603
604         DEBUG("Setting timestamps on `%s'", dentry->full_path_utf8);
605
606         ni = ntfs_pathname_to_inode(vol, NULL, dentry->full_path_utf8);
607         if (!ni) {
608                 ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
609                                  dentry->full_path_utf8);
610                 return WIMLIB_ERR_NTFS_3G;
611         }
612
613         p = buf;
614         p = put_u64(p, dentry->d_inode->i_creation_time);
615         p = put_u64(p, dentry->d_inode->i_last_write_time);
616         p = put_u64(p, dentry->d_inode->i_last_access_time);
617         ret = ntfs_inode_set_times(ni, (const char*)buf, 3 * sizeof(u64), 0);
618         if (ret != 0) {
619                 ERROR_WITH_ERRNO("Failed to set NTFS timestamps on `%s'",
620                                  dentry->full_path_utf8);
621                 ret = WIMLIB_ERR_NTFS_3G;
622         }
623
624         if (ntfs_inode_close(ni) != 0) {
625                 if (ret == 0)
626                         ret = WIMLIB_ERR_NTFS_3G;
627                 ERROR_WITH_ERRNO("Failed to close NTFS inode for `%s'",
628                                  dentry->full_path_utf8);
629         }
630         return ret;
631 }