Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/bin/rm/rm.c
39475 views
1
/*-
2
* SPDX-License-Identifier: BSD-3-Clause
3
*
4
* Copyright (c) 1990, 1993, 1994
5
* The Regents of the University of California. All rights reserved.
6
*
7
* Redistribution and use in source and binary forms, with or without
8
* modification, are permitted provided that the following conditions
9
* are met:
10
* 1. Redistributions of source code must retain the above copyright
11
* notice, this list of conditions and the following disclaimer.
12
* 2. Redistributions in binary form must reproduce the above copyright
13
* notice, this list of conditions and the following disclaimer in the
14
* documentation and/or other materials provided with the distribution.
15
* 3. Neither the name of the University nor the names of its contributors
16
* may be used to endorse or promote products derived from this software
17
* without specific prior written permission.
18
*
19
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29
* SUCH DAMAGE.
30
*/
31
32
#include <sys/stat.h>
33
#include <sys/param.h>
34
#include <sys/mount.h>
35
36
#include <err.h>
37
#include <errno.h>
38
#include <fcntl.h>
39
#include <fts.h>
40
#include <grp.h>
41
#include <locale.h>
42
#include <pwd.h>
43
#include <stdio.h>
44
#include <stdlib.h>
45
#include <string.h>
46
#include <sysexits.h>
47
#include <unistd.h>
48
49
static int dflag, eval, fflag, iflag, vflag, Wflag, stdin_ok;
50
static int rflag, Iflag, xflag;
51
static uid_t uid;
52
static volatile sig_atomic_t info;
53
54
static int check(const char *, const char *, struct stat *);
55
static int check2(char **);
56
static void checkdot(char **);
57
static void checkslash(char **);
58
static void rm_file(char **);
59
static void rm_tree(char **);
60
static void siginfo(int __unused);
61
static void usage(void);
62
63
/*
64
* rm --
65
* This rm is different from historic rm's, but is expected to match
66
* POSIX 1003.2 behavior. The most visible difference is that -f
67
* has two specific effects now, ignore non-existent files and force
68
* file removal.
69
*/
70
int
71
main(int argc, char *argv[])
72
{
73
int ch;
74
char *p;
75
76
(void)setlocale(LC_ALL, "");
77
78
/*
79
* Test for the special case where the utility is called as
80
* "unlink", for which the functionality provided is greatly
81
* simplified.
82
*/
83
if ((p = strrchr(argv[0], '/')) == NULL)
84
p = argv[0];
85
else
86
++p;
87
if (strcmp(p, "unlink") == 0) {
88
if (argc == 2)
89
rm_file(&argv[1]);
90
else if (argc == 3 && strcmp(argv[1], "--") == 0)
91
rm_file(&argv[2]);
92
else
93
usage();
94
exit(eval);
95
}
96
97
rflag = xflag = 0;
98
while ((ch = getopt(argc, argv, "dfiIPRrvWx")) != -1)
99
switch(ch) {
100
case 'd':
101
dflag = 1;
102
break;
103
case 'f':
104
fflag = 1;
105
iflag = 0;
106
break;
107
case 'i':
108
fflag = 0;
109
iflag = 1;
110
break;
111
case 'I':
112
Iflag = 1;
113
break;
114
case 'P':
115
/* Compatibility no-op. */
116
break;
117
case 'R':
118
case 'r': /* Compatibility. */
119
rflag = 1;
120
break;
121
case 'v':
122
vflag = 1;
123
break;
124
case 'W':
125
Wflag = 1;
126
break;
127
case 'x':
128
xflag = 1;
129
break;
130
default:
131
usage();
132
}
133
argc -= optind;
134
argv += optind;
135
136
if (argc < 1) {
137
if (fflag)
138
return (0);
139
usage();
140
}
141
142
checkdot(argv);
143
checkslash(argv);
144
uid = geteuid();
145
146
(void)signal(SIGINFO, siginfo);
147
if (*argv) {
148
stdin_ok = isatty(STDIN_FILENO);
149
150
if (Iflag) {
151
if (check2(argv) == 0)
152
exit (1);
153
}
154
if (rflag)
155
rm_tree(argv);
156
else
157
rm_file(argv);
158
}
159
160
exit (eval);
161
}
162
163
static void
164
rm_tree(char **argv)
165
{
166
FTS *fts;
167
FTSENT *p;
168
int needstat;
169
int flags;
170
int rval;
171
172
/*
173
* Remove a file hierarchy. If forcing removal (-f), or interactive
174
* (-i) or can't ask anyway (stdin_ok), don't stat the file.
175
*/
176
needstat = !uid || (!fflag && !iflag && stdin_ok);
177
178
/*
179
* If the -i option is specified, the user can skip on the pre-order
180
* visit. The fts_number field flags skipped directories.
181
*/
182
#define SKIPPED 1
183
184
flags = FTS_PHYSICAL;
185
if (!needstat)
186
flags |= FTS_NOSTAT;
187
if (Wflag)
188
flags |= FTS_WHITEOUT;
189
if (xflag)
190
flags |= FTS_XDEV;
191
if (!(fts = fts_open(argv, flags, NULL))) {
192
if (fflag && errno == ENOENT)
193
return;
194
err(1, "fts_open");
195
}
196
while (errno = 0, (p = fts_read(fts)) != NULL) {
197
switch (p->fts_info) {
198
case FTS_DNR:
199
if (!fflag || p->fts_errno != ENOENT) {
200
warnx("%s: %s",
201
p->fts_path, strerror(p->fts_errno));
202
eval = 1;
203
}
204
continue;
205
case FTS_ERR:
206
errx(1, "%s: %s", p->fts_path, strerror(p->fts_errno));
207
case FTS_NS:
208
/*
209
* Assume that since fts_read() couldn't stat the
210
* file, it can't be unlinked.
211
*/
212
if (!needstat)
213
break;
214
if (!fflag || p->fts_errno != ENOENT) {
215
warnx("%s: %s",
216
p->fts_path, strerror(p->fts_errno));
217
eval = 1;
218
}
219
continue;
220
case FTS_D:
221
/* Pre-order: give user chance to skip. */
222
if (!fflag && !check(p->fts_path, p->fts_accpath,
223
p->fts_statp)) {
224
(void)fts_set(fts, p, FTS_SKIP);
225
p->fts_number = SKIPPED;
226
}
227
else if (!uid &&
228
(p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
229
!(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
230
lchflags(p->fts_accpath,
231
p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE)) < 0)
232
goto err;
233
continue;
234
case FTS_DP:
235
/* Post-order: see if user skipped. */
236
if (p->fts_number == SKIPPED)
237
continue;
238
break;
239
default:
240
if (!fflag &&
241
!check(p->fts_path, p->fts_accpath, p->fts_statp))
242
continue;
243
}
244
245
rval = 0;
246
if (!uid &&
247
(p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
248
!(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)))
249
rval = lchflags(p->fts_accpath,
250
p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE));
251
if (rval == 0) {
252
/*
253
* If we can't read or search the directory, may still be
254
* able to remove it. Don't print out the un{read,search}able
255
* message unless the remove fails.
256
*/
257
switch (p->fts_info) {
258
case FTS_DP:
259
case FTS_DNR:
260
rval = rmdir(p->fts_accpath);
261
if (rval == 0 || (fflag && errno == ENOENT)) {
262
if (rval == 0 && vflag)
263
(void)printf("%s\n",
264
p->fts_path);
265
if (rval == 0 && info) {
266
info = 0;
267
(void)printf("%s\n",
268
p->fts_path);
269
}
270
continue;
271
}
272
break;
273
274
case FTS_W:
275
rval = undelete(p->fts_accpath);
276
if (rval == 0 && (fflag && errno == ENOENT)) {
277
if (vflag)
278
(void)printf("%s\n",
279
p->fts_path);
280
if (info) {
281
info = 0;
282
(void)printf("%s\n",
283
p->fts_path);
284
}
285
continue;
286
}
287
break;
288
289
case FTS_NS:
290
/*
291
* Assume that since fts_read() couldn't stat
292
* the file, it can't be unlinked.
293
*/
294
if (fflag)
295
continue;
296
/* FALLTHROUGH */
297
298
case FTS_F:
299
case FTS_NSOK:
300
default:
301
rval = unlink(p->fts_accpath);
302
if (rval == 0 || (fflag && errno == ENOENT)) {
303
if (rval == 0 && vflag)
304
(void)printf("%s\n",
305
p->fts_path);
306
if (rval == 0 && info) {
307
info = 0;
308
(void)printf("%s\n",
309
p->fts_path);
310
}
311
continue;
312
}
313
}
314
}
315
err:
316
warn("%s", p->fts_path);
317
eval = 1;
318
}
319
if (!fflag && errno)
320
err(1, "fts_read");
321
fts_close(fts);
322
}
323
324
static void
325
rm_file(char **argv)
326
{
327
struct stat sb;
328
int rval;
329
char *f;
330
331
/*
332
* Remove a file. POSIX 1003.2 states that, by default, attempting
333
* to remove a directory is an error, so must always stat the file.
334
*/
335
while ((f = *argv++) != NULL) {
336
/* Assume if can't stat the file, can't unlink it. */
337
if (lstat(f, &sb)) {
338
if (Wflag) {
339
sb.st_mode = S_IFWHT|S_IWUSR|S_IRUSR;
340
} else {
341
if (!fflag || errno != ENOENT) {
342
warn("%s", f);
343
eval = 1;
344
}
345
continue;
346
}
347
} else if (Wflag) {
348
warnx("%s: %s", f, strerror(EEXIST));
349
eval = 1;
350
continue;
351
}
352
353
if (S_ISDIR(sb.st_mode) && !dflag) {
354
warnx("%s: is a directory", f);
355
eval = 1;
356
continue;
357
}
358
if (!fflag && !S_ISWHT(sb.st_mode) && !check(f, f, &sb))
359
continue;
360
rval = 0;
361
if (!uid && !S_ISWHT(sb.st_mode) &&
362
(sb.st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
363
!(sb.st_flags & (SF_APPEND|SF_IMMUTABLE)))
364
rval = lchflags(f, sb.st_flags & ~(UF_APPEND|UF_IMMUTABLE));
365
if (rval == 0) {
366
if (S_ISWHT(sb.st_mode))
367
rval = undelete(f);
368
else if (S_ISDIR(sb.st_mode))
369
rval = rmdir(f);
370
else
371
rval = unlink(f);
372
}
373
if (rval && (!fflag || errno != ENOENT)) {
374
warn("%s", f);
375
eval = 1;
376
}
377
if (vflag && rval == 0)
378
(void)printf("%s\n", f);
379
if (info && rval == 0) {
380
info = 0;
381
(void)printf("%s\n", f);
382
}
383
}
384
}
385
386
static int
387
check(const char *path, const char *name, struct stat *sp)
388
{
389
int ch, first;
390
char modep[15], *flagsp;
391
392
/* Check -i first. */
393
if (iflag)
394
(void)fprintf(stderr, "remove %s? ", path);
395
else {
396
/*
397
* If it's not a symbolic link and it's unwritable and we're
398
* talking to a terminal, ask. Symbolic links are excluded
399
* because their permissions are meaningless. Check stdin_ok
400
* first because we may not have stat'ed the file.
401
*/
402
if (!stdin_ok || S_ISLNK(sp->st_mode) ||
403
(!access(name, W_OK) &&
404
!(sp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
405
(!(sp->st_flags & (UF_APPEND|UF_IMMUTABLE)) || !uid)))
406
return (1);
407
strmode(sp->st_mode, modep);
408
if ((flagsp = fflagstostr(sp->st_flags)) == NULL)
409
err(1, "fflagstostr");
410
(void)fprintf(stderr, "override %s%s%s/%s %s%sfor %s? ",
411
modep + 1, modep[10] == ' ' ? "" : " ",
412
user_from_uid(sp->st_uid, 0),
413
group_from_gid(sp->st_gid, 0),
414
*flagsp ? flagsp : "", *flagsp ? " " : "",
415
path);
416
free(flagsp);
417
}
418
(void)fflush(stderr);
419
420
first = ch = getchar();
421
while (ch != '\n' && ch != EOF)
422
ch = getchar();
423
return (first == 'y' || first == 'Y');
424
}
425
426
#define ISSLASH(a) ((a)[0] == '/' && (a)[1] == '\0')
427
static void
428
checkslash(char **argv)
429
{
430
char **t, **u;
431
int complained;
432
433
complained = 0;
434
for (t = argv; *t;) {
435
if (ISSLASH(*t)) {
436
if (!complained++)
437
warnx("\"/\" may not be removed");
438
eval = 1;
439
for (u = t; u[0] != NULL; ++u)
440
u[0] = u[1];
441
} else {
442
++t;
443
}
444
}
445
}
446
447
static int
448
check2(char **argv)
449
{
450
struct stat st;
451
int first;
452
int ch;
453
int fcount = 0;
454
int dcount = 0;
455
int i;
456
const char *dname = NULL;
457
458
for (i = 0; argv[i]; ++i) {
459
if (lstat(argv[i], &st) == 0) {
460
if (S_ISDIR(st.st_mode)) {
461
++dcount;
462
dname = argv[i]; /* only used if 1 dir */
463
} else {
464
++fcount;
465
}
466
}
467
}
468
first = 0;
469
while (first != 'n' && first != 'N' && first != 'y' && first != 'Y') {
470
if (dcount && rflag) {
471
fprintf(stderr, "recursively remove");
472
if (dcount == 1)
473
fprintf(stderr, " %s", dname);
474
else
475
fprintf(stderr, " %d dirs", dcount);
476
if (fcount == 1)
477
fprintf(stderr, " and 1 file");
478
else if (fcount > 1)
479
fprintf(stderr, " and %d files", fcount);
480
} else if (dcount + fcount > 3) {
481
fprintf(stderr, "remove %d files", dcount + fcount);
482
} else {
483
return(1);
484
}
485
fprintf(stderr, "? ");
486
fflush(stderr);
487
488
first = ch = getchar();
489
while (ch != '\n' && ch != EOF)
490
ch = getchar();
491
if (ch == EOF)
492
break;
493
}
494
return (first == 'y' || first == 'Y');
495
}
496
497
#define ISDOT(a) ((a)[0] == '.' && (!(a)[1] || ((a)[1] == '.' && !(a)[2])))
498
static void
499
checkdot(char **argv)
500
{
501
char *p, **save, **t;
502
int complained;
503
504
complained = 0;
505
for (t = argv; *t;) {
506
if ((p = strrchr(*t, '/')) != NULL)
507
++p;
508
else
509
p = *t;
510
if (ISDOT(p)) {
511
if (!complained++)
512
warnx("\".\" and \"..\" may not be removed");
513
eval = 1;
514
for (save = t; (t[0] = t[1]) != NULL; ++t)
515
continue;
516
t = save;
517
} else
518
++t;
519
}
520
}
521
522
static void
523
usage(void)
524
{
525
526
(void)fprintf(stderr, "%s\n%s\n",
527
"usage: rm [-f | -i] [-dIPRrvWx] file ...",
528
" unlink [--] file");
529
exit(EX_USAGE);
530
}
531
532
static void
533
siginfo(int sig __unused)
534
{
535
536
info = 1;
537
}
538
539