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.
21static 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
42static 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
74static 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.
95static 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.
110static 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
153static 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
173static 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.
178static 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.
212static 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  }
280cleanup:
281  fclose(fp);
282  free(line);
283  return found;
284}
285
286
287void 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