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