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