1 /*
2 * CDDL HEADER START
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
52 static struct stat rootdir;
53
54 struct dlist {
55 int fd; /* Stores directory fd */
56 int flags; /* DIR_* Flags */
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
main(int argc,char ** argv)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);
157 }
158
159 for (; *argv != NULL; argv++) {
160 char *p = strrchr(*argv, '/');
161 if (p == NULL)
162 p = *argv;
163 else
164 p = p + 1;
165 if (strcmp(p, ".") == 0 || strcmp(p, "..") == 0) {
166 (void) fprintf(stderr,
167 gettext("rm of %s is not allowed\n"), *argv);
168 errcnt++;
169 continue;
170 }
171 /* Retry when we can't walk back up. */
172 while (rm(*argv, rec = cur = &top) != 0)
173 ;
174 }
175
176 return (errcnt != 0 ? 2 : 0);
177 }
178
179 static void
pushfilename(const char * fname)180 pushfilename(const char *fname)
181 {
182 char *p;
183 const char *q = fname;
184
185 if (cur == &top) {
186 p = pathbuf;
187 } else {
188 p = pathbuf + cur->up->pathend;
189 *p++ = '/';
190 }
191 while (*q != '\0') {
192 if (p - pathbuf + 2 >= pathbuflen) {
193 char *np;
194 pathbuflen += MAXPATHLEN;
195 np = realloc(pathbuf, pathbuflen);
196 if (np == NULL)
197 memerror();
198 p = np + (p - pathbuf);
199 pathbuf = np;
200 }
201 *p++ = *q++;
202 }
203 *p = '\0';
204 cur->pathend = p - pathbuf;
205 }
206
207 static void
closeframe(struct dlist * frm)208 closeframe(struct dlist *frm)
209 {
210 if (frm->dp != NULL) {
211 (void) closedir(frm->dp);
212 nfds--;
213 frm->dp = NULL;
214 frm->fd = -1;
215 }
216 }
217
218 static int
reclaim(void)219 reclaim(void)
220 {
221 while (rec != NULL && (rec->flags & DIR_CANTCLOSE) != 0)
222 rec = rec->down;
223 if (rec == NULL || rec == cur || rec->dp == NULL)
224 return (-1);
225 rec->diroff = telldir(rec->dp);
226 closeframe(rec);
227 rec = rec->down;
228 return (0);
229 }
230
231 static void
pushdir(struct dlist * frm)232 pushdir(struct dlist *frm)
233 {
234 frm->up = cur;
235 frm->down = NULL;
236 cur->down = frm;
237 cur = frm;
238 }
239
240 static int
opendirat(int dirfd,const char * entry,struct dlist * frm)241 opendirat(int dirfd, const char *entry, struct dlist *frm)
242 {
243 int fd;
244
245 if (nfds >= maxfds)
246 (void) reclaim();
247
248 while ((fd = openat(dirfd, entry, O_RDONLY|O_NONBLOCK)) == -1 &&
249 errno == EMFILE) {
250 if (nfds < maxfds)
251 maxfds = nfds;
252 if (reclaim() != 0)
253 return (-1);
254 }
255 if (fd < 0)
256 return (-1);
257 frm->fd = fd;
258 frm->dp = fdopendir(fd);
259 if (frm->dp == NULL) {
260 (void) close(fd);
261 return (-1);
262 }
263 nfds++;
264 return (0);
265 }
266
267 /*
268 * Since we never pop the top frame, cur->up can never be NULL.
269 * If we pop beyond a frame we closed, we try to reopen "..".
270 */
271 static int
popdir(boolean_t noerror)272 popdir(boolean_t noerror)
273 {
274 struct stat buf;
275 int ret = noerror ? 0 : -1;
276 pathbuf[cur->up->pathend] = '\0';
277
278 if (noerror && cur->up->fd == -1) {
279 rec = cur->up;
280 if (opendirat(cur->fd, "..", rec) != 0 ||
281 fstat(rec->fd, &buf) != 0) {
282 (void) fprintf(stderr,
283 gettext("rm: cannot reopen %s: %s\n"),
284 pathbuf, strerror(errno));
285 exit(2);
286 }
287 if (rec->ino != buf.st_ino || rec->dev != buf.st_dev) {
288 (void) fprintf(stderr, gettext("rm: WARNING: "
289 "The directory %s was moved or linked to "
290 "another directory during the execution of rm\n"),
291 pathbuf);
292 closeframe(rec);
293 ret = -1;
294 } else {
295 /* If telldir failed, we take it from the top. */
296 if (rec->diroff != -1)
297 seekdir(rec->dp, rec->diroff);
298 }
299 } else if (rec == cur)
300 rec = cur->up;
301 closeframe(cur);
302 cur = cur->up;
303 cur->down = NULL;
304 return (ret);
305 }
306
307 /*
308 * The stack frame of this function is minimized so that we can
309 * recurse quite a bit before we overflow the stack; around
310 * 30,000-40,000 nested directories can be removed with the default
311 * stack limit.
312 */
313 static int
rm(const char * entry,struct dlist * caller)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++;
356 return (0);
357 }
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,
450 gettext("rm: %s: directory renamed\n"), pathbuf);
451 closeframe(&frame);
452 errcnt++;
453 return (0);
454 }
455
456 if (caller != &top) {
457 if (checkdir(caller, &frame) != 0) {
458 closeframe(&frame);
459 goto unlinkit;
460 }
461 }
462 pushdir(&frame);
463
464 /*
465 * rm() only returns -1 if popdir failed at some point;
466 * frame.dp is no longer reliable and we must drop out.
467 */
468 while ((dent = readdir(frame.dp)) != NULL) {
469 if (strcmp(dent->d_name, ".") == 0 ||
470 strcmp(dent->d_name, "..") == 0)
471 continue;
472
473 if (rm(dent->d_name, &frame) != 0)
474 break;
475 }
476
477 if (popdir(dent == NULL) != 0)
478 return (-1);
479
480 /*
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
confirm(FILE * fp,const char * q,...)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
memerror(void)573 memerror(void)
574 {
575 (void) fprintf(stderr, gettext("rm: Insufficient memory.\n"));
576 exit(1);
577 }
578
579 /*
580 * If we can't stat "..", it's either not there or we can't search
581 * the current directory; in that case we can't return back through
582 * "..", so we need to keep the parent open.
583 * Check that we came from "..", if not then this directory entry is an
584 * additional link and there is risk of a filesystem cycle and we also
585 * can't go back up through ".." and we keep the directory open.
586 */
587 static int
checkdir(struct dlist * caller,struct dlist * frmp)588 checkdir(struct dlist *caller, struct dlist *frmp)
589 {
590 struct stat up;
591 struct dlist *ptr;
592
593 if (fstatat(frmp->fd, "..", &up, 0) != 0) {
594 caller->flags |= DIR_CANTCLOSE;
595 return (0);
596 } else if (up.st_ino == caller->ino && up.st_dev == caller->dev) {
597 return (0);
598 }
599
600 /* Directory hard link, check cycle */
601 for (ptr = caller; ptr != NULL; ptr = ptr->up) {
602 if (frmp->dev == ptr->dev && frmp->ino == ptr->ino) {
603 (void) fprintf(stderr,
604 gettext("rm: cycle detected for %s\n"), pathbuf);
605 errcnt++;
606 return (-1);
607 }
608 }
609 caller->flags |= DIR_CANTCLOSE;
610 return (0);
611 }
612