313 lines
8.4 KiB
C
313 lines
8.4 KiB
C
|
/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
|
||
|
* Use of this source code is governed by a BSD-style license that can be
|
||
|
* found in the LICENSE file.
|
||
|
*/
|
||
|
|
||
|
#include <ctype.h>
|
||
|
#include <string.h>
|
||
|
#include <sys/stat.h>
|
||
|
#include <sys/types.h>
|
||
|
#include <unistd.h>
|
||
|
|
||
|
#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 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);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 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 != GptSanityCheck(&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;
|
||
|
}
|
||
|
|
||
|
// 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);
|
||
|
|
||
|
fp = fopen(PROC_MTD, "re");
|
||
|
if (!fp) {
|
||
|
free(line);
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
if (ReadNorFlash(temp_dir) != 0) {
|
||
|
perror("ReadNorFlash");
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CgptFind(CgptFindParams *params) {
|
||
|
if (params == NULL)
|
||
|
return;
|
||
|
|
||
|
if (params->drive_name != NULL)
|
||
|
do_search(params, params->drive_name);
|
||
|
else
|
||
|
scan_real_devs(params);
|
||
|
}
|