]> wimlib.net Git - wimlib/blob - src/unix_capture.c
Use LGPLv3+ for src/*.c
[wimlib] / src / unix_capture.c
1 /*
2  * unix_capture.c:  Capture a directory tree on UNIX.
3  */
4
5 /*
6  * Copyright (C) 2012, 2013, 2014 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 <unistd.h>
34
35 #include "wimlib/capture.h"
36 #include "wimlib/dentry.h"
37 #include "wimlib/error.h"
38 #include "wimlib/lookup_table.h"
39 #include "wimlib/reparse.h"
40 #include "wimlib/timestamp.h"
41 #include "wimlib/unix_data.h"
42
43 #ifdef HAVE_FDOPENDIR
44 #  define my_fdopendir(dirfd_p) fdopendir(*(dirfd_p))
45 #else
46 static DIR *
47 my_fdopendir(int *dirfd_p)
48 {
49         DIR *dir = NULL;
50         int old_pwd;
51
52         old_pwd = open(".", O_RDONLY);
53         if (old_pwd >= 0) {
54                 if (!fchdir(*dirfd_p)) {
55                         dir = opendir(".");
56                         if (dir) {
57                                 close(*dirfd_p);
58                                 *dirfd_p = dirfd(dir);
59                         }
60                         fchdir(old_pwd);
61                 }
62                 close(old_pwd);
63         }
64         return dir;
65 }
66 #endif
67
68 #ifdef HAVE_OPENAT
69 #  define my_openat(full_path, dirfd, relpath, flags) \
70                 openat((dirfd), (relpath), (flags))
71 #else
72 #  define my_openat(full_path, dirfd, relpath, flags) \
73                 open((full_path), (flags))
74 #endif
75
76 #ifdef HAVE_READLINKAT
77 #  define my_readlinkat(full_path, dirfd, relpath, buf, bufsize) \
78                 readlinkat((dirfd), (relpath), (buf), (bufsize))
79 #else
80 #  define my_readlinkat(full_path, dirfd, relpath, buf, bufsize) \
81                 readlink((full_path), (buf), (bufsize))
82 #endif
83
84 #ifdef HAVE_FSTATAT
85 #  define my_fstatat(full_path, dirfd, relpath, stbuf, flags)   \
86         fstatat((dirfd), (relpath), (stbuf), (flags))
87 #else
88 #  define my_fstatat(full_path, dirfd, relpath, stbuf, flags)   \
89         ((flags) & AT_SYMLINK_NOFOLLOW) ? \
90                 lstat((full_path), (stbuf)) : \
91                 stat((full_path), (stbuf))
92 #endif
93
94 #ifndef AT_FDCWD
95 #  define AT_FDCWD      -100
96 #endif
97
98 #ifndef AT_SYMLINK_NOFOLLOW
99 #  define AT_SYMLINK_NOFOLLOW   0x100
100 #endif
101
102 static int
103 unix_scan_regular_file(const char *path, u64 size, struct wim_inode *inode,
104                        struct list_head *unhashed_streams)
105 {
106         struct wim_lookup_table_entry *lte;
107         char *file_on_disk;
108
109         inode->i_attributes = FILE_ATTRIBUTE_NORMAL;
110
111         /* Empty files do not have to have a lookup table entry. */
112         if (!size)
113                 return 0;
114
115         file_on_disk = STRDUP(path);
116         if (!file_on_disk)
117                 return WIMLIB_ERR_NOMEM;
118         lte = new_lookup_table_entry();
119         if (!lte) {
120                 FREE(file_on_disk);
121                 return WIMLIB_ERR_NOMEM;
122         }
123         lte->file_on_disk = file_on_disk;
124         lte->resource_location = RESOURCE_IN_FILE_ON_DISK;
125         lte->size = size;
126         add_unhashed_stream(lte, inode, 0, unhashed_streams);
127         inode->i_lte = lte;
128         return 0;
129 }
130
131 static int
132 unix_build_dentry_tree_recursive(struct wim_dentry **tree_ret,
133                                  char *path, size_t path_len,
134                                  int dirfd, const char *relpath,
135                                  struct add_image_params *params);
136
137 static int
138 unix_scan_directory(struct wim_dentry *dir_dentry,
139                     char *full_path, size_t full_path_len,
140                     int parent_dirfd, const char *dir_relpath,
141                     struct add_image_params *params)
142 {
143
144         int dirfd;
145         DIR *dir;
146         int ret;
147
148         dirfd = my_openat(full_path, parent_dirfd, dir_relpath, O_RDONLY);
149         if (dirfd < 0) {
150                 ERROR_WITH_ERRNO("\"%s\": Can't open directory", full_path);
151                 return WIMLIB_ERR_OPENDIR;
152         }
153
154         dir_dentry->d_inode->i_attributes = FILE_ATTRIBUTE_DIRECTORY;
155         dir = my_fdopendir(&dirfd);
156         if (!dir) {
157                 ERROR_WITH_ERRNO("\"%s\": Can't open directory", full_path);
158                 close(dirfd);
159                 return WIMLIB_ERR_OPENDIR;
160         }
161
162         ret = 0;
163         for (;;) {
164                 struct dirent *entry;
165                 struct wim_dentry *child;
166                 size_t name_len;
167
168                 errno = 0;
169                 entry = readdir(dir);
170                 if (!entry) {
171                         if (errno) {
172                                 ret = WIMLIB_ERR_READ;
173                                 ERROR_WITH_ERRNO("\"%s\": Error reading directory",
174                                                  full_path);
175                         }
176                         break;
177                 }
178
179                 if (entry->d_name[0] == '.' &&
180                     (entry->d_name[1] == '\0' ||
181                      (entry->d_name[1] == '.' && entry->d_name[2] == '\0')))
182                         continue;
183
184                 full_path[full_path_len] = '/';
185                 name_len = strlen(entry->d_name);
186                 memcpy(&full_path[full_path_len + 1], entry->d_name, name_len + 1);
187                 ret = unix_build_dentry_tree_recursive(&child,
188                                                        full_path,
189                                                        full_path_len + 1 + name_len,
190                                                        dirfd,
191                                                        &full_path[full_path_len + 1],
192                                                        params);
193                 full_path[full_path_len] = '\0';
194                 if (ret)
195                         break;
196                 if (child)
197                         dentry_add_child(dir_dentry, child);
198         }
199         closedir(dir);
200         return ret;
201 }
202
203 /* Given an absolute symbolic link target @dest (UNIX-style, beginning
204  * with '/'), determine whether it points into the directory specified by
205  * @ino and @dev.  If so, return the target modified to be "absolute"
206  * relative to this directory.  Otherwise, return NULL.  */
207 static char *
208 unix_fixup_abslink(char *dest, u64 ino, u64 dev)
209 {
210         char *p = dest;
211
212         do {
213                 char save;
214                 struct stat stbuf;
215                 int ret;
216
217                 /* Skip non-slashes.  */
218                 while (*p && *p != '/')
219                         p++;
220
221                 /* Skip slashes.  */
222                 while (*p && *p == '/')
223                         p++;
224
225                 /* Get inode and device for this prefix.  */
226                 save = *p;
227                 *p = '\0';
228                 ret = stat(dest, &stbuf);
229                 *p = save;
230
231                 if (ret) {
232                         /* stat() failed.  Assume the link points outside the
233                          * directory tree being captured.  */
234                         break;
235                 }
236
237                 if (stbuf.st_ino == ino && stbuf.st_dev == dev) {
238                         /* Link points inside directory tree being captured.
239                          * Return abbreviated path.  */
240                         *--p = '/';
241                         while (p > dest && *(p - 1) == '/')
242                                 p--;
243                         return p;
244                 }
245         } while (*p);
246
247         /* Link does not point inside directory tree being captured.  */
248         return NULL;
249 }
250
251 static int
252 unix_scan_symlink(const char *full_path, int dirfd, const char *relpath,
253                   struct wim_inode *inode, struct add_image_params *params)
254 {
255         char deref_name_buf[4096];
256         ssize_t deref_name_len;
257         char *dest;
258         int ret;
259
260         inode->i_attributes = FILE_ATTRIBUTE_REPARSE_POINT;
261         inode->i_reparse_tag = WIM_IO_REPARSE_TAG_SYMLINK;
262
263         /* The idea here is to call readlink() to get the UNIX target of the
264          * symbolic link, then turn the target into a reparse point data buffer
265          * that contains a relative or absolute symbolic link. */
266         deref_name_len = my_readlinkat(full_path, dirfd, relpath,
267                                        deref_name_buf, sizeof(deref_name_buf) - 1);
268         if (deref_name_len < 0) {
269                 ERROR_WITH_ERRNO("\"%s\": Can't read target of symbolic link",
270                                  full_path);
271                 return WIMLIB_ERR_READLINK;
272         }
273
274         dest = deref_name_buf;
275
276         dest[deref_name_len] = '\0';
277
278         if ((params->add_flags & WIMLIB_ADD_FLAG_RPFIX) &&
279              dest[0] == '/')
280         {
281                 char *fixed_dest;
282
283                 /* RPFIX (reparse point fixup) mode:  Change target of absolute
284                  * symbolic link to be "absolute" relative to the tree being
285                  * captured.  */
286                 fixed_dest = unix_fixup_abslink(dest,
287                                                 params->capture_root_ino,
288                                                 params->capture_root_dev);
289                 params->progress.scan.cur_path = full_path;
290                 params->progress.scan.symlink_target = deref_name_buf;
291                 if (fixed_dest) {
292                         /* Link points inside the tree being captured, so it was
293                          * fixed.  */
294                         inode->i_not_rpfixed = 0;
295                         dest = fixed_dest;
296                         ret = do_capture_progress(params,
297                                                   WIMLIB_SCAN_DENTRY_FIXED_SYMLINK,
298                                                   NULL);
299                 } else {
300                         /* Link points outside the tree being captured, so it
301                          * was not fixed.  */
302                         ret = do_capture_progress(params,
303                                                   WIMLIB_SCAN_DENTRY_NOT_FIXED_SYMLINK,
304                                                   NULL);
305                 }
306                 if (ret)
307                         return ret;
308         }
309         ret = wim_inode_set_symlink(inode, dest, params->lookup_table);
310         if (ret)
311                 return ret;
312
313         /* Unfortunately, Windows seems to have the concept of "file" symbolic
314          * links as being different from "directory" symbolic links...  so
315          * FILE_ATTRIBUTE_DIRECTORY needs to be set on the symbolic link if the
316          * *target* of the symbolic link is a directory.  */
317         struct stat stbuf;
318         if (my_fstatat(full_path, dirfd, relpath, &stbuf, 0) == 0 &&
319             S_ISDIR(stbuf.st_mode))
320                 inode->i_attributes |= FILE_ATTRIBUTE_DIRECTORY;
321         return 0;
322 }
323
324 static int
325 unix_build_dentry_tree_recursive(struct wim_dentry **tree_ret,
326                                  char *full_path, size_t full_path_len,
327                                  int dirfd, const char *relpath,
328                                  struct add_image_params *params)
329 {
330         struct wim_dentry *tree = NULL;
331         struct wim_inode *inode = NULL;
332         int ret;
333         struct stat stbuf;
334         int stat_flags;
335
336         ret = try_exclude(full_path, full_path_len, params);
337         if (ret < 0) /* Excluded? */
338                 goto out_progress;
339         if (ret > 0) /* Error? */
340                 goto out;
341
342         if (params->add_flags & (WIMLIB_ADD_FLAG_DEREFERENCE |
343                                  WIMLIB_ADD_FLAG_ROOT))
344                 stat_flags = 0;
345         else
346                 stat_flags = AT_SYMLINK_NOFOLLOW;
347
348         ret = my_fstatat(full_path, dirfd, relpath, &stbuf, stat_flags);
349
350         if (ret) {
351                 ERROR_WITH_ERRNO("\"%s\": Can't read metadata", full_path);
352                 ret = WIMLIB_ERR_STAT;
353                 goto out;
354         }
355
356         if (!(params->add_flags & WIMLIB_ADD_FLAG_UNIX_DATA)) {
357                 if (unlikely(!S_ISREG(stbuf.st_mode) &&
358                              !S_ISDIR(stbuf.st_mode) &&
359                              !S_ISLNK(stbuf.st_mode)))
360                 {
361                         if (params->add_flags &
362                             WIMLIB_ADD_FLAG_NO_UNSUPPORTED_EXCLUDE)
363                         {
364                                 ERROR("\"%s\": File type is unsupported",
365                                       full_path);
366                                 ret = WIMLIB_ERR_UNSUPPORTED_FILE;
367                                 goto out;
368                         }
369                         params->progress.scan.cur_path = full_path;
370                         ret = do_capture_progress(params,
371                                                   WIMLIB_SCAN_DENTRY_UNSUPPORTED,
372                                                   NULL);
373                         goto out;
374                 }
375         }
376
377         ret = inode_table_new_dentry(params->inode_table, relpath,
378                                      stbuf.st_ino, stbuf.st_dev,
379                                      S_ISDIR(stbuf.st_mode), &tree);
380         if (ret)
381                 goto out;
382
383         inode = tree->d_inode;
384
385         /* Already seen this inode?  */
386         if (inode->i_nlink > 1)
387                 goto out_progress;
388
389 #ifdef HAVE_STAT_NANOSECOND_PRECISION
390         inode->i_creation_time = timespec_to_wim_timestamp(stbuf.st_mtim);
391         inode->i_last_write_time = timespec_to_wim_timestamp(stbuf.st_mtim);
392         inode->i_last_access_time = timespec_to_wim_timestamp(stbuf.st_atim);
393 #else
394         inode->i_creation_time = unix_timestamp_to_wim(stbuf.st_mtime);
395         inode->i_last_write_time = unix_timestamp_to_wim(stbuf.st_mtime);
396         inode->i_last_access_time = unix_timestamp_to_wim(stbuf.st_atime);
397 #endif
398         inode->i_resolved = 1;
399         if (params->add_flags & WIMLIB_ADD_FLAG_UNIX_DATA) {
400                 struct wimlib_unix_data unix_data;
401
402                 unix_data.uid = stbuf.st_uid;
403                 unix_data.gid = stbuf.st_gid;
404                 unix_data.mode = stbuf.st_mode;
405                 unix_data.rdev = stbuf.st_rdev;
406                 if (!inode_set_unix_data(inode, &unix_data, UNIX_DATA_ALL)) {
407                         ret = WIMLIB_ERR_NOMEM;
408                         goto out;
409                 }
410         }
411
412         if (params->add_flags & WIMLIB_ADD_FLAG_ROOT) {
413                 params->capture_root_ino = stbuf.st_ino;
414                 params->capture_root_dev = stbuf.st_dev;
415                 params->add_flags &= ~WIMLIB_ADD_FLAG_ROOT;
416         }
417
418         if (S_ISREG(stbuf.st_mode)) {
419                 ret = unix_scan_regular_file(full_path, stbuf.st_size,
420                                              inode, params->unhashed_streams);
421         } else if (S_ISDIR(stbuf.st_mode)) {
422                 ret = unix_scan_directory(tree, full_path, full_path_len,
423                                           dirfd, relpath, params);
424         } else if (S_ISLNK(stbuf.st_mode)) {
425                 ret = unix_scan_symlink(full_path, dirfd, relpath,
426                                         inode, params);
427         }
428
429         if (ret)
430                 goto out;
431
432 out_progress:
433         params->progress.scan.cur_path = full_path;
434         if (likely(tree))
435                 ret = do_capture_progress(params, WIMLIB_SCAN_DENTRY_OK, inode);
436         else
437                 ret = do_capture_progress(params, WIMLIB_SCAN_DENTRY_EXCLUDED, NULL);
438 out:
439         if (likely(ret == 0))
440                 *tree_ret = tree;
441         else
442                 free_dentry_tree(tree, params->lookup_table);
443         return ret;
444 }
445
446 /*
447  * unix_build_dentry_tree():
448  *      Builds a tree of WIM dentries from an on-disk directory tree (UNIX
449  *      version; no NTFS-specific data is captured).
450  *
451  * @root_ret:   Place to return a pointer to the root of the dentry tree.  Only
452  *              modified if successful.  Set to NULL if the file or directory was
453  *              excluded from capture.
454  *
455  * @root_disk_path:  The path to the root of the directory tree on disk.
456  *
457  * @params:     See doc for `struct add_image_params'.
458  *
459  * @return:     0 on success, nonzero on failure.  It is a failure if any of
460  *              the files cannot be `stat'ed, or if any of the needed
461  *              directories cannot be opened or read.  Failure to add the files
462  *              to the WIM may still occur later when trying to actually read
463  *              the on-disk files during a call to wimlib_write() or
464  *              wimlib_overwrite().
465  */
466 int
467 unix_build_dentry_tree(struct wim_dentry **root_ret,
468                        const char *root_disk_path,
469                        struct add_image_params *params)
470 {
471         size_t path_len;
472         size_t path_bufsz;
473         char *path_buf;
474         int ret;
475
476         path_len = strlen(root_disk_path);
477         path_bufsz = min(32790, PATH_MAX + 1);
478
479         if (path_len >= path_bufsz)
480                 return WIMLIB_ERR_INVALID_PARAM;
481
482         path_buf = MALLOC(path_bufsz);
483         if (!path_buf)
484                 return WIMLIB_ERR_NOMEM;
485         memcpy(path_buf, root_disk_path, path_len + 1);
486
487         params->capture_root_nchars = path_len;
488
489         ret = unix_build_dentry_tree_recursive(root_ret, path_buf, path_len,
490                                                AT_FDCWD, path_buf, params);
491         FREE(path_buf);
492         return ret;
493 }
494
495 #endif /* !__WIN32__ */