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

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: