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