NetBSD Playing with disklabels

Lobsters Hottest News

Summary

The article provides a hands-on exploration of BSD disklabels on NetBSD 11, detailing their format, documentation sources, and structure through hex editing and C programming examples.

<p><a href="https://lobste.rs/s/qknavp/netbsd_playing_with_disklabels">Comments</a></p>
Original Article
View Cached Full Text

Cached at: 09/26/26, 03:20 AM

# NetBSD: Playing with disklabels Source: [https://movq.de/blog/postings/2026-09-25/0/POSTING-en.html](https://movq.de/blog/postings/2026-09-25/0/POSTING-en.html) [blog](https://movq.de/blog/)\-[git](https://movq.de/git/)\-[desktop](https://movq.de/desktop/)\-[contact](https://movq.de/contact.html) --- 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.](https://movq.de/blog/postings/2026-09-25/0/t/bine-initial.png.jpg)](https://movq.de/blog/postings/2026-09-25/0/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?](https://movq.de/blog/postings/2026-09-25/0/POSTING-en.html#where-to-find-documentation) - [Part 0: Where is the disklabel?](https://movq.de/blog/postings/2026-09-25/0/POSTING-en.html#part-0-where-is-the-disklabel) - [Part 1: "Disk metadata"](https://movq.de/blog/postings/2026-09-25/0/POSTING-en.html#part-1-disk-metadata) - [Part 2: The partition table](https://movq.de/blog/postings/2026-09-25/0/POSTING-en.html#part-2-the-partition-table) - [Verifying the checksum](https://movq.de/blog/postings/2026-09-25/0/POSTING-en.html#verifying-the-checksum) - [Exercise: Create some random partition in the hex editor](https://movq.de/blog/postings/2026-09-25/0/POSTING-en.html#exercise-create-some-random-partition-in-the-hex-editor) - [Conclusion](https://movq.de/blog/postings/2026-09-25/0/POSTING-en.html#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\. 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.](https://movq.de/blog/postings/2026-09-25/0/t/disklabel-metadata.png.jpg)](https://movq.de/blog/postings/2026-09-25/0/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.](https://movq.de/blog/postings/2026-09-25/0/t/disklabel-partitions-overview.png.jpg)](https://movq.de/blog/postings/2026-09-25/0/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.](https://movq.de/blog/postings/2026-09-25/0/t/disklabel-partition-a.png.jpg)](https://movq.de/blog/postings/2026-09-25/0/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 = and(xor(cksum, v), 0xFFFF) } 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 = and(xor(cksum, v), 0xFFFF) } 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](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.](https://movq.de/blog/postings/2026-09-25/0/t/bine-edit.png.jpg)](https://movq.de/blog/postings/2026-09-25/0/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\)\.

Similar Articles

NetBSD 11.0 released

Lobsters Hottest

NetBSD 11.0 has been released after a long delay, with installation images and notes available. The release acknowledges open security issues and plans a follow-up 11.1 within two months.

Research carried out using NetBSD

Hacker News Top

This NetBSD gallery page highlights various academic and industrial research projects utilizing the operating system, including NASA's satellite TCP experiments, the KAME IPv6/IPsec project, and high-speed networking demonstrations.

NetBSD GSoC 2026 Improving RAIDframe

Hacker News Top

This article reports on Google Summer of Code 2026 work to improve RAIDframe in NetBSD, including implementing N-way RAID 1 for multiple disk mirroring and adding RAID scrubbing support.

FreeBSD Ate My RAM

Hacker News Top

An article explaining why FreeBSD appears to use a lot of RAM, attributing it to disk caching and virtual memory management, similar to Linux's 'ate my RAM' phenomenon.