Various fixups and checks to make scan-build happy
[petitboot] / discover / grub2 / env.c
1
2 #include <stdio.h>
3 #include <string.h>
4
5 #include <log/log.h>
6 #include <file/file.h>
7 #include <types/types.h>
8 #include <talloc/talloc.h>
9 #include <util/util.h>
10
11 #include <discover/parser.h>
12
13 #include "grub2.h"
14
15 static const char *default_envfile = "grubenv";
16 static const char *signature = "# GRUB Environment Block\n";
17
18 static int parse_buf_to_env(struct grub2_script *script, void *buf, int len)
19 {
20         char *tmp = NULL, *line, *sep;
21         int siglen;
22
23         siglen = strlen(signature);
24
25         if (len < siglen) {
26                 pb_log("grub environment block too small\n");
27                 return -1;
28         }
29
30         if (memcmp(buf, signature, siglen)) {
31                 pb_log("grub environment block has invalid signature\n");
32                 return -1;
33         }
34
35         buf += siglen;
36
37         for (line = strtok_r(buf, "\n", &tmp); line;
38                                 line = strtok_r(NULL, "\n", &tmp)) {
39
40                 if (*line == '#')
41                         continue;
42
43                 sep = strchr(line, '=');
44                 if (!sep)
45                         continue;
46                 if (sep == line)
47                         continue;
48
49                 *sep = '\0';
50                 script_env_set(script, line, sep + 1);
51         }
52
53         return 0;
54 }
55
56 int builtin_load_env(struct grub2_script *script,
57                 void *data __attribute__((unused)),
58                 int argc, char *argv[]);
59
60 int builtin_load_env(struct grub2_script *script,
61                 void *data __attribute__((unused)),
62                 int argc, char *argv[])
63 {
64         struct discover_device *dev = script->ctx->device;
65         const char *envfile;
66         char *buf, *envpath;
67         int rc, len;
68
69         /* we only support local filesystems */
70         if (!dev->mounted) {
71                 pb_log("load_env: can't load from a non-mounted device (%s)\n",
72                                 dev->device->id);
73                 return -1;
74         }
75
76         if (argc == 3 && !strcmp(argv[1], "-f"))
77                 envfile = argv[2];
78         else
79                 envfile = default_envfile;
80
81         envpath = talloc_asprintf(script, "%s/%s",
82                                 script_env_get(script, "prefix") ? : "",
83                                 envfile);
84
85         rc = parser_request_file(script->ctx, dev, envpath, &buf, &len);
86
87         if (!rc) {
88                 rc = parse_buf_to_env(script, buf, len);
89                 if (rc)
90                         pb_debug_fn("Failed to set env\n");
91                 talloc_free(buf);
92         }
93
94         return 0;
95 }
96
97 static int update_env(char *buf, int buflen, const char *name,
98                 const char *value)
99 {
100         /* head and tail define the pointers within the existing data that we
101          * can insert our env entry, end is the last byte of valid environment
102          * data. if tail - head != entrylen, when we'll memmove to create (or
103          * remove) space */
104         int i, j, linelen, namelen, valuelen, entrylen, delta, space;
105         char *head, *tail, *end;
106         bool replace;
107
108         namelen = strlen(name);
109         valuelen = strlen(value);
110         entrylen = namelen + strlen("=") + valuelen + strlen("\n");
111
112         head = tail = end = NULL;
113         replace = false;
114
115         /* For each line (where linelen includes the trailing \n). Find
116          * head, tail and end.
117          */
118         for (i = 0; i < buflen; i += linelen) {
119                 char *eol = NULL, *sep = NULL, *line = buf + i;
120
121                 /* find eol and sep */
122                 for (j = 0; !eol && i + j < buflen; j++) {
123                         switch (line[j]) {
124                         case '\n':
125                                 eol = line + j;
126                                 break;
127                         case '=':
128                                 if (!sep)
129                                         sep = line + j;
130                                 break;
131                         }
132                 }
133
134                 /* no eol, put the new value at the start of this line,
135                  * no need to shift data. This will also match the
136                  * padding '#' chars at the end of the entries. */
137                 if (!eol) {
138                         if (!head) {
139                                 head = line;
140                                 tail = head + entrylen;
141                         }
142                         end = line;
143                         break;
144                 }
145
146                 linelen = (eol - line) + 1;
147                 end = line + linelen;
148
149                 /* invalid line? may as well overwrite it with valid data */
150                 if (!sep && !replace) {
151                         head = line;
152                         tail = line + linelen;
153                 }
154
155                 /* an existing entry for this var? We set replace, as we prefer
156                  * to overwrite an existing entry (the first one, in
157                  * particular) rather than use an invalid line.
158                  */
159                 if (sep && !replace && sep - line == namelen &&
160                                 !strncmp(name, line, namelen)) {
161                         head = line;
162                         tail = line + linelen;
163                         replace = true;
164                 }
165         }
166
167         if (!head || !tail || !end) {
168                 pb_log("grub save_env: can't parse buffer space\n");
169                 return -1;
170         }
171
172         /* how much extra space do we need? */
173         delta = entrylen - (tail - head);
174         /* how much space do we have? */
175         space = (buf + buflen) - end;
176
177         /* check we have enough space. the tail > buf-end check is required
178          * for the case where there was no eol, and we set
179          * tail = head + entrylen
180          */
181         if (delta > space || tail > buf + buflen) {
182                 pb_log("grub save_env: not enough buffer space\n");
183                 return -1;
184         }
185
186         /* create space between head & tail */
187         if (delta) {
188                 /* for positive delta, we need to reduce the copied data size */
189                 int shiftlen = delta > 0 ? delta : 0;
190                 memmove(tail + delta, tail, (buf + buflen) - tail - shiftlen);
191         }
192
193         /* if we've shifted data down towards head, we'll need to append
194          * padding */
195         if (delta < 0)
196                 memset(buf + buflen + delta, '#', 0 - delta);
197
198         /* set the entry data */
199         memcpy(head, name, namelen);
200         memcpy(head + namelen, "=", 1);
201         memcpy(head + namelen + 1, value, valuelen);
202         memcpy(head + namelen + 1 + valuelen, "\n", 1);
203
204         return 0;
205 }
206
207 int builtin_save_env(struct grub2_script *script,
208                 void *data __attribute__((unused)),
209                 int argc, char *argv[]);
210
211 int builtin_save_env(struct grub2_script *script,
212                 void *data __attribute__((unused)),
213                 int argc, char *argv[])
214 {
215         struct discover_device *dev = script->ctx->device;
216         int i, rc, len, siglen;
217         char *buf, *envpath;
218         const char *envfile;
219         bool using_dash_f = false;
220
221         /* we only support local filesystems */
222         if (!dev->mounted) {
223                 pb_log("save_env: can't save to a non-mounted device (%s)\n",
224                                 dev->device->id);
225                 return -1;
226         }
227
228         if (argc >= 2 && !strcmp(argv[1], "-f")) {
229                 if (argc < 3) {
230                         pb_log("save_env: for -f, need argument\n");
231                         return -1;
232                 }
233
234                 envfile = argv[2];
235                 using_dash_f = true;
236         } else
237                 envfile = default_envfile;
238
239         envpath = talloc_asprintf(script, "%s/%s",
240                                 script_env_get(script, "prefix") ? : "",
241                                 envfile);
242         buf = NULL;
243
244         rc = parser_request_file(script->ctx, dev, envpath, &buf, &len);
245
246         siglen = strlen(signature);
247
248         /* we require the environment to be pre-allocated, so abort if
249          * the file isn't present and valid */
250         if (rc || len < siglen || memcmp(buf, signature, siglen))
251                 goto err;
252
253         /* For "-f", skip the "-f <file>" arguments in picking the
254          * variables to save. */
255         i = (using_dash_f ? 3 : 1);
256
257         for (; i < argc; i++) {
258                 const char *name, *value;
259
260                 name = argv[i];
261                 value = script_env_get(script, name);
262                 if (!value) {
263                         pb_log("Saved unset environment variable %s!\n", name);
264                         value = "";
265                 }
266
267                 update_env(buf + siglen, len - siglen, name, value);
268         }
269
270         rc = parser_replace_file(script->ctx, dev, envpath, buf, len);
271
272 err:
273         talloc_free(buf);
274         talloc_free(envpath);
275         return rc;
276 }
277