Commit | Line | Data |
---|---|---|
1f978f4c | 1 | /* |
4d2ae24a KB |
2 | * Copyright (c) 1988 The Regents of the University of California. |
3 | * All rights reserved. | |
4 | * | |
5 | * This code is derived from software contributed to Berkeley by | |
6 | * David Hitz of Auspex Systems Inc. | |
7 | * | |
8 | * Redistribution and use in source and binary forms are permitted | |
9 | * provided that the above copyright notice and this paragraph are | |
10 | * duplicated in all such forms and that any documentation, | |
11 | * advertising materials, and other materials related to such | |
12 | * distribution and use acknowledge that the software was developed | |
13 | * by the University of California, Berkeley. The name of the | |
14 | * University may not be used to endorse or promote products derived | |
15 | * from this software without specific prior written permission. | |
16 | * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR | |
17 | * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED | |
18 | * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. | |
19 | */ | |
20 | ||
21 | #ifndef lint | |
22 | char copyright[] = | |
23 | "@(#) Copyright (c) 1988 The Regents of the University of California.\n\ | |
24 | All rights reserved.\n"; | |
25 | #endif /* not lint */ | |
26 | ||
27 | #ifndef lint | |
24e5a125 | 28 | static char sccsid[] = "@(#)cp.c 5.6 (Berkeley) %G%"; |
4d2ae24a KB |
29 | #endif /* not lint */ |
30 | ||
31 | /* | |
32 | * cp copies source files to target files. | |
ae5e5236 KB |
33 | * |
34 | * The global path_t structures "to" and "from" always contain paths to the | |
4d2ae24a KB |
35 | * current source and target files, respectively. Since cp does not change |
36 | * directories, these paths can be either absolute or dot-realative. | |
ae5e5236 KB |
37 | * |
38 | * The basic algorithm is to initialize "to" and "from", and then call the | |
39 | * recursive copy() function to do the actual work. If "from" is a file, | |
40 | * copy copies the data. If "from" is a directory, copy creates the | |
41 | * corresponding "to" directory, and calls itself recursively on all of | |
42 | * the entries in the "from" directory. | |
1f978f4c KM |
43 | */ |
44 | ||
4d2ae24a | 45 | #include <sys/param.h> |
84592a94 | 46 | #include <sys/stat.h> |
ae5e5236 | 47 | #include <sys/file.h> |
7afd0a98 | 48 | #include <sys/dir.h> |
0908a03a | 49 | #include <sys/time.h> |
5f89032b | 50 | |
4d2ae24a KB |
51 | #include <stdio.h> |
52 | #include <errno.h> | |
53 | #include <strings.h> | |
ae5e5236 KB |
54 | |
55 | typedef struct { | |
a783899b KB |
56 | char *p_path; /* pointer to the start of a path. */ |
57 | char *p_end; /* pointer to NULL at end of path. */ | |
4d2ae24a | 58 | } path_t; |
ae5e5236 | 59 | |
a783899b KB |
60 | #define type(st) ((st).st_mode&S_IFMT) |
61 | ||
4d2ae24a KB |
62 | char *path_append(), *path_basename(); |
63 | void path_restore(); | |
ae5e5236 | 64 | |
24e5a125 | 65 | int exit_val, symfollow; |
4d2ae24a KB |
66 | int interactive_flag, preserve_flag, recursive_flag; |
67 | char *buf; /* I/O; malloc for best alignment. */ | |
24e5a125 KB |
68 | char from_buf[MAXPATHLEN + 1], /* source path buffer */ |
69 | to_buf[MAXPATHLEN + 1]; /* target path buffer */ | |
4d2ae24a KB |
70 | path_t from = {from_buf, from_buf}; |
71 | path_t to = {to_buf, to_buf}; | |
84592a94 BJ |
72 | |
73 | main(argc, argv) | |
4d2ae24a KB |
74 | int argc; |
75 | char **argv; | |
84592a94 | 76 | { |
4d2ae24a KB |
77 | extern int optind, errno; |
78 | struct stat to_stat; | |
79 | register int c, r; | |
a783899b | 80 | int force_flag; |
4d2ae24a KB |
81 | char *old_to, *malloc(); |
82 | ||
a783899b KB |
83 | force_flag = 0; |
84 | while ((c = getopt(argc, argv, "Rfhipr")) != EOF) { | |
85 | switch ((char)c) { | |
86 | case 'f': | |
87 | force_flag = 1; | |
88 | break; | |
0493752e KB |
89 | case 'h': |
90 | symfollow = 1; | |
91 | break; | |
4d2ae24a KB |
92 | case 'i': |
93 | interactive_flag = isatty(fileno(stdin)); | |
94 | break; | |
95 | case 'p': | |
96 | preserve_flag = 1; | |
97 | (void)umask(0); | |
98 | break; | |
99 | case 'r': | |
100 | case 'R': | |
101 | recursive_flag = 1; | |
102 | break; | |
103 | case '?': | |
104 | default: | |
105 | usage(); | |
106 | break; | |
107 | } | |
84592a94 | 108 | } |
0493752e KB |
109 | argc -= optind; |
110 | argv += optind; | |
ae5e5236 | 111 | |
4d2ae24a KB |
112 | if (argc < 2) |
113 | usage(); | |
ae5e5236 | 114 | |
a783899b KB |
115 | if (force_flag) |
116 | interactive_flag = 0; | |
117 | ||
4d2ae24a KB |
118 | buf = (char *)malloc(MAXBSIZE); |
119 | if (!buf) { | |
0493752e | 120 | (void)fprintf(stderr, "cp: out of space.\n"); |
4d2ae24a | 121 | exit(1); |
ae5e5236 KB |
122 | } |
123 | ||
24e5a125 | 124 | /* consume last argument first. */ |
4d2ae24a KB |
125 | if (!path_set(&to, argv[--argc])) |
126 | exit(exit_val); | |
ae5e5236 | 127 | |
ae5e5236 | 128 | /* |
4d2ae24a KB |
129 | * Cp has two distinct cases: |
130 | * | |
131 | * Case (1) $ cp [-rip] source target | |
132 | * | |
133 | * Case (2) $ cp [-rip] source1 ... directory | |
134 | * | |
135 | * In both cases, source can be either a file or a directory. | |
136 | * | |
137 | * In (1), the target becomes a copy of the source. That is, if the | |
138 | * source is a file, the target will be a file, and likewise for | |
139 | * directories. | |
140 | * | |
141 | * In (2), the real target is not directory, but "directory/source". | |
ae5e5236 | 142 | */ |
ae5e5236 | 143 | |
4d2ae24a KB |
144 | r = stat(to.p_path, &to_stat); |
145 | if (r == -1 && errno != ENOENT) { | |
24e5a125 | 146 | error(to.p_path); |
4d2ae24a KB |
147 | exit(1); |
148 | } | |
a783899b | 149 | if (r == -1 || type(to_stat) != S_IFDIR) { |
4d2ae24a KB |
150 | /* |
151 | * Case (1). Target is not a directory. | |
152 | */ | |
153 | if (argc > 1) { | |
154 | usage(); | |
155 | exit(1); | |
156 | } | |
157 | if (!path_set(&from, *argv)) | |
158 | exit(exit_val); | |
159 | copy(); | |
160 | } | |
161 | else { | |
162 | /* | |
163 | * Case (2). Target is a directory. | |
164 | */ | |
24e5a125 | 165 | for (;; ++argv) { |
4d2ae24a KB |
166 | if (!path_set(&from, *argv)) |
167 | continue; | |
168 | old_to = path_append(&to, path_basename(&from), -1); | |
169 | if (!old_to) | |
170 | continue; | |
171 | copy(); | |
24e5a125 KB |
172 | if (!--argc) |
173 | break; | |
4d2ae24a KB |
174 | path_restore(&to, old_to); |
175 | } | |
ae5e5236 | 176 | } |
4d2ae24a | 177 | exit(exit_val); |
84592a94 BJ |
178 | } |
179 | ||
a783899b | 180 | /* copy file or directory at "from" to "to". */ |
ae5e5236 | 181 | copy() |
84592a94 | 182 | { |
4d2ae24a | 183 | struct stat from_stat, to_stat; |
24e5a125 | 184 | int dne, statval; |
ae5e5236 | 185 | |
0493752e KB |
186 | statval = symfollow || !recursive_flag ? |
187 | stat(from.p_path, &from_stat) : lstat(from.p_path, &from_stat); | |
188 | if (statval == -1) { | |
24e5a125 | 189 | error(from.p_path); |
4d2ae24a KB |
190 | return; |
191 | } | |
0493752e KB |
192 | |
193 | /* not an error, but need to remember it happened */ | |
4d2ae24a | 194 | if (stat(to.p_path, &to_stat) == -1) |
24e5a125 KB |
195 | dne = 1; |
196 | else { | |
197 | if (to_stat.st_dev == from_stat.st_dev && | |
198 | to_stat.st_ino == from_stat.st_ino) { | |
199 | (void)fprintf(stderr, | |
200 | "cp: %s and %s are identical (not copied).\n", | |
201 | to.p_path, from.p_path); | |
202 | exit_val = 1; | |
203 | return; | |
204 | } | |
205 | dne = 0; | |
84592a94 | 206 | } |
ae5e5236 | 207 | |
a783899b KB |
208 | switch(type(from_stat)) { |
209 | case S_IFLNK: | |
24e5a125 | 210 | copy_link(!dne); |
0493752e | 211 | return; |
a783899b | 212 | case S_IFDIR: |
4d2ae24a KB |
213 | if (!recursive_flag) { |
214 | (void)fprintf(stderr, | |
a783899b KB |
215 | "cp: %s is a directory (not copied).\n", |
216 | from.p_path); | |
4d2ae24a KB |
217 | exit_val = 1; |
218 | return; | |
219 | } | |
24e5a125 KB |
220 | if (dne) { |
221 | /* | |
222 | * If the directory doesn't exist, create the new | |
223 | * one with the from file mode plus owner RWX bits, | |
224 | * modified by the umask. Trade-off between being | |
225 | * able to write the directory (if from directory is | |
226 | * 555) and not causing a permissions race. If the | |
227 | * umask blocks owner writes cp fails. | |
228 | */ | |
229 | if (mkdir(to.p_path, from_stat.st_mode|S_IRWXU) < 0) { | |
230 | error(to.p_path); | |
4d2ae24a KB |
231 | return; |
232 | } | |
4d2ae24a | 233 | } |
a783899b KB |
234 | else if (type(to_stat) != S_IFDIR) { |
235 | (void)fprintf(stderr, "cp: %s: not a directory.\n", | |
236 | to.p_path); | |
4d2ae24a KB |
237 | return; |
238 | } | |
239 | copy_dir(); | |
24e5a125 KB |
240 | /* |
241 | * If directory didn't exist, set it to be the same as | |
242 | * the from directory, umodified by the umask; arguably | |
243 | * wrong, but it's been that way forever. | |
244 | */ | |
245 | if (dne && !preserve_flag) | |
246 | (void)chmod(to.p_path, from_stat.st_mode); | |
a783899b KB |
247 | break; |
248 | case S_IFCHR: | |
249 | case S_IFBLK: | |
24e5a125 KB |
250 | /* |
251 | * if recursive flag on, try and create the special device | |
252 | * otherwise copy the contents. | |
253 | */ | |
a783899b KB |
254 | if (recursive_flag) { |
255 | copy_special(&from_stat, &to_stat); | |
256 | if (preserve_flag) | |
24e5a125 | 257 | setfile(&from_stat, 0); |
a783899b KB |
258 | return; |
259 | } | |
260 | /* FALLTHROUGH */ | |
261 | default: | |
24e5a125 | 262 | copy_file(&from_stat); |
5f89032b | 263 | } |
ae5e5236 KB |
264 | } |
265 | ||
24e5a125 KB |
266 | copy_file(fs) |
267 | struct stat *fs; | |
ae5e5236 | 268 | { |
a783899b | 269 | register int from_fd, to_fd, rcount, wcount; |
4d2ae24a | 270 | |
24e5a125 KB |
271 | if ((from_fd = open(from.p_path, O_RDONLY, 0)) == -1) { |
272 | error(from.p_path); | |
273 | return; | |
4d2ae24a KB |
274 | } |
275 | ||
276 | /* | |
24e5a125 KB |
277 | * In the interactive case, use O_EXCL to notice existing files. |
278 | * If the file exists, verify with the user. | |
279 | * | |
280 | * If the file DNE, create it with the mode of the from file modified | |
281 | * by the umask; arguably wrong but it makes copying executables work | |
282 | * right and it's been that way forever. The other choice is 666 | |
283 | * or'ed with the execute bits on the from file modified by the umask. | |
4d2ae24a KB |
284 | */ |
285 | to_fd = open(to.p_path, | |
0493752e | 286 | (interactive_flag ? O_EXCL : 0) | O_WRONLY | O_CREAT | O_TRUNC, |
24e5a125 | 287 | fs->st_mode); |
ae5e5236 | 288 | |
4d2ae24a | 289 | if (to_fd == -1 && errno == EEXIST && interactive_flag) { |
03137eae KB |
290 | int checkch, ch; |
291 | ||
292 | (void)fprintf(stderr, "overwrite %s? ", to.p_path); | |
293 | checkch = ch = getchar(); | |
294 | while (ch != '\n' && ch != EOF) | |
295 | ch = getchar(); | |
296 | if (checkch != 'y') | |
24e5a125 | 297 | return; |
03137eae | 298 | /* try again. */ |
24e5a125 KB |
299 | to_fd = open(to.p_path, O_WRONLY | O_CREAT | O_TRUNC, |
300 | fs->st_mode); | |
5f89032b | 301 | } |
ae5e5236 | 302 | |
4d2ae24a | 303 | if (to_fd == -1) { |
24e5a125 | 304 | error(to.p_path); |
4d2ae24a | 305 | (void)close(from_fd); |
24e5a125 | 306 | return; |
ae5e5236 KB |
307 | } |
308 | ||
4d2ae24a KB |
309 | while ((rcount = read(from_fd, buf, MAXBSIZE)) > 0) { |
310 | wcount = write(to_fd, buf, rcount); | |
311 | if (rcount != wcount || wcount == -1) { | |
24e5a125 | 312 | error(to.p_path); |
4d2ae24a KB |
313 | break; |
314 | } | |
ae5e5236 | 315 | } |
24e5a125 KB |
316 | if (rcount < 0) |
317 | error(from.p_path); | |
318 | if (preserve_flag) | |
319 | setfile(fs, to_fd); | |
4d2ae24a KB |
320 | (void)close(from_fd); |
321 | (void)close(to_fd); | |
4d2ae24a | 322 | } |
ae5e5236 | 323 | |
4d2ae24a KB |
324 | copy_dir() |
325 | { | |
326 | struct stat from_stat; | |
4d2ae24a | 327 | struct direct *dp, **dir_list; |
24e5a125 KB |
328 | register int dir_cnt, i; |
329 | char *old_from, *old_to; | |
4d2ae24a KB |
330 | |
331 | dir_cnt = scandir(from.p_path, &dir_list, NULL, NULL); | |
332 | if (dir_cnt == -1) { | |
03137eae | 333 | (void)fprintf(stderr, "cp: can't read directory %s.\n", |
4d2ae24a KB |
334 | from.p_path); |
335 | exit_val = 1; | |
ae5e5236 KB |
336 | } |
337 | ||
24e5a125 KB |
338 | /* |
339 | * Instead of handling directory entries in the order they appear | |
340 | * on disk, do non-directory files before directory files. | |
341 | * There are two reasons to do directories last. The first is | |
342 | * efficiency. Files tend to be in the same cylinder group as | |
343 | * their parent, whereas directories tend not to be. Copying files | |
344 | * all at once reduces seeking. Second, deeply nested tree's | |
345 | * could use up all the file descriptors if we didn't close one | |
346 | * directory before recursivly starting on the next. | |
347 | */ | |
348 | /* copy files */ | |
4d2ae24a KB |
349 | for (i = 0; i < dir_cnt; ++i) { |
350 | dp = dir_list[i]; | |
351 | if (dp->d_namlen <= 2 && dp->d_name[0] == '.' | |
24e5a125 KB |
352 | && (dp->d_name[1] == NULL || dp->d_name[1] == '.')) |
353 | goto done; | |
4d2ae24a | 354 | old_from = path_append(&from, dp->d_name, (int)dp->d_namlen); |
24e5a125 KB |
355 | if (!old_from) |
356 | goto done; | |
4d2ae24a KB |
357 | |
358 | if (stat(from.p_path, &from_stat) < 0) { | |
24e5a125 KB |
359 | error(dp->d_name); |
360 | path_restore(&from, old_from); | |
361 | goto done; | |
362 | } | |
363 | if (type(from_stat) == S_IFDIR) { | |
4d2ae24a KB |
364 | path_restore(&from, old_from); |
365 | continue; | |
366 | } | |
24e5a125 KB |
367 | old_to = path_append(&to, dp->d_name, (int)dp->d_namlen); |
368 | if (old_to) { | |
4d2ae24a KB |
369 | copy(); |
370 | path_restore(&to, old_to); | |
4d2ae24a KB |
371 | } |
372 | path_restore(&from, old_from); | |
24e5a125 KB |
373 | done: dir_list[i] = NULL; |
374 | (void)free((char *)dp); | |
5f89032b | 375 | } |
ae5e5236 | 376 | |
24e5a125 | 377 | /* copy directories */ |
4d2ae24a KB |
378 | for (i = 0; i < dir_cnt; ++i) { |
379 | dp = dir_list[i]; | |
380 | if (!dp) | |
381 | continue; | |
382 | old_from = path_append(&from, dp->d_name, (int) dp->d_namlen); | |
383 | if (!old_from) { | |
384 | (void)free((char *)dp); | |
385 | continue; | |
386 | } | |
387 | old_to = path_append(&to, dp->d_name, (int) dp->d_namlen); | |
388 | if (!old_to) { | |
389 | (void)free((char *)dp); | |
390 | path_restore(&from, old_from); | |
391 | continue; | |
392 | } | |
393 | copy(); | |
394 | free((char *)dp); | |
395 | path_restore(&from, old_from); | |
396 | path_restore(&to, old_to); | |
5f89032b | 397 | } |
4d2ae24a | 398 | free((char *)dir_list); |
5f89032b | 399 | } |
1a4b831f | 400 | |
0493752e KB |
401 | copy_link(exists) |
402 | int exists; | |
403 | { | |
404 | char link[MAXPATHLEN]; | |
405 | ||
406 | if (readlink(from.p_path, link, sizeof(link)) == -1) { | |
24e5a125 | 407 | error(from.p_path); |
0493752e KB |
408 | return; |
409 | } | |
410 | if (exists && unlink(to.p_path)) { | |
24e5a125 | 411 | error(to.p_path); |
0493752e KB |
412 | return; |
413 | } | |
414 | if (symlink(link, to.p_path)) { | |
24e5a125 | 415 | error(link); |
0493752e KB |
416 | return; |
417 | } | |
418 | } | |
419 | ||
a783899b KB |
420 | copy_special(from_stat, to_stat) |
421 | struct stat *from_stat, *to_stat; | |
422 | { | |
423 | if (to_stat->st_ino != -1 && unlink(to.p_path)) { | |
24e5a125 | 424 | error(to.p_path); |
a783899b KB |
425 | return; |
426 | } | |
427 | if (mknod(to.p_path, from_stat->st_mode, from_stat->st_rdev)) { | |
24e5a125 | 428 | error(to.p_path); |
a783899b KB |
429 | return; |
430 | } | |
431 | } | |
432 | ||
24e5a125 KB |
433 | setfile(fs, fd) |
434 | register struct stat *fs; | |
435 | int fd; | |
a783899b KB |
436 | { |
437 | static struct timeval tv[2]; | |
24e5a125 | 438 | static int dochown = 1; |
a783899b | 439 | |
a783899b KB |
440 | tv[0].tv_sec = fs->st_atime; |
441 | tv[1].tv_sec = fs->st_mtime; | |
442 | if (utimes(to.p_path, tv)) | |
24e5a125 KB |
443 | error(to.p_path); |
444 | /* | |
445 | * Changing the ownership probably won't succeed, unless we're | |
446 | * root or POSIX_CHOWN_RESTRICTED is not set. Try it last so | |
447 | * everything else gets set first. | |
448 | */ | |
449 | if (fd) { | |
450 | if (fchmod(fd, fs->st_mode)) | |
451 | error(to.p_path); | |
452 | if (dochown && fchown(fd, fs->st_uid, fs->st_gid) == -1) | |
453 | if (errno == EPERM) | |
454 | dochown = 0; | |
455 | else | |
456 | error(to.p_path); | |
457 | } else { | |
458 | if (chmod(to.p_path, fs->st_mode)) | |
459 | error(to.p_path); | |
460 | if (dochown && chown(to.p_path, fs->st_uid, fs->st_gid) == -1) | |
461 | if (errno == EPERM) | |
462 | dochown = 0; | |
463 | else | |
464 | error(to.p_path); | |
465 | } | |
a783899b KB |
466 | } |
467 | ||
24e5a125 KB |
468 | error(s) |
469 | char *s; | |
0908a03a | 470 | { |
4d2ae24a | 471 | extern int errno; |
ae5e5236 | 472 | |
4d2ae24a | 473 | exit_val = 1; |
24e5a125 | 474 | (void)fprintf(stderr, "cp: %s: %s\n", s, strerror(errno)); |
4d2ae24a | 475 | } |
ae5e5236 KB |
476 | |
477 | /******************************************************************** | |
478 | * Path Manipulation Routines. | |
479 | ********************************************************************/ | |
480 | ||
481 | /* | |
482 | * These functions manipulate paths in "path_t" structures. | |
483 | * | |
484 | * They eliminate multiple slashes in paths when they notice them, and keep | |
485 | * the path non-slash terminated. | |
486 | * | |
4d2ae24a | 487 | * Both path_set() and path_append() return 0 if the requested name |
ae5e5236 KB |
488 | * would be too long. |
489 | */ | |
490 | ||
4d2ae24a KB |
491 | #define STRIP_TRAILING_SLASH(p) { \ |
492 | while ((p)->p_end > (p)->p_path && (p)->p_end[-1] == '/') \ | |
493 | *--(p)->p_end = 0; \ | |
494 | } | |
ae5e5236 KB |
495 | |
496 | /* | |
497 | * Move specified string into path. Convert "" to "." to handle BSD | |
498 | * semantics for a null path. Strip trailing slashes. | |
499 | */ | |
500 | path_set(p, string) | |
4d2ae24a KB |
501 | register path_t *p; |
502 | char *string; | |
ae5e5236 | 503 | { |
4d2ae24a | 504 | if (strlen(string) > MAXPATHLEN) { |
24e5a125 | 505 | (void)fprintf(stderr, "cp: %s: name too long.\n", string); |
4d2ae24a KB |
506 | exit_val = 1; |
507 | return(0); | |
508 | } | |
ae5e5236 | 509 | |
4d2ae24a KB |
510 | (void)strcpy(p->p_path, string); |
511 | p->p_end = p->p_path + strlen(p->p_path); | |
ae5e5236 | 512 | |
4d2ae24a KB |
513 | if (p->p_path == p->p_end) { |
514 | *p->p_end++ = '.'; | |
515 | *p->p_end = 0; | |
516 | } | |
ae5e5236 | 517 | |
4d2ae24a KB |
518 | STRIP_TRAILING_SLASH(p); |
519 | return(1); | |
ae5e5236 KB |
520 | } |
521 | ||
522 | /* | |
523 | * Append specified string to path, inserting '/' if necessary. Return a | |
524 | * pointer to the old end of path for restoration. | |
525 | */ | |
4d2ae24a | 526 | char * |
ae5e5236 | 527 | path_append(p, name, len) |
4d2ae24a KB |
528 | register path_t *p; |
529 | char *name; | |
530 | int len; | |
1a4b831f | 531 | { |
4d2ae24a | 532 | char *old; |
ae5e5236 | 533 | |
4d2ae24a KB |
534 | old = p->p_end; |
535 | if (len == -1) | |
536 | len = strlen(name); | |
ae5e5236 | 537 | |
4d2ae24a KB |
538 | /* |
539 | * The final "+ 1" accounts for the '/' between old path and name. | |
540 | */ | |
541 | if ((len + p->p_end - p->p_path + 1) > MAXPATHLEN) { | |
24e5a125 | 542 | (void)fprintf(stderr, |
03137eae | 543 | "cp: %s/%s: name too long.\n", p->p_path, name); |
4d2ae24a KB |
544 | exit_val = 1; |
545 | return(0); | |
546 | } | |
ae5e5236 | 547 | |
4d2ae24a KB |
548 | /* |
549 | * This code should always be executed, since paths shouldn't | |
550 | * end in '/'. | |
551 | */ | |
552 | if (p->p_end[-1] != '/') { | |
553 | *p->p_end++ = '/'; | |
554 | *p->p_end = 0; | |
555 | } | |
ae5e5236 | 556 | |
4d2ae24a KB |
557 | (void)strncat(p->p_end, name, len); |
558 | p->p_end += len; | |
559 | *p->p_end = 0; | |
ae5e5236 | 560 | |
4d2ae24a KB |
561 | STRIP_TRAILING_SLASH(p); |
562 | return(old); | |
ae5e5236 KB |
563 | } |
564 | ||
ae5e5236 KB |
565 | /* |
566 | * Restore path to previous value. (As returned by path_append.) | |
567 | */ | |
568 | void | |
569 | path_restore(p, old) | |
4d2ae24a KB |
570 | path_t *p; |
571 | char *old; | |
ae5e5236 | 572 | { |
4d2ae24a KB |
573 | p->p_end = old; |
574 | *p->p_end = 0; | |
ae5e5236 KB |
575 | } |
576 | ||
ae5e5236 KB |
577 | /* |
578 | * Return basename of path. (Like basename(1).) | |
579 | */ | |
4d2ae24a | 580 | char * |
ae5e5236 | 581 | path_basename(p) |
4d2ae24a | 582 | path_t *p; |
ae5e5236 | 583 | { |
4d2ae24a | 584 | char *basename; |
ae5e5236 | 585 | |
4d2ae24a | 586 | basename = rindex(p->p_path, '/'); |
24e5a125 | 587 | return(basename ? ++basename : p->p_path); |
4d2ae24a | 588 | } |
1a4b831f | 589 | |
4d2ae24a KB |
590 | usage() |
591 | { | |
592 | (void)fprintf(stderr, | |
593 | "usage: cp [-ip] f1 f2; or: cp [-irp] f1 ... fn directory\n"); | |
594 | exit(1); | |
1a4b831f | 595 | } |