1 /*********************************************************************** 2 * * 3 * This software is part of the ast package * 4 * Copyright (c) 1992-2012 AT&T Intellectual Property * 5 * and is licensed under the * 6 * Eclipse Public License, Version 1.0 * 7 * by AT&T Intellectual Property * 8 * * 9 * A copy of the License is available at * 10 * http://www.eclipse.org/org/documents/epl-v10.html * 11 * (with md5 checksum b35adb5213ca9657e911e9befb180842) * 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) 2012-02-14 $\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, ignore no file operands specified," 51 " and never prompt the user.]" 52 "[i:interactive|prompt?Prompt whether to remove each file." 53 " An affirmative response (\by\b or \bY\b) removes the file, a quit" 54 " response (\bq\b or \bQ\b) causes \brm\b to exit immediately, and" 55 " all other responses skip the current file.]" 56 "[r|R:recursive?Remove the contents of directories recursively.]" 57 "[u:unconditional?If \b--recursive\b and \b--force\b are also enabled then" 58 " the owner read, write and execute modes are enabled (if not already" 59 " enabled) for each directory before attempting to remove directory" 60 " contents.]" 61 "[v:verbose?Print the name of each file before removing it.]" 62 63 "\n" 64 "\nfile ...\n" 65 "\n" 66 67 "[+SEE ALSO?\bmv\b(1), \brmdir\b(2), \bunlink\b(2), \bremove\b(3)]" 68 ; 69 70 #include <cmd.h> 71 #include <ls.h> 72 #include <fts_fix.h> 73 #include <fs3d.h> 74 75 #define RM_ENTRY 1 76 77 #define beenhere(f) (((f)->fts_number>>1)==(f)->fts_statp->st_nlink) 78 #define isempty(f) (!((f)->fts_number&RM_ENTRY)) 79 #define nonempty(f) ((f)->fts_parent->fts_number|=RM_ENTRY) 80 #define pathchunk(n) roundof(n,1024) 81 #define retry(f) ((f)->fts_number=((f)->fts_statp->st_nlink<<1)) 82 83 typedef struct State_s /* program state */ 84 { 85 Shbltin_t* context; /* builtin context */ 86 int clobber; /* clear out file data first */ 87 int directory; /* remove(dir) not rmdir(dir) */ 88 int force; /* force actions */ 89 int fs3d; /* 3d enabled */ 90 int interactive; /* prompt for approval */ 91 int recursive; /* remove subtrees too */ 92 int terminal; /* attached to terminal */ 93 int uid; /* caller uid */ 94 int unconditional; /* enable dir rwx on preorder */ 95 int verbose; /* display each file */ 96 #if _lib_fsync 97 char buf[SF_BUFSIZE];/* clobber buffer */ 98 #endif 99 } State_t; 100 101 /* 102 * remove a single file 103 */ 104 105 static int 106 rm(State_t* state, register FTSENT* ent) 107 { 108 register char* path; 109 register int n; 110 int v; 111 struct stat st; 112 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 (!beenhere(ent)) 127 break; 128 if (!chmod(ent->fts_name, (ent->fts_statp->st_mode & S_IPERM)|S_IRWXU)) 129 { 130 fts_set(NiL, ent, FTS_AGAIN); 131 break; 132 } 133 error_info.errors++; 134 } 135 else if (!state->force) 136 error(2, "%s: cannot %s directory", ent->fts_path, (ent->fts_info & FTS_NR) ? "read" : "search"); 137 else 138 error_info.errors++; 139 fts_set(NiL, ent, FTS_SKIP); 140 nonempty(ent); 141 break; 142 case FTS_D: 143 case FTS_DC: 144 path = ent->fts_name; 145 if (path[0] == '.' && (!path[1] || path[1] == '.' && !path[2]) && (ent->fts_level > 0 || path[1])) 146 { 147 fts_set(NiL, ent, FTS_SKIP); 148 if (!state->force) 149 error(2, "%s: cannot remove", ent->fts_path); 150 else 151 error_info.errors++; 152 break; 153 } 154 if (!state->recursive) 155 { 156 fts_set(NiL, ent, FTS_SKIP); 157 error(2, "%s: directory", ent->fts_path); 158 break; 159 } 160 if (!beenhere(ent)) 161 { 162 if (state->unconditional && (ent->fts_statp->st_mode & S_IRWXU) != S_IRWXU) 163 chmod(path, (ent->fts_statp->st_mode & S_IPERM)|S_IRWXU); 164 if (ent->fts_level > 0) 165 { 166 char* s; 167 168 if (ent->fts_accpath == ent->fts_name || !(s = strrchr(ent->fts_accpath, '/'))) 169 v = !stat(".", &st); 170 else 171 { 172 path = ent->fts_accpath; 173 *s = 0; 174 v = !stat(path, &st); 175 *s = '/'; 176 } 177 if (v) 178 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'); 179 } 180 else 181 v = 1; 182 if (v) 183 { 184 if (state->interactive) 185 { 186 if ((v = astquery(-1, "remove directory %s? ", ent->fts_path)) < 0 || sh_checksig(state->context)) 187 return -1; 188 if (v > 0) 189 { 190 fts_set(NiL, ent, FTS_SKIP); 191 nonempty(ent); 192 } 193 } 194 if (ent->fts_info == FTS_D) 195 break; 196 } 197 else 198 { 199 ent->fts_info = FTS_DC; 200 error(1, "%s: hard link to directory", ent->fts_path); 201 } 202 } 203 else if (ent->fts_info == FTS_D) 204 break; 205 /*FALLTHROUGH*/ 206 case FTS_DP: 207 if (isempty(ent) || state->directory) 208 { 209 path = ent->fts_name; 210 if (path[0] != '.' || path[1]) 211 { 212 path = ent->fts_accpath; 213 if (state->verbose) 214 sfputr(sfstdout, ent->fts_path, '\n'); 215 if ((ent->fts_info == FTS_DC || state->directory) ? remove(path) : rmdir(path)) 216 switch (errno) 217 { 218 case ENOENT: 219 break; 220 case EEXIST: 221 #if defined(ENOTEMPTY) && (ENOTEMPTY) != (EEXIST) 222 case ENOTEMPTY: 223 #endif 224 if (ent->fts_info == FTS_DP && !beenhere(ent)) 225 { 226 retry(ent); 227 fts_set(NiL, ent, FTS_AGAIN); 228 break; 229 } 230 /*FALLTHROUGH*/ 231 default: 232 nonempty(ent); 233 if (!state->force) 234 error(ERROR_SYSTEM|2, "%s: directory not removed", ent->fts_path); 235 else 236 error_info.errors++; 237 break; 238 } 239 } 240 else if (!state->force) 241 error(2, "%s: cannot remove", ent->fts_path); 242 else 243 error_info.errors++; 244 } 245 else 246 { 247 nonempty(ent); 248 if (!state->force) 249 error(2, "%s: directory not removed", ent->fts_path); 250 else 251 error_info.errors++; 252 } 253 break; 254 default: 255 path = ent->fts_accpath; 256 if (state->verbose) 257 sfputr(sfstdout, ent->fts_path, '\n'); 258 if (state->interactive) 259 { 260 if ((v = astquery(-1, "remove %s? ", ent->fts_path)) < 0 || sh_checksig(state->context)) 261 return -1; 262 if (v > 0) 263 { 264 nonempty(ent); 265 break; 266 } 267 } 268 else if (!(ent->fts_info & FTS_SL) && !state->force && state->terminal && eaccess(path, W_OK)) 269 { 270 if ((v = astquery(-1, "override protection %s for %s? ", 271 #ifdef ETXTBSY 272 errno == ETXTBSY ? "``running program''" : 273 #endif 274 ent->fts_statp->st_uid != state->uid ? "``not owner''" : 275 fmtmode(ent->fts_statp->st_mode & S_IPERM, 0) + 1, ent->fts_path)) < 0 || 276 sh_checksig(state->context)) 277 return -1; 278 if (v > 0) 279 { 280 nonempty(ent); 281 break; 282 } 283 } 284 #if _lib_fsync 285 if (state->clobber && S_ISREG(ent->fts_statp->st_mode) && ent->fts_statp->st_size > 0) 286 { 287 if ((n = open(path, O_WRONLY|O_cloexec)) < 0) 288 error(ERROR_SYSTEM|2, "%s: cannot clear data", ent->fts_path); 289 else 290 { 291 off_t c = ent->fts_statp->st_size; 292 293 for (;;) 294 { 295 if (write(n, state->buf, sizeof(state->buf)) != sizeof(state->buf)) 296 { 297 error(ERROR_SYSTEM|2, "%s: data clear error", ent->fts_path); 298 break; 299 } 300 if (c <= sizeof(state->buf)) 301 break; 302 c -= sizeof(state->buf); 303 } 304 fsync(n); 305 close(n); 306 } 307 } 308 #endif 309 if (remove(path)) 310 { 311 nonempty(ent); 312 switch (errno) 313 { 314 case ENOENT: 315 break; 316 default: 317 if (!state->force || state->interactive) 318 error(ERROR_SYSTEM|2, "%s: not removed", ent->fts_path); 319 else 320 error_info.errors++; 321 break; 322 } 323 } 324 break; 325 } 326 return 0; 327 } 328 329 int 330 b_rm(int argc, register char** argv, Shbltin_t* 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.context = context; 340 state.fs3d = fs3d(FS3D_TEST); 341 state.terminal = isatty(0); 342 for (;;) 343 { 344 switch (optget(argv, usage)) 345 { 346 case 'd': 347 state.directory = 1; 348 continue; 349 case 'f': 350 state.force = 1; 351 state.interactive = 0; 352 continue; 353 case 'i': 354 state.interactive = 1; 355 state.force = 0; 356 continue; 357 case 'r': 358 case 'R': 359 state.recursive = 1; 360 continue; 361 case 'F': 362 #if _lib_fsync 363 state.clobber = 1; 364 #else 365 error(1, "%s not implemented on this system", opt_info.name); 366 #endif 367 continue; 368 case 'u': 369 state.unconditional = 1; 370 continue; 371 case 'v': 372 state.verbose = 1; 373 continue; 374 case '?': 375 error(ERROR_USAGE|4, "%s", opt_info.arg); 376 break; 377 case ':': 378 error(2, "%s", opt_info.arg); 379 break; 380 } 381 break; 382 } 383 argv += opt_info.index; 384 if (*argv && streq(*argv, "-") && !streq(*(argv - 1), "--")) 385 argv++; 386 if (error_info.errors || !*argv && !state.force) 387 error(ERROR_USAGE|4, "%s", optusage(NiL)); 388 if (!*argv) 389 return 0; 390 391 /* 392 * do it 393 */ 394 395 if (state.interactive) 396 state.verbose = 0; 397 state.uid = geteuid(); 398 state.unconditional = state.unconditional && state.recursive && state.force; 399 if (state.recursive && state.fs3d) 400 { 401 set3d = state.fs3d; 402 state.fs3d = 0; 403 fs3d(0); 404 } 405 else 406 set3d = 0; 407 if (fts = fts_open(argv, FTS_PHYSICAL, NiL)) 408 { 409 while (!sh_checksig(context) && (ent = fts_read(fts)) && !rm(&state, ent)); 410 fts_close(fts); 411 } 412 else if (!state.force) 413 error(ERROR_SYSTEM|2, "%s: cannot remove", argv[0]); 414 if (set3d) 415 fs3d(set3d); 416 return error_info.errors != 0; 417 } 418