xref: /freebsd/sys/geom/part/g_part_mbr.c (revision d876124d6ae9d56da5b4ff4c6015efd1d0c9222a)
1 /*-
2  * Copyright (c) 2007 Marcel Moolenaar
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 #include <sys/cdefs.h>
28 __FBSDID("$FreeBSD$");
29 
30 #include <sys/param.h>
31 #include <sys/bio.h>
32 #include <sys/diskmbr.h>
33 #include <sys/endian.h>
34 #include <sys/kernel.h>
35 #include <sys/kobj.h>
36 #include <sys/limits.h>
37 #include <sys/lock.h>
38 #include <sys/malloc.h>
39 #include <sys/mutex.h>
40 #include <sys/queue.h>
41 #include <sys/sbuf.h>
42 #include <sys/systm.h>
43 #include <geom/geom.h>
44 #include <geom/part/g_part.h>
45 
46 #include "g_part_if.h"
47 
48 #define	MBRSIZE		512
49 
50 struct g_part_mbr_table {
51 	struct g_part_table	base;
52 	u_char		mbr[MBRSIZE];
53 };
54 
55 struct g_part_mbr_entry {
56 	struct g_part_entry	base;
57 	struct dos_partition ent;
58 };
59 
60 static int g_part_mbr_add(struct g_part_table *, struct g_part_entry *,
61     struct g_part_parms *);
62 static int g_part_mbr_bootcode(struct g_part_table *, struct g_part_parms *);
63 static int g_part_mbr_create(struct g_part_table *, struct g_part_parms *);
64 static int g_part_mbr_destroy(struct g_part_table *, struct g_part_parms *);
65 static int g_part_mbr_dumpconf(struct g_part_table *, struct g_part_entry *,
66     struct sbuf *, const char *);
67 static int g_part_mbr_dumpto(struct g_part_table *, struct g_part_entry *);
68 static int g_part_mbr_modify(struct g_part_table *, struct g_part_entry *,
69     struct g_part_parms *);
70 static char *g_part_mbr_name(struct g_part_table *, struct g_part_entry *,
71     char *, size_t);
72 static int g_part_mbr_probe(struct g_part_table *, struct g_consumer *);
73 static int g_part_mbr_read(struct g_part_table *, struct g_consumer *);
74 static const char *g_part_mbr_type(struct g_part_table *, struct g_part_entry *,
75     char *, size_t);
76 static int g_part_mbr_write(struct g_part_table *, struct g_consumer *);
77 
78 static kobj_method_t g_part_mbr_methods[] = {
79 	KOBJMETHOD(g_part_add,		g_part_mbr_add),
80 	KOBJMETHOD(g_part_bootcode,	g_part_mbr_bootcode),
81 	KOBJMETHOD(g_part_create,	g_part_mbr_create),
82 	KOBJMETHOD(g_part_destroy,	g_part_mbr_destroy),
83 	KOBJMETHOD(g_part_dumpconf,	g_part_mbr_dumpconf),
84 	KOBJMETHOD(g_part_dumpto,	g_part_mbr_dumpto),
85 	KOBJMETHOD(g_part_modify,	g_part_mbr_modify),
86 	KOBJMETHOD(g_part_name,		g_part_mbr_name),
87 	KOBJMETHOD(g_part_probe,	g_part_mbr_probe),
88 	KOBJMETHOD(g_part_read,		g_part_mbr_read),
89 	KOBJMETHOD(g_part_type,		g_part_mbr_type),
90 	KOBJMETHOD(g_part_write,	g_part_mbr_write),
91 	{ 0, 0 }
92 };
93 
94 static struct g_part_scheme g_part_mbr_scheme = {
95 	"MBR",
96 	g_part_mbr_methods,
97 	sizeof(struct g_part_mbr_table),
98 	.gps_entrysz = sizeof(struct g_part_mbr_entry),
99 	.gps_minent = NDOSPART,
100 	.gps_maxent = NDOSPART,
101 	.gps_bootcodesz = MBRSIZE,
102 };
103 G_PART_SCHEME_DECLARE(g_part_mbr);
104 
105 static int
106 mbr_parse_type(const char *type, u_char *dp_typ)
107 {
108 	const char *alias;
109 	char *endp;
110 	long lt;
111 
112 	if (type[0] == '!') {
113 		lt = strtol(type + 1, &endp, 0);
114 		if (type[1] == '\0' || *endp != '\0' || lt <= 0 || lt >= 256)
115 			return (EINVAL);
116 		*dp_typ = (u_char)lt;
117 		return (0);
118 	}
119 	alias = g_part_alias_name(G_PART_ALIAS_FREEBSD);
120 	if (!strcasecmp(type, alias)) {
121 		*dp_typ = DOSPTYP_386BSD;
122 		return (0);
123 	}
124 	return (EINVAL);
125 }
126 
127 static int
128 mbr_probe_bpb(u_char *bpb)
129 {
130 	uint16_t secsz;
131 	uint8_t clstsz;
132 
133 #define PO2(x)	((x & (x - 1)) == 0)
134 	secsz = le16dec(bpb);
135 	if (secsz < 512 || secsz > 4096 || !PO2(secsz))
136 		return (0);
137 	clstsz = bpb[2];
138 	if (clstsz < 1 || clstsz > 128 || !PO2(clstsz))
139 		return (0);
140 #undef PO2
141 
142 	return (1);
143 }
144 
145 static void
146 mbr_set_chs(struct g_part_table *table, uint32_t lba, u_char *cylp, u_char *hdp,
147     u_char *secp)
148 {
149 	uint32_t cyl, hd, sec;
150 
151 	sec = lba % table->gpt_sectors + 1;
152 	lba /= table->gpt_sectors;
153 	hd = lba % table->gpt_heads;
154 	lba /= table->gpt_heads;
155 	cyl = lba;
156 	if (cyl > 1023)
157 		sec = hd = cyl = ~0;
158 
159 	*cylp = cyl & 0xff;
160 	*hdp = hd & 0xff;
161 	*secp = (sec & 0x3f) | ((cyl >> 2) & 0xc0);
162 }
163 
164 static int
165 g_part_mbr_add(struct g_part_table *basetable, struct g_part_entry *baseentry,
166     struct g_part_parms *gpp)
167 {
168 	struct g_part_mbr_entry *entry;
169 	struct g_part_mbr_table *table;
170 	uint32_t start, size, sectors;
171 
172 	if (gpp->gpp_parms & G_PART_PARM_LABEL)
173 		return (EINVAL);
174 
175 	sectors = basetable->gpt_sectors;
176 
177 	entry = (struct g_part_mbr_entry *)baseentry;
178 	table = (struct g_part_mbr_table *)basetable;
179 
180 	start = gpp->gpp_start;
181 	size = gpp->gpp_size;
182 	if (size < sectors)
183 		return (EINVAL);
184 	if (start % sectors) {
185 		size = size - sectors + (start % sectors);
186 		start = start - (start % sectors) + sectors;
187 	}
188 	if (size % sectors)
189 		size = size - (size % sectors);
190 	if (size < sectors)
191 		return (EINVAL);
192 
193 	if (baseentry->gpe_deleted)
194 		bzero(&entry->ent, sizeof(entry->ent));
195 
196 	KASSERT(baseentry->gpe_start <= start, (__func__));
197 	KASSERT(baseentry->gpe_end >= start + size - 1, (__func__));
198 	baseentry->gpe_start = start;
199 	baseentry->gpe_end = start + size - 1;
200 	entry->ent.dp_start = start;
201 	entry->ent.dp_size = size;
202 	mbr_set_chs(basetable, baseentry->gpe_start, &entry->ent.dp_scyl,
203 	    &entry->ent.dp_shd, &entry->ent.dp_ssect);
204 	mbr_set_chs(basetable, baseentry->gpe_end, &entry->ent.dp_ecyl,
205 	    &entry->ent.dp_ehd, &entry->ent.dp_esect);
206 	return (mbr_parse_type(gpp->gpp_type, &entry->ent.dp_typ));
207 }
208 
209 static int
210 g_part_mbr_bootcode(struct g_part_table *basetable, struct g_part_parms *gpp)
211 {
212 	struct g_part_mbr_table *table;
213 
214 	table = (struct g_part_mbr_table *)basetable;
215 	bcopy(gpp->gpp_codeptr, table->mbr, DOSPARTOFF);
216 	return (0);
217 }
218 
219 static int
220 g_part_mbr_create(struct g_part_table *basetable, struct g_part_parms *gpp)
221 {
222 	struct g_consumer *cp;
223 	struct g_provider *pp;
224 	struct g_part_mbr_table *table;
225 	uint64_t msize;
226 
227 	pp = gpp->gpp_provider;
228 	cp = LIST_FIRST(&pp->consumers);
229 
230 	if (pp->sectorsize < MBRSIZE)
231 		return (ENOSPC);
232 
233 	msize = pp->mediasize / pp->sectorsize;
234 	basetable->gpt_first = basetable->gpt_sectors;
235 	basetable->gpt_last = msize - (msize % basetable->gpt_sectors) - 1;
236 
237 	table = (struct g_part_mbr_table *)basetable;
238 	le16enc(table->mbr + DOSMAGICOFFSET, DOSMAGIC);
239 	return (0);
240 }
241 
242 static int
243 g_part_mbr_destroy(struct g_part_table *basetable, struct g_part_parms *gpp)
244 {
245 
246 	/* Wipe the first sector to clear the partitioning. */
247 	basetable->gpt_smhead |= 1;
248 	return (0);
249 }
250 
251 static int
252 g_part_mbr_dumpconf(struct g_part_table *table, struct g_part_entry *baseentry,
253     struct sbuf *sb, const char *indent)
254 {
255 	struct g_part_mbr_entry *entry;
256 
257 	if (indent != NULL)
258 		return (0);
259 
260 	entry = (struct g_part_mbr_entry *)baseentry;
261 	sbuf_printf(sb, " xs MBR xt %u", entry->ent.dp_typ);
262 	return (0);
263 }
264 
265 static int
266 g_part_mbr_dumpto(struct g_part_table *table, struct g_part_entry *baseentry)
267 {
268 	struct g_part_mbr_entry *entry;
269 
270 	/* Allow dumping to a FreeBSD partition only. */
271 	entry = (struct g_part_mbr_entry *)baseentry;
272 	return ((entry->ent.dp_typ == DOSPTYP_386BSD) ? 1 : 0);
273 }
274 
275 static int
276 g_part_mbr_modify(struct g_part_table *basetable,
277     struct g_part_entry *baseentry, struct g_part_parms *gpp)
278 {
279 	struct g_part_mbr_entry *entry;
280 
281 	if (gpp->gpp_parms & G_PART_PARM_LABEL)
282 		return (EINVAL);
283 
284 	entry = (struct g_part_mbr_entry *)baseentry;
285 	if (gpp->gpp_parms & G_PART_PARM_TYPE)
286 		return (mbr_parse_type(gpp->gpp_type, &entry->ent.dp_typ));
287 	return (0);
288 }
289 
290 static char *
291 g_part_mbr_name(struct g_part_table *table, struct g_part_entry *baseentry,
292     char *buf, size_t bufsz)
293 {
294 
295 	snprintf(buf, bufsz, "s%d", baseentry->gpe_index);
296 	return (buf);
297 }
298 
299 static int
300 g_part_mbr_probe(struct g_part_table *table, struct g_consumer *cp)
301 {
302 	struct g_provider *pp;
303 	u_char *buf, *p;
304 	int error, index, res, sum;
305 	uint16_t magic;
306 
307 	pp = cp->provider;
308 
309 	/* Sanity-check the provider. */
310 	if (pp->sectorsize < MBRSIZE || pp->mediasize < pp->sectorsize)
311 		return (ENOSPC);
312 	if (pp->sectorsize > 4096)
313 		return (ENXIO);
314 
315 	/* Check that there's a MBR. */
316 	buf = g_read_data(cp, 0L, pp->sectorsize, &error);
317 	if (buf == NULL)
318 		return (error);
319 
320 	/* We goto out on mismatch. */
321 	res = ENXIO;
322 
323 	magic = le16dec(buf + DOSMAGICOFFSET);
324 	if (magic != DOSMAGIC)
325 		goto out;
326 
327 	for (index = 0; index < NDOSPART; index++) {
328 		p = buf + DOSPARTOFF + index * DOSPARTSIZE;
329 		if (p[0] != 0 && p[0] != 0x80)
330 			goto out;
331 	}
332 
333 	/*
334 	 * If the partition table does not consist of all zeroes,
335 	 * assume we have a MBR. If it's all zeroes, we could have
336 	 * a boot sector. For example, a boot sector that doesn't
337 	 * have boot code -- common on non-i386 hardware. In that
338 	 * case we check if we have a possible BPB. If so, then we
339 	 * assume we have a boot sector instead.
340 	 */
341 	sum = 0;
342 	for (index = 0; index < NDOSPART * DOSPARTSIZE; index++)
343 		sum += buf[DOSPARTOFF + index];
344 	if (sum != 0 || !mbr_probe_bpb(buf + 0x0b))
345 		res = G_PART_PROBE_PRI_NORM;
346 
347  out:
348 	g_free(buf);
349 	return (res);
350 }
351 
352 static int
353 g_part_mbr_read(struct g_part_table *basetable, struct g_consumer *cp)
354 {
355 	struct dos_partition ent;
356 	struct g_provider *pp;
357 	struct g_part_mbr_table *table;
358 	struct g_part_mbr_entry *entry;
359 	u_char *buf, *p;
360 	off_t chs, msize;
361 	u_int sectors, heads;
362 	int error, index;
363 
364 	pp = cp->provider;
365 	table = (struct g_part_mbr_table *)basetable;
366 	msize = pp->mediasize / pp->sectorsize;
367 
368 	buf = g_read_data(cp, 0L, pp->sectorsize, &error);
369 	if (buf == NULL)
370 		return (error);
371 
372 	bcopy(buf, table->mbr, sizeof(table->mbr));
373 	for (index = NDOSPART - 1; index >= 0; index--) {
374 		p = buf + DOSPARTOFF + index * DOSPARTSIZE;
375 		ent.dp_flag = p[0];
376 		ent.dp_shd = p[1];
377 		ent.dp_ssect = p[2];
378 		ent.dp_scyl = p[3];
379 		ent.dp_typ = p[4];
380 		ent.dp_ehd = p[5];
381 		ent.dp_esect = p[6];
382 		ent.dp_ecyl = p[7];
383 		ent.dp_start = le32dec(p + 8);
384 		ent.dp_size = le32dec(p + 12);
385 		if (ent.dp_typ == 0 || ent.dp_typ == DOSPTYP_PMBR)
386 			continue;
387 		if (ent.dp_start == 0 || ent.dp_size == 0)
388 			continue;
389 		sectors = ent.dp_esect & 0x3f;
390 		if (sectors > basetable->gpt_sectors &&
391 		    !basetable->gpt_fixgeom) {
392 			g_part_geometry_heads(msize, sectors, &chs, &heads);
393 			if (chs != 0) {
394 				basetable->gpt_sectors = sectors;
395 				basetable->gpt_heads = heads;
396 			}
397 		}
398 		if ((ent.dp_start % basetable->gpt_sectors) != 0)
399 			printf("GEOM: %s: partition %d does not start on a "
400 			    "track boundary.\n", pp->name, index + 1);
401 		if ((ent.dp_size % basetable->gpt_sectors) != 0)
402 			printf("GEOM: %s: partition %d does not end on a "
403 			    "track boundary.\n", pp->name, index + 1);
404 
405 		entry = (struct g_part_mbr_entry *)g_part_new_entry(basetable,
406 		    index + 1, ent.dp_start, ent.dp_start + ent.dp_size - 1);
407 		entry->ent = ent;
408 	}
409 
410 	basetable->gpt_entries = NDOSPART;
411 	basetable->gpt_first = basetable->gpt_sectors;
412 	basetable->gpt_last = msize - (msize % basetable->gpt_sectors) - 1;
413 
414 	return (0);
415 }
416 
417 static const char *
418 g_part_mbr_type(struct g_part_table *basetable, struct g_part_entry *baseentry,
419     char *buf, size_t bufsz)
420 {
421 	struct g_part_mbr_entry *entry;
422 	int type;
423 
424 	entry = (struct g_part_mbr_entry *)baseentry;
425 	type = entry->ent.dp_typ;
426 	if (type == DOSPTYP_386BSD)
427 		return (g_part_alias_name(G_PART_ALIAS_FREEBSD));
428 	snprintf(buf, bufsz, "!%d", type);
429 	return (buf);
430 }
431 
432 static int
433 g_part_mbr_write(struct g_part_table *basetable, struct g_consumer *cp)
434 {
435 	struct g_part_entry *baseentry;
436 	struct g_part_mbr_entry *entry;
437 	struct g_part_mbr_table *table;
438 	u_char *p;
439 	int error, index;
440 
441 	table = (struct g_part_mbr_table *)basetable;
442 	baseentry = LIST_FIRST(&basetable->gpt_entry);
443 	for (index = 1; index <= basetable->gpt_entries; index++) {
444 		p = table->mbr + DOSPARTOFF + (index - 1) * DOSPARTSIZE;
445 		entry = (baseentry != NULL && index == baseentry->gpe_index)
446 		    ? (struct g_part_mbr_entry *)baseentry : NULL;
447 		if (entry != NULL && !baseentry->gpe_deleted) {
448 			p[0] = entry->ent.dp_flag;
449 			p[1] = entry->ent.dp_shd;
450 			p[2] = entry->ent.dp_ssect;
451 			p[3] = entry->ent.dp_scyl;
452 			p[4] = entry->ent.dp_typ;
453 			p[5] = entry->ent.dp_ehd;
454 			p[6] = entry->ent.dp_esect;
455 			p[7] = entry->ent.dp_ecyl;
456 			le32enc(p + 8, entry->ent.dp_start);
457 			le32enc(p + 12, entry->ent.dp_size);
458 		} else
459 			bzero(p, DOSPARTSIZE);
460 
461 		if (entry != NULL)
462 			baseentry = LIST_NEXT(baseentry, gpe_entry);
463 	}
464 
465 	error = g_write_data(cp, 0, table->mbr, cp->provider->sectorsize);
466 	return (error);
467 }
468