X-Git-Url: https://git.plutz.net/?p=confetti;a=blobdiff_plain;f=cgilite%2Fstorage.sh;h=17ea0d051eaf27233f8b5cd96f7396974c7a87f1;hp=7f70e6480d6c89c8a32d3a9ee3fb68a5bfcc35b6;hb=HEAD;hpb=1e5fed890ade56928978f895681129f2214c0778 diff --git a/cgilite/storage.sh b/cgilite/storage.sh index 7f70e64..5c61df0 100755 --- a/cgilite/storage.sh +++ b/cgilite/storage.sh @@ -1,21 +1,18 @@ #!/bin/sh -# Copyright 2018, 2019 Paul Hänsch -# -# This is a file format helper, part of CGIlite. +# Copyright 2018 - 2021 Paul Hänsch # -# CGIlite is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. # -# CGIlite is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with CGIlite. If not, see . +# THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +# IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. [ -n "$include_storage" ] && return 0 include_storage="$0" @@ -25,62 +22,47 @@ BR=' ' LOCK(){ - local lock timeout block - lock="${1}.lock" - timeout="${2-20}" - if [ \! -w "${lock%/*}" ] || [ -e "$lock" -a \! -d "$lock" ]; then - printf 'Impossible to get lock: %s\n' "$lock" >&2 + local lock="${1}.lock" timeout="${2-20}" block + + if [ \! -w "${lock%/*}" ] || [ -e "$lock" -a \! -f "$lock" ]; then + debug "Impossible to get lock: $lock" return 1 fi - while ! mkdir "$lock" 2>&-; do - block="$(cat "$lock/pid" || printf 1)" - if ! { ps -eo pid |grep -qwF "$block"; }; then - printf 'Overriding stale lock: %s\n' "$lock" >&2 - break - fi - if [ $timeout -le 0 ]; then - printf 'Timeout while trying to get lock: %s\n' "$lock" >&2 - return 1 + while [ $timeout -gt 0 ]; do + printf '%i\n' $$ >>"${lock}" + read block <"$lock" + if [ "$block" = $$ ]; then + return 0 + elif ! { ps -eo pid |grep -qwF "$block"; }; then + debug "Trying to override stale lock: $lock" + if LOCK "$lock" 1; then + rm -- "$lock" + RELEASE "$lock" + fi + else + timeout=$((timeout - 1)) + [ $timeout -gt 0 ] && sleep 1 fi - timeout=$((timeout - 1)) - sleep 1 done - printf '%i\n' $$ >"${lock}/pid" - return 0 + + debug "Timeout while trying to get lock: $lock" + return 1 } RELEASE(){ - local lock - lock="${1}.lock" - if [ "$(cat "$lock/pid")" = "$$" ]; then - rm "$lock/pid" - if ! rmdir "$lock"; then - printf 'Cannot remove tainted lock: %s\n' "$lock" >&2 - printf '%i\n' $$ >"${lock}/pid" - return 1 - fi + local lock="${1}.lock" block + + read block <"$lock" + if [ "$block" = $$ ]; then + rm -- "$lock" return 0 else - printf 'Refusing to release foreign lock: %s\n' "$lock" >&2 + debug "Refusing to release foreign lock: $lock" return 1 fi } -STRING=' - s;\\;\\\\;g; - s;\n;\\n;g; - s;\t;\\t;g; - s;\r;\\r;g; - s;\+;\\+;g; - s; ;+;g; -' - -STRING_OLD(){ - { [ $# -eq 0 ] && cat || printf %s "$*"; } \ - | sed -E ':X; $!{N;bX;}'"$STRING" -} - STRING(){ local in out='' [ $# -gt 0 ] && in="$*" || in="$(cat)" @@ -93,24 +75,9 @@ STRING(){ " "*) out="${out}+"; in="${in# }" ;; *) out="${out}${in%%[\\${CR}${BR} + ]*}"; in="${in#"${in%%[\\${BR}${CR} + ]*}"}" ;; esac; done - printf '%s' "$out" + printf '%s' "${out:-\\}" } - -UNSTRING=' - :UNSTRING_X - s;((^|[^\\])(\\\\)*)\\n;\1\n;g; - s;((^|[^\\])(\\\\)*)\\t;\1\t;g; - s;((^|[^\\])(\\\\)*)\\r;\1\r;g; - s;((^|[^\\])(\\\\)*)\+;\1 ;g; - tUNSTRING_X; - s;((^|[^\\])(\\\\)*)\\\+;\1+;g; - s;\\\\;\\;g; -' -UNSTRING_OLD(){ - { [ $# -eq 0 ] && cat || printf %s "$*"; } \ - | sed -E "$UNSTRING" -} UNSTRING(){ local in out='' [ $# -gt 0 ] && in="$*" || in="$(cat)" @@ -119,11 +86,118 @@ UNSTRING(){ \\n*) out="${out}${BR}"; in="${in#\\n}" ;; \\r*) out="${out}${CR}"; in="${in#\\r}" ;; \\t*) out="${out} "; in="${in#\\t}" ;; - \\+) out="${out}+"; in="${in#\\+}" ;; + \\+*) out="${out}+"; in="${in#\\+}" ;; +*) out="${out} "; in="${in#+}" ;; \\*) in="${in#\\}" ;; *) out="${out}${in%%[\\+]*}"; in="${in#"${in%%[\\+]*}"}" ;; esac; done - printf '%s' "$out" + printf '%s\n' "$out" +} + +RXLITERAL(){ + # sed -E 's;[].*+?^${}()|\[];\\&;g' + local in out='' + [ $# -gt 0 ] && in="$*" || in="$(cat)" + while [ "$in" ]; do case $in in + [.+^\$\{\}\(\)\[\]\*\?\|\\]*) + out="${out}\\${in%"${in#?}"}"; in="${in#?}"; + ;; + *)out="${out}${in%%[.+^\$\{\}\(\)\[\]\*\?\|\\]*}" + in="${in#"${in%%[.+^\$\{\}\(\)\[\]\*\?\|\\]*}"}" + ;; + esac; done + printf '%s\n' "$out" } +DBM() { + local file="$1" cmd="$2" + local k v key value + shift 2; + + case "$cmd" in + check|contains) + key="$(STRING "$1")" + while read -r k v; do if [ "$k" = "$key" ]; then + return 0 + fi; done <"$file" 2>&- + return 1 + ;; + get) + key="$(STRING "$1")" + while read -r k v; do if [ "$k" = "$key" ]; then + UNSTRING "$v" + return 0 + fi; done <"$file" 2>&- + return 1 + ;; + set|store) + key="$(STRING "$1")" value="$(STRING "$2")" + LOCK "$file" || return 1 + { while read -r k v; do + [ "$k" = "$key" ] || printf '%s\t%s\n' "$k" "$v" + done <"$file" 2>&- + printf '%s\t%s\n' "$key" "$value" + } >"${file}.$$.tmp" + mv "${file}.$$.tmp" "${file}" + RELEASE "$file" + return 0 + ;; + add|insert) + k="$1" key="$(STRING "$1")" value="$(STRING "$2")" + LOCK "$file" || return 1 + if DBM "$file" check "$k"; then + RELEASE "$file" + return 1 + else + printf '%s\t%s\n' "$key" "$value" >>"${file}" + RELEASE "$file" + return 0 + fi + ;; + update|replace) + k="$1" key="$(STRING "$1")" value="$(STRING "$2")" + LOCK "$file" || return 1 + if ! DBM "$file" check "$k"; then + RELEASE "$file" + return 1 + fi + { while read -r k v; do + [ "$k" = "$key" ] \ + && printf '%s\t%s\n' "$key" "$value" \ + || printf '%s\t%s\n' "$k" "$v" + done <"$file" 2>&- + } >"${file}.$$.tmp" + mv "${file}.$$.tmp" "${file}" + RELEASE "$file" + return 0 + ;; + append) + key="$(STRING "$1")" value="$(STRING "$2")" + LOCK "$file" || return 1 + if ! DBM "$file" check "$1"; then + RELEASE "$file" + return 1 + fi + { while read -r k v; do + [ "$k" = "$key" ] \ + && printf '%s\t%s\n' "$key" "$v$value" \ + || printf '%s\t%s\n' "$k" "$v" + done <"$file" 2>&- + } >"${file}.$$.tmp" + mv "${file}.$$.tmp" "${file}" + RELEASE "$file" + return 0 + ;; + delete|remove) + key="$(STRING "$1")" + LOCK "$file" || return 1 + { while read -r k v; do + [ "$k" = "$key" ] || printf '%s\t%s\n' "$k" "$v" + done <"$file" 2>&- + } >"${file}.$$.tmp" + mv "${file}.$$.tmp" "${file}" + RELEASE "$file" + return 0 + ;; + esac +}