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