Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/workflows/zsh-n.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ on:
- "tests/atinit-deferred-marker.zsh"
- "tests/archive-extraction.zsh"
- "tests/completion-refresh.zsh"
- "tests/home-preparation.zsh"
- "tests/hook-ownership.zsh"
- "tests/load-object-status.zsh"
- "tests/message-formatting.zsh"
Expand Down Expand Up @@ -40,6 +41,7 @@ on:
- "tests/atinit-deferred-marker.zsh"
- "tests/archive-extraction.zsh"
- "tests/completion-refresh.zsh"
- "tests/home-preparation.zsh"
- "tests/hook-ownership.zsh"
- "tests/load-object-status.zsh"
- "tests/message-formatting.zsh"
Expand Down Expand Up @@ -291,6 +293,17 @@ jobs:
- name: Test completion refresh
run: zsh tests/completion-refresh.zsh

home-preparation:
name: Home Preparation
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Zsh
run: sudo apt update && sudo apt-get install -yq zsh
- name: Test home preparation
run: zsh -f tests/home-preparation.zsh

hook-ownership:
name: Hook ownership
runs-on: ubuntu-latest
Expand Down
98 changes: 98 additions & 0 deletions tests/home-preparation.zsh
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/usr/bin/env zsh
# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*-
# vim: ft=zsh sw=2 ts=2 et

builtin emulate -R zsh
setopt pipe_fail

fail() {
builtin print -u2 -r -- "not ok - $1"
exit 1
}

typeset project_root="${ZI_TEST_CHECKOUT:-${0:A:h:h}}"
typeset temp_root
temp_root="$(command mktemp -d "${TMPDIR:-/tmp}/zi-home-prep-test.XXXXXXXX")" ||
fail "create temporary directory"
# The failure case makes a directory read-only; restore it so cleanup can work.
trap 'command chmod -R u+rwX -- "$temp_root" 2>/dev/null; command rm -rf -- "$temp_root"' EXIT INT TERM

command mkdir -p \
"${temp_root}/home" \
"${temp_root}/cache" \
"${temp_root}/config" \
"${temp_root}/data" \
"${temp_root}/zdotdir" || fail "create isolated environment"

# Running as root would bypass the permission case entirely.
if [[ ${EUID:-$UID} -eq 0 ]]; then
builtin print -r -- "ok - skipped, the permission case is meaningless as root"
exit 0
fi

env \
HOME="${temp_root}/home" \
XDG_CACHE_HOME="${temp_root}/cache" \
XDG_CONFIG_HOME="${temp_root}/config" \
XDG_DATA_HOME="${temp_root}/data" \
ZDOTDIR="${temp_root}/zdotdir" \
ZI_TEST_CHECKOUT="$project_root" \
zsh -f <<'ZSH' || fail "home preparation is not complete, propagating or retryable"
builtin emulate -R zsh
setopt pipe_fail

builtin source "${ZI_TEST_CHECKOUT}/zi.zsh" >/dev/null 2>&1 || return 1

# A partial layout. The services directory used to be created only inside the
# branch guarded on the snippets directory being absent, so a tree with
# snippets/ present and services/ missing was accepted as ready.
command rm -rf "${ZI[SERVICES_DIR]}"
[[ -d ${ZI[SNIPPETS_DIR]} ]] || {
builtin print -u2 -r -- "fixture: the snippets directory should still exist"
return 1
}
unset 'ZI[HOME_READY]'
.zi-prepare-home || {
builtin print -u2 -r -- "partial layout: preparation reported failure"
return 1
}
[[ -d ${ZI[SERVICES_DIR]} ]] || {
builtin print -u2 -r -- "partial layout: the services directory was not created"
return 1
}

# A required directory that cannot be created has to fail, and must not mark the
# home ready, so a later call can retry.
command rm -rf "${ZI[SERVICES_DIR]}"
command chmod 500 "${ZI[HOME_DIR]}"
unset 'ZI[HOME_READY]'
.zi-prepare-home 2>/dev/null && {
command chmod 700 "${ZI[HOME_DIR]}"
builtin print -u2 -r -- "unwritable home: preparation reported success"
return 1
}
[[ -z ${ZI[HOME_READY]} ]] || {
command chmod 700 "${ZI[HOME_DIR]}"
builtin print -u2 -r -- "unwritable home: ZI[HOME_READY] was set despite failure"
return 1
}

# Retry after the cause is removed.
command chmod 700 "${ZI[HOME_DIR]}"
.zi-prepare-home || {
builtin print -u2 -r -- "retry: preparation still reported failure"
return 1
}
[[ -d ${ZI[SERVICES_DIR]} && -n ${ZI[HOME_READY]} ]] || {
builtin print -u2 -r -- "retry: the home was not completed"
return 1
}

# An already-ready home is a cheap no-op.
.zi-prepare-home || {
builtin print -u2 -r -- "second call on a ready home reported failure"
return 1
}
ZSH

builtin print -r -- "ok - home preparation completes partial layouts, propagates failure and stays retryable"
27 changes: 25 additions & 2 deletions zi.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -1380,8 +1380,7 @@ builtin setopt no_aliases
# FUNCTION: .zi-prepare-home. [[[
# Establish all required directories.
.zi-prepare-home() {
[[ -n ${ZI[HOME_READY]} ]] && return
ZI[HOME_READY]=1
[[ -n ${ZI[HOME_READY]} ]] && return 0
if [[ ! -d ${ZI[HOME_DIR]} ]]; then
command mkdir -p "${ZI[HOME_DIR]}"
command chmod 700 "${ZI[HOME_DIR]}"
Expand Down Expand Up @@ -1440,9 +1439,33 @@ builtin setopt no_aliases
command mkdir -p "${ZI[SNIPPETS_DIR]}/OMZ::plugins"
command chmod go-w "${ZI[SNIPPETS_DIR]}"
( builtin cd -q ${ZI[SNIPPETS_DIR]}; command ln -s OMZ::plugins plugins; )
fi
# Independently of the snippets directory. These were nested, so a layout with
# snippets/ already present and services/ missing, which a migration or a
# manual repair can leave behind, never created the services directory and was
# still accepted as ready. Service startup then tried to place locks and FIFOs
# below a directory that did not exist.
if [[ ! -d ${ZI[SERVICES_DIR]} ]]; then
command mkdir -p "${ZI[SERVICES_DIR]}"
command chmod go-w "${ZI[SERVICES_DIR]}"
fi
# Validate before declaring the home ready, and leave ZI[HOME_READY] unset on
# failure so a later call can retry. $ZPFX and the manual directory stay
# best-effort: they are user-facing install prefixes rather than Zi's own
# state, and the manual tree has always tolerated a creation failure.
local ___required
for ___required (
${ZI[HOME_DIR]} ${ZI[CACHE_DIR]} ${ZI[CONFIG_DIR]} ${ZI[LOG_DIR]}
${ZI[ZMODULES_DIR]} ${ZI[PLUGINS_DIR]} ${ZI[PLUGINS_DIR]}/_local---zi
${ZI[COMPLETIONS_DIR]} ${ZI[SNIPPETS_DIR]} ${ZI[SERVICES_DIR]}
) {
[[ -d $___required ]] || {
builtin print -u2 -r -- "zi: home preparation incomplete, missing: ${___required}"
return 1
}
}
ZI[HOME_READY]=1
return 0
} # ]]]
# FUNCTION: .zi-load-object. [[[
.zi-load-object() {
Expand Down
Loading