OpenWrt – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | /* |
2 | * --- ZyXEL header format --- |
||
3 | * Original Version by Benjamin Berg <benjamin@sipsolutions.net> |
||
4 | * C implementation based on generation-script by Christian Lamparter <chunkeey@gmail.com> |
||
5 | * |
||
6 | * The firmware image prefixed with a header (which is written into the MTD device). |
||
7 | * The header is one erase block (~64KiB) in size, but the checksum only convers the |
||
8 | * first 2KiB. Padding is 0xff. All integers are in big-endian. |
||
9 | * |
||
10 | * The checksum is always a 16-Bit System V checksum (sum -s) stored in a 32-Bit integer. |
||
11 | * |
||
12 | * 4 bytes: checksum of the rootfs image |
||
13 | * 4 bytes: length of the contained rootfs image file (big endian) |
||
14 | * 32 bytes: Firmware Version string (NUL terminated, 0xff padded) |
||
15 | * 4 bytes: checksum over the header partition (big endian - see below) |
||
16 | * 64 bytes: Model (e.g. "NBG6617", NUL termiated, 0xff padded) |
||
17 | * 4 bytes: checksum of the kernel partition |
||
18 | * 4 bytes: length of the contained kernel image file (big endian) |
||
19 | * rest: 0xff padding (To erase block size) |
||
20 | * |
||
21 | * The kernel partition checksum and length is not used for every device. |
||
22 | * If it's notused, pad those 8 bytes with 0xFF. |
||
23 | * |
||
24 | * The checksums are calculated by adding up all bytes and if a 16bit |
||
25 | * overflow occurs, one is added and the sum is masked to 16 bit: |
||
26 | * csum = csum + databyte; if (csum > 0xffff) { csum += 1; csum &= 0xffff }; |
||
27 | * Should the file have an odd number of bytes then the byte len-0x800 is |
||
28 | * used additionally. |
||
29 | * |
||
30 | * The checksum for the header is calculated over the first 2048 bytes with |
||
31 | * the rootfs image checksum as the placeholder during calculation. |
||
32 | * |
||
33 | * This program is free software; you can redistribute it and/or modify it |
||
34 | * under the terms of the GNU General Public License version 2 as published |
||
35 | * by the Free Software Foundation. |
||
36 | * |
||
37 | */ |
||
38 | #include <fcntl.h> |
||
39 | #include <getopt.h> |
||
40 | #include <libgen.h> |
||
41 | #include <stdio.h> |
||
42 | #include <string.h> |
||
43 | #include <stdlib.h> |
||
44 | #include <unistd.h> |
||
45 | |||
46 | #include <sys/mman.h> |
||
47 | #include <sys/stat.h> |
||
48 | |||
49 | #include <arpa/inet.h> |
||
50 | |||
51 | #define VERSION_STRING_LEN 31 |
||
52 | #define ROOTFS_HEADER_LEN 40 |
||
53 | |||
54 | #define KERNEL_HEADER_LEN 8 |
||
55 | |||
56 | #define BOARD_NAME_LEN 64 |
||
57 | #define BOARD_HEADER_LEN 68 |
||
58 | |||
59 | #define HEADER_PARTITION_CALC_LENGTH 2048 |
||
60 | #define HEADER_PARTITION_LENGTH 0x10000 |
||
61 | |||
62 | struct file_info { |
||
63 | char *name; /* name of the file */ |
||
64 | char *data; /* file content */ |
||
65 | size_t size; /* length of the file */ |
||
66 | }; |
||
67 | |||
68 | static char *progname; |
||
69 | |||
70 | static char *board_name = 0; |
||
71 | static char *version_name = 0; |
||
72 | static unsigned int rootfs_size = 0; |
||
73 | |||
74 | static struct file_info kernel = { NULL, NULL, 0 }; |
||
75 | static struct file_info rootfs = { NULL, NULL, 0 }; |
||
76 | static struct file_info rootfs_out = { NULL, NULL, 0 }; |
||
77 | static struct file_info out = { NULL, NULL, 0 }; |
||
78 | |||
79 | #define ERR(fmt, ...) do { \ |
||
80 | fprintf(stderr, "[%s] *** error: " fmt "\n", \ |
||
81 | progname, ## __VA_ARGS__ ); \ |
||
82 | } while (0) |
||
83 | |||
84 | void map_file(struct file_info *finfo) |
||
85 | { |
||
86 | struct stat file_stat = {0}; |
||
87 | int fd; |
||
88 | |||
89 | fd = open(finfo->name, O_RDONLY, (mode_t)0600); |
||
90 | if (fd == -1) { |
||
91 | ERR("Error while opening file %s.", finfo->name); |
||
92 | exit(EXIT_FAILURE); |
||
93 | } |
||
94 | |||
95 | if (fstat(fd, &file_stat) == -1) { |
||
96 | ERR("Error getting file size for %s.", finfo->name); |
||
97 | exit(EXIT_FAILURE); |
||
98 | } |
||
99 | |||
100 | finfo->size = file_stat.st_size; |
||
101 | finfo->data = mmap(0, finfo->size, PROT_READ, MAP_SHARED, fd, 0); |
||
102 | |||
103 | if (finfo->data == MAP_FAILED) { |
||
104 | ERR("Error mapping file %s.", finfo->name); |
||
105 | exit(EXIT_FAILURE); |
||
106 | } |
||
107 | |||
108 | close(fd); |
||
109 | } |
||
110 | |||
111 | void unmap_file(struct file_info *finfo) |
||
112 | { |
||
113 | if(munmap(finfo->data, finfo->size) == -1) { |
||
114 | ERR("Error unmapping file %s.", finfo->name); |
||
115 | exit(EXIT_FAILURE); |
||
116 | } |
||
117 | } |
||
118 | |||
119 | void write_file(struct file_info *finfo) |
||
120 | { |
||
121 | FILE *fout = fopen(finfo->name, "w"); |
||
122 | |||
123 | fwrite(finfo->data, finfo->size, 1, fout); |
||
124 | |||
125 | if (ferror(fout)) { |
||
126 | ERR("Wanted to write, but something went wrong."); |
||
127 | exit(EXIT_FAILURE); |
||
128 | } |
||
129 | |||
130 | fclose(fout); |
||
131 | } |
||
132 | |||
133 | void usage(int status) |
||
134 | { |
||
135 | FILE *stream = (status != EXIT_SUCCESS) ? stderr : stdout; |
||
136 | |||
137 | fprintf(stream, "Usage: %s [OPTIONS...]\n", progname); |
||
138 | fprintf(stream, |
||
139 | "\n" |
||
140 | "Options:\n" |
||
141 | " -k <kernel> path for kernel image\n" |
||
142 | " -r <rootfs> path for rootfs image\n" |
||
143 | " -s <rfssize> size of output rootfs\n" |
||
144 | " -v <version> version string\n" |
||
145 | " -b <boardname> name of board to generate image for\n" |
||
146 | " -o <out_name> name of output image\n" |
||
147 | " -h show this screen\n" |
||
148 | ); |
||
149 | |||
150 | exit(status); |
||
151 | } |
||
152 | |||
153 | static int sysv_chksm(const unsigned char *data, int size) |
||
154 | { |
||
155 | int r; |
||
156 | int checksum; |
||
157 | unsigned int s = 0; /* The sum of all the input bytes, modulo (UINT_MAX + 1). */ |
||
158 | |||
159 | |||
160 | for (int i = 0; i < size; i++) { |
||
161 | s += data[i]; |
||
162 | } |
||
163 | |||
164 | r = (s & 0xffff) + ((s & 0xffffffff) >> 16); |
||
165 | checksum = (r & 0xffff) + (r >> 16); |
||
166 | |||
167 | return checksum; |
||
168 | } |
||
169 | |||
170 | static int zyxel_chksm(const unsigned char *data, int size) |
||
171 | { |
||
172 | return htonl(sysv_chksm(data, size)); |
||
173 | } |
||
174 | |||
175 | char *generate_rootfs_header(struct file_info filesystem, char *version) |
||
176 | { |
||
177 | size_t version_string_length; |
||
178 | unsigned int chksm, size; |
||
179 | char *rootfs_header; |
||
180 | size_t ptr = 0; |
||
181 | |||
182 | rootfs_header = malloc(ROOTFS_HEADER_LEN); |
||
183 | if (!rootfs_header) { |
||
184 | ERR("Couldn't allocate memory for rootfs header!"); |
||
185 | exit(EXIT_FAILURE); |
||
186 | } |
||
187 | |||
188 | /* Prepare padding for firmware-version string here */ |
||
189 | memset(rootfs_header, 0xff, ROOTFS_HEADER_LEN); |
||
190 | |||
191 | chksm = zyxel_chksm((const unsigned char *)filesystem.data, filesystem.size); |
||
192 | size = htonl(filesystem.size); |
||
193 | |||
194 | /* 4 bytes: checksum of the rootfs image */ |
||
195 | memcpy(rootfs_header + ptr, &chksm, 4); |
||
196 | ptr += 4; |
||
197 | |||
198 | /* 4 bytes: length of the contained rootfs image file (big endian) */ |
||
199 | memcpy(rootfs_header + ptr, &size, 4); |
||
200 | ptr += 4; |
||
201 | |||
202 | /* 32 bytes: Firmware Version string (NUL terminated, 0xff padded) */ |
||
203 | version_string_length = strlen(version) <= VERSION_STRING_LEN ? strlen(version) : VERSION_STRING_LEN; |
||
204 | memcpy(rootfs_header + ptr, version, version_string_length); |
||
205 | ptr += version_string_length; |
||
206 | /* Add null-terminator */ |
||
207 | rootfs_header[ptr] = 0x0; |
||
208 | |||
209 | return rootfs_header; |
||
210 | } |
||
211 | |||
212 | char *generate_kernel_header(struct file_info kernel) |
||
213 | { |
||
214 | unsigned int chksm, size; |
||
215 | char *kernel_header; |
||
216 | size_t ptr = 0; |
||
217 | |||
218 | kernel_header = malloc(KERNEL_HEADER_LEN); |
||
219 | if (!kernel_header) { |
||
220 | ERR("Couldn't allocate memory for kernel header!"); |
||
221 | exit(EXIT_FAILURE); |
||
222 | } |
||
223 | |||
224 | chksm = zyxel_chksm((const unsigned char *)kernel.data, kernel.size); |
||
225 | size = htonl(kernel.size); |
||
226 | |||
227 | /* 4 bytes: checksum of the kernel image */ |
||
228 | memcpy(kernel_header + ptr, &chksm, 4); |
||
229 | ptr += 4; |
||
230 | |||
231 | /* 4 bytes: length of the contained kernel image file (big endian) */ |
||
232 | memcpy(kernel_header + ptr, &size, 4); |
||
233 | |||
234 | return kernel_header; |
||
235 | } |
||
236 | |||
237 | unsigned int generate_board_header_checksum(char *kernel_hdr, char *rootfs_hdr, char *boardname) |
||
238 | { |
||
239 | char *board_hdr_tmp; |
||
240 | unsigned int sum; |
||
241 | size_t ptr = 0; |
||
242 | |||
243 | /* |
||
244 | * The checksum of the board header is calculated over the first 2048 bytes of |
||
245 | * the header partition with the rootfs checksum used as a placeholder for then |
||
246 | * board checksum we calculate in this step. The checksum gained from this step |
||
247 | * is then used for the final board header partition. |
||
248 | */ |
||
249 | |||
250 | board_hdr_tmp = malloc(HEADER_PARTITION_CALC_LENGTH); |
||
251 | if (!board_hdr_tmp) { |
||
252 | ERR("Couldn't allocate memory for temporary board header!"); |
||
253 | exit(EXIT_FAILURE); |
||
254 | } |
||
255 | memset(board_hdr_tmp, 0xff, HEADER_PARTITION_CALC_LENGTH); |
||
256 | |||
257 | /* 40 bytes: RootFS header */ |
||
258 | memcpy(board_hdr_tmp, rootfs_hdr, ROOTFS_HEADER_LEN); |
||
259 | ptr += ROOTFS_HEADER_LEN; |
||
260 | |||
261 | /* 4 bytes: RootFS checksum (BE) as placeholder for board-header checksum */ |
||
262 | memcpy(board_hdr_tmp + ptr, rootfs_hdr, 4); |
||
263 | ptr += 4; |
||
264 | |||
265 | /* 32 bytes: Model (e.g. "NBG6617", NUL termiated, 0xff padded) */ |
||
266 | memcpy(board_hdr_tmp + ptr, boardname, strlen(boardname)); |
||
267 | ptr += strlen(boardname); |
||
268 | /* Add null-terminator */ |
||
269 | board_hdr_tmp[ptr] = 0x0; |
||
270 | ptr = ROOTFS_HEADER_LEN + 4 + BOARD_NAME_LEN; |
||
271 | |||
272 | /* 8 bytes: Kernel header */ |
||
273 | if (kernel_hdr) |
||
274 | memcpy(board_hdr_tmp + ptr, kernel_hdr, 8); |
||
275 | |||
276 | /* Calculate the checksum over the first 2048 bytes */ |
||
277 | sum = zyxel_chksm((const unsigned char *)board_hdr_tmp, HEADER_PARTITION_CALC_LENGTH); |
||
278 | free(board_hdr_tmp); |
||
279 | return sum; |
||
280 | } |
||
281 | |||
282 | char *generate_board_header(char *kernel_hdr, char *rootfs_hdr, char *boardname) |
||
283 | { |
||
284 | unsigned int board_checksum; |
||
285 | char *board_hdr; |
||
286 | |||
287 | board_hdr = malloc(BOARD_HEADER_LEN); |
||
288 | if (!board_hdr) { |
||
289 | ERR("Couldn't allocate memory for board header!"); |
||
290 | exit(EXIT_FAILURE); |
||
291 | } |
||
292 | memset(board_hdr, 0xff, BOARD_HEADER_LEN); |
||
293 | |||
294 | /* 4 bytes: checksum over the header partition (big endian) */ |
||
295 | board_checksum = generate_board_header_checksum(kernel_hdr, rootfs_hdr, boardname); |
||
296 | memcpy(board_hdr, &board_checksum, 4); |
||
297 | |||
298 | /* 32 bytes: Model (e.g. "NBG6617", NUL termiated, 0xff padded) */ |
||
299 | memcpy(board_hdr + 4, boardname, strlen(boardname)); |
||
300 | board_hdr[4 + strlen(boardname)] = 0x0; |
||
301 | |||
302 | return board_hdr; |
||
303 | } |
||
304 | |||
305 | int build_image() |
||
306 | { |
||
307 | char *rootfs_header = NULL; |
||
308 | char *kernel_header = NULL; |
||
309 | char *board_header = NULL; |
||
310 | |||
311 | size_t ptr; |
||
312 | |||
313 | /* Load files */ |
||
314 | if (kernel.name) |
||
315 | map_file(&kernel); |
||
316 | map_file(&rootfs); |
||
317 | |||
318 | /* |
||
319 | * Allocate memory and copy input rootfs for temporary output rootfs. |
||
320 | * This is important as we have to generate the rootfs checksum over the |
||
321 | * entire rootfs partition. As we might have to pad the partition to allow |
||
322 | * for flashing via ZyXEL's Web-GUI, we prepare the rootfs partition for the |
||
323 | * output image here (and also use it for calculating the rootfs checksum). |
||
324 | * |
||
325 | * The roofs padding has to be done with 0x00. |
||
326 | */ |
||
327 | rootfs_out.data = calloc(rootfs_out.size, sizeof(char)); |
||
328 | memcpy(rootfs_out.data, rootfs.data, rootfs.size); |
||
329 | |||
330 | /* Prepare headers */ |
||
331 | rootfs_header = generate_rootfs_header(rootfs_out, version_name); |
||
332 | if (kernel.name) |
||
333 | kernel_header = generate_kernel_header(kernel); |
||
334 | board_header = generate_board_header(kernel_header, rootfs_header, board_name); |
||
335 | |||
336 | /* Prepare output file */ |
||
337 | out.size = HEADER_PARTITION_LENGTH + rootfs_out.size; |
||
338 | if (kernel.name) |
||
339 | out.size += kernel.size; |
||
340 | out.data = malloc(out.size); |
||
341 | memset(out.data, 0xFF, out.size); |
||
342 | |||
343 | /* Build output image */ |
||
344 | memcpy(out.data, rootfs_header, ROOTFS_HEADER_LEN); |
||
345 | memcpy(out.data + ROOTFS_HEADER_LEN, board_header, BOARD_HEADER_LEN); |
||
346 | if (kernel.name) |
||
347 | memcpy(out.data + ROOTFS_HEADER_LEN + BOARD_HEADER_LEN, kernel_header, KERNEL_HEADER_LEN); |
||
348 | ptr = HEADER_PARTITION_LENGTH; |
||
349 | memcpy(out.data + ptr, rootfs_out.data, rootfs_out.size); |
||
350 | ptr += rootfs_out.size; |
||
351 | if (kernel.name) |
||
352 | memcpy(out.data + ptr, kernel.data, kernel.size); |
||
353 | |||
354 | /* Write back output image */ |
||
355 | write_file(&out); |
||
356 | |||
357 | /* Free allocated memory */ |
||
358 | if (kernel.name) |
||
359 | unmap_file(&kernel); |
||
360 | unmap_file(&rootfs); |
||
361 | free(out.data); |
||
362 | free(rootfs_out.data); |
||
363 | |||
364 | free(rootfs_header); |
||
365 | if (kernel.name) |
||
366 | free(kernel_header); |
||
367 | free(board_header); |
||
368 | |||
369 | return 0; |
||
370 | } |
||
371 | |||
372 | int check_options() |
||
373 | { |
||
374 | if (!rootfs.name) { |
||
375 | ERR("No rootfs filename supplied"); |
||
376 | return -2; |
||
377 | } |
||
378 | |||
379 | if (!out.name) { |
||
380 | ERR("No output filename supplied"); |
||
381 | return -3; |
||
382 | } |
||
383 | |||
384 | if (!board_name) { |
||
385 | ERR("No board-name supplied"); |
||
386 | return -4; |
||
387 | } |
||
388 | |||
389 | if (!version_name) { |
||
390 | ERR("No version supplied"); |
||
391 | return -5; |
||
392 | } |
||
393 | |||
394 | if (rootfs_size <= 0) { |
||
395 | ERR("Invalid rootfs size supplied"); |
||
396 | return -6; |
||
397 | } |
||
398 | |||
399 | if (strlen(board_name) > 31) { |
||
400 | ERR("Board name is to long"); |
||
401 | return -7; |
||
402 | } |
||
403 | return 0; |
||
404 | } |
||
405 | |||
406 | int main(int argc, char *argv[]) |
||
407 | { |
||
408 | int ret; |
||
409 | progname = basename(argv[0]); |
||
410 | while (1) { |
||
411 | int c; |
||
412 | |||
413 | c = getopt(argc, argv, "b:k:o:r:s:v:h"); |
||
414 | if (c == -1) |
||
415 | break; |
||
416 | |||
417 | switch (c) { |
||
418 | case 'b': |
||
419 | board_name = optarg; |
||
420 | break; |
||
421 | case 'h': |
||
422 | usage(EXIT_SUCCESS); |
||
423 | break; |
||
424 | case 'k': |
||
425 | kernel.name = optarg; |
||
426 | break; |
||
427 | case 'o': |
||
428 | out.name = optarg; |
||
429 | break; |
||
430 | case 'r': |
||
431 | rootfs.name = optarg; |
||
432 | break; |
||
433 | case 's': |
||
434 | sscanf(optarg, "%u", &rootfs_size); |
||
435 | break; |
||
436 | case 'v': |
||
437 | version_name = optarg; |
||
438 | break; |
||
439 | default: |
||
440 | usage(EXIT_FAILURE); |
||
441 | break; |
||
442 | } |
||
443 | } |
||
444 | |||
445 | ret = check_options(); |
||
446 | if (ret) |
||
447 | usage(EXIT_FAILURE); |
||
448 | |||
449 | /* As ZyXEL Web-GUI only accept images with a rootfs equal or larger than the first firmware shipped |
||
450 | * for the device, we need to pad rootfs partition to this size. To perform further calculations, we |
||
451 | * decide the size of this part here. In case the rootfs we want to integrate in our image is larger, |
||
452 | * take it's size, otherwise the supplied size. |
||
453 | * |
||
454 | * Be careful! We rely on assertion of correct size to be performed beforehand. It is unknown if images |
||
455 | * with a to large rootfs are accepted or not. |
||
456 | */ |
||
457 | rootfs_out.size = rootfs_size < rootfs.size ? rootfs.size : rootfs_size; |
||
458 | return build_image(); |
||
459 | } |