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:
Tõnis Tiigi
2026-03-17 09:36:00 -07:00
committed by GitHub
3 changed files with 80 additions and 56 deletions
+54
View File
@@ -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
View File
@@ -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) {