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