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 /* 23*48011479Ssn199410 * 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 36*48011479Ssn199410 #include <sys/param.h> 377c478bd9Sstevel@tonic-gate #include <sys/stat.h> 387c478bd9Sstevel@tonic-gate #include <dirent.h> 39*48011479Ssn199410 #include <errno.h> 40*48011479Ssn199410 #include <fcntl.h> 41*48011479Ssn199410 #include <langinfo.h> 427c478bd9Sstevel@tonic-gate #include <limits.h> 437c478bd9Sstevel@tonic-gate #include <locale.h> 44*48011479Ssn199410 #include <stdarg.h> 45*48011479Ssn199410 #include <stdio.h> 467c478bd9Sstevel@tonic-gate #include <stdlib.h> 47*48011479Ssn199410 #include <string.h> 48*48011479Ssn199410 #include <unistd.h> 49*48011479Ssn199410 #include <values.h> 507c478bd9Sstevel@tonic-gate 51*48011479Ssn199410 #define E_OK 010 /* make __accessat() use effective ids */ 527c478bd9Sstevel@tonic-gate 53*48011479Ssn199410 #define DIR_CANTCLOSE 1 547c478bd9Sstevel@tonic-gate 55*48011479Ssn199410 static struct stat rootdir; 56*48011479Ssn199410 57*48011479Ssn199410 struct dlist { 58*48011479Ssn199410 int fd; /* Stores directory fd */ 59*48011479Ssn199410 int flags; /* DIR_* Flags */ 60*48011479Ssn199410 DIR *dp; /* Open directory (opened with fd) */ 61*48011479Ssn199410 long diroff; /* Saved directory offset when closing */ 62*48011479Ssn199410 struct dlist *up; /* Up one step in the tree (toward "/") */ 63*48011479Ssn199410 struct dlist *down; /* Down one step in the tree */ 64*48011479Ssn199410 ino_t ino; /* st_ino of directory */ 65*48011479Ssn199410 dev_t dev; /* st_dev of directory */ 66*48011479Ssn199410 int pathend; /* Offset of name end in the pathbuffer */ 67*48011479Ssn199410 }; 68*48011479Ssn199410 69*48011479Ssn199410 static struct dlist top = { 70*48011479Ssn199410 (int)AT_FDCWD, 71*48011479Ssn199410 DIR_CANTCLOSE, 72*48011479Ssn199410 }; 737c478bd9Sstevel@tonic-gate 747c478bd9Sstevel@tonic-gate static char yeschr[SCHAR_MAX + 2]; 757c478bd9Sstevel@tonic-gate static char nochr[SCHAR_MAX + 2]; 767c478bd9Sstevel@tonic-gate 77*48011479Ssn199410 static struct dlist *cur, *rec; 787c478bd9Sstevel@tonic-gate 79*48011479Ssn199410 static int rm(const char *, struct dlist *); 80*48011479Ssn199410 static int confirm(FILE *, const char *, ...); 81*48011479Ssn199410 static void memerror(void); 82*48011479Ssn199410 static int checkdir(struct dlist *, struct dlist *); 83*48011479Ssn199410 static int errcnt; 84*48011479Ssn199410 static boolean_t silent, interactive, recursive, ontty; 857c478bd9Sstevel@tonic-gate 86*48011479Ssn199410 static char *pathbuf; 87*48011479Ssn199410 static size_t pathbuflen; 887c478bd9Sstevel@tonic-gate 89*48011479Ssn199410 static int maxfds = MAXINT; 90*48011479Ssn199410 static int nfds; 917c478bd9Sstevel@tonic-gate 92*48011479Ssn199410 extern int __accessat(int, const char *, int); 937c478bd9Sstevel@tonic-gate 947c478bd9Sstevel@tonic-gate int 95*48011479Ssn199410 main(int argc, char **argv) 967c478bd9Sstevel@tonic-gate { 977c478bd9Sstevel@tonic-gate int errflg = 0; 987c478bd9Sstevel@tonic-gate int c; 997c478bd9Sstevel@tonic-gate 1007c478bd9Sstevel@tonic-gate (void) setlocale(LC_ALL, ""); 1017c478bd9Sstevel@tonic-gate #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ 1027c478bd9Sstevel@tonic-gate #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ 1037c478bd9Sstevel@tonic-gate #endif 1047c478bd9Sstevel@tonic-gate (void) textdomain(TEXT_DOMAIN); 1057c478bd9Sstevel@tonic-gate 1067c478bd9Sstevel@tonic-gate (void) strncpy(yeschr, nl_langinfo(YESSTR), SCHAR_MAX + 1); 1077c478bd9Sstevel@tonic-gate (void) strncpy(nochr, nl_langinfo(NOSTR), SCHAR_MAX + 1); 1087c478bd9Sstevel@tonic-gate 1097c478bd9Sstevel@tonic-gate while ((c = getopt(argc, argv, "frRi")) != EOF) 1107c478bd9Sstevel@tonic-gate switch (c) { 1117c478bd9Sstevel@tonic-gate case 'f': 112*48011479Ssn199410 silent = B_TRUE; 1137c478bd9Sstevel@tonic-gate #ifdef XPG4 114*48011479Ssn199410 interactive = B_FALSE; 1157c478bd9Sstevel@tonic-gate #endif 1167c478bd9Sstevel@tonic-gate break; 1177c478bd9Sstevel@tonic-gate case 'i': 118*48011479Ssn199410 interactive = B_TRUE; 1197c478bd9Sstevel@tonic-gate #ifdef XPG4 120*48011479Ssn199410 silent = B_FALSE; 1217c478bd9Sstevel@tonic-gate #endif 1227c478bd9Sstevel@tonic-gate break; 1237c478bd9Sstevel@tonic-gate case 'r': 1247c478bd9Sstevel@tonic-gate case 'R': 125*48011479Ssn199410 recursive = B_TRUE; 1267c478bd9Sstevel@tonic-gate break; 1277c478bd9Sstevel@tonic-gate case '?': 1287c478bd9Sstevel@tonic-gate errflg = 1; 1297c478bd9Sstevel@tonic-gate break; 1307c478bd9Sstevel@tonic-gate } 1317c478bd9Sstevel@tonic-gate 1327c478bd9Sstevel@tonic-gate /* 1337c478bd9Sstevel@tonic-gate * For BSD compatibility allow '-' to delimit the end 1347c478bd9Sstevel@tonic-gate * of options. However, if options were already explicitly 1357c478bd9Sstevel@tonic-gate * terminated with '--', then treat '-' literally: otherwise, 1367c478bd9Sstevel@tonic-gate * "rm -- -" won't remove '-'. 1377c478bd9Sstevel@tonic-gate */ 1387c478bd9Sstevel@tonic-gate if (optind < argc && 1397c478bd9Sstevel@tonic-gate strcmp(argv[optind], "-") == 0 && 1407c478bd9Sstevel@tonic-gate strcmp(argv[optind - 1], "--") != 0) 1417c478bd9Sstevel@tonic-gate optind++; 1427c478bd9Sstevel@tonic-gate 1437c478bd9Sstevel@tonic-gate argc -= optind; 1447c478bd9Sstevel@tonic-gate argv = &argv[optind]; 1457c478bd9Sstevel@tonic-gate 1467c478bd9Sstevel@tonic-gate if ((argc < 1 && !silent) || errflg) { 147*48011479Ssn199410 (void) fprintf(stderr, gettext("usage: rm [-fiRr] file ...\n")); 148*48011479Ssn199410 exit(2); 149*48011479Ssn199410 } 150*48011479Ssn199410 151*48011479Ssn199410 ontty = isatty(STDIN_FILENO) != 0; 152*48011479Ssn199410 153*48011479Ssn199410 if (recursive && stat("/", &rootdir) != 0) { 1547c478bd9Sstevel@tonic-gate (void) fprintf(stderr, 155*48011479Ssn199410 gettext("rm: cannot stat root directory: %s\n"), 156*48011479Ssn199410 strerror(errno)); 1577c478bd9Sstevel@tonic-gate exit(2); 1587c478bd9Sstevel@tonic-gate } 1597c478bd9Sstevel@tonic-gate 160*48011479Ssn199410 for (; *argv != NULL; argv++) { 161*48011479Ssn199410 char *p = strrchr(*argv, '/'); 162*48011479Ssn199410 if (p == NULL) 163*48011479Ssn199410 p = *argv; 164*48011479Ssn199410 else 165*48011479Ssn199410 p = p + 1; 166*48011479Ssn199410 if (strcmp(p, ".") == 0 || strcmp(p, "..") == 0) { 167*48011479Ssn199410 (void) fprintf(stderr, 168*48011479Ssn199410 gettext("rm of %s is not allowed\n"), *argv); 169*48011479Ssn199410 errcnt++; 170*48011479Ssn199410 continue; 171*48011479Ssn199410 } 172*48011479Ssn199410 /* Retry when we can't walk back up. */ 173*48011479Ssn199410 while (rm(*argv, rec = cur = &top) != 0) 174996aa816Ssn199410 ; 1757c478bd9Sstevel@tonic-gate } 17612a9e0efSsn199410 177*48011479Ssn199410 return (errcnt != 0 ? 2 : 0); 178*48011479Ssn199410 } 179*48011479Ssn199410 180*48011479Ssn199410 static void 181*48011479Ssn199410 pushfilename(const char *fname) 182*48011479Ssn199410 { 183*48011479Ssn199410 char *p; 184*48011479Ssn199410 const char *q = fname; 185*48011479Ssn199410 186*48011479Ssn199410 if (cur == &top) { 187*48011479Ssn199410 p = pathbuf; 188*48011479Ssn199410 } else { 189*48011479Ssn199410 p = pathbuf + cur->up->pathend; 190*48011479Ssn199410 *p++ = '/'; 191*48011479Ssn199410 } 192*48011479Ssn199410 while (*q != '\0') { 193*48011479Ssn199410 if (p - pathbuf + 2 >= pathbuflen) { 194*48011479Ssn199410 char *np; 195*48011479Ssn199410 pathbuflen += MAXPATHLEN; 196*48011479Ssn199410 np = realloc(pathbuf, pathbuflen); 197*48011479Ssn199410 if (np == NULL) 198*48011479Ssn199410 memerror(); 199*48011479Ssn199410 p = np + (p - pathbuf); 200*48011479Ssn199410 pathbuf = np; 201*48011479Ssn199410 } 202*48011479Ssn199410 *p++ = *q++; 203*48011479Ssn199410 } 204*48011479Ssn199410 *p = '\0'; 205*48011479Ssn199410 cur->pathend = p - pathbuf; 206*48011479Ssn199410 } 207*48011479Ssn199410 208*48011479Ssn199410 static void 209*48011479Ssn199410 closeframe(struct dlist *frm) 210*48011479Ssn199410 { 211*48011479Ssn199410 if (frm->dp != NULL) { 212*48011479Ssn199410 (void) closedir(frm->dp); 213*48011479Ssn199410 nfds--; 214*48011479Ssn199410 frm->dp = NULL; 215*48011479Ssn199410 frm->fd = -1; 216*48011479Ssn199410 } 2177c478bd9Sstevel@tonic-gate } 2187c478bd9Sstevel@tonic-gate 219996aa816Ssn199410 static int 220*48011479Ssn199410 reclaim(void) 2217c478bd9Sstevel@tonic-gate { 222*48011479Ssn199410 while (rec != NULL && (rec->flags & DIR_CANTCLOSE) != 0) 223*48011479Ssn199410 rec = rec->down; 224*48011479Ssn199410 if (rec == NULL || rec == cur || rec->dp == NULL) 225*48011479Ssn199410 return (-1); 226*48011479Ssn199410 rec->diroff = telldir(rec->dp); 227*48011479Ssn199410 closeframe(rec); 228*48011479Ssn199410 rec = rec->down; 229*48011479Ssn199410 return (0); 230*48011479Ssn199410 } 231*48011479Ssn199410 232*48011479Ssn199410 static void 233*48011479Ssn199410 pushdir(struct dlist *frm) 234*48011479Ssn199410 { 235*48011479Ssn199410 frm->up = cur; 236*48011479Ssn199410 frm->down = NULL; 237*48011479Ssn199410 cur->down = frm; 238*48011479Ssn199410 cur = frm; 239*48011479Ssn199410 } 240*48011479Ssn199410 241*48011479Ssn199410 static int 242*48011479Ssn199410 opendirat(int dirfd, const char *entry, struct dlist *frm) 243*48011479Ssn199410 { 244*48011479Ssn199410 int fd; 245*48011479Ssn199410 246*48011479Ssn199410 if (nfds >= maxfds) 247*48011479Ssn199410 (void) reclaim(); 248*48011479Ssn199410 249*48011479Ssn199410 while ((fd = openat(dirfd, entry, O_RDONLY|O_NONBLOCK)) == -1 && 250*48011479Ssn199410 errno == EMFILE) { 251*48011479Ssn199410 if (nfds < maxfds) 252*48011479Ssn199410 maxfds = nfds; 253*48011479Ssn199410 if (reclaim() != 0) 254*48011479Ssn199410 return (-1); 255*48011479Ssn199410 } 256*48011479Ssn199410 if (fd < 0) 257*48011479Ssn199410 return (-1); 258*48011479Ssn199410 frm->fd = fd; 259*48011479Ssn199410 frm->dp = fdopendir(fd); 260*48011479Ssn199410 if (frm->dp == NULL) { 261*48011479Ssn199410 (void) close(fd); 262*48011479Ssn199410 return (-1); 263*48011479Ssn199410 } 264*48011479Ssn199410 nfds++; 265*48011479Ssn199410 return (0); 266*48011479Ssn199410 } 2677c478bd9Sstevel@tonic-gate 2687c478bd9Sstevel@tonic-gate /* 269*48011479Ssn199410 * Since we never pop the top frame, cur->up can never be NULL. 270*48011479Ssn199410 * If we pop beyond a frame we closed, we try to reopen "..". 2717c478bd9Sstevel@tonic-gate */ 272*48011479Ssn199410 static int 273*48011479Ssn199410 popdir(boolean_t noerror) 274*48011479Ssn199410 { 275*48011479Ssn199410 struct stat buf; 276*48011479Ssn199410 int ret = noerror ? 0 : -1; 277*48011479Ssn199410 pathbuf[cur->up->pathend] = '\0'; 278*48011479Ssn199410 279*48011479Ssn199410 if (noerror && cur->up->fd == -1) { 280*48011479Ssn199410 rec = cur->up; 281*48011479Ssn199410 if (opendirat(cur->fd, "..", rec) != 0 || 282*48011479Ssn199410 fstat(rec->fd, &buf) != 0) { 283*48011479Ssn199410 (void) fprintf(stderr, 284*48011479Ssn199410 gettext("rm: cannot reopen %s: %s\n"), 285*48011479Ssn199410 pathbuf, strerror(errno)); 286*48011479Ssn199410 exit(2); 287*48011479Ssn199410 } 288*48011479Ssn199410 if (rec->ino != buf.st_ino || rec->dev != buf.st_dev) { 289*48011479Ssn199410 (void) fprintf(stderr, gettext("rm: WARNING: " 290*48011479Ssn199410 "The directory %s was moved or linked to " 291*48011479Ssn199410 "another directory during the execution of rm\n"), 292*48011479Ssn199410 pathbuf); 293*48011479Ssn199410 closeframe(rec); 294*48011479Ssn199410 ret = -1; 295*48011479Ssn199410 } else { 296*48011479Ssn199410 /* If telldir failed, we take it from the top. */ 297*48011479Ssn199410 if (rec->diroff != -1) 298*48011479Ssn199410 seekdir(rec->dp, rec->diroff); 299*48011479Ssn199410 } 300*48011479Ssn199410 } else if (rec == cur) 301*48011479Ssn199410 rec = cur->up; 302*48011479Ssn199410 closeframe(cur); 303*48011479Ssn199410 cur = cur->up; 304*48011479Ssn199410 cur->down = NULL; 305*48011479Ssn199410 return (ret); 306*48011479Ssn199410 } 307*48011479Ssn199410 308*48011479Ssn199410 /* 309*48011479Ssn199410 * The stack frame of this function is minimized so that we can 310*48011479Ssn199410 * recurse quite a bit before we overflow the stack; around 311*48011479Ssn199410 * 30,000-40,000 nested directories can be removed with the default 312*48011479Ssn199410 * stack limit. 313*48011479Ssn199410 */ 314*48011479Ssn199410 static int 315*48011479Ssn199410 rm(const char *entry, struct dlist *caller) 316*48011479Ssn199410 { 317*48011479Ssn199410 struct dlist frame; 318*48011479Ssn199410 int flag; 319*48011479Ssn199410 struct stat temp; 320*48011479Ssn199410 struct dirent *dent; 321*48011479Ssn199410 int err; 322*48011479Ssn199410 323*48011479Ssn199410 /* 324*48011479Ssn199410 * Construct the pathname: note that the entry may live in memory 325*48011479Ssn199410 * allocated by readdir and that after return from recursion 326*48011479Ssn199410 * the memory is no longer valid. So after the recursive rm() 327*48011479Ssn199410 * call, we use the global pathbuf instead of the entry argument. 328*48011479Ssn199410 */ 329*48011479Ssn199410 pushfilename(entry); 330*48011479Ssn199410 331*48011479Ssn199410 if (fstatat(caller->fd, entry, &temp, AT_SYMLINK_NOFOLLOW) != 0) { 3327c478bd9Sstevel@tonic-gate if (!silent) { 333*48011479Ssn199410 (void) fprintf(stderr, "rm: %s: %s\n", pathbuf, 334*48011479Ssn199410 strerror(errno)); 335*48011479Ssn199410 errcnt++; 3367c478bd9Sstevel@tonic-gate } 337996aa816Ssn199410 return (0); 3387c478bd9Sstevel@tonic-gate } 3397c478bd9Sstevel@tonic-gate 340*48011479Ssn199410 if (S_ISDIR(temp.st_mode)) { 3417c478bd9Sstevel@tonic-gate /* 3427c478bd9Sstevel@tonic-gate * If "-r" wasn't specified, trying to remove directories 3437c478bd9Sstevel@tonic-gate * is an error. 3447c478bd9Sstevel@tonic-gate */ 3457c478bd9Sstevel@tonic-gate if (!recursive) { 3467c478bd9Sstevel@tonic-gate (void) fprintf(stderr, 347*48011479Ssn199410 gettext("rm: %s is a directory\n"), pathbuf); 348*48011479Ssn199410 errcnt++; 349996aa816Ssn199410 return (0); 3507c478bd9Sstevel@tonic-gate } 3517c478bd9Sstevel@tonic-gate 352*48011479Ssn199410 if (temp.st_ino == rootdir.st_ino && 353*48011479Ssn199410 temp.st_dev == rootdir.st_dev) { 3547c478bd9Sstevel@tonic-gate (void) fprintf(stderr, 355*48011479Ssn199410 gettext("rm of %s is not allowed\n"), "/"); 356*48011479Ssn199410 errcnt++; 357996aa816Ssn199410 return (0); 3587c478bd9Sstevel@tonic-gate } 3597c478bd9Sstevel@tonic-gate /* 3607c478bd9Sstevel@tonic-gate * TRANSLATION_NOTE - The following message will contain the 3617c478bd9Sstevel@tonic-gate * first character of the strings for "yes" and "no" defined 3627c478bd9Sstevel@tonic-gate * in the file "nl_langinfo.po". After substitution, the 3637c478bd9Sstevel@tonic-gate * message will appear as follows: 3647c478bd9Sstevel@tonic-gate * rm: examine files in directory <directoryname> (y/n)? 3657c478bd9Sstevel@tonic-gate * where <directoryname> is the directory to be removed 3667c478bd9Sstevel@tonic-gate * 3677c478bd9Sstevel@tonic-gate */ 368*48011479Ssn199410 if (interactive && !confirm(stderr, 3697c478bd9Sstevel@tonic-gate gettext("rm: examine files in directory %s (%s/%s)? "), 370*48011479Ssn199410 pathbuf, yeschr, nochr)) { 371*48011479Ssn199410 return (0); 3727c478bd9Sstevel@tonic-gate } 3737c478bd9Sstevel@tonic-gate 374*48011479Ssn199410 frame.dev = temp.st_dev; 375*48011479Ssn199410 frame.ino = temp.st_ino; 376*48011479Ssn199410 frame.flags = 0; 377*48011479Ssn199410 flag = AT_REMOVEDIR; 378*48011479Ssn199410 3797c478bd9Sstevel@tonic-gate #ifdef XPG4 3807c478bd9Sstevel@tonic-gate /* 381*48011479Ssn199410 * XCU4 and POSIX.2: If not interactive, check to see whether 3827c478bd9Sstevel@tonic-gate * or not directory is readable or writable and if not, 3837c478bd9Sstevel@tonic-gate * prompt user for response. 3847c478bd9Sstevel@tonic-gate */ 385*48011479Ssn199410 if (ontty && !interactive && !silent && 386*48011479Ssn199410 __accessat(caller->fd, entry, W_OK|X_OK|E_OK) != 0 && 387*48011479Ssn199410 !confirm(stderr, 388*48011479Ssn199410 gettext("rm: examine files in directory %s (%s/%s)? "), 389*48011479Ssn199410 pathbuf, yeschr, nochr)) { 390*48011479Ssn199410 return (0); 3917c478bd9Sstevel@tonic-gate } 3927c478bd9Sstevel@tonic-gate #endif 393*48011479Ssn199410 if (opendirat(caller->fd, entry, &frame) == -1) { 394*48011479Ssn199410 err = errno; 3957c478bd9Sstevel@tonic-gate 3967c478bd9Sstevel@tonic-gate if (interactive) { 3977c478bd9Sstevel@tonic-gate /* 3987c478bd9Sstevel@tonic-gate * Print an error message that 3997c478bd9Sstevel@tonic-gate * we could not read the directory 4007c478bd9Sstevel@tonic-gate * as the user wanted to examine 4017c478bd9Sstevel@tonic-gate * files in the directory. Only 4027c478bd9Sstevel@tonic-gate * affect the error status if 4037c478bd9Sstevel@tonic-gate * user doesn't want to remove the 4047c478bd9Sstevel@tonic-gate * directory as we still may be able 4057c478bd9Sstevel@tonic-gate * remove the directory successfully. 4067c478bd9Sstevel@tonic-gate */ 4077c478bd9Sstevel@tonic-gate (void) fprintf(stderr, gettext( 408*48011479Ssn199410 "rm: cannot read directory %s: %s\n"), 409*48011479Ssn199410 pathbuf, strerror(err)); 410*48011479Ssn199410 411*48011479Ssn199410 /* 412*48011479Ssn199410 * TRANSLATION_NOTE - The following message will contain the 413*48011479Ssn199410 * first character of the strings for "yes" and "no" defined 414*48011479Ssn199410 * in the file "nl_langinfo.po". After substitution, the 415*48011479Ssn199410 * message will appear as follows: 416*48011479Ssn199410 * rm: remove <filename> (y/n)? 417*48011479Ssn199410 * For example, in German, this will appear as 418*48011479Ssn199410 * rm: l�schen <filename> (j/n)? 419*48011479Ssn199410 * where j=ja, n=nein, <filename>=the file to be removed 420*48011479Ssn199410 */ 421*48011479Ssn199410 if (!confirm(stderr, 422*48011479Ssn199410 gettext("rm: remove %s (%s/%s)? "), 423*48011479Ssn199410 pathbuf, yeschr, nochr)) { 424*48011479Ssn199410 errcnt++; 425*48011479Ssn199410 return (0); 4267c478bd9Sstevel@tonic-gate } 4277c478bd9Sstevel@tonic-gate } 428*48011479Ssn199410 /* If it's empty we may still be able to rm it */ 429*48011479Ssn199410 if (unlinkat(caller->fd, entry, flag) == 0) 430*48011479Ssn199410 return (0); 431*48011479Ssn199410 if (interactive) 432*48011479Ssn199410 err = errno; 433*48011479Ssn199410 (void) fprintf(stderr, 434*48011479Ssn199410 interactive ? 435*48011479Ssn199410 gettext("rm: Unable to remove directory %s: %s\n") : 436*48011479Ssn199410 gettext("rm: cannot read directory %s: %s\n"), 437*48011479Ssn199410 pathbuf, strerror(err)); 438*48011479Ssn199410 errcnt++; 439*48011479Ssn199410 return (0); 440*48011479Ssn199410 } 4417c478bd9Sstevel@tonic-gate 4427c478bd9Sstevel@tonic-gate /* 443*48011479Ssn199410 * There is a race condition here too; if we open a directory 444*48011479Ssn199410 * we have to make sure it's still the same directory we 445*48011479Ssn199410 * stat'ed and checked against root earlier. Let's check. 4467c478bd9Sstevel@tonic-gate */ 447*48011479Ssn199410 if (fstat(frame.fd, &temp) != 0 || 448*48011479Ssn199410 frame.ino != temp.st_ino || 449*48011479Ssn199410 frame.dev != temp.st_dev) { 450*48011479Ssn199410 (void) fprintf(stderr, 451*48011479Ssn199410 gettext("rm: %s: directory renamed\n"), pathbuf); 452*48011479Ssn199410 closeframe(&frame); 453*48011479Ssn199410 errcnt++; 454*48011479Ssn199410 return (0); 4557c478bd9Sstevel@tonic-gate } 4567c478bd9Sstevel@tonic-gate 457*48011479Ssn199410 if (caller != &top) { 458*48011479Ssn199410 if (checkdir(caller, &frame) != 0) { 459*48011479Ssn199410 closeframe(&frame); 460*48011479Ssn199410 goto unlinkit; 4617c478bd9Sstevel@tonic-gate } 462*48011479Ssn199410 } 463*48011479Ssn199410 pushdir(&frame); 4647c478bd9Sstevel@tonic-gate 4657c478bd9Sstevel@tonic-gate /* 466*48011479Ssn199410 * rm() only returns -1 if popdir failed at some point; 467*48011479Ssn199410 * frame.dp is no longer reliable and we must drop out. 4687c478bd9Sstevel@tonic-gate */ 469*48011479Ssn199410 while ((dent = readdir(frame.dp)) != NULL) { 470*48011479Ssn199410 if (strcmp(dent->d_name, ".") == 0 || 471*48011479Ssn199410 strcmp(dent->d_name, "..") == 0) 4727c478bd9Sstevel@tonic-gate continue; 4737c478bd9Sstevel@tonic-gate 474*48011479Ssn199410 if (rm(dent->d_name, &frame) != 0) 475996aa816Ssn199410 break; 4767c478bd9Sstevel@tonic-gate } 4777c478bd9Sstevel@tonic-gate 478*48011479Ssn199410 if (popdir(dent == NULL) != 0) 479996aa816Ssn199410 return (-1); 4807c478bd9Sstevel@tonic-gate 4817c478bd9Sstevel@tonic-gate /* 482*48011479Ssn199410 * We recursed and the subdirectory may have set the CANTCLOSE 483*48011479Ssn199410 * flag; we need to clear it except for &top. 484*48011479Ssn199410 * Recursion may have invalidated entry because of closedir(). 4857c478bd9Sstevel@tonic-gate */ 486*48011479Ssn199410 if (caller != &top) { 487*48011479Ssn199410 caller->flags &= ~DIR_CANTCLOSE; 488*48011479Ssn199410 entry = &pathbuf[caller->up->pathend + 1]; 4897c478bd9Sstevel@tonic-gate } 490*48011479Ssn199410 } else { 491*48011479Ssn199410 flag = 0; 4927c478bd9Sstevel@tonic-gate } 493*48011479Ssn199410 unlinkit: 4947c478bd9Sstevel@tonic-gate /* 4957c478bd9Sstevel@tonic-gate * If interactive, ask for acknowledgement. 4967c478bd9Sstevel@tonic-gate */ 4977c478bd9Sstevel@tonic-gate if (interactive) { 498*48011479Ssn199410 if (!confirm(stderr, gettext("rm: remove %s (%s/%s)? "), 499*48011479Ssn199410 pathbuf, yeschr, nochr)) { 500*48011479Ssn199410 return (0); 5017c478bd9Sstevel@tonic-gate } 502*48011479Ssn199410 } else if (!silent && flag == 0) { 503*48011479Ssn199410 /* 504*48011479Ssn199410 * If not silent, and stdin is a terminal, and there's 505*48011479Ssn199410 * no write access, and the file isn't a symbolic link, 506*48011479Ssn199410 * ask for permission. If flag is set, then we know it's 507*48011479Ssn199410 * a directory so we skip this test as it was done above. 508*48011479Ssn199410 * 509*48011479Ssn199410 * TRANSLATION_NOTE - The following message will contain the 510*48011479Ssn199410 * first character of the strings for "yes" and "no" defined 511*48011479Ssn199410 * in the file "nl_langinfo.po". After substitution, the 512*48011479Ssn199410 * message will appear as follows: 513*48011479Ssn199410 * rm: <filename>: override protection XXX (y/n)? 514*48011479Ssn199410 * where XXX is the permission mode bits of the file in octal 515*48011479Ssn199410 * and <filename> is the file to be removed 516*48011479Ssn199410 * 517*48011479Ssn199410 */ 518*48011479Ssn199410 if (ontty && !S_ISLNK(temp.st_mode) && 519*48011479Ssn199410 __accessat(caller->fd, entry, W_OK|E_OK) != 0 && 520*48011479Ssn199410 !confirm(stdout, 521*48011479Ssn199410 gettext("rm: %s: override protection %o (%s/%s)? "), 522*48011479Ssn199410 pathbuf, temp.st_mode & 0777, yeschr, nochr)) { 523*48011479Ssn199410 return (0); 5247c478bd9Sstevel@tonic-gate } 5257c478bd9Sstevel@tonic-gate } 5267c478bd9Sstevel@tonic-gate 527*48011479Ssn199410 if (unlinkat(caller->fd, entry, flag) != 0) { 528*48011479Ssn199410 err = errno; 529*48011479Ssn199410 if (err == ENOENT) 530*48011479Ssn199410 return (0); 531*48011479Ssn199410 532*48011479Ssn199410 if (flag != 0) { 533*48011479Ssn199410 if (err == EINVAL) { 534*48011479Ssn199410 (void) fprintf(stderr, gettext( 535*48011479Ssn199410 "rm: Cannot remove any directory in the " 536*48011479Ssn199410 "path of the current working directory\n" 537*48011479Ssn199410 "%s\n"), pathbuf); 538*48011479Ssn199410 } else { 539*48011479Ssn199410 if (err == EEXIST) 540*48011479Ssn199410 err = ENOTEMPTY; 541*48011479Ssn199410 (void) fprintf(stderr, 542*48011479Ssn199410 gettext("rm: Unable to remove directory %s:" 543*48011479Ssn199410 " %s\n"), pathbuf, strerror(err)); 544*48011479Ssn199410 } 545*48011479Ssn199410 } else { 546*48011479Ssn199410 #ifndef XPG4 547*48011479Ssn199410 if (!silent || interactive) { 548*48011479Ssn199410 #endif 549*48011479Ssn199410 550*48011479Ssn199410 (void) fprintf(stderr, 551*48011479Ssn199410 gettext("rm: %s not removed: %s\n"), 552*48011479Ssn199410 pathbuf, strerror(err)); 553*48011479Ssn199410 #ifndef XPG4 554*48011479Ssn199410 } 555*48011479Ssn199410 #endif 556*48011479Ssn199410 } 557*48011479Ssn199410 errcnt++; 558*48011479Ssn199410 } 559*48011479Ssn199410 return (0); 560*48011479Ssn199410 } 5617c478bd9Sstevel@tonic-gate 5627c478bd9Sstevel@tonic-gate static int 5637c478bd9Sstevel@tonic-gate yes(void) 5647c478bd9Sstevel@tonic-gate { 5657c478bd9Sstevel@tonic-gate int i, b; 5667c478bd9Sstevel@tonic-gate char ans[SCHAR_MAX + 1]; 5677c478bd9Sstevel@tonic-gate 5687c478bd9Sstevel@tonic-gate for (i = 0; ; i++) { 5697c478bd9Sstevel@tonic-gate b = getchar(); 5707c478bd9Sstevel@tonic-gate if (b == '\n' || b == '\0' || b == EOF) { 5717c478bd9Sstevel@tonic-gate ans[i] = 0; 5727c478bd9Sstevel@tonic-gate break; 5737c478bd9Sstevel@tonic-gate } 5747c478bd9Sstevel@tonic-gate if (i < SCHAR_MAX) 575*48011479Ssn199410 ans[i] = (char)b; 5767c478bd9Sstevel@tonic-gate } 5777c478bd9Sstevel@tonic-gate if (i >= SCHAR_MAX) { 5787c478bd9Sstevel@tonic-gate i = SCHAR_MAX; 5797c478bd9Sstevel@tonic-gate ans[SCHAR_MAX] = 0; 5807c478bd9Sstevel@tonic-gate } 5817c478bd9Sstevel@tonic-gate if ((i == 0) | (strncmp(yeschr, ans, i))) 5827c478bd9Sstevel@tonic-gate return (0); 5837c478bd9Sstevel@tonic-gate return (1); 5847c478bd9Sstevel@tonic-gate } 5857c478bd9Sstevel@tonic-gate 5867c478bd9Sstevel@tonic-gate static int 587*48011479Ssn199410 confirm(FILE *fp, const char *q, ...) 5887c478bd9Sstevel@tonic-gate { 589*48011479Ssn199410 va_list ap; 5907c478bd9Sstevel@tonic-gate 591*48011479Ssn199410 va_start(ap, q); 592*48011479Ssn199410 (void) vfprintf(fp, q, ap); 593*48011479Ssn199410 va_end(ap); 594*48011479Ssn199410 return (yes()); 5957c478bd9Sstevel@tonic-gate } 5967c478bd9Sstevel@tonic-gate 597*48011479Ssn199410 static void 598*48011479Ssn199410 memerror(void) 599*48011479Ssn199410 { 600*48011479Ssn199410 (void) fprintf(stderr, gettext("rm: Insufficient memory.\n")); 601*48011479Ssn199410 exit(1); 602*48011479Ssn199410 } 6037c478bd9Sstevel@tonic-gate 6047c478bd9Sstevel@tonic-gate /* 605*48011479Ssn199410 * If we can't stat "..", it's either not there or we can't search 606*48011479Ssn199410 * the current directory; in that case we can't return back through 607*48011479Ssn199410 * "..", so we need to keep the parent open. 608*48011479Ssn199410 * Check that we came from "..", if not then this directory entry is an 609*48011479Ssn199410 * additional link and there is risk of a filesystem cycle and we also 610*48011479Ssn199410 * can't go back up through ".." and we keep the directory open. 6117c478bd9Sstevel@tonic-gate */ 612*48011479Ssn199410 static int 613*48011479Ssn199410 checkdir(struct dlist *caller, struct dlist *frmp) 614*48011479Ssn199410 { 615*48011479Ssn199410 struct stat up; 616*48011479Ssn199410 struct dlist *ptr; 617*48011479Ssn199410 618*48011479Ssn199410 if (fstatat(frmp->fd, "..", &up, 0) != 0) { 619*48011479Ssn199410 caller->flags |= DIR_CANTCLOSE; 620*48011479Ssn199410 return (0); 621*48011479Ssn199410 } else if (up.st_ino == caller->ino && up.st_dev == caller->dev) { 6227c478bd9Sstevel@tonic-gate return (0); 6237c478bd9Sstevel@tonic-gate } 6247c478bd9Sstevel@tonic-gate 625*48011479Ssn199410 /* Directory hard link, check cycle */ 626*48011479Ssn199410 for (ptr = caller; ptr != NULL; ptr = ptr->up) { 627*48011479Ssn199410 if (frmp->dev == ptr->dev && frmp->ino == ptr->ino) { 6287c478bd9Sstevel@tonic-gate (void) fprintf(stderr, 629*48011479Ssn199410 gettext("rm: cycle detected for %s\n"), pathbuf); 630*48011479Ssn199410 errcnt++; 631*48011479Ssn199410 return (-1); 6327c478bd9Sstevel@tonic-gate } 6337c478bd9Sstevel@tonic-gate } 634*48011479Ssn199410 caller->flags |= DIR_CANTCLOSE; 635996aa816Ssn199410 return (0); 6367c478bd9Sstevel@tonic-gate } 637