From: Karel Zak Date: Thu, 31 May 2007 12:31:51 +0000 (+0200) Subject: fdisk: add GPT detection code X-Git-Url: https://err.no/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5dbff4c0eb7c326e21891b6ce4f123ba96511f87;p=util-linux fdisk: add GPT detection code The GPT (GUID Partition Table) is unsupported by fdisk, sfdisk and cfdisk. Unfortunately, the fdisk doesn't complain about GPT.. that's dangerous, because user is able to blindly edit PT with unexpected results. Signed-off-by: Karel Zak --- diff --git a/fdisk/Makefile.am b/fdisk/Makefile.am index 1e4ad02d..c407bbfd 100644 --- a/fdisk/Makefile.am +++ b/fdisk/Makefile.am @@ -2,7 +2,7 @@ include $(top_srcdir)/config/include-Makefile.am EXTRA_DIST = README.fdisk README.cfdisk sfdisk.examples partitiontype.c -fdisk_common = disksize.c i386_sys_types.c common.h +fdisk_common = disksize.c i386_sys_types.c common.h gpt.c gpt.h if !M68K diff --git a/fdisk/cfdisk.c b/fdisk/cfdisk.c index cd0f6864..40b23460 100644 --- a/fdisk/cfdisk.c +++ b/fdisk/cfdisk.c @@ -83,6 +83,7 @@ #include "nls.h" #include "xstrncpy.h" #include "common.h" +#include "gpt.h" #define DEFAULT_DEVICE "/dev/hda" #define ALTERNATE_DEVICE "/dev/sda" @@ -1669,6 +1670,13 @@ fill_p_info(void) { opentype = O_RDWR; opened = TRUE; + if (gpt_probe_signature_devname(fd)) { + print_warning(_("Warning!! Unsupported GPT (GUID Partition Table) detected. Use GNU Parted.")); + refresh(); + getch(); + clear_warning(); + } + /* Blocks are visible in more than one way: e.g. as block on /dev/hda and as block on /dev/hda3 By a bug in the Linux buffer cache, we will see the old diff --git a/fdisk/fdisk.8 b/fdisk/fdisk.8 index a5f77695..7b487051 100644 --- a/fdisk/fdisk.8 +++ b/fdisk/fdisk.8 @@ -42,6 +42,11 @@ is a menu driven program for creation and manipulation of partition tables. It understands DOS type partition tables and BSD or SUN type disklabels. +.B fdisk +doesn't understand GUID Partition Table (GPT) and +it is not designed for large partitions. In particular case use more advanced GNU +.B parted(8). + The .I device is usually one of the following: diff --git a/fdisk/fdisk.c b/fdisk/fdisk.c index c4f6458c..ecaac90e 100644 --- a/fdisk/fdisk.c +++ b/fdisk/fdisk.c @@ -34,6 +34,8 @@ #include #endif +#include "gpt.h" + static void delete_partition(int i); #define hex_val(c) ({ \ @@ -2371,6 +2373,14 @@ is_ide_cdrom_or_tape(char *device) { return is_ide; } +static void +gpt_warning(char *dev) +{ + if (dev && gpt_probe_signature_devname(dev)) + fprintf(stderr, _("\nWARNING: GPT (GUID Partition Table) detected on '%s'! " + "The util fdisk doesn't support GPT. Use GNU Parted.\n\n"), dev); +} + static void try(char *device, int user_specified) { int gb; @@ -2381,6 +2391,7 @@ try(char *device, int user_specified) { if (!user_specified) if (is_ide_cdrom_or_tape(device)) return; + gpt_warning(device); if ((fd = open(disk_device, type_open)) >= 0) { gb = get_boot(try_only); if (gb > 0) { /* I/O error */ @@ -2440,6 +2451,8 @@ unknown_command(int c) { printf(_("%c: unknown command\n"), c); } + + int main(int argc, char **argv) { int j, c; @@ -2544,6 +2557,7 @@ main(int argc, char **argv) { for (j = optind; j < argc; j++) { disk_device = argv[j]; + gpt_warning(disk_device); if ((fd = open(disk_device, type_open)) < 0) fatal(unable_to_open); if (disksize(fd, &size)) @@ -2564,6 +2578,7 @@ main(int argc, char **argv) { else fatal(usage2); + gpt_warning(disk_device); get_boot(fdisk); if (osf_label) { diff --git a/fdisk/gpt.c b/fdisk/gpt.c new file mode 100644 index 00000000..f1d751ac --- /dev/null +++ b/fdisk/gpt.c @@ -0,0 +1,302 @@ +/* + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * + * GPT (GUID Partition Table) signature detection. Based on libparted and + * util-linux/partx. + * + * Warning: this code doesn't do all GPT checks (CRC32, Protective MBR, ..). + * It's really GPT signature detection only. + * + * Copyright (C) 2007 Karel Zak + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gpt.h" + +#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c)) +#define SECTOR_SIZE 512 /* default */ + +#define _GET_BYTE(x, n) ( ((x) >> (8 * (n))) & 0xff ) + +#define _PED_SWAP64(x) ( (_GET_BYTE(x, 0) << 56) \ + + (_GET_BYTE(x, 1) << 48) \ + + (_GET_BYTE(x, 2) << 40) \ + + (_GET_BYTE(x, 3) << 32) \ + + (_GET_BYTE(x, 4) << 24) \ + + (_GET_BYTE(x, 5) << 16) \ + + (_GET_BYTE(x, 6) << 8) \ + + (_GET_BYTE(x, 7) << 0) ) + +#define PED_SWAP64(x) ((uint64_t) _PED_SWAP64( (uint64_t) (x) )) + +#if __BYTE_ORDER == __LITTLE_ENDIAN +# define CPU_TO_LE64(x) (x) +#else +# define CPU_TO_LE64(x) PED_SWAP64(x) +#endif + +#define BLKSSZGET _IO(0x12,104) /* get block device sector size */ +#define BLKGETLASTSECT _IO(0x12,108) /* get last sector of block device */ +#define BLKGETSIZE _IO(0x12,96) /* return device size */ +#define BLKGETSIZE64 _IOR(0x12,114,size_t) /* return device size in bytes (u64 *arg) */ + +#define GPT_HEADER_SIGNATURE 0x5452415020494645LL +#define GPT_PRIMARY_PARTITION_TABLE_LBA 1 + +typedef struct { + uint32_t time_low; + uint16_t time_mid; + uint16_t time_hi_and_version; + uint8_t clock_seq_hi_and_reserved; + uint8_t clock_seq_low; + uint8_t node[6]; +} /* __attribute__ ((packed)) */ efi_guid_t; +/* commented out "__attribute__ ((packed))" to work around gcc bug (fixed + * in gcc3.1): __attribute__ ((packed)) breaks addressing on initialized + * data. It turns out we don't need it in this case, so it doesn't break + * anything :) + */ + +typedef struct _GuidPartitionTableHeader_t { + uint64_t Signature; + uint32_t Revision; + uint32_t HeaderSize; + uint32_t HeaderCRC32; + uint32_t Reserved1; + uint64_t MyLBA; + uint64_t AlternateLBA; + uint64_t FirstUsableLBA; + uint64_t LastUsableLBA; + efi_guid_t DiskGUID; + uint64_t PartitionEntryLBA; + uint32_t NumberOfPartitionEntries; + uint32_t SizeOfPartitionEntry; + uint32_t PartitionEntryArrayCRC32; + uint8_t Reserved2[512 - 92]; +} __attribute__ ((packed)) GuidPartitionTableHeader_t; + +struct blkdev_ioctl_param { + unsigned int block; + size_t content_length; + char * block_contents; +}; + +static int +_get_linux_version (void) +{ + static int kver = -1; + struct utsname uts; + int major; + int minor; + int teeny; + + if (kver != -1) + return kver; + if (uname (&uts)) + return kver = 0; + if (sscanf (uts.release, "%u.%u.%u", &major, &minor, &teeny) != 3) + return kver = 0; + return kver = KERNEL_VERSION (major, minor, teeny); +} + +static unsigned int +_get_sector_size (int fd) +{ + unsigned int sector_size; + + if (_get_linux_version() < KERNEL_VERSION (2,3,0)) + return SECTOR_SIZE; + if (ioctl (fd, BLKSSZGET, §or_size)) + return SECTOR_SIZE; + return sector_size; +} + +static uint64_t +_get_num_sectors(int fd) +{ + int version = _get_linux_version(); + unsigned long size; + uint64_t bytes=0; + + if (version >= KERNEL_VERSION(2,5,4) || + (version < KERNEL_VERSION(2,5,0) && + version >= KERNEL_VERSION (2,4,18))) + { + if (ioctl(fd, BLKGETSIZE64, &bytes) == 0) + return bytes / _get_sector_size(fd); + } + if (ioctl (fd, BLKGETSIZE, &size)) + return 0; + return size; +} + +static uint64_t +last_lba(int fd) +{ + int rc; + uint64_t sectors = 0; + struct stat s; + + memset(&s, 0, sizeof (s)); + rc = fstat(fd, &s); + if (rc == -1) + { + fprintf(stderr, "last_lba() could not stat: %s\n", + strerror(errno)); + return 0; + } + if (S_ISBLK(s.st_mode)) + sectors = _get_num_sectors(fd); + else + { + fprintf(stderr, + "last_lba(): I don't know how to handle files with mode %x\n", + s.st_mode); + sectors = 1; + } + return sectors - 1; +} + +static ssize_t +read_lastoddsector(int fd, uint64_t lba, void *buffer, size_t count) +{ + int rc; + struct blkdev_ioctl_param ioctl_param; + + if (!buffer) return 0; + + ioctl_param.block = 0; /* read the last sector */ + ioctl_param.content_length = count; + ioctl_param.block_contents = buffer; + + rc = ioctl(fd, BLKGETLASTSECT, &ioctl_param); + if (rc == -1) perror("read failed"); + + return !rc; +} + +static ssize_t +read_lba(int fd, uint64_t lba, void *buffer, size_t bytes) +{ + int sector_size = _get_sector_size(fd); + off_t offset = lba * sector_size; + ssize_t bytesread; + + lseek(fd, offset, SEEK_SET); + bytesread = read(fd, buffer, bytes); + + /* Kludge. This is necessary to read/write the last + block of an odd-sized disk, until Linux 2.5.x kernel fixes. + This is only used by gpt.c, and only to read + one sector, so we don't have to be fancy. + */ + if (!bytesread && !(last_lba(fd) & 1) && lba == last_lba(fd)) + bytesread = read_lastoddsector(fd, lba, buffer, bytes); + return bytesread; +} + +static GuidPartitionTableHeader_t * +alloc_read_gpt_header(int fd, uint64_t lba) +{ + GuidPartitionTableHeader_t *gpt = + (GuidPartitionTableHeader_t *) malloc(sizeof (GuidPartitionTableHeader_t)); + if (!gpt) + return NULL; + memset(gpt, 0, sizeof (*gpt)); + if (!read_lba(fd, lba, gpt, sizeof (GuidPartitionTableHeader_t))) + { + free(gpt); + return NULL; + } + return gpt; +} + +static int +gpt_check_signature(int fd, uint64_t lba) +{ + GuidPartitionTableHeader_t *gpt; + int res=0; + + if ((gpt = alloc_read_gpt_header(fd, lba))) + { + if (gpt->Signature == CPU_TO_LE64(GPT_HEADER_SIGNATURE)) + res = 1; + free(gpt); + } + return res; +} + +/* returns: + * 0 not found GPT + * 1 for valid primary GPT header + * 2 for valid alternative GPT header + */ +int +gpt_probe_signature_fd(int fd) +{ + int res = 0; + + /* check primary GPT header */ + if (gpt_check_signature(fd, GPT_PRIMARY_PARTITION_TABLE_LBA)) + res = 1; + else + { + /* check alternative GPT header */ + uint64_t lastlba = last_lba(fd); + if (gpt_check_signature(fd, lastlba)) + res = 2; + } + return res; +} + +int +gpt_probe_signature_devname(char *devname) +{ + int res, fd; + if ((fd = open(devname, O_RDONLY)) < 0) + return 0; + res = gpt_probe_signature_fd(fd); + close(fd); + return res; +} + +#ifdef GPT_TEST_MAIN +int +main(int argc, char **argv) +{ + if (argc!=2) + { + fprintf(stderr, "usage: %s \n", argv[0]); + exit(EXIT_FAILURE); + } + if (gpt_probe_signature_devname(argv[1])) + printf("GPT (GUID Partition Table) detected on %s\n", argv[1]); + exit(EXIT_SUCCESS); +} +#endif diff --git a/fdisk/gpt.h b/fdisk/gpt.h new file mode 100644 index 00000000..e57c24a3 --- /dev/null +++ b/fdisk/gpt.h @@ -0,0 +1,8 @@ +#ifndef FDISK_GPT_H +#define FDISK_GPT_H + +extern int gpt_probe_signature_fd(int fd); +extern int gpt_probe_signature_devname(char *devname); + +#endif /* FDISK_GPT_H */ + diff --git a/fdisk/sfdisk.8 b/fdisk/sfdisk.8 index 4e1b5a04..37052ab4 100644 --- a/fdisk/sfdisk.8 +++ b/fdisk/sfdisk.8 @@ -18,6 +18,11 @@ has four (main) uses: list the size of a partition, list the partitions on a device, check the partitions on a device, and - very dangerous - repartition a device. +.B sfdisk +doesn't understand GUID Partition Table (GPT) and +it is not designed for large partitions. In particular case use more advanced GNU +.B parted(8). + .SS "List Sizes" .BI "sfdisk \-s " partition gives the size of diff --git a/fdisk/sfdisk.c b/fdisk/sfdisk.c index d2155357..79e9c20e 100644 --- a/fdisk/sfdisk.c +++ b/fdisk/sfdisk.c @@ -50,6 +50,8 @@ #include "nls.h" #include "common.h" +#include "gpt.h" + #define SIZE(a) (sizeof(a)/sizeof(a[0])) /* @@ -2444,6 +2446,23 @@ nextproc(void) { return NULL; } +static void +gpt_warning(char *dev, int warn_only) +{ + if (force) + warn_only = 1; + + if (dev && gpt_probe_signature_devname(dev)) { + fflush(stdout); + fprintf(stderr, _("\nWARNING: GPT (GUID Partition Table) detected on '%s'! " + "The util sfdisk doesn't support GPT. Use GNU Parted.\n\n"), dev); + if (!warn_only) { + fprintf(stderr, _("Use the --force flag to overrule this check.\n")); + exit(1); + } + } +} + static void do_list(char *dev, int silent); static void do_size(char *dev, int silent); static void do_geom(char *dev, int silent); @@ -2589,6 +2608,7 @@ main(int argc, char **argv) { while ((dev = nextproc()) != NULL) { if (is_ide_cdrom_or_tape(dev)) continue; + gpt_warning(dev, 1); if (opt_out_geom) do_geom(dev, 1); if (opt_out_pt_geom) @@ -2616,6 +2636,7 @@ main(int argc, char **argv) { if (opt_list || opt_out_geom || opt_out_pt_geom || opt_size || verify) { while (optind < argc) { + gpt_warning(argv[optind], 1); if (opt_out_geom) do_geom(argv[optind], 0); if (opt_out_pt_geom) @@ -2629,6 +2650,9 @@ main(int argc, char **argv) { exit(exit_status); } + if (optind != argc-1) + gpt_warning(argv[optind], 0); + if (activate) { do_activate(argv+optind, argc-optind, activatearg); exit(exit_status); @@ -2648,7 +2672,7 @@ main(int argc, char **argv) { (optind == argc-2) ? 0 : argv[optind+2]); exit(exit_status); } - + if (optind != argc-1) fatal(_("can specify only one device (except with -l or -s)\n")); dev = argv[optind];