diff --git a/leaf b/leaf index 0c2dc57..4a66c2c 100755 --- a/leaf +++ b/leaf @@ -440,9 +440,81 @@ leaf_trace_package() { find . -type l | sort > "${_trace_dir}"/LINKS sed -i "s/.//" "${_trace_dir}"/{DIRS,FILES,LINKS} install -vm644 "${PKGBUILD_DIR}/${PKG_PREFIX}/${PKG_NAME}.PKGBUILD" "${_trace_dir}/PKGBUILD" + leaf_trace_conffiles "${_trace_dir}" popd > /dev/null 2>&1 } +leaf_trace_conffiles() { + # usage: leaf_trace_conffiles + # called with CWD = pkgdir (same as leaf_trace_package) + local _trace_dir="$1" + local _out="${_trace_dir}/CONFFILES" + local _p _type _val _mode _uid _gid + + [ -d ./etc ] || return 0 + + : > "${_out}" + + # regular files + while IFS= read -r -d '' _p; do + _mode=$(stat -c %a -- "$_p") + _uid=$(stat -c %u -- "$_p") + _gid=$(stat -c %g -- "$_p") + _val=$(sha256sum -- "$_p" | awk '{print $1}') + printf '%s\tf\t%s\t%s\t%s\t%s\n' "/${_p#./}" "${_val}" "${_mode}" "${_uid}" "${_gid}" >> "${_out}" + done < <(find ./etc -type f -print0) + + # symlinks + while IFS= read -r -d '' _p; do + _mode=$(stat -c %a -- "$_p") + _uid=$(stat -c %u -- "$_p") + _gid=$(stat -c %g -- "$_p") + _val=$(readlink -- "$_p") + printf '%s\tl\t%s\t%s\t%s\t%s\n' "/${_p#./}" "${_val}" "${_mode}" "${_uid}" "${_gid}" >> "${_out}" + done < <(find ./etc -type l -print0) + + sort -o "${_out}" "${_out}" 2>/dev/null || true +} + +leaf_conffile_is_unmodified() { + # usage: leaf_conffile_is_unmodified + # return 0 if unmodified (safe to remove), 1 otherwise + local path="$1" tdir="$2" + local cf="${tdir}/CONFFILES" + local line type val mode uid gid + local cur_val cur_mode cur_uid cur_gid + + [ -r "${cf}" ] || return 1 + [ -e "${path}" ] || return 1 + + line="$(grep -F -m1 -- "${path}"$'\t' "${cf}" 2>/dev/null)" || return 1 + + IFS=$'\t' read -r _p type val mode uid gid <<< "${line}" + + cur_mode="$(stat -c %a -- "${path}" 2>/dev/null)" || return 1 + cur_uid="$(stat -c %u -- "${path}" 2>/dev/null)" || return 1 + cur_gid="$(stat -c %g -- "${path}" 2>/dev/null)" || return 1 + + # metadata changed -> treat as modified + if [ "${cur_mode}" != "${mode}" ] || [ "${cur_uid}" != "${uid}" ] || [ "${cur_gid}" != "${gid}" ]; then + return 1 + fi + + case "${type}" in + f) + cur_val="$(sha256sum -- "${path}" 2>/dev/null | awk '{print $1}')" || return 1 + [ "${cur_val}" = "${val}" ] && return 0 || return 1 + ;; + l) + cur_val="$(readlink -- "${path}" 2>/dev/null)" || return 1 + [ "${cur_val}" = "${val}" ] && return 0 || return 1 + ;; + *) + return 1 + ;; + esac +} + leaf_update_package_database() { local _item="$(echo "$2" | awk 'BEGIN{FS="/";OFS="\\/"}{print $1,$2}')" case $1 in @@ -645,33 +717,73 @@ leaf_remove_package() { [ -n "$(grep "${PKG_PREFIX}/${PKG_NAME}" ${INSTALLED_PACKAGES})" ] || { leaf_error "Package ${PKG_PREFIX}/${PKG_NAME} is NOT installed" } + leaf_phase_must "Preremove" src_preremove "Preremove failed." + local _file _link _directory _etc_backup_path _relative_path _backup_etc=false - _etc_backup_path="/etc/.leaf_backup/${PKGNAME}_$(date +%Y%m%d%H%M%S)" - cat "${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}/FILES" | while read -r _file; do - if [[ "${_file}" == /etc/* && -f "${_file}" ]]; then - _backup_etc=true - _relative_path="${_file#/etc/}" - mkdir -pv "${_etc_backup_path}/$(dirname "${_relative_path}")" - mv -v "${_file}" "${_etc_backup_path}/${_relative_path}" + local _trace_dir="${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}" + local _has_conffiles=0 + + [ -r "${_trace_dir}/CONFFILES" ] && _has_conffiles=1 + + _etc_backup_path="/etc/.leaf_backup/${PKG_NAME}_$(date +%Y%m%d%H%M%S)" + + # FILES + while read -r _file; do + if [[ "${_file}" == /etc/* && -e "${_file}" ]]; then + if [ "${_has_conffiles}" -eq 1 ]; then + if leaf_conffile_is_unmodified "${_file}" "${_trace_dir}"; then + rm -fv -- "${_file}" + else + leaf_record_message "Preserved modified config file: ${_file}" + fi + continue + fi + + # fallback for old packages without CONFFILES (keep old behavior) + if [[ -f "${_file}" ]]; then + _backup_etc=true + _relative_path="${_file#/etc/}" + mkdir -pv "${_etc_backup_path}/$(dirname "${_relative_path}")" + mv -v -- "${_file}" "${_etc_backup_path}/${_relative_path}" + else + rm -fv -- "${_file}" + fi else - rm -fv "${_file}" + rm -fv -- "${_file}" fi - done + done < "${_trace_dir}/FILES" + if [[ "${_backup_etc}" == true ]]; then leaf_record_message "Found files in /etc, backuped to ${_etc_backup_path}." fi - cat "${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}/LINKS" | while read -r _link; do - [ ! -L "${_link}" ] || rm -fv "${_link}" - done - cat "${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}/DIRS" | while read -r _directory; do - if [[ -d ${_directory} ]]; then + + # LINKS + while read -r _link; do + if [[ "${_link}" == /etc/* && -e "${_link}" ]]; then + if [ "${_has_conffiles}" -eq 1 ]; then + if leaf_conffile_is_unmodified "${_link}" "${_trace_dir}"; then + rm -fv -- "${_link}" + else + leaf_record_message "Preserved modified config link: ${_link}" + fi + continue + fi + else + [ ! -L "${_link}" ] || rm -fv -- "${_link}" + fi + done < "${_trace_dir}/LINKS" + + # DIRS + while read -r _directory; do + if [[ -d "${_directory}" ]]; then rmdir --ignore-fail-on-non-empty "${_directory}" fi - done + done < "${_trace_dir}/DIRS" + leaf_phase_must "Postremove" src_postremove "Postremove failed." leaf_invoke_hooks remove - rm -rf "${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}" + rm -rf "${_trace_dir}" leaf_update_package_database delete "${PKG_PREFIX}/${PKG_NAME}" leaf_update_environment }