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