446 lines
12 KiB
C
446 lines
12 KiB
C
#include <libcbd.h>
|
|
|
|
#include <cbdutil.h>
|
|
|
|
#include <lz4.h>
|
|
/* XXX */
|
|
typedef off_t off64_t;
|
|
#include <zlib.h>
|
|
|
|
struct check_state
|
|
{
|
|
int fd;
|
|
bool check_lblk_data;
|
|
bool clean;
|
|
u64 pblk_used;
|
|
u64 lblk_used;
|
|
u8** pbatv;
|
|
|
|
u8* compress_buf;
|
|
u8* lz4_workmem;
|
|
z_stream zlib_dstream;
|
|
};
|
|
|
|
static void
|
|
pblk_read(int fd, u64 pblk, u32 count, u8* data)
|
|
{
|
|
off_t pos;
|
|
size_t remain;
|
|
ssize_t ret;
|
|
|
|
pos = lseek(fd, pblk * PBLK_SIZE, SEEK_SET);
|
|
if (pos == (off_t)-1) {
|
|
error("Failed to seek\n");
|
|
}
|
|
|
|
remain = count * PBLK_SIZE;
|
|
while (remain) {
|
|
ret = read(fd, data, remain);
|
|
if (ret <= 0) {
|
|
error("Failed to read\n");
|
|
}
|
|
remain -= ret;
|
|
data += ret;
|
|
}
|
|
}
|
|
|
|
static void
|
|
pblk_write(int fd, u64 pblk, u32 count, const u8* data)
|
|
{
|
|
off_t pos;
|
|
size_t remain;
|
|
ssize_t ret;
|
|
|
|
pos = lseek(fd, pblk * PBLK_SIZE, SEEK_SET);
|
|
if (pos == (off_t)-1) {
|
|
error("Failed to seek\n");
|
|
}
|
|
|
|
remain = count * PBLK_SIZE;
|
|
while (remain) {
|
|
ret = write(fd, data, remain);
|
|
if (ret <= 0) {
|
|
error("Failed to write\n");
|
|
}
|
|
remain -= ret;
|
|
data += ret;
|
|
}
|
|
}
|
|
|
|
static void
|
|
pbat_read(int fd, const struct cbd_params* params, u32 zone, u8* data)
|
|
{
|
|
pblk_read(fd, pbat_off(params, zone), pbat_len(params), data);
|
|
}
|
|
|
|
static void
|
|
pbat_write(int fd, const struct cbd_params* params, u32 zone, const u8* data)
|
|
{
|
|
pblk_write(fd, pbat_off(params, zone), pbat_len(params), data);
|
|
}
|
|
|
|
static void
|
|
lbat_read(int fd, const struct cbd_params* params, u32 zone, u8* data)
|
|
{
|
|
pblk_read(fd, lbat_off(params, zone), lbat_len(params), data);
|
|
}
|
|
|
|
static void
|
|
lbat_write(int fd, const struct cbd_params* params, u32 zone, const u8* data)
|
|
{
|
|
pblk_write(fd, lbat_off(params, zone), lbat_len(params), data);
|
|
}
|
|
|
|
static void
|
|
check_header(const struct cbd_header* header)
|
|
{
|
|
if (memcmp(header->magic, CBD_MAGIC, sizeof(header->magic))) {
|
|
error("Bad magic\n");
|
|
}
|
|
if (header->version_major != CBD_VERSION_MAJOR) {
|
|
error("Bad major version\n");
|
|
}
|
|
if (header->version_minor != CBD_VERSION_MINOR) {
|
|
error("Bad major version\n");
|
|
}
|
|
if (header->params.algorithm == CBD_ALG_NONE ||
|
|
header->params.algorithm >= CBD_ALG_MAX) {
|
|
error("Bad algorithm\n");
|
|
}
|
|
if (header->params.compression > 9) {
|
|
error("Bad compression\n");
|
|
}
|
|
if (header->params.lblk_shift < LBLK_SHIFT_MIN ||
|
|
header->params.lblk_shift >= LBLK_SHIFT_MAX) {
|
|
error("Bad logical block shift\n");
|
|
}
|
|
}
|
|
|
|
static bool
|
|
check_decompress_lz4(struct check_state* state, const struct cbd_params* params, u8* buf, u32 clen)
|
|
{
|
|
int ret;
|
|
u32 dlen = PBLK_SIZE * lblk_per_pblk(params);
|
|
|
|
ret = LZ4_decompress_safe((const char*)buf, (char*)state->compress_buf, clen, dlen);
|
|
if (ret != dlen) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
check_decompress_zlib(struct check_state* state, const struct cbd_params* params, u8* buf, u32 clen)
|
|
{
|
|
int ret;
|
|
z_stream* stream;
|
|
u32 dlen = PBLK_SIZE * lblk_per_pblk(params);
|
|
|
|
stream = &state->zlib_dstream;
|
|
ret = inflateReset(stream);
|
|
assert(ret == Z_OK);
|
|
stream->next_in = buf;
|
|
stream->avail_in = clen;
|
|
stream->next_out = state->compress_buf;
|
|
stream->avail_out = dlen;
|
|
ret = inflate(stream, Z_SYNC_FLUSH);
|
|
if (ret == Z_OK && !stream->avail_in && stream->avail_out) {
|
|
u8 zerostuff = 0;
|
|
stream->next_in = &zerostuff;
|
|
stream->avail_in = 1;
|
|
ret = inflate(stream, Z_FINISH);
|
|
}
|
|
if (ret != Z_STREAM_END || stream->total_out != dlen) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
check_decompress(struct check_state* state, const struct cbd_params* params, u8* buf, u32 clen)
|
|
{
|
|
bool ret = false;
|
|
|
|
switch (params->algorithm) {
|
|
case CBD_ALG_LZ4:
|
|
ret = check_decompress_lz4(state, params, buf, clen);
|
|
break;
|
|
case CBD_ALG_ZLIB:
|
|
ret = check_decompress_zlib(state, params, buf, clen);
|
|
break;
|
|
default:
|
|
ret = false;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool
|
|
check_lblk_data(struct check_state* state,
|
|
const struct cbd_params* params,
|
|
u64 lblk, u8* lba)
|
|
{
|
|
bool ret = false;
|
|
u8* data;
|
|
u8* buf;
|
|
u32 len;
|
|
u32 n_alloc;
|
|
u32 n;
|
|
u64 pblk;
|
|
|
|
data = calloc(PBLK_SIZE, lblk_per_pblk(params));
|
|
buf = lba;
|
|
len = lba_len_get(params, buf);
|
|
if (len == 0 || len == CBD_UNCOMPRESSED) {
|
|
return true;
|
|
}
|
|
n_alloc = DIV_ROUND_UP(len, PBLK_SIZE);
|
|
for (n = 0; n < n_alloc; ++n) {
|
|
pblk = lba_pblk_get(params, buf, n);
|
|
pblk_read(state->fd, pblk, 1, data + n * PBLK_SIZE);
|
|
}
|
|
ret = check_decompress(state, params, data, len);
|
|
free(data);
|
|
if (!ret) {
|
|
if (ask_user_bool("lblk %u: failed to decompress. Clear?", lblk)) {
|
|
memset(lba, 0, lba_len(params));
|
|
return true;
|
|
}
|
|
state->clean = false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
check_lblk_alloc(struct check_state* state,
|
|
const struct cbd_params* params,
|
|
u64 lblk, u8* lba)
|
|
{
|
|
u8* buf = lba;
|
|
u32 len;
|
|
u32 n_alloc;
|
|
u32 n;
|
|
u64 pblk;
|
|
u32 pblk_zone;
|
|
u32 pblk_off;
|
|
|
|
len = lba_len_get(params, buf);
|
|
if (!len) {
|
|
verbose(2, " lblk[%u]: EMPTY\n", lblk);
|
|
return false;
|
|
}
|
|
if (len == CBD_UNCOMPRESSED) {
|
|
verbose(2, " lblk[%u]: UNCOMPRESSED\n", lblk);
|
|
}
|
|
else {
|
|
verbose(2, " lblk[%u]: len=%u\n", lblk, len);
|
|
}
|
|
if (len > PBLK_SIZE * lblk_per_pblk(params)) {
|
|
if (ask_user_bool("lblk %u: length %u out of bounds. Clear?", lblk, len)) {
|
|
memset(lba, 0, lba_len(params));
|
|
return true;
|
|
}
|
|
state->clean = false;
|
|
return false;
|
|
}
|
|
n_alloc = (len == CBD_UNCOMPRESSED) ?
|
|
lblk_per_pblk(params) :
|
|
DIV_ROUND_UP(len, PBLK_SIZE);
|
|
for (n = 0; n < n_alloc; ++n) {
|
|
pblk = lba_pblk_get(params, buf, n);
|
|
if (pblk < CBD_HEADER_BLOCKS) {
|
|
verbose(2, " [%u] :E: Alloc in header: %lu\n", n, pblk);
|
|
if (ask_user_bool("lblk %u: alloc %u in header. Clear?", lblk, n)) {
|
|
memset(lba, 0, lba_len(params));
|
|
return true;
|
|
}
|
|
state->clean = false;
|
|
continue;
|
|
}
|
|
pblk_zone = zone_for_pblk(params, pblk);
|
|
if (pblk_zone == ZONE_NONE || pblk_zone >= params->nr_zones) {
|
|
verbose(2, " [%u] :E: Alloc beyond end: %lu\n", n, pblk);
|
|
if (ask_user_bool("lblk %u: alloc %u beyond end. Clear?", lblk, n)) {
|
|
memset(lba, 0, lba_len(params));
|
|
return true;
|
|
}
|
|
state->clean = false;
|
|
continue;
|
|
}
|
|
if (pblk < zone_data_off(params, pblk_zone)) {
|
|
verbose(2, " [%u] :E: Alloc in metadata: %lu\n", n, pblk);
|
|
if (ask_user_bool("lblk %u alloc in medatada. Clear?", lblk)) {
|
|
memset(lba, 0, lba_len(params));
|
|
return true;
|
|
}
|
|
state->clean = false;
|
|
continue;
|
|
}
|
|
pblk_off = pblk - zone_data_off(params, pblk_zone);
|
|
verbose(3, " [%u] pblk=%lu\n", n, (unsigned long)pblk);
|
|
if (cbd_bitmap_isset(state->pbatv[pblk_zone], pblk_off)) {
|
|
verbose(2, " [%u] :E: Duplicate allocation for pblk %lu\n", n, (unsigned long)pblk);
|
|
if (ask_user_bool("lblk %u duplicate alloc for pblk %lu. Clear?", lblk, pblk)) {
|
|
memset(lba, 0, lba_len(params));
|
|
return true;
|
|
}
|
|
state->clean = false;
|
|
continue;
|
|
}
|
|
cbd_bitmap_set(state->pbatv[pblk_zone], pblk_off);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void
|
|
check_lbat(struct check_state* state, const struct cbd_params* params)
|
|
{
|
|
u32 zone;
|
|
|
|
for (zone = 0; zone < params->nr_zones; ++zone) {
|
|
u8* lbat = calloc(PBLK_SIZE, lbat_len(params));
|
|
bool zone_empty = true;
|
|
bool changed = false;
|
|
u32 n;
|
|
|
|
lbat_read(state->fd, params, zone, lbat);
|
|
|
|
verbose(2, "Zone %u: lbat=[%lu..%lu] alloc=[%lu .. %lu]\n",
|
|
(unsigned int)zone,
|
|
(unsigned long)zone_off(params, zone),
|
|
(unsigned long)(zone_data_off(params, zone) - 1),
|
|
(unsigned long)zone_data_off(params, zone),
|
|
(unsigned long)zone_off(params, zone + 1));
|
|
for (n = 0; n < params->lblk_per_zone; ++n) {
|
|
u8* buf = lbat + n * lba_len(params);
|
|
if (lba_len_get(params, buf) != 0) {
|
|
zone_empty = false;
|
|
break;
|
|
}
|
|
}
|
|
if (zone_empty) {
|
|
verbose(2, " [empty]\n");
|
|
continue;
|
|
}
|
|
for (n = 0; n < params->lblk_per_zone; ++n) {
|
|
u64 lblk = zone * params->lblk_per_zone + n;
|
|
u8* buf = lbat + n * lba_len(params);
|
|
u32 len;
|
|
if (check_lblk_alloc(state, params, lblk, buf)) {
|
|
changed = true;
|
|
}
|
|
if (state->check_lblk_data) {
|
|
if (check_lblk_data(state, params, lblk, buf)) {
|
|
changed = true;
|
|
}
|
|
}
|
|
len = lba_len_get(params, buf);
|
|
if (len != 0) {
|
|
++state->lblk_used;
|
|
state->pblk_used += DIV_ROUND_UP(len, PBLK_SIZE);
|
|
}
|
|
}
|
|
if (changed) {
|
|
lbat_write(state->fd, params, zone, lbat);
|
|
}
|
|
free(lbat);
|
|
}
|
|
}
|
|
|
|
static void
|
|
check_pbat(struct check_state* state, const struct cbd_params* params)
|
|
{
|
|
u32 zone;
|
|
u8* pbat;
|
|
|
|
pbat = malloc(PBLK_SIZE * pbat_len(params));
|
|
for (zone = 0; zone < params->nr_zones; ++zone) {
|
|
bool changed = false;
|
|
pbat_read(state->fd, params, zone, pbat);
|
|
if (memcmp(pbat, state->pbatv[zone], PBLK_SIZE * pbat_len(params)) != 0) {
|
|
if (ask_user_bool("zone %u has incorrect pbat. Fix?", zone)) {
|
|
memcpy(pbat, state->pbatv[zone], PBLK_SIZE * pbat_len(params));
|
|
changed = true;
|
|
}
|
|
else {
|
|
state->clean = false;
|
|
}
|
|
if (changed) {
|
|
pbat_write(state->fd, params, zone, state->pbatv[zone]);
|
|
}
|
|
}
|
|
}
|
|
free(pbat);
|
|
}
|
|
|
|
int
|
|
cbd_check(const char* dev,
|
|
bool force,
|
|
tristate_t auto_response,
|
|
bool full_check)
|
|
{
|
|
struct check_state state;
|
|
struct cbd_header header;
|
|
uint8_t pblkbuf[PBLK_SIZE];
|
|
u32 n;
|
|
|
|
memset(&state, 0, sizeof(state));
|
|
state.fd = open(dev, O_RDWR);
|
|
if (state.fd < 0) {
|
|
error("Cannot open device\n");
|
|
}
|
|
state.check_lblk_data = full_check;
|
|
state.clean = true;
|
|
|
|
verbose(1, "Reading header\n");
|
|
pblk_read(state.fd, 0, 1, pblkbuf);
|
|
cbd_header_get(pblkbuf, &header);
|
|
verbose(1, "Checking header\n");
|
|
check_header(&header);
|
|
if (!force && !(header.params.flags & CBD_FLAG_DIRTY)) {
|
|
printf("%s: clean\n", dev);
|
|
close(state.fd);
|
|
return 0;
|
|
}
|
|
|
|
state.pbatv = calloc(header.params.nr_zones, sizeof(u8*));
|
|
for (n = 0; n < header.params.nr_zones; ++n) {
|
|
state.pbatv[n] = calloc(PBLK_SIZE, pbat_len(&header.params));
|
|
}
|
|
|
|
verbose(1, "Checking lbat\n");
|
|
check_lbat(&state, &header.params);
|
|
verbose(1, "Checking pbat\n");
|
|
check_pbat(&state, &header.params);
|
|
|
|
for (n = 0; n < header.params.nr_zones; ++n) {
|
|
free(state.pbatv[n]);
|
|
}
|
|
free(state.pbatv);
|
|
|
|
if (state.clean) {
|
|
if (state.pblk_used != header.stats.pblk_used) {
|
|
verbose(1, "pblk used incorrect (%lu expected, %lu found), fixing\n",
|
|
(unsigned long)state.pblk_used,
|
|
(unsigned long)header.stats.pblk_used);
|
|
header.stats.pblk_used = state.pblk_used;
|
|
}
|
|
if (state.lblk_used != header.stats.lblk_used) {
|
|
verbose(1, "lblk used incorrect (%lu expected, %lu found), fixing\n",
|
|
(unsigned long)state.lblk_used,
|
|
(unsigned long)header.stats.lblk_used);
|
|
header.stats.lblk_used = state.lblk_used;
|
|
}
|
|
header.params.flags &= ~(CBD_FLAG_ERROR | CBD_FLAG_DIRTY);
|
|
cbd_header_put(pblkbuf, &header);
|
|
pblk_write(state.fd, 0, 1, pblkbuf);
|
|
}
|
|
close(state.fd);
|
|
|
|
return 0;
|
|
}
|