Refactor headers
[wimlib] / src / unix_apply.c
1 /*
2  * unix_apply.c - Code to apply files from a WIM image on UNIX.
3  */
4
5 /*
6  * Copyright (C) 2012, 2013 Eric Biggers
7  *
8  * This file is part of wimlib, a library for working with WIM files.
9  *
10  * wimlib is free software; you can redistribute it and/or modify it under the
11  * terms of the GNU General Public License as published by the Free
12  * Software Foundation; either version 3 of the License, or (at your option)
13  * any later version.
14  *
15  * wimlib is distributed in the hope that it will be useful, but WITHOUT ANY
16  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
17  * A PARTICULAR PURPOSE. See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with wimlib; if not, see http://www.gnu.org/licenses/.
22  */
23
24 #ifndef __WIN32__
25
26 #ifdef HAVE_CONFIG_H
27 #  include "config.h"
28 #endif
29
30 #include <dirent.h>
31 #include <errno.h>
32 #include <fcntl.h>
33 #include <string.h>
34 #include <sys/stat.h>
35 #include <sys/time.h>
36 #include <unistd.h>
37 #ifdef HAVE_UTIME_H
38 #  include <utime.h>
39 #endif
40
41 #include "wimlib/apply.h"
42 #include "wimlib/error.h"
43 #include "wimlib/lookup_table.h"
44 #include "wimlib/reparse.h"
45 #include "wimlib/timestamp.h"
46
47 /* Returns the number of components of @path.  */
48 static unsigned
49 get_num_path_components(const char *path)
50 {
51         unsigned num_components = 0;
52         while (*path) {
53                 while (*path == '/')
54                         path++;
55                 if (*path)
56                         num_components++;
57                 while (*path && *path != '/')
58                         path++;
59         }
60         return num_components;
61 }
62
63 static const char *
64 path_next_part(const char *path)
65 {
66         while (*path && *path != '/')
67                 path++;
68         while (*path && *path == '/')
69                 path++;
70         return path;
71 }
72
73 static int
74 unix_extract_regular_file_linked(struct wim_dentry *dentry,
75                                  const char *output_path,
76                                  struct apply_args *args,
77                                  struct wim_lookup_table_entry *lte)
78 {
79         /* This mode overrides the normal hard-link extraction and
80          * instead either symlinks or hardlinks *all* identical files in
81          * the WIM, even if they are in a different image (in the case
82          * of a multi-image extraction) */
83
84         if (args->extract_flags & WIMLIB_EXTRACT_FLAG_HARDLINK) {
85                 if (link(lte->extracted_file, output_path) != 0) {
86                         ERROR_WITH_ERRNO("Failed to hard link "
87                                          "`%s' to `%s'",
88                                          output_path, lte->extracted_file);
89                         return WIMLIB_ERR_LINK;
90                 }
91         } else {
92                 int num_path_components;
93                 int num_output_dir_path_components;
94                 size_t extracted_file_len;
95                 char *p;
96                 const char *p2;
97                 size_t i;
98
99                 num_path_components = get_num_path_components(dentry->_full_path) - 1;
100                 num_output_dir_path_components = get_num_path_components(args->target);
101
102                 if (args->extract_flags & WIMLIB_EXTRACT_FLAG_MULTI_IMAGE) {
103                         num_path_components++;
104                         num_output_dir_path_components--;
105                 }
106                 extracted_file_len = strlen(lte->extracted_file);
107
108                 char buf[extracted_file_len + 3 * num_path_components + 1];
109                 p = &buf[0];
110
111                 for (i = 0; i < num_path_components; i++) {
112                         *p++ = '.';
113                         *p++ = '.';
114                         *p++ = '/';
115                 }
116                 p2 = lte->extracted_file;
117                 while (*p2 == '/')
118                         p2++;
119                 while (num_output_dir_path_components > 0) {
120                         p2 = path_next_part(p2);
121                         num_output_dir_path_components--;
122                 }
123                 strcpy(p, p2);
124                 if (symlink(buf, output_path) != 0) {
125                         ERROR_WITH_ERRNO("Failed to symlink `%s' to `%s'",
126                                          buf, lte->extracted_file);
127                         return WIMLIB_ERR_LINK;
128                 }
129         }
130         return 0;
131 }
132
133 static int
134 symlink_apply_unix_data(const char *link,
135                         const struct wimlib_unix_data *unix_data)
136 {
137         if (lchown(link, unix_data->uid, unix_data->gid)) {
138                 if (errno == EPERM) {
139                         /* Ignore */
140                         WARNING_WITH_ERRNO("failed to set symlink UNIX "
141                                            "owner/group on \"%s\"", link);
142                 } else {
143                         ERROR_WITH_ERRNO("failed to set symlink UNIX "
144                                          "owner/group on \"%s\"", link);
145                         return WIMLIB_ERR_INVALID_DENTRY;
146                 }
147         }
148         return 0;
149 }
150
151 static int
152 fd_apply_unix_data(int fd, const char *path,
153                    const struct wimlib_unix_data *unix_data,
154                    int extract_flags)
155 {
156         if (extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS)
157                 return 0;
158
159         if (fchown(fd, unix_data->uid, unix_data->gid)) {
160                 if (errno == EPERM &&
161                     !(extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS))
162                 {
163                         WARNING_WITH_ERRNO("failed to set file UNIX "
164                                            "owner/group on \"%s\"", path);
165                 } else {
166                         ERROR_WITH_ERRNO("failed to set file UNIX "
167                                          "owner/group on \"%s\"", path);
168                         return (errno == EPERM) ? WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT :
169                                 WIMLIB_ERR_WRITE;
170                 }
171         }
172
173         if (fchmod(fd, unix_data->mode)) {
174                 if (errno == EPERM &&
175                     !(extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS))
176                 {
177                         WARNING_WITH_ERRNO("failed to set UNIX file mode "
178                                            "on \"%s\"", path);
179                 } else {
180                         ERROR_WITH_ERRNO("failed to set UNIX file mode "
181                                          "on \"%s\"", path);
182                         return (errno == EPERM) ? WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT :
183                                 WIMLIB_ERR_WRITE;
184                 }
185         }
186         return 0;
187 }
188
189 static int
190 dir_apply_unix_data(const char *dir, const struct wimlib_unix_data *unix_data,
191                     int extract_flags)
192 {
193         int dfd = open(dir, O_RDONLY);
194         int ret;
195         if (dfd >= 0) {
196                 ret = fd_apply_unix_data(dfd, dir, unix_data, extract_flags);
197                 if (close(dfd) && ret == 0) {
198                         ERROR_WITH_ERRNO("can't close directory `%s'", dir);
199                         ret = WIMLIB_ERR_WRITE;
200                 }
201         } else {
202                 ERROR_WITH_ERRNO("can't open directory `%s'", dir);
203                 ret = WIMLIB_ERR_OPENDIR;
204         }
205         return ret;
206 }
207
208 static int
209 unix_extract_regular_file_unlinked(struct wim_dentry *dentry,
210                                    struct apply_args *args,
211                                    const char *output_path,
212                                    struct wim_lookup_table_entry *lte)
213 {
214         /* Normal mode of extraction.  Regular files and hard links are
215          * extracted in the way that they appear in the WIM. */
216
217         int out_fd;
218         int ret;
219         struct wim_inode *inode = dentry->d_inode;
220
221         if (!((args->extract_flags & WIMLIB_EXTRACT_FLAG_MULTI_IMAGE)
222                 && (args->extract_flags & (WIMLIB_EXTRACT_FLAG_SYMLINK |
223                                      WIMLIB_EXTRACT_FLAG_HARDLINK))))
224         {
225                 /* If the dentry is part of a hard link set of at least 2
226                  * dentries and one of the other dentries has already been
227                  * extracted, make a hard link to the file corresponding to this
228                  * already-extracted directory.  Otherwise, extract the file and
229                  * set the inode->i_extracted_file field so that other dentries
230                  * in the hard link group can link to it. */
231                 if (inode->i_nlink > 1) {
232                         if (inode->i_extracted_file) {
233                                 DEBUG("Extracting hard link `%s' => `%s'",
234                                       output_path, inode->i_extracted_file);
235                                 if (link(inode->i_extracted_file, output_path) != 0) {
236                                         ERROR_WITH_ERRNO("Failed to hard link "
237                                                          "`%s' to `%s'",
238                                                          output_path,
239                                                          inode->i_extracted_file);
240                                         return WIMLIB_ERR_LINK;
241                                 }
242                                 return 0;
243                         }
244                         FREE(inode->i_extracted_file);
245                         inode->i_extracted_file = STRDUP(output_path);
246                         if (!inode->i_extracted_file) {
247                                 ERROR("Failed to allocate memory for filename");
248                                 return WIMLIB_ERR_NOMEM;
249                         }
250                 }
251         }
252
253         /* Extract the contents of the file to @output_path. */
254
255         out_fd = open(output_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
256         if (out_fd == -1) {
257                 ERROR_WITH_ERRNO("Failed to open the file `%s' for writing",
258                                  output_path);
259                 return WIMLIB_ERR_OPEN;
260         }
261
262         if (!lte) {
263                 /* Empty file with no lookup table entry */
264                 DEBUG("Empty file `%s'.", output_path);
265                 ret = 0;
266                 goto out_extract_unix_data;
267         }
268
269         ret = extract_wim_resource_to_fd(lte, out_fd, wim_resource_size(lte));
270         if (ret) {
271                 ERROR("Failed to extract resource to `%s'", output_path);
272                 goto out;
273         }
274
275 out_extract_unix_data:
276         if (args->extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA) {
277                 struct wimlib_unix_data unix_data;
278                 ret = inode_get_unix_data(inode, &unix_data, NULL);
279                 if (ret > 0)
280                         ;
281                 else if (ret < 0)
282                         ret = 0;
283                 else
284                         ret = fd_apply_unix_data(out_fd, output_path, &unix_data,
285                                                  args->extract_flags);
286                 if (ret)
287                         goto out;
288         }
289         if (lte)
290                 args->progress.extract.completed_bytes += wim_resource_size(lte);
291 out:
292         if (close(out_fd) != 0) {
293                 ERROR_WITH_ERRNO("Failed to close file `%s'", output_path);
294                 if (ret == 0)
295                         ret = WIMLIB_ERR_WRITE;
296         }
297         return ret;
298 }
299
300 static int
301 unix_extract_regular_file(struct wim_dentry *dentry,
302                           struct apply_args *args,
303                           const char *output_path)
304 {
305         struct wim_lookup_table_entry *lte;
306         const struct wim_inode *inode = dentry->d_inode;
307
308         lte = inode_unnamed_lte_resolved(inode);
309
310         if (lte && (args->extract_flags & (WIMLIB_EXTRACT_FLAG_SYMLINK |
311                                            WIMLIB_EXTRACT_FLAG_HARDLINK)))
312         {
313                 if (lte->extracted_file) {
314                         return unix_extract_regular_file_linked(dentry,
315                                                                 output_path,
316                                                                 args, lte);
317                 } else {
318                         lte->extracted_file = STRDUP(output_path);
319                         if (!lte->extracted_file)
320                                 return WIMLIB_ERR_NOMEM;
321                 }
322         }
323         return unix_extract_regular_file_unlinked(dentry, args, output_path, lte);
324 }
325
326 static int
327 unix_extract_symlink(struct wim_dentry *dentry,
328                      struct apply_args *args,
329                      const char *output_path)
330 {
331         char target[4096 + args->target_realpath_len];
332         char *fixed_target;
333         const struct wim_inode *inode = dentry->d_inode;
334
335         ssize_t ret = wim_inode_readlink(inode,
336                                          target + args->target_realpath_len,
337                                          sizeof(target) - args->target_realpath_len - 1);
338         struct wim_lookup_table_entry *lte;
339
340         if (ret <= 0) {
341                 ERROR("Could not read the symbolic link from dentry `%s'",
342                       dentry->_full_path);
343                 return WIMLIB_ERR_INVALID_DENTRY;
344         }
345         target[args->target_realpath_len + ret] = '\0';
346         if (target[args->target_realpath_len] == '/' &&
347             args->extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX)
348         {
349                 /* Fix absolute symbolic link target to point into the actual
350                  * extraction destination */
351                 memcpy(target, args->target_realpath,
352                        args->target_realpath_len);
353                 fixed_target = target;
354         } else {
355                 /* Keep same link target */
356                 fixed_target = target + args->target_realpath_len;
357         }
358         ret = symlink(fixed_target, output_path);
359         if (ret) {
360                 ERROR_WITH_ERRNO("Failed to symlink `%s' to `%s'",
361                                  output_path, fixed_target);
362                 return WIMLIB_ERR_LINK;
363         }
364         if (args->extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA) {
365                 struct wimlib_unix_data unix_data;
366                 ret = inode_get_unix_data(inode, &unix_data, NULL);
367                 if (ret > 0)
368                         ;
369                 else if (ret < 0)
370                         ret = 0;
371                 else
372                         ret = symlink_apply_unix_data(output_path, &unix_data);
373                 if (ret)
374                         return ret;
375         }
376         lte = inode_unnamed_lte_resolved(inode);
377         wimlib_assert(lte != NULL);
378         args->progress.extract.completed_bytes += wim_resource_size(lte);
379         return 0;
380 }
381
382 static int
383 unix_extract_directory(struct wim_dentry *dentry, const tchar *output_path,
384                        int extract_flags)
385 {
386         int ret;
387         struct stat stbuf;
388
389         ret = tstat(output_path, &stbuf);
390         if (ret == 0) {
391                 if (S_ISDIR(stbuf.st_mode)) {
392                         goto dir_exists;
393                 } else {
394                         ERROR("`%"TS"' is not a directory", output_path);
395                         return WIMLIB_ERR_MKDIR;
396                 }
397         } else {
398                 if (errno != ENOENT) {
399                         ERROR_WITH_ERRNO("Failed to stat `%"TS"'", output_path);
400                         return WIMLIB_ERR_STAT;
401                 }
402         }
403
404         if (tmkdir(output_path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH))
405         {
406                 ERROR_WITH_ERRNO("Cannot create directory `%"TS"'", output_path);
407                 return WIMLIB_ERR_MKDIR;
408         }
409 dir_exists:
410         ret = 0;
411 #ifndef __WIN32__
412         if (extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA) {
413                 struct wimlib_unix_data unix_data;
414                 ret = inode_get_unix_data(dentry->d_inode, &unix_data, NULL);
415                 if (ret > 0)
416                         ;
417                 else if (ret < 0)
418                         ret = 0;
419                 else
420                         ret = dir_apply_unix_data(output_path, &unix_data,
421                                                   extract_flags);
422         }
423 #endif
424         return ret;
425 }
426
427 int
428 unix_do_apply_dentry(const char *output_path, size_t output_path_len,
429                      struct wim_dentry *dentry, struct apply_args *args)
430 {
431         const struct wim_inode *inode = dentry->d_inode;
432
433         if (inode_is_symlink(inode))
434                 return unix_extract_symlink(dentry, args, output_path);
435         else if (inode_is_directory(inode))
436                 return unix_extract_directory(dentry, output_path, args->extract_flags);
437         else
438                 return unix_extract_regular_file(dentry, args, output_path);
439 }
440
441 int
442 unix_do_apply_dentry_timestamps(const char *output_path,
443                                 size_t output_path_len,
444                                 struct wim_dentry *dentry,
445                                 struct apply_args *args)
446 {
447         int ret;
448         const struct wim_inode *inode = dentry->d_inode;
449
450 #ifdef HAVE_UTIMENSAT
451         /* Convert the WIM timestamps, which are accurate to 100 nanoseconds,
452          * into `struct timespec's for passing to utimensat(), which is accurate
453          * to 1 nanosecond. */
454
455         struct timespec ts[2];
456         ts[0] = wim_timestamp_to_timespec(inode->i_last_access_time);
457         ts[1] = wim_timestamp_to_timespec(inode->i_last_write_time);
458         ret = utimensat(AT_FDCWD, output_path, ts, AT_SYMLINK_NOFOLLOW);
459         if (ret)
460                 ret = errno;
461 #else
462         ret = ENOSYS;
463 #endif
464
465         if (ret == ENOSYS) {
466                 /* utimensat() not implemented or not available */
467         #ifdef HAVE_LUTIMES
468                 /* Convert the WIM timestamps, which are accurate to 100
469                  * nanoseconds, into `struct timeval's for passing to lutimes(),
470                  * which is accurate to 1 microsecond. */
471                 struct timeval tv[2];
472                 tv[0] = wim_timestamp_to_timeval(inode->i_last_access_time);
473                 tv[1] = wim_timestamp_to_timeval(inode->i_last_write_time);
474                 ret = lutimes(output_path, tv);
475                 if (ret)
476                         ret = errno;
477         #endif
478         }
479
480         if (ret == ENOSYS) {
481                 /* utimensat() and lutimes() both not implemented or not
482                  * available */
483         #ifdef HAVE_UTIME
484                 /* Convert the WIM timestamps, which are accurate to 100
485                  * nanoseconds, into a `struct utimbuf's for passing to
486                  * utime(), which is accurate to 1 second. */
487                 struct utimbuf buf;
488                 buf.actime = wim_timestamp_to_unix(inode->i_last_access_time);
489                 buf.modtime = wim_timestamp_to_unix(inode->i_last_write_time);
490                 ret = utime(output_path, &buf);
491         #endif
492         }
493         if (ret && args->num_utime_warnings < 10) {
494                 WARNING_WITH_ERRNO("Failed to set timestamp on file `%s'",
495                                     output_path);
496                 args->num_utime_warnings++;
497         }
498         return 0;
499 }
500
501 #endif /* !__WIN32__ */