Blame


1 9ddaf4ab 2023-04-08 benni /*
2 9ddaf4ab 2023-04-08 benni * Copyright (c) 2023 Benjamin Stürz <benni@stuerz.xyz>
3 9ddaf4ab 2023-04-08 benni *
4 9ddaf4ab 2023-04-08 benni * Permission to use, copy, modify, and distribute this software for any
5 9ddaf4ab 2023-04-08 benni * purpose with or without fee is hereby granted, provided that the above
6 9ddaf4ab 2023-04-08 benni * copyright notice and this permission notice appear in all copies.
7 9ddaf4ab 2023-04-08 benni *
8 9ddaf4ab 2023-04-08 benni * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 9ddaf4ab 2023-04-08 benni * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 9ddaf4ab 2023-04-08 benni * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 9ddaf4ab 2023-04-08 benni * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 9ddaf4ab 2023-04-08 benni * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 9ddaf4ab 2023-04-08 benni * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 9ddaf4ab 2023-04-08 benni * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 9ddaf4ab 2023-04-08 benni */
16 33bf1ca4 2023-04-08 benni #define _XOPEN_SOURCE 700
17 33bf1ca4 2023-04-08 benni #define _BSD_SOURCE 1
18 33bf1ca4 2023-04-08 benni #define DKTYPENAMES
19 7c38b724 2023-05-15 benni #define WSDEBUG 0
20 33bf1ca4 2023-04-08 benni #include <stddef.h>
21 b0a1b420 2023-05-15 benni #include <dev/biovar.h>
22 9c5d8ecc 2023-05-02 benni #include <sys/cdefs.h>
23 33bf1ca4 2023-04-08 benni #include <sys/types.h>
24 33bf1ca4 2023-04-08 benni #include <sys/disklabel.h>
25 33bf1ca4 2023-04-08 benni #include <sys/sysctl.h>
26 33bf1ca4 2023-04-08 benni #include <sys/ioctl.h>
27 33bf1ca4 2023-04-08 benni #include <sys/mount.h>
28 33bf1ca4 2023-04-08 benni #include <sys/dkio.h>
29 33bf1ca4 2023-04-08 benni #include <inttypes.h>
30 8380cc51 2023-05-01 benni #include <stdbool.h>
31 33bf1ca4 2023-04-08 benni #include <string.h>
32 33bf1ca4 2023-04-08 benni #include <stdarg.h>
33 33bf1ca4 2023-04-08 benni #include <stdint.h>
34 33bf1ca4 2023-04-08 benni #include <stdlib.h>
35 33bf1ca4 2023-04-08 benni #include <unistd.h>
36 5691dc7b 2023-05-05 benni #include <libgen.h>
37 33bf1ca4 2023-04-08 benni #include <stdio.h>
38 33bf1ca4 2023-04-08 benni #include <fcntl.h>
39 185836d0 2023-05-15 benni #include <ctype.h>
40 33bf1ca4 2023-04-08 benni #include <errno.h>
41 0c422a5b 2023-05-05 benni #include <util.h>
42 44121ddf 2023-05-05 benni #include <err.h>
43 33bf1ca4 2023-04-08 benni
44 33bf1ca4 2023-04-08 benni static int diskcount (void)
45 33bf1ca4 2023-04-08 benni {
46 33bf1ca4 2023-04-08 benni const int mib[2] = { CTL_HW, HW_DISKCOUNT };
47 33bf1ca4 2023-04-08 benni int diskcount;
48 33bf1ca4 2023-04-08 benni size_t len = sizeof diskcount;
49 33bf1ca4 2023-04-08 benni
50 33bf1ca4 2023-04-08 benni if (sysctl (mib, 2, &diskcount, &len, NULL, 0) == -1)
51 44121ddf 2023-05-05 benni err (1, "sysctl(hw.diskcount)");
52 33bf1ca4 2023-04-08 benni
53 33bf1ca4 2023-04-08 benni return diskcount;
54 33bf1ca4 2023-04-08 benni }
55 33bf1ca4 2023-04-08 benni
56 33bf1ca4 2023-04-08 benni static char *disknames (void)
57 33bf1ca4 2023-04-08 benni {
58 33bf1ca4 2023-04-08 benni const int num = diskcount ();
59 33bf1ca4 2023-04-08 benni const int mib[2] = { CTL_HW, HW_DISKNAMES };
60 33bf1ca4 2023-04-08 benni size_t len = 32 * num;
61 33bf1ca4 2023-04-08 benni char *buffer = malloc (len);
62 33bf1ca4 2023-04-08 benni
63 33bf1ca4 2023-04-08 benni if (sysctl (mib, 2, buffer, &len, NULL, 0) == -1)
64 44121ddf 2023-05-05 benni err (1, "sysctl(hw.disknames)");
65 33bf1ca4 2023-04-08 benni
66 33bf1ca4 2023-04-08 benni return buffer;
67 33bf1ca4 2023-04-08 benni }
68 33bf1ca4 2023-04-08 benni
69 9c5d8ecc 2023-05-02 benni static char *stripdisk (char *n)
70 33bf1ca4 2023-04-08 benni {
71 9c5d8ecc 2023-05-02 benni const char sufx[] = " disk";
72 9c5d8ecc 2023-05-02 benni const size_t ln = strnlen (n, 16);
73 9c5d8ecc 2023-05-02 benni const size_t ls = sizeof sufx - 1;
74 9c5d8ecc 2023-05-02 benni
75 2f06f21f 2023-06-08 benni if (memcmp (n + ln - ls, sufx, ls) == 0) {
76 9c5d8ecc 2023-05-02 benni n[ln - ls] = '\0';
77 9c5d8ecc 2023-05-02 benni }
78 9c5d8ecc 2023-05-02 benni
79 9c5d8ecc 2023-05-02 benni return n;
80 9c5d8ecc 2023-05-02 benni }
81 9c5d8ecc 2023-05-02 benni
82 9c5d8ecc 2023-05-02 benni static void print_size (uint64_t sz)
83 9c5d8ecc 2023-05-02 benni {
84 2f06f21f 2023-06-08 benni const struct unit {
85 33bf1ca4 2023-04-08 benni char sym;
86 33bf1ca4 2023-04-08 benni uint64_t factor;
87 2f06f21f 2023-06-08 benni } units[] = {
88 33bf1ca4 2023-04-08 benni { 'P', 1ull << 50 },
89 33bf1ca4 2023-04-08 benni { 'T', 1ull << 40 },
90 33bf1ca4 2023-04-08 benni { 'G', 1ull << 30 },
91 33bf1ca4 2023-04-08 benni { 'M', 1ull << 20 },
92 33bf1ca4 2023-04-08 benni { 'K', 1ull << 10 },
93 33bf1ca4 2023-04-08 benni { '0', 0 },
94 33bf1ca4 2023-04-08 benni };
95 33bf1ca4 2023-04-08 benni
96 33bf1ca4 2023-04-08 benni char sym = 'B';
97 33bf1ca4 2023-04-08 benni uint64_t factor = 1;
98 33bf1ca4 2023-04-08 benni
99 33bf1ca4 2023-04-08 benni for (const struct unit *u = &units[0]; u->factor; ++u) {
100 33bf1ca4 2023-04-08 benni if (sz >= (u->factor * 9 / 10)) {
101 33bf1ca4 2023-04-08 benni sym = u->sym;
102 33bf1ca4 2023-04-08 benni factor = u->factor;
103 33bf1ca4 2023-04-08 benni break;
104 33bf1ca4 2023-04-08 benni }
105 33bf1ca4 2023-04-08 benni }
106 33bf1ca4 2023-04-08 benni
107 33bf1ca4 2023-04-08 benni const unsigned scaled10 = sz * 10 / factor;
108 33bf1ca4 2023-04-08 benni const unsigned scaled = sz / factor;
109 33bf1ca4 2023-04-08 benni if (scaled10 >= 1000) {
110 8a374bef 2023-04-08 benni printf ("%u", scaled);
111 33bf1ca4 2023-04-08 benni } else if (scaled10 >= 100) {
112 8a374bef 2023-04-08 benni printf (" %u", scaled);
113 33bf1ca4 2023-04-08 benni } else {
114 8a374bef 2023-04-08 benni printf ("%u.%u", scaled, scaled10 % 10);
115 33bf1ca4 2023-04-08 benni }
116 33bf1ca4 2023-04-08 benni
117 33bf1ca4 2023-04-08 benni putchar (sym);
118 9c5d8ecc 2023-05-02 benni putchar (' ');
119 33bf1ca4 2023-04-08 benni }
120 33bf1ca4 2023-04-08 benni
121 9c5d8ecc 2023-05-02 benni enum {
122 0c961f7d 2023-05-04 benni FIELD_NAME = 0x01,
123 1115ad3a 2023-05-05 benni FIELD_DUID = 0x02,
124 1115ad3a 2023-05-05 benni FIELD_SIZE = 0x04,
125 1115ad3a 2023-05-05 benni FIELD_USED = 0x08,
126 1115ad3a 2023-05-05 benni FIELD_FREE = 0x10,
127 1115ad3a 2023-05-05 benni FIELD_TYPE = 0x20,
128 1678d261 2023-05-15 benni FIELD_COMMENT = 0x40,
129 9c5d8ecc 2023-05-02 benni
130 1678d261 2023-05-15 benni FIELD_DEFAULT = FIELD_NAME | FIELD_SIZE | FIELD_TYPE | FIELD_COMMENT,
131 9c5d8ecc 2023-05-02 benni };
132 9c5d8ecc 2023-05-02 benni
133 0c961f7d 2023-05-04 benni enum {
134 0c961f7d 2023-05-04 benni OPT_NOHEADER = 0x01,
135 0c961f7d 2023-05-04 benni OPT_NOUNICODE = 0x02,
136 1678d261 2023-05-15 benni OPT_NOBIO = 0x04,
137 0c961f7d 2023-05-04 benni };
138 0c961f7d 2023-05-04 benni
139 b0a1b420 2023-05-15 benni struct my_diskinfo;
140 b0a1b420 2023-05-15 benni
141 9c5d8ecc 2023-05-02 benni struct my_partinfo {
142 1115ad3a 2023-05-05 benni char letter;
143 9c5d8ecc 2023-05-02 benni uint64_t size;
144 9c5d8ecc 2023-05-02 benni uint64_t fssize;
145 9c5d8ecc 2023-05-02 benni uint64_t free;
146 9c5d8ecc 2023-05-02 benni const char *fstype;
147 9c5d8ecc 2023-05-02 benni const char *mount;
148 1678d261 2023-05-15 benni const struct my_diskinfo *sub; // If this is part of a RAID
149 1678d261 2023-05-15 benni const char *raidstatus; // Only available if (sub != NULL)
150 9c5d8ecc 2023-05-02 benni };
151 9c5d8ecc 2023-05-02 benni
152 9c5d8ecc 2023-05-02 benni struct my_diskinfo {
153 9c5d8ecc 2023-05-02 benni char type[16];
154 9c5d8ecc 2023-05-02 benni char label[16];
155 69c9c7b9 2023-05-15 benni char name[8];
156 9c5d8ecc 2023-05-02 benni uint64_t size;
157 9c5d8ecc 2023-05-02 benni uint64_t used;
158 1115ad3a 2023-05-05 benni u_char duid[8];
159 b0a1b420 2023-05-15 benni uint8_t num_parts;
160 9c5d8ecc 2023-05-02 benni struct my_partinfo parts[MAXPARTITIONS];
161 1678d261 2023-05-15 benni const char *raidstatus; // If this is a RAID device
162 9c5d8ecc 2023-05-02 benni };
163 9c5d8ecc 2023-05-02 benni
164 7c38b724 2023-05-15 benni struct padding {
165 7c38b724 2023-05-15 benni int name;
166 7c38b724 2023-05-15 benni /* duid = 8 */
167 7c38b724 2023-05-15 benni /* size = 4 */
168 7c38b724 2023-05-15 benni /* used = 4 */
169 7c38b724 2023-05-15 benni /* free = 4 */
170 7c38b724 2023-05-15 benni int type;
171 7c38b724 2023-05-15 benni int comment;
172 7c38b724 2023-05-15 benni };
173 7c38b724 2023-05-15 benni
174 7c38b724 2023-05-15 benni static void print_header (int fields, const struct padding *p)
175 33bf1ca4 2023-04-08 benni {
176 9c5d8ecc 2023-05-02 benni if (fields & FIELD_NAME)
177 7c38b724 2023-05-15 benni printf ("%-*s ", p->name, "NAME");
178 9c5d8ecc 2023-05-02 benni
179 1115ad3a 2023-05-05 benni if (fields & FIELD_DUID)
180 1115ad3a 2023-05-05 benni printf ("%-18s ", "DUID");
181 1115ad3a 2023-05-05 benni
182 9c5d8ecc 2023-05-02 benni if (fields & FIELD_SIZE)
183 9c5d8ecc 2023-05-02 benni printf ("%-4s ", "SIZE");
184 9c5d8ecc 2023-05-02 benni
185 9c5d8ecc 2023-05-02 benni if (fields & FIELD_USED)
186 9c5d8ecc 2023-05-02 benni printf ("%-4s ", "USED");
187 9c5d8ecc 2023-05-02 benni
188 9c5d8ecc 2023-05-02 benni if (fields & FIELD_FREE)
189 9c5d8ecc 2023-05-02 benni printf ("%-4s ", "FREE");
190 9c5d8ecc 2023-05-02 benni
191 9c5d8ecc 2023-05-02 benni if (fields & FIELD_TYPE)
192 7c38b724 2023-05-15 benni printf ("%-*s ", p->type, "TYPE");
193 9c5d8ecc 2023-05-02 benni
194 1678d261 2023-05-15 benni if (fields & FIELD_COMMENT)
195 7c38b724 2023-05-15 benni printf ("%-*s ", p->comment, "COMMENT");
196 9c5d8ecc 2023-05-02 benni
197 7c38b724 2023-05-15 benni #if WSDEBUG
198 7c38b724 2023-05-15 benni putchar ('X');
199 7c38b724 2023-05-15 benni #endif
200 7c38b724 2023-05-15 benni
201 9c5d8ecc 2023-05-02 benni putchar ('\n');
202 33bf1ca4 2023-04-08 benni }
203 33bf1ca4 2023-04-08 benni
204 1115ad3a 2023-05-05 benni static void print_duid (const u_char *duid)
205 3103dec4 2023-05-01 benni {
206 1115ad3a 2023-05-05 benni for (size_t i = 0; i < 8; ++i) {
207 1115ad3a 2023-05-05 benni printf ("%02x", duid[i]);
208 1115ad3a 2023-05-05 benni }
209 1115ad3a 2023-05-05 benni }
210 1115ad3a 2023-05-05 benni
211 7c38b724 2023-05-15 benni static void print_disk (const struct my_diskinfo *, int, int, const char *, const struct padding *);
212 1115ad3a 2023-05-05 benni static void print_part (
213 1115ad3a 2023-05-05 benni const struct my_diskinfo *disk,
214 1115ad3a 2023-05-05 benni const struct my_partinfo *part,
215 1115ad3a 2023-05-05 benni int fields,
216 1115ad3a 2023-05-05 benni int options,
217 7c38b724 2023-05-15 benni bool last,
218 7c38b724 2023-05-15 benni const struct padding *p
219 1115ad3a 2023-05-05 benni ) {
220 0c961f7d 2023-05-04 benni if (fields & FIELD_NAME) {
221 0c961f7d 2023-05-04 benni const char *prefix = (options & OPT_NOUNICODE) ? " " : (last ? "└─" : "├─");
222 7c38b724 2023-05-15 benni printf (
223 7c38b724 2023-05-15 benni "%s%s%c%-*s ",
224 7c38b724 2023-05-15 benni prefix,
225 7c38b724 2023-05-15 benni disk->name,
226 7c38b724 2023-05-15 benni part->letter,
227 7c38b724 2023-05-15 benni p->name - 2 - (int)strlen (disk->name) - 1,
228 7c38b724 2023-05-15 benni ""
229 7c38b724 2023-05-15 benni );
230 0c961f7d 2023-05-04 benni }
231 3103dec4 2023-05-01 benni
232 1115ad3a 2023-05-05 benni if (fields & FIELD_DUID) {
233 1115ad3a 2023-05-05 benni print_duid (disk->duid);
234 1115ad3a 2023-05-05 benni printf (".%c ", part->letter);
235 1115ad3a 2023-05-05 benni }
236 1115ad3a 2023-05-05 benni
237 9c5d8ecc 2023-05-02 benni if (fields & FIELD_SIZE)
238 9c5d8ecc 2023-05-02 benni print_size (part->size);
239 9c5d8ecc 2023-05-02 benni
240 9c5d8ecc 2023-05-02 benni if (fields & FIELD_USED) {
241 9c5d8ecc 2023-05-02 benni if (part->fssize) {
242 9c5d8ecc 2023-05-02 benni print_size (part->fssize - part->free);
243 9c5d8ecc 2023-05-02 benni } else {
244 9c5d8ecc 2023-05-02 benni printf (" N/A ");
245 9c5d8ecc 2023-05-02 benni }
246 3103dec4 2023-05-01 benni }
247 3103dec4 2023-05-01 benni
248 9c5d8ecc 2023-05-02 benni if (fields & FIELD_FREE) {
249 9c5d8ecc 2023-05-02 benni if (part->fssize) {
250 9c5d8ecc 2023-05-02 benni print_size (part->free);
251 9c5d8ecc 2023-05-02 benni } else {
252 9c5d8ecc 2023-05-02 benni printf (" N/A ");
253 9c5d8ecc 2023-05-02 benni }
254 9c5d8ecc 2023-05-02 benni }
255 9c5d8ecc 2023-05-02 benni
256 9c5d8ecc 2023-05-02 benni if (fields & FIELD_TYPE)
257 7c38b724 2023-05-15 benni printf ("%-*s ", p->type, part->fstype);
258 9c5d8ecc 2023-05-02 benni
259 7c38b724 2023-05-15 benni if (fields & FIELD_COMMENT)
260 7c38b724 2023-05-15 benni printf ("%-*s ", p->comment, part->mount ? part->mount : "");
261 9c5d8ecc 2023-05-02 benni
262 7c38b724 2023-05-15 benni #if WSDEBUG
263 7c38b724 2023-05-15 benni putchar ('X');
264 7c38b724 2023-05-15 benni #endif
265 7c38b724 2023-05-15 benni
266 9c5d8ecc 2023-05-02 benni putchar ('\n');
267 b0a1b420 2023-05-15 benni
268 b0a1b420 2023-05-15 benni if (part->sub) {
269 7c38b724 2023-05-15 benni print_disk (part->sub, fields, options, part->raidstatus, p);
270 b0a1b420 2023-05-15 benni }
271 9c5d8ecc 2023-05-02 benni }
272 9c5d8ecc 2023-05-02 benni
273 7c38b724 2023-05-15 benni static void print_disk (
274 7c38b724 2023-05-15 benni const struct my_diskinfo *disk,
275 7c38b724 2023-05-15 benni int fields,
276 7c38b724 2023-05-15 benni int options,
277 7c38b724 2023-05-15 benni const char *raidstatus,
278 7c38b724 2023-05-15 benni const struct padding *p
279 7c38b724 2023-05-15 benni ) {
280 b0a1b420 2023-05-15 benni if (fields & FIELD_NAME) {
281 7e6c6845 2023-05-15 benni const char *prefix = raidstatus ? (options & OPT_NOUNICODE ? " " : "│ └─") : "";
282 7c38b724 2023-05-15 benni
283 7c38b724 2023-05-15 benni printf (
284 7c38b724 2023-05-15 benni "%s%-*s ",
285 7c38b724 2023-05-15 benni prefix,
286 7e6c6845 2023-05-15 benni p->name - (raidstatus ? 4 : 0),
287 7c38b724 2023-05-15 benni disk->name
288 7c38b724 2023-05-15 benni );
289 b0a1b420 2023-05-15 benni }
290 9c5d8ecc 2023-05-02 benni
291 1115ad3a 2023-05-05 benni if (fields & FIELD_DUID) {
292 1115ad3a 2023-05-05 benni print_duid (disk->duid);
293 1115ad3a 2023-05-05 benni printf (" ");
294 1115ad3a 2023-05-05 benni }
295 1115ad3a 2023-05-05 benni
296 9c5d8ecc 2023-05-02 benni if (fields & FIELD_SIZE)
297 9c5d8ecc 2023-05-02 benni print_size (disk->size);
298 9c5d8ecc 2023-05-02 benni
299 9c5d8ecc 2023-05-02 benni if (fields & FIELD_USED)
300 9c5d8ecc 2023-05-02 benni print_size (disk->used);
301 9c5d8ecc 2023-05-02 benni
302 9c5d8ecc 2023-05-02 benni if (fields & FIELD_FREE)
303 9c5d8ecc 2023-05-02 benni print_size (disk->size - disk->used);
304 9c5d8ecc 2023-05-02 benni
305 9c5d8ecc 2023-05-02 benni if (fields & FIELD_TYPE)
306 7c38b724 2023-05-15 benni printf ("%-*.16s ", p->type, disk->type);
307 9c5d8ecc 2023-05-02 benni
308 1678d261 2023-05-15 benni if (fields & FIELD_COMMENT) {
309 1678d261 2023-05-15 benni if (raidstatus) {
310 7c38b724 2023-05-15 benni printf ("%-*s ", p->comment, raidstatus);
311 1678d261 2023-05-15 benni } else if (disk->raidstatus) {
312 7c38b724 2023-05-15 benni printf (
313 7c38b724 2023-05-15 benni "%.16s (%s)%*s ",
314 7c38b724 2023-05-15 benni disk->label,
315 7c38b724 2023-05-15 benni disk->raidstatus,
316 7c38b724 2023-05-15 benni p->comment - (int)strnlen (disk->label, sizeof disk->label) - (int)strlen (disk->raidstatus) - 3,
317 7c38b724 2023-05-15 benni ""
318 7c38b724 2023-05-15 benni );
319 1678d261 2023-05-15 benni } else {
320 7c38b724 2023-05-15 benni printf ("%-*.16s ", p->comment, disk->label);
321 1678d261 2023-05-15 benni }
322 1678d261 2023-05-15 benni }
323 9c5d8ecc 2023-05-02 benni
324 7c38b724 2023-05-15 benni #if WSDEBUG
325 7c38b724 2023-05-15 benni putchar ('X');
326 7c38b724 2023-05-15 benni #endif
327 7c38b724 2023-05-15 benni
328 9c5d8ecc 2023-05-02 benni putchar ('\n');
329 9c5d8ecc 2023-05-02 benni
330 1678d261 2023-05-15 benni if (!raidstatus) {
331 b0a1b420 2023-05-15 benni for (uint8_t i = 0; i < disk->num_parts; ++i)
332 7c38b724 2023-05-15 benni print_part (disk, &disk->parts[i], fields, options, i == (disk->num_parts - 1), p);
333 b0a1b420 2023-05-15 benni }
334 9c5d8ecc 2023-05-02 benni }
335 9c5d8ecc 2023-05-02 benni
336 9c5d8ecc 2023-05-02 benni static const struct statfs *find_mount (const char *dev)
337 9c5d8ecc 2023-05-02 benni {
338 9c5d8ecc 2023-05-02 benni static struct statfs *mounts = NULL;
339 9c5d8ecc 2023-05-02 benni static int n_mounts;
340 9c5d8ecc 2023-05-02 benni
341 9c5d8ecc 2023-05-02 benni if (!mounts) {
342 9c5d8ecc 2023-05-02 benni n_mounts = getmntinfo (&mounts, MNT_NOWAIT);
343 9c5d8ecc 2023-05-02 benni if (n_mounts == 0)
344 44121ddf 2023-05-05 benni err (1, "getmntinfo()");
345 9c5d8ecc 2023-05-02 benni }
346 9c5d8ecc 2023-05-02 benni
347 9c5d8ecc 2023-05-02 benni for (int i = 0; i < n_mounts; ++i) {
348 9c5d8ecc 2023-05-02 benni if (!strcmp (dev, mounts[i].f_mntfromname))
349 9c5d8ecc 2023-05-02 benni return &mounts[i];
350 9c5d8ecc 2023-05-02 benni }
351 9c5d8ecc 2023-05-02 benni
352 9c5d8ecc 2023-05-02 benni return NULL;
353 3103dec4 2023-05-01 benni }
354 3103dec4 2023-05-01 benni
355 a88ae11e 2023-05-30 benni static int read_disk (const char *name, struct my_diskinfo *disk)
356 9c5d8ecc 2023-05-02 benni {
357 9c5d8ecc 2023-05-02 benni struct disklabel label;
358 0c422a5b 2023-05-05 benni char *ppath, *letter;
359 9c5d8ecc 2023-05-02 benni
360 f977c8f4 2023-05-30 benni memset (disk, 0, sizeof *disk);
361 9c5d8ecc 2023-05-02 benni
362 9c5d8ecc 2023-05-02 benni { // Read disklabel.
363 0c422a5b 2023-05-05 benni size_t len;
364 9c5d8ecc 2023-05-02 benni int fd;
365 9c5d8ecc 2023-05-02 benni
366 0c422a5b 2023-05-05 benni fd = opendev (name, O_RDONLY, OPENDEV_PART | OPENDEV_BLCK, &ppath);
367 a88ae11e 2023-05-30 benni if (fd < 0) {
368 a88ae11e 2023-05-30 benni warn ("read_disk(): opendev(%s)", name);
369 a88ae11e 2023-05-30 benni return -1;
370 a88ae11e 2023-05-30 benni }
371 0c422a5b 2023-05-05 benni
372 a88ae11e 2023-05-30 benni if (ioctl (fd, DIOCGDINFO, &label) < 0) {
373 a88ae11e 2023-05-30 benni warn ("read_disk(): ioctl(%s, DIOCGDINFO)", name);
374 a88ae11e 2023-05-30 benni close (fd);
375 a88ae11e 2023-05-30 benni return -1;
376 a88ae11e 2023-05-30 benni }
377 9c5d8ecc 2023-05-02 benni close (fd);
378 9c5d8ecc 2023-05-02 benni
379 0c422a5b 2023-05-05 benni len = strlen (ppath);
380 0c422a5b 2023-05-05 benni letter = ppath + len - 1;
381 0c422a5b 2023-05-05 benni }
382 5691dc7b 2023-05-05 benni
383 a88ae11e 2023-05-30 benni strlcpy (disk->name, name, sizeof disk->name);
384 a88ae11e 2023-05-30 benni disk->size = DL_GETDSIZE (&label) * label.d_secsize;
385 a88ae11e 2023-05-30 benni memcpy (disk->type, label.d_typename, sizeof disk->type);
386 a88ae11e 2023-05-30 benni stripdisk (disk->type);
387 a88ae11e 2023-05-30 benni memcpy (disk->label, label.d_packname, sizeof disk->label);
388 a88ae11e 2023-05-30 benni memcpy (disk->duid, label.d_uid, sizeof disk->duid);
389 a88ae11e 2023-05-30 benni disk->num_parts = 0;
390 9c5d8ecc 2023-05-02 benni
391 9c5d8ecc 2023-05-02 benni for (uint16_t i = 0; i < label.d_npartitions; ++i) {
392 9c5d8ecc 2023-05-02 benni const struct partition *p = &label.d_partitions[i];
393 9c5d8ecc 2023-05-02 benni const struct statfs *mnt;
394 9c5d8ecc 2023-05-02 benni struct my_partinfo part;
395 9c5d8ecc 2023-05-02 benni
396 f977c8f4 2023-05-30 benni memset (&part, 0, sizeof part);
397 9c5d8ecc 2023-05-02 benni
398 9c5d8ecc 2023-05-02 benni part.size = DL_GETPSIZE (p) * label.d_secsize;
399 9c5d8ecc 2023-05-02 benni
400 9c5d8ecc 2023-05-02 benni if (!part.size)
401 9c5d8ecc 2023-05-02 benni continue;
402 9c5d8ecc 2023-05-02 benni
403 1115ad3a 2023-05-05 benni part.letter = 'a' + i;
404 0c422a5b 2023-05-05 benni *letter = part.letter;
405 0c422a5b 2023-05-05 benni mnt = find_mount (ppath);
406 9c5d8ecc 2023-05-02 benni
407 9c5d8ecc 2023-05-02 benni if (mnt) {
408 9c5d8ecc 2023-05-02 benni const uint64_t bs = mnt->f_bsize;
409 9c5d8ecc 2023-05-02 benni part.mount = mnt->f_mntonname;
410 9c5d8ecc 2023-05-02 benni part.fssize = mnt->f_blocks * bs;
411 9c5d8ecc 2023-05-02 benni part.free = mnt->f_bfree * bs;
412 9c5d8ecc 2023-05-02 benni }
413 9c5d8ecc 2023-05-02 benni
414 9c5d8ecc 2023-05-02 benni part.fstype = fstypenames[p->p_fstype];
415 9c5d8ecc 2023-05-02 benni if (i != 2)
416 a88ae11e 2023-05-30 benni disk->used += part.size;
417 a88ae11e 2023-05-30 benni disk->parts[disk->num_parts++] = part;
418 9c5d8ecc 2023-05-02 benni }
419 9c5d8ecc 2023-05-02 benni
420 a88ae11e 2023-05-30 benni return 0;
421 9c5d8ecc 2023-05-02 benni }
422 9c5d8ecc 2023-05-02 benni
423 1678d261 2023-05-15 benni static const char *bd_statusstr (int status) {
424 1678d261 2023-05-15 benni switch (status) {
425 1678d261 2023-05-15 benni case BIOC_SDONLINE: return BIOC_SDONLINE_S;
426 1678d261 2023-05-15 benni case BIOC_SDOFFLINE: return BIOC_SDOFFLINE_S;
427 1678d261 2023-05-15 benni case BIOC_SDFAILED: return BIOC_SDFAILED_S;
428 1678d261 2023-05-15 benni case BIOC_SDREBUILD: return BIOC_SDREBUILD_S;
429 1678d261 2023-05-15 benni case BIOC_SDHOTSPARE: return BIOC_SDHOTSPARE_S;
430 1678d261 2023-05-15 benni case BIOC_SDUNUSED: return BIOC_SDUNUSED_S;
431 1678d261 2023-05-15 benni case BIOC_SDSCRUB: return BIOC_SDSCRUB_S;
432 1678d261 2023-05-15 benni case BIOC_SDINVALID: return BIOC_SDINVALID_S;
433 1678d261 2023-05-15 benni default: return "Unknown";
434 1678d261 2023-05-15 benni }
435 1678d261 2023-05-15 benni }
436 1678d261 2023-05-15 benni
437 1678d261 2023-05-15 benni static const char *bv_statusstr (int status) {
438 1678d261 2023-05-15 benni switch (status) {
439 1678d261 2023-05-15 benni case BIOC_SVONLINE: return BIOC_SVONLINE_S;
440 1678d261 2023-05-15 benni case BIOC_SVOFFLINE: return BIOC_SVOFFLINE_S;
441 1678d261 2023-05-15 benni case BIOC_SVDEGRADED: return BIOC_SVDEGRADED_S;
442 1678d261 2023-05-15 benni case BIOC_SVBUILDING: return BIOC_SVBUILDING_S;
443 1678d261 2023-05-15 benni case BIOC_SVSCRUB: return BIOC_SVSCRUB_S;
444 1678d261 2023-05-15 benni case BIOC_SVREBUILD: return BIOC_SVREBUILD_S;
445 1678d261 2023-05-15 benni case BIOC_SVINVALID: return BIOC_SVINVALID_S;
446 1678d261 2023-05-15 benni default: return "Unknown";
447 1678d261 2023-05-15 benni }
448 1678d261 2023-05-15 benni }
449 1678d261 2023-05-15 benni
450 b0a1b420 2023-05-15 benni static void read_raid (
451 1678d261 2023-05-15 benni struct my_diskinfo *disk,
452 b0a1b420 2023-05-15 benni struct my_diskinfo *disks,
453 b0a1b420 2023-05-15 benni size_t num_disks
454 b0a1b420 2023-05-15 benni ) {
455 b0a1b420 2023-05-15 benni struct bioc_inq bi;
456 b0a1b420 2023-05-15 benni int fd;
457 b0a1b420 2023-05-15 benni
458 b0a1b420 2023-05-15 benni fd = opendev (disk->name, O_RDONLY, OPENDEV_PART | OPENDEV_BLCK, NULL);
459 b0a1b420 2023-05-15 benni if (fd < 0) {
460 b0a1b420 2023-05-15 benni warn ("read_raid(): opendev(%s)", disk->name);
461 b0a1b420 2023-05-15 benni return;
462 b0a1b420 2023-05-15 benni }
463 b0a1b420 2023-05-15 benni
464 f977c8f4 2023-05-30 benni memset (&bi, 0, sizeof bi);
465 b0a1b420 2023-05-15 benni
466 b0a1b420 2023-05-15 benni if (ioctl (fd, BIOCINQ, &bi) == -1)
467 b0a1b420 2023-05-15 benni goto ret;
468 b0a1b420 2023-05-15 benni
469 1678d261 2023-05-15 benni for (int i = 0; i < bi.bi_novol; ++i) {
470 1678d261 2023-05-15 benni struct bioc_vol bv;
471 b0a1b420 2023-05-15 benni
472 f977c8f4 2023-05-30 benni memset (&bv, 0, sizeof bv);
473 1678d261 2023-05-15 benni memcpy (&bv.bv_bio, &bi.bi_bio, sizeof bi.bi_bio);
474 1678d261 2023-05-15 benni bv.bv_volid = i;
475 1678d261 2023-05-15 benni
476 1678d261 2023-05-15 benni if (ioctl (fd, BIOCVOL, &bv) == -1) {
477 1678d261 2023-05-15 benni warn ("read_raid(%s): BIOCVOL(%d)", disk->name, i);
478 b0a1b420 2023-05-15 benni continue;
479 b0a1b420 2023-05-15 benni }
480 b0a1b420 2023-05-15 benni
481 1678d261 2023-05-15 benni if (strcmp (disk->name, bv.bv_dev) != 0)
482 b0a1b420 2023-05-15 benni continue;
483 b0a1b420 2023-05-15 benni
484 1678d261 2023-05-15 benni disk->raidstatus = bv_statusstr (bv.bv_status);
485 1678d261 2023-05-15 benni
486 1678d261 2023-05-15 benni for (int j = 0; j < bv.bv_nodisk; ++j) {
487 1678d261 2023-05-15 benni struct bioc_disk bd;
488 1678d261 2023-05-15 benni size_t len_vendor;
489 1678d261 2023-05-15 benni char letter;
490 1678d261 2023-05-15 benni
491 f977c8f4 2023-05-30 benni memset (&bd, 0, sizeof bd);
492 1678d261 2023-05-15 benni memcpy (&bd.bd_bio, &bi.bi_bio, sizeof bi.bi_bio);
493 1678d261 2023-05-15 benni bd.bd_volid = i;
494 1678d261 2023-05-15 benni bd.bd_diskid = j;
495 1678d261 2023-05-15 benni
496 1678d261 2023-05-15 benni if (ioctl (fd, BIOCDISK, &bd) == -1) {
497 1678d261 2023-05-15 benni warn ("read_raid(%s): BIOCDISK(%d, %d)", disk->name, i, j);
498 1678d261 2023-05-15 benni continue;
499 1678d261 2023-05-15 benni }
500 1678d261 2023-05-15 benni
501 1678d261 2023-05-15 benni len_vendor = strlen (bd.bd_vendor);
502 185836d0 2023-05-15 benni if (len_vendor < 4 || len_vendor > 8) {
503 1678d261 2023-05-15 benni warnx ("read_raid(%s): unexpected vendor string: %.32s", disk->name, bd.bd_vendor);
504 1678d261 2023-05-15 benni continue;
505 1678d261 2023-05-15 benni }
506 1678d261 2023-05-15 benni letter = bd.bd_vendor[len_vendor - 1];
507 1678d261 2023-05-15 benni
508 1678d261 2023-05-15 benni for (size_t k = 0; k < num_disks; ++k) {
509 1678d261 2023-05-15 benni if (!memcmp (bd.bd_vendor, disks[k].name, 3)) {
510 1678d261 2023-05-15 benni for (size_t l = 0; l < disks[k].num_parts; ++l) {
511 1678d261 2023-05-15 benni if (letter == disks[k].parts[l].letter) {
512 1678d261 2023-05-15 benni disks[k].parts[l].sub = disk;
513 1678d261 2023-05-15 benni disks[k].parts[l].raidstatus = bd_statusstr(bd.bd_status);
514 1678d261 2023-05-15 benni goto found;
515 1678d261 2023-05-15 benni }
516 b0a1b420 2023-05-15 benni }
517 b0a1b420 2023-05-15 benni }
518 b0a1b420 2023-05-15 benni }
519 1678d261 2023-05-15 benni found:;
520 b0a1b420 2023-05-15 benni }
521 b0a1b420 2023-05-15 benni }
522 b0a1b420 2023-05-15 benni ret:
523 b0a1b420 2023-05-15 benni close (fd);
524 b0a1b420 2023-05-15 benni }
525 b0a1b420 2023-05-15 benni
526 9c5d8ecc 2023-05-02 benni static int usage (void)
527 9c5d8ecc 2023-05-02 benni {
528 1678d261 2023-05-15 benni fputs ("Usage: lsblk [-abinUuV] [disk...]\n", stderr);
529 9c5d8ecc 2023-05-02 benni return 1;
530 4d8e470c 2023-05-15 benni }
531 4d8e470c 2023-05-15 benni
532 7c38b724 2023-05-15 benni static void pad_update (int *pad, int newval)
533 7c38b724 2023-05-15 benni {
534 7c38b724 2023-05-15 benni if (newval > *pad)
535 7c38b724 2023-05-15 benni *pad = newval;
536 7c38b724 2023-05-15 benni }
537 7c38b724 2023-05-15 benni
538 7c38b724 2023-05-15 benni static void pad_disk (struct padding *p, const struct my_diskinfo *disk)
539 7c38b724 2023-05-15 benni {
540 7c38b724 2023-05-15 benni size_t len_disk;
541 7c38b724 2023-05-15 benni int comment;
542 7c38b724 2023-05-15 benni
543 7c38b724 2023-05-15 benni len_disk = strnlen (disk->name, sizeof disk->name);
544 7c38b724 2023-05-15 benni comment = strnlen (disk->label, sizeof disk->label);
545 7c38b724 2023-05-15 benni if (disk->raidstatus)
546 7c38b724 2023-05-15 benni comment += strlen (disk->raidstatus) + 3;
547 7c38b724 2023-05-15 benni
548 7c38b724 2023-05-15 benni pad_update (&p->name, len_disk);
549 7c38b724 2023-05-15 benni pad_update (&p->type, strnlen (disk->type, sizeof disk->type));
550 7c38b724 2023-05-15 benni pad_update (&p->comment, comment);
551 7c38b724 2023-05-15 benni
552 7c38b724 2023-05-15 benni for (int i = 0; i < disk->num_parts; ++i) {
553 7c38b724 2023-05-15 benni const struct my_partinfo *part = &disk->parts[i];
554 7c38b724 2023-05-15 benni
555 7c38b724 2023-05-15 benni pad_update (&p->name, len_disk + 3);
556 7c38b724 2023-05-15 benni pad_update (&p->type, strlen (part->fstype));
557 7c38b724 2023-05-15 benni
558 7c38b724 2023-05-15 benni if (part->sub) {
559 7c38b724 2023-05-15 benni pad_update (&p->name, strnlen (part->sub->name, sizeof part->sub->name) + 4);
560 7c38b724 2023-05-15 benni pad_update (&p->comment, strlen (part->raidstatus));
561 7c38b724 2023-05-15 benni } else if (part->mount) {
562 7c38b724 2023-05-15 benni pad_update (&p->comment, strlen (part->mount));
563 7c38b724 2023-05-15 benni }
564 7c38b724 2023-05-15 benni }
565 7c38b724 2023-05-15 benni }
566 7c38b724 2023-05-15 benni
567 7c38b724 2023-05-15 benni
568 4d8e470c 2023-05-15 benni static int compare_disk (const void *p1, const void *p2)
569 4d8e470c 2023-05-15 benni {
570 4d8e470c 2023-05-15 benni const struct my_diskinfo *d1 = p1;
571 4d8e470c 2023-05-15 benni const struct my_diskinfo *d2 = p2;
572 4d8e470c 2023-05-15 benni
573 4d8e470c 2023-05-15 benni return strcmp (d1->name, d2->name);
574 9c5d8ecc 2023-05-02 benni }
575 9c5d8ecc 2023-05-02 benni
576 33bf1ca4 2023-04-08 benni int main (int argc, char *argv[])
577 33bf1ca4 2023-04-08 benni {
578 8380cc51 2023-05-01 benni int option;
579 9c5d8ecc 2023-05-02 benni int fields = FIELD_DEFAULT;
580 0c961f7d 2023-05-04 benni int options = 0;
581 14fd8749 2023-05-30 benni int ret = 0;
582 8380cc51 2023-05-01 benni
583 aff15c23 2023-04-30 benni if (unveil ("/dev", "r") == -1)
584 44121ddf 2023-05-05 benni err (1, "unveil(/dev)");
585 aff15c23 2023-04-30 benni
586 aff15c23 2023-04-30 benni if (unveil (NULL, NULL) == -1)
587 44121ddf 2023-05-05 benni err (1, "unveil()");
588 aff15c23 2023-04-30 benni
589 1678d261 2023-05-15 benni while ((option = getopt (argc, argv, ":abinUuV")) != -1) {
590 33bf1ca4 2023-04-08 benni switch (option) {
591 bf0e6461 2023-05-02 benni case 'a':
592 bf0e6461 2023-05-02 benni fields = -1;
593 8380cc51 2023-05-01 benni break;
594 1678d261 2023-05-15 benni case 'b':
595 1678d261 2023-05-15 benni options |= OPT_NOBIO;
596 1678d261 2023-05-15 benni break;
597 0c961f7d 2023-05-04 benni case 'i':
598 0c961f7d 2023-05-04 benni options |= OPT_NOUNICODE;
599 0c961f7d 2023-05-04 benni break;
600 bf0e6461 2023-05-02 benni case 'n':
601 0c961f7d 2023-05-04 benni options |= OPT_NOHEADER;
602 bf0e6461 2023-05-02 benni break;
603 1115ad3a 2023-05-05 benni case 'U':
604 1115ad3a 2023-05-05 benni fields |= FIELD_DUID;
605 1115ad3a 2023-05-05 benni break;
606 9c5d8ecc 2023-05-02 benni case 'u':
607 9c5d8ecc 2023-05-02 benni fields |= FIELD_USED | FIELD_FREE;
608 9c5d8ecc 2023-05-02 benni break;
609 1115ad3a 2023-05-05 benni case 'V':
610 1115ad3a 2023-05-05 benni puts ("lsblk-" VERSION);
611 1115ad3a 2023-05-05 benni return 0;
612 33bf1ca4 2023-04-08 benni default:
613 33bf1ca4 2023-04-08 benni return usage ();
614 33bf1ca4 2023-04-08 benni }
615 33bf1ca4 2023-04-08 benni }
616 33bf1ca4 2023-04-08 benni
617 5691dc7b 2023-05-05 benni argv += optind;
618 5691dc7b 2023-05-05 benni argc -= optind;
619 33bf1ca4 2023-04-08 benni
620 5691dc7b 2023-05-05 benni char *names = argc == 0 ? disknames () : NULL;
621 33bf1ca4 2023-04-08 benni
622 33bf1ca4 2023-04-08 benni if (pledge ("stdio rpath disklabel", NULL) == -1)
623 44121ddf 2023-05-05 benni err (1, "pledge()");
624 33bf1ca4 2023-04-08 benni
625 5691dc7b 2023-05-05 benni size_t cap_disks;
626 5691dc7b 2023-05-05 benni if (argc == 0) {
627 5691dc7b 2023-05-05 benni const char *s = names;
628 56e08374 2023-06-08 benni cap_disks = 1;
629 5691dc7b 2023-05-05 benni while ((s = strchr (s, ',')) != NULL)
630 5691dc7b 2023-05-05 benni ++cap_disks, ++s;
631 5691dc7b 2023-05-05 benni } else {
632 5691dc7b 2023-05-05 benni cap_disks = argc;
633 5691dc7b 2023-05-05 benni }
634 5691dc7b 2023-05-05 benni
635 9c5d8ecc 2023-05-02 benni struct my_diskinfo *disks = calloc (cap_disks, sizeof (struct my_diskinfo));
636 5691dc7b 2023-05-05 benni size_t num_disks = 0;
637 8380cc51 2023-05-01 benni
638 5691dc7b 2023-05-05 benni if (argc == 0) {
639 a88ae11e 2023-05-30 benni for (char *name; (name = strsep (&names, ",")) != NULL; ) {
640 a88ae11e 2023-05-30 benni char *colon = strchr (name, ':');
641 a88ae11e 2023-05-30 benni struct my_diskinfo disk;
642 a88ae11e 2023-05-30 benni
643 5691dc7b 2023-05-05 benni if (colon)
644 5691dc7b 2023-05-05 benni *colon = '\0';
645 a88ae11e 2023-05-30 benni if (read_disk (name, &disk) == 0) {
646 a88ae11e 2023-05-30 benni disks[num_disks++] = disk;
647 14fd8749 2023-05-30 benni } else {
648 14fd8749 2023-05-30 benni ret = 1;
649 a88ae11e 2023-05-30 benni }
650 a88ae11e 2023-05-30 benni
651 5691dc7b 2023-05-05 benni if (colon)
652 5691dc7b 2023-05-05 benni *colon = ':';
653 33bf1ca4 2023-04-08 benni }
654 5691dc7b 2023-05-05 benni } else {
655 5691dc7b 2023-05-05 benni for (int i = 0; i < argc; ++i) {
656 a88ae11e 2023-05-30 benni char *name = basename (argv[i]);
657 a88ae11e 2023-05-30 benni char *last = name + strlen (name) - 1;
658 a88ae11e 2023-05-30 benni struct my_diskinfo disk;
659 a88ae11e 2023-05-30 benni
660 185836d0 2023-05-15 benni if (isalpha (*last)) {
661 a88ae11e 2023-05-30 benni warnx ("%s: specifying a partition is not supported, using the drive.", name);
662 185836d0 2023-05-15 benni *last = '\0';
663 185836d0 2023-05-15 benni }
664 a88ae11e 2023-05-30 benni if (read_disk (name, &disk) == 0) {
665 a88ae11e 2023-05-30 benni disks[num_disks++] = disk;
666 14fd8749 2023-05-30 benni } else {
667 14fd8749 2023-05-30 benni ret = 1;
668 a88ae11e 2023-05-30 benni }
669 5691dc7b 2023-05-05 benni }
670 33bf1ca4 2023-04-08 benni }
671 33bf1ca4 2023-04-08 benni
672 33bf1ca4 2023-04-08 benni free (names);
673 9c5d8ecc 2023-05-02 benni
674 14fd8749 2023-05-30 benni if (num_disks == 0)
675 14fd8749 2023-05-30 benni return ret;
676 14fd8749 2023-05-30 benni
677 4d8e470c 2023-05-15 benni mergesort (disks, num_disks, sizeof *disks, compare_disk);
678 4d8e470c 2023-05-15 benni
679 1678d261 2023-05-15 benni if (!(options & OPT_NOBIO)) {
680 1678d261 2023-05-15 benni for (size_t i = 0; i < num_disks; ++i) {
681 1678d261 2023-05-15 benni read_raid (&disks[i], disks, num_disks);
682 1678d261 2023-05-15 benni }
683 b0a1b420 2023-05-15 benni }
684 b0a1b420 2023-05-15 benni
685 7c38b724 2023-05-15 benni struct padding p = {
686 7c38b724 2023-05-15 benni .name = strlen ("NAME"),
687 7c38b724 2023-05-15 benni .type = strlen ("TYPE"),
688 7c38b724 2023-05-15 benni .comment = strlen ("COMMENT"),
689 7c38b724 2023-05-15 benni };
690 7c38b724 2023-05-15 benni
691 7c38b724 2023-05-15 benni for (size_t i = 0; i < num_disks; ++i)
692 7c38b724 2023-05-15 benni pad_disk (&p, &disks[i]);
693 7c38b724 2023-05-15 benni
694 0c961f7d 2023-05-04 benni if (!(options & OPT_NOHEADER))
695 7c38b724 2023-05-15 benni print_header (fields, &p);
696 9c5d8ecc 2023-05-02 benni
697 9c5d8ecc 2023-05-02 benni for (size_t i = 0; i < num_disks; ++i) {
698 7c38b724 2023-05-15 benni print_disk (&disks[i], fields, options, NULL, &p);
699 9c5d8ecc 2023-05-02 benni }
700 5691dc7b 2023-05-05 benni
701 14fd8749 2023-05-30 benni return ret;
702 33bf1ca4 2023-04-08 benni }