PageRenderTime 59ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

/tools/arch-tmpfiles

https://bitbucket.org/TZ86/initscripts-fork
#! | 435 lines | 351 code | 84 blank | 0 comment | 0 complexity | e95e8e6fa59ec5e9d7ef1e3e9e53aee6 MD5 | raw file
  1. #!/bin/bash
  2. #
  3. # /usr/lib/initscripts/arch-tmpfiles
  4. #
  5. # Control creation, deletion, and cleaning of volatile and temporary files
  6. #
  7. warninvalid() {
  8. local description=$1 file=${2:-${files[$TOTALNUM]}} linenum=${3:-${linenums[$TOTALNUM]}}
  9. printf "%s:line %d: ignoring invalid entry: %s\n" "$file" "$linenum" "$description"
  10. (( ++error ))
  11. } >&2
  12. checkparams() {
  13. shift
  14. local path=$1 mode=$2 uid=$3 gid=$4 age=$5
  15. # mode must be valid octal and 3 or 4 digits
  16. if [[ $mode != '-' ]]; then
  17. if [[ ! $mode =~ ^[0-7]{3,4}$ ]]; then
  18. warninvalid "invalid mode '$mode'"
  19. return 1
  20. fi
  21. fi
  22. # uid must be numeric or a valid user name
  23. # don't try to resolve numeric IDs in case they don't exist
  24. if [[ $uid != '-' ]]; then
  25. if [[ $uid != +([0-9]) ]] && ! getent passwd "$uid" >/dev/null; then
  26. warninvalid "unknown user '$uid'"
  27. return 1
  28. fi
  29. fi
  30. # gid must be numeric or a valid group name
  31. # don't try to resolve numeric IDs in case they don't exist
  32. if [[ $gid != '-' ]]; then
  33. if [[ $gid != +([0-9]) ]] && ! getent group "$gid" >/dev/null; then
  34. warninvalid "unknown group '$gid'"
  35. return 1
  36. fi
  37. fi
  38. # age must be list of numerics separated by the following postfixes:
  39. # s, sec, m, min, h, d, w
  40. # also it can be prefixed by '~'
  41. if [[ $age != '-' ]]; then
  42. if [[ ! $age =~ ^~?([0-9]+(s|sec|m|min|h|d|w)?)+$ ]]; then
  43. warninvalid "invalid age '$age'"
  44. return 1
  45. fi
  46. fi
  47. return 0
  48. }
  49. relabel() {
  50. local -a paths=($1)
  51. local mode=$2 uid=$3 gid=$4
  52. for path in "${paths[@]}"; do
  53. if [[ -e $path ]]; then
  54. [[ $uid != '-' ]] && chown $CHOPTS "$uid" "$path"
  55. [[ $gid != '-' ]] && chgrp $CHOPTS "$gid" "$path"
  56. [[ $mode != '-' ]] && chmod $CHOPTS "$mode" "$path"
  57. fi
  58. done
  59. return 0
  60. }
  61. parse_age() {
  62. local seconds=0
  63. local numbers=(${1//[^0-9]/ })
  64. local units=(${1//[0-9]/ })
  65. for (( i = 0; i < ${#numbers[@]}; i++ )); do
  66. if [ "${units[i]}" == "m" ] || [ "${units[i]}" == "min" ]; then
  67. (( seconds += numbers[i] * 60 ))
  68. elif [ "${units[i]}" == "h" ]; then
  69. (( seconds += numbers[i] * 3600 ))
  70. elif [ "${units[i]}" == "d" ]; then
  71. (( seconds += numbers[i] * 86400 ))
  72. elif [ "${units[i]}" == "w" ]; then
  73. (( seconds += numbers[i] * 604800 ))
  74. else
  75. (( seconds += numbers[i] ))
  76. fi
  77. done
  78. echo $seconds
  79. }
  80. in_list() {
  81. local search=$1
  82. for item in "${EXCLUDE_LIST[@]}"; do
  83. [[ "$search" == $item ]] && return 0
  84. done
  85. return 1
  86. }
  87. cleanup_dir() {
  88. local path=$1 age=$2
  89. local depth=1
  90. # keep first level
  91. if [[ ${age:0:1} == '~' ]]; then
  92. depth=2
  93. age=${age#'~'}
  94. fi
  95. local age=$(parse_age $age)
  96. local current_time=$(date +%s)
  97. while read -d '' file; do
  98. # don't try to remove directories which still contains some files
  99. [[ -d "$file" && $(ls -A "$file") ]] && continue
  100. local mod_time=$(stat -c %Y "$file")
  101. if (( (current_time - mod_time) > age )); then
  102. ! in_list "$file" && rm -fd "$file"
  103. fi
  104. done < <(find -P "$path" -mindepth $depth -depth -xdev -print0)
  105. }
  106. _f() {
  107. # Create a file if it doesn't exist yet
  108. local path=$1 mode=$2 uid=$3 gid=$4
  109. if [[ ! -e $path ]]; then
  110. install -m"$mode" -o"$uid" -g"$gid" /dev/null "$path"
  111. fi
  112. }
  113. _F() {
  114. # Create or truncate a file
  115. local path=$1 mode=$2 uid=$3 gid=$4
  116. install -m"$mode" -o"$uid" -g"$gid" /dev/null "$path"
  117. }
  118. _d() {
  119. # Create a directory if it doesn't exist yet
  120. local path=$1 mode=$2 uid=$3 gid=$4 age=$5
  121. if (( CLEAN )); then
  122. if [[ $age != '-' ]] && [[ -d "$path" ]]; then
  123. cleanup_dir "$path" "$age"
  124. fi
  125. fi
  126. if (( CREATE )); then
  127. if [[ ! -d "$path" ]]; then
  128. install -d -m"$mode" -o"$uid" -g"$gid" "$path"
  129. fi
  130. fi
  131. }
  132. _D() {
  133. # Create or empty a directory
  134. local path=$1 mode=$2 uid=$3 gid=$4
  135. if [[ -d $path ]] && (( REMOVE )); then
  136. find "$path" -mindepth 1 -maxdepth 1 -xdev -exec rm -rf {} +
  137. fi
  138. _d "$@"
  139. }
  140. _p() {
  141. # Create a named pipe (FIFO) if it doesn't exist yet
  142. local path=$1 mode=$2 uid=$3 gid=$4
  143. if [[ ! -p "$path" ]]; then
  144. mkfifo -m$mode "$path"
  145. chown "$uid:$gid" "$path"
  146. fi
  147. }
  148. _x() {
  149. # Ignore a path during cleaning. Use this type to exclude paths from clean-up as
  150. # controlled with the Age parameter. Note that lines of this type do not
  151. # influence the effect of r or R lines. Lines of this type accept shell-style
  152. # globs in place of of normal path names.
  153. local path=$1
  154. EXCLUDE_LIST+=("$path*(/*)")
  155. }
  156. _X() {
  157. # Ignore a path during cleanup. Use this type to prevent path removal as controlled
  158. # with the Age parameter. Note that if path is a directory, content of a directory is not
  159. # excluded from clean-up, only directory itself. Lines of this type accept
  160. # shell-style globs in place of normal path names.
  161. local path=$1
  162. EXCLUDE_LIST+=("$path")
  163. }
  164. _r() {
  165. # Remove a file or directory if it exists. This may not be used to remove
  166. # non-empty directories, use R for that. Lines of this type accept shell-style
  167. # globs in place of normal path names.
  168. local path
  169. local -a paths=($1)
  170. for path in "${paths[@]}"; do
  171. if [[ -f $path ]]; then
  172. rm -f "$path"
  173. elif [[ -d $path ]]; then
  174. rmdir "$path"
  175. fi
  176. done
  177. }
  178. _R() {
  179. # Recursively remove a path and all its subdirectories (if it is a directory).
  180. # Lines of this type accept shell-style globs in place of normal path names.
  181. local path
  182. local -a paths=($1)
  183. for path in "${paths[@]}"; do
  184. [[ -d $path ]] && rm -rf --one-file-system "$path"
  185. done
  186. }
  187. _z() {
  188. # Set ownership, access mode and relabel security context of a file or
  189. # directory if it exists. Lines of this type accept shell-style globs in
  190. # place of normal path names.
  191. local -a paths=($1)
  192. local mode=$2 uid=$3 gid=$4
  193. relabel "$@"
  194. }
  195. _Z() {
  196. # Recursively set ownership, access mode and relabel security context of a
  197. # path and all its subdirectories (if it is a directory). Lines of this type
  198. # accept shell-style globs in place of normal path names.
  199. CHOPTS=-R relabel "$@"
  200. }
  201. _m() {
  202. # If the specified file path exists, adjust its access mode, group and user to
  203. # the specified values. If it does not exist, do nothing.
  204. relabel "$@"
  205. }
  206. _w() {
  207. # Write the argument parameter to a file, if the file exists. Lines of this
  208. # type accept shell-style globs in place of normal path names. The argument
  209. # parameter will be written without a trailing newline.
  210. local path
  211. local -a paths=($1)
  212. local argument="$6"
  213. for path in "${paths[@]}"; do
  214. [[ -f $path ]] && echo -n "$argument" > "$path"
  215. done
  216. }
  217. _L() {
  218. # Create a symlink if it does not exist yet.
  219. local path=$1 source=$6
  220. if [[ ! -e "$path" && -e "$source" ]]; then
  221. ln -s "$source" "$path"
  222. fi
  223. }
  224. _L+() {
  225. # 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.
  226. local path=$1 source=$6
  227. if [[ -e "$source" ]]; then
  228. ln -sf "$source" "$path"
  229. fi
  230. }
  231. process_lines ()
  232. {
  233. local actions="$1"
  234. TOTALNUM=0
  235. while read -a line; do
  236. (( ++TOTALNUM ))
  237. [[ "${line[0]:0:1}" != $actions ]] && continue
  238. # fill empty parameters
  239. [[ "${line[2]}" ]] || line[2]='-'
  240. [[ "${line[3]}" ]] || line[3]='-'
  241. [[ "${line[4]}" ]] || line[4]='-'
  242. [[ "${line[5]}" ]] || line[5]='-'
  243. # skip invalid entries
  244. if ! checkparams "${line[@]}"; then
  245. continue
  246. fi
  247. # fall back on defaults when parameters are passed as '-'
  248. if [[ ${line[2]} = '-' ]]; then
  249. case ${line[0]} in
  250. p|f|F) line[2]=0644 ;;
  251. d|D) line[2]=0755 ;;
  252. esac
  253. fi
  254. if [[ "${line[0]}" = [pfFdD] ]]; then
  255. [[ ${line[3]} = '-' ]] && line[3]='root'
  256. [[ ${line[4]} = '-' ]] && line[4]='root'
  257. fi
  258. "_${line[@]}"
  259. done < <(printf '%s\n' "${lines[@]}")
  260. }
  261. shopt -s nullglob
  262. shopt -s extglob
  263. declare -i CREATE=0 REMOVE=0 CLEAN=0 ONBOOT=0
  264. declare -i error=0 LINENUM=0 TOTALNUM=0
  265. declare FILE=
  266. declare -A fragments
  267. declare -a tmpfiles_d=(
  268. /usr/lib/tmpfiles.d/*.conf
  269. /etc/tmpfiles.d/*.conf
  270. /run/tmpfiles.d/*.conf
  271. )
  272. declare -a EXCLUDE_LIST lines linenums files
  273. while (( $# )); do
  274. case $1 in
  275. --create) CREATE=1 ;;
  276. --remove) REMOVE=1 ;;
  277. --clean) CLEAN=1 ;;
  278. --boot) ONBOOT=1 ;;
  279. *) break ;;
  280. esac
  281. shift
  282. done
  283. if (( !(CREATE + REMOVE + CLEAN) )); then
  284. printf 'usage: %s [--create] [--remove] [--clean] [--boot] [FILES...]\n' "${0##*/}"
  285. exit 1
  286. fi
  287. # directories declared later in the tmpfiles_d array will override earlier
  288. # directories, on a per file basis.
  289. # Example: `/etc/tmpfiles.d/foo.conf' supersedes `/usr/lib/tmpfiles.d/foo.conf'.
  290. for path in "${tmpfiles_d[@]}"; do
  291. [[ -f $path ]] && fragments[${path##*/}]=${path%/*}
  292. done
  293. # catch errors in functions so we can exit with something meaningful
  294. set -E
  295. trap '(( ++error ))' ERR
  296. # loop through the gathered fragments, sorted globally by filename.
  297. # `/run/tmpfiles/foo.conf' will always be read after `/etc/tmpfiles.d/bar.conf'
  298. while read -d '' fragment; do
  299. LINENUM=0
  300. # make sure that a fragment contains only a base filename
  301. if [[ "$fragment" = /* ]] && [[ -f "$fragment" ]]; then
  302. fragments[${fragment##*/}]=${fragment%/*}
  303. fragment=${fragment##*/}
  304. fi
  305. if [[ -z ${fragments[$fragment]} ]]; then
  306. printf 'warning: %s does not found\n' "$fragment"
  307. continue
  308. fi
  309. printf -v FILE '%s/%s' "${fragments[$fragment]}" "$fragment"
  310. ### FILE FORMAT ###
  311. # 0 1 2 3 4 5
  312. # Type Path Mode UID GID Age
  313. # d /run/user 0755 root root 10d
  314. # omit read's -r flag to honor escapes here, so that whitespace can be
  315. # escaped for paths. We will _not_ honor quoted paths. Also make sure that
  316. # last line will be processed even if it does not contain terminating '\n'.
  317. while read -a line || [[ -n "${line[@]}" ]]; do
  318. (( ++LINENUM ))
  319. # skip over comments and empty lines
  320. if (( ! ${#line[*]} )) || [[ ${line[0]:0:1} = '#' ]]; then
  321. continue
  322. fi
  323. # process the lines with unsafe operation marker only if --boot option is
  324. # specified
  325. if [[ "${line[0]}" == *! ]]; then
  326. (( ONBOOT )) || continue
  327. line[0]=${line[0]%!}
  328. fi
  329. # whine about invalid entries
  330. if ! type -t _${line[0]} >/dev/null; then
  331. warninvalid "unknown action '${line[0]}'" "$FILE" "$LINENUM"
  332. continue
  333. fi
  334. # path cannot be empty
  335. if [[ -z "${line[1]}" ]]; then
  336. warninvalid "missed path" "$FILE" "$LINENUM"
  337. continue
  338. fi
  339. (( ++TOTALNUM ))
  340. lines[$TOTALNUM]="${line[@]}"
  341. linenums[$TOTALNUM]=$LINENUM
  342. files[$TOTALNUM]="$FILE"
  343. done <"$FILE"
  344. done < <(printf '%s\0' "${@:-${!fragments[@]}}" | sort -z)
  345. # Fill exclude list first
  346. (( CLEAN )) && process_lines "[xX]"
  347. process_lines "[dD]"
  348. (( CREATE )) && process_lines "[fFpzZmwL]"
  349. (( REMOVE )) && process_lines "[rR]"
  350. exit $error
  351. # vim: set ts=2 sw=2 noet: