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:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user