44 "cmp"
55 "context"
66 "fmt"
7+ "io/fs"
8+ "os"
79 "path/filepath"
810 "runtime"
911 "slices"
@@ -25,6 +27,7 @@ import (
2527 "github.com/moby/buildkit/util/stack"
2628 "github.com/moby/buildkit/worker"
2729 "github.com/pkg/errors"
30+ fstypes "github.com/tonistiigi/fsutil/types"
2831 "golang.org/x/sync/errgroup"
2932)
3033
@@ -280,23 +283,28 @@ func PrepareMounts(ctx context.Context, mm *mounts.MountManager, cm cache.Manage
280283 return p , nil
281284}
282285
286+ type gatewayMount struct {
287+ mounter snapshot.Mounter
288+ }
289+
283290type gatewayContainer struct {
284- id string
285- netMode opspb.NetMode
286- hostname string
287- extraHosts []executor.HostIP
288- platform * opspb.Platform
289- rootFS executor.Mount
290- mounts []executor.Mount
291- executor executor.Executor
292- sm * session.Manager
293- group session.Group
294- started bool
295- errGroup * errgroup.Group
296- mu sync.Mutex
297- cleanup []func () error
298- ctx context.Context
299- cancel func (error )
291+ id string
292+ netMode opspb.NetMode
293+ hostname string
294+ extraHosts []executor.HostIP
295+ platform * opspb.Platform
296+ rootFS executor.Mount
297+ mounts []executor.Mount
298+ executor executor.Executor
299+ sm * session.Manager
300+ group session.Group
301+ started bool
302+ errGroup * errgroup.Group
303+ mu sync.Mutex
304+ cleanup []func () error
305+ ctx context.Context
306+ cancel func (error )
307+ localMounts map [executor.Mount ]fs.FS
300308}
301309
302310func (gwCtr * gatewayContainer ) Start (ctx context.Context , req client.StartRequest ) (client.ContainerProcess , error ) {
@@ -419,6 +427,124 @@ func (gwCtr *gatewayContainer) Release(ctx context.Context) error {
419427 return stack .Enable (err2 )
420428}
421429
430+ func (ctr * gatewayContainer ) ReadFile (ctx context.Context , req client.ReadRequest ) ([]byte , error ) {
431+ fsys , path , err := ctr .mount (ctx , req .Filename )
432+ if err != nil {
433+ return nil , err
434+ }
435+ return fs .ReadFile (fsys , path )
436+ }
437+
438+ func (ctr * gatewayContainer ) ReadDir (ctx context.Context , req client.ReadDirRequest ) ([]* fstypes.Stat , error ) {
439+ fsys , path , err := ctr .mount (ctx , req .Path )
440+ if err != nil {
441+ return nil , err
442+ }
443+
444+ entries , err := fs .ReadDir (fsys , path )
445+ if err != nil {
446+ return nil , err
447+ }
448+
449+ files := make ([]* fstypes.Stat , len (entries ))
450+ for i , e := range entries {
451+ fullpath := filepath .Join (path , e .Name ())
452+ fi , err := e .Info ()
453+ if err != nil {
454+ return nil , err
455+ }
456+
457+ files [i ], err = mkstat (fsys , fullpath , e .Name (), fi )
458+ if err != nil {
459+ return nil , errors .Wrap (err , "mkstat" )
460+ }
461+ }
462+ return files , nil
463+ }
464+
465+ func (ctr * gatewayContainer ) StatFile (ctx context.Context , req client.StatRequest ) (* fstypes.Stat , error ) {
466+ fsys , path , err := ctr .mount (ctx , req .Path )
467+ if err != nil {
468+ return nil , err
469+ }
470+
471+ fi , err := fs .Stat (fsys , path )
472+ if err != nil {
473+ return nil , err
474+ }
475+ return mkstat (fsys , req .Path , filepath .Base (req .Path ), fi )
476+ }
477+
478+ func (ctr * gatewayContainer ) mount (ctx context.Context , fullpath string ) (fs.FS , string , error ) {
479+ mount , path := ctr .findMount (ctx , fullpath )
480+
481+ ctr .mu .Lock ()
482+ defer ctr .mu .Unlock ()
483+
484+ // Check if this mount has already been mounted.
485+ if f , ok := ctr .localMounts [mount ]; ok {
486+ return f , path , nil
487+ }
488+
489+ ref , err := mount .Src .Mount (ctx , true )
490+ if err != nil {
491+ return nil , "" , err
492+ }
493+
494+ mounter := snapshot .LocalMounter (ref )
495+ dir , err := mounter .Mount ()
496+ if err != nil {
497+ return nil , "" , err
498+ }
499+
500+ // Register cleanup.
501+ ctr .cleanup = append (ctr .cleanup , func () error {
502+ return mounter .Unmount ()
503+ })
504+
505+ root , err := os .OpenRoot (dir )
506+ if err != nil {
507+ return nil , "" , err
508+ }
509+
510+ ctr .cleanup = append (ctr .cleanup , func () error {
511+ return root .Close ()
512+ })
513+
514+ if ctr .localMounts == nil {
515+ ctr .localMounts = make (map [executor.Mount ]fs.FS )
516+ }
517+
518+ f := root .FS ()
519+ ctr .localMounts [mount ] = f
520+ return f , path , nil
521+ }
522+
523+ func (ctr * gatewayContainer ) findMount (ctx context.Context , fullpath string ) (m executor.Mount , path string ) {
524+ m = ctr .rootFS
525+ path , _ = filepath .Rel ("/" , fullpath )
526+ if len (ctr .mounts ) == 0 {
527+ return m , path
528+ }
529+
530+ for _ , mount := range ctr .mounts {
531+ if strings .HasPrefix (fullpath , mount .Dest ) {
532+ remainder , err := filepath .Rel (mount .Dest , fullpath )
533+ if err != nil {
534+ bklog .G (ctx ).Warnf ("skipping mount at %q because it could not be converted into a relative path from %q" , mount .Dest , fullpath )
535+ continue
536+ }
537+
538+ if len (remainder ) < len (path ) {
539+ // Prefix matches and the remaining path is shorter so the prefix
540+ // must be longer. This match works better.
541+ m , path = mount , remainder
542+ }
543+ }
544+ }
545+ return m , path
546+ }
547+
422548type gatewayContainerProcess struct {
423549 errGroup * errgroup.Group
424550 groupCtx context.Context
@@ -511,3 +637,44 @@ type mountable struct {
511637func (m * mountable ) Mount (ctx context.Context , readonly bool ) (snapshot.Mountable , error ) {
512638 return m .m .Mount (ctx , readonly , m .g )
513639}
640+
641+ // constructs a Stat object. path is where the path can be found right
642+ // now, relpath is the desired path to be recorded in the stat (so
643+ // relative to whatever base dir is relevant). fi is the os.Stat
644+ // info. inodemap is used to calculate hardlinks over a series of
645+ // mkstat calls and maps inode to the canonical (aka "first") path for
646+ // a set of hardlinks to that inode.
647+ func mkstat (fsys fs.FS , path , relpath string , fi os.FileInfo ) (* fstypes.Stat , error ) {
648+ relpath = filepath .ToSlash (relpath )
649+
650+ stat := & fstypes.Stat {
651+ Path : filepath .FromSlash (relpath ),
652+ Mode : uint32 (fi .Mode ()),
653+ ModTime : fi .ModTime ().UnixNano (),
654+ }
655+
656+ if ! fi .IsDir () {
657+ stat .Size = fi .Size ()
658+ if fi .Mode ()& os .ModeSymlink != 0 {
659+ link , err := fs .ReadLink (fsys , path )
660+ if err != nil {
661+ return nil , errors .WithStack (err )
662+ }
663+ stat .Linkname = link
664+ }
665+ }
666+
667+ if runtime .GOOS == "windows" {
668+ permPart := stat .Mode & uint32 (os .ModePerm )
669+ noPermPart := stat .Mode &^ uint32 (os .ModePerm )
670+ // Add the x bit: make everything +x from windows
671+ permPart |= 0111
672+ permPart &= 0755
673+ stat .Mode = noPermPart | permPart
674+ }
675+
676+ // Clear the socket bit since archive/tar.FileInfoHeader does not handle it
677+ stat .Mode &^= uint32 (os .ModeSocket )
678+
679+ return stat , nil
680+ }
0 commit comments