/* Copyright (c) 1997-1998 Karl M. Hegbloom <karlheg@debian.org>
* (note: above email address is defunct)
*
* Originally Based on `tmpwatch-1.2/1.4' RHS, Erik Troan <ewt@redhat.com>
*
* This program may be freely redistributed under the terms of the GNU
* Public License. You should be able to find a copy of the GPL in
* your "/usr/doc/copyright" directory on most GNU/Linux installations.
*
* Now maintained by ps - Paul Slootman <tmpreaper@packages.debian.org>
* 2001-12-03 1.4.15
* Patches from * mb - Marcus Brinkmann <Marcus.Brinkmann@ruhr-uni-bochum.de>
* for using get_current_dir_name() and portability on HURD. ps hacked this
* so that it should (still?) compile on non-glibc systems.
* 2001-12-02 1.5.0
* Added autoconf support. Also added getopt.c, getopt1.c, getopt.h so that
* tmpreaper can be compiled on e.g. Solaris. It builds and runs on Solaris
* and netBSD in addition to Linux now.
* Ignore ext2fs immutable files.
* 2002-02-05 1.6.0
* ctime option added, check ctime in addition to atime, for those cases where
* files get created with an old date in atime and mtime (e.g. via Samba from
* a PC).
* See changelog for later changes.
*/
#define _LARGEFILE_SOURCE 1 /* handle >2GB files where possible */
#define _FILE_OFFSET_BITS 64
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#define _GNU_SOURCE /* For get_current_dir_name() */
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#if HAVE_DIRENT_H
# include <dirent.h>
# define NAMLEN(dirent) strlen((dirent)->d_name)
#else
# define dirent direct
# define NAMLEN(dirent) (dirent)->d_namlen
# if HAVE_SYS_NDIR_H
# include <sys/ndir.h>
# endif
# if HAVE_SYS_DIR_H
# include <sys/dir.h>
# endif
# if HAVE_NDIR_H
# include <ndir.h>
# endif
#endif
#ifdef HAVE_ERRNO_H
# include <errno.h>
#endif
#include <getopt.h>
#include <glob.h>
#ifdef HAVE_LIMITS_H
# include <limits.h>
#endif
#include <stdarg.h>
#include <stdio.h>
#if STDC_HEADERS
# include <string.h>
#else
# ifndef HAVE_MEMCPY
# define memmove(d, s, n) bcopy ((s), (d), (n))
# endif
#endif
#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif
#include <time.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#ifdef HAVE_STDLIB_H
# include <stdlib.h>
#endif
#include <fcntl.h>
#ifdef __linux__
# include <sys/ioctl.h>
# include <ext2fs/ext2_fs.h>
# include <utime.h>
#endif
/* do we need to work around a glibc dependency? */
#ifndef __USE_GNU
# ifdef __linux__ /* sanity check */
# error "__USE_GNU not defined on a linux system, investigate! And report to tmpreaper@packages.debian.org"
# else
# define NO_get_current_dir_name
/* XXX TODO: a workaround for missing get_current_dir_name() */
# endif
#endif
#ifndef MAX_FORKS
# define MAX_FORKS 200
#endif
#ifdef GLOB_BRACE
# define TMPREAPER_GLOB_BRACE GLOB_BRACE
#else
# define TMPREAPER_GLOB_BRACE 0
#endif
/*
* tmpreaper.c -- remove files in a directory, but do it carefully
*/
/* `--force': also rm files that are mode `-w EUID $$' */
#define FLAGS_FORCE (1 << 0)
#define FLAGS_FORCE_P(fl) ((fl) & FLAGS_FORCE)
/* `--mtime': Use `mtime' rather than `atime' */
#define FLAGS_MTIME (1 << 1)
#define FLAGS_MTIME_P(fl) ((fl) & FLAGS_MTIME)
/* `--mtime-dir': rm empty directories based on mtime */
#define FLAGS_MTIME_DIR (1 << 2)
#define FLAGS_MTIME_DIR_P(fl) ((fl) & FLAGS_MTIME_DIR)
/* `--symlinks': also rm symlinks like files and directories */
#define FLAGS_SYMLINKS (1 << 3)
#define FLAGS_SYMLINKS_P(fl) ((fl) & FLAGS_SYMLINKS)
/* `--all': also rm symlinks, fifos, devices, and sockets... */
#define FLAGS_ALLFILES (1 << 4)
#define FLAGS_ALLFILES_P(fl) ((fl) & FLAGS_ALLFILES)
/* the `--protect' option was given */
#define FLAGS_PROTECT (1 << 5)
#define FLAGS_PROTECT_P(fl) ((fl) & FLAGS_PROTECT)
/* the `--test' option was given */
#define FLAGS_TEST (1 << 6)
#define FLAGS_TEST_P(fl) ((fl) & FLAGS_TEST)
/* `--ctime': Use `ctime' in addition to `atime' */
#define FLAGS_CTIME (1 << 7)
#define FLAGS_CTIME_P(fl) ((fl) & FLAGS_CTIME)
/* The `--showdeleted' option was given */
#define FLAGS_SHOWDEL (1 << 8)
#define FLAGS_SHOWDEL_P(fl) ((fl) & FLAGS_SHOWDEL)
#define TIME_SECONDS 1
#define TIME_MINUTES 60
#define TIME_HOURS 60 * 60
#define TIME_DAYS 24 * 60 * 60
glob_t protect_glob; /* list of files to not rm */
typedef struct protect_entry
{
ino_t inode;
char * name;
} protect_entry;
protect_entry * protect_table; /* Global */
#define LOG_REALDEBUG 1
#define LOG_DEBUG 2
#define LOG_VERBOSE 3
#define LOG_NORMAL 4
#define LOG_ERROR 5
#define LOG_FATAL 6
int logLevel = LOG_NORMAL;
int delayMax = 0;
int runtimeMax = 55;
void
message (const int level,
const char * format,
...) /*VARARGS*/
{
va_list args;
FILE * where = stderr;
if (level >= logLevel) {
va_start (args, format);
switch (level) {
case LOG_DEBUG:
where = stdout;
/*FALLTHRU*/
case LOG_REALDEBUG:
fprintf (where, "debug: ");
break;
case LOG_ERROR:
case LOG_FATAL:
fprintf (where, "error: ");
break;
case LOG_NORMAL:
case LOG_VERBOSE:
where = stdout;
break;
default:
break;
}
vfprintf (where, format, args);
if (level == LOG_FATAL)
exit (1);
}
}
void
sig_alarmhandler(int sig)
{
message(LOG_FATAL, "run time exceeded!\n"
"This may be indicative of an attack to use tmpreaper to remove critical files;\n"
"or the directories to clean up are excessive large and/or messed up.\n"
"Please investigate.\n");
exit(1); /* redundant */
}
int
safe_chdir (const char * dirname)
{
struct stat sb1, sb2;
if (lstat (dirname, &sb1)) {
message (LOG_ERROR,
"lstat() of directory `%s' failed: %s\n",
dirname, strerror (errno));
return 1;
}
if (! S_ISDIR (sb1.st_mode)) {
if (S_ISLNK (sb1.st_mode)) {
message (LOG_ERROR,
"safe_chdir(): Will not chdir across symlink `%s' (inode %lu).\n",
dirname, (u_long) sb1.st_ino);
return 1;
}
message (LOG_ERROR,
"Not a directory: `%s' (inode %lu).\n",
dirname, (u_long) sb1.st_ino);
return 1;
}
message (LOG_DEBUG,
"safe_chdir() : Before chdir(), `dirname' is inode `%lu'.\n",
(u_long) sb1.st_ino);
message (LOG_DEBUG,
"safe_chdir() : Before chdir(), `dirname' on device `%lu'.\n",
(u_long) sb1.st_dev);
if (chdir (dirname)) {
message (LOG_ERROR,
"chdir() to directory `%s' (inode %lu) failed: %s\n",
dirname, (u_long) sb1.st_ino, strerror (errno));
return 1;
}
if (lstat (".", &sb2)) {
message (LOG_ERROR,
"lstat() of directory `%s' after chdir() failed: %s\n",
dirname, strerror (errno));
return 1;
}
message (LOG_DEBUG,
"safe_chdir() : After chdir(), `dirname' is inode `%lu'.\n",
(u_long) sb2.st_ino);
message (LOG_DEBUG,
"safe_chdir() : After chdir(), `dirname' on device `%lu'.\n",
(u_long) sb2.st_dev);
if (sb1.st_ino != sb2.st_ino) {
message (LOG_ERROR,
"Inode mismatch!!! : `%s' (inode %lu) != `.' (inode %lu)\n",
dirname,
(u_long) sb1.st_ino, (u_long) sb2.st_ino);
message (LOG_FATAL,
"This indicates a possible subversion attempt.\n");
/*NOTREACHED*/
}
else if (sb1.st_dev != sb2.st_dev) {
message (LOG_ERROR,
"Device mismatch!!!\n");
message (LOG_ERROR,
"`%s' (inode %lu), device `%lu' != `.' (inode %lu), device `%lu'\n",
dirname,
(u_long) sb1.st_ino, (u_long) sb1.st_dev,
(u_long) sb2.st_ino, (u_long) sb2.st_dev);
message (LOG_FATAL,
"This indicates a possible subversion attempt.\n");
/*NOTREACHED*/
}
return 0;
}
int
dir_empty_p (const char * dirname)
{
DIR * dir;
struct dirent * ent;
int count = 0;
if (! (dir = opendir(dirname))) {
message(LOG_ERROR,
"Cannot opendir(%s): %s\n", dirname, strerror(errno));
return 0;
}
while ((ent = readdir(dir))) {
if ((ent->d_name[0] == '.') && (
(ent->d_name[1] == '\0') ||
((ent->d_name[1] == '.') && (ent->d_name[2] == '\0'))
)
)
continue;
count++;
}
closedir(dir);
return count != 0 ? 0 : 1; /* #t when empty. */
}
int
cleanupDirectory (const char * dirname ,
const unsigned int killTime,
const int flags)
{
DIR * dir;
struct dirent * ent;
struct stat sb, here;
int status, pid, skip = 0;
int i;
static int fork_count = 0;
if (FLAGS_TEST_P (flags)) {
message (LOG_VERBOSE,
"(PID %u) Pretending to clean up directory `%s'.\n",
(u_int) getpid(), dirname);
}
else {
message(LOG_DEBUG,
"Cleaning up directory `%s'.\n",
dirname);
}
if (fork_count > MAX_FORKS) {
#ifdef NO_get_current_dir_name
char path[PATH_MAX], *p;
#else
char *path, *p;
#endif
int l;
/* the admin will want to know where the problem lies */
#ifdef NO_get_current_dir_name
if (getcwd(path, PATH_MAX) < 0)
/* too long even for PATH_MAX, show something anyway */
#else
if ((path = get_current_dir_name()) < 0)
#endif
{
p = "..../";
}
else {
l = strlen(path);
if (l > 65) {
/* remove middle part of the path */
p = path + l - 30;
strcpy(&path[30], " ... ");
memmove(&path[35], p, 30);
path[65] = 0;
}
p = path;
}
message(LOG_FATAL,
"Too deep directory nesting detected while trying to clean up\n`%s/%s',\npossible attack?\n",
p, dirname);
exit(2);
}
/*
* Do everything in a child process so we don't have to chdir(".."),
* which would lead to a race condition. fork() on Linux is very efficient
* so this shouldn't be a big deal (probably just a exception on one page
* of stack, not bad). I should probably just keep a directory stack
* and fchdir() back up it, but it's not worth changing now. - ewt
*
* After reading "http://www.geek-girl.com/bugtraq/1996_2/0054.html" (look
* for "cd .."), I think it is best to use this method rather than the
* contrasting algorithm mentioned in Eric's comment. IMHO he has made the
* right choice by having chosen the `recursive forking' algorithm. It is
* much easier to implement and understand as well as being more secure
* through avoiding `cd ..'. - karlheg
*
* The geek girl link is dead. fork() is not efficient on all systems,
* so it would still be better to use a directory stack + fchdir(). - mb
*
* This is another link to what geek-girl had:
* http://www.geocrawler.com/mail/msg.php3?msg_id=184906&list=91
* - ps
*/
if (! (pid = fork())) {
if (safe_chdir (dirname))
exit(1);
fork_count++;
if (! (dir = opendir ("."))) {
message (LOG_FATAL,
"Opening directory, `%s' as `.' : %s\n",
dirname, strerror (errno));
}
if (lstat (".", &here)) {
message (LOG_FATAL,
"Statting current directory, `%s' as `.' : %s\n",
dirname, strerror (errno));
}
do {
errno = 0;
ent = readdir (dir);
if (errno) {
message (LOG_ERROR,
"Reading directory entry. : %s\n",
strerror (errno));
}
if (!ent)
break;
/*
* if name starts with dot, and
* is 1 byte long, or
* has a second dot and is 2 bytes long,
* skip it.
*/
if ((ent->d_name[0] == '.') && (
(ent->d_name[1] == '\0') ||
((ent->d_name[1] == '.') && (ent->d_name[2] == '\0'))
)
)
continue;
message (LOG_DEBUG,
"(PID %u) Found directory entry `%s'.\n",
(u_int) getpid(), ent->d_name);
if (lstat (ent->d_name, &sb)) {
message (LOG_ERROR,
"Failed to lstat() `%s/%s': %s\n",
dirname, ent->d_name, strerror (errno));
continue;
}
if (((!getuid() && !sb.st_uid) || (sb.st_uid == geteuid())) &&
!FLAGS_FORCE_P (flags) && !(sb.st_mode & S_IWUSR)) {
message (LOG_VERBOSE,
"Non-writeable file, owned by UID (%u) skipped: `%s'\n",
(u_int) sb.st_uid, ent->d_name);
continue;
}
if (sb.st_dev != here.st_dev) {
message (LOG_VERBOSE,
"File on different device skipped: `%s/%s'\n",
dirname, ent->d_name);
continue;
}
if (S_ISDIR (sb.st_mode)) {
char *dst;
if ((dst = malloc(strlen(ent->d_name) + 3)) == NULL)
message (LOG_FATAL, "malloc failed.\n");
strcpy(dst, ent->d_name);
strcat(dst, "/X");
rename(ent->d_name, dst);
if (errno == EXDEV) {
free(dst);
message (LOG_VERBOSE,
"File on different device skipped: `%s/%s'\n",
dirname, ent->d_name);
continue;
}
free(dst);
cleanupDirectory (ent->d_name, killTime, flags);
message (LOG_VERBOSE,
"(PID %u) Back from recursing down `%s'.\n",
(u_int) getpid(), ent->d_name);
}
if (FLAGS_PROTECT_P (flags)) {
skip = i = 0;
do {
if (sb.st_ino == protect_table[i].inode) {
message (LOG_VERBOSE,
"Entry matching `--protect' pattern skipped. `%s'\n",
protect_table[i].name);
skip = 1;
break;
}
} while (protect_table[i++].name);
if (skip)
continue;
}
/* Decide whether to remove the file or not */
/* check for mtime on directory instead of atime if requested */
if ( FLAGS_MTIME_P(flags) ||
(FLAGS_MTIME_DIR_P(flags) && S_ISDIR(sb.st_mode))) {
if (sb.st_mtime >= killTime)
continue;
}
else {
if (sb.st_atime >= killTime)
continue;
/* If ctime option is given, the ctime must also be so */
/* long ago. This is e.g. for samba as DOS preserves */
/* mtime when copying, so such files get removed too soon */
/* Don't do it for directories though, as ctime may get */
/* reset below through utime() */
if (!S_ISDIR(sb.st_mode) &&
FLAGS_CTIME_P(flags) && sb.st_ctime >= killTime)
continue;
}
if (S_ISDIR (sb.st_mode)) {
if (dir_empty_p (ent->d_name)) {
if (FLAGS_TEST_P (flags)) {
message (LOG_VERBOSE,
"Pretending to remove empty directory `%s'.\n",
ent->d_name);
if (FLAGS_SHOWDEL_P(flags))
message(LOG_NORMAL, "rmdir %s/%s\n", dirname, ent->d_name);
}
else {
message (LOG_VERBOSE,
"Removing directory `%s'.\n",
ent->d_name);
if (rmdir (ent->d_name)) {
message (LOG_ERROR,
"Failed to rmdir `%s': %s\n",
ent->d_name, strerror (errno));
}
else {
if (FLAGS_SHOWDEL_P(flags))
message(LOG_NORMAL, "rmdir %s/%s\n", dirname, ent->d_name);
}
}
}
}
else if (FLAGS_ALLFILES_P (flags) || S_ISREG (sb.st_mode)
|| (FLAGS_SYMLINKS_P (flags) && S_ISLNK (sb.st_mode))) {
if (FLAGS_TEST_P (flags)) {
message (LOG_VERBOSE,
"Pretending to remove file `%s/%s'.\n",
dirname, ent->d_name);
if (FLAGS_SHOWDEL_P(flags))
message(LOG_NORMAL, "rm %s/%s\n", dirname, ent->d_name);
}
else {
message (LOG_VERBOSE,
"Removing file `%s/%s'.\n",
dirname, ent->d_name);
if (unlink (ent->d_name))
#if defined(__linux__)
{
int give_error = 1;
int fd;
if ((fd = open(ent->d_name, O_RDONLY)) >= 0) {
unsigned int flags;
if (ioctl(fd, EXT2_IOC_GETFLAGS, &flags) >= 0) {
if (flags & EXT2_IMMUTABLE_FL) {
give_error = 0;
}
}
close(fd);
/* Restore access time! */
/* However, only bother if we give a warning. */
/* With no warning, it doesn't matter. */
/* And it delays the next attempt :-) */
if (give_error) {
struct utimbuf ts;
ts.actime = sb.st_atime;
ts.modtime = sb.st_mtime;
utime(ent->d_name, &ts);
}
}
if (give_error)
#endif
message (LOG_ERROR,
"Failed to unlink `%s': %s\n",
ent->d_name, strerror (errno));
#if defined(__linux__)
else
message (LOG_VERBOSE,
"file `%s' marked immutable -- skipping.\n",
ent->d_name);
}
#endif
else {
if (FLAGS_SHOWDEL_P(flags))
message(LOG_NORMAL, "rm %s/%s\n", dirname, ent->d_name);
}
}
}
else {
if (FLAGS_SYMLINKS_P (flags)) {
message (LOG_VERBOSE,
"Not a regular file, symlink, or directory `%s' -- skipping.\n",
ent->d_name);
}
else {
message (LOG_VERBOSE,
"Not a regular file or directory `%s' -- skipping.\n",
ent->d_name);
}
}
} while (ent);
closedir (dir);
exit (0);
}
if (pid < 0) {
message(LOG_ERROR, "fork failed: %s\n", strerror(errno));
return 1;
}
if (waitpid(pid, &status, 0) < 0) {
message(LOG_ERROR, "waitpid failed: %s\n", strerror(errno));
return 1;
}
if (WIFEXITED (status))
return WEXITSTATUS (status);
return 0;
}
void
printCopyright (void)
{
fprintf (stderr,
#ifdef DEBIAN
"tmpreaper -- Version: " VERSION "-DEB\n"
#else
"tmpreaper -- Version: " VERSION "\n"
#endif
"(c) 1997 Karl M. Hegbloom <tmpreaper@packages.debian.org>\n"
"(c) 2006 Paul Slootman <tmpreaper@packages.debian.org>\n"
"This may be freely redistributed under the terms of the GNU Public License.\n");
}
/* 1 2 3 4 5 6 7 8
02345678901234567890123456789012345678901234567890123456789012345678901234567890
*/
void
usage(void)
{
printCopyright ();
fprintf (stderr, "\n"
"tmpreaper [-htvfmMsadVT] [--help] [--test] [--verbose] [--force]\n"
" [--delay=99] [--runtime=99] [--showdeleted]\n"
" [--ctime] [--mtime] [--mtime-dir] [--symlinks] [--all]\n"
" [[--protect '<shell_pattern>']...] <time> <dirs>...\n"
"<time> is time since a file in a <dir> was last accessed.\n"
"It defaults to hours, or you may suffix with `s', `m', `h', or `d'.\n\n");
exit (1);
}
int
main (int argc,
char ** argv)
{
unsigned int grace = 0;
unsigned int killTime = 0;
int long_index = 0;
int ret = 0, arg = 0, i = 0, j = 0, k = 0, adir = 0;
int flags = 0;
char * pwd = NULL;
char **pp;
struct stat sb;
/*
* Utilize GNU C dynamic array allocation and statement expressions to
* allocate an array to hold the --protect argument strings.
*/
char *protect_argv[ ({ int i;
int count = 0;
for (i = 1; i < argc; i++)
if (! strncmp (argv[i], "--protect", 9))
count++;
++count; }) ];
char **p = protect_argv;
struct option const options[] = {
{ "help", no_argument, NULL, 'h' },
{ "test", no_argument, NULL, 't' },
{ "verbose", optional_argument, NULL, 'v' },
{ "force", no_argument, NULL, 'f' },
{ "ctime", no_argument, NULL, 'c' },
{ "mtime", no_argument, NULL, 'm' },
{ "mtime-dirs", no_argument, NULL, 'M' },
{ "symlinks", no_argument, NULL, 's' },
{ "all", no_argument, NULL, 'a' },
{ "protect", required_argument, NULL, 'p' },
{ "delay", optional_argument, NULL, 'd' },
{ "showdeleted",no_argument, NULL, 'V' },
{ "runtime", required_argument, NULL, 'T' },
{ 0, 0, 0, 0 }
};
if (argc == 1)
usage ();
while (1) {
long_index = 0;
arg = getopt_long(argc, argv, "htv::fcmMsap:d::VT:", options, &long_index);
if (arg == -1)
break;
switch (arg) {
case '?':
case 'h':
usage ();
break;
case 't':
flags |= FLAGS_TEST;
optarg = 0;
/*FALLTHRU*/
case 'v':
if (!optarg)
logLevel ? logLevel -= 1 : 0;
else if (atoi(optarg) <= 0)
logLevel = LOG_NORMAL;
else if (atoi(optarg) > 3)
logLevel = LOG_REALDEBUG;
else
logLevel = LOG_NORMAL - atoi(optarg);
break;
case 'f':
flags |= FLAGS_FORCE;
break;
case 'c':
if ((flags & FLAGS_MTIME) == FLAGS_MTIME) {
message(LOG_FATAL, "--ctime conflicts with --mtime option\n");
/*NOTREACHED*/
}
flags |= FLAGS_CTIME;
break;
case 'm':
if ((flags & FLAGS_CTIME) == FLAGS_CTIME) {
message(LOG_FATAL, "--mtime conflicts with --ctime option\n");
/*NOTREACHED*/
}
flags |= FLAGS_MTIME;
break;
case 'M':
flags |= FLAGS_MTIME_DIR;
break;
case 's':
flags |= FLAGS_SYMLINKS;
break;
case 'a':
flags |= FLAGS_ALLFILES;
break;
case 'p':
protect_argv[k++] = optarg;
protect_argv[k] = NULL;
flags |= FLAGS_PROTECT;
break;
case 'd':
if (optarg)
delayMax = atoi(optarg);
else
delayMax = 256;
message(LOG_DEBUG, "delay is %d seconds max\n", delayMax);
break;
case 'V':
flags |= FLAGS_SHOWDEL;
break;
case 'T':
runtimeMax = atoi(optarg);
message(LOG_DEBUG, "runtime is %d seconds max\n", runtimeMax);
if (runtimeMax < 5) {
message(LOG_FATAL, "runtime must be at least 5 seconds\n");
/*NOTREACHED*/
}
}
}
if (optind == argc) {
message (LOG_FATAL,
"Time must be given.\n");
/*NOTREACHED*/
}
#define multiplier j
i = strlen (argv[optind]) - 1;
switch (argv[optind][i]) {
case 's':
case 'S':
multiplier = TIME_SECONDS;
argv[optind][i] = '\0';
break;
case 'm':
case 'M':
multiplier = TIME_MINUTES;
argv[optind][i] = '\0';
break;
case 'd':
case 'D':
multiplier = TIME_DAYS;
argv[optind][i] = '\0';
break;
case 'h':
case 'H':
argv[optind][i] = '\0';
default:
multiplier = TIME_HOURS;
}
if (sscanf (argv[optind], "%d", &grace) != 1) {
message (LOG_FATAL,
"Bad time argument `%s'.\n",
argv[optind]);
/*NOTREACHED*/
}
optind++;
if (optind == argc) {
message (LOG_FATAL,
"Directory name(s) expected.\n");
/*NOTREACHED*/
}
/*
* Use line-buffered I/O for stdout, so that even when the output is being
* piped to a file, it gets flushed after a newline. If we don't do this,
* when it forks, the child inherits an unflushed buffer, so when it
* flushes and the parent does too, we get doubled output. Note that
* `stderr' is unbuffered already.
* See: `APUE', W. Richard Stevens, pp. 189-190
* Bug pointed out by Joey Hess <joey@kitenet.net>
*/
#ifdef SETVBUF_REVERSED
if (setvbuf (stdout, _IOLBF, (void *)NULL, BUFSIZ) != 0)
#else
if (setvbuf (stdout, (void *)NULL, _IOLBF, BUFSIZ) != 0)
#endif
{
message (LOG_FATAL,
"setvbuf() failed for `stdout'.\n");
/*NOTREACHED*/
}
grace = grace * multiplier;
message (LOG_DEBUG,
"Grace period is `%u' seconds.\n",
(u_int)grace);
message (LOG_DEBUG,
"This is PID (%u) being run by UID (%u), EUID (%u).\n",
getpid(), getuid(), geteuid());
killTime = time (NULL) - grace;
/* use getcwd() to test whether current directory is valid */
if (!(pwd = malloc(PATH_MAX))) {
message (LOG_FATAL,
"malloc for getcwd failed: %s\n",
strerror(errno));
/*NOTREACHED*/
}
if (getcwd (pwd, PATH_MAX) == NULL) {
message (LOG_FATAL,
"getcwd() failed: %s\n",
strerror(errno));
/*NOTREACHED*/
}
/* don't need pwd, but leave it allocated for possible debug things below */
/* Set an alarm, so that we aren't vulnerable to the type of attack that */
/* needs to have multiple concurrent tmpreaper instances running. */
/* A little less than one minute should be enough... */
/* First, delay a random (small) time to foil wannabee bad guys even more */
/* if --delay was given as option. */
if (delayMax) {
int fd;
unsigned char x;
unsigned int timetosleep;
if ((fd = open("/dev/urandom", O_RDONLY)) >= 0) {
read(fd, &x, 1);
close(fd);
timetosleep = ((x+0.01)/256.0)*delayMax;
}
else {
/* fallback if open /dev/urandom failed */
srand(getpid());
timetosleep = 1+(int)((delayMax+0.01)*rand()/(RAND_MAX+1.0));
}
message (LOG_DEBUG, "timetosleep=%u\n", timetosleep);
sleep(timetosleep);
}
signal(SIGALRM, sig_alarmhandler);
alarm(runtimeMax);
/* For each directory in the cleanup list, cd there, glob, then delete */
for (adir = optind; adir < argc; adir++) {
if ((ret = chdir (argv[adir])) != 0) {
message (LOG_ERROR,
"Cannot chdir() to `%s' for --protect glob: %s\n",
argv[adir], strerror(errno));
continue;
}
if (LOG_DEBUG >= logLevel) {
/* this test is to prevent non-necessary calls to getcwd() */
message (LOG_DEBUG,
"getcwd() is: %s\n",
getcwd (pwd, PATH_MAX));
}
/* ... for each protect_argv, glob, using GLOB_APPEND */
if (FLAGS_PROTECT_P (flags)) {
pp = p;
while (*pp != NULL) {
if (p == pp) {
ret = glob (*pp, (GLOB_NOSORT | TMPREAPER_GLOB_BRACE), NULL, &protect_glob);
} else {
ret = glob (*pp, (GLOB_APPEND | GLOB_NOSORT | TMPREAPER_GLOB_BRACE), NULL, &protect_glob);
}
if (ret == GLOB_NOSPACE) {
message (LOG_FATAL,
"glob() returned GLOB_NOSPACE : Insufficient memory to hold pattern expansion.\n");
/*NOTREACHED*/
}
if (ret == GLOB_NOMATCH) {
if (LOG_VERBOSE >= logLevel) {
/* test to prevent non-necessary calls to getcwd() */
message (LOG_VERBOSE,
"Nothing in `%s' matches `%s'.\n",
getcwd (pwd, PATH_MAX), *pp);
}
}
pp++;
}
}
protect_table = malloc ((1 + protect_glob.gl_pathc) * sizeof (protect_entry));
if (! protect_table) {
message (LOG_FATAL, "malloc() failed: %s\n", strerror (errno));
/*NOTREACHED*/
}
j = 0;
for (i = 0; i < protect_glob.gl_pathc; i++) {
if (lstat (protect_glob.gl_pathv[i], &sb)) {
message (LOG_ERROR,
"lstat() of `%s' failed (`--protect'): %s\n",
protect_glob.gl_pathv[i], strerror (errno));
continue;
}
protect_table[j].inode = sb.st_ino;
protect_table[j++].name = protect_glob.gl_pathv[j];
}
protect_table[j].name = NULL;
/* Now go to work */
if (lstat (argv[adir], &sb)) {
message (LOG_ERROR,
"lstat() of directory `%s' failed (argv): %s\n",
argv[adir], strerror (errno));
}
if (S_ISLNK (sb.st_mode)) {
message (LOG_DEBUG,
"Initial directory `%s' is a symlink -- skipping\n",
argv[adir]);
}
else {
cleanupDirectory (argv[adir], killTime, flags);
}
free(protect_table);
globfree(&protect_glob);
}
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1