Coverage Report

Created: 2025-10-12 06:56

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/util-linux/libblkid/src/superblocks/btrfs.c
Line
Count
Source
1
/*
2
 * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
3
 *
4
 * This file may be redistributed under the terms of the
5
 * GNU Lesser General Public License.
6
 */
7
#include <stdio.h>
8
#include <stdlib.h>
9
#include <unistd.h>
10
#include <string.h>
11
#include <stdint.h>
12
#include <stdbool.h>
13
#include <assert.h>
14
#include <inttypes.h>
15
16
#ifdef HAVE_LINUX_BLKZONED_H
17
#include <linux/blkzoned.h>
18
#endif
19
20
#include "superblocks.h"
21
#include "crc32c.h"
22
#include "sha256.h"
23
#include "xxhash.h"
24
25
enum btrfs_super_block_csum_type {
26
  BTRFS_SUPER_BLOCK_CSUM_TYPE_CRC32C = 0,
27
  BTRFS_SUPER_BLOCK_CSUM_TYPE_XXHASH = 1,
28
  BTRFS_SUPER_BLOCK_CSUM_TYPE_SHA256 = 2,
29
};
30
31
union btrfs_super_block_csum {
32
  uint8_t bytes[32];
33
  uint32_t crc32c;
34
  XXH64_hash_t xxh64;
35
  uint8_t sha256[UL_SHA256LENGTH];
36
};
37
38
struct btrfs_super_block {
39
  union btrfs_super_block_csum csum;
40
  uint8_t fsid[16];
41
  uint64_t bytenr;
42
  uint64_t flags;
43
  uint8_t magic[8];
44
  uint64_t generation;
45
  uint64_t root;
46
  uint64_t chunk_root;
47
  uint64_t log_root;
48
  uint64_t log_root_transid;
49
  uint64_t total_bytes;
50
  uint64_t bytes_used;
51
  uint64_t root_dir_objectid;
52
  uint64_t num_devices;
53
  uint32_t sectorsize;
54
  uint32_t nodesize;
55
  uint32_t leafsize;
56
  uint32_t stripesize;
57
  uint32_t sys_chunk_array_size;
58
  uint64_t chunk_root_generation;
59
  uint64_t compat_flags;
60
  uint64_t compat_ro_flags;
61
  uint64_t incompat_flags;
62
  uint16_t csum_type;
63
  uint8_t root_level;
64
  uint8_t chunk_root_level;
65
  uint8_t log_root_level;
66
  struct btrfs_dev_item {
67
    uint64_t devid;
68
    uint64_t total_bytes;
69
    uint64_t bytes_used;
70
    uint32_t io_align;
71
    uint32_t io_width;
72
    uint32_t sector_size;
73
    uint64_t type;
74
    uint64_t generation;
75
    uint64_t start_offset;
76
    uint32_t dev_group;
77
    uint8_t seek_speed;
78
    uint8_t bandwidth;
79
    uint8_t uuid[16];
80
    uint8_t fsid[16];
81
  } __attribute__ ((__packed__)) dev_item;
82
  uint8_t label[256];
83
  uint8_t padding[3541]; /* pad to BTRFS_SUPER_INFO_SIZE for csum calculation */
84
} __attribute__ ((__packed__));
85
86
0
#define BTRFS_SUPER_INFO_SIZE 4096
87
88
/* Number of superblock log zones */
89
0
#define BTRFS_NR_SB_LOG_ZONES 2
90
91
/* Introduce some macros and types to unify the code with kernel side */
92
0
#define SECTOR_SHIFT 9
93
94
typedef uint64_t sector_t;
95
96
#ifdef HAVE_LINUX_BLKZONED_H
97
static int sb_write_pointer(blkid_probe pr, struct blk_zone *zones, uint64_t *wp_ret)
98
0
{
99
0
  bool empty[BTRFS_NR_SB_LOG_ZONES];
100
0
  bool full[BTRFS_NR_SB_LOG_ZONES];
101
0
  sector_t sector;
102
103
0
  assert(zones[0].type != BLK_ZONE_TYPE_CONVENTIONAL &&
104
0
         zones[1].type != BLK_ZONE_TYPE_CONVENTIONAL);
105
106
0
  empty[0] = zones[0].cond == BLK_ZONE_COND_EMPTY;
107
0
  empty[1] = zones[1].cond == BLK_ZONE_COND_EMPTY;
108
0
  full[0] = zones[0].cond == BLK_ZONE_COND_FULL;
109
0
  full[1] = zones[1].cond == BLK_ZONE_COND_FULL;
110
111
  /*
112
   * Possible states of log buffer zones
113
   *
114
   *           Empty[0]  In use[0]  Full[0]
115
   * Empty[1]         *          x        0
116
   * In use[1]        0          x        0
117
   * Full[1]          1          1        C
118
   *
119
   * Log position:
120
   *   *: Special case, no superblock is written
121
   *   0: Use write pointer of zones[0]
122
   *   1: Use write pointer of zones[1]
123
   *   C: Compare super blocks from zones[0] and zones[1], use the latest
124
   *      one determined by generation
125
   *   x: Invalid state
126
   */
127
128
0
  if (empty[0] && empty[1]) {
129
    /* Special case to distinguish no superblock to read */
130
0
    *wp_ret = zones[0].start << SECTOR_SHIFT;
131
0
    return -ENOENT;
132
0
  } else if (full[0] && full[1]) {
133
    /* Compare two super blocks */
134
0
    struct btrfs_super_block *super[BTRFS_NR_SB_LOG_ZONES];
135
0
    int i;
136
137
0
    for (i = 0; i < BTRFS_NR_SB_LOG_ZONES; i++) {
138
0
      uint64_t bytenr;
139
140
0
      bytenr = ((zones[i].start + zones[i].len)
141
0
           << SECTOR_SHIFT) - BTRFS_SUPER_INFO_SIZE;
142
143
0
      super[i] = (struct btrfs_super_block *)
144
0
        blkid_probe_get_buffer(pr, bytenr, BTRFS_SUPER_INFO_SIZE);
145
0
      if (!super[i])
146
0
        return -EIO;
147
0
      DBG(LOWPROBE, ul_debug("(btrfs) checking #%d zone "
148
0
            "[start=%" PRIu64", len=%" PRIu64", sb-offset=%" PRIu64"]",
149
0
            i, (uint64_t) zones[i].start,
150
0
            (uint64_t) zones[i].len, bytenr));
151
0
    }
152
153
0
    if (super[0]->generation > super[1]->generation)
154
0
      sector = zones[1].start;
155
0
    else
156
0
      sector = zones[0].start;
157
0
  } else if (!full[0] && (empty[1] || full[1])) {
158
0
    sector = zones[0].wp;
159
0
  } else if (full[0]) {
160
0
    sector = zones[1].wp;
161
0
  } else {
162
0
    return -EUCLEAN;
163
0
  }
164
0
  *wp_ret = sector << SECTOR_SHIFT;
165
166
0
  DBG(LOWPROBE, ul_debug("(btrfs) write pointer: %" PRIu64" sector", sector));
167
0
  return 0;
168
0
}
169
170
static int sb_log_offset(blkid_probe pr, uint64_t *bytenr_ret)
171
0
{
172
0
  uint32_t zone_num = 0;
173
0
  uint32_t zone_size_sector;
174
0
  struct blk_zone_report *rep;
175
0
  struct blk_zone *zones;
176
0
  int ret;
177
0
  int i;
178
0
  uint64_t wp;
179
180
181
0
  zone_size_sector = pr->zone_size >> SECTOR_SHIFT;
182
0
  rep = blkdev_get_zonereport(pr->fd, zone_num * zone_size_sector, 2);
183
0
  if (!rep) {
184
0
    ret = -errno;
185
0
    goto out;
186
0
  }
187
0
  zones = (struct blk_zone *)(rep + 1);
188
189
  /*
190
   * Use the head of the first conventional zone, if the zones
191
   * contain one.
192
   */
193
0
  for (i = 0; i < BTRFS_NR_SB_LOG_ZONES; i++) {
194
0
    if (zones[i].type == BLK_ZONE_TYPE_CONVENTIONAL) {
195
0
      DBG(LOWPROBE, ul_debug("(btrfs) checking conventional zone"));
196
0
      *bytenr_ret = zones[i].start << SECTOR_SHIFT;
197
0
      ret = 0;
198
0
      goto out;
199
0
    }
200
0
  }
201
202
0
  ret = sb_write_pointer(pr, zones, &wp);
203
0
  if (ret != -ENOENT && ret) {
204
0
    ret = 1;
205
0
    goto out;
206
0
  }
207
0
  if (ret != -ENOENT) {
208
0
    if (wp == zones[0].start << SECTOR_SHIFT)
209
0
      wp = (zones[1].start + zones[1].len) << SECTOR_SHIFT;
210
0
    wp -= BTRFS_SUPER_INFO_SIZE;
211
0
  }
212
0
  *bytenr_ret = wp;
213
214
0
  ret = 0;
215
0
out:
216
0
  free(rep);
217
218
0
  return ret;
219
0
}
220
#endif
221
222
static int btrfs_verify_csum(blkid_probe pr, const struct btrfs_super_block *bfs)
223
0
{
224
0
  uint16_t csum_type = le16_to_cpu(bfs->csum_type);
225
0
  const void *csum_data = (char *) bfs + sizeof(bfs->csum);
226
0
  size_t csum_data_size = sizeof(*bfs) - sizeof(bfs->csum);
227
0
  switch (csum_type) {
228
0
    case BTRFS_SUPER_BLOCK_CSUM_TYPE_CRC32C: {
229
0
      uint32_t crc = ~crc32c(~0L, csum_data, csum_data_size);
230
0
      return blkid_probe_verify_csum(pr, crc,
231
0
          le32_to_cpu(bfs->csum.crc32c));
232
0
    }
233
0
    case BTRFS_SUPER_BLOCK_CSUM_TYPE_XXHASH: {
234
0
      XXH64_hash_t xxh64 = XXH64(csum_data, csum_data_size, 0);
235
0
      return blkid_probe_verify_csum(pr, xxh64,
236
0
          le64_to_cpu(bfs->csum.xxh64));
237
0
    }
238
0
    case BTRFS_SUPER_BLOCK_CSUM_TYPE_SHA256: {
239
0
      uint8_t sha256[UL_SHA256LENGTH];
240
0
      ul_SHA256(sha256, csum_data, csum_data_size);
241
0
      return blkid_probe_verify_csum_buf(pr, UL_SHA256LENGTH,
242
0
          sha256, bfs->csum.sha256);
243
0
    }
244
0
    default:
245
0
      DBG(LOWPROBE, ul_debug("(btrfs) unknown checksum type %d, skipping validation",
246
0
                 csum_type));
247
0
      return 1;
248
0
  }
249
0
}
250
251
static int probe_btrfs(blkid_probe pr, const struct blkid_idmag *mag)
252
0
{
253
0
  const struct btrfs_super_block *bfs;
254
255
0
  if (pr->zone_size) {
256
0
#ifdef HAVE_LINUX_BLKZONED_H
257
0
    uint64_t offset = 0;
258
0
    int ret;
259
260
0
    ret = sb_log_offset(pr, &offset);
261
0
    if (ret)
262
0
      return ret;
263
0
    bfs = (struct btrfs_super_block *)
264
0
      blkid_probe_get_buffer(pr, offset,
265
0
                 sizeof(struct btrfs_super_block));
266
#else
267
    /* Nothing can be done */
268
    return 1;
269
#endif
270
0
  } else {
271
0
    bfs = blkid_probe_get_sb(pr, mag, struct btrfs_super_block);
272
0
  }
273
0
  if (!bfs)
274
0
    return errno ? -errno : 1;
275
276
0
  if (!btrfs_verify_csum(pr, bfs))
277
0
    return 1;
278
279
  /* Invalid sector size; total_bytes would be bogus. */
280
0
  if (!le32_to_cpu(bfs->sectorsize))
281
0
    return 1;
282
283
0
  if (*bfs->label)
284
0
    blkid_probe_set_label(pr,
285
0
        (unsigned char *) bfs->label,
286
0
        sizeof(bfs->label));
287
288
0
  blkid_probe_set_uuid(pr, bfs->fsid);
289
0
  blkid_probe_set_uuid_as(pr, bfs->dev_item.uuid, "UUID_SUB");
290
0
  blkid_probe_set_fsblocksize(pr, le32_to_cpu(bfs->sectorsize));
291
0
  blkid_probe_set_block_size(pr, le32_to_cpu(bfs->sectorsize));
292
293
0
  uint32_t sectorsize_log = 31 -
294
0
    __builtin_clz(le32_to_cpu(bfs->sectorsize));
295
0
  blkid_probe_set_fslastblock(pr,
296
0
      le64_to_cpu(bfs->total_bytes) >> sectorsize_log);
297
298
  /* The size is calculated without the RAID factor. It could not be
299
   * obtained from the superblock as it is property of device tree.
300
   *  Without the factor we would show fs size with the redundant data. The
301
   * acquisition of the factor will require additional parsing of btrfs
302
   * tree.
303
   */
304
0
  blkid_probe_set_fssize(pr, le64_to_cpu(bfs->total_bytes));
305
306
0
  return 0;
307
0
}
308
309
const struct blkid_idinfo btrfs_idinfo =
310
{
311
  .name   = "btrfs",
312
  .usage    = BLKID_USAGE_FILESYSTEM,
313
  .probefunc  = probe_btrfs,
314
  .minsz    = 1024 * 1024,
315
  .magics   =
316
  {
317
    { .magic = "_BHRfS_M", .len = 8, .sboff = 0x40, .kboff = 64 },
318
    /* For zoned btrfs */
319
    { .magic = "_BHRfS_M", .len = 8, .sboff = 0x40,
320
      .is_zoned = 1, .zonenum = 0, .kboff_inzone = 0 },
321
    { .magic = "_BHRfS_M", .len = 8, .sboff = 0x40,
322
      .is_zoned = 1, .zonenum = 1, .kboff_inzone = 0 },
323
    { NULL }
324
  }
325
};
326