#!/usr/bin/env sh

# doasedit
# Link:         git.io/doasedit

# Dependencies: opendoas <https://github.com/Duncaen/OpenDoas> or doas <https://cvsweb.openbsd.org/src/usr.bin/doas/>

# Author:       Stanislav <git.io/monesonn>
# Description:  doas equivalent to sudoedit.
# Licence:      ISC
# Contribution: If you have some ideas, how to improve this script, feel free to make pull request or write an issue.

# Tips:         Use this script with enabled persistence in doas.conf file, like:
#
#                   `permit persist :wheel`
#
#               Also make sure that your doas.conf is owned by root with read-only permissions, like:
#
#                   # chown -c root:root /path/to/doas.conf && chmod 0400 path/to/doas.conf
#

# Throw error message.
err() {
    printf "%s%s\n" "doasedit: " "${1}" >&2
    exit "${2}"
}

# Catch arguments.
if [ -n "${2}" ]; then
    err "expected only one argument" 1
elif [ -z "${1}" ]; then
    err "no file path provided" 2
elif [ "$(id -u)" -eq 0 ]; then
    err "doasedit: cannot be run as root" 3
elif [ -d "${1}" ]; then
    err "${1} is a directory" 4
fi

# Safe shell options.
set -eu

# Reset doas password promt. If you want, feel free to comment this.
case "$(uname -s | tr '[:upper:]' '[:lower:]')" in
    linux) doas -L ;;
esac

# Absolute path to the source file (file for editing).
src="$(doas readlink -f "${1}")"

# Filename for the source file.
filename="${src##*/}"

# If filename have suffix then also use it for a temporary file.
# It's kinda useful for editors, that have plugins or something else for certain file extensions.
[ "$filename" = "${filename##*.}" ] && filename_ext="" || filename_ext=".${filename##*.}"

# Be sure /tmp as tmp directory by setting TMPDIR env.
export TMPDIR=/tmp

# Create a temporary directory.
tmp_d=$(mktemp -d)

# Create a temporary file for the source file in a temporary directory.
tmp_f="${tmp_d}/${filename}$(xxd -l4 -ps /dev/urandom)${filename_ext}"

# File writeability condition.
if [ -w "$src" ]; then
    err "$filename: editing files in a writable directory is not permitted" 5
fi

# Other conditions: if file exist, readable, etc.
if [ -f "${src}" ] && [ ! -r "${src}" ]; then
    doas cat "${src}" > "${tmp_f}" || err "the file is not readable" 6
elif [ -r "${src}" ]; then
    cat "${src}" > "${tmp_f}" || err "cannot transfer to content of the file to temporary one" 7
elif [ ! -f "${src}" ]; then
    # Grant .rw-r--r-- permission for newly created files by default owned by root.
    :> "${tmp_f}" && doas chmod -R 0644 "${tmp_f}" || err "cannot create new file" 8
else
    err "unexpected script behaviour" 9
fi

# Create copy of the temporary file.
tmp_cf="${tmp_d}/${filename}$(xxd -l4 -ps /dev/urandom).copy"

# Hooks for recursive removing of a temporary directory.
trap 'rm -rf ${tmp_d}' EXIT HUP QUIT TERM INT ABRT

# Move the contents of a temporary file to its copy for later comparison.
cat "${tmp_f}" > "${tmp_cf}"

# Editing the file by the user using the default editor, if not specified, the vi is used.
${EDITOR:-vi} "${tmp_f}"

# Compare the temporary file and the temporary copy.
if cmp -s "${tmp_f}" "${tmp_cf}"; then
    printf "%s\n" "doasedit: $filename unchanged"
    exit 0
else
    # Replace the source file with temporary, repeats three times if it fails.
    for doas_tries in $(seq 1 3); do doas cp -f "${tmp_f}" "${src}" && break; done
    printf "%s\n" "doasedit: $filename changes are accepted"
    exit 0
fi
