| // 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; |
| } |