# Copyright (c) 2017 The NetBSD Foundation, Inc. # All rights reserved. # # This code is derived from software contributed to The NetBSD Foundation # by Johnny C. Lam. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS # ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # # NAME # refcount_file.subr -- directory-based reference-count implementation # # SYNOPSIS # task_refcount_file exists database resource # task_refcount_file add [-t token] database resource # task_refcount_file remove [-t token] database resource # task_refcount_file delete database resource # # task_refcount_file prop_exists database resource property # task_refcount_file prop_put database resource property [value ...] # task_refcount_file prop_match database resource property [value ...] # task_refcount_file prop_delete database resource property # # DESCRIPTION # The functions implement the refcount API functions with the # corresponding names. # # IMPLEMENTATION # This is a file-based implementation of a reference-counting API # for packages. The resource being reference-counted is assigned a # directory pathname that "shadows" the resource. It is populated # with the following files: # # ${PKGNAME} # This text file represents a reference on the resource by # the package whose full package name is ${PKGNAME}. Each # line of the file lists the location of the metadata files # for the package, known as the "package metadata directory" # (default "${PKG_DBDIR}/${PKGNAME}"). # # +OWNER # This text file consists of one line that lists the package # metadata directory of the package that owns the resource. # # +PREEXISTING # This text file consists of one line that lists the package # metadata directory of the package that discovered the # resource was pre-existing. # # +PERMISSIONS # This text file consists of one line that lists the mode # and permissions of a file resource. The line is of the # form: "mode owner group", where "mode" is the file mode in # octal form, "owner" is the user ID of the file, and # "group" is the group ID of the file. # # There are four directories located under ${PKG_REFCOUNT_DBDIR} # that are databases for resources that are reference-counted by the # packages: # # dirs/ # files/ The relative paths under these directories shadow the # resource at the same path relative to the root directory. # # groups/ # The entries of this directory are the names of groups. # # users/ The entries of this directory are the names of users. # # ENVIRONMENT # The following variables are used if they are set: # # MV The name or path to the mv(1) utility. # # PKGNAME # The name of the package manipulating the reference counts. # # PKG_DBDIR # The location of the package metadata directory database. # The default is "/var/db/pkg". # # PKG_DESTDIR # A "destdir" prefix that is prepended to all filesystem # paths. The default value is the empty string. # # PKG_METADATA_DIR # The absolute path to the location of the meta-data files # of ${PKGNAME}. The default is "${PKG_DBDIR}/${PKGNAME}". # # PKG_REFCOUNT_DBDIR # The location of the directory tree that shadows the # resources that are reference-counted. The default is # "${PKG_DBDIR}.refcount". # # RM The name or path to the rm(1) utility. # # RMDIR The name or path to the rmdir(1) utility. # __task_refcount_file__="yes" __task_refcount_file_init__="_task_refcount_file_init" task_load cleanup task_load makedir task_load maketemp task_load match task_load quote task_refcount_file() { [ $# -gt 0 ] || return 127 local command="$1"; shift local fn case $command in exists|add|remove|delete|prop_exists|prop_put|prop_match|prop_delete) eval fn="_task_refcount_file_$command" ;; *) return 127 ;; esac $fn "$@" } _task_refcount_file_pkg_token() { : ${PKGNAME:=${0##*/}} : ${PKG_DBDIR:=/var/db/pkg} : ${PKG_METADATA_DIR:=${PKG_DBDIR}/${PKGNAME}} pkg_token=${PKG_METADATA_DIR} # POST-CONDITION: # pkg_token is set to the package token unique to the system. } _task_refcount_file_shadowdir() { : ${PKG_DBDIR:=/var/db/pkg} : ${PKG_REFCOUNT_DBDIR:=${PKG_DBDIR}.refcount} local db="$1"; shift local resource="$1"; shift case $resource in /*) shadowdir="${PKG_REFCOUNT_DBDIR}/$db$resource" ;; *) shadowdir="${PKG_REFCOUNT_DBDIR}/$db/$resource" ;; esac shadowdir="${PKG_DESTDIR}/$shadowdir" # POST-CONDITION: # shadowdir is set to the location of the shadow directory. } _task_refcount_file_exists() { local db="$1"; shift local resource="$1"; shift local shadowdir _task_refcount_file_shadowdir "$db" "$resource" # shadowdir is now set to the location of the shadow directory. set -- "$shadowdir"/* local entry for entry; do case $entry in "$shadowdir/*") : "no references found" ;; "$shadowdir"/+*) : "property file" ;; *) # package reference found return 0 ;; esac done return 1 } _task_refcount_file_delete() { : ${RM:=rm} : ${RMDIR:=rmdir} local db="$1"; shift local resource="$1"; shift local shadowdir _task_refcount_file_shadowdir "$db" "$resource" # shadowdir is now set to the location of the shadow directory. # Remove all references and properties. ${RM} -f "$shadowdir"/* # Remove the shadow directory. ${RMDIR} -p "$shadowdir" 2>/dev/null [ ! -d "$shadowdir" ] || return 1 } _task_refcount_file_add() { : ${CHMOD:=chmod} : ${CP:=cp} : ${MV:=mv} local token= local arg local OPTIND=1 while getopts ":t:" arg "$@"; do case $arg in t) token=${OPTARG} ;; *) return 127 ;; esac done shift $(( ${OPTIND} - 1 )) local db="$1"; shift local resource="$1"; shift if [ -z "$token" ]; then local pkg_token _task_refcount_file_pkg_token # pkg_token is now set to the package token unique to the system. token=$pkg_token fi local shadowdir _task_refcount_file_shadowdir "$db" "$resource" # shadowdir is now set to the location of the shadow directory. local countfile="$shadowdir/${PKGNAME}" task_makedir "$shadowdir" 2>/dev/null [ -d "$shadowdir" ] || return 1 local result=0 local temp temp_quoted quoted temp=$( task_maketemp "$countfile.pkgtasks.XXXXXXXXXX" ) if [ -n "$temp" ]; then task_quote "$temp" temp_quoted=$quoted __task_refcount_file_temps__="$__task_refcount_file_temps__ $temp_quoted" if [ -f "$countfile" ]; then # Preserve existing ownership and permissions onto tempfile. ${CP} -fp "$countfile" "$temp" # Overwrite contents of tempfile. { task_match -sv "$token" < $countfile; echo "$token"; } > $temp else # Overwrite contents of tempfile. echo "$token" > $temp # Relax permissions of new file. ${CHMOD} go+r "$temp" fi # rename(2) is atomic if ${MV} -f "$temp" "$countfile"; then __task_refcount_file_temps__=${__task_refcount_file_temps__% $temp_quoted} else result=1 fi else # cannot create temporary file result=1 fi _task_refcount_file_cleanup return $result } _task_refcount_file_remove() { : ${CP:=cp} : ${MV:=mv} : ${RM:=rm} : ${RMDIR:=rmdir} local token= local arg local OPTIND=1 while getopts ":t:" arg "$@"; do case $arg in t) token=${OPTARG} ;; *) return 127 ;; esac done shift $(( ${OPTIND} - 1 )) local db="$1"; shift local resource="$1"; shift if [ -z "$token" ]; then local pkg_token _task_refcount_file_pkg_token # pkg_token is now set to the package token unique to the system. token=$pkg_token fi local shadowdir _task_refcount_file_shadowdir "$db" "$resource" # shadowdir is now set to the location of the shadow directory. local countfile="$shadowdir/${PKGNAME}" local result=0 if [ -f "$countfile" ]; then local temp temp_quoted quoted temp=$( task_maketemp "$countfile.pkgtasks.XXXXXXXXXX" ) if [ -n "$temp" ]; then task_quote "$temp" temp_quoted=$quoted __task_refcount_file_temps__="$__task_refcount_file_temps__ $temp_quoted" # Preserve existing ownership and permissions onto tempfile. ${CP} -fp "$countfile" "$temp" # Overwrite contents of tempfile. task_match -sv "$token" < $countfile > $temp local remaining= if [ -f "$temp" ]; then local line while read line; do # reference file is non-empty remaining="yes" break done < $temp # rename(2) is atomic if ${MV} -f "$temp" "$countfile"; then __task_refcount_file_temps__=${__task_refcount_file_temps__% $temp_quoted} else result=1 fi else # cannot create temporary file result=1 fi if [ -z "$remaining" ]; then ${RM} -f "$countfile" || result=1 fi fi fi # Remove shadow directory if empty. ${RMDIR} -p "$shadowdir" 2>/dev/null _task_refcount_file_cleanup return $result } _task_refcount_file_propfile() { local db="$1"; shift local resource="$1"; shift local property="$1"; shift local shadowdir _task_refcount_file_shadowdir "$db" "$resource" # shadowdir is now set to the location of the shadow directory. propfile= case $property in owner) propfile="$shadowdir/+OWNER" ;; preexist) propfile="$shadowdir/+PREEXISTING" ;; permissions) propfile="$shadowdir/+PERMISSIONS" ;; +*) propfile="$shadowdir/$property" ;; *) propfile="$shadowdir/+$property" ;; esac # POST-CONDITION: # propfile is set to the location of the property file. } _task_refcount_file_prop_exists() { local db="$1"; shift local resource="$1"; shift local property="$1"; shift local propfile _task_refcount_file_propfile "$db" "$resource" "$property" # propfile is now set to the location of the property file. [ -f "$propfile" ] || return 1 return 0 } _task_refcount_file_prop_put() { : ${CHMOD:=chmod} : ${CP:=cp} : ${MV:=mv} local db="$1"; shift local resource="$1"; shift local property="$1"; shift local propfile _task_refcount_file_propfile "$db" "$resource" "$property" # propfile is now set to the location of the property file. local pkg_token _task_refcount_file_pkg_token # pkg_token is now set to the package token unique to the system. local shadowdir="${propfile%/*}" task_makedir "$shadowdir" 2>/dev/null [ -d "$shadowdir" ] || return 1 local result=0 local temp temp_quoted quoted temp=$( task_maketemp "$propfile.pkgtasks.XXXXXXXXXX" ) if [ -n "$temp" ]; then task_quote "$temp" temp_quoted=$quoted __task_refcount_file_temps__="$__task_refcount_file_temps__ $temp_quoted" if [ -f "$propfile" ]; then # Preserve existing ownership and permissions onto tempfile. ${CP} -fp "$propfile" "$temp" else # Relax permissions of new file. ${CHMOD} go+r "$temp" fi # Overwrite contents of tempfile. local value if [ $# -gt 0 ]; then value="$@" else value=$pkg_token fi echo "$value" > $temp # rename(2) is atomic if ${MV} -f "$temp" "$propfile"; then __task_refcount_file_temps__=${__task_refcount_file_temps__% $temp_quoted} else result=1 fi else # cannot create temporary file result=1 fi _task_refcount_file_cleanup return $result } _task_refcount_file_prop_match() { local db="$1"; shift local resource="$1"; shift local property="$1"; shift local propfile _task_refcount_file_propfile "$db" "$resource" "$property" # propfile is now set to the location of the property file. local pkg_token _task_refcount_file_pkg_token # pkg_token is now set to the package token unique to the system. if [ -f "$propfile" ]; then local pattern if [ $# -gt 0 ]; then pattern="$@" else pattern=$pkg_token fi if task_match -q "$pattern" < $propfile; then return 0 fi fi return 1 } _task_refcount_file_prop_delete() { : ${RM:=rm} : ${RMDIR:=rmdir} local db="$1"; shift local resource="$1"; shift local property="$1"; shift local propfile _task_refcount_file_propfile "$db" "$resource" "$property" # propfile is now set to the location of the property file. local result=0 ${RM} -f "$propfile" [ ! -f "$propfile" ] || result=1 # Try to clean up the shadow directory in case it is empty. local shadowdir="${propfile%/*}" ${RMDIR} -p "$shadowdir" 2>/dev/null return $result } _task_refcount_file_cleanup() { : ${RM:=rm} eval set -- $__task_refcount_file_temps__ local file for file; do ${RM} -f "$file" done __task_refcount_file_temps__= } _task_refcount_file_init() { task_cleanup_add_hook _task_refcount_file_cleanup } # Static variable for temporary files that should be removed if an error occurs. __task_refcount_file_temps__=