fix errno scoping for ANSI C
[unix-history] / usr / src / bin / cp / cp.c
CommitLineData
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
22char 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
3c66b1ab 28static char sccsid[] = "@(#)cp.c 5.14 (Berkeley) %G%";
4d2ae24a
KB
29#endif /* not lint */
30
31/*
32 * cp copies source files to target files.
ae5e5236 33 *
9f329525 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>
4d2ae24a
KB
50#include <stdio.h>
51#include <errno.h>
52#include <strings.h>
ae5e5236 53
9f329525
KB
54#define type(st) ((st).st_mode & S_IFMT)
55
ae5e5236 56typedef struct {
9f329525
KB
57 char p_path[MAXPATHLEN + 1]; /* pointer to the start of a path. */
58 char *p_end; /* pointer to NULL at end of path. */
59} PATH_T;
ae5e5236 60
9f329525
KB
61PATH_T from = { "", from.p_path };
62PATH_T to = { "", to.p_path };
a783899b 63
1a4aed3b
KB
64uid_t myuid;
65int exit_val, myumask;
4d2ae24a 66int interactive_flag, preserve_flag, recursive_flag;
d57b5e76
KB
67int (*statfcn)();
68char *buf, *pname;
1a4aed3b
KB
69char *path_append(), *path_basename();
70
84592a94 71main(argc, argv)
4d2ae24a
KB
72 int argc;
73 char **argv;
84592a94 74{
4d2ae24a
KB
75 extern int optind, errno;
76 struct stat to_stat;
77 register int c, r;
a68fe064 78 int force_flag, symfollow, lstat(), stat();
d57b5e76
KB
79 char *old_to, *p, *malloc();
80
81 /*
82 * cp is used by mv(1) -- except for usage statements, print
83 * the "called as" program name.
84 */
85 pname = (p = rindex(*argv,'/')) ? ++p : *argv;
4d2ae24a 86
a68fe064 87 force_flag = symfollow = 0;
a783899b
KB
88 while ((c = getopt(argc, argv, "Rfhipr")) != EOF) {
89 switch ((char)c) {
90 case 'f':
91 force_flag = 1;
92 break;
0493752e
KB
93 case 'h':
94 symfollow = 1;
95 break;
4d2ae24a
KB
96 case 'i':
97 interactive_flag = isatty(fileno(stdin));
98 break;
99 case 'p':
100 preserve_flag = 1;
4d2ae24a
KB
101 break;
102 case 'r':
103 case 'R':
104 recursive_flag = 1;
105 break;
106 case '?':
107 default:
108 usage();
109 break;
110 }
84592a94 111 }
0493752e
KB
112 argc -= optind;
113 argv += optind;
ae5e5236 114
4d2ae24a
KB
115 if (argc < 2)
116 usage();
ae5e5236 117
a783899b
KB
118 if (force_flag)
119 interactive_flag = 0;
120
4d2ae24a
KB
121 buf = (char *)malloc(MAXBSIZE);
122 if (!buf) {
d57b5e76 123 (void)fprintf(stderr, "%s: out of space.\n", pname);
4d2ae24a 124 exit(1);
ae5e5236
KB
125 }
126
1a4aed3b
KB
127 myuid = getuid();
128
129 /* copy the umask for explicit mode setting */
130 myumask = umask(0);
131 (void)umask(myumask);
132
24e5a125 133 /* consume last argument first. */
4d2ae24a
KB
134 if (!path_set(&to, argv[--argc]))
135 exit(exit_val);
ae5e5236 136
a68fe064
KB
137 statfcn = symfollow || !recursive_flag ? stat : lstat;
138
ae5e5236 139 /*
4d2ae24a
KB
140 * Cp has two distinct cases:
141 *
142 * Case (1) $ cp [-rip] source target
143 *
144 * Case (2) $ cp [-rip] source1 ... directory
145 *
146 * In both cases, source can be either a file or a directory.
147 *
148 * In (1), the target becomes a copy of the source. That is, if the
149 * source is a file, the target will be a file, and likewise for
150 * directories.
151 *
152 * In (2), the real target is not directory, but "directory/source".
ae5e5236 153 */
ae5e5236 154
4d2ae24a
KB
155 r = stat(to.p_path, &to_stat);
156 if (r == -1 && errno != ENOENT) {
24e5a125 157 error(to.p_path);
4d2ae24a
KB
158 exit(1);
159 }
a783899b 160 if (r == -1 || type(to_stat) != S_IFDIR) {
4d2ae24a
KB
161 /*
162 * Case (1). Target is not a directory.
163 */
164 if (argc > 1) {
165 usage();
166 exit(1);
167 }
168 if (!path_set(&from, *argv))
169 exit(exit_val);
170 copy();
171 }
172 else {
173 /*
174 * Case (2). Target is a directory.
175 */
24e5a125 176 for (;; ++argv) {
4d2ae24a
KB
177 if (!path_set(&from, *argv))
178 continue;
179 old_to = path_append(&to, path_basename(&from), -1);
180 if (!old_to)
181 continue;
182 copy();
24e5a125
KB
183 if (!--argc)
184 break;
4d2ae24a
KB
185 path_restore(&to, old_to);
186 }
ae5e5236 187 }
4d2ae24a 188 exit(exit_val);
84592a94
BJ
189}
190
a783899b 191/* copy file or directory at "from" to "to". */
ae5e5236 192copy()
84592a94 193{
4d2ae24a 194 struct stat from_stat, to_stat;
24e5a125 195 int dne, statval;
ae5e5236 196
a68fe064 197 statval = statfcn(from.p_path, &from_stat);
0493752e 198 if (statval == -1) {
24e5a125 199 error(from.p_path);
4d2ae24a
KB
200 return;
201 }
0493752e
KB
202
203 /* not an error, but need to remember it happened */
4d2ae24a 204 if (stat(to.p_path, &to_stat) == -1)
24e5a125
KB
205 dne = 1;
206 else {
207 if (to_stat.st_dev == from_stat.st_dev &&
208 to_stat.st_ino == from_stat.st_ino) {
209 (void)fprintf(stderr,
d57b5e76
KB
210 "%s: %s and %s are identical (not copied).\n",
211 pname, to.p_path, from.p_path);
24e5a125
KB
212 exit_val = 1;
213 return;
214 }
215 dne = 0;
84592a94 216 }
ae5e5236 217
a783899b
KB
218 switch(type(from_stat)) {
219 case S_IFLNK:
24e5a125 220 copy_link(!dne);
0493752e 221 return;
a783899b 222 case S_IFDIR:
4d2ae24a
KB
223 if (!recursive_flag) {
224 (void)fprintf(stderr,
d57b5e76
KB
225 "%s: %s is a directory (not copied).\n",
226 pname, from.p_path);
4d2ae24a
KB
227 exit_val = 1;
228 return;
229 }
24e5a125
KB
230 if (dne) {
231 /*
232 * If the directory doesn't exist, create the new
233 * one with the from file mode plus owner RWX bits,
234 * modified by the umask. Trade-off between being
235 * able to write the directory (if from directory is
236 * 555) and not causing a permissions race. If the
237 * umask blocks owner writes cp fails.
238 */
239 if (mkdir(to.p_path, from_stat.st_mode|S_IRWXU) < 0) {
240 error(to.p_path);
4d2ae24a
KB
241 return;
242 }
4d2ae24a 243 }
a783899b 244 else if (type(to_stat) != S_IFDIR) {
d57b5e76
KB
245 (void)fprintf(stderr, "%s: %s: not a directory.\n",
246 pname, to.p_path);
4d2ae24a
KB
247 return;
248 }
249 copy_dir();
24e5a125 250 /*
9ac69c84
KB
251 * If not -p and directory didn't exist, set it to be the
252 * same as the from directory, umodified by the umask;
253 * arguably wrong, but it's been that way forever.
24e5a125 254 */
9ac69c84
KB
255 if (preserve_flag)
256 setfile(&from_stat, 0);
257 else if (dne)
24e5a125 258 (void)chmod(to.p_path, from_stat.st_mode);
a783899b
KB
259 break;
260 case S_IFCHR:
261 case S_IFBLK:
24e5a125
KB
262 /*
263 * if recursive flag on, try and create the special device
264 * otherwise copy the contents.
265 */
a783899b 266 if (recursive_flag) {
3c66b1ab 267 copy_special(&from_stat, !dne);
a783899b 268 if (preserve_flag)
24e5a125 269 setfile(&from_stat, 0);
a783899b
KB
270 return;
271 }
272 /* FALLTHROUGH */
273 default:
1a4aed3b 274 copy_file(&from_stat, dne);
5f89032b 275 }
ae5e5236
KB
276}
277
1a4aed3b 278copy_file(fs, dne)
24e5a125 279 struct stat *fs;
1a4aed3b 280 int dne;
ae5e5236 281{
a783899b 282 register int from_fd, to_fd, rcount, wcount;
1a4aed3b 283 struct stat to_stat;
4d2ae24a 284
24e5a125
KB
285 if ((from_fd = open(from.p_path, O_RDONLY, 0)) == -1) {
286 error(from.p_path);
287 return;
4d2ae24a
KB
288 }
289
290 /*
1a4aed3b
KB
291 * If the file exists and we're interactive, verify with the user.
292 * If the file DNE, set the mode to be the from file, minus setuid
293 * bits, modified by the umask; arguably wrong, but it makes copying
294 * executables work right and it's been that way forever. (The
295 * other choice is 666 or'ed with the execute bits on the from file
296 * modified by the umask.)
4d2ae24a 297 */
1a4aed3b
KB
298 if (!dne) {
299 if (interactive_flag) {
300 int checkch, ch;
301
302 (void)fprintf(stderr, "overwrite %s? ", to.p_path);
303 checkch = ch = getchar();
304 while (ch != '\n' && ch != EOF)
305 ch = getchar();
306 if (checkch != 'y') {
307 (void)close(from_fd);
308 return;
309 }
310 }
311 to_fd = open(to.p_path, O_WRONLY|O_TRUNC, 0);
312 } else
313 to_fd = open(to.p_path, O_WRONLY|O_CREAT|O_TRUNC,
314 fs->st_mode & ~(S_ISUID|S_ISGID));
ae5e5236 315
4d2ae24a 316 if (to_fd == -1) {
24e5a125 317 error(to.p_path);
4d2ae24a 318 (void)close(from_fd);
24e5a125 319 return;
ae5e5236
KB
320 }
321
4d2ae24a
KB
322 while ((rcount = read(from_fd, buf, MAXBSIZE)) > 0) {
323 wcount = write(to_fd, buf, rcount);
324 if (rcount != wcount || wcount == -1) {
24e5a125 325 error(to.p_path);
4d2ae24a
KB
326 break;
327 }
ae5e5236 328 }
24e5a125
KB
329 if (rcount < 0)
330 error(from.p_path);
331 if (preserve_flag)
332 setfile(fs, to_fd);
1a4aed3b
KB
333 /*
334 * If the source was setuid, set the bits on the copy if the copy
335 * was created and is owned by the same uid. If the source was
336 * setgid, set the bits on the copy if the copy was created and is
337 * owned by the same gid and the user is a member of that group.
c942f008
KB
338 * If both setuid and setgid, lose both bits unless all the above
339 * conditions are met.
1a4aed3b
KB
340 */
341 else if (fs->st_mode & (S_ISUID|S_ISGID)) {
c942f008
KB
342 if (fs->st_mode & S_ISUID && myuid != fs->st_uid)
343 fs->st_mode &= ~(S_ISUID|S_ISGID);
1a4aed3b
KB
344 if (fs->st_mode & S_ISGID) {
345 if (fstat(to_fd, &to_stat)) {
346 error(to.p_path);
c942f008 347 fs->st_mode &= ~(S_ISUID|S_ISGID);
1a4aed3b
KB
348 }
349 else if (fs->st_gid != to_stat.st_gid ||
350 !ismember(fs->st_gid))
c942f008 351 fs->st_mode &= ~(S_ISUID|S_ISGID);
1a4aed3b
KB
352 }
353 if (fs->st_mode & (S_ISUID|S_ISGID) && fchmod(to_fd,
354 fs->st_mode & (S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO) &
355 ~myumask))
356 error(to.p_path);
357 }
4d2ae24a
KB
358 (void)close(from_fd);
359 (void)close(to_fd);
4d2ae24a 360}
ae5e5236 361
4d2ae24a
KB
362copy_dir()
363{
364 struct stat from_stat;
4d2ae24a 365 struct direct *dp, **dir_list;
24e5a125
KB
366 register int dir_cnt, i;
367 char *old_from, *old_to;
4d2ae24a
KB
368
369 dir_cnt = scandir(from.p_path, &dir_list, NULL, NULL);
370 if (dir_cnt == -1) {
d57b5e76
KB
371 (void)fprintf(stderr, "%s: can't read directory %s.\n",
372 pname, from.p_path);
4d2ae24a 373 exit_val = 1;
ae5e5236
KB
374 }
375
24e5a125
KB
376 /*
377 * Instead of handling directory entries in the order they appear
378 * on disk, do non-directory files before directory files.
379 * There are two reasons to do directories last. The first is
380 * efficiency. Files tend to be in the same cylinder group as
381 * their parent, whereas directories tend not to be. Copying files
382 * all at once reduces seeking. Second, deeply nested tree's
383 * could use up all the file descriptors if we didn't close one
384 * directory before recursivly starting on the next.
385 */
386 /* copy files */
4d2ae24a
KB
387 for (i = 0; i < dir_cnt; ++i) {
388 dp = dir_list[i];
389 if (dp->d_namlen <= 2 && dp->d_name[0] == '.'
24e5a125
KB
390 && (dp->d_name[1] == NULL || dp->d_name[1] == '.'))
391 goto done;
4d2ae24a 392 old_from = path_append(&from, dp->d_name, (int)dp->d_namlen);
24e5a125
KB
393 if (!old_from)
394 goto done;
4d2ae24a 395
a68fe064 396 if (statfcn(from.p_path, &from_stat) < 0) {
24e5a125
KB
397 error(dp->d_name);
398 path_restore(&from, old_from);
399 goto done;
400 }
401 if (type(from_stat) == S_IFDIR) {
4d2ae24a
KB
402 path_restore(&from, old_from);
403 continue;
404 }
24e5a125
KB
405 old_to = path_append(&to, dp->d_name, (int)dp->d_namlen);
406 if (old_to) {
4d2ae24a
KB
407 copy();
408 path_restore(&to, old_to);
4d2ae24a
KB
409 }
410 path_restore(&from, old_from);
24e5a125
KB
411done: dir_list[i] = NULL;
412 (void)free((char *)dp);
5f89032b 413 }
ae5e5236 414
24e5a125 415 /* copy directories */
4d2ae24a
KB
416 for (i = 0; i < dir_cnt; ++i) {
417 dp = dir_list[i];
418 if (!dp)
419 continue;
420 old_from = path_append(&from, dp->d_name, (int) dp->d_namlen);
421 if (!old_from) {
422 (void)free((char *)dp);
423 continue;
424 }
425 old_to = path_append(&to, dp->d_name, (int) dp->d_namlen);
426 if (!old_to) {
427 (void)free((char *)dp);
428 path_restore(&from, old_from);
429 continue;
430 }
431 copy();
432 free((char *)dp);
433 path_restore(&from, old_from);
434 path_restore(&to, old_to);
5f89032b 435 }
4d2ae24a 436 free((char *)dir_list);
5f89032b 437}
1a4b831f 438
0493752e
KB
439copy_link(exists)
440 int exists;
441{
14dc7750 442 int len;
0493752e
KB
443 char link[MAXPATHLEN];
444
14dc7750 445 if ((len = readlink(from.p_path, link, sizeof(link))) == -1) {
24e5a125 446 error(from.p_path);
0493752e
KB
447 return;
448 }
14dc7750 449 link[len] = '\0';
0493752e 450 if (exists && unlink(to.p_path)) {
24e5a125 451 error(to.p_path);
0493752e
KB
452 return;
453 }
454 if (symlink(link, to.p_path)) {
24e5a125 455 error(link);
0493752e
KB
456 return;
457 }
458}
459
3c66b1ab
KB
460copy_special(from_stat, exists)
461 struct stat *from_stat;
462 int exists;
a783899b 463{
3c66b1ab 464 if (exists && unlink(to.p_path)) {
24e5a125 465 error(to.p_path);
a783899b
KB
466 return;
467 }
468 if (mknod(to.p_path, from_stat->st_mode, from_stat->st_rdev)) {
24e5a125 469 error(to.p_path);
a783899b
KB
470 return;
471 }
472}
473
24e5a125
KB
474setfile(fs, fd)
475 register struct stat *fs;
476 int fd;
a783899b
KB
477{
478 static struct timeval tv[2];
479
a783899b
KB
480 tv[0].tv_sec = fs->st_atime;
481 tv[1].tv_sec = fs->st_mtime;
482 if (utimes(to.p_path, tv))
24e5a125
KB
483 error(to.p_path);
484 /*
c942f008
KB
485 * Changing the ownership probably won't succeed, unless we're root
486 * or POSIX_CHOWN_RESTRICTED is not set. Set uid before setting the
487 * mode; current BSD behavior is to remove all setuid bits on chown.
488 * If setuid, lose the bits if chown fails.
489 * If setgid, lose the bits if chgrp fails.
490 * If both, lose the bits if either fails.
24e5a125 491 */
c942f008
KB
492 if ((fd ?
493 fchown(fd, fs->st_uid, -1) : chown(to.p_path, fs->st_uid, -1))) {
494 if (errno != EPERM)
495 error(to.p_path);
496 if (fs->st_mode & S_ISUID)
497 fs->st_mode &= ~(S_ISUID|S_ISGID);
498 }
499 if ((fd ?
500 fchown(fd, -1, fs->st_gid) : chown(to.p_path, -1, fs->st_gid))) {
501 if (errno != EPERM)
502 error(to.p_path);
503 if (fs->st_mode & S_ISGID)
504 fs->st_mode &= ~(S_ISUID|S_ISGID);
1a4aed3b 505 }
1a4aed3b
KB
506 fs->st_mode &= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
507 if (fd ? fchmod(fd, fs->st_mode) : chmod(to.p_path, fs->st_mode))
508 error(to.p_path);
509}
510
511ismember(gid)
512 gid_t gid;
513{
514 extern int errno;
515 register int cnt;
516 static int ngroups, groups[NGROUPS];
517
518 if (!ngroups) {
519 ngroups = getgroups(NGROUPS, groups);
520 if (ngroups == -1) {
521 ngroups = 0;
522 exit_val = 1;
d57b5e76
KB
523 (void)fprintf(stderr, "%s: %s\n",
524 pname, strerror(errno));
1a4aed3b
KB
525 return(0);
526 }
24e5a125 527 }
1a4aed3b
KB
528 for (cnt = ngroups; cnt--;)
529 if (gid == groups[cnt])
530 return(1);
531 return(0);
a783899b
KB
532}
533
24e5a125
KB
534error(s)
535 char *s;
0908a03a 536{
4d2ae24a 537 extern int errno;
ae5e5236 538
4d2ae24a 539 exit_val = 1;
d57b5e76 540 (void)fprintf(stderr, "%s: %s: %s\n", pname, s, strerror(errno));
4d2ae24a 541}
ae5e5236
KB
542
543/********************************************************************
544 * Path Manipulation Routines.
545 ********************************************************************/
546
547/*
9f329525 548 * These functions manipulate paths in PATH_T structures.
ae5e5236
KB
549 *
550 * They eliminate multiple slashes in paths when they notice them, and keep
551 * the path non-slash terminated.
552 *
4d2ae24a 553 * Both path_set() and path_append() return 0 if the requested name
ae5e5236
KB
554 * would be too long.
555 */
556
4d2ae24a
KB
557#define STRIP_TRAILING_SLASH(p) { \
558 while ((p)->p_end > (p)->p_path && (p)->p_end[-1] == '/') \
559 *--(p)->p_end = 0; \
560 }
ae5e5236
KB
561
562/*
563 * Move specified string into path. Convert "" to "." to handle BSD
564 * semantics for a null path. Strip trailing slashes.
565 */
566path_set(p, string)
9f329525 567 register PATH_T *p;
4d2ae24a 568 char *string;
ae5e5236 569{
4d2ae24a 570 if (strlen(string) > MAXPATHLEN) {
d57b5e76
KB
571 (void)fprintf(stderr, "%s: %s: name too long.\n",
572 pname, string);
4d2ae24a
KB
573 exit_val = 1;
574 return(0);
575 }
ae5e5236 576
4d2ae24a
KB
577 (void)strcpy(p->p_path, string);
578 p->p_end = p->p_path + strlen(p->p_path);
ae5e5236 579
4d2ae24a
KB
580 if (p->p_path == p->p_end) {
581 *p->p_end++ = '.';
582 *p->p_end = 0;
583 }
ae5e5236 584
4d2ae24a
KB
585 STRIP_TRAILING_SLASH(p);
586 return(1);
ae5e5236
KB
587}
588
589/*
590 * Append specified string to path, inserting '/' if necessary. Return a
591 * pointer to the old end of path for restoration.
592 */
4d2ae24a 593char *
ae5e5236 594path_append(p, name, len)
9f329525 595 register PATH_T *p;
4d2ae24a
KB
596 char *name;
597 int len;
1a4b831f 598{
4d2ae24a 599 char *old;
ae5e5236 600
4d2ae24a
KB
601 old = p->p_end;
602 if (len == -1)
603 len = strlen(name);
ae5e5236 604
4d2ae24a
KB
605 /*
606 * The final "+ 1" accounts for the '/' between old path and name.
607 */
608 if ((len + p->p_end - p->p_path + 1) > MAXPATHLEN) {
24e5a125 609 (void)fprintf(stderr,
d57b5e76 610 "%s: %s/%s: name too long.\n", pname, p->p_path, name);
4d2ae24a
KB
611 exit_val = 1;
612 return(0);
613 }
ae5e5236 614
4d2ae24a
KB
615 /*
616 * This code should always be executed, since paths shouldn't
617 * end in '/'.
618 */
619 if (p->p_end[-1] != '/') {
620 *p->p_end++ = '/';
621 *p->p_end = 0;
622 }
ae5e5236 623
4d2ae24a
KB
624 (void)strncat(p->p_end, name, len);
625 p->p_end += len;
626 *p->p_end = 0;
ae5e5236 627
4d2ae24a
KB
628 STRIP_TRAILING_SLASH(p);
629 return(old);
ae5e5236
KB
630}
631
ae5e5236
KB
632/*
633 * Restore path to previous value. (As returned by path_append.)
634 */
ae5e5236 635path_restore(p, old)
9f329525 636 PATH_T *p;
4d2ae24a 637 char *old;
ae5e5236 638{
4d2ae24a
KB
639 p->p_end = old;
640 *p->p_end = 0;
ae5e5236
KB
641}
642
ae5e5236
KB
643/*
644 * Return basename of path. (Like basename(1).)
645 */
4d2ae24a 646char *
ae5e5236 647path_basename(p)
9f329525 648 PATH_T *p;
ae5e5236 649{
4d2ae24a 650 char *basename;
ae5e5236 651
4d2ae24a 652 basename = rindex(p->p_path, '/');
24e5a125 653 return(basename ? ++basename : p->p_path);
4d2ae24a 654}
1a4b831f 655
4d2ae24a
KB
656usage()
657{
658 (void)fprintf(stderr,
9f329525 659"usage: cp [-fhipr] src target;\n or: cp [-fhipr] src1 ... srcN directory\n");
4d2ae24a 660 exit(1);
1a4b831f 661}