diff options
author | Denis Vlasenko <vda.linux@googlemail.com> | 2007-09-16 18:50:56 +0000 |
---|---|---|
committer | Denis Vlasenko <vda.linux@googlemail.com> | 2007-09-16 18:50:56 +0000 |
commit | e755e827f7c8ecb21787a4369d7afdeda54d112b (patch) | |
tree | 007b1506e69ffcc25bc6ba3b0e6686d806a34264 /libbb | |
parent | ed6ac53104d811ee88c71aff45c7cad666aaee46 (diff) | |
download | busybox-w32-1_7_1.tar.gz busybox-w32-1_7_1.tar.bz2 busybox-w32-1_7_1.zip |
apply post 1.7.0 patches, set version to 1.7.11_7_1
Diffstat (limited to 'libbb')
-rw-r--r-- | libbb/copy_file.c | 191 | ||||
-rw-r--r-- | libbb/xreadlink.c | 22 |
2 files changed, 125 insertions, 88 deletions
diff --git a/libbb/copy_file.c b/libbb/copy_file.c index b68a257b5..3da8a3531 100644 --- a/libbb/copy_file.c +++ b/libbb/copy_file.c | |||
@@ -19,28 +19,31 @@ | |||
19 | // (or fail, if it points to dir/nonexistent location/etc). | 19 | // (or fail, if it points to dir/nonexistent location/etc). |
20 | // This is strange, but POSIX-correct. | 20 | // This is strange, but POSIX-correct. |
21 | // coreutils cp has --remove-destination to override this... | 21 | // coreutils cp has --remove-destination to override this... |
22 | // | ||
23 | // NB: we have special code which still allows for "cp file /dev/node" | ||
24 | // to work POSIX-ly (the only realistic case where it makes sense) | ||
22 | 25 | ||
23 | #define DO_POSIX_CP 0 /* 1 - POSIX behavior, 0 - safe behavior */ | 26 | #define DO_POSIX_CP 0 /* 1 - POSIX behavior, 0 - safe behavior */ |
24 | 27 | ||
25 | 28 | // errno must be set to relevant value ("why we cannot create dest?") | |
29 | // for POSIX mode to give reasonable error message | ||
26 | static int ask_and_unlink(const char *dest, int flags) | 30 | static int ask_and_unlink(const char *dest, int flags) |
27 | { | 31 | { |
28 | // If !DO_POSIX_CP, act as if -f is always in effect - we don't want | ||
29 | // "'file' exists" msg, we want unlink to be done (silently unless -i | ||
30 | // is also in effect). | ||
31 | // This prevents safe way from asking more questions than POSIX does. | ||
32 | #if DO_POSIX_CP | 32 | #if DO_POSIX_CP |
33 | if (!(flags & (FILEUTILS_FORCE|FILEUTILS_INTERACTIVE))) { | 33 | if (!(flags & (FILEUTILS_FORCE|FILEUTILS_INTERACTIVE))) { |
34 | fprintf(stderr, "'%s' exists\n", dest); | 34 | // Either it exists, or the *path* doesnt exist |
35 | bb_perror_msg("cannot create '%s'", dest); | ||
35 | return -1; | 36 | return -1; |
36 | } | 37 | } |
37 | #endif | 38 | #endif |
39 | // If !DO_POSIX_CP, act as if -f is always in effect - we don't want | ||
40 | // "cannot create" msg, we want unlink to be done (silently unless -i). | ||
38 | 41 | ||
39 | // TODO: maybe we should do it only if ctty is present? | 42 | // TODO: maybe we should do it only if ctty is present? |
40 | if (flags & FILEUTILS_INTERACTIVE) { | 43 | if (flags & FILEUTILS_INTERACTIVE) { |
41 | // We would not do POSIX insanity. -i asks, | 44 | // We would not do POSIX insanity. -i asks, |
42 | // then _unlinks_ the offender. Presto. | 45 | // then _unlinks_ the offender. Presto. |
43 | // (No opening without O_EXCL, no unlinks only if -f) | 46 | // (No "opening without O_EXCL", no "unlink only if -f") |
44 | // Or else we will end up having 3 open()s! | 47 | // Or else we will end up having 3 open()s! |
45 | fprintf(stderr, "%s: overwrite '%s'? ", applet_name, dest); | 48 | fprintf(stderr, "%s: overwrite '%s'? ", applet_name, dest); |
46 | if (!bb_ask_confirmation()) | 49 | if (!bb_ask_confirmation()) |
@@ -60,9 +63,11 @@ static int ask_and_unlink(const char *dest, int flags) | |||
60 | */ | 63 | */ |
61 | int copy_file(const char *source, const char *dest, int flags) | 64 | int copy_file(const char *source, const char *dest, int flags) |
62 | { | 65 | { |
66 | /* This is a recursive function, try to minimize stack usage */ | ||
67 | /* NB: each struct stat is ~100 bytes */ | ||
63 | struct stat source_stat; | 68 | struct stat source_stat; |
64 | struct stat dest_stat; | 69 | struct stat dest_stat; |
65 | int status = 0; | 70 | signed char retval = 0; |
66 | signed char dest_exists = 0; | 71 | signed char dest_exists = 0; |
67 | signed char ovr; | 72 | signed char ovr; |
68 | 73 | ||
@@ -112,6 +117,7 @@ int copy_file(const char *source, const char *dest, int flags) | |||
112 | 117 | ||
113 | if (S_ISDIR(source_stat.st_mode)) { | 118 | if (S_ISDIR(source_stat.st_mode)) { |
114 | DIR *dp; | 119 | DIR *dp; |
120 | const char *tp; | ||
115 | struct dirent *d; | 121 | struct dirent *d; |
116 | mode_t saved_umask = 0; | 122 | mode_t saved_umask = 0; |
117 | 123 | ||
@@ -120,12 +126,23 @@ int copy_file(const char *source, const char *dest, int flags) | |||
120 | return -1; | 126 | return -1; |
121 | } | 127 | } |
122 | 128 | ||
123 | /* Create DEST. */ | 129 | /* Did we ever create source ourself before? */ |
130 | tp = is_in_ino_dev_hashtable(&source_stat); | ||
131 | if (tp) { | ||
132 | /* We did! it's a recursion! man the lifeboats... */ | ||
133 | bb_error_msg("recursion detected, omitting directory '%s'", | ||
134 | source); | ||
135 | return -1; | ||
136 | } | ||
137 | |||
138 | /* Create DEST */ | ||
124 | if (dest_exists) { | 139 | if (dest_exists) { |
125 | if (!S_ISDIR(dest_stat.st_mode)) { | 140 | if (!S_ISDIR(dest_stat.st_mode)) { |
126 | bb_error_msg("target '%s' is not a directory", dest); | 141 | bb_error_msg("target '%s' is not a directory", dest); |
127 | return -1; | 142 | return -1; |
128 | } | 143 | } |
144 | /* race here: user can substitute a symlink between | ||
145 | * this check and actual creation of files inside dest */ | ||
129 | } else { | 146 | } else { |
130 | mode_t mode; | 147 | mode_t mode; |
131 | saved_umask = umask(0); | 148 | saved_umask = umask(0); |
@@ -133,22 +150,29 @@ int copy_file(const char *source, const char *dest, int flags) | |||
133 | mode = source_stat.st_mode; | 150 | mode = source_stat.st_mode; |
134 | if (!(flags & FILEUTILS_PRESERVE_STATUS)) | 151 | if (!(flags & FILEUTILS_PRESERVE_STATUS)) |
135 | mode = source_stat.st_mode & ~saved_umask; | 152 | mode = source_stat.st_mode & ~saved_umask; |
153 | /* Allow owner to access new dir (at least for now) */ | ||
136 | mode |= S_IRWXU; | 154 | mode |= S_IRWXU; |
137 | |||
138 | if (mkdir(dest, mode) < 0) { | 155 | if (mkdir(dest, mode) < 0) { |
139 | umask(saved_umask); | 156 | umask(saved_umask); |
140 | bb_perror_msg("cannot create directory '%s'", dest); | 157 | bb_perror_msg("cannot create directory '%s'", dest); |
141 | return -1; | 158 | return -1; |
142 | } | 159 | } |
143 | |||
144 | umask(saved_umask); | 160 | umask(saved_umask); |
161 | /* need stat info for add_to_ino_dev_hashtable */ | ||
162 | if (lstat(dest, &dest_stat) < 0) { | ||
163 | bb_perror_msg("cannot stat '%s'", dest); | ||
164 | return -1; | ||
165 | } | ||
145 | } | 166 | } |
167 | /* remember (dev,inode) of each created dir. | ||
168 | * NULL: name is not remembered */ | ||
169 | add_to_ino_dev_hashtable(&dest_stat, NULL); | ||
146 | 170 | ||
147 | /* Recursively copy files in SOURCE. */ | 171 | /* Recursively copy files in SOURCE */ |
148 | dp = opendir(source); | 172 | dp = opendir(source); |
149 | if (dp == NULL) { | 173 | if (dp == NULL) { |
150 | status = -1; | 174 | retval = -1; |
151 | goto preserve_status; | 175 | goto preserve_mode_ugid_time; |
152 | } | 176 | } |
153 | 177 | ||
154 | while ((d = readdir(dp)) != NULL) { | 178 | while ((d = readdir(dp)) != NULL) { |
@@ -159,7 +183,7 @@ int copy_file(const char *source, const char *dest, int flags) | |||
159 | continue; | 183 | continue; |
160 | new_dest = concat_path_file(dest, d->d_name); | 184 | new_dest = concat_path_file(dest, d->d_name); |
161 | if (copy_file(new_source, new_dest, flags) < 0) | 185 | if (copy_file(new_source, new_dest, flags) < 0) |
162 | status = -1; | 186 | retval = -1; |
163 | free(new_source); | 187 | free(new_source); |
164 | free(new_dest); | 188 | free(new_dest); |
165 | } | 189 | } |
@@ -168,11 +192,13 @@ int copy_file(const char *source, const char *dest, int flags) | |||
168 | if (!dest_exists | 192 | if (!dest_exists |
169 | && chmod(dest, source_stat.st_mode & ~saved_umask) < 0 | 193 | && chmod(dest, source_stat.st_mode & ~saved_umask) < 0 |
170 | ) { | 194 | ) { |
171 | bb_perror_msg("cannot change permissions of '%s'", dest); | 195 | bb_perror_msg("cannot preserve %s of '%s'", "permissions", dest); |
172 | status = -1; | 196 | /* retval = -1; - WRONG! copy *WAS* made */ |
173 | } | 197 | } |
198 | goto preserve_mode_ugid_time; | ||
199 | } | ||
174 | 200 | ||
175 | } else if (flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) { | 201 | if (flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) { |
176 | int (*lf)(const char *oldpath, const char *newpath); | 202 | int (*lf)(const char *oldpath, const char *newpath); |
177 | make_links: | 203 | make_links: |
178 | // Hmm... maybe | 204 | // Hmm... maybe |
@@ -188,47 +214,52 @@ int copy_file(const char *source, const char *dest, int flags) | |||
188 | return -1; | 214 | return -1; |
189 | } | 215 | } |
190 | } | 216 | } |
217 | /* _Not_ jumping to preserve_mode_ugid_time: | ||
218 | * hard/softlinks don't have those */ | ||
191 | return 0; | 219 | return 0; |
220 | } | ||
192 | 221 | ||
193 | } else if (S_ISREG(source_stat.st_mode) | 222 | if (S_ISREG(source_stat.st_mode) |
194 | /* Huh? DEREF uses stat, which never returns links! */ | 223 | /* DEREF uses stat, which never returns S_ISLNK() == true. */ |
195 | /* || (FLAGS_DEREF && S_ISLNK(source_stat.st_mode)) */ | 224 | /* || (FLAGS_DEREF && S_ISLNK(source_stat.st_mode)) */ |
196 | ) { | 225 | ) { |
197 | int src_fd; | 226 | int src_fd; |
198 | int dst_fd; | 227 | int dst_fd; |
199 | if (ENABLE_FEATURE_PRESERVE_HARDLINKS) { | ||
200 | char *link_target; | ||
201 | 228 | ||
202 | if (!FLAGS_DEREF) { | 229 | if (ENABLE_FEATURE_PRESERVE_HARDLINKS && !FLAGS_DEREF) { |
203 | link_target = is_in_ino_dev_hashtable(&source_stat); | 230 | const char *link_target; |
204 | if (link_target) { | 231 | link_target = is_in_ino_dev_hashtable(&source_stat); |
232 | if (link_target) { | ||
233 | if (link(link_target, dest) < 0) { | ||
234 | ovr = ask_and_unlink(dest, flags); | ||
235 | if (ovr <= 0) | ||
236 | return ovr; | ||
205 | if (link(link_target, dest) < 0) { | 237 | if (link(link_target, dest) < 0) { |
206 | ovr = ask_and_unlink(dest, flags); | 238 | bb_perror_msg("cannot create link '%s'", dest); |
207 | if (ovr <= 0) | 239 | return -1; |
208 | return ovr; | ||
209 | if (link(link_target, dest) < 0) { | ||
210 | bb_perror_msg("cannot create link '%s'", dest); | ||
211 | return -1; | ||
212 | } | ||
213 | } | 240 | } |
214 | return 0; | ||
215 | } | 241 | } |
242 | return 0; | ||
216 | } | 243 | } |
217 | add_to_ino_dev_hashtable(&source_stat, dest); | 244 | add_to_ino_dev_hashtable(&source_stat, dest); |
218 | } | 245 | } |
219 | 246 | ||
220 | src_fd = open_or_warn(source, O_RDONLY); | 247 | src_fd = open_or_warn(source, O_RDONLY); |
221 | if (src_fd < 0) { | 248 | if (src_fd < 0) |
222 | return -1; | 249 | return -1; |
223 | } | ||
224 | 250 | ||
225 | #if DO_POSIX_CP /* POSIX way (a security problem versus symlink attacks!): */ | 251 | /* POSIX way is a security problem versus symlink attacks, |
226 | dst_fd = open(dest, (flags & FILEUTILS_INTERACTIVE) | 252 | * we do it only for non-symlinks, and only for non-recursive, |
227 | ? O_WRONLY|O_CREAT|O_EXCL | 253 | * non-interactive cp. NB: it is still racy |
228 | : O_WRONLY|O_CREAT|O_TRUNC, source_stat.st_mode); | 254 | * for "cp file /home/bad_user/file" case |
229 | #else /* safe way: */ | 255 | * (user can rm file and create a link to /etc/passwd) */ |
230 | dst_fd = open(dest, O_WRONLY|O_CREAT|O_EXCL, source_stat.st_mode); | 256 | if (DO_POSIX_CP |
231 | #endif | 257 | || (dest_exists && !(flags & (FILEUTILS_RECUR|FILEUTILS_INTERACTIVE)) |
258 | && !S_ISLNK(dest_stat.st_mode)) | ||
259 | ) { | ||
260 | dst_fd = open(dest, O_WRONLY|O_CREAT|O_TRUNC, source_stat.st_mode); | ||
261 | } else /* safe way: */ | ||
262 | dst_fd = open(dest, O_WRONLY|O_CREAT|O_EXCL, source_stat.st_mode); | ||
232 | if (dst_fd == -1) { | 263 | if (dst_fd == -1) { |
233 | ovr = ask_and_unlink(dest, flags); | 264 | ovr = ask_and_unlink(dest, flags); |
234 | if (ovr <= 0) { | 265 | if (ovr <= 0) { |
@@ -264,60 +295,55 @@ int copy_file(const char *source, const char *dest, int flags) | |||
264 | } | 295 | } |
265 | #endif | 296 | #endif |
266 | if (bb_copyfd_eof(src_fd, dst_fd) == -1) | 297 | if (bb_copyfd_eof(src_fd, dst_fd) == -1) |
267 | status = -1; | 298 | retval = -1; |
299 | /* Ok, writing side I can understand... */ | ||
268 | if (close(dst_fd) < 0) { | 300 | if (close(dst_fd) < 0) { |
269 | bb_perror_msg("cannot close '%s'", dest); | 301 | bb_perror_msg("cannot close '%s'", dest); |
270 | status = -1; | 302 | retval = -1; |
271 | } | ||
272 | if (close(src_fd) < 0) { | ||
273 | bb_perror_msg("cannot close '%s'", source); | ||
274 | status = -1; | ||
275 | } | 303 | } |
304 | /* ...but read size is already checked by bb_copyfd_eof */ | ||
305 | close(src_fd); | ||
306 | goto preserve_mode_ugid_time; | ||
307 | } | ||
276 | 308 | ||
277 | } else if (S_ISBLK(source_stat.st_mode) || S_ISCHR(source_stat.st_mode) | 309 | /* Source is a symlink or a special file */ |
278 | || S_ISSOCK(source_stat.st_mode) || S_ISFIFO(source_stat.st_mode) | 310 | /* We are lazy here, a bit lax with races... */ |
279 | || S_ISLNK(source_stat.st_mode) | 311 | if (dest_exists) { |
280 | ) { | 312 | errno = EEXIST; |
281 | // We are lazy here, a bit lax with races... | 313 | ovr = ask_and_unlink(dest, flags); |
282 | if (dest_exists) { | 314 | if (ovr <= 0) |
283 | ovr = ask_and_unlink(dest, flags); | 315 | return ovr; |
284 | if (ovr <= 0) | 316 | } |
285 | return ovr; | 317 | if (S_ISLNK(source_stat.st_mode)) { |
286 | } | 318 | char *lpath = xmalloc_readlink_or_warn(source); |
287 | if (S_ISFIFO(source_stat.st_mode)) { | 319 | if (lpath) { |
288 | if (mkfifo(dest, source_stat.st_mode) < 0) { | 320 | int r = symlink(lpath, dest); |
289 | bb_perror_msg("cannot create fifo '%s'", dest); | 321 | free(lpath); |
290 | return -1; | 322 | if (r < 0) { |
291 | } | ||
292 | } else if (S_ISLNK(source_stat.st_mode)) { | ||
293 | char *lpath; | ||
294 | |||
295 | lpath = xmalloc_readlink_or_warn(source); | ||
296 | if (lpath && symlink(lpath, dest) < 0) { | ||
297 | bb_perror_msg("cannot create symlink '%s'", dest); | 323 | bb_perror_msg("cannot create symlink '%s'", dest); |
298 | free(lpath); | ||
299 | return -1; | 324 | return -1; |
300 | } | 325 | } |
301 | free(lpath); | ||
302 | |||
303 | if (flags & FILEUTILS_PRESERVE_STATUS) | 326 | if (flags & FILEUTILS_PRESERVE_STATUS) |
304 | if (lchown(dest, source_stat.st_uid, source_stat.st_gid) < 0) | 327 | if (lchown(dest, source_stat.st_uid, source_stat.st_gid) < 0) |
305 | bb_perror_msg("cannot preserve %s of '%s'", "ownership", dest); | 328 | bb_perror_msg("cannot preserve %s of '%s'", "ownership", dest); |
306 | 329 | } | |
307 | return 0; | 330 | /* _Not_ jumping to preserve_mode_ugid_time: |
308 | 331 | * symlinks don't have those */ | |
309 | } else { | 332 | return 0; |
310 | if (mknod(dest, source_stat.st_mode, source_stat.st_rdev) < 0) { | 333 | } |
311 | bb_perror_msg("cannot create '%s'", dest); | 334 | if (S_ISBLK(source_stat.st_mode) || S_ISCHR(source_stat.st_mode) |
312 | return -1; | 335 | || S_ISSOCK(source_stat.st_mode) || S_ISFIFO(source_stat.st_mode) |
313 | } | 336 | ) { |
337 | if (mknod(dest, source_stat.st_mode, source_stat.st_rdev) < 0) { | ||
338 | bb_perror_msg("cannot create '%s'", dest); | ||
339 | return -1; | ||
314 | } | 340 | } |
315 | } else { | 341 | } else { |
316 | bb_error_msg("internal error: unrecognized file type"); | 342 | bb_error_msg("unrecognized file '%s' with mode %x", source, source_stat.st_mode); |
317 | return -1; | 343 | return -1; |
318 | } | 344 | } |
319 | 345 | ||
320 | preserve_status: | 346 | preserve_mode_ugid_time: |
321 | 347 | ||
322 | if (flags & FILEUTILS_PRESERVE_STATUS | 348 | if (flags & FILEUTILS_PRESERVE_STATUS |
323 | /* Cannot happen: */ | 349 | /* Cannot happen: */ |
@@ -327,6 +353,7 @@ int copy_file(const char *source, const char *dest, int flags) | |||
327 | 353 | ||
328 | times.actime = source_stat.st_atime; | 354 | times.actime = source_stat.st_atime; |
329 | times.modtime = source_stat.st_mtime; | 355 | times.modtime = source_stat.st_mtime; |
356 | /* BTW, utimes sets usec-precision time - just FYI */ | ||
330 | if (utime(dest, ×) < 0) | 357 | if (utime(dest, ×) < 0) |
331 | bb_perror_msg("cannot preserve %s of '%s'", "times", dest); | 358 | bb_perror_msg("cannot preserve %s of '%s'", "times", dest); |
332 | if (chown(dest, source_stat.st_uid, source_stat.st_gid) < 0) { | 359 | if (chown(dest, source_stat.st_uid, source_stat.st_gid) < 0) { |
@@ -337,5 +364,5 @@ int copy_file(const char *source, const char *dest, int flags) | |||
337 | bb_perror_msg("cannot preserve %s of '%s'", "permissions", dest); | 364 | bb_perror_msg("cannot preserve %s of '%s'", "permissions", dest); |
338 | } | 365 | } |
339 | 366 | ||
340 | return status; | 367 | return retval; |
341 | } | 368 | } |
diff --git a/libbb/xreadlink.c b/libbb/xreadlink.c index 18a8b9467..4d87b944d 100644 --- a/libbb/xreadlink.c +++ b/libbb/xreadlink.c | |||
@@ -10,8 +10,7 @@ | |||
10 | * NOTE: This function returns a malloced char* that you will have to free | 10 | * NOTE: This function returns a malloced char* that you will have to free |
11 | * yourself. You have been warned. | 11 | * yourself. You have been warned. |
12 | */ | 12 | */ |
13 | 13 | char *xmalloc_readlink(const char *path) | |
14 | char *xmalloc_readlink_or_warn(const char *path) | ||
15 | { | 14 | { |
16 | enum { GROWBY = 80 }; /* how large we will grow strings by */ | 15 | enum { GROWBY = 80 }; /* how large we will grow strings by */ |
17 | 16 | ||
@@ -20,20 +19,30 @@ char *xmalloc_readlink_or_warn(const char *path) | |||
20 | 19 | ||
21 | do { | 20 | do { |
22 | buf = xrealloc(buf, bufsize += GROWBY); | 21 | buf = xrealloc(buf, bufsize += GROWBY); |
23 | readsize = readlink(path, buf, bufsize); /* 1st try */ | 22 | readsize = readlink(path, buf, bufsize); |
24 | if (readsize == -1) { | 23 | if (readsize == -1) { |
25 | bb_perror_msg("%s", path); | ||
26 | free(buf); | 24 | free(buf); |
27 | return NULL; | 25 | return NULL; |
28 | } | 26 | } |
29 | } | 27 | } while (bufsize < readsize + 1); |
30 | while (bufsize < readsize + 1); | ||
31 | 28 | ||
32 | buf[readsize] = '\0'; | 29 | buf[readsize] = '\0'; |
33 | 30 | ||
34 | return buf; | 31 | return buf; |
35 | } | 32 | } |
36 | 33 | ||
34 | char *xmalloc_readlink_or_warn(const char *path) | ||
35 | { | ||
36 | char *buf = xmalloc_readlink(path); | ||
37 | if (!buf) { | ||
38 | /* EINVAL => "file: Invalid argument" => puzzled user */ | ||
39 | bb_error_msg("%s: cannot read link (not a symlink?)", path); | ||
40 | } | ||
41 | return buf; | ||
42 | } | ||
43 | |||
44 | /* UNUSED */ | ||
45 | #if 0 | ||
37 | char *xmalloc_realpath(const char *path) | 46 | char *xmalloc_realpath(const char *path) |
38 | { | 47 | { |
39 | #if defined(__GLIBC__) && !defined(__UCLIBC__) | 48 | #if defined(__GLIBC__) && !defined(__UCLIBC__) |
@@ -46,3 +55,4 @@ char *xmalloc_realpath(const char *path) | |||
46 | return xstrdup(realpath(path, buf)); | 55 | return xstrdup(realpath(path, buf)); |
47 | #endif | 56 | #endif |
48 | } | 57 | } |
58 | #endif | ||