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