1 /*- 2 * Copyright (c) 1990 The Regents of the University of California. 3 * All rights reserved. 4 * 5 * %sccs.include.redist.c% 6 */ 7 8 #ifndef lint 9 char copyright[] = 10 "@(#) Copyright (c) 1990 The Regents of the University of California.\n\ 11 All rights reserved.\n"; 12 #endif /* not lint */ 13 14 #ifndef lint 15 static char sccsid[] = "@(#)rm.c 4.27 (Berkeley) 01/27/92"; 16 #endif /* not lint */ 17 18 #include <sys/types.h> 19 #include <sys/stat.h> 20 #include <sys/errno.h> 21 #include <fts.h> 22 #include <unistd.h> 23 #include <stdio.h> 24 #include <string.h> 25 #include <stdlib.h> 26 27 int dflag, fflag, iflag, retval, stdin_ok; 28 29 /* 30 * rm -- 31 * This rm is different from historic rm's, but is expected to match 32 * POSIX 1003.2 behavior. The most visible difference is that -f 33 * has two specific effects now, ignore non-existent files and force 34 * file removal. 35 */ 36 37 main(argc, argv) 38 int argc; 39 char **argv; 40 { 41 extern char *optarg; 42 extern int optind; 43 int ch, rflag; 44 45 rflag = 0; 46 while ((ch = getopt(argc, argv, "dfiRr")) != EOF) 47 switch(ch) { 48 case 'd': 49 dflag = 1; 50 break; 51 case 'f': 52 fflag = 1; 53 iflag = 0; 54 break; 55 case 'i': 56 fflag = 0; 57 iflag = 1; 58 break; 59 case 'R': 60 case 'r': /* compatibility */ 61 rflag = 1; 62 break; 63 case '?': 64 default: 65 usage(); 66 } 67 argc -= optind; 68 argv += optind; 69 70 if (argc < 1) 71 usage(); 72 73 checkdot(argv); 74 if (!*argv) 75 exit(retval); 76 77 stdin_ok = isatty(STDIN_FILENO); 78 79 if (rflag) 80 rmtree(argv); 81 else 82 rmfile(argv); 83 exit(retval); 84 } 85 86 rmtree(argv) 87 char **argv; 88 { 89 register FTS *fts; 90 register FTSENT *p; 91 register int needstat; 92 struct stat sb; 93 94 /* 95 * Remove a file hierarchy. If forcing removal (-f), or interactive 96 * (-i) or can't ask anyway (stdin_ok), don't stat the file. 97 */ 98 needstat = !fflag && !iflag && stdin_ok; 99 100 /* 101 * If the -i option is specified, the user can skip on the pre-order 102 * visit. The fts_number field flags skipped directories. 103 */ 104 #define SKIPPED 1 105 106 if (!(fts = fts_open(argv, 107 needstat ? FTS_PHYSICAL : FTS_PHYSICAL|FTS_NOSTAT, 108 (int (*)())NULL))) { 109 (void)fprintf(stderr, "rm: %s.\n", strerror(errno)); 110 exit(1); 111 } 112 while (p = fts_read(fts)) { 113 switch(p->fts_info) { 114 case FTS_DNR: 115 case FTS_ERR: 116 error(p->fts_path, errno); 117 exit(1); 118 /* 119 * FTS_NS: assume that if can't stat the file, it can't be 120 * unlinked. 121 */ 122 case FTS_NS: 123 if (!needstat) 124 break; 125 if (!fflag || errno != ENOENT) 126 error(p->fts_path, errno); 127 continue; 128 /* Pre-order: give user chance to skip. */ 129 case FTS_D: 130 if (iflag && !check(p->fts_path, p->fts_accpath, 131 p->fts_statp)) { 132 (void)fts_set(fts, p, FTS_SKIP); 133 p->fts_number = SKIPPED; 134 } 135 continue; 136 /* Post-order: see if user skipped. */ 137 case FTS_DP: 138 if (p->fts_number == SKIPPED) 139 continue; 140 break; 141 } 142 143 if (!fflag && 144 !check(p->fts_path, p->fts_accpath, p->fts_statp)) 145 continue; 146 147 /* 148 * If we can't read or search the directory, may still be 149 * able to remove it. Don't print out the un{read,search}able 150 * message unless the remove fails. 151 */ 152 if (p->fts_info == FTS_DP || p->fts_info == FTS_DNR) { 153 if (!rmdir(p->fts_accpath)) 154 continue; 155 if (errno == ENOENT) { 156 if (fflag) 157 continue; 158 } else if (p->fts_info != FTS_DP) 159 (void)fprintf(stderr, 160 "rm: unable to read %s.\n", p->fts_path); 161 } else if (!unlink(p->fts_accpath) || fflag && errno == ENOENT) 162 continue; 163 error(p->fts_path, errno); 164 } 165 } 166 167 rmfile(argv) 168 char **argv; 169 { 170 register int df; 171 register char *f; 172 struct stat sb; 173 174 df = dflag; 175 /* 176 * Remove a file. POSIX 1003.2 states that, by default, attempting 177 * to remove a directory is an error, so must always stat the file. 178 */ 179 while (f = *argv++) { 180 /* Assume if can't stat the file, can't unlink it. */ 181 if (lstat(f, &sb)) { 182 if (!fflag || errno != ENOENT) 183 error(f, errno); 184 continue; 185 } 186 if (S_ISDIR(sb.st_mode) && !df) { 187 (void)fprintf(stderr, "rm: %s: is a directory\n", f); 188 retval = 1; 189 continue; 190 } 191 if (!fflag && !check(f, f, &sb)) 192 continue; 193 if ((S_ISDIR(sb.st_mode) ? rmdir(f) : unlink(f)) && 194 (!fflag || errno != ENOENT)) 195 error(f, errno); 196 } 197 } 198 199 check(path, name, sp) 200 char *path, *name; 201 struct stat *sp; 202 { 203 register int first, ch; 204 char modep[15], *user_from_uid(), *group_from_gid(); 205 206 /* Check -i first. */ 207 if (iflag) 208 (void)fprintf(stderr, "remove %s? ", path); 209 else { 210 /* 211 * If it's not a symbolic link and it's unwritable and we're 212 * talking to a terminal, ask. Symbolic links are excluded 213 * because their permissions are meaningless. 214 */ 215 if (S_ISLNK(sp->st_mode) || !stdin_ok || !access(name, W_OK)) 216 return(1); 217 strmode(sp->st_mode, modep); 218 (void)fprintf(stderr, "override %s%s%s/%s for %s? ", 219 modep + 1, modep[9] == ' ' ? "" : " ", 220 user_from_uid(sp->st_uid, 0), 221 group_from_gid(sp->st_gid, 0), path); 222 } 223 (void)fflush(stderr); 224 225 first = ch = getchar(); 226 while (ch != '\n' && ch != EOF) 227 ch = getchar(); 228 return(first == 'y'); 229 } 230 231 #define ISDOT(a) ((a)[0] == '.' && (!(a)[1] || (a)[1] == '.' && !(a)[2])) 232 checkdot(argv) 233 char **argv; 234 { 235 register char *p, **t, **save; 236 int complained; 237 238 complained = 0; 239 for (t = argv; *t;) { 240 if (p = rindex(*t, '/')) 241 ++p; 242 else 243 p = *t; 244 if (ISDOT(p)) { 245 if (!complained++) 246 (void)fprintf(stderr, 247 "rm: \".\" and \"..\" may not be removed.\n"); 248 retval = 1; 249 for (save = t; t[0] = t[1]; ++t); 250 t = save; 251 } else 252 ++t; 253 } 254 } 255 256 error(name, val) 257 char *name; 258 int val; 259 { 260 (void)fprintf(stderr, "rm: %s: %s.\n", name, strerror(val)); 261 retval = 1; 262 } 263 264 usage() 265 { 266 (void)fprintf(stderr, "usage: rm [-dfiRr] file ...\n"); 267 exit(1); 268 } 269