]> git.plutz.net Git - serve0/blob - cgilite/storage.sh
experimental lock algorithm
[serve0] / cgilite / storage.sh
1 #!/bin/sh
2
3 # Copyright 2018, 2019 Paul Hänsch
4 #
5 # This is a file format helper, part of CGIlite.
6
7 # CGIlite is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11
12 # CGIlite is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU Affero General Public License for more details.
16
17 # You should have received a copy of the GNU Affero General Public License
18 # along with CGIlite.  If not, see <http://www.gnu.org/licenses/>. 
19
20 [ -n "$include_storage" ] && return 0
21 include_storage="$0"
22
23 CR="\r"
24 BR='
25 '
26
27 LOCK(){
28   local lock timeout block
29   lock="${1}.lock"
30   timeout="${2-20}"
31
32   if [ \! -w "${lock%/*}" ] || [ -e "$lock" -a \! -f "$lock" ]; then
33     debug "Impossible to get lock: $lock"
34     return 1
35   fi
36
37   while [ $timeout -gt 0 ]; do
38     printf '%i\n' $$ >>"${lock}"
39     read block <"$lock"
40     if [ "$block" = $$ ]; then
41       return 0
42     elif ! { ps -eo pid |grep -qwF "$block"; }; then
43       debug "Overriding stale lock: $lock"
44       if LOCK "$lock" 1; then
45         rm -- "$lock"
46         RELEASE "$lock"
47       fi
48     else
49       timeout=$((timeout - 1))
50       sleep 1
51     fi
52   done
53
54   debug "Timeout while trying to get lock: $lock"
55   return 1
56 }
57
58 RELEASE(){
59   local lock block
60   lock="${1}.lock"
61
62   read block <"$lock"
63   if [ "$block" = $$ ]; then
64     rm -- "$lock"
65     return 0
66   else
67     debug "Refusing to release foreign lock: $lock"
68     return 1
69   fi
70 }
71
72 # STRING='
73 #   s;\\;\\\\;g; s;\t;\\t;g;
74 #   s;\n;\\n;g;  s;\r;\\r;g;
75 #   s;\+;\\+;g;  s; ;+;g;
76 # '
77 STRING(){
78   local in out=''
79   [ $# -gt 0 ] && in="$*" || in="$(cat)"
80   while [ "$in" ]; do case $in in
81     \\*) out="${out}\\\\"; in="${in#\\}" ;;
82     "$BR"*) out="${out}\\n"; in="${in#${BR}}" ;;
83     "$CR"*) out="${out}\\r"; in="${in#${CR}}" ;;
84     "   "*) out="${out}\\t"; in="${in#  }" ;;
85     +*) out="${out}\\+"; in="${in#+}" ;;
86     " "*) out="${out}+"; in="${in# }" ;;
87     *) out="${out}${in%%[\\${CR}${BR}   + ]*}"; in="${in#"${in%%[\\${BR}${CR}   + ]*}"}" ;;
88   esac; done
89   printf '%s' "$out"
90 }
91
92 UNSTRING='
93   :UNSTRING_X
94   s;((^|[^\\])(\\\\)*)\\n;\1\n;g;
95   s;((^|[^\\])(\\\\)*)\\t;\1\t;g;
96   s;((^|[^\\])(\\\\)*)\\r;\1\r;g;
97   s;((^|[^\\])(\\\\)*)\+;\1 ;g;
98   tUNSTRING_X;
99   s;((^|[^\\])(\\\\)*)\\\+;\1+;g;
100   s;\\\\;\\;g;
101 '
102 UNSTRING(){
103   local in out=''
104   [ $# -gt 0 ] && in="$*" || in="$(cat)"
105   while [ "$in" ]; do case $in in
106     \\\\*) out="${out}\\"; in="${in#\\\\}" ;;
107     \\n*) out="${out}${BR}"; in="${in#\\n}" ;;
108     \\r*) out="${out}${CR}"; in="${in#\\r}" ;;
109     \\t*) out="${out}   "; in="${in#\\t}" ;;
110     \\+*) out="${out}+"; in="${in#\\+}" ;;
111     +*) out="${out} "; in="${in#+}" ;;
112     \\*) in="${in#\\}" ;;
113     *) out="${out}${in%%[\\+]*}"; in="${in#"${in%%[\\+]*}"}" ;;
114   esac; done
115   printf '%s' "$out"
116 }
117
118 DBM() {
119   local file="$1" cmd="$2"
120   local k v key value
121   shift 2;
122
123   case "$cmd" in
124     check|contains)
125       key="$(STRING "$1")"
126       while read -r k v; do if [ "$k" = "$key" ]; then
127         return 0
128       fi; done <"$file" 2>&-
129       return 1
130       ;;
131     get)
132       key="$(STRING "$1")"
133       while read -r k v; do if [ "$k" = "$key" ]; then
134         UNSTRING "$v"
135         return 0
136       fi; done <"$file" 2>&-
137       return 1
138       ;;
139     set|store)
140       key="$(STRING "$1")" value="$(STRING "$2")"
141       LOCK "$file" || return 1
142       { while read -r k v; do
143           [ "$k" = "$key" ] || printf '%s\t%s\n' "$k" "$v"
144         done <"$file" 2>&-
145         printf '%s\t%s\n' "$key" "$value"
146       } >"${file}.$$.tmp"
147       mv "${file}.$$.tmp" "${file}"
148       RELEASE "$file"
149       return 0
150       ;;
151     add|insert)
152       k="$1" key="$(STRING "$1")" value="$(STRING "$2")"
153       LOCK "$file" || return 1
154       if DBM "$file" check "$k"; then
155         RELEASE "$file"
156         return 1
157       else
158         printf '%s\t%s\n' "$key" "$value" >>"${file}"
159         RELEASE "$file"
160         return 0
161       fi
162       ;;
163     update|replace)
164       k="$1" key="$(STRING "$1")" value="$(STRING "$2")"
165       LOCK "$file" || return 1
166       if ! DBM "$file" check "$k"; then
167         RELEASE "$file"
168         return 1
169       fi
170       { while read -r k v; do
171           [ "$k" = "$key" ] \
172           && printf '%s\t%s\n' "$key" "$value" \
173           || printf '%s\t%s\n' "$k" "$v"
174         done <"$file" 2>&-
175       } >"${file}.$$.tmp"
176       mv "${file}.$$.tmp" "${file}"
177       RELEASE "$file"
178       return 0
179       ;;
180     append)
181       key="$(STRING "$1")" value="$(STRING "$2")"
182       LOCK "$file" || return 1
183       if ! DBM "$file" check "$1"; then
184         RELEASE "$file"
185         return 1
186       fi
187       { while read -r k v; do
188           [ "$k" = "$key" ] \
189           && printf '%s\t%s\n' "$key" "$v$value" \
190           || printf '%s\t%s\n' "$k" "$v"
191         done <"$file" 2>&-
192       } >"${file}.$$.tmp"
193       mv "${file}.$$.tmp" "${file}"
194       RELEASE "$file"
195       return 0
196       ;;
197     delete|remove)
198       key="$(STRING "$1")"
199       LOCK "$file" || return 1
200       { while read -r k v; do
201           [ "$k" = "$key" ] || printf '%s\t%s\n' "$k" "$v"
202         done <"$file" 2>&-
203       } >"${file}.$$.tmp"
204       mv "${file}.$$.tmp" "${file}"
205       RELEASE "$file"
206       return 0
207       ;;
208   esac
209 }