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