blob: dab1bfa7c12270713918203c6ced58932f5468bc [file] [log] [blame]
// Copyright 2018 The Fuchsia 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 <ddk/debug.h>
#include <kvstore/kvstore.h>
#include <zircon/boot/sysconfig.h>
#include <zircon/device/block.h>
#include <zircon/hw/gpt.h>
#include <dirent.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define DEV_BLOCK "/dev/class/block"
typedef enum {
OP_READ,
OP_WRITE,
OP_EDIT,
} sysconfig_op_t;
static const uint8_t sysconfig_guid[GPT_GUID_LEN] = GUID_SYS_CONFIG_VALUE;
static void usage(void) {
fprintf(stderr,
"Usage:\n"
" sysconfig read <section> [key]*\n"
" sysconfig write <section> [key=value]*\n"
" sysconfig edit <section> [key=value]*\n"
"\n"
"Where <section> is one of: {version-a, version-b, boot-default, boot-oneshot}\n"
"\n"
"read: Print values for the specified keys. If no keys are provided after \"read\",\n"
" then all key/value pairs are printed.\n"
"write: Write the provided key/value pairs to the specified section.\n"
"edit: Write the provided key/value pairs to the specified section,\n"
" preserving any existing key/value pairs already in the partition\n");
}
// returns a file descriptor to the raw sysconfig partition
static int open_sysconfig(void) {
struct dirent* de;
DIR* dir = opendir(DEV_BLOCK);
if (!dir) {
printf("Error opening %s\n", DEV_BLOCK);
return -1;
}
while ((de = readdir(dir)) != NULL) {
if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) {
continue;
}
char path[PATH_MAX];
snprintf(path, sizeof(path), "%s/%s", DEV_BLOCK, de->d_name);
int fd = open(path, O_RDWR);
if (fd < 0) {
fprintf(stderr, "Error opening %s\n", path);
continue;
}
uint8_t guid[GPT_GUID_LEN];
if (ioctl_block_get_type_guid(fd, &guid, sizeof(guid)) < 0) {
close(fd);
continue;
}
if (memcmp(guid, sysconfig_guid, sizeof(sysconfig_guid))) {
close(fd);
continue;
}
return fd;
}
closedir(dir);
return -1;
}
static int print_func(void *cookie, const char* key, const char* value) {
printf("%s=%s\n", key, value);
return 0;
}
static int copy_func(void *cookie, const char* key, const char* value) {
struct kvstore* kvs = cookie;
// copy values to kvs if they aren't set already
const char* new_value = kvs_get(kvs, key, NULL);
if (new_value) {
return 0;
} else {
return kvs_add(kvs, key, value);
}
}
int main(int argc, char **argv) {
int ret = 0;
if (argc < 3) {
usage();
return -1;
}
// skip "sysconfig"
argv++;
argc--;
const char* op_name = *argv++;
argc--;
sysconfig_op_t op;
if (!strcmp(op_name, "read")) {
op = OP_READ;
} else if (!strcmp(op_name, "write")) {
op = OP_WRITE;
} else if (!strcmp(op_name, "edit")) {
op = OP_EDIT;
} else {
usage();
return -1;
}
off_t section_offset;
const char* section = *argv++;
argc--;
if (!strcmp(section, "version-a")) {
section_offset = ZX_SYSCONFIG_VERSION_A_OFFSET;
} else if (!strcmp(section, "version-b")) {
section_offset = ZX_SYSCONFIG_VERSION_B_OFFSET;
} else if (!strcmp(section, "boot-default")) {
section_offset = ZX_SYSCONFIG_BOOT_DEFAULT_OFFSET;
} else if (!strcmp(section, "boot-oneshot")) {
section_offset = ZX_SYSCONFIG_BOOT_ONESHOT_OFFSET;
} else {
usage();
return -1;
}
int fd = open_sysconfig();
if (fd < 0) {
fprintf(stderr, "could not find sysconfig partition\n");
return -1;
}
ret = lseek(fd, section_offset, SEEK_SET);
if (ret < 0) {
fprintf(stderr, "lseek failed\n");
goto done;
}
uint8_t old_buffer[ZX_SYSCONFIG_KVSTORE_SIZE];
uint8_t new_buffer[ZX_SYSCONFIG_KVSTORE_SIZE];
if ((ret = read(fd, old_buffer, sizeof(old_buffer))) != sizeof(old_buffer)) {
fprintf(stderr, "could not read sysconfig partition: %d\n", ret);
goto done;
}
// we will read the current section into old_kvs and write new section from new_kvs
struct kvstore old_kvs, new_kvs;
ret = kvs_load(&old_kvs, old_buffer, sizeof(old_buffer));
if (ret == KVS_ERR_PARSE_HDR) {
if (op == OP_WRITE || op == OP_EDIT) {
printf("initializing empty or corrupt sysconfig partition\n");
kvs_init(&old_kvs, old_buffer, sizeof(old_buffer));
} else {
fprintf(stderr, "kvs_load failed: %d\n", ret);
goto done;
}
} else if (ret < 0) {
fprintf(stderr, "unexpected error %d from kvs_load\n", ret);
goto done;
}
if (op == OP_WRITE || op == OP_EDIT) {
kvs_init(&new_kvs, new_buffer, sizeof(new_buffer));
}
if (argc == 0 && op == OP_READ) {
// print all key/value pairs
kvs_foreach(&old_kvs, NULL, print_func);
goto done;
}
while (argc > 0) {
const char* arg = *argv++;
argc--;
char* equals = strchr(arg, '=');
// we should only find an '=' if we are writing or editing
if (!!equals == (op == OP_READ)) {
usage();
ret = -1;
goto done;
}
if (op == OP_WRITE || op == OP_EDIT) {
// separate arg into key and value strings
*equals = 0;
const char* key = arg;
const char* value = equals + 1;
ret = kvs_add(&new_kvs, key, value);
if (ret < 0) {
fprintf(stderr, "kvs_add failed: %d\n", ret);
goto done;
}
} else {
const char* key = arg;
const char* value = kvs_get(&old_kvs, key, "");
printf("%s=%s\n", key, value);
}
}
if (op == OP_EDIT) {
// copy the other key/value pairs from old_kvs to new_kvs
ret = kvs_foreach(&old_kvs, &new_kvs, copy_func);
if (ret < 0) {
fprintf(stderr, "failed to copy existing values to new kvs: %d\n", ret);
goto done;
}
}
if (op == OP_WRITE || op == OP_EDIT) {
kvs_save(&new_kvs);
ret = lseek(fd, section_offset, SEEK_SET);
if (ret < 0) {
fprintf(stderr, "lseek failed\n");
goto done;
}
if ((ret = write(fd, new_buffer, sizeof(new_buffer))) != sizeof(new_buffer)) {
fprintf(stderr, "could not write sysconfig partition: %d\n", ret);
goto done;
}
}
done:
close(fd);
return ret;
}