| /* 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> |
| #if !defined(__FreeBSD__) |
| #include <linux/major.h> |
| #endif |
| #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" |
| #include "subprocess.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; |
| } |
| |
| // TODO(b:184812319): Remove these functions and use subprocess_run everywhere. |
| 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; |
| } |
| |
| static 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; |
| va_end(ap); |
| 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); |
| } |
| |
| #define FLASHROM_RW_GPT_PRI "RW_GPT_PRIMARY:rw_gpt_1", |
| #define FLASHROM_RW_GPT_SEC "RW_GPT_SECONDARY:rw_gpt_2" |
| #define FLASHROM_RW_GPT "RW_GPT:rw_gpt" |
| |
| // Read RW_GPT from NOR flash to "rw_gpt" in a dir. |
| // TODO(b:184812319): Replace this function with flashrom_read. |
| int ReadNorFlash(const char *dir) { |
| int ret = 0; |
| |
| // Read RW_GPT section from NOR flash to "rw_gpt". |
| ret++; |
| |
| char *cwd = getcwd(NULL, 0); |
| if (!cwd) { |
| Error("Cannot get current directory.\n"); |
| return ret; |
| } |
| if (chdir(dir) < 0) { |
| Error("Cannot change directory.\n"); |
| goto out_free; |
| } |
| const char *const argv[] = {FLASHROM_PATH, "-i", FLASHROM_RW_GPT, "-r"}; |
| // Redirect stdout to /dev/null so that flashrom does not muck up cgpt's |
| // output. |
| if (subprocess_run(argv, &subprocess_null, &subprocess_null, NULL) != 0) { |
| Error("Cannot exec flashrom to read from RW_GPT section.\n"); |
| } else { |
| ret = 0; |
| } |
| if (chdir(cwd) < 0) { |
| Error("Cannot change directory back to original.\n"); |
| goto out_free; |
| } |
| |
| out_free: |
| free(cwd); |
| return ret; |
| } |
| |
| static int FlashromWriteRegion(const char *region) |
| { |
| const char *const argv[] = {FLASHROM_PATH, "-i", region, "-w", "--noverify-all"}; |
| // Redirect stdout to /dev/null so that flashrom does not muck up cgpt's |
| // output. |
| if (subprocess_run(argv, &subprocess_null, &subprocess_null, NULL) != 0) { |
| Warning("Cannot write '%s' back with flashrom.\n", region); |
| return 1; |
| } |
| return 0; |
| } |
| |
| // Write "rw_gpt" back to NOR flash. We write the file in two parts for safety. |
| // TODO(b:184812319): Replace this function with flashrom_write. |
| 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; |
| |
| char *cwd = getcwd(NULL, 0); |
| if (!cwd) { |
| Error("Cannot get current directory.\n"); |
| return ret; |
| } |
| if (chdir(dir) < 0) { |
| Error("Cannot change directory.\n"); |
| goto out_free; |
| } |
| if (FlashromWriteRegion(FLASHROM_RW_GPT_PRI)) |
| nr_fails++; |
| if (FlashromWriteRegion(FLASHROM_RW_GPT_SEC)) |
| nr_fails++; |
| |
| if (chdir(cwd) < 0) { |
| Error("Cannot change directory back to original.\n"); |
| goto out_free; |
| } |
| 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; |
| } |
| |
| out_free: |
| free(cwd); |
| return ret; |
| } |