1 /*********************************************************************** 2 * * 3 * This software is part of the ast package * 4 * Copyright (c) 1992-2008 AT&T Intellectual Property * 5 * and is licensed under the * 6 * Common Public License, Version 1.0 * 7 * by AT&T Intellectual Property * 8 * * 9 * A copy of the License is available at * 10 * http://www.opensource.org/licenses/cpl1.0.txt * 11 * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * 12 * * 13 * Information and Software Systems Research * 14 * AT&T Research * 15 * Florham Park NJ * 16 * * 17 * Glenn Fowler <gsf@research.att.com> * 18 * David Korn <dgk@research.att.com> * 19 * * 20 ***********************************************************************/ 21 #pragma prototyped 22 /* 23 * Glenn Fowler 24 * AT&T Research 25 * 26 * rm [-fir] [file ...] 27 */ 28 29 static const char usage[] = 30 "[-?\n@(#)$Id: rm (AT&T Research) 2008-10-15 $\n]" 31 USAGE_LICENSE 32 "[+NAME?rm - remove files]" 33 "[+DESCRIPTION?\brm\b removes the named \afile\a arguments. By default it" 34 " does not remove directories. If a file is unwritable, the" 35 " standard input is a terminal, and the \b--force\b option is not" 36 " given, \brm\b prompts the user for whether to remove the file." 37 " An affirmative response (\by\b or \bY\b) removes the file, a quit" 38 " response (\bq\b or \bQ\b) causes \brm\b to exit immediately, and" 39 " all other responses skip the current file.]" 40 41 "[c|F:clear|clobber?Clear the contents of each file before removing by" 42 " writing a 0 filled buffer the same size as the file, executing" 43 " \bfsync\b(2) and closing before attempting to remove. Implemented" 44 " only on systems that support \bfsync\b(2).]" 45 "[d:directory?\bremove\b(3) (or \bunlink\b(2)) directories rather than" 46 " \brmdir\b(2), and don't require that they be empty before removal." 47 " The caller requires sufficient privilege, not to mention a strong" 48 " constitution, to use this option. Even though the directory must" 49 " not be empty, \brm\b still attempts to empty it before removal.]" 50 "[f:force?Ignore nonexistent files and never prompt the user.]" 51 "[i:interactive|prompt?Prompt whether to remove each file." 52 " An affirmative response (\by\b or \bY\b) removes the file, a quit" 53 " response (\bq\b or \bQ\b) causes \brm\b to exit immediately, and" 54 " all other responses skip the current file.]" 55 "[r|R:recursive?Remove the contents of directories recursively.]" 56 "[u:unconditional?If \b--recursive\b and \b--force\b are also enabled then" 57 " the owner read, write and execute modes are enabled (if not already" 58 " enabled) for each directory before attempting to remove directory" 59 " contents.]" 60 "[v:verbose?Print the name of each file before removing it.]" 61 62 "\n" 63 "\nfile ...\n" 64 "\n" 65 66 "[+SEE ALSO?\bmv\b(1), \brmdir\b(2), \bunlink\b(2), \bremove\b(3)]" 67 ; 68 69 #include <cmd.h> 70 #include <ls.h> 71 #include <fts.h> 72 #include <fs3d.h> 73 74 #define RM_ENTRY 1 75 76 #define beenhere(f) (((f)->fts_number>>1)==(f)->fts_statp->st_nlink) 77 #define isempty(f) (!((f)->fts_number&RM_ENTRY)) 78 #define nonempty(f) ((f)->fts_parent->fts_number|=RM_ENTRY) 79 #define pathchunk(n) roundof(n,1024) 80 #define retry(f) ((f)->fts_number=((f)->fts_statp->st_nlink<<1)) 81 82 typedef struct State_s /* program state */ 83 { 84 int clobber; /* clear out file data first */ 85 int directory; /* remove(dir) not rmdir(dir) */ 86 int force; /* force actions */ 87 int fs3d; /* 3d enabled */ 88 int interactive; /* prompt for approval */ 89 int recursive; /* remove subtrees too */ 90 int terminal; /* attached to terminal */ 91 int uid; /* caller uid */ 92 int unconditional; /* enable dir rwx on preorder */ 93 int verbose; /* display each file */ 94 #if _lib_fsync 95 char buf[SF_BUFSIZE];/* clobber buffer */ 96 #endif 97 } State_t; 98 99 /* 100 * remove a single file 101 */ 102 103 static int 104 rm(State_t* state, register FTSENT* ent) 105 { 106 register char* path; 107 register int n; 108 int v; 109 struct stat st; 110 111 if (ent->fts_info == FTS_NS || ent->fts_info == FTS_ERR || ent->fts_info == FTS_SLNONE) 112 { 113 if (!state->force) 114 error(2, "%s: not found", ent->fts_path); 115 } 116 else if (state->fs3d && iview(ent->fts_statp)) 117 fts_set(NiL, ent, FTS_SKIP); 118 else switch (ent->fts_info) 119 { 120 case FTS_DNR: 121 case FTS_DNX: 122 if (state->unconditional) 123 { 124 if (!chmod(ent->fts_name, (ent->fts_statp->st_mode & S_IPERM)|S_IRWXU)) 125 { 126 fts_set(NiL, ent, FTS_AGAIN); 127 break; 128 } 129 error_info.errors++; 130 } 131 else if (!state->force) 132 error(2, "%s: cannot %s directory", ent->fts_path, (ent->fts_info & FTS_NR) ? "read" : "search"); 133 else 134 error_info.errors++; 135 fts_set(NiL, ent, FTS_SKIP); 136 nonempty(ent); 137 break; 138 case FTS_D: 139 case FTS_DC: 140 path = ent->fts_name; 141 if (path[0] == '.' && (!path[1] || path[1] == '.' && !path[2]) && (ent->fts_level > 0 || path[1])) 142 { 143 fts_set(NiL, ent, FTS_SKIP); 144 if (!state->force) 145 error(2, "%s: cannot remove", ent->fts_path); 146 else 147 error_info.errors++; 148 break; 149 } 150 if (!state->recursive) 151 { 152 fts_set(NiL, ent, FTS_SKIP); 153 error(2, "%s: directory", ent->fts_path); 154 break; 155 } 156 if (!beenhere(ent)) 157 { 158 if (state->unconditional && (ent->fts_statp->st_mode ^ S_IRWXU)) 159 chmod(path, (ent->fts_statp->st_mode & S_IPERM)|S_IRWXU); 160 if (ent->fts_level > 0) 161 { 162 char* s; 163 164 if (ent->fts_accpath == ent->fts_name || !(s = strrchr(ent->fts_accpath, '/'))) 165 v = !stat(".", &st); 166 else 167 { 168 path = ent->fts_accpath; 169 *s = 0; 170 v = !stat(path, &st); 171 *s = '/'; 172 } 173 if (v) 174 v = st.st_nlink <= 2 || st.st_ino == ent->fts_parent->fts_statp->st_ino && st.st_dev == ent->fts_parent->fts_statp->st_dev || strchr(astconf("PATH_ATTRIBUTES", path, NiL), 'l'); 175 } 176 else 177 v = 1; 178 if (v) 179 { 180 if (state->interactive) 181 { 182 if ((v = astquery(-1, "remove directory %s? ", ent->fts_path)) < 0) 183 return -1; 184 if (v > 0) 185 { 186 fts_set(NiL, ent, FTS_SKIP); 187 nonempty(ent); 188 } 189 } 190 if (ent->fts_info == FTS_D) 191 break; 192 } 193 else 194 { 195 ent->fts_info = FTS_DC; 196 error(1, "%s: hard link to directory", ent->fts_path); 197 } 198 } 199 else if (ent->fts_info == FTS_D) 200 break; 201 /*FALLTHROUGH*/ 202 case FTS_DP: 203 if (isempty(ent) || state->directory) 204 { 205 path = ent->fts_name; 206 if (path[0] != '.' || path[1]) 207 { 208 path = ent->fts_accpath; 209 if (state->verbose) 210 sfputr(sfstdout, ent->fts_path, '\n'); 211 if ((ent->fts_info == FTS_DC || state->directory) ? remove(path) : rmdir(path)) 212 switch (errno) 213 { 214 case ENOENT: 215 break; 216 case EEXIST: 217 #if defined(ENOTEMPTY) && (ENOTEMPTY) != (EEXIST) 218 case ENOTEMPTY: 219 #endif 220 if (ent->fts_info == FTS_DP && !beenhere(ent)) 221 { 222 retry(ent); 223 fts_set(NiL, ent, FTS_AGAIN); 224 break; 225 } 226 /*FALLTHROUGH*/ 227 default: 228 nonempty(ent); 229 if (!state->force) 230 error(ERROR_SYSTEM|2, "%s: directory not removed", ent->fts_path); 231 else 232 error_info.errors++; 233 break; 234 } 235 } 236 else if (!state->force) 237 error(2, "%s: cannot remove", ent->fts_path); 238 else 239 error_info.errors++; 240 } 241 else 242 { 243 nonempty(ent); 244 if (!state->force) 245 error(2, "%s: directory not removed", ent->fts_path); 246 else 247 error_info.errors++; 248 } 249 break; 250 default: 251 path = ent->fts_accpath; 252 if (state->verbose) 253 sfputr(sfstdout, ent->fts_path, '\n'); 254 if (state->interactive) 255 { 256 if ((v = astquery(-1, "remove %s? ", ent->fts_path)) < 0) 257 return -1; 258 if (v > 0) 259 { 260 nonempty(ent); 261 break; 262 } 263 } 264 else if (!state->force && state->terminal && S_ISREG(ent->fts_statp->st_mode)) 265 { 266 if ((n = open(path, O_RDWR)) < 0) 267 { 268 if ( 269 #ifdef ENOENT 270 errno != ENOENT && 271 #endif 272 #ifdef EROFS 273 errno != EROFS && 274 #endif 275 (v = astquery(-1, "override protection %s for %s? ", 276 #ifdef ETXTBSY 277 errno == ETXTBSY ? "``running program''" : 278 #endif 279 ent->fts_statp->st_uid != state->uid ? "``not owner''" : 280 fmtmode(ent->fts_statp->st_mode & S_IPERM, 0) + 1, ent->fts_path)) < 0) 281 return -1; 282 if (v > 0) 283 { 284 nonempty(ent); 285 break; 286 } 287 } 288 else 289 close(n); 290 } 291 #if _lib_fsync 292 if (state->clobber && S_ISREG(ent->fts_statp->st_mode) && ent->fts_statp->st_size > 0) 293 { 294 if ((n = open(path, O_WRONLY)) < 0) 295 error(ERROR_SYSTEM|2, "%s: cannot clear data", ent->fts_path); 296 else 297 { 298 off_t c = ent->fts_statp->st_size; 299 300 for (;;) 301 { 302 if (write(n, state->buf, sizeof(state->buf)) != sizeof(state->buf)) 303 { 304 error(ERROR_SYSTEM|2, "%s: data clear error", ent->fts_path); 305 break; 306 } 307 if (c <= sizeof(state->buf)) 308 break; 309 c -= sizeof(state->buf); 310 } 311 fsync(n); 312 close(n); 313 } 314 } 315 #endif 316 if (remove(path)) 317 { 318 nonempty(ent); 319 switch (errno) 320 { 321 case ENOENT: 322 break; 323 default: 324 if (!state->force || state->interactive) 325 error(ERROR_SYSTEM|2, "%s: not removed", ent->fts_path); 326 else 327 error_info.errors++; 328 break; 329 } 330 } 331 break; 332 } 333 return 0; 334 } 335 336 int 337 b_rm(int argc, register char** argv, void* context) 338 { 339 State_t state; 340 FTS* fts; 341 FTSENT* ent; 342 int set3d; 343 344 cmdinit(argc, argv, context, ERROR_CATALOG, ERROR_NOTIFY); 345 memset(&state, 0, sizeof(state)); 346 state.fs3d = fs3d(FS3D_TEST); 347 state.terminal = isatty(0); 348 for (;;) 349 { 350 switch (optget(argv, usage)) 351 { 352 case 'd': 353 state.directory = 1; 354 continue; 355 case 'f': 356 state.force = 1; 357 state.interactive = 0; 358 continue; 359 case 'i': 360 state.interactive = 1; 361 state.force = 0; 362 continue; 363 case 'r': 364 case 'R': 365 state.recursive = 1; 366 continue; 367 case 'F': 368 #if _lib_fsync 369 state.clobber = 1; 370 #else 371 error(1, "%s not implemented on this system", opt_info.name); 372 #endif 373 continue; 374 case 'u': 375 state.unconditional = 1; 376 continue; 377 case 'v': 378 state.verbose = 1; 379 continue; 380 case '?': 381 error(ERROR_USAGE|4, "%s", opt_info.arg); 382 continue; 383 case ':': 384 error(2, "%s", opt_info.arg); 385 continue; 386 } 387 break; 388 } 389 argv += opt_info.index; 390 if (*argv && streq(*argv, "-") && !streq(*(argv - 1), "--")) 391 argv++; 392 if (error_info.errors || !*argv) 393 error(ERROR_USAGE|4, "%s", optusage(NiL)); 394 395 /* 396 * do it 397 */ 398 399 if (state.interactive) 400 state.verbose = 0; 401 state.uid = geteuid(); 402 state.unconditional = state.unconditional && state.recursive && state.force; 403 if (state.recursive && state.fs3d) 404 { 405 set3d = state.fs3d; 406 state.fs3d = 0; 407 fs3d(0); 408 } 409 else 410 set3d = 0; 411 if (fts = fts_open(argv, FTS_PHYSICAL, NiL)) 412 { 413 while (!sh_checksig(context) && (ent = fts_read(fts)) && !rm(&state, ent)); 414 fts_close(fts); 415 } 416 else if (!state.force) 417 error(ERROR_SYSTEM|2, "%s: cannot remove", argv[0]); 418 if (set3d) 419 fs3d(set3d); 420 return error_info.errors != 0; 421 } 422