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