OpenZFS Bug Ported to Zig
Someone on IRC shared this link with me: An (almost) catastrophic OpenZFS bug and the humans that made it (and Rust is here too)
They wanted to know, would Zig catch this?
I went through the trouble of porting the code snippet to Zig, so I thought I'd go the next step and turn it into a small blog post.
Original snippet, in C:
/*
* This code converts an asize into the largest psize that can safely be written
* to an allocation of that size for this vdev.
*
* Note that this function will not take into account the effect of gang
* headers, which also modify the ASIZE of the DVAs. It is purely a reverse of
* the psize_to_asize function.
*/
static uint64_t
vdev_raidz_asize_to_psize(vdev_t *vd, uint64_t asize, uint64_t txg)
{
vdev_raidz_t *vdrz = vd->vdev_tsd;
uint64_t psize;
uint64_t ashift = vd->vdev_top->vdev_ashift;
uint64_t cols = vdrz->vd_original_width;
uint64_t nparity = vdrz->vd_nparity;
cols = vdev_raidz_get_logical_width(vdrz, txg);
ASSERT0(asize % (1 << ashift));
psize = (asize >> ashift);
psize -= nparity * DIV_ROUND_UP(psize, cols);
psize <<= ashift;
return (asize);
}
The blog post author encourages us to try to spot the fail before the answer is revealed.
I couldn't do it before getting bored, so I ported the code to Zig instead:
const std = @import("std");
const divCeil = std.math.divCeil;
const assert = std.debug.assert;
const vdev_t = @import("the_rest_of_the_software.zig").vdev_t;
/// This code converts an asize into the largest psize that can safely be written
/// to an allocation of that size for this vdev.
///
/// Note that this function will not take into account the effect of gang
/// headers, which also modify the ASIZE of the DVAs. It is purely a reverse of
/// the psize_to_asize function.
fn vdev_raidz_asize_to_psize(vd: *vdev_t, asize: u64, txg: u64) u64 {
const vdrz = vd.vdev_tsd;
const ashift = vd.vdev_top.vdev_ashift;
const cols = vdrz.vd_original_width;
const nparity = vdrz.vd_nparity;
const cols = vdrz.get_logical_width(txg);
assert(asize % (1 << ashift));
const asize_shifted = (asize >> ashift);
const parity_adjusted = asize_shifted - nparity * (divCeil(asize_shifted, cols) catch unreachable);
const psize = parity_adjusted << ashift;
return asize;
}
Let's start with the code formatting tool alone, zig fmt
:
andy@bark ~/tmp> zig fmt bug.zig --ast-check
bug.zig:18:11: error: redeclaration of local constant 'cols'
const cols = vdrz.get_logical_width(txg);
^~~~
bug.zig:15:11: note: previous declaration here
const cols = vdrz.vd_original_width;
^~~~
Hmm, alright. That's definitely strange. The original blog post didn't notice that the
initialization value of cols
is dead code. That doesn't look like a logic
error though. Let's fix it and try again:
@@ -12,7 +12,6 @@
fn vdev_raidz_asize_to_psize(vd: *vdev_t, asize: u64, txg: u64) u64 {
const vdrz = vd.vdev_tsd;
const ashift = vd.vdev_top.vdev_ashift;
- const cols = vdrz.vd_original_width;
const nparity = vdrz.vd_nparity;
const cols = vdrz.get_logical_width(txg);
andy@bark ~/tmp> zig fmt bug.zig --ast-check
bug.zig:23:11: error: unused local constant
const psize = parity_adjusted << ashift;
^~~~~
Bingo. We didn't even have to use the type checker.