#!/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 # # This instance based on the Arch Linux version: # http://projects.archlinux.org/initscripts.git/tree/arch-tmpfiles # As of 2012/01/01 # # See the tmpfiles.d manpage as well: # http://0pointer.de/public/systemd-man/tmpfiles.d.html # This script should match the manpage as of 2012/03/12 # DRYRUN=0 warninvalid() { printf "tmpfiles: ignoring invalid entry on line %d of \`%s'\n" "$LINENUM" "$FILE" error=$(( error+1 )) } >&2 dryrun_or_real() { local dryrun= [ $DRYRUN -eq 1 ] && dryrun=echo $dryrun "$@" } relabel() { local path local paths=$1 mode=$2 uid=$3 gid=$4 for path in ${paths}; do if [ -e "$path" ]; then [ $uid != '-' ] && dryrun_or_real chown $CHOPTS "$uid" "$path" [ $gid != '-' ] && dryrun_or_real chgrp $CHOPTS "$gid" "$path" [ $mode != '-' ] && dryrun_or_real chmod $CHOPTS "$mode" "$path" [ -x /sbin/restorecon ] && dryrun_or_real restorecon $CHOPTS "$path" fi done } _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 [ ! -e "$path" ] && dryrun_or_real mknod $path b ${arg%:*} ${arg#*:} } _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 [ ! -e "$path" ] && dryrun_or_real mknod $path c ${arg%:*} ${arg#*:} } _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 dryrun_or_real install -m"$mode" -o"$uid" -g"$gid" /dev/null "$path" [ -n "$arg" ] && _w "$@" 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 install -m"$mode" -o"$uid" -g"$gid" /dev/null "$path" [ -n "$arg" ] && _w "$@" } _d() { # Create a directory if it doesn't exist yet local path=$1 mode=$2 uid=$3 gid=$4 [ $CREATE -gt 0 ] || return 0 if [ ! -d "$path" ]; then dryrun_or_real 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 [ -d "$path" ] && [ $REMOVE -gt 0 ]; then dryrun_or_real find "$path" -mindepth 1 -maxdepth 1 -xdev -exec rm -rf {} + fi if [ $CREATE -gt 0 ]; then dryrun_or_real install -d -m"$mode" -o"$uid" -g"$gid" "$path" fi } _L() { # Create a symlink if it doesn't exist yet local path=$1 mode=$2 uid=$3 gid=$4 age=$5 arg=$6 [ ! -e "$path" ] && dryrun_or_real ln -s "$args" "$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 dryrun_or_real mkfifo -m$mode "$path" dryrun_or_real 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 paths=$1 [ $REMOVE -gt 0 ] || return 0 for path in ${paths}; do if [ -f "$path" ]; then dryrun_or_real rm -f "$path" elif [ -d "$path" ]; then dryrun_or_real 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 paths=$1 [ $REMOVE -gt 0 ] || return 0 for path in ${paths}; do [ -d "$path" ] && dryrun_or_real rm -rf --one-file-system "$path" done } _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 "$@" } CREATE=0 REMOVE=0 CLEAN=0 VERBOSE=0 DRYRUN=0 error=0 LINENO=0 FILE= fragments= # XXX: The harcoding of /usr/lib/ is an explicit choice by upstream tmpfiles_dirs='/usr/lib/tmpfiles.d/ /etc/tmpfiles.d/ /run/tmpfiles.d/' tmpfiles_basenames='' tmpfiles_d='' # 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 [ "$f" = "$d/systemd.conf" ] && continue [ -f $f ] && tmpfiles_basenames="${tmpfiles_basenames}\n${f##*/}" done # for f in ${d} done # for d in ${tmpfiles_dirs} tmpfiles_basenames="`printf "${tmpfiles_basenames}\n" | sort | uniq`" for b in $tmpfiles_basenames ; do 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}" done while [ $# -gt 0 ]; do case $1 in --create) CREATE=1 ;; --remove) REMOVE=1 ;; --clean) CLEAN=1 ;; # TODO: Not implemented --verbose) VERBOSE=1 ;; --dryrun|--dry-run) DRYRUN=1 ;; esac shift done if [ $(( CLEAN )) -eq 1 ] ; then printf '%s clean mode is not implemented\n' "${0##*/}" exit 1 fi if [ $(( CREATE + REMOVE )) -ne 1 ] ; then printf 'usage: %s [--create] [--remove] [--clean] [--verbose] [--dry-run]\n' "${0##*/}" exit 1 fi 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' 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! # 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 line; do LINENUM=$(( LINENUM+1 )) # This will skip over comments and empty lines set -- $line # Unless we have both command and path, skip this line. if [ -z "$1" -o -z "$2" ]; then continue fi # whine about invalid entries case $1 in f|F|w|d|D|p|L|c|b|x|r|R|z|Z) ;; \#) continue ;; *) warninvalid ; continue ;; esac cmd=$1 path=$2 # fall back on defaults when parameters are passed as '-' if [ "$3" = '-' -o "$3" = '' ]; then case ${1} in p|f|F) mode=0644 ;; d|D) mode=0755 ;; z|Z|x|r|R|L) ;; esac else mode=$3 fi uid=$4 gid=$5 age=$6 arg=$7 [ "${4}" = '-' -o "${4}" = '' ] && uid=0 [ "${5}" = '-' -o "${5}" = '' ] && gid=0 [ "${6}" = '-' -o "${6}" = '' ] && age=0 [ "${7}" = '-' -o "${7}" = '' ] && arg='' set -- "$path" "$mode" "$uid" "$gid" "$age" "$arg" [ "$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: