NTFS apply ADS fixes
[wimlib] / src / ntfs-capture.c
1 /*
2  * ntfs-capture.c
3  *
4  * Capture a WIM image from a NTFS volume.  We capture everything we can,
5  * including 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 Lesser General Public License as published by the Free
15  * Software Foundation; either version 2.1 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 Lesser General Public License for more
21  * details.
22  *
23  * You should have received a copy of the GNU Lesser General Public License
24  * along with wimlib; if not, see http://www.gnu.org/licenses/.
25  */
26
27 #include "config.h"
28 #include "wimlib_internal.h"
29
30
31 #ifdef WITH_NTFS_3G
32 #include "dentry.h"
33 #include "lookup_table.h"
34 #include "io.h"
35 #include <ntfs-3g/layout.h>
36 #include <ntfs-3g/acls.h>
37 #include <ntfs-3g/attrib.h>
38 #include <ntfs-3g/misc.h>
39 #include <ntfs-3g/reparse.h>
40 #include <ntfs-3g/security.h>
41 #include <ntfs-3g/volume.h>
42 #include <stdlib.h>
43 #include <unistd.h>
44
45 extern int ntfs_inode_get_security(ntfs_inode *ni, u32 selection, char *buf,
46                                    u32 buflen, u32 *psize);
47
48 extern int ntfs_inode_get_attributes(ntfs_inode *ni);
49
50 /* Structure that allows searching the security descriptors by SHA1 message
51  * digest. */
52 struct sd_set {
53         struct wim_security_data *sd;
54         struct sd_node *root;
55 };
56
57 /* Binary tree node of security descriptors, indexed by the @hash field. */
58 struct sd_node {
59         int security_id;
60         u8 hash[SHA1_HASH_SIZE];
61         struct sd_node *left;
62         struct sd_node *right;
63 };
64
65 static void free_sd_tree(struct sd_node *root)
66 {
67         if (root) {
68                 free_sd_tree(root->left);
69                 free_sd_tree(root->right);
70                 FREE(root);
71         }
72 }
73 /* Frees a security descriptor index set. */
74 static void destroy_sd_set(struct sd_set *sd_set)
75 {
76         free_sd_tree(sd_set->root);
77 }
78
79 /* Inserts a a new node into the security descriptor index tree. */
80 static void insert_sd_node(struct sd_node *new, struct sd_node *root)
81 {
82         int cmp = hashes_cmp(new->hash, root->hash);
83         if (cmp < 0) {
84                 if (root->left)
85                         insert_sd_node(new, root->left);
86                 else 
87                         root->left = new;
88         } else if (cmp > 0) {
89                 if (root->right)
90                         insert_sd_node(new, root->right);
91                 else 
92                         root->right = new;
93         } else {
94                 wimlib_assert(0);
95         }
96 }
97
98 /* Returns the security ID of the security data having a SHA1 message digest of
99  * @hash in the security descriptor index tree rooted at @root. 
100  *
101  * If not found, return -1. */
102 static int lookup_sd(const u8 hash[SHA1_HASH_SIZE], struct sd_node *root)
103 {
104         int cmp;
105         if (!root)
106                 return -1;
107         cmp = hashes_cmp(hash, root->hash);
108         if (cmp < 0)
109                 return lookup_sd(hash, root->left);
110         else if (cmp > 0)
111                 return lookup_sd(hash, root->right);
112         else
113                 return root->security_id;
114 }
115
116 /*
117  * Adds a security descriptor to the indexed security descriptor set as well as
118  * the corresponding `struct wim_security_data', and returns the new security
119  * ID; or, if there is an existing security descriptor that is the same, return
120  * the security ID for it.  If a new security descriptor cannot be allocated,
121  * return -1.
122  */
123 static int sd_set_add_sd(struct sd_set *sd_set, const char descriptor[],
124                          size_t size)
125 {
126         u8 hash[SHA1_HASH_SIZE];
127         int security_id;
128         struct sd_node *new;
129         u8 **descriptors;
130         u64 *sizes;
131         u8 *descr_copy;
132         struct wim_security_data *sd;
133
134         sha1_buffer((const u8*)descriptor, size, hash);
135
136         security_id = lookup_sd(hash, sd_set->root);
137         if (security_id >= 0)
138                 return security_id;
139
140         new = MALLOC(sizeof(*new));
141         if (!new)
142                 goto out;
143         descr_copy = MALLOC(size);
144         if (!descr_copy)
145                 goto out_free_node;
146
147         sd = sd_set->sd;
148
149         memcpy(descr_copy, descriptor, size);
150         new->security_id = sd->num_entries;
151         new->left = NULL;
152         new->right = NULL;
153         copy_hash(new->hash, hash);
154
155
156         descriptors = REALLOC(sd->descriptors,
157                               (sd->num_entries + 1) * sizeof(sd->descriptors[0]));
158         if (!descriptors)
159                 goto out_free_descr;
160         sd->descriptors = descriptors;
161         sizes = REALLOC(sd->sizes,
162                         (sd->num_entries + 1) * sizeof(sd->sizes[0]));
163         if (!sizes)
164                 goto out_free_descr;
165         sd->sizes = sizes;
166         sd->descriptors[sd->num_entries] = descr_copy;
167         sd->sizes[sd->num_entries] = size;
168         sd->num_entries++;
169         DEBUG("There are now %d security descriptors", sd->num_entries);
170         sd->total_length += size + sizeof(sd->sizes[0]);
171
172         if (sd_set->root)
173                 insert_sd_node(new, sd_set->root);
174         else
175                 sd_set->root = new;
176         return new->security_id;
177 out_free_descr:
178         FREE(descr_copy);
179 out_free_node:
180         FREE(new);
181 out:
182         return -1;
183 }
184
185 static inline ntfschar *attr_record_name(ATTR_RECORD *ar)
186 {
187         return (ntfschar*)((u8*)ar + le16_to_cpu(ar->name_offset));
188 }
189
190 /* Calculates the SHA1 message digest of a NTFS attribute. 
191  *
192  * @ni:  The NTFS inode containing the attribute.
193  * @ar:  The ATTR_RECORD describing the attribute.
194  * @md:  If successful, the returned SHA1 message digest.
195  * @reparse_tag_ret:    Optional pointer into which the first 4 bytes of the
196  *                              attribute will be written (to get the reparse
197  *                              point ID)
198  *
199  * Return 0 on success or nonzero on error.
200  */
201 static int ntfs_attr_sha1sum(ntfs_inode *ni, ATTR_RECORD *ar,
202                              u8 md[SHA1_HASH_SIZE],
203                              u32 *reparse_tag_ret)
204 {
205         s64 pos = 0;
206         s64 bytes_remaining;
207         char buf[4096];
208         ntfs_attr *na;
209         SHA_CTX ctx;
210
211         na = ntfs_attr_open(ni, ar->type, attr_record_name(ar),
212                             ar->name_length);
213         if (!na) {
214                 ERROR_WITH_ERRNO("Failed to open NTFS attribute");
215                 return WIMLIB_ERR_NTFS_3G;
216         }
217
218         bytes_remaining = na->data_size;
219         sha1_init(&ctx);
220
221         DEBUG2("Calculating SHA1 message digest (%"PRIu64" bytes)",
222                bytes_remaining);
223
224         while (bytes_remaining) {
225                 s64 to_read = min(bytes_remaining, sizeof(buf));
226                 if (ntfs_attr_pread(na, pos, to_read, buf) != to_read) {
227                         ERROR_WITH_ERRNO("Error reading NTFS attribute");
228                         return WIMLIB_ERR_NTFS_3G;
229                 }
230                 if (bytes_remaining == na->data_size && reparse_tag_ret)
231                         *reparse_tag_ret = le32_to_cpu(*(u32*)buf);
232                 sha1_update(&ctx, buf, to_read);
233                 pos += to_read;
234                 bytes_remaining -= to_read;
235         }
236         sha1_final(md, &ctx);
237         ntfs_attr_close(na);
238         return 0;
239 }
240
241 /* Load the streams from a WIM file or reparse point in the NTFS volume into the
242  * WIM lookup table */
243 static int capture_ntfs_streams(struct dentry *dentry, ntfs_inode *ni,
244                                 char path[], size_t path_len,
245                                 struct lookup_table *lookup_table,
246                                 ntfs_volume **ntfs_vol_p,
247                                 ATTR_TYPES type)
248 {
249
250         ntfs_attr_search_ctx *actx;
251         u8 attr_hash[SHA1_HASH_SIZE];
252         struct ntfs_location *ntfs_loc = NULL;
253         int ret = 0;
254         struct lookup_table_entry *lte;
255
256         DEBUG2("Capturing NTFS data streams from `%s'", path);
257
258         /* Get context to search the streams of the NTFS file. */
259         actx = ntfs_attr_get_search_ctx(ni, NULL);
260         if (!actx) {
261                 ERROR_WITH_ERRNO("Cannot get NTFS attribute search "
262                                  "context");
263                 return WIMLIB_ERR_NTFS_3G;
264         }
265
266         /* Capture each data stream or reparse data stream. */
267         while (!ntfs_attr_lookup(type, NULL, 0,
268                                  CASE_SENSITIVE, 0, NULL, 0, actx))
269         {
270                 char *stream_name_utf8;
271                 size_t stream_name_utf16_len;
272                 u32 reparse_tag;
273                 u64 data_size = ntfs_get_attribute_value_length(actx->attr);
274                 u64 name_length = actx->attr->name_length;
275
276                 if (data_size == 0) { 
277                         if (errno != 0) {
278                                 ERROR_WITH_ERRNO("Failed to get size of attribute of "
279                                                  "`%s'", path);
280                                 ret = WIMLIB_ERR_NTFS_3G;
281                                 goto out_put_actx;
282                         }
283                         /* Empty stream.  No lookup table entry is needed. */
284                         lte = NULL;
285                 } else {
286                         if (type == AT_REPARSE_POINT && data_size < 8) {
287                                 ERROR("`%s': reparse point buffer too small");
288                                 ret = WIMLIB_ERR_NTFS_3G;
289                                 goto out_put_actx;
290                         }
291                         /* Checksum the stream. */
292                         ret = ntfs_attr_sha1sum(ni, actx->attr, attr_hash, &reparse_tag);
293                         if (ret != 0)
294                                 goto out_put_actx;
295
296                         /* Make a lookup table entry for the stream, or use an existing
297                          * one if there's already an identical stream. */
298                         lte = __lookup_resource(lookup_table, attr_hash);
299                         ret = WIMLIB_ERR_NOMEM;
300                         if (lte) {
301                                 lte->refcnt++;
302                         } else {
303                                 ntfs_loc = CALLOC(1, sizeof(*ntfs_loc));
304                                 if (!ntfs_loc)
305                                         goto out_put_actx;
306                                 ntfs_loc->ntfs_vol_p = ntfs_vol_p;
307                                 ntfs_loc->path_utf8 = MALLOC(path_len + 1);
308                                 if (!ntfs_loc->path_utf8)
309                                         goto out_free_ntfs_loc;
310                                 memcpy(ntfs_loc->path_utf8, path, path_len + 1);
311                                 if (name_length) {
312                                         ntfs_loc->stream_name_utf16 = MALLOC(name_length * 2);
313                                         if (!ntfs_loc->stream_name_utf16)
314                                                 goto out_free_ntfs_loc;
315                                         memcpy(ntfs_loc->stream_name_utf16,
316                                                attr_record_name(actx->attr),
317                                                actx->attr->name_length * 2);
318                                         ntfs_loc->stream_name_utf16_num_chars = name_length;
319                                 }
320
321                                 lte = new_lookup_table_entry();
322                                 if (!lte)
323                                         goto out_free_ntfs_loc;
324                                 lte->ntfs_loc = ntfs_loc;
325                                 lte->resource_location = RESOURCE_IN_NTFS_VOLUME;
326                                 if (type == AT_REPARSE_POINT) {
327                                         dentry->reparse_tag = reparse_tag;
328                                         ntfs_loc->is_reparse_point = true;
329                                         lte->resource_entry.original_size = data_size - 8;
330                                         lte->resource_entry.size = data_size - 8;
331                                 } else {
332                                         ntfs_loc->is_reparse_point = false;
333                                         lte->resource_entry.original_size = data_size;
334                                         lte->resource_entry.size = data_size;
335                                 }
336                                 ntfs_loc = NULL;
337                                 DEBUG("Add resource for `%s' (size = %zu)",
338                                       dentry->file_name_utf8,
339                                       lte->resource_entry.original_size);
340                                 copy_hash(lte->hash, attr_hash);
341                                 lookup_table_insert(lookup_table, lte);
342                         }
343                 }
344                 if (name_length == 0) {
345                         /* Unnamed data stream.  Put the reference to it in the
346                          * dentry. */
347                         if (dentry->lte) {
348                                 ERROR("Found two un-named data streams for "
349                                       "`%s'", path);
350                                 ret = WIMLIB_ERR_NTFS_3G;
351                                 goto out_free_lte;
352                         }
353                         dentry->lte = lte;
354                 } else {
355                         /* Named data stream.  Put the reference to it in the
356                          * alternate data stream entries */
357                         struct ads_entry *new_ads_entry;
358                         size_t stream_name_utf8_len;
359                         stream_name_utf8 = utf16_to_utf8((const char*)attr_record_name(actx->attr),
360                                                          name_length * 2,
361                                                          &stream_name_utf8_len);
362                         if (!stream_name_utf8)
363                                 goto out_free_lte;
364                         new_ads_entry = dentry_add_ads(dentry, stream_name_utf8);
365                         FREE(stream_name_utf8);
366                         if (!new_ads_entry)
367                                 goto out_free_lte;
368
369                         wimlib_assert(new_ads_entry->stream_name_len == name_length * 2);
370                                 
371                         new_ads_entry->lte = lte;
372                 }
373         }
374         ret = 0;
375         goto out_put_actx;
376 out_free_lte:
377         free_lookup_table_entry(lte);
378 out_free_ntfs_loc:
379         if (ntfs_loc) {
380                 FREE(ntfs_loc->path_utf8);
381                 FREE(ntfs_loc->stream_name_utf16);
382                 FREE(ntfs_loc);
383         }
384 out_put_actx:
385         ntfs_attr_put_search_ctx(actx);
386         if (ret == 0)
387                 DEBUG2("Successfully captured NTFS streams from `%s'", path);
388         else
389                 ERROR("Failed to capture NTFS streams from `%s", path);
390         return ret;
391 }
392
393 struct readdir_ctx {
394         struct dentry       *parent;
395         ntfs_inode          *dir_ni;
396         char                *path;
397         size_t               path_len;
398         struct lookup_table *lookup_table;
399         struct sd_set       *sd_set;
400         const struct capture_config *config;
401         ntfs_volume        **ntfs_vol_p;
402         int                  flags;
403 };
404
405 static int
406 build_dentry_tree_ntfs_recursive(struct dentry **root_p, ntfs_inode *ni,
407                                  char path[], size_t path_len,
408                                  struct lookup_table *lookup_table,
409                                  struct sd_set *sd_set,
410                                  const struct capture_config *config,
411                                  ntfs_volume **ntfs_vol_p,
412                                  int flags);
413
414 static int wim_ntfs_capture_filldir(void *dirent, const ntfschar *name,
415                                     const int name_len, const int name_type,
416                                     const s64 pos, const MFT_REF mref,
417                                     const unsigned dt_type)
418 {
419         struct readdir_ctx *ctx;
420         size_t utf8_name_len;
421         char *utf8_name;
422         struct dentry *child = NULL;
423         int ret;
424         size_t path_len;
425
426         if (name_type == FILE_NAME_DOS)
427                 return 0;
428
429         ret = -1;
430
431         utf8_name = utf16_to_utf8((const char*)name, name_len * 2,
432                                   &utf8_name_len);
433         if (!utf8_name)
434                 goto out;
435
436         if (utf8_name[0] == '.' &&
437              (utf8_name[1] == '\0' ||
438               (utf8_name[1] == '.' && utf8_name[2] == '\0'))) {
439                 ret = 0;
440                 goto out_free_utf8_name;
441         }
442
443         ctx = dirent;
444
445         ntfs_inode *ni = ntfs_inode_open(ctx->dir_ni->vol, mref);
446         if (!ni) {
447                 ERROR_WITH_ERRNO("Failed to open NTFS inode");
448                 ret = 1;
449         }
450         path_len = ctx->path_len;
451         if (path_len != 1)
452                 ctx->path[path_len++] = '/';
453         memcpy(ctx->path + path_len, utf8_name, utf8_name_len + 1);
454         path_len += utf8_name_len;
455         ret = build_dentry_tree_ntfs_recursive(&child, ni, ctx->path, path_len,
456                                                ctx->lookup_table, ctx->sd_set,
457                                                ctx->config, ctx->ntfs_vol_p,
458                                                ctx->flags);
459
460         if (child)
461                 link_dentry(child, ctx->parent);
462
463         ntfs_inode_close(ni);
464 out_free_utf8_name:
465         FREE(utf8_name);
466 out:
467         return ret;
468 }
469
470 /* Recursively build a WIM dentry tree corresponding to a NTFS volume.
471  * At the same time, update the WIM lookup table with lookup table entries for
472  * the NTFS streams, and build an array of security descriptors.
473  */
474 static int build_dentry_tree_ntfs_recursive(struct dentry **root_p,
475                                             ntfs_inode *ni,
476                                             char path[],
477                                             size_t path_len,
478                                             struct lookup_table *lookup_table,
479                                             struct sd_set *sd_set,
480                                             const struct capture_config *config,
481                                             ntfs_volume **ntfs_vol_p,
482                                             int flags)
483 {
484         u32 attributes;
485         int mrec_flags;
486         u32 sd_size = 0;
487         int ret = 0;
488         struct dentry *root;
489
490         mrec_flags = ni->mrec->flags;
491         attributes = ntfs_inode_get_attributes(ni);
492
493         if (exclude_path(path, config, false)) {
494                 if (flags & WIMLIB_ADD_IMAGE_FLAG_VERBOSE) {
495                         const char *file_type;
496                         if (attributes & MFT_RECORD_IS_DIRECTORY)
497                                 file_type = "directory";
498                         else
499                                 file_type = "file";
500                         printf("Excluding %s `%s' from capture\n",
501                                file_type, path);
502                 }
503                 *root_p = NULL;
504                 return 0;
505         }
506
507         if (flags & WIMLIB_ADD_IMAGE_FLAG_VERBOSE)
508                 printf("Scanning `%s'\n", path);
509
510         root = new_dentry(path_basename(path));
511         if (!root)
512                 return WIMLIB_ERR_NOMEM;
513
514         *root_p = root;
515         root->creation_time    = le64_to_cpu(ni->creation_time);
516         root->last_write_time  = le64_to_cpu(ni->last_data_change_time);
517         root->last_access_time = le64_to_cpu(ni->last_access_time);
518         root->attributes       = le32_to_cpu(attributes);
519         root->link_group_id    = ni->mft_no;
520         root->resolved         = true;
521
522         if (attributes & FILE_ATTR_REPARSE_POINT) {
523                 /* Junction point, symbolic link, or other reparse point */
524                 ret = capture_ntfs_streams(root, ni, path, path_len,
525                                            lookup_table, ntfs_vol_p,
526                                            AT_REPARSE_POINT);
527         } else if (mrec_flags & MFT_RECORD_IS_DIRECTORY) {
528
529                 /* Normal directory */
530                 s64 pos = 0;
531                 struct readdir_ctx ctx = {
532                         .parent       = root,
533                         .dir_ni       = ni,
534                         .path         = path,
535                         .path_len     = path_len,
536                         .lookup_table = lookup_table,
537                         .sd_set       = sd_set,
538                         .config       = config,
539                         .ntfs_vol_p   = ntfs_vol_p,
540                         .flags        = flags,
541                 };
542                 ret = ntfs_readdir(ni, &pos, &ctx, wim_ntfs_capture_filldir);
543                 if (ret != 0) {
544                         ERROR_WITH_ERRNO("ntfs_readdir()");
545                         ret = WIMLIB_ERR_NTFS_3G;
546                 }
547         } else {
548                 /* Normal file */
549                 ret = capture_ntfs_streams(root, ni, path, path_len,
550                                            lookup_table, ntfs_vol_p,
551                                            AT_DATA);
552         }
553         if (ret != 0)
554                 return ret;
555
556         ret = ntfs_inode_get_security(ni,
557                                       OWNER_SECURITY_INFORMATION |
558                                       GROUP_SECURITY_INFORMATION |
559                                       DACL_SECURITY_INFORMATION  |
560                                       SACL_SECURITY_INFORMATION,
561                                       NULL, 0, &sd_size);
562         char sd[sd_size];
563         ret = ntfs_inode_get_security(ni,
564                                       OWNER_SECURITY_INFORMATION |
565                                       GROUP_SECURITY_INFORMATION |
566                                       DACL_SECURITY_INFORMATION  |
567                                       SACL_SECURITY_INFORMATION,
568                                       sd, sd_size, &sd_size);
569         if (ret == 0) {
570                 ERROR_WITH_ERRNO("Failed to get security information from "
571                                  "`%s'", path);
572                 ret = WIMLIB_ERR_NTFS_3G;
573         } else {
574                 if (ret > 0) {
575                         /*print_security_descriptor(sd, sd_size);*/
576                         root->security_id = sd_set_add_sd(sd_set, sd, sd_size);
577                         if (root->security_id == -1) {
578                                 ERROR("Out of memory");
579                                 return WIMLIB_ERR_NOMEM;
580                         }
581                         DEBUG("Added security ID = %u for `%s'",
582                               root->security_id, path);
583                 } else { 
584                         root->security_id = -1;
585                         DEBUG("No security ID for `%s'", path);
586                 }
587                 ret = 0;
588         }
589         return ret;
590 }
591
592 static int build_dentry_tree_ntfs(struct dentry **root_p,
593                                   const char *device,
594                                   struct lookup_table *lookup_table,
595                                   struct wim_security_data *sd,
596                                   const struct capture_config *config,
597                                   int flags,
598                                   void *extra_arg)
599 {
600         ntfs_volume *vol;
601         ntfs_inode *root_ni;
602         int ret = 0;
603         struct sd_set sd_set = {
604                 .sd = sd,
605                 .root = NULL,
606         };
607         ntfs_volume **ntfs_vol_p = extra_arg;
608
609         DEBUG("Mounting NTFS volume `%s' read-only", device);
610         
611         vol = ntfs_mount(device, MS_RDONLY);
612         if (!vol) {
613                 ERROR_WITH_ERRNO("Failed to mount NTFS volume `%s' read-only",
614                                  device);
615                 return WIMLIB_ERR_NTFS_3G;
616         }
617         ntfs_open_secure(vol);
618
619         /* We don't want to capture the special NTFS files such as $Bitmap.  Not
620          * to be confused with "hidden" or "system" files which are real files
621          * that we do need to capture.  */
622         NVolClearShowSysFiles(vol);
623
624         DEBUG("Opening root NTFS dentry");
625         root_ni = ntfs_inode_open(vol, FILE_root);
626         if (!root_ni) {
627                 ERROR_WITH_ERRNO("Failed to open root inode of NTFS volume "
628                                  "`%s'", device);
629                 ret = WIMLIB_ERR_NTFS_3G;
630                 goto out;
631         }
632
633         /* Currently we assume that all the UTF-8 paths fit into this length and
634          * there is no check for overflow. */
635         char *path = MALLOC(32768);
636         if (!path) {
637                 ERROR("Could not allocate memory for NTFS pathname");
638                 goto out_cleanup;
639         }
640
641         path[0] = '/';
642         path[1] = '\0';
643         ret = build_dentry_tree_ntfs_recursive(root_p, root_ni, path, 1,
644                                                lookup_table, &sd_set,
645                                                config, ntfs_vol_p, flags);
646 out_cleanup:
647         FREE(path);
648         ntfs_inode_close(root_ni);
649         destroy_sd_set(&sd_set);
650
651 out:
652         if (ret) {
653                 if (ntfs_umount(vol, FALSE) != 0) {
654                         ERROR_WITH_ERRNO("Failed to unmount NTFS volume `%s'",
655                                          device);
656                         if (ret == 0)
657                                 ret = WIMLIB_ERR_NTFS_3G;
658                 }
659         } else {
660                 /* We need to leave the NTFS volume mounted so that we can read
661                  * the NTFS files again when we are actually writing the WIM */
662                 *ntfs_vol_p = vol;
663         }
664         return ret;
665 }
666
667
668
669 WIMLIBAPI int wimlib_add_image_from_ntfs_volume(WIMStruct *w,
670                                                 const char *device,
671                                                 const char *name,
672                                                 const char *config_str,
673                                                 size_t config_len,
674                                                 int flags)
675 {
676         if (flags & (WIMLIB_ADD_IMAGE_FLAG_DEREFERENCE)) {
677                 ERROR("Cannot dereference files when capturing directly from NTFS");
678                 return WIMLIB_ERR_INVALID_PARAM;
679         }
680         return do_add_image(w, device, name, config_str, config_len, flags,
681                             build_dentry_tree_ntfs, &w->ntfs_vol);
682 }
683
684 #else /* WITH_NTFS_3G */
685 WIMLIBAPI int wimlib_add_image_from_ntfs_volume(WIMStruct *w,
686                                                 const char *device,
687                                                 const char *name,
688                                                 const char *config_str,
689                                                 size_t config_len,
690                                                 int flags)
691 {
692         ERROR("wimlib was compiled without support for NTFS-3g, so");
693         ERROR("we cannot capture a WIM image directly from a NTFS volume");
694         return WIMLIB_ERR_UNSUPPORTED;
695 }
696 #endif /* WITH_NTFS_3G */