1/*-
2 * Copyright (c) 2002 Poul-Henning Kamp
3 * Copyright (c) 2002 Networks Associates Technology, Inc.
4 * All rights reserved.
5 *
6 * This software was developed for the FreeBSD Project by Poul-Henning Kamp
7 * and NAI Labs, the Security Research Division of Network Associates, Inc.
8 * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the
9 * DARPA CHATS research program.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. The names of the authors may not be used to endorse or promote
20 * products derived from this software without specific prior written
21 * permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 *
35 * This is the method for dealing with BSD disklabels. It has been
36 * extensively (by my standards at least) commented, in the vain hope that
37 * it will serve as the source in future copy&paste operations.
38 */
39
40#include <sys/cdefs.h>
41__FBSDID("$FreeBSD: head/sys/geom/geom_bsd.c 119660 2003-09-01 20:45:32Z phk $");
42
43#include <sys/param.h>
44#include <sys/endian.h>
45#include <sys/systm.h>
46#include <sys/kernel.h>
47#include <sys/conf.h>
48#include <sys/bio.h>
49#include <sys/malloc.h>
50#include <sys/lock.h>
51#include <sys/mutex.h>
52#include <sys/md5.h>
53#include <sys/errno.h>
54#include <sys/disklabel.h>
55#include <geom/geom.h>
56#include <geom/geom_slice.h>
57
58#define BSD_CLASS_NAME "BSD"
59
60#define ALPHA_LABEL_OFFSET 64
61
62#define LABELSIZE (148 + 16 * MAXPARTITIONS)
63
64static void g_bsd_hotwrite(void *arg, int flag);
65/*
66 * Our private data about one instance. All the rest is handled by the
67 * slice code and stored in its softc, so this is just the stuff
68 * specific to BSD disklabels.
69 */
70struct g_bsd_softc {
71 off_t labeloffset;
72 off_t mbroffset;
73 off_t rawoffset;
74 struct disklabel ondisk;
75 u_char label[LABELSIZE];
76 u_char labelsum[16];
77};
78
79/*
80 * Modify our slicer to match proposed disklabel, if possible.
81 * This is where we make sure we don't do something stupid.
82 */
83static int
84g_bsd_modify(struct g_geom *gp, u_char *label)
85{
86 int i, error;
87 struct partition *ppp;
88 struct g_slicer *gsp;
89 struct g_consumer *cp;
90 struct g_bsd_softc *ms;
91 u_int secsize, u;
92 off_t rawoffset, o;
93 struct disklabel dl;
94 MD5_CTX md5sum;
95
96 g_topology_assert();
97 gsp = gp->softc;
98 ms = gsp->softc;
99
100 error = bsd_disklabel_le_dec(label, &dl, MAXPARTITIONS);
101 if (error) {
102 return (error);
103 }
104
105 /* Get dimensions of our device. */
106 cp = LIST_FIRST(&gp->consumer);
107 secsize = cp->provider->sectorsize;
108
109 /* ... or a smaller sector size. */
110 if (dl.d_secsize < secsize) {
111 return (EINVAL);
112 }
113
114 /* ... or a non-multiple sector size. */
115 if (dl.d_secsize % secsize != 0) {
116 return (EINVAL);
117 }
118
119 /* Historical braindamage... */
120 rawoffset = (off_t)dl.d_partitions[RAW_PART].p_offset * dl.d_secsize;
121
122 for (i = 0; i < dl.d_npartitions; i++) {
123 ppp = &dl.d_partitions[i];
124 if (ppp->p_size == 0)
125 continue;
126 o = (off_t)ppp->p_offset * dl.d_secsize;
127
128 if (o < rawoffset)
129 rawoffset = 0;
130 }
131
132 if (rawoffset != 0 && (off_t)rawoffset != ms->mbroffset)
133 printf("WARNING: Expected rawoffset %jd, found %jd\n",
134 (intmax_t)ms->mbroffset/dl.d_secsize,
135 (intmax_t)rawoffset/dl.d_secsize);
136
137 /* Don't munge open partitions. */
138 for (i = 0; i < dl.d_npartitions; i++) {
139 ppp = &dl.d_partitions[i];
140
141 o = (off_t)ppp->p_offset * dl.d_secsize;
142 if (o == 0)
143 o = rawoffset;
144 error = g_slice_config(gp, i, G_SLICE_CONFIG_CHECK,
145 o - rawoffset,
146 (off_t)ppp->p_size * dl.d_secsize,
147 dl.d_secsize,
148 "%s%c", gp->name, 'a' + i);
149 if (error)
150 return (error);
151 }
152
153 /* Look good, go for it... */
154 for (u = 0; u < gsp->nslice; u++) {
155 ppp = &dl.d_partitions[u];
156 o = (off_t)ppp->p_offset * dl.d_secsize;
157 if (o == 0)
158 o = rawoffset;
159 g_slice_config(gp, u, G_SLICE_CONFIG_SET,
160 o - rawoffset,
161 (off_t)ppp->p_size * dl.d_secsize,
162 dl.d_secsize,
163 "%s%c", gp->name, 'a' + u);
164 }
165
166 /* Update our softc */
167 ms->ondisk = dl;
168 if (label != ms->label)
169 bcopy(label, ms->label, LABELSIZE);
170 ms->rawoffset = rawoffset;
171
172 /*
173 * In order to avoid recursively attaching to the same
174 * on-disk label (it's usually visible through the 'c'
175 * partition) we calculate an MD5 and ask if other BSD's
176 * below us love that label. If they do, we don't.
177 */
178 MD5Init(&md5sum);
179 MD5Update(&md5sum, ms->label, sizeof(ms->label));
180 MD5Final(ms->labelsum, &md5sum);
181
182 return (0);
183}
184
185/*
186 * This is an internal helper function, called multiple times from the taste
187 * function to try to locate a disklabel on the disk. More civilized formats
188 * will not need this, as there is only one possible place on disk to look
189 * for the magic spot.
190 */
191
192static int
193g_bsd_try(struct g_geom *gp, struct g_slicer *gsp, struct g_consumer *cp, int secsize, struct g_bsd_softc *ms, off_t offset)
194{
195 int error;
196 u_char *buf;
197 struct disklabel *dl;
198 off_t secoff;
199
200 /*
201 * We need to read entire aligned sectors, and we assume that the
202 * disklabel does not span sectors, so one sector is enough.
203 */
204 error = 0;
205 secoff = offset % secsize;
206 buf = g_read_data(cp, offset - secoff, secsize, &error);
207 if (buf == NULL || error != 0)
208 return (ENOENT);
209
210 /* Decode into our native format. */
211 dl = &ms->ondisk;
212 error = bsd_disklabel_le_dec(buf + secoff, dl, MAXPARTITIONS);
213 if (!error)
214 bcopy(buf + secoff, ms->label, LABELSIZE);
215
216 /* Remember to free the buffer g_read_data() gave us. */
217 g_free(buf);
218
219 ms->labeloffset = offset;
220 return (error);
221}
222
223/*
224 * This function writes the current label to disk, possibly updating
225 * the alpha SRM checksum.
226 */
227
228static int
229g_bsd_writelabel(struct g_geom *gp, u_char *bootcode)
230{
231 off_t secoff;
232 u_int secsize;
233 struct g_consumer *cp;
234 struct g_slicer *gsp;
235 struct g_bsd_softc *ms;
236 u_char *buf;
237 uint64_t sum;
238 int error, i;
239
240 gsp = gp->softc;
241 ms = gsp->softc;
242 cp = LIST_FIRST(&gp->consumer);
243 /* Get sector size, we need it to read data. */
244 secsize = cp->provider->sectorsize;
245 secoff = ms->labeloffset % secsize;
246 if (bootcode == NULL) {
247 buf = g_read_data(cp, ms->labeloffset - secoff, secsize, &error);
248 if (buf == NULL || error != 0)
249 return (error);
250 bcopy(ms->label, buf + secoff, sizeof(ms->label));
251 } else {
252 buf = bootcode;
253 bcopy(ms->label, buf + ms->labeloffset, sizeof(ms->label));
254 }
255 if (ms->labeloffset == ALPHA_LABEL_OFFSET) {
256 sum = 0;
257 for (i = 0; i < 63; i++)
258 sum += le64dec(buf + i * 8);
259 le64enc(buf + 504, sum);
260 }
261 if (bootcode == NULL) {
262 error = g_write_data(cp, ms->labeloffset - secoff, buf, secsize);
263 g_free(buf);
264 } else {
265 error = g_write_data(cp, 0, bootcode, BBSIZE);
266 }
267 return(error);
268}
269
270/*
271 * If the user tries to overwrite our disklabel through an open partition
272 * or via a magicwrite config call, we end up here and try to prevent
273 * footshooting as best we can.
274 */
275static void
276g_bsd_hotwrite(void *arg, int flag)
277{
278 struct bio *bp;
279 struct g_geom *gp;
280 struct g_slicer *gsp;
281 struct g_slice *gsl;
282 struct g_bsd_softc *ms;
283 u_char *p;
284 int error;
285
286 g_topology_assert();
287 /*
288 * We should never get canceled, because that would amount to a removal
289 * of the geom while there was outstanding I/O requests.
290 */
291 KASSERT(flag != EV_CANCEL, ("g_bsd_hotwrite cancelled"));
292 bp = arg;
293 gp = bp->bio_to->geom;
294 gsp = gp->softc;
295 ms = gsp->softc;
296 gsl = &gsp->slices[bp->bio_to->index];
297 p = (u_char*)bp->bio_data + ms->labeloffset
298 - (bp->bio_offset + gsl->offset);
299 error = g_bsd_modify(gp, p);
300 if (error) {
301 g_io_deliver(bp, EPERM);
302 return;
303 }
304 g_slice_finish_hot(bp);
305}
306
307/*-
308 * This start routine is only called for non-trivial requests, all the
309 * trivial ones are handled autonomously by the slice code.
310 * For requests we handle here, we must call the g_io_deliver() on the
311 * bio, and return non-zero to indicate to the slice code that we did so.
312 * This code executes in the "DOWN" I/O path, this means:
313 * * No sleeping.
314 * * Don't grab the topology lock.
315 * * Don't call biowait, g_getattr(), g_setattr() or g_read_data()
316 */
317static int
318g_bsd_ioctl(struct g_provider *pp, u_long cmd, void * data, struct thread *td)
319{
320 struct g_geom *gp;
321 struct g_bsd_softc *ms;
322 struct g_slicer *gsp;
323 u_char *label;
324 int error;
325
326 gp = pp->geom;
327 gsp = gp->softc;
328 ms = gsp->softc;
329
330 switch(cmd) {
331 case DIOCGDINFO:
332 /* Return a copy of the disklabel to userland. */
333 bsd_disklabel_le_dec(ms->label, data, MAXPARTITIONS);
334 return(0);
335 case DIOCBSDBB: {
336 struct g_consumer *cp;
337 u_char *buf;
338 void *p;
339 int error, i;
340 uint64_t sum;
341
342 /* The disklabel to set is the ioctl argument. */
343 buf = g_malloc(BBSIZE, M_WAITOK);
344 p = *(void **)data;
345 error = copyin(p, buf, BBSIZE);
346 if (!error) {
347 /* XXX: Rude, but supposedly safe */
348 DROP_GIANT();
349 g_topology_lock();
350 /* Validate and modify our slice instance to match. */
351 error = g_bsd_modify(gp, buf + ms->labeloffset);
352 if (!error) {
353 cp = LIST_FIRST(&gp->consumer);
354 if (ms->labeloffset == ALPHA_LABEL_OFFSET) {
355 sum = 0;
356 for (i = 0; i < 63; i++)
357 sum += le64dec(buf + i * 8);
358 le64enc(buf + 504, sum);
359 }
360 error = g_write_data(cp, 0, buf, BBSIZE);
361 }
362 g_topology_unlock();
363 PICKUP_GIANT();
364 }
365 g_free(buf);
366 return (error);
367 }
368 case DIOCSDINFO:
369 case DIOCWDINFO: {
370 label = g_malloc(LABELSIZE, M_WAITOK);
371
372 /* The disklabel to set is the ioctl argument. */
373 bsd_disklabel_le_enc(label, data);
374
375 DROP_GIANT();
376 g_topology_lock();
377 /* Validate and modify our slice instance to match. */
378 error = g_bsd_modify(gp, label);
379 if (error == 0 && cmd == DIOCWDINFO)
380 error = g_bsd_writelabel(gp, NULL);
381 g_topology_unlock();
382 PICKUP_GIANT();
383 g_free(label);
384 return(error);
385 }
386 default:
387 return (ENOIOCTL);
388 }
389}
390
391static int
392g_bsd_start(struct bio *bp)
393{
394 struct g_geom *gp;
395 struct g_bsd_softc *ms;
396 struct g_slicer *gsp;
397
398 gp = bp->bio_to->geom;
399 gsp = gp->softc;
400 ms = gsp->softc;
401 if (bp->bio_cmd == BIO_GETATTR) {
402 if (g_handleattr(bp, "BSD::labelsum", ms->labelsum,
403 sizeof(ms->labelsum)))
404 return (1);
405 }
406 return (0);
407}
408
409/*
410 * Dump configuration information in XML format.
411 * Notice that the function is called once for the geom and once for each
412 * consumer and provider. We let g_slice_dumpconf() do most of the work.
413 */
414static void
415g_bsd_dumpconf(struct sbuf *sb, const char *indent, struct g_geom *gp, struct g_consumer *cp, struct g_provider *pp)
416{
417 struct g_bsd_softc *ms;
418 struct g_slicer *gsp;
419
420 gsp = gp->softc;
421 ms = gsp->softc;
422 g_slice_dumpconf(sb, indent, gp, cp, pp);
423 if (indent != NULL && pp == NULL && cp == NULL) {
424 sbuf_printf(sb, "%s<labeloffset>%jd</labeloffset>\n",
425 indent, (intmax_t)ms->labeloffset);
426 sbuf_printf(sb, "%s<rawoffset>%jd</rawoffset>\n",
427 indent, (intmax_t)ms->rawoffset);
428 sbuf_printf(sb, "%s<mbroffset>%jd</mbroffset>\n",
429 indent, (intmax_t)ms->mbroffset);
430 } else if (pp != NULL) {
431 if (indent == NULL)
432 sbuf_printf(sb, " ty %d",
433 ms->ondisk.d_partitions[pp->index].p_fstype);
434 else
435 sbuf_printf(sb, "%s<type>%d</type>\n", indent,
436 ms->ondisk.d_partitions[pp->index].p_fstype);
437 }
438}
439
440/*
441 * The taste function is called from the event-handler, with the topology
442 * lock already held and a provider to examine. The flags are unused.
443 *
444 * If flags == G_TF_NORMAL, the idea is to take a bite of the provider and
445 * if we find valid, consistent magic on it, build a geom on it.
446 * any magic bits which indicate that we should automatically put a BSD
447 * geom on it.
448 *
449 * There may be cases where the operator would like to put a BSD-geom on
450 * providers which do not meet all of the requirements. This can be done
451 * by instead passing the G_TF_INSIST flag, which will override these
452 * checks.
453 *
454 * The final flags value is G_TF_TRANSPARENT, which instructs the method
455 * to put a geom on top of the provider and configure it to be as transparent
456 * as possible. This is not really relevant to the BSD method and therefore
457 * not implemented here.
458 */
459
460static struct g_geom *
461g_bsd_taste(struct g_class *mp, struct g_provider *pp, int flags)
462{
463 struct g_geom *gp;
464 struct g_consumer *cp;
465 int error, i;
466 struct g_bsd_softc *ms;
467 u_int secsize;
468 struct g_slicer *gsp;
469 u_char hash[16];
470 MD5_CTX md5sum;
471
472 g_trace(G_T_TOPOLOGY, "bsd_taste(%s,%s)", mp->name, pp->name);
473 g_topology_assert();
474
475 /* We don't implement transparent inserts. */
476 if (flags == G_TF_TRANSPARENT)
477 return (NULL);
478
479 /*
480 * BSD labels are a subclass of the general "slicing" topology so
481 * a lot of the work can be done by the common "slice" code.
482 * Create a geom with space for MAXPARTITIONS providers, one consumer
483 * and a softc structure for us. Specify the provider to attach
484 * the consumer to and our "start" routine for special requests.
485 * The provider is opened with mode (1,0,0) so we can do reads
486 * from it.
487 */
488 gp = g_slice_new(mp, MAXPARTITIONS, pp, &cp, &ms,
489 sizeof(*ms), g_bsd_start);
490 if (gp == NULL)
491 return (NULL);
492
493 /*
494 * Fill in the optional details, in our case we have a dumpconf
495 * routine which the "slice" code should call at the right time
496 */
497 gp->dumpconf = g_bsd_dumpconf;
498 gp->ioctl = g_bsd_ioctl;
499
500 /* Get the geom_slicer softc from the geom. */
501 gsp = gp->softc;
502
503 /*
504 * The do...while loop here allows us to have multiple escapes
505 * using a simple "break". This improves code clarity without
506 * ending up in deep nesting and without using goto or come from.
507 */
508 do {
509 /*
510 * If the provider is an MBR we will only auto attach
511 * to type 165 slices in the G_TF_NORMAL case. We will
512 * attach to any other type.
513 */
514 error = g_getattr("MBR::type", cp, &i);
515 if (!error) {
516 if (i != 165 && flags == G_TF_NORMAL)
517 break;
518 error = g_getattr("MBR::offset", cp, &ms->mbroffset);
519 if (error)
520 break;
521 }
522
523 /* Same thing if we are inside a PC98 */
524 error = g_getattr("PC98::type", cp, &i);
525 if (!error) {
526 if (i != 0xc494 && flags == G_TF_NORMAL)
527 break;
528 error = g_getattr("PC98::offset", cp, &ms->mbroffset);
529 if (error)
530 break;
531 }
532
533 /* Get sector size, we need it to read data. */
534 secsize = cp->provider->sectorsize;
535 if (secsize < 512)
536 break;
537
538 /* First look for a label at the start of the second sector. */
539 error = g_bsd_try(gp, gsp, cp, secsize, ms, secsize);
540
541 /* Next, look for alpha labels */
542 if (error)
543 error = g_bsd_try(gp, gsp, cp, secsize, ms,
544 ALPHA_LABEL_OFFSET);
545
546 /* If we didn't find a label, punt. */
547 if (error)
548 break;
549
550 /*
551 * In order to avoid recursively attaching to the same
552 * on-disk label (it's usually visible through the 'c'
553 * partition) we calculate an MD5 and ask if other BSD's
554 * below us love that label. If they do, we don't.
555 */
556 MD5Init(&md5sum);
557 MD5Update(&md5sum, ms->label, sizeof(ms->label));
558 MD5Final(ms->labelsum, &md5sum);
559
560 error = g_getattr("BSD::labelsum", cp, &hash);
561 if (!error && !bcmp(ms->labelsum, hash, sizeof(hash)))
562 break;
563
564 /*
565 * Process the found disklabel, and modify our "slice"
566 * instance to match it, if possible.
567 */
568 error = g_bsd_modify(gp, ms->label);
569 } while (0);
570
571 /* Success or failure, we can close our provider now. */
572 error = g_access_rel(cp, -1, 0, 0);
573
574 /* If we have configured any providers, return the new geom. */
575 if (gsp->nprovider > 0) {
576 g_slice_conf_hot(gp, 0, ms->labeloffset, LABELSIZE,
577 G_SLICE_HOT_ALLOW, G_SLICE_HOT_DENY, G_SLICE_HOT_CALL);
578 gsp->hot = g_bsd_hotwrite;
579 return (gp);
580 }
581 /*
582 * ...else push the "self-destruct" button, by spoiling our own
583 * consumer. This triggers a call to g_slice_spoiled which will
584 * dismantle what was setup.
585 */
586 g_slice_spoiled(cp);
587 return (NULL);
588}
589
590struct h0h0 {
591 struct g_geom *gp;
592 struct g_bsd_softc *ms;
593 u_char *label;
594 int error;
595};
596
597static void
598g_bsd_callconfig(void *arg, int flag)
599{
600 struct h0h0 *hp;
601
602 hp = arg;
603 hp->error = g_bsd_modify(hp->gp, hp->label);
604 if (!hp->error)
605 hp->error = g_bsd_writelabel(hp->gp, NULL);
606}
607
608/*
609 * NB! curthread is user process which GCTL'ed.
610 */
611static void
612g_bsd_config(struct gctl_req *req, struct g_class *mp, char const *verb)
613{
614 u_char *label;
615 int error;
616 struct h0h0 h0h0;
617 struct g_geom *gp;
618 struct g_slicer *gsp;
619 struct g_consumer *cp;
620 struct g_bsd_softc *ms;
621
622 g_topology_assert();
623 gp = gctl_get_geom(req, mp, "geom");
624 if (gp == NULL)
625 return;
626 cp = LIST_FIRST(&gp->consumer);
627 gsp = gp->softc;
628 ms = gsp->softc;
629 if (!strcmp(verb, "read mbroffset")) {
630 gctl_set_param(req, "mbroffset",
631 &ms->mbroffset, sizeof(ms->mbroffset));
632 return;
633 } else if (!strcmp(verb, "write label")) {
634 label = gctl_get_paraml(req, "label", LABELSIZE);
635 if (label == NULL)
636 return;
637 h0h0.gp = gp;
638 h0h0.ms = gsp->softc;
639 h0h0.label = label;
640 h0h0.error = -1;
641 /* XXX: Does this reference register with our selfdestruct code ? */
642 error = g_access_rel(cp, 1, 1, 1);
643 if (error) {
644 gctl_error(req, "could not access consumer");
645 return;
646 }
647 g_bsd_callconfig(&h0h0, 0);
648 error = h0h0.error;
649 g_access_rel(cp, -1, -1, -1);
650 } else if (!strcmp(verb, "write bootcode")) {
651 label = gctl_get_paraml(req, "bootcode", BBSIZE);
652 if (label == NULL)
653 return;
654 /* XXX: Does this reference register with our selfdestruct code ? */
655 error = g_access_rel(cp, 1, 1, 1);
656 if (error) {
657 gctl_error(req, "could not access consumer");
658 return;
659 }
660 error = g_bsd_writelabel(gp, label);
661 g_access_rel(cp, -1, -1, -1);
662 } else {
663 gctl_error(req, "Unknown verb parameter");
664 }
665
666 return;
667}
668
669/* Finally, register with GEOM infrastructure. */
670static struct g_class g_bsd_class = {
671 .name = BSD_CLASS_NAME,
672 .taste = g_bsd_taste,
673 .ctlreq = g_bsd_config,
674};
675
676DECLARE_GEOM_CLASS(g_bsd_class, g_bsd);
2 * Copyright (c) 2002 Poul-Henning Kamp
3 * Copyright (c) 2002 Networks Associates Technology, Inc.
4 * All rights reserved.
5 *
6 * This software was developed for the FreeBSD Project by Poul-Henning Kamp
7 * and NAI Labs, the Security Research Division of Network Associates, Inc.
8 * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the
9 * DARPA CHATS research program.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. The names of the authors may not be used to endorse or promote
20 * products derived from this software without specific prior written
21 * permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 *
35 * This is the method for dealing with BSD disklabels. It has been
36 * extensively (by my standards at least) commented, in the vain hope that
37 * it will serve as the source in future copy&paste operations.
38 */
39
40#include <sys/cdefs.h>
41__FBSDID("$FreeBSD: head/sys/geom/geom_bsd.c 119660 2003-09-01 20:45:32Z phk $");
42
43#include <sys/param.h>
44#include <sys/endian.h>
45#include <sys/systm.h>
46#include <sys/kernel.h>
47#include <sys/conf.h>
48#include <sys/bio.h>
49#include <sys/malloc.h>
50#include <sys/lock.h>
51#include <sys/mutex.h>
52#include <sys/md5.h>
53#include <sys/errno.h>
54#include <sys/disklabel.h>
55#include <geom/geom.h>
56#include <geom/geom_slice.h>
57
58#define BSD_CLASS_NAME "BSD"
59
60#define ALPHA_LABEL_OFFSET 64
61
62#define LABELSIZE (148 + 16 * MAXPARTITIONS)
63
64static void g_bsd_hotwrite(void *arg, int flag);
65/*
66 * Our private data about one instance. All the rest is handled by the
67 * slice code and stored in its softc, so this is just the stuff
68 * specific to BSD disklabels.
69 */
70struct g_bsd_softc {
71 off_t labeloffset;
72 off_t mbroffset;
73 off_t rawoffset;
74 struct disklabel ondisk;
75 u_char label[LABELSIZE];
76 u_char labelsum[16];
77};
78
79/*
80 * Modify our slicer to match proposed disklabel, if possible.
81 * This is where we make sure we don't do something stupid.
82 */
83static int
84g_bsd_modify(struct g_geom *gp, u_char *label)
85{
86 int i, error;
87 struct partition *ppp;
88 struct g_slicer *gsp;
89 struct g_consumer *cp;
90 struct g_bsd_softc *ms;
91 u_int secsize, u;
92 off_t rawoffset, o;
93 struct disklabel dl;
94 MD5_CTX md5sum;
95
96 g_topology_assert();
97 gsp = gp->softc;
98 ms = gsp->softc;
99
100 error = bsd_disklabel_le_dec(label, &dl, MAXPARTITIONS);
101 if (error) {
102 return (error);
103 }
104
105 /* Get dimensions of our device. */
106 cp = LIST_FIRST(&gp->consumer);
107 secsize = cp->provider->sectorsize;
108
109 /* ... or a smaller sector size. */
110 if (dl.d_secsize < secsize) {
111 return (EINVAL);
112 }
113
114 /* ... or a non-multiple sector size. */
115 if (dl.d_secsize % secsize != 0) {
116 return (EINVAL);
117 }
118
119 /* Historical braindamage... */
120 rawoffset = (off_t)dl.d_partitions[RAW_PART].p_offset * dl.d_secsize;
121
122 for (i = 0; i < dl.d_npartitions; i++) {
123 ppp = &dl.d_partitions[i];
124 if (ppp->p_size == 0)
125 continue;
126 o = (off_t)ppp->p_offset * dl.d_secsize;
127
128 if (o < rawoffset)
129 rawoffset = 0;
130 }
131
132 if (rawoffset != 0 && (off_t)rawoffset != ms->mbroffset)
133 printf("WARNING: Expected rawoffset %jd, found %jd\n",
134 (intmax_t)ms->mbroffset/dl.d_secsize,
135 (intmax_t)rawoffset/dl.d_secsize);
136
137 /* Don't munge open partitions. */
138 for (i = 0; i < dl.d_npartitions; i++) {
139 ppp = &dl.d_partitions[i];
140
141 o = (off_t)ppp->p_offset * dl.d_secsize;
142 if (o == 0)
143 o = rawoffset;
144 error = g_slice_config(gp, i, G_SLICE_CONFIG_CHECK,
145 o - rawoffset,
146 (off_t)ppp->p_size * dl.d_secsize,
147 dl.d_secsize,
148 "%s%c", gp->name, 'a' + i);
149 if (error)
150 return (error);
151 }
152
153 /* Look good, go for it... */
154 for (u = 0; u < gsp->nslice; u++) {
155 ppp = &dl.d_partitions[u];
156 o = (off_t)ppp->p_offset * dl.d_secsize;
157 if (o == 0)
158 o = rawoffset;
159 g_slice_config(gp, u, G_SLICE_CONFIG_SET,
160 o - rawoffset,
161 (off_t)ppp->p_size * dl.d_secsize,
162 dl.d_secsize,
163 "%s%c", gp->name, 'a' + u);
164 }
165
166 /* Update our softc */
167 ms->ondisk = dl;
168 if (label != ms->label)
169 bcopy(label, ms->label, LABELSIZE);
170 ms->rawoffset = rawoffset;
171
172 /*
173 * In order to avoid recursively attaching to the same
174 * on-disk label (it's usually visible through the 'c'
175 * partition) we calculate an MD5 and ask if other BSD's
176 * below us love that label. If they do, we don't.
177 */
178 MD5Init(&md5sum);
179 MD5Update(&md5sum, ms->label, sizeof(ms->label));
180 MD5Final(ms->labelsum, &md5sum);
181
182 return (0);
183}
184
185/*
186 * This is an internal helper function, called multiple times from the taste
187 * function to try to locate a disklabel on the disk. More civilized formats
188 * will not need this, as there is only one possible place on disk to look
189 * for the magic spot.
190 */
191
192static int
193g_bsd_try(struct g_geom *gp, struct g_slicer *gsp, struct g_consumer *cp, int secsize, struct g_bsd_softc *ms, off_t offset)
194{
195 int error;
196 u_char *buf;
197 struct disklabel *dl;
198 off_t secoff;
199
200 /*
201 * We need to read entire aligned sectors, and we assume that the
202 * disklabel does not span sectors, so one sector is enough.
203 */
204 error = 0;
205 secoff = offset % secsize;
206 buf = g_read_data(cp, offset - secoff, secsize, &error);
207 if (buf == NULL || error != 0)
208 return (ENOENT);
209
210 /* Decode into our native format. */
211 dl = &ms->ondisk;
212 error = bsd_disklabel_le_dec(buf + secoff, dl, MAXPARTITIONS);
213 if (!error)
214 bcopy(buf + secoff, ms->label, LABELSIZE);
215
216 /* Remember to free the buffer g_read_data() gave us. */
217 g_free(buf);
218
219 ms->labeloffset = offset;
220 return (error);
221}
222
223/*
224 * This function writes the current label to disk, possibly updating
225 * the alpha SRM checksum.
226 */
227
228static int
229g_bsd_writelabel(struct g_geom *gp, u_char *bootcode)
230{
231 off_t secoff;
232 u_int secsize;
233 struct g_consumer *cp;
234 struct g_slicer *gsp;
235 struct g_bsd_softc *ms;
236 u_char *buf;
237 uint64_t sum;
238 int error, i;
239
240 gsp = gp->softc;
241 ms = gsp->softc;
242 cp = LIST_FIRST(&gp->consumer);
243 /* Get sector size, we need it to read data. */
244 secsize = cp->provider->sectorsize;
245 secoff = ms->labeloffset % secsize;
246 if (bootcode == NULL) {
247 buf = g_read_data(cp, ms->labeloffset - secoff, secsize, &error);
248 if (buf == NULL || error != 0)
249 return (error);
250 bcopy(ms->label, buf + secoff, sizeof(ms->label));
251 } else {
252 buf = bootcode;
253 bcopy(ms->label, buf + ms->labeloffset, sizeof(ms->label));
254 }
255 if (ms->labeloffset == ALPHA_LABEL_OFFSET) {
256 sum = 0;
257 for (i = 0; i < 63; i++)
258 sum += le64dec(buf + i * 8);
259 le64enc(buf + 504, sum);
260 }
261 if (bootcode == NULL) {
262 error = g_write_data(cp, ms->labeloffset - secoff, buf, secsize);
263 g_free(buf);
264 } else {
265 error = g_write_data(cp, 0, bootcode, BBSIZE);
266 }
267 return(error);
268}
269
270/*
271 * If the user tries to overwrite our disklabel through an open partition
272 * or via a magicwrite config call, we end up here and try to prevent
273 * footshooting as best we can.
274 */
275static void
276g_bsd_hotwrite(void *arg, int flag)
277{
278 struct bio *bp;
279 struct g_geom *gp;
280 struct g_slicer *gsp;
281 struct g_slice *gsl;
282 struct g_bsd_softc *ms;
283 u_char *p;
284 int error;
285
286 g_topology_assert();
287 /*
288 * We should never get canceled, because that would amount to a removal
289 * of the geom while there was outstanding I/O requests.
290 */
291 KASSERT(flag != EV_CANCEL, ("g_bsd_hotwrite cancelled"));
292 bp = arg;
293 gp = bp->bio_to->geom;
294 gsp = gp->softc;
295 ms = gsp->softc;
296 gsl = &gsp->slices[bp->bio_to->index];
297 p = (u_char*)bp->bio_data + ms->labeloffset
298 - (bp->bio_offset + gsl->offset);
299 error = g_bsd_modify(gp, p);
300 if (error) {
301 g_io_deliver(bp, EPERM);
302 return;
303 }
304 g_slice_finish_hot(bp);
305}
306
307/*-
308 * This start routine is only called for non-trivial requests, all the
309 * trivial ones are handled autonomously by the slice code.
310 * For requests we handle here, we must call the g_io_deliver() on the
311 * bio, and return non-zero to indicate to the slice code that we did so.
312 * This code executes in the "DOWN" I/O path, this means:
313 * * No sleeping.
314 * * Don't grab the topology lock.
315 * * Don't call biowait, g_getattr(), g_setattr() or g_read_data()
316 */
317static int
318g_bsd_ioctl(struct g_provider *pp, u_long cmd, void * data, struct thread *td)
319{
320 struct g_geom *gp;
321 struct g_bsd_softc *ms;
322 struct g_slicer *gsp;
323 u_char *label;
324 int error;
325
326 gp = pp->geom;
327 gsp = gp->softc;
328 ms = gsp->softc;
329
330 switch(cmd) {
331 case DIOCGDINFO:
332 /* Return a copy of the disklabel to userland. */
333 bsd_disklabel_le_dec(ms->label, data, MAXPARTITIONS);
334 return(0);
335 case DIOCBSDBB: {
336 struct g_consumer *cp;
337 u_char *buf;
338 void *p;
339 int error, i;
340 uint64_t sum;
341
342 /* The disklabel to set is the ioctl argument. */
343 buf = g_malloc(BBSIZE, M_WAITOK);
344 p = *(void **)data;
345 error = copyin(p, buf, BBSIZE);
346 if (!error) {
347 /* XXX: Rude, but supposedly safe */
348 DROP_GIANT();
349 g_topology_lock();
350 /* Validate and modify our slice instance to match. */
351 error = g_bsd_modify(gp, buf + ms->labeloffset);
352 if (!error) {
353 cp = LIST_FIRST(&gp->consumer);
354 if (ms->labeloffset == ALPHA_LABEL_OFFSET) {
355 sum = 0;
356 for (i = 0; i < 63; i++)
357 sum += le64dec(buf + i * 8);
358 le64enc(buf + 504, sum);
359 }
360 error = g_write_data(cp, 0, buf, BBSIZE);
361 }
362 g_topology_unlock();
363 PICKUP_GIANT();
364 }
365 g_free(buf);
366 return (error);
367 }
368 case DIOCSDINFO:
369 case DIOCWDINFO: {
370 label = g_malloc(LABELSIZE, M_WAITOK);
371
372 /* The disklabel to set is the ioctl argument. */
373 bsd_disklabel_le_enc(label, data);
374
375 DROP_GIANT();
376 g_topology_lock();
377 /* Validate and modify our slice instance to match. */
378 error = g_bsd_modify(gp, label);
379 if (error == 0 && cmd == DIOCWDINFO)
380 error = g_bsd_writelabel(gp, NULL);
381 g_topology_unlock();
382 PICKUP_GIANT();
383 g_free(label);
384 return(error);
385 }
386 default:
387 return (ENOIOCTL);
388 }
389}
390
391static int
392g_bsd_start(struct bio *bp)
393{
394 struct g_geom *gp;
395 struct g_bsd_softc *ms;
396 struct g_slicer *gsp;
397
398 gp = bp->bio_to->geom;
399 gsp = gp->softc;
400 ms = gsp->softc;
401 if (bp->bio_cmd == BIO_GETATTR) {
402 if (g_handleattr(bp, "BSD::labelsum", ms->labelsum,
403 sizeof(ms->labelsum)))
404 return (1);
405 }
406 return (0);
407}
408
409/*
410 * Dump configuration information in XML format.
411 * Notice that the function is called once for the geom and once for each
412 * consumer and provider. We let g_slice_dumpconf() do most of the work.
413 */
414static void
415g_bsd_dumpconf(struct sbuf *sb, const char *indent, struct g_geom *gp, struct g_consumer *cp, struct g_provider *pp)
416{
417 struct g_bsd_softc *ms;
418 struct g_slicer *gsp;
419
420 gsp = gp->softc;
421 ms = gsp->softc;
422 g_slice_dumpconf(sb, indent, gp, cp, pp);
423 if (indent != NULL && pp == NULL && cp == NULL) {
424 sbuf_printf(sb, "%s<labeloffset>%jd</labeloffset>\n",
425 indent, (intmax_t)ms->labeloffset);
426 sbuf_printf(sb, "%s<rawoffset>%jd</rawoffset>\n",
427 indent, (intmax_t)ms->rawoffset);
428 sbuf_printf(sb, "%s<mbroffset>%jd</mbroffset>\n",
429 indent, (intmax_t)ms->mbroffset);
430 } else if (pp != NULL) {
431 if (indent == NULL)
432 sbuf_printf(sb, " ty %d",
433 ms->ondisk.d_partitions[pp->index].p_fstype);
434 else
435 sbuf_printf(sb, "%s<type>%d</type>\n", indent,
436 ms->ondisk.d_partitions[pp->index].p_fstype);
437 }
438}
439
440/*
441 * The taste function is called from the event-handler, with the topology
442 * lock already held and a provider to examine. The flags are unused.
443 *
444 * If flags == G_TF_NORMAL, the idea is to take a bite of the provider and
445 * if we find valid, consistent magic on it, build a geom on it.
446 * any magic bits which indicate that we should automatically put a BSD
447 * geom on it.
448 *
449 * There may be cases where the operator would like to put a BSD-geom on
450 * providers which do not meet all of the requirements. This can be done
451 * by instead passing the G_TF_INSIST flag, which will override these
452 * checks.
453 *
454 * The final flags value is G_TF_TRANSPARENT, which instructs the method
455 * to put a geom on top of the provider and configure it to be as transparent
456 * as possible. This is not really relevant to the BSD method and therefore
457 * not implemented here.
458 */
459
460static struct g_geom *
461g_bsd_taste(struct g_class *mp, struct g_provider *pp, int flags)
462{
463 struct g_geom *gp;
464 struct g_consumer *cp;
465 int error, i;
466 struct g_bsd_softc *ms;
467 u_int secsize;
468 struct g_slicer *gsp;
469 u_char hash[16];
470 MD5_CTX md5sum;
471
472 g_trace(G_T_TOPOLOGY, "bsd_taste(%s,%s)", mp->name, pp->name);
473 g_topology_assert();
474
475 /* We don't implement transparent inserts. */
476 if (flags == G_TF_TRANSPARENT)
477 return (NULL);
478
479 /*
480 * BSD labels are a subclass of the general "slicing" topology so
481 * a lot of the work can be done by the common "slice" code.
482 * Create a geom with space for MAXPARTITIONS providers, one consumer
483 * and a softc structure for us. Specify the provider to attach
484 * the consumer to and our "start" routine for special requests.
485 * The provider is opened with mode (1,0,0) so we can do reads
486 * from it.
487 */
488 gp = g_slice_new(mp, MAXPARTITIONS, pp, &cp, &ms,
489 sizeof(*ms), g_bsd_start);
490 if (gp == NULL)
491 return (NULL);
492
493 /*
494 * Fill in the optional details, in our case we have a dumpconf
495 * routine which the "slice" code should call at the right time
496 */
497 gp->dumpconf = g_bsd_dumpconf;
498 gp->ioctl = g_bsd_ioctl;
499
500 /* Get the geom_slicer softc from the geom. */
501 gsp = gp->softc;
502
503 /*
504 * The do...while loop here allows us to have multiple escapes
505 * using a simple "break". This improves code clarity without
506 * ending up in deep nesting and without using goto or come from.
507 */
508 do {
509 /*
510 * If the provider is an MBR we will only auto attach
511 * to type 165 slices in the G_TF_NORMAL case. We will
512 * attach to any other type.
513 */
514 error = g_getattr("MBR::type", cp, &i);
515 if (!error) {
516 if (i != 165 && flags == G_TF_NORMAL)
517 break;
518 error = g_getattr("MBR::offset", cp, &ms->mbroffset);
519 if (error)
520 break;
521 }
522
523 /* Same thing if we are inside a PC98 */
524 error = g_getattr("PC98::type", cp, &i);
525 if (!error) {
526 if (i != 0xc494 && flags == G_TF_NORMAL)
527 break;
528 error = g_getattr("PC98::offset", cp, &ms->mbroffset);
529 if (error)
530 break;
531 }
532
533 /* Get sector size, we need it to read data. */
534 secsize = cp->provider->sectorsize;
535 if (secsize < 512)
536 break;
537
538 /* First look for a label at the start of the second sector. */
539 error = g_bsd_try(gp, gsp, cp, secsize, ms, secsize);
540
541 /* Next, look for alpha labels */
542 if (error)
543 error = g_bsd_try(gp, gsp, cp, secsize, ms,
544 ALPHA_LABEL_OFFSET);
545
546 /* If we didn't find a label, punt. */
547 if (error)
548 break;
549
550 /*
551 * In order to avoid recursively attaching to the same
552 * on-disk label (it's usually visible through the 'c'
553 * partition) we calculate an MD5 and ask if other BSD's
554 * below us love that label. If they do, we don't.
555 */
556 MD5Init(&md5sum);
557 MD5Update(&md5sum, ms->label, sizeof(ms->label));
558 MD5Final(ms->labelsum, &md5sum);
559
560 error = g_getattr("BSD::labelsum", cp, &hash);
561 if (!error && !bcmp(ms->labelsum, hash, sizeof(hash)))
562 break;
563
564 /*
565 * Process the found disklabel, and modify our "slice"
566 * instance to match it, if possible.
567 */
568 error = g_bsd_modify(gp, ms->label);
569 } while (0);
570
571 /* Success or failure, we can close our provider now. */
572 error = g_access_rel(cp, -1, 0, 0);
573
574 /* If we have configured any providers, return the new geom. */
575 if (gsp->nprovider > 0) {
576 g_slice_conf_hot(gp, 0, ms->labeloffset, LABELSIZE,
577 G_SLICE_HOT_ALLOW, G_SLICE_HOT_DENY, G_SLICE_HOT_CALL);
578 gsp->hot = g_bsd_hotwrite;
579 return (gp);
580 }
581 /*
582 * ...else push the "self-destruct" button, by spoiling our own
583 * consumer. This triggers a call to g_slice_spoiled which will
584 * dismantle what was setup.
585 */
586 g_slice_spoiled(cp);
587 return (NULL);
588}
589
590struct h0h0 {
591 struct g_geom *gp;
592 struct g_bsd_softc *ms;
593 u_char *label;
594 int error;
595};
596
597static void
598g_bsd_callconfig(void *arg, int flag)
599{
600 struct h0h0 *hp;
601
602 hp = arg;
603 hp->error = g_bsd_modify(hp->gp, hp->label);
604 if (!hp->error)
605 hp->error = g_bsd_writelabel(hp->gp, NULL);
606}
607
608/*
609 * NB! curthread is user process which GCTL'ed.
610 */
611static void
612g_bsd_config(struct gctl_req *req, struct g_class *mp, char const *verb)
613{
614 u_char *label;
615 int error;
616 struct h0h0 h0h0;
617 struct g_geom *gp;
618 struct g_slicer *gsp;
619 struct g_consumer *cp;
620 struct g_bsd_softc *ms;
621
622 g_topology_assert();
623 gp = gctl_get_geom(req, mp, "geom");
624 if (gp == NULL)
625 return;
626 cp = LIST_FIRST(&gp->consumer);
627 gsp = gp->softc;
628 ms = gsp->softc;
629 if (!strcmp(verb, "read mbroffset")) {
630 gctl_set_param(req, "mbroffset",
631 &ms->mbroffset, sizeof(ms->mbroffset));
632 return;
633 } else if (!strcmp(verb, "write label")) {
634 label = gctl_get_paraml(req, "label", LABELSIZE);
635 if (label == NULL)
636 return;
637 h0h0.gp = gp;
638 h0h0.ms = gsp->softc;
639 h0h0.label = label;
640 h0h0.error = -1;
641 /* XXX: Does this reference register with our selfdestruct code ? */
642 error = g_access_rel(cp, 1, 1, 1);
643 if (error) {
644 gctl_error(req, "could not access consumer");
645 return;
646 }
647 g_bsd_callconfig(&h0h0, 0);
648 error = h0h0.error;
649 g_access_rel(cp, -1, -1, -1);
650 } else if (!strcmp(verb, "write bootcode")) {
651 label = gctl_get_paraml(req, "bootcode", BBSIZE);
652 if (label == NULL)
653 return;
654 /* XXX: Does this reference register with our selfdestruct code ? */
655 error = g_access_rel(cp, 1, 1, 1);
656 if (error) {
657 gctl_error(req, "could not access consumer");
658 return;
659 }
660 error = g_bsd_writelabel(gp, label);
661 g_access_rel(cp, -1, -1, -1);
662 } else {
663 gctl_error(req, "Unknown verb parameter");
664 }
665
666 return;
667}
668
669/* Finally, register with GEOM infrastructure. */
670static struct g_class g_bsd_class = {
671 .name = BSD_CLASS_NAME,
672 .taste = g_bsd_taste,
673 .ctlreq = g_bsd_config,
674};
675
676DECLARE_GEOM_CLASS(g_bsd_class, g_bsd);