make the linux-ppc packags be in synch with other platforms
[tangerine.git] / arch / common / boot / grub2 / util / biosdisk.c
blob4dd46d6b14bbf4dea5aa6a1f412815f98ea9b214
1 /* biosdisk.c - emulate biosdisk */
2 /*
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 1999,2000,2001,2002,2003,2004,2006,2007,2008 Free Software Foundation, Inc.
6 * GRUB is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * GRUB is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
20 #include <grub/disk.h>
21 #include <grub/partition.h>
22 #include <grub/pc_partition.h>
23 #include <grub/types.h>
24 #include <grub/err.h>
25 #include <grub/util/misc.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <ctype.h>
31 #include <assert.h>
32 #include <unistd.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <fcntl.h>
36 #include <errno.h>
37 #include <limits.h>
39 #ifdef __linux__
40 # include <sys/ioctl.h> /* ioctl */
41 # if !defined(__GLIBC__) || \
42 ((__GLIBC__ < 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ < 1)))
43 /* Maybe libc doesn't have large file support. */
44 # include <linux/unistd.h> /* _llseek */
45 # endif /* (GLIBC < 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR < 1)) */
46 # ifndef BLKFLSBUF
47 # define BLKFLSBUF _IO (0x12,97) /* flush buffer cache */
48 # endif /* ! BLKFLSBUF */
49 # include <sys/ioctl.h> /* ioctl */
50 # ifndef HDIO_GETGEO
51 # define HDIO_GETGEO 0x0301 /* get device geometry */
52 /* If HDIO_GETGEO is not defined, it is unlikely that hd_geometry is
53 defined. */
54 struct hd_geometry
56 unsigned char heads;
57 unsigned char sectors;
58 unsigned short cylinders;
59 unsigned long start;
61 # endif /* ! HDIO_GETGEO */
62 # ifndef BLKGETSIZE64
63 # define BLKGETSIZE64 _IOR(0x12,114,size_t) /* return device size */
64 # endif /* ! BLKGETSIZE64 */
65 # ifndef MAJOR
66 # ifndef MINORBITS
67 # define MINORBITS 8
68 # endif /* ! MINORBITS */
69 # define MAJOR(dev) ((unsigned) ((dev) >> MINORBITS))
70 # endif /* ! MAJOR */
71 # ifndef FLOPPY_MAJOR
72 # define FLOPPY_MAJOR 2
73 # endif /* ! FLOPPY_MAJOR */
74 # ifndef LOOP_MAJOR
75 # define LOOP_MAJOR 7
76 # endif /* ! LOOP_MAJOR */
77 #endif /* __linux__ */
79 static char *map[256];
81 #ifdef __linux__
82 /* Check if we have devfs support. */
83 static int
84 have_devfs (void)
86 static int dev_devfsd_exists = -1;
88 if (dev_devfsd_exists < 0)
90 struct stat st;
92 dev_devfsd_exists = stat ("/dev/.devfsd", &st) == 0;
95 return dev_devfsd_exists;
97 #endif /* __linux__ */
99 static int
100 get_drive (const char *name)
102 unsigned long drive;
103 char *p;
105 if ((name[0] != 'f' && name[0] != 'h') || name[1] != 'd')
106 goto fail;
108 drive = strtoul (name + 2, &p, 10);
109 if (p == name + 2)
110 goto fail;
112 if (name[0] == 'h')
113 drive += 0x80;
115 if (drive > sizeof (map) / sizeof (map[0]))
116 goto fail;
118 return (int) drive;
120 fail:
121 grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a biosdisk");
122 return -1;
125 static int
126 call_hook (int (*hook) (const char *name), int drive)
128 char name[10];
130 sprintf (name, (drive & 0x80) ? "hd%d" : "fd%d", drive & (~0x80));
131 return hook (name);
134 static int
135 grub_util_biosdisk_iterate (int (*hook) (const char *name))
137 unsigned i;
139 for (i = 0; i < sizeof (map) / sizeof (map[0]); i++)
140 if (map[i] && call_hook (hook, i))
141 return 1;
143 return 0;
146 static grub_err_t
147 grub_util_biosdisk_open (const char *name, grub_disk_t disk)
149 int drive;
150 struct stat st;
152 drive = get_drive (name);
153 if (drive < 0)
154 return grub_errno;
156 if (! map[drive])
157 return grub_error (GRUB_ERR_BAD_DEVICE,
158 "no mapping exists for `%s'", name);
160 disk->has_partitions = (drive & 0x80);
161 disk->id = drive;
163 /* Get the size. */
164 #ifdef __linux__
166 unsigned long long nr;
167 int fd;
169 fd = open (map[drive], O_RDONLY);
170 if (fd == -1)
171 return grub_error (GRUB_ERR_BAD_DEVICE, "cannot open `%s'", map[drive]);
173 if (fstat (fd, &st) < 0 || ! S_ISBLK (st.st_mode))
175 close (fd);
176 goto fail;
179 if (ioctl (fd, BLKGETSIZE64, &nr))
181 close (fd);
182 goto fail;
185 close (fd);
186 disk->total_sectors = nr / 512;
188 if (nr % 512)
189 grub_util_error ("unaligned device size");
191 grub_util_info ("the size of %s is %llu", name, disk->total_sectors);
193 return GRUB_ERR_NONE;
196 fail:
197 /* In GNU/Hurd, stat() will return the right size. */
198 #elif !defined (__GNU__)
199 # warning "No special routine to get the size of a block device is implemented for your OS. This is not possibly fatal."
200 #endif
201 if (stat (map[drive], &st) < 0)
202 return grub_error (GRUB_ERR_BAD_DEVICE, "cannot stat `%s'", map[drive]);
204 disk->total_sectors = st.st_size >> GRUB_DISK_SECTOR_BITS;
206 grub_util_info ("the size of %s is %lu", name, disk->total_sectors);
208 return GRUB_ERR_NONE;
211 #ifdef __linux__
212 static int
213 linux_find_partition (char *dev, unsigned long sector)
215 size_t len = strlen (dev);
216 const char *format;
217 char *p;
218 int i;
219 char real_dev[PATH_MAX];
221 strcpy(real_dev, dev);
223 if (have_devfs () && strcmp (real_dev + len - 5, "/disc") == 0)
225 p = real_dev + len - 4;
226 format = "part%d";
228 else if ((strncmp (real_dev + 5, "hd", 2) == 0
229 || strncmp (real_dev + 5, "sd", 2) == 0)
230 && real_dev[7] >= 'a' && real_dev[7] <= 'z')
232 p = real_dev + 8;
233 format = "%d";
235 else if (strncmp (real_dev + 5, "rd/c", 4) == 0)
237 p = strchr (real_dev + 9, 'd');
238 if (! p)
239 return 0;
241 p++;
242 while (*p && isdigit (*p))
243 p++;
245 format = "p%d";
247 else
248 return 0;
250 for (i = 1; i < 10000; i++)
252 int fd;
253 struct hd_geometry hdg;
255 sprintf (p, format, i);
256 fd = open (real_dev, O_RDONLY);
257 if (fd == -1)
258 return 0;
260 if (ioctl (fd, HDIO_GETGEO, &hdg))
262 close (fd);
263 return 0;
266 close (fd);
268 if (hdg.start == sector)
270 strcpy (dev, real_dev);
271 return 1;
275 return 0;
277 #endif /* __linux__ */
279 static int
280 open_device (const grub_disk_t disk, grub_disk_addr_t sector, int flags)
282 int fd;
284 #ifdef O_LARGEFILE
285 flags |= O_LARGEFILE;
286 #endif
287 #ifdef O_SYNC
288 flags |= O_SYNC;
289 #endif
290 #ifdef O_FSYNC
291 flags |= O_FSYNC;
292 #endif
294 #ifdef __linux__
295 /* Linux has a bug that the disk cache for a whole disk is not consistent
296 with the one for a partition of the disk. */
298 int is_partition = 0;
299 char dev[PATH_MAX];
301 strcpy (dev, map[disk->id]);
302 if (disk->partition && strncmp (map[disk->id], "/dev/", 5) == 0)
303 is_partition = linux_find_partition (dev, disk->partition->start);
305 /* Open the partition. */
306 grub_util_info ("opening the device `%s'", dev);
307 fd = open (dev, flags);
308 if (fd < 0)
310 grub_error (GRUB_ERR_BAD_DEVICE, "cannot open `%s'", dev);
311 return -1;
314 /* Make the buffer cache consistent with the physical disk. */
315 ioctl (fd, BLKFLSBUF, 0);
317 if (is_partition)
318 sector -= disk->partition->start;
320 #else /* ! __linux__ */
321 fd = open (map[disk->id], flags);
322 if (fd < 0)
324 grub_error (GRUB_ERR_BAD_DEVICE, "cannot open `%s'", map[disk->id]);
325 return -1;
327 #endif /* ! __linux__ */
329 #if defined(__linux__) && (!defined(__GLIBC__) || \
330 ((__GLIBC__ < 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ < 1))))
331 /* Maybe libc doesn't have large file support. */
333 loff_t offset, result;
334 static int _llseek (uint filedes, ulong hi, ulong lo,
335 loff_t *res, uint wh);
336 _syscall5 (int, _llseek, uint, filedes, ulong, hi, ulong, lo,
337 loff_t *, res, uint, wh);
339 offset = (loff_t) sector << GRUB_DISK_SECTOR_BITS;
340 if (_llseek (fd, offset >> 32, offset & 0xffffffff, &result, SEEK_SET))
342 grub_error (GRUB_ERR_BAD_DEVICE, "cannot seek `%s'", map[disk->id]);
343 close (fd);
344 return -1;
347 #else
349 off_t offset = (off_t) sector << GRUB_DISK_SECTOR_BITS;
351 if (lseek (fd, offset, SEEK_SET) != offset)
353 grub_error (GRUB_ERR_BAD_DEVICE, "cannot seek `%s'", map[disk->id]);
354 close (fd);
355 return -1;
358 #endif
360 return fd;
363 /* Read LEN bytes from FD in BUF. Return less than or equal to zero if an
364 error occurs, otherwise return LEN. */
365 static ssize_t
366 nread (int fd, char *buf, size_t len)
368 ssize_t size = len;
370 while (len)
372 ssize_t ret = read (fd, buf, len);
374 if (ret <= 0)
376 if (errno == EINTR)
377 continue;
378 else
379 return ret;
382 len -= ret;
383 buf += ret;
386 return size;
389 /* Write LEN bytes from BUF to FD. Return less than or equal to zero if an
390 error occurs, otherwise return LEN. */
391 static ssize_t
392 nwrite (int fd, const char *buf, size_t len)
394 ssize_t size = len;
396 while (len)
398 ssize_t ret = write (fd, buf, len);
400 if (ret <= 0)
402 if (errno == EINTR)
403 continue;
404 else
405 return ret;
408 len -= ret;
409 buf += ret;
412 return size;
415 static grub_err_t
416 grub_util_biosdisk_read (grub_disk_t disk, grub_disk_addr_t sector,
417 grub_size_t size, char *buf)
419 int fd;
421 fd = open_device (disk, sector, O_RDONLY);
422 if (fd < 0)
423 return grub_errno;
425 #ifdef __linux__
426 if (sector == 0 && size > 1)
428 /* Work around a bug in Linux ez remapping. Linux remaps all
429 sectors that are read together with the MBR in one read. It
430 should only remap the MBR, so we split the read in two
431 parts. -jochen */
432 if (nread (fd, buf, GRUB_DISK_SECTOR_SIZE) != GRUB_DISK_SECTOR_SIZE)
434 grub_error (GRUB_ERR_READ_ERROR, "cannot read `%s'", map[disk->id]);
435 close (fd);
436 return grub_errno;
439 buf += GRUB_DISK_SECTOR_SIZE;
440 size--;
442 #endif /* __linux__ */
444 if (nread (fd, buf, size << GRUB_DISK_SECTOR_BITS)
445 != (ssize_t) (size << GRUB_DISK_SECTOR_BITS))
446 grub_error (GRUB_ERR_READ_ERROR, "cannot read from `%s'", map[disk->id]);
448 close (fd);
449 return grub_errno;
452 static grub_err_t
453 grub_util_biosdisk_write (grub_disk_t disk, grub_disk_addr_t sector,
454 grub_size_t size, const char *buf)
456 int fd;
458 fd = open_device (disk, sector, O_WRONLY);
459 if (fd < 0)
460 return grub_errno;
462 if (nwrite (fd, buf, size << GRUB_DISK_SECTOR_BITS)
463 != (ssize_t) (size << GRUB_DISK_SECTOR_BITS))
464 grub_error (GRUB_ERR_WRITE_ERROR, "cannot write to `%s'", map[disk->id]);
466 close (fd);
467 return grub_errno;
470 static struct grub_disk_dev grub_util_biosdisk_dev =
472 .name = "biosdisk",
473 .id = GRUB_DISK_DEVICE_BIOSDISK_ID,
474 .iterate = grub_util_biosdisk_iterate,
475 .open = grub_util_biosdisk_open,
476 .close = 0,
477 .read = grub_util_biosdisk_read,
478 .write = grub_util_biosdisk_write,
479 .next = 0
482 static void
483 read_device_map (const char *dev_map)
485 FILE *fp;
486 char buf[1024]; /* XXX */
487 int lineno = 0;
488 auto void show_error (const char *msg);
490 void show_error (const char *msg)
492 grub_util_error ("%s:%d: %s", dev_map, lineno, msg);
495 fp = fopen (dev_map, "r");
496 if (! fp)
497 grub_util_error ("Cannot open `%s'", dev_map);
499 while (fgets (buf, sizeof (buf), fp))
501 char *p = buf;
502 char *e;
503 int drive;
505 lineno++;
507 /* Skip leading spaces. */
508 while (*p && isspace (*p))
509 p++;
511 /* If the first character is `#' or NUL, skip this line. */
512 if (*p == '\0' || *p == '#')
513 continue;
515 if (*p != '(')
516 show_error ("No open parenthesis found");
518 p++;
519 drive = get_drive (p);
520 if (drive < 0 || drive >= (int) (sizeof (map) / sizeof (map[0])))
521 show_error ("Bad device name");
523 p = strchr (p, ')');
524 if (! p)
525 show_error ("No close parenthesis found");
527 p++;
528 /* Skip leading spaces. */
529 while (*p && isspace (*p))
530 p++;
532 if (*p == '\0')
533 show_error ("No filename found");
535 /* NUL-terminate the filename. */
536 e = p;
537 while (*e && ! isspace (*e))
538 e++;
539 *e = '\0';
541 /* Multiple entries for a given drive is not allowed. */
542 if (map[drive])
543 show_error ("Duplicated entry found");
545 #ifdef __linux__
546 /* On Linux, the devfs uses symbolic links horribly, and that
547 confuses the interface very much, so use realpath to expand
548 symbolic links. */
549 map[drive] = xmalloc (PATH_MAX);
550 if (! realpath (p, map[drive]))
551 grub_util_error ("Cannot get the real path of `%s'", p);
552 #else
553 map[drive] = xstrdup (p);
554 #endif
557 fclose (fp);
560 void
561 grub_util_biosdisk_init (const char *dev_map)
563 read_device_map (dev_map);
564 grub_disk_dev_register (&grub_util_biosdisk_dev);
567 void
568 grub_util_biosdisk_fini (void)
570 unsigned i;
572 for (i = 0; i < sizeof (map) / sizeof (map[0]); i++)
573 free (map[i]);
575 grub_disk_dev_unregister (&grub_util_biosdisk_dev);
578 static char *
579 make_device_name (int drive, int dos_part, int bsd_part)
581 char *p;
583 p = xmalloc (30);
584 sprintf (p, (drive & 0x80) ? "hd%d" : "fd%d", drive & ~0x80);
586 if (dos_part >= 0)
587 sprintf (p + strlen (p), ",%d", dos_part + 1);
589 if (bsd_part >= 0)
590 sprintf (p + strlen (p), ",%c", bsd_part + 'a');
592 return p;
595 static char *
596 get_os_disk (const char *os_dev)
598 char *path, *p;
600 #if defined(__linux__)
601 path = xmalloc (PATH_MAX);
602 if (! realpath (os_dev, path))
603 return 0;
605 if (strncmp ("/dev/", path, 5) == 0)
607 p = path + 5;
609 /* If this is an IDE disk. */
610 if (strncmp ("ide/", p, 4) == 0)
612 p = strstr (p, "part");
613 if (p)
614 strcpy (p, "disc");
616 return path;
619 /* If this is a SCSI disk. */
620 if (strncmp ("scsi/", p, 5) == 0)
622 p = strstr (p, "part");
623 if (p)
624 strcpy (p, "disc");
626 return path;
629 /* If this is a DAC960 disk. */
630 if (strncmp ("rd/c", p, 4) == 0)
632 /* /dev/rd/c[0-9]+d[0-9]+(p[0-9]+)? */
633 p = strchr (p, 'p');
634 if (p)
635 *p = '\0';
637 return path;
640 /* If this is an IDE disk or a SCSI disk. */
641 if ((strncmp ("hd", p, 2) == 0
642 || strncmp ("sd", p, 2) == 0)
643 && p[2] >= 'a' && p[2] <= 'z')
645 /* /dev/[hs]d[a-z][0-9]* */
646 p[3] = '\0';
647 return path;
651 return path;
653 #elif defined(__GNU__)
654 path = xstrdup (os_dev);
655 if (strncmp ("/dev/sd", path, 7) == 0 || strncmp ("/dev/hd", path, 7) == 0)
657 p = strchr (path, 's');
658 if (p)
659 *p = '\0';
661 return path;
663 #else
664 # warning "The function `get_os_disk' might not work on your OS correctly."
665 return xstrdup (os_dev);
666 #endif
669 static int
670 find_drive (const char *os_dev)
672 int i;
673 char *os_disk;
675 os_disk = get_os_disk (os_dev);
676 if (! os_disk)
677 return -1;
679 for (i = 0; i < (int) (sizeof (map) / sizeof (map[0])); i++)
680 if (map[i] && strcmp (map[i], os_disk) == 0)
682 free (os_disk);
683 return i;
686 free (os_disk);
687 return -1;
690 char *
691 grub_util_biosdisk_get_grub_dev (const char *os_dev)
693 struct stat st;
694 int drive;
696 if (stat (os_dev, &st) < 0)
698 grub_error (GRUB_ERR_BAD_DEVICE, "cannot stat `%s'", os_dev);
699 return 0;
702 drive = find_drive (os_dev);
703 if (drive < 0)
705 grub_error (GRUB_ERR_BAD_DEVICE,
706 "no mapping exists for `%s'", os_dev);
707 return 0;
710 if (! S_ISBLK (st.st_mode))
711 return make_device_name (drive, -1, -1);
713 #if defined(__linux__)
714 /* Linux counts partitions uniformly, whether a BSD partition or a DOS
715 partition, so mapping them to GRUB devices is not trivial.
716 Here, get the start sector of a partition by HDIO_GETGEO, and
717 compare it with each partition GRUB recognizes. */
719 char *name;
720 grub_disk_t disk;
721 int fd;
722 struct hd_geometry hdg;
723 int dos_part = -1;
724 int bsd_part = -1;
725 auto int find_partition (grub_disk_t disk,
726 const grub_partition_t partition);
728 int find_partition (grub_disk_t disk __attribute__ ((unused)),
729 const grub_partition_t partition)
731 struct grub_pc_partition *pcdata = NULL;
733 if (strcmp (partition->partmap->name, "pc_partition_map") == 0)
734 pcdata = partition->data;
736 if (pcdata)
738 if (pcdata->bsd_part < 0)
739 grub_util_info ("DOS partition %d starts from %lu",
740 pcdata->dos_part, partition->start);
741 else
742 grub_util_info ("BSD partition %d,%c starts from %lu",
743 pcdata->dos_part, pcdata->bsd_part + 'a',
744 partition->start);
746 else
748 grub_util_info ("Partition %d starts from %lu",
749 partition->index, partition->start);
752 if (hdg.start == partition->start)
754 if (pcdata)
756 dos_part = pcdata->dos_part;
757 bsd_part = pcdata->bsd_part;
759 else
761 dos_part = partition->index;
762 bsd_part = -1;
764 return 1;
767 return 0;
770 name = make_device_name (drive, -1, -1);
772 if (MAJOR (st.st_rdev) == FLOPPY_MAJOR)
773 return name;
775 fd = open (os_dev, O_RDONLY);
776 if (fd == -1)
778 grub_error (GRUB_ERR_BAD_DEVICE, "cannot open `%s'", os_dev);
779 free (name);
780 return 0;
783 if (ioctl (fd, HDIO_GETGEO, &hdg))
785 grub_error (GRUB_ERR_BAD_DEVICE,
786 "cannot get geometry of `%s'", os_dev);
787 close (fd);
788 free (name);
789 return 0;
792 close (fd);
794 grub_util_info ("%s starts from %lu", os_dev, hdg.start);
796 if (hdg.start == 0)
797 return name;
799 grub_util_info ("opening the device %s", name);
800 disk = grub_disk_open (name);
801 free (name);
803 if (! disk)
804 return 0;
806 if (grub_partition_iterate (disk, find_partition) != GRUB_ERR_NONE)
808 grub_disk_close (disk);
809 return 0;
812 if (dos_part < 0)
814 grub_disk_close (disk);
815 grub_error (GRUB_ERR_BAD_DEVICE,
816 "cannot find the partition of `%s'", os_dev);
817 return 0;
820 return make_device_name (drive, dos_part, bsd_part);
823 #elif defined(__GNU__)
824 /* GNU uses "/dev/[hs]d[0-9]+(s[0-9]+[a-z]?)?". */
826 char *p;
827 int dos_part = -1;
828 int bsd_part = -1;
830 p = strrchr (os_dev, 's');
831 if (p)
833 long int n;
834 char *q;
836 p++;
837 n = strtol (p, &q, 10);
838 if (p != q && n != LONG_MIN && n != LONG_MAX)
840 dos_part = (int) n;
842 if (*q >= 'a' && *q <= 'g')
843 bsd_part = *q - 'a';
847 return make_device_name (drive, dos_part, bsd_part);
850 #else
851 # warning "The function `grub_util_biosdisk_get_grub_dev' might not work on your OS correctly."
852 return make_device_name (drive, -1, -1);
853 #endif