#!/sbin/openrc-run

: ${pg_version:=$(pg_versions get-default)}

name="PostgreSQL $pg_version"
description="PostgreSQL server"

extra_started_commands="stop_fast stop_force stop_smart reload reload_force promote"
description_stop_fast="Stop using Fast Shutdown mode (SIGINT)"
description_stop_force="Stop using Immediate Shutdown mode (SIGQUIT)"
description_stop_smart="Stop using Smart Shutdown mode (SIGTERM)"
description_reload="Reload configuration"
description_reload_force="Reload configuration and restart if needed"
description_promote="Promote standby server to master - exit recovery and begin read-write operations"

extra_stopped_commands="setup"
description_setup="Initialize a new $name cluster"

: ${user:="postgres"}
: ${group:="postgres"}

: ${auto_setup:="yes"}
: ${start_timeout:=10}
# nice_timeout, rude_timeout and force_timeout are for backward compatibility.
: ${stop_smart_timeout:=${nice_timeout:-5}}
: ${stop_fast_timeout:=${rude_timeout:-10}}
: ${stop_force_timeout:=${force_timeout:-0}}

: ${data_dir:="/var/lib/postgresql/$pg_version/data"}
: ${conf_dir:="/etc/postgresql"}
: ${logfile:="/var/log/postgresql/postmaster.log"}
: ${env_vars:=}
: ${pg_opts:=}
: ${port:=5432}
: ${initdb_opts:="-E UTF-8 --locale-provider=icu --icu-locale=en-001-x-icu --data-checksums"}

command="/usr/libexec/postgresql$pg_version/postgres"

conffile="$conf_dir/postgresql.conf"
pidfile="$data_dir/postmaster.pid"
start_stop_daemon_args="
	--user $user
	--group $group
	--pidfile $pidfile
	--wait 100"

depend() {
	use net
	after firewall

	if [ "$(get_config log_destination)" = "syslog" ]; then
		use logger
	fi
}

start_pre() {
	check_deprecated_var nice_timeout stop_smart_timeout
	check_deprecated_var rude_timeout stop_fast_timeout
	check_deprecated_var rude_quit stop_fast_timeout
	check_deprecated_var force_timeout stop_force_timeout
	check_deprecated_var force_quit stop_force_timeout
	check_deprecated_var env_vars 'export NAME=VALUE'

	# For backward compatibility only.
	[ "$rude_quit" = no ] && [ "stop_fast_timeout" -eq 10 ] && stop_fast_timeout=0
	[ "$force_quit" = yes ] && [ "$stop_force_timeout" -eq 0 ] && stop_force_timeout=2

	if [ ! -d "$data_dir/base" ]; then
		if yesno "$auto_setup"; then
			setup || return 1
		else
			eerror "Database not found at: $data_dir"
			eerror "Please make sure that 'data_dir' points to the right path."
			eerror "You can run '/etc/init.d/postgresql setup' to setup a new database cluster."
			return 1
		fi
	fi

	# This is mainly for backward compatibility with the former $conf_dir default value.
	if [ "$conf_dir" = /etc/postgresql ] && ! [ -f "$conf_dir/postgresql.conf" ]; then
		conf_dir=$data_dir
	fi

	local socket_dirs=$(get_config "unix_socket_directories" "/run/postgresql")
	local port=$(get_config "port" "$port")

	start_stop_daemon_args="$start_stop_daemon_args --env PGPORT=$port"

	local var; for var in $env_vars; do
		start_stop_daemon_args="$start_stop_daemon_args --env $var"
	done

	(
		# Set the proper permission for the socket paths and create them if
		# they don't exist.
		set -f; IFS=","
		for dir in $socket_dirs; do
			if [ -e "${dir%/}/.s.PGSQL.$port" ]; then
				eerror "Socket conflict. A server is already listening on:"
				eerror "    ${dir%/}/.s.PGSQL.$port"
				eerror "Hint: Change 'port' to listen on a different socket."
				return 1
			elif [ "${dir%/}" != "/tmp" ]; then
				checkpath -d -m 1775 -o $user:$group "$dir"
			fi
		done
	)
}

start() {
	ebegin "Starting $name"

	rm -f "$pidfile"
	start-stop-daemon --start \
		$start_stop_daemon_args \
		--exec /usr/bin/pg_ctl \
		-- start \
			--silent \
			-w --timeout="$start_timeout" \
			--log="$logfile" \
			--pgdata="$conf_dir" \
			-o "--data-directory=$data_dir $pg_opts"

	if eend $? "Failed to start $name"; then
		service_set_value "command" "$command"
		service_set_value "pidfile" "$pidfile"
	else
		eerror "Check the log for a possible explanation of the above error:"
		eerror "    $logfile"
		return 1
	fi
}

stop() {
	local command=$(service_get_value "command" || echo "$command")
	local pidfile=$(service_get_value "pidfile" || echo "$pidfile")
	local retry=''

	[ "$stop_smart_timeout" -eq 0 ] \
		|| retry="SIGTERM/$stop_smart_timeout"
	[ "$stop_fast_timeout" -eq 0 ] \
		|| retry="${retry:+$retry/}SIGINT/$stop_fast_timeout"
	[ "$stop_force_timeout" -eq 0 ] \
		|| retry="${retry:+$retry/}SIGQUIT/$stop_force_timeout"
	[ "$retry" ] \
		|| retry='SIGINT/5'

	local seconds=$(( $stop_smart_timeout + $stop_fast_timeout + $stop_force_timeout ))

	ebegin "Stopping $name (this can take up to $seconds seconds)"

	start-stop-daemon --stop \
		--exec "$command" \
		--retry "$retry" \
		--progress \
		--pidfile "$pidfile"
	eend $? "Failed to stop $name"
}

stop_smart() {
	_stop SIGTERM "smart shutdown"
}

stop_fast() {
	_stop SIGINT "fast shutdown"
}

stop_force() {
	_stop SIGQUIT "immediate shutdown"
}

_stop() {
	local command=$(service_get_value "command" || echo "$command")
	local pidfile=$(service_get_value "pidfile" || echo "$pidfile")

	ebegin "Stopping $name ($2)"

	start-stop-daemon --stop \
		--exec "$command" \
		--signal "$1" \
		--pidfile "$pidfile" \
		&& mark_service_stopped "$RC_SVCNAME"
	eend $? "Failed to stop $name"
}

reload() {
	ebegin "Reloading $name configuration"

	start-stop-daemon --signal HUP --pidfile "$pidfile" && check_config_errors
	local retval=$?

	is_pending_restart || true

	eend $retval
}

reload_force() {
	ebegin "Reloading $name configuration"

	start-stop-daemon --signal HUP --pidfile "$pidfile" && check_config_errors
	local retval=$?

	if [ $retval -eq 0 ] && is_pending_restart; then
		rc-service --nodeps "$RC_SVCNAME" restart
		retval=$?
	fi
	eend $retval
}

promote() {
	ebegin "Promoting $name to master"

	cd "$data_dir"  # to avoid the: could not change directory to "/root"
	su $user -c "pg_ctl promote --wait --log=$logfile --pgdata=$conf_dir -o '--data-directory=$data_dir'"
	eend $?
}

setup() {
	local bkpdir

	ebegin "Creating a new $name database cluster"

	if [ -d "$data_dir/base" ]; then
		eend 1 "$data_dir/base already exists!"; return 1
	fi

	# If data_dir exists, backup configs.
	if [ -d "$data_dir" ]; then
		bkpdir="$(mktemp -d)"
		find "$data_dir" -type f -name "*.conf" -maxdepth 1 \
			-exec mv -v {} "$bkpdir"/ \;
		rm -rf "$data_dir"/*
	fi

	install -d -m 0700 -o $user -g $group "$data_dir"
	install -d -m 0750 -o $user -g $group "$conf_dir"

	cd "$data_dir"  # to avoid the: could not change directory to "/root"
	su $user -c "/usr/bin/initdb $initdb_opts --pgdata $data_dir"
	local retval=$?

	if [ -d "$bkpdir" ]; then
		# Move backuped configs back.
		mv -v "$bkpdir"/* "$data_dir"/
		rm -rf "$bkpdir"
	fi

	local conf_dir=$(readlink -f "$conf_dir")

	if [ "${data_dir%/}" != "${conf_dir%/}" ]; then
		# Move configs from data_dir to conf_dir and symlink them to data_dir.
		local name newname
		for name in postgresql.conf pg_hba.conf pg_ident.conf; do
			newname="$name"
			[ ! -e "$conf_dir"/$name ] || newname="$name.new"

			mv "$data_dir"/$name "$conf_dir"/$newname
			ln -s "$conf_dir"/$name "$data_dir"/$name
		done
	fi

	eend $retval
} 


get_config() {
	local name="$1"
	local default="${2:-}"

	if [ ! -f "$conffile" ]; then
		printf '%s\n' "$default"
		return 1
	fi
	sed -En "/^\s*${name}\b/{                      # find line starting with the name
		  s/^\s*${name}\s*=?\s*([^#]+).*/\1/;  # capture the value
		  s/\s*$//;                            # trim trailing whitespaces
		  s/^['\"](.*)['\"]$/\1/;              # remove delimiting quotes
		  p
		}" "$conffile" \
		| grep . || printf '%s\n' "$default"
}

check_config_errors() {
	local out; out=$(psql_command "
		select
		  sourcefile || ': line ' || sourceline || ': ' || error ||
		    case when name is not null
		    then ': ' || name || ' = ''' || setting || ''''
		    else ''
		    end
		from pg_file_settings
		where error is not null
		  and name not in (select name from pg_settings where pending_restart = true);
		")
	if [ $? -eq 0 ] && [ "$out" ]; then
		eerror 'Configuration file contains errors:'
		printf '%s\n' "$out" | while read line; do
			eerror "  $line"
		done
		return 1
	fi
}

is_pending_restart() {
	local out; out=$(psql_command "select name from pg_settings where pending_restart = true;")

	if [ $? -eq 0 ] && [ "$out" ]; then
		ewarn 'PostgreSQL must be restarted to apply changes in the following parameters:'
		local line; for line in $out; do
			ewarn "  $line"
		done
		return 0
	fi
	return 1
}

check_deprecated_var() {
	local old_name="$1"
	local new_name="$2"

	if [ -n "$(getval "$old_name")" ]; then
		ewarn "Variable '$old_name' is deprecated, please use '$new_name' instead."
	fi
}

getval() {
	eval "printf '%s\n' \"\$$1\""
}

psql_command() {
	su $user -c "psql --no-psqlrc --no-align --tuples-only -q -c \"$1\""
}
