diff options
Diffstat (limited to 'lib/x1000-installer/src/xf_package.c')
-rw-r--r-- | lib/x1000-installer/src/xf_package.c | 264 |
1 files changed, 264 insertions, 0 deletions
diff --git a/lib/x1000-installer/src/xf_package.c b/lib/x1000-installer/src/xf_package.c new file mode 100644 index 0000000000..78bddded68 --- /dev/null +++ b/lib/x1000-installer/src/xf_package.c | |||
@@ -0,0 +1,264 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2021 Aidan MacDonald | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | |||
22 | #include "xf_package.h" | ||
23 | #include "xf_error.h" | ||
24 | #include "pathfuncs.h" | ||
25 | #include "file.h" | ||
26 | #include "core_alloc.h" | ||
27 | #include "md5.h" | ||
28 | #include "system.h" | ||
29 | #include <stdbool.h> | ||
30 | #include <string.h> | ||
31 | |||
32 | #ifdef ROCKBOX | ||
33 | # include "microtar-rockbox.h" | ||
34 | #else | ||
35 | # include "microtar-stdio.h" | ||
36 | #endif | ||
37 | |||
38 | #define METADATA_SIZE 4096 /* size of the metadata buffer */ | ||
39 | #define MAX_MAP_SIZE 32 /* maximum number of map entries */ | ||
40 | |||
41 | static int pkg_alloc(struct xf_package* pkg) | ||
42 | { | ||
43 | memset(pkg, 0, sizeof(*pkg)); | ||
44 | |||
45 | /* calculate allocation size */ | ||
46 | size_t alloc_size = 0; | ||
47 | alloc_size += ALIGN_UP_P2(sizeof(mtar_t), 3); | ||
48 | alloc_size += ALIGN_UP_P2(sizeof(struct xf_map) * MAX_MAP_SIZE, 3); | ||
49 | alloc_size += ALIGN_UP_P2(METADATA_SIZE, 3); | ||
50 | alloc_size += 7; /* for alignment */ | ||
51 | |||
52 | pkg->alloc_handle = core_alloc("xf_package", alloc_size); | ||
53 | if(pkg->alloc_handle < 0) | ||
54 | return XF_E_OUT_OF_MEMORY; | ||
55 | |||
56 | /* distribute memory */ | ||
57 | uint8_t* buf = (uint8_t*)core_get_data(pkg->alloc_handle); | ||
58 | memset(buf, 0, alloc_size); | ||
59 | ALIGN_BUFFER(buf, alloc_size, 8); | ||
60 | |||
61 | pkg->tar = (mtar_t*)buf; | ||
62 | buf += ALIGN_UP_P2(sizeof(mtar_t), 3); | ||
63 | |||
64 | pkg->map = (struct xf_map*)buf; | ||
65 | buf += ALIGN_UP_P2(sizeof(struct xf_map) * MAX_MAP_SIZE, 3); | ||
66 | |||
67 | pkg->metadata = (char*)buf; | ||
68 | buf += ALIGN_UP_P2(METADATA_SIZE, 3); | ||
69 | |||
70 | return XF_E_SUCCESS; | ||
71 | } | ||
72 | |||
73 | static int read_meta_line_cb(int n, char* buf, void* arg) | ||
74 | { | ||
75 | struct xf_package* pkg = (struct xf_package*)arg; | ||
76 | size_t length = strlen(buf); | ||
77 | |||
78 | /* skip blank lines and the first line (it's reserved for old format) */ | ||
79 | if(n == 0 || length == 0) | ||
80 | return 0; | ||
81 | |||
82 | /* metadata lines require an '=' sign to separate key and value */ | ||
83 | if(!strchr(buf, '=')) | ||
84 | return XF_E_SYNTAX_ERROR; | ||
85 | |||
86 | /* we need space to copy the key-value pair plus a null terminator */ | ||
87 | if(length + 1 >= METADATA_SIZE - pkg->metadata_len) | ||
88 | return XF_E_BUF_OVERFLOW; | ||
89 | |||
90 | memcpy(&pkg->metadata[pkg->metadata_len], buf, length + 1); | ||
91 | pkg->metadata_len += length + 1; | ||
92 | return 0; | ||
93 | } | ||
94 | |||
95 | static int pkg_read_meta(struct xf_package* pkg) | ||
96 | { | ||
97 | struct xf_stream stream; | ||
98 | int rc = xf_open_tar(pkg->tar, "bootloader-info.txt", &stream); | ||
99 | if(rc) | ||
100 | return XF_E_CANNOT_OPEN_FILE; | ||
101 | |||
102 | char buf[200]; | ||
103 | rc = xf_stream_read_lines(&stream, buf, sizeof(buf), read_meta_line_cb, pkg); | ||
104 | xf_stream_close(&stream); | ||
105 | return rc; | ||
106 | } | ||
107 | |||
108 | static int pkg_read_map(struct xf_package* pkg, | ||
109 | const struct xf_map* dflt_map, int dflt_map_size) | ||
110 | { | ||
111 | /* Attempt to load and parse the map file */ | ||
112 | struct xf_stream stream; | ||
113 | int rc = xf_open_tar(pkg->tar, "flashmap.txt", &stream); | ||
114 | |||
115 | /* If the flash map is absent but a default map has been provided, | ||
116 | * then the update is in the old fixed format. */ | ||
117 | if(rc == MTAR_ENOTFOUND && dflt_map) { | ||
118 | if(dflt_map_size > MAX_MAP_SIZE) | ||
119 | return XF_E_INVALID_PARAMETER; | ||
120 | |||
121 | for(int i = 0; i < dflt_map_size; ++i) { | ||
122 | pkg->map[i] = dflt_map[i]; | ||
123 | pkg->map[i].flags &= ~(XF_MAP_HAS_MD5 | XF_MAP_HAS_FILE_LENGTH); | ||
124 | } | ||
125 | |||
126 | pkg->map_size = dflt_map_size; | ||
127 | return XF_E_SUCCESS; | ||
128 | } | ||
129 | |||
130 | if(rc != MTAR_ESUCCESS) | ||
131 | return XF_E_CANNOT_OPEN_FILE; | ||
132 | |||
133 | rc = xf_map_parse(&stream, pkg->map, MAX_MAP_SIZE); | ||
134 | if(rc < 0) | ||
135 | goto err; | ||
136 | |||
137 | /* Sort the map; reject it if there is any overlap. */ | ||
138 | pkg->map_size = rc; | ||
139 | if(xf_map_sort(pkg->map, pkg->map_size)) { | ||
140 | rc = XF_E_INVALID_PARAMETER; | ||
141 | goto err; | ||
142 | } | ||
143 | |||
144 | /* All packages in the 'new' format are required to have MD5 sums. */ | ||
145 | for(int i = 0; i < pkg->map_size; ++i) { | ||
146 | if(!(pkg->map[i].flags & XF_MAP_HAS_MD5) || | ||
147 | !(pkg->map[i].flags & XF_MAP_HAS_FILE_LENGTH)) { | ||
148 | rc = XF_E_VERIFY_FAILED; | ||
149 | goto err; | ||
150 | } | ||
151 | } | ||
152 | |||
153 | rc = XF_E_SUCCESS; | ||
154 | |||
155 | err: | ||
156 | xf_stream_close(&stream); | ||
157 | return rc; | ||
158 | } | ||
159 | |||
160 | static int pkg_verify(struct xf_package* pkg) | ||
161 | { | ||
162 | struct xf_stream stream; | ||
163 | md5_context ctx; | ||
164 | uint8_t buffer[128]; | ||
165 | |||
166 | for(int i = 0; i < pkg->map_size; ++i) { | ||
167 | /* At a bare minimum, check that the file exists. */ | ||
168 | int rc = xf_open_tar(pkg->tar, pkg->map[i].name, &stream); | ||
169 | if(rc) | ||
170 | return XF_E_VERIFY_FAILED; | ||
171 | |||
172 | /* Also check that it isn't bigger than the update region. | ||
173 | * That would normally indicate a problem. */ | ||
174 | off_t streamsize = xf_stream_get_size(&stream); | ||
175 | if(streamsize > (off_t)pkg->map[i].length) { | ||
176 | rc = XF_E_VERIFY_FAILED; | ||
177 | goto err; | ||
178 | } | ||
179 | |||
180 | /* Check against the listed file length. */ | ||
181 | if(pkg->map[i].flags & XF_MAP_HAS_FILE_LENGTH) { | ||
182 | if(streamsize != (off_t)pkg->map[i].file_length) { | ||
183 | rc = XF_E_VERIFY_FAILED; | ||
184 | goto err; | ||
185 | } | ||
186 | } | ||
187 | |||
188 | /* Check the MD5 sum if we have it. */ | ||
189 | if(pkg->map[i].flags & XF_MAP_HAS_MD5) { | ||
190 | md5_starts(&ctx); | ||
191 | while(1) { | ||
192 | ssize_t n = xf_stream_read(&stream, buffer, sizeof(buffer)); | ||
193 | if(n < 0) { | ||
194 | rc = XF_E_IO; | ||
195 | goto err; | ||
196 | } | ||
197 | |||
198 | md5_update(&ctx, buffer, n); | ||
199 | if((size_t)n < sizeof(buffer)) | ||
200 | break; | ||
201 | } | ||
202 | |||
203 | md5_finish(&ctx, buffer); | ||
204 | if(memcpy(buffer, pkg->map[i].md5, 16)) { | ||
205 | rc = XF_E_VERIFY_FAILED; | ||
206 | goto err; | ||
207 | } | ||
208 | } | ||
209 | |||
210 | err: | ||
211 | xf_stream_close(&stream); | ||
212 | if(rc) | ||
213 | return rc; | ||
214 | } | ||
215 | |||
216 | /* All files passed verification */ | ||
217 | return XF_E_SUCCESS; | ||
218 | } | ||
219 | |||
220 | int xf_package_open_ex(struct xf_package* pkg, const char* file, | ||
221 | const struct xf_map* dflt_map, int dflt_map_size) | ||
222 | { | ||
223 | int rc = pkg_alloc(pkg); | ||
224 | if(rc) | ||
225 | return rc; | ||
226 | |||
227 | #ifdef ROCKBOX | ||
228 | rc = mtar_open(pkg->tar, file, O_RDONLY); | ||
229 | #else | ||
230 | rc = mtar_open(pkg->tar, file, "r"); | ||
231 | #endif | ||
232 | if(rc != MTAR_ESUCCESS) { | ||
233 | rc = XF_E_CANNOT_OPEN_FILE; | ||
234 | goto err; | ||
235 | } | ||
236 | |||
237 | rc = pkg_read_meta(pkg); | ||
238 | if(rc) | ||
239 | goto err; | ||
240 | |||
241 | rc = pkg_read_map(pkg, dflt_map, dflt_map_size); | ||
242 | if(rc) | ||
243 | goto err; | ||
244 | |||
245 | rc = pkg_verify(pkg); | ||
246 | if(rc) | ||
247 | goto err; | ||
248 | |||
249 | err: | ||
250 | if(rc) | ||
251 | xf_package_close(pkg); | ||
252 | return rc; | ||
253 | } | ||
254 | |||
255 | void xf_package_close(struct xf_package* pkg) | ||
256 | { | ||
257 | if(mtar_is_open(pkg->tar)) | ||
258 | mtar_close(pkg->tar); | ||
259 | |||
260 | if(pkg->alloc_handle > 0) { | ||
261 | core_free(pkg->alloc_handle); | ||
262 | pkg->alloc_handle = 0; | ||
263 | } | ||
264 | } | ||