e8e86c1ecd5efcd1bd5cd360079773f7fb8c1d56
[wimlib] / src / ntfs-apply.c
1 /*
2  * ntfs-apply.c
3  *
4  * Apply a WIM image to a NTFS volume, restoring everything we can, including
5  * security data and alternate data streams.  There should be no loss of
6  * information.
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 Lesser General Public License as published by the Free
16  * Software Foundation; either version 2.1 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 Lesser General Public License for more
22  * details.
23  *
24  * You should have received a copy of the GNU Lesser General Public License
25  * along with wimlib; if not, see http://www.gnu.org/licenses/.
26  */
27
28 #include "config.h"
29 #include "wimlib_internal.h"
30
31
32 #ifdef WITH_NTFS_3G
33 #include "dentry.h"
34 #include "lookup_table.h"
35 #include "io.h"
36 #include <ntfs-3g/layout.h>
37 #include <ntfs-3g/acls.h>
38 #include <ntfs-3g/attrib.h>
39 #include <ntfs-3g/misc.h>
40 #include <ntfs-3g/reparse.h>
41 #include <ntfs-3g/security.h>
42 #include <ntfs-3g/volume.h>
43 #include <stdlib.h>
44 #include <unistd.h>
45
46 struct ntfs_apply_args {
47         ntfs_volume *vol;
48         int extract_flags;
49         WIMStruct *w;
50 };
51
52 extern int ntfs_inode_set_security(ntfs_inode *ni, u32 selection,
53                                    const char *attr);
54 extern int ntfs_inode_set_attributes(ntfs_inode *ni, s32 attrib);
55
56 /* 
57  * Extracts a WIM resource to a NTFS attribute.
58  */
59 static int
60 extract_wim_resource_to_ntfs_attr(const struct lookup_table_entry *lte,
61                                   ntfs_attr *na)
62 {
63         u64 bytes_remaining = wim_resource_size(lte);
64         u8 buf[min(WIM_CHUNK_SIZE, bytes_remaining)];
65         u64 offset = 0;
66         int ret = 0;
67         u8 hash[SHA1_HASH_SIZE];
68
69         SHA_CTX ctx;
70         sha1_init(&ctx);
71
72         while (bytes_remaining) {
73                 u64 to_read = min(bytes_remaining, WIM_CHUNK_SIZE);
74                 ret = read_wim_resource(lte, buf, to_read, offset, false);
75                 if (ret != 0)
76                         break;
77                 sha1_update(&ctx, buf, to_read);
78                 if (ntfs_attr_pwrite(na, offset, to_read, buf) != to_read) {
79                         ERROR_WITH_ERRNO("Error extracting WIM resource");
80                         return WIMLIB_ERR_WRITE;
81                 }
82                 bytes_remaining -= to_read;
83                 offset += to_read;
84         }
85         sha1_final(hash, &ctx);
86         if (!hashes_equal(hash, lte->hash)) {
87                 ERROR("Invalid checksum on a WIM resource "
88                       "(detected when extracting to NTFS stream)");
89                 ERROR("The following WIM resource is invalid:");
90                 print_lookup_table_entry(lte);
91                 return WIMLIB_ERR_INVALID_RESOURCE_HASH;
92         }
93         return 0;
94 }
95
96 /* Writes the data streams to a NTFS file
97  *
98  * @ni:      The NTFS inode for the file.
99  * @dentry:  The directory entry in the WIM file.
100  * @w:       The WIMStruct for the WIM containing the image we are applying.
101  *
102  * Returns 0 on success, nonzero on failure.
103  */
104 static int write_ntfs_data_streams(ntfs_inode *ni, const struct dentry *dentry,
105                                    WIMStruct *w)
106 {
107         int ret = 0;
108         unsigned stream_idx = 0;
109         ntfschar *stream_name = AT_UNNAMED;
110         u32 stream_name_len = 0;
111
112         DEBUG("Writing %u NTFS data stream%s for `%s'",
113               dentry->num_ads + 1,
114               (dentry->num_ads == 0 ? "" : "s"),
115               dentry->full_path_utf8);
116
117         while (1) {
118                 struct lookup_table_entry *lte;
119                 ntfs_attr *na;
120
121                 lte = dentry_stream_lte(dentry, stream_idx, w->lookup_table);
122                 na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len);
123                 if (!na) {
124                         ERROR_WITH_ERRNO("Failed to open a data stream of "
125                                          "extracted file `%s'",
126                                          dentry->full_path_utf8);
127                         ret = WIMLIB_ERR_NTFS_3G;
128                         break;
129                 }
130                 if (lte)
131                         ret = extract_wim_resource_to_ntfs_attr(lte, na);
132                 ntfs_attr_close(na);
133                 if (ret != 0)
134                         break;
135                 if (stream_idx == dentry->num_ads)
136                         break;
137                 stream_name = (ntfschar*)dentry->ads_entries[stream_idx].stream_name;
138                 stream_name_len = dentry->ads_entries[stream_idx].stream_name_len / 2;
139                 stream_idx++;
140         }
141         return ret;
142 }
143
144 /*
145  * Makes a NTFS hard link
146  *
147  * It is named @from_dentry->file_name and is located under the directory
148  * specified by @dir_ni, and it is made to point to the previously extracted
149  * file located at @to_dentry->extracted_file.
150  *
151  * Return 0 on success, nonzero on failure.
152  */
153 static int wim_apply_hardlink_ntfs(const struct dentry *from_dentry,
154                                    const struct dentry *to_dentry,
155                                    ntfs_inode *dir_ni,
156                                    ntfs_inode **to_ni_ret)
157 {
158         int ret;
159         char *p;
160         char orig;
161         const char *dir_name;
162
163         ntfs_inode *to_ni;
164         ntfs_volume *vol;
165
166         wimlib_assert(dentry_is_regular_file(from_dentry)
167                         && dentry_is_regular_file(to_dentry));
168
169         if (ntfs_inode_close(dir_ni) != 0) {
170                 ERROR_WITH_ERRNO("Error closing directory");
171                 return WIMLIB_ERR_NTFS_3G;
172         }
173
174         vol = dir_ni->vol;
175
176         DEBUG("Extracting NTFS hard link `%s' => `%s'",
177               from_dentry->full_path_utf8, to_dentry->extracted_file);
178
179         to_ni = ntfs_pathname_to_inode(vol, NULL,
180                                        to_dentry->extracted_file);
181         if (!to_ni) {
182                 ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
183                                  to_dentry->extracted_file);
184                 return WIMLIB_ERR_NTFS_3G;
185         }
186         p = from_dentry->full_path_utf8 + from_dentry->full_path_utf8_len;
187         do {
188                 p--;
189         } while (*p != '/');
190
191         orig = *p;
192         *p = '\0';
193         dir_name = from_dentry->full_path_utf8;
194
195         dir_ni = ntfs_pathname_to_inode(vol, NULL, dir_name);
196         if (!dir_ni) {
197                 ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
198                                  from_dentry->full_path_utf8);
199                 *p = orig;
200                 return WIMLIB_ERR_NTFS_3G;
201         }
202         *p = orig;
203
204         ret = ntfs_link(to_ni, dir_ni,
205                         (ntfschar*)from_dentry->file_name,
206                         from_dentry->file_name_len / 2);
207         if (ret != 0) {
208                 ERROR_WITH_ERRNO("Could not create hard link `%s' => `%s'",
209                                  from_dentry->full_path_utf8,
210                                  to_dentry->extracted_file);
211                 ret = WIMLIB_ERR_NTFS_3G;
212         }
213         *to_ni_ret = to_ni;
214         return ret;
215 }
216
217 static int
218 apply_file_attributes_and_security_data(ntfs_inode *ni,
219                                         const struct dentry *dentry,
220                                         const WIMStruct *w)
221 {
222         DEBUG("Setting NTFS file attributes on `%s' to %#"PRIx32,
223               dentry->full_path_utf8, dentry->attributes);
224         if (!ntfs_inode_set_attributes(ni, dentry->attributes)) {
225                 ERROR("Failed to set NTFS file attributes on `%s'",
226                        dentry->full_path_utf8);
227                 return WIMLIB_ERR_NTFS_3G;
228         }
229
230         if (dentry->security_id != -1) {
231                 const struct wim_security_data *sd;
232                 
233                 sd = wim_const_security_data(w);
234                 wimlib_assert(dentry->security_id < sd->num_entries);
235                 DEBUG("Applying security descriptor %d to `%s'",
236                       dentry->security_id, dentry->full_path_utf8);
237                 u32 selection = OWNER_SECURITY_INFORMATION |
238                                 GROUP_SECURITY_INFORMATION |
239                                 DACL_SECURITY_INFORMATION  |
240                                 SACL_SECURITY_INFORMATION;
241                                 
242                 if (!ntfs_inode_set_security(ni, selection,
243                                              (const char*)sd->descriptors[dentry->security_id]))
244                 {
245                         ERROR_WITH_ERRNO("Failed to set security data on `%s'",
246                                         dentry->full_path_utf8);
247                         return WIMLIB_ERR_NTFS_3G;
248                 }
249         }
250         return 0;
251 }
252
253 static int apply_reparse_data(ntfs_inode *ni, const struct dentry *dentry,
254                               const WIMStruct *w)
255 {
256         struct lookup_table_entry *lte;
257         int ret = 0;
258
259         wimlib_assert(dentry->attributes & FILE_ATTRIBUTE_REPARSE_POINT);
260
261         lte = dentry_unnamed_lte(dentry, w->lookup_table);
262
263         DEBUG("Applying reparse data to `%s'", dentry->full_path_utf8);
264
265         if (!lte) {
266                 ERROR("Could not find reparse data for `%s'",
267                       dentry->full_path_utf8);
268                 return WIMLIB_ERR_INVALID_DENTRY;
269         }
270
271         if (wim_resource_size(lte) >= 0xffff) {
272                 ERROR("Reparse data of `%s' is too long (%lu bytes)",
273                       dentry->full_path_utf8, wim_resource_size(lte));
274                 return WIMLIB_ERR_INVALID_DENTRY;
275         }
276
277         u8 reparse_data_buf[8 + wim_resource_size(lte)];
278         u8 *p = reparse_data_buf;
279         p = put_u32(p, dentry->reparse_tag); /* ReparseTag */
280         p = put_u16(p, wim_resource_size(lte)); /* ReparseDataLength */
281         p = put_u16(p, 0); /* Reserved */
282
283         ret = read_full_wim_resource(lte, p);
284         if (ret != 0)
285                 return ret;
286
287         ret = ntfs_set_ntfs_reparse_data(ni, (char*)reparse_data_buf,
288                                          wim_resource_size(lte) + 8, 0);
289         if (ret != 0) {
290                 ERROR_WITH_ERRNO("Failed to set NTFS reparse data on `%s'",
291                                  dentry->full_path_utf8);
292                 return WIMLIB_ERR_NTFS_3G;
293         }
294         return 0;
295 }
296
297 static int do_wim_apply_dentry_ntfs(struct dentry *dentry, ntfs_inode *dir_ni,
298                                     WIMStruct *w);
299
300 /* 
301  * If @dentry is part of a hard link group, search for hard-linked dentries in
302  * the same directory that have a nonempty DOS (short) filename.  There should
303  * be exactly 0 or 1 such dentries.  If there is 1, extract that dentry first,
304  * so that the DOS name is correctly associated with the corresponding long name
305  * in the Win32 namespace, and not any of the additional names in the POSIX
306  * namespace created from hard links.
307  */
308 static int preapply_dentry_with_dos_name(struct dentry *dentry,
309                                          ntfs_inode **dir_ni_p,
310                                          WIMStruct *w)
311 {
312         int ret;
313         struct dentry *other;
314         struct dentry *dentry_with_dos_name;
315
316         if (dentry->link_group_list.next == &dentry->link_group_list)
317                 return 0;
318
319         dentry_with_dos_name = NULL;
320         list_for_each_entry(other, &dentry->link_group_list,
321                             link_group_list)
322         {
323                 if (dentry->parent == other->parent && other->short_name_len) {
324                         if (dentry_with_dos_name) {
325                                 ERROR("Found multiple DOS names for file `%s' "
326                                       "in the same directory",
327                                       dentry_with_dos_name->full_path_utf8);
328                                 return WIMLIB_ERR_INVALID_DENTRY;
329                         }
330                         dentry_with_dos_name = other;
331                 }
332         }
333         /* If there's a dentry with a DOS name, extract it first */
334         if (dentry_with_dos_name && !dentry_with_dos_name->extracted_file) {
335                 char *p;
336                 const char *dir_name;
337                 char orig;
338                 ntfs_volume *vol = (*dir_ni_p)->vol;
339
340                 DEBUG("pre-applying DOS name `%s'",
341                       dentry_with_dos_name->full_path_utf8);
342                 ret = do_wim_apply_dentry_ntfs(dentry_with_dos_name,
343                                                *dir_ni_p, w);
344                 if (ret != 0)
345                         return ret;
346                 p = dentry->full_path_utf8 + dentry->full_path_utf8_len;
347                 do {
348                         p--;
349                 } while (*p != '/');
350
351                 orig = *p;
352                 *p = '\0';
353                 dir_name = dentry->full_path_utf8;
354
355                 *dir_ni_p = ntfs_pathname_to_inode(vol, NULL, dir_name);
356                 *p = orig;
357                 if (!*dir_ni_p) {
358                         ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
359                                          dir_name);
360                         return WIMLIB_ERR_NTFS_3G;
361                 }
362         }
363         return 0;
364 }
365
366 /* 
367  * Applies a WIM dentry to a NTFS filesystem.
368  *
369  * @dentry:  The WIM dentry to apply
370  * @dir_ni:  The NTFS inode for the parent directory
371  * @w:       The WIMStruct for the WIM containing the image we are applying.
372  *
373  * @return:  0 on success; nonzero on failure.
374  */
375 static int do_wim_apply_dentry_ntfs(struct dentry *dentry, ntfs_inode *dir_ni,
376                                     WIMStruct *w)
377 {
378         int ret = 0;
379         mode_t type;
380         ntfs_inode *ni = NULL;
381         bool is_hardlink = false;
382         ntfs_volume *vol = dir_ni->vol;
383
384         if (dentry->attributes & FILE_ATTRIBUTE_DIRECTORY) {
385                 type = S_IFDIR;
386         } else {
387                 struct dentry *other;
388
389                 /* Apply hard-linked directory in same directory with DOS name
390                  * (if there is one) before this dentry */
391                 ret = preapply_dentry_with_dos_name(dentry, &dir_ni, w);
392                 if (ret != 0)
393                         return ret;
394
395                 type = S_IFREG;
396                 /* See if we can make a hard link */
397                 list_for_each_entry(other, &dentry->link_group_list,
398                                     link_group_list) {
399                         if (other->extracted_file) {
400                                 /* Already extracted another dentry in the hard
401                                  * link group.  We can make a hard link instead
402                                  * of extracting the file data. */
403                                 ret = wim_apply_hardlink_ntfs(dentry, other,
404                                                               dir_ni, &ni);
405                                 is_hardlink = true;
406                                 if (ret != 0)
407                                         goto out_close_dir_ni;
408                                 else
409                                         goto out_set_dos_name;
410                         }
411                 }
412                 /* Can't make a hard link; extract the file itself */
413                 FREE(dentry->extracted_file);
414                 dentry->extracted_file = STRDUP(dentry->full_path_utf8);
415                 if (!dentry->extracted_file) {
416                         ERROR("Failed to allocate memory for filename");
417                         return WIMLIB_ERR_NOMEM;
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 (!dentry_is_directory(dentry) &&
440              !(dentry->attributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
441                 ret = write_ntfs_data_streams(ni, dentry, w);
442                 if (ret != 0)
443                         goto out_close_dir_ni;
444         }
445
446
447         ret = apply_file_attributes_and_security_data(ni, dentry, w);
448         if (ret != 0)
449                 goto out_close_dir_ni;
450
451         if (dentry->attributes & FILE_ATTR_REPARSE_POINT) {
452                 ret = apply_reparse_data(ni, dentry, w);
453                 if (ret != 0)
454                         goto out_close_dir_ni;
455         }
456
457 out_set_dos_name:
458         /* Set DOS (short) name if given */
459         if (dentry->short_name_len != 0) {
460
461                 char *short_name_utf8;
462                 size_t short_name_utf8_len;
463                 short_name_utf8 = utf16_to_utf8(dentry->short_name,
464                                                 dentry->short_name_len,
465                                                 &short_name_utf8_len);
466                 if (!short_name_utf8) {
467                         ERROR("Out of memory");
468                         ret = WIMLIB_ERR_NOMEM;
469                         goto out_close_dir_ni;
470                 }
471
472                 if (is_hardlink) {
473                         char *p;
474                         char orig;
475                         const char *dir_name;
476
477                         /* ntfs_set_ntfs_dos_name() closes the inodes in the
478                          * wrong order if we have applied a hard link.   Close
479                          * them ourselves, then re-open then. */
480                         if (ntfs_inode_close(dir_ni) != 0) {
481                                 if (ret == 0)
482                                         ret = WIMLIB_ERR_NTFS_3G;
483                                 ERROR_WITH_ERRNO("Failed to close directory inode");
484                         }
485                         if (ntfs_inode_close(ni) != 0) {
486                                 if (ret == 0)
487                                         ret = WIMLIB_ERR_NTFS_3G;
488                                 ERROR_WITH_ERRNO("Failed to close hard link target inode");
489                         }
490                         p = dentry->full_path_utf8 + dentry->full_path_utf8_len;
491                         do {
492                                 p--;
493                         } while (*p != '/');
494
495                         orig = *p;
496                         *p = '\0';
497                         dir_name = dentry->full_path_utf8;
498
499                         dir_ni = ntfs_pathname_to_inode(vol, NULL, dir_name);
500                         *p = orig;
501                         if (!dir_ni) {
502                                 ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
503                                                  dir_name);
504                                 return WIMLIB_ERR_NTFS_3G;
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                                                  dir_name);
511                                 return WIMLIB_ERR_NTFS_3G;
512                         }
513                 }
514
515                 DEBUG("Setting short (DOS) name of `%s' to %s",
516                       dentry->full_path_utf8, short_name_utf8);
517
518                 ret = ntfs_set_ntfs_dos_name(ni, dir_ni, short_name_utf8,
519                                              short_name_utf8_len, 0);
520                 FREE(short_name_utf8);
521                 if (ret != 0) {
522                         ERROR_WITH_ERRNO("Could not set DOS (short) name for `%s'",
523                                          dentry->full_path_utf8);
524                         ret = WIMLIB_ERR_NTFS_3G;
525                 }
526                 /* inodes have been closed by ntfs_set_ntfs_dos_name(). */
527                 return ret;
528         }
529
530 out_close_dir_ni:
531         if (ntfs_inode_close(dir_ni) != 0) {
532                 if (ret == 0)
533                         ret = WIMLIB_ERR_NTFS_3G;
534                 ERROR_WITH_ERRNO("Failed to close directory inode");
535         }
536         if (ni && ntfs_inode_close(ni) != 0) {
537                 if (ret == 0)
538                         ret = WIMLIB_ERR_NTFS_3G;
539                 ERROR_WITH_ERRNO("Failed to close inode");
540         }
541         return ret;
542 }
543
544 static int wim_apply_root_dentry_ntfs(const struct dentry *dentry,
545                                       ntfs_volume *vol,
546                                       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, 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 static int wim_apply_dentry_ntfs(struct dentry *dentry, void *arg)
568 {
569         struct ntfs_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->extracted_file)
579                 return 0;
580
581         wimlib_assert(dentry->full_path_utf8);
582
583         DEBUG("Applying dentry `%s' to NTFS", dentry->full_path_utf8);
584
585         if (extract_flags & WIMLIB_EXTRACT_FLAG_VERBOSE)
586                 puts(dentry->full_path_utf8);
587
588         if (dentry_is_root(dentry))
589                 return wim_apply_root_dentry_ntfs(dentry, vol, w);
590
591         p = dentry->full_path_utf8 + dentry->full_path_utf8_len;
592         do {
593                 p--;
594         } while (*p != '/');
595
596         orig = *p;
597         *p = '\0';
598         dir_name = dentry->full_path_utf8;
599
600         dir_ni = ntfs_pathname_to_inode(vol, NULL, dir_name);
601         if (dir_ni)
602                 DEBUG("Found NTFS inode for `%s'", dir_name);
603         *p = orig;
604         if (!dir_ni) {
605                 ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
606                                  dir_name);
607                 return WIMLIB_ERR_NTFS_3G;
608         }
609         return do_wim_apply_dentry_ntfs(dentry, dir_ni, w);
610 }
611
612 static int wim_apply_dentry_timestamps(struct dentry *dentry, void *arg)
613 {
614         struct ntfs_apply_args *args = arg;
615         ntfs_volume *vol             = args->vol;
616         u8 *p;
617         u8 buf[24];
618         ntfs_inode *ni;
619         int ret = 0;
620
621
622         DEBUG("Setting timestamps on `%s'", dentry->full_path_utf8);
623
624         ni = ntfs_pathname_to_inode(vol, NULL, dentry->full_path_utf8);
625         if (!ni) {
626                 ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
627                                  dentry->full_path_utf8);
628                 return WIMLIB_ERR_NTFS_3G;
629         }
630
631         p = buf;
632         p = put_u64(p, dentry->creation_time);
633         p = put_u64(p, dentry->last_write_time);
634         p = put_u64(p, dentry->last_access_time);
635         ret = ntfs_inode_set_times(ni, (const char*)buf, 3 * sizeof(u64), 0);
636         if (ret != 0) {
637                 ERROR_WITH_ERRNO("Failed to set NTFS timestamps on `%s'",
638                                  dentry->full_path_utf8);
639                 ret = WIMLIB_ERR_NTFS_3G;
640         }
641
642         if (ntfs_inode_close(ni) != 0) {
643                 if (ret == 0)
644                         ret = WIMLIB_ERR_NTFS_3G;
645                 ERROR_WITH_ERRNO("Failed to close NTFS inode for `%s'",
646                                  dentry->full_path_utf8);
647         }
648         return ret;
649 }
650
651 static int do_wim_apply_image_ntfs(WIMStruct *w, const char *device, int extract_flags)
652 {
653         ntfs_volume *vol;
654         int ret;
655         
656         DEBUG("Mounting NTFS volume `%s'", device);
657         vol = ntfs_mount(device, 0);
658         if (!vol) {
659                 ERROR_WITH_ERRNO("Failed to mount NTFS volume `%s'", device);
660                 return WIMLIB_ERR_NTFS_3G;
661         }
662         struct ntfs_apply_args args = {
663                 .vol           = vol,
664                 .extract_flags = extract_flags,
665                 .w             = w,
666         };
667         ret = for_dentry_in_tree(wim_root_dentry(w), wim_apply_dentry_ntfs,
668                                  &args);
669
670         if (ret != 0)
671                 goto out;
672         DEBUG("Setting NTFS timestamps");
673         ret = for_dentry_in_tree_depth(wim_root_dentry(w),
674                                        wim_apply_dentry_timestamps,
675                                        &args);
676 out:
677         DEBUG("Unmounting NTFS volume `%s'", device);
678         if (ntfs_umount(vol, FALSE) != 0) {
679                 ERROR_WITH_ERRNO("Failed to unmount NTFS volume `%s'", device);
680                 if (ret == 0)
681                         ret = WIMLIB_ERR_NTFS_3G;
682         }
683         return ret;
684 }
685
686
687 /* 
688  * API entry point for applying a WIM image to a NTFS volume.
689  *
690  * Please note that this is a NTFS *volume* and not a directory.  The intention
691  * is that the volume contain an empty filesystem, and the WIM image contain a
692  * full filesystem to be applied to the volume.
693  */
694 WIMLIBAPI int wimlib_apply_image_to_ntfs_volume(WIMStruct *w, int image,
695                                                 const char *device, int flags)
696 {
697         int ret;
698
699         if (!device)
700                 return WIMLIB_ERR_INVALID_PARAM;
701         if (image == WIM_ALL_IMAGES) {
702                 ERROR("Can only apply a single image when applying "
703                       "directly to a NTFS volume");
704                 return WIMLIB_ERR_INVALID_PARAM;
705         }
706         if (flags & (WIMLIB_EXTRACT_FLAG_SYMLINK | WIMLIB_EXTRACT_FLAG_HARDLINK)) {
707                 ERROR("Cannot specify symlink or hardlink flags when applying ");
708                 ERROR("directly to a NTFS volume");
709                 return WIMLIB_ERR_INVALID_PARAM;
710         }
711         ret = wimlib_select_image(w, image);
712         if (ret != 0)
713                 return ret;
714
715         return do_wim_apply_image_ntfs(w, device, flags);
716 }
717
718 #else /* WITH_NTFS_3G */
719 WIMLIBAPI int wimlib_apply_image_to_ntfs_volume(WIMStruct *w, int image,
720                                                 const char *device, int flags)
721 {
722         ERROR("wimlib was compiled without support for NTFS-3g, so");
723         ERROR("we cannot apply a WIM image directly to a NTFS volume");
724         return WIMLIB_ERR_UNSUPPORTED;
725 }
726 #endif /* WITH_NTFS_3G */