xref: /titanic_51/usr/src/boot/sys/boot/common/disk.c (revision 0ab6d540bdecde4862447c171df9ad7f68dd3710)
1 /*-
2  * Copyright (c) 1998 Michael Smith <msmith@freebsd.org>
3  * Copyright (c) 2012 Andrey V. Elsukov <ae@FreeBSD.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
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 AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <sys/cdefs.h>
29 #include <sys/disk.h>
30 #include <sys/queue.h>
31 #include <stand.h>
32 #include <stdarg.h>
33 #include <bootstrap.h>
34 #include <part.h>
35 
36 #include "disk.h"
37 
38 #ifdef DISK_DEBUG
39 # define DEBUG(fmt, args...)	printf("%s: " fmt "\n" , __func__ , ## args)
40 #else
41 # define DEBUG(fmt, args...)
42 #endif
43 
44 struct open_disk {
45 	struct ptable		*table;
46 	uint64_t		mediasize;
47 	uint64_t		entrysize;
48 	u_int			sectorsize;
49 };
50 
51 struct print_args {
52 	struct disk_devdesc	*dev;
53 	const char		*prefix;
54 	int			verbose;
55 };
56 
57 /* Convert size to a human-readable number. */
58 static char *
59 display_size(uint64_t size, u_int sectorsize)
60 {
61 	static char buf[80];
62 	char unit;
63 
64 	size = size * sectorsize / 1024;
65 	unit = 'K';
66 	if (size >= 10485760000LL) {
67 		size /= 1073741824;
68 		unit = 'T';
69 	} else if (size >= 10240000) {
70 		size /= 1048576;
71 		unit = 'G';
72 	} else if (size >= 10000) {
73 		size /= 1024;
74 		unit = 'M';
75 	}
76 	sprintf(buf, "%ld%cB", (long)size, unit);
77 	return (buf);
78 }
79 
80 static int
81 ptblread(void *d, void *buf, size_t blocks, uint64_t offset)
82 {
83 	struct disk_devdesc *dev;
84 	struct open_disk *od;
85 
86 	dev = (struct disk_devdesc *)d;
87 	od = (struct open_disk *)dev->d_opendata;
88 
89 	/*
90 	 * The strategy function assumes the offset is in units of 512 byte
91 	 * sectors. For larger sector sizes, we need to adjust the offset to
92 	 * match the actual sector size.
93 	 */
94 	offset *= (od->sectorsize / 512);
95 	/*
96 	 * As the GPT backup partition is located at the end of the disk,
97 	 * to avoid reading past disk end, flag bcache not to use RA.
98 	 */
99 	return (dev->d_dev->dv_strategy(dev, F_READ | F_NORA , offset,
100 	    blocks * od->sectorsize, (char *)buf, NULL));
101 }
102 
103 #define	PWIDTH	35
104 static int
105 ptable_print(void *arg, const char *pname, const struct ptable_entry *part)
106 {
107 	struct disk_devdesc dev;
108 	struct print_args *pa, bsd;
109 	struct open_disk *od;
110 	struct ptable *table;
111 	char line[80];
112 	int ret = 0;
113 
114 	pa = (struct print_args *)arg;
115 	od = (struct open_disk *)pa->dev->d_opendata;
116 	sprintf(line, "  %s%s: %s", pa->prefix, pname,
117 	    parttype2str(part->type));
118 	if (pa->verbose)
119 		sprintf(line, "%-*s%s", PWIDTH, line,
120 		    display_size(part->end - part->start + 1,
121 		    od->sectorsize));
122 	strcat(line, "\n");
123 	ret = pager_output(line);
124 	if (ret != 0)
125 		return (ret);
126 	if (part->type == PART_FREEBSD || part->type == PART_SOLARIS2) {
127 		/* Open slice with BSD or VTOC label */
128 		dev.d_dev = pa->dev->d_dev;
129 		dev.d_unit = pa->dev->d_unit;
130 		dev.d_slice = part->index;
131 		dev.d_partition = -1;
132 		if (disk_open(&dev, part->end - part->start + 1,
133 		    od->sectorsize) == 0) {
134 			table = ptable_open(&dev, part->end - part->start + 1,
135 			    od->sectorsize, ptblread);
136 			if (table != NULL) {
137 				sprintf(line, "  %s%s", pa->prefix, pname);
138 				bsd.dev = &dev;
139 				bsd.prefix = line;
140 				bsd.verbose = pa->verbose;
141 				ret = ptable_iterate(table, &bsd, ptable_print);
142 				ptable_close(table);
143 			}
144 			disk_close(&dev);
145 		}
146 	}
147 	return (ret);
148 }
149 #undef PWIDTH
150 
151 int
152 disk_print(struct disk_devdesc *dev, char *prefix, int verbose)
153 {
154 	struct open_disk *od;
155 	struct print_args pa;
156 
157 	/* Disk should be opened */
158 	od = (struct open_disk *)dev->d_opendata;
159 	pa.dev = dev;
160 	pa.prefix = prefix;
161 	pa.verbose = verbose;
162 	return (ptable_iterate(od->table, &pa, ptable_print));
163 }
164 
165 int
166 disk_read(struct disk_devdesc *dev, void *buf, uint64_t offset, u_int blocks)
167 {
168 	struct open_disk *od;
169 	int ret;
170 
171 	od = (struct open_disk *)dev->d_opendata;
172 	ret = dev->d_dev->dv_strategy(dev, F_READ, dev->d_offset + offset,
173 	    blocks * od->sectorsize, buf, NULL);
174 
175 	return (ret);
176 }
177 
178 int
179 disk_write(struct disk_devdesc *dev, void *buf, uint64_t offset, u_int blocks)
180 {
181 	struct open_disk *od;
182 	int ret;
183 
184 	od = (struct open_disk *)dev->d_opendata;
185 	ret = dev->d_dev->dv_strategy(dev, F_WRITE, dev->d_offset + offset,
186 	    blocks * od->sectorsize, buf, NULL);
187 
188 	return (ret);
189 }
190 
191 int
192 disk_ioctl(struct disk_devdesc *dev, u_long cmd, void *data)
193 {
194 	struct open_disk *od = dev->d_opendata;
195 
196 	if (od == NULL)
197 		return (ENOTTY);
198 
199 	switch (cmd) {
200 	case DIOCGSECTORSIZE:
201 		*(u_int *)data = od->sectorsize;
202 		break;
203 	case DIOCGMEDIASIZE:
204 		if (dev->d_offset == 0)
205 			*(uint64_t *)data = od->mediasize;
206 		else
207 			*(uint64_t *)data = od->entrysize * od->sectorsize;
208 		break;
209 	default:
210 		return (ENOTTY);
211 	}
212 
213 	return (0);
214 }
215 
216 int
217 disk_open(struct disk_devdesc *dev, uint64_t mediasize, u_int sectorsize)
218 {
219 	struct open_disk *od;
220 	struct ptable *table;
221 	struct ptable_entry part;
222 	int rc, slice, partition;
223 
224 	rc = 0;
225 	/*
226 	 * While we are reading disk metadata, make sure we do it relative
227 	 * to the start of the disk
228 	 */
229 	dev->d_offset = 0;
230 	table = NULL;
231 	slice = dev->d_slice;
232 	partition = dev->d_partition;
233 	od = (struct open_disk *)malloc(sizeof(struct open_disk));
234 	if (od == NULL) {
235 		DEBUG("no memory");
236 		return (ENOMEM);
237 	}
238 	dev->d_opendata = od;
239 	od->entrysize = 0;
240 	od->mediasize = mediasize;
241 	od->sectorsize = sectorsize;
242 	DEBUG("%s unit %d, slice %d, partition %d => %p",
243 	    disk_fmtdev(dev), dev->d_unit, dev->d_slice, dev->d_partition, od);
244 
245 	/* Determine disk layout. */
246 	od->table = ptable_open(dev, mediasize / sectorsize, sectorsize,
247 	    ptblread);
248 	if (od->table == NULL) {
249 		DEBUG("Can't read partition table");
250 		rc = ENXIO;
251 		goto out;
252 	}
253 
254 	if (ptable_getsize(od->table, &mediasize) != 0) {
255 		rc = ENXIO;
256 		goto out;
257 	}
258 	if (mediasize > od->mediasize) {
259 		od->mediasize = mediasize;
260 	}
261 
262 	if (ptable_gettype(od->table) == PTABLE_BSD &&
263 	    partition >= 0) {
264 		/* It doesn't matter what value has d_slice */
265 		rc = ptable_getpart(od->table, &part, partition);
266 		if (rc == 0) {
267 			dev->d_offset = part.start;
268 			od->entrysize = part.end - part.start + 1;
269 		}
270 	} else if (slice >= 0) {
271 		/* Try to get information about partition */
272 		if (slice == 0)
273 			rc = ptable_getbestpart(od->table, &part);
274 		else
275 			rc = ptable_getpart(od->table, &part, slice);
276 		if (rc != 0) /* Partition doesn't exist */
277 			goto out;
278 		dev->d_offset = part.start;
279 		od->entrysize = part.end - part.start + 1;
280 		slice = part.index;
281 		if (ptable_gettype(od->table) == PTABLE_GPT) {
282 			partition = 255;
283 			goto out; /* Nothing more to do */
284 		} else if (partition == 255) {
285 			/*
286 			 * When we try to open GPT partition, but partition
287 			 * table isn't GPT, reset d_partition value to -1
288 			 * and try to autodetect appropriate value.
289 			 */
290 			partition = -1;
291 		}
292 		/*
293 		 * If d_partition < 0 and we are looking at a BSD/VTOC slice,
294 		 * then try to read label, otherwise return the
295 		 * whole MBR slice.
296 		 */
297 		if (partition == -1 &&
298 		    (part.type != PART_FREEBSD || part.type != PART_SOLARIS2))
299 			goto out;
300 		/* Try to read label */
301 		table = ptable_open(dev, part.end - part.start + 1,
302 		    od->sectorsize, ptblread);
303 		if (table == NULL) {
304 			DEBUG("Can't read BSD/VTOC label");
305 			rc = ENXIO;
306 			goto out;
307 		}
308 		/*
309 		 * If slice contains BSD/VTOC label and d_partition < 0, then
310 		 * assume the 'a' partition. Otherwise just return the
311 		 * whole MBR slice, because it can contain ZFS.
312 		 */
313 		if (partition < 0) {
314 			if (ptable_gettype(table) != PTABLE_BSD ||
315 			    ptable_gettype(table) != PTABLE_VTOC)
316 				goto out;
317 			partition = 0;
318 		}
319 		rc = ptable_getpart(table, &part, partition);
320 		if (rc != 0)
321 			goto out;
322 		dev->d_offset += part.start;
323 		od->entrysize = part.end - part.start + 1;
324 	}
325 out:
326 	if (table != NULL)
327 		ptable_close(table);
328 
329 	if (rc != 0) {
330 		if (od->table != NULL)
331 			ptable_close(od->table);
332 		free(od);
333 		DEBUG("%s could not open", disk_fmtdev(dev));
334 	} else {
335 		/* Save the slice and partition number to the dev */
336 		dev->d_slice = slice;
337 		dev->d_partition = partition;
338 		DEBUG("%s offset %lld => %p", disk_fmtdev(dev),
339 		    (long long)dev->d_offset, od);
340 	}
341 	return (rc);
342 }
343 
344 int
345 disk_close(struct disk_devdesc *dev)
346 {
347 	struct open_disk *od;
348 
349 	od = (struct open_disk *)dev->d_opendata;
350 	DEBUG("%s closed => %p", disk_fmtdev(dev), od);
351 	ptable_close(od->table);
352 	free(od);
353 	return (0);
354 }
355 
356 char*
357 disk_fmtdev(struct disk_devdesc *dev)
358 {
359 	static char buf[128];
360 	char *cp;
361 
362 	cp = buf + sprintf(buf, "%s%d", dev->d_dev->dv_name, dev->d_unit);
363 	if (dev->d_slice >= 0) {
364 #ifdef LOADER_GPT_SUPPORT
365 		if (dev->d_partition == 255) {
366 			sprintf(cp, "p%d:", dev->d_slice);
367 			return (buf);
368 		} else
369 #endif
370 #ifdef LOADER_MBR_SUPPORT
371 			cp += sprintf(cp, "s%d", dev->d_slice);
372 #endif
373 	}
374 	if (dev->d_partition >= 0)
375 		cp += sprintf(cp, "%c", dev->d_partition + 'a');
376 	strcat(cp, ":");
377 	return (buf);
378 }
379 
380 int
381 disk_parsedev(struct disk_devdesc *dev, const char *devspec, const char **path)
382 {
383 	int unit, slice, partition;
384 	const char *np;
385 	char *cp;
386 
387 	np = devspec;
388 	unit = slice = partition = -1;
389 	if (*np != '\0' && *np != ':') {
390 		unit = strtol(np, &cp, 10);
391 		if (cp == np)
392 			return (EUNIT);
393 #ifdef LOADER_GPT_SUPPORT
394 		if (*cp == 'p') {
395 			np = cp + 1;
396 			slice = strtol(np, &cp, 10);
397 			if (np == cp)
398 				return (ESLICE);
399 			/* we don't support nested partitions on GPT */
400 			if (*cp != '\0' && *cp != ':')
401 				return (EINVAL);
402 			partition = 255;
403 		} else
404 #endif
405 #ifdef LOADER_MBR_SUPPORT
406 		if (*cp == 's') {
407 			np = cp + 1;
408 			slice = strtol(np, &cp, 10);
409 			if (np == cp)
410 				return (ESLICE);
411 		}
412 #endif
413 		if (*cp != '\0' && *cp != ':') {
414 			partition = *cp - 'a';
415 			if (partition < 0)
416 				return (EPART);
417 			cp++;
418 		}
419 	} else
420 		return (EINVAL);
421 
422 	if (*cp != '\0' && *cp != ':')
423 		return (EINVAL);
424 	dev->d_unit = unit;
425 	dev->d_slice = slice;
426 	dev->d_partition = partition;
427 	if (path != NULL)
428 		*path = (*cp == '\0') ? cp: cp + 1;
429 	return (0);
430 }
431