Print this page
4815 Want rm support for -v option
4816 rm does not print error messages when -f is used
Split |
Close |
Expand all |
Collapse all |
--- old/usr/src/cmd/rm/rm.c
+++ new/usr/src/cmd/rm/rm.c
1 1 /*
2 2 * CDDL HEADER START
3 3 *
4 4 * The contents of this file are subject to the terms of the
5 5 * Common Development and Distribution License (the "License").
6 6 * You may not use this file except in compliance with the License.
7 7 *
8 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 9 * or http://www.opensolaris.org/os/licensing.
10 10 * See the License for the specific language governing permissions
11 11 * and limitations under the License.
12 12 *
↓ open down ↓ |
12 lines elided |
↑ open up ↑ |
13 13 * When distributing Covered Code, include this CDDL HEADER in each
14 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 15 * If applicable, add the following below this CDDL HEADER, with the
16 16 * fields enclosed by brackets "[]" replaced with your own identifying
17 17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 18 *
19 19 * CDDL HEADER END
20 20 */
21 21
22 22 /*
23 + * Copyright 2014 Andrew Stormont.
24 + */
25 +
26 +/*
23 27 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
24 28 * Use is subject to license terms.
25 29 */
26 30
27 31 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
28 32 /* All Rights Reserved */
29 33
30 34 /*
31 - * rm [-fiRr] file ...
35 + * rm [-fiRrv] file ...
32 36 */
33 37
34 38 #include <sys/param.h>
35 39 #include <sys/stat.h>
36 40 #include <dirent.h>
37 41 #include <errno.h>
38 42 #include <fcntl.h>
39 43 #include <langinfo.h>
40 44 #include <limits.h>
41 45 #include <locale.h>
42 46 #include <stdarg.h>
43 47 #include <stdio.h>
44 48 #include <stdlib.h>
45 49 #include <string.h>
46 50 #include <unistd.h>
47 51 #include <values.h>
48 52 #include "getresponse.h"
49 53
50 54 #define DIR_CANTCLOSE 1
51 55
52 56 static struct stat rootdir;
53 57
54 58 struct dlist {
55 59 int fd; /* Stores directory fd */
56 60 int flags; /* DIR_* Flags */
57 61 DIR *dp; /* Open directory (opened with fd) */
58 62 long diroff; /* Saved directory offset when closing */
59 63 struct dlist *up; /* Up one step in the tree (toward "/") */
60 64 struct dlist *down; /* Down one step in the tree */
61 65 ino_t ino; /* st_ino of directory */
62 66 dev_t dev; /* st_dev of directory */
63 67 int pathend; /* Offset of name end in the pathbuffer */
64 68 };
65 69
66 70 static struct dlist top = {
↓ open down ↓ |
25 lines elided |
↑ open up ↑ |
67 71 (int)AT_FDCWD,
68 72 DIR_CANTCLOSE,
69 73 };
70 74
71 75 static struct dlist *cur, *rec;
72 76
73 77 static int rm(const char *, struct dlist *);
74 78 static int confirm(FILE *, const char *, ...);
75 79 static void memerror(void);
76 80 static int checkdir(struct dlist *, struct dlist *);
77 -static int errcnt;
78 -static boolean_t silent, interactive, recursive, ontty;
81 +static int errcnt = 0;
82 +
83 +static boolean_t force = B_FALSE;
84 +static boolean_t interactive = B_FALSE;
85 +static boolean_t recursive = B_FALSE;
86 +static boolean_t ontty = B_FALSE;
87 +static boolean_t verbose = B_FALSE;
79 88
80 89 static char *pathbuf;
81 90 static size_t pathbuflen = MAXPATHLEN;
82 91
83 92 static int maxfds = MAXINT;
84 93 static int nfds;
85 94
86 95 int
87 96 main(int argc, char **argv)
88 97 {
89 98 int errflg = 0;
90 99 int c;
91 100
92 101 (void) setlocale(LC_ALL, "");
93 102 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
94 103 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */
95 104 #endif
96 105 (void) textdomain(TEXT_DOMAIN);
97 106
98 - while ((c = getopt(argc, argv, "frRi")) != EOF)
107 + while ((c = getopt(argc, argv, "frRiv")) != EOF)
99 108 switch (c) {
100 109 case 'f':
101 - silent = B_TRUE;
102 -#ifdef XPG4
110 + force = B_TRUE;
103 111 interactive = B_FALSE;
104 -#endif
105 112 break;
106 113 case 'i':
107 114 interactive = B_TRUE;
108 -#ifdef XPG4
109 - silent = B_FALSE;
110 -#endif
115 + force = B_FALSE;
111 116 break;
112 117 case 'r':
113 118 case 'R':
114 119 recursive = B_TRUE;
115 120 break;
121 + case 'v':
122 + verbose = B_TRUE;
123 + break;
116 124 case '?':
117 125 errflg = 1;
118 126 break;
119 127 }
120 128
121 129 /*
122 130 * For BSD compatibility allow '-' to delimit the end
123 131 * of options. However, if options were already explicitly
124 132 * terminated with '--', then treat '-' literally: otherwise,
125 133 * "rm -- -" won't remove '-'.
126 134 */
127 135 if (optind < argc &&
128 136 strcmp(argv[optind], "-") == 0 &&
129 137 strcmp(argv[optind - 1], "--") != 0)
130 138 optind++;
131 139
132 140 argc -= optind;
133 141 argv = &argv[optind];
134 142
135 - if ((argc < 1 && !silent) || errflg) {
136 - (void) fprintf(stderr, gettext("usage: rm [-fiRr] file ...\n"));
143 + if ((argc < 1 && !force) || errflg) {
144 + (void) fprintf(stderr,
145 + gettext("usage: rm [-fiRrv] file ...\n"));
137 146 exit(2);
138 147 }
139 148
140 149 ontty = isatty(STDIN_FILENO) != 0;
141 150
142 151 if (recursive && stat("/", &rootdir) != 0) {
143 152 (void) fprintf(stderr,
144 153 gettext("rm: cannot stat root directory: %s\n"),
145 154 strerror(errno));
146 155 exit(2);
147 156 }
148 157
149 158 pathbuf = malloc(pathbuflen);
150 159 if (pathbuf == NULL)
151 160 memerror();
152 161
153 162 if (init_yes() < 0) {
154 163 (void) fprintf(stderr, gettext(ERR_MSG_INIT_YES),
155 164 strerror(errno));
156 165 exit(2);
157 166 }
158 167
159 168 for (; *argv != NULL; argv++) {
160 169 char *p = strrchr(*argv, '/');
161 170 if (p == NULL)
162 171 p = *argv;
163 172 else
164 173 p = p + 1;
165 174 if (strcmp(p, ".") == 0 || strcmp(p, "..") == 0) {
166 175 (void) fprintf(stderr,
167 176 gettext("rm of %s is not allowed\n"), *argv);
168 177 errcnt++;
169 178 continue;
170 179 }
171 180 /* Retry when we can't walk back up. */
172 181 while (rm(*argv, rec = cur = &top) != 0)
173 182 ;
174 183 }
175 184
176 185 return (errcnt != 0 ? 2 : 0);
177 186 }
178 187
179 188 static void
180 189 pushfilename(const char *fname)
181 190 {
182 191 char *p;
183 192 const char *q = fname;
184 193
185 194 if (cur == &top) {
186 195 p = pathbuf;
187 196 } else {
188 197 p = pathbuf + cur->up->pathend;
189 198 *p++ = '/';
190 199 }
191 200 while (*q != '\0') {
192 201 if (p - pathbuf + 2 >= pathbuflen) {
193 202 char *np;
194 203 pathbuflen += MAXPATHLEN;
195 204 np = realloc(pathbuf, pathbuflen);
196 205 if (np == NULL)
197 206 memerror();
198 207 p = np + (p - pathbuf);
199 208 pathbuf = np;
200 209 }
201 210 *p++ = *q++;
202 211 }
203 212 *p = '\0';
204 213 cur->pathend = p - pathbuf;
205 214 }
206 215
207 216 static void
208 217 closeframe(struct dlist *frm)
209 218 {
210 219 if (frm->dp != NULL) {
211 220 (void) closedir(frm->dp);
212 221 nfds--;
213 222 frm->dp = NULL;
214 223 frm->fd = -1;
215 224 }
216 225 }
217 226
218 227 static int
219 228 reclaim(void)
220 229 {
221 230 while (rec != NULL && (rec->flags & DIR_CANTCLOSE) != 0)
222 231 rec = rec->down;
223 232 if (rec == NULL || rec == cur || rec->dp == NULL)
224 233 return (-1);
225 234 rec->diroff = telldir(rec->dp);
226 235 closeframe(rec);
227 236 rec = rec->down;
228 237 return (0);
229 238 }
230 239
231 240 static void
232 241 pushdir(struct dlist *frm)
233 242 {
234 243 frm->up = cur;
235 244 frm->down = NULL;
236 245 cur->down = frm;
237 246 cur = frm;
238 247 }
239 248
240 249 static int
241 250 opendirat(int dirfd, const char *entry, struct dlist *frm)
242 251 {
243 252 int fd;
244 253
245 254 if (nfds >= maxfds)
246 255 (void) reclaim();
247 256
248 257 while ((fd = openat(dirfd, entry, O_RDONLY|O_NONBLOCK)) == -1 &&
249 258 errno == EMFILE) {
250 259 if (nfds < maxfds)
251 260 maxfds = nfds;
252 261 if (reclaim() != 0)
253 262 return (-1);
254 263 }
255 264 if (fd < 0)
256 265 return (-1);
257 266 frm->fd = fd;
258 267 frm->dp = fdopendir(fd);
259 268 if (frm->dp == NULL) {
260 269 (void) close(fd);
261 270 return (-1);
262 271 }
263 272 nfds++;
264 273 return (0);
265 274 }
266 275
267 276 /*
268 277 * Since we never pop the top frame, cur->up can never be NULL.
269 278 * If we pop beyond a frame we closed, we try to reopen "..".
270 279 */
271 280 static int
272 281 popdir(boolean_t noerror)
273 282 {
274 283 struct stat buf;
275 284 int ret = noerror ? 0 : -1;
276 285 pathbuf[cur->up->pathend] = '\0';
277 286
278 287 if (noerror && cur->up->fd == -1) {
279 288 rec = cur->up;
280 289 if (opendirat(cur->fd, "..", rec) != 0 ||
281 290 fstat(rec->fd, &buf) != 0) {
282 291 (void) fprintf(stderr,
283 292 gettext("rm: cannot reopen %s: %s\n"),
284 293 pathbuf, strerror(errno));
285 294 exit(2);
286 295 }
287 296 if (rec->ino != buf.st_ino || rec->dev != buf.st_dev) {
288 297 (void) fprintf(stderr, gettext("rm: WARNING: "
289 298 "The directory %s was moved or linked to "
290 299 "another directory during the execution of rm\n"),
291 300 pathbuf);
292 301 closeframe(rec);
293 302 ret = -1;
294 303 } else {
295 304 /* If telldir failed, we take it from the top. */
296 305 if (rec->diroff != -1)
297 306 seekdir(rec->dp, rec->diroff);
298 307 }
299 308 } else if (rec == cur)
300 309 rec = cur->up;
301 310 closeframe(cur);
302 311 cur = cur->up;
303 312 cur->down = NULL;
304 313 return (ret);
305 314 }
306 315
307 316 /*
308 317 * The stack frame of this function is minimized so that we can
309 318 * recurse quite a bit before we overflow the stack; around
310 319 * 30,000-40,000 nested directories can be removed with the default
311 320 * stack limit.
312 321 */
313 322 static int
314 323 rm(const char *entry, struct dlist *caller)
315 324 {
316 325 struct dlist frame;
317 326 int flag;
318 327 struct stat temp;
319 328 struct dirent *dent;
320 329 int err;
↓ open down ↓ |
174 lines elided |
↑ open up ↑ |
321 330
322 331 /*
323 332 * Construct the pathname: note that the entry may live in memory
324 333 * allocated by readdir and that after return from recursion
325 334 * the memory is no longer valid. So after the recursive rm()
326 335 * call, we use the global pathbuf instead of the entry argument.
327 336 */
328 337 pushfilename(entry);
329 338
330 339 if (fstatat(caller->fd, entry, &temp, AT_SYMLINK_NOFOLLOW) != 0) {
331 - if (!silent) {
332 - (void) fprintf(stderr, "rm: %s: %s\n", pathbuf,
333 - strerror(errno));
334 - errcnt++;
335 - }
340 + (void) fprintf(stderr, "rm: %s: %s\n", pathbuf,
341 + strerror(errno));
342 + errcnt++;
336 343 return (0);
337 344 }
338 345
339 346 if (S_ISDIR(temp.st_mode)) {
340 347 /*
341 348 * If "-r" wasn't specified, trying to remove directories
342 349 * is an error.
343 350 */
344 351 if (!recursive) {
345 352 (void) fprintf(stderr,
346 353 gettext("rm: %s is a directory\n"), pathbuf);
347 354 errcnt++;
348 355 return (0);
349 356 }
350 357
351 358 if (temp.st_ino == rootdir.st_ino &&
352 359 temp.st_dev == rootdir.st_dev) {
353 360 (void) fprintf(stderr,
354 361 gettext("rm of %s is not allowed\n"), "/");
355 362 errcnt++;
356 363 return (0);
357 364 }
358 365 /*
359 366 * TRANSLATION_NOTE - The following message will contain the
360 367 * first character of the strings for "yes" and "no" defined
361 368 * in the file "nl_langinfo.po". After substitution, the
362 369 * message will appear as follows:
363 370 * rm: examine files in directory <directoryname> (y/n)?
364 371 * where <directoryname> is the directory to be removed
365 372 *
366 373 */
367 374 if (interactive && !confirm(stderr,
↓ open down ↓ |
22 lines elided |
↑ open up ↑ |
368 375 gettext("rm: examine files in directory %s (%s/%s)? "),
369 376 pathbuf, yesstr, nostr)) {
370 377 return (0);
371 378 }
372 379
373 380 frame.dev = temp.st_dev;
374 381 frame.ino = temp.st_ino;
375 382 frame.flags = 0;
376 383 flag = AT_REMOVEDIR;
377 384
378 -#ifdef XPG4
385 +#ifndef SUS
379 386 /*
380 387 * XCU4 and POSIX.2: If not interactive, check to see whether
381 388 * or not directory is readable or writable and if not,
382 389 * prompt user for response.
383 390 */
384 - if (ontty && !interactive && !silent &&
391 + if (ontty && !interactive && !force &&
385 392 faccessat(caller->fd, entry, W_OK|X_OK, AT_EACCESS) != 0 &&
386 393 !confirm(stderr,
387 394 gettext("rm: examine files in directory %s (%s/%s)? "),
388 395 pathbuf, yesstr, nostr)) {
389 396 return (0);
390 397 }
391 398 #endif
399 +
392 400 if (opendirat(caller->fd, entry, &frame) == -1) {
393 401 err = errno;
394 402
395 403 if (interactive) {
396 404 /*
397 405 * Print an error message that
398 406 * we could not read the directory
399 407 * as the user wanted to examine
400 408 * files in the directory. Only
401 409 * affect the error status if
402 410 * user doesn't want to remove the
403 411 * directory as we still may be able
404 412 * remove the directory successfully.
405 413 */
406 414 (void) fprintf(stderr, gettext(
407 415 "rm: cannot read directory %s: %s\n"),
408 416 pathbuf, strerror(err));
409 417
410 418 /*
411 419 * TRANSLATION_NOTE - The following message will contain the
412 420 * first character of the strings for "yes" and "no" defined
413 421 * in the file "nl_langinfo.po". After substitution, the
414 422 * message will appear as follows:
415 423 * rm: remove <filename> (y/n)?
416 424 * For example, in German, this will appear as
417 425 * rm: löschen <filename> (j/n)?
↓ open down ↓ |
16 lines elided |
↑ open up ↑ |
418 426 * where j=ja, n=nein, <filename>=the file to be removed
419 427 */
420 428 if (!confirm(stderr,
421 429 gettext("rm: remove %s (%s/%s)? "),
422 430 pathbuf, yesstr, nostr)) {
423 431 errcnt++;
424 432 return (0);
425 433 }
426 434 }
427 435 /* If it's empty we may still be able to rm it */
428 - if (unlinkat(caller->fd, entry, flag) == 0)
436 + if (unlinkat(caller->fd, entry, flag) == 0) {
437 + if (verbose)
438 + (void) printf(gettext("removed "
439 + "directory: `%s'\n"), pathbuf);
429 440 return (0);
441 + }
430 442 if (interactive)
431 443 err = errno;
432 444 (void) fprintf(stderr,
433 445 interactive ?
434 446 gettext("rm: Unable to remove directory %s: %s\n") :
435 447 gettext("rm: cannot read directory %s: %s\n"),
436 448 pathbuf, strerror(err));
437 449 errcnt++;
438 450 return (0);
439 451 }
440 452
441 453 /*
442 454 * There is a race condition here too; if we open a directory
443 455 * we have to make sure it's still the same directory we
444 456 * stat'ed and checked against root earlier. Let's check.
445 457 */
446 458 if (fstat(frame.fd, &temp) != 0 ||
447 459 frame.ino != temp.st_ino ||
448 460 frame.dev != temp.st_dev) {
449 461 (void) fprintf(stderr,
450 462 gettext("rm: %s: directory renamed\n"), pathbuf);
451 463 closeframe(&frame);
452 464 errcnt++;
453 465 return (0);
454 466 }
455 467
456 468 if (caller != &top) {
457 469 if (checkdir(caller, &frame) != 0) {
458 470 closeframe(&frame);
459 471 goto unlinkit;
460 472 }
461 473 }
462 474 pushdir(&frame);
463 475
464 476 /*
465 477 * rm() only returns -1 if popdir failed at some point;
466 478 * frame.dp is no longer reliable and we must drop out.
467 479 */
468 480 while ((dent = readdir(frame.dp)) != NULL) {
469 481 if (strcmp(dent->d_name, ".") == 0 ||
470 482 strcmp(dent->d_name, "..") == 0)
471 483 continue;
472 484
473 485 if (rm(dent->d_name, &frame) != 0)
474 486 break;
475 487 }
476 488
477 489 if (popdir(dent == NULL) != 0)
478 490 return (-1);
479 491
480 492 /*
481 493 * We recursed and the subdirectory may have set the CANTCLOSE
482 494 * flag; we need to clear it except for &top.
483 495 * Recursion may have invalidated entry because of closedir().
484 496 */
485 497 if (caller != &top) {
486 498 caller->flags &= ~DIR_CANTCLOSE;
487 499 entry = &pathbuf[caller->up->pathend + 1];
488 500 }
489 501 } else {
490 502 flag = 0;
↓ open down ↓ |
51 lines elided |
↑ open up ↑ |
491 503 }
492 504 unlinkit:
493 505 /*
494 506 * If interactive, ask for acknowledgement.
495 507 */
496 508 if (interactive) {
497 509 if (!confirm(stderr, gettext("rm: remove %s (%s/%s)? "),
498 510 pathbuf, yesstr, nostr)) {
499 511 return (0);
500 512 }
501 - } else if (!silent && flag == 0) {
513 + } else if (!force && flag == 0) {
502 514 /*
503 - * If not silent, and stdin is a terminal, and there's
515 + * If not force, and stdin is a terminal, and there's
504 516 * no write access, and the file isn't a symbolic link,
505 517 * ask for permission. If flag is set, then we know it's
506 518 * a directory so we skip this test as it was done above.
507 519 *
508 520 * TRANSLATION_NOTE - The following message will contain the
509 521 * first character of the strings for "yes" and "no" defined
510 522 * in the file "nl_langinfo.po". After substitution, the
511 523 * message will appear as follows:
512 524 * rm: <filename>: override protection XXX (y/n)?
513 525 * where XXX is the permission mode bits of the file in octal
514 526 * and <filename> is the file to be removed
515 527 *
↓ open down ↓ |
2 lines elided |
↑ open up ↑ |
516 528 */
517 529 if (ontty && !S_ISLNK(temp.st_mode) &&
518 530 faccessat(caller->fd, entry, W_OK, AT_EACCESS) != 0 &&
519 531 !confirm(stdout,
520 532 gettext("rm: %s: override protection %o (%s/%s)? "),
521 533 pathbuf, temp.st_mode & 0777, yesstr, nostr)) {
522 534 return (0);
523 535 }
524 536 }
525 537
526 - if (unlinkat(caller->fd, entry, flag) != 0) {
538 + if (unlinkat(caller->fd, entry, flag) == 0) {
539 + if (verbose)
540 + (void) printf(S_ISDIR(temp.st_mode) ?
541 + gettext("removed directory: `%s'\n") :
542 + gettext("removed `%s'\n"), pathbuf);
543 + return (0);
544 + } else {
527 545 err = errno;
528 546 if (err == ENOENT)
529 547 return (0);
530 548
531 549 if (flag != 0) {
532 550 if (err == EINVAL) {
533 551 (void) fprintf(stderr, gettext(
534 552 "rm: Cannot remove any directory in the "
535 553 "path of the current working directory\n"
536 554 "%s\n"), pathbuf);
537 555 } else {
538 556 if (err == EEXIST)
539 557 err = ENOTEMPTY;
540 558 (void) fprintf(stderr,
541 559 gettext("rm: Unable to remove directory %s:"
542 560 " %s\n"), pathbuf, strerror(err));
543 561 }
562 +#ifndef SUS
544 563 } else {
545 -#ifndef XPG4
546 - if (!silent || interactive) {
547 -#endif
548 -
549 - (void) fprintf(stderr,
550 - gettext("rm: %s not removed: %s\n"),
551 - pathbuf, strerror(err));
552 -#ifndef XPG4
553 - }
564 + (void) fprintf(stderr,
565 + gettext("rm: %s not removed: %s\n"),
566 + pathbuf, strerror(err));
554 567 #endif
555 568 }
556 569 errcnt++;
557 570 }
558 571 return (0);
559 572 }
560 573
561 574 static int
562 575 confirm(FILE *fp, const char *q, ...)
563 576 {
564 577 va_list ap;
565 578
566 579 va_start(ap, q);
567 580 (void) vfprintf(fp, q, ap);
568 581 va_end(ap);
569 582 return (yes());
570 583 }
571 584
572 585 static void
573 586 memerror(void)
574 587 {
575 588 (void) fprintf(stderr, gettext("rm: Insufficient memory.\n"));
576 589 exit(1);
577 590 }
578 591
579 592 /*
580 593 * If we can't stat "..", it's either not there or we can't search
581 594 * the current directory; in that case we can't return back through
582 595 * "..", so we need to keep the parent open.
583 596 * Check that we came from "..", if not then this directory entry is an
584 597 * additional link and there is risk of a filesystem cycle and we also
585 598 * can't go back up through ".." and we keep the directory open.
586 599 */
587 600 static int
588 601 checkdir(struct dlist *caller, struct dlist *frmp)
589 602 {
590 603 struct stat up;
591 604 struct dlist *ptr;
592 605
593 606 if (fstatat(frmp->fd, "..", &up, 0) != 0) {
594 607 caller->flags |= DIR_CANTCLOSE;
595 608 return (0);
596 609 } else if (up.st_ino == caller->ino && up.st_dev == caller->dev) {
597 610 return (0);
598 611 }
599 612
600 613 /* Directory hard link, check cycle */
601 614 for (ptr = caller; ptr != NULL; ptr = ptr->up) {
602 615 if (frmp->dev == ptr->dev && frmp->ino == ptr->ino) {
603 616 (void) fprintf(stderr,
604 617 gettext("rm: cycle detected for %s\n"), pathbuf);
605 618 errcnt++;
606 619 return (-1);
607 620 }
608 621 }
609 622 caller->flags |= DIR_CANTCLOSE;
610 623 return (0);
611 624 }
↓ open down ↓ |
48 lines elided |
↑ open up ↑ |
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX