1/* Copyright 2015 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
6#include <err.h>
7#include <errno.h>
8#include <fcntl.h>
9#include <ftw.h>
10#include <inttypes.h>
11#include <linux/major.h>
12#include <stdbool.h>
13#include <stdarg.h>
14#include <stdlib.h>
15#include <stdint.h>
16#include <stdio.h>
17#include <string.h>
18#include <sys/stat.h>
19#include <sys/types.h>
20#include <sys/wait.h>
21#include <unistd.h>
22
23#include "cgpt.h"
24#include "cgpt_nor.h"
25
26static const char FLASHROM_PATH[] = "/usr/sbin/flashrom";
27
28// Obtain the MTD size from its sysfs node.
29int GetMtdSize(const char *mtd_device, uint64_t *size) {
30  mtd_device = strrchr(mtd_device, '/');
31  if (mtd_device == NULL) {
32    errno = EINVAL;
33    return 1;
34  }
35  char *sysfs_name;
36  if (asprintf(&sysfs_name, "/sys/class/mtd%s/size", mtd_device) == -1) {
37    return 1;
38  }
39  FILE *fp = fopen(sysfs_name, "r");
40  free(sysfs_name);
41  if (fp == NULL) {
42    return 1;
43  }
44  int ret = (fscanf(fp, "%" PRIu64 "\n", size) != 1);
45  fclose(fp);
46  return ret;
47}
48
49int ForkExecV(const char *cwd, const char *const argv[]) {
50  pid_t pid = fork();
51  if (pid == -1) {
52    return -1;
53  }
54  int status = -1;
55  if (pid == 0) {
56    if (cwd && chdir(cwd) != 0) {
57      return -1;
58    }
59    execv(argv[0], (char *const *)argv);
60    // If this is reached, execv fails.
61    err(-1, "Cannot exec %s in %s.", argv[0], cwd);
62  } else {
63    if (waitpid(pid, &status, 0) != -1 && WIFEXITED(status))
64      return WEXITSTATUS(status);
65  }
66  return status;
67}
68
69int ForkExecL(const char *cwd, const char *cmd, ...) {
70  int argc;
71  va_list ap;
72  va_start(ap, cmd);
73  for (argc = 1; va_arg(ap, char *) != NULL; ++argc);
74  va_end(ap);
75
76  va_start(ap, cmd);
77  const char **argv = calloc(argc + 1, sizeof(char *));
78  if (argv == NULL) {
79    errno = ENOMEM;
80    return -1;
81  }
82  argv[0] = cmd;
83  int i;
84  for (i = 1; i < argc; ++i) {
85    argv[i] = va_arg(ap, char *);
86  }
87  va_end(ap);
88
89  int ret = ForkExecV(cwd, argv);
90  free(argv);
91  return ret;
92}
93
94static int read_write(int source_fd,
95                      uint64_t size,
96                      const char *src_name,
97                      int idx) {
98  int ret = 1;
99  const int bufsize = 4096;
100  char *buf = malloc(bufsize);
101  if (buf == NULL) {
102    goto clean_exit;
103  }
104
105  ret++;
106  char *dest;
107  if (asprintf(&dest, "%s_%d", src_name, idx) == -1) {
108    goto free_buf;
109  }
110
111  ret++;
112  int dest_fd = open(dest, O_WRONLY | O_CLOEXEC | O_CREAT, 0600);
113  if (dest_fd < 0) {
114    goto free_dest;
115  }
116
117  ret++;
118  uint64_t copied = 0;
119  ssize_t nr_read;
120  ssize_t nr_write;
121  while (copied < size) {
122    size_t to_read = size - copied;
123    if (to_read > bufsize) {
124      to_read = bufsize;
125    }
126    nr_read = read(source_fd, buf, to_read);
127    if (nr_read < 0) {
128      goto close_dest_fd;
129    }
130    nr_write = 0;
131    while (nr_write < nr_read) {
132      ssize_t s = write(dest_fd, buf + nr_write, nr_read - nr_write);
133      if (s < 0) {
134        goto close_dest_fd;
135      }
136      nr_write += s;
137    }
138    copied += nr_read;
139  }
140
141  ret = 0;
142
143close_dest_fd:
144  close(dest_fd);
145free_dest:
146  free(dest);
147free_buf:
148  free(buf);
149clean_exit:
150  return ret;
151}
152
153static int split_gpt(const char *dir_name, const char *file_name) {
154  int ret = 1;
155  char *source;
156  if (asprintf(&source, "%s/%s", dir_name, file_name) == -1) {
157    goto clean_exit;
158  }
159
160  ret++;
161  int fd = open(source, O_RDONLY | O_CLOEXEC);
162  if (fd < 0) {
163    goto free_source;
164  }
165
166  ret++;
167  struct stat stat;
168  if (fstat(fd, &stat) != 0 || (stat.st_size & 1) != 0) {
169    goto close_fd;
170  }
171  uint64_t half_size = stat.st_size / 2;
172
173  ret++;
174  if (read_write(fd, half_size, source, 1) != 0 ||
175      read_write(fd, half_size, source, 2) != 0) {
176    goto close_fd;
177  }
178
179  ret = 0;
180close_fd:
181  close(fd);
182free_source:
183  free(source);
184clean_exit:
185  return ret;
186}
187
188static int remove_file_or_dir(const char *fpath, const struct stat *sb,
189                              int typeflag, struct FTW *ftwbuf) {
190  return remove(fpath);
191}
192
193int RemoveDir(const char *dir) {
194  return nftw(dir, remove_file_or_dir, 20, FTW_DEPTH | FTW_PHYS);
195}
196
197// Read RW_GPT from NOR flash to "rw_gpt" in a temp dir |temp_dir_template|.
198// |temp_dir_template| is passed to mkdtemp() so it must satisfy all
199// requirements by mkdtemp.
200int ReadNorFlash(char *temp_dir_template) {
201  int ret = 0;
202
203  // Create a temp dir to work in.
204  ret++;
205  if (mkdtemp(temp_dir_template) == NULL) {
206    Error("Cannot create a temporary directory.\n");
207    return ret;
208  }
209
210  // Read RW_GPT section from NOR flash to "rw_gpt".
211  ret++;
212  int fd_flags = fcntl(1, F_GETFD);
213  // Close stdout on exec so that flashrom does not muck up cgpt's output.
214  fcntl(1, F_SETFD, FD_CLOEXEC);
215  if (ForkExecL(temp_dir_template, FLASHROM_PATH, "-i", "RW_GPT:rw_gpt", "-r",
216                NULL) != 0) {
217    Error("Cannot exec flashrom to read from RW_GPT section.\n");
218    RemoveDir(temp_dir_template);
219  } else {
220    ret = 0;
221  }
222
223  fcntl(1, F_SETFD, fd_flags);
224  return ret;
225}
226
227// Write "rw_gpt" back to NOR flash. We write the file in two parts for safety.
228int WriteNorFlash(const char *dir) {
229  int ret = 0;
230  ret++;
231  if (split_gpt(dir, "rw_gpt") != 0) {
232    Error("Cannot split rw_gpt in two.\n");
233    return ret;
234  }
235  ret++;
236  int nr_fails = 0;
237  int fd_flags = fcntl(1, F_GETFD);
238  // Close stdout on exec so that flashrom does not muck up cgpt's output.
239  fcntl(1, F_SETFD, FD_CLOEXEC);
240  if (ForkExecL(dir, FLASHROM_PATH, "-i", "RW_GPT_PRIMARY:rw_gpt_1",
241                "-w", "--fast-verify", NULL) != 0) {
242    Warning("Cannot write the 1st half of rw_gpt back with flashrom.\n");
243    nr_fails++;
244  }
245  if (ForkExecL(dir, FLASHROM_PATH, "-i", "RW_GPT_SECONDARY:rw_gpt_2",
246                "-w", "--fast-verify", NULL) != 0) {
247    Warning("Cannot write the 2nd half of rw_gpt back with flashrom.\n");
248    nr_fails++;
249  }
250  fcntl(1, F_SETFD, fd_flags);
251  switch (nr_fails) {
252    case 0: ret = 0; break;
253    case 1: Warning("It might still be okay.\n"); break;
254    case 2: Error("Cannot write both parts back with flashrom.\n"); break;
255  }
256  return ret;
257}
258