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