/* Copyright 2012 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include #include #include #include #include #include "cgpt.h" #include "cgptlib_internal.h" #include "cgpt_nor.h" #include "vboot_host.h" #define BUFSIZE 1024 // fill comparebuf with the data to be examined, returning true on success. static int FillBuffer(CgptFindParams *params, int fd, uint64_t pos, uint64_t count) { uint8_t *bufptr = params->comparebuf; if (-1 == lseek(fd, pos, SEEK_SET)) return 0; // keep reading until done or error while (count) { ssize_t bytes_read = read(fd, bufptr, count); // negative means error, 0 means (unexpected) EOF if (bytes_read <= 0) return 0; count -= bytes_read; bufptr += bytes_read; } return 1; } // check partition data content. return true for match, 0 for no match or error static int match_content(CgptFindParams *params, struct drive *drive, GptEntry *entry) { uint64_t part_size; if (!params->matchlen) return 1; // Ensure that the region we want to match against is inside the partition. part_size = drive->gpt.sector_bytes * (entry->ending_lba - entry->starting_lba + 1); if (params->matchoffset + params->matchlen > part_size) { return 0; } // Read the partition data. if (!FillBuffer(params, drive->fd, (drive->gpt.sector_bytes * entry->starting_lba) + params->matchoffset, params->matchlen)) { Error("unable to read partition data\n"); return 0; } // Compare it if (0 == memcmp(params->matchbuf, params->comparebuf, params->matchlen)) { return 1; } // Nope. return 0; } // This needs to handle /dev/mmcblk0 -> /dev/mmcblk0p3, /dev/sda -> /dev/sda3 static void showmatch(CgptFindParams *params, const char *filename, int partnum, GptEntry *entry) { const char * format = "%s%d\n"; /* * Follow convention from disk_name() in kernel block/partition-generic.c * code: * If the last digit of the device name is a number, add a 'p' between the * device name and the partition number. */ if (isdigit(filename[strlen(filename) - 1])) format = "%sp%d\n"; if (params->numeric) { printf("%d\n", partnum); } else { if (params->show_fn) { params->show_fn(params, filename, partnum, entry); } else { printf(format, filename, partnum); } } if (params->verbose > 0) EntryDetails(entry, partnum - 1, params->numeric); } // This returns true if a GPT partition matches the search criteria. If a match // isn't found (or if the file doesn't contain a GPT), it returns false. The // filename and partition number that matched is left in a global, since we // could have multiple hits. static int gpt_search(CgptFindParams *params, struct drive *drive, const char *filename) { int i; GptEntry *entry; int retval = 0; char partlabel[GPT_PARTNAME_LEN]; if (GPT_SUCCESS != GptValidityCheck(&drive->gpt)) { return 0; } for (i = 0; i < GetNumberOfEntries(drive); ++i) { entry = GetEntry(&drive->gpt, ANY_VALID, i); if (GuidIsZero(&entry->type)) continue; int found = 0; if ((params->set_unique && GuidEqual(¶ms->unique_guid, &entry->unique)) || (params->set_type && GuidEqual(¶ms->type_guid, &entry->type))) { found = 1; } else if (params->set_label) { if (CGPT_OK != UTF16ToUTF8(entry->name, sizeof(entry->name) / sizeof(entry->name[0]), (uint8_t *)partlabel, sizeof(partlabel))) { Error("The label cannot be converted from UTF16, so abort.\n"); return 0; } if (!strncmp(params->label, partlabel, sizeof(partlabel))) found = 1; } if (found && match_content(params, drive, entry)) { params->hits++; retval++; showmatch(params, filename, i+1, entry); if (!params->match_partnum) params->match_partnum = i+1; } } return retval; } static int do_search(CgptFindParams *params, const char *fileName) { int retval; struct drive drive; if (CGPT_OK != DriveOpen(fileName, &drive, O_RDONLY, params->drive_size)) return 0; retval = gpt_search(params, &drive, fileName); (void) DriveClose(&drive, 0); return retval; } #define PROC_MTD "/proc/mtd" #define PROC_PARTITIONS "/proc/partitions" #define DEV_DIR "/dev" #define SYS_BLOCK_DIR "/sys/block" #define MAX_PARTITION_NAME_LEN 128 static const char *devdirs[] = { "/dev", "/devices", "/devfs", 0 }; // Given basename "foo", see if we can find a whole, real device by that name. // This is copied from the logic in the linux utility 'findfs', although that // does more exhaustive searching. static char *is_wholedev(const char *basename) { int i; struct stat statbuf; static char pathname[BUFSIZE]; // we'll return this. char tmpname[BUFSIZE]; // It should be a block device under /dev/, for (i = 0; devdirs[i]; i++) { sprintf(pathname, "%s/%s", devdirs[i], basename); if (0 != stat(pathname, &statbuf)) continue; if (!S_ISBLK(statbuf.st_mode)) continue; // It should have a symlink called /sys/block/*/device sprintf(tmpname, "%s/%s/device", SYS_BLOCK_DIR, basename); if (0 != lstat(tmpname, &statbuf)) continue; if (!S_ISLNK(statbuf.st_mode)) continue; // found it return pathname; } return 0; } #ifdef GPT_SPI_NOR // This handles the MTD devices. ChromeOS uses /dev/mtdX for kernel partitions, // /dev/ubiblockX_0 for root partitions, and /dev/ubiX for stateful partition. static void chromeos_mtd_show(CgptFindParams *params, const char *filename, int partnum, GptEntry *entry) { if (GuidEqual(&guid_chromeos_kernel, &entry->type)) { printf("/dev/mtd%d\n", partnum); } else if (GuidEqual(&guid_chromeos_rootfs, &entry->type)) { printf("/dev/ubiblock%d_0\n", partnum); } else { printf("/dev/ubi%d_0\n", partnum); } } static int scan_spi_gpt(CgptFindParams *params) { int found = 0; char partname[MAX_PARTITION_NAME_LEN]; FILE *fp; size_t line_length = 0; char *line = NULL; fp = fopen(PROC_MTD, "re"); if (!fp) { return found; } while (getline(&line, &line_length, fp) != -1) { uint64_t sz; uint32_t erasesz; char name[128]; // dev: size erasesize name if (sscanf(line, "%64[^:]: %" PRIx64 " %x \"%127[^\"]\"", partname, &sz, &erasesz, name) != 4) continue; if (strcmp(partname, "mtd0") == 0) { char temp_dir[] = "/tmp/cgpt_find.XXXXXX"; if (params->drive_size == 0) { if (GetMtdSize("/dev/mtd0", ¶ms->drive_size) != 0) { perror("GetMtdSize"); goto cleanup; } } // Create a temp dir to work in. if (mkdtemp(temp_dir) == NULL) { perror("Cannot create a temporary directory.\n"); goto cleanup; } if (ReadNorFlash(temp_dir) != 0) { perror("ReadNorFlash"); RemoveDir(temp_dir); goto cleanup; } char nor_file[64]; if (snprintf(nor_file, sizeof(nor_file), "%s/rw_gpt", temp_dir) > 0) { params->show_fn = chromeos_mtd_show; if (do_search(params, nor_file)) { found++; } params->show_fn = NULL; } RemoveDir(temp_dir); break; } } cleanup: fclose(fp); free(line); return found; } #else // Stub static int scan_spi_gpt(CgptFindParams *params) { return 0; } #endif // This scans all the physical devices it can find, looking for a match. It // returns true if any matches were found, false otherwise. static int scan_real_devs(CgptFindParams *params) { int found = 0; char partname[MAX_PARTITION_NAME_LEN]; char partname_prev[MAX_PARTITION_NAME_LEN]; FILE *fp; char *pathname; fp = fopen(PROC_PARTITIONS, "re"); if (!fp) { perror("can't read " PROC_PARTITIONS); return found; } size_t line_length = 0; char *line = NULL; partname_prev[0] = '\0'; while (getline(&line, &line_length, fp) != -1) { int ma, mi; long long unsigned int sz; if (sscanf(line, " %d %d %llu %127[^\n ]", &ma, &mi, &sz, partname) != 4) continue; /* Only check devices that have partitions under them. * We can tell by checking that an entry like "sda" is immediately * followed by one like "sda0". */ if (!strncmp(partname_prev, partname, strlen(partname_prev)) && strlen(partname_prev)) { if ((pathname = is_wholedev(partname_prev))) { if (do_search(params, pathname)) { found++; } } } strcpy(partname_prev, partname); } fclose(fp); free(line); found += scan_spi_gpt(params); return found; } void CgptFind(CgptFindParams *params) { if (params == NULL) return; if (params->drive_name != NULL) do_search(params, params->drive_name); else scan_real_devs(params); }