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