NTFS filename namespace issues...
[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()).  I am assuming this is an acceptable behavior; however, it's
202  * possible that the original name was actually in the Win32 namespace.  Note
203  * that the WIM format does not provide enough information to distinguish Win32
204  * names from POSIX names in all cases.
205  *
206  * Return 0 on success, nonzero on failure.
207  */
208 static int apply_ntfs_hardlink(const struct wim_dentry *from_dentry,
209                                const struct wim_inode *inode,
210                                ntfs_inode **dir_ni_p)
211 {
212         int ret;
213         ntfs_inode *to_ni;
214         ntfs_inode *dir_ni;
215         ntfs_volume *vol;
216
217         dir_ni = *dir_ni_p;
218         vol = dir_ni->vol;
219         ret = ntfs_inode_close(dir_ni);
220         *dir_ni_p = NULL;
221         if (ret != 0) {
222                 ERROR_WITH_ERRNO("Error closing directory");
223                 return WIMLIB_ERR_NTFS_3G;
224         }
225
226         DEBUG("Extracting NTFS hard link `%s' => `%s'",
227               from_dentry->full_path_utf8, inode->i_extracted_file);
228
229         to_ni = ntfs_pathname_to_inode(vol, NULL, inode->i_extracted_file);
230         if (!to_ni) {
231                 ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
232                                  inode->i_extracted_file);
233                 return WIMLIB_ERR_NTFS_3G;
234         }
235
236         dir_ni = dentry_open_parent_ni(from_dentry, vol);
237         if (!dir_ni) {
238                 ntfs_inode_close(to_ni);
239                 return WIMLIB_ERR_NTFS_3G;
240         }
241
242         *dir_ni_p = dir_ni;
243
244         ret = ntfs_link(to_ni, dir_ni,
245                         (ntfschar*)from_dentry->file_name,
246                         from_dentry->file_name_len / 2);
247         if (ntfs_inode_close_in_dir(to_ni, dir_ni) || ret != 0) {
248                 ERROR_WITH_ERRNO("Could not create hard link `%s' => `%s'",
249                                  from_dentry->full_path_utf8,
250                                  inode->i_extracted_file);
251                 ret = WIMLIB_ERR_NTFS_3G;
252         }
253         return ret;
254 }
255
256 /* Transfers file attributes and possibly a security descriptor from a WIM inode
257  * to a NTFS inode.
258  *
259  * @ni:      The NTFS inode to apply the metadata to.
260  * @dir_ni:  The NTFS inode for a directory containing @ni.
261  * @dentry:  The WIM dentry whose inode contains the metadata to apply.
262  * @w:       The WIMStruct for the WIM, through which the table of security
263  *              descriptors can be accessed.
264  *
265  * Returns 0 on success, nonzero on failure.
266  */
267 static int
268 apply_file_attributes_and_security_data(ntfs_inode *ni,
269                                         ntfs_inode *dir_ni,
270                                         const struct wim_dentry *dentry,
271                                         const WIMStruct *w)
272 {
273         int ret;
274         struct SECURITY_CONTEXT ctx;
275         u32 attributes_le32;
276         const struct wim_inode *inode;
277
278         inode = dentry->d_inode;
279
280         DEBUG("Setting NTFS file attributes on `%s' to %#"PRIx32,
281               dentry->full_path_utf8, inode->i_attributes);
282
283         attributes_le32 = cpu_to_le32(inode->i_attributes);
284         memset(&ctx, 0, sizeof(ctx));
285         ctx.vol = ni->vol;
286         ret = ntfs_xattr_system_setxattr(&ctx, XATTR_NTFS_ATTRIB,
287                                          ni, dir_ni,
288                                          (const char*)&attributes_le32,
289                                          sizeof(u32), 0);
290         if (ret != 0) {
291                 ERROR("Failed to set NTFS file attributes on `%s'",
292                        dentry->full_path_utf8);
293                 return WIMLIB_ERR_NTFS_3G;
294         }
295         if (inode->i_security_id != -1) {
296                 const char *desc;
297                 const struct wim_security_data *sd;
298
299                 sd = wim_const_security_data(w);
300                 wimlib_assert(inode->i_security_id < sd->num_entries);
301                 desc = (const char *)sd->descriptors[inode->i_security_id];
302                 DEBUG("Applying security descriptor %d to `%s'",
303                       inode->i_security_id, dentry->full_path_utf8);
304
305                 ret = ntfs_xattr_system_setxattr(&ctx, XATTR_NTFS_ACL,
306                                                  ni, dir_ni, desc,
307                                                  sd->sizes[inode->i_security_id], 0);
308
309                 if (ret != 0) {
310                         ERROR_WITH_ERRNO("Failed to set security data on `%s'",
311                                         dentry->full_path_utf8);
312                         return WIMLIB_ERR_NTFS_3G;
313                 }
314         }
315         return 0;
316 }
317
318 /*
319  * Transfers the reparse data from a WIM inode (which must represent a reparse
320  * point) to a NTFS inode.
321  */
322 static int apply_reparse_data(ntfs_inode *ni, const struct wim_dentry *dentry,
323                               union wimlib_progress_info *progress_info)
324 {
325         struct wim_lookup_table_entry *lte;
326         int ret = 0;
327
328         lte = inode_unnamed_lte_resolved(dentry->d_inode);
329
330         DEBUG("Applying reparse data to `%s'", dentry->full_path_utf8);
331
332         if (!lte) {
333                 ERROR("Could not find reparse data for `%s'",
334                       dentry->full_path_utf8);
335                 return WIMLIB_ERR_INVALID_DENTRY;
336         }
337
338         if (wim_resource_size(lte) >= 0xffff) {
339                 ERROR("Reparse data of `%s' is too long (%"PRIu64" bytes)",
340                       dentry->full_path_utf8, wim_resource_size(lte));
341                 return WIMLIB_ERR_INVALID_DENTRY;
342         }
343
344         u8 reparse_data_buf[8 + wim_resource_size(lte)];
345         u8 *p = reparse_data_buf;
346         p = put_u32(p, dentry->d_inode->i_reparse_tag); /* ReparseTag */
347         p = put_u16(p, wim_resource_size(lte)); /* ReparseDataLength */
348         p = put_u16(p, 0); /* Reserved */
349
350         ret = read_full_wim_resource(lte, p, 0);
351         if (ret != 0)
352                 return ret;
353
354         ret = ntfs_set_ntfs_reparse_data(ni, (char*)reparse_data_buf,
355                                          wim_resource_size(lte) + 8, 0);
356         if (ret != 0) {
357                 ERROR_WITH_ERRNO("Failed to set NTFS reparse data on `%s'",
358                                  dentry->full_path_utf8);
359                 return WIMLIB_ERR_NTFS_3G;
360         }
361         progress_info->extract.completed_bytes += wim_resource_size(lte);
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 wim_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         ntfs_volume *vol = dir_ni->vol;
380         struct wim_inode *inode = dentry->d_inode;
381         dentry->is_extracted = 1;
382
383         if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) {
384                 type = S_IFDIR;
385         } else {
386                 type = S_IFREG;
387                 if (inode->i_nlink > 1) {
388                         /* Inode has multiple dentries referencing it. */
389
390                         if (inode->i_extracted_file) {
391                                 /* Already extracted another dentry in the hard
392                                  * link group.  Make a hard link instead of
393                                  * extracting the file data. */
394                                 ret = apply_ntfs_hardlink(dentry, inode,
395                                                           &dir_ni);
396                                 goto out_close_dir_ni;
397                         } else {
398                                 /* None of the dentries of this inode have been
399                                  * extracted yet, so go ahead and extract the
400                                  * first one. */
401                                 FREE(inode->i_extracted_file);
402                                 inode->i_extracted_file = STRDUP(dentry->full_path_utf8);
403                                 if (!inode->i_extracted_file) {
404                                         ret = WIMLIB_ERR_NOMEM;
405                                         goto out_close_dir_ni;
406                                 }
407                         }
408                 }
409         }
410
411         /* Create a NTFS directory or file.
412          *
413          * Note: For symbolic links that are not directory junctions, S_IFREG is
414          * passed here, since the reparse data and file attributes are set
415          * later. */
416         ni = ntfs_create(dir_ni, 0, (ntfschar*)dentry->file_name,
417                          dentry->file_name_len / 2, type);
418
419         if (!ni) {
420                 ERROR_WITH_ERRNO("Could not create NTFS inode for `%s'",
421                                  dentry->full_path_utf8);
422                 ret = WIMLIB_ERR_NTFS_3G;
423                 goto out_close_dir_ni;
424         }
425
426         /* Write the data streams, unless this is a directory or reparse point
427          * */
428         if (!(inode->i_attributes & (FILE_ATTRIBUTE_REPARSE_POINT |
429                                    FILE_ATTRIBUTE_DIRECTORY))) {
430                 ret = write_ntfs_data_streams(ni, dentry, &args->progress);
431                 if (ret != 0)
432                         goto out_close_dir_ni;
433         }
434
435
436         ret = apply_file_attributes_and_security_data(ni, dir_ni, dentry,
437                                                       args->w);
438         if (ret != 0)
439                 goto out_close_dir_ni;
440
441         if (inode->i_attributes & FILE_ATTR_REPARSE_POINT) {
442                 ret = apply_reparse_data(ni, dentry, &args->progress);
443                 if (ret != 0)
444                         goto out_close_dir_ni;
445         }
446
447         /* Set DOS (short) name if given */
448         if (dentry->short_name_len != 0) {
449                 char *short_name_utf8;
450                 size_t short_name_utf8_len;
451                 ret = utf16_to_utf8(dentry->short_name,
452                                     dentry->short_name_len,
453                                     &short_name_utf8,
454                                     &short_name_utf8_len);
455                 if (ret != 0)
456                         goto out_close_dir_ni;
457
458                 DEBUG("Setting short (DOS) name of `%s' to %s",
459                       dentry->full_path_utf8, short_name_utf8);
460
461                 ret = ntfs_set_ntfs_dos_name(ni, dir_ni, short_name_utf8,
462                                              short_name_utf8_len, 0);
463                 FREE(short_name_utf8);
464                 if (ret != 0) {
465                         ERROR_WITH_ERRNO("Could not set DOS (short) name for `%s'",
466                                          dentry->full_path_utf8);
467                         ret = WIMLIB_ERR_NTFS_3G;
468                 }
469                 /* inodes have been closed by ntfs_set_ntfs_dos_name(). */
470                 goto out;
471         }
472 out_close_dir_ni:
473         if (dir_ni) {
474                 if (ni) {
475                         if (ntfs_inode_close_in_dir(ni, dir_ni)) {
476                                 if (ret == 0)
477                                         ret = WIMLIB_ERR_NTFS_3G;
478                                 ERROR_WITH_ERRNO("Failed to close inode for `%s'",
479                                                  dentry->full_path_utf8);
480                         }
481                 }
482                 if (ntfs_inode_close(dir_ni)) {
483                         if (ret == 0)
484                                 ret = WIMLIB_ERR_NTFS_3G;
485                         ERROR_WITH_ERRNO("Failed to close inode of directory "
486                                          "containing `%s'", dentry->full_path_utf8);
487                 }
488         }
489 out:
490         return ret;
491 }
492
493 static int apply_root_dentry_ntfs(const struct wim_dentry *dentry,
494                                   ntfs_volume *vol, const WIMStruct *w)
495 {
496         ntfs_inode *ni;
497         int ret = 0;
498
499         ni = ntfs_pathname_to_inode(vol, NULL, "/");
500         if (!ni) {
501                 ERROR_WITH_ERRNO("Could not find root NTFS inode");
502                 return WIMLIB_ERR_NTFS_3G;
503         }
504         ret = apply_file_attributes_and_security_data(ni, ni, dentry, w);
505         if (ntfs_inode_close(ni) != 0) {
506                 ERROR_WITH_ERRNO("Failed to close NTFS inode for root "
507                                  "directory");
508                 ret = WIMLIB_ERR_NTFS_3G;
509         }
510         return ret;
511 }
512
513 /* Applies a WIM dentry to the NTFS volume */
514 int apply_dentry_ntfs(struct wim_dentry *dentry, void *arg)
515 {
516         struct apply_args *args = arg;
517         ntfs_volume *vol = args->vol;
518         WIMStruct *w = args->w;
519         struct wim_dentry *orig_dentry;
520         struct wim_dentry *other;
521         int ret;
522
523         /* Treat the root dentry specially. */
524         if (dentry_is_root(dentry))
525                 return apply_root_dentry_ntfs(dentry, vol, w);
526         /* NTFS filename namespaces need careful consideration.  A name for a
527          * NTFS file may be in either the POSIX, Win32, DOS, or Win32+DOS
528          * namespaces.  The following list of assumptions and facts clarify the
529          * way that WIM dentries are mapped to NTFS files.  The statements
530          * marked ASSUMPTION are statements I am assuming to be true due to the
531          * lack of documentation; they are verified in verify_dentry() and
532          * verify_inode() in verify.c.
533          *
534          * - ASSUMPTION: The root WIM dentry has neither a "long name" nor a
535          *   "short name".
536          *
537          * - ASSUMPTION: Every WIM dentry other than the root directory provides
538          *   a non-empty "long name" and a possibly empty "short name".  The
539          *   "short name" corresponds to the DOS name of the file, while the
540          *   "long name" may be Win32 or POSIX.
541          *
542          *   XXX It may actually be legal to have a short name but no long name
543          *
544          * - FACT: If a dentry has a "long name" but no "short name", then it is
545          *   ambigious whether the name is POSIX or Win32+DOS, unless the name
546          *   is a valid POSIX name but not a valid Win32+DOS name.  wimlib
547          *   currently will always create POSIX names for these files, as this
548          *   is the behavior of the ntfs_create() and ntfs_link() functions.
549          *
550          * - FACT: Multiple WIM dentries may correspond to the same underlying
551          *   inode, as provided at this point in the code by the d_inode member.
552          */
553
554
555         /* Currently wimlib does not apply DOS names to hard linked files due to
556          * issues with ntfs-3g, so the following is commented out. */
557 #if 0
558 again:
559         /*
560          * libntfs-3g requires that for an NTFS inode with a DOS name, the
561          * corresponding long name be extracted first so that the DOS name is
562          * associated with the correct long name.  Note that by the last
563          * ASSUMPTION above, a NTFS inode can have at most one DOS name (i.e. a
564          * WIM inode can have at most one non-empty short name).
565          *
566          * Therefore, search for an alias of this dentry that has a short name,
567          * and extract it first unless it was already extracted.
568          */
569         orig_dentry = NULL;
570         if (!dentry->d_inode->i_dos_name_extracted) {
571                 inode_for_each_dentry(other, dentry->d_inode) {
572                         if (other->short_name_len && other != dentry &&
573                             !other->is_extracted)
574                         {
575                                 orig_dentry = dentry;
576                                 dentry = other;
577                                 break;
578                         }
579                 }
580                 dentry->d_inode->i_dos_name_extracted = 1;
581         }
582 #endif
583
584         ntfs_inode *dir_ni = dentry_open_parent_ni(dentry, vol);
585         if (dir_ni)
586                 ret = do_apply_dentry_ntfs(dentry, dir_ni, arg);
587         else
588                 ret = WIMLIB_ERR_NTFS_3G;
589
590 #if 0
591         if (ret == 0 && orig_dentry) {
592                 dentry = orig_dentry;
593                 goto again;
594         }
595 #endif
596         return ret;
597 }
598
599 /* Transfers the 100-nanosecond precision timestamps from a WIM dentry to a NTFS
600  * inode */
601 int apply_dentry_timestamps_ntfs(struct wim_dentry *dentry, void *arg)
602 {
603         struct apply_args *args = arg;
604         ntfs_volume *vol = args->vol;
605         u8 *p;
606         u8 buf[24];
607         ntfs_inode *ni;
608         int ret;
609
610         DEBUG("Setting timestamps on `%s'", dentry->full_path_utf8);
611
612         ni = ntfs_pathname_to_inode(vol, NULL, dentry->full_path_utf8);
613         if (!ni) {
614                 ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
615                                  dentry->full_path_utf8);
616                 return WIMLIB_ERR_NTFS_3G;
617         }
618
619         p = buf;
620         p = put_u64(p, dentry->d_inode->i_creation_time);
621         p = put_u64(p, dentry->d_inode->i_last_write_time);
622         p = put_u64(p, dentry->d_inode->i_last_access_time);
623         ret = ntfs_inode_set_times(ni, (const char*)buf, 3 * sizeof(u64), 0);
624         if (ret != 0) {
625                 ERROR_WITH_ERRNO("Failed to set NTFS timestamps on `%s'",
626                                  dentry->full_path_utf8);
627                 ret = WIMLIB_ERR_NTFS_3G;
628         }
629
630         if (ntfs_inode_close(ni) != 0) {
631                 if (ret == 0)
632                         ret = WIMLIB_ERR_NTFS_3G;
633                 ERROR_WITH_ERRNO("Failed to close NTFS inode for `%s'",
634                                  dentry->full_path_utf8);
635         }
636         return ret;
637 }