| /* |
| gpt.[ch] |
| |
| Copyright (C) 2000-2001 Dell Computer Corporation <Matt_Domsch@dell.com> |
| |
| EFI GUID Partition Table handling |
| Per Intel EFI Specification v1.02 |
| http://developer.intel.com/technology/efi/efi.htm |
| |
| 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| |
| // For TWRP purposes, we'll be opting for version 3 of the GPL |
| |
| #include <stdio.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <inttypes.h> |
| #include <sys/stat.h> |
| #include <sys/ioctl.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #include <sys/utsname.h> |
| #include <asm/byteorder.h> |
| #include "gpt.h" |
| #include "gptcrc32.h" |
| |
| #define BLKGETLASTSECT _IO(0x12,108) /* get last sector of block device */ |
| #define BLKGETSIZE _IO(0x12,96) /* return device size */ |
| #define BLKSSZGET _IO(0x12,104) /* get block device sector size */ |
| #define BLKGETSIZE64 _IOR(0x12,114,uint64_t) /* return device size in bytes (u64 *arg) */ |
| |
| struct blkdev_ioctl_param { |
| unsigned int block; |
| size_t content_length; |
| char * block_contents; |
| }; |
| |
| static inline int |
| efi_guidcmp(efi_guid_t left, efi_guid_t right) |
| { |
| return memcmp(&left, &right, sizeof (efi_guid_t)); |
| } |
| |
| static int |
| get_sector_size(int filedes) |
| { |
| int rc, sector_size = 512; |
| |
| rc = ioctl(filedes, BLKSSZGET, §or_size); |
| if (rc) |
| sector_size = 512; |
| return sector_size; |
| } |
| |
| /** |
| * efi_crc32() - EFI version of crc32 function |
| * @buf: buffer to calculate crc32 of |
| * @len - length of buf |
| * |
| * Description: Returns EFI-style CRC32 value for @buf |
| * |
| * This function uses the little endian Ethernet polynomial |
| * but seeds the function with ~0, and xor's with ~0 at the end. |
| * Note, the EFI Specification, v1.02, has a reference to |
| * Dr. Dobbs Journal, May 1994 (actually it's in May 1992). |
| */ |
| static inline uint32_t |
| efi_crc32(const void *buf, unsigned long len) |
| { |
| return (gptcrc32(buf, len, ~0L) ^ ~0L); |
| } |
| |
| /** |
| * is_pmbr_valid(): test Protective MBR for validity |
| * @mbr: pointer to a legacy mbr structure |
| * |
| * Description: Returns 1 if PMBR is valid, 0 otherwise. |
| * Validity depends on two things: |
| * 1) MSDOS signature is in the last two bytes of the MBR |
| * 2) One partition of type 0xEE is found |
| */ |
| static int |
| is_pmbr_valid(legacy_mbr *mbr) |
| { |
| int i, found = 0, signature = 0; |
| if (!mbr) |
| return 0; |
| signature = (__le16_to_cpu(mbr->signature) == MSDOS_MBR_SIGNATURE); |
| for (i = 0; signature && i < 4; i++) { |
| if (mbr->partition[i].os_type == |
| EFI_PMBR_OSTYPE_EFI_GPT) { |
| found = 1; |
| break; |
| } |
| } |
| return (signature && found); |
| } |
| |
| /** |
| * kernel_has_blkgetsize64() |
| * |
| * Returns: 0 on false, 1 on true |
| * True means kernel is 2.4.x, x>=18, or |
| * is 2.5.x, x>4, or |
| * is > 2.5 |
| */ |
| static int |
| kernel_has_blkgetsize64(void) |
| { |
| int major=0, minor=0, patch=0, parsed; |
| int rc; |
| struct utsname u; |
| |
| memset(&u, 0, sizeof(u)); |
| rc = uname(&u); |
| if (rc) return 0; |
| |
| parsed = sscanf(u.release, "%d.%d.%d", &major, &minor, &patch); |
| if (parsed < 3) return 0; |
| if (major > 2) return 1; |
| if (major == 2 && minor > 5) return 1; |
| if (major == 2 && minor == 5 && patch >= 4) return 1; |
| if (major == 2 && minor == 4 && patch >= 18) return 1; |
| return 0; |
| } |
| |
| |
| /************************************************************ |
| * _get_num_sectors |
| * Requires: |
| * - filedes is an open file descriptor, suitable for reading |
| * Modifies: nothing |
| * Returns: |
| * Last LBA value on success |
| * 0 on error |
| * |
| * Try getting BLKGETSIZE64 and BLKSSZGET first, |
| * then BLKGETSIZE if necessary. |
| * Kernels 2.4.15-2.4.18 and 2.5.0-2.5.3 have a broken BLKGETSIZE64 |
| * which returns the number of 512-byte sectors, not the size of |
| * the disk in bytes. Fixed in kernels 2.4.18-pre8 and 2.5.4-pre3. |
| ************************************************************/ |
| static uint64_t |
| _get_num_sectors(int filedes) |
| { |
| unsigned long sectors=0; |
| uint64_t bytes=0; |
| int rc; |
| if (kernel_has_blkgetsize64()) { |
| rc = ioctl(filedes, BLKGETSIZE64, &bytes); |
| if (!rc) |
| return bytes / get_sector_size(filedes); |
| } |
| |
| rc = ioctl(filedes, BLKGETSIZE, §ors); |
| if (rc) |
| return 0; |
| |
| return sectors; |
| } |
| |
| /************************************************************ |
| * last_lba(): return number of last logical block of device |
| * |
| * @fd |
| * |
| * Description: returns Last LBA value on success, 0 on error. |
| * Notes: The value st_blocks gives the size of the file |
| * in 512-byte blocks, which is OK if |
| * EFI_BLOCK_SIZE_SHIFT == 9. |
| ************************************************************/ |
| |
| static uint64_t |
| last_lba(int filedes) |
| { |
| int rc; |
| uint64_t sectors = 0; |
| struct stat s; |
| memset(&s, 0, sizeof (s)); |
| rc = fstat(filedes, &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(filedes); |
| } 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 __unused, 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; |
| void *aligned; |
| void *unaligned; |
| |
| if (bytes % sector_size) |
| return EINVAL; |
| |
| unaligned = malloc(bytes+sector_size-1); |
| aligned = (void *) |
| (((unsigned long)unaligned + sector_size - 1) & |
| ~(unsigned long)(sector_size-1)); |
| memset(aligned, 0, bytes); |
| |
| |
| lseek(fd, offset, SEEK_SET); |
| bytesread = read(fd, aligned, bytes); |
| memcpy(buffer, aligned, bytesread); |
| free(unaligned); |
| |
| /* 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; |
| } |
| |
| /** |
| * alloc_read_gpt_entries(): reads partition entries from disk |
| * @fd is an open file descriptor to the whole disk |
| * @gpt is a buffer into which the GPT will be put |
| * Description: Returns ptes on success, NULL on error. |
| * Allocates space for PTEs based on information found in @gpt. |
| * Notes: remember to free pte when you're done! |
| */ |
| static gpt_entry * |
| alloc_read_gpt_entries(int fd, gpt_header * gpt) |
| { |
| gpt_entry *pte; |
| size_t count = __le32_to_cpu(gpt->num_partition_entries) * |
| __le32_to_cpu(gpt->sizeof_partition_entry); |
| |
| if (!count) return NULL; |
| |
| pte = (gpt_entry *)malloc(count); |
| if (!pte) |
| return NULL; |
| memset(pte, 0, count); |
| |
| if (!read_lba(fd, __le64_to_cpu(gpt->partition_entry_lba), pte, |
| count)) { |
| free(pte); |
| return NULL; |
| } |
| return pte; |
| } |
| |
| /** |
| * alloc_read_gpt_header(): Allocates GPT header, reads into it from disk |
| * @fd is an open file descriptor to the whole disk |
| * @lba is the Logical Block Address of the partition table |
| * |
| * Description: returns GPT header on success, NULL on error. Allocates |
| * and fills a GPT header starting at @ from @bdev. |
| * Note: remember to free gpt when finished with it. |
| */ |
| static gpt_header * |
| alloc_read_gpt_header(int fd, uint64_t lba) |
| { |
| gpt_header *gpt; |
| gpt = (gpt_header *) |
| malloc(sizeof (gpt_header)); |
| if (!gpt) |
| return NULL; |
| memset(gpt, 0, sizeof (*gpt)); |
| if (!read_lba(fd, lba, gpt, sizeof (gpt_header))) { |
| free(gpt); |
| return NULL; |
| } |
| |
| return gpt; |
| } |
| |
| /** |
| * is_gpt_valid() - tests one GPT header and PTEs for validity |
| * @fd is an open file descriptor to the whole disk |
| * @lba is the logical block address of the GPT header to test |
| * @gpt is a GPT header ptr, filled on return. |
| * @ptes is a PTEs ptr, filled on return. |
| * |
| * Description: returns 1 if valid, 0 on error. |
| * If valid, returns pointers to newly allocated GPT header and PTEs. |
| */ |
| static int |
| is_gpt_valid(int fd, uint64_t lba, |
| gpt_header ** gpt, gpt_entry ** ptes) |
| { |
| int rc = 0; /* default to not valid */ |
| uint32_t crc, origcrc; |
| |
| if (!gpt || !ptes) |
| return 0; |
| if (!(*gpt = alloc_read_gpt_header(fd, lba))) |
| return 0; |
| |
| /* Check the GUID Partition Table signature */ |
| if (__le64_to_cpu((*gpt)->signature) != GPT_HEADER_SIGNATURE) { |
| /* |
| printf("GUID Partition Table Header signature is wrong: %" PRIx64" != %" PRIx64 "\n", |
| __le64_to_cpu((*gpt)->signature), GUID_PT_HEADER_SIGNATURE); |
| */ |
| free(*gpt); |
| *gpt = NULL; |
| return rc; |
| } |
| |
| /* Check the GUID Partition Table Header CRC */ |
| origcrc = __le32_to_cpu((*gpt)->header_crc32); |
| (*gpt)->header_crc32 = 0; |
| crc = efi_crc32(*gpt, __le32_to_cpu((*gpt)->header_size)); |
| if (crc != origcrc) { |
| // printf( "GPTH CRC check failed, %x != %x.\n", origcrc, crc); |
| (*gpt)->header_crc32 = __cpu_to_le32(origcrc); |
| free(*gpt); |
| *gpt = NULL; |
| return 0; |
| } |
| (*gpt)->header_crc32 = __cpu_to_le32(origcrc); |
| |
| /* Check that the my_lba entry points to the LBA |
| * that contains the GPT we read */ |
| if (__le64_to_cpu((*gpt)->my_lba) != lba) { |
| // printf( "my_lba % PRIx64 "x != lba %"PRIx64 "x.\n", __le64_to_cpu((*gpt)->my_lba), lba); |
| free(*gpt); |
| *gpt = NULL; |
| return 0; |
| } |
| |
| if (!(*ptes = alloc_read_gpt_entries(fd, *gpt))) { |
| free(*gpt); |
| *gpt = NULL; |
| return 0; |
| } |
| |
| /* Check the GUID Partition Entry Array CRC */ |
| crc = efi_crc32(*ptes, |
| __le32_to_cpu((*gpt)->num_partition_entries) * |
| __le32_to_cpu((*gpt)->sizeof_partition_entry)); |
| if (crc != __le32_to_cpu((*gpt)->partition_entry_array_crc32)) { |
| // printf("GUID Partitition Entry Array CRC check failed.\n"); |
| free(*gpt); |
| *gpt = NULL; |
| free(*ptes); |
| *ptes = NULL; |
| return 0; |
| } |
| |
| /* We're done, all's well */ |
| return 1; |
| } |
| /** |
| * compare_gpts() - Search disk for valid GPT headers and PTEs |
| * @pgpt is the primary GPT header |
| * @agpt is the alternate GPT header |
| * @lastlba is the last LBA number |
| * Description: Returns nothing. Sanity checks pgpt and agpt fields |
| * and prints warnings on discrepancies. |
| * |
| */ |
| static void |
| compare_gpts(gpt_header *pgpt, gpt_header *agpt, uint64_t lastlba) |
| { |
| int error_found = 0; |
| if (!pgpt || !agpt) |
| return; |
| if (__le64_to_cpu(pgpt->my_lba) != __le64_to_cpu(agpt->alternate_lba)) { |
| fprintf(stderr, |
| "GPT:Primary header LBA != Alt. header alternate_lba\n"); |
| fprintf(stderr, "GPT:0x%" PRIx64 " != 0x%" PRIx64 "\n", |
| __le64_to_cpu(pgpt->my_lba), |
| __le64_to_cpu(agpt->alternate_lba)); |
| error_found++; |
| } |
| if (__le64_to_cpu(pgpt->alternate_lba) != __le64_to_cpu(agpt->my_lba)) { |
| fprintf(stderr, |
| "GPT:Primary header alternate_lba != Alt. header my_lba\n"); |
| fprintf(stderr, "GPT:0x%" PRIx64 " != 0x%" PRIx64 "\n", |
| __le64_to_cpu(pgpt->alternate_lba), |
| __le64_to_cpu(agpt->my_lba)); |
| error_found++; |
| } |
| if (__le64_to_cpu(pgpt->first_usable_lba) != |
| __le64_to_cpu(agpt->first_usable_lba)) { |
| fprintf(stderr, "GPT:first_usable_lbas don't match.\n"); |
| fprintf(stderr, "GPT:0x%" PRIx64 " != 0x%" PRIx64 "\n", |
| __le64_to_cpu(pgpt->first_usable_lba), |
| __le64_to_cpu(agpt->first_usable_lba)); |
| error_found++; |
| } |
| if (__le64_to_cpu(pgpt->last_usable_lba) != |
| __le64_to_cpu(agpt->last_usable_lba)) { |
| fprintf(stderr, "GPT:last_usable_lbas don't match.\n"); |
| fprintf(stderr, "GPT:0x%" PRIx64 " != 0x%" PRIx64 "\n", |
| __le64_to_cpu(pgpt->last_usable_lba), |
| __le64_to_cpu(agpt->last_usable_lba)); |
| error_found++; |
| } |
| if (efi_guidcmp(pgpt->disk_guid, agpt->disk_guid)) { |
| fprintf(stderr, "GPT:disk_guids don't match.\n"); |
| error_found++; |
| } |
| if (__le32_to_cpu(pgpt->num_partition_entries) != |
| __le32_to_cpu(agpt->num_partition_entries)) { |
| fprintf(stderr, "GPT:num_partition_entries don't match: " |
| "0x%x != 0x%x\n", |
| __le32_to_cpu(pgpt->num_partition_entries), |
| __le32_to_cpu(agpt->num_partition_entries)); |
| error_found++; |
| } |
| if (__le32_to_cpu(pgpt->sizeof_partition_entry) != |
| __le32_to_cpu(agpt->sizeof_partition_entry)) { |
| fprintf(stderr, |
| "GPT:sizeof_partition_entry values don't match: " |
| "0x%x != 0x%x\n", |
| __le32_to_cpu(pgpt->sizeof_partition_entry), |
| __le32_to_cpu(agpt->sizeof_partition_entry)); |
| error_found++; |
| } |
| if (__le32_to_cpu(pgpt->partition_entry_array_crc32) != |
| __le32_to_cpu(agpt->partition_entry_array_crc32)) { |
| fprintf(stderr, |
| "GPT:partition_entry_array_crc32 values don't match: " |
| "0x%x != 0x%x\n", |
| __le32_to_cpu(pgpt->partition_entry_array_crc32), |
| __le32_to_cpu(agpt->partition_entry_array_crc32)); |
| error_found++; |
| } |
| if (__le64_to_cpu(pgpt->alternate_lba) != lastlba) { |
| fprintf(stderr, |
| "GPT:Primary header thinks Alt. header is not at the end of the disk.\n"); |
| fprintf(stderr, "GPT:0x%" PRIx64 " != 0x%" PRIx64 "\n", |
| __le64_to_cpu(pgpt->alternate_lba), lastlba); |
| error_found++; |
| } |
| |
| if (__le64_to_cpu(agpt->my_lba) != lastlba) { |
| fprintf(stderr, |
| "GPT:Alternate GPT header not at the end of the disk.\n"); |
| fprintf(stderr, "GPT:0x%" PRIx64 " != 0x%" PRIx64 "\n", |
| __le64_to_cpu(agpt->my_lba), lastlba); |
| error_found++; |
| } |
| |
| if (error_found) |
| fprintf(stderr, |
| "GPT: Use GNU Parted to correct GPT errors.\n"); |
| return; |
| } |
| |
| /** |
| * find_valid_gpt() - Search disk for valid GPT headers and PTEs |
| * @fd is an open file descriptor to the whole disk |
| * @gpt is a GPT header ptr, filled on return. |
| * @ptes is a PTEs ptr, filled on return. |
| * Description: Returns 1 if valid, 0 on error. |
| * If valid, returns pointers to newly allocated GPT header and PTEs. |
| * Validity depends on finding either the Primary GPT header and PTEs valid, |
| * or the Alternate GPT header and PTEs valid, and the PMBR valid. |
| */ |
| static int |
| find_valid_gpt(int fd, gpt_header ** gpt, gpt_entry ** ptes) |
| { |
| int good_pgpt = 0, good_agpt = 0, good_pmbr = 0; |
| gpt_header *pgpt = NULL, *agpt = NULL; |
| gpt_entry *pptes = NULL, *aptes = NULL; |
| legacy_mbr *legacymbr = NULL; |
| uint64_t lastlba; |
| if (!gpt || !ptes) |
| return 0; |
| |
| lastlba = last_lba(fd); |
| good_pgpt = is_gpt_valid(fd, GPT_PRIMARY_PARTITION_TABLE_LBA, |
| &pgpt, &pptes); |
| if (good_pgpt) { |
| good_agpt = is_gpt_valid(fd, |
| __le64_to_cpu(pgpt->alternate_lba), |
| &agpt, &aptes); |
| if (!good_agpt) { |
| good_agpt = is_gpt_valid(fd, lastlba, |
| &agpt, &aptes); |
| } |
| } |
| else { |
| good_agpt = is_gpt_valid(fd, lastlba, |
| &agpt, &aptes); |
| } |
| |
| /* The obviously unsuccessful case */ |
| if (!good_pgpt && !good_agpt) { |
| goto fail; |
| } |
| |
| /* This will be added to the EFI Spec. per Intel after v1.02. */ |
| legacymbr = malloc(sizeof (*legacymbr)); |
| if (legacymbr) { |
| memset(legacymbr, 0, sizeof (*legacymbr)); |
| read_lba(fd, 0, (uint8_t *) legacymbr, |
| sizeof (*legacymbr)); |
| good_pmbr = is_pmbr_valid(legacymbr); |
| free(legacymbr); |
| legacymbr=NULL; |
| } |
| |
| /* Failure due to bad PMBR */ |
| if ((good_pgpt || good_agpt) && !good_pmbr) { |
| fprintf(stderr, |
| " Warning: Disk has a valid GPT signature " |
| "but invalid PMBR.\n" |
| " Assuming this disk is *not* a GPT disk anymore.\n" |
| " Use gpt kernel option to override. " |
| "Use GNU Parted to correct disk.\n"); |
| goto fail; |
| } |
| |
| /* Would fail due to bad PMBR, but force GPT anyhow */ |
| if ((good_pgpt || good_agpt) && !good_pmbr) { |
| fprintf(stderr, |
| " Warning: Disk has a valid GPT signature but " |
| "invalid PMBR.\n" |
| " Use GNU Parted to correct disk.\n" |
| " gpt option taken, disk treated as GPT.\n"); |
| } |
| |
| compare_gpts(pgpt, agpt, lastlba); |
| |
| /* The good cases */ |
| if (good_pgpt && (good_pmbr)) { |
| *gpt = pgpt; |
| *ptes = pptes; |
| if (agpt) { free(agpt); agpt = NULL; } |
| if (aptes) { free(aptes); aptes = NULL; } |
| if (!good_agpt) { |
| fprintf(stderr, |
| "Alternate GPT is invalid, " |
| "using primary GPT.\n"); |
| } |
| return 1; |
| } |
| else if (good_agpt && (good_pmbr)) { |
| *gpt = agpt; |
| *ptes = aptes; |
| if (pgpt) { free(pgpt); pgpt = NULL; } |
| if (pptes) { free(pptes); pptes = NULL; } |
| fprintf(stderr, |
| "Primary GPT is invalid, using alternate GPT.\n"); |
| return 1; |
| } |
| |
| fail: |
| if (pgpt) { free(pgpt); pgpt=NULL; } |
| if (agpt) { free(agpt); agpt=NULL; } |
| if (pptes) { free(pptes); pptes=NULL; } |
| if (aptes) { free(aptes); aptes=NULL; } |
| *gpt = NULL; |
| *ptes = NULL; |
| return 0; |
| } |
| |
| void guid_to_ascii(const char *guid, char *s) |
| { |
| uint32_t p1; |
| uint16_t p2; |
| uint16_t p3; |
| unsigned char p4[8]; |
| |
| memcpy(&p1, guid + 0, 4); |
| memcpy(&p2, guid + 4, 2); |
| memcpy(&p3, guid + 6, 2); |
| memcpy(p4, guid + 8, 8); |
| |
| sprintf(s, "%08x%04x%04x%02x%02x%02x%02x%02x%02x%02x%02x", |
| p1, p2, p3, p4[0], p4[1], |
| p4[2], p4[3], p4[4], p4[5], p4[6], p4[7]); |
| } |
| |
| |
| /************************************************************ |
| * gpt_disk_get_partition_info() |
| * Requires: |
| * - open file descriptor fd |
| * - start, size, signature, mbr_type, signature_type |
| * Modifies: all these |
| * Returns: |
| * 0 on success |
| * non-zero on failure |
| * |
| ************************************************************/ |
| int |
| gpt_disk_get_partition_info(int fd, uint32_t num, |
| char *type, char *part) |
| { |
| gpt_header *gpt = NULL; |
| gpt_entry *ptes = NULL, *p; |
| |
| if (!find_valid_gpt(fd, &gpt, &ptes)) |
| return 1; |
| |
| if (num > 0 && num <= __le32_to_cpu(gpt->num_partition_entries)) { |
| p = &ptes[num - 1]; |
| guid_to_ascii((char*)&p->partition_type_guid, type); |
| guid_to_ascii((char*)&p->unique_partition_guid, part); |
| } else { |
| fprintf (stderr,"partition %d is not valid\n", num); |
| return 1; |
| } |
| return 0; |
| } |
| |
| /* |
| * Overrides for Emacs so that we follow Linus's tabbing style. |
| * Emacs will notice this stuff at the end of the file and automatically |
| * adjust the settings for this buffer only. This must remain at the end |
| * of the file. |
| * --------------------------------------------------------------------------- |
| * Local variables: |
| * c-indent-level: 4 |
| * c-brace-imaginary-offset: 0 |
| * c-brace-offset: -4 |
| * c-argdecl-indent: 4 |
| * c-label-offset: -4 |
| * c-continued-statement-offset: 4 |
| * c-continued-brace-offset: 0 |
| * indent-tabs-mode: nil |
| * tab-width: 8 |
| * End: |
| */ |