Andrew Kelley - OpenZFS Bug Ported to Zig (2025 Jul 14)

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.


Thanks for reading my blog post.

Home Page | RSS feed | Sponsor the Zig Software Foundation