Skip to content

Commit 3b87097

Browse files
authored
Merge pull request #3701 from jsternberg/dap-fix-can-invoke
dap: fix the check to determine whether exec will succeed
2 parents ac1a8ee + b33ef2c commit 3b87097

File tree

3 files changed

+80
-56
lines changed

3 files changed

+80
-56
lines changed

build/invoke.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import (
44
"context"
55
_ "crypto/sha256" // ensure digests can be computed
66
"io"
7+
"io/fs"
8+
"os"
9+
"path"
710
"sync"
811
"sync/atomic"
912
"syscall"
@@ -146,6 +149,57 @@ 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) error {
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 errors.New("no command specified")
162+
}
163+
164+
const symlinkResolutionLimit = 40
165+
for range symlinkResolutionLimit {
166+
fpath, index, err := c.resultCtx.inferMountIndex(cmd, cfg)
167+
if err != nil {
168+
return err
169+
}
170+
171+
st, err := c.container.StatFile(ctx, gateway.StatContainerRequest{
172+
StatRequest: gateway.StatRequest{
173+
Path: fpath,
174+
},
175+
MountIndex: index,
176+
})
177+
if err != nil {
178+
return errors.Wrapf(err, "stat error: %s", cmd)
179+
}
180+
181+
mode := fs.FileMode(st.Mode)
182+
if mode&os.ModeSymlink != 0 {
183+
// Follow the link.
184+
if path.IsAbs(st.Linkname) {
185+
cmd = st.Linkname
186+
} else {
187+
cmd = path.Join(path.Dir(fpath), st.Linkname)
188+
}
189+
continue
190+
}
191+
192+
if !mode.IsRegular() {
193+
return errors.Errorf("%s: not a file", cmd)
194+
}
195+
if mode&0o111 == 0 {
196+
return errors.Errorf("%s: not an executable", cmd)
197+
}
198+
return nil
199+
}
200+
return errors.Errorf("%s: reached symlink resolution limit", cmd)
201+
}
202+
149203
func (c *Container) ReadFile(ctx context.Context, req gateway.ReadContainerRequest) ([]byte, error) {
150204
return c.container.ReadFile(ctx, req)
151205
}

build/result.go

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"encoding/json"
88
"io"
99
iofs "io/fs"
10-
"path/filepath"
10+
"path"
1111
"slices"
1212
"strings"
1313
"sync"
@@ -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,37 @@ 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]
105-
relpath, err := filepath.Rel(m.Dest, fpath)
106-
if err != nil {
107-
return nil, err
108-
}
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)
112+
relpath := strings.TrimPrefix(fpath, m.Dest)
113+
return path.Join("/", relpath), m.Index, nil
116114
}
117115

118116
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 err := ctr.CanInvoke(ctx, cfg); err != nil {
208+
writeLineF(in.Stdout, "Build container is not executable. (reason: %s)", err)
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)