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}" |