Gentoo Archives: gentoo-commits

From: Alec Warner <antarus@g.o>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] data/api:master commit in: bin/
Date: Tue, 09 Feb 2021 07:22:49
Message-Id: 1612854904.f3639b24b860a182d375e5617fa77755b9f9a5a6.antarus@gentoo
1 commit: f3639b24b860a182d375e5617fa77755b9f9a5a6
2 Author: Alec Warner <antarus <AT> gentoo <DOT> org>
3 AuthorDate: Tue Feb 9 07:15:04 2021 +0000
4 Commit: Alec Warner <antarus <AT> gentoo <DOT> org>
5 CommitDate: Tue Feb 9 07:15:04 2021 +0000
6 URL: https://gitweb.gentoo.org/data/api.git/commit/?id=f3639b24
7
8 used_free_uidgids.sh: new script to output uid gid information.
9
10 Signed-off-by: Alec Warner <antarus <AT> gentoo.org>
11 Signed-off-by: Jaco Kroon <jaco <AT> iewc.co.za>
12
13 bin/used_free_uidgids.sh | 260 +++++++++++++++++++++++++++++++++++++++++++++++
14 1 file changed, 260 insertions(+)
15
16 diff --git a/bin/used_free_uidgids.sh b/bin/used_free_uidgids.sh
17 new file mode 100755
18 index 0000000..b0ed417
19 --- /dev/null
20 +++ b/bin/used_free_uidgids.sh
21 @@ -0,0 +1,260 @@
22 +#! /bin/bash
23 +#
24 +# Copyright 2021 Gentoo Authors
25 +# Distributed under the terms of the GNU General Public License v2
26 +#
27 +# Author: Jaco Kroon <jaco@××××××.za>
28 +# So that you can contact me if you need help with the below insanity.
29 +#
30 +# Configuration options:
31 +# max => maximum value of uid/gid that we're interested in/willing to allocate
32 +# from. Can be set to - to go maximum possible 32-bit value.
33 +# debug => if non-zero outputs some cryptic debug output (will inherit from environment).
34 +#
35 +max=500
36 +debug=${debug:+1} # set non-zero to enable debug output.
37 +
38 +#
39 +# Basic Design:
40 +#
41 +# There is nothing beautiful about this script, it's downright nasty and I
42 +# (Jaco Kroon <jaco@××××××.za>) will be the first to admit that.
43 +#
44 +# For each of the uid and gid ranges, we primarily keep two variables.
45 +# ranges and reason. reason is simply one of USED or RESERVED. Free ranges
46 +# are not mapped into these arrays.
47 +# ranges_ maps a start index onto an end index. So for example, let's say
48 +# uid range 0..10 is USED (allocated, for whatever purposes):
49 +#
50 +# ranges_uid[0]=10
51 +# reasons_uid[0]=USED
52 +#
53 +# The above says that UID 0 to 10 is USED.
54 +#
55 +# We start with an initially empty set, and then insert into, either merging or
56 +# potentially splitting as we go, by way of the consume function, once completed
57 +# we compact some things and then output.
58 +#
59 +
60 +ranges_uid=()
61 +ranges_gid=()
62 +reason_uid=()
63 +reason_gid=()
64 +
65 +# Colours to be used if output is a TTY.
66 +colour_USED="\e[0;91m" # brightred
67 +colour_FREE="\e[0;92m" # brightgreen
68 +colour_RESERVED="\e[0;94m" # brightblue
69 +colour_RESET="\e[0m" # reset all styles.
70 +
71 +if ! [[ -t 1 ]]; then
72 + colour_USED=
73 + colour_FREE=
74 + colour_RESERVED=
75 + colour_RESET=
76 +fi
77 +
78 +# Find input file if not piped in on stdin, or show a warning about it on
79 +# stderr if we can't find the file.
80 +if [[ -t 0 ]]; then
81 + def_infile="$(dirname "$0")/../files/uid-gid.txt"
82 + if ! [[ -r "${def_infile}" ]] || ! exec <"${def_infile}"; then
83 + echo "Reading from stdin (which happens to be a tty, you should pipe input file to stdin)" >&2
84 + fi
85 +fi
86 +
87 +consume()
88 +{
89 + # The basic principle here is that we can either add a new range, or split
90 + # an existing range. Partial overlaps not dealt with, nor range
91 + # extensions. Which would (I believe) negate the need for compact.
92 + # TODO: deal with range merging here, eg, if we have 0..10, and adding 11, then
93 + # we can simply adjust the range to 0..11, for example.
94 + local variant="$1"
95 + local ids="$2"
96 + local type=$([[ "$3" == reserved ]] && echo RESERVED || echo USED)
97 + local range_start="${ids%..*}"
98 + local range_end="${ids#*..}"
99 + declare -n ranges="ranges_${variant}"
100 + declare -n reasons="reason_${variant}"
101 +
102 + [[ -z "${ids}" ]] && return
103 + [[ "${ids}" == - ]] && return
104 +
105 + for k in "${!ranges[@]}"; do
106 + # can the new range be inserted before the next range already in the set?
107 + [[ ${k} -gt ${range_end} ]] && break
108 + [[ ${ranges[k]} -lt ${range_start} ]] && continue
109 + if [[ ${k} -le ${range_start} && ${range_end} -le ${ranges[k]} ]]; then
110 + # new range is contained completely inside.
111 + [[ ${reasons[k]} == ${type} ]] && return # same type.
112 + [[ ${type} == RESERVED ]] && return # USED takes precedence over RESERVED.
113 +
114 + if [[ ${range_end} -lt ${ranges[k]} ]]; then
115 + ranges[range_end+1]=${ranges[k]}
116 + reasons{range_end+1]=${reasons[k]}
117 + fi
118 + [[ ${range_start} -gt ${k} ]] && ranges[k]=$(( range_start - 1 ))
119 + break
120 + else
121 + echo "${range_start}..${range_end} (${type}) overlaps with ${k}..${ranges[k]} (${reasons[k]}"
122 + echo "Cannot handle partial overlap."
123 + exit 1
124 + fi
125 + done
126 +
127 + ranges[range_start]="${range_end}"
128 + reasons[range_start]="${type}"
129 +}
130 +
131 +compact()
132 +{
133 + # This simply coalesces ranges that follow directly on each other. In
134 + # other words, if range ends at 10 and the next range starts at 11, just
135 + # merge the two by adjusting the end of the first range, and removing the
136 + # immediately following.
137 + # Param: uid or gid to determine which set we're working with.
138 + declare -n ranges="ranges_$1"
139 + declare -n reasons="reason_$1"
140 + local k e ne
141 + for k in "${ranges[@]}"; do
142 + [[ -n "${ranges[k]:+set}" ]] || continue
143 + e=${ranges[k]}
144 + while [[ -n "${ranges[e+1]:+set}" && "${reasons[k]}" == "${reasons[e+1]}" ]]; do
145 + ne=${ranges[e+1]}
146 + unset "ranges[e+1]"
147 + e=${ne}
148 + done
149 + ranges[k]=${e}
150 + done
151 +}
152 +
153 +output()
154 +{
155 + # Outputs the raw list as provided (param: uid or gid)
156 + declare -n ranges="ranges_$1"
157 + declare -n reasons="reason_$1"
158 + local k c=0
159 +
160 + echo "$1 list:"
161 + for k in "${!ranges[@]}"; do
162 + echo "$(( c++ )): ${k} => ${ranges[k]} / ${reasons[k]}"
163 + done
164 +}
165 +
166 +# Read the input file which is structured as "username uid gid provider and
167 +# potentially more stuff" Lines starting with # are comments, thus we can
168 +# filter those out.
169 +while read un uid gid provider rest; do
170 + [[ "${un}" == \#* ]] && continue
171 + consume uid "${uid}" "${provider}"
172 + consume gid "${gid}" "${provider}"
173 +done
174 +
175 +compact uid
176 +compact gid
177 +
178 +# If we're debugging, just output both lists so we can inspect that everything is correct here.
179 +if [[ -n "${debug}" ]]; then
180 + output uid
181 + output gid
182 +fi
183 +
184 +# Get the various range starts.
185 +uids=("${!ranges_uid[@]}")
186 +gids=("${!ranges_gid[@]}")
187 +
188 +# Set max to 2^32-1 if set to -.
189 +if [[ ${max} == - ]]; then
190 + max=$((2 ** 32 - 1))
191 +fi
192 +
193 +ui=0 # index into uids array.
194 +gi=0 # index into gids array.
195 +idbase=0 # "start" of range about to be output.
196 +freeuid=0 # count number of free UIDs
197 +freegid=0 # count number of free GIDs
198 +
199 +printf "%-*s%10s%10s\n" $(( ${#max} * 2 + 5 )) "#ID" UID GID
200 +
201 +while [[ ${idbase} -le ${max} ]]; do
202 + # skip over uid and gid ranges that we're no longer interested in (end of range is
203 + # lower than start of output range).
204 + while [[ ${ui} -lt ${#uids[@]} && ${ranges_uid[uids[ui]]} -lt ${idbase} ]]; do
205 + (( ui++ ))
206 + done
207 + while [[ ${gi} -lt ${#gids[@]} && ${ranges_gid[gids[gi]]} -lt ${idbase} ]]; do
208 + (( gi++ ))
209 + done
210 + # Assume that range we're going to output is the remainder of the legal
211 + # space we're interested in, and then adjust downwards as needed. For each
212 + # of the UID and GID space, if the start range is beyond the current output
213 + # start we're looking at a FREE range, so downward adjust re (range end) to
214 + # the next non-FREE range's start - 1, or if we're in the non-FREE range,
215 + # adjust downward to that range's end.
216 + re=${max}
217 + uid_start=-1
218 + gid_start=-1
219 + if [[ ${ui} -lt ${#uids[@]} ]]; then
220 + uid_start=${uids[ui]}
221 + if [[ ${uid_start} -gt ${idbase} && ${uid_start} -le ${re} ]]; then
222 + re=$(( ${uid_start} - 1 ))
223 + fi
224 + if [[ ${ranges_uid[uid_start]} -lt ${re} ]]; then
225 + re=${ranges_uid[uid_start]}
226 + fi
227 + fi
228 + if [[ ${gi} -lt ${#gids[@]} ]]; then
229 + gid_start=${gids[gi]}
230 + if [[ ${gid_start} -gt ${idbase} && ${gid_start} -le ${re} ]]; then
231 + re=$(( ${gid_start} - 1 ))
232 + fi
233 + if [[ ${ranges_gid[gid_start]} -lt ${re} ]]; then
234 + re=${ranges_gid[gid_start]}
235 + fi
236 + fi
237 +
238 + # If we're debugging, just dump various variables above, which allows
239 + # validating that the above logic works correctly.
240 + [[ -n "${debug}" ]] && echo "ui=${ui} (${uid_start}..${ranges_uid[uid_start]}), gi=${gi} (${gid_start}..${ranges_gid[gid_start]}), idbase=${idbase}, re=${re}"
241 +
242 + # Determine the state of the UID and GID ranges.
243 + if [[ ${ui} -lt ${#uids[@]} && ${uid_start} -le ${idbase} ]]; then
244 + uidstate="${reason_uid[uid_start]}"
245 + else
246 + uidstate=FREE
247 + freeuid=$(( freeuid + re - idbase + 1 ))
248 + fi
249 +
250 + if [[ ${gi} -lt ${#gids[@]} && ${gid_start} -le ${idbase} ]]; then
251 + gidstate="${reason_gid[gids[gi]]}"
252 + else
253 + gidstate=FREE
254 + freegid=$(( freegid + re - idbase + 1 ))
255 + fi
256 +
257 + # If the ranges are FREE (or at least one of), adjust selection recommendations
258 + # accordingly.
259 + if [[ "${gidstate}" == FREE ]]; then
260 + if [[ "${uidstate}" == FREE ]]; then
261 + uidgidboth=${re}
262 + else
263 + gidonly=${re}
264 + fi
265 + elif [[ "${uidstate}" == FREE ]]; then
266 + uidonly=${re}
267 + fi
268 +
269 + vn="colour_${uidstate}"
270 + colour_uid="${!vn}"
271 + vn="colour_${gidstate}"
272 + colour_gid="${!vn}"
273 + printf "%-*s${colour_uid}%10s${colour_gid}%10s${colour_RESET}\n" $(( ${#max} * 2 + 5 )) "${idbase}$([[ ${re} -gt ${idbase} ]] && echo "..${re}")" "${uidstate}" "${gidstate}"
274 + idbase=$(( re + 1 ))
275 +done
276 +
277 +echo "Recommended GID only: ${gidonly:-${uidgidboth:-none}}"
278 +echo "Recommended UID only: ${uidonly:=${uidgidboth:-none}}"
279 +echo "Recommended UID+GID both: ${uidgidboth:-none}"
280 +echo "Free UIDs: ${freeuid}"
281 +echo "Free GIDs: ${freegid}"