#!/usr/bin/perl -w
#
#NAME
#  FindCaseless - search directories for file names that differ only in case
#
#SYNOPSIS
#  FindCaseless [dir]..
#
#DESCRIPTION
#  With the advent of OSX, which  does  caseless  filename  matching  in  the
#  kernel,  there  is  a  major problem in copying files between systems with
#  tools like rsync: If a directory contains two  files  whose  names  differ
#  only  in  capitalization,  they  may be mapped to a single file during the
#  copy operation.
#
#  This is a simple tool for searching for potential filename  capitalization
#  problems.   It does a recursive walk of the directory tree(s) named on the
#  command line, and lists all pathnames that differ only in capitalization.
#
#  This list may then be used either as a warning that something  won't  work
#  on  OSX  (and possibly MS Windows).  It may also be used to go through the
#  file systems and code, and fix this naming problem.
#
#OPTIONS
#
#EXAMPLES
#
#FILES
#
#BUGS
#
#SEE ALSO
#
#AUTHOR
#  John Chambers <jc@trillian.mit.edu>

$| = 1;
($P = $0) =~ s".*/"";
$V = $ENV{"V_$P"} || 1;
$exitstat = 0;		# Set to nonzero for serious problems

@ARGV = (".") unless @ARGV;

for $d (@ARGV) {
	print "DIR $d ...\n" if $V>1;
	&dir($d);
}

exit $exitstat;

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #

sub dir {my $F='dir';
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# Process one directory.  Note that we modify the directory's pathname to end #
# with '/'.                                                                   #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	local($dir) = @_;
	local($f,$p,@l);
	local($dev2,$dev1,$ino2,$ino1,$pth2,$pth1);
	$dir =~ s'/*$'/';
	unless (opendir(DIR, $dir)) {
		printf STDERR "$pth1: Can't open dir \"$dir\" ($!)\n" if $V>0;
		$exitstat = int($!);
		return;
	}
	@l = readdir(DIR);
	close DIR;
	for $f (@l) {
		next if $f eq '.' || $f eq '..';
		print "$F: f=\"$f\"\n" if $V>3;
		$pth1 = "$dir$f";
		$pth1 =~ s'^\./'';
		print "$F: P=\"$pth1\"\n" if $V>2;
		while ($pth1 =~ s'/([^/]+)/\.\./'/') {
			print "$F: P=\"$pth1\"\n" if $V>2;
		}
		$p = lc($pth1);		# Lower-case version of pathname
		print "$F: p=\"$p\"\n" if $V>2;
		if ($pth2 = $Path{$p}) {		# Has this pathname been seen?
			($dev2,$ino2) = stat($pth2);
			($dev1,$ino1) = stat($pth1);
			if (($dev2 == $dev1) && ($ino2 == $ino1)) {
				print "$F: Linked.n" if $V>2;
			} else {
				system "ls -lid $Path{$p}\n"
					if $Pcnt{$p}==1;	# Show first path for first conflict
				system "ls -lid $pth1\n";	# Show conflicting name.
			}
		} else {
			$Path{$p} = $pth1;	# Remember this path
		}
		++$Pcnt{$p};			# Count the incidents of each path
		if (-d $pth1) {
			print "$pth1/ ...\n" if $V>1;
			&dir($pth1);
		}
	}
}
