blob: 068a24458fc9ed4ce8c3fa51405def220cfa90ef [file] [log] [blame]
Ethan Yonker66a19492015-12-10 10:19:45 -06001/*
2 gpt.[ch]
3
4 Copyright (C) 2000-2001 Dell Computer Corporation <Matt_Domsch@dell.com>
5
6 EFI GUID Partition Table handling
7 Per Intel EFI Specification v1.02
8 http://developer.intel.com/technology/efi/efi.htm
9
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License, or
13 (at your option) any later version.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23*/
24
25// For TWRP purposes, we'll be opting for version 3 of the GPL
26
27#include <stdio.h>
28#include <string.h>
29#include <stdlib.h>
30#include <inttypes.h>
31#include <sys/stat.h>
32#include <sys/ioctl.h>
33#include <fcntl.h>
34#include <unistd.h>
35#include <errno.h>
36#include <sys/utsname.h>
37#include <asm/byteorder.h>
38#include "gpt.h"
39#include "gptcrc32.h"
40
41#define BLKGETLASTSECT _IO(0x12,108) /* get last sector of block device */
42#define BLKGETSIZE _IO(0x12,96) /* return device size */
43#define BLKSSZGET _IO(0x12,104) /* get block device sector size */
44#define BLKGETSIZE64 _IOR(0x12,114,uint64_t) /* return device size in bytes (u64 *arg) */
45
46struct blkdev_ioctl_param {
47 unsigned int block;
48 size_t content_length;
49 char * block_contents;
50};
51
52static inline int
53efi_guidcmp(efi_guid_t left, efi_guid_t right)
54{
55 return memcmp(&left, &right, sizeof (efi_guid_t));
56}
57
58static int
59get_sector_size(int filedes)
60{
61 int rc, sector_size = 512;
62
63 rc = ioctl(filedes, BLKSSZGET, &sector_size);
64 if (rc)
65 sector_size = 512;
66 return sector_size;
67}
68
69/**
70 * efi_crc32() - EFI version of crc32 function
71 * @buf: buffer to calculate crc32 of
72 * @len - length of buf
73 *
74 * Description: Returns EFI-style CRC32 value for @buf
75 *
76 * This function uses the little endian Ethernet polynomial
77 * but seeds the function with ~0, and xor's with ~0 at the end.
78 * Note, the EFI Specification, v1.02, has a reference to
79 * Dr. Dobbs Journal, May 1994 (actually it's in May 1992).
80 */
81static inline uint32_t
82efi_crc32(const void *buf, unsigned long len)
83{
84 return (gptcrc32(buf, len, ~0L) ^ ~0L);
85}
86
87/**
88 * is_pmbr_valid(): test Protective MBR for validity
89 * @mbr: pointer to a legacy mbr structure
90 *
91 * Description: Returns 1 if PMBR is valid, 0 otherwise.
92 * Validity depends on two things:
93 * 1) MSDOS signature is in the last two bytes of the MBR
94 * 2) One partition of type 0xEE is found
95 */
96static int
97is_pmbr_valid(legacy_mbr *mbr)
98{
99 int i, found = 0, signature = 0;
100 if (!mbr)
101 return 0;
102 signature = (__le16_to_cpu(mbr->signature) == MSDOS_MBR_SIGNATURE);
103 for (i = 0; signature && i < 4; i++) {
104 if (mbr->partition[i].os_type ==
105 EFI_PMBR_OSTYPE_EFI_GPT) {
106 found = 1;
107 break;
108 }
109 }
110 return (signature && found);
111}
112
113/**
114 * kernel_has_blkgetsize64()
115 *
116 * Returns: 0 on false, 1 on true
117 * True means kernel is 2.4.x, x>=18, or
118 * is 2.5.x, x>4, or
119 * is > 2.5
120 */
121static int
122kernel_has_blkgetsize64(void)
123{
124 int major=0, minor=0, patch=0, parsed;
125 int rc;
126 struct utsname u;
127
128 memset(&u, 0, sizeof(u));
129 rc = uname(&u);
130 if (rc) return 0;
131
132 parsed = sscanf(u.release, "%d.%d.%d", &major, &minor, &patch);
133 if (parsed < 3) return 0;
134 if (major > 2) return 1;
135 if (major == 2 && minor > 5) return 1;
136 if (major == 2 && minor == 5 && patch >= 4) return 1;
137 if (major == 2 && minor == 4 && patch >= 18) return 1;
138 return 0;
139}
140
141
142/************************************************************
143 * _get_num_sectors
144 * Requires:
145 * - filedes is an open file descriptor, suitable for reading
146 * Modifies: nothing
147 * Returns:
148 * Last LBA value on success
149 * 0 on error
150 *
151 * Try getting BLKGETSIZE64 and BLKSSZGET first,
152 * then BLKGETSIZE if necessary.
153 * Kernels 2.4.15-2.4.18 and 2.5.0-2.5.3 have a broken BLKGETSIZE64
154 * which returns the number of 512-byte sectors, not the size of
155 * the disk in bytes. Fixed in kernels 2.4.18-pre8 and 2.5.4-pre3.
156 ************************************************************/
157static uint64_t
158_get_num_sectors(int filedes)
159{
160 unsigned long sectors=0;
161 uint64_t bytes=0;
162 int rc;
163 if (kernel_has_blkgetsize64()) {
164 rc = ioctl(filedes, BLKGETSIZE64, &bytes);
165 if (!rc)
166 return bytes / get_sector_size(filedes);
167 }
168
169 rc = ioctl(filedes, BLKGETSIZE, &sectors);
170 if (rc)
171 return 0;
172
173 return sectors;
174}
175
176/************************************************************
177 * last_lba(): return number of last logical block of device
178 *
179 * @fd
180 *
181 * Description: returns Last LBA value on success, 0 on error.
182 * Notes: The value st_blocks gives the size of the file
183 * in 512-byte blocks, which is OK if
184 * EFI_BLOCK_SIZE_SHIFT == 9.
185 ************************************************************/
186
187static uint64_t
188last_lba(int filedes)
189{
190 int rc;
191 uint64_t sectors = 0;
192 struct stat s;
193 memset(&s, 0, sizeof (s));
194 rc = fstat(filedes, &s);
195 if (rc == -1) {
196 fprintf(stderr, "last_lba() could not stat: %s\n",
197 strerror(errno));
198 return 0;
199 }
200
201 if (S_ISBLK(s.st_mode)) {
202 sectors = _get_num_sectors(filedes);
203 } else {
204 fprintf(stderr,
205 "last_lba(): I don't know how to handle files with mode %x\n",
206 s.st_mode);
207 sectors = 1;
208 }
209
210 return sectors - 1;
211}
212
213
214static ssize_t
215read_lastoddsector(int fd, uint64_t lba __unused, void *buffer, size_t count)
216{
217 int rc;
218 struct blkdev_ioctl_param ioctl_param;
219
220 if (!buffer) return 0;
221
222 ioctl_param.block = 0; /* read the last sector */
223 ioctl_param.content_length = count;
224 ioctl_param.block_contents = buffer;
225
226 rc = ioctl(fd, BLKGETLASTSECT, &ioctl_param);
227 if (rc == -1) perror("read failed");
228
229 return !rc;
230}
231
232static ssize_t
233read_lba(int fd, uint64_t lba, void *buffer, size_t bytes)
234{
235 int sector_size = get_sector_size(fd);
236 off_t offset = lba * sector_size;
237 ssize_t bytesread;
238 void *aligned;
239 void *unaligned;
240
241 if (bytes % sector_size)
242 return EINVAL;
243
244 unaligned = malloc(bytes+sector_size-1);
245 aligned = (void *)
246 (((unsigned long)unaligned + sector_size - 1) &
247 ~(unsigned long)(sector_size-1));
248 memset(aligned, 0, bytes);
249
250
251 lseek(fd, offset, SEEK_SET);
252 bytesread = read(fd, aligned, bytes);
253 memcpy(buffer, aligned, bytesread);
254 free(unaligned);
255
256 /* Kludge. This is necessary to read/write the last
257 block of an odd-sized disk, until Linux 2.5.x kernel fixes.
258 This is only used by gpt.c, and only to read
259 one sector, so we don't have to be fancy.
260 */
261 if (!bytesread && !(last_lba(fd) & 1) && lba == last_lba(fd)) {
262 bytesread = read_lastoddsector(fd, lba, buffer, bytes);
263 }
264 return bytesread;
265}
266
267/**
268 * alloc_read_gpt_entries(): reads partition entries from disk
269 * @fd is an open file descriptor to the whole disk
270 * @gpt is a buffer into which the GPT will be put
271 * Description: Returns ptes on success, NULL on error.
272 * Allocates space for PTEs based on information found in @gpt.
273 * Notes: remember to free pte when you're done!
274 */
275static gpt_entry *
276alloc_read_gpt_entries(int fd, gpt_header * gpt)
277{
278 gpt_entry *pte;
279 size_t count = __le32_to_cpu(gpt->num_partition_entries) *
280 __le32_to_cpu(gpt->sizeof_partition_entry);
281
282 if (!count) return NULL;
283
284 pte = (gpt_entry *)malloc(count);
285 if (!pte)
286 return NULL;
287 memset(pte, 0, count);
288
289 if (!read_lba(fd, __le64_to_cpu(gpt->partition_entry_lba), pte,
290 count)) {
291 free(pte);
292 return NULL;
293 }
294 return pte;
295}
296
297/**
298 * alloc_read_gpt_header(): Allocates GPT header, reads into it from disk
299 * @fd is an open file descriptor to the whole disk
300 * @lba is the Logical Block Address of the partition table
301 *
302 * Description: returns GPT header on success, NULL on error. Allocates
303 * and fills a GPT header starting at @ from @bdev.
304 * Note: remember to free gpt when finished with it.
305 */
306static gpt_header *
307alloc_read_gpt_header(int fd, uint64_t lba)
308{
309 gpt_header *gpt;
310 gpt = (gpt_header *)
311 malloc(sizeof (gpt_header));
312 if (!gpt)
313 return NULL;
314 memset(gpt, 0, sizeof (*gpt));
315 if (!read_lba(fd, lba, gpt, sizeof (gpt_header))) {
316 free(gpt);
317 return NULL;
318 }
319
320 return gpt;
321}
322
323/**
324 * is_gpt_valid() - tests one GPT header and PTEs for validity
325 * @fd is an open file descriptor to the whole disk
326 * @lba is the logical block address of the GPT header to test
327 * @gpt is a GPT header ptr, filled on return.
328 * @ptes is a PTEs ptr, filled on return.
329 *
330 * Description: returns 1 if valid, 0 on error.
331 * If valid, returns pointers to newly allocated GPT header and PTEs.
332 */
333static int
334is_gpt_valid(int fd, uint64_t lba,
335 gpt_header ** gpt, gpt_entry ** ptes)
336{
337 int rc = 0; /* default to not valid */
338 uint32_t crc, origcrc;
339
340 if (!gpt || !ptes)
341 return 0;
342 if (!(*gpt = alloc_read_gpt_header(fd, lba)))
343 return 0;
344
345 /* Check the GUID Partition Table signature */
346 if (__le64_to_cpu((*gpt)->signature) != GPT_HEADER_SIGNATURE) {
347 /*
348 printf("GUID Partition Table Header signature is wrong: %" PRIx64" != %" PRIx64 "\n",
349 __le64_to_cpu((*gpt)->signature), GUID_PT_HEADER_SIGNATURE);
350 */
351 free(*gpt);
352 *gpt = NULL;
353 return rc;
354 }
355
356 /* Check the GUID Partition Table Header CRC */
357 origcrc = __le32_to_cpu((*gpt)->header_crc32);
358 (*gpt)->header_crc32 = 0;
359 crc = efi_crc32(*gpt, __le32_to_cpu((*gpt)->header_size));
360 if (crc != origcrc) {
361 // printf( "GPTH CRC check failed, %x != %x.\n", origcrc, crc);
362 (*gpt)->header_crc32 = __cpu_to_le32(origcrc);
363 free(*gpt);
364 *gpt = NULL;
365 return 0;
366 }
367 (*gpt)->header_crc32 = __cpu_to_le32(origcrc);
368
369 /* Check that the my_lba entry points to the LBA
370 * that contains the GPT we read */
371 if (__le64_to_cpu((*gpt)->my_lba) != lba) {
372 // printf( "my_lba % PRIx64 "x != lba %"PRIx64 "x.\n", __le64_to_cpu((*gpt)->my_lba), lba);
373 free(*gpt);
374 *gpt = NULL;
375 return 0;
376 }
377
378 if (!(*ptes = alloc_read_gpt_entries(fd, *gpt))) {
379 free(*gpt);
380 *gpt = NULL;
381 return 0;
382 }
383
384 /* Check the GUID Partition Entry Array CRC */
385 crc = efi_crc32(*ptes,
386 __le32_to_cpu((*gpt)->num_partition_entries) *
387 __le32_to_cpu((*gpt)->sizeof_partition_entry));
388 if (crc != __le32_to_cpu((*gpt)->partition_entry_array_crc32)) {
389 // printf("GUID Partitition Entry Array CRC check failed.\n");
390 free(*gpt);
391 *gpt = NULL;
392 free(*ptes);
393 *ptes = NULL;
394 return 0;
395 }
396
397 /* We're done, all's well */
398 return 1;
399}
400/**
401 * compare_gpts() - Search disk for valid GPT headers and PTEs
402 * @pgpt is the primary GPT header
403 * @agpt is the alternate GPT header
404 * @lastlba is the last LBA number
405 * Description: Returns nothing. Sanity checks pgpt and agpt fields
406 * and prints warnings on discrepancies.
407 *
408 */
409static void
410compare_gpts(gpt_header *pgpt, gpt_header *agpt, uint64_t lastlba)
411{
412 int error_found = 0;
413 if (!pgpt || !agpt)
414 return;
415 if (__le64_to_cpu(pgpt->my_lba) != __le64_to_cpu(agpt->alternate_lba)) {
416 fprintf(stderr,
417 "GPT:Primary header LBA != Alt. header alternate_lba\n");
418 fprintf(stderr, "GPT:0x%" PRIx64 " != 0x%" PRIx64 "\n",
419 __le64_to_cpu(pgpt->my_lba),
420 __le64_to_cpu(agpt->alternate_lba));
421 error_found++;
422 }
423 if (__le64_to_cpu(pgpt->alternate_lba) != __le64_to_cpu(agpt->my_lba)) {
424 fprintf(stderr,
425 "GPT:Primary header alternate_lba != Alt. header my_lba\n");
426 fprintf(stderr, "GPT:0x%" PRIx64 " != 0x%" PRIx64 "\n",
427 __le64_to_cpu(pgpt->alternate_lba),
428 __le64_to_cpu(agpt->my_lba));
429 error_found++;
430 }
431 if (__le64_to_cpu(pgpt->first_usable_lba) !=
432 __le64_to_cpu(agpt->first_usable_lba)) {
433 fprintf(stderr, "GPT:first_usable_lbas don't match.\n");
434 fprintf(stderr, "GPT:0x%" PRIx64 " != 0x%" PRIx64 "\n",
435 __le64_to_cpu(pgpt->first_usable_lba),
436 __le64_to_cpu(agpt->first_usable_lba));
437 error_found++;
438 }
439 if (__le64_to_cpu(pgpt->last_usable_lba) !=
440 __le64_to_cpu(agpt->last_usable_lba)) {
441 fprintf(stderr, "GPT:last_usable_lbas don't match.\n");
442 fprintf(stderr, "GPT:0x%" PRIx64 " != 0x%" PRIx64 "\n",
443 __le64_to_cpu(pgpt->last_usable_lba),
444 __le64_to_cpu(agpt->last_usable_lba));
445 error_found++;
446 }
447 if (efi_guidcmp(pgpt->disk_guid, agpt->disk_guid)) {
448 fprintf(stderr, "GPT:disk_guids don't match.\n");
449 error_found++;
450 }
451 if (__le32_to_cpu(pgpt->num_partition_entries) !=
452 __le32_to_cpu(agpt->num_partition_entries)) {
453 fprintf(stderr, "GPT:num_partition_entries don't match: "
454 "0x%x != 0x%x\n",
455 __le32_to_cpu(pgpt->num_partition_entries),
456 __le32_to_cpu(agpt->num_partition_entries));
457 error_found++;
458 }
459 if (__le32_to_cpu(pgpt->sizeof_partition_entry) !=
460 __le32_to_cpu(agpt->sizeof_partition_entry)) {
461 fprintf(stderr,
462 "GPT:sizeof_partition_entry values don't match: "
463 "0x%x != 0x%x\n",
464 __le32_to_cpu(pgpt->sizeof_partition_entry),
465 __le32_to_cpu(agpt->sizeof_partition_entry));
466 error_found++;
467 }
468 if (__le32_to_cpu(pgpt->partition_entry_array_crc32) !=
469 __le32_to_cpu(agpt->partition_entry_array_crc32)) {
470 fprintf(stderr,
471 "GPT:partition_entry_array_crc32 values don't match: "
472 "0x%x != 0x%x\n",
473 __le32_to_cpu(pgpt->partition_entry_array_crc32),
474 __le32_to_cpu(agpt->partition_entry_array_crc32));
475 error_found++;
476 }
477 if (__le64_to_cpu(pgpt->alternate_lba) != lastlba) {
478 fprintf(stderr,
479 "GPT:Primary header thinks Alt. header is not at the end of the disk.\n");
480 fprintf(stderr, "GPT:0x%" PRIx64 " != 0x%" PRIx64 "\n",
481 __le64_to_cpu(pgpt->alternate_lba), lastlba);
482 error_found++;
483 }
484
485 if (__le64_to_cpu(agpt->my_lba) != lastlba) {
486 fprintf(stderr,
487 "GPT:Alternate GPT header not at the end of the disk.\n");
488 fprintf(stderr, "GPT:0x%" PRIx64 " != 0x%" PRIx64 "\n",
489 __le64_to_cpu(agpt->my_lba), lastlba);
490 error_found++;
491 }
492
493 if (error_found)
494 fprintf(stderr,
495 "GPT: Use GNU Parted to correct GPT errors.\n");
496 return;
497}
498
499/**
500 * find_valid_gpt() - Search disk for valid GPT headers and PTEs
501 * @fd is an open file descriptor to the whole disk
502 * @gpt is a GPT header ptr, filled on return.
503 * @ptes is a PTEs ptr, filled on return.
504 * Description: Returns 1 if valid, 0 on error.
505 * If valid, returns pointers to newly allocated GPT header and PTEs.
506 * Validity depends on finding either the Primary GPT header and PTEs valid,
507 * or the Alternate GPT header and PTEs valid, and the PMBR valid.
508 */
509static int
510find_valid_gpt(int fd, gpt_header ** gpt, gpt_entry ** ptes)
511{
512 int good_pgpt = 0, good_agpt = 0, good_pmbr = 0;
513 gpt_header *pgpt = NULL, *agpt = NULL;
514 gpt_entry *pptes = NULL, *aptes = NULL;
515 legacy_mbr *legacymbr = NULL;
516 uint64_t lastlba;
517 if (!gpt || !ptes)
518 return 0;
519
520 lastlba = last_lba(fd);
521 good_pgpt = is_gpt_valid(fd, GPT_PRIMARY_PARTITION_TABLE_LBA,
522 &pgpt, &pptes);
523 if (good_pgpt) {
524 good_agpt = is_gpt_valid(fd,
525 __le64_to_cpu(pgpt->alternate_lba),
526 &agpt, &aptes);
527 if (!good_agpt) {
528 good_agpt = is_gpt_valid(fd, lastlba,
529 &agpt, &aptes);
530 }
531 }
532 else {
533 good_agpt = is_gpt_valid(fd, lastlba,
534 &agpt, &aptes);
535 }
536
537 /* The obviously unsuccessful case */
538 if (!good_pgpt && !good_agpt) {
539 goto fail;
540 }
541
542 /* This will be added to the EFI Spec. per Intel after v1.02. */
543 legacymbr = malloc(sizeof (*legacymbr));
544 if (legacymbr) {
545 memset(legacymbr, 0, sizeof (*legacymbr));
546 read_lba(fd, 0, (uint8_t *) legacymbr,
547 sizeof (*legacymbr));
548 good_pmbr = is_pmbr_valid(legacymbr);
549 free(legacymbr);
550 legacymbr=NULL;
551 }
552
553 /* Failure due to bad PMBR */
554 if ((good_pgpt || good_agpt) && !good_pmbr) {
555 fprintf(stderr,
556 " Warning: Disk has a valid GPT signature "
557 "but invalid PMBR.\n"
558 " Assuming this disk is *not* a GPT disk anymore.\n"
559 " Use gpt kernel option to override. "
560 "Use GNU Parted to correct disk.\n");
561 goto fail;
562 }
563
564 /* Would fail due to bad PMBR, but force GPT anyhow */
565 if ((good_pgpt || good_agpt) && !good_pmbr) {
566 fprintf(stderr,
567 " Warning: Disk has a valid GPT signature but "
568 "invalid PMBR.\n"
569 " Use GNU Parted to correct disk.\n"
570 " gpt option taken, disk treated as GPT.\n");
571 }
572
573 compare_gpts(pgpt, agpt, lastlba);
574
575 /* The good cases */
576 if (good_pgpt && (good_pmbr)) {
577 *gpt = pgpt;
578 *ptes = pptes;
579 if (agpt) { free(agpt); agpt = NULL; }
580 if (aptes) { free(aptes); aptes = NULL; }
581 if (!good_agpt) {
582 fprintf(stderr,
583 "Alternate GPT is invalid, "
584 "using primary GPT.\n");
585 }
586 return 1;
587 }
588 else if (good_agpt && (good_pmbr)) {
589 *gpt = agpt;
590 *ptes = aptes;
591 if (pgpt) { free(pgpt); pgpt = NULL; }
592 if (pptes) { free(pptes); pptes = NULL; }
593 fprintf(stderr,
594 "Primary GPT is invalid, using alternate GPT.\n");
595 return 1;
596 }
597
598 fail:
599 if (pgpt) { free(pgpt); pgpt=NULL; }
600 if (agpt) { free(agpt); agpt=NULL; }
601 if (pptes) { free(pptes); pptes=NULL; }
602 if (aptes) { free(aptes); aptes=NULL; }
603 *gpt = NULL;
604 *ptes = NULL;
605 return 0;
606}
607
608void guid_to_ascii(const char *guid, char *s)
609{
610 uint32_t p1;
611 uint16_t p2;
612 uint16_t p3;
613 unsigned char p4[8];
614
615 memcpy(&p1, guid + 0, 4);
616 memcpy(&p2, guid + 4, 2);
617 memcpy(&p3, guid + 6, 2);
618 memcpy(p4, guid + 8, 8);
619
620 sprintf(s, "%08x%04x%04x%02x%02x%02x%02x%02x%02x%02x%02x",
621 p1, p2, p3, p4[0], p4[1],
622 p4[2], p4[3], p4[4], p4[5], p4[6], p4[7]);
623}
624
625
626/************************************************************
627 * gpt_disk_get_partition_info()
628 * Requires:
629 * - open file descriptor fd
630 * - start, size, signature, mbr_type, signature_type
631 * Modifies: all these
632 * Returns:
633 * 0 on success
634 * non-zero on failure
635 *
636 ************************************************************/
637int
638gpt_disk_get_partition_info(int fd, uint32_t num,
639 char *type, char *part)
640{
641 gpt_header *gpt = NULL;
642 gpt_entry *ptes = NULL, *p;
643
644 if (!find_valid_gpt(fd, &gpt, &ptes))
645 return 1;
646
647 if (num > 0 && num <= __le32_to_cpu(gpt->num_partition_entries)) {
648 p = &ptes[num - 1];
649 guid_to_ascii((char*)&p->partition_type_guid, type);
650 guid_to_ascii((char*)&p->unique_partition_guid, part);
651 } else {
652 fprintf (stderr,"partition %d is not valid\n", num);
653 return 1;
654 }
655 return 0;
656}
657
658/*
659 * Overrides for Emacs so that we follow Linus's tabbing style.
660 * Emacs will notice this stuff at the end of the file and automatically
661 * adjust the settings for this buffer only. This must remain at the end
662 * of the file.
663 * ---------------------------------------------------------------------------
664 * Local variables:
665 * c-indent-level: 4
666 * c-brace-imaginary-offset: 0
667 * c-brace-offset: -4
668 * c-argdecl-indent: 4
669 * c-label-offset: -4
670 * c-continued-statement-offset: 4
671 * c-continued-brace-offset: 0
672 * indent-tabs-mode: nil
673 * tab-width: 8
674 * End:
675 */