dap: fix the check to determine whether exec will succeed

This refines the check for determining whether exec will succeed to work
when an error occurs. This check previously relied on the `Ref` being
populated in the result context but this would only happen if we were
paused from a breakpoint or by stepping. An error would not fill in this
field.

The check is now refined to use the new gateway filesystem exec API so
we can create the container and then check even if we don't have a
returned gateway reference. The logic to determine which mount to check
has also been moved.

Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
This commit is contained in:
Jonathan A. Sternberg
2026-03-17 10:31:11 -05:00
parent c4b5d6766e
commit b33ef2c9d4
3 changed files with 80 additions and 56 deletions
+8 -36
View File
@@ -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.