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:
[](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:
[](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:
[](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`:
[](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:
[](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\)\.