rm.c (996aa81675f6b63ed02041243b97e61ee7bd51d2) rm.c (48011479cce51f5534141868012dcb9828a0fd63)
1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE

--- 6 unchanged lines hidden (view full) ---

15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE

--- 6 unchanged lines hidden (view full) ---

15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
23 * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
25 */
26
27/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
28/* All Rights Reserved */
29
30#pragma ident "%Z%%M% %I% %E% SMI"
31
32/*
33 * rm [-fiRr] file ...
34 */
35
24 * Use is subject to license terms.
25 */
26
27/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
28/* All Rights Reserved */
29
30#pragma ident "%Z%%M% %I% %E% SMI"
31
32/*
33 * rm [-fiRr] file ...
34 */
35
36#include <stdio.h>
37#include <fcntl.h>
38#include <string.h>
39#include <sys/types.h>
36#include <sys/param.h>
40#include <sys/stat.h>
41#include <dirent.h>
37#include <sys/stat.h>
38#include <dirent.h>
39#include <errno.h>
40#include <fcntl.h>
41#include <langinfo.h>
42#include <limits.h>
43#include <locale.h>
42#include <limits.h>
43#include <locale.h>
44#include <langinfo.h>
45#include <unistd.h>
44#include <stdarg.h>
45#include <stdio.h>
46#include <stdlib.h>
46#include <stdlib.h>
47#include <errno.h>
48#include <sys/resource.h>
49#include <sys/avl.h>
50#include <libcmdutils.h>
47#include <string.h>
48#include <unistd.h>
49#include <values.h>
51
50
52#define ARGCNT 5 /* Number of arguments */
53#define CHILD 0
54#define DIRECTORY ((buffer.st_mode&S_IFMT) == S_IFDIR)
55#define SYMLINK ((buffer.st_mode&S_IFMT) == S_IFLNK)
56#define FAIL -1
57#define MAXFORK 100 /* Maximum number of forking attempts */
58#define NAMESIZE MAXNAMLEN + 1 /* "/" + (file name size) */
59#define TRUE 1
60#define FALSE 0
61#define WRITE 02
62#define SEARCH 07
51#define E_OK 010 /* make __accessat() use effective ids */
63
52
64static int errcode;
65static int interactive, recursive, silent; /* flags for command line options */
53#define DIR_CANTCLOSE 1
66
54
67static int rm(char *, int);
68static int undir(char *, int, dev_t, ino_t);
69static int yes(void);
70static int mypath(dev_t, ino_t);
55static struct stat rootdir;
71
56
72static char yeschr[SCHAR_MAX + 2];
73static char nochr[SCHAR_MAX + 2];
57struct dlist {
58 int fd; /* Stores directory fd */
59 int flags; /* DIR_* Flags */
60 DIR *dp; /* Open directory (opened with fd) */
61 long diroff; /* Saved directory offset when closing */
62 struct dlist *up; /* Up one step in the tree (toward "/") */
63 struct dlist *down; /* Down one step in the tree */
64 ino_t ino; /* st_ino of directory */
65 dev_t dev; /* st_dev of directory */
66 int pathend; /* Offset of name end in the pathbuffer */
67};
74
68
75static char *fullpath;
76static int initdirfd;
69static struct dlist top = {
70 (int)AT_FDCWD,
71 DIR_CANTCLOSE,
72};
77
73
78static void push_name(char *name, int first);
79static int pop_name(int first);
80static void force_chdir(char *);
81static void ch_dir(char *);
82static char *get_filename(char *name);
83static void chdir_init(void);
84static void check_initdir(void);
85static void cleanup(void);
74static char yeschr[SCHAR_MAX + 2];
75static char nochr[SCHAR_MAX + 2];
86
76
87static char *cwd; /* pathname of init dir, from getcwd() */
88static rlim_t maxfiles; /* maximum number of open files */
89static int first_dir = 1; /* flag set when first trying to remove a dir */
90 /* flag set when can't get dev/inode of a parent dir */
91static int parent_err = 0;
92static avl_tree_t *tree; /* tree to keep track of nodes visited */
77static struct dlist *cur, *rec;
93
78
94struct dir_id {
95 dev_t dev;
96 ino_t inode;
97 struct dir_id *next;
98};
79static int rm(const char *, struct dlist *);
80static int confirm(FILE *, const char *, ...);
81static void memerror(void);
82static int checkdir(struct dlist *, struct dlist *);
83static int errcnt;
84static boolean_t silent, interactive, recursive, ontty;
99
85
100 /*
101 * initdir is the first of a linked list of structures
102 * containing unique identifying device and inode numbers for
103 * each directory, from the initial dir up to the root.
104 * current_dir is a pointer to the most recent directory pushed
105 * on during a recursive rm() call.
106 */
107static struct dir_id initdir, *current_dir;
86static char *pathbuf;
87static size_t pathbuflen;
108
88
89static int maxfds = MAXINT;
90static int nfds;
91
92extern int __accessat(int, const char *, int);
93
109int
94int
110main(int argc, char *argv[])
95main(int argc, char **argv)
111{
96{
112 extern int optind;
113 int errflg = 0;
114 int c;
115 struct rlimit rl;
97 int errflg = 0;
98 int c;
116
117 (void) setlocale(LC_ALL, "");
118#if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
119#define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */
120#endif
121 (void) textdomain(TEXT_DOMAIN);
122
123 (void) strncpy(yeschr, nl_langinfo(YESSTR), SCHAR_MAX + 1);
124 (void) strncpy(nochr, nl_langinfo(NOSTR), SCHAR_MAX + 1);
125
126 while ((c = getopt(argc, argv, "frRi")) != EOF)
127 switch (c) {
128 case 'f':
99
100 (void) setlocale(LC_ALL, "");
101#if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
102#define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */
103#endif
104 (void) textdomain(TEXT_DOMAIN);
105
106 (void) strncpy(yeschr, nl_langinfo(YESSTR), SCHAR_MAX + 1);
107 (void) strncpy(nochr, nl_langinfo(NOSTR), SCHAR_MAX + 1);
108
109 while ((c = getopt(argc, argv, "frRi")) != EOF)
110 switch (c) {
111 case 'f':
129 silent = TRUE;
112 silent = B_TRUE;
130#ifdef XPG4
113#ifdef XPG4
131 interactive = FALSE;
114 interactive = B_FALSE;
132#endif
133 break;
134 case 'i':
115#endif
116 break;
117 case 'i':
135 interactive = TRUE;
118 interactive = B_TRUE;
136#ifdef XPG4
119#ifdef XPG4
137 silent = FALSE;
120 silent = B_FALSE;
138#endif
139 break;
140 case 'r':
141 case 'R':
121#endif
122 break;
123 case 'r':
124 case 'R':
142 recursive = TRUE;
125 recursive = B_TRUE;
143 break;
144 case '?':
145 errflg = 1;
146 break;
147 }
148
149 /*
150 * For BSD compatibility allow '-' to delimit the end

--- 5 unchanged lines hidden (view full) ---

156 strcmp(argv[optind], "-") == 0 &&
157 strcmp(argv[optind - 1], "--") != 0)
158 optind++;
159
160 argc -= optind;
161 argv = &argv[optind];
162
163 if ((argc < 1 && !silent) || errflg) {
126 break;
127 case '?':
128 errflg = 1;
129 break;
130 }
131
132 /*
133 * For BSD compatibility allow '-' to delimit the end

--- 5 unchanged lines hidden (view full) ---

139 strcmp(argv[optind], "-") == 0 &&
140 strcmp(argv[optind - 1], "--") != 0)
141 optind++;
142
143 argc -= optind;
144 argv = &argv[optind];
145
146 if ((argc < 1 && !silent) || errflg) {
164 (void) fprintf(stderr,
165 gettext("usage: rm [-fiRr] file ...\n"));
147 (void) fprintf(stderr, gettext("usage: rm [-fiRr] file ...\n"));
166 exit(2);
167 }
168
148 exit(2);
149 }
150
169 if (getrlimit(RLIMIT_NOFILE, &rl)) {
170 perror("getrlimit");
151 ontty = isatty(STDIN_FILENO) != 0;
152
153 if (recursive && stat("/", &rootdir) != 0) {
154 (void) fprintf(stderr,
155 gettext("rm: cannot stat root directory: %s\n"),
156 strerror(errno));
171 exit(2);
157 exit(2);
172 } else
173 maxfiles = rl.rlim_cur - 2;
158 }
174
159
175 while (argc-- > 0) {
176 tree = NULL;
177 /* Retry if rm() fails due to bad chdir */
178 while (rm(*argv, 1) < 0)
160 for (; *argv != NULL; argv++) {
161 char *p = strrchr(*argv, '/');
162 if (p == NULL)
163 p = *argv;
164 else
165 p = p + 1;
166 if (strcmp(p, ".") == 0 || strcmp(p, "..") == 0) {
167 (void) fprintf(stderr,
168 gettext("rm of %s is not allowed\n"), *argv);
169 errcnt++;
170 continue;
171 }
172 /* Retry when we can't walk back up. */
173 while (rm(*argv, rec = cur = &top) != 0)
179 ;
174 ;
180 argv++;
181 destroy_tree(tree);
182 }
183
175 }
176
184 cleanup();
185 return (errcode ? 2 : 0);
186 /* NOTREACHED */
177 return (errcnt != 0 ? 2 : 0);
187}
188
178}
179
189static int
190rm(char *path, int first)
180static void
181pushfilename(const char *fname)
191{
182{
192 struct stat buffer;
193 char *filepath;
194 char *p;
195 char resolved_path[PATH_MAX];
183 char *p;
184 const char *q = fname;
196
185
197 /*
198 * Check file to see if it exists.
199 */
200 if (lstat(path, &buffer) == FAIL) {
201 if (!silent) {
202 perror(path);
203 ++errcode;
186 if (cur == &top) {
187 p = pathbuf;
188 } else {
189 p = pathbuf + cur->up->pathend;
190 *p++ = '/';
191 }
192 while (*q != '\0') {
193 if (p - pathbuf + 2 >= pathbuflen) {
194 char *np;
195 pathbuflen += MAXPATHLEN;
196 np = realloc(pathbuf, pathbuflen);
197 if (np == NULL)
198 memerror();
199 p = np + (p - pathbuf);
200 pathbuf = np;
204 }
201 }
205 return (0);
202 *p++ = *q++;
206 }
203 }
204 *p = '\0';
205 cur->pathend = p - pathbuf;
206}
207
207
208 /* prevent removal of / but allow removal of sym-links */
209 if (!S_ISLNK(buffer.st_mode) && realpath(path, resolved_path) != NULL &&
210 strcmp(resolved_path, "/") == 0) {
211 (void) fprintf(stderr,
212 gettext("rm of %s is not allowed\n"), resolved_path);
213 errcode++;
214 return (0);
208static void
209closeframe(struct dlist *frm)
210{
211 if (frm->dp != NULL) {
212 (void) closedir(frm->dp);
213 nfds--;
214 frm->dp = NULL;
215 frm->fd = -1;
215 }
216 }
217}
216
218
217 /* prevent removal of . or .. (directly) */
218 if (p = strrchr(path, '/'))
219 p++;
220 else
221 p = path;
222 if (strcmp(".", p) == 0 || strcmp("..", p) == 0) {
223 (void) fprintf(stderr,
224 gettext("rm of %s is not allowed\n"), path);
225 errcode++;
226 return (0);
219static int
220reclaim(void)
221{
222 while (rec != NULL && (rec->flags & DIR_CANTCLOSE) != 0)
223 rec = rec->down;
224 if (rec == NULL || rec == cur || rec->dp == NULL)
225 return (-1);
226 rec->diroff = telldir(rec->dp);
227 closeframe(rec);
228 rec = rec->down;
229 return (0);
230}
231
232static void
233pushdir(struct dlist *frm)
234{
235 frm->up = cur;
236 frm->down = NULL;
237 cur->down = frm;
238 cur = frm;
239}
240
241static int
242opendirat(int dirfd, const char *entry, struct dlist *frm)
243{
244 int fd;
245
246 if (nfds >= maxfds)
247 (void) reclaim();
248
249 while ((fd = openat(dirfd, entry, O_RDONLY|O_NONBLOCK)) == -1 &&
250 errno == EMFILE) {
251 if (nfds < maxfds)
252 maxfds = nfds;
253 if (reclaim() != 0)
254 return (-1);
227 }
255 }
256 if (fd < 0)
257 return (-1);
258 frm->fd = fd;
259 frm->dp = fdopendir(fd);
260 if (frm->dp == NULL) {
261 (void) close(fd);
262 return (-1);
263 }
264 nfds++;
265 return (0);
266}
267
268/*
269 * Since we never pop the top frame, cur->up can never be NULL.
270 * If we pop beyond a frame we closed, we try to reopen "..".
271 */
272static int
273popdir(boolean_t noerror)
274{
275 struct stat buf;
276 int ret = noerror ? 0 : -1;
277 pathbuf[cur->up->pathend] = '\0';
278
279 if (noerror && cur->up->fd == -1) {
280 rec = cur->up;
281 if (opendirat(cur->fd, "..", rec) != 0 ||
282 fstat(rec->fd, &buf) != 0) {
283 (void) fprintf(stderr,
284 gettext("rm: cannot reopen %s: %s\n"),
285 pathbuf, strerror(errno));
286 exit(2);
287 }
288 if (rec->ino != buf.st_ino || rec->dev != buf.st_dev) {
289 (void) fprintf(stderr, gettext("rm: WARNING: "
290 "The directory %s was moved or linked to "
291 "another directory during the execution of rm\n"),
292 pathbuf);
293 closeframe(rec);
294 ret = -1;
295 } else {
296 /* If telldir failed, we take it from the top. */
297 if (rec->diroff != -1)
298 seekdir(rec->dp, rec->diroff);
299 }
300 } else if (rec == cur)
301 rec = cur->up;
302 closeframe(cur);
303 cur = cur->up;
304 cur->down = NULL;
305 return (ret);
306}
307
308/*
309 * The stack frame of this function is minimized so that we can
310 * recurse quite a bit before we overflow the stack; around
311 * 30,000-40,000 nested directories can be removed with the default
312 * stack limit.
313 */
314static int
315rm(const char *entry, struct dlist *caller)
316{
317 struct dlist frame;
318 int flag;
319 struct stat temp;
320 struct dirent *dent;
321 int err;
322
228 /*
323 /*
229 * If it's a directory, remove its contents.
324 * Construct the pathname: note that the entry may live in memory
325 * allocated by readdir and that after return from recursion
326 * the memory is no longer valid. So after the recursive rm()
327 * call, we use the global pathbuf instead of the entry argument.
230 */
328 */
231 if (DIRECTORY) {
329 pushfilename(entry);
330
331 if (fstatat(caller->fd, entry, &temp, AT_SYMLINK_NOFOLLOW) != 0) {
332 if (!silent) {
333 (void) fprintf(stderr, "rm: %s: %s\n", pathbuf,
334 strerror(errno));
335 errcnt++;
336 }
337 return (0);
338 }
339
340 if (S_ISDIR(temp.st_mode)) {
232 /*
233 * If "-r" wasn't specified, trying to remove directories
234 * is an error.
235 */
236 if (!recursive) {
237 (void) fprintf(stderr,
341 /*
342 * If "-r" wasn't specified, trying to remove directories
343 * is an error.
344 */
345 if (!recursive) {
346 (void) fprintf(stderr,
238 gettext("rm: %s is a directory\n"), path);
239 ++errcode;
347 gettext("rm: %s is a directory\n"), pathbuf);
348 errcnt++;
240 return (0);
241 }
242
349 return (0);
350 }
351
243 if (first_dir) {
244 check_initdir();
245 current_dir = NULL;
246 first_dir = 0;
247 }
248
249 return (undir(path, first, buffer.st_dev, buffer.st_ino));
250 }
251
252 filepath = get_filename(path);
253
254 /*
255 * If interactive, ask for acknowledgement.
256 *
257 * TRANSLATION_NOTE - The following message will contain the
258 * first character of the strings for "yes" and "no" defined
259 * in the file "nl_langinfo.po". After substitution, the
260 * message will appear as follows:
261 * rm: remove <filename> (y/n)?
262 * For example, in German, this will appear as
263 * rm: l�schen <filename> (j/n)?
264 * where j=ja, n=nein, <filename>=the file to be removed
265 *
266 */
267
268
269 if (interactive) {
270 (void) fprintf(stderr, gettext("rm: remove %s (%s/%s)? "),
271 filepath, yeschr, nochr);
272 if (!yes()) {
273 free(filepath);
352 if (temp.st_ino == rootdir.st_ino &&
353 temp.st_dev == rootdir.st_dev) {
354 (void) fprintf(stderr,
355 gettext("rm of %s is not allowed\n"), "/");
356 errcnt++;
274 return (0);
275 }
357 return (0);
358 }
276 } else if (!silent) {
277 /*
359 /*
278 * If not silent, and stdin is a terminal, and there's
279 * no write access, and the file isn't a symbolic link,
280 * ask for permission.
281 *
282 * TRANSLATION_NOTE - The following message will contain the
283 * first character of the strings for "yes" and "no" defined
284 * in the file "nl_langinfo.po". After substitution, the
285 * message will appear as follows:
360 * TRANSLATION_NOTE - The following message will contain the
361 * first character of the strings for "yes" and "no" defined
362 * in the file "nl_langinfo.po". After substitution, the
363 * message will appear as follows:
286 * rm: <filename>: override protection XXX (y/n)?
287 * where XXX is the permission mode bits of the file in octal
288 * and <filename> is the file to be removed
364 * rm: examine files in directory <directoryname> (y/n)?
365 * where <directoryname> is the directory to be removed
289 *
290 */
366 *
367 */
291 if (!SYMLINK && access(path, W_OK) == FAIL &&
292 isatty(fileno(stdin))) {
293 (void) printf(
294 gettext("rm: %s: override protection %o (%s/%s)? "),
295 filepath, buffer.st_mode & 0777, yeschr, nochr);
296 /*
297 * If permission isn't given, skip the file.
298 */
299 if (!yes()) {
300 free(filepath);
301 return (0);
302 }
368 if (interactive && !confirm(stderr,
369 gettext("rm: examine files in directory %s (%s/%s)? "),
370 pathbuf, yeschr, nochr)) {
371 return (0);
303 }
372 }
304 }
305
373
306 /*
307 * If the unlink fails, inform the user. For /usr/bin/rm, only inform
308 * the user if interactive or not silent.
309 * If unlink fails with errno = ENOENT because file was removed
310 * in between the lstat call and unlink don't inform the user and
311 * don't change errcode.
312 */
374 frame.dev = temp.st_dev;
375 frame.ino = temp.st_ino;
376 frame.flags = 0;
377 flag = AT_REMOVEDIR;
313
378
314 if (unlink(path) == FAIL) {
315 if (errno == ENOENT) {
316 free(filepath);
379#ifdef XPG4
380 /*
381 * XCU4 and POSIX.2: If not interactive, check to see whether
382 * or not directory is readable or writable and if not,
383 * prompt user for response.
384 */
385 if (ontty && !interactive && !silent &&
386 __accessat(caller->fd, entry, W_OK|X_OK|E_OK) != 0 &&
387 !confirm(stderr,
388 gettext("rm: examine files in directory %s (%s/%s)? "),
389 pathbuf, yeschr, nochr)) {
317 return (0);
318 }
390 return (0);
391 }
319#ifndef XPG4
320 if (!silent || interactive) {
321#endif
392#endif
393 if (opendirat(caller->fd, entry, &frame) == -1) {
394 err = errno;
395
396 if (interactive) {
397 /*
398 * Print an error message that
399 * we could not read the directory
400 * as the user wanted to examine
401 * files in the directory. Only
402 * affect the error status if
403 * user doesn't want to remove the
404 * directory as we still may be able
405 * remove the directory successfully.
406 */
407 (void) fprintf(stderr, gettext(
408 "rm: cannot read directory %s: %s\n"),
409 pathbuf, strerror(err));
410
411/*
412 * TRANSLATION_NOTE - The following message will contain the
413 * first character of the strings for "yes" and "no" defined
414 * in the file "nl_langinfo.po". After substitution, the
415 * message will appear as follows:
416 * rm: remove <filename> (y/n)?
417 * For example, in German, this will appear as
418 * rm: l�schen <filename> (j/n)?
419 * where j=ja, n=nein, <filename>=the file to be removed
420 */
421 if (!confirm(stderr,
422 gettext("rm: remove %s (%s/%s)? "),
423 pathbuf, yeschr, nochr)) {
424 errcnt++;
425 return (0);
426 }
427 }
428 /* If it's empty we may still be able to rm it */
429 if (unlinkat(caller->fd, entry, flag) == 0)
430 return (0);
431 if (interactive)
432 err = errno;
322 (void) fprintf(stderr,
433 (void) fprintf(stderr,
323 gettext("rm: %s not removed: "), filepath);
324 perror("");
325#ifndef XPG4
434 interactive ?
435 gettext("rm: Unable to remove directory %s: %s\n") :
436 gettext("rm: cannot read directory %s: %s\n"),
437 pathbuf, strerror(err));
438 errcnt++;
439 return (0);
326 }
440 }
327#endif
328 ++errcode;
329 }
330
441
331 free(filepath);
332 return (0);
333}
334
335static int
336undir(char *path, int first, dev_t dev, ino_t ino)
337{
338 char *newpath;
339 DIR *name;
340 struct dirent *direct;
341 int ismypath;
342 int ret;
343 int chdir_failed = 0;
344 int bad_chdir = 0;
345 size_t len;
346
347 push_name(path, first);
348
349 /*
350 * If interactive and this file isn't in the path of the
351 * current working directory, ask for acknowledgement.
352 *
353 * TRANSLATION_NOTE - The following message will contain the
354 * first character of the strings for "yes" and "no" defined
355 * in the file "nl_langinfo.po". After substitution, the
356 * message will appear as follows:
357 * rm: examine files in directory <directoryname> (y/n)?
358 * where <directoryname> is the directory to be removed
359 *
360 */
361 ismypath = mypath(dev, ino);
362 if (interactive) {
363 (void) fprintf(stderr,
364 gettext("rm: examine files in directory %s (%s/%s)? "),
365 fullpath, yeschr, nochr);
366 /*
442 /*
367 * If the answer is no, skip the directory.
443 * There is a race condition here too; if we open a directory
444 * we have to make sure it's still the same directory we
445 * stat'ed and checked against root earlier. Let's check.
368 */
446 */
369 if (!yes())
370 return (pop_name(first));
371 }
372
373#ifdef XPG4
374 /*
375 * XCU4 and POSIX.2: If not interactive and file is not in the
376 * path of the current working directory, check to see whether
377 * or not directory is readable or writable and if not,
378 * prompt user for response.
379 */
380 if (!interactive && !ismypath &&
381 (access(path, W_OK|X_OK) == FAIL) && isatty(fileno(stdin))) {
382 if (!silent) {
447 if (fstat(frame.fd, &temp) != 0 ||
448 frame.ino != temp.st_ino ||
449 frame.dev != temp.st_dev) {
383 (void) fprintf(stderr,
450 (void) fprintf(stderr,
384 gettext(
385 "rm: examine files in directory %s (%s/%s)? "),
386 fullpath, yeschr, nochr);
387 /*
388 * If the answer is no, skip the directory.
389 */
390 if (!yes())
391 return (pop_name(first));
451 gettext("rm: %s: directory renamed\n"), pathbuf);
452 closeframe(&frame);
453 errcnt++;
454 return (0);
392 }
455 }
393 }
394#endif
395
456
396 /*
397 * Add this node to the search tree so we don't
398 * get into a endless loop. If the add fails then
399 * we have visited this node before.
400 */
401 ret = add_tnode(&tree, dev, ino);
402 if (ret != 1) {
403 if (ret == 0) {
404 (void) fprintf(stderr,
405 gettext("rm: cycle detected for %s\n"),
406 fullpath);
407 } else if (ret == -1) {
408 perror("rm");
457 if (caller != &top) {
458 if (checkdir(caller, &frame) != 0) {
459 closeframe(&frame);
460 goto unlinkit;
461 }
409 }
462 }
410 errcode++;
411 return (pop_name(first));
412 }
463 pushdir(&frame);
413
464
414 /*
415 * Open the directory for reading.
416 */
417 if ((name = opendir(path)) == NULL) {
418 int saveerrno = errno;
419
420 /*
465 /*
421 * If interactive, ask for acknowledgement.
466 * rm() only returns -1 if popdir failed at some point;
467 * frame.dp is no longer reliable and we must drop out.
422 */
468 */
423 if (interactive) {
424 /*
425 * Print an error message that
426 * we could not read the directory
427 * as the user wanted to examine
428 * files in the directory. Only
429 * affect the error status if
430 * user doesn't want to remove the
431 * directory as we still may be able
432 * remove the directory successfully.
433 */
434 (void) fprintf(stderr, gettext(
435 "rm: cannot read directory %s: "),
436 fullpath);
437 errno = saveerrno;
438 perror("");
439 (void) fprintf(stderr, gettext(
440 "rm: remove %s: (%s/%s)? "),
441 fullpath, yeschr, nochr);
442 if (!yes()) {
443 ++errcode;
444 return (pop_name(first));
445 }
469 while ((dent = readdir(frame.dp)) != NULL) {
470 if (strcmp(dent->d_name, ".") == 0 ||
471 strcmp(dent->d_name, "..") == 0)
472 continue;
473
474 if (rm(dent->d_name, &frame) != 0)
475 break;
446 }
447
476 }
477
478 if (popdir(dent == NULL) != 0)
479 return (-1);
480
448 /*
481 /*
449 * If the directory is empty, we may be able to
450 * go ahead and remove it.
482 * We recursed and the subdirectory may have set the CANTCLOSE
483 * flag; we need to clear it except for &top.
484 * Recursion may have invalidated entry because of closedir().
451 */
485 */
452 if (rmdir(path) == FAIL) {
453 if (interactive) {
454 int rmdirerr = errno;
455 (void) fprintf(stderr, gettext(
456 "rm: Unable to remove directory %s: "),
457 fullpath);
458 errno = rmdirerr;
459 perror("");
460 } else {
461 (void) fprintf(stderr, gettext(
462 "rm: cannot read directory %s: "),
463 fullpath);
464 errno = saveerrno;
465 perror("");
466 }
467 ++errcode;
486 if (caller != &top) {
487 caller->flags &= ~DIR_CANTCLOSE;
488 entry = &pathbuf[caller->up->pathend + 1];
468 }
489 }
469
470 /* Continue to next file/directory rather than exit */
471 return (pop_name(first));
490 } else {
491 flag = 0;
472 }
492 }
473
493unlinkit:
474 /*
494 /*
475 * XCU4 requires that rm -r descend the directory
476 * hierarchy without regard to PATH_MAX. If we can't
477 * chdir() do not increment error counter and do not
478 * print message.
479 *
480 * However, if we cannot chdir because someone has taken away
481 * execute access we may still be able to delete the directory
482 * if it's empty. The old rm could do this.
495 * If interactive, ask for acknowledgement.
483 */
496 */
484
485 if (chdir(path) == -1) {
486 chdir_failed = 1;
487 }
488
489 /*
490 * Read every directory entry.
491 */
492 while ((direct = readdir(name)) != NULL) {
497 if (interactive) {
498 if (!confirm(stderr, gettext("rm: remove %s (%s/%s)? "),
499 pathbuf, yeschr, nochr)) {
500 return (0);
501 }
502 } else if (!silent && flag == 0) {
493 /*
503 /*
494 * Ignore "." and ".." entries.
504 * If not silent, and stdin is a terminal, and there's
505 * no write access, and the file isn't a symbolic link,
506 * ask for permission. If flag is set, then we know it's
507 * a directory so we skip this test as it was done above.
508 *
509 * TRANSLATION_NOTE - The following message will contain the
510 * first character of the strings for "yes" and "no" defined
511 * in the file "nl_langinfo.po". After substitution, the
512 * message will appear as follows:
513 * rm: <filename>: override protection XXX (y/n)?
514 * where XXX is the permission mode bits of the file in octal
515 * and <filename> is the file to be removed
516 *
495 */
517 */
496 if (strcmp(direct->d_name, ".") == 0 ||
497 strcmp(direct->d_name, "..") == 0)
498 continue;
499 /*
500 * Try to remove the file.
501 */
502 len = strlen(direct->d_name) + 1;
503 if (chdir_failed) {
504 len += strlen(path) + 2;
518 if (ontty && !S_ISLNK(temp.st_mode) &&
519 __accessat(caller->fd, entry, W_OK|E_OK) != 0 &&
520 !confirm(stdout,
521 gettext("rm: %s: override protection %o (%s/%s)? "),
522 pathbuf, temp.st_mode & 0777, yeschr, nochr)) {
523 return (0);
505 }
524 }
525 }
506
526
507 newpath = malloc(len);
508 if (newpath == NULL) {
509 (void) fprintf(stderr,
510 gettext("rm: Insufficient memory.\n"));
511 cleanup();
512 exit(1);
513 }
527 if (unlinkat(caller->fd, entry, flag) != 0) {
528 err = errno;
529 if (err == ENOENT)
530 return (0);
514
531
515 if (!chdir_failed) {
516 (void) strcpy(newpath, direct->d_name);
532 if (flag != 0) {
533 if (err == EINVAL) {
534 (void) fprintf(stderr, gettext(
535 "rm: Cannot remove any directory in the "
536 "path of the current working directory\n"
537 "%s\n"), pathbuf);
538 } else {
539 if (err == EEXIST)
540 err = ENOTEMPTY;
541 (void) fprintf(stderr,
542 gettext("rm: Unable to remove directory %s:"
543 " %s\n"), pathbuf, strerror(err));
544 }
517 } else {
545 } else {
518 (void) snprintf(newpath, len, "%s/%s",
519 path, direct->d_name);
520 }
546#ifndef XPG4
547 if (!silent || interactive) {
548#endif
521
549
522
523 /*
524 * If a spare file descriptor is available, just call the
525 * "rm" function with the file name; otherwise close the
526 * directory and reopen it when the child is removed.
527 */
528 if (name->dd_fd >= maxfiles) {
529 (void) closedir(name);
530 if (rm(newpath, 0) < 0)
531 bad_chdir = -1;
532 if (!chdir_failed)
533 name = opendir(".");
534 else
535 name = opendir(path);
536 if (name == NULL) {
537 (void) fprintf(stderr,
550 (void) fprintf(stderr,
538 gettext("rm: cannot read directory %s: "),
539 fullpath);
540 perror("");
541 cleanup();
542 exit(2);
551 gettext("rm: %s not removed: %s\n"),
552 pathbuf, strerror(err));
553#ifndef XPG4
543 }
554 }
544 } else if (rm(newpath, 0) < 0)
545 bad_chdir = -1;
546
547 free(newpath);
548 if (bad_chdir)
549 break;
550 }
551
552 /*
553 * Close the directory we just finished reading.
554 */
555 (void) closedir(name);
556 if (bad_chdir)
557 return (-1);
558
559 /*
560 * The contents of the directory have been removed. If the
561 * directory itself is in the path of the current working
562 * directory, don't try to remove it.
563 * When the directory itself is the current working directory, mypath()
564 * has a return code == 2.
565 *
566 * XCU4: Because we've descended the directory hierarchy in order
567 * to avoid PATH_MAX limitation, we must now start ascending
568 * one level at a time and remove files/directories.
569 */
570
571 if (!chdir_failed) {
572 if (first)
573 chdir_init();
574 else if (chdir("..") == -1) {
575 (void) fprintf(stderr,
576 gettext("rm: cannot change to parent of "
577 "directory %s: "),
578 fullpath);
579 perror("");
580 cleanup();
581 exit(2);
555#endif
582 }
556 }
557 errcnt++;
583 }
558 }
584
585 switch (ismypath) {
586 case 3:
587 return (pop_name(first));
588 case 2:
589 (void) fprintf(stderr,
590 gettext("rm: Cannot remove any directory in the path "
591 "of the current working directory\n%s\n"), fullpath);
592 ++errcode;
593 return (pop_name(first));
594 case 1:
595 ++errcode;
596 return (pop_name(first));
597 case 0:
598 break;
599 }
600
601 /*
602 * If interactive, ask for acknowledgement.
603 */
604 if (interactive) {
605 (void) fprintf(stderr, gettext("rm: remove %s: (%s/%s)? "),
606 fullpath, yeschr, nochr);
607 if (!yes())
608 return (pop_name(first));
609 }
610 if (rmdir(path) == FAIL) {
611 (void) fprintf(stderr,
612 gettext("rm: Unable to remove directory %s: "),
613 fullpath);
614 perror("");
615 ++errcode;
616 }
617 return (pop_name(first));
559 return (0);
618}
619
560}
561
620
621static int
622yes(void)
623{
562static int
563yes(void)
564{
624 int i, b;
625 char ans[SCHAR_MAX + 1];
565 int i, b;
566 char ans[SCHAR_MAX + 1];
626
627 for (i = 0; ; i++) {
628 b = getchar();
629 if (b == '\n' || b == '\0' || b == EOF) {
630 ans[i] = 0;
631 break;
632 }
633 if (i < SCHAR_MAX)
567
568 for (i = 0; ; i++) {
569 b = getchar();
570 if (b == '\n' || b == '\0' || b == EOF) {
571 ans[i] = 0;
572 break;
573 }
574 if (i < SCHAR_MAX)
634 ans[i] = b;
575 ans[i] = (char)b;
635 }
636 if (i >= SCHAR_MAX) {
637 i = SCHAR_MAX;
638 ans[SCHAR_MAX] = 0;
639 }
640 if ((i == 0) | (strncmp(yeschr, ans, i)))
641 return (0);
642 return (1);
643}
644
576 }
577 if (i >= SCHAR_MAX) {
578 i = SCHAR_MAX;
579 ans[SCHAR_MAX] = 0;
580 }
581 if ((i == 0) | (strncmp(yeschr, ans, i)))
582 return (0);
583 return (1);
584}
585
645
646static int
586static int
647mypath(dev_t dev, ino_t ino)
587confirm(FILE *fp, const char *q, ...)
648{
588{
649 struct dir_id *curdir;
589 va_list ap;
650
590
651 /*
652 * Check to see if this is our current directory
653 * Indicated by return 2;
654 */
655 if (dev == initdir.dev && ino == initdir.inode) {
656 return (2);
657 }
658
659 curdir = initdir.next;
660
661 while (curdir != NULL) {
662 /*
663 * If we find a match, the directory (dev, ino) passed to
664 * mypath() is an ancestor of ours. Indicated by return 3.
665 */
666 if (curdir->dev == dev && curdir->inode == ino)
667 return (3);
668 curdir = curdir->next;
669 }
670 /*
671 * parent_err indicates we couldn't stat or chdir to
672 * one of our parent dirs, so the linked list of dir_id structs
673 * is incomplete
674 */
675 if (parent_err) {
676#ifndef XPG4
677 if (!silent || interactive) {
678#endif
679 (void) fprintf(stderr, gettext("rm: cannot determine "
680 "if this is an ancestor of the current "
681 "working directory\n%s\n"), fullpath);
682#ifndef XPG4
683 }
684#endif
685 /* assume it is. least dangerous */
686 return (1);
687 }
688 return (0);
591 va_start(ap, q);
592 (void) vfprintf(fp, q, ap);
593 va_end(ap);
594 return (yes());
689}
690
595}
596
691static int maxlen;
692static int curlen;
693
694static char *
695get_filename(char *name)
696{
697 char *path;
698 size_t len;
699
700 if (fullpath == NULL || *fullpath == '\0') {
701 path = strdup(name);
702 if (path == NULL) {
703 (void) fprintf(stderr,
704 gettext("rm: Insufficient memory.\n"));
705 cleanup();
706 exit(1);
707 }
708 } else {
709 len = strlen(fullpath) + strlen(name) + 2;
710 path = malloc(len);
711 if (path == NULL) {
712 (void) fprintf(stderr,
713 gettext("rm: Insufficient memory.\n"));
714 cleanup();
715 exit(1);
716 }
717 (void) snprintf(path, len, "%s/%s", fullpath, name);
718 }
719 return (path);
720}
721
722static void
597static void
723push_name(char *name, int first)
598memerror(void)
724{
599{
725 int namelen;
726 struct stat buffer;
727 struct dir_id *newdir;
728
729 namelen = strlen(name) + 1; /* 1 for "/" */
730 if ((curlen + namelen) >= maxlen) {
731 maxlen += PATH_MAX;
732 fullpath = (char *)realloc(fullpath, (size_t)(maxlen + 1));
733 }
734 if (first) {
735 (void) strcpy(fullpath, name);
736 } else {
737 (void) strcat(fullpath, "/");
738 (void) strcat(fullpath, name);
739 }
740 curlen = strlen(fullpath);
741
742 if (stat(".", &buffer) == -1) {
743 (void) fprintf(stderr,
744 gettext("rm: cannot stat current directory: "));
745 perror("");
746 exit(2);
747 }
748 if ((newdir = malloc(sizeof (struct dir_id))) == NULL) {
749 (void) fprintf(stderr,
750 gettext("rm: Insufficient memory.\n"));
751 cleanup();
752 exit(1);
753 }
754
755 newdir->dev = buffer.st_dev;
756 newdir->inode = buffer.st_ino;
757 newdir->next = current_dir;
758 current_dir = newdir;
600 (void) fprintf(stderr, gettext("rm: Insufficient memory.\n"));
601 exit(1);
759}
760
602}
603
604/*
605 * If we can't stat "..", it's either not there or we can't search
606 * the current directory; in that case we can't return back through
607 * "..", so we need to keep the parent open.
608 * Check that we came from "..", if not then this directory entry is an
609 * additional link and there is risk of a filesystem cycle and we also
610 * can't go back up through ".." and we keep the directory open.
611 */
761static int
612static int
762pop_name(int first)
613checkdir(struct dlist *caller, struct dlist *frmp)
763{
614{
764 int retval = 0;
765 char *slash;
766 struct stat buffer;
767 struct dir_id *remove_dir;
615 struct stat up;
616 struct dlist *ptr;
768
617
769 if (first) {
770 *fullpath = '\0';
618 if (fstatat(frmp->fd, "..", &up, 0) != 0) {
619 caller->flags |= DIR_CANTCLOSE;
771 return (0);
620 return (0);
621 } else if (up.st_ino == caller->ino && up.st_dev == caller->dev) {
622 return (0);
772 }
623 }
773 slash = strrchr(fullpath, '/');
774 if (slash)
775 *slash = '\0';
776 else
777 *fullpath = '\0';
778 curlen = strlen(fullpath);
779
624
780 if (stat(".", &buffer) == -1) {
781 (void) fprintf(stderr,
782 gettext("rm: cannot stat current directory: "));
783 perror("");
784 exit(2);
785 }
786
787 /*
788 * For each pop operation, verify that the device and inode numbers
789 * of "." match the numbers recorded before the chdir was done into
790 * the directory. If they do not match, it is an indication of
791 * possible malicious activity and rm has chdir to an unintended
792 * directory
793 */
794 if ((current_dir->inode != buffer.st_ino) || (current_dir->dev !=
795 buffer.st_dev)) {
796 (void) fprintf(stderr, gettext("rm: WARNING: "
797 "A subdirectory of %s was moved or linked to "
798 "another directory during the execution of rm\n"),
799 fullpath);
800 retval = -1;
801 }
802 remove_dir = current_dir;
803 current_dir = current_dir->next;
804 free(remove_dir);
805 return (retval);
806}
807
808static void
809force_chdir(char *dirname)
810{
811 char *pathname, *mp, *tp;
812
813 /* use pathname instead of dirname, so dirname won't be modified */
814 if ((pathname = strdup(dirname)) == NULL) {
815 (void) fprintf(stderr, gettext("rm: strdup: "));
816 perror("");
817 cleanup();
818 exit(2);
819 }
820
821 /* pathname is an absolute full path from getcwd() */
822 mp = pathname;
823 while (mp) {
824 tp = strchr(mp, '/');
825 if (strlen(mp) >= PATH_MAX) {
826 /*
827 * after the first iteration through this
828 * loop, the below will NULL out the '/'
829 * which follows the first dir on pathname
830 */
831 *tp = 0;
832 tp++;
833 if (*mp == NULL)
834 ch_dir("/");
835 else
836 /*
837 * mp points to the start of a dirname,
838 * terminated by NULL, so ch_dir()
839 * here will move down one directory
840 */
841 ch_dir(mp);
842 /*
843 * reset mp to the start of the dirname
844 * which follows the one we just chdir'd to
845 */
846 mp = tp;
847 continue; /* probably can remove this */
848 } else {
849 ch_dir(mp);
850 break;
851 }
852 }
853 free(pathname);
854}
855
856static void
857ch_dir(char *dirname)
858{
859 if (chdir(dirname) == -1) {
860 (void) fprintf(stderr,
861 gettext("rm: cannot change to %s directory: "), dirname);
862 perror("");
863 cleanup();
864 exit(2);
865 }
866}
867
868static void
869chdir_init(void)
870{
871 /*
872 * Go back to init dir--the dir from where rm was executed--using
873 * one of two methods, depending on which method works
874 * for the given permissions of the init dir and its
875 * parent directories.
876 */
877 if (initdirfd != -1) {
878 if (fchdir(initdirfd) == -1) {
625 /* Directory hard link, check cycle */
626 for (ptr = caller; ptr != NULL; ptr = ptr->up) {
627 if (frmp->dev == ptr->dev && frmp->ino == ptr->ino) {
879 (void) fprintf(stderr,
628 (void) fprintf(stderr,
880 gettext("rm: cannot change to starting "
881 "directory: "));
882 perror("");
883 cleanup();
884 exit(2);
629 gettext("rm: cycle detected for %s\n"), pathbuf);
630 errcnt++;
631 return (-1);
885 }
632 }
886 } else {
887 if (strlen(cwd) < PATH_MAX)
888 ch_dir(cwd);
889 else
890 force_chdir(cwd);
891 }
633 }
634 caller->flags |= DIR_CANTCLOSE;
635 return (0);
892}
636}
893
894/*
895 * check_initdir -
896 * is only called the first time rm tries to
897 * remove a directory. It saves the current directory, i.e.,
898 * init dir, so we can go back to it after traversing elsewhere.
899 * It also saves all the device and inode numbers of each
900 * dir from the initial dir back to the root in a linked list, so we
901 * can later check, via mypath(), if we are trying to remove our current
902 * dir or an ancestor.
903 */
904static void
905check_initdir(void)
906{
907 int size; /* size allocated for pathname of init dir (cwd) */
908 struct stat buffer;
909 struct dir_id *lastdir, *curdir;
910
911 /*
912 * We need to save where we currently are (the "init dir") so
913 * we can return after traversing down directories we're
914 * removing. Two methods are attempted:
915 *
916 * 1) open() the initial dir so we can use the fd
917 * to fchdir() back. This requires read permission
918 * on the initial dir.
919 *
920 * 2) getcwd() so we can chdir() to go back. This
921 * requires search (x) permission on the init dir,
922 * and read and search permission on all parent dirs. Also,
923 * getcwd() will not work if the init dir is > 341
924 * directories deep (see open bugid 4033182 - getcwd needs
925 * to work for pathnames of any depth).
926 *
927 * If neither method works, we can't remove any directories
928 * and rm will fail.
929 *
930 * For future enhancement, a possible 3rd option to use
931 * would be to fork a process to remove a directory,
932 * eliminating the need to chdir back to the initial directory
933 * and eliminating the permission restrictions on the initial dir
934 * or its parent dirs.
935 */
936 initdirfd = open(".", O_RDONLY);
937 if (initdirfd == -1) {
938 size = PATH_MAX;
939 while ((cwd = getcwd(NULL, size)) == NULL) {
940 if (errno == ERANGE) {
941 size = PATH_MAX + size;
942 continue;
943 } else {
944 (void) fprintf(stderr,
945 gettext("rm: cannot open starting "
946 "directory: "));
947 perror("pwd");
948 exit(2);
949 }
950 }
951 }
952
953 /*
954 * since we exit on error here, we're guaranteed to at least
955 * have info in the first dir_id struct, initdir
956 */
957 if (stat(".", &buffer) == -1) {
958 (void) fprintf(stderr,
959 gettext("rm: cannot stat current directory: "));
960 perror("");
961 exit(2);
962 }
963 initdir.dev = buffer.st_dev;
964 initdir.inode = buffer.st_ino;
965 initdir.next = NULL;
966
967 lastdir = &initdir;
968 /*
969 * Starting from current working directory, walk toward the
970 * root, looking at each directory along the way.
971 */
972 for (;;) {
973 if (chdir("..") == -1 || lstat(".", &buffer) == -1) {
974 parent_err = 1;
975 break;
976 }
977
978 if ((lastdir->next = malloc(sizeof (struct dir_id))) ==
979 NULL) {
980 (void) fprintf(stderr,
981 gettext("rm: Insufficient memory.\n"));
982 cleanup();
983 exit(1);
984 }
985
986 curdir = lastdir->next;
987 curdir->dev = buffer.st_dev;
988 curdir->inode = buffer.st_ino;
989 curdir->next = NULL;
990
991 /*
992 * Stop when we reach the root; note that chdir("..")
993 * at the root dir will stay in root. Get rid of
994 * the redundant dir_id struct for root.
995 */
996 if (curdir->dev == lastdir->dev && curdir->inode ==
997 lastdir->inode) {
998 lastdir->next = NULL;
999 free(curdir);
1000 break;
1001 }
1002
1003 /* loop again to go back another level */
1004 lastdir = curdir;
1005 }
1006 /* go back to init directory */
1007 chdir_init();
1008}
1009
1010/*
1011 * cleanup the dynamically-allocated list of device numbers and inodes,
1012 * if any. If initdir was never used, it is external and static so
1013 * it is guaranteed initialized to zero, thus initdir.next would be NULL.
1014 */
1015
1016static void
1017cleanup(void) {
1018
1019 struct dir_id *lastdir, *curdir;
1020
1021 curdir = initdir.next;
1022
1023 while (curdir != NULL) {
1024 lastdir = curdir;
1025 curdir = curdir->next;
1026 free(lastdir);
1027 }
1028}