mkrasimage.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  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. #include <sys/mman.h>
  46. #include <sys/stat.h>
  47. #include <arpa/inet.h>
  48. #define VERSION_STRING_LEN 31
  49. #define ROOTFS_HEADER_LEN 40
  50. #define KERNEL_HEADER_LEN 8
  51. #define BOARD_NAME_LEN 64
  52. #define BOARD_HEADER_LEN 68
  53. #define HEADER_PARTITION_CALC_LENGTH 2048
  54. #define HEADER_PARTITION_LENGTH 0x10000
  55. struct file_info {
  56. char *name; /* name of the file */
  57. char *data; /* file content */
  58. size_t size; /* length of the file */
  59. };
  60. static char *progname;
  61. static char *board_name = 0;
  62. static char *version_name = 0;
  63. static unsigned int rootfs_size = 0;
  64. static struct file_info kernel = { NULL, NULL, 0 };
  65. static struct file_info rootfs = { NULL, NULL, 0 };
  66. static struct file_info rootfs_out = { NULL, NULL, 0 };
  67. static struct file_info out = { NULL, NULL, 0 };
  68. #define ERR(fmt, ...) do { \
  69. fprintf(stderr, "[%s] *** error: " fmt "\n", \
  70. progname, ## __VA_ARGS__ ); \
  71. } while (0)
  72. void map_file(struct file_info *finfo)
  73. {
  74. struct stat file_stat = {0};
  75. int fd;
  76. fd = open(finfo->name, O_RDONLY, (mode_t)0600);
  77. if (fd == -1) {
  78. ERR("Error while opening file %s.", finfo->name);
  79. exit(EXIT_FAILURE);
  80. }
  81. if (fstat(fd, &file_stat) == -1) {
  82. ERR("Error getting file size for %s.", finfo->name);
  83. exit(EXIT_FAILURE);
  84. }
  85. finfo->size = file_stat.st_size;
  86. finfo->data = mmap(0, finfo->size, PROT_READ, MAP_SHARED, fd, 0);
  87. if (finfo->data == MAP_FAILED) {
  88. ERR("Error mapping file %s.", finfo->name);
  89. exit(EXIT_FAILURE);
  90. }
  91. close(fd);
  92. }
  93. void unmap_file(struct file_info *finfo)
  94. {
  95. if(munmap(finfo->data, finfo->size) == -1) {
  96. ERR("Error unmapping file %s.", finfo->name);
  97. exit(EXIT_FAILURE);
  98. }
  99. }
  100. void write_file(struct file_info *finfo)
  101. {
  102. FILE *fout = fopen(finfo->name, "w");
  103. fwrite(finfo->data, finfo->size, 1, fout);
  104. if (ferror(fout)) {
  105. ERR("Wanted to write, but something went wrong.");
  106. exit(EXIT_FAILURE);
  107. }
  108. fclose(fout);
  109. }
  110. void usage(int status)
  111. {
  112. FILE *stream = (status != EXIT_SUCCESS) ? stderr : stdout;
  113. fprintf(stream, "Usage: %s [OPTIONS...]\n", progname);
  114. fprintf(stream,
  115. "\n"
  116. "Options:\n"
  117. " -k <kernel> path for kernel image\n"
  118. " -r <rootfs> path for rootfs image\n"
  119. " -s <rfssize> size of output rootfs\n"
  120. " -v <version> version string\n"
  121. " -b <boardname> name of board to generate image for\n"
  122. " -o <out_name> name of output image\n"
  123. " -h show this screen\n"
  124. );
  125. exit(status);
  126. }
  127. static int sysv_chksm(const unsigned char *data, int size)
  128. {
  129. int r;
  130. int checksum;
  131. unsigned int s = 0; /* The sum of all the input bytes, modulo (UINT_MAX + 1). */
  132. for (int i = 0; i < size; i++) {
  133. s += data[i];
  134. }
  135. r = (s & 0xffff) + ((s & 0xffffffff) >> 16);
  136. checksum = (r & 0xffff) + (r >> 16);
  137. return checksum;
  138. }
  139. static int zyxel_chksm(const unsigned char *data, int size)
  140. {
  141. return htonl(sysv_chksm(data, size));
  142. }
  143. char *generate_rootfs_header(struct file_info filesystem, char *version)
  144. {
  145. size_t version_string_length;
  146. unsigned int chksm, size;
  147. char *rootfs_header;
  148. size_t ptr = 0;
  149. rootfs_header = malloc(ROOTFS_HEADER_LEN);
  150. if (!rootfs_header) {
  151. ERR("Couldn't allocate memory for rootfs header!");
  152. exit(EXIT_FAILURE);
  153. }
  154. /* Prepare padding for firmware-version string here */
  155. memset(rootfs_header, 0xff, ROOTFS_HEADER_LEN);
  156. chksm = zyxel_chksm((const unsigned char *)filesystem.data, filesystem.size);
  157. size = htonl(filesystem.size);
  158. /* 4 bytes: checksum of the rootfs image */
  159. memcpy(rootfs_header + ptr, &chksm, 4);
  160. ptr += 4;
  161. /* 4 bytes: length of the contained rootfs image file (big endian) */
  162. memcpy(rootfs_header + ptr, &size, 4);
  163. ptr += 4;
  164. /* 32 bytes: Firmware Version string (NUL terminated, 0xff padded) */
  165. version_string_length = strlen(version) <= VERSION_STRING_LEN ? strlen(version) : VERSION_STRING_LEN;
  166. memcpy(rootfs_header + ptr, version, version_string_length);
  167. ptr += version_string_length;
  168. /* Add null-terminator */
  169. rootfs_header[ptr] = 0x0;
  170. return rootfs_header;
  171. }
  172. char *generate_kernel_header(struct file_info kernel)
  173. {
  174. unsigned int chksm, size;
  175. char *kernel_header;
  176. size_t ptr = 0;
  177. kernel_header = malloc(KERNEL_HEADER_LEN);
  178. if (!kernel_header) {
  179. ERR("Couldn't allocate memory for kernel header!");
  180. exit(EXIT_FAILURE);
  181. }
  182. chksm = zyxel_chksm((const unsigned char *)kernel.data, kernel.size);
  183. size = htonl(kernel.size);
  184. /* 4 bytes: checksum of the kernel image */
  185. memcpy(kernel_header + ptr, &chksm, 4);
  186. ptr += 4;
  187. /* 4 bytes: length of the contained kernel image file (big endian) */
  188. memcpy(kernel_header + ptr, &size, 4);
  189. return kernel_header;
  190. }
  191. unsigned int generate_board_header_checksum(char *kernel_hdr, char *rootfs_hdr, char *boardname)
  192. {
  193. char *board_hdr_tmp;
  194. unsigned int sum;
  195. size_t ptr = 0;
  196. /*
  197. * The checksum of the board header is calculated over the first 2048 bytes of
  198. * the header partition with the rootfs checksum used as a placeholder for then
  199. * board checksum we calculate in this step. The checksum gained from this step
  200. * is then used for the final board header partition.
  201. */
  202. board_hdr_tmp = malloc(HEADER_PARTITION_CALC_LENGTH);
  203. if (!board_hdr_tmp) {
  204. ERR("Couldn't allocate memory for temporary board header!");
  205. exit(EXIT_FAILURE);
  206. }
  207. memset(board_hdr_tmp, 0xff, HEADER_PARTITION_CALC_LENGTH);
  208. /* 40 bytes: RootFS header */
  209. memcpy(board_hdr_tmp, rootfs_hdr, ROOTFS_HEADER_LEN);
  210. ptr += ROOTFS_HEADER_LEN;
  211. /* 4 bytes: RootFS checksum (BE) as placeholder for board-header checksum */
  212. memcpy(board_hdr_tmp + ptr, rootfs_hdr, 4);
  213. ptr += 4;
  214. /* 32 bytes: Model (e.g. "NBG6617", NUL termiated, 0xff padded) */
  215. memcpy(board_hdr_tmp + ptr, boardname, strlen(boardname));
  216. ptr += strlen(boardname);
  217. /* Add null-terminator */
  218. board_hdr_tmp[ptr] = 0x0;
  219. ptr = ROOTFS_HEADER_LEN + 4 + BOARD_NAME_LEN;
  220. /* 8 bytes: Kernel header */
  221. if (kernel_hdr)
  222. memcpy(board_hdr_tmp + ptr, kernel_hdr, 8);
  223. /* Calculate the checksum over the first 2048 bytes */
  224. sum = zyxel_chksm((const unsigned char *)board_hdr_tmp, HEADER_PARTITION_CALC_LENGTH);
  225. free(board_hdr_tmp);
  226. return sum;
  227. }
  228. char *generate_board_header(char *kernel_hdr, char *rootfs_hdr, char *boardname)
  229. {
  230. unsigned int board_checksum;
  231. char *board_hdr;
  232. board_hdr = malloc(BOARD_HEADER_LEN);
  233. if (!board_hdr) {
  234. ERR("Couldn't allocate memory for board header!");
  235. exit(EXIT_FAILURE);
  236. }
  237. memset(board_hdr, 0xff, BOARD_HEADER_LEN);
  238. /* 4 bytes: checksum over the header partition (big endian) */
  239. board_checksum = generate_board_header_checksum(kernel_hdr, rootfs_hdr, boardname);
  240. memcpy(board_hdr, &board_checksum, 4);
  241. /* 32 bytes: Model (e.g. "NBG6617", NUL termiated, 0xff padded) */
  242. memcpy(board_hdr + 4, boardname, strlen(boardname));
  243. board_hdr[4 + strlen(boardname)] = 0x0;
  244. return board_hdr;
  245. }
  246. int build_image()
  247. {
  248. char *rootfs_header = NULL;
  249. char *kernel_header = NULL;
  250. char *board_header = NULL;
  251. size_t ptr;
  252. /* Load files */
  253. if (kernel.name)
  254. map_file(&kernel);
  255. map_file(&rootfs);
  256. /*
  257. * Allocate memory and copy input rootfs for temporary output rootfs.
  258. * This is important as we have to generate the rootfs checksum over the
  259. * entire rootfs partition. As we might have to pad the partition to allow
  260. * for flashing via ZyXEL's Web-GUI, we prepare the rootfs partition for the
  261. * output image here (and also use it for calculating the rootfs checksum).
  262. *
  263. * The roofs padding has to be done with 0x00.
  264. */
  265. rootfs_out.data = calloc(rootfs_out.size, sizeof(char));
  266. memcpy(rootfs_out.data, rootfs.data, rootfs.size);
  267. /* Prepare headers */
  268. rootfs_header = generate_rootfs_header(rootfs_out, version_name);
  269. if (kernel.name)
  270. kernel_header = generate_kernel_header(kernel);
  271. board_header = generate_board_header(kernel_header, rootfs_header, board_name);
  272. /* Prepare output file */
  273. out.size = HEADER_PARTITION_LENGTH + rootfs_out.size;
  274. if (kernel.name)
  275. out.size += kernel.size;
  276. out.data = malloc(out.size);
  277. memset(out.data, 0xFF, out.size);
  278. /* Build output image */
  279. memcpy(out.data, rootfs_header, ROOTFS_HEADER_LEN);
  280. memcpy(out.data + ROOTFS_HEADER_LEN, board_header, BOARD_HEADER_LEN);
  281. if (kernel.name)
  282. memcpy(out.data + ROOTFS_HEADER_LEN + BOARD_HEADER_LEN, kernel_header, KERNEL_HEADER_LEN);
  283. ptr = HEADER_PARTITION_LENGTH;
  284. memcpy(out.data + ptr, rootfs_out.data, rootfs_out.size);
  285. ptr += rootfs_out.size;
  286. if (kernel.name)
  287. memcpy(out.data + ptr, kernel.data, kernel.size);
  288. /* Write back output image */
  289. write_file(&out);
  290. /* Free allocated memory */
  291. if (kernel.name)
  292. unmap_file(&kernel);
  293. unmap_file(&rootfs);
  294. free(out.data);
  295. free(rootfs_out.data);
  296. free(rootfs_header);
  297. if (kernel.name)
  298. free(kernel_header);
  299. free(board_header);
  300. return 0;
  301. }
  302. int check_options()
  303. {
  304. if (!rootfs.name) {
  305. ERR("No rootfs filename supplied");
  306. return -2;
  307. }
  308. if (!out.name) {
  309. ERR("No output filename supplied");
  310. return -3;
  311. }
  312. if (!board_name) {
  313. ERR("No board-name supplied");
  314. return -4;
  315. }
  316. if (!version_name) {
  317. ERR("No version supplied");
  318. return -5;
  319. }
  320. if (rootfs_size <= 0) {
  321. ERR("Invalid rootfs size supplied");
  322. return -6;
  323. }
  324. if (strlen(board_name) > 31) {
  325. ERR("Board name is to long");
  326. return -7;
  327. }
  328. return 0;
  329. }
  330. int main(int argc, char *argv[])
  331. {
  332. int ret;
  333. progname = basename(argv[0]);
  334. while (1) {
  335. int c;
  336. c = getopt(argc, argv, "b:k:o:r:s:v:h");
  337. if (c == -1)
  338. break;
  339. switch (c) {
  340. case 'b':
  341. board_name = optarg;
  342. break;
  343. case 'h':
  344. usage(EXIT_SUCCESS);
  345. break;
  346. case 'k':
  347. kernel.name = optarg;
  348. break;
  349. case 'o':
  350. out.name = optarg;
  351. break;
  352. case 'r':
  353. rootfs.name = optarg;
  354. break;
  355. case 's':
  356. sscanf(optarg, "%u", &rootfs_size);
  357. break;
  358. case 'v':
  359. version_name = optarg;
  360. break;
  361. default:
  362. usage(EXIT_FAILURE);
  363. break;
  364. }
  365. }
  366. ret = check_options();
  367. if (ret)
  368. usage(EXIT_FAILURE);
  369. /* As ZyXEL Web-GUI only accept images with a rootfs equal or larger than the first firmware shipped
  370. * for the device, we need to pad rootfs partition to this size. To perform further calculations, we
  371. * decide the size of this part here. In case the rootfs we want to integrate in our image is larger,
  372. * take it's size, otherwise the supplied size.
  373. *
  374. * Be careful! We rely on assertion of correct size to be performed beforehand. It is unknown if images
  375. * with a to large rootfs are accepted or not.
  376. */
  377. rootfs_out.size = rootfs_size < rootfs.size ? rootfs.size : rootfs_size;
  378. return build_image();
  379. }