g_part_mbr.c revision 222243
1/*-
2 * Copyright (c) 2007, 2008 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: head/sys/geom/part/g_part_mbr.c 222243 2011-05-24 06:44:16Z ae $");
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 <sys/sysctl.h>
44#include <geom/geom.h>
45#include <geom/part/g_part.h>
46
47#include "g_part_if.h"
48
49FEATURE(geom_part_mbr, "GEOM partitioning class for MBR support");
50
51#define	MBRSIZE		512
52
53struct g_part_mbr_table {
54	struct g_part_table	base;
55	u_char		mbr[MBRSIZE];
56};
57
58struct g_part_mbr_entry {
59	struct g_part_entry	base;
60	struct dos_partition ent;
61};
62
63static int g_part_mbr_add(struct g_part_table *, struct g_part_entry *,
64    struct g_part_parms *);
65static int g_part_mbr_bootcode(struct g_part_table *, struct g_part_parms *);
66static int g_part_mbr_create(struct g_part_table *, struct g_part_parms *);
67static int g_part_mbr_destroy(struct g_part_table *, struct g_part_parms *);
68static void g_part_mbr_dumpconf(struct g_part_table *, struct g_part_entry *,
69    struct sbuf *, const char *);
70static int g_part_mbr_dumpto(struct g_part_table *, struct g_part_entry *);
71static int g_part_mbr_modify(struct g_part_table *, struct g_part_entry *,
72    struct g_part_parms *);
73static const char *g_part_mbr_name(struct g_part_table *, struct g_part_entry *,
74    char *, size_t);
75static int g_part_mbr_probe(struct g_part_table *, struct g_consumer *);
76static int g_part_mbr_read(struct g_part_table *, struct g_consumer *);
77static int g_part_mbr_setunset(struct g_part_table *, struct g_part_entry *,
78    const char *, unsigned int);
79static const char *g_part_mbr_type(struct g_part_table *, struct g_part_entry *,
80    char *, size_t);
81static int g_part_mbr_write(struct g_part_table *, struct g_consumer *);
82static int g_part_mbr_resize(struct g_part_table *, struct g_part_entry *,
83    struct g_part_parms *);
84
85static kobj_method_t g_part_mbr_methods[] = {
86	KOBJMETHOD(g_part_add,		g_part_mbr_add),
87	KOBJMETHOD(g_part_bootcode,	g_part_mbr_bootcode),
88	KOBJMETHOD(g_part_create,	g_part_mbr_create),
89	KOBJMETHOD(g_part_destroy,	g_part_mbr_destroy),
90	KOBJMETHOD(g_part_dumpconf,	g_part_mbr_dumpconf),
91	KOBJMETHOD(g_part_dumpto,	g_part_mbr_dumpto),
92	KOBJMETHOD(g_part_modify,	g_part_mbr_modify),
93	KOBJMETHOD(g_part_resize,	g_part_mbr_resize),
94	KOBJMETHOD(g_part_name,		g_part_mbr_name),
95	KOBJMETHOD(g_part_probe,	g_part_mbr_probe),
96	KOBJMETHOD(g_part_read,		g_part_mbr_read),
97	KOBJMETHOD(g_part_setunset,	g_part_mbr_setunset),
98	KOBJMETHOD(g_part_type,		g_part_mbr_type),
99	KOBJMETHOD(g_part_write,	g_part_mbr_write),
100	{ 0, 0 }
101};
102
103static struct g_part_scheme g_part_mbr_scheme = {
104	"MBR",
105	g_part_mbr_methods,
106	sizeof(struct g_part_mbr_table),
107	.gps_entrysz = sizeof(struct g_part_mbr_entry),
108	.gps_minent = NDOSPART,
109	.gps_maxent = NDOSPART,
110	.gps_bootcodesz = MBRSIZE,
111};
112G_PART_SCHEME_DECLARE(g_part_mbr);
113
114static struct g_part_mbr_alias {
115	u_char		typ;
116	int		alias;
117} mbr_alias_match[] = {
118	{ DOSPTYP_386BSD,	G_PART_ALIAS_FREEBSD },
119	{ DOSPTYP_EXT,		G_PART_ALIAS_EBR },
120	{ DOSPTYP_NTFS,		G_PART_ALIAS_MS_NTFS },
121	{ DOSPTYP_FAT32,	G_PART_ALIAS_MS_FAT32 },
122	{ DOSPTYP_LINSWP,	G_PART_ALIAS_LINUX_SWAP },
123	{ DOSPTYP_LINUX,	G_PART_ALIAS_LINUX_DATA },
124	{ DOSPTYP_LINLVM,	G_PART_ALIAS_LINUX_LVM },
125	{ DOSPTYP_LINRAID,	G_PART_ALIAS_LINUX_RAID },
126};
127
128static int
129mbr_parse_type(const char *type, u_char *dp_typ)
130{
131	const char *alias;
132	char *endp;
133	long lt;
134	int i;
135
136	if (type[0] == '!') {
137		lt = strtol(type + 1, &endp, 0);
138		if (type[1] == '\0' || *endp != '\0' || lt <= 0 || lt >= 256)
139			return (EINVAL);
140		*dp_typ = (u_char)lt;
141		return (0);
142	}
143	for (i = 0;
144	    i < sizeof(mbr_alias_match) / sizeof(mbr_alias_match[0]); i++) {
145		alias = g_part_alias_name(mbr_alias_match[i].alias);
146		if (strcasecmp(type, alias) == 0) {
147			*dp_typ = mbr_alias_match[i].typ;
148			return (0);
149		}
150	}
151	return (EINVAL);
152}
153
154static int
155mbr_probe_bpb(u_char *bpb)
156{
157	uint16_t secsz;
158	uint8_t clstsz;
159
160#define PO2(x)	((x & (x - 1)) == 0)
161	secsz = le16dec(bpb);
162	if (secsz < 512 || secsz > 4096 || !PO2(secsz))
163		return (0);
164	clstsz = bpb[2];
165	if (clstsz < 1 || clstsz > 128 || !PO2(clstsz))
166		return (0);
167#undef PO2
168
169	return (1);
170}
171
172static void
173mbr_set_chs(struct g_part_table *table, uint32_t lba, u_char *cylp, u_char *hdp,
174    u_char *secp)
175{
176	uint32_t cyl, hd, sec;
177
178	sec = lba % table->gpt_sectors + 1;
179	lba /= table->gpt_sectors;
180	hd = lba % table->gpt_heads;
181	lba /= table->gpt_heads;
182	cyl = lba;
183	if (cyl > 1023)
184		sec = hd = cyl = ~0;
185
186	*cylp = cyl & 0xff;
187	*hdp = hd & 0xff;
188	*secp = (sec & 0x3f) | ((cyl >> 2) & 0xc0);
189}
190
191static int
192g_part_mbr_add(struct g_part_table *basetable, struct g_part_entry *baseentry,
193    struct g_part_parms *gpp)
194{
195	struct g_part_mbr_entry *entry;
196	struct g_part_mbr_table *table;
197	uint32_t start, size, sectors;
198
199	if (gpp->gpp_parms & G_PART_PARM_LABEL)
200		return (EINVAL);
201
202	sectors = basetable->gpt_sectors;
203
204	entry = (struct g_part_mbr_entry *)baseentry;
205	table = (struct g_part_mbr_table *)basetable;
206
207	start = gpp->gpp_start;
208	size = gpp->gpp_size;
209	if (size < sectors)
210		return (EINVAL);
211	if (start % sectors) {
212		size = size - sectors + (start % sectors);
213		start = start - (start % sectors) + sectors;
214	}
215	if (size % sectors)
216		size = size - (size % sectors);
217	if (size < sectors)
218		return (EINVAL);
219
220	if (baseentry->gpe_deleted)
221		bzero(&entry->ent, sizeof(entry->ent));
222
223	KASSERT(baseentry->gpe_start <= start, ("%s", __func__));
224	KASSERT(baseentry->gpe_end >= start + size - 1, ("%s", __func__));
225	baseentry->gpe_start = start;
226	baseentry->gpe_end = start + size - 1;
227	entry->ent.dp_start = start;
228	entry->ent.dp_size = size;
229	mbr_set_chs(basetable, baseentry->gpe_start, &entry->ent.dp_scyl,
230	    &entry->ent.dp_shd, &entry->ent.dp_ssect);
231	mbr_set_chs(basetable, baseentry->gpe_end, &entry->ent.dp_ecyl,
232	    &entry->ent.dp_ehd, &entry->ent.dp_esect);
233	return (mbr_parse_type(gpp->gpp_type, &entry->ent.dp_typ));
234}
235
236static int
237g_part_mbr_bootcode(struct g_part_table *basetable, struct g_part_parms *gpp)
238{
239	struct g_part_mbr_table *table;
240	size_t codesz;
241
242	codesz = DOSPARTOFF;
243	table = (struct g_part_mbr_table *)basetable;
244	bzero(table->mbr, codesz);
245	codesz = MIN(codesz,  gpp->gpp_codesize);
246	if (codesz > 0)
247		bcopy(gpp->gpp_codeptr, table->mbr, codesz);
248	return (0);
249}
250
251static int
252g_part_mbr_create(struct g_part_table *basetable, struct g_part_parms *gpp)
253{
254	struct g_provider *pp;
255	struct g_part_mbr_table *table;
256	uint32_t msize;
257
258	pp = gpp->gpp_provider;
259	if (pp->sectorsize < MBRSIZE)
260		return (ENOSPC);
261
262	msize = MIN(pp->mediasize / pp->sectorsize, UINT32_MAX);
263	basetable->gpt_first = basetable->gpt_sectors;
264	basetable->gpt_last = msize - (msize % basetable->gpt_sectors) - 1;
265
266	table = (struct g_part_mbr_table *)basetable;
267	le16enc(table->mbr + DOSMAGICOFFSET, DOSMAGIC);
268	return (0);
269}
270
271static int
272g_part_mbr_destroy(struct g_part_table *basetable, struct g_part_parms *gpp)
273{
274
275	/* Wipe the first sector to clear the partitioning. */
276	basetable->gpt_smhead |= 1;
277	return (0);
278}
279
280static void
281g_part_mbr_dumpconf(struct g_part_table *table, struct g_part_entry *baseentry,
282    struct sbuf *sb, const char *indent)
283{
284	struct g_part_mbr_entry *entry;
285
286	entry = (struct g_part_mbr_entry *)baseentry;
287	if (indent == NULL) {
288		/* conftxt: libdisk compatibility */
289		sbuf_printf(sb, " xs MBR xt %u", entry->ent.dp_typ);
290	} else if (entry != NULL) {
291		/* confxml: partition entry information */
292		sbuf_printf(sb, "%s<rawtype>%u</rawtype>\n", indent,
293		    entry->ent.dp_typ);
294		if (entry->ent.dp_flag & 0x80)
295			sbuf_printf(sb, "%s<attrib>active</attrib>\n", indent);
296	} else {
297		/* confxml: scheme information */
298	}
299}
300
301static int
302g_part_mbr_dumpto(struct g_part_table *table, struct g_part_entry *baseentry)
303{
304	struct g_part_mbr_entry *entry;
305
306	/* Allow dumping to a FreeBSD partition only. */
307	entry = (struct g_part_mbr_entry *)baseentry;
308	return ((entry->ent.dp_typ == DOSPTYP_386BSD) ? 1 : 0);
309}
310
311static int
312g_part_mbr_modify(struct g_part_table *basetable,
313    struct g_part_entry *baseentry, struct g_part_parms *gpp)
314{
315	struct g_part_mbr_entry *entry;
316
317	if (gpp->gpp_parms & G_PART_PARM_LABEL)
318		return (EINVAL);
319
320	entry = (struct g_part_mbr_entry *)baseentry;
321	if (gpp->gpp_parms & G_PART_PARM_TYPE)
322		return (mbr_parse_type(gpp->gpp_type, &entry->ent.dp_typ));
323	return (0);
324}
325
326static int
327g_part_mbr_resize(struct g_part_table *basetable,
328    struct g_part_entry *baseentry, struct g_part_parms *gpp)
329{
330	struct g_part_mbr_entry *entry;
331	uint32_t size, sectors;
332
333	sectors = basetable->gpt_sectors;
334	size = gpp->gpp_size;
335
336	if (size < sectors)
337		return (EINVAL);
338	if (size % sectors)
339		size = size - (size % sectors);
340	if (size < sectors)
341		return (EINVAL);
342
343	entry = (struct g_part_mbr_entry *)baseentry;
344	baseentry->gpe_end = baseentry->gpe_start + size - 1;
345	entry->ent.dp_size = size;
346	mbr_set_chs(basetable, baseentry->gpe_end, &entry->ent.dp_ecyl,
347	    &entry->ent.dp_ehd, &entry->ent.dp_esect);
348	return (0);
349}
350
351static const char *
352g_part_mbr_name(struct g_part_table *table, struct g_part_entry *baseentry,
353    char *buf, size_t bufsz)
354{
355
356	snprintf(buf, bufsz, "s%d", baseentry->gpe_index);
357	return (buf);
358}
359
360static int
361g_part_mbr_probe(struct g_part_table *table, struct g_consumer *cp)
362{
363	char psn[8];
364	struct g_provider *pp;
365	u_char *buf, *p;
366	int error, index, res, sum;
367	uint16_t magic;
368
369	pp = cp->provider;
370
371	/* Sanity-check the provider. */
372	if (pp->sectorsize < MBRSIZE || pp->mediasize < pp->sectorsize)
373		return (ENOSPC);
374	if (pp->sectorsize > 4096)
375		return (ENXIO);
376
377	/* We don't nest under an MBR (see EBR instead). */
378	error = g_getattr("PART::scheme", cp, &psn);
379	if (error == 0 && strcmp(psn, g_part_mbr_scheme.name) == 0)
380		return (ELOOP);
381
382	/* Check that there's a MBR. */
383	buf = g_read_data(cp, 0L, pp->sectorsize, &error);
384	if (buf == NULL)
385		return (error);
386
387	/* We goto out on mismatch. */
388	res = ENXIO;
389
390	magic = le16dec(buf + DOSMAGICOFFSET);
391	if (magic != DOSMAGIC)
392		goto out;
393
394	for (index = 0; index < NDOSPART; index++) {
395		p = buf + DOSPARTOFF + index * DOSPARTSIZE;
396		if (p[0] != 0 && p[0] != 0x80)
397			goto out;
398	}
399
400	/*
401	 * If the partition table does not consist of all zeroes,
402	 * assume we have a MBR. If it's all zeroes, we could have
403	 * a boot sector. For example, a boot sector that doesn't
404	 * have boot code -- common on non-i386 hardware. In that
405	 * case we check if we have a possible BPB. If so, then we
406	 * assume we have a boot sector instead.
407	 */
408	sum = 0;
409	for (index = 0; index < NDOSPART * DOSPARTSIZE; index++)
410		sum += buf[DOSPARTOFF + index];
411	if (sum != 0 || !mbr_probe_bpb(buf + 0x0b))
412		res = G_PART_PROBE_PRI_NORM;
413
414 out:
415	g_free(buf);
416	return (res);
417}
418
419static int
420g_part_mbr_read(struct g_part_table *basetable, struct g_consumer *cp)
421{
422	struct dos_partition ent;
423	struct g_provider *pp;
424	struct g_part_mbr_table *table;
425	struct g_part_mbr_entry *entry;
426	u_char *buf, *p;
427	off_t chs, msize;
428	u_int sectors, heads;
429	int error, index;
430
431	pp = cp->provider;
432	table = (struct g_part_mbr_table *)basetable;
433	msize = MIN(pp->mediasize / pp->sectorsize, UINT32_MAX);
434
435	buf = g_read_data(cp, 0L, pp->sectorsize, &error);
436	if (buf == NULL)
437		return (error);
438
439	bcopy(buf, table->mbr, sizeof(table->mbr));
440	for (index = NDOSPART - 1; index >= 0; index--) {
441		p = buf + DOSPARTOFF + index * DOSPARTSIZE;
442		ent.dp_flag = p[0];
443		ent.dp_shd = p[1];
444		ent.dp_ssect = p[2];
445		ent.dp_scyl = p[3];
446		ent.dp_typ = p[4];
447		ent.dp_ehd = p[5];
448		ent.dp_esect = p[6];
449		ent.dp_ecyl = p[7];
450		ent.dp_start = le32dec(p + 8);
451		ent.dp_size = le32dec(p + 12);
452		if (ent.dp_typ == 0 || ent.dp_typ == DOSPTYP_PMBR)
453			continue;
454		if (ent.dp_start == 0 || ent.dp_size == 0)
455			continue;
456		sectors = ent.dp_esect & 0x3f;
457		if (sectors > basetable->gpt_sectors &&
458		    !basetable->gpt_fixgeom) {
459			g_part_geometry_heads(msize, sectors, &chs, &heads);
460			if (chs != 0) {
461				basetable->gpt_sectors = sectors;
462				basetable->gpt_heads = heads;
463			}
464		}
465
466		entry = (struct g_part_mbr_entry *)g_part_new_entry(basetable,
467		    index + 1, ent.dp_start, ent.dp_start + ent.dp_size - 1);
468		entry->ent = ent;
469	}
470
471	basetable->gpt_entries = NDOSPART;
472	basetable->gpt_first = basetable->gpt_sectors;
473	basetable->gpt_last = msize - (msize % basetable->gpt_sectors) - 1;
474
475	g_free(buf);
476	return (0);
477}
478
479static int
480g_part_mbr_setunset(struct g_part_table *table, struct g_part_entry *baseentry,
481    const char *attrib, unsigned int set)
482{
483	struct g_part_entry *iter;
484	struct g_part_mbr_entry *entry;
485	int changed;
486
487	if (strcasecmp(attrib, "active") != 0)
488		return (EINVAL);
489
490	/* Only one entry can have the active attribute. */
491	LIST_FOREACH(iter, &table->gpt_entry, gpe_entry) {
492		if (iter->gpe_deleted)
493			continue;
494		changed = 0;
495		entry = (struct g_part_mbr_entry *)iter;
496		if (iter == baseentry) {
497			if (set && (entry->ent.dp_flag & 0x80) == 0) {
498				entry->ent.dp_flag |= 0x80;
499				changed = 1;
500			} else if (!set && (entry->ent.dp_flag & 0x80)) {
501				entry->ent.dp_flag &= ~0x80;
502				changed = 1;
503			}
504		} else {
505			if (set && (entry->ent.dp_flag & 0x80)) {
506				entry->ent.dp_flag &= ~0x80;
507				changed = 1;
508			}
509		}
510		if (changed && !iter->gpe_created)
511			iter->gpe_modified = 1;
512	}
513	return (0);
514}
515
516static const char *
517g_part_mbr_type(struct g_part_table *basetable, struct g_part_entry *baseentry,
518    char *buf, size_t bufsz)
519{
520	struct g_part_mbr_entry *entry;
521	int i;
522
523	entry = (struct g_part_mbr_entry *)baseentry;
524	for (i = 0;
525	    i < sizeof(mbr_alias_match) / sizeof(mbr_alias_match[0]); i++) {
526		if (mbr_alias_match[i].typ == entry->ent.dp_typ)
527			return (g_part_alias_name(mbr_alias_match[i].alias));
528	}
529	snprintf(buf, bufsz, "!%d", entry->ent.dp_typ);
530	return (buf);
531}
532
533static int
534g_part_mbr_write(struct g_part_table *basetable, struct g_consumer *cp)
535{
536	struct g_part_entry *baseentry;
537	struct g_part_mbr_entry *entry;
538	struct g_part_mbr_table *table;
539	u_char *p;
540	int error, index;
541
542	table = (struct g_part_mbr_table *)basetable;
543	baseentry = LIST_FIRST(&basetable->gpt_entry);
544	for (index = 1; index <= basetable->gpt_entries; index++) {
545		p = table->mbr + DOSPARTOFF + (index - 1) * DOSPARTSIZE;
546		entry = (baseentry != NULL && index == baseentry->gpe_index)
547		    ? (struct g_part_mbr_entry *)baseentry : NULL;
548		if (entry != NULL && !baseentry->gpe_deleted) {
549			p[0] = entry->ent.dp_flag;
550			p[1] = entry->ent.dp_shd;
551			p[2] = entry->ent.dp_ssect;
552			p[3] = entry->ent.dp_scyl;
553			p[4] = entry->ent.dp_typ;
554			p[5] = entry->ent.dp_ehd;
555			p[6] = entry->ent.dp_esect;
556			p[7] = entry->ent.dp_ecyl;
557			le32enc(p + 8, entry->ent.dp_start);
558			le32enc(p + 12, entry->ent.dp_size);
559		} else
560			bzero(p, DOSPARTSIZE);
561
562		if (entry != NULL)
563			baseentry = LIST_NEXT(baseentry, gpe_entry);
564	}
565
566	error = g_write_data(cp, 0, table->mbr, cp->provider->sectorsize);
567	return (error);
568}
569