X-Git-Url: https://git.plutz.net/?p=cgilite;a=blobdiff_plain;f=storage.sh;h=22e6accbcba7cddd75bbaec0235a174b4aa53bd3;hp=bf8b7b9f44343d3c210c57800b5e606208f6e6b2;hb=6bfa64b084ea028f9078f679a4a77ffc57e02361;hpb=438b528a68c8638b7681e7dca95ee225d07df48d
diff --git a/storage.sh b/storage.sh
index bf8b7b9..22e6acc 100755
--- a/storage.sh
+++ b/storage.sh
@@ -1,6 +1,6 @@
#!/bin/sh
-# Copyright 2018 Paul Hänsch
+# Copyright 2018, 2019, 2021 Paul Hänsch
#
# This is a file format helper, part of CGIlite.
#
@@ -17,57 +17,175 @@
# You should have received a copy of the GNU Affero General Public License
# along with CGIlite. If not, see .
-# ksh and zsh workaround
-case "${0##*/}" in
- zsh) setopt -o OCTAL_ZEROES 2>&- ;; # zsh, mostly ignored by other shells
- ksh) set -o posix ;; # ksh, will crash most other shells
-esac
+[ -n "$include_storage" ] && return 0
+include_storage="$0"
-STORE(){
- # Store fields from a HTTP GET or POST string in a file
- # usage: STORE "${QUERY_STRING}"
- # or: STORE "$(head -n $HTTP_CONTENT_LENGTH)"
- # backslashes and newline characters will be escaped in field values,
- # the escape symbol is a backslash
- # hexadecimal character descriptions (%00 - %FF) will be expanded
- # the + character will be converted to [space]
- # one line in the output corresponds to exactly one field, so you can
- # use grep to filter which fields of a query should be stored
- printf "$(
- printf '%s' "$@" \
- | sed -r ':X; $bY; N; bX; :Y;
- s;\+; ;g; s;(\\|5[Cc]);\\\\\\\\;g; s;(\n|%0[Aa]);\\\\n;g; s;(^|&)([^=]+)=;\1\2:;g; s;&;\n;g;
- s;^\n+;;; s;$;\\n;; s;\n+;\n;g;
- # Hexadecimal { %00 - %FF } will be transformed to octal { \000 - \377 } for posix printf
- s;%[0123].;&\\0;g; s;%[4567].;&\\1;g; s;%[89AB].;&\\2;g; s;%[CDEF].;&\\3;g;
- s;%[048C][0-7]\\.;&0;g; s;%[048C][89A-F]\\.;&1;g; s;%[159D][0-7]\\.;&2;g; s;%[159D][89A-F]\\.;&3;g;
- s;%[26AE][0-7]\\.;&4;g; s;%[26AE][89A-F]\\.;&5;g; s;%[37BF][0-7]\\.;&6;g; s;%[37BF][89A-F]\\.;&7;g;
- s;%.[08](\\..);\10;g; s;%.[19](\\..);\11;g; s;%.[2A](\\..);\12;g; s;%.[3B](\\..);\13;g;
- s;%.[4C](\\..);\14;g; s;%.[5D](\\..);\15;g; s;%.[6E](\\..);\16;g; s;%.[7F](\\..);\17;g;
- '
- )"
+CR="
"
+BR='
+'
+
+LOCK(){
+ 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 [ $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
+ done
+
+ debug "Timeout while trying to get lock: $lock"
+ return 1
+}
+
+RELEASE(){
+ local lock="${1}.lock" block
+
+ read block <"$lock"
+ if [ "$block" = $$ ]; then
+ rm -- "$lock"
+ return 0
+ else
+ debug "Refusing to release foreign lock: $lock"
+ return 1
+ fi
+}
+
+STRING(){
+ local in out=''
+ [ $# -gt 0 ] && in="$*" || in="$(cat)"
+ while [ "$in" ]; do case $in in
+ \\*) out="${out}\\\\"; in="${in#\\}" ;;
+ "$BR"*) out="${out}\\n"; in="${in#${BR}}" ;;
+ "$CR"*) out="${out}\\r"; in="${in#${CR}}" ;;
+ " "*) out="${out}\\t"; in="${in# }" ;;
+ +*) out="${out}\\+"; in="${in#+}" ;;
+ " "*) out="${out}+"; in="${in# }" ;;
+ *) out="${out}${in%%[\\${CR}${BR} + ]*}"; in="${in#"${in%%[\\${BR}${CR} + ]*}"}" ;;
+ esac; done
+ printf '%s' "${out:-\\}"
+}
+
+UNSTRING(){
+ local in out=''
+ [ $# -gt 0 ] && in="$*" || in="$(cat)"
+ while [ "$in" ]; do case $in in
+ \\\\*) out="${out}\\"; in="${in#\\\\}" ;;
+ \\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#+}" ;;
+ \\*) in="${in#\\}" ;;
+ *) out="${out}${in%%[\\+]*}"; in="${in#"${in%%[\\+]*}"}" ;;
+ esac; done
+ printf '%s\n' "$out"
}
-LOAD(){
- # read a file written by STORE and assign all fields to variables
- # usage: eval "$(LOAD &-
+ 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
}