]>
Commit | Line | Data |
---|---|---|
63cccae4 KZ |
1 | /* |
2 | * mkcramfs - make a cramfs file system | |
3 | * | |
19922f22 | 4 | * Copyright (C) 1999-2002 Transmeta Corporation |
63cccae4 KZ |
5 | * |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2 of the License, or | |
9 | * (at your option) any later version. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License | |
17 | * along with this program; if not, write to the Free Software | |
18 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
19 | */ | |
20 | ||
95f1bdee KZ |
21 | /* |
22 | * Old version would die on largish filesystems. Change to mmap the | |
23 | * files one by one instaed of all simultaneously. - aeb, 2002-11-01 | |
24 | */ | |
25 | ||
63cccae4 KZ |
26 | #include <sys/types.h> |
27 | #include <stdio.h> | |
28 | #include <sys/stat.h> | |
29 | #include <unistd.h> | |
30 | #include <sys/mman.h> | |
31 | #include <sys/fcntl.h> | |
32 | #include <dirent.h> | |
33 | #include <stdlib.h> | |
34 | #include <errno.h> | |
35 | #include <string.h> | |
36 | #include <assert.h> | |
37 | #include <getopt.h> | |
19922f22 | 38 | #include <stdarg.h> |
63cccae4 KZ |
39 | #include <zlib.h> |
40 | ||
41 | #include "cramfs.h" | |
95f1bdee | 42 | #include "md5.h" |
63cccae4 KZ |
43 | #include "nls.h" |
44 | ||
19922f22 KZ |
45 | /* Exit codes used by mkfs-type programs */ |
46 | #define MKFS_OK 0 /* No errors */ | |
47 | #define MKFS_ERROR 8 /* Operational error */ | |
48 | #define MKFS_USAGE 16 /* Usage or syntax error */ | |
49 | ||
50 | /* The kernel only supports PAD_SIZE of 0 and 512. */ | |
51 | #define PAD_SIZE 512 | |
63cccae4 KZ |
52 | |
53 | static const char *progname = "mkcramfs"; | |
54 | static int verbose = 0; | |
55 | ||
19922f22 | 56 | static unsigned int blksize; /* settable via -b option */ |
63cccae4 KZ |
57 | static long total_blocks = 0, total_nodes = 1; /* pre-count the root node */ |
58 | static int image_length = 0; | |
59 | ||
60 | /* | |
61 | * If opt_holes is set, then mkcramfs can create explicit holes in the | |
62 | * data, which saves 26 bytes per hole (which is a lot smaller a | |
95f1bdee | 63 | * saving than for most filesystems). |
63cccae4 KZ |
64 | * |
65 | * Note that kernels up to at least 2.3.39 don't support cramfs holes, | |
66 | * which is why this is turned off by default. | |
67 | */ | |
68 | static int opt_edition = 0; | |
69 | static int opt_errors = 0; | |
70 | static int opt_holes = 0; | |
71 | static int opt_pad = 0; | |
72 | static char *opt_image = NULL; | |
73 | static char *opt_name = NULL; | |
74 | ||
95f1bdee KZ |
75 | static int warn_dev = 0; |
76 | static int warn_gid = 0; | |
77 | static int warn_namelen = 0; | |
78 | static int warn_skip = 0; | |
79 | static int warn_size = 0; | |
80 | static int warn_uid = 0; | |
63cccae4 KZ |
81 | |
82 | #ifndef MIN | |
83 | # define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b)) | |
84 | #endif | |
85 | ||
86 | /* In-core version of inode / directory entry. */ | |
87 | struct entry { | |
88 | /* stats */ | |
19922f22 | 89 | unsigned char *name; |
63cccae4 | 90 | unsigned int mode, size, uid, gid; |
95f1bdee KZ |
91 | unsigned char md5sum[16]; |
92 | unsigned char flags; | |
93 | #define HAVE_MD5 1 | |
94 | #define INVALID 2 | |
63cccae4 KZ |
95 | |
96 | /* FS data */ | |
95f1bdee | 97 | char *path; |
19922f22 | 98 | int fd; /* temporarily open files while mmapped */ |
95f1bdee KZ |
99 | struct entry *same; /* points to other identical file */ |
100 | unsigned int offset; /* pointer to compressed data in archive */ | |
101 | unsigned int dir_offset; /* offset of directory entry in archive */ | |
63cccae4 KZ |
102 | |
103 | /* organization */ | |
95f1bdee | 104 | struct entry *child; /* NULL for non-directory and empty dir */ |
63cccae4 KZ |
105 | struct entry *next; |
106 | }; | |
107 | ||
108 | /* | |
109 | * Width of various bitfields in struct cramfs_inode. | |
110 | * Used only to generate warnings. | |
111 | */ | |
112 | #define CRAMFS_SIZE_WIDTH 24 | |
113 | #define CRAMFS_UID_WIDTH 16 | |
114 | #define CRAMFS_GID_WIDTH 8 | |
115 | #define CRAMFS_OFFSET_WIDTH 26 | |
116 | ||
95f1bdee KZ |
117 | /* Input status of 0 to print help and exit without an error. */ |
118 | static void | |
119 | usage(int status) { | |
120 | FILE *stream = status ? stderr : stdout; | |
121 | ||
122 | fprintf(stream, | |
19922f22 | 123 | _("usage: %s [-h] [-v] [-b blksize] [-e edition] [-i file] " |
b22550fa | 124 | "[-n name] dirname outfile\n" |
95f1bdee KZ |
125 | " -h print this help\n" |
126 | " -v be verbose\n" | |
127 | " -E make all warnings errors " | |
128 | "(non-zero exit status)\n" | |
19922f22 | 129 | " -b blksize use this blocksize, must equal page size\n" |
95f1bdee KZ |
130 | " -e edition set edition number (part of fsid)\n" |
131 | " -i file insert a file image into the filesystem " | |
132 | "(requires >= 2.4.0)\n" | |
133 | " -n name set name of cramfs filesystem\n" | |
134 | " -p pad by %d bytes for boot code\n" | |
135 | " -s sort directory entries (old option, ignored)\n" | |
136 | " -z make explicit holes (requires >= 2.3.39)\n" | |
137 | " dirname root of the filesystem to be compressed\n" | |
138 | " outfile output file\n"), | |
139 | progname, PAD_SIZE); | |
140 | ||
141 | exit(status); | |
142 | } | |
143 | ||
144 | /* malloc or die */ | |
145 | static void * | |
146 | xmalloc (size_t size) { | |
147 | void *t = malloc(size); | |
148 | if (t == NULL) { | |
149 | perror(NULL); | |
150 | exit(8); /* out of memory */ | |
151 | } | |
152 | return t; | |
153 | } | |
154 | ||
155 | static char * | |
156 | do_mmap(char *path, unsigned int size, unsigned int mode){ | |
157 | int fd; | |
158 | char *start; | |
159 | ||
160 | if (!size) | |
161 | return NULL; | |
162 | ||
163 | if (S_ISLNK(mode)) { | |
164 | start = xmalloc(size); | |
165 | if (readlink(path, start, size) < 0) { | |
166 | perror(path); | |
167 | warn_skip = 1; | |
168 | start = NULL; | |
169 | } | |
170 | return start; | |
171 | } | |
172 | ||
173 | fd = open(path, O_RDONLY); | |
174 | if (fd < 0) { | |
175 | perror(path); | |
176 | warn_skip = 1; | |
177 | return NULL; | |
178 | } | |
179 | ||
180 | start = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); | |
181 | if (-1 == (int) (long) start) { | |
182 | perror("mmap"); | |
183 | exit(8); | |
184 | } | |
185 | close(fd); | |
186 | ||
187 | return start; | |
188 | } | |
189 | ||
190 | static void | |
191 | do_munmap(char *start, unsigned int size, unsigned int mode){ | |
192 | if (S_ISLNK(mode)) | |
193 | free(start); | |
194 | else | |
195 | munmap(start, size); | |
196 | } | |
197 | ||
198 | /* compute md5sums, so that we do not have to compare every pair of files */ | |
199 | static void | |
200 | mdfile(struct entry *e) { | |
201 | MD5_CTX ctx; | |
202 | char *start; | |
203 | ||
204 | start = do_mmap(e->path, e->size, e->mode); | |
205 | if (start == NULL) { | |
206 | e->flags |= INVALID; | |
207 | } else { | |
208 | MD5Init(&ctx); | |
209 | MD5Update(&ctx, start, e->size); | |
210 | MD5Final(e->md5sum, &ctx); | |
211 | ||
212 | do_munmap(start, e->size, e->mode); | |
213 | ||
214 | e->flags |= HAVE_MD5; | |
215 | } | |
216 | } | |
217 | ||
218 | /* md5 digests are equal; files are almost certainly the same, | |
219 | but just to be sure, do the comparison */ | |
220 | static int | |
221 | identical_file(struct entry *e1, struct entry *e2){ | |
222 | char *start1, *start2; | |
223 | int equal; | |
224 | ||
225 | start1 = do_mmap(e1->path, e1->size, e1->mode); | |
226 | if (!start1) | |
227 | return 0; | |
228 | start2 = do_mmap(e2->path, e2->size, e2->mode); | |
229 | if (!start2) | |
230 | return 0; | |
231 | equal = !memcmp(start1, start2, e1->size); | |
232 | do_munmap(start1, e1->size, e1->mode); | |
233 | do_munmap(start2, e2->size, e2->mode); | |
234 | return equal; | |
235 | } | |
236 | ||
63cccae4 KZ |
237 | /* |
238 | * The longest file name component to allow for in the input directory tree. | |
239 | * Ext2fs (and many others) allow up to 255 bytes. A couple of filesystems | |
240 | * allow longer (e.g. smbfs 1024), but there isn't much use in supporting | |
241 | * >255-byte names in the input directory tree given that such names get | |
242 | * truncated to 255 bytes when written to cramfs. | |
243 | */ | |
244 | #define MAX_INPUT_NAMELEN 255 | |
245 | ||
95f1bdee | 246 | static int find_identical_file(struct entry *orig, struct entry *new) |
63cccae4 | 247 | { |
95f1bdee KZ |
248 | if (orig == new) |
249 | return 1; | |
250 | if (!orig) | |
251 | return 0; | |
252 | if (orig->size == new->size && orig->path) { | |
253 | if (!orig->flags) | |
254 | mdfile(orig); | |
255 | if (!new->flags) | |
256 | mdfile(new); | |
257 | ||
258 | if ((orig->flags & HAVE_MD5) && (new->flags & HAVE_MD5) && | |
259 | !memcmp(orig->md5sum, new->md5sum, 16) && | |
260 | identical_file(orig, new)) { | |
261 | new->same = orig; | |
262 | return 1; | |
263 | } | |
63cccae4 | 264 | } |
95f1bdee KZ |
265 | return find_identical_file(orig->child, new) || |
266 | find_identical_file(orig->next, new); | |
63cccae4 KZ |
267 | } |
268 | ||
95f1bdee KZ |
269 | static void eliminate_doubles(struct entry *root, struct entry *orig) { |
270 | if (orig) { | |
271 | if (orig->size && orig->path) | |
63cccae4 KZ |
272 | find_identical_file(root,orig); |
273 | eliminate_doubles(root,orig->child); | |
274 | eliminate_doubles(root,orig->next); | |
275 | } | |
276 | } | |
277 | ||
278 | /* | |
279 | * We define our own sorting function instead of using alphasort which | |
280 | * uses strcoll and changes ordering based on locale information. | |
281 | */ | |
282 | static int cramsort (const void *a, const void *b) | |
283 | { | |
284 | return strcmp ((*(const struct dirent **) a)->d_name, | |
285 | (*(const struct dirent **) b)->d_name); | |
286 | } | |
287 | ||
288 | static unsigned int parse_directory(struct entry *root_entry, const char *name, struct entry **prev, loff_t *fslen_ub) | |
289 | { | |
290 | struct dirent **dirlist; | |
291 | int totalsize = 0, dircount, dirindex; | |
292 | char *path, *endpath; | |
293 | size_t len = strlen(name); | |
294 | ||
295 | /* Set up the path. */ | |
296 | /* TODO: Reuse the parent's buffer to save memcpy'ing and duplication. */ | |
95f1bdee | 297 | path = xmalloc(len + 1 + MAX_INPUT_NAMELEN + 1); |
63cccae4 KZ |
298 | memcpy(path, name, len); |
299 | endpath = path + len; | |
300 | *endpath = '/'; | |
301 | endpath++; | |
302 | ||
303 | /* read in the directory and sort */ | |
304 | dircount = scandir(name, &dirlist, 0, cramsort); | |
305 | ||
306 | if (dircount < 0) { | |
307 | perror(name); | |
308 | exit(8); | |
309 | } | |
310 | ||
311 | /* process directory */ | |
312 | for (dirindex = 0; dirindex < dircount; dirindex++) { | |
313 | struct dirent *dirent; | |
314 | struct entry *entry; | |
315 | struct stat st; | |
316 | int size; | |
317 | size_t namelen; | |
318 | ||
319 | dirent = dirlist[dirindex]; | |
320 | ||
321 | /* Ignore "." and ".." - we won't be adding them | |
322 | to the archive */ | |
323 | if (dirent->d_name[0] == '.') { | |
324 | if (dirent->d_name[1] == '\0') | |
325 | continue; | |
326 | if (dirent->d_name[1] == '.') { | |
327 | if (dirent->d_name[2] == '\0') | |
328 | continue; | |
329 | } | |
330 | } | |
331 | namelen = strlen(dirent->d_name); | |
332 | if (namelen > MAX_INPUT_NAMELEN) { | |
333 | fprintf(stderr, | |
334 | _("Very long (%u bytes) filename `%s' found.\n" | |
335 | " Please increase MAX_INPUT_NAMELEN in " | |
336 | "mkcramfs.c and recompile. Exiting.\n"), | |
337 | namelen, dirent->d_name); | |
338 | exit(8); | |
339 | } | |
340 | memcpy(endpath, dirent->d_name, namelen + 1); | |
341 | ||
342 | if (lstat(path, &st) < 0) { | |
343 | perror(endpath); | |
344 | warn_skip = 1; | |
345 | continue; | |
346 | } | |
347 | entry = calloc(1, sizeof(struct entry)); | |
348 | if (!entry) { | |
349 | perror(NULL); | |
350 | exit(8); | |
351 | } | |
352 | entry->name = strdup(dirent->d_name); | |
353 | if (!entry->name) { | |
354 | perror(NULL); | |
355 | exit(8); | |
356 | } | |
357 | if (namelen > 255) { | |
358 | /* Can't happen when reading from ext2fs. */ | |
359 | ||
360 | /* TODO: we ought to avoid chopping in half | |
361 | multi-byte UTF8 characters. */ | |
362 | entry->name[namelen = 255] = '\0'; | |
363 | warn_namelen = 1; | |
364 | } | |
365 | entry->mode = st.st_mode; | |
366 | entry->size = st.st_size; | |
367 | entry->uid = st.st_uid; | |
368 | if (entry->uid >= 1 << CRAMFS_UID_WIDTH) | |
369 | warn_uid = 1; | |
370 | entry->gid = st.st_gid; | |
371 | if (entry->gid >= 1 << CRAMFS_GID_WIDTH) | |
372 | /* TODO: We ought to replace with a default | |
373 | gid instead of truncating; otherwise there | |
374 | are security problems. Maybe mode should | |
375 | be &= ~070. Same goes for uid once Linux | |
376 | supports >16-bit uids. */ | |
377 | warn_gid = 1; | |
378 | size = sizeof(struct cramfs_inode) + ((namelen + 3) & ~3); | |
379 | *fslen_ub += size; | |
380 | if (S_ISDIR(st.st_mode)) { | |
381 | entry->size = parse_directory(root_entry, path, &entry->child, fslen_ub); | |
382 | } else if (S_ISREG(st.st_mode)) { | |
95f1bdee | 383 | entry->path = strdup(path); |
63cccae4 | 384 | if (entry->size) { |
95f1bdee | 385 | if (entry->size >= (1 << CRAMFS_SIZE_WIDTH)) { |
63cccae4 KZ |
386 | warn_size = 1; |
387 | entry->size = (1 << CRAMFS_SIZE_WIDTH) - 1; | |
388 | } | |
63cccae4 | 389 | } |
63cccae4 | 390 | } else if (S_ISLNK(st.st_mode)) { |
95f1bdee | 391 | entry->path = strdup(path); |
63cccae4 KZ |
392 | } else if (S_ISFIFO(st.st_mode) || S_ISSOCK(st.st_mode)) { |
393 | /* maybe we should skip sockets */ | |
394 | entry->size = 0; | |
395 | } else { | |
396 | entry->size = st.st_rdev; | |
397 | if (entry->size & -(1<<CRAMFS_SIZE_WIDTH)) | |
398 | warn_dev = 1; | |
399 | } | |
400 | ||
401 | if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) { | |
402 | int blocks = ((entry->size - 1) / blksize + 1); | |
403 | ||
404 | /* block pointers & data expansion allowance + data */ | |
95f1bdee | 405 | if (entry->size) |
63cccae4 KZ |
406 | *fslen_ub += (4+26)*blocks + entry->size + 3; |
407 | } | |
408 | ||
409 | /* Link it into the list */ | |
410 | *prev = entry; | |
411 | prev = &entry->next; | |
412 | totalsize += size; | |
413 | } | |
414 | free(path); | |
415 | free(dirlist); /* allocated by scandir() with malloc() */ | |
416 | return totalsize; | |
417 | } | |
418 | ||
419 | /* Returns sizeof(struct cramfs_super), which includes the root inode. */ | |
420 | static unsigned int write_superblock(struct entry *root, char *base, int size) | |
421 | { | |
422 | struct cramfs_super *super = (struct cramfs_super *) base; | |
423 | unsigned int offset = sizeof(struct cramfs_super) + image_length; | |
424 | ||
425 | if (opt_pad) { | |
426 | offset += opt_pad; | |
427 | } | |
428 | ||
429 | super->magic = CRAMFS_MAGIC; | |
430 | super->flags = CRAMFS_FLAG_FSID_VERSION_2 | CRAMFS_FLAG_SORTED_DIRS; | |
431 | if (opt_holes) | |
432 | super->flags |= CRAMFS_FLAG_HOLES; | |
433 | if (image_length > 0) | |
434 | super->flags |= CRAMFS_FLAG_SHIFTED_ROOT_OFFSET; | |
435 | super->size = size; | |
436 | memcpy(super->signature, CRAMFS_SIGNATURE, sizeof(super->signature)); | |
437 | ||
438 | super->fsid.crc = crc32(0L, Z_NULL, 0); | |
439 | super->fsid.edition = opt_edition; | |
440 | super->fsid.blocks = total_blocks; | |
441 | super->fsid.files = total_nodes; | |
442 | ||
443 | memset(super->name, 0x00, sizeof(super->name)); | |
444 | if (opt_name) | |
445 | strncpy(super->name, opt_name, sizeof(super->name)); | |
446 | else | |
447 | strncpy(super->name, "Compressed", sizeof(super->name)); | |
448 | ||
449 | super->root.mode = root->mode; | |
450 | super->root.uid = root->uid; | |
451 | super->root.gid = root->gid; | |
452 | super->root.size = root->size; | |
453 | super->root.offset = offset >> 2; | |
454 | ||
455 | return offset; | |
456 | } | |
457 | ||
458 | static void set_data_offset(struct entry *entry, char *base, unsigned long offset) | |
459 | { | |
460 | struct cramfs_inode *inode = (struct cramfs_inode *) (base + entry->dir_offset); | |
461 | if (offset >= (1 << (2 + CRAMFS_OFFSET_WIDTH))) { | |
462 | fprintf(stderr, _("filesystem too big. Exiting.\n")); | |
463 | exit(8); | |
464 | } | |
465 | inode->offset = (offset >> 2); | |
466 | } | |
467 | ||
468 | ||
469 | /* | |
470 | * We do a width-first printout of the directory | |
471 | * entries, using a stack to remember the directories | |
472 | * we've seen. | |
473 | */ | |
474 | #define MAXENTRIES (100) | |
475 | static unsigned int write_directory_structure(struct entry *entry, char *base, unsigned int offset) | |
476 | { | |
477 | int stack_entries = 0; | |
478 | struct entry *entry_stack[MAXENTRIES]; | |
479 | ||
480 | for (;;) { | |
481 | int dir_start = stack_entries; | |
482 | while (entry) { | |
483 | struct cramfs_inode *inode = | |
484 | (struct cramfs_inode *) (base + offset); | |
485 | size_t len = strlen(entry->name); | |
486 | ||
487 | entry->dir_offset = offset; | |
488 | ||
489 | inode->mode = entry->mode; | |
490 | inode->uid = entry->uid; | |
491 | inode->gid = entry->gid; | |
492 | inode->size = entry->size; | |
493 | inode->offset = 0; | |
494 | /* Non-empty directories, regfiles and symlinks will | |
495 | write over inode->offset later. */ | |
496 | ||
497 | offset += sizeof(struct cramfs_inode); | |
498 | total_nodes++; /* another node */ | |
499 | memcpy(base + offset, entry->name, len); | |
500 | /* Pad up the name to a 4-byte boundary */ | |
501 | while (len & 3) { | |
502 | *(base + offset + len) = '\0'; | |
503 | len++; | |
504 | } | |
505 | inode->namelen = len >> 2; | |
506 | offset += len; | |
507 | ||
508 | if (verbose) | |
509 | printf(" %s\n", entry->name); | |
510 | if (entry->child) { | |
511 | if (stack_entries >= MAXENTRIES) { | |
512 | fprintf(stderr, | |
513 | _("Exceeded MAXENTRIES. Raise" | |
514 | " this value in mkcramfs.c " | |
515 | "and recompile. Exiting.\n") | |
516 | ); | |
517 | exit(8); | |
518 | } | |
519 | entry_stack[stack_entries] = entry; | |
520 | stack_entries++; | |
521 | } | |
522 | entry = entry->next; | |
523 | } | |
524 | ||
525 | /* | |
526 | * Reverse the order the stack entries pushed during | |
527 | * this directory, for a small optimization of disk | |
528 | * access in the created fs. This change makes things | |
529 | * `ls -UR' order. | |
530 | */ | |
531 | { | |
532 | struct entry **lo = entry_stack + dir_start; | |
533 | struct entry **hi = entry_stack + stack_entries; | |
534 | struct entry *tmp; | |
535 | ||
536 | while (lo < --hi) { | |
537 | tmp = *lo; | |
538 | *lo++ = *hi; | |
539 | *hi = tmp; | |
540 | } | |
541 | } | |
542 | ||
543 | /* Pop a subdirectory entry from the stack, and recurse. */ | |
544 | if (!stack_entries) | |
545 | break; | |
546 | stack_entries--; | |
547 | entry = entry_stack[stack_entries]; | |
548 | ||
549 | set_data_offset(entry, base, offset); | |
550 | if (verbose) | |
551 | printf("'%s':\n", entry->name); | |
552 | entry = entry->child; | |
553 | } | |
554 | return offset; | |
555 | } | |
556 | ||
557 | static int is_zero(char const *begin, unsigned len) | |
558 | { | |
559 | if (opt_holes) | |
560 | /* Returns non-zero iff the first LEN bytes from BEGIN are | |
561 | all NULs. */ | |
562 | return (len-- == 0 || | |
563 | (begin[0] == '\0' && | |
564 | (len-- == 0 || | |
565 | (begin[1] == '\0' && | |
566 | (len-- == 0 || | |
567 | (begin[2] == '\0' && | |
568 | (len-- == 0 || | |
569 | (begin[3] == '\0' && | |
570 | memcmp(begin, begin + 4, len) == 0)))))))); | |
571 | else | |
572 | /* Never create holes. */ | |
573 | return 0; | |
574 | } | |
575 | ||
576 | /* | |
577 | * One 4-byte pointer per block and then the actual blocked | |
578 | * output. The first block does not need an offset pointer, | |
579 | * as it will start immediately after the pointer block; | |
580 | * so the i'th pointer points to the end of the i'th block | |
581 | * (i.e. the start of the (i+1)'th block or past EOF). | |
582 | * | |
583 | * Note that size > 0, as a zero-sized file wouldn't ever | |
584 | * have gotten here in the first place. | |
585 | */ | |
95f1bdee KZ |
586 | static unsigned int |
587 | do_compress(char *base, unsigned int offset, char const *name, | |
588 | char *path, unsigned int size, unsigned int mode) | |
63cccae4 | 589 | { |
95f1bdee | 590 | unsigned long original_size, original_offset, new_size, blocks, curr; |
63cccae4 | 591 | int change; |
95f1bdee KZ |
592 | char *p, *start; |
593 | ||
594 | /* get uncompressed data */ | |
595 | start = do_mmap(path, size, mode); | |
596 | if (start == NULL) | |
597 | return offset; | |
598 | p = start; | |
599 | ||
600 | original_size = size; | |
601 | original_offset = offset; | |
602 | blocks = (size - 1) / blksize + 1; | |
603 | curr = offset + 4 * blocks; | |
63cccae4 KZ |
604 | |
605 | total_blocks += blocks; | |
606 | ||
607 | do { | |
608 | unsigned long len = 2 * blksize; | |
609 | unsigned int input = size; | |
610 | if (input > blksize) | |
611 | input = blksize; | |
612 | size -= input; | |
95f1bdee KZ |
613 | if (!is_zero (p, input)) { |
614 | compress(base + curr, &len, p, input); | |
63cccae4 KZ |
615 | curr += len; |
616 | } | |
95f1bdee | 617 | p += input; |
63cccae4 KZ |
618 | |
619 | if (len > blksize*2) { | |
620 | /* (I don't think this can happen with zlib.) */ | |
621 | printf(_("AIEEE: block \"compressed\" to > " | |
622 | "2*blocklength (%ld)\n"), | |
623 | len); | |
624 | exit(8); | |
625 | } | |
626 | ||
627 | *(u32 *) (base + offset) = curr; | |
628 | offset += 4; | |
629 | } while (size); | |
630 | ||
95f1bdee KZ |
631 | do_munmap(start, original_size, mode); |
632 | ||
63cccae4 KZ |
633 | curr = (curr + 3) & ~3; |
634 | new_size = curr - original_offset; | |
635 | /* TODO: Arguably, original_size in these 2 lines should be | |
95f1bdee | 636 | st_blocks * 512. But if you say that, then perhaps |
63cccae4 KZ |
637 | administrative data should also be included in both. */ |
638 | change = new_size - original_size; | |
639 | if (verbose) | |
640 | printf(_("%6.2f%% (%+d bytes)\t%s\n"), | |
641 | (change * 100) / (double) original_size, change, name); | |
642 | ||
643 | return curr; | |
644 | } | |
645 | ||
646 | ||
647 | /* | |
648 | * Traverse the entry tree, writing data for every item that has | |
95f1bdee | 649 | * non-null entry->path (i.e. every symlink and non-empty |
63cccae4 KZ |
650 | * regfile). |
651 | */ | |
95f1bdee KZ |
652 | static unsigned int |
653 | write_data(struct entry *entry, char *base, unsigned int offset) { | |
654 | struct entry *e; | |
655 | ||
656 | for (e = entry; e; e = e->next) { | |
657 | if (e->path) { | |
658 | if (e->same) { | |
659 | set_data_offset(e, base, e->same->offset); | |
660 | e->offset = e->same->offset; | |
63cccae4 | 661 | } else { |
95f1bdee KZ |
662 | set_data_offset(e, base, offset); |
663 | e->offset = offset; | |
664 | offset = do_compress(base, offset, e->name, | |
665 | e->path, e->size,e->mode); | |
63cccae4 | 666 | } |
95f1bdee KZ |
667 | } else if (e->child) |
668 | offset = write_data(e->child, base, offset); | |
669 | } | |
63cccae4 KZ |
670 | return offset; |
671 | } | |
672 | ||
673 | static unsigned int write_file(char *file, char *base, unsigned int offset) | |
674 | { | |
675 | int fd; | |
676 | char *buf; | |
677 | ||
678 | fd = open(file, O_RDONLY); | |
679 | if (fd < 0) { | |
680 | perror(file); | |
681 | exit(8); | |
682 | } | |
683 | buf = mmap(NULL, image_length, PROT_READ, MAP_PRIVATE, fd, 0); | |
684 | memcpy(base + offset, buf, image_length); | |
685 | munmap(buf, image_length); | |
686 | close (fd); | |
687 | /* Pad up the image_length to a 4-byte boundary */ | |
688 | while (image_length & 3) { | |
689 | *(base + offset + image_length) = '\0'; | |
690 | image_length++; | |
691 | } | |
692 | return (offset + image_length); | |
693 | } | |
694 | ||
695 | /* | |
696 | * Maximum size fs you can create is roughly 256MB. (The last file's | |
697 | * data must begin within 256MB boundary but can extend beyond that.) | |
698 | * | |
699 | * Note that if you want it to fit in a ROM then you're limited to what the | |
700 | * hardware and kernel can support (64MB?). | |
701 | */ | |
95f1bdee KZ |
702 | static unsigned int |
703 | maxfslen(void) { | |
704 | return (((1 << CRAMFS_OFFSET_WIDTH) - 1) << 2) /* offset */ | |
705 | + (1 << CRAMFS_SIZE_WIDTH) - 1 /* filesize */ | |
706 | + (1 << CRAMFS_SIZE_WIDTH) * 4 / blksize; /* block pointers */ | |
707 | } | |
63cccae4 KZ |
708 | |
709 | /* | |
710 | * Usage: | |
711 | * | |
712 | * mkcramfs directory-name outfile | |
713 | * | |
714 | * where "directory-name" is simply the root of the directory | |
715 | * tree that we want to generate a compressed filesystem out | |
716 | * of. | |
717 | */ | |
718 | int main(int argc, char **argv) | |
719 | { | |
720 | struct stat st; /* used twice... */ | |
721 | struct entry *root_entry; | |
722 | char *rom_image; | |
723 | ssize_t offset, written; | |
724 | int fd; | |
725 | /* initial guess (upper-bound) of required filesystem size */ | |
726 | loff_t fslen_ub = sizeof(struct cramfs_super); | |
95f1bdee | 727 | unsigned int fslen_max; |
63cccae4 KZ |
728 | char const *dirname, *outfile; |
729 | u32 crc = crc32(0L, Z_NULL, 0); | |
95f1bdee | 730 | int c; |
63cccae4 | 731 | |
19922f22 | 732 | blksize = sysconf(_SC_PAGESIZE); |
63cccae4 KZ |
733 | total_blocks = 0; |
734 | ||
735 | if (argc) { | |
736 | char *p; | |
737 | progname = argv[0]; | |
738 | if ((p = strrchr(progname, '/')) != NULL) | |
739 | progname = p+1; | |
740 | } | |
741 | ||
742 | /* command line options */ | |
95f1bdee | 743 | while ((c = getopt(argc, argv, "hb:Ee:i:n:psVvz")) != EOF) { |
63cccae4 KZ |
744 | switch (c) { |
745 | case 'h': | |
746 | usage(0); | |
95f1bdee KZ |
747 | case 'b': |
748 | blksize = atoi(optarg); | |
749 | if (blksize <= 0) | |
750 | usage(1); | |
751 | break; | |
63cccae4 KZ |
752 | case 'E': |
753 | opt_errors = 1; | |
754 | break; | |
755 | case 'e': | |
756 | opt_edition = atoi(optarg); | |
757 | break; | |
758 | case 'i': | |
759 | opt_image = optarg; | |
760 | if (lstat(opt_image, &st) < 0) { | |
761 | perror(opt_image); | |
762 | exit(16); | |
763 | } | |
764 | image_length = st.st_size; /* may be padded later */ | |
765 | fslen_ub += (image_length + 3); /* 3 is for padding */ | |
766 | break; | |
767 | case 'n': | |
768 | opt_name = optarg; | |
769 | break; | |
770 | case 'p': | |
771 | opt_pad = PAD_SIZE; | |
772 | fslen_ub += PAD_SIZE; | |
773 | break; | |
774 | case 's': | |
775 | /* old option, ignored */ | |
776 | break; | |
777 | case 'V': | |
baf39af1 KZ |
778 | printf(_("%s (%s)\n"), |
779 | progname, PACKAGE_STRING); | |
63cccae4 KZ |
780 | exit(0); |
781 | case 'v': | |
782 | verbose = 1; | |
783 | break; | |
784 | case 'z': | |
785 | opt_holes = 1; | |
786 | break; | |
787 | } | |
788 | } | |
789 | ||
790 | if ((argc - optind) != 2) | |
791 | usage(16); | |
792 | dirname = argv[optind]; | |
793 | outfile = argv[optind + 1]; | |
794 | ||
795 | if (stat(dirname, &st) < 0) { | |
796 | perror(dirname); | |
797 | exit(16); | |
798 | } | |
799 | fd = open(outfile, O_WRONLY | O_CREAT | O_TRUNC, 0666); | |
800 | ||
801 | root_entry = calloc(1, sizeof(struct entry)); | |
802 | if (!root_entry) { | |
803 | perror(NULL); | |
804 | exit(8); | |
805 | } | |
806 | root_entry->mode = st.st_mode; | |
807 | root_entry->uid = st.st_uid; | |
808 | root_entry->gid = st.st_gid; | |
809 | ||
810 | root_entry->size = parse_directory(root_entry, dirname, &root_entry->child, &fslen_ub); | |
811 | ||
812 | /* always allocate a multiple of blksize bytes because that's | |
813 | what we're going to write later on */ | |
814 | fslen_ub = ((fslen_ub - 1) | (blksize - 1)) + 1; | |
95f1bdee | 815 | fslen_max = maxfslen(); |
63cccae4 | 816 | |
95f1bdee | 817 | if (fslen_ub > fslen_max) { |
63cccae4 KZ |
818 | fprintf(stderr, |
819 | _("warning: guestimate of required size (upper bound) " | |
820 | "is %LdMB, but maximum image size is %uMB. " | |
821 | "We might die prematurely.\n"), | |
822 | fslen_ub >> 20, | |
95f1bdee KZ |
823 | fslen_max >> 20); |
824 | fslen_ub = fslen_max; | |
63cccae4 KZ |
825 | } |
826 | ||
95f1bdee | 827 | /* find duplicate files */ |
63cccae4 KZ |
828 | eliminate_doubles(root_entry,root_entry); |
829 | ||
830 | /* TODO: Why do we use a private/anonymous mapping here | |
831 | followed by a write below, instead of just a shared mapping | |
832 | and a couple of ftruncate calls? Is it just to save us | |
833 | having to deal with removing the file afterwards? If we | |
834 | really need this huge anonymous mapping, we ought to mmap | |
835 | in smaller chunks, so that the user doesn't need nn MB of | |
836 | RAM free. If the reason is to be able to write to | |
837 | un-mmappable block devices, then we could try shared mmap | |
838 | and revert to anonymous mmap if the shared mmap fails. */ | |
839 | rom_image = mmap(NULL, | |
840 | fslen_ub?fslen_ub:1, | |
841 | PROT_READ | PROT_WRITE, | |
842 | MAP_PRIVATE | MAP_ANONYMOUS, | |
843 | -1, 0); | |
844 | ||
845 | if (-1 == (int) (long) rom_image) { | |
846 | perror("ROM image map"); | |
847 | exit(8); | |
848 | } | |
849 | ||
850 | /* Skip the first opt_pad bytes for boot loader code */ | |
851 | offset = opt_pad; | |
852 | memset(rom_image, 0x00, opt_pad); | |
853 | ||
854 | /* Skip the superblock and come back to write it later. */ | |
855 | offset += sizeof(struct cramfs_super); | |
856 | ||
857 | /* Insert a file image. */ | |
858 | if (opt_image) { | |
859 | if (verbose) | |
860 | printf(_("Including: %s\n"), opt_image); | |
861 | offset = write_file(opt_image, rom_image, offset); | |
862 | } | |
863 | ||
864 | offset = write_directory_structure(root_entry->child, rom_image, offset); | |
865 | if (verbose) | |
866 | printf(_("Directory data: %d bytes\n"), offset); | |
867 | ||
868 | offset = write_data(root_entry, rom_image, offset); | |
869 | ||
870 | /* We always write a multiple of blksize bytes, so that | |
871 | losetup works. */ | |
872 | offset = ((offset - 1) | (blksize - 1)) + 1; | |
873 | if (verbose) | |
874 | printf(_("Everything: %d kilobytes\n"), offset >> 10); | |
875 | ||
876 | /* Write the superblock now that we can fill in all of the fields. */ | |
877 | write_superblock(root_entry, rom_image+opt_pad, offset); | |
878 | if (verbose) | |
879 | printf(_("Super block: %d bytes\n"), | |
880 | sizeof(struct cramfs_super)); | |
881 | ||
882 | /* Put the checksum in. */ | |
883 | crc = crc32(crc, (rom_image+opt_pad), (offset-opt_pad)); | |
884 | ((struct cramfs_super *) (rom_image+opt_pad))->fsid.crc = crc; | |
885 | if (verbose) | |
886 | printf(_("CRC: %x\n"), crc); | |
887 | ||
888 | /* Check to make sure we allocated enough space. */ | |
889 | if (fslen_ub < offset) { | |
890 | fprintf(stderr, | |
891 | _("not enough space allocated for ROM image " | |
892 | "(%Ld allocated, %d used)\n"), | |
893 | fslen_ub, offset); | |
894 | exit(8); | |
895 | } | |
896 | ||
897 | written = write(fd, rom_image, offset); | |
898 | if (written < 0) { | |
899 | perror("ROM image"); | |
900 | exit(8); | |
901 | } | |
902 | if (offset != written) { | |
903 | fprintf(stderr, _("ROM image write failed (%d %d)\n"), | |
904 | written, offset); | |
905 | exit(8); | |
906 | } | |
907 | ||
908 | /* (These warnings used to come at the start, but they scroll off the | |
909 | screen too quickly.) */ | |
910 | if (warn_namelen) /* (can't happen when reading from ext2fs) */ | |
911 | fprintf(stderr, /* bytes, not chars: think UTF8. */ | |
912 | _("warning: filenames truncated to 255 bytes.\n")); | |
913 | if (warn_skip) | |
914 | fprintf(stderr, | |
915 | _("warning: files were skipped due to errors.\n")); | |
916 | if (warn_size) | |
917 | fprintf(stderr, | |
918 | _("warning: file sizes truncated to %luMB " | |
919 | "(minus 1 byte).\n"), | |
920 | 1L << (CRAMFS_SIZE_WIDTH - 20)); | |
921 | if (warn_uid) /* (not possible with current Linux versions) */ | |
922 | fprintf(stderr, | |
923 | _("warning: uids truncated to %u bits. " | |
924 | "(This may be a security concern.)\n"), | |
925 | CRAMFS_UID_WIDTH); | |
926 | if (warn_gid) | |
927 | fprintf(stderr, | |
928 | _("warning: gids truncated to %u bits. " | |
929 | "(This may be a security concern.)\n"), | |
930 | CRAMFS_GID_WIDTH); | |
931 | if (warn_dev) | |
932 | fprintf(stderr, | |
933 | _("WARNING: device numbers truncated to %u bits. " | |
934 | "This almost certainly means\n" | |
935 | "that some device files will be wrong.\n"), | |
936 | CRAMFS_OFFSET_WIDTH); | |
937 | if (opt_errors && | |
938 | (warn_namelen|warn_skip|warn_size|warn_uid|warn_gid|warn_dev)) | |
939 | exit(8); | |
940 | return 0; | |
941 | } |