Skip to content

Commit

Permalink
CAF::Path: add has_hardlinks() and is_hardlink() methods
Browse files Browse the repository at this point in the history
- has_hardlinks(): returns the number of hardlinks of a file
- is_hardlink(): test if two paths refer to the same file (inode)
- CAF::Path requires LC::Check >= 1.22
  • Loading branch information
jouvin committed Mar 10, 2017
1 parent f89c766 commit 47d0477
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 15 deletions.
65 changes: 64 additions & 1 deletion src/main/perl/Path.pm
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -678,6 +678,69 @@ sub symlink
}


=item has_hardlinks
Method that returns the number of hardlinks for C<file>. The number of
hardlinks is the number of entries referring to the inodes minus 1. If
C<file> has no hardlink, the return value is 0. If C<file> is not a file,
the return value is C<undef>.
=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<path1> and C<path2> refer to the same file (inode).
It returns 0 if C<path1> and C<path2> both exist but are different files or are the same path
and C<undef> 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<path1> and C<path2>
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<owner>, C<group>, C<mode> and/or C<mtime>.
Expand Down
48 changes: 34 additions & 14 deletions src/test/perl/path.t
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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();
Expand All @@ -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");
Expand All @@ -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);
Expand Down

0 comments on commit 47d0477

Please sign in to comment.