werkator build succeeded after 00:04
Found on mih09 with Werkator 1.37.0 (repository werkrevisor, PR#5): a CI build and a pull-request review started together and both imported the same new rootfs. ## Imports of one image wait for each other `Store.Import` stages in `<image>.tmp` and removes that directory first, so the next import cleans up after an interrupted one. A second import at the same time removed the first one's files: one failed with `latest.tmp/image.json: no such file or directory`, the other's sandbox could not execute `/bin/sh`. An import now holds an exclusive `flock` on `$WERKDOCK_HOME/locks/import-<hash>.lock`, across goroutines and processes; the kernel drops it when the holder dies. The import that waited refuses with `already exists`, as an import after it would. `TestConcurrentImportsOfOneImageLeaveOneCompleteImage`: four imports of a 3000-file archive at once leave one success, three refusals and one complete image. It failed 3 of 3 runs without the lock. ## `werkdock run` answers 127 or 126 `engine.CheckCommand` looks the command up like execvp inside the rootfs (symlinks resolved in the rootfs, PATH in order, a non-executable candidate skipped for a later one). `run` refuses a command that cannot start with docker's 127 (not found) or 126 (not executable) before anything is created; until now bwrap's 1 came back. Paths under a mount, the implicit tmpfs areas included, are not checked. `create`/`start` and the Docker API are unchanged. The resolver moved from `store` to its own package `rootpath`, shared with `engine`. Smoke-tested with the built binary: `run smoke missing` exits 127, a file without execute bit 126, a second import 125 with `already exists`. Version 0.11.0 with its CHANGELOG section. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: mhoennig <michael@hoennig.de> Reviewed-on: #13
59 lines
1.7 KiB
Go
59 lines
1.7 KiB
Go
// Package rootpath maps paths inside an image to their place in the
|
|
// unpacked rootfs on the host, the way the sandbox will see them.
|
|
package rootpath
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// Resolve maps an absolute in-image path to its host location
|
|
// under rootfs, following symlinks the way the sandbox would: relative
|
|
// to the link's directory, absolute ones relative to rootfs, and ".."
|
|
// above the root stays at the root, exactly as in a chroot — so a
|
|
// hostile layer can rewrite the image, never the host. Components that
|
|
// do not exist yet are appended verbatim.
|
|
func Resolve(rootfs, name string) (string, error) {
|
|
root := filepath.Clean(rootfs)
|
|
resolved := root
|
|
remaining := strings.Split(strings.Trim(path.Clean("/"+name), "/"), "/")
|
|
for hops := 0; len(remaining) > 0; {
|
|
component := remaining[0]
|
|
remaining = remaining[1:]
|
|
switch component {
|
|
case "", ".":
|
|
continue
|
|
case "..":
|
|
if resolved != root {
|
|
resolved = filepath.Dir(resolved)
|
|
}
|
|
continue
|
|
}
|
|
next := filepath.Join(resolved, component)
|
|
fi, err := os.Lstat(next)
|
|
if err != nil || fi.Mode()&os.ModeSymlink == 0 {
|
|
resolved = next
|
|
continue
|
|
}
|
|
if hops++; hops > 64 {
|
|
return "", fmt.Errorf("%s: too many levels of symbolic links", name)
|
|
}
|
|
link, err := os.Readlink(next)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
linkParts := strings.Split(strings.Trim(path.Clean(link), "/"), "/")
|
|
if path.IsAbs(link) {
|
|
resolved = root
|
|
}
|
|
remaining = append(linkParts, remaining...)
|
|
}
|
|
if resolved != root && !strings.HasPrefix(resolved, root+string(filepath.Separator)) {
|
|
return "", fmt.Errorf("%s: escapes the rootfs", name)
|
|
}
|
|
return resolved, nil
|
|
}
|