-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
CAF::Path: add methods to manage links #225
Conversation
27c4c58
to
9cdfd6f
Compare
@@ -346,6 +346,54 @@ sub any_exists | |||
return $path && (-e $path || -l $path); | |||
} | |||
|
|||
=item is_symlink | |||
|
|||
Test if C<path> is a symlink. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pod should mention this reutrn true for broken symlink
src/main/perl/Path.pm
Outdated
|
||
=cut | ||
|
||
sub make_symlink |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
like all other methods in CAF::Path, this has to support NoAction (incl unittests). i'd suggest to check the LC code and use that for now. we can replace LC later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i would also rename it to just symlink
. the result of this method should be a symlink, it's possible none should be created.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I looked at directory()
and I don't see NoAction
being handled (NoAction
is handled only for temporary directories)... This may explain quattor/maven-tools#139... Or is NoAction
handled by LC
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NoAction is handled in the LC call, the Noaction flag is passed in https://github.com/quattor/CAF/blob/master/src/main/perl/Path.pm#L252; only functionality not in LC requires explicit NoAction code.
best point to start is to look how symlinking is handled in LC.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is what I was doing and I have the feeling that the method in CAF::Path
can be almost as simple as calling LC::Check::symlink
... Good news!
src/main/perl/Path.pm
Outdated
{ | ||
my ($self, $target, $path) = @_; | ||
my $status = SUCCESS; # Assume success | ||
unless ($path) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i propose to keep the same style as the rest of CAF::Path, so don't check this. lets assume that people who use this know what the are doing.
src/main/perl/Path.pm
Outdated
unless ($target) { | ||
$self->error("create_symlink(): 'target' argument missing"); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you have too untaint path and target, look at eg the directory code (you can ignore the tempdir part)
src/main/perl/Path.pm
Outdated
|
||
if ( $self->is_symlink($path) ) { | ||
$self->debug(1, "Removing symlink $path"); | ||
unlink ($path); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use CAF::Path methods to remove things.
src/main/perl/Path.pm
Outdated
} | ||
|
||
if ( $self->is_symlink($path) ) { | ||
$self->debug(1, "Removing symlink $path"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should only remove of something target is different
src/main/perl/Path.pm
Outdated
return; | ||
} | ||
|
||
$status = symlink $target, $path; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use LC method
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and make (or try to make) the parent dir first
src/main/perl/Path.pm
Outdated
} | ||
|
||
$status = symlink $target, $path; | ||
$status = SUCCESS if $status; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
success should be eturned when nothing has changed (eg link already existed and pointed to same target).
return CHANGED if a link was actually created
src/main/perl/Path.pm
Outdated
$self->debug(1, "Removing symlink $path"); | ||
unlink ($path); | ||
} elsif ( $self->any_exists($path) ) { | ||
$self->error("$path already exists and is not a symlink"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
report failures with fail
, eg. return $self->fail("$path already exists and is not a symlink");
in particular, CAF should try to minimise the reported errors. there might be a good reason that something fails, it's up to the component to promote a failure as an error (since any error means component failed).
@jouvin please have a look at eg the directory code. try to use the wrapped LC calls or existing CAF::Path methods. |
@stdweird thanks for all the remarks, I'll try to improve the code later today... |
@stdweird @jrha @ned21 any opinion on the argument order (link path versus target)? Perl |
Reimplemented over |
@jouvin - tough question! Following LC::Check::symlink makes it easier to refactor code in the short term but long term following |
a21e6cc
to
aaac8fa
Compare
I think the code is now in pretty good shape. We may just need to adjust the argument order and in fact part of the refactoring we want to do after having these methods is replacing I am still struggling with the tests: @stdweird |
9d86d33
to
ea7aafd
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jouvin i'm reading the unittests, my head is starting to spin . so i vote for perl/linux ln
usage and variable nameing, so ln TARGET LINK_NAME
(and i'd use $link_name
instead of $path
)
src/main/perl/Path.pm
Outdated
sub _make_link | ||
{ | ||
my ($self, $path, $target, %opts) = @_; | ||
$path = $self->_untaint_path($path, "symlink") || return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be symlink path
as message, and why isn't the target untainted?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mistake of mine! I thought I did it!
src/main/perl/Path.pm
Outdated
|
||
$self->_reset_exception_fail('symlink'); | ||
|
||
my $status = LC::Check::link($path, $target, %opts); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use the wrapper to call LC
, eg https://github.com/quattor/CAF/blob/master/src/main/perl/Path.pm#L532
src/main/perl/Path.pm
Outdated
return ($status ? CHANGED : SUCCESS); | ||
} else { | ||
my $link_type = $opts{hard} ? "hardlink" : "symlink"; | ||
$self->fail("Failed to create $link_type $path (target=$target)"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return $self->fail()
src/main/perl/Path.pm
Outdated
|
||
Create a hardlink C<path> whose target is C<target>. | ||
|
||
Returns an error if C<path> already exists and is not a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returns an error
-> Returns undef on failure and sets the fail attribute
src/main/perl/Path.pm
Outdated
|
||
Create a symlink C<path> whose target is C<target>. | ||
|
||
Returns an error if C<path> already exists and is not a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same Returns error
as above
src/main/perl/Path.pm
Outdated
$opts{nocheck} = 1; | ||
} | ||
$self->verbose("nocheck=$opts{nocheck}"); | ||
my $status = $self->_make_link($path, $target, %opts); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
simply return $self->_make_links
src/test/perl/path.t
Outdated
@@ -108,9 +108,9 @@ sub verify_exception | |||
} else { | |||
ok(! $ec_check->error(), "Error reset after $msg"); | |||
}; | |||
if ($noreset) { | |||
if ($noreset && $mc->{fail}) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please don't modify existing tests. using the LC
wrapper is probably enough to make this pass.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't really modify existing tests but made a commit explictly dedicated to this as I think the test is wrong... I don't like that you may want to test/print something undefined. It doesn't make sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it changes the logic of the test, from one condition to two. if $mc->{fail}
is not defined, something is wrong and test will fail, as it is supposed to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand that my problem was coming from the fact I was not using _safe_eval()
. Nevertheless, I think we should protect in some way against an undef
argument rather than adding a warning to the error...
BTW, I think there is a typo at the end of _safe_eval()
. Should be chomp $res_text
and verbose()
should print $res_text
, shouldn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice catch on the res_txt
! (i'm waiting for quattor/maven-tools#130 to catch these sort of errors)
if you want to, you can make the tests more robust, but not by changing the logic like this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it is changing the logic, at least it was not the goal! I need to reread what I did may be too quickly...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right that $noreset
was not honored when $mc->{fail}
was undefined. Should be fixed now and I really think that nothing is changed in the logic, just meaningless like()
are not executed. @stdweird do you agree?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not really. now there's no catch-all else block anymore. in particular i don't see what you gain with this. what did the error message look like when $mc->{fail}
is undefined?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jouvin this still needs to be addressed. i would revert the changes and keep the old logic. it's in any case more important that it fails rather that the message is readable/human friendly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jouvin ping
src/test/perl/path.t
Outdated
makefile("$basetest/$target_file1"); | ||
makefile($target_file2); | ||
my %opts; | ||
is($mc->symlink($dirlink, $target_directory), CHANGED, "directory symlink created"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add a symlink test $mc->is_symlink(
for every symlink created
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the main reason for this is so you can use the same/similar code block under noaction with minimal changes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure to understand... Why is_symlink()
is improving the ability to use the same code with NoAction
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the code block for NoAction will be the the smae, except where you now have/would have ok($mc->is_symlink,..)
, you wil have ok(!$mc->is_symlink
; since noaction is not supposed to create actual symlink (unless keepsstate is set).
src/test/perl/path.t
Outdated
is($mc->symlink($filelink, $target_file2), SUCCESS, "file symlink already exists: nothing done"); | ||
is($mc->symlink($filelink, $target_file1), CHANGED, "file symlink already exists: nothing done"); | ||
my $link_status = $mc->symlink($target_file2, $target_file1); | ||
ok(! $link_status, "existing file not replaced by a symlink"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it has to return undef
, so use ok(!defined($link_status))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and the message should also have symlink failed failed, reason
src/test/perl/path.t
Outdated
is($mc->symlink($filelink, $target_file1), CHANGED, "file symlink already exists: nothing done"); | ||
my $link_status = $mc->symlink($target_file2, $target_file1); | ||
ok(! $link_status, "existing file not replaced by a symlink"); | ||
is($mc->{fail}, "Failed to create symlink $target_file2 (target=$target_file1)"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add a message to the test symlink failed because existing file not replaced by a symlink sets correct fail attrribute
src/test/perl/path.t
Outdated
is($mc->{fail}, "Failed to create symlink $target_file2 (target=$target_file1)"); | ||
$opts{force} = 1; | ||
is($mc->symlink($target_file2, $target_file1, %opts), CHANGED, "existing file replaced by a symlink (force option set)"); | ||
ok(! $mc->symlink("$basetest/$target_directory", $target_file1, %opts), "directory not replaced by a symlink (force option set)"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, i reread the pod, and this is implied, but it would be better to make it explicit in the pod (or use the not replacing of a directory as an exmaple in the pod).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same remark as above, here (and everywhere else) add an explicit test that nothing was created when it shouldn't have.
src/test/perl/path.t
Outdated
$opts{force} = 1; | ||
is($mc->symlink($target_file2, $target_file1, %opts), CHANGED, "existing file replaced by a symlink (force option set)"); | ||
ok(! $mc->symlink("$basetest/$target_directory", $target_file1, %opts), "directory not replaced by a symlink (force option set)"); | ||
$opts{nocheck} = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ahhh, this is confusing. so the pod is wrong?
src/test/perl/path.t
Outdated
$opts{nocheck} = 0; | ||
ok(! $mc->symlink($brokenlink, "really_really_missing", %opts), "broken symlink not created (target existence enforced)"); | ||
delete $opts{nocheck}; | ||
is($mc->symlink($brokenlink, "really_missing"), CHANGED, "broken symlink created"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you have to test this, either use !-e $brokenlink && -l $brokenlink
or a combination of CAF::Path methods
9e778fa
to
e5907a4
Compare
@ned21 @jrha @stdweird Adding more unit tests for hardlinks (in particular hardlinks to symlinks, something allowed by |
PR should be almost ok but I still have to figure out the problem with |
I realized that we had a test-specific version of |
Ready for final review 😄 |
bf48b16
to
fb59f93
Compare
src/main/perl/Path.pm
Outdated
is honored (overrides C<NoAction> if true). One important | ||
difference is the order of the arguments: C<CAF::Path:_make_link> | ||
and the methods based on it are following the Perl C<symlink> | ||
(and Shell C<ln>) argument order. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ln
has nothing to do with Shell
, it's part of base utils on a lot of Unix systems.
src/main/perl/Path.pm
Outdated
and the methods based on it are following the Perl C<symlink> | ||
(and Shell C<ln>) argument order. | ||
|
||
This in internal method, not supposed to be called directly. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in
-> is an
src/main/perl/Path.pm
Outdated
If C<link_path> exists and is a file, it is updated. | ||
C<target> must exist (C<check> flag available in symlink() | ||
is ignored for hardlinks) and it must reside in the same | ||
file system as C<link_path>. If C<target_path> is a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
file system
-> filesystem
src/test/perl/path.t
Outdated
|
||
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"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is false because the files are the same. should change the message
src/test/perl/path.t
Outdated
ok($mc->file_exists($target_file2) && ! $mc->is_symlink($target_file2), "File $target_file2 has not be replaced by a symlink"); | ||
$opts{force} = 1; | ||
$CAF::Object::NoAction = 1; | ||
is($mc->symlink($target_file1, $target_file2, %opts), CHANGED, "existing file replaced by a symlink (force option set)"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
adapt message. this is force=1, but also NoAction=1; so the file shouldn't be replaced.
@jouvin more minor fixes. can you address them in a separate commit(s)? you can squash them before merging (it would make the review easier) |
- Protect against uninitialized variables - Clarify messages
- Creates or updates a symlink or hardlink respectively - Wrapper over LC::Check::link()
- 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
- CAF::Path has a requirement on LC::Check version that causes the tests for other CAF modules using CAF::Path to fail.
3 methods added to manage symlinks:
is_symlink()
: returns true if the argument is a symlinksymlink()
: creates a new symlink or updates an existing one. If the symlink path already exists and is not a symlink, returns an error.symlink()
for hardlinks.