The logic to make cp -d or -P treat things like regular files should only
[oweals/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 #include <utime.h>
13 #include <errno.h>
14
15 int copy_file(const char *source, const char *dest, int flags)
16 {
17         struct stat source_stat;
18         struct stat dest_stat;
19         int dest_exists = 0;
20         int status = 0;
21
22         if ((!(flags & FILEUTILS_DEREFERENCE) &&
23                         lstat(source, &source_stat) < 0) ||
24                         ((flags & FILEUTILS_DEREFERENCE) &&
25                          stat(source, &source_stat) < 0)) {
26                 bb_perror_msg("%s", source);
27                 return -1;
28         }
29
30         if (lstat(dest, &dest_stat) < 0) {
31                 if (errno != ENOENT) {
32                         bb_perror_msg("unable to stat `%s'", dest);
33                         return -1;
34                 }
35         } else {
36                 if (source_stat.st_dev == dest_stat.st_dev &&
37                         source_stat.st_ino == dest_stat.st_ino)
38                 {
39                         bb_error_msg("`%s' and `%s' are the same file", source, dest);
40                         return -1;
41                 }
42                 dest_exists = 1;
43         }
44
45         if (S_ISDIR(source_stat.st_mode)) {
46                 DIR *dp;
47                 struct dirent *d;
48                 mode_t saved_umask = 0;
49
50                 if (!(flags & FILEUTILS_RECUR)) {
51                         bb_error_msg("%s: omitting directory", source);
52                         return -1;
53                 }
54
55                 /* Create DEST.  */
56                 if (dest_exists) {
57                         if (!S_ISDIR(dest_stat.st_mode)) {
58                                 bb_error_msg("`%s' is not a directory", dest);
59                                 return -1;
60                         }
61                 } else {
62                         mode_t mode;
63                         saved_umask = umask(0);
64
65                         mode = source_stat.st_mode;
66                         if (!(flags & FILEUTILS_PRESERVE_STATUS))
67                                 mode = source_stat.st_mode & ~saved_umask;
68                         mode |= S_IRWXU;
69
70                         if (mkdir(dest, mode) < 0) {
71                                 umask(saved_umask);
72                                 bb_perror_msg("cannot create directory `%s'", dest);
73                                 return -1;
74                         }
75
76                         umask(saved_umask);
77                 }
78
79                 /* Recursively copy files in SOURCE.  */
80                 if ((dp = bb_opendir(source)) == NULL) {
81                         status = -1;
82                         goto preserve_status;
83                 }
84
85                 while ((d = readdir(dp)) != NULL) {
86                         char *new_source, *new_dest;
87
88                         new_source = concat_subpath_file(source, d->d_name);
89                         if(new_source == NULL)
90                                 continue;
91                         new_dest = concat_path_file(dest, d->d_name);
92                         if (copy_file(new_source, new_dest, flags) < 0)
93                                 status = -1;
94                         free(new_source);
95                         free(new_dest);
96                 }
97                 /* closedir have only EBADF error, but "dp" not changes */
98                 closedir(dp);
99
100                 if (!dest_exists &&
101                                 chmod(dest, source_stat.st_mode & ~saved_umask) < 0) {
102                         bb_perror_msg("unable to change permissions of `%s'", dest);
103                         status = -1;
104                 }
105         } else if (S_ISREG(source_stat.st_mode) ||
106                    (S_ISLNK(source_stat.st_mode) && (flags & FILEUTILS_DEREFERENCE)))
107         {
108                 int src_fd;
109                 int dst_fd;
110                 if (ENABLE_FEATURE_PRESERVE_HARDLINKS) {
111                         char *link_name;
112
113                         if (!(flags & FILEUTILS_DEREFERENCE) &&
114                                         is_in_ino_dev_hashtable(&source_stat, &link_name)) {
115                                 if (link(link_name, dest) < 0) {
116                                         bb_perror_msg("unable to link `%s'", dest);
117                                         return -1;
118                                 }
119
120                                 return 0;
121                         }
122                         add_to_ino_dev_hashtable(&source_stat, dest);
123                 }
124                 src_fd = open(source, O_RDONLY);
125                 if (src_fd == -1) {
126                         bb_perror_msg("unable to open `%s'", source);
127                         return(-1);
128                 }
129
130                 if (dest_exists) {
131                         if (flags & FILEUTILS_INTERACTIVE) {
132                                 fprintf(stderr, "%s: overwrite `%s'? ", bb_applet_name, dest);
133                                 if (!bb_ask_confirmation()) {
134                                         close (src_fd);
135                                         return 0;
136                                 }
137                         }
138
139                         dst_fd = open(dest, O_WRONLY|O_TRUNC);
140                         if (dst_fd == -1) {
141                                 if (!(flags & FILEUTILS_FORCE)) {
142                                         bb_perror_msg("unable to open `%s'", dest);
143                                         close(src_fd);
144                                         return -1;
145                                 }
146
147                                 if (unlink(dest) < 0) {
148                                         bb_perror_msg("unable to remove `%s'", dest);
149                                         close(src_fd);
150                                         return -1;
151                                 }
152
153                                 goto dest_removed;
154                         }
155                 } else {
156 dest_removed:
157                         dst_fd = open(dest, O_WRONLY|O_CREAT, source_stat.st_mode);
158                         if (dst_fd == -1) {
159                                 bb_perror_msg("unable to open `%s'", dest);
160                                 close(src_fd);
161                                 return(-1);
162                         }
163                 }
164
165                 if (bb_copyfd_eof(src_fd, dst_fd) == -1)
166                         status = -1;
167
168                 if (close(dst_fd) < 0) {
169                         bb_perror_msg("unable to close `%s'", dest);
170                         status = -1;
171                 }
172
173                 if (close(src_fd) < 0) {
174                         bb_perror_msg("unable to close `%s'", source);
175                         status = -1;
176                 }
177         } else if (S_ISBLK(source_stat.st_mode) || S_ISCHR(source_stat.st_mode) ||
178             S_ISSOCK(source_stat.st_mode) || S_ISFIFO(source_stat.st_mode) ||
179             S_ISLNK(source_stat.st_mode)) {
180
181                 if (dest_exists) {
182                         if((flags & FILEUTILS_FORCE) == 0) {
183                                 fprintf(stderr, "`%s' exists\n", dest);
184                                 return -1;
185                         }
186                         if(unlink(dest) < 0) {
187                                 bb_perror_msg("unable to remove `%s'", dest);
188                                 return -1;
189                         }
190                 }
191                 if (S_ISFIFO(source_stat.st_mode)) {
192                         if (mkfifo(dest, source_stat.st_mode) < 0) {
193                                 bb_perror_msg("cannot create fifo `%s'", dest);
194                                 return -1;
195                         }
196                 } else if (S_ISLNK(source_stat.st_mode)) {
197                         char *lpath;
198
199                         lpath = xreadlink(source);
200                         if (symlink(lpath, dest) < 0) {
201                                 bb_perror_msg("cannot create symlink `%s'", dest);
202                                 return -1;
203                         }
204                         free(lpath);
205
206                         if (flags & FILEUTILS_PRESERVE_STATUS)
207                                 if (lchown(dest, source_stat.st_uid, source_stat.st_gid) < 0)
208                                         bb_perror_msg("unable to preserve ownership of `%s'", dest);
209
210                         return 0;
211
212                 } else {
213                         if (mknod(dest, source_stat.st_mode, source_stat.st_rdev) < 0) {
214                                 bb_perror_msg("unable to create `%s'", dest);
215                                 return -1;
216                         }
217                 }
218         } else {
219                 bb_error_msg("internal error: unrecognized file type");
220                 return -1;
221         }
222
223 preserve_status:
224
225         if (flags & FILEUTILS_PRESERVE_STATUS) {
226                 struct utimbuf times;
227                 char *msg="unable to preserve %s of `%s'";
228
229                 times.actime = source_stat.st_atime;
230                 times.modtime = source_stat.st_mtime;
231                 if (utime(dest, &times) < 0)
232                         bb_perror_msg(msg, "times", dest);
233                 if (chown(dest, source_stat.st_uid, source_stat.st_gid) < 0) {
234                         source_stat.st_mode &= ~(S_ISUID | S_ISGID);
235                         bb_perror_msg(msg, "ownership", dest);
236                 }
237                 if (chmod(dest, source_stat.st_mode) < 0)
238                         bb_perror_msg(msg, "permissions", dest);
239         }
240
241         return status;
242 }