/* hostdisk.c - emulate biosdisk */ /* * GRUB -- GRand Unified Bootloader * Copyright (C) 1999,2000,2001,2002,2003,2004,2006,2007,2008,2009,2010,2011,2012,2013 Free Software Foundation, Inc. * * GRUB is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * GRUB is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GRUB. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include # include /* ioctl */ # include # ifndef BLKFLSBUF # define BLKFLSBUF _IO (0x12,97) /* flush buffer cache */ # endif /* ! BLKFLSBUF */ # include /* ioctl */ # ifndef HDIO_GETGEO # define HDIO_GETGEO 0x0301 /* get device geometry */ /* If HDIO_GETGEO is not defined, it is unlikely that hd_geometry is defined. */ struct hd_geometry { unsigned char heads; unsigned char sectors; unsigned short cylinders; unsigned long start; }; # endif /* ! HDIO_GETGEO */ # ifndef BLKGETSIZE64 # define BLKGETSIZE64 _IOR(0x12,114,size_t) /* return device size */ # endif /* ! BLKGETSIZE64 */ grub_int64_t grub_util_get_fd_size_os (grub_util_fd_t fd, const char *name, unsigned *log_secsize) { unsigned long long nr; unsigned sector_size, log_sector_size; if (ioctl (fd, BLKGETSIZE64, &nr)) return -1; if (ioctl (fd, BLKSSZGET, §or_size)) return -1; if (sector_size & (sector_size - 1) || !sector_size) return -1; for (log_sector_size = 0; (1 << log_sector_size) < sector_size; log_sector_size++); if (log_secsize) *log_secsize = log_sector_size; if (nr & ((1 << log_sector_size) - 1)) grub_util_error ("%s", _("unaligned device size")); return nr; } static char * sysfs_partition_path (const char *dev, const char *entry) { const char *argv[7]; int fd; pid_t pid; FILE *udevadm; char *buf = NULL; size_t len = 0; char *path = NULL; argv[0] = "udevadm"; argv[1] = "info"; argv[2] = "--query"; argv[3] = "path"; argv[4] = "--name"; argv[5] = dev; argv[6] = NULL; pid = grub_util_exec_pipe (argv, &fd); if (!pid) return NULL; /* Parent. Read udevadm's output. */ udevadm = fdopen (fd, "r"); if (!udevadm) { grub_util_warn (_("Unable to open stream from %s: %s"), "udevadm", strerror (errno)); close (fd); goto out; } if (getline (&buf, &len, udevadm) > 0) { char *newline; newline = strchr (buf, '\n'); if (newline) *newline = '\0'; path = xasprintf ("/sys%s/%s", buf, entry); } out: if (udevadm) fclose (udevadm); waitpid (pid, NULL, 0); free (buf); return path; } static int sysfs_partition_start (const char *dev, grub_disk_addr_t *start) { char *path; FILE *fp; unsigned long long val; int ret = 0; path = sysfs_partition_path (dev, "start"); if (!path) return 0; fp = grub_util_fopen (path, "r"); if (!fp) goto out; if (fscanf (fp, "%llu", &val) == 1) { *start = (grub_disk_addr_t) val; ret = 1; } out: free (path); if (fp) fclose (fp); return ret; } grub_disk_addr_t grub_util_find_partition_start_os (const char *dev) { grub_disk_addr_t start = 0; grub_util_fd_t fd; struct hd_geometry hdg; if (sysfs_partition_start (dev, &start)) return start; fd = open (dev, O_RDONLY); if (fd == -1) { grub_error (GRUB_ERR_BAD_DEVICE, N_("cannot open `%s': %s"), dev, strerror (errno)); return 0; } if (ioctl (fd, HDIO_GETGEO, &hdg)) { grub_error (GRUB_ERR_BAD_DEVICE, "cannot get disk geometry of `%s'", dev); close (fd); return 0; } close (fd); return hdg.start; } /* Cache of partition start sectors for each disk. */ struct linux_partition_cache { struct linux_partition_cache *next; struct linux_partition_cache **prev; char *dev; unsigned long start; int partno; }; struct linux_partition_cache *linux_partition_cache_list; /* Check if we have devfs support. */ static int have_devfs (void) { static int dev_devfsd_exists = -1; if (dev_devfsd_exists < 0) { struct stat st; dev_devfsd_exists = stat ("/dev/.devfsd", &st) == 0; } return dev_devfsd_exists; } #pragma GCC diagnostic ignored "-Wformat-nonliteral" static int grub_hostdisk_linux_find_partition (char *dev, grub_disk_addr_t sector) { size_t len = strlen (dev); const char *format; char *p; int i; char real_dev[PATH_MAX]; struct linux_partition_cache *cache; int missing = 0; strcpy(real_dev, dev); if (have_devfs () && strcmp (real_dev + len - 5, "/disc") == 0) { p = real_dev + len - 4; format = "part%d"; } else if (strncmp (real_dev, "/dev/disk/by-id/", sizeof ("/dev/disk/by-id/") - 1) == 0) { p = real_dev + len; format = "-part%d"; } else if (real_dev[len - 1] >= '0' && real_dev[len - 1] <= '9') { p = real_dev + len; format = "p%d"; } else { p = real_dev + len; format = "%d"; } for (cache = linux_partition_cache_list; cache; cache = cache->next) { if (strcmp (cache->dev, dev) == 0 && cache->start == sector) { sprintf (p, format, cache->partno); strcpy (dev, real_dev); return 1; } } for (i = 1; i < 10000; i++) { grub_util_fd_t fd; grub_disk_addr_t start; struct stat st; sprintf (p, format, i); fd = open (real_dev, O_RDONLY); if (fd == -1) { if (missing++ < 10) continue; else return 0; } missing = 0; if (fstat (fd, &st) < 0 || !grub_util_device_is_mapped_stat (&st) || !grub_util_get_dm_node_linear_info (st.st_rdev, 0, 0, &start)) start = grub_util_find_partition_start_os (real_dev); /* We don't care about errors here. */ grub_errno = GRUB_ERR_NONE; close (fd); if (start == sector) { struct linux_partition_cache *new_cache_item; new_cache_item = xmalloc (sizeof *new_cache_item); new_cache_item->dev = xstrdup (dev); new_cache_item->start = start; new_cache_item->partno = i; grub_list_push (GRUB_AS_LIST_P (&linux_partition_cache_list), GRUB_AS_LIST (new_cache_item)); strcpy (dev, real_dev); return 1; } } return 0; } #pragma GCC diagnostic error "-Wformat-nonliteral" void grub_hostdisk_flush_initial_buffer (const char *os_dev) { grub_util_fd_t fd; struct stat st; fd = open (os_dev, O_RDONLY); if (fd >= 0 && fstat (fd, &st) >= 0 && S_ISBLK (st.st_mode)) ioctl (fd, BLKFLSBUF, 0); if (fd >= 0) close (fd); } int grub_util_fd_open_device (const grub_disk_t disk, grub_disk_addr_t sector, int flags, grub_disk_addr_t *max) { grub_util_fd_t fd; struct grub_util_hostdisk_data *data = disk->data; *max = ~0ULL; #ifdef O_LARGEFILE flags |= O_LARGEFILE; #endif #ifdef O_SYNC flags |= O_SYNC; #endif #ifdef O_FSYNC flags |= O_FSYNC; #endif #ifdef O_BINARY flags |= O_BINARY; #endif #ifdef O_CLOEXEC flags |= O_CLOEXEC; #endif /* Linux has a bug that the disk cache for a whole disk is not consistent with the one for a partition of the disk. */ { int is_partition = 0; char dev[PATH_MAX]; grub_disk_addr_t part_start = 0; part_start = grub_partition_get_start (disk->partition) >> (disk->log_sector_size - GRUB_DISK_SECTOR_BITS); strncpy (dev, grub_util_biosdisk_get_osdev (disk), sizeof (dev) - 1); dev[sizeof(dev) - 1] = '\0'; if (disk->partition && strncmp (dev, "/dev/", 5) == 0) { if (sector >= part_start) is_partition = grub_hostdisk_linux_find_partition (dev, part_start); else *max = part_start - sector; } reopen: if (data->dev && strcmp (data->dev, dev) == 0 && data->access_mode == (flags & O_ACCMODE)) { grub_dprintf ("hostdisk", "reusing open device `%s'\n", dev); fd = data->fd; } else { free (data->dev); data->dev = 0; if (data->fd != -1) { if (data->access_mode == O_RDWR || data->access_mode == O_WRONLY) { fsync (data->fd); if (data->is_disk) ioctl (data->fd, BLKFLSBUF, 0); } close (data->fd); data->fd = -1; } /* Open the partition. */ grub_dprintf ("hostdisk", "opening the device `%s' in open_device()\n", dev); fd = open (dev, flags); if (fd < 0) { grub_error (GRUB_ERR_BAD_DEVICE, N_("cannot open `%s': %s"), dev, strerror (errno)); return -1; } data->dev = xstrdup (dev); data->access_mode = (flags & O_ACCMODE); data->fd = fd; if (data->is_disk) ioctl (data->fd, BLKFLSBUF, 0); } if (is_partition) { *max = grub_util_get_fd_size (fd, dev, 0); *max >>= disk->log_sector_size; if (sector - part_start >= *max) { *max = disk->partition->len - (sector - part_start); if (*max == 0) *max = ~0ULL; is_partition = 0; strncpy (dev, grub_util_biosdisk_get_osdev (disk), sizeof (dev) - 1); dev[sizeof(dev) - 1] = '\0'; goto reopen; } sector -= part_start; *max -= sector; } } if (grub_util_fd_seek (fd, sector << disk->log_sector_size)) { close (fd); grub_error (GRUB_ERR_BAD_DEVICE, N_("cannot seek `%s': %s"), grub_util_biosdisk_get_osdev (disk), strerror (errno)); return -1; } return fd; }