diff --git a/src/main/perl/Path.pm b/src/main/perl/Path.pm index fb449135..3531e245 100644 --- a/src/main/perl/Path.pm +++ b/src/main/perl/Path.pm @@ -1,7 +1,7 @@ #${PMpre} CAF::Path${PMpost} use CAF::Object qw(SUCCESS CHANGED); -use LC::Check; +use LC::Check 1.22; use LC::Exception qw (throw_error); use Readonly; @@ -678,6 +678,69 @@ sub symlink } +=item has_hardlinks + +Method that returns the number of hardlinks for C. The number of +hardlinks is the number of entries referring to the inodes minus 1. If +C has no hardlink, the return value is 0. If C is not a file, +the return value is C. + +=cut + +sub has_hardlinks +{ + my ($self, $file) = @_; + $file = $self->_untaint_path($file, "has_hardlinks") || return; + + if ( ! $self->file_exists($file) && ! $self->is_symlink($file) ) { + $self->debug(2, "has_hardlinks(): $file doesn't exist or is not a file"); + return; + } + + my $nlinks = (lstat($file))[3]; + $self->debug(2, "Number of links to $file: $nlinks (hardlink if > 2)"); + return $nlinks ? $nlinks - 1 : 0; +} + + +=item is_hardlink + +This method returns SUCCESS if C and C refer to the same file (inode). +It returns 0 if C and C both exist but are different files or are the same path +and C if one of the paths doesn't exist or is not a file. + +Note: the result returned will be identical whatever is the order of C and C +arguments. + +=cut + +sub is_hardlink +{ + my ($self, $path1, $path2) = @_; + $path1 = $self->_untaint_path($path1, "is_hardlink path1") || return; + $path2 = $self->_untaint_path($path2, "is_hardlink path2") || return; + + if ( ! $self->file_exists($path1) && ! $self->is_symlink($path1) ) { + $self->debug(2, "is_hardlink(): $path1 doesn't exist or is not a file"); + return; + } + if ( ! $self->file_exists($path2) && ! $self->is_symlink($path2) ) { + $self->debug(2, "is_hardlink(): $path2 doesn't exist or is not a file"); + return; + } + + my $link_inode = (lstat($path1))[1]; + my $target_inode = (lstat($path2))[1]; + + $self->debug(2, "Comparing $path1 inode ($link_inode) and $path2 inode ($target_inode)"); + if ( ($link_inode == $target_inode) && ($path1 ne $path2) ) { + return SUCCESS; + } else { + return 0; + } +} + + =item status Set the path stat options: C, C, C and/or C. diff --git a/src/test/perl/path.t b/src/test/perl/path.t index d537429c..0b940a64 100644 --- a/src/test/perl/path.t +++ b/src/test/perl/path.t @@ -313,23 +313,34 @@ ok(! $mc->directory_exists($basetest), "directory_exists false on missing direct ok(! $mc->file_exists($basetest), "file_exists false on missing directory"); ok(! $mc->any_exists($basetest), "any_exists false on missing directory"); ok(! $mc->is_symlink($basetest), "is_symlink false on missing directory"); +ok(! $mc->has_hardlinks($basetest), "has_hardlinks false on missing directory"); +is($mc->is_hardlink($basetest, $basetest), undef, "is_hardlink false with missing directories"); ok(! $mc->directory_exists($basetestfile), "directory_exists false on missing file"); ok(! $mc->file_exists($basetestfile), "file_exists false on missing file"); ok(! $mc->any_exists($basetestfile), "any_exists false on missing file"); ok(! $mc->is_symlink($basetestfile), "is_symlink false on missing file"); +ok(! $mc->has_hardlinks($basetestfile), "has_hardlinks false on missing file"); +is($mc->is_hardlink($basetestfile, $basetestfile), undef, "is_hardlink false with missing files"); makefile($basetestfile); +my $basetestfile2 = $basetestfile . "_2"; +makefile($basetestfile2); ok($mc->directory_exists($basetest), "directory_exists true on created directory"); ok($mc->any_exists($basetest), "any_exists true on created directory"); ok(! $mc->file_exists($basetest), "file_exists false on created directory"); ok(! $mc->is_symlink($basetest), "is_symlink false on created directory"); +ok(! $mc->has_hardlinks($basetest), "has_hardlinks false on created directory"); +is($mc->is_hardlink($basetest, $basetest), undef, "is_hardlink false with created directories"); ok(! $mc->directory_exists($basetestfile), "directory_exists false on created file"); ok($mc->any_exists($basetestfile), "any_exists true on created file"); ok($mc->file_exists($basetestfile), "file_exists true on created file"); ok(! $mc->is_symlink($basetestfile), "is_symlink false on created file"); +ok(! $mc->has_hardlinks($basetestfile), "has_hardlinks false on created file"); +is($mc->is_hardlink($basetestfile, $basetestfile), 0, "is_hardlink false with a non-hardlinked file"); +is($mc->is_hardlink($basetestfile, $basetestfile2), 0, "is_hardlink false with non-hardlinked files"); # noreset=1 verify_exception("existence tests (file/directory)", undef, 0, 1); @@ -434,34 +445,47 @@ verify_exception("symlink tests", "Failed to create symlink", $symlink_call_coun ok($mc->_reset_exception_fail(), "_reset_exception_fail after symlink tests"); -# Test xxx_exists methods with symlinks +# Test xxx_exists, is_hardlink and has_hardlinks methods with symlinks # Needs symlinks created in previous step (symlink creation/update) init_exception("existence tests (symlinks)"); +my $hardlink = "$basetest/a_hardlink"; ok(! $mc->directory_exists($brokenlink), "directory_exists false on brokenlink"); ok(! $mc->file_exists($brokenlink), "file_exists false on brokenlink"); ok($mc->any_exists($brokenlink), "any_exists true on brokenlink"); ok($mc->is_symlink($brokenlink), "is_symlink true on brokenlink"); +is($mc->hardlink($brokenlink, $hardlink), CHANGED, "hardlink created on broken link"); +ok($mc->has_hardlinks($brokenlink), "broken link is a hard link"); +is($mc->is_hardlink($brokenlink, $hardlink), 1, "$brokenlink and $hardlink are hard linked"); ok($mc->directory_exists($dirlink), "directory_exists true on dirlink"); ok(! $mc->file_exists($dirlink), "file_exists false on dirlink"); ok($mc->any_exists($dirlink), "any_exists true on dirlink"); ok($mc->is_symlink($dirlink), "is_symlink true on dirlink"); +is($mc->hardlink($dirlink, $hardlink), CHANGED, "hardlink created on directory link"); +ok($mc->has_hardlinks($dirlink), "directory link is a hard link"); +is($mc->is_hardlink($dirlink, $hardlink), 1, "$dirlink and $hardlink are hard linked"); ok(! $mc->directory_exists($filelink), "directory_exists false on filelink"); ok($mc->file_exists($filelink), "file_exists true on filelink"); ok($mc->any_exists($filelink), "any_exists true on filelink"); ok($mc->is_symlink($filelink), "is_symlink true on filelink"); +is($mc->hardlink($filelink, $hardlink), CHANGED, "hardlink created on file link"); +ok($mc->has_hardlinks($filelink), "file link is a hard link"); +is($mc->is_hardlink($filelink, $hardlink), 1, "$filelink and $hardlink are hard linked"); + +is(! $mc->is_hardlink($filelink, $dirlink), 1, "$filelink and $dirlink are not hard linked (different hard links)"); # noreset=1 -verify_exception("existence tests (symlinks)", undef, 0, 1); +verify_exception("existence tests (symlinks)", undef, 6, 1); ok($mc->_reset_exception_fail(), "_reset_exception_fail after existence tests (symlinks)"); # Test hardlink creation # Function to do the hardlink checks, taking into account NoAction flag +# This function also acts a unit test for has_hardlinks() and is_hardlink() methods sub check_hardlink { my ($mc, $target, $link_path, $expected_status) = @_; my $noaction = $mc->_get_noaction(); @@ -474,19 +498,16 @@ sub check_hardlink { } my $target_msg = "$link_path hardlink has the expected " . ($expected_status == CHANGED ? "changed " : "") . "target ($target)"; - my ($link_inode, $nlink) = (stat($link_path))[1, 3]; - my $target_inode = (stat($target))[1]; - # Test if symlink was created or not according to NoAction flag my $ok_condition; if ( $noaction ) { $ok_condition = ! $mc->any_exists($link_path); - }else { - $ok_condition = $nlink && ($nlink > 1); + } else { + $ok_condition = $mc->has_hardlinks($link_path); } ok($ok_condition, $ok_msg); - ok($link_inode == $target_inode, $target_msg) unless $noaction; + ok($mc->is_hardlink($link_path, $target), $target_msg) unless $noaction; }; init_exception("hardlink tests"); @@ -511,24 +532,23 @@ for $CAF::Object::NoAction (1,0) { } is($mc->hardlink($target_file3, $hardlink1), CHANGED, "hardlink updated"); check_hardlink($mc, $target_file3, $hardlink1, CHANGED); + is($mc->hardlink($relative_target_file3, $hardlink2), CHANGED, "relative hardlink created"); + check_hardlink($mc, $target_file3, $hardlink2, CHANGED); } # The following tests are not affected by NoAction flag $link_status = $mc->hardlink("missing_file", $hardlink1); ok(! $link_status, "hardlink not created (target has to exist)"); is($mc->{fail}, - "*** invalid target (missing_file): stat($basetest/$target_directory/missing_file): No such file or directory", + "*** invalid target (missing_file): lstat(missing_file): No such file or directory", "fail attribute set after hardlink error"); $link_status = $mc->hardlink($target_file1, "$basetest/$target_directory"); ok(! $link_status, "hardlink not created (do not replace an existing directory)"); is($mc->{fail}, "*** cannot hard link $basetest/$target_directory: it is a directory", "fail attribute set after hardlink error (existing directory not replaced)"); -$link_status = $mc->hardlink($relative_target_file3, $hardlink2); -ok(! $link_status, "hardlink not created (relative target, link path directory prepended)"); -is($mc->{fail}, - "*** invalid target ($relative_target_file3): stat(". dirname($hardlink2) . "/$relative_target_file3): No such file or directory", - "fail attribute set after hardlink error (relative target, link path directory prepended)"); +is($mc->hardlink($target_file1, $hardlink2), CHANGED, "hardlink2 created"); +is($mc->is_hardlink($hardlink1, $hardlink2), 0, "is_hardlink false (0) with 2 different hardlinks"); # noreset=0 verify_exception("hardlink tests", "\\*\\*\\* invalid target", $hardlink_call_count * 2, 0);