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