1 /* biosdisk.c - emulate biosdisk */
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 1999,2000,2001,2002,2003,2004,2006,2007,2008,2009 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>
25 #include <grub/util/misc.h>
26 #include <grub/util/hostdisk.h>
27 #include <grub/misc.h>
35 #include <sys/types.h>
42 # include <sys/ioctl.h> /* ioctl */
43 # if !defined(__GLIBC__) || \
44 ((__GLIBC__ < 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ < 1)))
45 /* Maybe libc doesn't have large file support. */
46 # include <linux/unistd.h> /* _llseek */
47 # endif /* (GLIBC < 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR < 1)) */
49 # define BLKFLSBUF _IO (0x12,97) /* flush buffer cache */
50 # endif /* ! BLKFLSBUF */
51 # include <sys/ioctl.h> /* ioctl */
53 # define HDIO_GETGEO 0x0301 /* get device geometry */
54 /* If HDIO_GETGEO is not defined, it is unlikely that hd_geometry is
59 unsigned char sectors
;
60 unsigned short cylinders
;
63 # endif /* ! HDIO_GETGEO */
65 # define BLKGETSIZE64 _IOR(0x12,114,size_t) /* return device size */
66 # endif /* ! BLKGETSIZE64 */
70 # endif /* ! MINORBITS */
71 # define MAJOR(dev) ((unsigned) ((dev) >> MINORBITS))
74 # define FLOPPY_MAJOR 2
75 # endif /* ! FLOPPY_MAJOR */
78 # endif /* ! LOOP_MAJOR */
79 #endif /* __linux__ */
82 # include <sys/ioctl.h>
83 # include <cygwin/fs.h> /* BLKGETSIZE64 */
84 # include <cygwin/hdreg.h> /* HDIO_GETGEO */
85 # define MAJOR(dev) ((unsigned) ((dev) >> 16))
86 # define FLOPPY_MAJOR 2
89 #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
90 # include <sys/disk.h> /* DIOCGMEDIASIZE */
100 /* Check if we have devfs support. */
104 static int dev_devfsd_exists
= -1;
106 if (dev_devfsd_exists
< 0)
110 dev_devfsd_exists
= stat ("/dev/.devfsd", &st
) == 0;
113 return dev_devfsd_exists
;
115 #endif /* __linux__ */
118 find_grub_drive (const char *name
)
124 for (i
= 0; i
< sizeof (map
) / sizeof (map
[0]); i
++)
125 if (map
[i
].drive
&& ! strcmp (map
[i
].drive
, name
))
137 for (i
= 0; i
< sizeof (map
) / sizeof (map
[0]); i
++)
145 grub_util_biosdisk_iterate (int (*hook
) (const char *name
))
149 for (i
= 0; i
< sizeof (map
) / sizeof (map
[0]); i
++)
150 if (map
[i
].drive
&& hook (map
[i
].drive
))
157 grub_util_biosdisk_open (const char *name
, grub_disk_t disk
)
162 drive
= find_grub_drive (name
);
164 return grub_error (GRUB_ERR_BAD_DEVICE
,
165 "no mapping exists for `%s'", name
);
167 disk
->has_partitions
= 1;
171 #if defined(__MINGW32__)
175 size
= grub_util_get_disk_size (map
[drive
].device
);
178 grub_util_error ("unaligned device size");
180 disk
->total_sectors
= size
>> 9;
182 grub_util_info ("the size of %s is %llu", name
, disk
->total_sectors
);
184 return GRUB_ERR_NONE
;
186 #elif defined(__linux__) || defined(__CYGWIN__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
188 unsigned long long nr
;
191 fd
= open (map
[drive
].device
, O_RDONLY
);
193 return grub_error (GRUB_ERR_BAD_DEVICE
, "cannot open `%s' while attempting to get disk size", map
[drive
].device
);
195 # if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
196 if (fstat (fd
, &st
) < 0 || ! S_ISCHR (st
.st_mode
))
198 if (fstat (fd
, &st
) < 0 || ! S_ISBLK (st
.st_mode
))
205 # if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
206 if (ioctl (fd
, DIOCGMEDIASIZE
, &nr
))
208 if (ioctl (fd
, BLKGETSIZE64
, &nr
))
216 disk
->total_sectors
= nr
/ 512;
219 grub_util_error ("unaligned device size");
221 grub_util_info ("the size of %s is %llu", name
, disk
->total_sectors
);
223 return GRUB_ERR_NONE
;
227 /* In GNU/Hurd, stat() will return the right size. */
228 #elif !defined (__GNU__)
229 # warning "No special routine to get the size of a block device is implemented for your OS. This is not possibly fatal."
231 if (stat (map
[drive
].device
, &st
) < 0)
232 return grub_error (GRUB_ERR_BAD_DEVICE
, "cannot stat `%s'", map
[drive
].device
);
234 disk
->total_sectors
= st
.st_size
>> GRUB_DISK_SECTOR_BITS
;
236 grub_util_info ("the size of %s is %lu", name
, disk
->total_sectors
);
238 return GRUB_ERR_NONE
;
243 linux_find_partition (char *dev
, unsigned long sector
)
245 size_t len
= strlen (dev
);
249 char real_dev
[PATH_MAX
];
251 strcpy(real_dev
, dev
);
253 if (have_devfs () && strcmp (real_dev
+ len
- 5, "/disc") == 0)
255 p
= real_dev
+ len
- 4;
258 else if (real_dev
[len
- 1] >= '0' && real_dev
[len
- 1] <= '9')
269 for (i
= 1; i
< 10000; i
++)
272 struct hd_geometry hdg
;
274 sprintf (p
, format
, i
);
275 fd
= open (real_dev
, O_RDONLY
);
279 if (ioctl (fd
, HDIO_GETGEO
, &hdg
))
287 if (hdg
.start
== sector
)
289 strcpy (dev
, real_dev
);
296 #endif /* __linux__ */
299 open_device (const grub_disk_t disk
, grub_disk_addr_t sector
, int flags
)
304 flags
|= O_LARGEFILE
;
317 /* Linux has a bug that the disk cache for a whole disk is not consistent
318 with the one for a partition of the disk. */
320 int is_partition
= 0;
323 strcpy (dev
, map
[disk
->id
].device
);
324 if (disk
->partition
&& strncmp (map
[disk
->id
].device
, "/dev/", 5) == 0)
325 is_partition
= linux_find_partition (dev
, disk
->partition
->start
);
327 /* Open the partition. */
328 grub_dprintf ("hostdisk", "opening the device `%s' in open_device()", dev
);
329 fd
= open (dev
, flags
);
332 grub_error (GRUB_ERR_BAD_DEVICE
, "cannot open `%s'", dev
);
336 /* Make the buffer cache consistent with the physical disk. */
337 ioctl (fd
, BLKFLSBUF
, 0);
340 sector
-= disk
->partition
->start
;
342 #else /* ! __linux__ */
343 fd
= open (map
[disk
->id
].device
, flags
);
346 grub_error (GRUB_ERR_BAD_DEVICE
, "cannot open `%s' in open_device()", map
[disk
->id
].device
);
349 #endif /* ! __linux__ */
351 #if defined(__linux__) && (!defined(__GLIBC__) || \
352 ((__GLIBC__ < 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ < 1))))
353 /* Maybe libc doesn't have large file support. */
355 loff_t offset
, result
;
356 static int _llseek (uint filedes
, ulong hi
, ulong lo
,
357 loff_t
*res
, uint wh
);
358 _syscall5 (int, _llseek
, uint
, filedes
, ulong
, hi
, ulong
, lo
,
359 loff_t
*, res
, uint
, wh
);
361 offset
= (loff_t
) sector
<< GRUB_DISK_SECTOR_BITS
;
362 if (_llseek (fd
, offset
>> 32, offset
& 0xffffffff, &result
, SEEK_SET
))
364 grub_error (GRUB_ERR_BAD_DEVICE
, "cannot seek `%s'", map
[disk
->id
].device
);
371 off_t offset
= (off_t
) sector
<< GRUB_DISK_SECTOR_BITS
;
373 if (lseek (fd
, offset
, SEEK_SET
) != offset
)
375 grub_error (GRUB_ERR_BAD_DEVICE
, "cannot seek `%s'", map
[disk
->id
].device
);
385 /* Read LEN bytes from FD in BUF. Return less than or equal to zero if an
386 error occurs, otherwise return LEN. */
388 nread (int fd
, char *buf
, size_t len
)
394 ssize_t ret
= read (fd
, buf
, len
);
411 /* Write LEN bytes from BUF to FD. Return less than or equal to zero if an
412 error occurs, otherwise return LEN. */
414 nwrite (int fd
, const char *buf
, size_t len
)
420 ssize_t ret
= write (fd
, buf
, len
);
438 grub_util_biosdisk_read (grub_disk_t disk
, grub_disk_addr_t sector
,
439 grub_size_t size
, char *buf
)
443 fd
= open_device (disk
, sector
, O_RDONLY
);
448 if (sector
== 0 && size
> 1)
450 /* Work around a bug in Linux ez remapping. Linux remaps all
451 sectors that are read together with the MBR in one read. It
452 should only remap the MBR, so we split the read in two
454 if (nread (fd
, buf
, GRUB_DISK_SECTOR_SIZE
) != GRUB_DISK_SECTOR_SIZE
)
456 grub_error (GRUB_ERR_READ_ERROR
, "cannot read `%s'", map
[disk
->id
].device
);
461 buf
+= GRUB_DISK_SECTOR_SIZE
;
464 #endif /* __linux__ */
466 if (nread (fd
, buf
, size
<< GRUB_DISK_SECTOR_BITS
)
467 != (ssize_t
) (size
<< GRUB_DISK_SECTOR_BITS
))
468 grub_error (GRUB_ERR_READ_ERROR
, "cannot read from `%s'", map
[disk
->id
].device
);
475 grub_util_biosdisk_write (grub_disk_t disk
, grub_disk_addr_t sector
,
476 grub_size_t size
, const char *buf
)
480 fd
= open_device (disk
, sector
, O_WRONLY
);
484 if (nwrite (fd
, buf
, size
<< GRUB_DISK_SECTOR_BITS
)
485 != (ssize_t
) (size
<< GRUB_DISK_SECTOR_BITS
))
486 grub_error (GRUB_ERR_WRITE_ERROR
, "cannot write to `%s'", map
[disk
->id
].device
);
492 static struct grub_disk_dev grub_util_biosdisk_dev
=
495 .id
= GRUB_DISK_DEVICE_BIOSDISK_ID
,
496 .iterate
= grub_util_biosdisk_iterate
,
497 .open
= grub_util_biosdisk_open
,
499 .read
= grub_util_biosdisk_read
,
500 .write
= grub_util_biosdisk_write
,
505 read_device_map (const char *dev_map
)
508 char buf
[1024]; /* XXX */
512 auto void show_error (const char *msg
);
513 void show_error (const char *msg
)
515 grub_util_error ("%s:%d: %s", dev_map
, lineno
, msg
);
518 fp
= fopen (dev_map
, "r");
520 grub_util_error ("Cannot open `%s'", dev_map
);
522 while (fgets (buf
, sizeof (buf
), fp
))
530 /* Skip leading spaces. */
531 while (*p
&& isspace (*p
))
534 /* If the first character is `#' or NUL, skip this line. */
535 if (*p
== '\0' || *p
== '#')
539 show_error ("No open parenthesis found");
542 /* Find a free slot. */
543 drive
= find_free_slot ();
545 show_error ("Map table size exceeded");
550 show_error ("No close parenthesis found");
552 map
[drive
].drive
= xmalloc (p
- e
+ sizeof ('\0'));
553 strncpy (map
[drive
].drive
, e
, p
- e
+ sizeof ('\0'));
554 map
[drive
].drive
[p
- e
] = '\0';
557 /* Skip leading spaces. */
558 while (*p
&& isspace (*p
))
562 show_error ("No filename found");
564 /* NUL-terminate the filename. */
566 while (*e
&& ! isspace (*e
))
572 if (grub_util_get_disk_size (p
) == -1LL)
574 if (stat (p
, &st
) == -1)
577 free (map
[drive
].drive
);
578 map
[drive
].drive
= NULL
;
579 grub_util_info ("Cannot stat `%s', skipping", p
);
584 /* On Linux, the devfs uses symbolic links horribly, and that
585 confuses the interface very much, so use realpath to expand
587 map
[drive
].device
= xmalloc (PATH_MAX
);
588 if (! realpath (p
, map
[drive
].device
))
589 grub_util_error ("Cannot get the real path of `%s'", p
);
591 map
[drive
].device
= xstrdup (p
);
599 grub_util_biosdisk_init (const char *dev_map
)
601 read_device_map (dev_map
);
602 grub_disk_dev_register (&grub_util_biosdisk_dev
);
606 grub_util_biosdisk_fini (void)
610 for (i
= 0; i
< sizeof (map
) / sizeof (map
[0]); i
++)
615 free (map
[i
].device
);
616 map
[i
].drive
= map
[i
].device
= NULL
;
619 grub_disk_dev_unregister (&grub_util_biosdisk_dev
);
623 make_device_name (int drive
, int dos_part
, int bsd_part
)
628 sprintf (p
, "%s", map
[drive
].drive
);
631 sprintf (p
+ strlen (p
), ",%d", dos_part
+ 1);
634 sprintf (p
+ strlen (p
), ",%c", bsd_part
+ 'a');
640 convert_system_partition_to_system_disk (const char *os_dev
)
642 #if defined(__linux__)
643 char *path
= xmalloc (PATH_MAX
);
644 if (! realpath (os_dev
, path
))
647 if (strncmp ("/dev/", path
, 5) == 0)
651 /* If this is an IDE disk. */
652 if (strncmp ("ide/", p
, 4) == 0)
654 p
= strstr (p
, "part");
661 /* If this is a SCSI disk. */
662 if (strncmp ("scsi/", p
, 5) == 0)
664 p
= strstr (p
, "part");
671 /* If this is a DAC960 disk. */
672 if (strncmp ("rd/c", p
, 4) == 0)
674 /* /dev/rd/c[0-9]+d[0-9]+(p[0-9]+)? */
682 /* If this is a CCISS disk. */
683 if (strncmp ("cciss/c", p
, sizeof ("cciss/c") - 1) == 0)
685 /* /dev/cciss/c[0-9]+d[0-9]+(p[0-9]+)? */
693 /* If this is a Compaq Intelligent Drive Array. */
694 if (strncmp ("ida/c", p
, sizeof ("ida/c") - 1) == 0)
696 /* /dev/ida/c[0-9]+d[0-9]+(p[0-9]+)? */
704 /* If this is an I2O disk. */
705 if (strncmp ("i2o/hd", p
, sizeof ("i2o/hd") - 1) == 0)
707 /* /dev/i2o/hd[a-z]([0-9]+)? */
708 p
[sizeof ("i2o/hda") - 1] = '\0';
712 /* If this is a MultiMediaCard (MMC). */
713 if (strncmp ("mmcblk", p
, sizeof ("mmcblk") - 1) == 0)
715 /* /dev/mmcblk[0-9]+(p[0-9]+)? */
723 /* If this is an IDE, SCSI or Virtio disk. */
724 if ((strncmp ("hd", p
, 2) == 0
725 || strncmp ("vd", p
, 2) == 0
726 || strncmp ("sd", p
, 2) == 0)
727 && p
[2] >= 'a' && p
[2] <= 'z')
729 /* /dev/[hsv]d[a-z][0-9]* */
734 /* If this is a Xen virtual block device. */
735 if ((strncmp ("xvd", p
, 3) == 0) && p
[3] >= 'a' && p
[3] <= 'z')
737 /* /dev/xvd[a-z][0-9]* */
745 #elif defined(__GNU__)
746 char *path
= xstrdup (os_dev
);
747 if (strncmp ("/dev/sd", path
, 7) == 0 || strncmp ("/dev/hd", path
, 7) == 0)
749 char *p
= strchr (path
+ 7, 's');
755 #elif defined(__CYGWIN__)
756 char *path
= xstrdup (os_dev
);
757 if (strncmp ("/dev/sd", path
, 7) == 0 && 'a' <= path
[7] && path
[7] <= 'z')
761 #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
762 char *path
= xstrdup (os_dev
);
763 if (strncmp ("/dev/", path
, 5) == 0)
766 for (p
= path
+ 5; *p
; ++p
)
767 if (grub_isdigit(*p
))
778 # warning "The function `convert_system_partition_to_system_disk' might not work on your OS correctly."
779 return xstrdup (os_dev
);
784 find_system_device (const char *os_dev
)
789 os_disk
= convert_system_partition_to_system_disk (os_dev
);
793 for (i
= 0; i
< (int) (sizeof (map
) / sizeof (map
[0])); i
++)
794 if (map
[i
].device
&& strcmp (map
[i
].device
, os_disk
) == 0)
805 grub_util_biosdisk_get_grub_dev (const char *os_dev
)
810 if (stat (os_dev
, &st
) < 0)
812 grub_error (GRUB_ERR_BAD_DEVICE
, "cannot stat `%s'", os_dev
);
816 drive
= find_system_device (os_dev
);
819 grub_error (GRUB_ERR_BAD_DEVICE
,
820 "no mapping exists for `%s'", os_dev
);
824 #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
825 if (! S_ISCHR (st
.st_mode
))
827 if (! S_ISBLK (st
.st_mode
))
829 return make_device_name (drive
, -1, -1);
831 #if defined(__linux__) || defined(__CYGWIN__)
832 /* Linux counts partitions uniformly, whether a BSD partition or a DOS
833 partition, so mapping them to GRUB devices is not trivial.
834 Here, get the start sector of a partition by HDIO_GETGEO, and
835 compare it with each partition GRUB recognizes.
837 Cygwin /dev/sdXN emulation uses Windows partition mapping. It
838 does not count the extended partition and missing primary
839 partitions. Use same method as on Linux here. */
844 struct hd_geometry hdg
;
847 auto int find_partition (grub_disk_t disk
,
848 const grub_partition_t partition
);
850 int find_partition (grub_disk_t disk
__attribute__ ((unused
)),
851 const grub_partition_t partition
)
853 struct grub_pc_partition
*pcdata
= NULL
;
855 if (strcmp (partition
->partmap
->name
, "pc_partition_map") == 0)
856 pcdata
= partition
->data
;
860 if (pcdata
->bsd_part
< 0)
861 grub_util_info ("DOS partition %d starts from %lu",
862 pcdata
->dos_part
, partition
->start
);
864 grub_util_info ("BSD partition %d,%c starts from %lu",
865 pcdata
->dos_part
, pcdata
->bsd_part
+ 'a',
870 grub_util_info ("Partition %d starts from %lu",
871 partition
->index
, partition
->start
);
874 if (hdg
.start
== partition
->start
)
878 dos_part
= pcdata
->dos_part
;
879 bsd_part
= pcdata
->bsd_part
;
883 dos_part
= partition
->index
;
892 name
= make_device_name (drive
, -1, -1);
894 if (MAJOR (st
.st_rdev
) == FLOPPY_MAJOR
)
897 fd
= open (os_dev
, O_RDONLY
);
900 grub_error (GRUB_ERR_BAD_DEVICE
, "cannot open `%s' while attempting to get disk geometry", os_dev
);
905 if (ioctl (fd
, HDIO_GETGEO
, &hdg
))
907 grub_error (GRUB_ERR_BAD_DEVICE
,
908 "cannot get geometry of `%s'", os_dev
);
916 grub_util_info ("%s starts from %lu", os_dev
, hdg
.start
);
921 grub_util_info ("opening the device %s", name
);
922 disk
= grub_disk_open (name
);
928 grub_partition_iterate (disk
, find_partition
);
929 if (grub_errno
!= GRUB_ERR_NONE
)
931 grub_disk_close (disk
);
937 grub_disk_close (disk
);
938 grub_error (GRUB_ERR_BAD_DEVICE
,
939 "cannot find the partition of `%s'", os_dev
);
943 return make_device_name (drive
, dos_part
, bsd_part
);
946 #elif defined(__GNU__)
947 /* GNU uses "/dev/[hs]d[0-9]+(s[0-9]+[a-z]?)?". */
953 p
= strrchr (os_dev
, 's');
960 n
= strtol (p
, &q
, 10);
961 if (p
!= q
&& n
!= LONG_MIN
&& n
!= LONG_MAX
)
965 if (*q
>= 'a' && *q
<= 'g')
970 return make_device_name (drive
, dos_part
, bsd_part
);
973 #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
974 /* FreeBSD uses "/dev/[a-z]+[0-9]+(s[0-9]+[a-z]?)?". */
979 if (strncmp ("/dev/", os_dev
, 5) == 0)
984 for (p
= os_dev
+ 5; *p
; ++p
)
985 if (grub_isdigit(*p
))
991 n
= strtol (p
, &q
, 10);
992 if (p
!= q
&& n
!= LONG_MIN
&& n
!= LONG_MAX
)
994 dos_part
= (int) n
- 1;
996 if (*q
>= 'a' && *q
<= 'g')
1004 return make_device_name (drive
, dos_part
, bsd_part
);
1008 # warning "The function `grub_util_biosdisk_get_grub_dev' might not work on your OS correctly."
1009 return make_device_name (drive
, -1, -1);