Skip to content

Commit 359e195

Browse files
committed
cowfs: fix symlink support
1 parent f338a62 commit 359e195

File tree

1 file changed

+46
-25
lines changed

1 file changed

+46
-25
lines changed

fs/cowfs/cowfs.go

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
package cowfs
2828

2929
import (
30+
"context"
3031
"crypto/sha1"
3132
"errors"
3233
"fmt"
@@ -241,16 +242,17 @@ func (u *FS) resolvePath(path string) (string, error) {
241242
// shouldCopy determines if a file needs to be copied from base to overlay.
242243
// Returns true if the file exists in base but not in overlay.
243244
// Returns false if already in overlay or doesn't exist in base.
245+
// Uses Lstat to properly handle symlinks without following them.
244246
func (u *FS) shouldCopy(name string) (bool, error) {
245-
// Already in overlay?
246-
if _, err := fs.Stat(u.Overlay, name); err == nil {
247+
// Already in overlay? (use Lstat to not follow symlinks)
248+
if _, err := fs.Lstat(u.Overlay, name); err == nil {
247249
return false, nil
248250
} else if !errors.Is(err, fs.ErrNotExist) {
249251
return false, err
250252
}
251253

252-
// Exists in base?
253-
if _, err := fs.Stat(u.Base, name); err == nil {
254+
// Exists in base? (use Lstat to not follow symlinks)
255+
if _, err := fs.Lstat(u.Base, name); err == nil {
254256
return true, nil
255257
} else if errors.Is(err, fs.ErrNotExist) {
256258
return false, fs.ErrNotExist
@@ -348,16 +350,16 @@ func (u *FS) Rename(oldname, newname string) error {
348350
}
349351
// Note: Do NOT resolve newname through renames - POSIX rename overwrites newname itself
350352

351-
// 2. Check if source exists in base and overlay
353+
// 2. Check if source exists in base and overlay (use Lstat to not follow symlinks)
352354
srcInBase := false
353-
if _, err := fs.Stat(u.Base, src); err == nil {
355+
if _, err := fs.Lstat(u.Base, src); err == nil {
354356
srcInBase = true
355357
} else if !errors.Is(err, fs.ErrNotExist) {
356358
return err
357359
}
358360

359361
srcInOverlay := false
360-
if _, err := fs.Stat(u.Overlay, src); err == nil {
362+
if _, err := fs.Lstat(u.Overlay, src); err == nil {
361363
srcInOverlay = true
362364
} else if !errors.Is(err, fs.ErrNotExist) {
363365
return err
@@ -369,7 +371,8 @@ func (u *FS) Rename(oldname, newname string) error {
369371
}
370372

371373
// 4. Handle existing destination: remove if in overlay, tombstone if in base
372-
statInfo, overlayErr := fs.Stat(u.Overlay, newname)
374+
// Use Lstat to not follow symlinks
375+
statInfo, overlayErr := fs.Lstat(u.Overlay, newname)
373376
if overlayErr == nil {
374377
// Destination exists in overlay - remove it
375378
if statInfo.IsDir() {
@@ -386,7 +389,7 @@ func (u *FS) Rename(oldname, newname string) error {
386389
return overlayErr
387390
}
388391

389-
_, baseErr := fs.Stat(u.Base, newname)
392+
_, baseErr := fs.Lstat(u.Base, newname)
390393
if baseErr == nil {
391394
// Destination exists in base - tombstone it
392395
if err := u.tombstone(newname); err != nil {
@@ -483,17 +486,19 @@ func (u *FS) Remove(name string) error {
483486
}
484487

485488
// 2. Check if file exists in base (we'll need this info)
489+
// Use Lstat to not follow symlinks
486490
existsInBase := false
487-
if _, err := fs.Stat(u.Base, target); err == nil {
491+
if _, err := fs.Lstat(u.Base, target); err == nil {
488492
existsInBase = true
489493
} else if !errors.Is(err, fs.ErrNotExist) {
490494
log.Println("stat base error", err)
491495
return err
492496
}
493497

494498
// 3. Check if file exists in overlay
499+
// Use Lstat to not follow symlinks
495500
existsInOverlay := false
496-
if _, err := fs.Stat(u.Overlay, target); err == nil {
501+
if _, err := fs.Lstat(u.Overlay, target); err == nil {
497502
existsInOverlay = true
498503
} else if !errors.Is(err, fs.ErrNotExist) {
499504
log.Println("stat overlay error", err)
@@ -520,14 +525,14 @@ func (u *FS) Remove(name string) error {
520525
return fs.ErrNotExist
521526
}
522527

523-
// 6. Check if it's a directory
528+
// 6. Check if it's a directory (use Lstat to not follow symlinks)
524529
isDir := false
525530
if existsInOverlay {
526-
if info, err := fs.Stat(u.Overlay, target); err == nil {
531+
if info, err := fs.Lstat(u.Overlay, target); err == nil {
527532
isDir = info.IsDir()
528533
}
529534
} else if existsInBase {
530-
if info, err := fs.Stat(u.Base, target); err == nil {
535+
if info, err := fs.Lstat(u.Base, target); err == nil {
531536
isDir = info.IsDir()
532537
}
533538
}
@@ -661,15 +666,16 @@ func (u *FS) Symlink(oldname, newname string) error {
661666
}
662667

663668
// 3. Handle existing file at target path (remove overlay, tombstone base)
664-
if _, err := fs.Stat(u.Overlay, newpath); err == nil {
669+
// Use Lstat to not follow symlinks
670+
if _, err := fs.Lstat(u.Overlay, newpath); err == nil {
665671
if err := fs.Remove(u.Overlay, newpath); err != nil {
666672
return err
667673
}
668674
} else if !errors.Is(err, fs.ErrNotExist) {
669675
return err
670676
}
671677

672-
if _, err := fs.Stat(u.Base, newpath); err == nil {
678+
if _, err := fs.Lstat(u.Base, newpath); err == nil {
673679
// Hide base entry
674680
if err := u.tombstone(newpath); err != nil {
675681
return err
@@ -705,14 +711,15 @@ func (u *FS) Mkdir(name string, perm os.FileMode) error {
705711
// log.Println("Mkdir", name, perm)
706712

707713
// 2. Check if directory already exists in either layer
708-
if _, err := fs.Stat(u.Overlay, path); err == nil {
714+
// Use Lstat to not follow symlinks
715+
if _, err := fs.Lstat(u.Overlay, path); err == nil {
709716
return fs.ErrExist
710717
} else if !errors.Is(err, fs.ErrNotExist) {
711718
return err
712719
}
713720

714721
existsInBase := false
715-
if _, err := fs.Stat(u.Base, path); err == nil {
722+
if _, err := fs.Lstat(u.Base, path); err == nil {
716723
existsInBase = true
717724
} else if !errors.Is(err, fs.ErrNotExist) {
718725
return err
@@ -779,9 +786,17 @@ func (u *FS) Create(name string) (fs.File, error) {
779786
// The path is resolved through rename chains before processing.
780787
// Prefers overlay, falls back to base. Returns fs.ErrNotExist if tombstoned.
781788
func (u *FS) Stat(name string) (os.FileInfo, error) {
789+
return u.StatContext(context.Background(), name)
790+
}
791+
792+
// StatContext returns file information for the named file with context support.
793+
// Respects the NoFollow context flag for symlink handling.
794+
// The path is resolved through rename chains before processing.
795+
// Prefers overlay, falls back to base. Returns fs.ErrNotExist if tombstoned.
796+
func (u *FS) StatContext(ctx context.Context, name string) (os.FileInfo, error) {
782797
// 0. Normalize path
783798
name = filepath.Clean(name)
784-
// log.Println("Stat", name)
799+
// log.Println("StatContext", name, "follow:", fs.FollowSymlinks(ctx))
785800

786801
// 1. Resolve rename chain
787802
path, err := u.resolvePath(name)
@@ -794,15 +809,21 @@ func (u *FS) Stat(name string) (os.FileInfo, error) {
794809
return nil, fs.ErrNotExist
795810
}
796811

812+
// Choose stat function based on whether we should follow symlinks
813+
statFn := fs.Stat
814+
if !fs.FollowSymlinks(ctx) {
815+
statFn = fs.Lstat
816+
}
817+
797818
// 3. Try overlay first
798-
if fi, err := fs.Stat(u.Overlay, path); err == nil {
819+
if fi, err := statFn(u.Overlay, path); err == nil {
799820
return fi, nil
800821
} else if !errors.Is(err, fs.ErrNotExist) {
801822
return nil, err
802823
}
803824

804825
// 4. Fallback to base
805-
fi, err := fs.Stat(u.Base, path)
826+
fi, err := statFn(u.Base, path)
806827
if err != nil {
807828
return nil, err
808829
}
@@ -901,16 +922,16 @@ func (u *FS) OpenFile(name string, flag int, perm os.FileMode) (fs.File, error)
901922
}
902923
}
903924

904-
// 2. Check existence in both layers
925+
// 2. Check existence in both layers (use Lstat to not follow symlinks)
905926
existsInOverlay := false
906-
if _, err := fs.Stat(u.Overlay, path); err == nil {
927+
if _, err := fs.Lstat(u.Overlay, path); err == nil {
907928
existsInOverlay = true
908929
} else if !errors.Is(err, fs.ErrNotExist) {
909930
return nil, err
910931
}
911932

912933
existsInBase := false
913-
if _, err := fs.Stat(u.Base, path); err == nil {
934+
if _, err := fs.Lstat(u.Base, path); err == nil {
914935
existsInBase = true
915936
} else if !errors.Is(err, fs.ErrNotExist) {
916937
return nil, err
@@ -921,7 +942,7 @@ func (u *FS) OpenFile(name string, flag int, perm os.FileMode) (fs.File, error)
921942
exclusive := flag&os.O_EXCL != 0
922943
if creating && exclusive {
923944
baseExists := false
924-
if _, err := fs.Stat(u.Base, path); err == nil {
945+
if _, err := fs.Lstat(u.Base, path); err == nil {
925946
if _, dead := u.tombstones.Load(path); !dead {
926947
baseExists = true
927948
}

0 commit comments

Comments
 (0)