70ce59a96a3bf765203bd5ff2394abaebb235b47
[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, 2013 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 #include <ntfs-3g/endians.h>
31 #include <ntfs-3g/types.h>
32
33 #include "wimlib_internal.h"
34
35
36 #include "dentry.h"
37 #include "lookup_table.h"
38 #include "buffer_io.h"
39 #include <ntfs-3g/layout.h>
40 #include <ntfs-3g/acls.h>
41 #include <ntfs-3g/attrib.h>
42 #include <ntfs-3g/misc.h>
43 #include <ntfs-3g/reparse.h>
44 #include <ntfs-3g/security.h> /* security.h before xattrs.h */
45 #include <ntfs-3g/xattrs.h>
46 #include <ntfs-3g/volume.h>
47 #include <stdlib.h>
48 #include <unistd.h>
49 #include <errno.h>
50 #include "security.h"
51
52 static inline ntfschar *attr_record_name(ATTR_RECORD *ar)
53 {
54         return (ntfschar*)((u8*)ar + le16_to_cpu(ar->name_offset));
55 }
56
57 /* Calculates the SHA1 message digest of a NTFS attribute.
58  *
59  * @ni:  The NTFS inode containing the attribute.
60  * @ar:  The ATTR_RECORD describing the attribute.
61  * @md:  If successful, the returned SHA1 message digest.
62  * @reparse_tag_ret:    Optional pointer into which the first 4 bytes of the
63  *                              attribute will be written (to get the reparse
64  *                              point ID)
65  *
66  * Return 0 on success or nonzero on error.
67  */
68 static int ntfs_attr_sha1sum(ntfs_inode *ni, ATTR_RECORD *ar,
69                              u8 md[SHA1_HASH_SIZE],
70                              bool is_reparse_point,
71                              u32 *reparse_tag_ret)
72 {
73         s64 pos = 0;
74         s64 bytes_remaining;
75         char buf[BUFFER_SIZE];
76         ntfs_attr *na;
77         SHA_CTX ctx;
78
79         na = ntfs_attr_open(ni, ar->type, attr_record_name(ar),
80                             ar->name_length);
81         if (!na) {
82                 ERROR_WITH_ERRNO("Failed to open NTFS attribute");
83                 return WIMLIB_ERR_NTFS_3G;
84         }
85
86         bytes_remaining = na->data_size;
87
88         if (is_reparse_point) {
89                 if (ntfs_attr_pread(na, 0, 8, buf) != 8)
90                         goto out_error;
91                 *reparse_tag_ret = le32_to_cpu(*(u32*)buf);
92                 DEBUG("ReparseTag = %#x", *reparse_tag_ret);
93                 pos = 8;
94                 bytes_remaining -= 8;
95         }
96
97         sha1_init(&ctx);
98         while (bytes_remaining) {
99                 s64 to_read = min(bytes_remaining, sizeof(buf));
100                 if (ntfs_attr_pread(na, pos, to_read, buf) != to_read)
101                         goto out_error;
102                 sha1_update(&ctx, buf, to_read);
103                 pos += to_read;
104                 bytes_remaining -= to_read;
105         }
106         sha1_final(md, &ctx);
107         ntfs_attr_close(na);
108         return 0;
109 out_error:
110         ERROR_WITH_ERRNO("Error reading NTFS attribute");
111         return WIMLIB_ERR_NTFS_3G;
112 }
113
114 /* Load the streams from a file or reparse point in the NTFS volume into the WIM
115  * lookup table */
116 static int capture_ntfs_streams(struct wim_dentry *dentry, ntfs_inode *ni,
117                                 char path[], size_t path_len,
118                                 struct wim_lookup_table *lookup_table,
119                                 ntfs_volume **ntfs_vol_p,
120                                 ATTR_TYPES type)
121 {
122         ntfs_attr_search_ctx *actx;
123         u8 attr_hash[SHA1_HASH_SIZE];
124         struct ntfs_location *ntfs_loc = NULL;
125         int ret = 0;
126         struct wim_lookup_table_entry *lte;
127
128         DEBUG2("Capturing NTFS data streams from `%s'", path);
129
130         /* Get context to search the streams of the NTFS file. */
131         actx = ntfs_attr_get_search_ctx(ni, NULL);
132         if (!actx) {
133                 ERROR_WITH_ERRNO("Cannot get NTFS attribute search "
134                                  "context");
135                 return WIMLIB_ERR_NTFS_3G;
136         }
137
138         /* Capture each data stream or reparse data stream. */
139         while (!ntfs_attr_lookup(type, NULL, 0,
140                                  CASE_SENSITIVE, 0, NULL, 0, actx))
141         {
142                 char *stream_name_utf8;
143                 u32 reparse_tag;
144                 u64 data_size = ntfs_get_attribute_value_length(actx->attr);
145                 u64 name_length = actx->attr->name_length;
146
147                 if (data_size == 0) {
148                         if (errno != 0) {
149                                 ERROR_WITH_ERRNO("Failed to get size of attribute of "
150                                                  "`%s'", path);
151                                 ret = WIMLIB_ERR_NTFS_3G;
152                                 goto out_put_actx;
153                         }
154                         /* Empty stream.  No lookup table entry is needed. */
155                         lte = NULL;
156                 } else {
157                         if (type == AT_REPARSE_POINT && data_size < 8) {
158                                 ERROR("`%s': reparse point buffer too small",
159                                       path);
160                                 ret = WIMLIB_ERR_NTFS_3G;
161                                 goto out_put_actx;
162                         }
163                         /* Checksum the stream. */
164                         ret = ntfs_attr_sha1sum(ni, actx->attr, attr_hash,
165                                                 type == AT_REPARSE_POINT, &reparse_tag);
166                         if (ret != 0)
167                                 goto out_put_actx;
168
169                         if (type == AT_REPARSE_POINT)
170                                 dentry->d_inode->i_reparse_tag = reparse_tag;
171
172                         /* Make a lookup table entry for the stream, or use an existing
173                          * one if there's already an identical stream. */
174                         lte = __lookup_resource(lookup_table, attr_hash);
175                         ret = WIMLIB_ERR_NOMEM;
176                         if (lte) {
177                                 lte->refcnt++;
178                         } else {
179                                 ntfs_loc = CALLOC(1, sizeof(*ntfs_loc));
180                                 if (!ntfs_loc)
181                                         goto out_put_actx;
182                                 ntfs_loc->ntfs_vol_p = ntfs_vol_p;
183                                 ntfs_loc->path_utf8 = MALLOC(path_len + 1);
184                                 if (!ntfs_loc->path_utf8)
185                                         goto out_free_ntfs_loc;
186                                 memcpy(ntfs_loc->path_utf8, path, path_len + 1);
187                                 if (name_length) {
188                                         ntfs_loc->stream_name_utf16 = MALLOC(name_length * 2);
189                                         if (!ntfs_loc->stream_name_utf16)
190                                                 goto out_free_ntfs_loc;
191                                         memcpy(ntfs_loc->stream_name_utf16,
192                                                attr_record_name(actx->attr),
193                                                actx->attr->name_length * 2);
194                                         ntfs_loc->stream_name_utf16_num_chars = name_length;
195                                 }
196
197                                 lte = new_lookup_table_entry();
198                                 if (!lte)
199                                         goto out_free_ntfs_loc;
200                                 lte->ntfs_loc = ntfs_loc;
201                                 lte->resource_location = RESOURCE_IN_NTFS_VOLUME;
202                                 if (type == AT_REPARSE_POINT) {
203                                         ntfs_loc->is_reparse_point = true;
204                                         lte->resource_entry.original_size = data_size - 8;
205                                         lte->resource_entry.size = data_size - 8;
206                                 } else {
207                                         ntfs_loc->is_reparse_point = false;
208                                         lte->resource_entry.original_size = data_size;
209                                         lte->resource_entry.size = data_size;
210                                 }
211                                 ntfs_loc = NULL;
212                                 DEBUG("Add resource for `%s' (size = %"PRIu64")",
213                                       dentry->file_name_utf8,
214                                       lte->resource_entry.original_size);
215                                 copy_hash(lte->hash, attr_hash);
216                                 lookup_table_insert(lookup_table, lte);
217                         }
218                 }
219                 if (name_length == 0) {
220                         /* Unnamed data stream.  Put the reference to it in the
221                          * dentry's inode. */
222                 #if 0
223                         if (dentry->d_inode->i_lte) {
224                                 ERROR("Found two un-named data streams for "
225                                       "`%s'", path);
226                                 ret = WIMLIB_ERR_NTFS_3G;
227                                 goto out_free_lte;
228                         }
229                         dentry->d_inode->i_lte = lte;
230                 #else
231                         if (dentry->d_inode->i_lte) {
232                                 WARNING("Found two un-named data streams for "
233                                         "`%s'", path);
234                                 free_lookup_table_entry(lte);
235                         } else {
236                                 dentry->d_inode->i_lte = lte;
237                         }
238                 #endif
239                 } else {
240                         /* Named data stream.  Put the reference to it in the
241                          * alternate data stream entries */
242                         struct wim_ads_entry *new_ads_entry;
243                         size_t stream_name_utf8_len;
244
245                         ret = utf16_to_utf8((const char*)attr_record_name(actx->attr),
246                                             name_length * 2,
247                                             &stream_name_utf8,
248                                             &stream_name_utf8_len);
249                         if (ret != 0)
250                                 goto out_free_lte;
251                         new_ads_entry = inode_add_ads(dentry->d_inode, stream_name_utf8);
252                         FREE(stream_name_utf8);
253                         if (!new_ads_entry)
254                                 goto out_free_lte;
255
256                         wimlib_assert(new_ads_entry->stream_name_len == name_length * 2);
257
258                         new_ads_entry->lte = lte;
259                 }
260         }
261         ret = 0;
262         goto out_put_actx;
263 out_free_lte:
264         free_lookup_table_entry(lte);
265 out_free_ntfs_loc:
266         if (ntfs_loc) {
267                 FREE(ntfs_loc->path_utf8);
268                 FREE(ntfs_loc->stream_name_utf16);
269                 FREE(ntfs_loc);
270         }
271 out_put_actx:
272         ntfs_attr_put_search_ctx(actx);
273         if (ret == 0)
274                 DEBUG2("Successfully captured NTFS streams from `%s'", path);
275         else
276                 ERROR("Failed to capture NTFS streams from `%s", path);
277         return ret;
278 }
279
280 /* Red-black tree that maps NTFS inode numbers to DOS names */
281 struct dos_name_map {
282         struct rb_root rb_root;
283 };
284
285 struct dos_name_node {
286         struct rb_node rb_node;
287         char dos_name[24];
288         int name_len_bytes;
289         u64 ntfs_ino;
290 };
291
292 /* Inserts a new DOS name into the map */
293 static int insert_dos_name(struct dos_name_map *map,
294                            const ntfschar *dos_name, int name_len,
295                            u64 ntfs_ino)
296 {
297         struct dos_name_node *new_node;
298         struct rb_node **p;
299         struct rb_root *root;
300         struct rb_node *rb_parent;
301
302         DEBUG("DOS name_len = %d", name_len);
303         new_node = MALLOC(sizeof(struct dos_name_node));
304         if (!new_node)
305                 return -1;
306
307         /* DOS names are supposed to be 12 characters max (that's 24 bytes,
308          * assuming 2-byte ntfs characters) */
309         wimlib_assert(name_len * sizeof(ntfschar) <= sizeof(new_node->dos_name));
310
311         /* Initialize the DOS name, DOS name length, and NTFS inode number of
312          * the red-black tree node */
313         memcpy(new_node->dos_name, dos_name, name_len * sizeof(ntfschar));
314         new_node->name_len_bytes = name_len * sizeof(ntfschar);
315         new_node->ntfs_ino = ntfs_ino;
316
317         /* Insert the red-black tree node */
318         root = &map->rb_root;
319         p = &root->rb_node;
320         rb_parent = NULL;
321         while (*p) {
322                 struct dos_name_node *this;
323
324                 this = container_of(*p, struct dos_name_node, rb_node);
325                 rb_parent = *p;
326                 if (new_node->ntfs_ino < this->ntfs_ino)
327                         p = &((*p)->rb_left);
328                 else if (new_node->ntfs_ino > this->ntfs_ino)
329                         p = &((*p)->rb_right);
330                 else {
331                         /* This should be impossible since a NTFS inode cannot
332                          * have multiple DOS names, and we only should get each
333                          * DOS name entry once from the ntfs_readdir() calls. */
334                         ERROR("NTFS inode %"PRIu64" has multiple DOS names",
335                               ntfs_ino);
336                         return -1;
337                 }
338         }
339         rb_link_node(&new_node->rb_node, rb_parent, p);
340         rb_insert_color(&new_node->rb_node, root);
341         DEBUG("Inserted DOS name for inode %"PRIu64, ntfs_ino);
342         return 0;
343 }
344
345 /* Returns a structure that contains the DOS name and its length for a NTFS
346  * inode, or NULL if the inode has no DOS name. */
347 static struct dos_name_node *
348 lookup_dos_name(const struct dos_name_map *map, u64 ntfs_ino)
349 {
350         struct rb_node *node = map->rb_root.rb_node;
351         while (node) {
352                 struct dos_name_node *this;
353                 this = container_of(node, struct dos_name_node, rb_node);
354                 if (ntfs_ino < this->ntfs_ino)
355                         node = node->rb_left;
356                 else if (ntfs_ino > this->ntfs_ino)
357                         node = node->rb_right;
358                 else
359                         return this;
360         }
361         return NULL;
362 }
363
364 static int set_dentry_dos_name(struct wim_dentry *dentry, void *arg)
365 {
366         const struct dos_name_map *map = arg;
367         const struct dos_name_node *node;
368
369         if (dentry->is_win32_name) {
370                 node = lookup_dos_name(map, dentry->d_inode->i_ino);
371                 if (node) {
372                         dentry->short_name = MALLOC(node->name_len_bytes);
373                         if (!dentry->short_name)
374                                 return WIMLIB_ERR_NOMEM;
375                         memcpy(dentry->short_name, node->dos_name,
376                                node->name_len_bytes);
377                         dentry->short_name_len = node->name_len_bytes;
378                         DEBUG("Assigned DOS name to ino %"PRIu64,
379                               dentry->d_inode->i_ino);
380                 } else {
381                         WARNING("NTFS inode %"PRIu64" has Win32 name with no "
382                                 "corresponding DOS name",
383                                 dentry->d_inode->i_ino);
384                 }
385         }
386         return 0;
387 }
388
389 static void free_dos_name_tree(struct rb_node *node) {
390         if (node) {
391                 free_dos_name_tree(node->rb_left);
392                 free_dos_name_tree(node->rb_right);
393                 FREE(container_of(node, struct dos_name_node, rb_node));
394         }
395 }
396
397 static void destroy_dos_name_map(struct dos_name_map *map)
398 {
399         free_dos_name_tree(map->rb_root.rb_node);
400 }
401
402 struct readdir_ctx {
403         struct wim_dentry *parent;
404         ntfs_inode *dir_ni;
405         char *path;
406         size_t path_len;
407         struct wim_lookup_table *lookup_table;
408         struct sd_set *sd_set;
409         struct dos_name_map *dos_name_map;
410         const struct capture_config *config;
411         ntfs_volume **ntfs_vol_p;
412         int add_image_flags;
413         wimlib_progress_func_t progress_func;
414 };
415
416 static int
417 build_dentry_tree_ntfs_recursive(struct wim_dentry **root_p, ntfs_inode *dir_ni,
418                                  ntfs_inode *ni, char path[], size_t path_len,
419                                  int name_type,
420                                  struct wim_lookup_table *lookup_table,
421                                  struct sd_set *sd_set,
422                                  const struct capture_config *config,
423                                  ntfs_volume **ntfs_vol_p,
424                                  int add_image_flags,
425                                  wimlib_progress_func_t progress_func);
426
427 static int wim_ntfs_capture_filldir(void *dirent, const ntfschar *name,
428                                     const int name_len, const int name_type,
429                                     const s64 pos, const MFT_REF mref,
430                                     const unsigned dt_type)
431 {
432         struct readdir_ctx *ctx;
433         size_t utf8_name_len;
434         char *utf8_name;
435         struct wim_dentry *child;
436         int ret;
437         size_t path_len;
438
439         ctx = dirent;
440         if (name_type & FILE_NAME_DOS) {
441                 /* If this is the entry for a DOS name, store it for later. */
442                 ret = insert_dos_name(ctx->dos_name_map, name,
443                                       name_len, mref & MFT_REF_MASK_CPU);
444
445                 /* Return now if an error occurred or if this is just a DOS name
446                  * and not a Win32+DOS name. */
447                 if (ret != 0 || name_type == FILE_NAME_DOS)
448                         return ret;
449         }
450         ret = utf16_to_utf8((const char*)name, name_len * 2,
451                             &utf8_name, &utf8_name_len);
452         if (ret != 0)
453                 return -1;
454
455         if (utf8_name[0] == '.' &&
456              (utf8_name[1] == '\0' ||
457               (utf8_name[1] == '.' && utf8_name[2] == '\0'))) {
458                 /* . or .. entries
459                  *
460                  * note: name_type is POSIX for these, so DOS names will not
461                  * have been inserted for them.  */
462                 ret = 0;
463                 goto out_free_utf8_name;
464         }
465
466         /* Open the inode for this directory entry and recursively capture the
467          * directory tree rooted at it */
468         ntfs_inode *ni = ntfs_inode_open(ctx->dir_ni->vol, mref);
469         if (!ni) {
470                 ERROR_WITH_ERRNO("Failed to open NTFS inode");
471                 goto out_free_utf8_name;
472         }
473         path_len = ctx->path_len;
474         if (path_len != 1)
475                 ctx->path[path_len++] = '/';
476         memcpy(ctx->path + path_len, utf8_name, utf8_name_len + 1);
477         path_len += utf8_name_len;
478         child = NULL;
479         ret = build_dentry_tree_ntfs_recursive(&child, ctx->dir_ni,
480                                                ni, ctx->path, path_len, name_type,
481                                                ctx->lookup_table, ctx->sd_set,
482                                                ctx->config, ctx->ntfs_vol_p,
483                                                ctx->add_image_flags,
484                                                ctx->progress_func);
485         if (child)
486                 dentry_add_child(ctx->parent, child);
487         ntfs_inode_close(ni);
488 out_free_utf8_name:
489         FREE(utf8_name);
490         return ret;
491 }
492
493 /* Recursively build a WIM dentry tree corresponding to a NTFS volume.
494  * At the same time, update the WIM lookup table with lookup table entries for
495  * the NTFS streams, and build an array of security descriptors.
496  */
497 static int build_dentry_tree_ntfs_recursive(struct wim_dentry **root_p,
498                                             ntfs_inode *dir_ni,
499                                             ntfs_inode *ni,
500                                             char path[],
501                                             size_t path_len,
502                                             int name_type,
503                                             struct wim_lookup_table *lookup_table,
504                                             struct sd_set *sd_set,
505                                             const struct capture_config *config,
506                                             ntfs_volume **ntfs_vol_p,
507                                             int add_image_flags,
508                                             wimlib_progress_func_t progress_func)
509 {
510         u32 attributes;
511         int ret;
512         struct wim_dentry *root;
513
514         if (exclude_path(path, config, false)) {
515                 /* Exclude a file or directory tree based on the capture
516                  * configuration file */
517                 if ((add_image_flags & WIMLIB_ADD_IMAGE_FLAG_VERBOSE)
518                     && progress_func)
519                 {
520                         union wimlib_progress_info info;
521                         info.scan.cur_path = path;
522                         info.scan.excluded = true;
523                         progress_func(WIMLIB_PROGRESS_MSG_SCAN_DENTRY, &info);
524                 }
525                 *root_p = NULL;
526                 return 0;
527         }
528
529         /* Get file attributes */
530         struct SECURITY_CONTEXT ctx;
531         memset(&ctx, 0, sizeof(ctx));
532         ctx.vol = ni->vol;
533         ret = ntfs_xattr_system_getxattr(&ctx, XATTR_NTFS_ATTRIB,
534                                          ni, dir_ni, (char *)&attributes,
535                                          sizeof(u32));
536         if (ret != 4) {
537                 ERROR_WITH_ERRNO("Failed to get NTFS attributes from `%s'",
538                                  path);
539                 return WIMLIB_ERR_NTFS_3G;
540         }
541
542         if ((add_image_flags & WIMLIB_ADD_IMAGE_FLAG_VERBOSE)
543             && progress_func)
544         {
545                 union wimlib_progress_info info;
546                 info.scan.cur_path = path;
547                 info.scan.excluded = false;
548                 progress_func(WIMLIB_PROGRESS_MSG_SCAN_DENTRY, &info);
549         }
550
551         /* Create the new WIM dentry */
552         root = new_dentry_with_timeless_inode(path_basename(path));
553         if (!root) {
554                 if (errno == EILSEQ)
555                         return WIMLIB_ERR_INVALID_UTF8_STRING;
556                 else if (errno == ENOMEM)
557                         return WIMLIB_ERR_NOMEM;
558                 else
559                         return WIMLIB_ERR_ICONV_NOT_AVAILABLE;
560         }
561         *root_p = root;
562
563         if (name_type & FILE_NAME_WIN32) /* Win32 or Win32+DOS name */
564                 root->is_win32_name = 1;
565         root->d_inode->i_creation_time    = le64_to_cpu(ni->creation_time);
566         root->d_inode->i_last_write_time  = le64_to_cpu(ni->last_data_change_time);
567         root->d_inode->i_last_access_time = le64_to_cpu(ni->last_access_time);
568         root->d_inode->i_attributes       = le32_to_cpu(attributes);
569         root->d_inode->i_ino              = ni->mft_no;
570         root->d_inode->i_resolved         = 1;
571
572         if (attributes & FILE_ATTR_REPARSE_POINT) {
573                 /* Junction point, symbolic link, or other reparse point */
574                 ret = capture_ntfs_streams(root, ni, path, path_len,
575                                            lookup_table, ntfs_vol_p,
576                                            AT_REPARSE_POINT);
577         } else if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) {
578
579                 /* Normal directory */
580                 s64 pos = 0;
581                 struct dos_name_map dos_name_map = { .rb_root = {.rb_node = NULL} };
582                 struct readdir_ctx ctx = {
583                         .parent          = root,
584                         .dir_ni          = ni,
585                         .path            = path,
586                         .path_len        = path_len,
587                         .lookup_table    = lookup_table,
588                         .sd_set          = sd_set,
589                         .dos_name_map    = &dos_name_map,
590                         .config          = config,
591                         .ntfs_vol_p      = ntfs_vol_p,
592                         .add_image_flags = add_image_flags,
593                         .progress_func   = progress_func,
594                 };
595                 ret = ntfs_readdir(ni, &pos, &ctx, wim_ntfs_capture_filldir);
596                 if (ret) {
597                         ERROR_WITH_ERRNO("ntfs_readdir()");
598                         ret = WIMLIB_ERR_NTFS_3G;
599                 } else {
600                         ret = for_dentry_child(root, set_dentry_dos_name,
601                                                &dos_name_map);
602                 }
603                 destroy_dos_name_map(&dos_name_map);
604         } else {
605                 /* Normal file */
606                 ret = capture_ntfs_streams(root, ni, path, path_len,
607                                            lookup_table, ntfs_vol_p,
608                                            AT_DATA);
609         }
610         if (ret != 0)
611                 return ret;
612
613         /* Get security descriptor */
614         char _sd[1];
615         char *sd = _sd;
616         errno = 0;
617         ret = ntfs_xattr_system_getxattr(&ctx, XATTR_NTFS_ACL,
618                                          ni, dir_ni, sd,
619                                          sizeof(sd));
620         if (ret > sizeof(sd)) {
621                 sd = alloca(ret);
622                 ret = ntfs_xattr_system_getxattr(&ctx, XATTR_NTFS_ACL,
623                                                  ni, dir_ni, sd, ret);
624         }
625         if (ret > 0) {
626                 root->d_inode->i_security_id = sd_set_add_sd(sd_set, sd, ret);
627                 if (root->d_inode->i_security_id == -1) {
628                         ERROR("Out of memory");
629                         return WIMLIB_ERR_NOMEM;
630                 }
631                 DEBUG("Added security ID = %u for `%s'",
632                       root->d_inode->i_security_id, path);
633                 ret = 0;
634         } else if (ret < 0) {
635                 ERROR_WITH_ERRNO("Failed to get security information from "
636                                  "`%s'", path);
637                 ret = WIMLIB_ERR_NTFS_3G;
638         } else {
639                 root->d_inode->i_security_id = -1;
640                 DEBUG("No security ID for `%s'", path);
641         }
642         return ret;
643 }
644
645 int build_dentry_tree_ntfs(struct wim_dentry **root_p,
646                            const char *device,
647                            struct wim_lookup_table *lookup_table,
648                            struct wim_security_data *sd,
649                            const struct capture_config *config,
650                            int add_image_flags,
651                            wimlib_progress_func_t progress_func,
652                            void *extra_arg)
653 {
654         ntfs_volume *vol;
655         ntfs_inode *root_ni;
656         int ret;
657         struct sd_set sd_set = {
658                 .sd = sd,
659                 .rb_root = {NULL},
660         };
661         ntfs_volume **ntfs_vol_p = extra_arg;
662
663         DEBUG("Mounting NTFS volume `%s' read-only", device);
664
665 #ifdef HAVE_NTFS_MNT_RDONLY
666         /* NTFS-3g 2013 */
667         vol = ntfs_mount(device, NTFS_MNT_RDONLY);
668 #else
669         /* NTFS-3g 2011, 2012 */
670         vol = ntfs_mount(device, MS_RDONLY);
671 #endif
672         if (!vol) {
673                 ERROR_WITH_ERRNO("Failed to mount NTFS volume `%s' read-only",
674                                  device);
675                 return WIMLIB_ERR_NTFS_3G;
676         }
677         ntfs_open_secure(vol);
678
679         /* We don't want to capture the special NTFS files such as $Bitmap.  Not
680          * to be confused with "hidden" or "system" files which are real files
681          * that we do need to capture.  */
682         NVolClearShowSysFiles(vol);
683
684         DEBUG("Opening root NTFS dentry");
685         root_ni = ntfs_inode_open(vol, FILE_root);
686         if (!root_ni) {
687                 ERROR_WITH_ERRNO("Failed to open root inode of NTFS volume "
688                                  "`%s'", device);
689                 ret = WIMLIB_ERR_NTFS_3G;
690                 goto out;
691         }
692
693         /* Currently we assume that all the UTF-8 paths fit into this length and
694          * there is no check for overflow. */
695         char *path = MALLOC(32768);
696         if (!path) {
697                 ERROR("Could not allocate memory for NTFS pathname");
698                 ret = WIMLIB_ERR_NOMEM;
699                 goto out_cleanup;
700         }
701
702         path[0] = '/';
703         path[1] = '\0';
704         ret = build_dentry_tree_ntfs_recursive(root_p, NULL, root_ni, path, 1,
705                                                FILE_NAME_POSIX, lookup_table,
706                                                &sd_set,
707                                                config, ntfs_vol_p,
708                                                add_image_flags,
709                                                progress_func);
710 out_cleanup:
711         FREE(path);
712         ntfs_inode_close(root_ni);
713         destroy_sd_set(&sd_set);
714 out:
715         ntfs_index_ctx_put(vol->secure_xsii);
716         ntfs_index_ctx_put(vol->secure_xsdh);
717         ntfs_inode_close(vol->secure_ni);
718
719         if (ret) {
720                 if (ntfs_umount(vol, FALSE) != 0) {
721                         ERROR_WITH_ERRNO("Failed to unmount NTFS volume `%s'",
722                                          device);
723                         if (ret == 0)
724                                 ret = WIMLIB_ERR_NTFS_3G;
725                 }
726         } else {
727                 /* We need to leave the NTFS volume mounted so that we can read
728                  * the NTFS files again when we are actually writing the WIM */
729                 *ntfs_vol_p = vol;
730         }
731         return ret;
732 }