rm.c revision 1.4
1/* $OpenBSD: rm.c,v 1.4 2015/11/17 19:09:06 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 <locale.h> 38#include <err.h> 39#include <errno.h> 40#include <fcntl.h> 41#include <fts.h> 42#include <stdio.h> 43#include <stdlib.h> 44#include <string.h> 45#include <unistd.h> 46#include <limits.h> 47#include <pwd.h> 48#include <grp.h> 49 50#define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b)) 51 52extern char *__progname; 53 54static int eval, stdin_ok; 55 56static int check(char *, char *, struct stat *); 57static void checkdot(char **); 58static void rm_file(char **); 59static void rm_tree(char **); 60 61static void __dead 62usage(void) 63{ 64 (void)fprintf(stderr, "usage: %s [-dfiPRr] file ...\n", __progname); 65 exit(1); 66} 67 68/* 69 * rm -- 70 * This rm is different from historic rm's, but is expected to match 71 * POSIX 1003.2 behavior. The most visible difference is that -f 72 * has two specific effects now, ignore non-existent files and force 73 * file removal. 74 */ 75int 76rmmain(int argc, char *argv[]) 77{ 78 if (pledge("stdio rpath cpath", NULL) == -1) 79 err(1, "pledge"); 80 81 checkdot(argv); 82 83 if (*argv) { 84 stdin_ok = isatty(STDIN_FILENO); 85 86 rm_tree(argv); 87 } 88 89 return (eval); 90} 91 92static void 93rm_tree(char **argv) 94{ 95 FTS *fts; 96 FTSENT *p; 97 int flags; 98 99 /* 100 * If the -i option is specified, the user can skip on the pre-order 101 * visit. The fts_number field flags skipped directories. 102 */ 103#define SKIPPED 1 104 105 flags = FTS_PHYSICAL; 106 flags |= FTS_NOSTAT; 107 if (!(fts = fts_open(argv, flags, NULL))) 108 err(1, NULL); 109 while ((p = fts_read(fts)) != NULL) { 110 switch (p->fts_info) { 111 case FTS_DNR: 112 if (p->fts_errno != ENOENT) { 113 warnx("%s: %s", 114 p->fts_path, strerror(p->fts_errno)); 115 eval = 1; 116 } 117 continue; 118 case FTS_ERR: 119 errc(1, p->fts_errno, "%s", p->fts_path); 120 case FTS_NS: 121 /* 122 * FTS_NS: assume that if can't stat the file, it 123 * can't be unlinked. 124 */ 125 break; 126 case FTS_D: 127 /* Pre-order: give user chance to skip. */ 128 continue; 129 case FTS_DP: 130 /* Post-order: see if user skipped. */ 131 if (p->fts_number == SKIPPED) 132 continue; 133 break; 134 default: 135 break; 136 } 137 138 /* 139 * If we can't read or search the directory, may still be 140 * able to remove it. Don't print out the un{read,search}able 141 * message unless the remove fails. 142 */ 143 switch (p->fts_info) { 144 case FTS_DP: 145 case FTS_DNR: 146 if (!rmdir(p->fts_accpath) || 147 (errno == ENOENT)) 148 continue; 149 break; 150 151 case FTS_F: 152 case FTS_NSOK: 153 default: 154 if (!unlink(p->fts_accpath) || 155 (errno == ENOENT)) 156 continue; 157 } 158 warn("%s", p->fts_path); 159 eval = 1; 160 } 161 if (errno) 162 err(1, "fts_read"); 163 fts_close(fts); 164} 165 166static void 167rm_file(char **argv) 168{ 169 struct stat sb; 170 int rval; 171 char *f; 172 173 /* 174 * Remove a file. POSIX 1003.2 states that, by default, attempting 175 * to remove a directory is an error, so must always stat the file. 176 */ 177 while ((f = *argv++) != NULL) { 178 /* Assume if can't stat the file, can't unlink it. */ 179 if (lstat(f, &sb)) { 180 if (errno != ENOENT) { 181 warn("%s", f); 182 eval = 1; 183 } 184 continue; 185 } 186 187 if (S_ISDIR(sb.st_mode)) { 188 warnx("%s: is a directory", f); 189 eval = 1; 190 continue; 191 } 192 if (S_ISDIR(sb.st_mode)) 193 rval = rmdir(f); 194 else { 195 rval = unlink(f); 196 } 197 if (rval && (errno != ENOENT)) { 198 warn("%s", f); 199 eval = 1; 200 } 201 } 202} 203 204static int 205check(char *path, char *name, struct stat *sp) 206{ 207 int ch, first; 208 char modep[15]; 209 210 /* 211 * If it's not a symbolic link and it's unwritable and we're 212 * talking to a terminal, ask. Symbolic links are excluded 213 * because their permissions are meaningless. Check stdin_ok 214 * first because we may not have stat'ed the file. 215 */ 216 if (!stdin_ok || S_ISLNK(sp->st_mode) || !access(name, W_OK) || 217 errno != EACCES) 218 return (1); 219 strmode(sp->st_mode, modep); 220 (void)fprintf(stderr, "override %s%s%s/%s for %s? ", 221 modep + 1, modep[9] == ' ' ? "" : " ", 222 user_from_uid(sp->st_uid, 0), 223 group_from_gid(sp->st_gid, 0), path); 224 (void)fflush(stderr); 225 226 first = ch = getchar(); 227 while (ch != '\n' && ch != EOF) 228 ch = getchar(); 229 return (first == 'y' || first == 'Y'); 230} 231 232/* 233 * POSIX.2 requires that if "." or ".." are specified as the basename 234 * portion of an operand, a diagnostic message be written to standard 235 * error and nothing more be done with such operands. 236 * 237 * Since POSIX.2 defines basename as the final portion of a path after 238 * trailing slashes have been removed, we'll remove them here. 239 */ 240#define ISDOT(a) ((a)[0] == '.' && (!(a)[1] || ((a)[1] == '.' && !(a)[2]))) 241static void 242checkdot(char **argv) 243{ 244 char *p, **save, **t; 245 int complained; 246 247 complained = 0; 248 for (t = argv; *t;) { 249 /* strip trailing slashes */ 250 p = strrchr (*t, '\0'); 251 while (--p > *t && *p == '/') 252 *p = '\0'; 253 254 /* extract basename */ 255 if ((p = strrchr(*t, '/')) != NULL) 256 ++p; 257 else 258 p = *t; 259 260 if (ISDOT(p)) { 261 if (!complained++) 262 warnx("\".\" and \"..\" may not be removed"); 263 eval = 1; 264 for (save = t; (t[0] = t[1]) != NULL; ++t) 265 continue; 266 t = save; 267 } else 268 ++t; 269 } 270} 271