/src/util-linux/libblkid/src/partitions/dasd.c
Line | Count | Source |
1 | | /* |
2 | | * DASD partition table probing |
3 | | * |
4 | | * Copyright (C) 2026 Red Hat, Inc. |
5 | | * |
6 | | * This file may be redistributed under the terms of the |
7 | | * GNU Lesser General Public License. |
8 | | * |
9 | | * Inspired by fdasd (s390-tools), Linux kernel and libparted. |
10 | | */ |
11 | | #include <stdio.h> |
12 | | #include <string.h> |
13 | | #include <stdlib.h> |
14 | | #include <stdint.h> |
15 | | #include <stddef.h> |
16 | | #include <stdbool.h> |
17 | | #include <inttypes.h> |
18 | | |
19 | | #include "pt-dasd.h" |
20 | | #include "strutils.h" |
21 | | #include "partitions.h" |
22 | | |
23 | | static void dasd_get_volser(const char *volid, char *volser, size_t volsersz) |
24 | 24 | { |
25 | 24 | size_t i; |
26 | | |
27 | 168 | for (i = 0; i < DASD_VOLSER_LENGTH && i < volsersz - 1; i++) |
28 | 144 | volser[i] = dasd_ebcdic_to_ascii[(unsigned char) volid[i]]; |
29 | 24 | volser[i] = '\0'; |
30 | | |
31 | 24 | rtrim_whitespace((unsigned char *) volser); |
32 | 24 | } |
33 | | |
34 | | static void dasd_get_dsnam(const struct dasd_format1_label *f1, |
35 | | char *dsnam, size_t dsnamsz) |
36 | 0 | { |
37 | 0 | size_t i; |
38 | |
|
39 | 0 | for (i = 0; i < sizeof(f1->DS1DSNAM) && i < dsnamsz - 1; i++) |
40 | 0 | dsnam[i] = dasd_ebcdic_to_ascii[(unsigned char) f1->DS1DSNAM[i]]; |
41 | 0 | dsnam[i] = '\0'; |
42 | |
|
43 | 0 | rtrim_whitespace((unsigned char *) dsnam); |
44 | 0 | } |
45 | | |
46 | | /* |
47 | | * CCHH for large volumes: |
48 | | * - upper 12 bits of hh hold the upper cylinder bits |
49 | | * - lower 4 bits of hh are the head number |
50 | | */ |
51 | | static uint32_t dasd_cchh_get_cc(const struct dasd_cchh *p) |
52 | 0 | { |
53 | 0 | uint32_t cyl; |
54 | |
|
55 | 0 | cyl = be16_to_cpu(p->hh) & 0xFFF0; |
56 | 0 | cyl <<= 12; |
57 | 0 | cyl |= be16_to_cpu(p->cc); |
58 | 0 | return cyl; |
59 | 0 | } |
60 | | |
61 | | static uint16_t dasd_cchh_get_hh(const struct dasd_cchh *p) |
62 | 0 | { |
63 | 0 | return be16_to_cpu(p->hh) & 0x000F; |
64 | 0 | } |
65 | | |
66 | | static bool is_dasd_cdl_label(const unsigned char *buf, size_t bufsz) |
67 | 10.9k | { |
68 | 10.9k | if (bufsz < sizeof(DASD_VOL1_MAGIC) - 1 + offsetof(struct dasd_volume_label_cdl, vollbl)) |
69 | 0 | return false; |
70 | 10.9k | return memcmp(buf + offsetof(struct dasd_volume_label_cdl, vollbl), |
71 | 10.9k | DASD_VOL1_MAGIC, sizeof(DASD_VOL1_MAGIC) - 1) == 0; |
72 | 10.9k | } |
73 | | |
74 | | static bool is_dasd_ldl_label(const unsigned char *buf, size_t bufsz) |
75 | 10.8k | { |
76 | 10.8k | if (bufsz < sizeof(DASD_LNX1_MAGIC) - 1) |
77 | 0 | return false; |
78 | 10.8k | return memcmp(buf, DASD_LNX1_MAGIC, sizeof(DASD_LNX1_MAGIC) - 1) == 0 || |
79 | 10.8k | memcmp(buf, DASD_CMS1_MAGIC, sizeof(DASD_CMS1_MAGIC) - 1) == 0; |
80 | 10.8k | } |
81 | | |
82 | | /* |
83 | | * Format 4: 44-byte key field filled with 0x04 + DS4IDFMT (0xf4) |
84 | | */ |
85 | | static bool is_dasd_f4_label(const unsigned char *buf, size_t bufsz) |
86 | 0 | { |
87 | 0 | size_t i; |
88 | |
|
89 | 0 | if (bufsz < DASD_F4_KEYCD_LENGTH + 1) |
90 | 0 | return false; |
91 | | |
92 | 0 | for (i = 0; i < DASD_F4_KEYCD_LENGTH; i++) { |
93 | 0 | if (buf[i] != DASD_F4_KEYCD_BYTE) |
94 | 0 | return false; |
95 | 0 | } |
96 | | |
97 | 0 | return buf[DASD_F4_KEYCD_LENGTH] == DASD_FMT_ID_F4; |
98 | 0 | } |
99 | | |
100 | | /* |
101 | | * CDL -- up to three partitions defined by the F1/8 labels |
102 | | */ |
103 | | static int probe_dasd_pt_cdl(blkid_probe pr, blkid_partlist ls, |
104 | | blkid_parttable tab, |
105 | | unsigned int blocksize) |
106 | 0 | { |
107 | 0 | const struct dasd_format4_label *f4; |
108 | 0 | const struct dasd_format1_label *f1; |
109 | 0 | const unsigned char *buf; |
110 | 0 | unsigned int blk_per_trk = 0; |
111 | 0 | uint16_t heads; |
112 | 0 | uint32_t cylinders; |
113 | 0 | unsigned int blk; |
114 | 0 | int partno = 0; |
115 | | |
116 | | /* |
117 | | * Looking for Format 4 label at blocks 3-20. |
118 | | * On a real DASD, it is at CC=0 HH=1 R=1, which maps to |
119 | | * linux block = blk_per_trk (one track after the start), |
120 | | * but we don't know blk_per_trk yet. |
121 | | */ |
122 | 0 | for (blk = 3; blk <= 20; blk++) { |
123 | 0 | buf = blkid_probe_get_buffer(pr, |
124 | 0 | (uint64_t) blk * blocksize, |
125 | 0 | sizeof(struct dasd_format4_label)); |
126 | 0 | if (!buf) |
127 | 0 | return errno ? -errno : BLKID_PROBE_NONE; |
128 | 0 | if (is_dasd_f4_label(buf, sizeof(struct dasd_format4_label))) { |
129 | 0 | blk_per_trk = blk; |
130 | 0 | break; |
131 | 0 | } |
132 | 0 | } |
133 | | |
134 | 0 | if (!blk_per_trk) { |
135 | 0 | DBG(LOWPROBE, ul_debug("DASD: CDL detected but no F4 label found")); |
136 | 0 | return BLKID_PROBE_NONE; |
137 | 0 | } |
138 | | |
139 | 0 | f4 = (const struct dasd_format4_label *) buf; |
140 | |
|
141 | 0 | heads = be16_to_cpu(f4->DS4DSTRK); |
142 | 0 | if (heads == 0) |
143 | 0 | return BLKID_PROBE_NONE; |
144 | | |
145 | 0 | cylinders = be16_to_cpu(f4->DS4DSCYL); |
146 | | |
147 | | /* large volume -> use DS4DCYL */ |
148 | 0 | if (cylinders == DASD_LV_COMPAT_CYL) |
149 | 0 | cylinders = be32_to_cpu(f4->DS4DCYL); |
150 | |
|
151 | 0 | DBG(LOWPROBE, ul_debug("DASD CDL: blk_per_trk=%u heads=%u cylinders=%u blocksize=%u", |
152 | 0 | blk_per_trk, heads, cylinders, blocksize)); |
153 | | |
154 | | /* scan for format 1 and format 8 labels describing the partitions */ |
155 | 0 | for (blk = blk_per_trk + 1; blk < blk_per_trk + 20 && partno < DASD_MAX_PARTITIONS; blk++) { |
156 | 0 | char dsnam[sizeof(f1->DS1DSNAM) + 1]; |
157 | 0 | char *last_dot; |
158 | 0 | size_t namelen; |
159 | 0 | uint32_t start_cc, end_cc; |
160 | 0 | uint16_t start_hh, end_hh; |
161 | 0 | uint64_t start_trk, end_trk; |
162 | 0 | uint64_t start_512, size_512; |
163 | 0 | blkid_partition par; |
164 | |
|
165 | 0 | buf = blkid_probe_get_buffer(pr, |
166 | 0 | (uint64_t) blk * blocksize, |
167 | 0 | sizeof(struct dasd_format1_label)); |
168 | 0 | if (!buf) |
169 | 0 | return errno ? -errno : BLKID_PROBE_NONE; |
170 | | |
171 | 0 | f1 = (const struct dasd_format1_label *) buf; |
172 | | |
173 | | /* only format 1 and 8 are valid partition descriptors */ |
174 | 0 | if (f1->DS1FMTID != DASD_FMT_ID_F1 && f1->DS1FMTID != DASD_FMT_ID_F8) |
175 | 0 | continue; |
176 | | |
177 | 0 | if (cylinders > DASD_LV_COMPAT_CYL) { |
178 | | /* large volume encoding */ |
179 | 0 | start_cc = dasd_cchh_get_cc(&f1->DS1EXT1.llimit); |
180 | 0 | start_hh = dasd_cchh_get_hh(&f1->DS1EXT1.llimit); |
181 | 0 | end_cc = dasd_cchh_get_cc(&f1->DS1EXT1.ulimit); |
182 | 0 | end_hh = dasd_cchh_get_hh(&f1->DS1EXT1.ulimit); |
183 | 0 | } else { |
184 | 0 | start_cc = be16_to_cpu(f1->DS1EXT1.llimit.cc); |
185 | 0 | start_hh = be16_to_cpu(f1->DS1EXT1.llimit.hh); |
186 | 0 | end_cc = be16_to_cpu(f1->DS1EXT1.ulimit.cc); |
187 | 0 | end_hh = be16_to_cpu(f1->DS1EXT1.ulimit.hh); |
188 | 0 | } |
189 | |
|
190 | 0 | start_trk = (uint64_t) start_cc * heads + start_hh; |
191 | 0 | end_trk = (uint64_t) end_cc * heads + end_hh; |
192 | |
|
193 | 0 | if (end_trk <= start_trk) |
194 | 0 | return BLKID_PROBE_NONE; |
195 | | |
196 | | /* convert to 512 sectors */ |
197 | 0 | start_512 = start_trk * blk_per_trk * blocksize / 512; |
198 | 0 | size_512 = (end_trk - start_trk + 1) * blk_per_trk * blocksize / 512; |
199 | |
|
200 | 0 | DBG(LOWPROBE, ul_debug("DASD CDL part%d: CC=%u-%u HH=%u-%u " |
201 | 0 | "trk=%"PRIu64"-%"PRIu64" start=%"PRIu64" size=%"PRIu64, |
202 | 0 | partno + 1, start_cc, end_cc, start_hh, end_hh, |
203 | 0 | start_trk, end_trk, start_512, size_512)); |
204 | |
|
205 | 0 | par = blkid_partlist_add_partition(ls, tab, start_512, size_512); |
206 | 0 | if (!par) |
207 | 0 | return -ENOMEM; |
208 | | |
209 | 0 | dasd_get_dsnam(f1, dsnam, sizeof(dsnam)); |
210 | | |
211 | | /* split dsnam into name and type at the last '.' */ |
212 | 0 | last_dot = strrchr(dsnam, '.'); |
213 | 0 | if (last_dot) { |
214 | 0 | namelen = min((size_t)(last_dot - dsnam), sizeof(dsnam) - 1); |
215 | 0 | blkid_partition_set_name(par, (unsigned char *) dsnam, namelen); |
216 | 0 | blkid_partition_set_type_string(par, (unsigned char *) last_dot + 1, |
217 | 0 | strlen(last_dot + 1)); |
218 | 0 | } else { |
219 | 0 | blkid_partition_set_type_string(par, (unsigned char *) dsnam, |
220 | 0 | strlen(dsnam)); |
221 | 0 | } |
222 | |
|
223 | 0 | partno++; |
224 | 0 | } |
225 | | |
226 | 0 | return BLKID_PROBE_OK; |
227 | 0 | } |
228 | | |
229 | | /* |
230 | | * LDL -- single implicit partition starting at block 3 |
231 | | */ |
232 | | static int probe_dasd_pt_ldl(blkid_probe pr, blkid_partlist ls, |
233 | | blkid_parttable tab, |
234 | | const struct dasd_volume_label_ldl *vlabel, |
235 | | unsigned int blocksize) |
236 | 0 | { |
237 | 0 | uint64_t start_512, size_512; |
238 | 0 | uint64_t blocks; |
239 | 0 | blkid_partition par; |
240 | |
|
241 | 0 | start_512 = (uint64_t) 3 * blocksize / 512; |
242 | |
|
243 | 0 | if ((unsigned char) vlabel->ldl_version >= 0xf2) { |
244 | 0 | blocks = be64_to_cpu(vlabel->formatted_blocks); |
245 | 0 | if (blocks <= 3) { |
246 | 0 | DBG(LOWPROBE, ul_debug("DASD LDL: invalid formatted_blocks %"PRIu64, blocks)); |
247 | 0 | return BLKID_PROBE_NONE; |
248 | 0 | } |
249 | 0 | size_512 = (blocks - 3) * blocksize / 512; |
250 | 0 | } else { |
251 | 0 | uint64_t dev_512 = blkid_probe_get_size(pr) / 512; |
252 | |
|
253 | 0 | if (dev_512 <= start_512) { |
254 | 0 | DBG(LOWPROBE, ul_debug("DASD LDL: device too small")); |
255 | 0 | return BLKID_PROBE_NONE; |
256 | 0 | } |
257 | 0 | size_512 = dev_512 - start_512; |
258 | 0 | } |
259 | | |
260 | 0 | DBG(LOWPROBE, ul_debug("DASD LDL: start=%"PRIu64" size=%"PRIu64" blocksize=%u", |
261 | 0 | start_512, size_512, blocksize)); |
262 | |
|
263 | 0 | par = blkid_partlist_add_partition(ls, tab, start_512, size_512); |
264 | 0 | if (!par) |
265 | 0 | return -ENOMEM; |
266 | | |
267 | 0 | return BLKID_PROBE_OK; |
268 | 0 | } |
269 | | |
270 | | static const unsigned int dasd_blocksizes[] = { 4096, 2048, 1024, 512 }; |
271 | | |
272 | | static int probe_dasd_pt(blkid_probe pr, |
273 | | const struct blkid_idmag *mag __attribute__((__unused__))) |
274 | 9.98k | { |
275 | 9.98k | const unsigned char *buf; |
276 | 9.98k | blkid_parttable tab = NULL; |
277 | 9.98k | blkid_partlist ls; |
278 | 9.98k | char volser[DASD_VOLSER_LENGTH + 1]; |
279 | 9.98k | unsigned int blocksize; |
280 | 9.98k | bool is_cdl = false; |
281 | 9.98k | bool is_ldl = false; |
282 | 9.98k | const struct dasd_volume_label_cdl *cdl = NULL; |
283 | 9.98k | const struct dasd_volume_label_ldl *ldl = NULL; |
284 | 9.98k | const char *magic; |
285 | 9.98k | size_t i = 0; |
286 | | |
287 | 9.98k | blocksize = blkid_probe_get_sectorsize(pr); |
288 | 9.98k | buf = blkid_probe_get_buffer(pr, |
289 | 9.98k | (uint64_t) 2 * blocksize, |
290 | 9.98k | sizeof(struct dasd_volume_label_ldl)); |
291 | 9.98k | if (!buf) |
292 | 6.15k | return errno ? -errno : BLKID_PROBE_NONE; |
293 | | |
294 | | /* CDL -- "VOL1" at byte 4 */ |
295 | 3.82k | if (is_dasd_cdl_label(buf, sizeof(struct dasd_volume_label_ldl))) |
296 | 8 | is_cdl = true; |
297 | | /* LDL -- "LNX1" or "CMS1" at byte 0 */ |
298 | 3.82k | else if (is_dasd_ldl_label(buf, sizeof(struct dasd_volume_label_ldl))) |
299 | 9 | is_ldl = true; |
300 | | |
301 | | /* |
302 | | * check the other known DASD block sizes as well in case we are |
303 | | * scanning e.g. 512 disk image |
304 | | */ |
305 | 3.82k | if (!is_cdl && !is_ldl) { |
306 | 19.0k | for (i = 0; i < ARRAY_SIZE(dasd_blocksizes); i++) { |
307 | 15.2k | if (dasd_blocksizes[i] == blocksize) |
308 | 3.80k | continue; |
309 | | |
310 | 11.4k | buf = blkid_probe_get_buffer(pr, |
311 | 11.4k | (uint64_t) 2 * dasd_blocksizes[i], |
312 | 11.4k | sizeof(struct dasd_volume_label_ldl)); |
313 | 11.4k | if (!buf) { |
314 | 4.35k | if (errno) |
315 | 0 | return -errno; |
316 | 4.35k | continue; |
317 | 4.35k | } |
318 | | |
319 | 7.07k | if (is_dasd_cdl_label(buf, sizeof(struct dasd_volume_label_ldl))) { |
320 | 2 | is_cdl = true; |
321 | 2 | blocksize = dasd_blocksizes[i]; |
322 | 2 | break; |
323 | 2 | } |
324 | 7.07k | if (is_dasd_ldl_label(buf, sizeof(struct dasd_volume_label_ldl))) { |
325 | 5 | is_ldl = true; |
326 | 5 | blocksize = dasd_blocksizes[i]; |
327 | 5 | break; |
328 | 5 | } |
329 | 7.07k | } |
330 | 3.81k | } |
331 | | |
332 | 3.82k | if (!is_cdl && !is_ldl) |
333 | 3.80k | return BLKID_PROBE_NONE; |
334 | | |
335 | 24 | DBG(LOWPROBE, ul_debug("DASD: %s label detected (blocksize=%u)", |
336 | 24 | is_cdl ? "CDL" : "LDL", blocksize)); |
337 | | |
338 | 24 | if (is_cdl) { |
339 | 10 | cdl = (const struct dasd_volume_label_cdl *) buf; |
340 | | |
341 | 10 | if (blkid_probe_set_magic(pr, |
342 | 10 | (uint64_t) 2 * blocksize + |
343 | 10 | offsetof(struct dasd_volume_label_cdl, vollbl), |
344 | 10 | 4, (const unsigned char *) DASD_VOL1_MAGIC)) |
345 | 0 | return BLKID_PROBE_NONE; |
346 | | |
347 | 10 | dasd_get_volser(cdl->volid, volser, sizeof(volser)); |
348 | 14 | } else { |
349 | 14 | ldl = (const struct dasd_volume_label_ldl *) buf; |
350 | 14 | magic = memcmp(buf, DASD_LNX1_MAGIC, 4) == 0 ? DASD_LNX1_MAGIC : DASD_CMS1_MAGIC; |
351 | | |
352 | 14 | if (blkid_probe_set_magic(pr, |
353 | 14 | (uint64_t) 2 * blocksize + |
354 | 14 | offsetof(struct dasd_volume_label_ldl, vollbl), |
355 | 14 | 4, (const unsigned char *) magic)) |
356 | 0 | return BLKID_PROBE_NONE; |
357 | | |
358 | 14 | dasd_get_volser(ldl->volid, volser, sizeof(volser)); |
359 | 14 | } |
360 | | |
361 | 24 | blkid_partitions_strcpy_ptuuid(pr, volser); |
362 | | |
363 | 24 | if (blkid_partitions_need_typeonly(pr)) |
364 | 24 | return BLKID_PROBE_OK; |
365 | | |
366 | 0 | ls = blkid_probe_get_partlist(pr); |
367 | 0 | if (!ls) |
368 | 0 | return BLKID_PROBE_NONE; |
369 | | |
370 | 0 | tab = blkid_partlist_new_parttable(ls, "dasd", 0); |
371 | 0 | if (!tab) |
372 | 0 | return -ENOMEM; |
373 | | |
374 | 0 | blkid_parttable_set_id(tab, (unsigned char *) volser); |
375 | |
|
376 | 0 | if (is_cdl) |
377 | 0 | return probe_dasd_pt_cdl(pr, ls, tab, blocksize); |
378 | | |
379 | 0 | return probe_dasd_pt_ldl(pr, ls, tab, ldl, blocksize); |
380 | 0 | } |
381 | | |
382 | | const struct blkid_idinfo dasd_pt_idinfo = |
383 | | { |
384 | | .name = "dasd", |
385 | | .probefunc = probe_dasd_pt, |
386 | | |
387 | | /* |
388 | | * magic location unfortunately depends on the device geometry and |
389 | | * DASD version and format (CDL or LDL) |
390 | | */ |
391 | | .magics = BLKID_NONE_MAGIC |
392 | | }; |