Changeset View
Changeset View
Standalone View
Standalone View
head/bin/rm/rm.c
Show First 20 Lines • Show All 55 Lines • ▼ Show 20 Lines | |||||
#include <pwd.h> | #include <pwd.h> | ||||
#include <stdint.h> | #include <stdint.h> | ||||
#include <stdio.h> | #include <stdio.h> | ||||
#include <stdlib.h> | #include <stdlib.h> | ||||
#include <string.h> | #include <string.h> | ||||
#include <sysexits.h> | #include <sysexits.h> | ||||
#include <unistd.h> | #include <unistd.h> | ||||
static int dflag, eval, fflag, iflag, Pflag, vflag, Wflag, stdin_ok; | static int dflag, eval, fflag, iflag, vflag, Wflag, stdin_ok; | ||||
static int rflag, Iflag, xflag; | static int rflag, Iflag, xflag; | ||||
static uid_t uid; | static uid_t uid; | ||||
static volatile sig_atomic_t info; | static volatile sig_atomic_t info; | ||||
static int check(const char *, const char *, struct stat *); | static int check(const char *, const char *, struct stat *); | ||||
static int check2(char **); | static int check2(char **); | ||||
static void checkdot(char **); | static void checkdot(char **); | ||||
static void checkslash(char **); | static void checkslash(char **); | ||||
static void rm_file(char **); | static void rm_file(char **); | ||||
static int rm_overwrite(const char *, struct stat *); | |||||
static void rm_tree(char **); | static void rm_tree(char **); | ||||
static void siginfo(int __unused); | static void siginfo(int __unused); | ||||
static void usage(void); | static void usage(void); | ||||
/* | /* | ||||
* rm -- | * rm -- | ||||
* This rm is different from historic rm's, but is expected to match | * This rm is different from historic rm's, but is expected to match | ||||
* POSIX 1003.2 behavior. The most visible difference is that -f | * POSIX 1003.2 behavior. The most visible difference is that -f | ||||
Show All 22 Lines | if (argc == 2) | ||||
rm_file(&argv[1]); | rm_file(&argv[1]); | ||||
else if (argc == 3 && strcmp(argv[1], "--") == 0) | else if (argc == 3 && strcmp(argv[1], "--") == 0) | ||||
rm_file(&argv[2]); | rm_file(&argv[2]); | ||||
else | else | ||||
usage(); | usage(); | ||||
exit(eval); | exit(eval); | ||||
} | } | ||||
Pflag = rflag = xflag = 0; | rflag = xflag = 0; | ||||
while ((ch = getopt(argc, argv, "dfiIPRrvWx")) != -1) | while ((ch = getopt(argc, argv, "dfiIPRrvWx")) != -1) | ||||
switch(ch) { | switch(ch) { | ||||
case 'd': | case 'd': | ||||
dflag = 1; | dflag = 1; | ||||
break; | break; | ||||
case 'f': | case 'f': | ||||
fflag = 1; | fflag = 1; | ||||
iflag = 0; | iflag = 0; | ||||
break; | break; | ||||
case 'i': | case 'i': | ||||
fflag = 0; | fflag = 0; | ||||
iflag = 1; | iflag = 1; | ||||
break; | break; | ||||
case 'I': | case 'I': | ||||
Iflag = 1; | Iflag = 1; | ||||
break; | break; | ||||
case 'P': | case 'P': | ||||
Pflag = 1; | /* Compatibility no-op. */ | ||||
break; | break; | ||||
case 'R': | case 'R': | ||||
case 'r': /* Compatibility. */ | case 'r': /* Compatibility. */ | ||||
rflag = 1; | rflag = 1; | ||||
break; | break; | ||||
case 'v': | case 'v': | ||||
vflag = 1; | vflag = 1; | ||||
break; | break; | ||||
▲ Show 20 Lines • Show All 168 Lines • ▼ Show 20 Lines | if (rval == 0) { | ||||
* the file, it can't be unlinked. | * the file, it can't be unlinked. | ||||
*/ | */ | ||||
if (fflag) | if (fflag) | ||||
continue; | continue; | ||||
/* FALLTHROUGH */ | /* FALLTHROUGH */ | ||||
case FTS_F: | case FTS_F: | ||||
case FTS_NSOK: | case FTS_NSOK: | ||||
if (Pflag) | |||||
if (!rm_overwrite(p->fts_accpath, p->fts_info == | |||||
FTS_NSOK ? NULL : p->fts_statp)) | |||||
continue; | |||||
/* FALLTHROUGH */ | |||||
default: | default: | ||||
rval = unlink(p->fts_accpath); | rval = unlink(p->fts_accpath); | ||||
if (rval == 0 || (fflag && errno == ENOENT)) { | if (rval == 0 || (fflag && errno == ENOENT)) { | ||||
if (rval == 0 && vflag) | if (rval == 0 && vflag) | ||||
(void)printf("%s\n", | (void)printf("%s\n", | ||||
p->fts_path); | p->fts_path); | ||||
if (rval == 0 && info) { | if (rval == 0 && info) { | ||||
info = 0; | info = 0; | ||||
▲ Show 20 Lines • Show All 54 Lines • ▼ Show 20 Lines | if (!uid && !S_ISWHT(sb.st_mode) && | ||||
(sb.st_flags & (UF_APPEND|UF_IMMUTABLE)) && | (sb.st_flags & (UF_APPEND|UF_IMMUTABLE)) && | ||||
!(sb.st_flags & (SF_APPEND|SF_IMMUTABLE))) | !(sb.st_flags & (SF_APPEND|SF_IMMUTABLE))) | ||||
rval = lchflags(f, sb.st_flags & ~(UF_APPEND|UF_IMMUTABLE)); | rval = lchflags(f, sb.st_flags & ~(UF_APPEND|UF_IMMUTABLE)); | ||||
if (rval == 0) { | if (rval == 0) { | ||||
if (S_ISWHT(sb.st_mode)) | if (S_ISWHT(sb.st_mode)) | ||||
rval = undelete(f); | rval = undelete(f); | ||||
else if (S_ISDIR(sb.st_mode)) | else if (S_ISDIR(sb.st_mode)) | ||||
rval = rmdir(f); | rval = rmdir(f); | ||||
else { | else | ||||
if (Pflag) | |||||
if (!rm_overwrite(f, &sb)) | |||||
continue; | |||||
rval = unlink(f); | rval = unlink(f); | ||||
} | } | ||||
} | |||||
if (rval && (!fflag || errno != ENOENT)) { | if (rval && (!fflag || errno != ENOENT)) { | ||||
warn("%s", f); | warn("%s", f); | ||||
eval = 1; | eval = 1; | ||||
} | } | ||||
if (vflag && rval == 0) | if (vflag && rval == 0) | ||||
(void)printf("%s\n", f); | (void)printf("%s\n", f); | ||||
if (info && rval == 0) { | if (info && rval == 0) { | ||||
info = 0; | info = 0; | ||||
(void)printf("%s\n", f); | (void)printf("%s\n", f); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
/* | |||||
* rm_overwrite -- | |||||
* Overwrite the file 3 times with varying bit patterns. | |||||
* | |||||
* XXX | |||||
* This is a cheap way to *really* delete files. Note that only regular | |||||
* files are deleted, directories (and therefore names) will remain. | |||||
* Also, this assumes a fixed-block file system (like FFS, or a V7 or a | |||||
* System V file system). In a logging or COW file system, you'll have to | |||||
* have kernel support. | |||||
*/ | |||||
static int | static int | ||||
rm_overwrite(const char *file, struct stat *sbp) | |||||
{ | |||||
struct stat sb, sb2; | |||||
struct statfs fsb; | |||||
off_t len; | |||||
int bsize, fd, wlen; | |||||
char *buf = NULL; | |||||
fd = -1; | |||||
if (sbp == NULL) { | |||||
if (lstat(file, &sb)) | |||||
goto err; | |||||
sbp = &sb; | |||||
} | |||||
if (!S_ISREG(sbp->st_mode)) | |||||
return (1); | |||||
if (sbp->st_nlink > 1 && !fflag) { | |||||
warnx("%s (inode %ju): not overwritten due to multiple links", | |||||
file, (uintmax_t)sbp->st_ino); | |||||
return (0); | |||||
} | |||||
if ((fd = open(file, O_WRONLY|O_NONBLOCK|O_NOFOLLOW, 0)) == -1) | |||||
goto err; | |||||
if (fstat(fd, &sb2)) | |||||
goto err; | |||||
if (sb2.st_dev != sbp->st_dev || sb2.st_ino != sbp->st_ino || | |||||
!S_ISREG(sb2.st_mode)) { | |||||
errno = EPERM; | |||||
goto err; | |||||
} | |||||
if (fstatfs(fd, &fsb) == -1) | |||||
goto err; | |||||
bsize = MAX(fsb.f_iosize, 1024); | |||||
if ((buf = malloc(bsize)) == NULL) | |||||
err(1, "%s: malloc", file); | |||||
#define PASS(byte) { \ | |||||
memset(buf, byte, bsize); \ | |||||
for (len = sbp->st_size; len > 0; len -= wlen) { \ | |||||
wlen = len < bsize ? len : bsize; \ | |||||
if (write(fd, buf, wlen) != wlen) \ | |||||
goto err; \ | |||||
} \ | |||||
} | |||||
PASS(0xff); | |||||
if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET)) | |||||
goto err; | |||||
PASS(0x00); | |||||
if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET)) | |||||
goto err; | |||||
PASS(0xff); | |||||
if (!fsync(fd) && !close(fd)) { | |||||
free(buf); | |||||
return (1); | |||||
} | |||||
err: eval = 1; | |||||
if (buf) | |||||
free(buf); | |||||
if (fd != -1) | |||||
close(fd); | |||||
warn("%s", file); | |||||
return (0); | |||||
} | |||||
static int | |||||
check(const char *path, const char *name, struct stat *sp) | check(const char *path, const char *name, struct stat *sp) | ||||
{ | { | ||||
int ch, first; | int ch, first; | ||||
char modep[15], *flagsp; | char modep[15], *flagsp; | ||||
/* Check -i first. */ | /* Check -i first. */ | ||||
if (iflag) | if (iflag) | ||||
(void)fprintf(stderr, "remove %s? ", path); | (void)fprintf(stderr, "remove %s? ", path); | ||||
else { | else { | ||||
/* | /* | ||||
* If it's not a symbolic link and it's unwritable and we're | * If it's not a symbolic link and it's unwritable and we're | ||||
* talking to a terminal, ask. Symbolic links are excluded | * talking to a terminal, ask. Symbolic links are excluded | ||||
* because their permissions are meaningless. Check stdin_ok | * because their permissions are meaningless. Check stdin_ok | ||||
* first because we may not have stat'ed the file. | * first because we may not have stat'ed the file. | ||||
*/ | */ | ||||
if (!stdin_ok || S_ISLNK(sp->st_mode) || | if (!stdin_ok || S_ISLNK(sp->st_mode) || | ||||
(!access(name, W_OK) && | (!access(name, W_OK) && | ||||
!(sp->st_flags & (SF_APPEND|SF_IMMUTABLE)) && | !(sp->st_flags & (SF_APPEND|SF_IMMUTABLE)) && | ||||
(!(sp->st_flags & (UF_APPEND|UF_IMMUTABLE)) || !uid))) | (!(sp->st_flags & (UF_APPEND|UF_IMMUTABLE)) || !uid))) | ||||
return (1); | return (1); | ||||
strmode(sp->st_mode, modep); | strmode(sp->st_mode, modep); | ||||
if ((flagsp = fflagstostr(sp->st_flags)) == NULL) | if ((flagsp = fflagstostr(sp->st_flags)) == NULL) | ||||
err(1, "fflagstostr"); | err(1, "fflagstostr"); | ||||
if (Pflag) | |||||
errx(1, | |||||
"%s: -P was specified, but file is not writable", | |||||
path); | |||||
(void)fprintf(stderr, "override %s%s%s/%s %s%sfor %s? ", | (void)fprintf(stderr, "override %s%s%s/%s %s%sfor %s? ", | ||||
modep + 1, modep[10] == ' ' ? "" : " ", | modep + 1, modep[10] == ' ' ? "" : " ", | ||||
user_from_uid(sp->st_uid, 0), | user_from_uid(sp->st_uid, 0), | ||||
group_from_gid(sp->st_gid, 0), | group_from_gid(sp->st_gid, 0), | ||||
*flagsp ? flagsp : "", *flagsp ? " " : "", | *flagsp ? flagsp : "", *flagsp ? " " : "", | ||||
path); | path); | ||||
free(flagsp); | free(flagsp); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 120 Lines • Show Last 20 Lines |