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