#!/bin/bash # # /usr/lib/initscripts/arch-tmpfiles # # Control creation, deletion, and cleaning of volatile and temporary files # warninvalid() { printf "arch-tmpfiles: ignoring invalid entry on line %d of \`%s'\n" "$LINENUM" "$FILE" (( ++error )) } >&2 checkparams() { local parmreq=$1; shift local path=$1 mode=$2 uid=$3 gid=$4 # parmcount must be >= parmreq if (( $# < parmreq )); then return 1 fi # mode must be valid octal and 3 or 4 digits if [[ $mode && ! $mode =~ ^[0-7]{3,4}$ ]]; then return 1 fi # uid must be numeric or a valid user name if [[ $uid ]] && ! getent passwd "$uid" >/dev/null; then return 1 fi # gid must be numeric or a valid group name if [[ $gid ]] && ! getent group "$gid" >/dev/null; then return 1 fi return 0 } relabel() { local -a paths=($1) local mode=$2 uid=$3 gid=$4 if ! checkparams 4 "$@"; then warninvalid return fi for path in "${paths[@]}"; do if [[ -e $path ]]; then [[ $uid != '-' ]] && chown $CHOPTS "$uid" "$path" [[ $gid != '-' ]] && chgrp $CHOPTS "$gid" "$path" [[ $mode != '-' ]] && chmod $CHOPTS "$mode" "$path" fi done } _f() { # Create a file if it doesn't exist yet local path=$1 mode=$2 uid=$3 gid=$4 (( CREATE )) || return 0 if ! checkparams 4 "$@"; then warninvalid return fi if [[ ! -e $path ]]; then install -m"$mode" -o"$uid" -g"$gid" /dev/null "$path" fi } _F() { # Create or truncate a file local path=$1 mode=$2 uid=$3 gid=$4 (( CREATE )) || return 0 if ! checkparams 4 "$@"; then warninvalid return fi install -m"$mode" -o"$uid" -g"$gid" /dev/null "$path" } _d() { # Create a directory if it doesn't exist yet local path=$1 mode=$2 uid=$3 gid=$4 (( CREATE )) || return 0 if ! checkparams 4 "$@"; then warninvalid return fi if [[ ! -d "$path" ]]; then install -d -m"$mode" -o"$uid" -g"$gid" "$path" fi } _D() { # Create or empty a directory local path=$1 mode=$2 uid=$3 gid=$4 if ! checkparams 4 "$@"; then warninvalid return fi if [[ -d $path ]] && (( REMOVE )); then find "$path" -mindepth 1 -maxdepth 1 -xdev -exec rm -rf {} + fi if (( CREATE )); then install -d -m"$mode" -o"$uid" -g"$gid" "$path" fi } _p() { # Create a named pipe (FIFO) if it doesn't exist yet local path=$1 mode=$2 uid=$3 gid=$4 (( CREATE )) || return 0 if ! checkparams 4 "$@"; then warninvalid return fi if [[ ! -p "$path" ]]; then mkfifo -m$mode "$path" chown "$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 } _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 -a paths=($1) (( REMOVE )) || return 0 if ! checkparams 1 "$@"; then warninvalid return fi for path in "${paths[@]}"; do if [[ -f $path ]]; then rm -f "$path" elif [[ -d $path ]]; then rmdir "$path" fi done } _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 -a paths=($1) (( REMOVE )) || return 0 if ! checkparams 1 "$@"; then warninvalid return fi for path in "${paths[@]}"; do [[ -d $path ]] && rm -rf --one-file-system "$path" done } _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. local -a paths=($1) local mode=$2 uid=$3 gid=$4 (( CREATE )) || 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 )) || return 0 CHOPTS=-R relabel "$@" } shopt -s nullglob declare -i CREATE=0 REMOVE=0 CLEAN=0 error=0 LINENO=0 declare FILE= declare -A fragments declare -a tmpfiles_d=( /usr/lib/tmpfiles.d/*.conf /etc/tmpfiles.d/*.conf /run/tmpfiles.d/*.conf ) while (( $# )); do case $1 in --create) CREATE=1 ;; --remove) REMOVE=1 ;; esac shift done if (( !(CREATE + REMOVE) )); then printf 'usage: %s [--create] [--remove]\n' "${0##*/}" exit 1 fi # directories declared later in the tmpfiles_d array will override earlier # directories, on a per file basis. # Example: `/etc/tmpfiles.d/foo.conf' supersedes `/usr/lib/tmpfiles.d/foo.conf'. for path in "${tmpfiles_d[@]}"; do [[ -f $path ]] && fragments[${path##*/}]=${path%/*} done # catch errors in functions so we can exit with something meaningful set -E trap '(( ++error ))' ERR # loop through the gathered fragments, sorted globally by filename. # `/run/tmpfiles/foo.conf' will always be read after `/etc/tmpfiles.d/bar.conf' while read -d '' fragment; do LINENUM=0 printf -v FILE '%s/%s' "${fragments[$fragment]}" "$fragment" ### FILE FORMAT ### # XXX: We ignore the final 'Age' parameter # 0 1 2 3 4 5 # Type Path Mode UID GID Age # d /run/user 0755 root root 10d # omit read's -r flag to honor escapes here, so that whitespace can be # escaped for paths. We will _not_ honor quoted paths. while read -a line; do (( ++LINENUM )) # skip over comments and empty lines if (( ! ${#line[*]} )) || [[ ${line[0]:0:1} = '#' ]]; then continue fi # whine about invalid entries if ! type -t _${line[0]} >/dev/null; then warninvalid continue fi # fall back on defaults when parameters are passed as '-' if [[ ${line[2]} = '-' ]]; then case ${line[0]} in p|f|F) line[2]=0644 ;; d|D) line[2]=0755 ;; esac fi [[ ${line[3]} = '-' ]] && line[3]=0 [[ ${line[4]} = '-' ]] && line[4]=0 "_${line[@]}" done <"$FILE" done < <(printf '%s\0' "${!fragments[@]}" | sort -z) exit $error # vim: set ts=2 sw=2 noet: