1 /*********************************************************************** 2 * * 3 * This software is part of the ast package * 4 * Copyright (c) 1992-2009 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) 2009-06-18 $\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 void* context; /* builtin context */ 85 int clobber; /* clear out file data first */ 86 int directory; /* remove(dir) not rmdir(dir) */ 87 int force; /* force actions */ 88 int fs3d; /* 3d enabled */ 89 int interactive; /* prompt for approval */ 90 int recursive; /* remove subtrees too */ 91 int terminal; /* attached to terminal */ 92 int uid; /* caller uid */ 93 int unconditional; /* enable dir rwx on preorder */ 94 int verbose; /* display each file */ 95 #if _lib_fsync 96 char buf[SF_BUFSIZE];/* clobber buffer */ 97 #endif 98 } State_t; 99 100 /* 101 * remove a single file 102 */ 103 104 static int 105 rm(State_t* state, register FTSENT* ent) 106 { 107 register char* path; 108 register int n; 109 int v; 110 struct stat st; 111 112 if (ent->fts_info == FTS_NS || ent->fts_info == FTS_ERR || ent->fts_info == FTS_SLNONE) 113 { 114 if (!state->force) 115 error(2, "%s: not found", ent->fts_path); 116 } 117 else if (state->fs3d && iview(ent->fts_statp)) 118 fts_set(NiL, ent, FTS_SKIP); 119 else switch (ent->fts_info) 120 { 121 case FTS_DNR: 122 case FTS_DNX: 123 if (state->unconditional) 124 { 125 if (!chmod(ent->fts_name, (ent->fts_statp->st_mode & S_IPERM)|S_IRWXU)) 126 { 127 fts_set(NiL, ent, FTS_AGAIN); 128 break; 129 } 130 error_info.errors++; 131 } 132 else if (!state->force) 133 error(2, "%s: cannot %s directory", ent->fts_path, (ent->fts_info & FTS_NR) ? "read" : "search"); 134 else 135 error_info.errors++; 136 fts_set(NiL, ent, FTS_SKIP); 137 nonempty(ent); 138 break; 139 case FTS_D: 140 case FTS_DC: 141 path = ent->fts_name; 142 if (path[0] == '.' && (!path[1] || path[1] == '.' && !path[2]) && (ent->fts_level > 0 || path[1])) 143 { 144 fts_set(NiL, ent, FTS_SKIP); 145 if (!state->force) 146 error(2, "%s: cannot remove", ent->fts_path); 147 else 148 error_info.errors++; 149 break; 150 } 151 if (!state->recursive) 152 { 153 fts_set(NiL, ent, FTS_SKIP); 154 error(2, "%s: directory", ent->fts_path); 155 break; 156 } 157 if (!beenhere(ent)) 158 { 159 if (state->unconditional && (ent->fts_statp->st_mode ^ S_IRWXU)) 160 chmod(path, (ent->fts_statp->st_mode & S_IPERM)|S_IRWXU); 161 if (ent->fts_level > 0) 162 { 163 char* s; 164 165 if (ent->fts_accpath == ent->fts_name || !(s = strrchr(ent->fts_accpath, '/'))) 166 v = !stat(".", &st); 167 else 168 { 169 path = ent->fts_accpath; 170 *s = 0; 171 v = !stat(path, &st); 172 *s = '/'; 173 } 174 if (v) 175 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'); 176 } 177 else 178 v = 1; 179 if (v) 180 { 181 if (state->interactive) 182 { 183 if ((v = astquery(-1, "remove directory %s? ", ent->fts_path)) < 0 || sh_checksig(state->context)) 184 return -1; 185 if (v > 0) 186 { 187 fts_set(NiL, ent, FTS_SKIP); 188 nonempty(ent); 189 } 190 } 191 if (ent->fts_info == FTS_D) 192 break; 193 } 194 else 195 { 196 ent->fts_info = FTS_DC; 197 error(1, "%s: hard link to directory", ent->fts_path); 198 } 199 } 200 else if (ent->fts_info == FTS_D) 201 break; 202 /*FALLTHROUGH*/ 203 case FTS_DP: 204 if (isempty(ent) || state->directory) 205 { 206 path = ent->fts_name; 207 if (path[0] != '.' || path[1]) 208 { 209 path = ent->fts_accpath; 210 if (state->verbose) 211 sfputr(sfstdout, ent->fts_path, '\n'); 212 if ((ent->fts_info == FTS_DC || state->directory) ? remove(path) : rmdir(path)) 213 switch (errno) 214 { 215 case ENOENT: 216 break; 217 case EEXIST: 218 #if defined(ENOTEMPTY) && (ENOTEMPTY) != (EEXIST) 219 case ENOTEMPTY: 220 #endif 221 if (ent->fts_info == FTS_DP && !beenhere(ent)) 222 { 223 retry(ent); 224 fts_set(NiL, ent, FTS_AGAIN); 225 break; 226 } 227 /*FALLTHROUGH*/ 228 default: 229 nonempty(ent); 230 if (!state->force) 231 error(ERROR_SYSTEM|2, "%s: directory not removed", ent->fts_path); 232 else 233 error_info.errors++; 234 break; 235 } 236 } 237 else if (!state->force) 238 error(2, "%s: cannot remove", ent->fts_path); 239 else 240 error_info.errors++; 241 } 242 else 243 { 244 nonempty(ent); 245 if (!state->force) 246 error(2, "%s: directory not removed", ent->fts_path); 247 else 248 error_info.errors++; 249 } 250 break; 251 default: 252 path = ent->fts_accpath; 253 if (state->verbose) 254 sfputr(sfstdout, ent->fts_path, '\n'); 255 if (state->interactive) 256 { 257 if ((v = astquery(-1, "remove %s? ", ent->fts_path)) < 0 || sh_checksig(state->context)) 258 return -1; 259 if (v > 0) 260 { 261 nonempty(ent); 262 break; 263 } 264 } 265 else if (!state->force && state->terminal && S_ISREG(ent->fts_statp->st_mode)) 266 { 267 if ((n = open(path, O_RDWR)) < 0) 268 { 269 if ( 270 #ifdef ENOENT 271 errno != ENOENT && 272 #endif 273 #ifdef EROFS 274 errno != EROFS && 275 #endif 276 (v = astquery(-1, "override protection %s for %s? ", 277 #ifdef ETXTBSY 278 errno == ETXTBSY ? "``running program''" : 279 #endif 280 ent->fts_statp->st_uid != state->uid ? "``not owner''" : 281 fmtmode(ent->fts_statp->st_mode & S_IPERM, 0) + 1, ent->fts_path)) < 0 || 282 sh_checksig(state->context)) 283 return -1; 284 if (v > 0) 285 { 286 nonempty(ent); 287 break; 288 } 289 } 290 else 291 close(n); 292 } 293 #if _lib_fsync 294 if (state->clobber && S_ISREG(ent->fts_statp->st_mode) && ent->fts_statp->st_size > 0) 295 { 296 if ((n = open(path, O_WRONLY)) < 0) 297 error(ERROR_SYSTEM|2, "%s: cannot clear data", ent->fts_path); 298 else 299 { 300 off_t c = ent->fts_statp->st_size; 301 302 for (;;) 303 { 304 if (write(n, state->buf, sizeof(state->buf)) != sizeof(state->buf)) 305 { 306 error(ERROR_SYSTEM|2, "%s: data clear error", ent->fts_path); 307 break; 308 } 309 if (c <= sizeof(state->buf)) 310 break; 311 c -= sizeof(state->buf); 312 } 313 fsync(n); 314 close(n); 315 } 316 } 317 #endif 318 if (remove(path)) 319 { 320 nonempty(ent); 321 switch (errno) 322 { 323 case ENOENT: 324 break; 325 default: 326 if (!state->force || state->interactive) 327 error(ERROR_SYSTEM|2, "%s: not removed", ent->fts_path); 328 else 329 error_info.errors++; 330 break; 331 } 332 } 333 break; 334 } 335 return 0; 336 } 337 338 int 339 b_rm(int argc, register char** argv, void* context) 340 { 341 State_t state; 342 FTS* fts; 343 FTSENT* ent; 344 int set3d; 345 346 cmdinit(argc, argv, context, ERROR_CATALOG, ERROR_NOTIFY); 347 memset(&state, 0, sizeof(state)); 348 state.context = context; 349 state.fs3d = fs3d(FS3D_TEST); 350 state.terminal = isatty(0); 351 for (;;) 352 { 353 switch (optget(argv, usage)) 354 { 355 case 'd': 356 state.directory = 1; 357 continue; 358 case 'f': 359 state.force = 1; 360 state.interactive = 0; 361 continue; 362 case 'i': 363 state.interactive = 1; 364 state.force = 0; 365 continue; 366 case 'r': 367 case 'R': 368 state.recursive = 1; 369 continue; 370 case 'F': 371 #if _lib_fsync 372 state.clobber = 1; 373 #else 374 error(1, "%s not implemented on this system", opt_info.name); 375 #endif 376 continue; 377 case 'u': 378 state.unconditional = 1; 379 continue; 380 case 'v': 381 state.verbose = 1; 382 continue; 383 case '?': 384 error(ERROR_USAGE|4, "%s", opt_info.arg); 385 continue; 386 case ':': 387 error(2, "%s", opt_info.arg); 388 continue; 389 } 390 break; 391 } 392 argv += opt_info.index; 393 if (*argv && streq(*argv, "-") && !streq(*(argv - 1), "--")) 394 argv++; 395 if (error_info.errors || !*argv) 396 error(ERROR_USAGE|4, "%s", optusage(NiL)); 397 398 /* 399 * do it 400 */ 401 402 if (state.interactive) 403 state.verbose = 0; 404 state.uid = geteuid(); 405 state.unconditional = state.unconditional && state.recursive && state.force; 406 if (state.recursive && state.fs3d) 407 { 408 set3d = state.fs3d; 409 state.fs3d = 0; 410 fs3d(0); 411 } 412 else 413 set3d = 0; 414 if (fts = fts_open(argv, FTS_PHYSICAL, NiL)) 415 { 416 while (!sh_checksig(context) && (ent = fts_read(fts)) && !rm(&state, ent)); 417 fts_close(fts); 418 } 419 else if (!state.force) 420 error(ERROR_SYSTEM|2, "%s: cannot remove", argv[0]); 421 if (set3d) 422 fs3d(set3d); 423 return error_info.errors != 0; 424 } 425