Coverage Report

Created: 2026-07-16 07:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
};