/tools/arch-tmpfiles
#! | 435 lines | 351 code | 84 blank | 0 comment | 0 complexity | e95e8e6fa59ec5e9d7ef1e3e9e53aee6 MD5 | raw file
- #!/bin/bash
- #
- # /usr/lib/initscripts/arch-tmpfiles
- #
- # Control creation, deletion, and cleaning of volatile and temporary files
- #
- warninvalid() {
- local description=$1 file=${2:-${files[$TOTALNUM]}} linenum=${3:-${linenums[$TOTALNUM]}}
- printf "%s:line %d: ignoring invalid entry: %s\n" "$file" "$linenum" "$description"
- (( ++error ))
- } >&2
- checkparams() {
- shift
- local path=$1 mode=$2 uid=$3 gid=$4 age=$5
- # mode must be valid octal and 3 or 4 digits
- if [[ $mode != '-' ]]; then
- if [[ ! $mode =~ ^[0-7]{3,4}$ ]]; then
- warninvalid "invalid mode '$mode'"
- return 1
- fi
- fi
- # uid must be numeric or a valid user name
- # don't try to resolve numeric IDs in case they don't exist
- if [[ $uid != '-' ]]; then
- if [[ $uid != +([0-9]) ]] && ! getent passwd "$uid" >/dev/null; then
- warninvalid "unknown user '$uid'"
- return 1
- fi
- fi
- # gid must be numeric or a valid group name
- # don't try to resolve numeric IDs in case they don't exist
- if [[ $gid != '-' ]]; then
- if [[ $gid != +([0-9]) ]] && ! getent group "$gid" >/dev/null; then
- warninvalid "unknown group '$gid'"
- return 1
- fi
- fi
- # age must be list of numerics separated by the following postfixes:
- # s, sec, m, min, h, d, w
- # also it can be prefixed by '~'
- if [[ $age != '-' ]]; then
- if [[ ! $age =~ ^~?([0-9]+(s|sec|m|min|h|d|w)?)+$ ]]; then
- warninvalid "invalid age '$age'"
- return 1
- fi
- fi
- return 0
- }
- relabel() {
- local -a paths=($1)
- local mode=$2 uid=$3 gid=$4
- 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
- return 0
- }
- parse_age() {
- local seconds=0
- local numbers=(${1//[^0-9]/ })
- local units=(${1//[0-9]/ })
- for (( i = 0; i < ${#numbers[@]}; i++ )); do
- if [ "${units[i]}" == "m" ] || [ "${units[i]}" == "min" ]; then
- (( seconds += numbers[i] * 60 ))
- elif [ "${units[i]}" == "h" ]; then
- (( seconds += numbers[i] * 3600 ))
- elif [ "${units[i]}" == "d" ]; then
- (( seconds += numbers[i] * 86400 ))
- elif [ "${units[i]}" == "w" ]; then
- (( seconds += numbers[i] * 604800 ))
- else
- (( seconds += numbers[i] ))
- fi
- done
- echo $seconds
- }
- in_list() {
- local search=$1
- for item in "${EXCLUDE_LIST[@]}"; do
- [[ "$search" == $item ]] && return 0
- done
- return 1
- }
- cleanup_dir() {
- local path=$1 age=$2
- local depth=1
- # keep first level
- if [[ ${age:0:1} == '~' ]]; then
- depth=2
- age=${age#'~'}
- fi
- local age=$(parse_age $age)
- local current_time=$(date +%s)
- while read -d '' file; do
- # don't try to remove directories which still contains some files
- [[ -d "$file" && $(ls -A "$file") ]] && continue
- local mod_time=$(stat -c %Y "$file")
- if (( (current_time - mod_time) > age )); then
- ! in_list "$file" && rm -fd "$file"
- fi
- done < <(find -P "$path" -mindepth $depth -depth -xdev -print0)
- }
- _f() {
- # Create a file if it doesn't exist yet
- local path=$1 mode=$2 uid=$3 gid=$4
- 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
- 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 age=$5
- if (( CLEAN )); then
- if [[ $age != '-' ]] && [[ -d "$path" ]]; then
- cleanup_dir "$path" "$age"
- fi
- fi
- if (( CREATE )); then
- if [[ ! -d "$path" ]]; then
- install -d -m"$mode" -o"$uid" -g"$gid" "$path"
- fi
- fi
- }
- _D() {
- # Create or empty a directory
- local path=$1 mode=$2 uid=$3 gid=$4
- if [[ -d $path ]] && (( REMOVE )); then
- find "$path" -mindepth 1 -maxdepth 1 -xdev -exec rm -rf {} +
- fi
- _d "$@"
- }
- _p() {
- # Create a named pipe (FIFO) if it doesn't exist yet
- local path=$1 mode=$2 uid=$3 gid=$4
- 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.
- local path=$1
- EXCLUDE_LIST+=("$path*(/*)")
- }
- _X() {
- # Ignore a path during cleanup. Use this type to prevent path removal as controlled
- # with the Age parameter. Note that if path is a directory, content of a directory is not
- # excluded from clean-up, only directory itself. Lines of this type accept
- # shell-style globs in place of normal path names.
- local path=$1
- EXCLUDE_LIST+=("$path")
- }
- _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)
- 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)
- 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
- 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.
- CHOPTS=-R relabel "$@"
- }
- _m() {
- # If the specified file path exists, adjust its access mode, group and user to
- # the specified values. If it does not exist, do nothing.
- relabel "$@"
- }
- _w() {
- # Write the argument parameter to a file, if the file exists. Lines of this
- # type accept shell-style globs in place of normal path names. The argument
- # parameter will be written without a trailing newline.
- local path
- local -a paths=($1)
- local argument="$6"
- for path in "${paths[@]}"; do
- [[ -f $path ]] && echo -n "$argument" > "$path"
- done
- }
- _L() {
- # Create a symlink if it does not exist yet.
- local path=$1 source=$6
- if [[ ! -e "$path" && -e "$source" ]]; then
- ln -s "$source" "$path"
- fi
- }
- _L+() {
- # Create a symlink if it does not exist yet. If a file already exists where the symlink is to be created, it will be removed and be replaced by the symlink.
- local path=$1 source=$6
- if [[ -e "$source" ]]; then
- ln -sf "$source" "$path"
- fi
- }
- process_lines ()
- {
- local actions="$1"
- TOTALNUM=0
- while read -a line; do
- (( ++TOTALNUM ))
- [[ "${line[0]:0:1}" != $actions ]] && continue
- # fill empty parameters
- [[ "${line[2]}" ]] || line[2]='-'
- [[ "${line[3]}" ]] || line[3]='-'
- [[ "${line[4]}" ]] || line[4]='-'
- [[ "${line[5]}" ]] || line[5]='-'
- # skip invalid entries
- if ! checkparams "${line[@]}"; then
- 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
- if [[ "${line[0]}" = [pfFdD] ]]; then
- [[ ${line[3]} = '-' ]] && line[3]='root'
- [[ ${line[4]} = '-' ]] && line[4]='root'
- fi
- "_${line[@]}"
- done < <(printf '%s\n' "${lines[@]}")
- }
- shopt -s nullglob
- shopt -s extglob
- declare -i CREATE=0 REMOVE=0 CLEAN=0 ONBOOT=0
- declare -i error=0 LINENUM=0 TOTALNUM=0
- declare FILE=
- declare -A fragments
- declare -a tmpfiles_d=(
- /usr/lib/tmpfiles.d/*.conf
- /etc/tmpfiles.d/*.conf
- /run/tmpfiles.d/*.conf
- )
- declare -a EXCLUDE_LIST lines linenums files
- while (( $# )); do
- case $1 in
- --create) CREATE=1 ;;
- --remove) REMOVE=1 ;;
- --clean) CLEAN=1 ;;
- --boot) ONBOOT=1 ;;
- *) break ;;
- esac
- shift
- done
- if (( !(CREATE + REMOVE + CLEAN) )); then
- printf 'usage: %s [--create] [--remove] [--clean] [--boot] [FILES...]\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
- # make sure that a fragment contains only a base filename
- if [[ "$fragment" = /* ]] && [[ -f "$fragment" ]]; then
- fragments[${fragment##*/}]=${fragment%/*}
- fragment=${fragment##*/}
- fi
- if [[ -z ${fragments[$fragment]} ]]; then
- printf 'warning: %s does not found\n' "$fragment"
- continue
- fi
- printf -v FILE '%s/%s' "${fragments[$fragment]}" "$fragment"
- ### FILE FORMAT ###
- # 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. Also make sure that
- # last line will be processed even if it does not contain terminating '\n'.
- while read -a line || [[ -n "${line[@]}" ]]; do
- (( ++LINENUM ))
- # skip over comments and empty lines
- if (( ! ${#line[*]} )) || [[ ${line[0]:0:1} = '#' ]]; then
- continue
- fi
- # process the lines with unsafe operation marker only if --boot option is
- # specified
- if [[ "${line[0]}" == *! ]]; then
- (( ONBOOT )) || continue
- line[0]=${line[0]%!}
- fi
- # whine about invalid entries
- if ! type -t _${line[0]} >/dev/null; then
- warninvalid "unknown action '${line[0]}'" "$FILE" "$LINENUM"
- continue
- fi
- # path cannot be empty
- if [[ -z "${line[1]}" ]]; then
- warninvalid "missed path" "$FILE" "$LINENUM"
- continue
- fi
- (( ++TOTALNUM ))
- lines[$TOTALNUM]="${line[@]}"
- linenums[$TOTALNUM]=$LINENUM
- files[$TOTALNUM]="$FILE"
- done <"$FILE"
- done < <(printf '%s\0' "${@:-${!fragments[@]}}" | sort -z)
- # Fill exclude list first
- (( CLEAN )) && process_lines "[xX]"
- process_lines "[dD]"
- (( CREATE )) && process_lines "[fFpzZmwL]"
- (( REMOVE )) && process_lines "[rR]"
- exit $error
- # vim: set ts=2 sw=2 noet: