]> wimlib.net Git - wimlib/blob - src/unix_capture.c
Document xattr support
[wimlib] / src / unix_capture.c
1 /*
2  * unix_capture.c:  Capture a directory tree on UNIX.
3  */
4
5 /*
6  * Copyright (C) 2012-2016 Eric Biggers
7  *
8  * This file is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU Lesser General Public License as published by the Free
10  * Software Foundation; either version 3 of the License, or (at your option) any
11  * later version.
12  *
13  * This file is distributed in the hope that it will be useful, but WITHOUT
14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this file; if not, see http://www.gnu.org/licenses/.
20  */
21
22 #ifndef __WIN32__
23
24 #ifdef HAVE_CONFIG_H
25 #  include "config.h"
26 #endif
27
28 #include <dirent.h>
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <limits.h> /* for PATH_MAX */
32 #include <sys/stat.h>
33 #include <sys/types.h>
34 #ifdef HAVE_SYS_XATTR_H
35 #  include <sys/xattr.h>
36 #endif
37 #include <unistd.h>
38
39 #include "wimlib/blob_table.h"
40 #include "wimlib/dentry.h"
41 #include "wimlib/error.h"
42 #include "wimlib/reparse.h"
43 #include "wimlib/scan.h"
44 #include "wimlib/timestamp.h"
45 #include "wimlib/unix_data.h"
46 #include "wimlib/xattr.h"
47
48 #ifdef HAVE_FDOPENDIR
49 #  define my_fdopendir(dirfd_p) fdopendir(*(dirfd_p))
50 #else
51 static DIR *
52 my_fdopendir(int *dirfd_p)
53 {
54         DIR *dir = NULL;
55         int old_pwd;
56
57         old_pwd = open(".", O_RDONLY);
58         if (old_pwd >= 0) {
59                 if (!fchdir(*dirfd_p)) {
60                         dir = opendir(".");
61                         if (dir) {
62                                 close(*dirfd_p);
63                                 *dirfd_p = dirfd(dir);
64                         }
65                         fchdir(old_pwd);
66                 }
67                 close(old_pwd);
68         }
69         return dir;
70 }
71 #endif
72
73 #ifdef HAVE_OPENAT
74 #  define my_openat(full_path, dirfd, relpath, flags) \
75                 openat((dirfd), (relpath), (flags))
76 #else
77 #  define my_openat(full_path, dirfd, relpath, flags) \
78                 open((full_path), (flags))
79 #endif
80
81 #ifdef HAVE_READLINKAT
82 #  define my_readlinkat(full_path, dirfd, relpath, buf, bufsize) \
83                 readlinkat((dirfd), (relpath), (buf), (bufsize))
84 #else
85 #  define my_readlinkat(full_path, dirfd, relpath, buf, bufsize) \
86                 readlink((full_path), (buf), (bufsize))
87 #endif
88
89 #ifdef HAVE_FSTATAT
90 #  define my_fstatat(full_path, dirfd, relpath, stbuf, flags)   \
91         fstatat((dirfd), (relpath), (stbuf), (flags))
92 #else
93 #  define my_fstatat(full_path, dirfd, relpath, stbuf, flags)   \
94         ((flags) & AT_SYMLINK_NOFOLLOW) ? \
95                 lstat((full_path), (stbuf)) : \
96                 stat((full_path), (stbuf))
97 #endif
98
99 #ifndef AT_FDCWD
100 #  define AT_FDCWD      -100
101 #endif
102
103 #ifndef AT_SYMLINK_NOFOLLOW
104 #  define AT_SYMLINK_NOFOLLOW   0x100
105 #endif
106
107 #ifdef HAVE_XATTR_SUPPORT
108 /*
109  * Retrieves the values of the xattrs named by the null-terminated @names of the
110  * file at @path and serializes the xattr names and values into @entries.  If
111  * successful, returns the number of bytes used in @entries.  If unsuccessful,
112  * returns -1 and sets errno (ERANGE if @entries was too small).
113  */
114 static ssize_t
115 gather_xattr_entries(const char *path, const char *names, size_t names_size,
116                      void *entries, size_t entries_size)
117 {
118         const char * const names_end = names + names_size;
119         void * const entries_end = entries + entries_size;
120         const char *name = names;
121         struct wimlib_xattr_entry *entry = entries;
122
123         wimlib_assert((uintptr_t)entries % 4 == 0 &&
124                       entries_size % 4 == 0 && names_size != 0);
125         do {
126                 size_t name_len = strnlen(name, names_end - name);
127                 void *value;
128                 ssize_t value_len;
129
130                 if (name_len == 0 || name_len >= names_end - name ||
131                     (u16)name_len != name_len)
132                         goto malformed;
133
134                 /*
135                  * Note: we take care to always call lgetxattr() with a nonzero
136                  * size, since zero size means to return the value length only.
137                  */
138                 if (entries_end - (void *)entry <= sizeof(*entry) + name_len) {
139                         errno = ERANGE;
140                         return -1;
141                 }
142
143                 entry->name_len = cpu_to_le16(name_len);
144                 entry->reserved = cpu_to_le16(0);
145                 value = mempcpy(entry->name, name, name_len);
146
147                 value_len = lgetxattr(path, name, value, entries_end - value);
148                 if (value_len < 0) {
149                         if (errno != ERANGE) {
150                                 ERROR_WITH_ERRNO("\"%s\": unable to read "
151                                                  "extended attribute \"%s\"",
152                                                  path, name);
153                         }
154                         return -1;
155                 }
156                 if ((u32)value_len != value_len)
157                         goto malformed;
158                 entry->value_len = cpu_to_le32(value_len);
159                 value += value_len;
160
161                 /* pad value to next 4-byte boundary */
162                 memset(value, 0, -(uintptr_t)value & 3);
163                 value += -(uintptr_t)value & 3;
164
165                 entry = value;
166                 name += name_len + 1;
167         } while (name < names_end);
168
169         return (void *)entry - entries;
170
171 malformed:
172         errno = EINVAL;
173         ERROR("\"%s\": malformed extended attribute name list", path);
174         return -1;
175 }
176
177 static int
178 create_xattr_stream(const char *path, struct wim_inode *inode,
179                     struct blob_table *blob_table,
180                     const char *names, size_t names_size)
181 {
182         char _entries[1024] _aligned_attribute(4);
183         char *entries = _entries;
184         size_t entries_avail = ARRAY_LEN(_entries);
185         ssize_t entries_size;
186         struct wim_inode_stream *strm;
187         int ret;
188
189 retry:
190         /* Serialize the xattrs into @entries */
191         entries_size = gather_xattr_entries(path, names, names_size,
192                                             entries, entries_avail);
193         if (entries_size < 0) {
194                 ret = WIMLIB_ERR_STAT;
195                 if (errno != ERANGE)
196                         goto out;
197                 /* Not enough space in @entries.  Reallocate it. */
198                 if (entries != _entries)
199                         FREE(entries);
200                 ret = WIMLIB_ERR_NOMEM;
201                 entries_avail *= 2;
202                 entries = MALLOC(entries_avail);
203                 if (!entries)
204                         goto out;
205                 goto retry;
206         }
207
208         /* Add the xattr stream from the serialized xattrs in @entries */
209         ret = WIMLIB_ERR_NOMEM;
210         strm = inode_add_stream_with_data(inode, STREAM_TYPE_LINUX_XATTR,
211                                           NO_STREAM_NAME, entries, entries_size,
212                                           blob_table);
213         if (!strm)
214                 goto out;
215
216         if (!inode_set_linux_xattr_hash(inode, stream_hash(strm))) {
217                 inode_remove_stream(inode, strm, blob_table);
218                 goto out;
219         }
220
221         ret = 0;
222 out:
223         if (entries != _entries)
224                 FREE(entries);
225         return ret;
226 }
227
228 /*
229  * If the file at @path has Linux-style extended attributes, read them into
230  * memory and add them to @inode as a stream (deduplicated in @blob_table).
231  */
232 static noinline_for_stack int
233 scan_linux_xattrs(const char *path, struct wim_inode *inode,
234                   struct blob_table *blob_table)
235 {
236         char _names[1024];
237         char *names = _names;
238         ssize_t names_size = ARRAY_LEN(_names);
239         int ret;
240
241 retry:
242         /* Gather the names of the xattrs of the file at @path */
243         ret = 0;
244         names_size = llistxattr(path, names, names_size);
245         if (names_size == 0) /* No xattrs? */
246                 goto out;
247         if (names_size < 0) {
248                 ret = 0;
249                 if (errno == ENOTSUP) /* No xattrs (unsupported or disabled)? */
250                         goto out;
251                 if (errno == ERANGE) {
252                         /*
253                          * Not enough space in @names.  Ask for how much space
254                          * we need, then try again.
255                          */
256                         names_size = llistxattr(path, NULL, 0);
257                         if (names_size >= 0) {
258                                 if (names != _names)
259                                         FREE(names);
260                                 ret = WIMLIB_ERR_NOMEM;
261                                 names = MALLOC(names_size);
262                                 if (!names)
263                                         goto out;
264                                 goto retry;
265                         }
266                 }
267                 WARNING_WITH_ERRNO("\"%s\": unable to list extended attributes",
268                                    path);
269                 ret = WIMLIB_ERR_STAT;
270                 goto out;
271         }
272
273         /*
274          * We have a nonempty list of xattr names.  Gather the xattr values and
275          * add the xattr stream.
276          */
277         ret = create_xattr_stream(path, inode, blob_table, names, names_size);
278 out:
279         if (names != _names)
280                 FREE(names);
281         return ret;
282 }
283 #endif /* HAVE_XATTR_SUPPORT */
284
285 static int
286 unix_scan_regular_file(const char *path, u64 blocks, u64 size,
287                        struct wim_inode *inode,
288                        struct list_head *unhashed_blobs)
289 {
290         struct blob_descriptor *blob = NULL;
291         struct wim_inode_stream *strm;
292
293         /*
294          * Set FILE_ATTRIBUTE_SPARSE_FILE if the file uses less disk space than
295          * expected given its size.
296          */
297         if (blocks < DIV_ROUND_UP(size, 512))
298                 inode->i_attributes = FILE_ATTRIBUTE_SPARSE_FILE;
299         else
300                 inode->i_attributes = FILE_ATTRIBUTE_NORMAL;
301
302         if (size) {
303                 blob = new_blob_descriptor();
304                 if (unlikely(!blob))
305                         goto err_nomem;
306                 blob->file_on_disk = STRDUP(path);
307                 if (unlikely(!blob->file_on_disk))
308                         goto err_nomem;
309                 blob->blob_location = BLOB_IN_FILE_ON_DISK;
310                 blob->size = size;
311                 blob->file_inode = inode;
312         }
313
314         strm = inode_add_stream(inode, STREAM_TYPE_DATA, NO_STREAM_NAME, blob);
315         if (unlikely(!strm))
316                 goto err_nomem;
317
318         prepare_unhashed_blob(blob, inode, strm->stream_id, unhashed_blobs);
319         return 0;
320
321 err_nomem:
322         free_blob_descriptor(blob);
323         return WIMLIB_ERR_NOMEM;
324 }
325
326 static int
327 unix_build_dentry_tree_recursive(struct wim_dentry **tree_ret,
328                                  char *path, size_t path_len,
329                                  int dirfd, const char *relpath,
330                                  struct scan_params *params);
331
332 static int
333 unix_scan_directory(struct wim_dentry *dir_dentry,
334                     char *full_path, size_t full_path_len,
335                     int parent_dirfd, const char *dir_relpath,
336                     struct scan_params *params)
337 {
338
339         int dirfd;
340         DIR *dir;
341         int ret;
342
343         dirfd = my_openat(full_path, parent_dirfd, dir_relpath, O_RDONLY);
344         if (dirfd < 0) {
345                 ERROR_WITH_ERRNO("\"%s\": Can't open directory", full_path);
346                 return WIMLIB_ERR_OPENDIR;
347         }
348
349         dir_dentry->d_inode->i_attributes = FILE_ATTRIBUTE_DIRECTORY;
350         dir = my_fdopendir(&dirfd);
351         if (!dir) {
352                 ERROR_WITH_ERRNO("\"%s\": Can't open directory", full_path);
353                 close(dirfd);
354                 return WIMLIB_ERR_OPENDIR;
355         }
356
357         ret = 0;
358         for (;;) {
359                 struct dirent *entry;
360                 struct wim_dentry *child;
361                 size_t name_len;
362
363                 errno = 0;
364                 entry = readdir(dir);
365                 if (!entry) {
366                         if (errno) {
367                                 ret = WIMLIB_ERR_READ;
368                                 ERROR_WITH_ERRNO("\"%s\": Error reading directory",
369                                                  full_path);
370                         }
371                         break;
372                 }
373
374                 name_len = strlen(entry->d_name);
375
376                 if (should_ignore_filename(entry->d_name, name_len))
377                         continue;
378
379                 full_path[full_path_len] = '/';
380                 memcpy(&full_path[full_path_len + 1], entry->d_name, name_len + 1);
381                 ret = unix_build_dentry_tree_recursive(&child,
382                                                        full_path,
383                                                        full_path_len + 1 + name_len,
384                                                        dirfd,
385                                                        &full_path[full_path_len + 1],
386                                                        params);
387                 full_path[full_path_len] = '\0';
388                 if (ret)
389                         break;
390                 attach_scanned_tree(dir_dentry, child, params->blob_table);
391         }
392         closedir(dir);
393         return ret;
394 }
395
396 /*
397  * Given an absolute symbolic link target (UNIX-style, beginning with '/'),
398  * determine whether it points into the directory identified by @ino and @dev.
399  * If yes, return the suffix of @target which is relative to this directory, but
400  * retaining leading slashes.  If no, return @target.
401  *
402  * Here are some examples, assuming that the @ino/@dev directory is "/home/e":
403  *
404  *      Original target         New target
405  *      ---------------         ----------
406  *      /home/e/test            /test
407  *      /home/e/test/           /test/
408  *      //home//e//test//       //test//
409  *      /home/e                                         (empty string)
410  *      /home/e/                /
411  *      /usr/lib                /usr/lib                (external link)
412  *
413  * Because of the possibility of other links into the @ino/@dev directory and/or
414  * multiple path separators, we can't simply do a string comparison; instead we
415  * need to stat() each ancestor directory.
416  *
417  * If the link points directly to the @ino/@dev directory with no trailing
418  * slashes, then the new target will be an empty string.  This is not a valid
419  * UNIX symlink target, but we store this in the archive anyway since the target
420  * is intended to be de-relativized when the link is extracted.
421  */
422 static char *
423 unix_relativize_link_target(char *target, u64 ino, u64 dev)
424 {
425         char *p = target;
426
427         do {
428                 char save;
429                 struct stat stbuf;
430                 int ret;
431
432                 /* Skip slashes (guaranteed to be at least one here)  */
433                 do {
434                         p++;
435                 } while (*p == '/');
436
437                 /* End of string?  */
438                 if (!*p)
439                         break;
440
441                 /* Skip non-slashes (guaranteed to be at least one here)  */
442                 do {
443                         p++;
444                 } while (*p && *p != '/');
445
446                 /* Get the inode and device numbers for this prefix.  */
447                 save = *p;
448                 *p = '\0';
449                 ret = stat(target, &stbuf);
450                 *p = save;
451
452                 if (ret) {
453                         /* stat() failed.  Assume the link points outside the
454                          * directory tree being captured.  */
455                         break;
456                 }
457
458                 if (stbuf.st_ino == ino && stbuf.st_dev == dev) {
459                         /* Link points inside directory tree being captured.
460                          * Return abbreviated path.  */
461                         return p;
462                 }
463         } while (*p);
464
465         /* Link does not point inside directory tree being captured.  */
466         return target;
467 }
468
469 static noinline_for_stack int
470 unix_scan_symlink(const char *full_path, int dirfd, const char *relpath,
471                   struct wim_inode *inode, struct scan_params *params)
472 {
473         char orig_target[REPARSE_POINT_MAX_SIZE];
474         char *target = orig_target;
475         int ret;
476
477         /* Read the UNIX symbolic link target.  */
478         ret = my_readlinkat(full_path, dirfd, relpath, target,
479                             sizeof(orig_target));
480         if (unlikely(ret < 0)) {
481                 ERROR_WITH_ERRNO("\"%s\": Can't read target of symbolic link",
482                                  full_path);
483                 return WIMLIB_ERR_READLINK;
484         }
485         if (unlikely(ret >= sizeof(orig_target))) {
486                 ERROR("\"%s\": target of symbolic link is too long", full_path);
487                 return WIMLIB_ERR_READLINK;
488         }
489         target[ret] = '\0';
490
491         /* If the link is absolute and reparse point fixups are enabled, then
492          * change it to be "absolute" relative to the tree being captured.  */
493         if (target[0] == '/' && (params->add_flags & WIMLIB_ADD_FLAG_RPFIX)) {
494                 int status = WIMLIB_SCAN_DENTRY_NOT_FIXED_SYMLINK;
495
496                 params->progress.scan.cur_path = full_path;
497                 params->progress.scan.symlink_target = target;
498
499                 target = unix_relativize_link_target(target,
500                                                      params->capture_root_ino,
501                                                      params->capture_root_dev);
502                 if (target != orig_target) {
503                         /* Link target was fixed.  */
504                         inode->i_rp_flags &= ~WIM_RP_FLAG_NOT_FIXED;
505                         status = WIMLIB_SCAN_DENTRY_FIXED_SYMLINK;
506                 }
507                 ret = do_scan_progress(params, status, NULL);
508                 if (ret)
509                         return ret;
510         }
511
512         /* Translate the UNIX symlink target into a Windows reparse point.  */
513         ret = wim_inode_set_symlink(inode, target, params->blob_table);
514         if (unlikely(ret)) {
515                 if (ret == WIMLIB_ERR_INVALID_UTF8_STRING) {
516                         ERROR("\"%s\": target of symbolic link is not valid "
517                               "UTF-8.  This is not supported.", full_path);
518                 }
519                 return ret;
520         }
521
522         /* On Windows, a reparse point can be set on both directory and
523          * non-directory files.  Usually, a link that is intended to point to a
524          * (non-)directory is stored as a reparse point on a (non-)directory
525          * file.  Replicate this behavior by examining the target file.  */
526         struct stat stbuf;
527         if (my_fstatat(full_path, dirfd, relpath, &stbuf, 0) == 0 &&
528             S_ISDIR(stbuf.st_mode))
529                 inode->i_attributes |= FILE_ATTRIBUTE_DIRECTORY;
530         return 0;
531 }
532
533 static int
534 unix_build_dentry_tree_recursive(struct wim_dentry **tree_ret,
535                                  char *full_path, size_t full_path_len,
536                                  int dirfd, const char *relpath,
537                                  struct scan_params *params)
538 {
539         struct wim_dentry *tree = NULL;
540         struct wim_inode *inode = NULL;
541         int ret;
542         struct stat stbuf;
543         int stat_flags;
544
545         ret = try_exclude(full_path, params);
546         if (unlikely(ret < 0)) /* Excluded? */
547                 goto out_progress;
548         if (unlikely(ret > 0)) /* Error? */
549                 goto out;
550
551         if (params->add_flags & (WIMLIB_ADD_FLAG_DEREFERENCE |
552                                  WIMLIB_ADD_FLAG_ROOT))
553                 stat_flags = 0;
554         else
555                 stat_flags = AT_SYMLINK_NOFOLLOW;
556
557         ret = my_fstatat(full_path, dirfd, relpath, &stbuf, stat_flags);
558
559         if (ret) {
560                 ERROR_WITH_ERRNO("\"%s\": Can't read metadata", full_path);
561                 ret = WIMLIB_ERR_STAT;
562                 goto out;
563         }
564
565         if (!(params->add_flags & WIMLIB_ADD_FLAG_UNIX_DATA)) {
566                 if (unlikely(!S_ISREG(stbuf.st_mode) &&
567                              !S_ISDIR(stbuf.st_mode) &&
568                              !S_ISLNK(stbuf.st_mode)))
569                 {
570                         if (params->add_flags &
571                             WIMLIB_ADD_FLAG_NO_UNSUPPORTED_EXCLUDE)
572                         {
573                                 ERROR("\"%s\": File type is unsupported",
574                                       full_path);
575                                 ret = WIMLIB_ERR_UNSUPPORTED_FILE;
576                                 goto out;
577                         }
578                         params->progress.scan.cur_path = full_path;
579                         ret = do_scan_progress(params,
580                                                WIMLIB_SCAN_DENTRY_UNSUPPORTED,
581                                                NULL);
582                         goto out;
583                 }
584         }
585
586         ret = inode_table_new_dentry(params->inode_table, relpath,
587                                      stbuf.st_ino, stbuf.st_dev, false, &tree);
588         if (unlikely(ret)) {
589                 if (ret == WIMLIB_ERR_INVALID_UTF8_STRING) {
590                         ERROR("\"%s\": filename is not valid UTF-8.  "
591                               "This is not supported.", full_path);
592                 }
593                 goto out;
594         }
595
596         inode = tree->d_inode;
597
598         /* Already seen this inode?  */
599         if (inode->i_nlink > 1)
600                 goto out_progress;
601
602 #ifdef HAVE_STAT_NANOSECOND_PRECISION
603         inode->i_creation_time = timespec_to_wim_timestamp(&stbuf.st_mtim);
604         inode->i_last_write_time = timespec_to_wim_timestamp(&stbuf.st_mtim);
605         inode->i_last_access_time = timespec_to_wim_timestamp(&stbuf.st_atim);
606 #else
607         inode->i_creation_time = time_t_to_wim_timestamp(stbuf.st_mtime);
608         inode->i_last_write_time = time_t_to_wim_timestamp(stbuf.st_mtime);
609         inode->i_last_access_time = time_t_to_wim_timestamp(stbuf.st_atime);
610 #endif
611         if (params->add_flags & WIMLIB_ADD_FLAG_UNIX_DATA) {
612                 struct wimlib_unix_data unix_data;
613
614                 unix_data.uid = stbuf.st_uid;
615                 unix_data.gid = stbuf.st_gid;
616                 unix_data.mode = stbuf.st_mode;
617                 unix_data.rdev = stbuf.st_rdev;
618                 if (!inode_set_unix_data(inode, &unix_data, UNIX_DATA_ALL)) {
619                         ret = WIMLIB_ERR_NOMEM;
620                         goto out;
621                 }
622 #ifdef HAVE_XATTR_SUPPORT
623                 ret = scan_linux_xattrs(full_path, inode, params->blob_table);
624                 if (ret)
625                         goto out;
626 #endif
627         }
628
629         if (params->add_flags & WIMLIB_ADD_FLAG_ROOT) {
630                 params->capture_root_ino = stbuf.st_ino;
631                 params->capture_root_dev = stbuf.st_dev;
632                 params->add_flags &= ~WIMLIB_ADD_FLAG_ROOT;
633         }
634
635         if (S_ISREG(stbuf.st_mode)) {
636                 ret = unix_scan_regular_file(full_path, stbuf.st_blocks,
637                                              stbuf.st_size, inode,
638                                              params->unhashed_blobs);
639         } else if (S_ISDIR(stbuf.st_mode)) {
640                 ret = unix_scan_directory(tree, full_path, full_path_len,
641                                           dirfd, relpath, params);
642         } else if (S_ISLNK(stbuf.st_mode)) {
643                 ret = unix_scan_symlink(full_path, dirfd, relpath,
644                                         inode, params);
645         }
646
647         if (ret)
648                 goto out;
649
650 out_progress:
651         params->progress.scan.cur_path = full_path;
652         if (likely(tree))
653                 ret = do_scan_progress(params, WIMLIB_SCAN_DENTRY_OK, inode);
654         else
655                 ret = do_scan_progress(params, WIMLIB_SCAN_DENTRY_EXCLUDED, NULL);
656 out:
657         if (unlikely(ret)) {
658                 free_dentry_tree(tree, params->blob_table);
659                 tree = NULL;
660                 ret = report_scan_error(params, ret, full_path);
661         }
662         *tree_ret = tree;
663         return ret;
664 }
665
666 /*
667  * unix_build_dentry_tree():
668  *      Builds a tree of WIM dentries from an on-disk directory tree (UNIX
669  *      version; no NTFS-specific data is captured).
670  *
671  * @root_ret:   Place to return a pointer to the root of the dentry tree.  Set
672  *              to NULL if the file or directory was excluded from capture.
673  *
674  * @root_disk_path:  The path to the root of the directory tree on disk.
675  *
676  * @params:     See doc for `struct scan_params'.
677  *
678  * @return:     0 on success, nonzero on failure.  It is a failure if any of
679  *              the files cannot be `stat'ed, or if any of the needed
680  *              directories cannot be opened or read.  Failure to add the files
681  *              to the WIM may still occur later when trying to actually read
682  *              the on-disk files during a call to wimlib_write() or
683  *              wimlib_overwrite().
684  */
685 int
686 unix_build_dentry_tree(struct wim_dentry **root_ret,
687                        const char *root_disk_path, struct scan_params *params)
688 {
689         size_t path_len;
690         size_t path_bufsz;
691         char *path_buf;
692         int ret;
693
694         path_len = strlen(root_disk_path);
695         path_bufsz = min(32790, PATH_MAX + 1);
696
697         if (path_len >= path_bufsz)
698                 return WIMLIB_ERR_INVALID_PARAM;
699
700         path_buf = MALLOC(path_bufsz);
701         if (!path_buf)
702                 return WIMLIB_ERR_NOMEM;
703         memcpy(path_buf, root_disk_path, path_len + 1);
704
705         params->capture_root_nchars = path_len;
706
707         ret = unix_build_dentry_tree_recursive(root_ret, path_buf, path_len,
708                                                AT_FDCWD, path_buf, params);
709         FREE(path_buf);
710         return ret;
711 }
712
713 #endif /* !__WIN32__ */