@feigi/fleet-ctl 3.17.5 → 3.17.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
package/scripts/review-core.mjs
CHANGED
|
@@ -755,7 +755,7 @@ export async function runReview(host, args) {
|
|
|
755
755
|
mkdir -p "${runRootParent}" || { echo SNAPSHOT_RUNROOT_FAILED; exit 1; }
|
|
756
756
|
RUN=$(mktemp -d "${runRootPrefix}XXXXXXXX") || { echo SNAPSHOT_RUNROOT_FAILED; exit 1; }
|
|
757
757
|
echo SNAPSHOT_RUN_ROOT="$RUN"
|
|
758
|
-
find "${runRootParent}" -maxdepth 1 -type d -name 'run-*' -mtime +7 -exec sh -c 'rc=0; for d; do chmod -R u+rwx "$d" 2>/dev/null; if command -v chflags >/dev/null 2>&1; then
|
|
758
|
+
find "${runRootParent}" -maxdepth 1 -type d -name 'run-*' -mtime +7 -exec sh -c 'rc=0; ph="{""}"; for d; do chmod -R u+rwx "$d" 2>/dev/null; m=$?; if command -v chflags >/dev/null 2>&1; then [ $m -eq 0 ] || { chflags nouchg,nouappnd "$d"; chmod u+rwx "$d"; find "$d" -type d -exec chflags nouchg,nouappnd "$ph" ";" -exec chmod u+rwx "$ph" ";"; }; chflags -R nouchg,nouappnd "$d" 2>/dev/null; fi; rm -rf "$d" || rc=1; done; exit $rc' sh {} + || echo SNAPSHOT_PRUNE_FAILED
|
|
759
759
|
SHA=$(git -C ${worktree} rev-parse --short HEAD) || { echo SNAPSHOT_REVPARSE_FAILED; exit 1; }
|
|
760
760
|
SNAP="$RUN/snapshot-$SHA"
|
|
761
761
|
echo SNAPSHOT_DEST="$SNAP"
|
|
@@ -164,31 +164,48 @@ const renderPrune = (path, runRootParent) =>
|
|
|
164
164
|
|
|
165
165
|
/**
|
|
166
166
|
* A `run-` root under `parent` holding a `locked` directory with no permission
|
|
167
|
-
* bits at all, aged `days`.
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
167
|
+
* bits at all, aged `days`. `nested` puts a second such directory, `deeper`,
|
|
168
|
+
* inside `locked`, and the file moves into it. On darwin, optionally, the file
|
|
169
|
+
* carries `fileFlag` (`uchg` or `uappnd`), and every locked directory carries
|
|
170
|
+
* `dirFlag` — set after the mode, so a directory's mode cannot be changed back
|
|
171
|
+
* until the flag is gone. `rootFlag` locks the run root itself the same way,
|
|
172
|
+
* with no permission bits and that flag. `fileMode` is the file's own mode:
|
|
173
|
+
* one that already grants owner rwx leaves `chmod -R u+rwx` nothing to change
|
|
174
|
+
* on the file, so that pass exits 0 over a flagged file. Aged before the root
|
|
175
|
+
* is locked: creating children sets the root's mtime to now, and a flagged
|
|
176
|
+
* root refuses `utimes`.
|
|
171
177
|
*/
|
|
172
|
-
function lockedRunRoot(parent, name, days, {
|
|
178
|
+
function lockedRunRoot(parent, name, days, { fileFlag = null, fileMode = 0o644, dirFlag = null, rootFlag = null, nested = false } = {}) {
|
|
173
179
|
const root = join(parent, name);
|
|
174
180
|
const locked = join(root, "locked");
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
181
|
+
const dirs = nested ? [join(locked, "deeper"), locked] : [locked];
|
|
182
|
+
mkdirSync(dirs[0], { recursive: true });
|
|
183
|
+
const file = join(dirs[0], "inner.txt");
|
|
184
|
+
writeFileSync(file, "");
|
|
185
|
+
chmodSync(file, fileMode);
|
|
186
|
+
if (fileFlag) execFileSync("chflags", [fileFlag, file]);
|
|
187
|
+
// Deepest first: once a directory is 000, nothing below it can be reached.
|
|
188
|
+
for (const dir of dirs) {
|
|
189
|
+
chmodSync(dir, 0);
|
|
190
|
+
if (dirFlag) execFileSync("chflags", [dirFlag, dir]);
|
|
191
|
+
}
|
|
180
192
|
const when = (Date.now() - days * DAY) / 1000;
|
|
181
193
|
utimesSync(root, when, when);
|
|
194
|
+
if (rootFlag) {
|
|
195
|
+
chmodSync(root, 0);
|
|
196
|
+
execFileSync("chflags", [rootFlag, root]);
|
|
197
|
+
}
|
|
182
198
|
return root;
|
|
183
199
|
}
|
|
184
200
|
|
|
185
201
|
/**
|
|
186
202
|
* Undoes `lockedRunRoot` so the scratch teardown can remove what a prune left.
|
|
187
|
-
*
|
|
188
|
-
*
|
|
203
|
+
* A flagged directory's mode is only changeable once its flag is gone, and a
|
|
204
|
+
* directory below it only reachable once that mode is back, so each directory
|
|
205
|
+
* is unlocked before `find` descends into it.
|
|
189
206
|
*/
|
|
190
207
|
function unlock(parent) {
|
|
191
|
-
spawnSync("sh", ["-c", 'chmod -R u+rwx "$1"; if command -v chflags >/dev/null; then
|
|
208
|
+
spawnSync("sh", ["-c", 'chmod -R u+rwx "$1"; if command -v chflags >/dev/null; then find "$1" -type d -exec chflags nouchg,nouappnd {} ";" -exec chmod u+rwx {} ";"; chflags -R nouchg,nouappnd "$1"; fi; :', "sh", parent], { env: ENV });
|
|
192
209
|
}
|
|
193
210
|
|
|
194
211
|
/**
|
|
@@ -596,8 +613,8 @@ for (const [name, path] of SOURCES) {
|
|
|
596
613
|
// #2322. A fixture that drops its own read bit, or a darwin file carrying the
|
|
597
614
|
// user-immutable flag, made the plain `rm -rf` fail on that root — so it
|
|
598
615
|
// stayed forever and every later review of the PR printed
|
|
599
|
-
// SNAPSHOT_PRUNE_FAILED. The prune now restores owner rwx and clears
|
|
600
|
-
// on the roots it SELECTED, then removes them.
|
|
616
|
+
// SNAPSHOT_PRUNE_FAILED. The prune now restores owner rwx and clears darwin's
|
|
617
|
+
// user flags on the roots it SELECTED, then removes them.
|
|
601
618
|
test(`${name}: the prune removes an aged run root a chmod 000 directory used to wedge`, (t) => {
|
|
602
619
|
const parent = join(scratch(t, "snapshot-repo-prune-perm-"), "pr7");
|
|
603
620
|
const stale = lockedRunRoot(parent, "run-staleaa", 9);
|
|
@@ -640,31 +657,75 @@ for (const [name, path] of SOURCES) {
|
|
|
640
657
|
}
|
|
641
658
|
});
|
|
642
659
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
}
|
|
654
|
-
|
|
660
|
+
// #2322, #2370. On darwin a user flag wedges the removal too: `uchg` refuses
|
|
661
|
+
// every change and `uappnd` refuses the unlink, and on a directory either
|
|
662
|
+
// one also refuses the `chmod` that makes a `000` directory readable again —
|
|
663
|
+
// while `chflags -R` cannot read a `000` directory it has just unflagged. So
|
|
664
|
+
// a directory's flag and mode have to come off together, before anything
|
|
665
|
+
// inside it is read, at every depth and on the run root itself. The `uappnd`
|
|
666
|
+
// file whose mode already grants owner rwx is the shape where `chmod -R`
|
|
667
|
+
// exits 0 over a flag.
|
|
668
|
+
for (const [shape, opts] of [
|
|
669
|
+
["a uchg file inside a chmod 000 directory", { fileFlag: "uchg" }],
|
|
670
|
+
["a uappnd file whose own mode leaves chmod nothing to change", { fileFlag: "uappnd", fileMode: 0o700 }],
|
|
671
|
+
["a directory that is both uchg and chmod 000", { dirFlag: "uchg" }],
|
|
672
|
+
["a directory that is both uappnd and chmod 000", { dirFlag: "uappnd" }],
|
|
673
|
+
["a uchg file inside a directory that is both uchg and chmod 000", { fileFlag: "uchg", dirFlag: "uchg" }],
|
|
674
|
+
["a uchg, chmod 000 directory inside another", { dirFlag: "uchg", nested: true }],
|
|
675
|
+
["its own uchg and chmod 000 bits", { rootFlag: "uchg" }],
|
|
676
|
+
["its own uappnd and chmod 000 bits", { rootFlag: "uappnd" }],
|
|
677
|
+
["its own uchg and chmod 000 bits over a uchg, chmod 000 directory", { rootFlag: "uchg", dirFlag: "uchg" }],
|
|
678
|
+
]) {
|
|
679
|
+
test(`${name}: on darwin the prune removes an aged run root with ${shape}`, { skip: process.platform !== "darwin" && "chflags/uchg/uappnd are darwin's" }, (t) => {
|
|
680
|
+
const parent = join(scratch(t, "snapshot-repo-prune-flags-"), "pr7");
|
|
681
|
+
const stale = lockedRunRoot(parent, "run-staleaa", 9, opts);
|
|
682
|
+
try {
|
|
683
|
+
const r = spawnSync("sh", ["-c", renderPrune(path, parent)], { env: ENV, encoding: "utf8" });
|
|
684
|
+
|
|
685
|
+
assert.doesNotMatch(r.stdout ?? "", /SNAPSHOT_PRUNE_FAILED/, `the prune named a failure on a root it can normalise: ${r.stderr}`);
|
|
686
|
+
assert.equal(r.status, 0, `the prune exited non-zero: ${r.stderr}`);
|
|
687
|
+
assert.ok(!existsSync(stale), `an aged run root with ${shape} survived the prune — it now stays forever and every later review prints SNAPSHOT_PRUNE_FAILED`);
|
|
688
|
+
} finally {
|
|
689
|
+
unlock(parent);
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
}
|
|
655
693
|
|
|
656
|
-
//
|
|
657
|
-
//
|
|
658
|
-
//
|
|
659
|
-
//
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
const
|
|
694
|
+
// GNU find (Linux) refuses an `-exec ... {} +` whose argument list holds the
|
|
695
|
+
// `{}` placeholder more than once — and counts a `{}` written inside an
|
|
696
|
+
// `sh -c` script argument as one. BSD find (darwin) does not, so a dev
|
|
697
|
+
// machine passes a prune that fails every Linux CI run. This shim enforces
|
|
698
|
+
// GNU's rule over the real `find`, so the case holds on either host.
|
|
699
|
+
test(`${name}: the prune's find invocation keeps to GNU find's one-placeholder rule for -exec ... +`, (t) => {
|
|
700
|
+
const parent = join(scratch(t, "snapshot-repo-prune-gnu-"), "pr7");
|
|
701
|
+
const stale = lockedRunRoot(parent, "run-staleaa", 9);
|
|
702
|
+
const bin = scratch(t, "snapshot-repo-prune-gnu-bin-");
|
|
703
|
+
const realFind = execFileSync("sh", ["-c", "command -v find"], { env: ENV, encoding: "utf8" }).trim();
|
|
704
|
+
writeFileSync(
|
|
705
|
+
join(bin, "find"),
|
|
706
|
+
`#!/bin/sh
|
|
707
|
+
# GNU find: with a terminating '+', only one argument may contain {}.
|
|
708
|
+
n=0; plus=0; prev=
|
|
709
|
+
for a in "$@"; do
|
|
710
|
+
case "$a" in *'{}'*) n=$((n + 1));; esac
|
|
711
|
+
[ "$a" = "+" ] && [ "$prev" = '{}' ] && plus=1
|
|
712
|
+
prev=$a
|
|
713
|
+
done
|
|
714
|
+
if [ "$plus" = 1 ] && [ "$n" -gt 1 ]; then
|
|
715
|
+
echo "find: Only one instance of {} is supported with -exec ... +" >&2
|
|
716
|
+
exit 1
|
|
717
|
+
fi
|
|
718
|
+
exec "${realFind}" "$@"
|
|
719
|
+
`,
|
|
720
|
+
{ mode: 0o755 },
|
|
721
|
+
);
|
|
722
|
+
const env = { ...ENV, PATH: `${bin}:${ENV.PATH}` };
|
|
663
723
|
try {
|
|
664
|
-
const r = spawnSync("sh", ["-c", renderPrune(path, parent)], { env
|
|
724
|
+
const r = spawnSync("sh", ["-c", renderPrune(path, parent)], { env, encoding: "utf8" });
|
|
665
725
|
|
|
666
|
-
assert.doesNotMatch(r.stdout ?? "", /SNAPSHOT_PRUNE_FAILED/, `
|
|
667
|
-
assert.
|
|
726
|
+
assert.doesNotMatch(r.stdout ?? "", /SNAPSHOT_PRUNE_FAILED/, `a GNU-rule find rejected the prune: ${r.stderr}`);
|
|
727
|
+
assert.doesNotMatch(r.stderr ?? "", /Only one instance of \{\}/, "the prune's find tripped GNU's one-placeholder rule");
|
|
728
|
+
assert.ok(!existsSync(stale), "the aged run root survived a find that enforces GNU's placeholder rule");
|
|
668
729
|
} finally {
|
|
669
730
|
unlock(parent);
|
|
670
731
|
}
|