1 // Copyright 2018 The Fuchsia 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 #include <ddk/debug.h>
6 #include <kvstore/kvstore.h>
7 #include <zircon/boot/sysconfig.h>
8 #include <zircon/device/block.h>
9 #include <zircon/hw/gpt.h>
10 #include <dirent.h>
11 #include <fcntl.h>
12 #include <limits.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <unistd.h>
17
18 #define DEV_BLOCK "/dev/class/block"
19
20 typedef enum {
21 OP_READ,
22 OP_WRITE,
23 OP_EDIT,
24 } sysconfig_op_t;
25
26 static const uint8_t sysconfig_guid[GPT_GUID_LEN] = GUID_SYS_CONFIG_VALUE;
27
usage(void)28 static void usage(void) {
29 fprintf(stderr,
30 "Usage:\n"
31 " sysconfig read <section> [key]*\n"
32 " sysconfig write <section> [key=value]*\n"
33 " sysconfig edit <section> [key=value]*\n"
34 "\n"
35 "Where <section> is one of: {version-a, version-b, boot-default, boot-oneshot}\n"
36 "\n"
37 "read: Print values for the specified keys. If no keys are provided after \"read\",\n"
38 " then all key/value pairs are printed.\n"
39 "write: Write the provided key/value pairs to the specified section.\n"
40 "edit: Write the provided key/value pairs to the specified section,\n"
41 " preserving any existing key/value pairs already in the partition\n");
42 }
43
44 // returns a file descriptor to the raw sysconfig partition
open_sysconfig(void)45 static int open_sysconfig(void) {
46 struct dirent* de;
47 DIR* dir = opendir(DEV_BLOCK);
48 if (!dir) {
49 printf("Error opening %s\n", DEV_BLOCK);
50 return -1;
51 }
52 while ((de = readdir(dir)) != NULL) {
53 if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) {
54 continue;
55 }
56 char path[PATH_MAX];
57 snprintf(path, sizeof(path), "%s/%s", DEV_BLOCK, de->d_name);
58 int fd = open(path, O_RDWR);
59 if (fd < 0) {
60 fprintf(stderr, "Error opening %s\n", path);
61 continue;
62 }
63
64 uint8_t guid[GPT_GUID_LEN];
65 if (ioctl_block_get_type_guid(fd, &guid, sizeof(guid)) < 0) {
66 close(fd);
67 continue;
68 }
69 if (memcmp(guid, sysconfig_guid, sizeof(sysconfig_guid))) {
70 close(fd);
71 continue;
72 }
73
74 return fd;
75 }
76 closedir(dir);
77 return -1;
78 }
79
print_func(void * cookie,const char * key,const char * value)80 static int print_func(void *cookie, const char* key, const char* value) {
81 printf("%s=%s\n", key, value);
82 return 0;
83 }
84
copy_func(void * cookie,const char * key,const char * value)85 static int copy_func(void *cookie, const char* key, const char* value) {
86 struct kvstore* kvs = cookie;
87
88 // copy values to kvs if they aren't set already
89 const char* new_value = kvs_get(kvs, key, NULL);
90 if (new_value) {
91 return 0;
92 } else {
93 return kvs_add(kvs, key, value);
94 }
95 }
96
main(int argc,char ** argv)97 int main(int argc, char **argv) {
98 int ret = 0;
99
100 if (argc < 3) {
101 usage();
102 return -1;
103 }
104
105 // skip "sysconfig"
106 argv++;
107 argc--;
108
109 const char* op_name = *argv++;
110 argc--;
111 sysconfig_op_t op;
112
113 if (!strcmp(op_name, "read")) {
114 op = OP_READ;
115 } else if (!strcmp(op_name, "write")) {
116 op = OP_WRITE;
117 } else if (!strcmp(op_name, "edit")) {
118 op = OP_EDIT;
119 } else {
120 usage();
121 return -1;
122 }
123
124 off_t section_offset;
125 const char* section = *argv++;
126 argc--;
127 if (!strcmp(section, "version-a")) {
128 section_offset = ZX_SYSCONFIG_VERSION_A_OFFSET;
129 } else if (!strcmp(section, "version-b")) {
130 section_offset = ZX_SYSCONFIG_VERSION_B_OFFSET;
131 } else if (!strcmp(section, "boot-default")) {
132 section_offset = ZX_SYSCONFIG_BOOT_DEFAULT_OFFSET;
133 } else if (!strcmp(section, "boot-oneshot")) {
134 section_offset = ZX_SYSCONFIG_BOOT_ONESHOT_OFFSET;
135 } else {
136 usage();
137 return -1;
138 }
139
140 int fd = open_sysconfig();
141 if (fd < 0) {
142 fprintf(stderr, "could not find sysconfig partition\n");
143 return -1;
144 }
145
146 ret = lseek(fd, section_offset, SEEK_SET);
147 if (ret < 0) {
148 fprintf(stderr, "lseek failed\n");
149 goto done;
150 }
151
152 uint8_t old_buffer[ZX_SYSCONFIG_KVSTORE_SIZE];
153 uint8_t new_buffer[ZX_SYSCONFIG_KVSTORE_SIZE];
154
155 if ((ret = read(fd, old_buffer, sizeof(old_buffer))) != sizeof(old_buffer)) {
156 fprintf(stderr, "could not read sysconfig partition: %d\n", ret);
157 goto done;
158 }
159
160 // we will read the current section into old_kvs and write new section from new_kvs
161 struct kvstore old_kvs, new_kvs;
162
163 ret = kvs_load(&old_kvs, old_buffer, sizeof(old_buffer));
164 if (ret == KVS_ERR_PARSE_HDR) {
165 if (op == OP_WRITE || op == OP_EDIT) {
166 printf("initializing empty or corrupt sysconfig partition\n");
167 kvs_init(&old_kvs, old_buffer, sizeof(old_buffer));
168 } else {
169 fprintf(stderr, "kvs_load failed: %d\n", ret);
170 goto done;
171 }
172 } else if (ret < 0) {
173 fprintf(stderr, "unexpected error %d from kvs_load\n", ret);
174 goto done;
175 }
176
177 if (op == OP_WRITE || op == OP_EDIT) {
178 kvs_init(&new_kvs, new_buffer, sizeof(new_buffer));
179 }
180
181 if (argc == 0 && op == OP_READ) {
182 // print all key/value pairs
183 kvs_foreach(&old_kvs, NULL, print_func);
184 goto done;
185 }
186
187 while (argc > 0) {
188 const char* arg = *argv++;
189 argc--;
190 char* equals = strchr(arg, '=');
191 // we should only find an '=' if we are writing or editing
192 if (!!equals == (op == OP_READ)) {
193 usage();
194 ret = -1;
195 goto done;
196 }
197
198 if (op == OP_WRITE || op == OP_EDIT) {
199 // separate arg into key and value strings
200 *equals = 0;
201 const char* key = arg;
202 const char* value = equals + 1;
203 ret = kvs_add(&new_kvs, key, value);
204 if (ret < 0) {
205 fprintf(stderr, "kvs_add failed: %d\n", ret);
206 goto done;
207 }
208 } else {
209 const char* key = arg;
210 const char* value = kvs_get(&old_kvs, key, "");
211 printf("%s=%s\n", key, value);
212 }
213 }
214
215 if (op == OP_EDIT) {
216 // copy the other key/value pairs from old_kvs to new_kvs
217 ret = kvs_foreach(&old_kvs, &new_kvs, copy_func);
218 if (ret < 0) {
219 fprintf(stderr, "failed to copy existing values to new kvs: %d\n", ret);
220 goto done;
221 }
222 }
223 if (op == OP_WRITE || op == OP_EDIT) {
224 kvs_save(&new_kvs);
225 ret = lseek(fd, section_offset, SEEK_SET);
226 if (ret < 0) {
227 fprintf(stderr, "lseek failed\n");
228 goto done;
229 }
230 if ((ret = write(fd, new_buffer, sizeof(new_buffer))) != sizeof(new_buffer)) {
231 fprintf(stderr, "could not write sysconfig partition: %d\n", ret);
232 goto done;
233 }
234 }
235
236 done:
237 close(fd);
238 return ret;
239 }
240