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
@@ -104,6 +107,26 @@ func NewContainer(ctx context.Context, cm cache.Manager, exec executor.Executor,
104107 ctr .rootFS = p .Root
105108 ctr .mounts = p .Mounts
106109
110+ // Setup the local mounts. These need to be in the same order as the original
111+ // parameters.
112+ ctr .localMounts = make ([]gatewayContainerMount , len (mnts ))
113+ for i , m := range mnts {
114+ if m .Dest == "/" {
115+ ctr .localMounts [i ].Src = p .Root .Src
116+ continue
117+ }
118+
119+ index := slices .IndexFunc (p .Mounts , func (em executor.Mount ) bool {
120+ return em .Dest == m .Dest
121+ })
122+ if index < 0 {
123+ // This shouldn't happen but handle it just in case.
124+ continue
125+ }
126+
127+ ctr .localMounts [i ].Src = p .Mounts [index ].Src
128+ }
129+
107130 for _ , o := range p .OutputRefs {
108131 ctr .cleanup = append (ctr .cleanup , func () error {
109132 return o .Ref .Release (context .TODO ())
@@ -281,22 +304,23 @@ func PrepareMounts(ctx context.Context, mm *mounts.MountManager, cm cache.Manage
281304}
282305
283306type 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 )
307+ id string
308+ netMode opspb.NetMode
309+ hostname string
310+ extraHosts []executor.HostIP
311+ platform * opspb.Platform
312+ rootFS executor.Mount
313+ mounts []executor.Mount
314+ executor executor.Executor
315+ sm * session.Manager
316+ group session.Group
317+ started bool
318+ errGroup * errgroup.Group
319+ mu sync.Mutex
320+ cleanup []func () error
321+ ctx context.Context
322+ cancel func (error )
323+ localMounts []gatewayContainerMount
300324}
301325
302326func (gwCtr * gatewayContainer ) Start (ctx context.Context , req client.StartRequest ) (client.ContainerProcess , error ) {
@@ -419,6 +443,122 @@ func (gwCtr *gatewayContainer) Release(ctx context.Context) error {
419443 return stack .Enable (err2 )
420444}
421445
446+ func (gwCtr * gatewayContainer ) ReadFile (ctx context.Context , req client.ReadContainerRequest ) ([]byte , error ) {
447+ fsys , err := gwCtr .mount (ctx , req .Index )
448+ if err != nil {
449+ return nil , err
450+ }
451+
452+ path , err := filepath .Rel ("/" , req .Filename )
453+ if err != nil {
454+ return nil , err
455+ }
456+ return fs .ReadFile (fsys , path )
457+ }
458+
459+ func (gwCtr * gatewayContainer ) ReadDir (ctx context.Context , req client.ReadDirContainerRequest ) ([]* fstypes.Stat , error ) {
460+ fsys , err := gwCtr .mount (ctx , req .Index )
461+ if err != nil {
462+ return nil , err
463+ }
464+
465+ path , err := filepath .Rel ("/" , req .Path )
466+ if err != nil {
467+ return nil , err
468+ }
469+
470+ entries , err := fs .ReadDir (fsys , path )
471+ if err != nil {
472+ return nil , err
473+ }
474+
475+ files := make ([]* fstypes.Stat , len (entries ))
476+ for i , e := range entries {
477+ fullpath := filepath .Join (req .Path , e .Name ())
478+ fi , err := e .Info ()
479+ if err != nil {
480+ return nil , err
481+ }
482+
483+ files [i ], err = mkstat (fsys , fullpath , e .Name (), fi )
484+ if err != nil {
485+ return nil , errors .Wrap (err , "mkstat" )
486+ }
487+ }
488+ return files , nil
489+ }
490+
491+ func (gwCtr * gatewayContainer ) StatFile (ctx context.Context , req client.StatContainerRequest ) (* fstypes.Stat , error ) {
492+ fsys , err := gwCtr .mount (ctx , req .Index )
493+ if err != nil {
494+ return nil , err
495+ }
496+
497+ path , err := filepath .Rel ("/" , req .Path )
498+ if err != nil {
499+ return nil , err
500+ }
501+
502+ fi , err := fs .Stat (fsys , path )
503+ if err != nil {
504+ return nil , err
505+ }
506+ return mkstat (fsys , req .Path , filepath .Base (req .Path ), fi )
507+ }
508+
509+ func (gwCtr * gatewayContainer ) mount (ctx context.Context , index int ) (fs.FS , error ) {
510+ // No lock needed for this because the number of mounts does
511+ // not change.
512+ if index < 0 || index >= len (gwCtr .localMounts ) {
513+ return nil , errors .Errorf ("mount index %d is out of bounds (%d available)" , index , len (gwCtr .localMounts ))
514+ }
515+
516+ gwCtr .mu .Lock ()
517+ defer gwCtr .mu .Unlock ()
518+
519+ mount := gwCtr .localMounts [index ]
520+
521+ // Already mounted?
522+ if mount .FS != nil {
523+ return mount .FS , nil
524+ }
525+
526+ // Defensively check that this mount really exists.
527+ if mount .Src == nil {
528+ return nil , errors .Errorf ("mountable %d not found" , index )
529+ }
530+
531+ // Need to mount an instance.
532+ ref , err := mount .Src .Mount (ctx , true )
533+ if err != nil {
534+ return nil , err
535+ }
536+
537+ mounter := snapshot .LocalMounter (ref )
538+ dir , err := mounter .Mount ()
539+ if err != nil {
540+ return nil , err
541+ }
542+
543+ // Register cleanup.
544+ gwCtr .cleanup = append (gwCtr .cleanup , func () error {
545+ return mounter .Unmount ()
546+ })
547+
548+ root , err := os .OpenRoot (dir )
549+ if err != nil {
550+ return nil , err
551+ }
552+
553+ gwCtr .cleanup = append (gwCtr .cleanup , func () error {
554+ return root .Close ()
555+ })
556+
557+ f := root .FS ()
558+ gwCtr .localMounts [index ].FS = f
559+ return f , nil
560+ }
561+
422562type gatewayContainerProcess struct {
423563 errGroup * errgroup.Group
424564 groupCtx context.Context
@@ -511,3 +651,49 @@ type mountable struct {
511651func (m * mountable ) Mount (ctx context.Context , readonly bool ) (snapshot.Mountable , error ) {
512652 return m .m .Mount (ctx , readonly , m .g )
513653}
654+
655+ // constructs a Stat object. path is where the path can be found right
656+ // now, relpath is the desired path to be recorded in the stat (so
657+ // relative to whatever base dir is relevant). fi is the os.Stat
658+ // info. inodemap is used to calculate hardlinks over a series of
659+ // mkstat calls and maps inode to the canonical (aka "first") path for
660+ // a set of hardlinks to that inode.
661+ func mkstat (fsys fs.FS , path , relpath string , fi os.FileInfo ) (* fstypes.Stat , error ) {
662+ relpath = filepath .ToSlash (relpath )
663+
664+ stat := & fstypes.Stat {
665+ Path : filepath .FromSlash (relpath ),
666+ Mode : uint32 (fi .Mode ()),
667+ ModTime : fi .ModTime ().UnixNano (),
668+ }
669+
670+ if ! fi .IsDir () {
671+ stat .Size = fi .Size ()
672+ if fi .Mode ()& os .ModeSymlink != 0 {
673+ link , err := fs .ReadLink (fsys , path )
674+ if err != nil {
675+ return nil , errors .WithStack (err )
676+ }
677+ stat .Linkname = link
678+ }
679+ }
680+
681+ if runtime .GOOS == "windows" {
682+ permPart := stat .Mode & uint32 (os .ModePerm )
683+ noPermPart := stat .Mode &^ uint32 (os .ModePerm )
684+ // Add the x bit: make everything +x from windows
685+ permPart |= 0o111
686+ permPart &= 0o755
687+ stat .Mode = noPermPart | permPart
688+ }
689+
690+ // Clear the socket bit since archive/tar.FileInfoHeader does not handle it
691+ stat .Mode &^= uint32 (os .ModeSocket )
692+
693+ return stat , nil
694+ }
695+
696+ type gatewayContainerMount struct {
697+ Src executor.Mountable
698+ FS fs.FS
699+ }
0 commit comments