Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package fscrypt for openSUSE:Factory checked in at 2026-08-25 15:54:27 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/fscrypt (Old) and /work/SRC/openSUSE:Factory/.fscrypt.new.1258 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "fscrypt" Tue Aug 25 15:54:27 2026 rev:7 rq:1373568 version:0.3.7 Changes: -------- --- /work/SRC/openSUSE:Factory/fscrypt/fscrypt.changes 2025-11-08 16:36:30.113161497 +0100 +++ /work/SRC/openSUSE:Factory/.fscrypt.new.1258/fscrypt.changes 2026-08-25 15:54:29.471289896 +0200 @@ -1,0 +2,21 @@ +Sat Aug 22 19:53:30 UTC 2026 - Marcus Rueckert <[email protected]> + +- update to 0.3.7: + - Upgraded various dependencies, including golang.org/x/crypto to + resolve the usual CVEs in it (but as usual, not actually + affecting fscrypt's use of it). + - When selecting password hashing parameters, fscrypt now takes + cgroup limitations into consideration. + - fscrypt encrypt no longer follows trailing symlinks when + writing the recovery instructions. + - fscrypt unlock no longer enters an infinite loop when an + incorrect key file is specified using --key=FILE, --quiet isn't + specified, and standard input is a terminal. + - fscrypt unlock no longer enters an infinite loop when an + incorrect password is specified, --quiet isn't specified, and + standard input isn't a terminal. + - The error message when multiple protectors are available now + mentions --unlock-with in addition to --protector. + - Documented the udev dependency for /dev/disk/by-uuid/ links. + +------------------------------------------------------------------- Old: ---- fscrypt-0.3.6.tar.gz New: ---- _service fscrypt-0.3.7.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ fscrypt.spec ++++++ --- /var/tmp/diff_new_pack.Sfb3QA/_old 2026-08-25 15:54:31.005344707 +0200 +++ /var/tmp/diff_new_pack.Sfb3QA/_new 2026-08-25 15:54:31.013344993 +0200 @@ -1,7 +1,7 @@ # # spec file for package fscrypt # -# Copyright (c) 2025 SUSE LLC and contributors +# Copyright (c) 2026 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,7 +17,7 @@ Name: fscrypt -Version: 0.3.6 +Version: 0.3.7 Release: 0 Summary: Go tool for managing Linux filesystem encryption License: Apache-2.0 ++++++ _service ++++++ <services> <service name="download_files" mode="manual"/> <service name="go_modules" mode="manual"> <param name="compression">xz</param> </service> </services> ++++++ fscrypt-0.3.6.tar.gz -> fscrypt-0.3.7.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fscrypt-0.3.6/.github/workflows/ci.yml new/fscrypt-0.3.7/.github/workflows/ci.yml --- old/fscrypt-0.3.6/.github/workflows/ci.yml 2025-11-04 23:08:18.000000000 +0100 +++ new/fscrypt-0.3.7/.github/workflows/ci.yml 2026-08-22 17:57:07.000000000 +0200 @@ -106,6 +106,26 @@ # make test # make test-teardown + test-cgroup-integration: + name: Test cgroup integration + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v2 + with: + go-version: '1.25' + - name: Build test binary + run: go test -c -o cgroup.test ./cgroup/ + - name: Run cgroup integration test + run: | + docker run --rm \ + --cpus=0.5 --memory=128m \ + -e CGROUP_EXPECTED_CPU_QUOTA=0.5 \ + -e CGROUP_EXPECTED_MEMORY_LIMIT=134217728 \ + -v "$PWD/cgroup.test":/cgroup.test:ro \ + debian:bookworm-slim \ + /cgroup.test -test.run TestIntegrationCgroupLimits -test.v + run-cli-tests: name: Run command-line interface tests runs-on: ubuntu-latest diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fscrypt-0.3.6/Makefile new/fscrypt-0.3.7/Makefile --- old/fscrypt-0.3.6/Makefile 2025-11-04 23:08:18.000000000 +0100 +++ new/fscrypt-0.3.7/Makefile 2026-08-22 17:57:07.000000000 +0200 @@ -16,7 +16,7 @@ # the License. # Update this on each new release, along with the NEWS.md file. -VERSION := v0.3.6 +VERSION := v0.3.7 NAME := fscrypt PAM_NAME := pam_$(NAME) @@ -115,6 +115,11 @@ clean: rm -f $(BIN)/$(NAME) $(PAM_MODULE) $(TOOLS) coverage.out $(COVERAGE_FILES) $(PAM_CONFIG) +###### Cgroup testdata ###### +.PHONY: gen-cgroup-testdata +gen-cgroup-testdata: + bin/gen-cgroup-testdata + ###### Go tests ###### .PHONY: test test-setup test-teardown diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fscrypt-0.3.6/NEWS.md new/fscrypt-0.3.7/NEWS.md --- old/fscrypt-0.3.6/NEWS.md 2025-11-04 23:08:18.000000000 +0100 +++ new/fscrypt-0.3.7/NEWS.md 2026-08-22 17:57:07.000000000 +0200 @@ -1,5 +1,28 @@ # `fscrypt` release notes +## Version 0.3.7 + +* Upgraded various dependencies, including `golang.org/x/crypto` to resolve the + usual CVEs in it (but as usual, not actually affecting `fscrypt`'s use of it). + +* When selecting password hashing parameters, `fscrypt` now takes cgroup + limitations into consideration. + +* `fscrypt encrypt` no longer follows trailing symlinks when writing the + recovery instructions. + +* `fscrypt unlock` no longer enters an infinite loop when an incorrect key file + is specified using `--key=FILE`, `--quiet` isn't specified, and standard input + is a terminal. + +* `fscrypt unlock` no longer enters an infinite loop when an incorrect password + is specified, `--quiet` isn't specified, and standard input isn't a terminal. + +* The error message when multiple protectors are available now mentions + `--unlock-with` in addition to `--protector`. + +* Documented the udev dependency for `/dev/disk/by-uuid/` links. + ## Version 0.3.6 * Upgraded various dependencies, including `golang.org/x/crypto` where the diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fscrypt-0.3.6/README.md new/fscrypt-0.3.7/README.md --- old/fscrypt-0.3.6/README.md 2025-11-04 23:08:18.000000000 +0100 +++ new/fscrypt-0.3.7/README.md 2026-08-22 17:57:07.000000000 +0200 @@ -29,6 +29,7 @@ - [Features](#features) - [Building and installing](#building-and-installing) - [Runtime dependencies](#runtime-dependencies) + - [udev dependency for cross-filesystem links](#udev-dependency-for-cross-filesystem-links) - [Configuration file](#configuration-file) - [Setting up `fscrypt` on a filesystem](#setting-up-fscrypt-on-a-filesystem) - [Setting up for login protectors](#setting-up-for-login-protectors) @@ -313,6 +314,32 @@ prerequisites may be needed too. See [Configuration file](#configuration-file). +### udev dependency for cross-filesystem links + +Cross-filesystem metadata links (for example `.fscrypt/protectors/*.link` files +that point at a protector stored on another filesystem) prefer to identify the +target filesystem by UUID via `/dev/disk/by-uuid`. Those symlinks are created by +the standard udev rule `60-persistent-storage.rules` (from systemd/udev, eudev, +or classic udev). + +This is a **runtime / link-creation** dependency, not a build-time dependency: + +* **Link creation time:** when `fscrypt` writes a cross-filesystem link, it looks + up the target filesystem UUID under `/dev/disk/by-uuid`. If that directory is + missing or does not contain a usable UUID symlink for the device, `fscrypt` + falls back to storing the mountpoint path only. +* **Runtime (following a link):** when resolving an existing link that contains a + `UUID=` entry, `fscrypt` again uses `/dev/disk/by-uuid` (with a path fallback if + the UUID cannot be resolved). + +Most Linux distributions ship `60-persistent-storage.rules` by default. If you +run a minimal or custom udev setup without that rule, ensure equivalent rules +still create `/dev/disk/by-uuid/*` symlinks before relying on UUID-based +cross-filesystem links. Example upstream sources: + +* [systemd `60-persistent-storage.rules`](https://github.com/systemd/systemd/blob/main/rules.d/60-persistent-storage.rules.in) +* [eudev `60-persistent-storage.rules`](https://github.com/eudev-project/eudev/blob/master/rules/60-persistent-storage.rules) + ## Configuration file Running `sudo fscrypt setup` will create the configuration file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fscrypt-0.3.6/actions/config.go new/fscrypt-0.3.7/actions/config.go --- old/fscrypt-0.3.6/actions/config.go 2025-11-04 23:08:18.000000000 +0100 +++ new/fscrypt-0.3.7/actions/config.go 2026-08-22 17:57:07.000000000 +0200 @@ -24,6 +24,7 @@ "bytes" "fmt" "log" + "math" "os" "runtime" "time" @@ -31,6 +32,7 @@ "golang.org/x/sys/unix" "google.golang.org/protobuf/proto" + "github.com/google/fscrypt/cgroup" "github.com/google/fscrypt/crypto" "github.com/google/fscrypt/filesystem" "github.com/google/fscrypt/metadata" @@ -186,8 +188,9 @@ func getHashingCosts(target time.Duration) (*metadata.HashingCosts, error) { log.Printf("Finding hashing costs that take %v\n", target) - // Start out with the minimal possible costs that use all the CPUs. - parallelism := int64(runtime.NumCPU()) + // Start out with the minimal possible costs that use all the available + // CPUs, respecting cgroup limits when present. + parallelism := int64(effectiveCPUCount()) // golang.org/x/crypto/argon2 only supports parallelism up to 255. // For compatibility, don't use more than that amount. if parallelism > metadata.MaxParallelism { @@ -248,9 +251,25 @@ } } +// effectiveCPUCount returns the number of CPUs available to this process, +// taking cgroup limits into account. Falls back to runtime.NumCPU() when +// cgroup information is unavailable. +func effectiveCPUCount() int { + cg, err := cgroup.New() + if err != nil { + return runtime.NumCPU() + } + quota, err := cg.CPUQuota() + if err != nil || quota <= 0 { + return runtime.NumCPU() + } + cpus := int(math.Ceil(quota)) + return min(cpus, runtime.NumCPU()) +} + // memoryBytesLimit returns the maximum amount of memory we will use for // passphrase hashing. This will never be more than a reasonable maximum (for -// compatibility) or an 8th the available system RAM. +// compatibility) or an 8th the available RAM (considering cgroup limits). func memoryBytesLimit() int64 { // The sysinfo syscall only fails if given a bad address var info unix.Sysinfo_t @@ -258,6 +277,11 @@ util.NeverError(err) totalRAMBytes := int64(info.Totalram) + if cg, err := cgroup.New(); err == nil { + if cgroupMem, err := cg.MemoryLimit(); err == nil && cgroupMem > 0 { + totalRAMBytes = util.MinInt64(totalRAMBytes, cgroupMem) + } + } return util.MinInt64(totalRAMBytes/8, maxMemoryBytes) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fscrypt-0.3.6/actions/recovery.go new/fscrypt-0.3.7/actions/recovery.go --- old/fscrypt-0.3.6/actions/recovery.go 2025-11-04 23:08:18.000000000 +0100 +++ new/fscrypt-0.3.7/actions/recovery.go 2026-08-22 17:57:07.000000000 +0200 @@ -23,6 +23,7 @@ "os" "strconv" + "golang.org/x/sys/unix" "google.golang.org/protobuf/proto" "github.com/google/fscrypt/crypto" @@ -91,7 +92,7 @@ // passphrase in a different location if they actually need it. func WriteRecoveryInstructions(recoveryPassphrase *crypto.Key, recoveryProtector *Protector, policy *Policy, path string) error { - file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0600) + file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL|unix.O_NOFOLLOW, 0600) if err != nil { return err } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fscrypt-0.3.6/bin/gen-cgroup-testdata new/fscrypt-0.3.7/bin/gen-cgroup-testdata --- old/fscrypt-0.3.6/bin/gen-cgroup-testdata 1970-01-01 01:00:00.000000000 +0100 +++ new/fscrypt-0.3.7/bin/gen-cgroup-testdata 2026-08-22 17:57:07.000000000 +0200 @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# +# gen-cgroup-testdata - Generate cgroup testdata by running +# bin/snapshot-cgroup inside Docker containers with known resource limits. +# +# Usage: gen-cgroup-testdata +# +# Prerequisites: Docker on a host running cgroup v2. +# +# Each testdata directory contains: +# expected.json - {"cpu_quota": <float>, "memory_limit": <int>} +# proc/ - snapshot of /proc/self/cgroup +# sys/ - snapshot of cgroup control files + +set -euo pipefail + +cd "$(dirname "$0")/.." + +testdata="cgroup/testdata" +snapshot_script="bin/snapshot-cgroup" + +generate() { + local name="$1" cpu_quota="$2" memory_limit="$3" + shift 3 + local outdir="$testdata/$name" + + echo "Generating $name..." + rm -rf "$outdir" + mkdir -p "$outdir" + + docker run --rm \ + --user "$(id -u):$(id -g)" \ + "$@" \ + -v "$PWD/$snapshot_script:/snapshot:ro" \ + -v "$PWD/$outdir:/out" \ + debian:bookworm-slim \ + /snapshot /out + + cat > "$outdir/expected.json" <<EOF +{"cpu_quota": $cpu_quota, "memory_limit": $memory_limit} +EOF +} + +generate "v2-two-cores-256m" 2.0 268435456 --cpus=2 --memory=256m +generate "v2-quarter-core-64m" 0.25 67108864 --cpus=0.25 --memory=64m +generate "v2-no-limit" null null + +echo "v2 testdata generated successfully." diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fscrypt-0.3.6/bin/snapshot-cgroup new/fscrypt-0.3.7/bin/snapshot-cgroup --- old/fscrypt-0.3.6/bin/snapshot-cgroup 1970-01-01 01:00:00.000000000 +0100 +++ new/fscrypt-0.3.7/bin/snapshot-cgroup 2026-08-22 17:57:07.000000000 +0200 @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# +# snapshot-cgroup - Copy cgroup v2 files from the live system into a +# directory tree suitable for use with TestIntegrationCgroupLimits. +# +# Usage: snapshot-cgroup <output-dir> +# +# The script reads /proc/self/cgroup to find the v2 group path and copies +# exactly the files that the cgroup package needs: +# +# proc/self/cgroup +# sys/fs/cgroup/<group>/cpu.max +# sys/fs/cgroup/<group>/memory.max + +set -euo pipefail + +if [[ $# -ne 1 ]]; then + echo "Usage: $0 <output-dir>" >&2 + exit 1 +fi + +out="$1" +mkdir -p "$out" + +copy_file() { + local src="$1" dst="$out/$2" + mkdir -p "$(dirname "$dst")" + cp "$src" "$dst" +} + +copy_file /proc/self/cgroup proc/self/cgroup + +group=$(awk -F: '/^0::/ { print $3 }' /proc/self/cgroup) +cgdir="/sys/fs/cgroup${group}" + +for f in cpu.max memory.max; do + if [[ -f "$cgdir/$f" ]]; then + copy_file "$cgdir/$f" "sys/fs/cgroup${group}/$f" + fi +done + +echo "Snapshot written to $out" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fscrypt-0.3.6/cgroup/cgroup.go new/fscrypt-0.3.7/cgroup/cgroup.go --- old/fscrypt-0.3.6/cgroup/cgroup.go 1970-01-01 01:00:00.000000000 +0100 +++ new/fscrypt-0.3.7/cgroup/cgroup.go 2026-08-22 17:57:07.000000000 +0200 @@ -0,0 +1,175 @@ +/* + * cgroup.go - Read CPU and memory limits from Linux cgroups v2. + * + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +// Package cgroup reads CPU and memory resource limits from Linux control +// groups (cgroup v2). +// +// References: +// - cgroups(7): https://man7.org/linux/man-pages/man7/cgroups.7.html +// - cgroup v2 (cpu.max, memory.max): https://docs.kernel.org/admin-guide/cgroup-v2.html +// - /proc/self/cgroup: https://man7.org/linux/man-pages/man7/cgroups.7.html (see "/proc files") +package cgroup + +import ( + "bufio" + "errors" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" +) + +// Errors. +var ( + // ErrNoLimit indicates that no cgroup limit is set. + ErrNoLimit = errors.New("no cgroup limit set") + + // ErrV1Detected indicates that cgroup v1 controllers were found. Only v2 is + // supported. + ErrV1Detected = errors.New("cgroup v1 detected; only v2 is supported") +) + +// Cgroup provides access to cgroup v2 resource limits. Create one with +// New or NewFromRoot. +type Cgroup struct { + // cgroupDir is the resolved filesystem path to the cgroup directory + // (e.g. /sys/fs/cgroup/user.slice/...). + cgroupDir string +} + +// New returns a Cgroup by reading /proc/self/cgroup on the live system. +func New() (Cgroup, error) { + return NewFromRoot("/") +} + +// NewFromRoot is like New but resolves all filesystem paths relative to +// root instead of "/". This is useful for testing with a mock filesystem. +func NewFromRoot(root string) (Cgroup, error) { + groupPath, err := parseProcCgroup(filepath.Join(root, "proc/self/cgroup")) + if err != nil { + return Cgroup{}, err + } + return Cgroup{ + cgroupDir: filepath.Join(root, "sys/fs/cgroup", groupPath), + }, nil +} + +// CPUQuota returns the CPU quota as a fractional number of CPUs (e.g. 0.5 +// means half a core). Returns ErrNoLimit if no CPU limit is configured. +func (c Cgroup) CPUQuota() (float64, error) { + data, err := c.readFile("cpu.max") + if err != nil { + return 0, err + } + return parseCPUMax(data) +} + +// MemoryLimit returns the cgroup memory limit in bytes. Returns ErrNoLimit +// if no memory limit is configured. +func (c Cgroup) MemoryLimit() (int64, error) { + data, err := c.readFile("memory.max") + if err != nil { + return 0, err + } + return parseMemoryMax(data) +} + +func (c Cgroup) readFile(path string) (string, error) { + data, err := os.ReadFile(filepath.Join(c.cgroupDir, path)) + if err != nil { + if os.IsNotExist(err) { + return "", ErrNoLimit + } + return "", err + } + return strings.TrimSpace(string(data)), nil +} + +// parseProcCgroup parses /proc/self/cgroup and returns the cgroup v2 group +// path. The v2 entry is the line with hierarchy-ID "0" and an empty +// controller list: "0::<path>". +// +// Returns an error if v1 controllers are detected or no v2 entry is found. +// +// https://man7.org/linux/man-pages/man7/cgroups.7.html +func parseProcCgroup(path string) (string, error) { + f, err := os.Open(path) + if err != nil { + return "", err + } + defer f.Close() + + var v2Path string + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + parts := strings.SplitN(scanner.Text(), ":", 3) + if len(parts) != 3 { + continue + } + if parts[0] == "0" && parts[1] == "" { + v2Path = parts[2] + } else if parts[1] != "" { + return "", ErrV1Detected + } + } + if err := scanner.Err(); err != nil { + return "", err + } + if v2Path == "" { + return "", fmt.Errorf("no cgroup v2 entry found in %s", path) + } + return v2Path, nil +} + +func parseCPUMax(content string) (float64, error) { + fields := strings.Fields(content) + if len(fields) == 0 || len(fields) > 2 { + return 0, fmt.Errorf("unexpected cpu.max format: %q", content) + } + if fields[0] == "max" { + return 0, ErrNoLimit + } + quota, err := strconv.ParseFloat(fields[0], 64) + if err != nil { + return 0, fmt.Errorf("parsing cpu.max quota: %w", err) + } + period := 100000.0 + if len(fields) == 2 { + period, err = strconv.ParseFloat(fields[1], 64) + if err != nil { + return 0, fmt.Errorf("parsing cpu.max period: %w", err) + } + if period == 0 { + return 0, fmt.Errorf("cpu.max period is zero") + } + } + return quota / period, nil +} + +func parseMemoryMax(content string) (int64, error) { + if content == "max" { + return 0, ErrNoLimit + } + v, err := strconv.ParseInt(content, 10, 64) + if err != nil { + return 0, fmt.Errorf("parsing memory.max: %w", err) + } + return v, nil +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fscrypt-0.3.6/cgroup/cgroup_test.go new/fscrypt-0.3.7/cgroup/cgroup_test.go --- old/fscrypt-0.3.6/cgroup/cgroup_test.go 1970-01-01 01:00:00.000000000 +0100 +++ new/fscrypt-0.3.7/cgroup/cgroup_test.go 2026-08-22 17:57:07.000000000 +0200 @@ -0,0 +1,164 @@ +/* + * cgroup_test.go - Tests for cgroup CPU and memory limit reading. + * + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package cgroup + +import ( + "encoding/json" + "errors" + "math" + "os" + "path/filepath" + "strconv" + "testing" +) + +func writeFile(t *testing.T, path, content string) { + t.Helper() + if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(path, []byte(content), 0644); err != nil { + t.Fatal(err) + } +} + +func TestCgroupV1Unsupported(t *testing.T) { + content := `12:memory:/docker/abc123 +11:cpu,cpuacct:/docker/abc123 +` + root := t.TempDir() + writeFile(t, filepath.Join(root, "proc/self/cgroup"), content) + _, err := NewFromRoot(root) + if !errors.Is(err, ErrV1Detected) { + t.Fatalf("NewFromRoot() error = %v, want %v", err, ErrV1Detected) + } +} + +// testdataExpected holds the expected values from a testdata/*/expected.json. +// Null fields indicate that ErrNoLimit is expected. +type testdataExpected struct { + CPUQuota *float64 `json:"cpu_quota"` + MemoryLimit *int64 `json:"memory_limit"` +} + +// TestWithRootFromTestdata runs NewFromRoot, CPUQuota, and MemoryLimit +// against filesystem snapshots captured from real Docker containers by +// bin/snapshot-cgroup. Each subdirectory of testdata/ is a separate test +// case containing a proc/ and sys/ tree plus an expected.json. +// +// Regenerate with: bin/gen-cgroup-testdata +func TestWithRootFromTestdata(t *testing.T) { + entries, err := os.ReadDir("testdata") + if err != nil { + t.Fatalf("no testdata directory: %v", err) + } + + for _, entry := range entries { + if !entry.IsDir() { + continue + } + name := entry.Name() + root := filepath.Join("testdata", name) + + t.Run(name, func(t *testing.T) { + data, err := os.ReadFile(filepath.Join(root, "expected.json")) + if err != nil { + t.Fatalf("reading expected.json: %v", err) + } + var want testdataExpected + if err := json.Unmarshal(data, &want); err != nil { + t.Fatalf("parsing expected.json: %v", err) + } + + cg, err := NewFromRoot(root) + if err != nil { + t.Fatalf("NewFromRoot(%q): %v", root, err) + } + + gotCPU, err := cg.CPUQuota() + if want.CPUQuota == nil { + if !errors.Is(err, ErrNoLimit) { + t.Errorf("CPUQuota() error = %v, want ErrNoLimit", err) + } + } else if err != nil { + t.Fatalf("CPUQuota(): %v", err) + } else if math.Abs(gotCPU-*want.CPUQuota) > 0.001 { + t.Errorf("CPUQuota() = %v, want %v", gotCPU, *want.CPUQuota) + } + + gotMem, err := cg.MemoryLimit() + if want.MemoryLimit == nil { + if !errors.Is(err, ErrNoLimit) { + t.Errorf("MemoryLimit() error = %v, want ErrNoLimit", err) + } + } else if err != nil { + t.Fatalf("MemoryLimit(): %v", err) + } else if gotMem != *want.MemoryLimit { + t.Errorf("MemoryLimit() = %v, want %v", gotMem, *want.MemoryLimit) + } + }) + } +} + +// TestIntegrationCgroupLimits calls the real New(), CPUQuota(), and +// MemoryLimit() against the live kernel cgroup interface. It is intended to +// run inside a Docker container started with --cpus and --memory flags. +// +// The test is skipped unless CGROUP_EXPECTED_CPU_QUOTA and +// CGROUP_EXPECTED_MEMORY_LIMIT are set in the environment. +func TestIntegrationCgroupLimits(t *testing.T) { + cpuStr := os.Getenv("CGROUP_EXPECTED_CPU_QUOTA") + memStr := os.Getenv("CGROUP_EXPECTED_MEMORY_LIMIT") + if cpuStr == "" && memStr == "" { + t.Skip("set CGROUP_EXPECTED_CPU_QUOTA and CGROUP_EXPECTED_MEMORY_LIMIT to run") + } + + cg, err := New() + if err != nil { + t.Fatalf("New() error: %v", err) + } + + if cpuStr != "" { + wantCPU, err := strconv.ParseFloat(cpuStr, 64) + if err != nil { + t.Fatalf("bad CGROUP_EXPECTED_CPU_QUOTA %q: %v", cpuStr, err) + } + gotCPU, err := cg.CPUQuota() + if err != nil { + t.Fatalf("CPUQuota() error: %v", err) + } + if math.Abs(gotCPU-wantCPU) > 0.001 { + t.Errorf("CPUQuota() = %v, want %v", gotCPU, wantCPU) + } + } + + if memStr != "" { + wantMem, err := strconv.ParseInt(memStr, 10, 64) + if err != nil { + t.Fatalf("bad CGROUP_EXPECTED_MEMORY_LIMIT %q: %v", memStr, err) + } + gotMem, err := cg.MemoryLimit() + if err != nil { + t.Fatalf("MemoryLimit() error: %v", err) + } + if gotMem != wantMem { + t.Errorf("MemoryLimit() = %v, want %v", gotMem, wantMem) + } + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fscrypt-0.3.6/cgroup/testdata/v2-no-limit/expected.json new/fscrypt-0.3.7/cgroup/testdata/v2-no-limit/expected.json --- old/fscrypt-0.3.6/cgroup/testdata/v2-no-limit/expected.json 1970-01-01 01:00:00.000000000 +0100 +++ new/fscrypt-0.3.7/cgroup/testdata/v2-no-limit/expected.json 2026-08-22 17:57:07.000000000 +0200 @@ -0,0 +1 @@ +{"cpu_quota": null, "memory_limit": null} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fscrypt-0.3.6/cgroup/testdata/v2-no-limit/proc/self/cgroup new/fscrypt-0.3.7/cgroup/testdata/v2-no-limit/proc/self/cgroup --- old/fscrypt-0.3.6/cgroup/testdata/v2-no-limit/proc/self/cgroup 1970-01-01 01:00:00.000000000 +0100 +++ new/fscrypt-0.3.7/cgroup/testdata/v2-no-limit/proc/self/cgroup 2026-08-22 17:57:07.000000000 +0200 @@ -0,0 +1 @@ +0::/ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fscrypt-0.3.6/cgroup/testdata/v2-no-limit/sys/fs/cgroup/cpu.max new/fscrypt-0.3.7/cgroup/testdata/v2-no-limit/sys/fs/cgroup/cpu.max --- old/fscrypt-0.3.6/cgroup/testdata/v2-no-limit/sys/fs/cgroup/cpu.max 1970-01-01 01:00:00.000000000 +0100 +++ new/fscrypt-0.3.7/cgroup/testdata/v2-no-limit/sys/fs/cgroup/cpu.max 2026-08-22 17:57:07.000000000 +0200 @@ -0,0 +1 @@ +max 100000 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fscrypt-0.3.6/cgroup/testdata/v2-no-limit/sys/fs/cgroup/memory.max new/fscrypt-0.3.7/cgroup/testdata/v2-no-limit/sys/fs/cgroup/memory.max --- old/fscrypt-0.3.6/cgroup/testdata/v2-no-limit/sys/fs/cgroup/memory.max 1970-01-01 01:00:00.000000000 +0100 +++ new/fscrypt-0.3.7/cgroup/testdata/v2-no-limit/sys/fs/cgroup/memory.max 2026-08-22 17:57:07.000000000 +0200 @@ -0,0 +1 @@ +max diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fscrypt-0.3.6/cgroup/testdata/v2-quarter-core-64m/expected.json new/fscrypt-0.3.7/cgroup/testdata/v2-quarter-core-64m/expected.json --- old/fscrypt-0.3.6/cgroup/testdata/v2-quarter-core-64m/expected.json 1970-01-01 01:00:00.000000000 +0100 +++ new/fscrypt-0.3.7/cgroup/testdata/v2-quarter-core-64m/expected.json 2026-08-22 17:57:07.000000000 +0200 @@ -0,0 +1 @@ +{"cpu_quota": 0.25, "memory_limit": 67108864} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fscrypt-0.3.6/cgroup/testdata/v2-quarter-core-64m/proc/self/cgroup new/fscrypt-0.3.7/cgroup/testdata/v2-quarter-core-64m/proc/self/cgroup --- old/fscrypt-0.3.6/cgroup/testdata/v2-quarter-core-64m/proc/self/cgroup 1970-01-01 01:00:00.000000000 +0100 +++ new/fscrypt-0.3.7/cgroup/testdata/v2-quarter-core-64m/proc/self/cgroup 2026-08-22 17:57:07.000000000 +0200 @@ -0,0 +1 @@ +0::/ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fscrypt-0.3.6/cgroup/testdata/v2-quarter-core-64m/sys/fs/cgroup/cpu.max new/fscrypt-0.3.7/cgroup/testdata/v2-quarter-core-64m/sys/fs/cgroup/cpu.max --- old/fscrypt-0.3.6/cgroup/testdata/v2-quarter-core-64m/sys/fs/cgroup/cpu.max 1970-01-01 01:00:00.000000000 +0100 +++ new/fscrypt-0.3.7/cgroup/testdata/v2-quarter-core-64m/sys/fs/cgroup/cpu.max 2026-08-22 17:57:07.000000000 +0200 @@ -0,0 +1 @@ +25000 100000 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fscrypt-0.3.6/cgroup/testdata/v2-quarter-core-64m/sys/fs/cgroup/memory.max new/fscrypt-0.3.7/cgroup/testdata/v2-quarter-core-64m/sys/fs/cgroup/memory.max --- old/fscrypt-0.3.6/cgroup/testdata/v2-quarter-core-64m/sys/fs/cgroup/memory.max 1970-01-01 01:00:00.000000000 +0100 +++ new/fscrypt-0.3.7/cgroup/testdata/v2-quarter-core-64m/sys/fs/cgroup/memory.max 2026-08-22 17:57:07.000000000 +0200 @@ -0,0 +1 @@ +67108864 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fscrypt-0.3.6/cgroup/testdata/v2-two-cores-256m/expected.json new/fscrypt-0.3.7/cgroup/testdata/v2-two-cores-256m/expected.json --- old/fscrypt-0.3.6/cgroup/testdata/v2-two-cores-256m/expected.json 1970-01-01 01:00:00.000000000 +0100 +++ new/fscrypt-0.3.7/cgroup/testdata/v2-two-cores-256m/expected.json 2026-08-22 17:57:07.000000000 +0200 @@ -0,0 +1 @@ +{"cpu_quota": 2.0, "memory_limit": 268435456} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fscrypt-0.3.6/cgroup/testdata/v2-two-cores-256m/proc/self/cgroup new/fscrypt-0.3.7/cgroup/testdata/v2-two-cores-256m/proc/self/cgroup --- old/fscrypt-0.3.6/cgroup/testdata/v2-two-cores-256m/proc/self/cgroup 1970-01-01 01:00:00.000000000 +0100 +++ new/fscrypt-0.3.7/cgroup/testdata/v2-two-cores-256m/proc/self/cgroup 2026-08-22 17:57:07.000000000 +0200 @@ -0,0 +1 @@ +0::/ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fscrypt-0.3.6/cgroup/testdata/v2-two-cores-256m/sys/fs/cgroup/cpu.max new/fscrypt-0.3.7/cgroup/testdata/v2-two-cores-256m/sys/fs/cgroup/cpu.max --- old/fscrypt-0.3.6/cgroup/testdata/v2-two-cores-256m/sys/fs/cgroup/cpu.max 1970-01-01 01:00:00.000000000 +0100 +++ new/fscrypt-0.3.7/cgroup/testdata/v2-two-cores-256m/sys/fs/cgroup/cpu.max 2026-08-22 17:57:07.000000000 +0200 @@ -0,0 +1 @@ +200000 100000 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fscrypt-0.3.6/cgroup/testdata/v2-two-cores-256m/sys/fs/cgroup/memory.max new/fscrypt-0.3.7/cgroup/testdata/v2-two-cores-256m/sys/fs/cgroup/memory.max --- old/fscrypt-0.3.6/cgroup/testdata/v2-two-cores-256m/sys/fs/cgroup/memory.max 1970-01-01 01:00:00.000000000 +0100 +++ new/fscrypt-0.3.7/cgroup/testdata/v2-two-cores-256m/sys/fs/cgroup/memory.max 2026-08-22 17:57:07.000000000 +0200 @@ -0,0 +1 @@ +268435456 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fscrypt-0.3.6/cli-tests/t_encrypt_raw_key.out new/fscrypt-0.3.7/cli-tests/t_encrypt_raw_key.out --- old/fscrypt-0.3.6/cli-tests/t_encrypt_raw_key.out 2025-11-04 23:08:18.000000000 +0100 +++ new/fscrypt-0.3.7/cli-tests/t_encrypt_raw_key.out 2026-08-22 17:57:07.000000000 +0200 @@ -72,3 +72,25 @@ Protected with 1 protector: PROTECTOR LINKED DESCRIPTION desc11 No raw key protector "prot" + +# Try to unlock with wrong key, both with and without --quiet +"MNT/dir" is now locked. +[ERROR] fscrypt unlock: incorrect key provided +[ERROR] fscrypt unlock: incorrect key provided +ext4 filesystem "MNT" has 1 protector and 1 policy. +All users can create fscrypt metadata on this filesystem. + +PROTECTOR LINKED DESCRIPTION +desc16 No raw key protector "prot" + +POLICY UNLOCKED PROTECTORS +desc17 No desc16 +"MNT/dir" is encrypted with fscrypt. + +Policy: desc17 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:2 +Unlocked: No + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc16 No raw key protector "prot" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fscrypt-0.3.6/cli-tests/t_encrypt_raw_key.sh new/fscrypt-0.3.7/cli-tests/t_encrypt_raw_key.sh --- old/fscrypt-0.3.6/cli-tests/t_encrypt_raw_key.sh 2025-11-04 23:08:18.000000000 +0100 +++ new/fscrypt-0.3.7/cli-tests/t_encrypt_raw_key.sh 2026-08-22 17:57:07.000000000 +0200 @@ -51,3 +51,12 @@ fscrypt lock "$dir" fscrypt unlock --quiet "$dir" < "$raw_key_file" show_status true + +begin "Try to unlock with wrong key, both with and without --quiet" +head -c 32 /dev/urandom > "$raw_key_file" +fscrypt encrypt --quiet --name=prot --source=raw_key --key="$raw_key_file" "$dir" +fscrypt lock "$dir" +head -c 32 /dev/urandom > "$raw_key_file" +_expect_failure "fscrypt unlock --quiet --key='$raw_key_file' '$dir'" +_expect_failure "fscrypt unlock --key='$raw_key_file' '$dir'" +show_status true diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fscrypt-0.3.6/cli-tests/t_unlock.out new/fscrypt-0.3.7/cli-tests/t_unlock.out --- old/fscrypt-0.3.6/cli-tests/t_unlock.out 2025-11-04 23:08:18.000000000 +0100 +++ new/fscrypt-0.3.7/cli-tests/t_unlock.out 2026-08-22 17:57:07.000000000 +0200 @@ -62,6 +62,30 @@ PROTECTOR LINKED DESCRIPTION desc2 No custom protector "prot" +# Try to unlock with no stdin +[ERROR] fscrypt unlock: incorrect key provided +"MNT/dir" is encrypted with fscrypt. + +Policy: desc1 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:2 +Unlocked: No + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc2 No custom protector "prot" + +# Try to unlock with only a newline +[ERROR] fscrypt unlock: incorrect key provided +"MNT/dir" is encrypted with fscrypt. + +Policy: desc1 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:2 +Unlocked: No + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc2 No custom protector "prot" + # Unlock directory Enter custom passphrase for protector "prot": "MNT/dir" is now unlocked and ready for use. @@ -90,7 +114,7 @@ the policy metadata for "MNT/dir". This directory has either been encrypted with another tool (such as e4crypt), or the file - "MNT/.fscrypt/policies/desc20" + "MNT/.fscrypt/policies/desc24" has been deleted. # Try to unlock with missing protector metadata @@ -103,14 +127,14 @@ [ERROR] fscrypt unlock: inconsistent metadata between encrypted directory "MNT/dir1" and its corresponding metadata file - "MNT/.fscrypt/policies/desc21". + "MNT/.fscrypt/policies/desc25". Directory has - descriptor:desc21 padding:32 + descriptor:desc25 padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:2 Metadata file has - descriptor:desc23 padding:32 + descriptor:desc27 padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:2 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fscrypt-0.3.6/cli-tests/t_unlock.sh new/fscrypt-0.3.7/cli-tests/t_unlock.sh --- old/fscrypt-0.3.6/cli-tests/t_unlock.sh 2025-11-04 23:08:18.000000000 +0100 +++ new/fscrypt-0.3.7/cli-tests/t_unlock.sh 2026-08-22 17:57:07.000000000 +0200 @@ -38,6 +38,14 @@ _expect_failure "echo bad | fscrypt unlock --quiet '$dir'" fscrypt status "$dir" +_print_header "Try to unlock with no stdin" +_expect_failure "fscrypt unlock --quiet '$dir' </dev/null" +fscrypt status "$dir" + +_print_header "Try to unlock with only a newline" +_expect_failure "echo | fscrypt unlock --quiet '$dir'" +fscrypt status "$dir" + _print_header "Unlock directory" echo hunter2 | fscrypt unlock "$dir" _print_header "=> Check dir status" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fscrypt-0.3.6/cmd/fscrypt/errors.go new/fscrypt-0.3.7/cmd/fscrypt/errors.go --- old/fscrypt-0.3.6/cmd/fscrypt/errors.go 2025-11-04 23:08:18.000000000 +0100 +++ new/fscrypt-0.3.7/cmd/fscrypt/errors.go 2026-08-22 17:57:07.000000000 +0200 @@ -272,7 +272,8 @@ return fmt.Sprintf("If desired, use %s to automatically run destructive operations.", shortDisplay(forceFlag)) case ErrSpecifyProtector: - return fmt.Sprintf("Use %s to specify a protector.", shortDisplay(protectorFlag)) + return fmt.Sprintf("Use %s or %s to specify a protector.", + shortDisplay(protectorFlag), shortDisplay(unlockWithFlag)) case ErrSpecifyKeyFile: return fmt.Sprintf("Use %s to specify a key file.", shortDisplay(keyFileFlag)) case ErrDropCachesPerm: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fscrypt-0.3.6/cmd/fscrypt/keys.go new/fscrypt-0.3.7/cmd/fscrypt/keys.go --- old/fscrypt-0.3.6/cmd/fscrypt/keys.go 2025-11-04 23:08:18.000000000 +0100 +++ new/fscrypt-0.3.7/cmd/fscrypt/keys.go 2026-08-22 17:57:07.000000000 +0200 @@ -144,11 +144,17 @@ if !supportRetry { panic("this KeyFunc does not support retrying") } - // Don't retry for non-interactive sessions - if quietFlag.Value { + // Don't retry in --quiet mode, for non-interactive + // sessions, or when a key file was specified. + if quietFlag.Value || !term.IsTerminal(stdinFd) || + (info.Source() == metadata.SourceType_raw_key && keyFileFlag.Value != "") { return nil, ErrWrongKey } - fmt.Println("Incorrect Passphrase") + if info.Source() == metadata.SourceType_raw_key { + fmt.Println("Incorrect Key") + } else { + fmt.Println("Incorrect Passphrase") + } } switch info.Source() { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fscrypt-0.3.6/go.mod new/fscrypt-0.3.7/go.mod --- old/fscrypt-0.3.6/go.mod 2025-11-04 23:08:18.000000000 +0100 +++ new/fscrypt-0.3.7/go.mod 2026-08-22 17:57:07.000000000 +0200 @@ -1,17 +1,15 @@ module github.com/google/fscrypt -go 1.23.0 - -toolchain go1.23.12 +go 1.25.0 require ( github.com/client9/misspell v0.3.4 github.com/pkg/errors v0.9.1 github.com/urfave/cli v1.22.17 github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad - golang.org/x/crypto v0.41.0 - golang.org/x/sys v0.35.0 - golang.org/x/term v0.34.0 + golang.org/x/crypto v0.52.0 + golang.org/x/sys v0.45.0 + golang.org/x/term v0.43.0 golang.org/x/tools v0.36.0 google.golang.org/protobuf v1.36.10 honnef.co/go/tools v0.6.1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fscrypt-0.3.6/go.sum new/fscrypt-0.3.7/go.sum --- old/fscrypt-0.3.6/go.sum 2025-11-04 23:08:18.000000000 +0100 +++ new/fscrypt-0.3.7/go.sum 2026-08-22 17:57:07.000000000 +0200 @@ -28,18 +28,18 @@ github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo= github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad h1:W0LEBv82YCGEtcmPA3uNZBI33/qF//HAAs3MawDjRa0= github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad/go.mod h1:Hy8o65+MXnS6EwGElrSRjUzQDLXreJlzYLlWiHtt8hM= -golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= -golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= +golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 h1:1P7xPZEwZMoBoz0Yze5Nx2/4pxj6nw9ZqHWXqP0iRgQ= golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= -golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= ++++++ vendor.tar.xz ++++++ ++++ 11407 lines of diff (skipped)
