syslogd: fix "readpath bug" by using readlink instead
[platform/upstream/busybox.git] / libbb / copy_file.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Mini copy_file implementation for busybox
4  *
5  * Copyright (C) 2001 by Matt Kraai <kraai@alumni.carnegiemellon.edu>
6  *
7  * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
8  *
9  */
10
11 #include "libbb.h"
12
13 static int retry_overwrite(const char *dest, int flags)
14 {
15         if (!(flags & (FILEUTILS_FORCE|FILEUTILS_INTERACTIVE))) {
16                 fprintf(stderr, "'%s' exists\n", dest);
17                 return -1;
18         }
19         if (flags & FILEUTILS_INTERACTIVE) {
20                 fprintf(stderr, "%s: overwrite '%s'? ", applet_name, dest);
21                 if (!bb_ask_confirmation())
22                         return 0; // not allowed to overwrite
23         }
24         if (unlink(dest) < 0) {
25                 bb_perror_msg("cannot remove '%s'", dest);
26                 return -1; // error
27         }
28         return 1; // ok (to try again)
29 }
30
31 int copy_file(const char *source, const char *dest, int flags)
32 {
33         struct stat source_stat;
34         struct stat dest_stat;
35         int status = 0;
36         signed char dest_exists = 0;
37         signed char ovr;
38
39 #define FLAGS_DEREF (flags & FILEUTILS_DEREFERENCE)
40
41         if ((FLAGS_DEREF ? stat : lstat)(source, &source_stat) < 0) {
42                 // This may be a dangling symlink.
43                 // Making [sym]links to dangling symlinks works, so...
44                 if (flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK))
45                         goto make_links;
46                 bb_perror_msg("cannot stat '%s'", source);
47                 return -1;
48         }
49
50         if (lstat(dest, &dest_stat) < 0) {
51                 if (errno != ENOENT) {
52                         bb_perror_msg("cannot stat '%s'", dest);
53                         return -1;
54                 }
55         } else {
56                 if (source_stat.st_dev == dest_stat.st_dev
57                  && source_stat.st_ino == dest_stat.st_ino
58                 ) {
59                         bb_error_msg("'%s' and '%s' are the same file", source, dest);
60                         return -1;
61                 }
62                 dest_exists = 1;
63         }
64
65         if (S_ISDIR(source_stat.st_mode)) {
66                 DIR *dp;
67                 struct dirent *d;
68                 mode_t saved_umask = 0;
69
70                 if (!(flags & FILEUTILS_RECUR)) {
71                         bb_error_msg("omitting directory '%s'", source);
72                         return -1;
73                 }
74
75                 /* Create DEST.  */
76                 if (dest_exists) {
77                         if (!S_ISDIR(dest_stat.st_mode)) {
78                                 bb_error_msg("target '%s' is not a directory", dest);
79                                 return -1;
80                         }
81                 } else {
82                         mode_t mode;
83                         saved_umask = umask(0);
84
85                         mode = source_stat.st_mode;
86                         if (!(flags & FILEUTILS_PRESERVE_STATUS))
87                                 mode = source_stat.st_mode & ~saved_umask;
88                         mode |= S_IRWXU;
89
90                         if (mkdir(dest, mode) < 0) {
91                                 umask(saved_umask);
92                                 bb_perror_msg("cannot create directory '%s'", dest);
93                                 return -1;
94                         }
95
96                         umask(saved_umask);
97                 }
98
99                 /* Recursively copy files in SOURCE.  */
100                 dp = opendir(source);
101                 if (dp == NULL) {
102                         status = -1;
103                         goto preserve_status;
104                 }
105
106                 while ((d = readdir(dp)) != NULL) {
107                         char *new_source, *new_dest;
108
109                         new_source = concat_subpath_file(source, d->d_name);
110                         if (new_source == NULL)
111                                 continue;
112                         new_dest = concat_path_file(dest, d->d_name);
113                         if (copy_file(new_source, new_dest, flags) < 0)
114                                 status = -1;
115                         free(new_source);
116                         free(new_dest);
117                 }
118                 closedir(dp);
119
120                 if (!dest_exists
121                  && chmod(dest, source_stat.st_mode & ~saved_umask) < 0
122                 ) {
123                         bb_perror_msg("cannot change permissions of '%s'", dest);
124                         status = -1;
125                 }
126
127         } else if (flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) {
128                 int (*lf)(const char *oldpath, const char *newpath);
129  make_links:
130                 // Hmm... maybe
131                 // if (DEREF && MAKE_SOFTLINK) source = realpath(source) ?
132                 // (but realpath returns NULL on dangling symlinks...)
133                 lf = (flags & FILEUTILS_MAKE_SOFTLINK) ? symlink : link;
134                 if (lf(source, dest) < 0) {
135                         ovr = retry_overwrite(dest, flags);
136                         if (ovr <= 0)
137                                 return ovr;
138                         if (lf(source, dest) < 0) {
139                                 bb_perror_msg("cannot create link '%s'", dest);
140                                 return -1;
141                         }
142                 }
143                 return 0;
144
145         } else if (S_ISREG(source_stat.st_mode)
146         // Huh? DEREF uses stat, which never returns links IIRC...
147          || (FLAGS_DEREF && S_ISLNK(source_stat.st_mode))
148         ) {
149                 int src_fd;
150                 int dst_fd;
151                 if (ENABLE_FEATURE_PRESERVE_HARDLINKS) {
152                         char *link_name;
153
154                         if (!FLAGS_DEREF
155                          && is_in_ino_dev_hashtable(&source_stat, &link_name)
156                         ) {
157                                 if (link(link_name, dest) < 0) {
158                                         ovr = retry_overwrite(dest, flags);
159                                         if (ovr <= 0)
160                                                 return ovr;
161                                         if (link(link_name, dest) < 0) {
162                                                 bb_perror_msg("cannot create link '%s'", dest);
163                                                 return -1;
164                                         }
165                                 }
166                                 return 0;
167                         }
168                         // TODO: probably is_in_.. and add_to_...
169                         // can be combined: find_or_add_...
170                         add_to_ino_dev_hashtable(&source_stat, dest);
171                 }
172
173                 src_fd = open(source, O_RDONLY);
174                 if (src_fd == -1) {
175                         bb_perror_msg("cannot open '%s'", source);
176                         return -1;
177                 }
178
179                 // POSIX: if exists and -i, ask (w/o -i assume yes).
180                 // Then open w/o EXCL.
181                 // If open still fails and -f, try unlink, then try open again.
182                 // Result: a mess:
183                 // If dest is a softlink, we overwrite softlink's destination!
184                 // (or fail, if it points to dir/nonexistent location/etc).
185                 // This is strange, but POSIX-correct.
186                 // coreutils cp has --remove-destination to override this...
187                 dst_fd = open(dest, (flags & FILEUTILS_INTERACTIVE)
188                                 ? O_WRONLY|O_CREAT|O_TRUNC|O_EXCL
189                                 : O_WRONLY|O_CREAT|O_TRUNC, source_stat.st_mode);
190                 if (dst_fd == -1) {
191                         // We would not do POSIX insanity. -i asks,
192                         // then _unlinks_ the offender. Presto.
193                         // Or else we will end up having 3 open()s!
194                         ovr = retry_overwrite(dest, flags);
195                         if (ovr <= 0) {
196                                 close(src_fd);
197                                 return ovr;
198                         }
199                         dst_fd = open(dest, O_WRONLY|O_CREAT|O_TRUNC, source_stat.st_mode);
200                         if (dst_fd == -1) {
201                                 bb_perror_msg("cannot open '%s'", dest);
202                                 close(src_fd);
203                                 return -1;
204                         }
205                 }
206
207                 if (bb_copyfd_eof(src_fd, dst_fd) == -1)
208                         status = -1;
209                 if (close(dst_fd) < 0) {
210                         bb_perror_msg("cannot close '%s'", dest);
211                         status = -1;
212                 }
213                 if (close(src_fd) < 0) {
214                         bb_perror_msg("cannot close '%s'", source);
215                         status = -1;
216                 }
217
218         } else if (S_ISBLK(source_stat.st_mode) || S_ISCHR(source_stat.st_mode)
219          || S_ISSOCK(source_stat.st_mode) || S_ISFIFO(source_stat.st_mode)
220          || S_ISLNK(source_stat.st_mode)
221         ) {
222                 // We are lazy here, a bit lax with races...
223                 if (dest_exists) {
224                         ovr = retry_overwrite(dest, flags);
225                         if (ovr <= 0)
226                                 return ovr;
227                 }
228                 if (S_ISFIFO(source_stat.st_mode)) {
229                         if (mkfifo(dest, source_stat.st_mode) < 0) {
230                                 bb_perror_msg("cannot create fifo '%s'", dest);
231                                 return -1;
232                         }
233                 } else if (S_ISLNK(source_stat.st_mode)) {
234                         char *lpath;
235
236                         lpath = xmalloc_readlink_or_warn(source);
237                         if (symlink(lpath, dest) < 0) {
238                                 bb_perror_msg("cannot create symlink '%s'", dest);
239                                 free(lpath);
240                                 return -1;
241                         }
242                         free(lpath);
243
244                         if (flags & FILEUTILS_PRESERVE_STATUS)
245                                 if (lchown(dest, source_stat.st_uid, source_stat.st_gid) < 0)
246                                         bb_perror_msg("cannot preserve %s of '%s'", "ownership", dest);
247
248                         return 0;
249
250                 } else {
251                         if (mknod(dest, source_stat.st_mode, source_stat.st_rdev) < 0) {
252                                 bb_perror_msg("cannot create '%s'", dest);
253                                 return -1;
254                         }
255                 }
256         } else {
257                 bb_error_msg("internal error: unrecognized file type");
258                 return -1;
259         }
260
261  preserve_status:
262
263         if (flags & FILEUTILS_PRESERVE_STATUS
264         /* Cannot happen: */
265         /* && !(flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) */
266         ) {
267                 struct utimbuf times;
268
269                 times.actime = source_stat.st_atime;
270                 times.modtime = source_stat.st_mtime;
271                 if (utime(dest, &times) < 0)
272                         bb_perror_msg("cannot preserve %s of '%s'", "times", dest);
273                 if (chown(dest, source_stat.st_uid, source_stat.st_gid) < 0) {
274                         source_stat.st_mode &= ~(S_ISUID | S_ISGID);
275                         bb_perror_msg("cannot preserve %s of '%s'", "ownership", dest);
276                 }
277                 if (chmod(dest, source_stat.st_mode) < 0)
278                         bb_perror_msg("cannot preserve %s of '%s'", "permissions", dest);
279         }
280
281         return status;
282 }