rm.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */
/*
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* rm [-fiRr] file ...
*/
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <dirent.h>
#include <limits.h>
#include <locale.h>
#include <langinfo.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/resource.h>
#define CHILD 0
#define FAIL -1
#define TRUE 1
#define FALSE 0
#define WRITE 02
#define SEARCH 07
static int errcode;
static void rm(char *, int);
static int yes(void);
static char *fullpath;
static int homedirfd;
static void force_chdir(char *);
static void ch_dir(char *);
static char *get_filename(char *name);
static void chdir_home(void);
static void check_homedir(void);
static void cleanup(void);
static char *cwd; /* pathname of home dir, from getcwd() */
static int parent_err = 0;
struct dir_id {
};
/*
* homedir is the first of a linked list of structures
* containing unique identifying device and inode numbers for
* each directory, from the home dir up to the root.
*/
int
{
extern int optind;
int errflg = 0;
int c;
#if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
#endif
(void) textdomain(TEXT_DOMAIN);
switch (c) {
case 'f':
#ifdef XPG4
interactive = FALSE;
#endif
break;
case 'i':
interactive = TRUE;
#ifdef XPG4
#endif
break;
case 'r':
case 'R':
break;
case '?':
errflg = 1;
break;
}
/*
* For BSD compatibility allow '-' to delimit the end
* of options. However, if options were already explicitly
* terminated with '--', then treat '-' literally: otherwise,
* "rm -- -" won't remove '-'.
*/
optind++;
gettext("usage: rm [-fiRr] file ...\n"));
exit(2);
}
perror("getrlimit");
exit(2);
} else
while (argc-- > 0) {
argv++;
}
cleanup();
return (errcode ? 2 : 0);
/* NOTREACHED */
}
static void
{
char *filepath;
char *p;
char resolved_path[PATH_MAX];
/*
* Check file to see if it exists.
*/
if (!silent) {
++errcode;
}
return;
}
/* prevent removal of / but allow removal of sym-links */
errcode++;
return;
}
/* prevent removal of . or .. (directly) */
p++;
else
p = path;
if (!silent) {
errcode++;
}
return;
}
/*
* If it's a directory, remove its contents.
*/
if (DIRECTORY) {
/*
* If "-r" wasn't specified, trying to remove directories
* is an error.
*/
if (!recursive) {
++errcode;
return;
}
if (first_dir) {
first_dir = 0;
}
return;
}
/*
* If interactive, ask for acknowledgement.
*
* TRANSLATION_NOTE - The following message will contain the
* first character of the strings for "yes" and "no" defined
* in the file "nl_langinfo.po". After substitution, the
* message will appear as follows:
* rm: remove <filename> (y/n)?
* For example, in German, this will appear as
* rm: l�schen <filename> (j/n)?
* where j=ja, n=nein, <filename>=the file to be removed
*
*/
if (interactive) {
if (!yes()) {
return;
}
} else if (!silent) {
/*
* If not silent, and stdin is a terminal, and there's
* no write access, and the file isn't a symbolic link,
* ask for permission.
*
* TRANSLATION_NOTE - The following message will contain the
* first character of the strings for "yes" and "no" defined
* in the file "nl_langinfo.po". After substitution, the
* message will appear as follows:
* rm: <filename>: override protection XXX (y/n)?
* where XXX is the permission mode bits of the file in octal
* and <filename> is the file to be removed
*
*/
(void) printf(
gettext("rm: %s: override protection %o (%s/%s)? "),
/*
* If permission isn't given, skip the file.
*/
if (!yes()) {
return;
}
}
}
/*
* the user if interactive or not silent.
* If unlink fails with errno = ENOENT because file was removed
* in between the lstat call and unlink don't inform the user and
* don't change errcode.
*/
return;
}
#ifndef XPG4
if (!silent || interactive) {
#endif
perror("");
#ifndef XPG4
}
#endif
++errcode;
}
}
static void
{
char *newpath;
int ismypath;
int chdir_failed = 0;
/*
* If interactive and this file isn't in the path of the
* current working directory, ask for acknowledgement.
*
* TRANSLATION_NOTE - The following message will contain the
* first character of the strings for "yes" and "no" defined
* in the file "nl_langinfo.po". After substitution, the
* message will appear as follows:
* rm: examine files in directory <directoryname> (y/n)?
* where <directoryname> is the directory to be removed
*
*/
if (interactive) {
gettext("rm: examine files in directory %s (%s/%s)? "),
/*
* If the answer is no, skip the directory.
*/
if (!yes()) {
return;
}
}
#ifdef XPG4
/*
* XCU4 and POSIX.2: If not interactive and file is not in the
* path of the current working directory, check to see whether
* or not directory is readable or writable and if not,
* prompt user for response.
*/
if (!interactive && !ismypath &&
if (!silent) {
"rm: examine files in directory %s (%s/%s)? "),
/*
* If the answer is no, skip the directory.
*/
if (!yes()) {
return;
}
}
}
#endif
/*
* Open the directory for reading.
*/
/*
* If interactive, ask for acknowledgement.
*/
if (interactive) {
/*
* Print an error message that
* we could not read the directory
* as the user wanted to examine
* files in the directory. Only
* affect the error status if
* user doesn't want to remove the
* directory as we still may be able
* remove the directory successfully.
*/
"rm: cannot read directory %s: "),
fullpath);
perror("");
"rm: remove %s: (%s/%s)? "),
if (!yes()) {
++errcode;
return;
}
}
/*
* If the directory is empty, we may be able to
* go ahead and remove it.
*/
if (interactive) {
"rm: Unable to remove directory %s: "),
fullpath);
perror("");
} else {
"rm: cannot read directory %s: "),
fullpath);
perror("");
}
++errcode;
}
return;
}
/*
* XCU4 requires that rm -r descend the directory
* hierarchy without regard to PATH_MAX. If we can't
* chdir() do not increment error counter and do not
* print message.
*
* However, if we cannot chdir because someone has taken away
* execute access we may still be able to delete the directory
* if it's empty. The old rm could do this.
*/
chdir_failed = 1;
}
/*
* Read every directory entry.
*/
/*
* Ignore "." and ".." entries.
*/
continue;
/*
* Try to remove the file.
*/
if (chdir_failed) {
}
gettext("rm: Insufficient memory.\n"));
cleanup();
exit(1);
}
if (!chdir_failed) {
} else {
}
/*
* If a spare file descriptor is available, just call the
* "rm" function with the file name; otherwise close the
* directory and reopen it when the child is removed.
*/
if (!chdir_failed)
else
gettext("rm: cannot read directory %s: "),
fullpath);
perror("");
cleanup();
exit(2);
}
} else
}
/*
* Close the directory we just finished reading.
*/
/*
* The contents of the directory have been removed. If the
* directory itself is in the path of the current working
* directory, don't try to remove it.
* When the directory itself is the current working directory, mypath()
* has a return code == 2.
*
* XCU4: Because we've descended the directory hierarchy in order
* to avoid PATH_MAX limitation, we must now start ascending
* one level at a time and remove files/directories.
*/
if (!chdir_failed) {
if (first)
chdir_home();
gettext("rm: cannot change to parent of "
"directory %s: "),
fullpath);
perror("");
cleanup();
exit(2);
}
}
switch (ismypath) {
case 3:
return;
case 2:
gettext("rm: Cannot remove any directory in the path "
"of the current working directory\n%s\n"), fullpath);
++errcode;
return;
case 1:
++errcode;
return;
case 0:
break;
}
/*
* If interactive, ask for acknowledgement.
*/
if (interactive) {
if (!yes()) {
return;
}
}
gettext("rm: Unable to remove directory %s: "),
fullpath);
perror("");
++errcode;
}
}
static int
yes(void)
{
int i, b;
for (i = 0; ; i++) {
b = getchar();
ans[i] = 0;
break;
}
if (i < SCHAR_MAX)
ans[i] = b;
}
if (i >= SCHAR_MAX) {
i = SCHAR_MAX;
}
return (0);
return (1);
}
static int
{
/*
* Check to see if this is our current directory
* Indicated by return 2;
*/
return (2);
}
/*
* If we find a match, the directory (dev, ino) passed to
* mypath() is an ancestor of ours. Indicated by return 3.
*/
return (3);
}
/*
* parent_err indicates we couldn't stat or chdir to
* one of our parent dirs, so the linked list of dir_id structs
* is incomplete
*/
if (parent_err) {
#ifndef XPG4
if (!silent || interactive) {
#endif
"if this is an ancestor of the current "
"working directory\n%s\n"), fullpath);
#ifndef XPG4
}
#endif
/* assume it is. least dangerous */
return (1);
}
return (0);
}
static int maxlen;
static int curlen;
static char *
get_filename(char *name)
{
char *path;
gettext("rm: Insufficient memory.\n"));
cleanup();
exit(1);
}
} else {
gettext("rm: Insufficient memory.\n"));
cleanup();
exit(1);
}
}
return (path);
}
static void
{
int namelen;
}
if (first) {
} else {
}
}
static void
{
char *slash;
if (first) {
*fullpath = '\0';
return;
}
if (slash)
*slash = '\0';
else
*fullpath = '\0';
}
static void
force_chdir(char *dirname)
{
/* use pathname instead of dirname, so dirname won't be modified */
perror("");
cleanup();
exit(2);
}
/* pathname is an absolute full path from getcwd() */
while (mp) {
/*
* after the first iteration through this
* loop, the below will NULL out the '/'
* which follows the first dir on pathname
*/
*tp = 0;
tp++;
ch_dir("/");
else
/*
* mp points to the start of a dirname,
* terminated by NULL, so ch_dir()
* here will move down one directory
*/
/*
* reset mp to the start of the dirname
* which follows the one we just chdir'd to
*/
continue; /* probably can remove this */
} else {
break;
}
}
}
static void
{
perror("");
cleanup();
exit(2);
}
}
static void
chdir_home(void)
{
/*
* Go back to home dir--the dir from where rm was executed--using
* one of two methods, depending on which method works
* for the given permissions of the home dir and its
* parent directories.
*/
if (homedirfd != -1) {
gettext("rm: cannot change to starting "
"directory: "));
perror("");
cleanup();
exit(2);
}
} else {
else
}
}
/*
* check_homedir -
* is only called the first time rm tries to
* remove a directory. It saves the current directory, i.e.,
* home dir, so we can go back to it after traversing elsewhere.
* It also saves all the device and inode numbers of each
* dir from the home dir back to the root in a linked list, so we
* can later check, via mypath(), if we are trying to remove our current
* dir or an ancestor.
*/
static void
check_homedir(void)
{
int size; /* size allocated for pathname of home dir (cwd) */
/*
* We need to save where we currently are (the "home dir") so
* we can return after traversing down directories we're
* removing. Two methods are attempted:
*
* 1) open() the home dir so we can use the fd
* to fchdir() back. This requires read permission
* on the home dir.
*
* 2) getcwd() so we can chdir() to go back. This
* requires search (x) permission on the home dir,
* and read and search permission on all parent dirs. Also,
* getcwd() will not work if the home dir is > 341
* directories deep (see open bugid 4033182 - getcwd needs
* to work for pathnames of any depth).
*
* If neither method works, we can't remove any directories
* and rm will fail.
*
* For future enhancement, a possible 3rd option to use
* would be to fork a process to remove a directory,
* eliminating the need to chdir back to the home directory
* and eliminating the permission restrictions on the home dir
* or its parent dirs.
*/
if (homedirfd == -1) {
continue;
} else {
gettext("rm: cannot open starting "
"directory: "));
perror("pwd");
exit(2);
}
}
}
/*
* since we exit on error here, we're guaranteed to at least
* have info in the first dir_id struct, homedir
*/
gettext("rm: cannot stat current directory: "));
perror("");
exit(2);
}
/*
* Starting from current working directory, walk toward the
* root, looking at each directory along the way.
*/
for (;;) {
parent_err = 1;
break;
}
NULL) {
gettext("rm: Insufficient memory.\n"));
cleanup();
exit(1);
}
/*
* Stop when we reach the root; note that chdir("..")
* at the root dir will stay in root. Get rid of
* the redundant dir_id struct for root.
*/
break;
}
/* loop again to go back another level */
}
/* go back to home directory */
chdir_home();
}
/*
* cleanup the dynamically-allocated list of device numbers and inodes,
* if any. If homedir was never used, it is external and static so
* it is guaranteed initialized to zero, thus homedir.next would be NULL.
*/
static void
cleanup(void) {
}
}