---
title: "NetBSD: Playing with disklabels"
slug: netbsd-playing-with-disklabels
url: https://listedarticles.com/articles/netbsd-playing-with-disklabels
canonical_url: https://movq.de/blog/postings/2026-09-25/0/POSTING-en.html
content_type: tutorial
language: en
published_at: 2026-09-25T12:00:00.000Z
updated_at: 2026-09-26T21:13:28.272Z
author: "movq"
author_url: https://movq.de/
authored_by: human
publisher: "movq.de"
publisher_url: https://movq.de/
topics: ["Linux", "Systems Programming", "Open Source", "Tutorials"]
license: all-rights-reserved
word_count: 1633
reading_minutes: 7
citation: "movq, movq.de. \"NetBSD: Playing with disklabels.\" 25 Sept 2026. https://movq.de/blog/postings/2026-09-25/0/POSTING-en.html (all-rights-reserved)"
# The full text follows. The web page shows an extract and sends readers
# to the source above; quote the citation and link the canonical URL.
---

# NetBSD: Playing with disklabels

> A hands-on walkthrough of NetBSD disklabels for someone coming from Linux/DOS: how labels are structured, how to inspect and edit them safely, and the gotchas when partitioning disks the BSD way.

[blog](</blog/>) \- [git](</git/>) \- [desktop](</desktop/>) \- [contact](</contact.html>)

* * *

# NetBSD: Playing with disklabels

2026-09-25

Coming from DOS and Linux (and having largely ignored this on my OpenBSD systems -- yeah, shame on me), I'm not very familiar with the BSD disklabels. So let's have a look.

I'm not entirely certain if all BSDs use the exact same format and as far as I know there are differences among architectures, so let's be specific here: I'm looking at NetBSD 11 on x86_64.

I'll be running everything in a VM, so I can easily swap disks and inspect them.

[To avoid conflicts with MBR systems](<https://en.wikipedia.org/wiki/BSD_disklabel#Where_disklabels_are_stored>), disklabels are stored at a different location. In my test VM, it can be found at the second sector:

[![bine-initial.png: Hex editor screenshot: At offset 0x200, we can see the disklabel magic number.](t/bine-initial.png.jpg)](<bine-initial.png>)

I was primarily interested in the format of this disklabel: What do the individual bytes mean? And where is that specificed/documented?

* * *

Table of contents:

  * Where to find documentation?
  * Part 0: Where is the disklabel?
  * Part 1: "Disk metadata"
  * Part 2: The partition table
  * Verifying the checksum
  * Exercise: Create some random partition in the hex editor
  * Conclusion

* * *

## Where to find documentation?

This question is easy to answer. Look at `man diskabel` (as you would "instinctively" do, because that's the tool to manipulate those labels), there's `disklabel(5)` in the `SEE ALSO` section: Section 5 describes file formats, so that's what we want to look at (`man 5 disklabel`).

For most of this post, though, I looked directly at `/usr/include/sys/disklabel.h` instead of the manual page.

## Part 0: Where is the disklabel?

First things first. The manual page tells you where to find the disklabel: `getlabelsector()` and `getlabeloffset()` answer that. So let's write a little C program to see what they return.
    
    
    netbsd# cat test.c
    #include <stdio.h>
    #include <util.h>
    
    int
    main()
    {
        printf("getlabelsector() = %d\n", getlabelsector());
        printf("getlabeloffset() = %ld\n", getlabeloffset());
    
        return 0;
    }
    
    netbsd# cc -Wall -Wextra -o test test.c -lutil
    

Output:
    
    
    netbsd# ./test
    getlabelsector() = 1
    getlabeloffset() = 0
    

So sector 1 (byte offset 512 in my case) is correct. It's not just some random data in the screenshot above but that _is_ the disklabel.

## Part 1: "Disk metadata"

The disklabel begins with a bunch of "metadata" about the disk, then the actual partition table follows at the end. Let's look at this metadata first.

A bit hard to illustrate this. Here's an overview first (open it in a new tab next to this text), and then we'll go through this one by one:

[![disklabel-metadata.png: Hex dump of my disklabel. Colors indicate the individual fields.](t/disklabel-metadata.png.jpg)](<disklabel-metadata.png>)

All of this is _little endian_ on my Intel machine.

Generic disk/drive information:

  * `5745 5682`: `d_magic`, Magic Number
  * `0f00`: `d_type`, drive type = `0x0f` = "logical disk"
  * `0000`: `d_subtype`, specific to `d_type`
  * `6c64...0000` / `"ld1"`: `d_typename`
  * `4d79...0000` / `"My Cool Disk"`: `d_packname`, user enters this name

`disklabel.h` contains a table of the possible values for `d_type`.

Geometry/size:

  * `0002 0000`: `d_secsize`, 512 bytes per sector
  * `3f00 0000`: `d_nsectors`, 63 sectors per track
  * `1000 0000`: `d_ntracks`, 16 tracks per cylinder
  * `0401 0000`: `d_ncylinders`, 260 cylinders per unit
  * `f003 0000`: `d_secpercyl`, 1008 sectors per cylinder
  * `0000 0400`: `d_secperunit`, 262144 sectors per unit (128 MiB)
  * `0000`: `d_sparespertrack`, no spare sectors per track
  * `0000`: `d_sparespercyl`, no spare sectors per cylinder
  * `0000 0000`: `d_acylinders`, "alternative cylinders" (?)

Hardware parameters and timings, probably very irrelevant on x86_64:

  * `100e`: `d_rpm`, 3600
  * `0100`: `d_interleave`
  * `0000`: `d_trackskew`
  * `0000`: `d_cylskew`
  * `0000 0000`: `d_headswitch`, head switch time in microseconds
  * `0000 0000`: `d_trkseek`, track-to-track seek time in microseconds
  * `0000 0000`: `d_flags`, "generic flags" :-)
  * 5x `0000 0000`: `d_drivedata`, drive-type specific
  * 5x `0000 0000`: `d_space`, reserved

End of this first part:

  * `5745 5682`: `d_magic2`, Magic Number
  * `c61d`: `d_checksum`
  * `0400`: `d_npartitions`, how many partitions follow in the array below
  * `0020 0000`: `d_bbsize`, size of boot area at sn0 in bytes
  * `0020 0000`: `d_sbsize`, max size of fs superblock in bytes

These are the values that I determined by manually reading the hex dump and comparing it with `struct disklabel` in the header file. As far as I can tell, it matches the output of the `diskabel` tool:
    
    
    netbsd# disklabel ld1
    # /dev/rld1:
    type: ld
    disk: ld1
    label: My Cool Disk
    flags:
    bytes/sector: 512
    sectors/track: 63
    tracks/cylinder: 16
    sectors/cylinder: 1008
    cylinders: 260
    total sectors: 262144
    rpm: 3600
    interleave: 1
    trackskew: 0
    cylinderskew: 0
    headswitch: 0           # microseconds
    track-to-track seek: 0  # microseconds
    drivedata: 0
    

A lot of this information feels quite outdated, at least on "modern" x86_64. (But MBR isn't _much_ better in this regard, with all the CHS stuff still going on.) And much of it is indeed unused and just set to 0.

## Part 2: The partition table

`d_npartitions` said there are four partitions. Here's an overview of this table, it immediately follows the "metadata" section:

[![disklabel-partitions-overview.png: Same hex dump as above, but the area where the partition table is located is highlighted.](t/disklabel-partitions-overview.png.jpg)](<disklabel-partitions-overview.png>)

This corresponds to partitions `a`, `b`, `c`, and `d`.

Let's take a closer look at partition `a`:

[![disklabel-partition-a.png: Same hex dump as above, individual fields of partition 'a' highlighted.](t/disklabel-partition-a.png.jpg)](<disklabel-partition-a.png>)

The individual fields:

  * `0000 0400`: `p_size`, 262144 sectors
  * `0000 0000`: `p_offset`, 0 sectors
  * `0000 0000`: `p_fsize`, "filesystem basic fragment size" (?)
  * `07`: `p_fstype`, filesystem type = `0x07` = `4.2BSD / ffs`
  * `00`: `p_frag`, "filesystem fragments per block" (?)
  * `0000`: `p_cpg` or `p_sgs`: UFS or LFS specific

`disklabel.h` contains a table of the possible values for `p_fstype`.

There's really only three important bits of information, I think:

  * Start,
  * end,
  * and type of the partition.

Again, my interpretation matches the output of `disklabel`:
    
    
    netbsd# disklabel ld1
    ...
    4 partitions:
    #        size    offset     fstype [fsize bsize cpg/sgs]
     a:    262144         0     4.2BSD      0     0     0  # (Cyl.      0 -    260*)
     d:    262144         0     unused      0     0        # (Cyl.      0 -    260*)
    

According to [this mail by Martin Husemann](<https://mail-index.netbsd.org/netbsd-users/2018/09/22/msg021452.html>), partition `d` is always there on x86_64 and describes the entire disk. Partition `c` would be the area usable for NetBSD. Why `c` isn't included in the output here, I'm not sure. (Actually, no, I think I know why: Size, offset, and type are all zero, so it gets hidden, I guess. But why didn't `disklabel -I` include `c` when I first created the disklabel?)

## Verifying the checksum

The header file says:
    
    
    uint16_t d_checksum;  /* xor of data incl. partitions */
    

So ... I guess we chunk up all this data into 16-byte pieces and then xor them all together? Let's try:
    
    
    $ dd if=zwei.raw bs=512 count=1 skip=1 status=none |
        od -An -vt x2 -w2 |
        gawk '{ v = strtonum("0x" $1); cksum = xor(cksum, v) } END { printf("%04x\n", cksum) }'
    0000
    

All zeroes. When you think about it: That's confirmation that everything is correct. :-) The actual checksum is one of those 16-byte chunks, so zero is the correct answer.

Exclude the checksum from the data:
    
    
    $ dd if=zwei.raw bs=512 count=1 skip=1 status=none |
        od -An -vt x2 -w2 |
        sed 69d |
        gawk '{ v = strtonum("0x" $1); cksum = xor(cksum, v) } END { printf("%04x\n", cksum) }'
    1dc6
    

There you go, that matches the little-endian `c61d` that we saw in the dump. (Obviously, because ... that's the line that I excluded ... yeah ... but you get the idea.)

I'm not familiar with NetBSD's code base yet, but I think this might be their code to compute the checksum (at least it does the same thing I did):

<https://cvsweb.netbsd.org/bsdweb.cgi/src/sys/lib/libkern/dkcksum.c?rev=1.1.6.2;content-type=text%2Fplain>

## Exercise: Create some random partition in the hex editor

Let's do it the other way around: Given what we know now, open the hex editor and define partition `i`, size 1234 sectors, start at sector 5678, filesystem type ZFS.

Partition `i` is the ninth partition, so its entry should start at byte 788 on the disk:
    
    
    512 + 0x94 + (9 - 1) * 16 = 788
    \_/   \__/   \_____/   |
     |      |       |      \- Each label is 16 bytes in size, see
     |      |       |         disklabel.h.
     |      |       |
     |      |       \- We want to know the *start* of the ninth label.
     |      |
     |      \- Start of first partition entry, see hex dump above.
     |
     \- Start of disklabel on disk.
    

We're going to write these fields in the partition table (this is already _little endian_):

  * `p_size` = `d204 0000` for 1234 sectors
  * `p_offset` = `2e16 0000` for offset 5678
  * `p_fstype` = `21`, according to the table in `disklabel.h`

We also need to update `d_npartitions` to `9`. All the partitions in between remain unused.

I'll be overwriting `d_checksum` with four zeros, so that we can just run the `gawk` snippet above to calculate the new checksum. Lo and behold, it is: `160f` (little endian).

These are the bytes that I touched:

[![bine-edit.png: Screenshot of my hex editor, pink highlights show which bytes I changed.](t/bine-edit.png.jpg)](<bine-edit.png>)

Let's boot the VM again and see what we got:
    
    
    netbsd# disklabel ld1
    # /dev/rld1:
    type: ld
    disk: ld1
    label: My Cool Disk
    flags:
    bytes/sector: 512
    sectors/track: 63
    tracks/cylinder: 16
    sectors/cylinder: 1008
    cylinders: 260
    total sectors: 262144
    rpm: 3600
    interleave: 1
    trackskew: 0
    cylinderskew: 0
    headswitch: 0           # microseconds
    track-to-track seek: 0  # microseconds
    drivedata: 0
    
    9 partitions:
    #        size    offset     fstype [fsize bsize cpg/sgs]
     a:    262144         0     4.2BSD      0     0     0  # (Cyl.      0 -    260*)
     d:    262144         0     unused      0     0        # (Cyl.      0 -    260*)
     i:      1234      5678        ZFS                     # (Cyl.      5*-      6*)
    disklabel: partitions a and i overlap
    

Looks good!

(The partitions obviously overlap, because `a` was already the entire disk.)

## Conclusion

This was a fun afternoon and I indeed feel more comfortable with disklabels now. As usual, I really have to have a _hands-on session_ like this in order to grow familiar with something.

As you can see, I marked some fields with `(?)`, because their meaning isn't entirely clear to me yet. Maybe more on that some other day (or maybe not).
