17c478bd9Sstevel@tonic-gate /* 27c478bd9Sstevel@tonic-gate * CDDL HEADER START 37c478bd9Sstevel@tonic-gate * 47c478bd9Sstevel@tonic-gate * The contents of this file are subject to the terms of the 579033acbSas145665 * Common Development and Distribution License (the "License"). 679033acbSas145665 * You may not use this file except in compliance with the License. 77c478bd9Sstevel@tonic-gate * 87c478bd9Sstevel@tonic-gate * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 97c478bd9Sstevel@tonic-gate * or http://www.opensolaris.org/os/licensing. 107c478bd9Sstevel@tonic-gate * See the License for the specific language governing permissions 117c478bd9Sstevel@tonic-gate * and limitations under the License. 127c478bd9Sstevel@tonic-gate * 137c478bd9Sstevel@tonic-gate * When distributing Covered Code, include this CDDL HEADER in each 147c478bd9Sstevel@tonic-gate * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 157c478bd9Sstevel@tonic-gate * If applicable, add the following below this CDDL HEADER, with the 167c478bd9Sstevel@tonic-gate * fields enclosed by brackets "[]" replaced with your own identifying 177c478bd9Sstevel@tonic-gate * information: Portions Copyright [yyyy] [name of copyright owner] 187c478bd9Sstevel@tonic-gate * 197c478bd9Sstevel@tonic-gate * CDDL HEADER END 207c478bd9Sstevel@tonic-gate */ 217c478bd9Sstevel@tonic-gate 227c478bd9Sstevel@tonic-gate /* 2348011479Ssn199410 * Copyright 2007 Sun Microsystems, Inc. All rights reserved. 247c478bd9Sstevel@tonic-gate * Use is subject to license terms. 257c478bd9Sstevel@tonic-gate */ 267c478bd9Sstevel@tonic-gate 27014a7923Sas145665 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 28014a7923Sas145665 /* All Rights Reserved */ 29014a7923Sas145665 307c478bd9Sstevel@tonic-gate #pragma ident "%Z%%M% %I% %E% SMI" 317c478bd9Sstevel@tonic-gate 327c478bd9Sstevel@tonic-gate /* 337c478bd9Sstevel@tonic-gate * rm [-fiRr] file ... 347c478bd9Sstevel@tonic-gate */ 357c478bd9Sstevel@tonic-gate 3648011479Ssn199410 #include <sys/param.h> 377c478bd9Sstevel@tonic-gate #include <sys/stat.h> 387c478bd9Sstevel@tonic-gate #include <dirent.h> 3948011479Ssn199410 #include <errno.h> 4048011479Ssn199410 #include <fcntl.h> 4148011479Ssn199410 #include <langinfo.h> 427c478bd9Sstevel@tonic-gate #include <limits.h> 437c478bd9Sstevel@tonic-gate #include <locale.h> 4448011479Ssn199410 #include <stdarg.h> 4548011479Ssn199410 #include <stdio.h> 467c478bd9Sstevel@tonic-gate #include <stdlib.h> 4748011479Ssn199410 #include <string.h> 4848011479Ssn199410 #include <unistd.h> 4948011479Ssn199410 #include <values.h> 50*3d63ea05Sas145665 #include "getresponse.h" 517c478bd9Sstevel@tonic-gate 5248011479Ssn199410 #define E_OK 010 /* make __accessat() use effective ids */ 537c478bd9Sstevel@tonic-gate 5448011479Ssn199410 #define DIR_CANTCLOSE 1 557c478bd9Sstevel@tonic-gate 5648011479Ssn199410 static struct stat rootdir; 5748011479Ssn199410 5848011479Ssn199410 struct dlist { 5948011479Ssn199410 int fd; /* Stores directory fd */ 6048011479Ssn199410 int flags; /* DIR_* Flags */ 6148011479Ssn199410 DIR *dp; /* Open directory (opened with fd) */ 6248011479Ssn199410 long diroff; /* Saved directory offset when closing */ 6348011479Ssn199410 struct dlist *up; /* Up one step in the tree (toward "/") */ 6448011479Ssn199410 struct dlist *down; /* Down one step in the tree */ 6548011479Ssn199410 ino_t ino; /* st_ino of directory */ 6648011479Ssn199410 dev_t dev; /* st_dev of directory */ 6748011479Ssn199410 int pathend; /* Offset of name end in the pathbuffer */ 6848011479Ssn199410 }; 6948011479Ssn199410 7048011479Ssn199410 static struct dlist top = { 7148011479Ssn199410 (int)AT_FDCWD, 7248011479Ssn199410 DIR_CANTCLOSE, 7348011479Ssn199410 }; 747c478bd9Sstevel@tonic-gate 7548011479Ssn199410 static struct dlist *cur, *rec; 767c478bd9Sstevel@tonic-gate 7748011479Ssn199410 static int rm(const char *, struct dlist *); 7848011479Ssn199410 static int confirm(FILE *, const char *, ...); 7948011479Ssn199410 static void memerror(void); 8048011479Ssn199410 static int checkdir(struct dlist *, struct dlist *); 8148011479Ssn199410 static int errcnt; 8248011479Ssn199410 static boolean_t silent, interactive, recursive, ontty; 837c478bd9Sstevel@tonic-gate 8448011479Ssn199410 static char *pathbuf; 85131f4edeSsn199410 static size_t pathbuflen = MAXPATHLEN; 867c478bd9Sstevel@tonic-gate 8748011479Ssn199410 static int maxfds = MAXINT; 8848011479Ssn199410 static int nfds; 897c478bd9Sstevel@tonic-gate 9048011479Ssn199410 extern int __accessat(int, const char *, int); 917c478bd9Sstevel@tonic-gate 927c478bd9Sstevel@tonic-gate int 9348011479Ssn199410 main(int argc, char **argv) 947c478bd9Sstevel@tonic-gate { 957c478bd9Sstevel@tonic-gate int errflg = 0; 967c478bd9Sstevel@tonic-gate int c; 977c478bd9Sstevel@tonic-gate 987c478bd9Sstevel@tonic-gate (void) setlocale(LC_ALL, ""); 997c478bd9Sstevel@tonic-gate #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ 1007c478bd9Sstevel@tonic-gate #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ 1017c478bd9Sstevel@tonic-gate #endif 1027c478bd9Sstevel@tonic-gate (void) textdomain(TEXT_DOMAIN); 1037c478bd9Sstevel@tonic-gate 1047c478bd9Sstevel@tonic-gate while ((c = getopt(argc, argv, "frRi")) != EOF) 1057c478bd9Sstevel@tonic-gate switch (c) { 1067c478bd9Sstevel@tonic-gate case 'f': 10748011479Ssn199410 silent = B_TRUE; 1087c478bd9Sstevel@tonic-gate #ifdef XPG4 10948011479Ssn199410 interactive = B_FALSE; 1107c478bd9Sstevel@tonic-gate #endif 1117c478bd9Sstevel@tonic-gate break; 1127c478bd9Sstevel@tonic-gate case 'i': 11348011479Ssn199410 interactive = B_TRUE; 1147c478bd9Sstevel@tonic-gate #ifdef XPG4 11548011479Ssn199410 silent = B_FALSE; 1167c478bd9Sstevel@tonic-gate #endif 1177c478bd9Sstevel@tonic-gate break; 1187c478bd9Sstevel@tonic-gate case 'r': 1197c478bd9Sstevel@tonic-gate case 'R': 12048011479Ssn199410 recursive = B_TRUE; 1217c478bd9Sstevel@tonic-gate break; 1227c478bd9Sstevel@tonic-gate case '?': 1237c478bd9Sstevel@tonic-gate errflg = 1; 1247c478bd9Sstevel@tonic-gate break; 1257c478bd9Sstevel@tonic-gate } 1267c478bd9Sstevel@tonic-gate 1277c478bd9Sstevel@tonic-gate /* 1287c478bd9Sstevel@tonic-gate * For BSD compatibility allow '-' to delimit the end 1297c478bd9Sstevel@tonic-gate * of options. However, if options were already explicitly 1307c478bd9Sstevel@tonic-gate * terminated with '--', then treat '-' literally: otherwise, 1317c478bd9Sstevel@tonic-gate * "rm -- -" won't remove '-'. 1327c478bd9Sstevel@tonic-gate */ 1337c478bd9Sstevel@tonic-gate if (optind < argc && 1347c478bd9Sstevel@tonic-gate strcmp(argv[optind], "-") == 0 && 1357c478bd9Sstevel@tonic-gate strcmp(argv[optind - 1], "--") != 0) 1367c478bd9Sstevel@tonic-gate optind++; 1377c478bd9Sstevel@tonic-gate 1387c478bd9Sstevel@tonic-gate argc -= optind; 1397c478bd9Sstevel@tonic-gate argv = &argv[optind]; 1407c478bd9Sstevel@tonic-gate 1417c478bd9Sstevel@tonic-gate if ((argc < 1 && !silent) || errflg) { 14248011479Ssn199410 (void) fprintf(stderr, gettext("usage: rm [-fiRr] file ...\n")); 14348011479Ssn199410 exit(2); 14448011479Ssn199410 } 14548011479Ssn199410 14648011479Ssn199410 ontty = isatty(STDIN_FILENO) != 0; 14748011479Ssn199410 14848011479Ssn199410 if (recursive && stat("/", &rootdir) != 0) { 1497c478bd9Sstevel@tonic-gate (void) fprintf(stderr, 15048011479Ssn199410 gettext("rm: cannot stat root directory: %s\n"), 15148011479Ssn199410 strerror(errno)); 1527c478bd9Sstevel@tonic-gate exit(2); 1537c478bd9Sstevel@tonic-gate } 1547c478bd9Sstevel@tonic-gate 155131f4edeSsn199410 pathbuf = malloc(pathbuflen); 156131f4edeSsn199410 if (pathbuf == NULL) 157131f4edeSsn199410 memerror(); 158131f4edeSsn199410 159*3d63ea05Sas145665 if (init_yes() < 0) { 160*3d63ea05Sas145665 (void) fprintf(stderr, gettext(ERR_MSG_INIT_YES), 161*3d63ea05Sas145665 strerror(errno)); 162*3d63ea05Sas145665 exit(2); 163*3d63ea05Sas145665 } 164*3d63ea05Sas145665 16548011479Ssn199410 for (; *argv != NULL; argv++) { 16648011479Ssn199410 char *p = strrchr(*argv, '/'); 16748011479Ssn199410 if (p == NULL) 16848011479Ssn199410 p = *argv; 16948011479Ssn199410 else 17048011479Ssn199410 p = p + 1; 17148011479Ssn199410 if (strcmp(p, ".") == 0 || strcmp(p, "..") == 0) { 17248011479Ssn199410 (void) fprintf(stderr, 17348011479Ssn199410 gettext("rm of %s is not allowed\n"), *argv); 17448011479Ssn199410 errcnt++; 17548011479Ssn199410 continue; 17648011479Ssn199410 } 17748011479Ssn199410 /* Retry when we can't walk back up. */ 17848011479Ssn199410 while (rm(*argv, rec = cur = &top) != 0) 179996aa816Ssn199410 ; 1807c478bd9Sstevel@tonic-gate } 18112a9e0efSsn199410 18248011479Ssn199410 return (errcnt != 0 ? 2 : 0); 18348011479Ssn199410 } 18448011479Ssn199410 18548011479Ssn199410 static void 18648011479Ssn199410 pushfilename(const char *fname) 18748011479Ssn199410 { 18848011479Ssn199410 char *p; 18948011479Ssn199410 const char *q = fname; 19048011479Ssn199410 19148011479Ssn199410 if (cur == &top) { 19248011479Ssn199410 p = pathbuf; 19348011479Ssn199410 } else { 19448011479Ssn199410 p = pathbuf + cur->up->pathend; 19548011479Ssn199410 *p++ = '/'; 19648011479Ssn199410 } 19748011479Ssn199410 while (*q != '\0') { 19848011479Ssn199410 if (p - pathbuf + 2 >= pathbuflen) { 19948011479Ssn199410 char *np; 20048011479Ssn199410 pathbuflen += MAXPATHLEN; 20148011479Ssn199410 np = realloc(pathbuf, pathbuflen); 20248011479Ssn199410 if (np == NULL) 20348011479Ssn199410 memerror(); 20448011479Ssn199410 p = np + (p - pathbuf); 20548011479Ssn199410 pathbuf = np; 20648011479Ssn199410 } 20748011479Ssn199410 *p++ = *q++; 20848011479Ssn199410 } 20948011479Ssn199410 *p = '\0'; 21048011479Ssn199410 cur->pathend = p - pathbuf; 21148011479Ssn199410 } 21248011479Ssn199410 21348011479Ssn199410 static void 21448011479Ssn199410 closeframe(struct dlist *frm) 21548011479Ssn199410 { 21648011479Ssn199410 if (frm->dp != NULL) { 21748011479Ssn199410 (void) closedir(frm->dp); 21848011479Ssn199410 nfds--; 21948011479Ssn199410 frm->dp = NULL; 22048011479Ssn199410 frm->fd = -1; 22148011479Ssn199410 } 2227c478bd9Sstevel@tonic-gate } 2237c478bd9Sstevel@tonic-gate 224996aa816Ssn199410 static int 22548011479Ssn199410 reclaim(void) 2267c478bd9Sstevel@tonic-gate { 22748011479Ssn199410 while (rec != NULL && (rec->flags & DIR_CANTCLOSE) != 0) 22848011479Ssn199410 rec = rec->down; 22948011479Ssn199410 if (rec == NULL || rec == cur || rec->dp == NULL) 23048011479Ssn199410 return (-1); 23148011479Ssn199410 rec->diroff = telldir(rec->dp); 23248011479Ssn199410 closeframe(rec); 23348011479Ssn199410 rec = rec->down; 23448011479Ssn199410 return (0); 23548011479Ssn199410 } 23648011479Ssn199410 23748011479Ssn199410 static void 23848011479Ssn199410 pushdir(struct dlist *frm) 23948011479Ssn199410 { 24048011479Ssn199410 frm->up = cur; 24148011479Ssn199410 frm->down = NULL; 24248011479Ssn199410 cur->down = frm; 24348011479Ssn199410 cur = frm; 24448011479Ssn199410 } 24548011479Ssn199410 24648011479Ssn199410 static int 24748011479Ssn199410 opendirat(int dirfd, const char *entry, struct dlist *frm) 24848011479Ssn199410 { 24948011479Ssn199410 int fd; 25048011479Ssn199410 25148011479Ssn199410 if (nfds >= maxfds) 25248011479Ssn199410 (void) reclaim(); 25348011479Ssn199410 25448011479Ssn199410 while ((fd = openat(dirfd, entry, O_RDONLY|O_NONBLOCK)) == -1 && 25548011479Ssn199410 errno == EMFILE) { 25648011479Ssn199410 if (nfds < maxfds) 25748011479Ssn199410 maxfds = nfds; 25848011479Ssn199410 if (reclaim() != 0) 25948011479Ssn199410 return (-1); 26048011479Ssn199410 } 26148011479Ssn199410 if (fd < 0) 26248011479Ssn199410 return (-1); 26348011479Ssn199410 frm->fd = fd; 26448011479Ssn199410 frm->dp = fdopendir(fd); 26548011479Ssn199410 if (frm->dp == NULL) { 26648011479Ssn199410 (void) close(fd); 26748011479Ssn199410 return (-1); 26848011479Ssn199410 } 26948011479Ssn199410 nfds++; 27048011479Ssn199410 return (0); 27148011479Ssn199410 } 2727c478bd9Sstevel@tonic-gate 2737c478bd9Sstevel@tonic-gate /* 27448011479Ssn199410 * Since we never pop the top frame, cur->up can never be NULL. 27548011479Ssn199410 * If we pop beyond a frame we closed, we try to reopen "..". 2767c478bd9Sstevel@tonic-gate */ 27748011479Ssn199410 static int 27848011479Ssn199410 popdir(boolean_t noerror) 27948011479Ssn199410 { 28048011479Ssn199410 struct stat buf; 28148011479Ssn199410 int ret = noerror ? 0 : -1; 28248011479Ssn199410 pathbuf[cur->up->pathend] = '\0'; 28348011479Ssn199410 28448011479Ssn199410 if (noerror && cur->up->fd == -1) { 28548011479Ssn199410 rec = cur->up; 28648011479Ssn199410 if (opendirat(cur->fd, "..", rec) != 0 || 28748011479Ssn199410 fstat(rec->fd, &buf) != 0) { 28848011479Ssn199410 (void) fprintf(stderr, 28948011479Ssn199410 gettext("rm: cannot reopen %s: %s\n"), 29048011479Ssn199410 pathbuf, strerror(errno)); 29148011479Ssn199410 exit(2); 29248011479Ssn199410 } 29348011479Ssn199410 if (rec->ino != buf.st_ino || rec->dev != buf.st_dev) { 29448011479Ssn199410 (void) fprintf(stderr, gettext("rm: WARNING: " 29548011479Ssn199410 "The directory %s was moved or linked to " 29648011479Ssn199410 "another directory during the execution of rm\n"), 29748011479Ssn199410 pathbuf); 29848011479Ssn199410 closeframe(rec); 29948011479Ssn199410 ret = -1; 30048011479Ssn199410 } else { 30148011479Ssn199410 /* If telldir failed, we take it from the top. */ 30248011479Ssn199410 if (rec->diroff != -1) 30348011479Ssn199410 seekdir(rec->dp, rec->diroff); 30448011479Ssn199410 } 30548011479Ssn199410 } else if (rec == cur) 30648011479Ssn199410 rec = cur->up; 30748011479Ssn199410 closeframe(cur); 30848011479Ssn199410 cur = cur->up; 30948011479Ssn199410 cur->down = NULL; 31048011479Ssn199410 return (ret); 31148011479Ssn199410 } 31248011479Ssn199410 31348011479Ssn199410 /* 31448011479Ssn199410 * The stack frame of this function is minimized so that we can 31548011479Ssn199410 * recurse quite a bit before we overflow the stack; around 31648011479Ssn199410 * 30,000-40,000 nested directories can be removed with the default 31748011479Ssn199410 * stack limit. 31848011479Ssn199410 */ 31948011479Ssn199410 static int 32048011479Ssn199410 rm(const char *entry, struct dlist *caller) 32148011479Ssn199410 { 32248011479Ssn199410 struct dlist frame; 32348011479Ssn199410 int flag; 32448011479Ssn199410 struct stat temp; 32548011479Ssn199410 struct dirent *dent; 32648011479Ssn199410 int err; 32748011479Ssn199410 32848011479Ssn199410 /* 32948011479Ssn199410 * Construct the pathname: note that the entry may live in memory 33048011479Ssn199410 * allocated by readdir and that after return from recursion 33148011479Ssn199410 * the memory is no longer valid. So after the recursive rm() 33248011479Ssn199410 * call, we use the global pathbuf instead of the entry argument. 33348011479Ssn199410 */ 33448011479Ssn199410 pushfilename(entry); 33548011479Ssn199410 33648011479Ssn199410 if (fstatat(caller->fd, entry, &temp, AT_SYMLINK_NOFOLLOW) != 0) { 3377c478bd9Sstevel@tonic-gate if (!silent) { 33848011479Ssn199410 (void) fprintf(stderr, "rm: %s: %s\n", pathbuf, 33948011479Ssn199410 strerror(errno)); 34048011479Ssn199410 errcnt++; 3417c478bd9Sstevel@tonic-gate } 342996aa816Ssn199410 return (0); 3437c478bd9Sstevel@tonic-gate } 3447c478bd9Sstevel@tonic-gate 34548011479Ssn199410 if (S_ISDIR(temp.st_mode)) { 3467c478bd9Sstevel@tonic-gate /* 3477c478bd9Sstevel@tonic-gate * If "-r" wasn't specified, trying to remove directories 3487c478bd9Sstevel@tonic-gate * is an error. 3497c478bd9Sstevel@tonic-gate */ 3507c478bd9Sstevel@tonic-gate if (!recursive) { 3517c478bd9Sstevel@tonic-gate (void) fprintf(stderr, 35248011479Ssn199410 gettext("rm: %s is a directory\n"), pathbuf); 35348011479Ssn199410 errcnt++; 354996aa816Ssn199410 return (0); 3557c478bd9Sstevel@tonic-gate } 3567c478bd9Sstevel@tonic-gate 35748011479Ssn199410 if (temp.st_ino == rootdir.st_ino && 35848011479Ssn199410 temp.st_dev == rootdir.st_dev) { 3597c478bd9Sstevel@tonic-gate (void) fprintf(stderr, 36048011479Ssn199410 gettext("rm of %s is not allowed\n"), "/"); 36148011479Ssn199410 errcnt++; 362996aa816Ssn199410 return (0); 3637c478bd9Sstevel@tonic-gate } 3647c478bd9Sstevel@tonic-gate /* 3657c478bd9Sstevel@tonic-gate * TRANSLATION_NOTE - The following message will contain the 3667c478bd9Sstevel@tonic-gate * first character of the strings for "yes" and "no" defined 3677c478bd9Sstevel@tonic-gate * in the file "nl_langinfo.po". After substitution, the 3687c478bd9Sstevel@tonic-gate * message will appear as follows: 3697c478bd9Sstevel@tonic-gate * rm: examine files in directory <directoryname> (y/n)? 3707c478bd9Sstevel@tonic-gate * where <directoryname> is the directory to be removed 3717c478bd9Sstevel@tonic-gate * 3727c478bd9Sstevel@tonic-gate */ 37348011479Ssn199410 if (interactive && !confirm(stderr, 3747c478bd9Sstevel@tonic-gate gettext("rm: examine files in directory %s (%s/%s)? "), 375*3d63ea05Sas145665 pathbuf, yesstr, nostr)) { 37648011479Ssn199410 return (0); 3777c478bd9Sstevel@tonic-gate } 3787c478bd9Sstevel@tonic-gate 37948011479Ssn199410 frame.dev = temp.st_dev; 38048011479Ssn199410 frame.ino = temp.st_ino; 38148011479Ssn199410 frame.flags = 0; 38248011479Ssn199410 flag = AT_REMOVEDIR; 38348011479Ssn199410 3847c478bd9Sstevel@tonic-gate #ifdef XPG4 3857c478bd9Sstevel@tonic-gate /* 38648011479Ssn199410 * XCU4 and POSIX.2: If not interactive, check to see whether 3877c478bd9Sstevel@tonic-gate * or not directory is readable or writable and if not, 3887c478bd9Sstevel@tonic-gate * prompt user for response. 3897c478bd9Sstevel@tonic-gate */ 39048011479Ssn199410 if (ontty && !interactive && !silent && 39148011479Ssn199410 __accessat(caller->fd, entry, W_OK|X_OK|E_OK) != 0 && 39248011479Ssn199410 !confirm(stderr, 39348011479Ssn199410 gettext("rm: examine files in directory %s (%s/%s)? "), 394*3d63ea05Sas145665 pathbuf, yesstr, nostr)) { 39548011479Ssn199410 return (0); 3967c478bd9Sstevel@tonic-gate } 3977c478bd9Sstevel@tonic-gate #endif 39848011479Ssn199410 if (opendirat(caller->fd, entry, &frame) == -1) { 39948011479Ssn199410 err = errno; 4007c478bd9Sstevel@tonic-gate 4017c478bd9Sstevel@tonic-gate if (interactive) { 4027c478bd9Sstevel@tonic-gate /* 4037c478bd9Sstevel@tonic-gate * Print an error message that 4047c478bd9Sstevel@tonic-gate * we could not read the directory 4057c478bd9Sstevel@tonic-gate * as the user wanted to examine 4067c478bd9Sstevel@tonic-gate * files in the directory. Only 4077c478bd9Sstevel@tonic-gate * affect the error status if 4087c478bd9Sstevel@tonic-gate * user doesn't want to remove the 4097c478bd9Sstevel@tonic-gate * directory as we still may be able 4107c478bd9Sstevel@tonic-gate * remove the directory successfully. 4117c478bd9Sstevel@tonic-gate */ 4127c478bd9Sstevel@tonic-gate (void) fprintf(stderr, gettext( 41348011479Ssn199410 "rm: cannot read directory %s: %s\n"), 41448011479Ssn199410 pathbuf, strerror(err)); 41548011479Ssn199410 41648011479Ssn199410 /* 41748011479Ssn199410 * TRANSLATION_NOTE - The following message will contain the 41848011479Ssn199410 * first character of the strings for "yes" and "no" defined 41948011479Ssn199410 * in the file "nl_langinfo.po". After substitution, the 42048011479Ssn199410 * message will appear as follows: 42148011479Ssn199410 * rm: remove <filename> (y/n)? 42248011479Ssn199410 * For example, in German, this will appear as 42348011479Ssn199410 * rm: l�schen <filename> (j/n)? 42448011479Ssn199410 * where j=ja, n=nein, <filename>=the file to be removed 42548011479Ssn199410 */ 42648011479Ssn199410 if (!confirm(stderr, 42748011479Ssn199410 gettext("rm: remove %s (%s/%s)? "), 428*3d63ea05Sas145665 pathbuf, yesstr, nostr)) { 42948011479Ssn199410 errcnt++; 43048011479Ssn199410 return (0); 4317c478bd9Sstevel@tonic-gate } 4327c478bd9Sstevel@tonic-gate } 43348011479Ssn199410 /* If it's empty we may still be able to rm it */ 43448011479Ssn199410 if (unlinkat(caller->fd, entry, flag) == 0) 43548011479Ssn199410 return (0); 43648011479Ssn199410 if (interactive) 43748011479Ssn199410 err = errno; 43848011479Ssn199410 (void) fprintf(stderr, 43948011479Ssn199410 interactive ? 44048011479Ssn199410 gettext("rm: Unable to remove directory %s: %s\n") : 44148011479Ssn199410 gettext("rm: cannot read directory %s: %s\n"), 44248011479Ssn199410 pathbuf, strerror(err)); 44348011479Ssn199410 errcnt++; 44448011479Ssn199410 return (0); 44548011479Ssn199410 } 4467c478bd9Sstevel@tonic-gate 4477c478bd9Sstevel@tonic-gate /* 44848011479Ssn199410 * There is a race condition here too; if we open a directory 44948011479Ssn199410 * we have to make sure it's still the same directory we 45048011479Ssn199410 * stat'ed and checked against root earlier. Let's check. 4517c478bd9Sstevel@tonic-gate */ 45248011479Ssn199410 if (fstat(frame.fd, &temp) != 0 || 45348011479Ssn199410 frame.ino != temp.st_ino || 45448011479Ssn199410 frame.dev != temp.st_dev) { 45548011479Ssn199410 (void) fprintf(stderr, 45648011479Ssn199410 gettext("rm: %s: directory renamed\n"), pathbuf); 45748011479Ssn199410 closeframe(&frame); 45848011479Ssn199410 errcnt++; 45948011479Ssn199410 return (0); 4607c478bd9Sstevel@tonic-gate } 4617c478bd9Sstevel@tonic-gate 46248011479Ssn199410 if (caller != &top) { 46348011479Ssn199410 if (checkdir(caller, &frame) != 0) { 46448011479Ssn199410 closeframe(&frame); 46548011479Ssn199410 goto unlinkit; 4667c478bd9Sstevel@tonic-gate } 46748011479Ssn199410 } 46848011479Ssn199410 pushdir(&frame); 4697c478bd9Sstevel@tonic-gate 4707c478bd9Sstevel@tonic-gate /* 47148011479Ssn199410 * rm() only returns -1 if popdir failed at some point; 47248011479Ssn199410 * frame.dp is no longer reliable and we must drop out. 4737c478bd9Sstevel@tonic-gate */ 47448011479Ssn199410 while ((dent = readdir(frame.dp)) != NULL) { 47548011479Ssn199410 if (strcmp(dent->d_name, ".") == 0 || 47648011479Ssn199410 strcmp(dent->d_name, "..") == 0) 4777c478bd9Sstevel@tonic-gate continue; 4787c478bd9Sstevel@tonic-gate 47948011479Ssn199410 if (rm(dent->d_name, &frame) != 0) 480996aa816Ssn199410 break; 4817c478bd9Sstevel@tonic-gate } 4827c478bd9Sstevel@tonic-gate 48348011479Ssn199410 if (popdir(dent == NULL) != 0) 484996aa816Ssn199410 return (-1); 4857c478bd9Sstevel@tonic-gate 4867c478bd9Sstevel@tonic-gate /* 48748011479Ssn199410 * We recursed and the subdirectory may have set the CANTCLOSE 48848011479Ssn199410 * flag; we need to clear it except for &top. 48948011479Ssn199410 * Recursion may have invalidated entry because of closedir(). 4907c478bd9Sstevel@tonic-gate */ 49148011479Ssn199410 if (caller != &top) { 49248011479Ssn199410 caller->flags &= ~DIR_CANTCLOSE; 49348011479Ssn199410 entry = &pathbuf[caller->up->pathend + 1]; 4947c478bd9Sstevel@tonic-gate } 49548011479Ssn199410 } else { 49648011479Ssn199410 flag = 0; 4977c478bd9Sstevel@tonic-gate } 49848011479Ssn199410 unlinkit: 4997c478bd9Sstevel@tonic-gate /* 5007c478bd9Sstevel@tonic-gate * If interactive, ask for acknowledgement. 5017c478bd9Sstevel@tonic-gate */ 5027c478bd9Sstevel@tonic-gate if (interactive) { 50348011479Ssn199410 if (!confirm(stderr, gettext("rm: remove %s (%s/%s)? "), 504*3d63ea05Sas145665 pathbuf, yesstr, nostr)) { 50548011479Ssn199410 return (0); 5067c478bd9Sstevel@tonic-gate } 50748011479Ssn199410 } else if (!silent && flag == 0) { 50848011479Ssn199410 /* 50948011479Ssn199410 * If not silent, and stdin is a terminal, and there's 51048011479Ssn199410 * no write access, and the file isn't a symbolic link, 51148011479Ssn199410 * ask for permission. If flag is set, then we know it's 51248011479Ssn199410 * a directory so we skip this test as it was done above. 51348011479Ssn199410 * 51448011479Ssn199410 * TRANSLATION_NOTE - The following message will contain the 51548011479Ssn199410 * first character of the strings for "yes" and "no" defined 51648011479Ssn199410 * in the file "nl_langinfo.po". After substitution, the 51748011479Ssn199410 * message will appear as follows: 51848011479Ssn199410 * rm: <filename>: override protection XXX (y/n)? 51948011479Ssn199410 * where XXX is the permission mode bits of the file in octal 52048011479Ssn199410 * and <filename> is the file to be removed 52148011479Ssn199410 * 52248011479Ssn199410 */ 52348011479Ssn199410 if (ontty && !S_ISLNK(temp.st_mode) && 52448011479Ssn199410 __accessat(caller->fd, entry, W_OK|E_OK) != 0 && 52548011479Ssn199410 !confirm(stdout, 52648011479Ssn199410 gettext("rm: %s: override protection %o (%s/%s)? "), 527*3d63ea05Sas145665 pathbuf, temp.st_mode & 0777, yesstr, nostr)) { 52848011479Ssn199410 return (0); 5297c478bd9Sstevel@tonic-gate } 5307c478bd9Sstevel@tonic-gate } 5317c478bd9Sstevel@tonic-gate 53248011479Ssn199410 if (unlinkat(caller->fd, entry, flag) != 0) { 53348011479Ssn199410 err = errno; 53448011479Ssn199410 if (err == ENOENT) 53548011479Ssn199410 return (0); 53648011479Ssn199410 53748011479Ssn199410 if (flag != 0) { 53848011479Ssn199410 if (err == EINVAL) { 53948011479Ssn199410 (void) fprintf(stderr, gettext( 54048011479Ssn199410 "rm: Cannot remove any directory in the " 54148011479Ssn199410 "path of the current working directory\n" 54248011479Ssn199410 "%s\n"), pathbuf); 54348011479Ssn199410 } else { 54448011479Ssn199410 if (err == EEXIST) 54548011479Ssn199410 err = ENOTEMPTY; 54648011479Ssn199410 (void) fprintf(stderr, 54748011479Ssn199410 gettext("rm: Unable to remove directory %s:" 54848011479Ssn199410 " %s\n"), pathbuf, strerror(err)); 54948011479Ssn199410 } 55048011479Ssn199410 } else { 55148011479Ssn199410 #ifndef XPG4 55248011479Ssn199410 if (!silent || interactive) { 55348011479Ssn199410 #endif 55448011479Ssn199410 55548011479Ssn199410 (void) fprintf(stderr, 55648011479Ssn199410 gettext("rm: %s not removed: %s\n"), 55748011479Ssn199410 pathbuf, strerror(err)); 55848011479Ssn199410 #ifndef XPG4 55948011479Ssn199410 } 56048011479Ssn199410 #endif 56148011479Ssn199410 } 56248011479Ssn199410 errcnt++; 56348011479Ssn199410 } 56448011479Ssn199410 return (0); 56548011479Ssn199410 } 5667c478bd9Sstevel@tonic-gate 5677c478bd9Sstevel@tonic-gate static int 56848011479Ssn199410 confirm(FILE *fp, const char *q, ...) 5697c478bd9Sstevel@tonic-gate { 57048011479Ssn199410 va_list ap; 5717c478bd9Sstevel@tonic-gate 57248011479Ssn199410 va_start(ap, q); 57348011479Ssn199410 (void) vfprintf(fp, q, ap); 57448011479Ssn199410 va_end(ap); 57548011479Ssn199410 return (yes()); 5767c478bd9Sstevel@tonic-gate } 5777c478bd9Sstevel@tonic-gate 57848011479Ssn199410 static void 57948011479Ssn199410 memerror(void) 58048011479Ssn199410 { 58148011479Ssn199410 (void) fprintf(stderr, gettext("rm: Insufficient memory.\n")); 58248011479Ssn199410 exit(1); 58348011479Ssn199410 } 5847c478bd9Sstevel@tonic-gate 5857c478bd9Sstevel@tonic-gate /* 58648011479Ssn199410 * If we can't stat "..", it's either not there or we can't search 58748011479Ssn199410 * the current directory; in that case we can't return back through 58848011479Ssn199410 * "..", so we need to keep the parent open. 58948011479Ssn199410 * Check that we came from "..", if not then this directory entry is an 59048011479Ssn199410 * additional link and there is risk of a filesystem cycle and we also 59148011479Ssn199410 * can't go back up through ".." and we keep the directory open. 5927c478bd9Sstevel@tonic-gate */ 59348011479Ssn199410 static int 59448011479Ssn199410 checkdir(struct dlist *caller, struct dlist *frmp) 59548011479Ssn199410 { 59648011479Ssn199410 struct stat up; 59748011479Ssn199410 struct dlist *ptr; 59848011479Ssn199410 59948011479Ssn199410 if (fstatat(frmp->fd, "..", &up, 0) != 0) { 60048011479Ssn199410 caller->flags |= DIR_CANTCLOSE; 60148011479Ssn199410 return (0); 60248011479Ssn199410 } else if (up.st_ino == caller->ino && up.st_dev == caller->dev) { 6037c478bd9Sstevel@tonic-gate return (0); 6047c478bd9Sstevel@tonic-gate } 6057c478bd9Sstevel@tonic-gate 60648011479Ssn199410 /* Directory hard link, check cycle */ 60748011479Ssn199410 for (ptr = caller; ptr != NULL; ptr = ptr->up) { 60848011479Ssn199410 if (frmp->dev == ptr->dev && frmp->ino == ptr->ino) { 6097c478bd9Sstevel@tonic-gate (void) fprintf(stderr, 61048011479Ssn199410 gettext("rm: cycle detected for %s\n"), pathbuf); 61148011479Ssn199410 errcnt++; 61248011479Ssn199410 return (-1); 6137c478bd9Sstevel@tonic-gate } 6147c478bd9Sstevel@tonic-gate } 61548011479Ssn199410 caller->flags |= DIR_CANTCLOSE; 616996aa816Ssn199410 return (0); 6177c478bd9Sstevel@tonic-gate } 618