1c8635fbc924427b880ab518ff3e3ba7664dc259
[petitboot] / discover / grub2 / env.c
1
2 #include <stdio.h>
3 #include <string.h>
4
5 #include <log/log.h>
6 #include <types/types.h>
7 #include <talloc/talloc.h>
8 #include <util/util.h>
9
10 #include <discover/parser.h>
11 #include <discover/file.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, *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                 talloc_free(buf);
90         }
91
92         return 0;
93 }
94
95 static int update_env(char *buf, int buflen, const char *name,
96                 const char *value)
97 {
98         /* head and tail define the pointers within the existing data that we
99          * can insert our env entry, end is the last byte of valid environment
100          * data. if tail - head != entrylen, when we'll memmove to create (or
101          * remove) space */
102         int i, j, linelen, namelen, valuelen, entrylen, delta, space;
103         char *head, *tail, *end;
104         bool replace;
105
106         namelen = strlen(name);
107         valuelen = strlen(value);
108         entrylen = namelen + strlen("=") + valuelen + strlen("\n");
109
110         head = tail = end = NULL;
111         replace = false;
112
113         /* For each line (where linelen includes the trailing \n). Find
114          * head, tail and end.
115          */
116         for (i = 0; i < buflen; i += linelen) {
117                 char *eol = NULL, *sep = NULL, *line = buf + i;
118
119                 /* find eol and sep */
120                 for (j = 0; !eol && i + j < buflen; j++) {
121                         switch (line[j]) {
122                         case '\n':
123                                 eol = line + j;
124                                 break;
125                         case '=':
126                                 if (!sep)
127                                         sep = line + j;
128                                 break;
129                         }
130                 }
131
132                 /* no eol, put the new value at the start of this line,
133                  * no need to shift data. This will also match the
134                  * padding '#' chars at the end of the entries. */
135                 if (!eol) {
136                         if (!head) {
137                                 head = line;
138                                 tail = head + entrylen;
139                         }
140                         end = line;
141                         break;
142                 }
143
144                 linelen = (eol - line) + 1;
145                 end = line + linelen;
146
147                 /* invalid line? may as well overwrite it with valid data */
148                 if (!sep && !replace) {
149                         head = line;
150                         tail = line + linelen;
151                 }
152
153                 /* an existing entry for this var? We set replace, as we prefer
154                  * to overwrite an existing entry (the first one, in
155                  * particular) rather than use an invalid line.
156                  */
157                 if (sep && !replace && sep - line == namelen &&
158                                 !strncmp(name, line, namelen)) {
159                         head = line;
160                         tail = line + linelen;
161                         replace = true;
162                 }
163         }
164
165         if (!head || !tail || !end) {
166                 pb_log("grub save_env: can't parse buffer space\n");
167                 return -1;
168         }
169
170         /* how much extra space do we need? */
171         delta = entrylen - (tail - head);
172         /* how much space do we have? */
173         space = (buf + buflen) - end;
174
175         /* check we have enough space. the tail > buf-end check is required
176          * for the case where there was no eol, and we set
177          * tail = head + entrylen
178          */
179         if (delta > space || tail > buf + buflen) {
180                 pb_log("grub save_env: not enough buffer space\n");
181                 return -1;
182         }
183
184         /* create space between head & tail */
185         if (delta) {
186                 /* for positive delta, we need to reduce the copied data size */
187                 int shiftlen = delta > 0 ? delta : 0;
188                 memmove(tail + delta, tail, (buf + buflen) - tail - shiftlen);
189         }
190
191         /* if we've shifted data down towards head, we'll need to append
192          * padding */
193         if (delta < 0)
194                 memset(buf + buflen + delta, '#', 0 - delta);
195
196         /* set the entry data */
197         memcpy(head, name, namelen);
198         memcpy(head + namelen, "=", 1);
199         memcpy(head + namelen + 1, value, valuelen);
200         memcpy(head + namelen + 1 + valuelen, "\n", 1);
201
202         return 0;
203 }
204
205 int builtin_save_env(struct grub2_script *script,
206                 void *data __attribute__((unused)),
207                 int argc, char *argv[]);
208
209 int builtin_save_env(struct grub2_script *script,
210                 void *data __attribute__((unused)),
211                 int argc, char *argv[])
212 {
213         struct discover_device *dev = script->ctx->device;
214         int i, rc, len, siglen;
215         char *buf, *envpath;
216         const char *envfile;
217
218         /* we only support local filesystems */
219         if (!dev->mounted) {
220                 pb_log("save_env: can't save to a non-mounted device (%s)\n",
221                                 dev->device->id);
222                 return -1;
223         }
224
225         if (argc == 3 && !strcmp(argv[1], "-f"))
226                 envfile = argv[2];
227         else
228                 envfile = default_envfile;
229
230         envpath = talloc_asprintf(script, "%s/%s",
231                                 script_env_get(script, "prefix") ? : "",
232                                 envfile);
233         buf = NULL;
234
235         rc = parser_request_file(script->ctx, dev, envpath, &buf, &len);
236
237         siglen = strlen(signature);
238
239         /* we require the environment to be pre-allocated, so abort if
240          * the file isn't present and valid */
241         if (rc || len < siglen || memcmp(buf, signature, siglen))
242                 goto err;
243
244         for (i = 1; i < argc; i++) {
245                 const char *name, *value;
246
247                 name = argv[i];
248                 value = script_env_get(script, name);
249
250                 update_env(buf + siglen, len - siglen, name, value);
251         }
252
253         rc = parser_replace_file(script->ctx, dev, envpath, buf, len);
254
255 err:
256         talloc_free(buf);
257         talloc_free(envpath);
258         return rc;
259 }
260