cb64baa6af60189239dffde0594047a628423cee
[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                                    union wimlib_progress_info *progress_info)
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
94                 lte = inode_stream_lte_resolved(inode, stream_idx);
95
96                 if (stream_name_len) {
97                         /* Create an empty named stream. */
98                         ret = ntfs_attr_add(ni, AT_DATA, stream_name,
99                                             stream_name_len, NULL, 0);
100                         if (ret != 0) {
101                                 ERROR_WITH_ERRNO("Failed to create name data "
102                                                  "stream for extracted file "
103                                                  "`%s'",
104                                                  dentry->full_path_utf8);
105                                 ret = WIMLIB_ERR_NTFS_3G;
106                                 break;
107
108                         }
109                 }
110                 /* If there's no lookup table entry, it's an empty stream.
111                  * Otherwise, we must open the attribute and extract the data.
112                  * */
113                 if (lte) {
114                         ntfs_attr *na;
115
116                         na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len);
117                         if (!na) {
118                                 ERROR_WITH_ERRNO("Failed to open a data stream of "
119                                                  "extracted file `%s'",
120                                                  dentry->full_path_utf8);
121                                 ret = WIMLIB_ERR_NTFS_3G;
122                                 break;
123                         }
124                         ret = ntfs_attr_truncate_solid(na, wim_resource_size(lte));
125                         if (ret != 0) {
126                                 ntfs_attr_close(na);
127                                 break;
128                         }
129
130                         ret = extract_wim_resource_to_ntfs_attr(lte, na);
131                         ntfs_attr_close(na);
132                         if (ret != 0)
133                                 break;
134                         progress_info->extract.completed_bytes += wim_resource_size(lte);
135                 }
136                 if (stream_idx == inode->num_ads)
137                         break;
138                 stream_name = (ntfschar*)inode->ads_entries[stream_idx].stream_name;
139                 stream_name_len = inode->ads_entries[stream_idx].stream_name_len / 2;
140                 stream_idx++;
141         }
142         return ret;
143 }
144
145 /* Open the NTFS inode that corresponds to the parent of a WIM dentry. */
146 static ntfs_inode *dentry_open_parent_ni(const struct dentry *dentry,
147                                          ntfs_volume *vol)
148 {
149         char *p;
150         const char *dir_name;
151         ntfs_inode *dir_ni;
152         char orig;
153
154         p = dentry->full_path_utf8 + dentry->full_path_utf8_len;
155         do {
156                 p--;
157         } while (*p != '/');
158
159         orig = *p;
160         *p = '\0';
161         dir_name = dentry->full_path_utf8;
162         dir_ni = ntfs_pathname_to_inode(vol, NULL, dir_name);
163         if (!dir_ni) {
164                 ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
165                                  dir_name);
166         }
167         *p = orig;
168         return dir_ni;
169 }
170
171 /*
172  * Makes a NTFS hard link
173  *
174  * It is named @from_dentry->file_name and is located under the directory
175  * specified by @dir_ni, and it is made to point to the previously extracted
176  * file located at @inode->extracted_file.
177  *
178  * Return 0 on success, nonzero on failure.
179  */
180 static int apply_hardlink_ntfs(const struct dentry *from_dentry,
181                                const struct inode *inode,
182                                ntfs_inode *dir_ni,
183                                ntfs_inode **to_ni_ret)
184 {
185         int ret;
186         char *p;
187         char orig;
188         const char *dir_name;
189
190         ntfs_inode *to_ni;
191         ntfs_volume *vol;
192
193         if (ntfs_inode_close(dir_ni) != 0) {
194                 ERROR_WITH_ERRNO("Error closing directory");
195                 return WIMLIB_ERR_NTFS_3G;
196         }
197
198         vol = dir_ni->vol;
199
200         DEBUG("Extracting NTFS hard link `%s' => `%s'",
201               from_dentry->full_path_utf8, inode->extracted_file);
202
203         to_ni = ntfs_pathname_to_inode(vol, NULL, inode->extracted_file);
204         if (!to_ni) {
205                 ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
206                                  inode->extracted_file);
207                 return WIMLIB_ERR_NTFS_3G;
208         }
209
210         *to_ni_ret = to_ni;
211
212         dir_ni = dentry_open_parent_ni(from_dentry, vol);
213         if (!dir_ni)
214                 return WIMLIB_ERR_NTFS_3G;
215
216         ret = ntfs_link(to_ni, dir_ni,
217                         (ntfschar*)from_dentry->file_name,
218                         from_dentry->file_name_len / 2);
219         if (ret != 0) {
220                 ERROR_WITH_ERRNO("Could not create hard link `%s' => `%s'",
221                                  from_dentry->full_path_utf8,
222                                  inode->extracted_file);
223                 ret = WIMLIB_ERR_NTFS_3G;
224         }
225         return ret;
226 }
227
228 static int
229 apply_file_attributes_and_security_data(ntfs_inode *ni,
230                                         ntfs_inode *dir_ni,
231                                         const struct dentry *dentry,
232                                         const WIMStruct *w)
233 {
234         DEBUG("Setting NTFS file attributes on `%s' to %#"PRIx32,
235               dentry->full_path_utf8, dentry->d_inode->attributes);
236         int ret;
237         struct SECURITY_CONTEXT ctx;
238         u32 attributes_le32;
239         attributes_le32 = cpu_to_le32(dentry->d_inode->attributes);
240         memset(&ctx, 0, sizeof(ctx));
241         ctx.vol = ni->vol;
242         ret = ntfs_xattr_system_setxattr(&ctx, XATTR_NTFS_ATTRIB,
243                                          ni, dir_ni,
244                                          (const char*)&attributes_le32,
245                                          sizeof(u32), 0);
246         if (ret != 0) {
247                 ERROR("Failed to set NTFS file attributes on `%s'",
248                        dentry->full_path_utf8);
249                 return WIMLIB_ERR_NTFS_3G;
250         }
251         if (dentry->d_inode->security_id != -1) {
252                 const struct wim_security_data *sd;
253                 const char *descriptor;
254
255                 sd = wim_const_security_data(w);
256                 wimlib_assert(dentry->d_inode->security_id < sd->num_entries);
257                 descriptor = (const char *)sd->descriptors[dentry->d_inode->security_id];
258                 DEBUG("Applying security descriptor %d to `%s'",
259                       dentry->d_inode->security_id, dentry->full_path_utf8);
260
261                 ret = ntfs_xattr_system_setxattr(&ctx, XATTR_NTFS_ACL,
262                                                  ni, dir_ni, descriptor,
263                                                  sd->sizes[dentry->d_inode->security_id], 0);
264
265                 if (ret != 0) {
266                         ERROR_WITH_ERRNO("Failed to set security data on `%s'",
267                                         dentry->full_path_utf8);
268                         return WIMLIB_ERR_NTFS_3G;
269                 }
270         }
271         return 0;
272 }
273
274 static int apply_reparse_data(ntfs_inode *ni, const struct dentry *dentry,
275                               union wimlib_progress_info *progress_info)
276 {
277         struct lookup_table_entry *lte;
278         int ret = 0;
279
280         lte = inode_unnamed_lte_resolved(dentry->d_inode);
281
282         DEBUG("Applying reparse data to `%s'", dentry->full_path_utf8);
283
284         if (!lte) {
285                 ERROR("Could not find reparse data for `%s'",
286                       dentry->full_path_utf8);
287                 return WIMLIB_ERR_INVALID_DENTRY;
288         }
289
290         if (wim_resource_size(lte) >= 0xffff) {
291                 ERROR("Reparse data of `%s' is too long (%"PRIu64" bytes)",
292                       dentry->full_path_utf8, wim_resource_size(lte));
293                 return WIMLIB_ERR_INVALID_DENTRY;
294         }
295
296         u8 reparse_data_buf[8 + wim_resource_size(lte)];
297         u8 *p = reparse_data_buf;
298         p = put_u32(p, dentry->d_inode->reparse_tag); /* ReparseTag */
299         p = put_u16(p, wim_resource_size(lte)); /* ReparseDataLength */
300         p = put_u16(p, 0); /* Reserved */
301
302         ret = read_full_wim_resource(lte, p, 0);
303         if (ret != 0)
304                 return ret;
305
306         ret = ntfs_set_ntfs_reparse_data(ni, (char*)reparse_data_buf,
307                                          wim_resource_size(lte) + 8, 0);
308         if (ret != 0) {
309                 ERROR_WITH_ERRNO("Failed to set NTFS reparse data on `%s'",
310                                  dentry->full_path_utf8);
311                 return WIMLIB_ERR_NTFS_3G;
312         }
313         progress_info->extract.completed_bytes += wim_resource_size(lte);
314         return 0;
315 }
316
317 static int do_apply_dentry_ntfs(struct dentry *dentry, ntfs_inode *dir_ni,
318                                 struct apply_args *args);
319
320 /*
321  * If @dentry is part of a hard link group, search for hard-linked dentries in
322  * the same directory that have a nonempty DOS (short) filename.  There should
323  * be exactly 0 or 1 such dentries.  If there is 1, extract that dentry first,
324  * so that the DOS name is correctly associated with the corresponding long name
325  * in the Win32 namespace, and not any of the additional names in the POSIX
326  * namespace created from hard links.
327  */
328 static int preapply_dentry_with_dos_name(struct dentry *dentry,
329                                          ntfs_inode **dir_ni_p,
330                                          struct apply_args *args)
331 {
332         struct dentry *other;
333         struct dentry *dentry_with_dos_name;
334
335         dentry_with_dos_name = NULL;
336         inode_for_each_dentry(other, dentry->d_inode) {
337                 if (other != dentry && (dentry->parent == other->parent)
338                     && other->short_name_len)
339                 {
340                         if (dentry_with_dos_name) {
341                                 ERROR("Found multiple DOS names for file `%s' "
342                                       "in the same directory",
343                                       dentry_with_dos_name->full_path_utf8);
344                                 return WIMLIB_ERR_INVALID_DENTRY;
345                         }
346                         dentry_with_dos_name = other;
347                 }
348         }
349         /* If there's a dentry with a DOS name, extract it first */
350         if (dentry_with_dos_name && !dentry_with_dos_name->is_extracted) {
351                 char *p;
352                 const char *dir_name;
353                 char orig;
354                 int ret;
355                 ntfs_volume *vol = (*dir_ni_p)->vol;
356
357                 DEBUG("pre-applying DOS name `%s'",
358                       dentry_with_dos_name->full_path_utf8);
359                 ret = do_apply_dentry_ntfs(dentry_with_dos_name,
360                                            *dir_ni_p, args);
361                 if (ret != 0)
362                         return ret;
363
364                 *dir_ni_p = dentry_open_parent_ni(dentry, vol);
365                 if (!*dir_ni_p)
366                         return WIMLIB_ERR_NTFS_3G;
367         }
368         return 0;
369 }
370
371 /*
372  * Applies a WIM dentry to a NTFS filesystem.
373  *
374  * @dentry:  The WIM dentry to apply
375  * @dir_ni:  The NTFS inode for the parent directory
376  *
377  * @return:  0 on success; nonzero on failure.
378  */
379 static int do_apply_dentry_ntfs(struct dentry *dentry, ntfs_inode *dir_ni,
380                                 struct apply_args *args)
381 {
382         int ret = 0;
383         mode_t type;
384         ntfs_inode *ni = NULL;
385         bool is_hardlink = false;
386         ntfs_volume *vol = dir_ni->vol;
387         struct inode *inode = dentry->d_inode;
388         dentry->is_extracted = true;
389
390         if (inode->attributes & FILE_ATTRIBUTE_DIRECTORY) {
391                 type = S_IFDIR;
392         } else {
393                 /* If this dentry is hard-linked to any other dentries in the
394                  * same directory, make sure to apply the one (if any) with a
395                  * DOS name first.  Otherwise, NTFS-3g might not assign the file
396                  * names correctly. */
397                 if (dentry->short_name_len == 0) {
398                         ret = preapply_dentry_with_dos_name(dentry,
399                                                             &dir_ni, args);
400                         if (ret != 0)
401                                 return ret;
402                 }
403
404                 type = S_IFREG;
405
406                 if (inode->link_count > 1) {
407                         /* Already extracted another dentry in the hard link
408                          * group.  We can make a hard link instead of extracting
409                          * the file data. */
410                         if (inode->extracted_file) {
411                                 ret = apply_hardlink_ntfs(dentry, inode,
412                                                           dir_ni, &ni);
413                                 is_hardlink = true;
414                                 if (ret)
415                                         goto out_close_dir_ni;
416                                 else
417                                         goto out_set_dos_name;
418                         }
419                         /* Can't make a hard link; extract the file itself */
420                         FREE(inode->extracted_file);
421                         inode->extracted_file = STRDUP(dentry->full_path_utf8);
422                         if (!inode->extracted_file) {
423                                 ret = WIMLIB_ERR_NOMEM;
424                                 goto out_close_dir_ni;
425                         }
426                 }
427         }
428
429         /*
430          * Create a directory or file.
431          *
432          * Note: For symbolic links that are not directory junctions, pass
433          * S_IFREG here, since we manually set the reparse data later.
434          */
435         ni = ntfs_create(dir_ni, 0, (ntfschar*)dentry->file_name,
436                          dentry->file_name_len / 2, type);
437
438         if (!ni) {
439                 ERROR_WITH_ERRNO("Could not create NTFS object for `%s'",
440                                  dentry->full_path_utf8);
441                 ret = WIMLIB_ERR_NTFS_3G;
442                 goto out_close_dir_ni;
443         }
444
445         /* Write the data streams, unless this is a directory or reparse point
446          * */
447         if (!(inode->attributes & (FILE_ATTRIBUTE_REPARSE_POINT |
448                                    FILE_ATTRIBUTE_DIRECTORY))) {
449                 ret = write_ntfs_data_streams(ni, dentry, &args->progress);
450                 if (ret != 0)
451                         goto out_close_dir_ni;
452         }
453
454
455         ret = apply_file_attributes_and_security_data(ni, dir_ni,
456                                                       dentry, args->w);
457         if (ret != 0)
458                 goto out_close_dir_ni;
459
460         if (inode->attributes & FILE_ATTR_REPARSE_POINT) {
461                 ret = apply_reparse_data(ni, dentry, &args->progress);
462                 if (ret != 0)
463                         goto out_close_dir_ni;
464         }
465
466 out_set_dos_name:
467         /* Set DOS (short) name if given */
468         if (dentry->short_name_len != 0) {
469
470                 char *short_name_utf8;
471                 size_t short_name_utf8_len;
472                 ret = utf16_to_utf8(dentry->short_name,
473                                     dentry->short_name_len,
474                                     &short_name_utf8,
475                                     &short_name_utf8_len);
476                 if (ret != 0)
477                         goto out_close_dir_ni;
478
479                 if (is_hardlink) {
480                         char *p;
481                         char orig;
482                         const char *dir_name;
483
484                         /* ntfs_set_ntfs_dos_name() closes the inodes in the
485                          * wrong order if we have applied a hard link.   Close
486                          * them ourselves, then re-open then. */
487                         if (ntfs_inode_close(dir_ni) != 0) {
488                                 if (ret == 0)
489                                         ret = WIMLIB_ERR_NTFS_3G;
490                                 ERROR_WITH_ERRNO("Failed to close directory inode");
491                                 goto out_close_ni;
492                         }
493                         if (ntfs_inode_close(ni) != 0) {
494                                 if (ret == 0)
495                                         ret = WIMLIB_ERR_NTFS_3G;
496                                 ERROR_WITH_ERRNO("Failed to close hard link target inode");
497                                 goto out;
498                         }
499
500                         dir_ni = dentry_open_parent_ni(dentry, vol);
501                         if (!dir_ni) {
502                                 ret = WIMLIB_ERR_NTFS_3G;
503                                 goto out;
504                         }
505
506                         ni = ntfs_pathname_to_inode(vol, dir_ni,
507                                                     dentry->file_name_utf8);
508                         if (!ni) {
509                                 ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
510                                                  dentry->full_path_utf8);
511                                 ntfs_inode_close(dir_ni);
512                                 ret = WIMLIB_ERR_NTFS_3G;
513                                 goto out_close_dir_ni;
514                         }
515                 }
516
517                 DEBUG("Setting short (DOS) name of `%s' to %s",
518                       dentry->full_path_utf8, short_name_utf8);
519
520                 ret = ntfs_set_ntfs_dos_name(ni, dir_ni, short_name_utf8,
521                                              short_name_utf8_len, 0);
522                 FREE(short_name_utf8);
523                 if (ret != 0) {
524                         ERROR_WITH_ERRNO("Could not set DOS (short) name for `%s'",
525                                          dentry->full_path_utf8);
526                         ret = WIMLIB_ERR_NTFS_3G;
527                 }
528                 /* inodes have been closed by ntfs_set_ntfs_dos_name(). */
529                 return ret;
530         }
531
532 out_close_dir_ni:
533         if (ni && dir_ni) {
534                 if (ntfs_inode_close_in_dir(ni, dir_ni) != 0) {
535                         ni = NULL;
536                         if (ret == 0)
537                                 ret = WIMLIB_ERR_NTFS_3G;
538                         ERROR_WITH_ERRNO("Failed to close inode for `%s'",
539                                          dentry->full_path_utf8);
540                 }
541         }
542         if (ntfs_inode_close(dir_ni) != 0) {
543                 if (ret == 0)
544                         ret = WIMLIB_ERR_NTFS_3G;
545                 ERROR_WITH_ERRNO("Failed to close directory inode");
546         }
547 out_close_ni:
548         if (ni && ntfs_inode_close(ni) != 0) {
549                 if (ret == 0)
550                         ret = WIMLIB_ERR_NTFS_3G;
551                 ERROR_WITH_ERRNO("Failed to close inode for `%s'",
552                                  dentry->full_path_utf8);
553         }
554 out:
555         return ret;
556 }
557
558 static int apply_root_dentry_ntfs(const struct dentry *dentry,
559                                   ntfs_volume *vol, const WIMStruct *w)
560 {
561         ntfs_inode *ni;
562         int ret = 0;
563
564         wimlib_assert(dentry_is_directory(dentry));
565         ni = ntfs_pathname_to_inode(vol, NULL, "/");
566         if (!ni) {
567                 ERROR_WITH_ERRNO("Could not find root NTFS inode");
568                 return WIMLIB_ERR_NTFS_3G;
569         }
570         ret = apply_file_attributes_and_security_data(ni, ni, dentry, w);
571         if (ntfs_inode_close(ni) != 0) {
572                 ERROR_WITH_ERRNO("Failed to close NTFS inode for root "
573                                  "directory");
574                 ret = WIMLIB_ERR_NTFS_3G;
575         }
576         return ret;
577 }
578
579 /* Applies a WIM dentry to the NTFS volume */
580 int apply_dentry_ntfs(struct dentry *dentry, void *arg)
581 {
582         struct apply_args *args = arg;
583         ntfs_volume *vol             = args->vol;
584         int extract_flags            = args->extract_flags;
585         WIMStruct *w                 = args->w;
586         ntfs_inode *dir_ni;
587         char *p;
588         char orig;
589         const char *dir_name;
590
591         if (dentry->is_extracted)
592                 return 0;
593
594         if (extract_flags & WIMLIB_EXTRACT_FLAG_NO_STREAMS)
595                 if (inode_unnamed_lte_resolved(dentry->d_inode))
596                         return 0;
597
598         DEBUG("Applying dentry `%s' to NTFS", dentry->full_path_utf8);
599
600         if ((extract_flags & WIMLIB_EXTRACT_FLAG_VERBOSE) &&
601              args->progress_func)
602         {
603                 args->progress.extract.cur_path = dentry->full_path_utf8;
604                 args->progress_func(WIMLIB_PROGRESS_MSG_EXTRACT_DENTRY,
605                                     &args->progress);
606         }
607
608         if (dentry_is_root(dentry))
609                 return apply_root_dentry_ntfs(dentry, vol, w);
610
611         dir_ni = dentry_open_parent_ni(dentry, vol);
612         if (dir_ni)
613                 return do_apply_dentry_ntfs(dentry, dir_ni, arg);
614         else
615                 return WIMLIB_ERR_NTFS_3G;
616 }
617
618 int apply_dentry_timestamps_ntfs(struct dentry *dentry, void *arg)
619 {
620         struct apply_args *args = arg;
621         ntfs_volume *vol = args->vol;
622         u8 *p;
623         u8 buf[24];
624         ntfs_inode *ni;
625         int ret = 0;
626
627         DEBUG("Setting timestamps on `%s'", dentry->full_path_utf8);
628
629         ni = ntfs_pathname_to_inode(vol, NULL, dentry->full_path_utf8);
630         if (!ni) {
631                 ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
632                                  dentry->full_path_utf8);
633                 return WIMLIB_ERR_NTFS_3G;
634         }
635
636         p = buf;
637         p = put_u64(p, dentry->d_inode->creation_time);
638         p = put_u64(p, dentry->d_inode->last_write_time);
639         p = put_u64(p, dentry->d_inode->last_access_time);
640         ret = ntfs_inode_set_times(ni, (const char*)buf, 3 * sizeof(u64), 0);
641         if (ret != 0) {
642                 ERROR_WITH_ERRNO("Failed to set NTFS timestamps on `%s'",
643                                  dentry->full_path_utf8);
644                 ret = WIMLIB_ERR_NTFS_3G;
645         }
646
647         if (ntfs_inode_close(ni) != 0) {
648                 if (ret == 0)
649                         ret = WIMLIB_ERR_NTFS_3G;
650                 ERROR_WITH_ERRNO("Failed to close NTFS inode for `%s'",
651                                  dentry->full_path_utf8);
652         }
653         return ret;
654 }