| GCC Code Coverage Report | |||||||||||||||||||||
| 
 | |||||||||||||||||||||
| Line | Branch | Exec | Source | 
| 1 | /* $OpenBSD: rm.c,v 1.42 2017/06/27 21:49:47 tedu Exp $ */ | ||
| 2 | /* $NetBSD: rm.c,v 1.19 1995/09/07 06:48:50 jtc Exp $ */ | ||
| 3 | |||
| 4 | /*- | ||
| 5 | * Copyright (c) 1990, 1993, 1994 | ||
| 6 | * The Regents of the University of California. All rights reserved. | ||
| 7 | * | ||
| 8 | * Redistribution and use in source and binary forms, with or without | ||
| 9 | * modification, are permitted provided that the following conditions | ||
| 10 | * are met: | ||
| 11 | * 1. Redistributions of source code must retain the above copyright | ||
| 12 | * notice, this list of conditions and the following disclaimer. | ||
| 13 | * 2. Redistributions in binary form must reproduce the above copyright | ||
| 14 | * notice, this list of conditions and the following disclaimer in the | ||
| 15 | * documentation and/or other materials provided with the distribution. | ||
| 16 | * 3. Neither the name of the University nor the names of its contributors | ||
| 17 | * may be used to endorse or promote products derived from this software | ||
| 18 | * without specific prior written permission. | ||
| 19 | * | ||
| 20 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND | ||
| 21 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
| 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||
| 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE | ||
| 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
| 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | ||
| 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | ||
| 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | ||
| 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | ||
| 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | ||
| 30 | * SUCH DAMAGE. | ||
| 31 | */ | ||
| 32 | |||
| 33 | #include <sys/types.h> | ||
| 34 | #include <sys/stat.h> | ||
| 35 | #include <sys/mount.h> | ||
| 36 | |||
| 37 | #include <err.h> | ||
| 38 | #include <errno.h> | ||
| 39 | #include <fcntl.h> | ||
| 40 | #include <fts.h> | ||
| 41 | #include <stdio.h> | ||
| 42 | #include <stdlib.h> | ||
| 43 | #include <string.h> | ||
| 44 | #include <unistd.h> | ||
| 45 | #include <limits.h> | ||
| 46 | #include <pwd.h> | ||
| 47 | #include <grp.h> | ||
| 48 | |||
| 49 | #define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b)) | ||
| 50 | |||
| 51 | extern char *__progname; | ||
| 52 | |||
| 53 | int dflag, eval, fflag, iflag, Pflag, vflag, stdin_ok; | ||
| 54 | |||
| 55 | int check(char *, char *, struct stat *); | ||
| 56 | void checkdot(char **); | ||
| 57 | void rm_file(char **); | ||
| 58 | int rm_overwrite(char *, struct stat *); | ||
| 59 | int pass(int, off_t, char *, size_t); | ||
| 60 | void rm_tree(char **); | ||
| 61 | void usage(void); | ||
| 62 | |||
| 63 | /* | ||
| 64 | * rm -- | ||
| 65 | * This rm is different from historic rm's, but is expected to match | ||
| 66 | * POSIX 1003.2 behavior. The most visible difference is that -f | ||
| 67 | * has two specific effects now, ignore non-existent files and force | ||
| 68 | * file removal. | ||
| 69 | */ | ||
| 70 | int | ||
| 71 | main(int argc, char *argv[]) | ||
| 72 | { | ||
| 73 | int ch, rflag; | ||
| 74 | |||
| 75 | 65498 | Pflag = rflag = 0; | |
| 76 | ✓✓ | 99927 | while ((ch = getopt(argc, argv, "dfiPRrv")) != -1) | 
| 77 | ✗✓✗✗ ✗✓✗✗ | 34429 | 		switch(ch) { | 
| 78 | case 'd': | ||
| 79 | dflag = 1; | ||
| 80 | break; | ||
| 81 | case 'f': | ||
| 82 | 32445 | fflag = 1; | |
| 83 | 32445 | iflag = 0; | |
| 84 | 32445 | break; | |
| 85 | case 'i': | ||
| 86 | fflag = 0; | ||
| 87 | iflag = 1; | ||
| 88 | break; | ||
| 89 | case 'P': | ||
| 90 | Pflag = 1; | ||
| 91 | break; | ||
| 92 | case 'R': | ||
| 93 | case 'r': /* Compatibility. */ | ||
| 94 | rflag = 1; | ||
| 95 | 1984 | break; | |
| 96 | case 'v': | ||
| 97 | vflag = 1; | ||
| 98 | break; | ||
| 99 | default: | ||
| 100 | usage(); | ||
| 101 | } | ||
| 102 | 32749 | argc -= optind; | |
| 103 | 32749 | argv += optind; | |
| 104 | |||
| 105 | ✗✓ | 32749 | 	if (Pflag) { | 
| 106 | 		if (pledge("stdio rpath wpath cpath getpw flock", NULL) == -1) | ||
| 107 | err(1, "pledge"); | ||
| 108 | 	} else { | ||
| 109 | ✗✓ | 32749 | 		if (pledge("stdio rpath cpath getpw flock wpath", NULL) == -1) | 
| 110 | err(1, "pledge"); | ||
| 111 | } | ||
| 112 | |||
| 113 | ✗✓ | 32749 | if (argc < 1 && fflag == 0) | 
| 114 | usage(); | ||
| 115 | |||
| 116 | 32749 | checkdot(argv); | |
| 117 | |||
| 118 | ✓✓ | 32749 | 	if (*argv) { | 
| 119 | 32748 | stdin_ok = isatty(STDIN_FILENO); | |
| 120 | |||
| 121 | ✓✓ | 32748 | if (rflag) | 
| 122 | 1984 | rm_tree(argv); | |
| 123 | else | ||
| 124 | 30764 | rm_file(argv); | |
| 125 | } | ||
| 126 | |||
| 127 | 32749 | return (eval); | |
| 128 | } | ||
| 129 | |||
| 130 | void | ||
| 131 | rm_tree(char **argv) | ||
| 132 | { | ||
| 133 | FTS *fts; | ||
| 134 | FTSENT *p; | ||
| 135 | int needstat; | ||
| 136 | int flags; | ||
| 137 | |||
| 138 | /* | ||
| 139 | * Remove a file hierarchy. If forcing removal (-f), or interactive | ||
| 140 | * (-i) or can't ask anyway (stdin_ok), don't stat the file. | ||
| 141 | */ | ||
| 142 | ✓✓ | 5988 | needstat = !fflag && !iflag && stdin_ok; | 
| 143 | |||
| 144 | /* | ||
| 145 | * If the -i option is specified, the user can skip on the pre-order | ||
| 146 | * visit. The fts_number field flags skipped directories. | ||
| 147 | */ | ||
| 148 | #define SKIPPED 1 | ||
| 149 | |||
| 150 | flags = FTS_PHYSICAL; | ||
| 151 | ✓✓ | 1984 | if (!needstat) | 
| 152 | 1948 | flags |= FTS_NOSTAT; | |
| 153 | ✗✓ | 1984 | if (!(fts = fts_open(argv, flags, NULL))) | 
| 154 | err(1, NULL); | ||
| 155 | ✓✓ | 23849 | 	while ((p = fts_read(fts)) != NULL) { | 
| 156 | ✓✗✓✓ ✓✓ | 21865 | 		switch (p->fts_info) { | 
| 157 | case FTS_DNR: | ||
| 158 | ✓✗✓✗ | 4 | 			if (!fflag || p->fts_errno != ENOENT) { | 
| 159 | 				warnx("%s: %s", | ||
| 160 | p->fts_path, strerror(p->fts_errno)); | ||
| 161 | eval = 1; | ||
| 162 | } | ||
| 163 | continue; | ||
| 164 | case FTS_ERR: | ||
| 165 | errc(1, p->fts_errno, "%s", p->fts_path); | ||
| 166 | case FTS_NS: | ||
| 167 | /* | ||
| 168 | * FTS_NS: assume that if can't stat the file, it | ||
| 169 | * can't be unlinked. | ||
| 170 | */ | ||
| 171 | ✗✓ | 1624 | if (!needstat) | 
| 172 | break; | ||
| 173 | 			if (!fflag || p->fts_errno != ENOENT) { | ||
| 174 | 				warnx("%s: %s", | ||
| 175 | p->fts_path, strerror(p->fts_errno)); | ||
| 176 | eval = 1; | ||
| 177 | } | ||
| 178 | continue; | ||
| 179 | case FTS_D: | ||
| 180 | /* Pre-order: give user chance to skip. */ | ||
| 181 | ✓✓✓✗ | 2526 | if (!fflag && !check(p->fts_path, p->fts_accpath, | 
| 182 | 36 | 			    p->fts_statp)) { | |
| 183 | (void)fts_set(fts, p, FTS_SKIP); | ||
| 184 | p->fts_number = SKIPPED; | ||
| 185 | } | ||
| 186 | continue; | ||
| 187 | case FTS_DP: | ||
| 188 | /* Post-order: see if user skipped. */ | ||
| 189 | ✗✓ | 2452 | if (p->fts_number == SKIPPED) | 
| 190 | continue; | ||
| 191 | break; | ||
| 192 | default: | ||
| 193 | ✓✓✗✓ | 15531 | if (!fflag && | 
| 194 | 198 | !check(p->fts_path, p->fts_accpath, p->fts_statp)) | |
| 195 | continue; | ||
| 196 | } | ||
| 197 | |||
| 198 | /* | ||
| 199 | * If we can't read or search the directory, may still be | ||
| 200 | 		 * able to remove it.  Don't print out the un{read,search}able | ||
| 201 | * message unless the remove fails. | ||
| 202 | */ | ||
| 203 | ✗✓✗✓ ✓ | 34740 | 		switch (p->fts_info) { | 
| 204 | case FTS_DP: | ||
| 205 | case FTS_DNR: | ||
| 206 | ✗✓✗✗ | 2452 | if (!rmdir(p->fts_accpath) || | 
| 207 | 			    (fflag && errno == ENOENT)) { | ||
| 208 | ✓✗ | 2452 | if (vflag) | 
| 209 | fprintf(stdout, "%s\n", p->fts_path); | ||
| 210 | continue; | ||
| 211 | } | ||
| 212 | break; | ||
| 213 | |||
| 214 | case FTS_F: | ||
| 215 | case FTS_NSOK: | ||
| 216 | ✗✓ | 15331 | if (Pflag) | 
| 217 | rm_overwrite(p->fts_accpath, p->fts_info == | ||
| 218 | FTS_NSOK ? NULL : p->fts_statp); | ||
| 219 | /* FALLTHROUGH */ | ||
| 220 | default: | ||
| 221 | ✓✓✓✗ | 18585 | if (!unlink(p->fts_accpath) || | 
| 222 | ✓✗ | 3256 | 			    (fflag && errno == ENOENT)) { | 
| 223 | ✓✗ | 16957 | if (vflag) | 
| 224 | fprintf(stdout, "%s\n", p->fts_path); | ||
| 225 | continue; | ||
| 226 | } | ||
| 227 | } | ||
| 228 | 		warn("%s", p->fts_path); | ||
| 229 | eval = 1; | ||
| 230 | } | ||
| 231 | ✗✓ | 1984 | if (errno) | 
| 232 | err(1, "fts_read"); | ||
| 233 | 1984 | fts_close(fts); | |
| 234 | 1984 | } | |
| 235 | |||
| 236 | void | ||
| 237 | rm_file(char **argv) | ||
| 238 | { | ||
| 239 | 61528 | struct stat sb; | |
| 240 | int rval; | ||
| 241 | char *f; | ||
| 242 | |||
| 243 | /* | ||
| 244 | * Remove a file. POSIX 1003.2 states that, by default, attempting | ||
| 245 | * to remove a directory is an error, so must always stat the file. | ||
| 246 | */ | ||
| 247 | ✓✓ | 209987 | 	while ((f = *argv++) != NULL) { | 
| 248 | /* Assume if can't stat the file, can't unlink it. */ | ||
| 249 | ✓✓ | 148459 | 		if (lstat(f, &sb)) { | 
| 250 | ✓✓✓✗ | 116732 | 			if (!fflag || errno != ENOENT) { | 
| 251 | 30 | 				warn("%s", f); | |
| 252 | 30 | eval = 1; | |
| 253 | 30 | } | |
| 254 | continue; | ||
| 255 | } | ||
| 256 | |||
| 257 | ✗✓ | 90078 | 		if (S_ISDIR(sb.st_mode) && !dflag) { | 
| 258 | 			warnx("%s: is a directory", f); | ||
| 259 | eval = 1; | ||
| 260 | continue; | ||
| 261 | } | ||
| 262 | ✓✓✗✓ | 90364 | if (!fflag && !check(f, f, &sb)) | 
| 263 | continue; | ||
| 264 | ✗✓ | 90078 | else if (S_ISDIR(sb.st_mode)) | 
| 265 | rval = rmdir(f); | ||
| 266 | 		else { | ||
| 267 | ✗✓ | 90078 | if (Pflag) | 
| 268 | rm_overwrite(f, &sb); | ||
| 269 | 90078 | rval = unlink(f); | |
| 270 | } | ||
| 271 | ✗✓✗✗ ✗✗ | 90078 | 		if (rval && (!fflag || errno != ENOENT)) { | 
| 272 | 			warn("%s", f); | ||
| 273 | eval = 1; | ||
| 274 | ✓✗ | 90078 | } else if (vflag) | 
| 275 | (void)fprintf(stdout, "%s\n", f); | ||
| 276 | } | ||
| 277 | 30764 | } | |
| 278 | |||
| 279 | /* | ||
| 280 | * rm_overwrite -- | ||
| 281 | * Overwrite the file with varying bit patterns. | ||
| 282 | * | ||
| 283 | * XXX | ||
| 284 | * This is a cheap way to *really* delete files. Note that only regular | ||
| 285 | * files are deleted, directories (and therefore names) will remain. | ||
| 286 | * Also, this assumes a fixed-block file system (like FFS, or a V7 or a | ||
| 287 | * System V file system). In a logging file system, you'll have to have | ||
| 288 | * kernel support. | ||
| 289 | * Returns 1 for success. | ||
| 290 | */ | ||
| 291 | int | ||
| 292 | rm_overwrite(char *file, struct stat *sbp) | ||
| 293 | { | ||
| 294 | struct stat sb, sb2; | ||
| 295 | struct statfs fsb; | ||
| 296 | size_t bsize; | ||
| 297 | int fd; | ||
| 298 | char *buf = NULL; | ||
| 299 | |||
| 300 | fd = -1; | ||
| 301 | 	if (sbp == NULL) { | ||
| 302 | if (lstat(file, &sb)) | ||
| 303 | goto err; | ||
| 304 | sbp = &sb; | ||
| 305 | } | ||
| 306 | if (!S_ISREG(sbp->st_mode)) | ||
| 307 | return (1); | ||
| 308 | 	if (sbp->st_nlink > 1) { | ||
| 309 | 		warnx("%s (inode %llu): not overwritten due to multiple links", | ||
| 310 | file, (unsigned long long)sbp->st_ino); | ||
| 311 | return (0); | ||
| 312 | } | ||
| 313 | if ((fd = open(file, O_WRONLY|O_NONBLOCK|O_NOFOLLOW, 0)) == -1) | ||
| 314 | goto err; | ||
| 315 | if (fstat(fd, &sb2)) | ||
| 316 | goto err; | ||
| 317 | if (sb2.st_dev != sbp->st_dev || sb2.st_ino != sbp->st_ino || | ||
| 318 | 	    !S_ISREG(sb2.st_mode)) { | ||
| 319 | errno = EPERM; | ||
| 320 | goto err; | ||
| 321 | } | ||
| 322 | if (fstatfs(fd, &fsb) == -1) | ||
| 323 | goto err; | ||
| 324 | bsize = MAXIMUM(fsb.f_iosize, 1024U); | ||
| 325 | if ((buf = malloc(bsize)) == NULL) | ||
| 326 | err(1, "%s: malloc", file); | ||
| 327 | |||
| 328 | if (!pass(fd, sbp->st_size, buf, bsize)) | ||
| 329 | goto err; | ||
| 330 | if (fsync(fd)) | ||
| 331 | goto err; | ||
| 332 | close(fd); | ||
| 333 | free(buf); | ||
| 334 | return (1); | ||
| 335 | |||
| 336 | err: | ||
| 337 | 	warn("%s", file); | ||
| 338 | close(fd); | ||
| 339 | eval = 1; | ||
| 340 | free(buf); | ||
| 341 | return (0); | ||
| 342 | } | ||
| 343 | |||
| 344 | int | ||
| 345 | pass(int fd, off_t len, char *buf, size_t bsize) | ||
| 346 | { | ||
| 347 | size_t wlen; | ||
| 348 | |||
| 349 | 	for (; len > 0; len -= wlen) { | ||
| 350 | wlen = len < bsize ? len : bsize; | ||
| 351 | arc4random_buf(buf, wlen); | ||
| 352 | if (write(fd, buf, wlen) != wlen) | ||
| 353 | return (0); | ||
| 354 | } | ||
| 355 | return (1); | ||
| 356 | } | ||
| 357 | |||
| 358 | int | ||
| 359 | check(char *path, char *name, struct stat *sp) | ||
| 360 | { | ||
| 361 | int ch, first; | ||
| 362 | 1040 | char modep[15]; | |
| 363 | |||
| 364 | /* Check -i first. */ | ||
| 365 | ✗✓ | 520 | if (iflag) | 
| 366 | (void)fprintf(stderr, "remove %s? ", path); | ||
| 367 | 	else { | ||
| 368 | /* | ||
| 369 | * If it's not a symbolic link and it's unwritable and we're | ||
| 370 | * talking to a terminal, ask. Symbolic links are excluded | ||
| 371 | * because their permissions are meaningless. Check stdin_ok | ||
| 372 | * first because we may not have stat'ed the file. | ||
| 373 | */ | ||
| 374 | ✓✓✓✓ ✗✓✗✗ | 1503 | if (!stdin_ok || S_ISLNK(sp->st_mode) || !access(name, W_OK) || | 
| 375 | errno != EACCES) | ||
| 376 | 520 | return (1); | |
| 377 | strmode(sp->st_mode, modep); | ||
| 378 | (void)fprintf(stderr, "override %s%s%s/%s for %s? ", | ||
| 379 | modep + 1, modep[9] == ' ' ? "" : " ", | ||
| 380 | user_from_uid(sp->st_uid, 0), | ||
| 381 | group_from_gid(sp->st_gid, 0), path); | ||
| 382 | } | ||
| 383 | (void)fflush(stderr); | ||
| 384 | |||
| 385 | first = ch = getchar(); | ||
| 386 | while (ch != '\n' && ch != EOF) | ||
| 387 | ch = getchar(); | ||
| 388 | return (first == 'y' || first == 'Y'); | ||
| 389 | 520 | } | |
| 390 | |||
| 391 | /* | ||
| 392 | * POSIX.2 requires that if "." or ".." are specified as the basename | ||
| 393 | * portion of an operand, a diagnostic message be written to standard | ||
| 394 | * error and nothing more be done with such operands. | ||
| 395 | * | ||
| 396 | * Since POSIX.2 defines basename as the final portion of a path after | ||
| 397 | * trailing slashes have been removed, we'll remove them here. | ||
| 398 | */ | ||
| 399 | #define ISDOT(a) ((a)[0] == '.' && (!(a)[1] || ((a)[1] == '.' && !(a)[2]))) | ||
| 400 | void | ||
| 401 | checkdot(char **argv) | ||
| 402 | { | ||
| 403 | char *p, **save, **t; | ||
| 404 | int complained; | ||
| 405 | 65498 | struct stat sb, root; | |
| 406 | |||
| 407 | 32749 | 	stat("/", &root); | |
| 408 | complained = 0; | ||
| 409 | ✓✓ | 218085 | 	for (t = argv; *t;) { | 
| 410 | ✓✓✗✗ | 152587 | if (lstat(*t, &sb) == 0 && | 
| 411 | ✗✓ | 111310 | 		    root.st_ino == sb.st_ino && root.st_dev == sb.st_dev) { | 
| 412 | if (!complained++) | ||
| 413 | 				warnx("\"/\" may not be removed"); | ||
| 414 | goto skip; | ||
| 415 | } | ||
| 416 | /* strip trailing slashes */ | ||
| 417 | 152587 | p = strrchr(*t, '\0'); | |
| 418 | ✓✓✗✓ | 457703 | while (--p > *t && *p == '/') | 
| 419 | *p = '\0'; | ||
| 420 | |||
| 421 | /* extract basename */ | ||
| 422 | ✓✓ | 152587 | if ((p = strrchr(*t, '/')) != NULL) | 
| 423 | 1798 | ++p; | |
| 424 | else | ||
| 425 | 150789 | p = *t; | |
| 426 | |||
| 427 | ✓✓✓✗ ✗✓✗✗ | 156285 | 		if (ISDOT(p)) { | 
| 428 | if (!complained++) | ||
| 429 | 				warnx("\".\" and \"..\" may not be removed"); | ||
| 430 | skip: | ||
| 431 | eval = 1; | ||
| 432 | for (save = t; (t[0] = t[1]) != NULL; ++t) | ||
| 433 | continue; | ||
| 434 | t = save; | ||
| 435 | } else | ||
| 436 | 152587 | ++t; | |
| 437 | } | ||
| 438 | 32749 | } | |
| 439 | |||
| 440 | void | ||
| 441 | usage(void) | ||
| 442 | { | ||
| 443 | (void)fprintf(stderr, "usage: %s [-dfiPRrv] file ...\n", __progname); | ||
| 444 | exit(1); | ||
| 445 | } | 
| Generated by: GCOVR (Version 3.3) |