Skip to content

Commit 11720b0

Browse files
author
Felix "xq" Queißner
committed
Implements MBR partition writing
1 parent 1f99f5e commit 11720b0

File tree

3 files changed

+142
-116
lines changed

3 files changed

+142
-116
lines changed

justfile

+2
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,7 @@ behaviour-test script: install
2828
@mkdir -p {{ join(out, parent_directory(script)) }}
2929
./zig-out/bin/dim --output {{ join(out, without_extension(script) + ".img") }} --script "{{script}}" --size 30M
3030

31+
# TODO(fqu): sfdisk --json .dim-out/tests/part/mbr/basic-single-part-unsized.img
32+
3133
fuzz:
3234
{{zig}} build install test --fuzz --port 35991

src/components/part/MbrPartitionTable.zig

+131-112
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ const dim = @import("../../dim.zig");
77

88
const PartTable = @This();
99

10+
const block_size = 512;
11+
1012
bootloader: ?dim.Content,
1113
disk_id: ?u32,
1214
partitions: [4]?Partition,
@@ -41,7 +43,7 @@ pub fn parse(ctx: dim.Context) !dim.Content {
4143
pf.bootloader = bootloader_content;
4244
},
4345
.ignore => {
44-
pf.partitions[next_part_id] = .unused;
46+
pf.partitions[next_part_id] = null;
4547
next_part_id += 1;
4648
},
4749
.part => {
@@ -58,6 +60,22 @@ pub fn parse(ctx: dim.Context) !dim.Content {
5860
try ctx.report_nonfatal_error("MBR partition {} does not have a size, but is not last.", .{prev});
5961
}
6062
}
63+
64+
var all_auto = true;
65+
var all_manual = true;
66+
for (pf.partitions) |part_or_null| {
67+
const part = part_or_null orelse continue;
68+
69+
if (part.offset != null) {
70+
all_auto = false;
71+
} else {
72+
all_manual = false;
73+
}
74+
}
75+
76+
if (!all_auto and !all_manual) {
77+
try ctx.report_nonfatal_error("not all partitions have an explicit offset!", .{});
78+
}
6179
}
6280

6381
return .create_handle(pf, .create(PartTable, .{
@@ -104,120 +122,121 @@ fn parse_partition(ctx: dim.Context) !Partition {
104122
return part;
105123
}
106124

107-
fn render(self: *PartTable, stream: *dim.BinaryStream) dim.Content.RenderError!void {
108-
_ = self;
109-
_ = stream;
110-
}
111-
112-
// .mbr => |table| { // MbrTable
113-
// {
114-
// var boot_sector: [512]u8 = .{0} ** 512;
115-
116-
// @memcpy(boot_sector[0..table.bootloader.len], &table.bootloader);
117-
118-
// std.mem.writeInt(u32, boot_sector[0x1B8..0x1BC], if (table.disk_id) |disk_id| disk_id else 0x0000_0000, .little);
119-
// std.mem.writeInt(u16, boot_sector[0x1BC..0x1BE], 0x0000, .little);
120-
121-
// var all_auto = true;
122-
// var all_manual = true;
123-
// for (table.partitions) |part_or_null| {
124-
// const part = part_or_null orelse continue;
125-
126-
// if (part.offset != null) {
127-
// all_auto = false;
128-
// } else {
129-
// all_manual = false;
130-
// }
131-
// }
132-
133-
// if (!all_auto and !all_manual) {
134-
// std.log.err("{s}: not all partitions have an explicit offset!", .{context.slice()});
135-
// return error.InvalidSectorBoundary;
136-
// }
137-
138-
// const part_base = 0x01BE;
139-
// var auto_offset: u64 = 2048;
140-
// for (table.partitions, 0..) |part_or_null, part_id| {
141-
// const reset_len = context.len;
142-
// defer context.len = reset_len;
143-
144-
// var buffer: [64]u8 = undefined;
145-
// context.appendSliceAssumeCapacity(std.fmt.bufPrint(&buffer, "[{}]", .{part_id}) catch unreachable);
146-
147-
// const desc = boot_sector[part_base + 16 * part_id ..][0..16];
148-
149-
// if (part_or_null) |part| {
150-
// // https://wiki.osdev.org/MBR#Partition_table_entry_format
151-
152-
// const part_offset = part.offset orelse auto_offset;
153-
154-
// if ((part_offset % 512) != 0) {
155-
// std.log.err("{s}: .offset is not divisible by 512!", .{context.slice()});
156-
// return error.InvalidSectorBoundary;
157-
// }
158-
// if ((part.size % 512) != 0) {
159-
// std.log.err("{s}: .size is not divisible by 512!", .{context.slice()});
160-
// return error.InvalidSectorBoundary;
161-
// }
162-
163-
// const lba_u64 = @divExact(part_offset, 512);
164-
// const size_u64 = @divExact(part.size, 512);
165-
166-
// const lba = std.math.cast(u32, lba_u64) orelse {
167-
// std.log.err("{s}: .offset is out of bounds!", .{context.slice()});
168-
// return error.InvalidSectorBoundary;
169-
// };
170-
// const size = std.math.cast(u32, size_u64) orelse {
171-
// std.log.err("{s}: .size is out of bounds!", .{context.slice()});
172-
// return error.InvalidSectorBoundary;
173-
// };
174-
175-
// desc[0] = if (part.bootable) 0x80 else 0x00;
176-
177-
// desc[1..4].* = mbr.encodeMbrChsEntry(lba); // chs_start
178-
// desc[4] = @intFromEnum(part.type);
179-
// desc[5..8].* = mbr.encodeMbrChsEntry(lba + size - 1); // chs_end
180-
// std.mem.writeInt(u32, desc[8..12], lba, .little); // lba_start
181-
// std.mem.writeInt(u32, desc[12..16], size, .little); // block_count
182-
183-
// auto_offset += part.size;
184-
// } else {
185-
// @memset(desc, 0); // inactive
186-
// }
187-
// }
188-
// boot_sector[0x01FE] = 0x55;
189-
// boot_sector[0x01FF] = 0xAA;
190-
191-
// try disk.handle.writeAll(&boot_sector);
192-
// }
193-
194-
// {
195-
// var auto_offset: u64 = 2048;
196-
// for (table.partitions, 0..) |part_or_null, part_id| {
197-
// const part = part_or_null orelse continue;
198-
199-
// const reset_len = context.len;
200-
// defer context.len = reset_len;
201-
202-
// var buffer: [64]u8 = undefined;
203-
// context.appendSliceAssumeCapacity(std.fmt.bufPrint(&buffer, "[{}]", .{part_id}) catch unreachable);
204-
205-
// try writeDiskImage(b, asking, disk, base + auto_offset, part.size, part.data, context);
206-
207-
// auto_offset += part.size;
208-
// }
209-
// }
210-
// },
125+
fn render(table: *PartTable, stream: *dim.BinaryStream) dim.Content.RenderError!void {
126+
const last_part_id = blk: {
127+
var last: usize = 0;
128+
for (table.partitions, 0..) |p, i| {
129+
if (p != null)
130+
last = i;
131+
}
132+
break :blk last;
133+
};
211134

212-
pub const Partition = struct {
213-
pub const unused: Partition = .{
214-
.offset = null,
215-
.size = 0,
216-
.bootable = false,
217-
.type = .empty,
218-
.contains = .empty,
135+
const PartInfo = struct {
136+
offset: u64,
137+
size: u64,
219138
};
139+
var part_infos: [4]?PartInfo = @splat(null);
140+
141+
// Compute and write boot sector, based on the follow:
142+
// - https://en.wikipedia.org/wiki/Master_boot_record#Sector_layout
143+
{
144+
var boot_sector: [block_size]u8 = @splat(0);
145+
146+
if (table.bootloader) |bootloader| {
147+
var sector: dim.BinaryStream = .init_buffer(&boot_sector);
148+
149+
try bootloader.render(&sector);
150+
151+
const upper_limit: u64 = if (table.disk_id != null)
152+
0x01B8
153+
else
154+
0x1BE;
155+
156+
if (sector.virtual_offset >= upper_limit) {
157+
// TODO(fqu): Emit warning diagnostics here that parts of the bootloader will be overwritten by the MBR data.
158+
}
159+
}
160+
161+
if (table.disk_id) |disk_id| {
162+
std.mem.writeInt(u32, boot_sector[0x1B8..0x1BC], disk_id, .little);
163+
}
220164

165+
// TODO(fqu): Implement "0x5A5A if copy-protected"
166+
std.mem.writeInt(u16, boot_sector[0x1BC..0x1BE], 0x0000, .little);
167+
168+
const part_base = 0x01BE;
169+
var auto_offset: u64 = 2048 * block_size; // TODO(fqu): Make this configurable by allowing `offset` on the first partition, but still allow auto-layouting
170+
for (table.partitions, &part_infos, 0..) |part_or_null, *pinfo, part_id| {
171+
const desc: *[16]u8 = boot_sector[part_base + 16 * part_id ..][0..16];
172+
173+
// Initialize to "inactive" state
174+
desc.* = @splat(0);
175+
pinfo.* = null;
176+
177+
if (part_or_null) |part| {
178+
// https://wiki.osdev.org/MBR#Partition_table_entry_format
179+
180+
const part_offset = part.offset orelse auto_offset;
181+
const part_size = part.size orelse if (part_id == last_part_id)
182+
std.mem.alignBackward(u64, stream.length - part_offset, block_size)
183+
else
184+
return error.ConfigurationError;
185+
186+
pinfo.* = .{
187+
.offset = part_offset,
188+
.size = part_size,
189+
};
190+
191+
if ((part_offset % block_size) != 0) {
192+
std.log.err("partition offset is not divisible by {}!", .{block_size});
193+
return error.ConfigurationError;
194+
}
195+
if ((part_size % block_size) != 0) {
196+
std.log.err("partition size is not divisible by {}!", .{block_size});
197+
return error.ConfigurationError;
198+
}
199+
200+
const lba_u64 = @divExact(part_offset, block_size);
201+
const size_u64 = @divExact(part_size, block_size);
202+
203+
const lba = std.math.cast(u32, lba_u64) orelse {
204+
std.log.err("partition offset is out of bounds!", .{});
205+
return error.ConfigurationError;
206+
};
207+
const size = std.math.cast(u32, size_u64) orelse {
208+
std.log.err("partition size is out of bounds!", .{});
209+
return error.ConfigurationError;
210+
};
211+
212+
desc[0] = if (part.bootable) 0x80 else 0x00;
213+
214+
desc[1..4].* = encodeMbrChsEntry(lba); // chs_start
215+
desc[4] = @intFromEnum(part.type);
216+
desc[5..8].* = encodeMbrChsEntry(lba + size - 1); // chs_end
217+
std.mem.writeInt(u32, desc[8..12], lba, .little); // lba_start
218+
std.mem.writeInt(u32, desc[12..16], size, .little); // block_count
219+
220+
auto_offset += part_size;
221+
}
222+
}
223+
boot_sector[0x01FE] = 0x55;
224+
boot_sector[0x01FF] = 0xAA;
225+
226+
try stream.write(0, &boot_sector);
227+
}
228+
229+
for (part_infos, table.partitions) |maybe_info, maybe_part| {
230+
const part = maybe_part orelse continue;
231+
const info = maybe_info orelse unreachable;
232+
233+
var sub_view = try stream.slice(info.offset, info.size);
234+
235+
try part.contains.render(&sub_view);
236+
}
237+
}
238+
239+
pub const Partition = struct {
221240
offset: ?u64 = null,
222241
size: ?u64,
223242

src/dim.zig

+9-4
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,10 @@ const Environment = struct {
336336
///
337337
///
338338
pub const Content = struct {
339-
pub const RenderError = FileName.OpenError || FileHandle.ReadError || BinaryStream.WriteError;
339+
pub const RenderError = FileName.OpenError || FileHandle.ReadError || BinaryStream.WriteError || error{
340+
ConfigurationError,
341+
OutOfBounds,
342+
};
340343
pub const GuessError = FileName.GetSizeError;
341344

342345
obj: *anyopaque,
@@ -542,7 +545,7 @@ pub const BinaryStream = struct {
542545
pub fn slice(bs: BinaryStream, offset: u64, length: ?u64) error{OutOfBounds}!BinaryStream {
543546
if (offset > bs.length)
544547
return error.OutOfBounds;
545-
const true_length = length or bs.length - offset;
548+
const true_length = length orelse bs.length - offset;
546549
if (true_length > bs.length)
547550
return error.OutOfBounds;
548551

@@ -551,8 +554,10 @@ pub const BinaryStream = struct {
551554
.backing = switch (bs.backing) {
552555
.buffer => |old| .{ .buffer = old + offset },
553556
.file => |old| .{
554-
.file = old.file,
555-
.base = old.base + offset,
557+
.file = .{
558+
.file = old.file,
559+
.base = old.base + offset,
560+
},
556561
},
557562
},
558563
};

0 commit comments

Comments
 (0)