xref: /freebsd/tools/boot/gptattr.c (revision c4fde87a3236b500246d4fce76dbca2a3d64f73a)
1 /*
2  *
3  * SPDX-License-Identifier: BSD-2-Clause
4  */
5 
6 /*
7  * Set a GPT attribute on a partition of a disk image file.
8  *
9  * gpart(8) does the same thing, but only on a real device and only as root.
10  * boot-test.sh builds every image as an unprivileged user, so it needs to
11  * edit the on-disk GPT itself.  Both the primary and the secondary entry
12  * table are updated: the loader only reads the primary, but the kernel
13  * checks both, and a mismatch would be reported as a corrupt GPT during the
14  * boot under test.
15  *
16  * Usage: gptattr <image> <index> <attribute-bit>
17  * where <index> is 1-based, matching gpart(8) partition numbering.
18  */
19 
20 #include <sys/param.h>
21 #include <sys/disk/gpt.h>
22 #include <sys/endian.h>
23 
24 #include <err.h>
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <stdint.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32 #include <zlib.h>
33 
34 #define	SECSZ	512
35 
36 static uint8_t *image;
37 static size_t imagesz;
38 
39 /*
40  * Apply the attribute to one entry table and refresh the CRCs of the header
41  * at hdroff that describes it.
42  */
43 static void
44 patch_table(uint64_t hdroff, u_int index, uint64_t attr)
45 {
46 	struct gpt_hdr *hdr;
47 	struct gpt_ent *ent;
48 	uint64_t tbloff;
49 	uint32_t entries, entsz, hdrsz;
50 
51 	if (hdroff + SECSZ > imagesz)
52 		errx(1, "GPT header at offset %ju is past end of image",
53 		    (uintmax_t)hdroff);
54 
55 	hdr = (struct gpt_hdr *)(image + hdroff);
56 	if (memcmp(hdr->hdr_sig, GPT_HDR_SIG, sizeof(hdr->hdr_sig)) != 0)
57 		errx(1, "no GPT header at offset %ju", (uintmax_t)hdroff);
58 
59 	hdrsz = le32toh(hdr->hdr_size);
60 	entries = le32toh(hdr->hdr_entries);
61 	entsz = le32toh(hdr->hdr_entsz);
62 	tbloff = le64toh(hdr->hdr_lba_table) * SECSZ;
63 
64 	if (index < 1 || index > entries)
65 		errx(1, "partition index %u out of range (1..%u)", index,
66 		    entries);
67 	if (tbloff + (uint64_t)entries * entsz > imagesz)
68 		errx(1, "GPT entry table is past end of image");
69 
70 	ent = (struct gpt_ent *)(image + tbloff + (uint64_t)(index - 1) * entsz);
71 	le64enc(&ent->ent_attr, le64toh(ent->ent_attr) | attr);
72 
73 	/*
74 	 * The table CRC covers the entries, and the header CRC covers the
75 	 * header including that table CRC, so recompute them in that order.
76 	 * hdr_crc_self is zeroed while it is being computed over itself.
77 	 */
78 	le32enc(&hdr->hdr_crc_table, crc32(0, image + tbloff,
79 	    entries * entsz));
80 	hdr->hdr_crc_self = 0;
81 	le32enc(&hdr->hdr_crc_self, crc32(0, (const uint8_t *)hdr, hdrsz));
82 }
83 
84 int
85 main(int argc, char *argv[])
86 {
87 	FILE *fp;
88 	char *end;
89 	uint64_t attr;
90 	u_long bit, index;
91 
92 	if (argc != 4) {
93 		fprintf(stderr,
94 		    "usage: %s <image> <index> <attribute-bit>\n", argv[0]);
95 		return (1);
96 	}
97 
98 	errno = 0;
99 	index = strtoul(argv[2], &end, 0);
100 	if (errno != 0 || *end != '\0' || index == 0)
101 		errx(1, "bad partition index: %s", argv[2]);
102 	bit = strtoul(argv[3], &end, 0);
103 	if (errno != 0 || *end != '\0' || bit > 63)
104 		errx(1, "bad attribute bit: %s", argv[3]);
105 	attr = (uint64_t)1 << bit;
106 
107 	if ((fp = fopen(argv[1], "r+")) == NULL)
108 		err(1, "%s", argv[1]);
109 	if (fseek(fp, 0, SEEK_END) != 0)
110 		err(1, "%s", argv[1]);
111 	imagesz = (size_t)ftell(fp);
112 	rewind(fp);
113 	if (imagesz < 2 * SECSZ)
114 		errx(1, "%s is too small to hold a GPT", argv[1]);
115 	if ((image = malloc(imagesz)) == NULL)
116 		err(1, "malloc");
117 	if (fread(image, 1, imagesz, fp) != imagesz)
118 		err(1, "%s: read", argv[1]);
119 
120 	/* Primary header is always at LBA 1; it names the secondary. */
121 	patch_table(SECSZ, index, attr);
122 	patch_table(le64toh(((struct gpt_hdr *)(image + SECSZ))->hdr_lba_alt) *
123 	    SECSZ, index, attr);
124 
125 	rewind(fp);
126 	if (fwrite(image, 1, imagesz, fp) != imagesz)
127 		err(1, "%s: write", argv[1]);
128 	if (fclose(fp) != 0)
129 		err(1, "%s: close", argv[1]);
130 	free(image);
131 
132 	return (0);
133 }
134