#!/usr/local/bin/perl # # $Id: pcpmac,v 0.70 2005/08/09 15:47:00 dankogai Exp $ # use strict; use Getopt::Std; use File::stat; use File::Basename; my %Opt; getopts("fiprv", \%Opt); $Opt{i} and delete $Opt{f}; # for safety my $IAM = basename($0); my %Hardlink; # key = inode, val = name of first file copied my $Dst = pop @ARGV; @ARGV or help(); my $Dstst = stat($Dst); # not lstat; symlink to directory allowed unless (-d _){ @ARGV == 1 or help(); do_copy($ARGV[0], $Dst); }else{ $Dst =~ s,/+$,,o; for my $src (@ARGV){ $src =~ s,/+$,,o; do_copy($src, $Dst . '/' . basename($src)); } } exit; use MacOSX::File::Copy; use MacOSX::File::Info; sub do_copy{ my ($src, $dst) = @_; my $srcst = lstat($src) or warn "$src: Can't lstat!" and return; $srcst->ino == $Dstst->ino and $srcst->dev == $Dstst->dev and warn "$src and $dst are identical. skipped" and return; $Opt{v} and print STDERR "$src\n"; if (my $dstst = lstat($dst) and -l _ or -f _){ $Opt{i} and prompt($dst) or return; $Opt{f} and unlink $dst; } $srcst = lstat($src); if (-l _){ # just copy the linkage symlink(readlink($src), $dst) or warn "$src -> $dst : $!"; }elsif(-f _){ if ($srcst->nlink > 1){ # MacOSX has hard links! if (my $link = $Hardlink{$srcst->ino}){ link($link, $dst) or warn "$link -> $dst:$!"; }else{ $Hardlink{$srcst->ino} = $dst; } } copy($src, $dst) # simple file-to-file copy or warn "$dst:$! ($MacOSX::File::OSErr)"; # set attributes if ($Opt{p}){ chown $srcst->uid, $srcst->gid, $dst; chmod $srcst->mode & 07777, $dst or warn "$dst : $!"; }else{ my $now = time(); utime $now, $now, $dst or warn "$dst: $!"; } }elsif(-d _){ # tough part; unless ($Opt{r}){ warn "$IAM: $src is a directory. skipped"; return; } mkdir $dst,0777 or die "$dst:$!"; opendir my $d, $src or die "$src:$!"; # see ._* is avoided my @f = grep !/^\.(?:\.?$|_)/o, readdir $d; closedir $d; my $finfo = getfinfo($src) or die "$src:Error $MacOSX::File::OSErr"; for my $f (@f){ my ($srcf, $dstf) = ("$src/$f", "$dst/$f"); # no cross-device traversal within source directory # so you can casually go like 'pcpmac -R / /Volumes/backup' lstat($srcf)->dev != $srcst->dev and next; -e $dstf and die "$dstf: already exists!"; # else lets' do it recursively do_copy($srcf, $dstf); } # copy finfo info after all traversal is done $finfo->set($dst) or warn "$dst : $MacOSX::File::OSErr"; # set attributes if ($Opt{p}){ chown $srcst->uid, $srcst->gid, $dst; chmod $srcst->mode & 07777, $dst or warn "$dst : $!"; }else{ # do nothing } }else{ # do nothing for devices, sockets and fifos; # devices are handled by devfs on Macs } } sub prompt{ my $path = shift; $| = 1; print "Overwrite $path? [y/N]:"; my $answer = ; chomp $answer; return lc($answer) eq 'y'; } sub help{ # warn caller; print STDERR <<"EOT"; usage: $IAM [-r] [-f|-i] [-p][-v] src target $IAM [-r] [-f|-i] [-p][-v] src1 ... srcN directory EOT exit; } 1; __END__ =head1 NAME pcpmac -- CpMac(1) or cp(1), implemented as perl script =head1 SYNOPSIS pcpmac [-r] [-f|-i] [-p][-v] source_file target_file pcpmac [-r] [-f|-i] [-p][-v] source_file ... target_directory =head1 TIGER As of Mac OS X v10.4 (Tiger), the ordinary L does support resource fork. =head1 DESCRIPTION pcpmac, as its name implies, copies files with finder info and resource fork. In the first synopsis form, pcpmac copies the contents of the source_file to the target_file. In the second synopsis form, the contents of each named source_file is copied to the destination target_directory. The names of the files themselves are not changed. If cpmac detects an attempt to copy a file to itself, the copy will fail. The following options are available: =item -r If source_file designates a directory, cp copies the directory and the entire subtree connected at that point. =item -f Foreach existing destination pathname, attempt to overwrite it. If permissions do not allow copy to succeed, remove it and create a new file, without prompting for confirmation. =item -i Causes pcpmac to write a prompt to the standard error output before copying a file that would overwrite an existing file. If the response from the standard input begins with the character "y" or "Y", the file copy is attempted. For safety, this option cancels -f option. This is the opposite of BSD cp implementation. =item -p Causes cp to preserve in the copy as many of the modification time, access time, file flags, file mode, user ID, and group ID as allowed by permissions. Note that no symlinks are followed. Instead the target will also be symlink which points to the same path. With this respect, This option corresponds to C of BSD cp. pcpmac also prevents from crossing devices when subtrees belong to different volumes. That way you can safely C without hanging yourself. This is anologous to -x(dev) option of find command but somehow nothing like that existed for cp so I made it. pcpmac also goes one step forward when both of two arguments are same I. Compare C and C where dir1 are identical. It is a kind of fun what cp does! =item -v Verbose mode. Prints each copied item to standard output. =head1 AUTHOR Dan Kogai =head1 SEE ALSO L F =head1 COPYRIGHT Copyright 2002-2005 Dan Kogai This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.