tplink-safeloader: refactor image ingestion
[project/firmware-utils.git] / src / mkh3cvfs.c
1 // SPDX-License-Identifier: GPL-2.0-only
2
3 #include <stdio.h>
4 #include <string.h>
5 #include <stdlib.h>
6 #include <stdint.h>
7 #include <sys/types.h>
8 #include <unistd.h>
9 #include <byteswap.h>
10 #include <endian.h>
11 #include <getopt.h>
12
13
14 #if !defined(__BYTE_ORDER)
15 #error "Unknown byte order"
16 #endif
17
18 #if __BYTE_ORDER == __BIG_ENDIAN
19 #define cpu_to_be16(x) (x)
20 #define cpu_to_be32(x) (x)
21 #define be16_to_cpu(x) (x)
22 #define be32_to_cpu(x) (x)
23 #elif __BYTE_ORDER == __LITTLE_ENDIAN
24 #define cpu_to_be16(x) bswap_16(x)
25 #define cpu_to_be32(x) bswap_32(x)
26 #define be16_to_cpu(x) bswap_16(x)
27 #define be32_to_cpu(x) bswap_32(x)
28 #else
29 #error "Unsupported endianness"
30 #endif
31
32
33 #define FAT_PTR_FLAGS_GET(x) ((be32_to_cpu(x) & 0xff000000) >> 24)
34 #define FAT_PTR_FLAGS_SET(x, y) (x = cpu_to_be32((be32_to_cpu(x) & 0x00ffffff) | ((y & 0x000000ff) << 24)))
35
36 #define FAT_PTR_VAL_GET(x) (be32_to_cpu(x) & 0x00ffffff)
37 #define FAT_PTR_VAL_SET(x, y) (x = cpu_to_be32((be32_to_cpu(x) & 0xff000000) | (y & 0x00ffffff)))
38
39
40 struct fat_entry {
41 /* first byte contains flags */
42 uint32_t previous;
43
44 /* first byte is reserved */
45 uint32_t next;
46 } __attribute__ ((packed));
47
48 struct file_entry {
49 uint8_t flags;
50
51 uint8_t res0[5];
52
53 uint16_t year;
54 uint8_t month;
55 uint8_t day;
56 uint8_t hour;
57 uint8_t minute;
58 uint8_t second;
59
60 uint8_t res1[3];
61
62 uint32_t length;
63
64 uint32_t parent_block;
65 uint16_t parent_index;
66
67 uint8_t res2[2];
68
69 uint32_t data_block;
70
71 char name[96];
72 } __attribute__ ((packed));
73
74
75 #define ERASEBLOCK_SIZE 0x10000
76 #define BLOCK_SIZE 0x400
77
78 #define BLOCKS_PER_ERASEBLOCK (ERASEBLOCK_SIZE / BLOCK_SIZE)
79 #define FILE_ENTRIES_PER_BLOCK (BLOCK_SIZE / sizeof(struct file_entry))
80
81
82 #define FLAG_FREE 0x80
83 #define FLAG_VALID 0x40
84 #define FLAG_INVALID 0x20
85 #define FLAG_HIDE 0x10
86 #define FLAG_DIRECTORY 0x02
87 #define FLAG_READONLY 0x01
88
89
90 static FILE *f;
91 static size_t file_size = 0;
92
93 static int dir_block = 1;
94 static int dir_count = 0;
95
96 static int next_data_block = 2;
97
98
99 static inline size_t fat_entry_offset(int block) {
100 return ERASEBLOCK_SIZE * (block / (BLOCKS_PER_ERASEBLOCK-1))
101 + sizeof(struct fat_entry) * (block % (BLOCKS_PER_ERASEBLOCK-1));
102 }
103
104 static inline size_t block_offset(int block) {
105 return ERASEBLOCK_SIZE * (block / (BLOCKS_PER_ERASEBLOCK-1))
106 + BLOCK_SIZE * (1 + (block % (BLOCKS_PER_ERASEBLOCK-1)));
107 }
108
109 static int init_eraseblock(size_t offset) {
110 size_t end = offset - (offset % ERASEBLOCK_SIZE) + ERASEBLOCK_SIZE;
111 char *fill = "\xff";
112 int i;
113
114 while (file_size < end) {
115 if (fseek(f, file_size, SEEK_SET)) {
116 fprintf(stderr, "failed to seek to end\n");
117 return -1;
118 }
119
120 for (i = 0; i < ERASEBLOCK_SIZE; i++) {
121 if (fwrite(fill, 1, 1, f) != 1) {
122 fprintf(stderr, "failed to write eraseblock\n");
123 return -1;
124 }
125 }
126
127 file_size += ERASEBLOCK_SIZE;
128 }
129
130 return 0;
131 }
132
133 static inline void init_fat_entry(struct fat_entry *out) {
134 memset(out, '\xff', sizeof(struct fat_entry));
135 }
136
137 static int read_fat_entry(struct fat_entry *out, int block) {
138 size_t offset = fat_entry_offset(block);
139
140 if (init_eraseblock(offset)) {
141 return -1;
142 }
143
144 if (fseek(f, offset, SEEK_SET)) {
145 fprintf(stderr, "failed to seek to fat entry\n");
146 return -1;
147 }
148
149 if (fread(out, sizeof(struct fat_entry), 1, f) != 1) {
150 fprintf(stderr, "failed to read fat entry\n");
151 return -1;
152 }
153
154 return 0;
155 }
156
157 static int write_fat_entry(struct fat_entry *in, int block) {
158 size_t offset = fat_entry_offset(block);
159
160 if (init_eraseblock(offset)) {
161 return -1;
162 }
163
164 if (fseek(f, offset, SEEK_SET)) {
165 fprintf(stderr, "failed to seek to fat entry\n");
166 return -1;
167 }
168
169 if (fwrite(in, sizeof(struct fat_entry), 1, f) != 1) {
170 fprintf(stderr, "failed to write fat entry\n");
171 return -1;
172 }
173
174 return 0;
175 }
176
177 static inline void init_file_entry(struct file_entry *out) {
178 memset(out, '\xff', sizeof(struct file_entry));
179 }
180
181 static int write_file_entry(struct file_entry *in, int block, int index) {
182 size_t offset = block_offset(block) + sizeof(struct file_entry) * index;
183
184 if (init_eraseblock(offset)) {
185 return -1;
186 }
187
188 if (fseek(f, offset, SEEK_SET)) {
189 fprintf(stderr, "failed to seek to file entry\n");
190 return -1;
191 }
192
193 if (fwrite(in, sizeof(struct file_entry), 1, f) != 1) {
194 fprintf(stderr, "failed to write file entry\n");
195 return -1;
196 }
197
198 return 0;
199 }
200
201 static int write_block(void *in, size_t len, int block) {
202 size_t offset = block_offset(block);
203
204 if (init_eraseblock(offset)) {
205 return -1;
206 }
207
208 if (fseek(f, offset, SEEK_SET)) {
209 fprintf(stderr, "failed to seek to block\n");
210 return -1;
211 }
212
213 if (fwrite(in, len, 1, f) != 1) {
214 fprintf(stderr, "failed to write block\n");
215 return -1;
216 }
217
218 return 0;
219 }
220
221 static int create_root_directory() {
222 struct fat_entry fat;
223 struct file_entry file;
224
225 /* write format flag / FAT entry for block 0 (contains root file entry) */
226 init_fat_entry(&fat);
227 fat.previous = cpu_to_be32((ERASEBLOCK_SIZE << 12) | BLOCK_SIZE);
228 if (write_fat_entry(&fat, 0)) {
229 return -1;
230 }
231
232 /* write root file entry */
233 init_file_entry(&file);
234 file.flags = ~(FLAG_FREE | FLAG_VALID) & 0xff;
235 file.parent_block = cpu_to_be32(0);
236 file.data_block = cpu_to_be32(1);
237 if (write_file_entry(&file, 0, 0)) {
238 return -1;
239 }
240
241 /* write FAT entry for block 1 (contains first file entries of root directory) */
242 init_fat_entry(&fat);
243 FAT_PTR_FLAGS_SET(fat.previous, ~(FLAG_FREE | FLAG_VALID));
244 if (write_fat_entry(&fat, 1)) {
245 return -1;
246 }
247
248 return 0;
249 }
250
251 static int write_file(char *name, char *path) {
252 int ret = -1;
253 struct fat_entry fat;
254 struct file_entry file;
255 FILE *fin;
256 char buf[BLOCK_SIZE];
257 size_t len;
258 size_t total = 0;
259 int first_data_block = next_data_block;
260 int data_block = 0;
261
262 fin = fopen(path, "r");
263 if (fin == NULL) {
264 fprintf(stderr, "failed to open input file\n");
265 return ret;
266 }
267
268 while ((len = fread(buf, 1, BLOCK_SIZE, fin)) != 0 || !data_block) {
269 total += len;
270
271 /* update next pointer of previous FAT entry */
272 if (data_block) {
273 if (read_fat_entry(&fat, data_block)) {
274 goto err;
275 }
276 FAT_PTR_VAL_SET(fat.next, next_data_block);
277 if (write_fat_entry(&fat, data_block)) {
278 goto err;
279 }
280 }
281
282 /* write FAT entry for new block */
283 init_fat_entry(&fat);
284 FAT_PTR_FLAGS_SET(fat.previous, ~(FLAG_FREE | FLAG_VALID));
285 if (data_block) {
286 FAT_PTR_VAL_SET(fat.previous, data_block);
287 }
288 if (write_fat_entry(&fat, next_data_block)) {
289 goto err;
290 }
291
292 /* write data block */
293 if (write_block(buf, len, next_data_block)) {
294 goto err;
295 }
296
297 data_block = next_data_block;
298 next_data_block++;
299 }
300
301 /* create new file entries block if necessary */
302 if (dir_count == FILE_ENTRIES_PER_BLOCK) {
303 /* update next pointer of previous FAT entry */
304 if (read_fat_entry(&fat, dir_block)) {
305 goto err;
306 }
307 FAT_PTR_VAL_SET(fat.next, next_data_block);
308 if (write_fat_entry(&fat, dir_block)) {
309 goto err;
310 }
311
312 /* write FAT entry for new block */
313 init_fat_entry(&fat);
314 FAT_PTR_FLAGS_SET(fat.previous, ~(FLAG_FREE | FLAG_VALID));
315 FAT_PTR_VAL_SET(fat.previous, dir_block);
316 if (write_fat_entry(&fat, next_data_block)) {
317 goto err;
318 }
319
320 dir_block = next_data_block;
321 dir_count = 0;
322 next_data_block++;
323 }
324
325 /* write file entry */
326 init_file_entry(&file);
327
328 file.flags = ~(FLAG_FREE | FLAG_VALID) & 0xff;
329
330 file.year = cpu_to_be16(1970);
331 file.month = 1;
332 file.day = 1;
333 file.hour = 0;
334 file.minute = 0;
335 file.second = 0;
336
337 file.length = cpu_to_be32(total);
338
339 file.parent_block = cpu_to_be32(0);
340 file.parent_index = cpu_to_be16(0);
341
342 file.data_block = cpu_to_be32(first_data_block);
343
344 snprintf(file.name, sizeof(file.name), "%s", name);
345
346 if (write_file_entry(&file, dir_block, dir_count)) {
347 goto err;
348 }
349
350 dir_count++;
351
352 ret = 0;
353 err:
354 fclose(fin);
355 return ret;
356 }
357
358 static void usage(char* argv[]) {
359 printf("Usage: %s [OPTIONS...]\n"
360 "\n"
361 "Options:\n"
362 " -f <filename> filename in image\n"
363 " -i <file> input filename\n"
364 " -o <file> output filename\n"
365 , argv[0]);
366 }
367
368 int main(int argc, char* argv[]) {
369 int ret = EXIT_FAILURE;
370
371 static char *filename = NULL;
372 static char *input_filename = NULL;
373 static char *output_filename = NULL;
374
375 while ( 1 ) {
376 int c;
377
378 c = getopt(argc, argv, "f:i:o:");
379 if (c == -1)
380 break;
381
382 switch (c) {
383 case 'f':
384 filename = optarg;
385 break;
386 case 'i':
387 input_filename = optarg;
388 break;
389 case 'o':
390 output_filename = optarg;
391 break;
392 default:
393 usage(argv);
394 goto err;
395 }
396 }
397
398 if (!filename || strlen(filename) == 0 ||
399 !input_filename || strlen(input_filename) == 0 ||
400 !output_filename || strlen(output_filename) == 0) {
401
402 usage(argv);
403 goto err;
404 }
405
406 f = fopen(output_filename, "w+");
407 if (f == NULL) {
408 fprintf(stderr, "failed to open output file\n");
409 goto err;
410 }
411
412 if (create_root_directory()) {
413 goto err_close;
414 }
415
416 if (write_file(filename, input_filename)) {
417 goto err_close;
418 }
419
420 ret = EXIT_SUCCESS;
421
422 err_close:
423 fclose(f);
424 err:
425 return ret;
426 }