mirror of
https://github.com/unslothai/unsloth.git
synced 2026-08-16 20:33:56 +00:00
* Studio: install sd.cpp under the Studio home, not beside it A custom UNSLOTH_STUDIO_HOME put the managed stable-diffusion.cpp tree at the home's parent, unlike llama.cpp, whisper.cpp and node, which all install under the home. For a relative home that parent collapses to the working directory, so a stable-diffusion.cpp checkout sitting there was picked up as the managed install and install_sd_cpp_prebuilt refused to run: the target was a pre-existing non-empty directory without the ownership marker. Derive the root as "studio home"/stable-diffusion.cpp from an absolutised home, in both the installer and the engine's finder, matching default_managed_llama_dir. The legacy default home ~/.unsloth/studio still maps to ~/.unsloth/stable-diffusion.cpp, and a tree an older build installed beside the home is still discovered and still repairable, gated on the ownership marker so an unrelated checkout is never adopted. * Stop the sd.cpp server under a custom root before uninstall deletes it Moving the managed tree under the Studio home left the uninstaller listing only the old sibling location as an owned root. The tree itself still goes, because the custom root is removed wholesale, but a resident sd-server survives unlinking its binary, so it kept running and holding its port while its install disappeared underneath it. List both locations, each still gated on the ownership marker so a checkout the user keeps at either path is never signalled. The Windows script needs no equivalent change: its handle scan already walks every known root by prefix, which now contains the tree. Comments in both scripts updated to describe where the install actually is. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Read the sd.cpp accelerator record from the root the binary is in The accelerator upgrade check read the record from the current managed root while the finder may hand back a binary from the tree an older build installed beside the Studio home. That root holds no record, unrecorded reads as a mismatch for a GPU target, and the matching bundle already on disk gets downloaded again on every load. owning_managed_root() returns the marker-owned root a binary actually lives under, and the check reads the record from there. is_managed_binary is now a thin wrapper over it. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Find a legacy install beside a symlinked home, and never serve a stale legacy server after an install - The compatibility lookup resolved the home before taking its parent, while the old installer took the lexical parent. For a home that is itself a symlink the tree an older build created sits next to the LINK, so the lookup missed it: a needless re-download, and the old install left orphaned from the uninstaller too. Lexical first, resolved after, both still marker-gated. - After an install that ships no sd-server, the finder's legacy probe could hand back the old server built for another accelerator, and ensure_sd_server_binary returned it without rechecking. A forced CUDA load then ran the legacy CPU server. It now returns None there, so the router uses the sd-cli the install just landed. * Apply the kwarg-spacing formatter to two files it had not been run over * Clear the legacy sd.cpp beside a symlinked Studio home, and stop reinstalling a serverless bundle Uninstall derived the legacy sibling from the canonicalized custom root, so for a Studio home that is itself a symlink it looked beside the link's TARGET while an older build had installed beside the LINK: that tree was never stopped and never removed. Take the lexical parent as an extra marker-gated candidate, matching what the finder already does. And once the current managed root holds a completed install for the accelerator being asked for, a mismatched sd-server still sitting in that legacy tree is not a reason to install again: the bundle simply shipped no server. Without this, every model load re-downloaded it. * Make the serverless suppression evidence-based, and close two gaps in the uninstall stop pass Four follow-ups on the review of the previous commit. The serverless guard fired on the accelerator record alone, which reads a server that was deleted by hand (or by the runnability repair) as a bundle that never had one, and then suppresses the very reinstall that would put it back. install() now records ships_server, taken off the archive member list, and the guard needs that recorded false plus a genuine mismatch on the legacy side. An unrecorded install stays unknown and keeps its old behavior, and a legacy server that matches the accelerator being asked for is still preferred over the one-shot CLI. The guard also has to run before _accelerator_changed, which reports "unchanged" while the managed tree is in use, so a load starting during a generation was handed the mismatched legacy server. In uninstall.sh, the lexical sibling reached the string-based deny list without being resolved, so a home carrying ".." could aim a removal at a protected tree; canonicalize a copy for that check. And the nested <root>/stable-diffusion.cpp is now stopped even when unmarked, provided the root is a Studio root this run deletes: the current-root finder can select an unmarked binary there, and the marker gate belongs to the paths that survive when unowned, not to a tree that goes regardless. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Memoise the bundle's server capability with the accelerator, not separately An unwritable install record already kept the accelerator in process memory, so a serverless install whose record could not be written read back as server-capable and the load that finds a mismatched legacy server went on reinstalling. Remember both together, or neither is trustworthy. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Give the Windows stop scan the physical path of a linked Studio home The backend resolves the home before deriving the nested sd.cpp tree, so on a junction or directory symlink the native binaries run out of the target. _CustomStudioRoots only normalizes the string (System.IO.Path.GetFullPath is lexical and never follows a reparse point), and the scan matches Win32_Process.ExecutablePath by prefix, so the running server was never matched and survived an uninstall that deleted its tree. Add the reparse target to the stop scan only; the deletes still refuse to chase a link out of the expected location. * Scope both uninstall stop paths to trees this run actually owns The lexical sd.cpp sweep skipped the ownership check the canonical loop makes first, so a stale or mistyped UNSLOTH_STUDIO_HOME pointing at a path that was never a Studio could take the marked legacy sibling of a different, valid install. Apply _is_studio_root there too. On Windows the stop scan was handed the whole physical target of a linked home. The delete leaves that target standing, so anything there that is not ours is neither locking nor being removed; pass the Studio-managed subtrees underneath it instead, and only for the homes, since a component dir can itself be a link onto a shared runtime. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
300 lines
16 KiB
Bash
Executable file
300 lines
16 KiB
Bash
Executable file
#!/bin/bash
|
|
# SPDX-License-Identifier: AGPL-3.0-only
|
|
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
|
|
# Unit test for custom/env-mode stable-diffusion.cpp removal in scripts/uninstall.sh.
|
|
#
|
|
# A custom Studio (UNSLOTH_STUDIO_HOME=<root>) installs its native diffusion build beside
|
|
# the root at <parent>/stable-diffusion.cpp -- find_sd_cpp_binary resolves it from
|
|
# UNSLOTH_STUDIO_HOME.parent (sd_cpp_engine.py). Uninstall must remove that sibling too, or
|
|
# a stale build lingers and a fresh install's finder can pick it up. Tested hermetically:
|
|
# the real custom-root removal loop + its helpers are extracted from uninstall.sh and run
|
|
# against per-test fixtures. Follows the extract-via-sed pattern of test_uninstall_shared_icon.sh.
|
|
set -e
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
UNINSTALL_SH="$SCRIPT_DIR/../../scripts/uninstall.sh"
|
|
PASS=0
|
|
FAIL=0
|
|
|
|
_TMP_ROOT=$(mktemp -d)
|
|
trap 'rm -rf "$_TMP_ROOT"' EXIT
|
|
# Deterministic deny-list checks: keep $HOME clear of the fixture trees.
|
|
HOME="$_TMP_ROOT/home"
|
|
mkdir -p "$HOME"
|
|
|
|
assert_nodir() { _l="$1"; [ -d "$2" ] && { echo " FAIL: $_l (still present: $2)"; FAIL=$((FAIL+1)); } || { echo " PASS: $_l"; PASS=$((PASS+1)); }; }
|
|
assert_dir() { _l="$1"; [ -d "$2" ] && { echo " PASS: $_l"; PASS=$((PASS+1)); } || { echo " FAIL: $_l (missing dir $2)"; FAIL=$((FAIL+1)); }; }
|
|
|
|
# Extract the helpers the loop depends on, plus the real custom-root removal loop.
|
|
HELPERS_FILE=$(mktemp -p "$_TMP_ROOT")
|
|
{
|
|
sed -n '/^_remove_path() {/,/^}/p' "$UNINSTALL_SH"
|
|
sed -n '/^_is_studio_root() {/,/^}/p' "$UNINSTALL_SH"
|
|
sed -n '/^_is_unsafe_root() {/,/^}/p' "$UNINSTALL_SH"
|
|
# The loop removes roots through this wrapper; without it and its marker helper the
|
|
# fragment dies with "command not found" and every assertion below is vacuous.
|
|
sed -n '/^_set_marker() {/,/^}/p' "$UNINSTALL_SH"
|
|
sed -n '/^_remove_root_recording_db() {/,/^}/p' "$UNINSTALL_SH"
|
|
# The owned-root lister that decides which sd-servers are pkilled before a tree is deleted.
|
|
sed -n '/^_owned_sd_cpp_roots() {/,/^}/p' "$UNINSTALL_SH"
|
|
# ... and the sibling-base lister it asks for the legacy <parent>/stable-diffusion.cpp paths.
|
|
sed -n '/^_sd_cpp_sibling_bases() {/,/^}/p' "$UNINSTALL_SH"
|
|
} > "$HELPERS_FILE"
|
|
grep -q '_owned_sd_cpp_roots' "$HELPERS_FILE" || { echo "FAIL: helpers missing _owned_sd_cpp_roots"; exit 1; }
|
|
grep -q '_sd_cpp_sibling_bases() {' "$HELPERS_FILE" || { echo "FAIL: helpers missing _sd_cpp_sibling_bases"; exit 1; }
|
|
# Both blocks sit inside the main removal function, so they are indented: anchor on optional
|
|
# leading whitespace, never on column 0, or the range matches nothing and every assertion below
|
|
# passes or fails vacuously against a fragment that was never extracted.
|
|
LOOP_FILE=$(mktemp -p "$_TMP_ROOT")
|
|
sed -n '/^[[:space:]]*_custom_studio_roots | while IFS= read -r _custom_root; do/,/^[[:space:]]*done/p' "$UNINSTALL_SH" > "$LOOP_FILE"
|
|
# The follow-up block that clears the legacy sibling hanging off the LEXICAL parent.
|
|
LEXICAL_FILE=$(mktemp -p "$_TMP_ROOT")
|
|
sed -n '/^[[:space:]]*_custom_studio_roots lexical 2>\/dev\/null | while IFS= read -r _lex_root; do/,/^[[:space:]]*done/p' "$UNINSTALL_SH" > "$LEXICAL_FILE"
|
|
# The real root resolver, for the symlinked-home cases at the end (the others stub it).
|
|
REAL_ROOTS_FILE=$(mktemp -p "$_TMP_ROOT")
|
|
sed -n '/^_custom_studio_roots() {/,/^}/p' "$UNINSTALL_SH" > "$REAL_ROOTS_FILE"
|
|
# The real default-mode ~/.unsloth/stable-diffusion.cpp removal block (marker-guarded).
|
|
DEFAULT_FILE=$(mktemp -p "$_TMP_ROOT")
|
|
sed -n '/^[[:space:]]*_default_sd_cpp="\$HOME\/\.unsloth\/stable-diffusion\.cpp"/,/^[[:space:]]*fi/p' "$UNINSTALL_SH" > "$DEFAULT_FILE"
|
|
|
|
# A silently empty extraction is what made this suite vacuous, so fail loudly instead.
|
|
for _f in "$HELPERS_FILE" "$LOOP_FILE" "$DEFAULT_FILE" "$LEXICAL_FILE" "$REAL_ROOTS_FILE"; do
|
|
[ -s "$_f" ] || { echo "FAIL: extracted an empty fragment from $UNINSTALL_SH"; exit 1; }
|
|
done
|
|
grep -q '_remove_path' "$LOOP_FILE" || { echo "FAIL: loop fragment missing _remove_path"; exit 1; }
|
|
grep -q '_remove_path' "$DEFAULT_FILE" || { echo "FAIL: default fragment missing _remove_path"; exit 1; }
|
|
|
|
# shellcheck disable=SC1090
|
|
. "$HELPERS_FILE"
|
|
|
|
# make_studio <root> : a valid custom Studio root (share/studio.conf owner marker) plus its
|
|
# sibling <parent>/stable-diffusion.cpp build carrying the Studio owner marker, each with a
|
|
# file so removal is observable.
|
|
make_studio() {
|
|
mkdir -p "$1/share"
|
|
: > "$1/share/studio.conf"
|
|
_sib="$(dirname "$1")/stable-diffusion.cpp"
|
|
mkdir -p "$_sib"
|
|
: > "$_sib/sd-cli"
|
|
: > "$_sib/.unsloth-studio-owned" # written by install_sd_cpp_prebuilt on a real install
|
|
}
|
|
run_loop() {
|
|
# shellcheck disable=SC1090
|
|
. "$LOOP_FILE"
|
|
}
|
|
run_default_removal() {
|
|
# shellcheck disable=SC1090
|
|
. "$DEFAULT_FILE"
|
|
}
|
|
run_lexical_removal() {
|
|
# shellcheck disable=SC1090
|
|
. "$LEXICAL_FILE"
|
|
}
|
|
|
|
# 1. Single custom root -> root AND its sibling stable-diffusion.cpp both removed.
|
|
p1="$_TMP_ROOT/inst1"
|
|
make_studio "$p1/studioA"
|
|
: > "$p1/keep.txt" # unrelated sibling file must be untouched
|
|
_custom_studio_roots() { printf '%s\n' "$p1/studioA"; }
|
|
run_loop
|
|
assert_nodir "single custom root removed" "$p1/studioA"
|
|
assert_nodir "custom-root sibling stable-diffusion.cpp removed" "$p1/stable-diffusion.cpp"
|
|
[ -f "$p1/keep.txt" ] && { echo " PASS: unrelated sibling file kept"; PASS=$((PASS+1)); } || { echo " FAIL: unrelated sibling file removed"; FAIL=$((FAIL+1)); }
|
|
|
|
# 2. Two custom roots sharing a parent share one sd.cpp -> all removed, no error on the
|
|
# second (already-gone) removal.
|
|
p2="$_TMP_ROOT/inst2"
|
|
make_studio "$p2/studioB"
|
|
make_studio "$p2/studioC" # same parent -> same sibling sd.cpp
|
|
_custom_studio_roots() { printf '%s\n%s\n' "$p2/studioB" "$p2/studioC"; }
|
|
run_loop
|
|
assert_nodir "shared-parent root B removed" "$p2/studioB"
|
|
assert_nodir "shared-parent root C removed" "$p2/studioC"
|
|
assert_nodir "shared sibling stable-diffusion.cpp removed" "$p2/stable-diffusion.cpp"
|
|
|
|
# 3. A sibling stable-diffusion.cpp WITHOUT the Studio owner marker (a user's own checkout,
|
|
# even a built one, beside a custom root -- or one left when UNSLOTH_SD_CPP_PATH points
|
|
# Studio elsewhere) is KEPT, though the Studio root itself is still removed.
|
|
p3="$_TMP_ROOT/inst3"
|
|
mkdir -p "$p3/studioD/share"; : > "$p3/studioD/share/studio.conf"
|
|
mkdir -p "$p3/stable-diffusion.cpp/build/bin"
|
|
: > "$p3/stable-diffusion.cpp/build/bin/sd-cli" # user's own build, no owner marker
|
|
: > "$p3/stable-diffusion.cpp/main.cpp"
|
|
_custom_studio_roots() { printf '%s\n' "$p3/studioD"; }
|
|
run_loop
|
|
assert_nodir "unowned-sibling: custom root still removed" "$p3/studioD"
|
|
assert_dir "unowned sibling stable-diffusion.cpp kept" "$p3/stable-diffusion.cpp"
|
|
|
|
# 4. Default-mode sd.cpp (a bare ~/.unsloth/stable-diffusion.cpp with no custom root) is NOT
|
|
# touched by the custom-root loop -- it is removed by the separate default-mode line.
|
|
mkdir -p "$HOME/.unsloth/stable-diffusion.cpp"
|
|
_custom_studio_roots() { printf '%s\n' "$p1/studioA"; } # a now-removed root -> guard skips
|
|
run_loop
|
|
assert_dir "default-mode sd.cpp untouched by custom loop" "$HOME/.unsloth/stable-diffusion.cpp"
|
|
|
|
# 5. Default-mode ~/.unsloth/stable-diffusion.cpp WITH the Studio owner marker (a real Studio
|
|
# default install) IS removed by the default-mode line.
|
|
rm -rf "$HOME/.unsloth/stable-diffusion.cpp"
|
|
mkdir -p "$HOME/.unsloth/stable-diffusion.cpp/build/bin"
|
|
: > "$HOME/.unsloth/stable-diffusion.cpp/build/bin/sd-cli"
|
|
: > "$HOME/.unsloth/stable-diffusion.cpp/.unsloth-studio-owned" # written by install_sd_cpp_prebuilt
|
|
run_default_removal
|
|
assert_nodir "default-mode owned sd.cpp removed" "$HOME/.unsloth/stable-diffusion.cpp"
|
|
|
|
# 6. Default-mode ~/.unsloth/stable-diffusion.cpp WITHOUT the marker -- a user's own checkout at
|
|
# the default path (or a pre-marker Studio build) -- is KEPT, mirroring the custom-root guard,
|
|
# so uninstall never deletes a user file.
|
|
rm -rf "$HOME/.unsloth/stable-diffusion.cpp"
|
|
mkdir -p "$HOME/.unsloth/stable-diffusion.cpp"
|
|
: > "$HOME/.unsloth/stable-diffusion.cpp/main.cpp" # user's own checkout, no owner marker
|
|
run_default_removal
|
|
assert_dir "default-mode unowned sd.cpp kept" "$HOME/.unsloth/stable-diffusion.cpp"
|
|
|
|
|
|
# ── _owned_sd_cpp_roots: which sd-servers get stopped before their tree is deleted ────────────
|
|
#
|
|
# The install now lands at <root>/stable-diffusion.cpp, inside the custom root, and the custom
|
|
# root is removed wholesale by the loop above. A resident sd-server survives unlinking its
|
|
# binary, so if that nested path is not listed here the tree disappears while the server keeps
|
|
# running and holding its port. The legacy <parent>/stable-diffusion.cpp sibling an older build
|
|
# wrote still has to be listed too, since it is deleted separately.
|
|
assert_lists() {
|
|
_l="$1"; _want="$2"
|
|
if _owned_sd_cpp_roots | grep -qxF "$_want"; then
|
|
echo " PASS: $_l"; PASS=$((PASS+1))
|
|
else
|
|
echo " FAIL: $_l (not listed: $_want)"; FAIL=$((FAIL+1))
|
|
fi
|
|
}
|
|
assert_not_lists() {
|
|
_l="$1"; _want="$2"
|
|
if _owned_sd_cpp_roots | grep -qxF "$_want"; then
|
|
echo " FAIL: $_l (listed anyway: $_want)"; FAIL=$((FAIL+1))
|
|
else
|
|
echo " PASS: $_l"; PASS=$((PASS+1))
|
|
fi
|
|
}
|
|
|
|
# 7. The nested install under the custom root is listed, so its sd-server is stopped first.
|
|
p7="$_TMP_ROOT/inst7/studioE"
|
|
mkdir -p "$p7/stable-diffusion.cpp/build/bin"
|
|
: > "$p7/stable-diffusion.cpp/build/bin/sd-server"
|
|
: > "$p7/stable-diffusion.cpp/.unsloth-studio-owned"
|
|
_custom_studio_roots() { printf '%s\n' "$p7"; }
|
|
assert_lists "nested <root>/stable-diffusion.cpp is stopped before removal" "$p7/stable-diffusion.cpp"
|
|
|
|
# 8. A root this run does NOT delete (no Studio sentinels) keeps its unowned nested build running.
|
|
p8="$_TMP_ROOT/inst8/studioF"
|
|
mkdir -p "$p8/stable-diffusion.cpp/build/bin"
|
|
: > "$p8/stable-diffusion.cpp/build/bin/sd-server" # no owner marker: the user's own build
|
|
_custom_studio_roots() { printf '%s\n' "$p8"; }
|
|
assert_not_lists "unowned nested build under a non-Studio root is left running" "$p8/stable-diffusion.cpp"
|
|
|
|
# 8b. But under a real Studio root, which the loop deletes wholesale, the unmarked nested build is
|
|
# stopped anyway: the current-root finder can select it without a marker, and deleting the tree
|
|
# around a live server just leaves it holding its port.
|
|
p8b="$_TMP_ROOT/inst8b/studioF2"
|
|
mkdir -p "$p8b/share" "$p8b/stable-diffusion.cpp/build/bin"
|
|
: > "$p8b/share/studio.conf"
|
|
: > "$p8b/stable-diffusion.cpp/build/bin/sd-server" # no owner marker
|
|
_custom_studio_roots() { printf '%s\n' "$p8b"; }
|
|
assert_lists "unmarked nested build under a doomed Studio root is stopped" "$p8b/stable-diffusion.cpp"
|
|
|
|
# 9. The legacy sibling an older build installed is still listed (it is still deleted).
|
|
p9="$_TMP_ROOT/inst9"
|
|
mkdir -p "$p9/studioG" "$p9/stable-diffusion.cpp"
|
|
: > "$p9/stable-diffusion.cpp/.unsloth-studio-owned"
|
|
_custom_studio_roots() { printf '%s\n' "$p9/studioG"; }
|
|
assert_lists "legacy <parent>/stable-diffusion.cpp still stopped" "$p9/stable-diffusion.cpp"
|
|
|
|
# 10. Both locations at once: neither shadows the other.
|
|
p10="$_TMP_ROOT/inst10"
|
|
mkdir -p "$p10/studioH/stable-diffusion.cpp" "$p10/stable-diffusion.cpp"
|
|
: > "$p10/studioH/stable-diffusion.cpp/.unsloth-studio-owned"
|
|
: > "$p10/stable-diffusion.cpp/.unsloth-studio-owned"
|
|
_custom_studio_roots() { printf '%s\n' "$p10/studioH"; }
|
|
assert_lists "both locations: nested listed" "$p10/studioH/stable-diffusion.cpp"
|
|
assert_lists "both locations: sibling listed" "$p10/stable-diffusion.cpp"
|
|
|
|
# 11. The nested tree goes away with the root it lives in.
|
|
p11="$_TMP_ROOT/inst11"
|
|
make_studio "$p11/studioI"
|
|
mkdir -p "$p11/studioI/stable-diffusion.cpp/build/bin"
|
|
: > "$p11/studioI/stable-diffusion.cpp/build/bin/sd-cli"
|
|
: > "$p11/studioI/stable-diffusion.cpp/.unsloth-studio-owned"
|
|
_custom_studio_roots() { printf '%s\n' "$p11/studioI"; }
|
|
run_loop
|
|
assert_nodir "nested stable-diffusion.cpp removed with its root" "$p11/studioI/stable-diffusion.cpp"
|
|
assert_nodir "its custom root removed" "$p11/studioI"
|
|
|
|
|
|
# ── A Studio home that is itself a symlink ────────────────────────────────────────────────────
|
|
#
|
|
# The old build derived the sd.cpp root with a plain `dirname "$UNSLOTH_STUDIO_HOME"`, so for
|
|
# UNSLOTH_STUDIO_HOME=<link> the tree it installed sits beside the LINK. _custom_studio_roots
|
|
# canonicalizes the home before this code takes its parent, which points every legacy consumer
|
|
# beside the link's TARGET instead: the tree beside the link was never stopped and never deleted.
|
|
# The real resolver from here on, since a stub cannot express the link at all.
|
|
# shellcheck disable=SC1090
|
|
. "$REAL_ROOTS_FILE"
|
|
unset STUDIO_HOME
|
|
|
|
# 12. The legacy sibling beside the link is listed, so its sd-server is stopped before removal.
|
|
p12="$_TMP_ROOT/inst12"
|
|
mkdir -p "$p12/real/studioJ/share" "$p12/stable-diffusion.cpp"
|
|
: > "$p12/real/studioJ/share/studio.conf"
|
|
ln -s "$p12/real/studioJ" "$p12/link"
|
|
: > "$p12/stable-diffusion.cpp/sd-server"
|
|
: > "$p12/stable-diffusion.cpp/.unsloth-studio-owned"
|
|
UNSLOTH_STUDIO_HOME="$p12/link"
|
|
export UNSLOTH_STUDIO_HOME
|
|
assert_lists "symlinked home: sd.cpp beside the link is stopped" "$p12/stable-diffusion.cpp"
|
|
|
|
# 13. ... and removed.
|
|
run_lexical_removal
|
|
assert_nodir "symlinked home: sd.cpp beside the link removed" "$p12/stable-diffusion.cpp"
|
|
|
|
# 14. An unowned checkout beside the link is kept, exactly like the canonical pass keeps one.
|
|
p14="$_TMP_ROOT/inst14"
|
|
mkdir -p "$p14/real/studioK/share" "$p14/stable-diffusion.cpp"
|
|
: > "$p14/real/studioK/share/studio.conf"
|
|
ln -s "$p14/real/studioK" "$p14/link"
|
|
: > "$p14/stable-diffusion.cpp/main.cpp" # the user's own checkout, no owner marker
|
|
UNSLOTH_STUDIO_HOME="$p14/link"
|
|
run_lexical_removal
|
|
assert_dir "symlinked home: unowned sd.cpp beside the link kept" "$p14/stable-diffusion.cpp"
|
|
assert_not_lists "symlinked home: unowned sd.cpp beside the link is left running" "$p14/stable-diffusion.cpp"
|
|
|
|
# 14b. A stale or mistyped home is not a Studio root, and the lexical pass must apply the same
|
|
# ownership check the canonical loop does: "<parent>/typo" must not take the marked
|
|
# <parent>/stable-diffusion.cpp belonging to a different, valid install.
|
|
p14b="$_TMP_ROOT/inst14b"
|
|
mkdir -p "$p14b/other/share" "$p14b/stable-diffusion.cpp"
|
|
: > "$p14b/other/share/studio.conf" # somebody else's Studio, sharing the parent
|
|
: > "$p14b/stable-diffusion.cpp/sd-cli"
|
|
: > "$p14b/stable-diffusion.cpp/.unsloth-studio-owned"
|
|
UNSLOTH_STUDIO_HOME="$p14b/typo" # never existed
|
|
run_lexical_removal
|
|
assert_dir "a mistyped home does not take a neighbour's legacy sd.cpp" "$p14b/stable-diffusion.cpp"
|
|
|
|
# 15. The deny list is a string match, so the lexical path has to be canonicalized before it is
|
|
# applied: a home carrying ".." (or a symlinked ancestor) otherwise produces a sibling that
|
|
# misses the pattern while resolving straight into a protected tree. $HOME's parent is on that
|
|
# list, so point HOME inside the fixture to get a deterministic denied target.
|
|
p15="$_TMP_ROOT/inst15"
|
|
mkdir -p "$p15/sub" "$p15/studioL/share" "$p15/stable-diffusion.cpp"
|
|
: > "$p15/studioL/share/studio.conf"
|
|
: > "$p15/stable-diffusion.cpp/sd-cli"
|
|
: > "$p15/stable-diffusion.cpp/.unsloth-studio-owned"
|
|
_HOME_BEFORE="$HOME"
|
|
HOME="$p15/stable-diffusion.cpp/home" # so dirname "$HOME" is the denied path
|
|
mkdir -p "$HOME"
|
|
UNSLOTH_STUDIO_HOME="$p15/sub/../studioL" # lexical parent "$p15/sub/.." -> misses the raw match
|
|
run_lexical_removal
|
|
assert_dir "lexical sibling resolving into a denied tree is refused" "$p15/stable-diffusion.cpp"
|
|
HOME="$_HOME_BEFORE"
|
|
unset UNSLOTH_STUDIO_HOME
|
|
|
|
echo ""
|
|
echo "Results: $PASS passed, $FAIL failed"
|
|
[ "$FAIL" = 0 ]
|