Skip to content
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

feat: handle hardlinks #17

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0601fda
feat: handle hardlinks
zhijie-yang Jul 18, 2024
ffd9951
test: hard link against symlink
zhijie-yang Jul 18, 2024
85a4cf9
test: hard link testing for fsutil.create
zhijie-yang Jul 18, 2024
19fc4ff
test: make TreeDumpEntry handle hard link
zhijie-yang Jul 19, 2024
eefd744
test: combine into hackopt
zhijie-yang Jul 19, 2024
1237a08
test: extract all types of file
zhijie-yang Jul 19, 2024
bc02824
chore: add comments for hard link assumptions
zhijie-yang Jul 19, 2024
a8bd58e
test: comprehensive cases for hard link in create_test
zhijie-yang Jul 19, 2024
381f563
Revert "test: make TreeDumpEntry handle hard link"
zhijie-yang Jul 19, 2024
c8a7931
test: handle hard link test in createTest suite
zhijie-yang Jul 19, 2024
265c49e
chore: adjust error/test messages and test matching regex
zhijie-yang Jul 19, 2024
7b21a1e
chore: fix comments
zhijie-yang Jul 22, 2024
06ccac5
test: TreeDumpEntry to handle hard links
zhijie-yang Jul 22, 2024
4090b4c
fix: multiple adjusts according to code review
zhijie-yang Jul 23, 2024
d3a7921
chore: adjust comments for fsutil.Create
zhijie-yang Jul 23, 2024
a798078
feat: drop handling hard link in TreeDumpEntry
zhijie-yang Jul 23, 2024
2782eb8
Revert "feat: drop handling hard link in TreeDumpEntry"
zhijie-yang Jul 24, 2024
3e8d872
chore: change commits and test case summaries
zhijie-yang Jul 24, 2024
03ce4d3
chore: change variable naming
zhijie-yang Jul 24, 2024
5b29138
feat: add test for same hardlink in slicer_test
zhijie-yang Jul 24, 2024
0dca7a0
test: update duplicate hardlink in slicer_test
zhijie-yang Jul 25, 2024
acec1a7
test: treeDumpReport hardlink target uses abs path
zhijie-yang Jul 26, 2024
4313070
chore: adjust tree dump hardlink string
zhijie-yang Jul 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion internal/deb/extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,11 +246,17 @@ func extractData(dataReader io.Reader, options *ExtractOptions) error {
}
}
// Create the entry itself.
link := tarHeader.Linkname
if tarHeader.Typeflag == tar.TypeLink {
// The hard link requires the real path of the target file.
zhijie-yang marked this conversation as resolved.
Show resolved Hide resolved
link = filepath.Join(options.TargetDir, link)
}

createOptions := &fsutil.CreateOptions{
Path: filepath.Join(options.TargetDir, targetPath),
Mode: tarHeader.FileInfo().Mode(),
Data: pathReader,
Link: tarHeader.Linkname,
Link: link,
MakeParents: true,
}
err := options.Create(extractInfos, createOptions)
Expand Down
34 changes: 34 additions & 0 deletions internal/deb/extract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,40 @@ var extractTests = []extractTest{{
},
},
error: `cannot extract from package "test-package": path /dir/ requested twice with diverging mode: 0777 != 0000`,
}, {
summary: "Hard link must be created if specified in the tarball",
zhijie-yang marked this conversation as resolved.
Show resolved Hide resolved
pkgdata: testutil.MustMakeDeb([]testutil.TarEntry{
testutil.Dir(0755, "./"),
testutil.Reg(0644, "./file1.txt", "text for file1.txt"),
testutil.Hln(0644, "./file2.txt", "./file1.txt"),
zhijie-yang marked this conversation as resolved.
Show resolved Hide resolved
}),
options: deb.ExtractOptions{
Extract: map[string][]deb.ExtractInfo{
"/*.txt": []deb.ExtractInfo{{
Path: "/*.txt",
zhijie-yang marked this conversation as resolved.
Show resolved Hide resolved
}},
},
},
result: map[string]string{
"/file1.txt": "file 0644 e926a7fb",
"/file2.txt": "file 0644 e926a7fb",
},
notCreated: []string{},
}, {
summary: "Dangling hard link must raise an error",
zhijie-yang marked this conversation as resolved.
Show resolved Hide resolved
pkgdata: testutil.MustMakeDeb([]testutil.TarEntry{
testutil.Dir(0755, "./"),
// testutil.Reg(0644, "./file1.txt", "text for file1.txt"),
zhijie-yang marked this conversation as resolved.
Show resolved Hide resolved
testutil.Hln(0644, "./file2.txt", "./file1.txt"),
}),
options: deb.ExtractOptions{
Extract: map[string][]deb.ExtractInfo{
"/*.txt": []deb.ExtractInfo{{
Path: "/*.txt",
}},
},
},
error: `cannot extract from package "test-package": the target file does not exist: .*/file1.txt`,
zhijie-yang marked this conversation as resolved.
Show resolved Hide resolved
}}

func (s *S) TestExtract(c *C) {
Expand Down
30 changes: 28 additions & 2 deletions internal/fsutil/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,12 @@ func Create(options *CreateOptions) (*Entry, error) {

switch o.Mode & fs.ModeType {
case 0:
err = createFile(o)
hash = hex.EncodeToString(rp.h.Sum(nil))
if o.Link != "" {
err = createHardLink(o)
} else {
err = createFile(o)
hash = hex.EncodeToString(rp.h.Sum(nil))
}
case fs.ModeDir:
err = createDir(o)
case fs.ModeSymlink:
Expand Down Expand Up @@ -121,6 +125,28 @@ func createSymlink(o *CreateOptions) error {
return os.Symlink(o.Link, o.Path)
}

func createHardLink(o *CreateOptions) error {
zhijie-yang marked this conversation as resolved.
Show resolved Hide resolved
debugf("Creating hard link: %s => %s", o.Path, o.Link)
targetInfo, err := os.Lstat(o.Link)
zhijie-yang marked this conversation as resolved.
Show resolved Hide resolved
zhijie-yang marked this conversation as resolved.
Show resolved Hide resolved
if err != nil && os.IsNotExist(err) {
return fmt.Errorf("the target file does not exist: %s", o.Link)
zhijie-yang marked this conversation as resolved.
Show resolved Hide resolved
zhijie-yang marked this conversation as resolved.
Show resolved Hide resolved
} else if err != nil {
return err
}

linkInfo, err := os.Lstat(o.Path)
if err == nil || os.IsExist(err) {
if os.SameFile(targetInfo, linkInfo) {
return nil
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did some more tests and ln does not respect this, let's remove it. Probably previous comment about naming does not apply as we no longer need those variables.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bookkeeping: As per the offline discussion, this has to exist since there can be different slices in one package that create the same hard link multiple times.

return fmt.Errorf("the link already exists: %s", o.Path)
zhijie-yang marked this conversation as resolved.
Show resolved Hide resolved
} else if !os.IsNotExist(err) {
return err
}

return os.Link(o.Link, o.Path)
}

// readerProxy implements the io.Reader interface proxying the calls to its
// inner io.Reader. On each read, the proxy keeps track of the file size and hash.
type readerProxy struct {
Expand Down
13 changes: 13 additions & 0 deletions internal/testutil/pkgdata.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,16 @@ func Lnk(mode int64, path, target string) TarEntry {
},
}
}

// Hln is a shortcut for creating a hard link TarEntry structure (with
// tar.Typeflag set to tar.TypeLink). Hln stands for "Hard LiNk".
zhijie-yang marked this conversation as resolved.
Show resolved Hide resolved
func Hln(mode int64, path, target string) TarEntry {
return TarEntry{
Header: tar.Header{
Typeflag: tar.TypeLink,
Name: path,
Mode: mode,
Linkname: target,
},
}
}