The debian-private mailing list leak, part 1. Volunteers have complained about Blackmail. Lynchings. Character assassination. Defamation. Cyberbullying. Volunteers who gave many years of their lives are picked out at random for cruel social experiments. The former DPL's girlfriend Molly de Blanc is given volunteers to experiment on for her crazy talks. These volunteers never consented to be used like lab rats. We don't either. debian-private can no longer be a safe space for the cabal. Let these monsters have nowhere to hide. Volunteers are not disposable. We stand with the victims.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: cleaning up /var/tmp



osiris@cs.utexas.edu (Rob Browning)  wrote on 27.03.97 in <87g1xhu6pn.fsf@nevermore.csres.utexas.edu>:

[quoteto.xps]
> Klee Dienes <klee@mit.edu> writes:
>
> > That's not really the issue, though.  The problem is that the attacker
> > can change one of the directory components of the path to point to a
> > different location in the time between the 'find' and the 'xargs rm'.
>
> Didn't someone mention a program called "reaper" that would do the
> right think last time we discussed this.  Could we use that?

That was Michael Meskes, I think, nearly a year ago, to Ian Jackson. It  
should be available as Bug #3114:

Date: 24 May 1996 03:03:00 +0100
From: ian@chiark.chu.cam.ac.uk (Ian Jackson)
Sender: debian-devel-request@lists.debian.org
To: debian-bugs@pixar.com
Message-ID: <m0uMmEE-0002ZXC@chiark.chu.cam.ac.uk>
In-Reply-To: <199605221105.NAA13989@feivel.informatik.rwth-aachen.de>
Subject: Bug#3114: cron.daily/standard script
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
Received: by muenster.westfalen.de (/\oo/\ Smail3.1.29.1 #29.3) id <m0uMoEh-0005CFC@muenster.westfalen.de>; Fri, 24 May 96 06:12 MET DST
Received: (from smartlst@localhost) by vega.netg.se id EAA21199 (8.7.5/IDA-1.6 for debian-devel-dist@lists.debian.org); Fri, 24 May 1996 04:18:26 +0200
Old-Return-Path: <iwj10@cus.cam.ac.uk>
Resent-From: Ian Jackson <ian@chiark.chu.cam.ac.uk>
Resent-To: debian-devel@lists.debian.org
Resent-CC: Michael Meskes <meskes@Informatik.RWTH-Aachen.DE>
Resent-Date: Fri, 24 May 1996 02:18:10 GMT
Resent-Message-ID: <debian-bugs-handler.3114.B05240205000@pixar.com>
Precedence: list
Resent-Sender: debian-devel-request@lists.debian.org

Package: cron
Version: 3.0pl1-21

Michael Meskes writes ("cron.daily/standard script"):
> I don't know if you're subscribed to linux-security. There was a message
> recently pointing out that find commands used to remove old files in tmp are
> a possible security hole in that it it possible to delete whatever file on
> the disk you like. Sorry, I don't have that mail anymore.
>
> Anyway, the suggestion was to use the following script (called filereaper)
> instead. Maybe it's worth including (it's GPL'ed).

Thanks, it probably is, but I'm not maintaining cron any more.

I've filed this as a bug report.

Ian.

> #!/usr/bin/perl #
>
> # This document is in text, HTML, and perl script format.
>
> sub __GNUC__ { 1 }
> $|=1;  # prevent stderr/stdout conflicts;
>
> # /tmp cleaner script by zblaxell@myrus.com (Zygo Blaxell)
> # Version 2.11, 96/05/08
> # Added 'ADD_DATE' variable
>
> # Version 2.1, 96/05/07
> # Fixed long-standing stat_fs bug
>
> # Version 2.0, 96/05/06
> # Fairly major restructuring.  Now the children are forked on initial
> # startup, whether reaping is necessary or not.  The logging
> # information is reorganized.
> # The specification for threshold space levels is now one of:
> # number        - percentage of disk
> # number K      - kilobytes
> # number M      - megabytes
>
> # Version 1.31, 96/05/01
> # Only print STDERR "Disk usage increased:" if QUIET is not set.
>
> # Version 1.3, 96/04/26
> # If we have a dangling symlink, ignore its atime.  We keep changing it
> # with readlink().
>
> # Version 1.24, 96/04/25
> # Changes to constant strings in output and comments.  I was going to
> # make a change, but then decided that the status quo is a feature.
>
> # Version 1.23, 96/04/12
> # Use 'localtime()' more often to print out dates.  No change in behavior.
>
> # Version 1.22, 96/04/10
> # Corrects a couple of reporting bugs (negative disk space and incorrect
> # file size).  No change in behavior.
>
> ########################################################################
> ########################################################################
> ########################################################################
>
> # No warranties express or implied; see the GNU GPL for copying
> # restrictions.
>
> # You may get a copy of the software licence from:
>
> # ftp://ftp.gnu.ai.mit.edu/pub/gnu/COPYING
>
> # This is filereaper, a stripped-down version of gfreaper.
> # Actually, it's partly stripped-up too.
>
> # This script is designed to maintain a particular amount of free
> # disk space on a partition by deleting files in a directory structure.
> # For example, if you wanted to always have 3% free space in /tmp, use:
>
> # filereaper 3 /tmp
>
> # Files are deleted in order approximated by "oldest files first".
> # Actually, there are some anomalies where symbolic links and
> # directories are concerned.  Directories are considered one second
> # older than the oldest file in them.  Symbolic links are considered one
> # second newer than the file that they point to, or their own age if they
> # don't point to a file.  Directories can be maintained by typing 'ln -s
> # . ...' in them--this will delete everything in the directory, of course,
> # but never the directory itself (actually, filereaper never deletes the
> # symlink, thus preserving the directory which never gets emptied).
>
> # This program also maintains some state between reapings that allows it
> # to run more efficiently.  It can begin deleting files within two
> # seconds of low-disk-space conditions occurring.
>
> # This program understands some security issues that many other programs
> # (and some sysadmins) don't.  I don't know how many times I have seen:
>
> # 0 * * * * find /tmp -mtime +1 -exec rm -f {} \;
>
> # or, even worse:
>
> # 0 * * * * find /tmp -mtime +1 -print | xargs rm -f
>
> # in crontab files running as root.
>
> # The problem with these is that they can be used by any hostile user
> # with write access to /tmp to delete any file on the filesystem.  In the
> # second case, the exploit is trivially easy.  To delete /etc/passwd and
> # /etc/group:
>
> # $ touch '/tmp/this filename contains whitespace and newlines
> # /etc/passwd
> # /etc/group'
>
> # In the first case, you need to exploit a race condition between find
> # and rm (which is exacerbated by xargs, I might add).  Consider a file
> # named '/tmp/foo/bar/baz/a/b/c/d/e/etc/passwd'.  The race condition is
> # exploited thus:
>
> # [find feeds '/tmp/foo/bar/baz/a/b/c/d/e/etc/passwd' to 'rm']
> # $ mv /tmp/foo/bar/baz/a/b/c/d/e/etc /tmp/e
> # $ ln -s /etc /tmp/foo/bar/baz/a/b/c/d/e/etc
> # [rm now deletes '/tmp/foo/bar/baz/a/b/c/d/e/etc/passwd', but the
> # directory '/tmp/.../etc' is now a symlink to the real '/etc', so in fact
> # rm will actually delete '/etc/passwd'.]
>
> # With some creativity it is possible to make stat() calls take several
> # minutes, so this race condition is not difficult to exploit.  Consider
> # what happens when you call stat() on a path with 500 directory
> # components, each of which is actually a symlink 8 levels deep through
> # 500 other directory components.  Each.  We're talking about reading
> # almost the entire inode table into memory here just to do ONE stat()
> # call, and that can't be very fast.  The attacker just moves the first
> # directory component aside and puts a symlink to '/etc/' (or wherever) in
> # its place, so the actual unlink() call to the wrong file will be nice
> # and fast.
>
> # Incidentally, since these examples use '-mtime', the file modification
> # time is used to schedule file deletion.  Since the file modification
> # time is entirely under the control of the user, the user can delete any
> # file they want deleted, when they want it deleted, and they can
> # prevent their own files from being deleted.  Use '-ctime' instead.  If
> # your 'find' doesn't have this feature, 'rm -f /usr/bin/find' (in case
> # someone else tries to use it) and get one of the freely-available
> # replacements, or write your own.
>
> # ONCE AGAIN:  DO NOT USE:
> # 0 * * * * find /tmp -mtime +1 -exec rm -f {} \;
> # IN ROOT'S CRONTAB.  YOU WILL ALLOW ANY USER TO DELETE ANY FILE ON THE
> # SYSTEM.  THIS IS A SECURITY HOLE THAT IS EASY TO EXPLOIT.  There, that
> # should place this document in a few full-text search engines.  :-)
>
> # There are alternatives to this that work.  One is:
> # 0 * * * * find /tmp -mtime +1 -exec safe_rm -f {} \;
>
> # where 'safe_rm' implements an algorithm similar to 'ch_dir' in this
> # program.  The algorithm is:
>
> # If any system call below fails, exit.
> # chdir("/");
> # split name into a list of path components and a filename.  The
> # filename must be non-empty and does not contain "/".
> # for each member of list:
> #       old1=lstat(member) (save the device and inode numbers in old1)
> #       old2=lstat(".")    (save the device and inode numbers in old2)
> #       chdir(member)
> #       new1=lstat(".")
> #       new2=lstat("..")
> #       if (old1 != new1 || old2 != new2) exit
> # next member
> # unlink or rmdir (filename)
>
> # This avoids the race condition by not following symlinks in the
> # directory components of the path name.  Attempts to exploit the
> # find/unlink race condition will fail because the ch_dir routine will
> # fail if it encounters a symbolic link in the directory components of
> # the path name.
>
> # This is not perfect; however, it is necessary for the user to be able
> # to write to parent directories in a path name in order to subvert files
> # later in a path name.  For instance, consider:
>
> # /tmp/user1/
> # /tmp/foo/bar/root/user2
>
> # where 'user1' is owned by user1, 'root' is owned by root and not
> # writable, and 'user2' is a file owned by user2.  User1 cannot delete
> # '/tmp/foo/bar/root/user2', because 'root' is not writable by user1.
>
> # However, User1 can exploit the security vulnerabiltiies left in the
> # algorithm above to cause the daemon to delete /tmp/foo/bar/root/user2 IF
> # AND ONLY IF /tmp/foo and /tmp/foo/bar are writable by user2, as follows:
>
> # 1.  Create files and directories such that '/tmp/user1/bar/root/user2'
> #     exists.
> # 2.  Wait for 'find' to output the name '/tmp/user1/root/user2'.
> # 3.  'mv /tmp/user1/bar /tmp/user1/BAR'
> # 4.  'mv /tmp/foo/bar /tmp/user1'.  This moves
> #     '/tmp/foo/bar/root/user2' to '/tmp/user1/bar/root/user2'.
> # 5.  The safe_rm will now delete '/tmp/user1/bar/root/user2'.  Because
> #     there are no symlinks, no anomalies will be detected.
>
> # This program has additional checks to try to prevent attacks like this.
> # It can still be exploited if /tmp/user1/bar/root/user2 is a hard link
> # to /tmp/foo/bar/root/user2.
>
> # The moral of the story is:  if someone can write to your parent
> # directory, all bets are off.
>
> # Note that in real life, /tmp should be mode 1777, i.e. it has the
> # sticky bit set.  In this case /tmp/foo has to be owned by user1 for
> # the exploit to work.  RTFM chmod(1,2) if you want to know why.
>
> # This program also does chroot() to the base of filesystems to be reaper,
> # if run as root, for extra security.
>
> ########################################################################
> ########################################################################
> ########################################################################
>
> # Here's a sample script to run from /etc/rc* at boot time.  Remove '### '
> # from the beginning of every line.
>
> ### #!/bin/sh
> ### # Filereaper sample script by Zygo Blaxell (C) 1996
> ### # License: ftp://ftp.gnu.ai.mit.edu/pub/gnu/COPYING
> ###
> ### # Prevent core dumps.  Those would be bad, even when chroot.
> ### ulimit -c 0
> ### # Set PATH so we can find filereaper
> ### PATH=/bin:/usr/bin:/usr/local/bin:/sbin:/usr/sbin:/usr/local/sbin
> ###
> ### {
> ###     # Delete old files in spool and temp directories if <1% free space.
> ###     # The /*/windows/temp tries to get /[CD]/windows/temp on our systems.
> ###     # Yes, we've been forced to use Windows '95, hence /*/recycled.
> ###     # When we have multiple partitions, there is a /tmp on each.
> ###     # You probably want this at elevated priority to prevent disks
> ###     # from getting filled faster than they can be emptied, hence 'nice'.
> ###
> ###     ADD_DATE=true QUIET=true INTERVAL=60 nice -n -20 filereaper 1 /*/tmp /tmp /*/recycled /*/windows/temp
> ###
> ### } >>/var/log/filereaper 2>&1 &
>
> ########################################################################
> ########################################################################
> ########################################################################
>
> # Variables:
>
> # inode_info{inode} = age\0(parent_inode_number,name\0)+
> #                    Information about an inode.
>
> # oldest_age = oldest timestamp of files we let stand.
>
> # root_inode = inode of highest-level directory to tmpclean (used to
> #              stop searching backwards)
>
> # root_dev = device number of root of filesystem (used to stop searching
> #            other filesystems)
>
> # root_path = string to prepend to all pathnames ("" if chroot,
> #             "/foo/bar/baz" if not).
>
> # min_spec = amount of space to keep free on filesystem (%, K, or M)
>
> # ROOTAGE = minimum age of a file owned by root before we delete it.
> # Use this if you have daemons writing stuff to /tmp that would be
> # annoyed, destructive, or vulnerable if their /tmp files go away.
>
> # Return values of ch_dir():
> $CH_DIR_SUCCESS=0;
> $CH_DIR_CROSS=1;
> $CH_DIR_ERROR=2;
>
> ###########################################################################
> ###########################################################################
> ###########################################################################
>
> # Initialization
> ($min_spec,@given_filesystems)=@ARGV;
> die <  [ [ [...]]]
> Deletes old files whenever the amount of free space on the filesystem drops
> below .  The amount of space can be specified as one of
> the following:
>
>                 - percentage of total disk space available to users
>         %       - same as
>         K       - number of kilobytes
>         M       - number of megabytes
>
> Environment Variables (set them if you want them):
> TEST    - test only, don't actually delete any files
>           (default of course is to really delete files)
> DEBUG   - print STDERR debugging information
>           (default is to print STDERR only warnings and fatal and non-fatal errors,
>           as well as the name of every file we try to delete)
> NOATIME - ignore the atime of files, use ctime only.
>           (default is to use the later of atime and ctime for non-directories.
>           mtime can be set by the file owner to any value, so it's ignored)
> ROOTAGE - minimum age of files owned by root in seconds.
>           (by default, any file is eligible for deletion unless that
>           file is owned by root and it is less than $ROOTAGE seconds old)
> MINAGE  - minimum age applies to all users, not just root (boolean).
>           (by default, files not owned by root are always eligible for
>           deletion)
> INTERVAL- interval, in seconds, between FS checks.  Default 60.
>           Negative values mean run only once.
> QUIET   - don't print STDERR messages that explain why we aren't doing something
> NODIRS  - don't preserve directories using 'ln -s . ...'.  By default,
>           if a directory contains the symlink '...' with target '.',
>           it will never be deleted by this program.
> ADD_DATE- prefix each output line with date and time.
> USAGE
>
> srand();
>
> $min_spec =~ s/\d$/$&\%/o;
>
> $TEST=$ENV{'TEST'};
> $DEBUG=$ENV{'DEBUG'};
> $NOATIME=$ENV{'ATIME'};
> $ROOTAGE=$ENV{'ROOTAGE'} || 24*60*60;
> $MINAGE=$ENV{'MINAGE'};
> $INTERVAL=$ENV{'INTERVAL'} || 60;
> $QUIET=$ENV{'QUIET'};
> $NODIRS=$ENV{'NODIRS'};
> $ADD_DATE=$ENV{'ADD_DATE'};
>
> print STDERR "$0 - parameter dump at ".localtime(time()).":\n";
> print STDERR $> ? "Running as uid $>--can't chroot, will try to manage without it\n" : "Running as root, can and will do chroot\n";
> print STDERR $TEST ? "Running in test mode only\n" : "This is not a test.\n";
> print STDERR "Debugging messages enabled\n" if $DEBUG;
> print STDERR "Will maintain $min_spec free space\n";
> print STDERR $NOATIME ? "Will ignore atime of files\n" : "Will honour atime of files\n";
> print STDERR "Minimum age of files owned by ", $MINAGE ? "all users" : "root", " is $ROOTAGE seconds.\n";
> print STDERR "Will check filesystems every $INTERVAL seconds.\n";
> print STDERR $QUIET ? "Will suppress spurious filesystem status messages\n" : "Will print STDERR spurious filesystem status messages\n";
> print STDERR $NOATIME ? "Will ignore symlinks to '.' named '...'\n" : "Will not delete directories containing symlinks to '.' named '...'\n";
>
> print STDERR "Reading passwd and group files...\n";
> setpwent();
> while (
>     ($name,$passwd,$uid,$gid,$quota,$comment,$gcos,$dir,$shell) = getpwent
>     ) {
>         $username_of{$uid}=$name unless defined($username_of{$uid});
> }
> endpwent();
> setgrent();
> while (
>     ($name,$passwd,$gid,$members) = getgrent
>     ) {
>         $groupname_of{$gid}=$name unless defined($groupname_of{$gid});
> }
> endgrent();
> print STDERR "Done reading passwd and group files.\n";
>
> $last_ch_dir="\0";
>
> # Really go to work now...
>
> print STDERR "Entering main loop\n";
> $SIG{'CHLD'}="IGNORE";
>
> while (1) {
>         #print STDERR "Reaping filesystems at ".localtime()."\n" unless $QUIET;
>
>         undef @filesystems;
>         foreach (@given_filesystems) {
>                 local($chdir_result)=&ch_dir($_);
>                 if ($chdir_result) {
>                         # Arguably, the status of paths on the command line may change...
>                         print STDERR "$_ could not be found or crossed symlinks--will not try again.\n";
>                         next;
>                 }
>                 push(@filesystems,$_);
>                 # Grim File Reaper...
>                 if ($fs_to_pid{$_}) {
>                         print STDERR "Still running: pid $fs_to_pid{$_}, fs $_\n" unless $QUIET;
>                         next;
>                 }
>                 $parent_pid=$$;
>                 $child=fork();
>                 unless (defined($child)) {
>                         warn "Didn't fork?  $!";
>                         next;
>                 }
>                 if (!($child)) {
>                         # Child of fork()
>                         $other_parent_id=$$;
>                         unless (open(STDERR ,"|-")) {
>                                 # Child of open()
>                                 die "Didn't fork?  $!" if ($$ == $other_parent_id);
>                                 while ($in=) {
>                                         if ($ADD_DATE) {
>                                                 print STDERR localtime()." $_: $in";
>                                         } else {
>                                                 print STDERR "$_: $in";
>                                         }
>                                 }
>                                 print STDERR "$_: Done\n" unless $QUIET;
>                                 exit(0);
>                         } else {
>                                 # parent of open()
>                                 open(STDOUT,">&STDERR ") || die "Dup stderr: $!";
>                         }
>                         &reap($_);
>                         exit(0);
>                 } else {
>                         # Parent of fork()
>                         #print STDERR "Reaping $_ in pid $child\n";     # Hardly spurious...
>                         $pid_to_fs{$child}=$_;
>                         $fs_to_pid{$_}=$child;
>                 }
>         }
>         @given_filesystems=@filesystems;
>
>         #print STDERR "Idle.\n" unless $QUIET;
>         sleep($INTERVAL) if $INTERVAL>=1;
>
>         for $child (keys(%pid_to_fs)) {
>                 next if kill(0,$child); # test for existence
>                 #print STDERR "Child $child no longer exists, free to clean $pid_to_fs{$child} again.\n";
>                 delete $fs_to_pid{$pid_to_fs{$child}};
>                 delete $pid_to_fs{$child};
>         }
>         last if $INTERVAL<0;
> }
> exit(0);
>
> # reap(directory);
> # Does the GFR bit in a given directory.  Sets up inodes, sets up state
> # for directory trees.
>
> sub reap {
>         ($root_path)=@_;
>         local($begin_reaping)=time();                    # reference point to "now" - saves time() calls
>         &ch_dir($root_path) && die "chdir '$root_path' failed.  Aborting.\n";
>         if ($>) {
>                 print STDERR "Not root, so can't chroot.  Too bad.\n";
>         } else {
>                 print STDERR "Running as root.  Doing chroot($root_path)..." unless $QUIET;
>                 local(@stat1)=(lstat("."))[0,1];
>                 die "Couldn't lstat '.' in '$root_path': $!\n" unless defined($stat1[0]);
>                 chroot(".") || die "chroot failed ($!).  Aborting.\n";
>                 chdir("/") || die "chdir '/' failed in chroot ($!).  Aborting.\n";
>                 local(@stat2)=(lstat("."))[0,1];
>                 die "Couldn't lstat '.' in '$root_path' after chroot: $!\n" unless defined($stat2[0]);
>                 local(@stat3)=(lstat(".."))[0,1];
>                 die "Couldn't lstat '..' in '$root_path' after chroot: $!\n" unless defined($stat3[0]);
>                 local(@stat4)=(lstat("/"))[0,1];
>                 die "Couldn't lstat '/' in '$root_path' after chroot: $!\n" unless defined($stat4[0]);
>                 # After chroot, '/' should be the same as '.' and '..',
>                 # which should be the same as '.' before chroot.
>                 die "I don't believe I have chrooted successfully.  Aborting.\n" if
>                         (join(" ",@stat1) ne join(" ",@stat2) ||
>                         join(" ",@stat2) ne join(" ",@stat3) ||
>                         join(" ",@stat3) ne join(" ",@stat4));
>                 print STDERR "Successfully chroot($root_path)\n" unless $QUIET;
>                 $root_path='';
>                 $last_ch_dir="\0";
>         }
>         ($root_dev,$root_inode)=(lstat("."))[0,1];
>         die "Couldn't lstat '.' in '$root_path': $!\n" unless defined($root_dev);
>
>         print STDERR "Gathering filesystem information\n" unless $QUIET;
>         &get_dir($root_path);
>
>         local($want_before_reaping)=&stat_fs();
>
>         $oldest_age=time();
>
>         print STDERR "Waiting for go-ahead\n" unless $QUIET;
>         local($want)=&stat_fs();
>         local($lastwant)=$want;
>         until ($want>0) {
>                 sleep(2);
>                 $want=&stat_fs();
>                 print STDERR (-$want)." bytes left\n" if ($want>$lastwant) && !$QUIET;
>                 $lastwant=$want if $lastwant<$want;
>         }
>
>         print STDERR "Now reaping files\n" unless $QUIET;
>
> inode:
>         for $inode (sort { $inode_info{$a} <=> $inode_info{$b} } keys(%inode_info)) {
>                 local($inode_age,@other_stuff)=split(/\0/,$inode_info{$inode});
>                 $oldest_age=$inode_age;
>
>                 local($want)=&stat_fs();
>                 if ($want<=0) {
>                         # We hang around until we run out of inodes,
>                         # Then our parent starts us again.
>                         print STDERR "Want $want bytes...pausing...Oldest file is stamped ".localtime($oldest_age)."\n";
>                         local($lastwant)=$want;
>                         until ($want>0) {
>                                 sleep(2);
>                                 $want=&stat_fs();
>                                 print STDERR (-$want)." bytes left\n" if ($want>$lastwant) && !$QUIET;
>                                 $lastwant=$want if $lastwant<$want;
>                         }
>                 }
>
>                 $count=0;
> name:
>                 foreach $file_name (&get_names($inode)) {
>                         local($dir,$file)= $file_name =~ m!^(.*)/(.*)$!;
>
>                         ($was_link,$link_target,$was_dir)=&get_inode_info($dir,$file);
>                         next name unless defined($was_link);
>
>                         unless ($count++) {
>                                 print STDERR "Want $want bytes, reaping inode $inode, age ".localtime($inode_age).", owner " .
>                                         ($username_of{$ino_uid} || ":$ino_uid:") . ":" .
>                                         ($groupname_of{$ino_gid} || ":$ino_gid:") . ", size " . ($link_blocks*512) . "\n";
>                                 if ( ( ! $ino_uid || $MINAGE ) && $inode_age > time()) {
>                                         print STDERR "skipping: Inode $inode name '$dir/$file' is owned by " .
>                                                 ($username_of{$ino_uid} || ":$ino_uid:") . ":" .
>                                                 ($groupname_of{$ino_gid} || ":$ino_gid:") . " and not old enough (",time()-$inode_age,").\n";
>                                         next inode;
>                                 }
>                                 # Timestamps of directories are changed by gfreaper.
>                                 unless ($was_dir) {
>                                         local($new_age);
>                                         if ($ino_atime>$ino_ctime && !$NOATIME && $ino_atime < time()+1) {
>                                                 $new_age=$ino_atime;
>                                         } else {
>                                                 $new_age=$ino_ctime;
>                                         }
>                                         $new_age+=$ROOTAGE if ( ( ! $link_uid || $MINAGE ));
>                                         $new_age-- if $was_link;
>                                         print STDERR "old age $inode_age, new age $new_age\n" if $DEBUG;
>                                         if ($new_age > $inode_age) {
>                                                 print STDERR "skipping: Inode $inode name '$dir/$file' age is newer (".localtime($new_age)." > ".localtime($ino
>                                                 next inode;
>                                         }
>                                 }
>                         }
>
>                         if ($link_dev != $root_dev) {
>                                 warn "skipping: Inode $inode name '$dir/$file' is not on the correct filesystem ('$link_dev' should be '$root_dev').\n";
>                                 next name;
>                         }
>                         if ($link_ino != $inode) {
>                                 warn "skipping: Name '$dir/$file' is associated with new inode ('$link_ino' should be '$inode')\n";
>                                 next name;
>                         }
>
>                         print STDERR "Reaping: $file_name\n";
>                         if ($TEST) {
>                                 print STDERR "Pretending to unlink $dir/$file\n";
>                         } else {
>                                 if ( $was_dir ) {
>                                         unless (rmdir($file)) {
>                                                 warn "rmdir('$file') in '$dir' failed: $!\n";
>                                                 next name;
>                                         }
>                                 } else {  # if -d _ ...
>                                         unless (unlink($file)) {
>                                                 warn "unlink('$file') in '$dir' failed: $!\n";
>                                                 next name;
>                                         }
>                                 }
>                         } # if $TEST...
>                 } # foreach (&get_names...
>         } # for $inode (...
>
>         # Done reaping, print STDERR reapage stats
>         printf STDERR ("All files deleted.  Run time (seconds):  User %d, Sys %d, Total %d, Real %d\n",
>                 (times)[0],(times)[1],(times)[0]+(times)[1],time()-$begin_reaping) unless $QUIET;
> }
>
> # get_names(inode);
> # Finds the full pathnames of all links to the given inode, and appends
> # all of the names in basename.
>
> sub get_names {
>         local($inode)=@_;
>
>         # The inode has a list of parents, and a separate list of names
>         # under each parent.  We must find each parent, and prepend its
>         # name to all the names of the inode under that parent.
>
>         # There are no parents of the root inode.
>
>         # This is a little bogus in perl.  Infinite loops are possible
>         # here if you can rewrite the directory structure a bit,
>         # particularly if you can change the parent/child relationships
>         # on directories.
>
>         print STDERR "get_names called on $inode (root=$root_inode)\n" if $DEBUG;
>
>         return ($root_path) if ($inode==$root_inode);
>
>         if (!($inode_info{$inode})) {
>                 die "WARNING:  get_names called on unknown inode $inode";
>         }
>
>         local($age,@names)=split(/\0/,$inode_info{$inode});
>         local(%my_names);
>         foreach (@names) {
>                 local($parent_inode,$name)=m/^(\d+),([\000-\377]+)$/;
>                 warn "WARNING:  parent_inode is null?" unless $parent_inode;
>                 foreach (&get_names($parent_inode)) {
>                         $my_names{"$_/$name"}++;
>                 }
>         }
>
>         warn "WARNING:  No names found for '$inode'" unless keys(%my_names);
>         print STDERR "get_names($inode)=".join(" ",keys(%my_names))."\n" if $DEBUG;
>         return sort(keys(%my_names));
> }
>
> # get_dir(directory,parent_inode);
> # Incorporates information about all files in the directory into the
> # global database.  Returns the age of the newest file in the directory.
>
> sub get_dir {
>         local($directory)=@_;
>         local($newest_child_age)=0;
>         print STDERR "get_dir($directory)\n" if $DEBUG;
>         ($was_link,$link_target,$was_dir)=&get_inode_info($directory,".");
>         unless (defined($was_link)) {
>                 warn "Could not lstat '.' in '$directory': $!\n";
>                 return;
>         }
>         local($parent_inode)=$ino_ino;
>         unless (opendir(DIR,".")) {
>                 warn "Couldn't opendir '.' in '$directory': $!\n";
>                 return;
>         }
>         local(@dirfiles)=readdir(DIR);
>         closedir(DIR);
>         foreach (@dirfiles) {
>                 next if /^\.\.?$/;  # Ignore '.' and '..'
>                 ($was_link,$link_target,$was_dir)=&get_inode_info($directory,$_);
>                 next unless (defined($was_link));
>                 if ($link_dev != $root_dev) {
>                         warn "Not crossing device in '$directory/$_' (root is $root_dev, $_ is $link_dev)\n";
>                         # Not likely to be able to delete it, either...
>                         # I guess we could explicitly umount it... ;-)
>                         next;
>                 }
>                 local($inode)=$link_ino;
>                 local($inode_age,@parents)=split(/\0/,$inode_info{$inode});
>                 # file, fifo, block device, char device, socket...
>                 if ($ino_atime>$ino_ctime && !$NOATIME && $ino_atime < time()+1) {
>                         $inode_age=$ino_atime;
>                 } else {
>                         $inode_age=$ino_ctime;
>                 }
>                 if ( $was_link ) { # symlink to extant file
>                         $inode_age--;   # guarantee symlink goes away before file does
>                 }
>                 $inode_age+=$ROOTAGE if ( ( ! $link_uid || $MINAGE ));
>                 if ($was_link && !$NODIRS && $link_target eq '.' && $_ eq '...') {
>                         # Omit this from inode list
>                         $inode_age=time();
>                 } else {
>                         if ( $was_dir ) { # directory
>                                 $inode_age=$ino_ctime;
>                                 local($subdir_age)=&get_dir("$directory/$_");
>                                 $inode_age=$subdir_age if ($subdir_age>$inode_age);
>                         }
>                         $inode_info{$inode}=join("\0",$inode_age,@parents,"$parent_inode,$_");
>                 }
>                 $newest_child_age=$inode_age if $inode_age>$newest_child_age;
>                 #print STDERR "$inode_info{$inode}\n";
>         }
>         return $newest_child_age+1;
> }
>
> $foo=<
>
> --
> Michael Meskes                   |    _____ ________ __  ____
>                                  |   / ___// ____/ // / / __ \___  __________
> meskes@informatik.rwth-aachen.de |   \__ \/ /_  / // /_/ /_/ / _ \/ ___/ ___/
>                                  |  ___/ / __/ /__  __/\__, /  __/ /  (__  )
> Use Debian Linux!		 | /____/_/      /_/  /____/\___/_/  /____/
>


MfG Kai