#!/usr/bin/perl -w # md5locate (c) 2004 Christoph Berg # This program is free software covered by the GNU GPL. # cb 2004-01-07: v0.1 use strict; use Getopt::Std; use Digest::MD5; use IO::File; my %opt; getopts('adf:m:uvo:', \%opt); foreach (split / *, */, $opt{o} || '') { $opt{$_} = 1; } my %SUMS; unless($opt{f} or -d "$ENV{HOME}/.md5locate") { mkdir "$ENV{HOME}/.md5locate", 0700 or die "mkdir $ENV{HOME}/.md5locate: $!"; } $opt{f} ||= "$ENV{HOME}/.md5locate/sums"; dbmopen(%SUMS, $opt{f}, 0600) or die "$opt{f}: $!"; #unless(@ARGV or $opt{a} or $opt{d} or $opt{o}) { # print < # #usage: $0 -a file ... #EOT # exit 1; #} my $md5 = Digest::MD5->new; sub canonize { my $file = shift; $file = "$ENV{PWD}/$file" unless $file =~ m!^/!; $file =~ s!/\.?/!/!g; return $file; } sub add_file { my $file = shift; return if -d $file; die "$file: no plain file" unless -f $file; my $fh = new IO::File $file; die "$file: $!" unless defined $fh; $md5->addfile($fh); $fh->close; my $hexdigest = $md5->hexdigest; my $mtime = (stat $file)[9]; $SUMS{$file} = $hexdigest; $SUMS{"mtime:$file"} = $mtime; $SUMS{$hexdigest} .= "$file\n" unless $SUMS{$hexdigest} and $SUMS{$hexdigest} =~ /^$file$/m; print "$hexdigest $file\n" if $opt{v}; } sub delete_file { my $file = shift; my $hexdigest = $SUMS{$file}; delete $SUMS{$file}; delete $SUMS{"mtime:$file"}; my @files = split /\n/, $SUMS{$hexdigest}; delete $SUMS{$hexdigest}; foreach my $f (@files) { $SUMS{$hexdigest} .= "$f\n" if $f ne $file; } print "deleted $hexdigest $file\n" if $opt{v}; } if($opt{a}) { die "no files to add" unless @ARGV; foreach my $file (@ARGV) { add_file(canonize($file)); } exit 0; } sub update_file { my $file = shift; unless(-f $file) { delete_file($file); next; } if($SUMS{$file}) { if((stat $file)[9] != $SUMS{"mtime:$file"}) { add_file($file); } else { print "$file: not changed\n" if $opt{v}; } } else { add_file($file); } } if($opt{u}) { if(@ARGV) { foreach my $file (map { canonize($_); } @ARGV) { update_file($file); } } else { foreach my $file (keys %SUMS) { next unless $file =~ m!^/!; update_file($file); } } exit 0; } if($opt{d}) { foreach (sort keys %SUMS) { print "$_: $SUMS{$_}\n"; } exit 0; } if($opt{single} or $opt{dupes} or $opt{m}) { foreach my $key (keys %SUMS) { next if $key =~ m!^/!; next if $opt{m} and $SUMS{$key} !~ /$opt{m}/; my @F = split /\n/, $SUMS{$key}; if($opt{dupes}) { next if @F == 1; } elsif($opt{single}) { next unless @F == 1; } if($opt{basename_mismatch}) { my $base; foreach my $f (@F) { $f =~ m!/([^/]+$)! or die "$f"; $base = "///" if $base and $1 ne $base; $base ||= $1; } next unless $base eq "///"; } print "$key ". shift(@F) ."\n"; foreach my $f (@F) { print((' ' x 32). " $f\n"); } } } foreach my $file (@ARGV) { my $fh = new IO::File $file; die "$file: $!" unless defined $fh; $md5->addfile($fh); $fh->close; my $hexdigest = $md5->hexdigest; if($SUMS{$hexdigest}) { my @F = split /\n/, $SUMS{$hexdigest}; print "$hexdigest ". shift(@F) ."\n"; foreach my $f (@F) { print((' ' x 32). " $f\n"); } } else { print "$hexdigest\n"; } }