mirror of
https://github.com/acmesh-official/acme.sh.git
synced 2026-10-21 13:31:18 +00:00
* 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:
1 parent
a1de626512
commit
6467ca9764
1 file changed
+49
-26
@@ -2800,6 +2800,35 @@ _sed_escape_rhs() {
|
|||||||
sed -e 's/\\/\\\\/g' -e 's/&/\\&/g' -e 's/|/\\|/g'
|
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 "file" "opt" "=" "value" [";"]
|
||||||
_setopt() {
|
_setopt() {
|
||||||
__conf="$1"
|
__conf="$1"
|
||||||
@@ -2826,39 +2855,29 @@ _setopt() {
|
|||||||
return 1
|
return 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
if [ -n "$(_tail_c 1 <"$__conf")" ]; then
|
if ! __text="$(cat "$__conf")"; then
|
||||||
echo >>"$__conf"
|
_err "Cannot read $__conf."
|
||||||
|
return 1
|
||||||
fi
|
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
|
if grep -n "^$__opt$__sep" "$__conf" >/dev/null; then
|
||||||
_debug3 OK
|
_debug3 OK
|
||||||
__val="$(printf -- "%s\n" "$__val" | _sed_escape_rhs)"
|
__val="$(printf -- "%s\n" "$__val" | _sed_escape_rhs)"
|
||||||
text="$(cat "$__conf")"
|
__text="$(printf -- "%s\n" "$__text" | sed "s|^$__opt$__sep.*$|$__opt$__sep$__val$__end|")" || __sed_err=1
|
||||||
#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
|
|
||||||
|
|
||||||
elif grep -n "^#$__opt$__sep" "$__conf" >/dev/null; then
|
elif grep -n "^#$__opt$__sep" "$__conf" >/dev/null; then
|
||||||
__val="$(printf -- "%s\n" "$__val" | _sed_escape_rhs)"
|
__val="$(printf -- "%s\n" "$__val" | _sed_escape_rhs)"
|
||||||
text="$(cat "$__conf")"
|
__text="$(printf -- "%s\n" "$__text" | sed "s|^#$__opt$__sep.*$|$__opt$__sep$__val$__end|")" || __sed_err=1
|
||||||
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
|
|
||||||
|
|
||||||
else
|
else
|
||||||
_debug3 APP
|
_debug3 APP
|
||||||
#printf, not echo: dash's builtin echo interprets backslash escapes in
|
__text="$__text${__text:+$__nl}$__opt$__sep$__val$__end"
|
||||||
#the value and would corrupt it
|
fi
|
||||||
printf -- "%s\n" "$__opt$__sep$__val$__end" >>"$__conf"
|
if [ "$__sed_err" ] || ! _write_conf "$__conf" "$__text"; then
|
||||||
|
_err "Cannot save '$__opt' to $__conf."
|
||||||
|
return 1
|
||||||
fi
|
fi
|
||||||
_debug3 "$(grep -n "^$__opt$__sep" "$__conf")"
|
_debug3 "$(grep -n "^$__opt$__sep" "$__conf")"
|
||||||
}
|
}
|
||||||
@@ -2888,7 +2907,11 @@ _clear_conf() {
|
|||||||
_conf_data="$(cat "$_c_c_f")"
|
_conf_data="$(cat "$_c_c_f")"
|
||||||
#printf, not echo: dash's builtin echo interprets backslash escapes and
|
#printf, not echo: dash's builtin echo interprets backslash escapes and
|
||||||
#would corrupt saved values that contain them on every rewrite
|
#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
|
else
|
||||||
_err "Config file is empty, cannot clear"
|
_err "Config file is empty, cannot clear"
|
||||||
fi
|
fi
|
||||||
|
|||||||