- add and use bb_opendir(), bb_xopendir().
[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 = bb_opendir(source)) == NULL) {
96                         status = -1;
97                         goto preserve_status;
98                 }
99
100                 while ((d = readdir(dp)) != NULL) {
101                         char *new_source, *new_dest;
102
103                         new_source = concat_subpath_file(source, d->d_name);
104                         if(new_source == NULL)
105                                 continue;
106                         new_dest = concat_path_file(dest, d->d_name);
107                         if (copy_file(new_source, new_dest, flags) < 0)
108                                 status = -1;
109                         free(new_source);
110                         free(new_dest);
111                 }
112                 /* closedir have only EBADF error, but "dp" not changes */
113                 closedir(dp);
114
115                 if (!dest_exists &&
116                                 chmod(dest, source_stat.st_mode & ~saved_umask) < 0) {
117                         bb_perror_msg("unable to change permissions of `%s'", dest);
118                         status = -1;
119                 }
120         } else if (S_ISREG(source_stat.st_mode) || (flags & FILEUTILS_DEREFERENCE))
121         {
122                 int src_fd;
123                 int dst_fd;
124 #ifdef CONFIG_FEATURE_PRESERVE_HARDLINKS
125                 char *link_name;
126
127                 if (!(flags & FILEUTILS_DEREFERENCE) &&
128                                 is_in_ino_dev_hashtable(&source_stat, &link_name)) {
129                         if (link(link_name, dest) < 0) {
130                                 bb_perror_msg("unable to link `%s'", dest);
131                                 return -1;
132                         }
133
134                         return 0;
135                 }
136                 add_to_ino_dev_hashtable(&source_stat, dest);
137 #endif
138                 src_fd = open(source, O_RDONLY);
139                 if (src_fd == -1) {
140                         bb_perror_msg("unable to open `%s'", source);
141                         return(-1);
142                 }
143
144                 if (dest_exists) {
145                         if (flags & FILEUTILS_INTERACTIVE) {
146                                 fprintf(stderr, "%s: overwrite `%s'? ", bb_applet_name, dest);
147                                 if (!bb_ask_confirmation()) {
148                                         close (src_fd);
149                                         return 0;
150                                 }
151                         }
152
153                         dst_fd = open(dest, O_WRONLY|O_TRUNC);
154                         if (dst_fd == -1) {
155                                 if (!(flags & FILEUTILS_FORCE)) {
156                                         bb_perror_msg("unable to open `%s'", dest);
157                                         close(src_fd);
158                                         return -1;
159                                 }
160
161                                 if (unlink(dest) < 0) {
162                                         bb_perror_msg("unable to remove `%s'", dest);
163                                         close(src_fd);
164                                         return -1;
165                                 }
166
167                                 goto dest_removed;
168                         }
169                 } else {
170 dest_removed:
171                         dst_fd = open(dest, O_WRONLY|O_CREAT, source_stat.st_mode);
172                         if (dst_fd == -1) {
173                                 bb_perror_msg("unable to open `%s'", dest);
174                                 close(src_fd);
175                                 return(-1);
176                         }
177                 }
178
179                 if (bb_copyfd_eof(src_fd, dst_fd) == -1)
180                         status = -1;
181
182                 if (close(dst_fd) < 0) {
183                         bb_perror_msg("unable to close `%s'", dest);
184                         status = -1;
185                 }
186
187                 if (close(src_fd) < 0) {
188                         bb_perror_msg("unable to close `%s'", source);
189                         status = -1;
190                 }
191         } else if (S_ISBLK(source_stat.st_mode) || S_ISCHR(source_stat.st_mode) ||
192             S_ISSOCK(source_stat.st_mode) || S_ISFIFO(source_stat.st_mode) ||
193             S_ISLNK(source_stat.st_mode)) {
194
195                 if (dest_exists) {
196                         if((flags & FILEUTILS_FORCE) == 0) {
197                                 fprintf(stderr, "`%s' exists\n", dest);
198                                 return -1;
199                         }
200                         if(unlink(dest) < 0) {
201                                 bb_perror_msg("unable to remove `%s'", dest);
202                                 return -1;
203                         }
204                 }
205                 if (S_ISFIFO(source_stat.st_mode)) {
206                         if (mkfifo(dest, source_stat.st_mode) < 0) {
207                                 bb_perror_msg("cannot create fifo `%s'", dest);
208                                 return -1;
209                         }
210                 } else if (S_ISLNK(source_stat.st_mode)) {
211                         char *lpath;
212
213                         lpath = xreadlink(source);
214                         if (symlink(lpath, dest) < 0) {
215                                 bb_perror_msg("cannot create symlink `%s'", dest);
216                                 return -1;
217                         }
218                         free(lpath);
219
220                         if (flags & FILEUTILS_PRESERVE_STATUS)
221                                 if (lchown(dest, source_stat.st_uid, source_stat.st_gid) < 0)
222                                         bb_perror_msg("unable to preserve ownership of `%s'", dest);
223
224                         return 0;
225
226                 } else {
227                         if (mknod(dest, source_stat.st_mode, source_stat.st_rdev) < 0) {
228                                 bb_perror_msg("unable to create `%s'", dest);
229                                 return -1;
230                         }
231                 }
232         } else {
233                 bb_error_msg("internal error: unrecognized file type");
234                 return -1;
235         }
236
237 preserve_status:
238
239         if (flags & FILEUTILS_PRESERVE_STATUS) {
240                 struct utimbuf times;
241                 char *msg="unable to preserve %s of `%s'";
242
243                 times.actime = source_stat.st_atime;
244                 times.modtime = source_stat.st_mtime;
245                 if (utime(dest, &times) < 0)
246                         bb_perror_msg(msg, "times", dest);
247                 if (chown(dest, source_stat.st_uid, source_stat.st_gid) < 0) {
248                         source_stat.st_mode &= ~(S_ISUID | S_ISGID);
249                         bb_perror_msg(msg, "ownership", dest);
250                 }
251                 if (chmod(dest, source_stat.st_mode) < 0)
252                         bb_perror_msg(msg, "permissions", dest);
253         }
254
255         return status;
256 }