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