/* Copyright 2015 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 <err.h>
#include <errno.h>
#include <fcntl.h>
#include <ftw.h>
#include <inttypes.h>
#include <linux/major.h>
#include <stdbool.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "cgpt.h"
#include "cgpt_nor.h"
static const char FLASHROM_PATH[] = "/usr/sbin/flashrom";
// Obtain the MTD size from its sysfs node.
int GetMtdSize(const char *mtd_device, uint64_t *size) {
mtd_device = strrchr(mtd_device, '/');
if (mtd_device == NULL) {
errno = EINVAL;
return 1;
}
char *sysfs_name;
if (asprintf(&sysfs_name, "/sys/class/mtd%s/size", mtd_device) == -1) {
return 1;
}
FILE *fp = fopen(sysfs_name, "r");
free(sysfs_name);
if (fp == NULL) {
return 1;
}
int ret = (fscanf(fp, "%" PRIu64 "\n", size) != 1);
fclose(fp);
return ret;
}
int ForkExecV(const char *cwd, const char *const argv[]) {
pid_t pid = fork();
if (pid == -1) {
return -1;
}
int status = -1;
if (pid == 0) {
if (cwd && chdir(cwd) != 0) {
return -1;
}
execv(argv[0], (char *const *)argv);
// If this is reached, execv fails.
err(-1, "Cannot exec %s in %s.", argv[0], cwd);
} else {
if (waitpid(pid, &status, 0) != -1 && WIFEXITED(status))
return WEXITSTATUS(status);
}
return status;
}
int ForkExecL(const char *cwd, const char *cmd, ...) {
int argc;
va_list ap;
va_start(ap, cmd);
for (argc = 1; va_arg(ap, char *) != NULL; ++argc);
va_end(ap);
va_start(ap, cmd);
const char **argv = calloc(argc + 1, sizeof(char *));
if (argv == NULL) {
errno = ENOMEM;
return -1;
}
argv[0] = cmd;
int i;
for (i = 1; i < argc; ++i) {
argv[i] = va_arg(ap, char *);
}
va_end(ap);
int ret = ForkExecV(cwd, argv);
free(argv);
return ret;
}
static int read_write(int source_fd,
uint64_t size,
const char *src_name,
int idx) {
int ret = 1;
const int bufsize = 4096;
char *buf = malloc(bufsize);
if (buf == NULL) {
goto clean_exit;
}
ret++;
char *dest;
if (asprintf(&dest, "%s_%d", src_name, idx) == -1) {
goto free_buf;
}
ret++;
int dest_fd = open(dest, O_WRONLY | O_CLOEXEC | O_CREAT, 0600);
if (dest_fd < 0) {
goto free_dest;
}
ret++;
uint64_t copied = 0;
ssize_t nr_read;
ssize_t nr_write;
while (copied < size) {
size_t to_read = size - copied;
if (to_read > bufsize) {
to_read = bufsize;
}
nr_read = read(source_fd, buf, to_read);
if (nr_read < 0) {
goto close_dest_fd;
}
nr_write = 0;
while (nr_write < nr_read) {
ssize_t s = write(dest_fd, buf + nr_write, nr_read - nr_write);
if (s < 0) {
goto close_dest_fd;
}
nr_write += s;
}
copied += nr_read;
}
ret = 0;
close_dest_fd:
close(dest_fd);
free_dest:
free(dest);
free_buf:
free(buf);
clean_exit:
return ret;
}
static int split_gpt(const char *dir_name, const char *file_name) {
int ret = 1;
char *source;
if (asprintf(&source, "%s/%s", dir_name, file_name) == -1) {
goto clean_exit;
}
ret++;
int fd = open(source, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
goto free_source;
}
ret++;
struct stat stat;
if (fstat(fd, &stat) != 0 || (stat.st_size & 1) != 0) {
goto close_fd;
}
uint64_t half_size = stat.st_size / 2;
ret++;
if (read_write(fd, half_size, source, 1) != 0 ||
read_write(fd, half_size, source, 2) != 0) {
goto close_fd;
}
ret = 0;
close_fd:
close(fd);
free_source:
free(source);
clean_exit:
return ret;
}
static int remove_file_or_dir(const char *fpath, const struct stat *sb,
int typeflag, struct FTW *ftwbuf) {
return remove(fpath);
}
int RemoveDir(const char *dir) {
return nftw(dir, remove_file_or_dir, 20, FTW_DEPTH | FTW_PHYS);
}
// Read RW_GPT from NOR flash to "rw_gpt" in a temp dir |temp_dir_template|.
// |temp_dir_template| is passed to mkdtemp() so it must satisfy all
// requirements by mkdtemp.
int ReadNorFlash(char *temp_dir_template) {
int ret = 0;
// Create a temp dir to work in.
ret++;
if (mkdtemp(temp_dir_template) == NULL) {
Error("Cannot create a temporary directory.\n");
return ret;
}
// Read RW_GPT section from NOR flash to "rw_gpt".
ret++;
int fd_flags = fcntl(1, F_GETFD);
// Close stdout on exec so that flashrom does not muck up cgpt's output.
fcntl(1, F_SETFD, FD_CLOEXEC);
if (ForkExecL(temp_dir_template, FLASHROM_PATH, "-i", "RW_GPT:rw_gpt", "-r",
NULL) != 0) {
Error("Cannot exec flashrom to read from RW_GPT section.\n");
RemoveDir(temp_dir_template);
} else {
ret = 0;
}
fcntl(1, F_SETFD, fd_flags);
return ret;
}
// Write "rw_gpt" back to NOR flash. We write the file in two parts for safety.
int WriteNorFlash(const char *dir) {
int ret = 0;
ret++;
if (split_gpt(dir, "rw_gpt") != 0) {
Error("Cannot split rw_gpt in two.\n");
return ret;
}
ret++;
int nr_fails = 0;
int fd_flags = fcntl(1, F_GETFD);
// Close stdout on exec so that flashrom does not muck up cgpt's output.
fcntl(1, F_SETFD, FD_CLOEXEC);
if (ForkExecL(dir, FLASHROM_PATH, "-i", "RW_GPT_PRIMARY:rw_gpt_1",
"-w", "--fast-verify", NULL) != 0) {
Warning("Cannot write the 1st half of rw_gpt back with flashrom.\n");
nr_fails++;
}
if (ForkExecL(dir, FLASHROM_PATH, "-i", "RW_GPT_SECONDARY:rw_gpt_2",
"-w", "--fast-verify", NULL) != 0) {
Warning("Cannot write the 2nd half of rw_gpt back with flashrom.\n");
nr_fails++;
}
fcntl(1, F_SETFD, fd_flags);
switch (nr_fails) {
case 0: ret = 0; break;
case 1: Warning("It might still be okay.\n"); break;
case 2: Error("Cannot write both parts back with flashrom.\n"); break;
}
return ret;
}