Merge pull request #3701 from jsternberg/dap-fix-can-invoke
dap: fix the check to determine whether exec will succeed
This commit is contained in:
@@ -4,6 +4,9 @@ import (
|
||||
"context"
|
||||
_ "crypto/sha256" // ensure digests can be computed
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
+18
-20
@@ -7,7 +7,7 @@ import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
iofs "io/fs"
|
||||
"path/filepath"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
|
||||
candidateMounts := make([]gateway.Mount, 0, len(containerCfg.Mounts))
|
||||
for _, m := range containerCfg.Mounts {
|
||||
type mountCandidate struct {
|
||||
gateway.Mount
|
||||
Index int
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
+8
-36
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user