1*f90e70deSderaadt /* $OpenBSD: rm.c,v 1.44 2022/08/16 13:52:41 deraadt Exp $ */
2df930be7Sderaadt /* $NetBSD: rm.c,v 1.19 1995/09/07 06:48:50 jtc Exp $ */
3df930be7Sderaadt
4df930be7Sderaadt /*-
5df930be7Sderaadt * Copyright (c) 1990, 1993, 1994
6df930be7Sderaadt * The Regents of the University of California. All rights reserved.
7df930be7Sderaadt *
8df930be7Sderaadt * Redistribution and use in source and binary forms, with or without
9df930be7Sderaadt * modification, are permitted provided that the following conditions
10df930be7Sderaadt * are met:
11df930be7Sderaadt * 1. Redistributions of source code must retain the above copyright
12df930be7Sderaadt * notice, this list of conditions and the following disclaimer.
13df930be7Sderaadt * 2. Redistributions in binary form must reproduce the above copyright
14df930be7Sderaadt * notice, this list of conditions and the following disclaimer in the
15df930be7Sderaadt * documentation and/or other materials provided with the distribution.
1629295d1cSmillert * 3. Neither the name of the University nor the names of its contributors
17df930be7Sderaadt * may be used to endorse or promote products derived from this software
18df930be7Sderaadt * without specific prior written permission.
19df930be7Sderaadt *
20df930be7Sderaadt * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21df930be7Sderaadt * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22df930be7Sderaadt * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23df930be7Sderaadt * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24df930be7Sderaadt * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25df930be7Sderaadt * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26df930be7Sderaadt * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27df930be7Sderaadt * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28df930be7Sderaadt * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29df930be7Sderaadt * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30df930be7Sderaadt * SUCH DAMAGE.
31df930be7Sderaadt */
32df930be7Sderaadt
33df930be7Sderaadt #include <sys/types.h>
34df930be7Sderaadt #include <sys/stat.h>
3513b097dfSaaron #include <sys/mount.h>
36df930be7Sderaadt
37df930be7Sderaadt #include <err.h>
38df930be7Sderaadt #include <errno.h>
39df930be7Sderaadt #include <fcntl.h>
40df930be7Sderaadt #include <fts.h>
41df930be7Sderaadt #include <stdio.h>
42df930be7Sderaadt #include <stdlib.h>
43df930be7Sderaadt #include <string.h>
44df930be7Sderaadt #include <unistd.h>
45b9fc9a72Sderaadt #include <limits.h>
46764064c4Smickey #include <pwd.h>
47764064c4Smickey #include <grp.h>
48df930be7Sderaadt
49b9fc9a72Sderaadt #define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b))
50b9fc9a72Sderaadt
519517852aSmpech extern char *__progname;
529517852aSmpech
53e507bd9aStedu int dflag, eval, fflag, iflag, Pflag, vflag, stdin_ok;
54df930be7Sderaadt
55c72b5b24Smillert int check(char *, char *, struct stat *);
5653ac4003Sdaniel void checkdot(char **);
57c72b5b24Smillert void rm_file(char **);
58fc82f293Stedu int rm_overwrite(char *, struct stat *);
597c5c57baStedu int pass(int, off_t, char *, size_t);
60c72b5b24Smillert void rm_tree(char **);
61c72b5b24Smillert void usage(void);
62df930be7Sderaadt
63df930be7Sderaadt /*
64df930be7Sderaadt * rm --
65df930be7Sderaadt * This rm is different from historic rm's, but is expected to match
66df930be7Sderaadt * POSIX 1003.2 behavior. The most visible difference is that -f
67df930be7Sderaadt * has two specific effects now, ignore non-existent files and force
68df930be7Sderaadt * file removal.
69df930be7Sderaadt */
70df930be7Sderaadt int
main(int argc,char * argv[])71ab83b6d6Sderaadt main(int argc, char *argv[])
72df930be7Sderaadt {
73df930be7Sderaadt int ch, rflag;
74df930be7Sderaadt
75df930be7Sderaadt Pflag = rflag = 0;
76e507bd9aStedu while ((ch = getopt(argc, argv, "dfiPRrv")) != -1)
77df930be7Sderaadt switch(ch) {
78df930be7Sderaadt case 'd':
79df930be7Sderaadt dflag = 1;
80df930be7Sderaadt break;
81df930be7Sderaadt case 'f':
82df930be7Sderaadt fflag = 1;
83df930be7Sderaadt iflag = 0;
84df930be7Sderaadt break;
85df930be7Sderaadt case 'i':
86df930be7Sderaadt fflag = 0;
87df930be7Sderaadt iflag = 1;
88df930be7Sderaadt break;
89df930be7Sderaadt case 'P':
90df930be7Sderaadt Pflag = 1;
91df930be7Sderaadt break;
92df930be7Sderaadt case 'R':
93df930be7Sderaadt case 'r': /* Compatibility. */
94df930be7Sderaadt rflag = 1;
95df930be7Sderaadt break;
96e507bd9aStedu case 'v':
97e507bd9aStedu vflag = 1;
98e507bd9aStedu break;
99df930be7Sderaadt default:
100df930be7Sderaadt usage();
101df930be7Sderaadt }
102df930be7Sderaadt argc -= optind;
103df930be7Sderaadt argv += optind;
104df930be7Sderaadt
105d5cafc5dSderaadt if (Pflag) {
106204e6881Sderaadt if (pledge("stdio rpath wpath cpath getpw", NULL) == -1)
1070bd1216cSderaadt err(1, "pledge");
108d5cafc5dSderaadt } else {
109204e6881Sderaadt if (pledge("stdio rpath cpath getpw", NULL) == -1)
1100bd1216cSderaadt err(1, "pledge");
111d5cafc5dSderaadt }
112d5cafc5dSderaadt
113849591b1Smillert if (argc < 1 && fflag == 0)
114df930be7Sderaadt usage();
115df930be7Sderaadt
11653ac4003Sdaniel checkdot(argv);
117df930be7Sderaadt
118df930be7Sderaadt if (*argv) {
119df930be7Sderaadt stdin_ok = isatty(STDIN_FILENO);
120df930be7Sderaadt
121df930be7Sderaadt if (rflag)
122df930be7Sderaadt rm_tree(argv);
123df930be7Sderaadt else
124df930be7Sderaadt rm_file(argv);
125df930be7Sderaadt }
126df930be7Sderaadt
12758715da7Sschwarze return (eval);
128df930be7Sderaadt }
129df930be7Sderaadt
130df930be7Sderaadt void
rm_tree(char ** argv)131ab83b6d6Sderaadt rm_tree(char **argv)
132df930be7Sderaadt {
133df930be7Sderaadt FTS *fts;
134df930be7Sderaadt FTSENT *p;
135df930be7Sderaadt int needstat;
136df930be7Sderaadt int flags;
137df930be7Sderaadt
138df930be7Sderaadt /*
139df930be7Sderaadt * Remove a file hierarchy. If forcing removal (-f), or interactive
140df930be7Sderaadt * (-i) or can't ask anyway (stdin_ok), don't stat the file.
141df930be7Sderaadt */
142df930be7Sderaadt needstat = !fflag && !iflag && stdin_ok;
143df930be7Sderaadt
144df930be7Sderaadt /*
145df930be7Sderaadt * If the -i option is specified, the user can skip on the pre-order
146df930be7Sderaadt * visit. The fts_number field flags skipped directories.
147df930be7Sderaadt */
148df930be7Sderaadt #define SKIPPED 1
149df930be7Sderaadt
150df930be7Sderaadt flags = FTS_PHYSICAL;
151df930be7Sderaadt if (!needstat)
152df930be7Sderaadt flags |= FTS_NOSTAT;
153cbc6bef2Stedu if (!(fts = fts_open(argv, flags, NULL)))
154df930be7Sderaadt err(1, NULL);
155df930be7Sderaadt while ((p = fts_read(fts)) != NULL) {
156df930be7Sderaadt switch (p->fts_info) {
157df930be7Sderaadt case FTS_DNR:
158df930be7Sderaadt if (!fflag || p->fts_errno != ENOENT) {
159df930be7Sderaadt warnx("%s: %s",
160df930be7Sderaadt p->fts_path, strerror(p->fts_errno));
161df930be7Sderaadt eval = 1;
162df930be7Sderaadt }
163df930be7Sderaadt continue;
164df930be7Sderaadt case FTS_ERR:
165cd5cfea9Sguenther errc(1, p->fts_errno, "%s", p->fts_path);
166df930be7Sderaadt case FTS_NS:
167df930be7Sderaadt /*
168df930be7Sderaadt * FTS_NS: assume that if can't stat the file, it
169df930be7Sderaadt * can't be unlinked.
170df930be7Sderaadt */
171df930be7Sderaadt if (!needstat)
172df930be7Sderaadt break;
173df930be7Sderaadt if (!fflag || p->fts_errno != ENOENT) {
174df930be7Sderaadt warnx("%s: %s",
175df930be7Sderaadt p->fts_path, strerror(p->fts_errno));
176df930be7Sderaadt eval = 1;
177df930be7Sderaadt }
178df930be7Sderaadt continue;
179df930be7Sderaadt case FTS_D:
180df930be7Sderaadt /* Pre-order: give user chance to skip. */
181df930be7Sderaadt if (!fflag && !check(p->fts_path, p->fts_accpath,
182df930be7Sderaadt p->fts_statp)) {
183df930be7Sderaadt (void)fts_set(fts, p, FTS_SKIP);
184df930be7Sderaadt p->fts_number = SKIPPED;
185df930be7Sderaadt }
186df930be7Sderaadt continue;
187df930be7Sderaadt case FTS_DP:
188df930be7Sderaadt /* Post-order: see if user skipped. */
189df930be7Sderaadt if (p->fts_number == SKIPPED)
190df930be7Sderaadt continue;
191df930be7Sderaadt break;
192df930be7Sderaadt default:
193df930be7Sderaadt if (!fflag &&
194df930be7Sderaadt !check(p->fts_path, p->fts_accpath, p->fts_statp))
195df930be7Sderaadt continue;
196df930be7Sderaadt }
197df930be7Sderaadt
198df930be7Sderaadt /*
199df930be7Sderaadt * If we can't read or search the directory, may still be
200df930be7Sderaadt * able to remove it. Don't print out the un{read,search}able
201df930be7Sderaadt * message unless the remove fails.
202df930be7Sderaadt */
203df930be7Sderaadt switch (p->fts_info) {
204df930be7Sderaadt case FTS_DP:
205df930be7Sderaadt case FTS_DNR:
206*f90e70deSderaadt if (!rmdir(p->fts_accpath)) {
207e507bd9aStedu if (vflag)
2087feebab4Stedu fprintf(stdout, "%s\n", p->fts_path);
209df930be7Sderaadt continue;
210e507bd9aStedu }
211*f90e70deSderaadt if (fflag && errno == ENOENT)
212*f90e70deSderaadt continue;
213df930be7Sderaadt break;
214df930be7Sderaadt
21507a53563Sguenther case FTS_F:
21607a53563Sguenther case FTS_NSOK:
217df930be7Sderaadt if (Pflag)
21807a53563Sguenther rm_overwrite(p->fts_accpath, p->fts_info ==
21907a53563Sguenther FTS_NSOK ? NULL : p->fts_statp);
22007a53563Sguenther /* FALLTHROUGH */
22107a53563Sguenther default:
222*f90e70deSderaadt if (!unlink(p->fts_accpath)) {
223e507bd9aStedu if (vflag)
2247feebab4Stedu fprintf(stdout, "%s\n", p->fts_path);
225df930be7Sderaadt continue;
226df930be7Sderaadt }
227*f90e70deSderaadt if (fflag && errno == ENOENT)
228*f90e70deSderaadt continue;
229e507bd9aStedu }
230df930be7Sderaadt warn("%s", p->fts_path);
231df930be7Sderaadt eval = 1;
232df930be7Sderaadt }
233df930be7Sderaadt if (errno)
234df930be7Sderaadt err(1, "fts_read");
23519a1923bSotto fts_close(fts);
236df930be7Sderaadt }
237df930be7Sderaadt
238df930be7Sderaadt void
rm_file(char ** argv)239ab83b6d6Sderaadt rm_file(char **argv)
240df930be7Sderaadt {
241df930be7Sderaadt struct stat sb;
242df930be7Sderaadt int rval;
243df930be7Sderaadt char *f;
244df930be7Sderaadt
245df930be7Sderaadt /*
246df930be7Sderaadt * Remove a file. POSIX 1003.2 states that, by default, attempting
247df930be7Sderaadt * to remove a directory is an error, so must always stat the file.
248df930be7Sderaadt */
249df930be7Sderaadt while ((f = *argv++) != NULL) {
250df930be7Sderaadt /* Assume if can't stat the file, can't unlink it. */
251df930be7Sderaadt if (lstat(f, &sb)) {
252df930be7Sderaadt if (!fflag || errno != ENOENT) {
253df930be7Sderaadt warn("%s", f);
254df930be7Sderaadt eval = 1;
255df930be7Sderaadt }
256df930be7Sderaadt continue;
257df930be7Sderaadt }
258df930be7Sderaadt
259df930be7Sderaadt if (S_ISDIR(sb.st_mode) && !dflag) {
260df930be7Sderaadt warnx("%s: is a directory", f);
261df930be7Sderaadt eval = 1;
262df930be7Sderaadt continue;
263df930be7Sderaadt }
26486d5f11cSmillert if (!fflag && !check(f, f, &sb))
265df930be7Sderaadt continue;
266df930be7Sderaadt else if (S_ISDIR(sb.st_mode))
267df930be7Sderaadt rval = rmdir(f);
268df930be7Sderaadt else {
269df930be7Sderaadt if (Pflag)
270b74da601Sray rm_overwrite(f, &sb);
271df930be7Sderaadt rval = unlink(f);
272df930be7Sderaadt }
273df930be7Sderaadt if (rval && (!fflag || errno != ENOENT)) {
274df930be7Sderaadt warn("%s", f);
275df930be7Sderaadt eval = 1;
276*f90e70deSderaadt } else if (rval == 0 && vflag)
277e507bd9aStedu (void)fprintf(stdout, "%s\n", f);
278df930be7Sderaadt }
279df930be7Sderaadt }
280df930be7Sderaadt
281df930be7Sderaadt /*
282df930be7Sderaadt * rm_overwrite --
2837c5c57baStedu * Overwrite the file with varying bit patterns.
284df930be7Sderaadt *
285df930be7Sderaadt * XXX
286df930be7Sderaadt * This is a cheap way to *really* delete files. Note that only regular
287df930be7Sderaadt * files are deleted, directories (and therefore names) will remain.
288df930be7Sderaadt * Also, this assumes a fixed-block file system (like FFS, or a V7 or a
289df930be7Sderaadt * System V file system). In a logging file system, you'll have to have
290df930be7Sderaadt * kernel support.
291fc82f293Stedu * Returns 1 for success.
292df930be7Sderaadt */
293fc82f293Stedu int
rm_overwrite(char * file,struct stat * sbp)294ab83b6d6Sderaadt rm_overwrite(char *file, struct stat *sbp)
295df930be7Sderaadt {
296a5353f8dSmillert struct stat sb, sb2;
29713b097dfSaaron struct statfs fsb;
29860b36417Sotto size_t bsize;
29960b36417Sotto int fd;
3004ac1cca3Sweingart char *buf = NULL;
301df930be7Sderaadt
302df930be7Sderaadt fd = -1;
303df930be7Sderaadt if (sbp == NULL) {
304df930be7Sderaadt if (lstat(file, &sb))
305df930be7Sderaadt goto err;
306df930be7Sderaadt sbp = &sb;
307df930be7Sderaadt }
308df930be7Sderaadt if (!S_ISREG(sbp->st_mode))
309fc82f293Stedu return (1);
31044cd3b97Shugh if (sbp->st_nlink > 1) {
3114a323c25Sderaadt warnx("%s (inode %llu): not overwritten due to multiple links",
3124a323c25Sderaadt file, (unsigned long long)sbp->st_ino);
313fc82f293Stedu return (0);
31444cd3b97Shugh }
315b7041c07Sderaadt if ((fd = open(file, O_WRONLY|O_NONBLOCK|O_NOFOLLOW)) == -1)
316df930be7Sderaadt goto err;
317a5353f8dSmillert if (fstat(fd, &sb2))
318a5353f8dSmillert goto err;
319a5353f8dSmillert if (sb2.st_dev != sbp->st_dev || sb2.st_ino != sbp->st_ino ||
320a5353f8dSmillert !S_ISREG(sb2.st_mode)) {
321a5353f8dSmillert errno = EPERM;
322a5353f8dSmillert goto err;
323a5353f8dSmillert }
32413b097dfSaaron if (fstatfs(fd, &fsb) == -1)
32513b097dfSaaron goto err;
326b9fc9a72Sderaadt bsize = MAXIMUM(fsb.f_iosize, 1024U);
32713b097dfSaaron if ((buf = malloc(bsize)) == NULL)
328fc82f293Stedu err(1, "%s: malloc", file);
329df930be7Sderaadt
3307c5c57baStedu if (!pass(fd, sbp->st_size, buf, bsize))
331df930be7Sderaadt goto err;
3327c5c57baStedu if (fsync(fd))
333fc82f293Stedu goto err;
334fc82f293Stedu close(fd);
33513b097dfSaaron free(buf);
336fc82f293Stedu return (1);
337df930be7Sderaadt
338fc82f293Stedu err:
33960b36417Sotto warn("%s", file);
340fc82f293Stedu close(fd);
341fc82f293Stedu eval = 1;
34213b097dfSaaron free(buf);
343fc82f293Stedu return (0);
344df930be7Sderaadt }
345df930be7Sderaadt
34660b36417Sotto int
pass(int fd,off_t len,char * buf,size_t bsize)3477c5c57baStedu pass(int fd, off_t len, char *buf, size_t bsize)
34860b36417Sotto {
34960b36417Sotto size_t wlen;
35060b36417Sotto
35160b36417Sotto for (; len > 0; len -= wlen) {
35260b36417Sotto wlen = len < bsize ? len : bsize;
353b85d50d7Snaddy arc4random_buf(buf, wlen);
35460b36417Sotto if (write(fd, buf, wlen) != wlen)
35560b36417Sotto return (0);
35660b36417Sotto }
35760b36417Sotto return (1);
35860b36417Sotto }
359df930be7Sderaadt
360df930be7Sderaadt int
check(char * path,char * name,struct stat * sp)361ab83b6d6Sderaadt check(char *path, char *name, struct stat *sp)
362df930be7Sderaadt {
363df930be7Sderaadt int ch, first;
364df930be7Sderaadt char modep[15];
365df930be7Sderaadt
366df930be7Sderaadt /* Check -i first. */
367df930be7Sderaadt if (iflag)
368df930be7Sderaadt (void)fprintf(stderr, "remove %s? ", path);
369df930be7Sderaadt else {
370df930be7Sderaadt /*
371df930be7Sderaadt * If it's not a symbolic link and it's unwritable and we're
372df930be7Sderaadt * talking to a terminal, ask. Symbolic links are excluded
373df930be7Sderaadt * because their permissions are meaningless. Check stdin_ok
374df930be7Sderaadt * first because we may not have stat'ed the file.
375df930be7Sderaadt */
376af322779Sotto if (!stdin_ok || S_ISLNK(sp->st_mode) || !access(name, W_OK) ||
377af322779Sotto errno != EACCES)
378df930be7Sderaadt return (1);
379df930be7Sderaadt strmode(sp->st_mode, modep);
380df930be7Sderaadt (void)fprintf(stderr, "override %s%s%s/%s for %s? ",
381df930be7Sderaadt modep + 1, modep[9] == ' ' ? "" : " ",
382df930be7Sderaadt user_from_uid(sp->st_uid, 0),
383df930be7Sderaadt group_from_gid(sp->st_gid, 0), path);
384df930be7Sderaadt }
385df930be7Sderaadt (void)fflush(stderr);
386df930be7Sderaadt
387df930be7Sderaadt first = ch = getchar();
388df930be7Sderaadt while (ch != '\n' && ch != EOF)
389df930be7Sderaadt ch = getchar();
390df930be7Sderaadt return (first == 'y' || first == 'Y');
391df930be7Sderaadt }
392df930be7Sderaadt
393df930be7Sderaadt /*
394df930be7Sderaadt * POSIX.2 requires that if "." or ".." are specified as the basename
395df930be7Sderaadt * portion of an operand, a diagnostic message be written to standard
396df930be7Sderaadt * error and nothing more be done with such operands.
397df930be7Sderaadt *
398df930be7Sderaadt * Since POSIX.2 defines basename as the final portion of a path after
399df930be7Sderaadt * trailing slashes have been removed, we'll remove them here.
400df930be7Sderaadt */
401764064c4Smickey #define ISDOT(a) ((a)[0] == '.' && (!(a)[1] || ((a)[1] == '.' && !(a)[2])))
402df930be7Sderaadt void
checkdot(char ** argv)40353ac4003Sdaniel checkdot(char **argv)
404df930be7Sderaadt {
405df930be7Sderaadt char *p, **save, **t;
40653ac4003Sdaniel int complained;
407c11d908cStedu struct stat sb, root;
408df930be7Sderaadt
409c11d908cStedu stat("/", &root);
41053ac4003Sdaniel complained = 0;
411df930be7Sderaadt for (t = argv; *t;) {
412c11d908cStedu if (lstat(*t, &sb) == 0 &&
413c11d908cStedu root.st_ino == sb.st_ino && root.st_dev == sb.st_dev) {
414c11d908cStedu if (!complained++)
415c11d908cStedu warnx("\"/\" may not be removed");
416c11d908cStedu goto skip;
417c11d908cStedu }
418df930be7Sderaadt /* strip trailing slashes */
419df930be7Sderaadt p = strrchr(*t, '\0');
420df930be7Sderaadt while (--p > *t && *p == '/')
421df930be7Sderaadt *p = '\0';
422df930be7Sderaadt
423df930be7Sderaadt /* extract basename */
424df930be7Sderaadt if ((p = strrchr(*t, '/')) != NULL)
425df930be7Sderaadt ++p;
426df930be7Sderaadt else
427df930be7Sderaadt p = *t;
428df930be7Sderaadt
429df930be7Sderaadt if (ISDOT(p)) {
43053ac4003Sdaniel if (!complained++)
431df930be7Sderaadt warnx("\".\" and \"..\" may not be removed");
432c11d908cStedu skip:
433df930be7Sderaadt eval = 1;
434df930be7Sderaadt for (save = t; (t[0] = t[1]) != NULL; ++t)
435df930be7Sderaadt continue;
436df930be7Sderaadt t = save;
43753ac4003Sdaniel } else
43853ac4003Sdaniel ++t;
439df930be7Sderaadt }
440df930be7Sderaadt }
441df930be7Sderaadt
442df930be7Sderaadt void
usage(void)443ab83b6d6Sderaadt usage(void)
444df930be7Sderaadt {
445e507bd9aStedu (void)fprintf(stderr, "usage: %s [-dfiPRrv] file ...\n", __progname);
446df930be7Sderaadt exit(1);
447df930be7Sderaadt }
448