Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions build/invoke.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import (
"context"
_ "crypto/sha256" // ensure digests can be computed
"io"
"io/fs"
"os"
"path"
"sync"
"sync/atomic"
"syscall"
Expand Down Expand Up @@ -146,6 +149,57 @@ func (c *Container) Exec(ctx context.Context, cfg *InvokeConfig, stdin io.ReadCl
return err
}

func (c *Container) CanInvoke(ctx context.Context, cfg *InvokeConfig) error {
var cmd string
if len(cfg.Entrypoint) > 0 {
cmd = cfg.Entrypoint[0]
} else if len(cfg.Cmd) > 0 {
cmd = cfg.Cmd[0]
}

if cmd == "" {
return errors.New("no command specified")
}

const symlinkResolutionLimit = 40
for range symlinkResolutionLimit {
fpath, index, err := c.resultCtx.inferMountIndex(cmd, cfg)
if err != nil {
return err
}

st, err := c.container.StatFile(ctx, gateway.StatContainerRequest{
StatRequest: gateway.StatRequest{
Path: fpath,
},
MountIndex: index,
})
if err != nil {
return errors.Wrapf(err, "stat error: %s", cmd)
}

mode := fs.FileMode(st.Mode)
if mode&os.ModeSymlink != 0 {
// Follow the link.
if path.IsAbs(st.Linkname) {
cmd = st.Linkname
} else {
cmd = path.Join(path.Dir(fpath), st.Linkname)
}
continue
}

if !mode.IsRegular() {
return errors.Errorf("%s: not a file", cmd)
}
if mode&0o111 == 0 {
return errors.Errorf("%s: not an executable", cmd)
}
return nil
}
return errors.Errorf("%s: reached symlink resolution limit", cmd)
}

func (c *Container) ReadFile(ctx context.Context, req gateway.ReadContainerRequest) ([]byte, error) {
return c.container.ReadFile(ctx, req)
}
Expand Down
38 changes: 18 additions & 20 deletions build/result.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"encoding/json"
"io"
iofs "io/fs"
"path/filepath"
"path"
"slices"
"strings"
"sync"
Expand All @@ -19,7 +19,6 @@ import (
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/tonistiigi/fsutil/types"
)

// NewResultHandle stores a gateway client, gateway reference, and the error from
Expand Down Expand Up @@ -81,38 +80,37 @@ func (r *ResultHandle) NewContainer(ctx context.Context, cfg *InvokeConfig) (gat
return r.gwClient.NewContainer(ctx, req)
}

func (r *ResultHandle) StatFile(ctx context.Context, fpath string, cfg *InvokeConfig) (*types.Stat, error) {
func (r *ResultHandle) inferMountIndex(fpath string, cfg *InvokeConfig) (string, int, error) {
containerCfg, err := r.getContainerConfig(cfg)
if err != nil {
return nil, err
return "", 0, err
}

type mountCandidate struct {
gateway.Mount
Index int
}

candidateMounts := make([]gateway.Mount, 0, len(containerCfg.Mounts))
for _, m := range containerCfg.Mounts {
candidateMounts := make([]mountCandidate, 0, len(containerCfg.Mounts))
for i, m := range containerCfg.Mounts {
if strings.HasPrefix(fpath, m.Dest) {
candidateMounts = append(candidateMounts, m)
candidateMounts = append(candidateMounts, mountCandidate{
Mount: m,
Index: i,
})
}
}
if len(candidateMounts) == 0 {
return nil, iofs.ErrNotExist
return "", 0, iofs.ErrNotExist
}

slices.SortFunc(candidateMounts, func(a, b gateway.Mount) int {
slices.SortFunc(candidateMounts, func(a, b mountCandidate) int {
return cmp.Compare(len(a.Dest), len(b.Dest))
})

m := candidateMounts[len(candidateMounts)-1]
relpath, err := filepath.Rel(m.Dest, fpath)
if err != nil {
return nil, err
}

if m.Ref == nil {
return nil, iofs.ErrNotExist
}

req := gateway.StatRequest{Path: filepath.ToSlash(relpath)}
return m.Ref.StatFile(ctx, req)
relpath := strings.TrimPrefix(fpath, m.Dest)
return path.Join("/", relpath), m.Index, nil
}

func (r *ResultHandle) getContainerConfig(cfg *InvokeConfig) (containerCfg gateway.NewContainerRequest, _ error) {
Expand Down
44 changes: 8 additions & 36 deletions dap/debug_shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"fmt"
"io"
"io/fs"
"net"
"os"
"path/filepath"
Expand Down Expand Up @@ -192,14 +191,6 @@ func (s *shell) attach(ctx context.Context, f dap.StackFrame, rCtx *build.Result
}
}()

// Check if the entrypoint is executable. If it isn't, don't bother
// trying to invoke.
if reason, ok := s.canInvoke(ctx, rCtx, cfg); !ok {
writeLineF(in.Stdout, "Build container is not executable. (reason: %s)", reason)
<-ctx.Done()
return context.Cause(ctx)
}

if err := s.sem.Acquire(ctx, 1); err != nil {
return err
}
Expand All @@ -211,6 +202,14 @@ func (s *shell) attach(ctx context.Context, f dap.StackFrame, rCtx *build.Result
}
defer ctr.Cancel()

// Check if the entrypoint is executable. If it isn't, don't bother
// trying to invoke.
if err := ctr.CanInvoke(ctx, cfg); err != nil {
writeLineF(in.Stdout, "Build container is not executable. (reason: %s)", err)
<-ctx.Done()
return context.Cause(ctx)
}

writeLineF(in.Stdout, "Running %s in build container from line %d.",
strings.Join(append(cfg.Entrypoint, cfg.Cmd...), " "),
f.Line,
Expand All @@ -231,33 +230,6 @@ func (s *shell) attach(ctx context.Context, f dap.StackFrame, rCtx *build.Result
return nil
}

func (s *shell) canInvoke(ctx context.Context, rCtx *build.ResultHandle, cfg *build.InvokeConfig) (reason string, ok bool) {
var cmd string
if len(cfg.Entrypoint) > 0 {
cmd = cfg.Entrypoint[0]
} else if len(cfg.Cmd) > 0 {
cmd = cfg.Cmd[0]
}

if cmd == "" {
return "no command specified", false
}

st, err := rCtx.StatFile(ctx, cmd, cfg)
if err != nil {
return fmt.Sprintf("stat error: %s", err), false
}

mode := fs.FileMode(st.Mode)
if !mode.IsRegular() {
return fmt.Sprintf("%s: not a file", cmd), false
}
if mode&0111 == 0 {
return fmt.Sprintf("%s: not an executable", cmd), false
}
return "", true
}

// SendRunInTerminalRequest will send the request to the client to attach to
// the socket path that was created by Init. This is intended to be run
// from the adapter and interact directly with the client.
Expand Down
Loading