From c75352af3d787377c4aa62baa1331f37db3d1d97 Mon Sep 17 00:00:00 2001 From: "Robin H. Johnson" Date: Mon, 12 Mar 2012 01:28:44 -0700 Subject: [PATCH] sh/tmpfiles: tmpfiles.d support. This is the baseline support for tmpfiles.d. Still missing: - SELinux relabel, pending upstream clarification - LIBDIR vs multilib systems, pending upstream clarification - Whitespace in paths? - Clean support not implemented - "x" exclude type not implemented X-Gentoo-Bug: 396003 X-Gentoo-Bug-URL: https://bugs.gentoo.org/show_bug.cgi?id=396003 Signed-off-by: Robin H. Johnson --- sh/.gitignore | 1 + sh/Makefile | 4 +- sh/tmpfiles.sh.in | 286 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 289 insertions(+), 2 deletions(-) create mode 100755 sh/tmpfiles.sh.in diff --git a/sh/.gitignore b/sh/.gitignore index f67b992a..3f6ef3f7 100644 --- a/sh/.gitignore +++ b/sh/.gitignore @@ -9,3 +9,4 @@ init-early.sh ifwatchd-carrier.sh ifwatchd-nocarrier.sh udhcpc-hook.sh +tmpfiles.sh diff --git a/sh/Makefile b/sh/Makefile index 15b24d06..4df8fdea 100644 --- a/sh/Makefile +++ b/sh/Makefile @@ -1,8 +1,8 @@ DIR= ${LIBEXECDIR}/sh SRCS= init.sh.in functions.sh.in gendepends.sh.in init-common-post.sh.in \ - rc-functions.sh.in runscript.sh.in ${SRCS-${OS}} + rc-functions.sh.in runscript.sh.in tmpfiles.sh.in ${SRCS-${OS}} INC= init-common-post.sh rc-mount.sh functions.sh rc-functions.sh -BIN= gendepends.sh init.sh runscript.sh ${BIN-${OS}} +BIN= gendepends.sh init.sh runscript.sh tmpfiles.sh ${BIN-${OS}} INSTALLAFTER= _installafter diff --git a/sh/tmpfiles.sh.in b/sh/tmpfiles.sh.in new file mode 100755 index 00000000..9c738975 --- /dev/null +++ b/sh/tmpfiles.sh.in @@ -0,0 +1,286 @@ +#!/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 +# + +warninvalid() { + printf "tmpfiles: ignoring invalid entry on line %d of \`%s'\n" "$LINENUM" "$FILE" + error=$(( error+1 )) +} >&2 + +relabel() { + local paths=$1 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" + # TODO: SELinux relabel + 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" ] && 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" ] && 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 + 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 + + 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 + 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 + find "$path" -mindepth 1 -maxdepth 1 -xdev -exec rm -rf {} + + fi + + if [ $CREATE -gt 0 ]; then + 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" ] && 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 + 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 paths=$1 + + [ $REMOVE -gt 0 ] || return 0 + + 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 paths=$1 + + [ $REMOVE -gt 0 ] || return 0 + + for path in "${paths}"; do + [ -d $path ] && 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 + [ -f "$path" ] && echo "$arg" >>"$path" +} + +_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= +# TODO: The systemd spec explicitly says /usr/lib/, but it should probably be +# OUTSIDE of lib entirely, or at the very least handle multilib systems better. +tmpfiles_dirs='/usr/lib64/tmpfiles.d/ /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 $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 [ $(( CREATE + REMOVE )) -ne 1 ] ; then + printf 'usage: %s [--create] [--remove]\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! + + # TODO: Sorry, we don't handle whitespace in paths. + while read line; do + LINENUM=$(( LINENUM+1 )) + + # This will fix up whitespace and comment lines + # skip over comments and empty lines + set -- $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) ;; + *) 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} = '-' ] && uid=0 + [ ${5} = '-' ] && gid=0 + [ ${6} = '-' ] && age=0 + [ ${7} = '-' ] && arg='' + set -- "$path" "$mode" "$uid" "$gid" "$age" "$arg" + + [ "$VERBOSE" -eq "1" ] && echo _$cmd "$@" + if [ "${DRYRUN}" -eq "0" ]; then + _$cmd "$@" + rc=$? + [ $rc -ne 0 ] && error=$((error + 1)) + fi + done <$FILE +done + +exit $error + +# vim: set ts=2 sw=2 sts=2 noet ft=sh: