Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7e7602207 | ||
|
|
9e4d3fdb54 |
@@ -33,6 +33,14 @@ latest run passes.
|
||||
create or change anything elsewhere, and `DATA_DIR=/etc` no longer gives
|
||||
`/etc` to `netwatch`. The `README.md` section "Running under upaas" says which
|
||||
values are accepted
|
||||
- 2026-10-03: `backend/script/lint` says what went wrong with its
|
||||
`.golangci.yml` check (issue #34). On a hash mismatch it says to compare the
|
||||
file with the org standard: if they differ, restore the org standard; if they
|
||||
are the same, the org standard changed, so update `GOLANGCI_CONFIG_SHA256` in
|
||||
that script. It used to say only to restore the file, which loops once the org
|
||||
standard itself has moved. A missing `.golangci.yml`, and a `sha256sum` that
|
||||
is missing or prints no hash, each get their own message instead of being
|
||||
reported as a mismatch; every one still fails the lint
|
||||
- 2026-10-03: the Go tests run with the race detector and coverage (issue #88):
|
||||
`backend/script/test` runs `go test -timeout 30s -race -cover ./...` and, if
|
||||
that fails, runs it again with `-v` and fails. Go's `-timeout` bounds the
|
||||
|
||||
@@ -27,11 +27,15 @@ var ErrDataDirOutsideVolume = errors.New(
|
||||
// volume: it follows a link only when it is written as a relative
|
||||
// path that stays inside volume, and refuses any other. A process on
|
||||
// the host can swap a link onto a path in volume at any moment while
|
||||
// this runs. Even then, the os.Root checks each link as it reaches it,
|
||||
// MkdirAll never creates a directory through a link, Lchown never
|
||||
// changes what a link points to, and the modes are set on directories
|
||||
// already opened (see chmodDir), so the most that process can do is
|
||||
// make a step fail or wait, or act on something else inside volume.
|
||||
// this runs. Even then, the os.Root checks each link as it reaches
|
||||
// it. MkdirAll creates each directory inside a parent it already has
|
||||
// open, never following a link at the name it creates, and follows a
|
||||
// link on the path only as the os.Root allows, so a relative link
|
||||
// inside volume can lead it to create directories elsewhere inside
|
||||
// volume. Lchown never changes what a link points to, and the modes
|
||||
// are set on directories already opened (see chmodDir), so the most
|
||||
// that process can do is make a step fail or wait, or act on
|
||||
// something else inside volume.
|
||||
func PrepareDataDir(volume, dir string, owner *user.User) error {
|
||||
// rel is dir as a path from volume; IsLocal is false for one that
|
||||
// leads out of it.
|
||||
@@ -62,16 +66,7 @@ func PrepareDataDir(volume, dir string, owner *user.User) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// WalkDir does not follow a symbolic link, and Lchown gives the
|
||||
// link itself to owner, not what it points to.
|
||||
err = fs.WalkDir(root.FS(), ".",
|
||||
func(name string, _ fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return root.Lchown(name, uid, gid)
|
||||
})
|
||||
err = lchownAll(root, ".", uid, gid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -84,6 +79,48 @@ func PrepareDataDir(volume, dir string, owner *user.User) error {
|
||||
return chmodDir(root, rel)
|
||||
}
|
||||
|
||||
// lchownAll gives name, a directory inside root, and everything in it
|
||||
// to uid and gid. It reads each directory opened through root, not
|
||||
// through root.FS(), which refuses a name that is not valid UTF-8, and
|
||||
// calls Lchown on every entry, which gives a symbolic link itself to
|
||||
// them, not what it points to. It goes into an entry only when the
|
||||
// read found a directory there, so it follows no link it finds; one
|
||||
// swapped in for that directory afterwards is followed only as the
|
||||
// os.Root allows.
|
||||
func lchownAll(root *os.Root, name string, uid, gid int) error {
|
||||
err := root.Lchown(name, uid, gid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dir, err := root.Open(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
entries, err := dir.ReadDir(-1)
|
||||
_ = dir.Close()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
entryName := filepath.Join(name, entry.Name())
|
||||
if entry.IsDir() {
|
||||
err = lchownAll(root, entryName, uid, gid)
|
||||
} else {
|
||||
err = root.Lchown(entryName, uid, gid)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// chmodDir gives name, a directory inside root, the mode the server
|
||||
// gives a directory it creates. Root.Chmod would not hold: on Linux it
|
||||
// checks that name is not a symbolic link, then sets the mode by name,
|
||||
|
||||
@@ -218,6 +218,42 @@ func TestPrepareDataDirRefusesDanglingLink(t *testing.T) {
|
||||
requireMissing(t, filepath.Join(volume, "missing"))
|
||||
}
|
||||
|
||||
// TestPrepareDataDirTakesDirectoryNamedInLatin1: a host directory can
|
||||
// hold names that are not valid UTF-8, here "café" written in Latin-1.
|
||||
// A test not run as root can only check that PrepareDataDir goes into
|
||||
// such a directory and gives it, and what it holds, to the current
|
||||
// user.
|
||||
func TestPrepareDataDirTakesDirectoryNamedInLatin1(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
volume := t.TempDir()
|
||||
latin1 := filepath.Join(volume, "caf\xe9")
|
||||
|
||||
err := os.Mkdir(latin1, 0o700)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(filepath.Join(latin1, "f"), nil, 0o600)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
owner := currentUser()
|
||||
|
||||
err = reportbuf.PrepareDataDir(volume, filepath.Join(volume, reports),
|
||||
owner)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
uid, _ := strconv.ParseUint(owner.Uid, 10, 32)
|
||||
gid, _ := strconv.ParseUint(owner.Gid, 10, 32)
|
||||
|
||||
requireOwner(t, latin1, uint32(uid), uint32(gid))
|
||||
requireOwner(t, filepath.Join(latin1, "f"), uint32(uid), uint32(gid))
|
||||
}
|
||||
|
||||
// TestPrepareDataDirGivesVolumeToOwner gives everything in the volume
|
||||
// to a uid and gid that own nothing, which only root can do. A
|
||||
// symbolic link in the volume to a directory outside it is given to
|
||||
|
||||
+20
-2
@@ -14,16 +14,34 @@ set -eu
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||
|
||||
# The sha256 of the org standard .golangci.yml. When that file changes in
|
||||
# sneak/prompts and is copied here again, this changes with it.
|
||||
GOLANGCI_CONFIG_SHA256="a79b63a254602a5318db5d0e9a06bc71b84bf0c1d896305229d8bfed1d1b1776"
|
||||
|
||||
main() {
|
||||
cd "$ROOT"
|
||||
if [ ! -f .golangci.yml ]; then
|
||||
echo "backend/.golangci.yml is missing. Copy the org standard verbatim" >&2
|
||||
echo "from https://git.eeqj.de/sneak/prompts/raw/branch/main/.golangci.yml" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual="$(sha256sum .golangci.yml | cut -d' ' -f1)"
|
||||
if [ -z "$actual" ]; then
|
||||
echo "sha256sum is missing or printed no hash, so" >&2
|
||||
echo "backend/.golangci.yml could not be checked." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ "$actual" != "$GOLANGCI_CONFIG_SHA256" ]; then
|
||||
echo ".golangci.yml has drifted from the org standard." >&2
|
||||
echo "backend/.golangci.yml does not match GOLANGCI_CONFIG_SHA256" >&2
|
||||
echo "in backend/script/lint." >&2
|
||||
echo " expected $GOLANGCI_CONFIG_SHA256" >&2
|
||||
echo " actual $actual" >&2
|
||||
echo "Restore it verbatim from sneak/prompts; do not edit it." >&2
|
||||
echo "Compare it with the org standard," >&2
|
||||
echo "https://git.eeqj.de/sneak/prompts/raw/branch/main/.golangci.yml" >&2
|
||||
echo "- If they differ, it was edited here: restore the org standard" >&2
|
||||
echo " verbatim. Do not edit it." >&2
|
||||
echo "- If they are the same, the org standard changed: set" >&2
|
||||
echo " GOLANGCI_CONFIG_SHA256 in backend/script/lint to the actual hash." >&2
|
||||
exit 1
|
||||
fi
|
||||
golangci-lint run ./...
|
||||
|
||||
Reference in New Issue
Block a user