summaryrefslogblamecommitdiffstats
path: root/fdisk/gpt.c
blob: f1d751aca388c3fe248715efa6cf306aacbf25bc (plain) (tree)













































































































































































































































































































                                                                                            
/*
 *
 * 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, &sector_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 <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