3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22 /*
23 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
25 */
26
27 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
28 /* All Rights Reserved */
29
30 /*
31 * rm [-fiRr] file ...
32 */
33
34 #include <sys/param.h>
35 #include <sys/stat.h>
36 #include <dirent.h>
37 #include <errno.h>
38 #include <fcntl.h>
39 #include <langinfo.h>
40 #include <limits.h>
41 #include <locale.h>
42 #include <stdarg.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <unistd.h>
47 #include <values.h>
48 #include "getresponse.h"
49
50 #define DIR_CANTCLOSE 1
51
57 DIR *dp; /* Open directory (opened with fd) */
58 long diroff; /* Saved directory offset when closing */
59 struct dlist *up; /* Up one step in the tree (toward "/") */
60 struct dlist *down; /* Down one step in the tree */
61 ino_t ino; /* st_ino of directory */
62 dev_t dev; /* st_dev of directory */
63 int pathend; /* Offset of name end in the pathbuffer */
64 };
65
66 static struct dlist top = {
67 (int)AT_FDCWD,
68 DIR_CANTCLOSE,
69 };
70
71 static struct dlist *cur, *rec;
72
73 static int rm(const char *, struct dlist *);
74 static int confirm(FILE *, const char *, ...);
75 static void memerror(void);
76 static int checkdir(struct dlist *, struct dlist *);
77 static int errcnt;
78 static boolean_t silent, interactive, recursive, ontty;
79
80 static char *pathbuf;
81 static size_t pathbuflen = MAXPATHLEN;
82
83 static int maxfds = MAXINT;
84 static int nfds;
85
86 int
87 main(int argc, char **argv)
88 {
89 int errflg = 0;
90 int c;
91
92 (void) setlocale(LC_ALL, "");
93 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
94 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */
95 #endif
96 (void) textdomain(TEXT_DOMAIN);
97
98 while ((c = getopt(argc, argv, "frRi")) != EOF)
99 switch (c) {
100 case 'f':
101 silent = B_TRUE;
102 #ifdef XPG4
103 interactive = B_FALSE;
104 #endif
105 break;
106 case 'i':
107 interactive = B_TRUE;
108 #ifdef XPG4
109 silent = B_FALSE;
110 #endif
111 break;
112 case 'r':
113 case 'R':
114 recursive = B_TRUE;
115 break;
116 case '?':
117 errflg = 1;
118 break;
119 }
120
121 /*
122 * For BSD compatibility allow '-' to delimit the end
123 * of options. However, if options were already explicitly
124 * terminated with '--', then treat '-' literally: otherwise,
125 * "rm -- -" won't remove '-'.
126 */
127 if (optind < argc &&
128 strcmp(argv[optind], "-") == 0 &&
129 strcmp(argv[optind - 1], "--") != 0)
130 optind++;
131
132 argc -= optind;
133 argv = &argv[optind];
134
135 if ((argc < 1 && !silent) || errflg) {
136 (void) fprintf(stderr, gettext("usage: rm [-fiRr] file ...\n"));
137 exit(2);
138 }
139
140 ontty = isatty(STDIN_FILENO) != 0;
141
142 if (recursive && stat("/", &rootdir) != 0) {
143 (void) fprintf(stderr,
144 gettext("rm: cannot stat root directory: %s\n"),
145 strerror(errno));
146 exit(2);
147 }
148
149 pathbuf = malloc(pathbuflen);
150 if (pathbuf == NULL)
151 memerror();
152
153 if (init_yes() < 0) {
154 (void) fprintf(stderr, gettext(ERR_MSG_INIT_YES),
155 strerror(errno));
156 exit(2);
311 * stack limit.
312 */
313 static int
314 rm(const char *entry, struct dlist *caller)
315 {
316 struct dlist frame;
317 int flag;
318 struct stat temp;
319 struct dirent *dent;
320 int err;
321
322 /*
323 * Construct the pathname: note that the entry may live in memory
324 * allocated by readdir and that after return from recursion
325 * the memory is no longer valid. So after the recursive rm()
326 * call, we use the global pathbuf instead of the entry argument.
327 */
328 pushfilename(entry);
329
330 if (fstatat(caller->fd, entry, &temp, AT_SYMLINK_NOFOLLOW) != 0) {
331 if (!silent) {
332 (void) fprintf(stderr, "rm: %s: %s\n", pathbuf,
333 strerror(errno));
334 errcnt++;
335 }
336 return (0);
337 }
338
339 if (S_ISDIR(temp.st_mode)) {
340 /*
341 * If "-r" wasn't specified, trying to remove directories
342 * is an error.
343 */
344 if (!recursive) {
345 (void) fprintf(stderr,
346 gettext("rm: %s is a directory\n"), pathbuf);
347 errcnt++;
348 return (0);
349 }
350
351 if (temp.st_ino == rootdir.st_ino &&
352 temp.st_dev == rootdir.st_dev) {
353 (void) fprintf(stderr,
354 gettext("rm of %s is not allowed\n"), "/");
355 errcnt++;
358 /*
359 * TRANSLATION_NOTE - The following message will contain the
360 * first character of the strings for "yes" and "no" defined
361 * in the file "nl_langinfo.po". After substitution, the
362 * message will appear as follows:
363 * rm: examine files in directory <directoryname> (y/n)?
364 * where <directoryname> is the directory to be removed
365 *
366 */
367 if (interactive && !confirm(stderr,
368 gettext("rm: examine files in directory %s (%s/%s)? "),
369 pathbuf, yesstr, nostr)) {
370 return (0);
371 }
372
373 frame.dev = temp.st_dev;
374 frame.ino = temp.st_ino;
375 frame.flags = 0;
376 flag = AT_REMOVEDIR;
377
378 #ifdef XPG4
379 /*
380 * XCU4 and POSIX.2: If not interactive, check to see whether
381 * or not directory is readable or writable and if not,
382 * prompt user for response.
383 */
384 if (ontty && !interactive && !silent &&
385 faccessat(caller->fd, entry, W_OK|X_OK, AT_EACCESS) != 0 &&
386 !confirm(stderr,
387 gettext("rm: examine files in directory %s (%s/%s)? "),
388 pathbuf, yesstr, nostr)) {
389 return (0);
390 }
391 #endif
392 if (opendirat(caller->fd, entry, &frame) == -1) {
393 err = errno;
394
395 if (interactive) {
396 /*
397 * Print an error message that
398 * we could not read the directory
399 * as the user wanted to examine
400 * files in the directory. Only
401 * affect the error status if
402 * user doesn't want to remove the
403 * directory as we still may be able
404 * remove the directory successfully.
405 */
406 (void) fprintf(stderr, gettext(
407 "rm: cannot read directory %s: %s\n"),
408 pathbuf, strerror(err));
409
410 /*
411 * TRANSLATION_NOTE - The following message will contain the
412 * first character of the strings for "yes" and "no" defined
413 * in the file "nl_langinfo.po". After substitution, the
414 * message will appear as follows:
415 * rm: remove <filename> (y/n)?
416 * For example, in German, this will appear as
417 * rm: löschen <filename> (j/n)?
418 * where j=ja, n=nein, <filename>=the file to be removed
419 */
420 if (!confirm(stderr,
421 gettext("rm: remove %s (%s/%s)? "),
422 pathbuf, yesstr, nostr)) {
423 errcnt++;
424 return (0);
425 }
426 }
427 /* If it's empty we may still be able to rm it */
428 if (unlinkat(caller->fd, entry, flag) == 0)
429 return (0);
430 if (interactive)
431 err = errno;
432 (void) fprintf(stderr,
433 interactive ?
434 gettext("rm: Unable to remove directory %s: %s\n") :
435 gettext("rm: cannot read directory %s: %s\n"),
436 pathbuf, strerror(err));
437 errcnt++;
438 return (0);
439 }
440
441 /*
442 * There is a race condition here too; if we open a directory
443 * we have to make sure it's still the same directory we
444 * stat'ed and checked against root earlier. Let's check.
445 */
446 if (fstat(frame.fd, &temp) != 0 ||
447 frame.ino != temp.st_ino ||
448 frame.dev != temp.st_dev) {
449 (void) fprintf(stderr,
481 * We recursed and the subdirectory may have set the CANTCLOSE
482 * flag; we need to clear it except for &top.
483 * Recursion may have invalidated entry because of closedir().
484 */
485 if (caller != &top) {
486 caller->flags &= ~DIR_CANTCLOSE;
487 entry = &pathbuf[caller->up->pathend + 1];
488 }
489 } else {
490 flag = 0;
491 }
492 unlinkit:
493 /*
494 * If interactive, ask for acknowledgement.
495 */
496 if (interactive) {
497 if (!confirm(stderr, gettext("rm: remove %s (%s/%s)? "),
498 pathbuf, yesstr, nostr)) {
499 return (0);
500 }
501 } else if (!silent && flag == 0) {
502 /*
503 * If not silent, and stdin is a terminal, and there's
504 * no write access, and the file isn't a symbolic link,
505 * ask for permission. If flag is set, then we know it's
506 * a directory so we skip this test as it was done above.
507 *
508 * TRANSLATION_NOTE - The following message will contain the
509 * first character of the strings for "yes" and "no" defined
510 * in the file "nl_langinfo.po". After substitution, the
511 * message will appear as follows:
512 * rm: <filename>: override protection XXX (y/n)?
513 * where XXX is the permission mode bits of the file in octal
514 * and <filename> is the file to be removed
515 *
516 */
517 if (ontty && !S_ISLNK(temp.st_mode) &&
518 faccessat(caller->fd, entry, W_OK, AT_EACCESS) != 0 &&
519 !confirm(stdout,
520 gettext("rm: %s: override protection %o (%s/%s)? "),
521 pathbuf, temp.st_mode & 0777, yesstr, nostr)) {
522 return (0);
523 }
524 }
525
526 if (unlinkat(caller->fd, entry, flag) != 0) {
527 err = errno;
528 if (err == ENOENT)
529 return (0);
530
531 if (flag != 0) {
532 if (err == EINVAL) {
533 (void) fprintf(stderr, gettext(
534 "rm: Cannot remove any directory in the "
535 "path of the current working directory\n"
536 "%s\n"), pathbuf);
537 } else {
538 if (err == EEXIST)
539 err = ENOTEMPTY;
540 (void) fprintf(stderr,
541 gettext("rm: Unable to remove directory %s:"
542 " %s\n"), pathbuf, strerror(err));
543 }
544 } else {
545 #ifndef XPG4
546 if (!silent || interactive) {
547 #endif
548
549 (void) fprintf(stderr,
550 gettext("rm: %s not removed: %s\n"),
551 pathbuf, strerror(err));
552 #ifndef XPG4
553 }
554 #endif
555 }
556 errcnt++;
557 }
558 return (0);
559 }
560
561 static int
562 confirm(FILE *fp, const char *q, ...)
563 {
564 va_list ap;
565
566 va_start(ap, q);
567 (void) vfprintf(fp, q, ap);
568 va_end(ap);
569 return (yes());
570 }
571
572 static void
573 memerror(void)
|
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22 /*
23 * Copyright 2014 Andrew Stormont.
24 */
25
26 /*
27 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
28 * Use is subject to license terms.
29 */
30
31 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
32 /* All Rights Reserved */
33
34 /*
35 * rm [-fiRrv] file ...
36 */
37
38 #include <sys/param.h>
39 #include <sys/stat.h>
40 #include <dirent.h>
41 #include <errno.h>
42 #include <fcntl.h>
43 #include <langinfo.h>
44 #include <limits.h>
45 #include <locale.h>
46 #include <stdarg.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <unistd.h>
51 #include <values.h>
52 #include "getresponse.h"
53
54 #define DIR_CANTCLOSE 1
55
61 DIR *dp; /* Open directory (opened with fd) */
62 long diroff; /* Saved directory offset when closing */
63 struct dlist *up; /* Up one step in the tree (toward "/") */
64 struct dlist *down; /* Down one step in the tree */
65 ino_t ino; /* st_ino of directory */
66 dev_t dev; /* st_dev of directory */
67 int pathend; /* Offset of name end in the pathbuffer */
68 };
69
70 static struct dlist top = {
71 (int)AT_FDCWD,
72 DIR_CANTCLOSE,
73 };
74
75 static struct dlist *cur, *rec;
76
77 static int rm(const char *, struct dlist *);
78 static int confirm(FILE *, const char *, ...);
79 static void memerror(void);
80 static int checkdir(struct dlist *, struct dlist *);
81 static int errcnt = 0;
82
83 static boolean_t force = B_FALSE;
84 static boolean_t interactive = B_FALSE;
85 static boolean_t recursive = B_FALSE;
86 static boolean_t ontty = B_FALSE;
87 static boolean_t verbose = B_FALSE;
88
89 static char *pathbuf;
90 static size_t pathbuflen = MAXPATHLEN;
91
92 static int maxfds = MAXINT;
93 static int nfds;
94
95 int
96 main(int argc, char **argv)
97 {
98 int errflg = 0;
99 int c;
100
101 (void) setlocale(LC_ALL, "");
102 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
103 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */
104 #endif
105 (void) textdomain(TEXT_DOMAIN);
106
107 while ((c = getopt(argc, argv, "frRiv")) != EOF)
108 switch (c) {
109 case 'f':
110 force = B_TRUE;
111 interactive = B_FALSE;
112 break;
113 case 'i':
114 interactive = B_TRUE;
115 force = B_FALSE;
116 break;
117 case 'r':
118 case 'R':
119 recursive = B_TRUE;
120 break;
121 case 'v':
122 verbose = B_TRUE;
123 break;
124 case '?':
125 errflg = 1;
126 break;
127 }
128
129 /*
130 * For BSD compatibility allow '-' to delimit the end
131 * of options. However, if options were already explicitly
132 * terminated with '--', then treat '-' literally: otherwise,
133 * "rm -- -" won't remove '-'.
134 */
135 if (optind < argc &&
136 strcmp(argv[optind], "-") == 0 &&
137 strcmp(argv[optind - 1], "--") != 0)
138 optind++;
139
140 argc -= optind;
141 argv = &argv[optind];
142
143 if ((argc < 1 && !force) || errflg) {
144 (void) fprintf(stderr,
145 gettext("usage: rm [-fiRrv] file ...\n"));
146 exit(2);
147 }
148
149 ontty = isatty(STDIN_FILENO) != 0;
150
151 if (recursive && stat("/", &rootdir) != 0) {
152 (void) fprintf(stderr,
153 gettext("rm: cannot stat root directory: %s\n"),
154 strerror(errno));
155 exit(2);
156 }
157
158 pathbuf = malloc(pathbuflen);
159 if (pathbuf == NULL)
160 memerror();
161
162 if (init_yes() < 0) {
163 (void) fprintf(stderr, gettext(ERR_MSG_INIT_YES),
164 strerror(errno));
165 exit(2);
320 * stack limit.
321 */
322 static int
323 rm(const char *entry, struct dlist *caller)
324 {
325 struct dlist frame;
326 int flag;
327 struct stat temp;
328 struct dirent *dent;
329 int err;
330
331 /*
332 * Construct the pathname: note that the entry may live in memory
333 * allocated by readdir and that after return from recursion
334 * the memory is no longer valid. So after the recursive rm()
335 * call, we use the global pathbuf instead of the entry argument.
336 */
337 pushfilename(entry);
338
339 if (fstatat(caller->fd, entry, &temp, AT_SYMLINK_NOFOLLOW) != 0) {
340 (void) fprintf(stderr, "rm: %s: %s\n", pathbuf,
341 strerror(errno));
342 errcnt++;
343 return (0);
344 }
345
346 if (S_ISDIR(temp.st_mode)) {
347 /*
348 * If "-r" wasn't specified, trying to remove directories
349 * is an error.
350 */
351 if (!recursive) {
352 (void) fprintf(stderr,
353 gettext("rm: %s is a directory\n"), pathbuf);
354 errcnt++;
355 return (0);
356 }
357
358 if (temp.st_ino == rootdir.st_ino &&
359 temp.st_dev == rootdir.st_dev) {
360 (void) fprintf(stderr,
361 gettext("rm of %s is not allowed\n"), "/");
362 errcnt++;
365 /*
366 * TRANSLATION_NOTE - The following message will contain the
367 * first character of the strings for "yes" and "no" defined
368 * in the file "nl_langinfo.po". After substitution, the
369 * message will appear as follows:
370 * rm: examine files in directory <directoryname> (y/n)?
371 * where <directoryname> is the directory to be removed
372 *
373 */
374 if (interactive && !confirm(stderr,
375 gettext("rm: examine files in directory %s (%s/%s)? "),
376 pathbuf, yesstr, nostr)) {
377 return (0);
378 }
379
380 frame.dev = temp.st_dev;
381 frame.ino = temp.st_ino;
382 frame.flags = 0;
383 flag = AT_REMOVEDIR;
384
385 #ifndef SUS
386 /*
387 * XCU4 and POSIX.2: If not interactive, check to see whether
388 * or not directory is readable or writable and if not,
389 * prompt user for response.
390 */
391 if (ontty && !interactive && !force &&
392 faccessat(caller->fd, entry, W_OK|X_OK, AT_EACCESS) != 0 &&
393 !confirm(stderr,
394 gettext("rm: examine files in directory %s (%s/%s)? "),
395 pathbuf, yesstr, nostr)) {
396 return (0);
397 }
398 #endif
399
400 if (opendirat(caller->fd, entry, &frame) == -1) {
401 err = errno;
402
403 if (interactive) {
404 /*
405 * Print an error message that
406 * we could not read the directory
407 * as the user wanted to examine
408 * files in the directory. Only
409 * affect the error status if
410 * user doesn't want to remove the
411 * directory as we still may be able
412 * remove the directory successfully.
413 */
414 (void) fprintf(stderr, gettext(
415 "rm: cannot read directory %s: %s\n"),
416 pathbuf, strerror(err));
417
418 /*
419 * TRANSLATION_NOTE - The following message will contain the
420 * first character of the strings for "yes" and "no" defined
421 * in the file "nl_langinfo.po". After substitution, the
422 * message will appear as follows:
423 * rm: remove <filename> (y/n)?
424 * For example, in German, this will appear as
425 * rm: löschen <filename> (j/n)?
426 * where j=ja, n=nein, <filename>=the file to be removed
427 */
428 if (!confirm(stderr,
429 gettext("rm: remove %s (%s/%s)? "),
430 pathbuf, yesstr, nostr)) {
431 errcnt++;
432 return (0);
433 }
434 }
435 /* If it's empty we may still be able to rm it */
436 if (unlinkat(caller->fd, entry, flag) == 0) {
437 if (verbose)
438 (void) printf(gettext("removed "
439 "directory: `%s'\n"), pathbuf);
440 return (0);
441 }
442 if (interactive)
443 err = errno;
444 (void) fprintf(stderr,
445 interactive ?
446 gettext("rm: Unable to remove directory %s: %s\n") :
447 gettext("rm: cannot read directory %s: %s\n"),
448 pathbuf, strerror(err));
449 errcnt++;
450 return (0);
451 }
452
453 /*
454 * There is a race condition here too; if we open a directory
455 * we have to make sure it's still the same directory we
456 * stat'ed and checked against root earlier. Let's check.
457 */
458 if (fstat(frame.fd, &temp) != 0 ||
459 frame.ino != temp.st_ino ||
460 frame.dev != temp.st_dev) {
461 (void) fprintf(stderr,
493 * We recursed and the subdirectory may have set the CANTCLOSE
494 * flag; we need to clear it except for &top.
495 * Recursion may have invalidated entry because of closedir().
496 */
497 if (caller != &top) {
498 caller->flags &= ~DIR_CANTCLOSE;
499 entry = &pathbuf[caller->up->pathend + 1];
500 }
501 } else {
502 flag = 0;
503 }
504 unlinkit:
505 /*
506 * If interactive, ask for acknowledgement.
507 */
508 if (interactive) {
509 if (!confirm(stderr, gettext("rm: remove %s (%s/%s)? "),
510 pathbuf, yesstr, nostr)) {
511 return (0);
512 }
513 } else if (!force && flag == 0) {
514 /*
515 * If not force, and stdin is a terminal, and there's
516 * no write access, and the file isn't a symbolic link,
517 * ask for permission. If flag is set, then we know it's
518 * a directory so we skip this test as it was done above.
519 *
520 * TRANSLATION_NOTE - The following message will contain the
521 * first character of the strings for "yes" and "no" defined
522 * in the file "nl_langinfo.po". After substitution, the
523 * message will appear as follows:
524 * rm: <filename>: override protection XXX (y/n)?
525 * where XXX is the permission mode bits of the file in octal
526 * and <filename> is the file to be removed
527 *
528 */
529 if (ontty && !S_ISLNK(temp.st_mode) &&
530 faccessat(caller->fd, entry, W_OK, AT_EACCESS) != 0 &&
531 !confirm(stdout,
532 gettext("rm: %s: override protection %o (%s/%s)? "),
533 pathbuf, temp.st_mode & 0777, yesstr, nostr)) {
534 return (0);
535 }
536 }
537
538 if (unlinkat(caller->fd, entry, flag) == 0) {
539 if (verbose)
540 (void) printf(S_ISDIR(temp.st_mode) ?
541 gettext("removed directory: `%s'\n") :
542 gettext("removed `%s'\n"), pathbuf);
543 return (0);
544 } else {
545 err = errno;
546 if (err == ENOENT)
547 return (0);
548
549 if (flag != 0) {
550 if (err == EINVAL) {
551 (void) fprintf(stderr, gettext(
552 "rm: Cannot remove any directory in the "
553 "path of the current working directory\n"
554 "%s\n"), pathbuf);
555 } else {
556 if (err == EEXIST)
557 err = ENOTEMPTY;
558 (void) fprintf(stderr,
559 gettext("rm: Unable to remove directory %s:"
560 " %s\n"), pathbuf, strerror(err));
561 }
562 #ifndef SUS
563 } else {
564 (void) fprintf(stderr,
565 gettext("rm: %s not removed: %s\n"),
566 pathbuf, strerror(err));
567 #endif
568 }
569 errcnt++;
570 }
571 return (0);
572 }
573
574 static int
575 confirm(FILE *fp, const char *q, ...)
576 {
577 va_list ap;
578
579 va_start(ap, q);
580 (void) vfprintf(fp, q, ap);
581 va_end(ap);
582 return (yes());
583 }
584
585 static void
586 memerror(void)
|