fix(install): atomic binary replace via staged .new + mv

Overwriting the installed binary in place could trash the text segment
of any running officecli process mmap'd on the same path — macOS does
not block ETXTBSY, so the live process would fault into uninterruptible
`UE` state on the next code page fault. Stage the new binary as
`<target>.new` alongside the destination, codesign/unquarantine there,
then rename atomically over the target. Applied identically in build.sh,
install.sh, and dev-install.sh.
This commit is contained in:
zmworm 2026-04-11 23:05:34 +08:00
parent 1b5785afaf
commit 2d7d0a6274
3 changed files with 34 additions and 12 deletions

View file

@ -59,16 +59,24 @@ build_config() {
echo "[$CONFIG] Building $RID -> $NAME"
dotnet publish "$PROJECT" -c "$CONFIG" -r "$RID" -o "$TMPDIR" --nologo -v quiet
# Atomic replace: stage as .new alongside the target, sign there, then rename.
# Overwriting the binary in place would trash the text segment of any
# running officecli process that happens to be mmap'd on this path
# (macOS does not block ETXTBSY), leaving it stuck in uninterruptible
# `UE` state on the next code page fault.
if [ -f "$TMPDIR/officecli.exe" ]; then
cp "$TMPDIR/officecli.exe" "$OUTPUT/$NAME"
cp "$TMPDIR/officecli.exe" "$OUTPUT/$NAME.new"
else
cp "$TMPDIR/officecli" "$OUTPUT/$NAME"
cp "$TMPDIR/officecli" "$OUTPUT/$NAME.new"
fi
# Ad-hoc codesign on macOS (required by AppleSystemPolicy)
# Ad-hoc codesign on macOS (required by AppleSystemPolicy).
# Done on the staged .new copy so the live binary is never mutated in place.
if [ "$(uname -s)" = "Darwin" ] && [[ "$RID" == osx-* ]]; then
codesign -s - -f "$OUTPUT/$NAME" 2>/dev/null || true
codesign -s - -f "$OUTPUT/$NAME.new" 2>/dev/null || true
fi
mv -f "$OUTPUT/$NAME.new" "$OUTPUT/$NAME"
cp "$TMPDIR/officecli.pdb" "$OUTPUT/${NAME%.*}.pdb"
rm -rf "$TMPDIR"

View file

@ -55,16 +55,23 @@ else
fi
mkdir -p "$INSTALL_DIR"
cp "$TMPDIR/$BINARY_NAME" "$INSTALL_DIR/$BINARY_NAME"
chmod +x "$INSTALL_DIR/$BINARY_NAME"
# Atomic replace: stage as .new alongside the target, sign there, then rename.
# Overwriting the binary in place would trash the text segment of any
# running officecli process (macOS does not block ETXTBSY), leaving it
# stuck in uninterruptible `UE` state on the next code page fault.
cp "$TMPDIR/$BINARY_NAME" "$INSTALL_DIR/$BINARY_NAME.new"
chmod +x "$INSTALL_DIR/$BINARY_NAME.new"
rm -rf "$TMPDIR"
# macOS: remove quarantine flag and ad-hoc codesign (required by AppleSystemPolicy)
# Done on the staged .new copy so the live binary is never mutated in place.
if [ "$(uname -s)" = "Darwin" ]; then
xattr -d com.apple.quarantine "$INSTALL_DIR/$BINARY_NAME" 2>/dev/null || true
codesign -s - -f "$INSTALL_DIR/$BINARY_NAME" 2>/dev/null || true
xattr -d com.apple.quarantine "$INSTALL_DIR/$BINARY_NAME.new" 2>/dev/null || true
codesign -s - -f "$INSTALL_DIR/$BINARY_NAME.new" 2>/dev/null || true
fi
mv -f "$INSTALL_DIR/$BINARY_NAME.new" "$INSTALL_DIR/$BINARY_NAME"
# Hint if not in PATH
case ":$PATH:" in
*":$INSTALL_DIR:"*) ;;

View file

@ -119,15 +119,22 @@ else
fi
mkdir -p "$INSTALL_DIR"
cp "$SOURCE" "$INSTALL_DIR/$BINARY_NAME"
chmod +x "$INSTALL_DIR/$BINARY_NAME"
# Atomic replace: stage as .new alongside the target, sign there, then rename.
# Overwriting the binary in place would trash the text segment of any
# running officecli process (macOS does not block ETXTBSY), leaving it
# stuck in uninterruptible `UE` state on the next code page fault.
cp "$SOURCE" "$INSTALL_DIR/$BINARY_NAME.new"
chmod +x "$INSTALL_DIR/$BINARY_NAME.new"
# macOS: remove quarantine flag and ad-hoc codesign (required by AppleSystemPolicy)
# Done on the staged .new copy so the live binary is never mutated in place.
if [ "$(uname -s)" = "Darwin" ]; then
xattr -d com.apple.quarantine "$INSTALL_DIR/$BINARY_NAME" 2>/dev/null || true
codesign -s - -f "$INSTALL_DIR/$BINARY_NAME" 2>/dev/null || true
xattr -d com.apple.quarantine "$INSTALL_DIR/$BINARY_NAME.new" 2>/dev/null || true
codesign -s - -f "$INSTALL_DIR/$BINARY_NAME.new" 2>/dev/null || true
fi
mv -f "$INSTALL_DIR/$BINARY_NAME.new" "$INSTALL_DIR/$BINARY_NAME"
# Auto-add to PATH if needed
case ":$PATH:" in
*":$INSTALL_DIR:"*) ;;