firmware-utils/tplink-safeloader: refactor code for further board support
[librecmc/librecmc.git] / tools / firmware-utils / src / tplink-safeloader.c
1 /*
2   Copyright (c) 2014, Matthias Schiffer <mschiffer@universe-factory.net>
3   All rights reserved.
4
5   Redistribution and use in source and binary forms, with or without
6   modification, are permitted provided that the following conditions are met:
7
8     1. Redistributions of source code must retain the above copyright notice,
9        this list of conditions and the following disclaimer.
10     2. Redistributions in binary form must reproduce the above copyright notice,
11        this list of conditions and the following disclaimer in the documentation
12        and/or other materials provided with the distribution.
13
14   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18   FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19   DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21   CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26
27 /*
28    tplink-safeloader
29
30    Image generation tool for the TP-LINK SafeLoader as seen on
31    TP-LINK Pharos devices (CPE210/220/510/520)
32 */
33
34
35 #include <assert.h>
36 #include <errno.h>
37 #include <stdbool.h>
38 #include <stdio.h>
39 #include <stdint.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <time.h>
43 #include <unistd.h>
44
45 #include <arpa/inet.h>
46
47 #include <sys/types.h>
48 #include <sys/stat.h>
49
50 #include "md5.h"
51
52
53 #define ALIGN(x,a) ({ typeof(a) __a = (a); (((x) + __a - 1) & ~(__a - 1)); })
54
55
56 #define MAX_PARTITIONS  32
57
58 /** An image partition table entry */
59 struct image_partition_entry {
60         const char *name;
61         size_t size;
62         uint8_t *data;
63 };
64
65 /** A flash partition table entry */
66 struct flash_partition_entry {
67         const char *name;
68         uint32_t base;
69         uint32_t size;
70 };
71
72 /** Firmware layout description */
73 struct device_info {
74         const char *id;
75         const char *vendor;
76         const char *support_list;
77         char support_trail;
78         const struct flash_partition_entry partitions[MAX_PARTITIONS+1];
79         const char *first_sysupgrade_partition;
80         const char *last_sysupgrade_partition;
81 };
82
83 /** The content of the soft-version structure */
84 struct __attribute__((__packed__)) soft_version {
85         uint32_t magic;
86         uint32_t zero;
87         uint8_t pad1;
88         uint8_t version_major;
89         uint8_t version_minor;
90         uint8_t version_patch;
91         uint8_t year_hi;
92         uint8_t year_lo;
93         uint8_t month;
94         uint8_t day;
95         uint32_t rev;
96         uint8_t pad2;
97 };
98
99
100 static const uint8_t jffs2_eof_mark[4] = {0xde, 0xad, 0xc0, 0xde};
101
102
103 /**
104    Salt for the MD5 hash
105
106    Fortunately, TP-LINK seems to use the same salt for most devices which use
107    the new image format.
108 */
109 static const uint8_t md5_salt[16] = {
110         0x7a, 0x2b, 0x15, 0xed,
111         0x9b, 0x98, 0x59, 0x6d,
112         0xe5, 0x04, 0xab, 0x44,
113         0xac, 0x2a, 0x9f, 0x4e,
114 };
115
116
117 /** Firmware layout table */
118 static struct device_info boards[] = {
119         /** Firmware layout for the CPE210/220 */
120         {
121                 .id     = "CPE210",
122                 .vendor = "CPE510(TP-LINK|UN|N300-5):1.0\r\n",
123                 .support_list =
124                         "SupportList:\r\n"
125                         "CPE210(TP-LINK|UN|N300-2):1.0\r\n"
126                         "CPE210(TP-LINK|UN|N300-2):1.1\r\n"
127                         "CPE210(TP-LINK|US|N300-2):1.1\r\n"
128                         "CPE210(TP-LINK|EU|N300-2):1.1\r\n"
129                         "CPE220(TP-LINK|UN|N300-2):1.1\r\n"
130                         "CPE220(TP-LINK|US|N300-2):1.1\r\n"
131                         "CPE220(TP-LINK|EU|N300-2):1.1\r\n",
132                 .support_trail = '\xff',
133
134                 .partitions = {
135                         {"fs-uboot", 0x00000, 0x20000},
136                         {"partition-table", 0x20000, 0x02000},
137                         {"default-mac", 0x30000, 0x00020},
138                         {"product-info", 0x31100, 0x00100},
139                         {"signature", 0x32000, 0x00400},
140                         {"os-image", 0x40000, 0x170000},
141                         {"soft-version", 0x1b0000, 0x00100},
142                         {"support-list", 0x1b1000, 0x00400},
143                         {"file-system", 0x1c0000, 0x600000},
144                         {"user-config", 0x7c0000, 0x10000},
145                         {"default-config", 0x7d0000, 0x10000},
146                         {"log", 0x7e0000, 0x10000},
147                         {"radio", 0x7f0000, 0x10000},
148                         {NULL, 0, 0}
149                 },
150
151                 .first_sysupgrade_partition = "os-image",
152                 .last_sysupgrade_partition = "file-system",
153         },
154
155         /** Firmware layout for the CPE510/520 */
156         {
157                 .id     = "CPE510",
158                 .vendor = "CPE510(TP-LINK|UN|N300-5):1.0\r\n",
159                 .support_list =
160                         "SupportList:\r\n"
161                         "CPE510(TP-LINK|UN|N300-5):1.0\r\n"
162                         "CPE510(TP-LINK|UN|N300-5):1.1\r\n"
163                         "CPE510(TP-LINK|UN|N300-5):1.1\r\n"
164                         "CPE510(TP-LINK|US|N300-5):1.1\r\n"
165                         "CPE510(TP-LINK|EU|N300-5):1.1\r\n"
166                         "CPE520(TP-LINK|UN|N300-5):1.1\r\n"
167                         "CPE520(TP-LINK|US|N300-5):1.1\r\n"
168                         "CPE520(TP-LINK|EU|N300-5):1.1\r\n",
169                 .support_trail = '\xff',
170
171                 .partitions = {
172                         {"fs-uboot", 0x00000, 0x20000},
173                         {"partition-table", 0x20000, 0x02000},
174                         {"default-mac", 0x30000, 0x00020},
175                         {"product-info", 0x31100, 0x00100},
176                         {"signature", 0x32000, 0x00400},
177                         {"os-image", 0x40000, 0x170000},
178                         {"soft-version", 0x1b0000, 0x00100},
179                         {"support-list", 0x1b1000, 0x00400},
180                         {"file-system", 0x1c0000, 0x600000},
181                         {"user-config", 0x7c0000, 0x10000},
182                         {"default-config", 0x7d0000, 0x10000},
183                         {"log", 0x7e0000, 0x10000},
184                         {"radio", 0x7f0000, 0x10000},
185                         {NULL, 0, 0}
186                 },
187
188                 .first_sysupgrade_partition = "os-image",
189                 .last_sysupgrade_partition = "file-system",
190         },
191
192         /** Firmware layout for the C2600 */
193         {
194                 .id = "C2600",
195                 .vendor = "",
196                 .support_list =
197                         "SupportList:\r\n"
198                         "{product_name:Archer C2600,product_ver:1.0.0,special_id:00000000}\r\n",
199                 .support_trail = '\x00',
200
201                 .partitions = {
202                         {"SBL1", 0x00000, 0x20000},
203                         {"MIBIB", 0x20000, 0x20000},
204                         {"SBL2", 0x40000, 0x20000},
205                         {"SBL3", 0x60000, 0x30000},
206                         {"DDRCONFIG", 0x90000, 0x10000},
207                         {"SSD", 0xa0000, 0x10000},
208                         {"TZ", 0xb0000, 0x30000},
209                         {"RPM", 0xe0000, 0x20000},
210                         {"fs-uboot", 0x100000, 0x70000},
211                         {"uboot-env", 0x170000, 0x40000},
212                         {"radio", 0x1b0000, 0x40000},
213                         {"os-image", 0x1f0000, 0x200000},
214                         {"file-system", 0x3f0000, 0x1b00000},
215                         {"default-mac", 0x1ef0000, 0x00200},
216                         {"pin", 0x1ef0200, 0x00200},
217                         {"product-info", 0x1ef0400, 0x0fc00},
218                         {"partition-table", 0x1f00000, 0x10000},
219                         {"soft-version", 0x1f10000, 0x10000},
220                         {"support-list", 0x1f20000, 0x10000},
221                         {"profile", 0x1f30000, 0x10000},
222                         {"default-config", 0x1f40000, 0x10000},
223                         {"user-config", 0x1f50000, 0x40000},
224                         {"qos-db", 0x1f90000, 0x40000},
225                         {"usb-config", 0x1fd0000, 0x10000},
226                         {"log", 0x1fe0000, 0x20000},
227                         {NULL, 0, 0}
228                 },
229
230                 .first_sysupgrade_partition = "os-image",
231                 .last_sysupgrade_partition = "file-system"
232         },
233
234         /** Firmware layout for the C9 */
235         {
236                 .id = "ARCHERC9",
237                 .vendor = "",
238                 .support_list =
239                         "SupportList:\n"
240                         "{product_name:ArcherC9,"
241                         "product_ver:1.0.0,"
242                         "special_id:00000000}\n",
243                 .support_trail = '\x00',
244
245                 .partitions = {
246                         {"fs-uboot", 0x00000, 0x40000},
247                         {"os-image", 0x40000, 0x200000},
248                         {"file-system", 0x240000, 0xc00000},
249                         {"default-mac", 0xe40000, 0x00200},
250                         {"pin", 0xe40200, 0x00200},
251                         {"product-info", 0xe40400, 0x00200},
252                         {"partition-table", 0xe50000, 0x10000},
253                         {"soft-version", 0xe60000, 0x00200},
254                         {"support-list", 0xe61000, 0x0f000},
255                         {"profile", 0xe70000, 0x10000},
256                         {"default-config", 0xe80000, 0x10000},
257                         {"user-config", 0xe90000, 0x50000},
258                         {"log", 0xee0000, 0x100000},
259                         {"radio_bk", 0xfe0000, 0x10000},
260                         {"radio", 0xff0000, 0x10000},
261                         {NULL, 0, 0}
262                 },
263
264                 .first_sysupgrade_partition = "os-image",
265                 .last_sysupgrade_partition = "file-system"
266         },
267
268         /** Firmware layout for the EAP120 */
269         {
270                 .id     = "EAP120",
271                 .vendor = "EAP120(TP-LINK|UN|N300-2):1.0\r\n",
272                 .support_list =
273                         "SupportList:\r\n"
274                         "EAP120(TP-LINK|UN|N300-2):1.0\r\n",
275                 .support_trail = '\xff',
276
277                 .partitions = {
278                         {"fs-uboot", 0x00000, 0x20000},
279                         {"partition-table", 0x20000, 0x02000},
280                         {"default-mac", 0x30000, 0x00020},
281                         {"support-list", 0x31000, 0x00100},
282                         {"product-info", 0x31100, 0x00100},
283                         {"soft-version", 0x32000, 0x00100},
284                         {"os-image", 0x40000, 0x180000},
285                         {"file-system", 0x1c0000, 0x600000},
286                         {"user-config", 0x7c0000, 0x10000},
287                         {"backup-config", 0x7d0000, 0x10000},
288                         {"log", 0x7e0000, 0x10000},
289                         {"radio", 0x7f0000, 0x10000},
290                         {NULL, 0, 0}
291                 },
292
293                 .first_sysupgrade_partition = "os-image",
294                 .last_sysupgrade_partition = "file-system"
295         },
296
297         {}
298 };
299
300 #define error(_ret, _errno, _str, ...)                          \
301         do {                                                    \
302                 fprintf(stderr, _str ": %s\n", ## __VA_ARGS__,  \
303                         strerror(_errno));                      \
304                 if (_ret)                                       \
305                         exit(_ret);                             \
306         } while (0)
307
308
309 /** Stores a uint32 as big endian */
310 static inline void put32(uint8_t *buf, uint32_t val) {
311         buf[0] = val >> 24;
312         buf[1] = val >> 16;
313         buf[2] = val >> 8;
314         buf[3] = val;
315 }
316
317 /** Allocates a new image partition */
318 static struct image_partition_entry alloc_image_partition(const char *name, size_t len) {
319         struct image_partition_entry entry = {name, len, malloc(len)};
320         if (!entry.data)
321                 error(1, errno, "malloc");
322
323         return entry;
324 }
325
326 /** Frees an image partition */
327 static void free_image_partition(struct image_partition_entry entry) {
328         free(entry.data);
329 }
330
331 /** Generates the partition-table partition */
332 static struct image_partition_entry make_partition_table(const struct flash_partition_entry *p) {
333         struct image_partition_entry entry = alloc_image_partition("partition-table", 0x800);
334
335         char *s = (char *)entry.data, *end = (char *)(s+entry.size);
336
337         *(s++) = 0x00;
338         *(s++) = 0x04;
339         *(s++) = 0x00;
340         *(s++) = 0x00;
341
342         size_t i;
343         for (i = 0; p[i].name; i++) {
344                 size_t len = end-s;
345                 size_t w = snprintf(s, len, "partition %s base 0x%05x size 0x%05x\n", p[i].name, p[i].base, p[i].size);
346
347                 if (w > len-1)
348                         error(1, 0, "flash partition table overflow?");
349
350                 s += w;
351         }
352
353         s++;
354
355         memset(s, 0xff, end-s);
356
357         return entry;
358 }
359
360
361 /** Generates a binary-coded decimal representation of an integer in the range [0, 99] */
362 static inline uint8_t bcd(uint8_t v) {
363         return 0x10 * (v/10) + v%10;
364 }
365
366
367 /** Generates the soft-version partition */
368 static struct image_partition_entry make_soft_version(uint32_t rev) {
369         struct image_partition_entry entry = alloc_image_partition("soft-version", sizeof(struct soft_version));
370         struct soft_version *s = (struct soft_version *)entry.data;
371
372         time_t t;
373
374         if (time(&t) == (time_t)(-1))
375                 error(1, errno, "time");
376
377         struct tm *tm = localtime(&t);
378
379         s->magic = htonl(0x0000000c);
380         s->zero = 0;
381         s->pad1 = 0xff;
382
383         s->version_major = 0;
384         s->version_minor = 0;
385         s->version_patch = 0;
386
387         s->year_hi = bcd((1900+tm->tm_year)/100);
388         s->year_lo = bcd(tm->tm_year%100);
389         s->month = bcd(tm->tm_mon+1);
390         s->day = bcd(tm->tm_mday);
391         s->rev = htonl(rev);
392
393         s->pad2 = 0xff;
394
395         return entry;
396 }
397
398 /** Generates the support-list partition */
399 static struct image_partition_entry make_support_list(const struct device_info *info) {
400         size_t len = strlen(info->support_list);
401         struct image_partition_entry entry = alloc_image_partition("support-list", len + 9);
402
403         put32(entry.data, len);
404         memset(entry.data+4, 0, 4);
405         memcpy(entry.data+8, info->support_list, len);
406         entry.data[len+8] = info->support_trail;
407
408         return entry;
409 }
410
411 /** Creates a new image partition with an arbitrary name from a file */
412 static struct image_partition_entry read_file(const char *part_name, const char *filename, bool add_jffs2_eof) {
413         struct stat statbuf;
414
415         if (stat(filename, &statbuf) < 0)
416                 error(1, errno, "unable to stat file `%s'", filename);
417
418         size_t len = statbuf.st_size;
419
420         if (add_jffs2_eof)
421                 len = ALIGN(len, 0x10000) + sizeof(jffs2_eof_mark);
422
423         struct image_partition_entry entry = alloc_image_partition(part_name, len);
424
425         FILE *file = fopen(filename, "rb");
426         if (!file)
427                 error(1, errno, "unable to open file `%s'", filename);
428
429         if (fread(entry.data, statbuf.st_size, 1, file) != 1)
430                 error(1, errno, "unable to read file `%s'", filename);
431
432         if (add_jffs2_eof) {
433                 uint8_t *eof = entry.data + statbuf.st_size, *end = entry.data+entry.size;
434
435                 memset(eof, 0xff, end - eof - sizeof(jffs2_eof_mark));
436                 memcpy(end - sizeof(jffs2_eof_mark), jffs2_eof_mark, sizeof(jffs2_eof_mark));
437         }
438
439         fclose(file);
440
441         return entry;
442 }
443
444
445 /**
446    Copies a list of image partitions into an image buffer and generates the image partition table while doing so
447
448    Example image partition table:
449
450      fwup-ptn partition-table base 0x00800 size 0x00800
451      fwup-ptn os-image base 0x01000 size 0x113b45
452      fwup-ptn file-system base 0x114b45 size 0x1d0004
453      fwup-ptn support-list base 0x2e4b49 size 0x000d1
454
455    Each line of the partition table is terminated with the bytes 09 0d 0a ("\t\r\n"),
456    the end of the partition table is marked with a zero byte.
457
458    The firmware image must contain at least the partition-table and support-list partitions
459    to be accepted. There aren't any alignment constraints for the image partitions.
460
461    The partition-table partition contains the actual flash layout; partitions
462    from the image partition table are mapped to the corresponding flash partitions during
463    the firmware upgrade. The support-list partition contains a list of devices supported by
464    the firmware image.
465
466    The base offsets in the firmware partition table are relative to the end
467    of the vendor information block, so the partition-table partition will
468    actually start at offset 0x1814 of the image.
469
470    I think partition-table must be the first partition in the firmware image.
471 */
472 static void put_partitions(uint8_t *buffer, const struct flash_partition_entry *flash_parts, const struct image_partition_entry *parts) {
473         size_t i, j;
474         char *image_pt = (char *)buffer, *end = image_pt + 0x800;
475
476         size_t base = 0x800;
477         for (i = 0; parts[i].name; i++) {
478                 for (j = 0; flash_parts[j].name; j++) {
479                         if (!strcmp(flash_parts[j].name, parts[i].name)) {
480                                 if (parts[i].size > flash_parts[j].size)
481                                         error(1, 0, "%s partition too big (more than %u bytes)", flash_parts[j].name, (unsigned)flash_parts[j].size);
482                                 break;
483                         }
484                 }
485
486                 assert(flash_parts[j].name);
487
488                 memcpy(buffer + base, parts[i].data, parts[i].size);
489
490                 size_t len = end-image_pt;
491                 size_t w = snprintf(image_pt, len, "fwup-ptn %s base 0x%05x size 0x%05x\t\r\n", parts[i].name, (unsigned)base, (unsigned)parts[i].size);
492
493                 if (w > len-1)
494                         error(1, 0, "image partition table overflow?");
495
496                 image_pt += w;
497
498                 base += parts[i].size;
499         }
500 }
501
502 /** Generates and writes the image MD5 checksum */
503 static void put_md5(uint8_t *md5, uint8_t *buffer, unsigned int len) {
504         MD5_CTX ctx;
505
506         MD5_Init(&ctx);
507         MD5_Update(&ctx, md5_salt, (unsigned int)sizeof(md5_salt));
508         MD5_Update(&ctx, buffer, len);
509         MD5_Final(md5, &ctx);
510 }
511
512
513 /**
514    Generates the firmware image in factory format
515
516    Image format:
517
518      Bytes (hex)  Usage
519      -----------  -----
520      0000-0003    Image size (4 bytes, big endian)
521      0004-0013    MD5 hash (hash of a 16 byte salt and the image data starting with byte 0x14)
522      0014-0017    Vendor information length (without padding) (4 bytes, big endian)
523      0018-1013    Vendor information (4092 bytes, padded with 0xff; there seem to be older
524                   (VxWorks-based) TP-LINK devices which use a smaller vendor information block)
525      1014-1813    Image partition table (2048 bytes, padded with 0xff)
526      1814-xxxx    Firmware partitions
527 */
528 static void * generate_factory_image(const struct device_info *info, const struct image_partition_entry *parts, size_t *len) {
529         *len = 0x1814;
530
531         size_t i;
532         for (i = 0; parts[i].name; i++)
533                 *len += parts[i].size;
534
535         uint8_t *image = malloc(*len);
536         if (!image)
537                 error(1, errno, "malloc");
538
539         memset(image, 0xff, *len);
540         put32(image, *len);
541
542         if (info->vendor) {
543                 size_t vendor_len = strlen(info->vendor);
544                 put32(image+0x14, vendor_len);
545                 memcpy(image+0x18, info->vendor, vendor_len);
546         }
547
548         put_partitions(image + 0x1014, info->partitions, parts);
549         put_md5(image+0x04, image+0x14, *len-0x14);
550
551         return image;
552 }
553
554 /**
555    Generates the firmware image in sysupgrade format
556
557    This makes some assumptions about the provided flash and image partition tables and
558    should be generalized when TP-LINK starts building its safeloader into hardware with
559    different flash layouts.
560 */
561 static void * generate_sysupgrade_image(const struct device_info *info, const struct image_partition_entry *image_parts, size_t *len) {
562         size_t i, j;
563         size_t flash_first_partition_index = 0;
564         size_t flash_last_partition_index = 0;
565         const struct flash_partition_entry *flash_first_partition = NULL;
566         const struct flash_partition_entry *flash_last_partition = NULL;
567         const struct image_partition_entry *image_last_partition = NULL;
568
569         /** Find first and last partitions */
570         for (i = 0; info->partitions[i].name; i++) {
571                 if (!strcmp(info->partitions[i].name, info->first_sysupgrade_partition)) {
572                         flash_first_partition = &info->partitions[i];
573                         flash_first_partition_index = i;
574                 } else if (!strcmp(info->partitions[i].name, info->last_sysupgrade_partition)) {
575                         flash_last_partition = &info->partitions[i];
576                         flash_last_partition_index = i;
577                 }
578         }
579
580         assert(flash_first_partition && flash_last_partition);
581         assert(flash_first_partition_index < flash_last_partition_index);
582
583         /** Find last partition from image to calculate needed size */
584         for (i = 0; image_parts[i].name; i++) {
585                 if (!strcmp(image_parts[i].name, info->last_sysupgrade_partition)) {
586                         image_last_partition = &image_parts[i];
587                         break;
588                 }
589         }
590
591         assert(image_last_partition);
592
593         *len = flash_last_partition->base - flash_first_partition->base + image_last_partition->size;
594
595         uint8_t *image = malloc(*len);
596         if (!image)
597                 error(1, errno, "malloc");
598
599         memset(image, 0xff, *len);
600
601         for (i = flash_first_partition_index; i <= flash_last_partition_index; i++) {
602                 for (j = 0; image_parts[j].name; j++) {
603                         if (!strcmp(info->partitions[i].name, image_parts[j].name)) {
604                                 if (image_parts[j].size > info->partitions[i].size)
605                                         error(1, 0, "%s partition too big (more than %u bytes)", info->partitions[i].name, (unsigned)info->partitions[i].size);
606                                 memcpy(image + info->partitions[i].base - flash_first_partition->base, image_parts[j].data, image_parts[j].size);
607                                 break;
608                         }
609
610                         assert(image_parts[j].name);
611                 }
612         }
613
614         return image;
615 }
616
617 /** Generates an image according to a given layout and writes it to a file */
618 static void build_image(const char *output,
619                 const char *kernel_image,
620                 const char *rootfs_image,
621                 uint32_t rev,
622                 bool add_jffs2_eof,
623                 bool sysupgrade,
624                 const struct device_info *info) {
625         struct image_partition_entry parts[6] = {};
626
627         parts[0] = make_partition_table(info->partitions);
628         parts[1] = make_soft_version(rev);
629         parts[2] = make_support_list(info);
630         parts[3] = read_file("os-image", kernel_image, false);
631         parts[4] = read_file("file-system", rootfs_image, add_jffs2_eof);
632
633         size_t len;
634         void *image;
635         if (sysupgrade)
636                 image = generate_sysupgrade_image(info, parts, &len);
637         else
638                 image = generate_factory_image(info, parts, &len);
639
640         FILE *file = fopen(output, "wb");
641         if (!file)
642                 error(1, errno, "unable to open output file");
643
644         if (fwrite(image, len, 1, file) != 1)
645                 error(1, 0, "unable to write output file");
646
647         fclose(file);
648
649         free(image);
650
651         size_t i;
652         for (i = 0; parts[i].name; i++)
653                 free_image_partition(parts[i]);
654 }
655
656 /** Usage output */
657 static void usage(const char *argv0) {
658         fprintf(stderr,
659                 "Usage: %s [OPTIONS...]\n"
660                 "\n"
661                 "Options:\n"
662                 "  -B <board>      create image for the board specified with <board>\n"
663                 "  -k <file>       read kernel image from the file <file>\n"
664                 "  -r <file>       read rootfs image from the file <file>\n"
665                 "  -o <file>       write output to the file <file>\n"
666                 "  -V <rev>        sets the revision number to <rev>\n"
667                 "  -j              add jffs2 end-of-filesystem markers\n"
668                 "  -S              create sysupgrade instead of factory image\n"
669                 "  -h              show this help\n",
670                 argv0
671         );
672 };
673
674
675 static const struct device_info *find_board(const char *id)
676 {
677         struct device_info *board = NULL;
678
679         for (board = boards; board->id != NULL; board++)
680                 if (strcasecmp(id, board->id) == 0)
681                         return board;
682
683         return NULL;
684 }
685
686 int main(int argc, char *argv[]) {
687         const char *board = NULL, *kernel_image = NULL, *rootfs_image = NULL, *output = NULL;
688         bool add_jffs2_eof = false, sysupgrade = false;
689         unsigned rev = 0;
690         const struct device_info *info;
691
692         while (true) {
693                 int c;
694
695                 c = getopt(argc, argv, "B:k:r:o:V:jSh");
696                 if (c == -1)
697                         break;
698
699                 switch (c) {
700                 case 'B':
701                         board = optarg;
702                         break;
703
704                 case 'k':
705                         kernel_image = optarg;
706                         break;
707
708                 case 'r':
709                         rootfs_image = optarg;
710                         break;
711
712                 case 'o':
713                         output = optarg;
714                         break;
715
716                 case 'V':
717                         sscanf(optarg, "r%u", &rev);
718                         break;
719
720                 case 'j':
721                         add_jffs2_eof = true;
722                         break;
723
724                 case 'S':
725                         sysupgrade = true;
726                         break;
727
728                 case 'h':
729                         usage(argv[0]);
730                         return 0;
731
732                 default:
733                         usage(argv[0]);
734                         return 1;
735                 }
736         }
737
738         if (!board)
739                 error(1, 0, "no board has been specified");
740         if (!kernel_image)
741                 error(1, 0, "no kernel image has been specified");
742         if (!rootfs_image)
743                 error(1, 0, "no rootfs image has been specified");
744         if (!output)
745                 error(1, 0, "no output filename has been specified");
746
747         info = find_board(board);
748
749         if (info == NULL)
750                 error(1, 0, "unsupported board %s", board);
751
752         build_image(output, kernel_image, rootfs_image, rev, add_jffs2_eof, sysupgrade, info);
753
754         return 0;
755 }