Skip to content

Commit 99f366e

Browse files
committed
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>
1 parent d315c08 commit 99f366e

File tree

3 files changed

+73
-52
lines changed

3 files changed

+73
-52
lines changed

build/invoke.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ package build
33
import (
44
"context"
55
_ "crypto/sha256" // ensure digests can be computed
6+
"fmt"
67
"io"
8+
"io/fs"
9+
"os"
710
"sync"
811
"sync/atomic"
912
"syscall"
@@ -146,6 +149,51 @@ func (c *Container) Exec(ctx context.Context, cfg *InvokeConfig, stdin io.ReadCl
146149
return err
147150
}
148151

152+
func (c *Container) CanInvoke(ctx context.Context, cfg *InvokeConfig) (string, bool) {
153+
var cmd string
154+
if len(cfg.Entrypoint) > 0 {
155+
cmd = cfg.Entrypoint[0]
156+
} else if len(cfg.Cmd) > 0 {
157+
cmd = cfg.Cmd[0]
158+
}
159+
160+
if cmd == "" {
161+
return "no command specified", false
162+
}
163+
164+
for {
165+
path, index, err := c.resultCtx.inferMountIndex(cmd, cfg)
166+
if err != nil {
167+
return err.Error(), false
168+
}
169+
170+
st, err := c.container.StatFile(ctx, gateway.StatContainerRequest{
171+
StatRequest: gateway.StatRequest{
172+
Path: path,
173+
},
174+
MountIndex: index,
175+
})
176+
if err != nil {
177+
return fmt.Sprintf("stat error %s: %s", cmd, err), false
178+
}
179+
180+
mode := fs.FileMode(st.Mode)
181+
if mode&os.ModeSymlink != 0 {
182+
// Follow the link.
183+
cmd = st.Linkname
184+
continue
185+
}
186+
187+
if !mode.IsRegular() {
188+
return fmt.Sprintf("%s: not a file", cmd), false
189+
}
190+
if mode&0o111 == 0 {
191+
return fmt.Sprintf("%s: not an executable", cmd), false
192+
}
193+
return "", true
194+
}
195+
}
196+
149197
func (c *Container) ReadFile(ctx context.Context, req gateway.ReadContainerRequest) ([]byte, error) {
150198
return c.container.ReadFile(ctx, req)
151199
}

build/result.go

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import (
1919
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
2020
"github.com/pkg/errors"
2121
"github.com/sirupsen/logrus"
22-
"github.com/tonistiigi/fsutil/types"
2322
)
2423

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

84-
func (r *ResultHandle) StatFile(ctx context.Context, fpath string, cfg *InvokeConfig) (*types.Stat, error) {
83+
func (r *ResultHandle) inferMountIndex(fpath string, cfg *InvokeConfig) (string, int, error) {
8584
containerCfg, err := r.getContainerConfig(cfg)
8685
if err != nil {
87-
return nil, err
86+
return "", 0, err
87+
}
88+
89+
type mountCandidate struct {
90+
gateway.Mount
91+
Index int
8892
}
8993

90-
candidateMounts := make([]gateway.Mount, 0, len(containerCfg.Mounts))
91-
for _, m := range containerCfg.Mounts {
94+
candidateMounts := make([]mountCandidate, 0, len(containerCfg.Mounts))
95+
for i, m := range containerCfg.Mounts {
9296
if strings.HasPrefix(fpath, m.Dest) {
93-
candidateMounts = append(candidateMounts, m)
97+
candidateMounts = append(candidateMounts, mountCandidate{
98+
Mount: m,
99+
Index: i,
100+
})
94101
}
95102
}
96103
if len(candidateMounts) == 0 {
97-
return nil, iofs.ErrNotExist
104+
return "", 0, iofs.ErrNotExist
98105
}
99106

100-
slices.SortFunc(candidateMounts, func(a, b gateway.Mount) int {
107+
slices.SortFunc(candidateMounts, func(a, b mountCandidate) int {
101108
return cmp.Compare(len(a.Dest), len(b.Dest))
102109
})
103110

104111
m := candidateMounts[len(candidateMounts)-1]
105112
relpath, err := filepath.Rel(m.Dest, fpath)
106113
if err != nil {
107-
return nil, err
114+
return "", 0, err
108115
}
109-
110-
if m.Ref == nil {
111-
return nil, iofs.ErrNotExist
112-
}
113-
114-
req := gateway.StatRequest{Path: filepath.ToSlash(relpath)}
115-
return m.Ref.StatFile(ctx, req)
116+
return filepath.Join("/", relpath), m.Index, nil
116117
}
117118

118119
func (r *ResultHandle) getContainerConfig(cfg *InvokeConfig) (containerCfg gateway.NewContainerRequest, _ error) {

dap/debug_shell.go

Lines changed: 8 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"fmt"
66
"io"
7-
"io/fs"
87
"net"
98
"os"
109
"path/filepath"
@@ -192,14 +191,6 @@ func (s *shell) attach(ctx context.Context, f dap.StackFrame, rCtx *build.Result
192191
}
193192
}()
194193

195-
// Check if the entrypoint is executable. If it isn't, don't bother
196-
// trying to invoke.
197-
if reason, ok := s.canInvoke(ctx, rCtx, cfg); !ok {
198-
writeLineF(in.Stdout, "Build container is not executable. (reason: %s)", reason)
199-
<-ctx.Done()
200-
return context.Cause(ctx)
201-
}
202-
203194
if err := s.sem.Acquire(ctx, 1); err != nil {
204195
return err
205196
}
@@ -211,6 +202,14 @@ func (s *shell) attach(ctx context.Context, f dap.StackFrame, rCtx *build.Result
211202
}
212203
defer ctr.Cancel()
213204

205+
// Check if the entrypoint is executable. If it isn't, don't bother
206+
// trying to invoke.
207+
if reason, ok := ctr.CanInvoke(ctx, cfg); !ok {
208+
writeLineF(in.Stdout, "Build container is not executable. (reason: %s)", reason)
209+
<-ctx.Done()
210+
return context.Cause(ctx)
211+
}
212+
214213
writeLineF(in.Stdout, "Running %s in build container from line %d.",
215214
strings.Join(append(cfg.Entrypoint, cfg.Cmd...), " "),
216215
f.Line,
@@ -231,33 +230,6 @@ func (s *shell) attach(ctx context.Context, f dap.StackFrame, rCtx *build.Result
231230
return nil
232231
}
233232

234-
func (s *shell) canInvoke(ctx context.Context, rCtx *build.ResultHandle, cfg *build.InvokeConfig) (reason string, ok bool) {
235-
var cmd string
236-
if len(cfg.Entrypoint) > 0 {
237-
cmd = cfg.Entrypoint[0]
238-
} else if len(cfg.Cmd) > 0 {
239-
cmd = cfg.Cmd[0]
240-
}
241-
242-
if cmd == "" {
243-
return "no command specified", false
244-
}
245-
246-
st, err := rCtx.StatFile(ctx, cmd, cfg)
247-
if err != nil {
248-
return fmt.Sprintf("stat error: %s", err), false
249-
}
250-
251-
mode := fs.FileMode(st.Mode)
252-
if !mode.IsRegular() {
253-
return fmt.Sprintf("%s: not a file", cmd), false
254-
}
255-
if mode&0111 == 0 {
256-
return fmt.Sprintf("%s: not an executable", cmd), false
257-
}
258-
return "", true
259-
}
260-
261233
// SendRunInTerminalRequest will send the request to the client to attach to
262234
// the socket path that was created by Init. This is intended to be run
263235
// from the adapter and interact directly with the client.

0 commit comments

Comments
 (0)