diff options
| author | davidovski <david@davidovski.xyz> | 2022-03-01 22:01:26 +0000 | 
|---|---|---|
| committer | davidovski <david@davidovski.xyz> | 2022-03-01 22:01:26 +0000 | 
| commit | efee4ebf43e376a7cd8b8abcef0c70aa90427bb4 (patch) | |
| tree | 13956b6dbca8622b71edbdd3a1c2a949c5d1aac8 /tmpfiles | |
initial commit
Diffstat (limited to 'tmpfiles')
| -rwxr-xr-x | tmpfiles | 592 | 
1 files changed, 592 insertions, 0 deletions
| diff --git a/tmpfiles b/tmpfiles new file mode 100755 index 0000000..5e4ee0a --- /dev/null +++ b/tmpfiles @@ -0,0 +1,592 @@ +#!/bin/sh +# This is a reimplementation of the systemd tmpfiles.d code +# Control creation, deletion, and cleaning of volatile and temporary files +# +# Copyright (c) 2012 Gentoo Foundation +# Released under the 2-clause BSD license. +# +# This instance is a pure-POSIX sh version, written by Robin H Johnson +# <robbat2@gentoo.org>, based on the Arch Linux version as of 2012/01/01: +# http://projects.archlinux.org/initscripts.git/tree/arch-tmpfiles +# +# See the tmpfiles.d manpage as well: +# https://www.freedesktop.org/software/systemd/man/tmpfiles.d.html +# This script should match the old manpage +# http://0pointer.de/public/systemd-man/tmpfiles.d.html +# as of 2012/03/12 and also implements some more recent features +# + +DRYRUN=0 + +checkprefix() { +	n=$1 +	shift +	for x in "$@"; do +		case ${n} in +			${x}*) return 0 ;; +	esac +	done +	return 1 +} + +warninvalid() { +	printf "tmpfiles: ignoring invalid entry on line %d of \`%s'\n" "${LINENUM}" "${FILE}" +	error=$(( error+1 )) +} >&2 + +invalid_option() { +	printf "tmpfiles: invalid option '%s'\n" "$1" >&2 +	exit 1 +} + +dryrun_or_real() { +	local dryrun= +	if [ ${DRYRUN} -eq 1 ]; then +		dryrun="echo" +	fi +	${dryrun} "$@" +} + +_chattr() { +	local attr="$2" +	case ${attr} in +		[+-=]*) : ;; +		'') return ;; +		*) attr="+${attr}" ;; +	esac +	local IFS= +	dryrun_or_real chattr "$1" "${attr}" -- "$3" +} + +_setfacl() { +	dryrun_or_real setfacl -P "$1" "$2" "$3" -- "$4" +} + +relabel() { +	local path +	local paths=$1 mode=$2 uid=$3 gid=$4 +	local status + +	status=0 +	for path in ${paths}; do +		if [ -e "${path}" ]; then +			if [ -x /sbin/restorecon ]; then +				dryrun_or_real restorecon ${CHOPTS} "${path}" || status="$?" +			fi +			if [ "${uid}" != '-' ]; then +				dryrun_or_real chown ${CHOPTS} "${uid}" "${path}" || status="$?" +			fi +			if [ "${gid}" != '-' ]; then +				dryrun_or_real chgrp ${CHOPTS} "${gid}" "${path}" || status="$?" +			fi +			if [ "${mode}" != '-' ]; then +				dryrun_or_real chmod ${CHOPTS} "${mode}" "${path}" || status="$?" +			fi +		fi +	done +	return ${status} +} + +splitpath() { +    local path=$1 +    while [ -n "${path}" ]; do +        printf '%s\n' "${path}" +        path=${path%/*} +    done +} + +_restorecon() { +    local path=$1 +    if [ -x /sbin/restorecon ]; then +        dryrun_or_real restorecon -F "$(splitpath "${path}")" +    fi +} + +createdirectory() { +	local mode="$1" uid="$2" gid="$3" path="$4" +	[ -d "${path}" ] || dryrun_or_real mkdir -p "${path}" +	if [ "${uid}" = - ]; then +		uid=root +	fi +	if [ "${gid}" = - ]; then +		gid=root +	fi +	if [ "${mode}" = - ]; then +		mode=0755 +	fi +	dryrun_or_real chown ${uid} "${path}" +	dryrun_or_real chgrp ${gid} "${path}" +	dryrun_or_real chmod ${mode} "${path}" +} + +createfile() { +	local mode="$1" uid="$2" gid="$3" path="$4" +	dryrun_or_real touch "${path}" +	if [ "${uid}" = - ]; then +		uid=root +	fi +	if [ "${gid}" = - ]; then +		gid=root +	fi +	if [ "${mode}" = - ]; then +		mode=0644 +	fi +	dryrun_or_real chown ${uid} "${path}" +	dryrun_or_real chgrp ${gid} "${path}" +	dryrun_or_real chmod ${mode} "${path}" +} + +createpipe() { +	local mode="$1" uid="$2" gid="$3" path="$4" +	dryrun_or_real mkfifo "${path}" +	if [ "${uid}" = - ]; then +		uid=root +	fi +	if [ "${gid}" = - ]; then +		gid=root +	fi +	if [ "${mode}" = - ]; then +		mode=0644 +	fi +	dryrun_or_real chown ${uid} "${path}" +	dryrun_or_real chgrp ${gid} "${path}" +	dryrun_or_real chmod ${mode} "${path}" +} + +_b() { +	# Create a block device node if it doesn't exist yet +	local path=$1 mode=$2 uid=$3 gid=$4 age=$5 arg=$6 +	if [ "${uid}" = - ]; then +		uid=root +	fi +	if [ "${gid}" = - ]; then +		gid=root +	fi +	if [ "${mode}" = - ]; then +		mode=0644 +	fi +	if [ ! -e "${path}" ]; then +		dryrun_or_real mknod -m ${mode} "${path}" b "${arg%:*}" "${arg#*:}" +		_restorecon "${path}" +		dryrun_or_real chown ${uid} "${path}" +		dryrun_or_real chgrp ${gid} "${path}" +	fi +} + +_c() { +	# Create a character device node if it doesn't exist yet +	local path=$1 mode=$2 uid=$3 gid=$4 age=$5 arg=$6 +	if [ "${uid}" = - ]; then +		uid=root +	fi +	if [ "${gid}" = - ]; then +		gid=root +	fi +	if [ "${mode}" = - ]; then +		mode=0644 +	fi +	if [ ! -e "${path}" ]; then +		dryrun_or_real mknod -m ${mode} "${path}" c "${arg%:*}" "${arg#*:}" +		_restorecon "${path}" +		dryrun_or_real chown ${uid} "${path}" +		dryrun_or_real chgrp ${gid} "${path}" +	fi +} + +_C() { +	# recursively copy a file or directory +	local path=$1 mode=$2 uid=$3 gid=$4 age=$5 arg=$6 +	if [ ! -e "${path}" ]; then +		dryrun_or_real cp -r "${arg}" "${path}" +		_restorecon "${path}" +		if [ "${uid}" != '-' ]; then +			dryrun_or_real chown "${uid}" "${path}" +		fi +		if [ "${gid}" != '-' ]; then +			dryrun_or_real chgrp "${gid}" "${path}" +		fi +		if [ "${mode}" != '-' ]; then +			dryrun_or_real chmod "${mode}" "${path}" +		fi +	fi +} + +_f() { +	# Create a file if it doesn't exist yet +	local path=$1 mode=$2 uid=$3 gid=$4 age=$5 arg=$6 + +	[ "${CREATE}" -gt 0 ] || return 0 + +	if [ ! -e "${path}" ]; then +		createfile "${mode}" "${uid}" "${gid}" "${path}" +		if [ -n "${arg}" ]; then +			_w "$@" +		fi +	fi +} + +_F() { +	# Create or truncate a file +	local path=$1 mode=$2 uid=$3 gid=$4 age=$5 arg=$6 + +	[ "${CREATE}" -gt 0 ] || return 0 + +	dryrun_or_real rm -f "${path}" +	createfile "${mode}" "${uid}" "${gid}" "${path}" +	if [ -n "${arg}" ]; then +		_w "$@" +	fi +} + +_d() { +	# Create a directory if it doesn't exist yet +	local path=$1 mode=$2 uid=$3 gid=$4 + +	if [ "${CREATE}" -gt 0 ]; then +		createdirectory "${mode}" "${uid}" "${gid}" "${path}" +		_restorecon "${path}" +	fi +} + +_D() { +	# Create or empty a directory +	local path=$1 mode=$2 uid=$3 gid=$4 + +	if [ -d "${path}" ] && [ "${REMOVE}" -gt 0 ]; then +		dryrun_or_real find "${path}" -mindepth 1 -maxdepth 1 -xdev -exec rm -rf {} + +		_restorecon "${path}" +	fi + +	if [ "${CREATE}" -gt 0 ]; then +		createdirectory "${mode}" "${uid}" "${gid}" "${path}" +		_restorecon "${path}" +	fi +} + +_v() { +	# Create a subvolume if the path does not exist yet and the file system +	# supports this (btrfs). Otherwise create a normal directory. +	# TODO: Implement btrfs subvol creation. +	_d "$@" +} + +_q() { +	# Similar to _v. However, make sure that the subvolume will be assigned +	# to the same higher-level quota groups as the subvolume it has +	# been created in. +	# TODO: Implement btrfs subvol creation. +	_d "$@" +} + +_Q() { +	# Similar to q. However, instead of copying the higher-level quota +	# group assignments from the parent as-is, the lowest quota group +	# of the parent subvolume is determined that is not the +	# leaf quota group. +	# TODO: Implement btrfs subvol creation. +	_d "$@" +} + +_a() { +	# Set/add file/directory ACL. Lines of this type accept +	# shell-style globs in place of normal path names. +	# The format of the argument field matches setfacl +	local ACTION='--remove-all --set' +	[ "${FORCE}" -gt 0 ] && ACTION='--modify' +	_setfacl '' "${ACTION}" "$6" "$1" +} + +_A() { +	# Recursively set/add file/directory ACL. Lines of this type accept +	# shell-syle globs in place of normal path names. +	# Does not follow symlinks +	local ACTION='--remove-all --set' +	[ "${FORCE}" -gt 0 ] && ACTION='--modify' +	_setfacl -R "${ACTION}" "$6" "$1" +} + +_h() { +	# Set file/directory attributes. Lines of this type accept +	# shell-style globs in place of normal path names. +	# The format of the argument field matches chattr +	_chattr '' "$6" "$1" +} + +_H() { +	# Recursively set file/directory attributes. Lines of this type accept +	# shell-syle globs in place of normal path names. +	# Does not follow symlinks +	_chattr -R "$6" "$1" +} + +_L() { +	# Create a symlink if it doesn't exist yet +	local path=$1 mode=$2 uid=$3 gid=$4 age=$5 arg=$6 +	if [ ! -e "${path}" ]; then +		dryrun_or_real ln -s "${arg}" "${path}" +	fi +	_restorecon "${path}" +} + +_p() { +	# Create a named pipe (FIFO) if it doesn't exist yet +	local path=$1 mode=$2 uid=$3 gid=$4 + +	[ "${CREATE}" -gt 0 ] || return 0 + +	if [ ! -p "${path}" ]; then +		createpipe "${mode}" "${uid}" "${gid}" "${path}" +	fi +} + +_x() { +	# Ignore a path during cleaning. Use this type to exclude paths from clean-up as +	# controlled with the Age parameter. Note that lines of this type do not +	# influence the effect of r or R lines. Lines of this type accept shell-style +	# globs in place of of normal path names. +	: +	# XXX: we don't implement this +} + +_X() { +	# Ignore a path during cleanup. Use this type to prevent path +	# removal as controled with the age parameter. Note that if path is +	# a directory, the content of the directory is not excluded from +	# clean-up, only the directory itself. +	# Lines of this type accept shell-style globs in place of normal path names. +	: +	# XXX: we don't implement this +} + +_r() { +	# Remove a file or directory if it exists. This may not be used to remove +	# non-empty directories, use R for that. Lines of this type accept shell-style +	# globs in place of normal path names. +	local path +	local paths=$1 +	local status + +	[ "${REMOVE}" -gt 0 ] || return 0 + +	status=0 +	for path in ${paths}; do +		if [ -f "${path}" ]; then +			dryrun_or_real rm -f "${path}" || status="$?" +		elif [ -d "${path}" ]; then +			dryrun_or_real rmdir "${path}" || status="$?" +		fi +	done +	return ${status} +} + +_R() { +	# Recursively remove a path and all its subdirectories (if it is a directory). +	# Lines of this type accept shell-style globs in place of normal path names. +	local path +	local paths=$1 +	local status + +	[ "${REMOVE}" -gt 0 ] || return 0 + +	status=0 +	for path in ${paths}; do +		if [ -d "${path}" ]; then +			dryrun_or_real rm -rf --one-file-system "${path}" || status="$?" +	fi +	done +	return ${status} +} + +_w() { +	# Write the argument parameter to a file, if it exists. +	local path=$1 mode=$2 uid=$3 gid=$4 age=$5 arg=$6 +	if [ -f "${path}" ]; then +		if [ ${DRYRUN} -eq 1 ]; then +			echo "echo \"${arg}\" >>\"${path}\"" +		else +			echo "${arg}" >>"${path}" +		fi +	fi +} + +_z() { +	# Set ownership, access mode and relabel security context of a file or +	# directory if it exists. Lines of this type accept shell-style globs in +	# place of normal path names. +	[ "${CREATE}" -gt 0 ] || return 0 + +	relabel "$@" +} + +_Z() { +	# Recursively set ownership, access mode and relabel security context of a +	# path and all its subdirectories (if it is a directory). Lines of this type +	# accept shell-style globs in place of normal path names. +	[ "${CREATE}" -gt 0 ] || return 0 + +	CHOPTS=-R relabel "$@" +} + +usage() { +	printf 'usage: %s [--exclude-prefix=path] [--prefix=path] [--boot] [--create] [--remove] [--clean] [--verbose] [--dry-run]\n' "${0##*/}" +	exit "${1:-0}" +} + +version() { +	# We don't record the version info anywhere currently. +	echo "opentmpfiles" +	exit 0 +} + +BOOT=0 CREATE=0 REMOVE=0 CLEAN=0 VERBOSE=0 DRYRUN=0 error=0 LINENO=0 +EXCLUDE= +PREFIX= +FILES= + +while [ $# -gt 0 ]; do +	case $1 in +		--boot) BOOT=1 ;; +		--create) CREATE=1 ;; +		--remove) REMOVE=1 ;; +		--clean) CLEAN=1 ;; # TODO: Not implemented +		--verbose) VERBOSE=1 ;; +		--dryrun|--dry-run) DRYRUN=1 ;; +		--exclude-prefix=*) EXCLUDE="${EXCLUDE}${1##--exclude-prefix=} " ;; +		--prefix=*) PREFIX="${PREFIX}${1##--prefix=} " ;; +		-h|--help) usage ;; +		--version) version ;; +		-*) invalid_option "$1" ;; +		*) FILES="${FILES} $1" +	esac +	shift +done + +if [ $(( CLEAN )) -eq 1 ] ; then +	printf '%s clean mode is not implemented\n' "${0##*/}" +	exit 1 +fi + +if [ "${CREATE}${REMOVE}" = '00' ]; then +	usage 1 >&2 +fi + +# XXX: The harcoding of /usr/lib/ is an explicit choice by upstream +tmpfiles_dirs='/usr/lib/tmpfiles.d /run/tmpfiles.d /etc/tmpfiles.d' +tmpfiles_basenames='' + +if [ -z "${FILES}" ]; then +	# Build a list of sorted unique basenames +	# directories declared later in the tmpfiles_d array will override earlier +	# directories, on a per file basename basis. +	# `/etc/tmpfiles.d/foo.conf' supersedes `/usr/lib/tmpfiles.d/foo.conf'. +	# `/run/tmpfiles/foo.conf' will always be read after `/etc/tmpfiles.d/bar.conf' +	for d in ${tmpfiles_dirs} ; do +		[ -d "${d}" ] && for f in "${d}"/*.conf ; do +			case "${f##*/}" in +				systemd.conf|systemd-*.conf) continue;; +			esac +			[ -f "${f}" ] && tmpfiles_basenames="${tmpfiles_basenames}\n${f##*/}" +		done # for f in ${d} +	done # for d in ${tmpfiles_dirs} +	# shellcheck disable=SC2059 +	FILES="$(printf "${tmpfiles_basenames}" | sort -u )" +fi + +tmpfiles_d='' + +for b in ${FILES} ; do +	if [ "${b##*/}" != "${b}" ]; then +		# The user specified a path on the command line +		# Just pass it through unaltered +		tmpfiles_d="${tmpfiles_d} ${b}" +	else +		real_f='' +		for d in ${tmpfiles_dirs} ; do +			f=${d}/${b} +			[ -f "${f}" ] && real_f=${f} +		done +		[ -f "${real_f}" ] && tmpfiles_d="${tmpfiles_d} ${real_f}" +	fi +done + +error=0 + +# loop through the gathered fragments, sorted globally by filename. +# `/run/tmpfiles/foo.conf' will always be read after `/etc/tmpfiles.d/bar.conf' +FILE= +for FILE in ${tmpfiles_d} ; do +	LINENUM=0 + +	### FILE FORMAT ### +	# XXX: We ignore the 'Age' parameter +	# 1    2              3    4    5    6   7 +	# Cmd  Path           Mode UID  GID  Age Argument +	# d    /run/user      0755 root root 10d - +	# Mode, UID, GID, Age, Argument may be omitted! +	# If Cmd ends with !, the line is only processed if --boot is passed + +	# XXX: Upstream says whitespace is NOT permitted in the Path argument. +	# But IS allowed when globs are expanded for the x/r/R/z/Z types. +	while read -r cmd path mode uid gid age arg rest; do +		LINENUM=$(( LINENUM+1 )) +		FORCE=0 + +		# Unless we have both command and path, skip this line. +		if [ -z "${cmd}" ] || [ -z "${path}" ]; then +			continue +		fi + +		case ${cmd} in +			\#*) continue ;; +		esac + +		while [ ${#cmd} -gt 1 ]; do +			case ${cmd} in +				*!) cmd=${cmd%!}; [ "${BOOT}" -eq "1" ] || continue 2 ;; +				*+) cmd=${cmd%+}; FORCE=1; ;; +				*) warninvalid ; continue 2 ;; +			esac +		done + +		# whine about invalid entries +		case ${cmd} in +			f|F|w|d|D|v|p|L|c|C|b|x|X|r|R|z|Z|q|Q|h|H|a|A) ;; +			*) warninvalid ; continue ;; +		esac + +		# fall back on defaults when parameters are passed as '-' +		if [ "${mode}" = '-' ] || [ "${mode}" = '' ]; then +			case "${cmd}" in +				p|f|F) mode=0644 ;; +				d|D|v) mode=0755 ;; +				C|z|Z|x|r|R|L) ;; +			esac +		fi + +		[ "${uid}" = '-' ] || [ "${uid}" = '' ] && uid=0 +		[ "${gid}" = '-' ] || [ "${gid}" = '' ] && gid=0 +		[ "${age}" = '-' ] || [ "${age}" = '' ] && age=0 +		[ "${arg}" = '-' ] || [ "${arg}" = '' ] && arg='' +		set -- "${path}" "${mode}" "${uid}" "${gid}" "${age}" "${arg}" + +		[ -n "${EXCLUDE}" ] && checkprefix "${path}" "${EXCLUDE}" && continue +		[ -n "${PREFIX}" ] && ! checkprefix "${path}" "${PREFIX}" && continue + +		if [ "${FORCE}" -gt 0 ]; then +			case ${cmd} in +				p|L|c|b) [ -f "${path}" ] && dryrun_or_real rm -f "${path}" +			esac +		fi + +		[ "${VERBOSE}" -eq "1" ] && echo "_${cmd}" "$@" +		"_${cmd}" "$@" +		rc=$? +		if [ "${DRYRUN}" -eq "0" ]; then +			[ ${rc} -ne 0 ] && error=$((error + 1)) +		fi +	done <"${FILE}" +done + +exit ${error} + +# vim: set ts=2 sw=2 sts=2 noet ft=sh: | 
