@@ -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`
127119Again:
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