util-linux/libblkid/src/superblocks/btrfs.c

260 lines
6.2 KiB
C

/*
* Copyright (C) 2009 Karel Zak <kzak@redhat.com>
*
* This file may be redistributed under the terms of the
* GNU Lesser General Public License.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
#include <inttypes.h>
#ifdef HAVE_LINUX_BLKZONED_H
#include <linux/blkzoned.h>
#endif
#include "superblocks.h"
struct btrfs_super_block {
uint8_t csum[32];
uint8_t fsid[16];
uint64_t bytenr;
uint64_t flags;
uint8_t magic[8];
uint64_t generation;
uint64_t root;
uint64_t chunk_root;
uint64_t log_root;
uint64_t log_root_transid;
uint64_t total_bytes;
uint64_t bytes_used;
uint64_t root_dir_objectid;
uint64_t num_devices;
uint32_t sectorsize;
uint32_t nodesize;
uint32_t leafsize;
uint32_t stripesize;
uint32_t sys_chunk_array_size;
uint64_t chunk_root_generation;
uint64_t compat_flags;
uint64_t compat_ro_flags;
uint64_t incompat_flags;
uint16_t csum_type;
uint8_t root_level;
uint8_t chunk_root_level;
uint8_t log_root_level;
struct btrfs_dev_item {
uint64_t devid;
uint64_t total_bytes;
uint64_t bytes_used;
uint32_t io_align;
uint32_t io_width;
uint32_t sector_size;
uint64_t type;
uint64_t generation;
uint64_t start_offset;
uint32_t dev_group;
uint8_t seek_speed;
uint8_t bandwidth;
uint8_t uuid[16];
uint8_t fsid[16];
} __attribute__ ((__packed__)) dev_item;
uint8_t label[256];
} __attribute__ ((__packed__));
#define BTRFS_SUPER_INFO_SIZE 4096
/* Number of superblock log zones */
#define BTRFS_NR_SB_LOG_ZONES 2
/* Introduce some macros and types to unify the code with kernel side */
#define SECTOR_SHIFT 9
typedef uint64_t sector_t;
#ifdef HAVE_LINUX_BLKZONED_H
static int sb_write_pointer(blkid_probe pr, struct blk_zone *zones, uint64_t *wp_ret)
{
bool empty[BTRFS_NR_SB_LOG_ZONES];
bool full[BTRFS_NR_SB_LOG_ZONES];
sector_t sector;
assert(zones[0].type != BLK_ZONE_TYPE_CONVENTIONAL &&
zones[1].type != BLK_ZONE_TYPE_CONVENTIONAL);
empty[0] = zones[0].cond == BLK_ZONE_COND_EMPTY;
empty[1] = zones[1].cond == BLK_ZONE_COND_EMPTY;
full[0] = zones[0].cond == BLK_ZONE_COND_FULL;
full[1] = zones[1].cond == BLK_ZONE_COND_FULL;
/*
* Possible states of log buffer zones
*
* Empty[0] In use[0] Full[0]
* Empty[1] * x 0
* In use[1] 0 x 0
* Full[1] 1 1 C
*
* Log position:
* *: Special case, no superblock is written
* 0: Use write pointer of zones[0]
* 1: Use write pointer of zones[1]
* C: Compare super blocks from zones[0] and zones[1], use the latest
* one determined by generation
* x: Invalid state
*/
if (empty[0] && empty[1]) {
/* Special case to distinguish no superblock to read */
*wp_ret = zones[0].start << SECTOR_SHIFT;
return -ENOENT;
} else if (full[0] && full[1]) {
/* Compare two super blocks */
struct btrfs_super_block *super[BTRFS_NR_SB_LOG_ZONES];
int i;
for (i = 0; i < BTRFS_NR_SB_LOG_ZONES; i++) {
uint64_t bytenr;
bytenr = ((zones[i].start + zones[i].len)
<< SECTOR_SHIFT) - BTRFS_SUPER_INFO_SIZE;
super[i] = (struct btrfs_super_block *)
blkid_probe_get_buffer(pr, bytenr, BTRFS_SUPER_INFO_SIZE);
if (!super[i])
return -EIO;
DBG(LOWPROBE, ul_debug("(btrfs) checking #%d zone "
"[start=%" PRIu64", len=%" PRIu64", sb-offset=%" PRIu64"]",
i, (uint64_t) zones[i].start,
(uint64_t) zones[i].len, bytenr));
}
if (super[0]->generation > super[1]->generation)
sector = zones[1].start;
else
sector = zones[0].start;
} else if (!full[0] && (empty[1] || full[1])) {
sector = zones[0].wp;
} else if (full[0]) {
sector = zones[1].wp;
} else {
return -EUCLEAN;
}
*wp_ret = sector << SECTOR_SHIFT;
DBG(LOWPROBE, ul_debug("(btrfs) write pointer: %" PRIu64" sector", sector));
return 0;
}
static int sb_log_offset(blkid_probe pr, uint64_t *bytenr_ret)
{
uint32_t zone_num = 0;
uint32_t zone_size_sector;
struct blk_zone_report *rep;
struct blk_zone *zones;
int ret;
int i;
uint64_t wp;
zone_size_sector = pr->zone_size >> SECTOR_SHIFT;
rep = blkdev_get_zonereport(pr->fd, zone_num * zone_size_sector, 2);
if (!rep) {
ret = -errno;
goto out;
}
zones = (struct blk_zone *)(rep + 1);
/*
* Use the head of the first conventional zone, if the zones
* contain one.
*/
for (i = 0; i < BTRFS_NR_SB_LOG_ZONES; i++) {
if (zones[i].type == BLK_ZONE_TYPE_CONVENTIONAL) {
DBG(LOWPROBE, ul_debug("(btrfs) checking conventional zone"));
*bytenr_ret = zones[i].start << SECTOR_SHIFT;
ret = 0;
goto out;
}
}
ret = sb_write_pointer(pr, zones, &wp);
if (ret != -ENOENT && ret) {
ret = 1;
goto out;
}
if (ret != -ENOENT) {
if (wp == zones[0].start << SECTOR_SHIFT)
wp = (zones[1].start + zones[1].len) << SECTOR_SHIFT;
wp -= BTRFS_SUPER_INFO_SIZE;
}
*bytenr_ret = wp;
ret = 0;
out:
free(rep);
return ret;
}
#endif
static int probe_btrfs(blkid_probe pr, const struct blkid_idmag *mag)
{
struct btrfs_super_block *bfs;
if (pr->zone_size) {
#ifdef HAVE_LINUX_BLKZONED_H
uint64_t offset = 0;
int ret;
ret = sb_log_offset(pr, &offset);
if (ret)
return ret;
bfs = (struct btrfs_super_block *)
blkid_probe_get_buffer(pr, offset,
sizeof(struct btrfs_super_block));
#else
/* Nothing can be done */
return 1;
#endif
} else {
bfs = blkid_probe_get_sb(pr, mag, struct btrfs_super_block);
}
if (!bfs)
return errno ? -errno : 1;
if (*bfs->label)
blkid_probe_set_label(pr,
(unsigned char *) bfs->label,
sizeof(bfs->label));
blkid_probe_set_uuid(pr, bfs->fsid);
blkid_probe_set_uuid_as(pr, bfs->dev_item.uuid, "UUID_SUB");
blkid_probe_set_block_size(pr, le32_to_cpu(bfs->sectorsize));
return 0;
}
const struct blkid_idinfo btrfs_idinfo =
{
.name = "btrfs",
.usage = BLKID_USAGE_FILESYSTEM,
.probefunc = probe_btrfs,
.minsz = 1024 * 1024,
.magics =
{
{ .magic = "_BHRfS_M", .len = 8, .sboff = 0x40, .kboff = 64 },
/* For zoned btrfs */
{ .magic = "_BHRfS_M", .len = 8, .sboff = 0x40,
.is_zoned = 1, .zonenum = 0, .kboff_inzone = 0 },
{ .magic = "_BHRfS_M", .len = 8, .sboff = 0x40,
.is_zoned = 1, .zonenum = 1, .kboff_inzone = 0 },
{ NULL }
}
};