* fix: keep the conf intact when writing it fails _setopt() and _clear_conf() redirected the new content straight into the conf file. The redirect truncates the file before anything is written, so a failed write (no space left on device, quota, I/O error) left a 0-byte conf, _save_conf still returned 0, and the cert could not be renewed any more even after space was freed. Write the new content to a temp file next to the conf, verify it, then rename it over the conf. The temp file is created with cp -p so the mode and owner are kept. A symlinked or bind mounted conf cannot be renamed over, so it is written in place once the temp copy is known to be good. A failed write now returns 1 with an error and the conf untouched. Fixes #7247 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RL9G1DHE5yNVtkm3FFPPNX * fix: route the append through _write_conf and keep the temp file on a failed in-place write The append branch of _setopt() still wrote into the conf with >>, so a write cut short (full disk, quota) left a half written line while _setopt returned 0, and sourcing the conf failed afterwards. Build the new content in all three branches and write it once through _write_conf. This also drops the separate trailing newline append. In the symlink / bind mount fallback, a failed in-place write leaves the conf truncated. Keep the temp file in that case, since it is the only complete copy, and name it in the error. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G7ohY7h4RUSNJ2ES14NMmY --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
Unverified
parent
a1de626512
commit
6467ca9764
@@ -2800,6 +2800,35 @@ _sed_escape_rhs() {
|
||||
sed -e 's/\\/\\\\/g' -e 's/&/\\&/g' -e 's/|/\\|/g'
|
||||
}
|
||||
|
||||
#_write_conf file content
|
||||
#Replace the conf file with the content.
|
||||
#Redirecting straight into the conf truncates it before anything is written, so
|
||||
#a failed write (e.g. no space left on device) left an empty conf and the cert
|
||||
#could not be renewed any more (#7247). Write a temp file next to the conf and
|
||||
#rename it over the conf only after the content is verified.
|
||||
_write_conf() {
|
||||
__w_conf="$1"
|
||||
__w_text="$2"
|
||||
__w_tmp="$__w_conf.$$.tmp"
|
||||
#cp -p, so the temp file carries the mode and owner of the conf
|
||||
if ! cp -p "$__w_conf" "$__w_tmp" 2>/dev/null ||
|
||||
! printf -- "%s\n" "$__w_text" 2>/dev/null >"$__w_tmp" ||
|
||||
[ "$(cat "$__w_tmp")" != "$__w_text" ]; then
|
||||
rm -f "$__w_tmp"
|
||||
return 1
|
||||
fi
|
||||
if [ ! -L "$__w_conf" ] && mv -f "$__w_tmp" "$__w_conf" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
#a symlink or a bind mounted file cannot be renamed over, write in place
|
||||
if ! cat "$__w_tmp" 2>/dev/null >"$__w_conf"; then
|
||||
#the conf may be truncated now, the temp file is the only complete copy
|
||||
_err "Cannot write $__w_conf, the new content is kept in $__w_tmp"
|
||||
return 1
|
||||
fi
|
||||
rm -f "$__w_tmp"
|
||||
}
|
||||
|
||||
#setopt "file" "opt" "=" "value" [";"]
|
||||
_setopt() {
|
||||
__conf="$1"
|
||||
@@ -2826,39 +2855,29 @@ _setopt() {
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
if [ -n "$(_tail_c 1 <"$__conf")" ]; then
|
||||
echo >>"$__conf"
|
||||
if ! __text="$(cat "$__conf")"; then
|
||||
_err "Cannot read $__conf."
|
||||
return 1
|
||||
fi
|
||||
|
||||
#build the new content first and write it once through _write_conf:
|
||||
#redirecting straight into the conf truncates it before anything is
|
||||
#written, so a failing sed (#2426) or a failing write (#7247) left a
|
||||
#truncated conf, and a short append left a half written line
|
||||
__sed_err=""
|
||||
if grep -n "^$__opt$__sep" "$__conf" >/dev/null; then
|
||||
_debug3 OK
|
||||
__val="$(printf -- "%s\n" "$__val" | _sed_escape_rhs)"
|
||||
text="$(cat "$__conf")"
|
||||
#capture first, write only on success: redirecting sed straight into the
|
||||
#conf file truncates it before sed runs, so a failing sed (e.g. on an
|
||||
#unescaped special character in the value) wiped the whole conf (#2426)
|
||||
if __text="$(printf -- "%s\n" "$text" | sed "s|^$__opt$__sep.*$|$__opt$__sep$__val$__end|")"; then
|
||||
printf -- "%s\n" "$__text" >"$__conf"
|
||||
else
|
||||
_err "Cannot save '$__opt' to $__conf."
|
||||
return 1
|
||||
fi
|
||||
|
||||
__text="$(printf -- "%s\n" "$__text" | sed "s|^$__opt$__sep.*$|$__opt$__sep$__val$__end|")" || __sed_err=1
|
||||
elif grep -n "^#$__opt$__sep" "$__conf" >/dev/null; then
|
||||
__val="$(printf -- "%s\n" "$__val" | _sed_escape_rhs)"
|
||||
text="$(cat "$__conf")"
|
||||
if __text="$(printf -- "%s\n" "$text" | sed "s|^#$__opt$__sep.*$|$__opt$__sep$__val$__end|")"; then
|
||||
printf -- "%s\n" "$__text" >"$__conf"
|
||||
else
|
||||
_err "Cannot save '$__opt' to $__conf."
|
||||
return 1
|
||||
fi
|
||||
|
||||
__text="$(printf -- "%s\n" "$__text" | sed "s|^#$__opt$__sep.*$|$__opt$__sep$__val$__end|")" || __sed_err=1
|
||||
else
|
||||
_debug3 APP
|
||||
#printf, not echo: dash's builtin echo interprets backslash escapes in
|
||||
#the value and would corrupt it
|
||||
printf -- "%s\n" "$__opt$__sep$__val$__end" >>"$__conf"
|
||||
__text="$__text${__text:+$__nl}$__opt$__sep$__val$__end"
|
||||
fi
|
||||
if [ "$__sed_err" ] || ! _write_conf "$__conf" "$__text"; then
|
||||
_err "Cannot save '$__opt' to $__conf."
|
||||
return 1
|
||||
fi
|
||||
_debug3 "$(grep -n "^$__opt$__sep" "$__conf")"
|
||||
}
|
||||
@@ -2888,7 +2907,11 @@ _clear_conf() {
|
||||
_conf_data="$(cat "$_c_c_f")"
|
||||
#printf, not echo: dash's builtin echo interprets backslash escapes and
|
||||
#would corrupt saved values that contain them on every rewrite
|
||||
printf -- "%s\n" "$_conf_data" | sed "/^$_sdkey *=.*$/d" >"$_c_c_f"
|
||||
if ! _conf_data="$(printf -- "%s\n" "$_conf_data" | sed "/^$_sdkey *=.*$/d")" ||
|
||||
! _write_conf "$_c_c_f" "$_conf_data"; then
|
||||
_err "Cannot clear '$_sdkey' in $_c_c_f."
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
_err "Config file is empty, cannot clear"
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user