rm.c (996aa81675f6b63ed02041243b97e61ee7bd51d2) | rm.c (48011479cce51f5534141868012dcb9828a0fd63) |
---|---|
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 --- 6 unchanged lines hidden (view full) --- 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/* | 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 --- 6 unchanged lines hidden (view full) --- 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 2006 Sun Microsystems, Inc. All rights reserved. | 23 * Copyright 2007 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#pragma ident "%Z%%M% %I% %E% SMI" 31 32/* 33 * rm [-fiRr] file ... 34 */ 35 | 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#pragma ident "%Z%%M% %I% %E% SMI" 31 32/* 33 * rm [-fiRr] file ... 34 */ 35 |
36#include <stdio.h> 37#include <fcntl.h> 38#include <string.h> 39#include <sys/types.h> | 36#include <sys/param.h> |
40#include <sys/stat.h> 41#include <dirent.h> | 37#include <sys/stat.h> 38#include <dirent.h> |
39#include <errno.h> 40#include <fcntl.h> 41#include <langinfo.h> |
|
42#include <limits.h> 43#include <locale.h> | 42#include <limits.h> 43#include <locale.h> |
44#include <langinfo.h> 45#include <unistd.h> | 44#include <stdarg.h> 45#include <stdio.h> |
46#include <stdlib.h> | 46#include <stdlib.h> |
47#include <errno.h> 48#include <sys/resource.h> 49#include <sys/avl.h> 50#include <libcmdutils.h> | 47#include <string.h> 48#include <unistd.h> 49#include <values.h> |
51 | 50 |
52#define ARGCNT 5 /* Number of arguments */ 53#define CHILD 0 54#define DIRECTORY ((buffer.st_mode&S_IFMT) == S_IFDIR) 55#define SYMLINK ((buffer.st_mode&S_IFMT) == S_IFLNK) 56#define FAIL -1 57#define MAXFORK 100 /* Maximum number of forking attempts */ 58#define NAMESIZE MAXNAMLEN + 1 /* "/" + (file name size) */ 59#define TRUE 1 60#define FALSE 0 61#define WRITE 02 62#define SEARCH 07 | 51#define E_OK 010 /* make __accessat() use effective ids */ |
63 | 52 |
64static int errcode; 65static int interactive, recursive, silent; /* flags for command line options */ | 53#define DIR_CANTCLOSE 1 |
66 | 54 |
67static int rm(char *, int); 68static int undir(char *, int, dev_t, ino_t); 69static int yes(void); 70static int mypath(dev_t, ino_t); | 55static struct stat rootdir; |
71 | 56 |
72static char yeschr[SCHAR_MAX + 2]; 73static char nochr[SCHAR_MAX + 2]; | 57struct dlist { 58 int fd; /* Stores directory fd */ 59 int flags; /* DIR_* Flags */ 60 DIR *dp; /* Open directory (opened with fd) */ 61 long diroff; /* Saved directory offset when closing */ 62 struct dlist *up; /* Up one step in the tree (toward "/") */ 63 struct dlist *down; /* Down one step in the tree */ 64 ino_t ino; /* st_ino of directory */ 65 dev_t dev; /* st_dev of directory */ 66 int pathend; /* Offset of name end in the pathbuffer */ 67}; |
74 | 68 |
75static char *fullpath; 76static int initdirfd; | 69static struct dlist top = { 70 (int)AT_FDCWD, 71 DIR_CANTCLOSE, 72}; |
77 | 73 |
78static void push_name(char *name, int first); 79static int pop_name(int first); 80static void force_chdir(char *); 81static void ch_dir(char *); 82static char *get_filename(char *name); 83static void chdir_init(void); 84static void check_initdir(void); 85static void cleanup(void); | 74static char yeschr[SCHAR_MAX + 2]; 75static char nochr[SCHAR_MAX + 2]; |
86 | 76 |
87static char *cwd; /* pathname of init dir, from getcwd() */ 88static rlim_t maxfiles; /* maximum number of open files */ 89static int first_dir = 1; /* flag set when first trying to remove a dir */ 90 /* flag set when can't get dev/inode of a parent dir */ 91static int parent_err = 0; 92static avl_tree_t *tree; /* tree to keep track of nodes visited */ | 77static struct dlist *cur, *rec; |
93 | 78 |
94struct dir_id { 95 dev_t dev; 96 ino_t inode; 97 struct dir_id *next; 98}; | 79static int rm(const char *, struct dlist *); 80static int confirm(FILE *, const char *, ...); 81static void memerror(void); 82static int checkdir(struct dlist *, struct dlist *); 83static int errcnt; 84static boolean_t silent, interactive, recursive, ontty; |
99 | 85 |
100 /* 101 * initdir is the first of a linked list of structures 102 * containing unique identifying device and inode numbers for 103 * each directory, from the initial dir up to the root. 104 * current_dir is a pointer to the most recent directory pushed 105 * on during a recursive rm() call. 106 */ 107static struct dir_id initdir, *current_dir; | 86static char *pathbuf; 87static size_t pathbuflen; |
108 | 88 |
89static int maxfds = MAXINT; 90static int nfds; 91 92extern int __accessat(int, const char *, int); 93 |
|
109int | 94int |
110main(int argc, char *argv[]) | 95main(int argc, char **argv) |
111{ | 96{ |
112 extern int optind; 113 int errflg = 0; 114 int c; 115 struct rlimit rl; | 97 int errflg = 0; 98 int c; |
116 117 (void) setlocale(LC_ALL, ""); 118#if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ 119#define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ 120#endif 121 (void) textdomain(TEXT_DOMAIN); 122 123 (void) strncpy(yeschr, nl_langinfo(YESSTR), SCHAR_MAX + 1); 124 (void) strncpy(nochr, nl_langinfo(NOSTR), SCHAR_MAX + 1); 125 126 while ((c = getopt(argc, argv, "frRi")) != EOF) 127 switch (c) { 128 case 'f': | 99 100 (void) setlocale(LC_ALL, ""); 101#if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ 102#define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ 103#endif 104 (void) textdomain(TEXT_DOMAIN); 105 106 (void) strncpy(yeschr, nl_langinfo(YESSTR), SCHAR_MAX + 1); 107 (void) strncpy(nochr, nl_langinfo(NOSTR), SCHAR_MAX + 1); 108 109 while ((c = getopt(argc, argv, "frRi")) != EOF) 110 switch (c) { 111 case 'f': |
129 silent = TRUE; | 112 silent = B_TRUE; |
130#ifdef XPG4 | 113#ifdef XPG4 |
131 interactive = FALSE; | 114 interactive = B_FALSE; |
132#endif 133 break; 134 case 'i': | 115#endif 116 break; 117 case 'i': |
135 interactive = TRUE; | 118 interactive = B_TRUE; |
136#ifdef XPG4 | 119#ifdef XPG4 |
137 silent = FALSE; | 120 silent = B_FALSE; |
138#endif 139 break; 140 case 'r': 141 case 'R': | 121#endif 122 break; 123 case 'r': 124 case 'R': |
142 recursive = TRUE; | 125 recursive = B_TRUE; |
143 break; 144 case '?': 145 errflg = 1; 146 break; 147 } 148 149 /* 150 * For BSD compatibility allow '-' to delimit the end --- 5 unchanged lines hidden (view full) --- 156 strcmp(argv[optind], "-") == 0 && 157 strcmp(argv[optind - 1], "--") != 0) 158 optind++; 159 160 argc -= optind; 161 argv = &argv[optind]; 162 163 if ((argc < 1 && !silent) || errflg) { | 126 break; 127 case '?': 128 errflg = 1; 129 break; 130 } 131 132 /* 133 * For BSD compatibility allow '-' to delimit the end --- 5 unchanged lines hidden (view full) --- 139 strcmp(argv[optind], "-") == 0 && 140 strcmp(argv[optind - 1], "--") != 0) 141 optind++; 142 143 argc -= optind; 144 argv = &argv[optind]; 145 146 if ((argc < 1 && !silent) || errflg) { |
164 (void) fprintf(stderr, 165 gettext("usage: rm [-fiRr] file ...\n")); | 147 (void) fprintf(stderr, gettext("usage: rm [-fiRr] file ...\n")); |
166 exit(2); 167 } 168 | 148 exit(2); 149 } 150 |
169 if (getrlimit(RLIMIT_NOFILE, &rl)) { 170 perror("getrlimit"); | 151 ontty = isatty(STDIN_FILENO) != 0; 152 153 if (recursive && stat("/", &rootdir) != 0) { 154 (void) fprintf(stderr, 155 gettext("rm: cannot stat root directory: %s\n"), 156 strerror(errno)); |
171 exit(2); | 157 exit(2); |
172 } else 173 maxfiles = rl.rlim_cur - 2; | 158 } |
174 | 159 |
175 while (argc-- > 0) { 176 tree = NULL; 177 /* Retry if rm() fails due to bad chdir */ 178 while (rm(*argv, 1) < 0) | 160 for (; *argv != NULL; argv++) { 161 char *p = strrchr(*argv, '/'); 162 if (p == NULL) 163 p = *argv; 164 else 165 p = p + 1; 166 if (strcmp(p, ".") == 0 || strcmp(p, "..") == 0) { 167 (void) fprintf(stderr, 168 gettext("rm of %s is not allowed\n"), *argv); 169 errcnt++; 170 continue; 171 } 172 /* Retry when we can't walk back up. */ 173 while (rm(*argv, rec = cur = &top) != 0) |
179 ; | 174 ; |
180 argv++; 181 destroy_tree(tree); | |
182 } 183 | 175 } 176 |
184 cleanup(); 185 return (errcode ? 2 : 0); 186 /* NOTREACHED */ | 177 return (errcnt != 0 ? 2 : 0); |
187} 188 | 178} 179 |
189static int 190rm(char *path, int first) | 180static void 181pushfilename(const char *fname) |
191{ | 182{ |
192 struct stat buffer; 193 char *filepath; 194 char *p; 195 char resolved_path[PATH_MAX]; | 183 char *p; 184 const char *q = fname; |
196 | 185 |
197 /* 198 * Check file to see if it exists. 199 */ 200 if (lstat(path, &buffer) == FAIL) { 201 if (!silent) { 202 perror(path); 203 ++errcode; | 186 if (cur == &top) { 187 p = pathbuf; 188 } else { 189 p = pathbuf + cur->up->pathend; 190 *p++ = '/'; 191 } 192 while (*q != '\0') { 193 if (p - pathbuf + 2 >= pathbuflen) { 194 char *np; 195 pathbuflen += MAXPATHLEN; 196 np = realloc(pathbuf, pathbuflen); 197 if (np == NULL) 198 memerror(); 199 p = np + (p - pathbuf); 200 pathbuf = np; |
204 } | 201 } |
205 return (0); | 202 *p++ = *q++; |
206 } | 203 } |
204 *p = '\0'; 205 cur->pathend = p - pathbuf; 206} |
|
207 | 207 |
208 /* prevent removal of / but allow removal of sym-links */ 209 if (!S_ISLNK(buffer.st_mode) && realpath(path, resolved_path) != NULL && 210 strcmp(resolved_path, "/") == 0) { 211 (void) fprintf(stderr, 212 gettext("rm of %s is not allowed\n"), resolved_path); 213 errcode++; 214 return (0); | 208static void 209closeframe(struct dlist *frm) 210{ 211 if (frm->dp != NULL) { 212 (void) closedir(frm->dp); 213 nfds--; 214 frm->dp = NULL; 215 frm->fd = -1; |
215 } | 216 } |
217} |
|
216 | 218 |
217 /* prevent removal of . or .. (directly) */ 218 if (p = strrchr(path, '/')) 219 p++; 220 else 221 p = path; 222 if (strcmp(".", p) == 0 || strcmp("..", p) == 0) { 223 (void) fprintf(stderr, 224 gettext("rm of %s is not allowed\n"), path); 225 errcode++; 226 return (0); | 219static int 220reclaim(void) 221{ 222 while (rec != NULL && (rec->flags & DIR_CANTCLOSE) != 0) 223 rec = rec->down; 224 if (rec == NULL || rec == cur || rec->dp == NULL) 225 return (-1); 226 rec->diroff = telldir(rec->dp); 227 closeframe(rec); 228 rec = rec->down; 229 return (0); 230} 231 232static void 233pushdir(struct dlist *frm) 234{ 235 frm->up = cur; 236 frm->down = NULL; 237 cur->down = frm; 238 cur = frm; 239} 240 241static int 242opendirat(int dirfd, const char *entry, struct dlist *frm) 243{ 244 int fd; 245 246 if (nfds >= maxfds) 247 (void) reclaim(); 248 249 while ((fd = openat(dirfd, entry, O_RDONLY|O_NONBLOCK)) == -1 && 250 errno == EMFILE) { 251 if (nfds < maxfds) 252 maxfds = nfds; 253 if (reclaim() != 0) 254 return (-1); |
227 } | 255 } |
256 if (fd < 0) 257 return (-1); 258 frm->fd = fd; 259 frm->dp = fdopendir(fd); 260 if (frm->dp == NULL) { 261 (void) close(fd); 262 return (-1); 263 } 264 nfds++; 265 return (0); 266} 267 268/* 269 * Since we never pop the top frame, cur->up can never be NULL. 270 * If we pop beyond a frame we closed, we try to reopen "..". 271 */ 272static int 273popdir(boolean_t noerror) 274{ 275 struct stat buf; 276 int ret = noerror ? 0 : -1; 277 pathbuf[cur->up->pathend] = '\0'; 278 279 if (noerror && cur->up->fd == -1) { 280 rec = cur->up; 281 if (opendirat(cur->fd, "..", rec) != 0 || 282 fstat(rec->fd, &buf) != 0) { 283 (void) fprintf(stderr, 284 gettext("rm: cannot reopen %s: %s\n"), 285 pathbuf, strerror(errno)); 286 exit(2); 287 } 288 if (rec->ino != buf.st_ino || rec->dev != buf.st_dev) { 289 (void) fprintf(stderr, gettext("rm: WARNING: " 290 "The directory %s was moved or linked to " 291 "another directory during the execution of rm\n"), 292 pathbuf); 293 closeframe(rec); 294 ret = -1; 295 } else { 296 /* If telldir failed, we take it from the top. */ 297 if (rec->diroff != -1) 298 seekdir(rec->dp, rec->diroff); 299 } 300 } else if (rec == cur) 301 rec = cur->up; 302 closeframe(cur); 303 cur = cur->up; 304 cur->down = NULL; 305 return (ret); 306} 307 308/* 309 * The stack frame of this function is minimized so that we can 310 * recurse quite a bit before we overflow the stack; around 311 * 30,000-40,000 nested directories can be removed with the default 312 * stack limit. 313 */ 314static int 315rm(const char *entry, struct dlist *caller) 316{ 317 struct dlist frame; 318 int flag; 319 struct stat temp; 320 struct dirent *dent; 321 int err; 322 |
|
228 /* | 323 /* |
229 * If it's a directory, remove its contents. | 324 * Construct the pathname: note that the entry may live in memory 325 * allocated by readdir and that after return from recursion 326 * the memory is no longer valid. So after the recursive rm() 327 * call, we use the global pathbuf instead of the entry argument. |
230 */ | 328 */ |
231 if (DIRECTORY) { | 329 pushfilename(entry); 330 331 if (fstatat(caller->fd, entry, &temp, AT_SYMLINK_NOFOLLOW) != 0) { 332 if (!silent) { 333 (void) fprintf(stderr, "rm: %s: %s\n", pathbuf, 334 strerror(errno)); 335 errcnt++; 336 } 337 return (0); 338 } 339 340 if (S_ISDIR(temp.st_mode)) { |
232 /* 233 * If "-r" wasn't specified, trying to remove directories 234 * is an error. 235 */ 236 if (!recursive) { 237 (void) fprintf(stderr, | 341 /* 342 * If "-r" wasn't specified, trying to remove directories 343 * is an error. 344 */ 345 if (!recursive) { 346 (void) fprintf(stderr, |
238 gettext("rm: %s is a directory\n"), path); 239 ++errcode; | 347 gettext("rm: %s is a directory\n"), pathbuf); 348 errcnt++; |
240 return (0); 241 } 242 | 349 return (0); 350 } 351 |
243 if (first_dir) { 244 check_initdir(); 245 current_dir = NULL; 246 first_dir = 0; 247 } 248 249 return (undir(path, first, buffer.st_dev, buffer.st_ino)); 250 } 251 252 filepath = get_filename(path); 253 254 /* 255 * If interactive, ask for acknowledgement. 256 * 257 * TRANSLATION_NOTE - The following message will contain the 258 * first character of the strings for "yes" and "no" defined 259 * in the file "nl_langinfo.po". After substitution, the 260 * message will appear as follows: 261 * rm: remove <filename> (y/n)? 262 * For example, in German, this will appear as 263 * rm: l�schen <filename> (j/n)? 264 * where j=ja, n=nein, <filename>=the file to be removed 265 * 266 */ 267 268 269 if (interactive) { 270 (void) fprintf(stderr, gettext("rm: remove %s (%s/%s)? "), 271 filepath, yeschr, nochr); 272 if (!yes()) { 273 free(filepath); | 352 if (temp.st_ino == rootdir.st_ino && 353 temp.st_dev == rootdir.st_dev) { 354 (void) fprintf(stderr, 355 gettext("rm of %s is not allowed\n"), "/"); 356 errcnt++; |
274 return (0); 275 } | 357 return (0); 358 } |
276 } else if (!silent) { | |
277 /* | 359 /* |
278 * If not silent, and stdin is a terminal, and there's 279 * no write access, and the file isn't a symbolic link, 280 * ask for permission. 281 * | |
282 * TRANSLATION_NOTE - The following message will contain the 283 * first character of the strings for "yes" and "no" defined 284 * in the file "nl_langinfo.po". After substitution, the 285 * message will appear as follows: | 360 * TRANSLATION_NOTE - The following message will contain the 361 * first character of the strings for "yes" and "no" defined 362 * in the file "nl_langinfo.po". After substitution, the 363 * message will appear as follows: |
286 * rm: <filename>: override protection XXX (y/n)? 287 * where XXX is the permission mode bits of the file in octal 288 * and <filename> is the file to be removed | 364 * rm: examine files in directory <directoryname> (y/n)? 365 * where <directoryname> is the directory to be removed |
289 * 290 */ | 366 * 367 */ |
291 if (!SYMLINK && access(path, W_OK) == FAIL && 292 isatty(fileno(stdin))) { 293 (void) printf( 294 gettext("rm: %s: override protection %o (%s/%s)? "), 295 filepath, buffer.st_mode & 0777, yeschr, nochr); 296 /* 297 * If permission isn't given, skip the file. 298 */ 299 if (!yes()) { 300 free(filepath); 301 return (0); 302 } | 368 if (interactive && !confirm(stderr, 369 gettext("rm: examine files in directory %s (%s/%s)? "), 370 pathbuf, yeschr, nochr)) { 371 return (0); |
303 } | 372 } |
304 } | |
305 | 373 |
306 /* 307 * If the unlink fails, inform the user. For /usr/bin/rm, only inform 308 * the user if interactive or not silent. 309 * If unlink fails with errno = ENOENT because file was removed 310 * in between the lstat call and unlink don't inform the user and 311 * don't change errcode. 312 */ | 374 frame.dev = temp.st_dev; 375 frame.ino = temp.st_ino; 376 frame.flags = 0; 377 flag = AT_REMOVEDIR; |
313 | 378 |
314 if (unlink(path) == FAIL) { 315 if (errno == ENOENT) { 316 free(filepath); | 379#ifdef XPG4 380 /* 381 * XCU4 and POSIX.2: If not interactive, check to see whether 382 * or not directory is readable or writable and if not, 383 * prompt user for response. 384 */ 385 if (ontty && !interactive && !silent && 386 __accessat(caller->fd, entry, W_OK|X_OK|E_OK) != 0 && 387 !confirm(stderr, 388 gettext("rm: examine files in directory %s (%s/%s)? "), 389 pathbuf, yeschr, nochr)) { |
317 return (0); 318 } | 390 return (0); 391 } |
319#ifndef XPG4 320 if (!silent || interactive) { | |
321#endif | 392#endif |
393 if (opendirat(caller->fd, entry, &frame) == -1) { 394 err = errno; 395 396 if (interactive) { 397 /* 398 * Print an error message that 399 * we could not read the directory 400 * as the user wanted to examine 401 * files in the directory. Only 402 * affect the error status if 403 * user doesn't want to remove the 404 * directory as we still may be able 405 * remove the directory successfully. 406 */ 407 (void) fprintf(stderr, gettext( 408 "rm: cannot read directory %s: %s\n"), 409 pathbuf, strerror(err)); 410 411/* 412 * TRANSLATION_NOTE - The following message will contain the 413 * first character of the strings for "yes" and "no" defined 414 * in the file "nl_langinfo.po". After substitution, the 415 * message will appear as follows: 416 * rm: remove <filename> (y/n)? 417 * For example, in German, this will appear as 418 * rm: l�schen <filename> (j/n)? 419 * where j=ja, n=nein, <filename>=the file to be removed 420 */ 421 if (!confirm(stderr, 422 gettext("rm: remove %s (%s/%s)? "), 423 pathbuf, yeschr, nochr)) { 424 errcnt++; 425 return (0); 426 } 427 } 428 /* If it's empty we may still be able to rm it */ 429 if (unlinkat(caller->fd, entry, flag) == 0) 430 return (0); 431 if (interactive) 432 err = errno; |
|
322 (void) fprintf(stderr, | 433 (void) fprintf(stderr, |
323 gettext("rm: %s not removed: "), filepath); 324 perror(""); 325#ifndef XPG4 | 434 interactive ? 435 gettext("rm: Unable to remove directory %s: %s\n") : 436 gettext("rm: cannot read directory %s: %s\n"), 437 pathbuf, strerror(err)); 438 errcnt++; 439 return (0); |
326 } | 440 } |
327#endif 328 ++errcode; 329 } | |
330 | 441 |
331 free(filepath); 332 return (0); 333} 334 335static int 336undir(char *path, int first, dev_t dev, ino_t ino) 337{ 338 char *newpath; 339 DIR *name; 340 struct dirent *direct; 341 int ismypath; 342 int ret; 343 int chdir_failed = 0; 344 int bad_chdir = 0; 345 size_t len; 346 347 push_name(path, first); 348 349 /* 350 * If interactive and this file isn't in the path of the 351 * current working directory, ask for acknowledgement. 352 * 353 * TRANSLATION_NOTE - The following message will contain the 354 * first character of the strings for "yes" and "no" defined 355 * in the file "nl_langinfo.po". After substitution, the 356 * message will appear as follows: 357 * rm: examine files in directory <directoryname> (y/n)? 358 * where <directoryname> is the directory to be removed 359 * 360 */ 361 ismypath = mypath(dev, ino); 362 if (interactive) { 363 (void) fprintf(stderr, 364 gettext("rm: examine files in directory %s (%s/%s)? "), 365 fullpath, yeschr, nochr); | |
366 /* | 442 /* |
367 * If the answer is no, skip the directory. | 443 * There is a race condition here too; if we open a directory 444 * we have to make sure it's still the same directory we 445 * stat'ed and checked against root earlier. Let's check. |
368 */ | 446 */ |
369 if (!yes()) 370 return (pop_name(first)); 371 } 372 373#ifdef XPG4 374 /* 375 * XCU4 and POSIX.2: If not interactive and file is not in the 376 * path of the current working directory, check to see whether 377 * or not directory is readable or writable and if not, 378 * prompt user for response. 379 */ 380 if (!interactive && !ismypath && 381 (access(path, W_OK|X_OK) == FAIL) && isatty(fileno(stdin))) { 382 if (!silent) { | 447 if (fstat(frame.fd, &temp) != 0 || 448 frame.ino != temp.st_ino || 449 frame.dev != temp.st_dev) { |
383 (void) fprintf(stderr, | 450 (void) fprintf(stderr, |
384 gettext( 385 "rm: examine files in directory %s (%s/%s)? "), 386 fullpath, yeschr, nochr); 387 /* 388 * If the answer is no, skip the directory. 389 */ 390 if (!yes()) 391 return (pop_name(first)); | 451 gettext("rm: %s: directory renamed\n"), pathbuf); 452 closeframe(&frame); 453 errcnt++; 454 return (0); |
392 } | 455 } |
393 } 394#endif | |
395 | 456 |
396 /* 397 * Add this node to the search tree so we don't 398 * get into a endless loop. If the add fails then 399 * we have visited this node before. 400 */ 401 ret = add_tnode(&tree, dev, ino); 402 if (ret != 1) { 403 if (ret == 0) { 404 (void) fprintf(stderr, 405 gettext("rm: cycle detected for %s\n"), 406 fullpath); 407 } else if (ret == -1) { 408 perror("rm"); | 457 if (caller != &top) { 458 if (checkdir(caller, &frame) != 0) { 459 closeframe(&frame); 460 goto unlinkit; 461 } |
409 } | 462 } |
410 errcode++; 411 return (pop_name(first)); 412 } | 463 pushdir(&frame); |
413 | 464 |
414 /* 415 * Open the directory for reading. 416 */ 417 if ((name = opendir(path)) == NULL) { 418 int saveerrno = errno; 419 | |
420 /* | 465 /* |
421 * If interactive, ask for acknowledgement. | 466 * rm() only returns -1 if popdir failed at some point; 467 * frame.dp is no longer reliable and we must drop out. |
422 */ | 468 */ |
423 if (interactive) { 424 /* 425 * Print an error message that 426 * we could not read the directory 427 * as the user wanted to examine 428 * files in the directory. Only 429 * affect the error status if 430 * user doesn't want to remove the 431 * directory as we still may be able 432 * remove the directory successfully. 433 */ 434 (void) fprintf(stderr, gettext( 435 "rm: cannot read directory %s: "), 436 fullpath); 437 errno = saveerrno; 438 perror(""); 439 (void) fprintf(stderr, gettext( 440 "rm: remove %s: (%s/%s)? "), 441 fullpath, yeschr, nochr); 442 if (!yes()) { 443 ++errcode; 444 return (pop_name(first)); 445 } | 469 while ((dent = readdir(frame.dp)) != NULL) { 470 if (strcmp(dent->d_name, ".") == 0 || 471 strcmp(dent->d_name, "..") == 0) 472 continue; 473 474 if (rm(dent->d_name, &frame) != 0) 475 break; |
446 } 447 | 476 } 477 |
478 if (popdir(dent == NULL) != 0) 479 return (-1); 480 |
|
448 /* | 481 /* |
449 * If the directory is empty, we may be able to 450 * go ahead and remove it. | 482 * We recursed and the subdirectory may have set the CANTCLOSE 483 * flag; we need to clear it except for &top. 484 * Recursion may have invalidated entry because of closedir(). |
451 */ | 485 */ |
452 if (rmdir(path) == FAIL) { 453 if (interactive) { 454 int rmdirerr = errno; 455 (void) fprintf(stderr, gettext( 456 "rm: Unable to remove directory %s: "), 457 fullpath); 458 errno = rmdirerr; 459 perror(""); 460 } else { 461 (void) fprintf(stderr, gettext( 462 "rm: cannot read directory %s: "), 463 fullpath); 464 errno = saveerrno; 465 perror(""); 466 } 467 ++errcode; | 486 if (caller != &top) { 487 caller->flags &= ~DIR_CANTCLOSE; 488 entry = &pathbuf[caller->up->pathend + 1]; |
468 } | 489 } |
469 470 /* Continue to next file/directory rather than exit */ 471 return (pop_name(first)); | 490 } else { 491 flag = 0; |
472 } | 492 } |
473 | 493unlinkit: |
474 /* | 494 /* |
475 * XCU4 requires that rm -r descend the directory 476 * hierarchy without regard to PATH_MAX. If we can't 477 * chdir() do not increment error counter and do not 478 * print message. 479 * 480 * However, if we cannot chdir because someone has taken away 481 * execute access we may still be able to delete the directory 482 * if it's empty. The old rm could do this. | 495 * If interactive, ask for acknowledgement. |
483 */ | 496 */ |
484 485 if (chdir(path) == -1) { 486 chdir_failed = 1; 487 } 488 489 /* 490 * Read every directory entry. 491 */ 492 while ((direct = readdir(name)) != NULL) { | 497 if (interactive) { 498 if (!confirm(stderr, gettext("rm: remove %s (%s/%s)? "), 499 pathbuf, yeschr, nochr)) { 500 return (0); 501 } 502 } else if (!silent && flag == 0) { |
493 /* | 503 /* |
494 * Ignore "." and ".." entries. | 504 * If not silent, and stdin is a terminal, and there's 505 * no write access, and the file isn't a symbolic link, 506 * ask for permission. If flag is set, then we know it's 507 * a directory so we skip this test as it was done above. 508 * 509 * TRANSLATION_NOTE - The following message will contain the 510 * first character of the strings for "yes" and "no" defined 511 * in the file "nl_langinfo.po". After substitution, the 512 * message will appear as follows: 513 * rm: <filename>: override protection XXX (y/n)? 514 * where XXX is the permission mode bits of the file in octal 515 * and <filename> is the file to be removed 516 * |
495 */ | 517 */ |
496 if (strcmp(direct->d_name, ".") == 0 || 497 strcmp(direct->d_name, "..") == 0) 498 continue; 499 /* 500 * Try to remove the file. 501 */ 502 len = strlen(direct->d_name) + 1; 503 if (chdir_failed) { 504 len += strlen(path) + 2; | 518 if (ontty && !S_ISLNK(temp.st_mode) && 519 __accessat(caller->fd, entry, W_OK|E_OK) != 0 && 520 !confirm(stdout, 521 gettext("rm: %s: override protection %o (%s/%s)? "), 522 pathbuf, temp.st_mode & 0777, yeschr, nochr)) { 523 return (0); |
505 } | 524 } |
525 } |
|
506 | 526 |
507 newpath = malloc(len); 508 if (newpath == NULL) { 509 (void) fprintf(stderr, 510 gettext("rm: Insufficient memory.\n")); 511 cleanup(); 512 exit(1); 513 } | 527 if (unlinkat(caller->fd, entry, flag) != 0) { 528 err = errno; 529 if (err == ENOENT) 530 return (0); |
514 | 531 |
515 if (!chdir_failed) { 516 (void) strcpy(newpath, direct->d_name); | 532 if (flag != 0) { 533 if (err == EINVAL) { 534 (void) fprintf(stderr, gettext( 535 "rm: Cannot remove any directory in the " 536 "path of the current working directory\n" 537 "%s\n"), pathbuf); 538 } else { 539 if (err == EEXIST) 540 err = ENOTEMPTY; 541 (void) fprintf(stderr, 542 gettext("rm: Unable to remove directory %s:" 543 " %s\n"), pathbuf, strerror(err)); 544 } |
517 } else { | 545 } else { |
518 (void) snprintf(newpath, len, "%s/%s", 519 path, direct->d_name); 520 } | 546#ifndef XPG4 547 if (!silent || interactive) { 548#endif |
521 | 549 |
522 523 /* 524 * If a spare file descriptor is available, just call the 525 * "rm" function with the file name; otherwise close the 526 * directory and reopen it when the child is removed. 527 */ 528 if (name->dd_fd >= maxfiles) { 529 (void) closedir(name); 530 if (rm(newpath, 0) < 0) 531 bad_chdir = -1; 532 if (!chdir_failed) 533 name = opendir("."); 534 else 535 name = opendir(path); 536 if (name == NULL) { | |
537 (void) fprintf(stderr, | 550 (void) fprintf(stderr, |
538 gettext("rm: cannot read directory %s: "), 539 fullpath); 540 perror(""); 541 cleanup(); 542 exit(2); | 551 gettext("rm: %s not removed: %s\n"), 552 pathbuf, strerror(err)); 553#ifndef XPG4 |
543 } | 554 } |
544 } else if (rm(newpath, 0) < 0) 545 bad_chdir = -1; 546 547 free(newpath); 548 if (bad_chdir) 549 break; 550 } 551 552 /* 553 * Close the directory we just finished reading. 554 */ 555 (void) closedir(name); 556 if (bad_chdir) 557 return (-1); 558 559 /* 560 * The contents of the directory have been removed. If the 561 * directory itself is in the path of the current working 562 * directory, don't try to remove it. 563 * When the directory itself is the current working directory, mypath() 564 * has a return code == 2. 565 * 566 * XCU4: Because we've descended the directory hierarchy in order 567 * to avoid PATH_MAX limitation, we must now start ascending 568 * one level at a time and remove files/directories. 569 */ 570 571 if (!chdir_failed) { 572 if (first) 573 chdir_init(); 574 else if (chdir("..") == -1) { 575 (void) fprintf(stderr, 576 gettext("rm: cannot change to parent of " 577 "directory %s: "), 578 fullpath); 579 perror(""); 580 cleanup(); 581 exit(2); | 555#endif |
582 } | 556 } |
557 errcnt++; |
|
583 } | 558 } |
584 585 switch (ismypath) { 586 case 3: 587 return (pop_name(first)); 588 case 2: 589 (void) fprintf(stderr, 590 gettext("rm: Cannot remove any directory in the path " 591 "of the current working directory\n%s\n"), fullpath); 592 ++errcode; 593 return (pop_name(first)); 594 case 1: 595 ++errcode; 596 return (pop_name(first)); 597 case 0: 598 break; 599 } 600 601 /* 602 * If interactive, ask for acknowledgement. 603 */ 604 if (interactive) { 605 (void) fprintf(stderr, gettext("rm: remove %s: (%s/%s)? "), 606 fullpath, yeschr, nochr); 607 if (!yes()) 608 return (pop_name(first)); 609 } 610 if (rmdir(path) == FAIL) { 611 (void) fprintf(stderr, 612 gettext("rm: Unable to remove directory %s: "), 613 fullpath); 614 perror(""); 615 ++errcode; 616 } 617 return (pop_name(first)); | 559 return (0); |
618} 619 | 560} 561 |
620 | |
621static int 622yes(void) 623{ | 562static int 563yes(void) 564{ |
624 int i, b; 625 char ans[SCHAR_MAX + 1]; | 565 int i, b; 566 char ans[SCHAR_MAX + 1]; |
626 627 for (i = 0; ; i++) { 628 b = getchar(); 629 if (b == '\n' || b == '\0' || b == EOF) { 630 ans[i] = 0; 631 break; 632 } 633 if (i < SCHAR_MAX) | 567 568 for (i = 0; ; i++) { 569 b = getchar(); 570 if (b == '\n' || b == '\0' || b == EOF) { 571 ans[i] = 0; 572 break; 573 } 574 if (i < SCHAR_MAX) |
634 ans[i] = b; | 575 ans[i] = (char)b; |
635 } 636 if (i >= SCHAR_MAX) { 637 i = SCHAR_MAX; 638 ans[SCHAR_MAX] = 0; 639 } 640 if ((i == 0) | (strncmp(yeschr, ans, i))) 641 return (0); 642 return (1); 643} 644 | 576 } 577 if (i >= SCHAR_MAX) { 578 i = SCHAR_MAX; 579 ans[SCHAR_MAX] = 0; 580 } 581 if ((i == 0) | (strncmp(yeschr, ans, i))) 582 return (0); 583 return (1); 584} 585 |
645 | |
646static int | 586static int |
647mypath(dev_t dev, ino_t ino) | 587confirm(FILE *fp, const char *q, ...) |
648{ | 588{ |
649 struct dir_id *curdir; | 589 va_list ap; |
650 | 590 |
651 /* 652 * Check to see if this is our current directory 653 * Indicated by return 2; 654 */ 655 if (dev == initdir.dev && ino == initdir.inode) { 656 return (2); 657 } 658 659 curdir = initdir.next; 660 661 while (curdir != NULL) { 662 /* 663 * If we find a match, the directory (dev, ino) passed to 664 * mypath() is an ancestor of ours. Indicated by return 3. 665 */ 666 if (curdir->dev == dev && curdir->inode == ino) 667 return (3); 668 curdir = curdir->next; 669 } 670 /* 671 * parent_err indicates we couldn't stat or chdir to 672 * one of our parent dirs, so the linked list of dir_id structs 673 * is incomplete 674 */ 675 if (parent_err) { 676#ifndef XPG4 677 if (!silent || interactive) { 678#endif 679 (void) fprintf(stderr, gettext("rm: cannot determine " 680 "if this is an ancestor of the current " 681 "working directory\n%s\n"), fullpath); 682#ifndef XPG4 683 } 684#endif 685 /* assume it is. least dangerous */ 686 return (1); 687 } 688 return (0); | 591 va_start(ap, q); 592 (void) vfprintf(fp, q, ap); 593 va_end(ap); 594 return (yes()); |
689} 690 | 595} 596 |
691static int maxlen; 692static int curlen; 693 694static char * 695get_filename(char *name) 696{ 697 char *path; 698 size_t len; 699 700 if (fullpath == NULL || *fullpath == '\0') { 701 path = strdup(name); 702 if (path == NULL) { 703 (void) fprintf(stderr, 704 gettext("rm: Insufficient memory.\n")); 705 cleanup(); 706 exit(1); 707 } 708 } else { 709 len = strlen(fullpath) + strlen(name) + 2; 710 path = malloc(len); 711 if (path == NULL) { 712 (void) fprintf(stderr, 713 gettext("rm: Insufficient memory.\n")); 714 cleanup(); 715 exit(1); 716 } 717 (void) snprintf(path, len, "%s/%s", fullpath, name); 718 } 719 return (path); 720} 721 | |
722static void | 597static void |
723push_name(char *name, int first) | 598memerror(void) |
724{ | 599{ |
725 int namelen; 726 struct stat buffer; 727 struct dir_id *newdir; 728 729 namelen = strlen(name) + 1; /* 1 for "/" */ 730 if ((curlen + namelen) >= maxlen) { 731 maxlen += PATH_MAX; 732 fullpath = (char *)realloc(fullpath, (size_t)(maxlen + 1)); 733 } 734 if (first) { 735 (void) strcpy(fullpath, name); 736 } else { 737 (void) strcat(fullpath, "/"); 738 (void) strcat(fullpath, name); 739 } 740 curlen = strlen(fullpath); 741 742 if (stat(".", &buffer) == -1) { 743 (void) fprintf(stderr, 744 gettext("rm: cannot stat current directory: ")); 745 perror(""); 746 exit(2); 747 } 748 if ((newdir = malloc(sizeof (struct dir_id))) == NULL) { 749 (void) fprintf(stderr, 750 gettext("rm: Insufficient memory.\n")); 751 cleanup(); 752 exit(1); 753 } 754 755 newdir->dev = buffer.st_dev; 756 newdir->inode = buffer.st_ino; 757 newdir->next = current_dir; 758 current_dir = newdir; | 600 (void) fprintf(stderr, gettext("rm: Insufficient memory.\n")); 601 exit(1); |
759} 760 | 602} 603 |
604/* 605 * If we can't stat "..", it's either not there or we can't search 606 * the current directory; in that case we can't return back through 607 * "..", so we need to keep the parent open. 608 * Check that we came from "..", if not then this directory entry is an 609 * additional link and there is risk of a filesystem cycle and we also 610 * can't go back up through ".." and we keep the directory open. 611 */ |
|
761static int | 612static int |
762pop_name(int first) | 613checkdir(struct dlist *caller, struct dlist *frmp) |
763{ | 614{ |
764 int retval = 0; 765 char *slash; 766 struct stat buffer; 767 struct dir_id *remove_dir; | 615 struct stat up; 616 struct dlist *ptr; |
768 | 617 |
769 if (first) { 770 *fullpath = '\0'; | 618 if (fstatat(frmp->fd, "..", &up, 0) != 0) { 619 caller->flags |= DIR_CANTCLOSE; |
771 return (0); | 620 return (0); |
621 } else if (up.st_ino == caller->ino && up.st_dev == caller->dev) { 622 return (0); |
|
772 } | 623 } |
773 slash = strrchr(fullpath, '/'); 774 if (slash) 775 *slash = '\0'; 776 else 777 *fullpath = '\0'; 778 curlen = strlen(fullpath); | |
779 | 624 |
780 if (stat(".", &buffer) == -1) { 781 (void) fprintf(stderr, 782 gettext("rm: cannot stat current directory: ")); 783 perror(""); 784 exit(2); 785 } 786 787 /* 788 * For each pop operation, verify that the device and inode numbers 789 * of "." match the numbers recorded before the chdir was done into 790 * the directory. If they do not match, it is an indication of 791 * possible malicious activity and rm has chdir to an unintended 792 * directory 793 */ 794 if ((current_dir->inode != buffer.st_ino) || (current_dir->dev != 795 buffer.st_dev)) { 796 (void) fprintf(stderr, gettext("rm: WARNING: " 797 "A subdirectory of %s was moved or linked to " 798 "another directory during the execution of rm\n"), 799 fullpath); 800 retval = -1; 801 } 802 remove_dir = current_dir; 803 current_dir = current_dir->next; 804 free(remove_dir); 805 return (retval); 806} 807 808static void 809force_chdir(char *dirname) 810{ 811 char *pathname, *mp, *tp; 812 813 /* use pathname instead of dirname, so dirname won't be modified */ 814 if ((pathname = strdup(dirname)) == NULL) { 815 (void) fprintf(stderr, gettext("rm: strdup: ")); 816 perror(""); 817 cleanup(); 818 exit(2); 819 } 820 821 /* pathname is an absolute full path from getcwd() */ 822 mp = pathname; 823 while (mp) { 824 tp = strchr(mp, '/'); 825 if (strlen(mp) >= PATH_MAX) { 826 /* 827 * after the first iteration through this 828 * loop, the below will NULL out the '/' 829 * which follows the first dir on pathname 830 */ 831 *tp = 0; 832 tp++; 833 if (*mp == NULL) 834 ch_dir("/"); 835 else 836 /* 837 * mp points to the start of a dirname, 838 * terminated by NULL, so ch_dir() 839 * here will move down one directory 840 */ 841 ch_dir(mp); 842 /* 843 * reset mp to the start of the dirname 844 * which follows the one we just chdir'd to 845 */ 846 mp = tp; 847 continue; /* probably can remove this */ 848 } else { 849 ch_dir(mp); 850 break; 851 } 852 } 853 free(pathname); 854} 855 856static void 857ch_dir(char *dirname) 858{ 859 if (chdir(dirname) == -1) { 860 (void) fprintf(stderr, 861 gettext("rm: cannot change to %s directory: "), dirname); 862 perror(""); 863 cleanup(); 864 exit(2); 865 } 866} 867 868static void 869chdir_init(void) 870{ 871 /* 872 * Go back to init dir--the dir from where rm was executed--using 873 * one of two methods, depending on which method works 874 * for the given permissions of the init dir and its 875 * parent directories. 876 */ 877 if (initdirfd != -1) { 878 if (fchdir(initdirfd) == -1) { | 625 /* Directory hard link, check cycle */ 626 for (ptr = caller; ptr != NULL; ptr = ptr->up) { 627 if (frmp->dev == ptr->dev && frmp->ino == ptr->ino) { |
879 (void) fprintf(stderr, | 628 (void) fprintf(stderr, |
880 gettext("rm: cannot change to starting " 881 "directory: ")); 882 perror(""); 883 cleanup(); 884 exit(2); | 629 gettext("rm: cycle detected for %s\n"), pathbuf); 630 errcnt++; 631 return (-1); |
885 } | 632 } |
886 } else { 887 if (strlen(cwd) < PATH_MAX) 888 ch_dir(cwd); 889 else 890 force_chdir(cwd); | |
891 } | 633 } |
634 caller->flags |= DIR_CANTCLOSE; 635 return (0); |
|
892} | 636} |
893 894/* 895 * check_initdir - 896 * is only called the first time rm tries to 897 * remove a directory. It saves the current directory, i.e., 898 * init dir, so we can go back to it after traversing elsewhere. 899 * It also saves all the device and inode numbers of each 900 * dir from the initial dir back to the root in a linked list, so we 901 * can later check, via mypath(), if we are trying to remove our current 902 * dir or an ancestor. 903 */ 904static void 905check_initdir(void) 906{ 907 int size; /* size allocated for pathname of init dir (cwd) */ 908 struct stat buffer; 909 struct dir_id *lastdir, *curdir; 910 911 /* 912 * We need to save where we currently are (the "init dir") so 913 * we can return after traversing down directories we're 914 * removing. Two methods are attempted: 915 * 916 * 1) open() the initial dir so we can use the fd 917 * to fchdir() back. This requires read permission 918 * on the initial dir. 919 * 920 * 2) getcwd() so we can chdir() to go back. This 921 * requires search (x) permission on the init dir, 922 * and read and search permission on all parent dirs. Also, 923 * getcwd() will not work if the init dir is > 341 924 * directories deep (see open bugid 4033182 - getcwd needs 925 * to work for pathnames of any depth). 926 * 927 * If neither method works, we can't remove any directories 928 * and rm will fail. 929 * 930 * For future enhancement, a possible 3rd option to use 931 * would be to fork a process to remove a directory, 932 * eliminating the need to chdir back to the initial directory 933 * and eliminating the permission restrictions on the initial dir 934 * or its parent dirs. 935 */ 936 initdirfd = open(".", O_RDONLY); 937 if (initdirfd == -1) { 938 size = PATH_MAX; 939 while ((cwd = getcwd(NULL, size)) == NULL) { 940 if (errno == ERANGE) { 941 size = PATH_MAX + size; 942 continue; 943 } else { 944 (void) fprintf(stderr, 945 gettext("rm: cannot open starting " 946 "directory: ")); 947 perror("pwd"); 948 exit(2); 949 } 950 } 951 } 952 953 /* 954 * since we exit on error here, we're guaranteed to at least 955 * have info in the first dir_id struct, initdir 956 */ 957 if (stat(".", &buffer) == -1) { 958 (void) fprintf(stderr, 959 gettext("rm: cannot stat current directory: ")); 960 perror(""); 961 exit(2); 962 } 963 initdir.dev = buffer.st_dev; 964 initdir.inode = buffer.st_ino; 965 initdir.next = NULL; 966 967 lastdir = &initdir; 968 /* 969 * Starting from current working directory, walk toward the 970 * root, looking at each directory along the way. 971 */ 972 for (;;) { 973 if (chdir("..") == -1 || lstat(".", &buffer) == -1) { 974 parent_err = 1; 975 break; 976 } 977 978 if ((lastdir->next = malloc(sizeof (struct dir_id))) == 979 NULL) { 980 (void) fprintf(stderr, 981 gettext("rm: Insufficient memory.\n")); 982 cleanup(); 983 exit(1); 984 } 985 986 curdir = lastdir->next; 987 curdir->dev = buffer.st_dev; 988 curdir->inode = buffer.st_ino; 989 curdir->next = NULL; 990 991 /* 992 * Stop when we reach the root; note that chdir("..") 993 * at the root dir will stay in root. Get rid of 994 * the redundant dir_id struct for root. 995 */ 996 if (curdir->dev == lastdir->dev && curdir->inode == 997 lastdir->inode) { 998 lastdir->next = NULL; 999 free(curdir); 1000 break; 1001 } 1002 1003 /* loop again to go back another level */ 1004 lastdir = curdir; 1005 } 1006 /* go back to init directory */ 1007 chdir_init(); 1008} 1009 1010/* 1011 * cleanup the dynamically-allocated list of device numbers and inodes, 1012 * if any. If initdir was never used, it is external and static so 1013 * it is guaranteed initialized to zero, thus initdir.next would be NULL. 1014 */ 1015 1016static void 1017cleanup(void) { 1018 1019 struct dir_id *lastdir, *curdir; 1020 1021 curdir = initdir.next; 1022 1023 while (curdir != NULL) { 1024 lastdir = curdir; 1025 curdir = curdir->next; 1026 free(lastdir); 1027 } 1028} | |