@@ -26,6 +26,7 @@ import (
26
26
"github.com/pkg/errors"
27
27
"github.com/stretchr/testify/require"
28
28
bolt "go.etcd.io/bbolt"
29
+ libcap "kernel.org/pub/linux/libs/security/libcap/cap"
29
30
)
30
31
31
32
func newSnapshotter (ctx context.Context , t * testing.T , snapshotterName string ) (_ context.Context , _ * mergeSnapshotter , rerr error ) {
@@ -372,6 +373,41 @@ func TestHardlinks(t *testing.T) {
372
373
}
373
374
}
374
375
376
+ func TestMergeFileCapabilities (t * testing.T ) {
377
+ requireRoot (t )
378
+ for _ , snName := range []string {"overlayfs" , "native" , "native-nohardlink" } {
379
+ snName := snName
380
+ t .Run (snName , func (t * testing.T ) {
381
+ t .Parallel ()
382
+
383
+ ctx , sn , err := newSnapshotter (context .Background (), t , snName )
384
+ require .NoError (t , err )
385
+
386
+ setCaps := "cap_net_bind_service=+ep"
387
+ base1Snap := committedKey (ctx , t , sn , identity .NewID (), "" ,
388
+ fstest .CreateFile ("hasCaps" , []byte ("capable" ), 0700 ),
389
+ fstest .Chown ("hasCaps" , 1000 , 1000 ),
390
+ setFileCap ("hasCaps" , setCaps ),
391
+ )
392
+ base2Snap := committedKey (ctx , t , sn , identity .NewID (), "" ,
393
+ fstest .CreateFile ("foo" , []byte ("bar" ), 0600 ),
394
+ )
395
+
396
+ mergeSnap := mergeKey (ctx , t , sn , identity .NewID (), []Diff {
397
+ {"" , base1Snap .Name },
398
+ {"" , base2Snap .Name },
399
+ })
400
+
401
+ actualCaps := getFileCap (ctx , t , sn , mergeSnap .Name , "hasCaps" )
402
+ require .Equal (t , "cap_net_bind_service=ep" , actualCaps )
403
+ stat := statPath (ctx , t , sn , mergeSnap .Name , "hasCaps" )
404
+ require .EqualValues (t , 1000 , stat .Uid )
405
+ require .EqualValues (t , 1000 , stat .Gid )
406
+ require .EqualValues (t , 0700 , stat .Mode & 0777 )
407
+ })
408
+ }
409
+ }
410
+
375
411
func TestUsage (t * testing.T ) {
376
412
for _ , snName := range []string {"overlayfs" , "native" , "native-nohardlink" } {
377
413
snName := snName
@@ -559,7 +595,7 @@ func requireMtime(t *testing.T, path string, mtime time.Time) {
559
595
require .Equal (t , mtime .UnixNano (), stat .Mtim .Nano ())
560
596
}
561
597
562
- func tryStatPath (ctx context.Context , t * testing.T , sn * mergeSnapshotter , key , path string ) ( st * syscall. Stat_t ) {
598
+ func pathCallback [ T any ] (ctx context.Context , t * testing.T , sn * mergeSnapshotter , key , path string , cb func ( t * testing. T , path string ) * T ) * T {
563
599
t .Helper ()
564
600
mounts , cleanup := getMounts (ctx , t , sn , key )
565
601
defer cleanup ()
@@ -577,24 +613,32 @@ func tryStatPath(ctx context.Context, t *testing.T, sn *mergeSnapshotter, key, p
577
613
}
578
614
}
579
615
if upperdir != "" {
580
- st = trySyscallStat (t , filepath .Join (upperdir , path ))
581
- if st != nil {
582
- return st
616
+ r := cb (t , filepath .Join (upperdir , path ))
617
+ if r != nil {
618
+ return r
583
619
}
584
620
}
585
621
for _ , lowerdir := range lowerdirs {
586
- st = trySyscallStat (t , filepath .Join (lowerdir , path ))
587
- if st != nil {
588
- return st
622
+ r := cb (t , filepath .Join (lowerdir , path ))
623
+ if r != nil {
624
+ return r
589
625
}
590
626
}
591
627
return nil
592
628
}
593
629
630
+ var r * T
594
631
withMount (ctx , t , sn , key , func (root string ) {
595
- st = trySyscallStat (t , filepath .Join (root , path ))
632
+ r = cb (t , filepath .Join (root , path ))
633
+ })
634
+ return r
635
+ }
636
+
637
+ func tryStatPath (ctx context.Context , t * testing.T , sn * mergeSnapshotter , key , path string ) * syscall.Stat_t {
638
+ t .Helper ()
639
+ return pathCallback (ctx , t , sn , key , path , func (t * testing.T , path string ) * syscall.Stat_t {
640
+ return trySyscallStat (t , path )
596
641
})
597
- return st
598
642
}
599
643
600
644
func statPath (ctx context.Context , t * testing.T , sn * mergeSnapshotter , key , path string ) (st * syscall.Stat_t ) {
@@ -610,3 +654,36 @@ func requireRoot(t *testing.T) {
610
654
t .Skip ("test requires root" )
611
655
}
612
656
}
657
+
658
+ func setFileCap (path string , caps string ) fstest.Applier {
659
+ return applyFn (func (root string ) error {
660
+ path := filepath .Join (root , path )
661
+ capSet , err := libcap .FromText (caps )
662
+ if err != nil {
663
+ return err
664
+ }
665
+ return capSet .SetFile (path )
666
+ })
667
+ }
668
+
669
+ func getFileCap (ctx context.Context , t * testing.T , sn * mergeSnapshotter , key , path string ) string {
670
+ t .Helper ()
671
+ caps := pathCallback (ctx , t , sn , key , path , func (t * testing.T , path string ) * string {
672
+ t .Helper ()
673
+ capSet , err := libcap .GetFile (path )
674
+ if err != nil {
675
+ require .ErrorIs (t , err , os .ErrNotExist )
676
+ return nil
677
+ }
678
+ caps := capSet .String ()
679
+ return & caps
680
+ })
681
+ require .NotNil (t , caps )
682
+ return * caps
683
+ }
684
+
685
+ type applyFn func (root string ) error
686
+
687
+ func (a applyFn ) Apply (root string ) error {
688
+ return a (root )
689
+ }
0 commit comments