| /* |
| * |
| * 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 <kzak@redhat.com> |
| * |
| */ |
| |
| #include <stdio.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <inttypes.h> |
| #include <sys/stat.h> |
| #include <sys/ioctl.h> |
| #include <sys/utsname.h> |
| #include <sys/types.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <errno.h> |
| |
| #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 if (S_ISREG(s.st_mode)) |
| sectors = s.st_size >> _get_sector_size(fd); |
| else |
| { |
| fprintf(stderr, |
| "last_lba(): I don't know how to handle files with mode %o\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 <dev>\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 |