Skip to content

Commit 48317f8

Browse files
committed
tarfs: implement hard links correctly
The previous change just hacked names into place. This change adds additional nodes -- ensuring that they're linked correctly -- and then has `Open` dereference them correctly. Closes: #714 Signed-off-by: Hank Donnay <[email protected]>
1 parent 9fdb583 commit 48317f8

File tree

1 file changed

+34
-24
lines changed

1 file changed

+34
-24
lines changed

pkg/tarfs/tarfs.go

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,15 @@ func New(r io.ReaderAt) (*FS, error) {
5555
r: r,
5656
lookup: make(map[string]int),
5757
}
58-
if err := s.add(".", newDir(".")); err != nil {
58+
hardlink := make(map[string][]string)
59+
if err := s.add(".", newDir("."), hardlink); err != nil {
5960
return nil, err
6061
}
6162

6263
segs, err := findSegments(r)
6364
if err != nil {
6465
return nil, fmt.Errorf("tarfs: error finding segments: %w", err)
6566
}
66-
hardlink := make(map[string][]string)
6767
for _, seg := range segs {
6868
r := io.NewSectionReader(r, seg.start, seg.size)
6969
rd := tar.NewReader(r)
@@ -84,32 +84,22 @@ func New(r io.ReaderAt) (*FS, error) {
8484
continue
8585
}
8686
i.children = make(map[int]struct{})
87-
case tar.TypeSymlink:
87+
case tar.TypeSymlink, tar.TypeLink:
8888
// Fixup the linkname. Can't tell from the spec if relative paths are allowed.
8989
if strings.HasPrefix(i.h.Linkname, ".") {
9090
i.h.Linkname = path.Join(path.Dir(n), i.h.Linkname)
9191
}
9292
i.h.Linkname = normPath(i.h.Linkname)
93-
case tar.TypeLink:
94-
tgt := normPath(i.h.Linkname)
95-
// If the target exist, short-circuit the add:
96-
if ino, ok := s.lookup[tgt]; ok {
97-
s.lookup[n] = ino
98-
continue
99-
}
100-
// Otherwise, defer the hardlink:
101-
hardlink[tgt] = append(hardlink[tgt], n)
10293
case tar.TypeReg:
10394
}
104-
if err := s.add(n, i); err != nil {
95+
if err := s.add(n, i, hardlink); err != nil {
10596
return nil, err
10697
}
107-
if revs, ok := hardlink[n]; ok {
108-
ino := s.lookup[n]
109-
for _, rev := range revs {
110-
s.lookup[rev] = ino
111-
}
112-
delete(hardlink, n)
98+
}
99+
// Cleanup any dangling hardlinks.
100+
for _, rms := range hardlink {
101+
for _, rm := range rms {
102+
delete(s.lookup, rm)
113103
}
114104
}
115105
return &s, nil
@@ -122,7 +112,9 @@ func New(r io.ReaderAt) (*FS, error) {
122112
// function attempts to follow the POSIX spec on actions when "creating" a file
123113
// that already exists:
124114
// https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap01.html#tagtcjh_14
125-
func (f *FS) add(name string, ino inode) error {
115+
//
116+
// The "hardlink" map is used for deferring hardlink creation.
117+
func (f *FS) add(name string, ino inode, hardlink map[string][]string) error {
126118
const op = `create`
127119
Again:
128120
if i, ok := f.lookup[name]; ok {
@@ -153,6 +145,17 @@ Again:
153145
f.inode[i] = ino
154146
return nil
155147
}
148+
149+
// Hardlink handling: if the target doesn't exist yet, make a note in passed-in map.
150+
if ino.h.Typeflag == tar.TypeLink {
151+
tgt := ino.h.Linkname
152+
if _, ok := f.lookup[tgt]; !ok {
153+
hardlink[tgt] = append(hardlink[tgt], name)
154+
}
155+
}
156+
if _, ok := hardlink[name]; ok {
157+
delete(hardlink, name)
158+
}
156159
i := len(f.inode)
157160
f.inode = append(f.inode, ino)
158161
f.lookup[name] = i
@@ -275,7 +278,7 @@ func (f *FS) walkTo(p string, create bool) (*inode, error) {
275278
child = &f.inode[ci]
276279
break Resolve
277280
case !ok && create:
278-
f.add(tgt, newDir(tgt))
281+
f.add(tgt, newDir(tgt), nil)
279282
ci = f.lookup[tgt]
280283
child = &f.inode[ci]
281284
case !ok && !create:
@@ -299,7 +302,7 @@ func (f *FS) walkTo(p string, create bool) (*inode, error) {
299302
case found && create, found && !create:
300303
// OK
301304
case !found && create:
302-
f.add(n, newDir(n))
305+
f.add(n, newDir(n), nil)
303306
ci := f.lookup[n]
304307
child = &f.inode[ci]
305308
case !found && !create:
@@ -318,8 +321,16 @@ func (f *FS) Open(name string) (fs.File, error) {
318321
return nil, err
319322
}
320323
typ := i.h.FileInfo().Mode().Type()
324+
var r *tar.Reader
321325
switch {
322-
case typ.IsRegular():
326+
case typ.IsRegular() && i.h.Typeflag != tar.TypeLink:
327+
r = tar.NewReader(io.NewSectionReader(f.r, i.off, i.sz))
328+
case typ.IsRegular() && i.h.Typeflag == tar.TypeLink:
329+
tgt, err := f.getInode(op, i.h.Linkname)
330+
if err != nil {
331+
return nil, err
332+
}
333+
r = tar.NewReader(io.NewSectionReader(f.r, tgt.off, tgt.sz))
323334
case typ.IsDir():
324335
d := dir{
325336
h: i.h,
@@ -343,7 +354,6 @@ func (f *FS) Open(name string) (fs.File, error) {
343354
Err: fs.ErrExist,
344355
}
345356
}
346-
r := tar.NewReader(io.NewSectionReader(f.r, i.off, i.sz))
347357
if _, err := r.Next(); err != nil {
348358
return nil, &fs.PathError{
349359
Op: op,

0 commit comments

Comments
 (0)