1 /*********************************************************************** 2 * * 3 * This software is part of the ast package * 4 * Copyright (c) 1992-2007 AT&T Knowledge Ventures * 5 * and is licensed under the * 6 * Common Public License, Version 1.0 * 7 * by AT&T Knowledge Ventures * 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) 2006-11-21 $\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 (cmdquit()) 112 return -1; 113 if (ent->fts_info == FTS_NS || ent->fts_info == FTS_ERR || ent->fts_info == FTS_SLNONE) 114 { 115 if (!state->force) 116 error(2, "%s: not found", ent->fts_path); 117 } 118 else if (state->fs3d && iview(ent->fts_statp)) 119 fts_set(NiL, ent, FTS_SKIP); 120 else switch (ent->fts_info) 121 { 122 case FTS_DNR: 123 case FTS_DNX: 124 if (state->unconditional) 125 { 126 if (!chmod(ent->fts_name, (ent->fts_statp->st_mode & S_IPERM)|S_IRWXU)) 127 { 128 fts_set(NiL, ent, FTS_AGAIN); 129 break; 130 } 131 error_info.errors++; 132 } 133 else if (!state->force) 134 error(2, "%s: cannot %s directory", ent->fts_path, (ent->fts_info & FTS_NR) ? "read" : "search"); 135 else 136 error_info.errors++; 137 fts_set(NiL, ent, FTS_SKIP); 138 nonempty(ent); 139 break; 140 case FTS_D: 141 case FTS_DC: 142 path = ent->fts_name; 143 if (path[0] == '.' && (!path[1] || path[1] == '.' && !path[2]) && (ent->fts_level > 0 || path[1])) 144 { 145 fts_set(NiL, ent, FTS_SKIP); 146 if (!state->force) 147 error(2, "%s: cannot remove", ent->fts_path); 148 else 149 error_info.errors++; 150 break; 151 } 152 if (!state->recursive) 153 { 154 fts_set(NiL, ent, FTS_SKIP); 155 error(2, "%s: directory", ent->fts_path); 156 break; 157 } 158 if (!beenhere(ent)) 159 { 160 if (state->unconditional && (ent->fts_statp->st_mode ^ S_IRWXU)) 161 chmod(path, (ent->fts_statp->st_mode & S_IPERM)|S_IRWXU); 162 if (ent->fts_level > 0) 163 { 164 char* s; 165 166 if (ent->fts_accpath == ent->fts_name || !(s = strrchr(ent->fts_accpath, '/'))) 167 v = !stat(".", &st); 168 else 169 { 170 path = ent->fts_accpath; 171 *s = 0; 172 v = !stat(path, &st); 173 *s = '/'; 174 } 175 if (v) 176 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'); 177 } 178 else 179 v = 1; 180 if (v) 181 { 182 if (state->interactive) 183 { 184 if ((v = astquery(-1, "remove directory %s? ", ent->fts_path)) < 0) 185 return -1; 186 if (v > 0) 187 { 188 fts_set(NiL, ent, FTS_SKIP); 189 nonempty(ent); 190 } 191 } 192 if (ent->fts_info == FTS_D) 193 break; 194 } 195 else 196 { 197 ent->fts_info = FTS_DC; 198 error(1, "%s: hard link to directory", ent->fts_path); 199 } 200 } 201 else if (ent->fts_info == FTS_D) 202 break; 203 /*FALLTHROUGH*/ 204 case FTS_DP: 205 if (isempty(ent) || state->directory) 206 { 207 path = ent->fts_name; 208 if (path[0] != '.' || path[1]) 209 { 210 path = ent->fts_accpath; 211 if (state->verbose) 212 sfputr(sfstdout, ent->fts_path, '\n'); 213 if ((ent->fts_info == FTS_DC || state->directory) ? remove(path) : rmdir(path)) 214 switch (errno) 215 { 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 if (!state->force || state->interactive) 320 error(ERROR_SYSTEM|2, "%s: not removed", ent->fts_path); 321 else 322 error_info.errors++; 323 } 324 break; 325 } 326 return 0; 327 } 328 329 int 330 b_rm(int argc, register char** argv, void* context) 331 { 332 State_t state; 333 FTS* fts; 334 FTSENT* ent; 335 int set3d; 336 337 cmdinit(argc, argv, context, ERROR_CATALOG, ERROR_NOTIFY); 338 memset(&state, 0, sizeof(state)); 339 state.fs3d = fs3d(FS3D_TEST); 340 state.terminal = isatty(0); 341 for (;;) 342 { 343 switch (optget(argv, usage)) 344 { 345 case 'd': 346 state.directory = 1; 347 continue; 348 case 'f': 349 state.force = 1; 350 state.interactive = 0; 351 continue; 352 case 'i': 353 state.interactive = 1; 354 state.force = 0; 355 continue; 356 case 'r': 357 case 'R': 358 state.recursive = 1; 359 continue; 360 case 'F': 361 #if _lib_fsync 362 state.clobber = 1; 363 #else 364 error(1, "%s not implemented on this system", opt_info.name); 365 #endif 366 continue; 367 case 'u': 368 state.unconditional = 1; 369 continue; 370 case 'v': 371 state.verbose = 1; 372 continue; 373 case '?': 374 error(ERROR_USAGE|4, "%s", opt_info.arg); 375 continue; 376 case ':': 377 error(2, "%s", opt_info.arg); 378 continue; 379 } 380 break; 381 } 382 argv += opt_info.index; 383 if (*argv && streq(*argv, "-") && !streq(*(argv - 1), "--")) 384 argv++; 385 if (error_info.errors || !*argv) 386 error(ERROR_USAGE|4, "%s", optusage(NiL)); 387 388 /* 389 * do it 390 */ 391 392 if (state.interactive) 393 state.verbose = 0; 394 state.uid = geteuid(); 395 state.unconditional = state.unconditional && state.recursive && state.force; 396 if (state.recursive && state.fs3d) 397 { 398 set3d = state.fs3d; 399 state.fs3d = 0; 400 fs3d(0); 401 } 402 else 403 set3d = 0; 404 if (fts = fts_open(argv, FTS_PHYSICAL, NiL)) 405 { 406 while ((ent = fts_read(fts)) && !rm(&state, ent)); 407 fts_close(fts); 408 } 409 else if (!state.force) 410 error(ERROR_SYSTEM|2, "%s: cannot remove", argv[0]); 411 if (set3d) 412 fs3d(set3d); 413 return error_info.errors != 0; 414 } 415