xref: /aosp_15_r20/external/vboot_reference/cgpt/cgpt_find.c (revision 8617a60d3594060b7ecbd21bc622a7c14f3cf2bc)
1 /* Copyright 2012 The ChromiumOS Authors
2  * Use of this source code is governed by a BSD-style license that can be
3  * found in the LICENSE file.
4  */
5 
6 #include <ctype.h>
7 #include <string.h>
8 #include <sys/stat.h>
9 #include <sys/types.h>
10 #include <unistd.h>
11 
12 #include "cgpt.h"
13 #include "cgptlib_internal.h"
14 #include "cgpt_nor.h"
15 #include "vboot_host.h"
16 
17 #define BUFSIZE 1024
18 
19 // fill comparebuf with the data to be examined, returning true on success.
FillBuffer(CgptFindParams * params,int fd,uint64_t pos,uint64_t count)20 static int FillBuffer(CgptFindParams *params, int fd, uint64_t pos,
21                        uint64_t count) {
22   uint8_t *bufptr = params->comparebuf;
23 
24   if (-1 == lseek(fd, pos, SEEK_SET))
25     return 0;
26 
27   // keep reading until done or error
28   while (count) {
29     ssize_t bytes_read = read(fd, bufptr, count);
30     // negative means error, 0 means (unexpected) EOF
31     if (bytes_read <= 0)
32       return 0;
33     count -= bytes_read;
34     bufptr += bytes_read;
35   }
36 
37   return 1;
38 }
39 
40 // check partition data content. return true for match, 0 for no match or error
match_content(CgptFindParams * params,struct drive * drive,GptEntry * entry)41 static int match_content(CgptFindParams *params, struct drive *drive,
42                              GptEntry *entry) {
43   uint64_t part_size;
44 
45   if (!params->matchlen)
46     return 1;
47 
48   // Ensure that the region we want to match against is inside the partition.
49   part_size = drive->gpt.sector_bytes *
50     (entry->ending_lba - entry->starting_lba + 1);
51   if (params->matchoffset + params->matchlen > part_size) {
52     return 0;
53   }
54 
55   // Read the partition data.
56   if (!FillBuffer(params, drive->fd,
57     (drive->gpt.sector_bytes * entry->starting_lba) + params->matchoffset,
58                   params->matchlen)) {
59     Error("unable to read partition data\n");
60     return 0;
61   }
62 
63   // Compare it
64   if (0 == memcmp(params->matchbuf, params->comparebuf, params->matchlen)) {
65     return 1;
66   }
67 
68   // Nope.
69   return 0;
70 }
71 
72 // This needs to handle /dev/mmcblk0 -> /dev/mmcblk0p3, /dev/sda -> /dev/sda3
showmatch(CgptFindParams * params,const char * filename,int partnum,GptEntry * entry)73 static void showmatch(CgptFindParams *params, const char *filename,
74                       int partnum, GptEntry *entry) {
75   const char * format = "%s%d\n";
76 
77   /*
78    * Follow convention from disk_name() in kernel block/partition-generic.c
79    * code:
80    * If the last digit of the device name is a number, add a 'p' between the
81    * device name and the partition number.
82    */
83   if (isdigit(filename[strlen(filename) - 1]))
84     format = "%sp%d\n";
85 
86   if (params->numeric) {
87     printf("%d\n", partnum);
88   } else {
89     if (params->show_fn) {
90       params->show_fn(params, filename, partnum, entry);
91     } else {
92       printf(format, filename, partnum);
93     }
94   }
95   if (params->verbose > 0)
96     EntryDetails(entry, partnum - 1, params->numeric);
97 }
98 
99 // This returns true if a GPT partition matches the search criteria. If a match
100 // isn't found (or if the file doesn't contain a GPT), it returns false. The
101 // filename and partition number that matched is left in a global, since we
102 // could have multiple hits.
gpt_search(CgptFindParams * params,struct drive * drive,const char * filename)103 static int gpt_search(CgptFindParams *params, struct drive *drive,
104                       const char *filename) {
105   int i;
106   GptEntry *entry;
107   int retval = 0;
108   char partlabel[GPT_PARTNAME_LEN];
109 
110   if (GPT_SUCCESS != GptValidityCheck(&drive->gpt)) {
111     return 0;
112   }
113 
114   for (i = 0; i < GetNumberOfEntries(drive); ++i) {
115     entry = GetEntry(&drive->gpt, ANY_VALID, i);
116 
117     if (GuidIsZero(&entry->type))
118       continue;
119 
120     int found = 0;
121     if ((params->set_unique && GuidEqual(&params->unique_guid, &entry->unique))
122         || (params->set_type && GuidEqual(&params->type_guid, &entry->type))) {
123       found = 1;
124     } else if (params->set_label) {
125       if (CGPT_OK != UTF16ToUTF8(entry->name,
126                                  sizeof(entry->name) / sizeof(entry->name[0]),
127                                  (uint8_t *)partlabel, sizeof(partlabel))) {
128         Error("The label cannot be converted from UTF16, so abort.\n");
129         return 0;
130       }
131       if (!strncmp(params->label, partlabel, sizeof(partlabel)))
132         found = 1;
133     }
134     if (found && match_content(params, drive, entry)) {
135       params->hits++;
136       retval++;
137       showmatch(params, filename, i+1, entry);
138       if (!params->match_partnum)
139         params->match_partnum = i+1;
140     }
141   }
142 
143   return retval;
144 }
145 
do_search(CgptFindParams * params,const char * fileName)146 static int do_search(CgptFindParams *params, const char *fileName) {
147   int retval;
148   struct drive drive;
149 
150   if (CGPT_OK != DriveOpen(fileName, &drive, O_RDONLY, params->drive_size))
151     return 0;
152 
153   retval = gpt_search(params, &drive, fileName);
154 
155   (void) DriveClose(&drive, 0);
156 
157   return retval;
158 }
159 
160 
161 #define PROC_MTD "/proc/mtd"
162 #define PROC_PARTITIONS "/proc/partitions"
163 #define DEV_DIR "/dev"
164 #define SYS_BLOCK_DIR "/sys/block"
165 #define MAX_PARTITION_NAME_LEN 128
166 
167 static const char *devdirs[] = { "/dev", "/devices", "/devfs", 0 };
168 
169 // Given basename "foo", see if we can find a whole, real device by that name.
170 // This is copied from the logic in the linux utility 'findfs', although that
171 // does more exhaustive searching.
is_wholedev(const char * basename)172 static char *is_wholedev(const char *basename) {
173   int i;
174   struct stat statbuf;
175   static char pathname[BUFSIZE];        // we'll return this.
176   char tmpname[BUFSIZE];
177 
178   // It should be a block device under /dev/,
179   for (i = 0; devdirs[i]; i++) {
180     sprintf(pathname, "%s/%s", devdirs[i], basename);
181 
182     if (0 != stat(pathname, &statbuf))
183       continue;
184 
185     if (!S_ISBLK(statbuf.st_mode))
186       continue;
187 
188     // It should have a symlink called /sys/block/*/device
189     sprintf(tmpname, "%s/%s/device", SYS_BLOCK_DIR, basename);
190 
191     if (0 != lstat(tmpname, &statbuf))
192       continue;
193 
194     if (!S_ISLNK(statbuf.st_mode))
195       continue;
196 
197     // found it
198     return pathname;
199   }
200 
201   return 0;
202 }
203 
204 #ifdef GPT_SPI_NOR
205 // This handles the MTD devices. ChromeOS uses /dev/mtdX for kernel partitions,
206 // /dev/ubiblockX_0 for root partitions, and /dev/ubiX for stateful partition.
chromeos_mtd_show(CgptFindParams * params,const char * filename,int partnum,GptEntry * entry)207 static void chromeos_mtd_show(CgptFindParams *params, const char *filename,
208                               int partnum, GptEntry *entry) {
209   if (GuidEqual(&guid_chromeos_kernel, &entry->type)) {
210     printf("/dev/mtd%d\n", partnum);
211   } else if (GuidEqual(&guid_chromeos_rootfs, &entry->type)) {
212     printf("/dev/ubiblock%d_0\n", partnum);
213   } else {
214     printf("/dev/ubi%d_0\n", partnum);
215   }
216 }
217 
scan_spi_gpt(CgptFindParams * params)218 static int scan_spi_gpt(CgptFindParams *params) {
219   int found = 0;
220   char partname[MAX_PARTITION_NAME_LEN];
221   FILE *fp;
222   size_t line_length = 0;
223   char *line = NULL;
224 
225   fp = fopen(PROC_MTD, "re");
226   if (!fp) {
227     return found;
228   }
229 
230   while (getline(&line, &line_length, fp) != -1) {
231     uint64_t sz;
232     uint32_t erasesz;
233     char name[128];
234     // dev:  size  erasesize  name
235     if (sscanf(line, "%64[^:]: %" PRIx64 " %x \"%127[^\"]\"",
236                partname, &sz, &erasesz, name) != 4)
237       continue;
238     if (strcmp(partname, "mtd0") == 0) {
239       char temp_dir[] = VBOOT_TMP_DIR "/cgpt_find.XXXXXX";
240       if (params->drive_size == 0) {
241         if (GetMtdSize("/dev/mtd0", &params->drive_size) != 0) {
242           perror("GetMtdSize");
243           goto cleanup;
244         }
245       }
246       // Create a temp dir to work in.
247       if (mkdtemp(temp_dir) == NULL) {
248         perror("Cannot create a temporary directory.\n");
249         goto cleanup;
250       }
251       if (ReadNorFlash(temp_dir) != 0) {
252         perror("ReadNorFlash");
253         RemoveDir(temp_dir);
254         goto cleanup;
255       }
256       char nor_file[64];
257       if (snprintf(nor_file, sizeof(nor_file), "%s/rw_gpt", temp_dir) > 0) {
258         params->show_fn = chromeos_mtd_show;
259         if (do_search(params, nor_file)) {
260           found++;
261         }
262         params->show_fn = NULL;
263       }
264       RemoveDir(temp_dir);
265       break;
266     }
267   }
268 cleanup:
269   fclose(fp);
270   free(line);
271   return found;
272 }
273 #else
274 // Stub
scan_spi_gpt(CgptFindParams * params)275 static int scan_spi_gpt(CgptFindParams *params) {
276   return 0;
277 }
278 #endif
279 
280 // This scans all the physical devices it can find, looking for a match. It
281 // returns true if any matches were found, false otherwise.
scan_real_devs(CgptFindParams * params)282 static int scan_real_devs(CgptFindParams *params) {
283   int found = 0;
284   char partname[MAX_PARTITION_NAME_LEN];
285   char partname_prev[MAX_PARTITION_NAME_LEN];
286   FILE *fp;
287   char *pathname;
288 
289   fp = fopen(PROC_PARTITIONS, "re");
290   if (!fp) {
291     perror("can't read " PROC_PARTITIONS);
292     return found;
293   }
294 
295   size_t line_length = 0;
296   char *line = NULL;
297   partname_prev[0] = '\0';
298   while (getline(&line, &line_length, fp) != -1) {
299     int ma, mi;
300     long long unsigned int sz;
301 
302     if (sscanf(line, " %d %d %llu %127[^\n ]", &ma, &mi, &sz, partname) != 4)
303       continue;
304 
305     /* Only check devices that have partitions under them.
306      * We can tell by checking that an entry like "sda" is immediately
307      * followed by one like "sda0". */
308     if (!strncmp(partname_prev, partname, strlen(partname_prev)) &&
309         strlen(partname_prev)) {
310       if ((pathname = is_wholedev(partname_prev))) {
311         if (do_search(params, pathname)) {
312           found++;
313         }
314       }
315     }
316 
317     strcpy(partname_prev, partname);
318   }
319 
320   fclose(fp);
321   free(line);
322 
323   found += scan_spi_gpt(params);
324 
325   return found;
326 }
327 
328 
CgptFind(CgptFindParams * params)329 void CgptFind(CgptFindParams *params) {
330   if (params == NULL)
331     return;
332 
333   if (params->drive_name != NULL)
334     do_search(params, params->drive_name);
335   else
336     scan_real_devs(params);
337 }
338