Bug#3114: cron.daily/standard script
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! | /____/_/ /_/ /____/\___/_/ /____/
>
Reply to: