xref: /freebsd/stand/common/disk.c (revision 397e83df75e0fcd0d3fcb95ae4d794cb7600fc89)
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/disk.h>
29 #include <sys/queue.h>
30 #include <stand.h>
31 #include <stdarg.h>
32 #include <bootstrap.h>
33 #include <part.h>
34 #include <assert.h>
35 
36 #include "disk.h"
37 
38 #ifdef DISK_DEBUG
39 # define DPRINTF(fmt, args...)	printf("%s: " fmt "\n" , __func__ , ## args)
40 #else
41 # define DPRINTF(fmt, args...)	((void)0)
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 	snprintf(buf, sizeof(buf), "%4ld%cB", (long)size, unit);
77 	return (buf);
78 }
79 
80 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->dd.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->dd.d_dev->dv_strategy(dev, F_READ | F_NORA, offset,
100 	    blocks * od->sectorsize, (char *)buf, NULL));
101 }
102 
103 static int
104 ptable_print(void *arg, const char *pname, const struct ptable_entry *part)
105 {
106 	struct disk_devdesc dev;
107 	struct print_args *pa, bsd;
108 	struct open_disk *od;
109 	struct ptable *table;
110 	char line[80];
111 	int res;
112 	u_int sectsize;
113 	uint64_t partsize;
114 
115 	pa = (struct print_args *)arg;
116 	od = (struct open_disk *)pa->dev->dd.d_opendata;
117 	sectsize = od->sectorsize;
118 	partsize = part->end - part->start + 1;
119 	snprintf(line, sizeof(line), "  %s%s: %s", pa->prefix, pname,
120 	    parttype2str(part->type));
121 	if (pager_output(line))
122 		return (1);
123 
124 	if (pa->verbose) {
125 		/* Emit extra tab when the line is shorter than 3 tab stops */
126 		if (strlen(line) < 24)
127 			(void) pager_output("\t");
128 
129 		snprintf(line, sizeof(line), "\t%s",
130 		    display_size(partsize, sectsize));
131 		if (pager_output(line))
132 			return (1);
133 	}
134 	if (pager_output("\n"))
135 		return (1);
136 
137 	res = 0;
138 	if (part->type == PART_FREEBSD) {
139 		/* Open slice with BSD label */
140 		dev.dd.d_dev = pa->dev->dd.d_dev;
141 		dev.dd.d_unit = pa->dev->dd.d_unit;
142 		dev.d_slice = part->index;
143 		dev.d_partition = D_PARTNONE;
144 		if (disk_open(&dev, partsize, sectsize) == 0) {
145 			table = ptable_open(&dev, partsize, sectsize, ptblread);
146 			if (table != NULL) {
147 				snprintf(line, sizeof(line), "  %s%s",
148 				    pa->prefix, pname);
149 				bsd.dev = pa->dev;
150 				bsd.prefix = line;
151 				bsd.verbose = pa->verbose;
152 				res = ptable_iterate(table, &bsd, ptable_print);
153 				ptable_close(table);
154 			}
155 			disk_close(&dev);
156 		}
157 	}
158 
159 	return (res);
160 }
161 
162 int
163 disk_print(struct disk_devdesc *dev, char *prefix, int verbose)
164 {
165 	struct open_disk *od;
166 	struct print_args pa;
167 
168 	/* Disk should be opened */
169 	od = (struct open_disk *)dev->dd.d_opendata;
170 	pa.dev = dev;
171 	pa.prefix = prefix;
172 	pa.verbose = verbose;
173 	return (ptable_iterate(od->table, &pa, ptable_print));
174 }
175 
176 int
177 disk_read(struct disk_devdesc *dev, void *buf, uint64_t offset, u_int blocks)
178 {
179 	struct open_disk *od;
180 	int ret;
181 
182 	od = (struct open_disk *)dev->dd.d_opendata;
183 	ret = dev->dd.d_dev->dv_strategy(dev, F_READ, dev->d_offset + offset,
184 	    blocks * od->sectorsize, buf, NULL);
185 
186 	return (ret);
187 }
188 
189 int
190 disk_write(struct disk_devdesc *dev, void *buf, uint64_t offset, u_int blocks)
191 {
192 	struct open_disk *od;
193 	int ret;
194 
195 	od = (struct open_disk *)dev->dd.d_opendata;
196 	ret = dev->dd.d_dev->dv_strategy(dev, F_WRITE, dev->d_offset + offset,
197 	    blocks * od->sectorsize, buf, NULL);
198 
199 	return (ret);
200 }
201 
202 int
203 disk_ioctl(struct disk_devdesc *dev, u_long cmd, void *data)
204 {
205 	struct open_disk *od = dev->dd.d_opendata;
206 
207 	if (od == NULL)
208 		return (ENOTTY);
209 
210 	switch (cmd) {
211 	case DIOCGSECTORSIZE:
212 		*(u_int *)data = od->sectorsize;
213 		break;
214 	case DIOCGMEDIASIZE:
215 		if (dev->d_offset == 0)
216 			*(uint64_t *)data = od->mediasize;
217 		else
218 			*(uint64_t *)data = od->entrysize * od->sectorsize;
219 		break;
220 	default:
221 		return (ENOTTY);
222 	}
223 
224 	return (0);
225 }
226 
227 int
228 disk_open(struct disk_devdesc *dev, uint64_t mediasize, u_int sectorsize)
229 {
230 	struct disk_devdesc partdev;
231 	struct open_disk *od;
232 	struct ptable *table;
233 	struct ptable_entry part;
234 	int rc, slice, partition;
235 
236 	if (sectorsize == 0) {
237 		DPRINTF("unknown sector size");
238 		return (ENXIO);
239 	}
240 	rc = 0;
241 	od = (struct open_disk *)malloc(sizeof(struct open_disk));
242 	if (od == NULL) {
243 		DPRINTF("no memory");
244 		return (ENOMEM);
245 	}
246 	dev->dd.d_opendata = od;
247 	od->entrysize = 0;
248 	od->mediasize = mediasize;
249 	od->sectorsize = sectorsize;
250 	/*
251 	 * While we are reading disk metadata, make sure we do it relative
252 	 * to the start of the disk
253 	 */
254 	memcpy(&partdev, dev, sizeof(partdev));
255 	partdev.d_offset = 0;
256 	partdev.d_slice = D_SLICENONE;
257 	partdev.d_partition = D_PARTNONE;
258 
259 	dev->d_offset = 0;
260 	table = NULL;
261 	slice = dev->d_slice;
262 	partition = dev->d_partition;
263 
264 	DPRINTF("%s unit %d, slice %d, partition %d => %p", disk_fmtdev(dev),
265 	    dev->dd.d_unit, dev->d_slice, dev->d_partition, od);
266 
267 	/* Determine disk layout. */
268 	od->table = ptable_open(&partdev, mediasize / sectorsize, sectorsize,
269 	    ptblread);
270 	if (od->table == NULL) {
271 		DPRINTF("Can't read partition table");
272 		rc = ENXIO;
273 		goto out;
274 	}
275 
276 	if (ptable_getsize(od->table, &mediasize) != 0) {
277 		rc = ENXIO;
278 		goto out;
279 	}
280 	od->mediasize = mediasize;
281 
282 	if (ptable_gettype(od->table) == PTABLE_BSD &&
283 	    partition >= 0) {
284 		/* It doesn't matter what value has d_slice */
285 		rc = ptable_getpart(od->table, &part, partition);
286 		if (rc == 0) {
287 			dev->d_offset = part.start;
288 			od->entrysize = part.end - part.start + 1;
289 		}
290 	} else if (ptable_gettype(od->table) == PTABLE_ISO9660) {
291 		dev->d_offset = 0;
292 		od->entrysize = mediasize;
293 	} else if (slice >= 0) {
294 		/* Try to get information about partition */
295 		if (slice == 0)
296 			rc = ptable_getbestpart(od->table, &part);
297 		else
298 			rc = ptable_getpart(od->table, &part, slice);
299 		if (rc != 0) /* Partition doesn't exist */
300 			goto out;
301 		dev->d_offset = part.start;
302 		od->entrysize = part.end - part.start + 1;
303 		slice = part.index;
304 		if (ptable_gettype(od->table) == PTABLE_GPT) {
305 			partition = D_PARTISGPT;
306 			goto out; /* Nothing more to do */
307 		} else if (partition == D_PARTISGPT) {
308 			/*
309 			 * When we try to open GPT partition, but partition
310 			 * table isn't GPT, reset partition value to
311 			 * D_PARTWILD and try to autodetect appropriate value.
312 			 */
313 			partition = D_PARTWILD;
314 		}
315 
316 		/*
317 		 * If partition is D_PARTNONE, then disk_open() was called
318 		 * to open raw MBR slice.
319 		 */
320 		if (partition == D_PARTNONE)
321 			goto out;
322 
323 		/*
324 		 * If partition is D_PARTWILD and we are looking at a BSD slice,
325 		 * then try to read BSD label, otherwise return the
326 		 * whole MBR slice.
327 		 */
328 		if (partition == D_PARTWILD &&
329 		    part.type != PART_FREEBSD)
330 			goto out;
331 		/* Try to read BSD label */
332 		table = ptable_open(dev, part.end - part.start + 1,
333 		    od->sectorsize, ptblread);
334 		if (table == NULL) {
335 			DPRINTF("Can't read BSD label");
336 			rc = ENXIO;
337 			goto out;
338 		}
339 		/*
340 		 * If slice contains BSD label and partition < 0, then
341 		 * assume the 'a' partition. Otherwise just return the
342 		 * whole MBR slice, because it can contain ZFS.
343 		 */
344 		if (partition < 0) {
345 			if (ptable_gettype(table) != PTABLE_BSD)
346 				goto out;
347 			partition = 0;
348 		}
349 		rc = ptable_getpart(table, &part, partition);
350 		if (rc != 0)
351 			goto out;
352 		dev->d_offset += part.start;
353 		od->entrysize = part.end - part.start + 1;
354 	}
355 out:
356 	if (table != NULL)
357 		ptable_close(table);
358 
359 	if (rc != 0) {
360 		if (od->table != NULL)
361 			ptable_close(od->table);
362 		free(od);
363 		DPRINTF("%s could not open", disk_fmtdev(dev));
364 	} else {
365 		/* Save the slice and partition number to the dev */
366 		dev->d_slice = slice;
367 		dev->d_partition = partition;
368 		DPRINTF("%s offset %lld => %p", disk_fmtdev(dev),
369 		    (long long)dev->d_offset, od);
370 	}
371 	return (rc);
372 }
373 
374 int
375 disk_close(struct disk_devdesc *dev)
376 {
377 	struct open_disk *od;
378 
379 	od = (struct open_disk *)dev->dd.d_opendata;
380 	DPRINTF("%s closed => %p", disk_fmtdev(dev), od);
381 	ptable_close(od->table);
382 	free(od);
383 	return (0);
384 }
385 
386 char *
387 disk_fmtdev(struct devdesc *vdev)
388 {
389 	struct disk_devdesc *dev = (struct disk_devdesc *)vdev;
390 	static char buf[128];
391 	char *cp;
392 
393 	assert(vdev->d_dev->dv_type == DEVT_DISK);
394 	cp = buf + sprintf(buf, "%s%d", dev->dd.d_dev->dv_name, dev->dd.d_unit);
395 	if (dev->d_slice > D_SLICENONE) {
396 #ifdef LOADER_GPT_SUPPORT
397 		if (dev->d_partition == D_PARTISGPT) {
398 			sprintf(cp, "p%d:", dev->d_slice);
399 			return (buf);
400 		} else
401 #endif
402 #ifdef LOADER_MBR_SUPPORT
403 			cp += sprintf(cp, "s%d", dev->d_slice);
404 #endif
405 	}
406 	if (dev->d_partition > D_PARTNONE)
407 		cp += sprintf(cp, "%c", dev->d_partition + 'a');
408 	strcat(cp, ":");
409 	return (buf);
410 }
411 
412 int
413 disk_parsedev(struct devdesc **idev, const char *devspec, const char **path)
414 {
415 	int unit, slice, partition;
416 	const char *np;
417 	char *cp;
418 	struct disk_devdesc *dev;
419 
420 	np = devspec + 4;	/* Skip the leading 'disk' */
421 	unit = -1;
422 	/*
423 	 * If there is path/file info after the device info, then any missing
424 	 * slice or partition info should be considered a request to search for
425 	 * an appropriate partition.  Otherwise we want to open the raw device
426 	 * itself and not try to fill in missing info by searching.
427 	 */
428 	if ((cp = strchr(np, ':')) != NULL && cp[1] != '\0') {
429 		slice = D_SLICEWILD;
430 		partition = D_PARTWILD;
431 	} else {
432 		slice = D_SLICENONE;
433 		partition = D_PARTNONE;
434 	}
435 
436 	if (*np != '\0' && *np != ':') {
437 		unit = strtol(np, &cp, 10);
438 		if (cp == np)
439 			return (EUNIT);
440 #ifdef LOADER_GPT_SUPPORT
441 		if (*cp == 'p') {
442 			np = cp + 1;
443 			slice = strtol(np, &cp, 10);
444 			if (np == cp)
445 				return (ESLICE);
446 			/* we don't support nested partitions on GPT */
447 			if (*cp != '\0' && *cp != ':')
448 				return (EINVAL);
449 			partition = D_PARTISGPT;
450 		} else
451 #endif
452 #ifdef LOADER_MBR_SUPPORT
453 		if (*cp == 's') {
454 			np = cp + 1;
455 			slice = strtol(np, &cp, 10);
456 			if (np == cp)
457 				return (ESLICE);
458 		}
459 #endif
460 		if (*cp != '\0' && *cp != ':') {
461 			partition = *cp - 'a';
462 			if (partition < 0)
463 				return (EPART);
464 			cp++;
465 		}
466 	} else
467 		return (EINVAL);
468 
469 	if (*cp != '\0' && *cp != ':')
470 		return (EINVAL);
471 	dev = malloc(sizeof(*dev));
472 	if (dev == NULL)
473 		return (ENOMEM);
474 	dev->dd.d_unit = unit;
475 	dev->d_slice = slice;
476 	dev->d_partition = partition;
477 	*idev = &dev->dd;
478 	if (path != NULL)
479 		*path = (*cp == '\0') ? cp: cp + 1;
480 	return (0);
481 }
482