]>
Commit | Line | Data |
---|---|---|
dd875c76 WD |
1 | /* |
2 | * cramfs.c | |
3 | * | |
4 | * Copyright (C) 1999 Linus Torvalds | |
5 | * | |
6 | * Copyright (C) 2000-2002 Transmeta Corporation | |
7 | * | |
8 | * Copyright (C) 2003 Kai-Uwe Bloem, | |
9 | * Auerswald GmbH & Co KG, <[email protected]> | |
10 | * - adapted from the www.tuxbox.org u-boot tree, added "ls" command | |
11 | * | |
12 | * This program is free software; you can redistribute it and/or modify | |
13 | * it under the terms of the GNU General Public License (Version 2) as | |
14 | * published by the Free Software Foundation. | |
15 | * | |
16 | * Compressed ROM filesystem for Linux. | |
17 | * | |
18 | * TODO: | |
19 | * add support for resolving symbolic links | |
20 | */ | |
21 | ||
22 | /* | |
23 | * These are the VFS interfaces to the compressed ROM filesystem. | |
24 | * The actual compression is based on zlib, see the other files. | |
25 | */ | |
26 | ||
27 | #include <common.h> | |
28 | #include <malloc.h> | |
dd875c76 WD |
29 | #include <asm/byteorder.h> |
30 | #include <linux/stat.h> | |
180d3f74 | 31 | #include <jffs2/jffs2.h> |
dd875c76 | 32 | #include <jffs2/load_kernel.h> |
180d3f74 | 33 | #include <cramfs/cramfs_fs.h> |
dd875c76 WD |
34 | |
35 | /* These two macros may change in future, to provide better st_ino | |
36 | semantics. */ | |
37 | #define CRAMINO(x) (CRAMFS_GET_OFFSET(x) ? CRAMFS_GET_OFFSET(x)<<2 : 1) | |
38 | #define OFFSET(x) ((x)->i_ino) | |
39 | ||
40 | struct cramfs_super super; | |
41 | ||
700a0c64 WD |
42 | /* CPU address space offset calculation macro, struct part_info offset is |
43 | * device address space offset, so we need to shift it by a device start address. */ | |
e856bdcf | 44 | #if defined(CONFIG_MTD_NOR_FLASH) |
e6f2e902 | 45 | extern flash_info_t flash_info[]; |
06503f16 SW |
46 | #define PART_OFFSET(x) ((ulong)x->offset + \ |
47 | flash_info[x->dev->id->num].start[0]) | |
62a813bc | 48 | #else |
06503f16 | 49 | #define PART_OFFSET(x) ((ulong)x->offset) |
62a813bc | 50 | #endif |
700a0c64 | 51 | |
d39a0d2c TH |
52 | static int cramfs_uncompress (unsigned long begin, unsigned long offset, |
53 | unsigned long loadoffset); | |
54 | ||
dd875c76 WD |
55 | static int cramfs_read_super (struct part_info *info) |
56 | { | |
57 | unsigned long root_offset; | |
58 | ||
59 | /* Read the first block and get the superblock from it */ | |
700a0c64 | 60 | memcpy (&super, (void *) PART_OFFSET(info), sizeof (super)); |
dd875c76 WD |
61 | |
62 | /* Do sanity checks on the superblock */ | |
63 | if (super.magic != CRAMFS_32 (CRAMFS_MAGIC)) { | |
64 | /* check at 512 byte offset */ | |
700a0c64 | 65 | memcpy (&super, (void *) PART_OFFSET(info) + 512, sizeof (super)); |
dd875c76 WD |
66 | if (super.magic != CRAMFS_32 (CRAMFS_MAGIC)) { |
67 | printf ("cramfs: wrong magic\n"); | |
68 | return -1; | |
69 | } | |
70 | } | |
71 | ||
72 | /* flags is reused several times, so swab it once */ | |
73 | super.flags = CRAMFS_32 (super.flags); | |
74 | super.size = CRAMFS_32 (super.size); | |
75 | ||
76 | /* get feature flags first */ | |
77 | if (super.flags & ~CRAMFS_SUPPORTED_FLAGS) { | |
78 | printf ("cramfs: unsupported filesystem features\n"); | |
79 | return -1; | |
80 | } | |
81 | ||
82 | /* Check that the root inode is in a sane state */ | |
83 | if (!S_ISDIR (CRAMFS_16 (super.root.mode))) { | |
84 | printf ("cramfs: root is not a directory\n"); | |
85 | return -1; | |
86 | } | |
87 | root_offset = CRAMFS_GET_OFFSET (&(super.root)) << 2; | |
88 | if (root_offset == 0) { | |
89 | printf ("cramfs: empty filesystem"); | |
90 | } else if (!(super.flags & CRAMFS_FLAG_SHIFTED_ROOT_OFFSET) && | |
91 | ((root_offset != sizeof (struct cramfs_super)) && | |
92 | (root_offset != 512 + sizeof (struct cramfs_super)))) { | |
93 | printf ("cramfs: bad root offset %lu\n", root_offset); | |
94 | return -1; | |
95 | } | |
96 | ||
97 | return 0; | |
98 | } | |
99 | ||
d39a0d2c TH |
100 | /* Unpack to an allocated buffer, trusting in the inode's size field. */ |
101 | static char *cramfs_uncompress_link (unsigned long begin, unsigned long offset) | |
102 | { | |
103 | struct cramfs_inode *inode = (struct cramfs_inode *)(begin + offset); | |
104 | unsigned long size = CRAMFS_24 (inode->size); | |
105 | char *link = malloc (size + 1); | |
106 | ||
107 | if (!link || cramfs_uncompress (begin, offset, (unsigned long)link) != size) { | |
108 | free (link); | |
109 | link = NULL; | |
110 | } else { | |
111 | link[size] = '\0'; | |
112 | } | |
113 | return link; | |
114 | } | |
115 | ||
700a0c64 | 116 | static unsigned long cramfs_resolve (unsigned long begin, unsigned long offset, |
dd875c76 WD |
117 | unsigned long size, int raw, |
118 | char *filename) | |
119 | { | |
120 | unsigned long inodeoffset = 0, nextoffset; | |
121 | ||
122 | while (inodeoffset < size) { | |
123 | struct cramfs_inode *inode; | |
124 | char *name; | |
125 | int namelen; | |
126 | ||
127 | inode = (struct cramfs_inode *) (begin + offset + | |
128 | inodeoffset); | |
129 | ||
130 | /* | |
131 | * Namelengths on disk are shifted by two | |
132 | * and the name padded out to 4-byte boundaries | |
133 | * with zeroes. | |
134 | */ | |
135 | namelen = CRAMFS_GET_NAMELEN (inode) << 2; | |
136 | name = (char *) inode + sizeof (struct cramfs_inode); | |
137 | ||
138 | nextoffset = | |
139 | inodeoffset + sizeof (struct cramfs_inode) + namelen; | |
140 | ||
141 | for (;;) { | |
142 | if (!namelen) | |
143 | return -1; | |
144 | if (name[namelen - 1]) | |
145 | break; | |
146 | namelen--; | |
147 | } | |
148 | ||
457dd025 HB |
149 | if (!strncmp(filename, name, namelen) && |
150 | (namelen == strlen(filename))) { | |
dd875c76 WD |
151 | char *p = strtok (NULL, "/"); |
152 | ||
153 | if (raw && (p == NULL || *p == '\0')) | |
154 | return offset + inodeoffset; | |
155 | ||
156 | if (S_ISDIR (CRAMFS_16 (inode->mode))) { | |
157 | return cramfs_resolve (begin, | |
158 | CRAMFS_GET_OFFSET | |
159 | (inode) << 2, | |
160 | CRAMFS_24 (inode-> | |
161 | size), raw, | |
162 | p); | |
163 | } else if (S_ISREG (CRAMFS_16 (inode->mode))) { | |
164 | return offset + inodeoffset; | |
d39a0d2c TH |
165 | } else if (S_ISLNK (CRAMFS_16 (inode->mode))) { |
166 | unsigned long ret; | |
167 | char *link; | |
168 | if (p && strlen(p)) { | |
169 | printf ("unsupported symlink to \ | |
170 | non-terminal path\n"); | |
171 | return 0; | |
172 | } | |
173 | link = cramfs_uncompress_link (begin, | |
174 | offset + inodeoffset); | |
175 | if (!link) { | |
176 | printf ("%*.*s: Error reading link\n", | |
177 | namelen, namelen, name); | |
178 | return 0; | |
179 | } else if (link[0] == '/') { | |
180 | printf ("unsupported symlink to \ | |
181 | absolute path\n"); | |
182 | free (link); | |
183 | return 0; | |
184 | } | |
185 | ret = cramfs_resolve (begin, | |
186 | offset, | |
187 | size, | |
188 | raw, | |
189 | strtok(link, "/")); | |
190 | free (link); | |
191 | return ret; | |
dd875c76 WD |
192 | } else { |
193 | printf ("%*.*s: unsupported file type (%x)\n", | |
194 | namelen, namelen, name, | |
195 | CRAMFS_16 (inode->mode)); | |
196 | return 0; | |
197 | } | |
198 | } | |
199 | ||
200 | inodeoffset = nextoffset; | |
201 | } | |
202 | ||
203 | printf ("can't find corresponding entry\n"); | |
204 | return 0; | |
205 | } | |
206 | ||
700a0c64 | 207 | static int cramfs_uncompress (unsigned long begin, unsigned long offset, |
dd875c76 WD |
208 | unsigned long loadoffset) |
209 | { | |
210 | struct cramfs_inode *inode = (struct cramfs_inode *) (begin + offset); | |
a6ea791c | 211 | u32 *block_ptrs = (u32 *) |
dd875c76 WD |
212 | (begin + (CRAMFS_GET_OFFSET (inode) << 2)); |
213 | unsigned long curr_block = (CRAMFS_GET_OFFSET (inode) + | |
214 | (((CRAMFS_24 (inode->size)) + | |
215 | 4095) >> 12)) << 2; | |
216 | int size, total_size = 0; | |
217 | int i; | |
218 | ||
219 | cramfs_uncompress_init (); | |
220 | ||
221 | for (i = 0; i < ((CRAMFS_24 (inode->size) + 4095) >> 12); i++) { | |
222 | size = cramfs_uncompress_block ((void *) loadoffset, | |
223 | (void *) (begin + curr_block), | |
224 | (CRAMFS_32 (block_ptrs[i]) - | |
225 | curr_block)); | |
226 | if (size < 0) | |
227 | return size; | |
228 | loadoffset += size; | |
229 | total_size += size; | |
230 | curr_block = CRAMFS_32 (block_ptrs[i]); | |
231 | } | |
232 | ||
233 | cramfs_uncompress_exit (); | |
234 | return total_size; | |
235 | } | |
236 | ||
237 | int cramfs_load (char *loadoffset, struct part_info *info, char *filename) | |
238 | { | |
239 | unsigned long offset; | |
240 | ||
241 | if (cramfs_read_super (info)) | |
242 | return -1; | |
243 | ||
700a0c64 | 244 | offset = cramfs_resolve (PART_OFFSET(info), |
dd875c76 WD |
245 | CRAMFS_GET_OFFSET (&(super.root)) << 2, |
246 | CRAMFS_24 (super.root.size), 0, | |
247 | strtok (filename, "/")); | |
248 | ||
249 | if (offset <= 0) | |
250 | return offset; | |
251 | ||
700a0c64 | 252 | return cramfs_uncompress (PART_OFFSET(info), offset, |
dd875c76 WD |
253 | (unsigned long) loadoffset); |
254 | } | |
255 | ||
dd875c76 WD |
256 | static int cramfs_list_inode (struct part_info *info, unsigned long offset) |
257 | { | |
258 | struct cramfs_inode *inode = (struct cramfs_inode *) | |
700a0c64 | 259 | (PART_OFFSET(info) + offset); |
dd875c76 WD |
260 | char *name, str[20]; |
261 | int namelen, nextoff; | |
262 | ||
263 | /* | |
264 | * Namelengths on disk are shifted by two | |
265 | * and the name padded out to 4-byte boundaries | |
266 | * with zeroes. | |
267 | */ | |
268 | namelen = CRAMFS_GET_NAMELEN (inode) << 2; | |
269 | name = (char *) inode + sizeof (struct cramfs_inode); | |
270 | nextoff = namelen; | |
271 | ||
272 | for (;;) { | |
273 | if (!namelen) | |
274 | return namelen; | |
275 | if (name[namelen - 1]) | |
276 | break; | |
277 | namelen--; | |
278 | } | |
279 | ||
280 | printf (" %s %8d %*.*s", mkmodestr (CRAMFS_16 (inode->mode), str), | |
281 | CRAMFS_24 (inode->size), namelen, namelen, name); | |
282 | ||
283 | if ((CRAMFS_16 (inode->mode) & S_IFMT) == S_IFLNK) { | |
d39a0d2c TH |
284 | char *link = cramfs_uncompress_link (PART_OFFSET(info), offset); |
285 | if (link) | |
286 | printf (" -> %s\n", link); | |
dd875c76 WD |
287 | else |
288 | printf (" [Error reading link]\n"); | |
d39a0d2c | 289 | free (link); |
dd875c76 WD |
290 | } else |
291 | printf ("\n"); | |
292 | ||
293 | return nextoff; | |
294 | } | |
295 | ||
296 | int cramfs_ls (struct part_info *info, char *filename) | |
297 | { | |
298 | struct cramfs_inode *inode; | |
299 | unsigned long inodeoffset = 0, nextoffset; | |
300 | unsigned long offset, size; | |
301 | ||
302 | if (cramfs_read_super (info)) | |
303 | return -1; | |
304 | ||
305 | if (strlen (filename) == 0 || !strcmp (filename, "/")) { | |
306 | /* Root directory. Use root inode in super block */ | |
307 | offset = CRAMFS_GET_OFFSET (&(super.root)) << 2; | |
308 | size = CRAMFS_24 (super.root.size); | |
309 | } else { | |
310 | /* Resolve the path */ | |
700a0c64 | 311 | offset = cramfs_resolve (PART_OFFSET(info), |
dd875c76 WD |
312 | CRAMFS_GET_OFFSET (&(super.root)) << |
313 | 2, CRAMFS_24 (super.root.size), 1, | |
314 | strtok (filename, "/")); | |
315 | ||
316 | if (offset <= 0) | |
317 | return offset; | |
318 | ||
319 | /* Resolving was successful. Examine the inode */ | |
700a0c64 | 320 | inode = (struct cramfs_inode *) (PART_OFFSET(info) + offset); |
dd875c76 WD |
321 | if (!S_ISDIR (CRAMFS_16 (inode->mode))) { |
322 | /* It's not a directory - list it, and that's that */ | |
323 | return (cramfs_list_inode (info, offset) > 0); | |
324 | } | |
325 | ||
326 | /* It's a directory. List files within */ | |
327 | offset = CRAMFS_GET_OFFSET (inode) << 2; | |
328 | size = CRAMFS_24 (inode->size); | |
329 | } | |
330 | ||
331 | /* List the given directory */ | |
332 | while (inodeoffset < size) { | |
700a0c64 | 333 | inode = (struct cramfs_inode *) (PART_OFFSET(info) + offset + |
dd875c76 WD |
334 | inodeoffset); |
335 | ||
336 | nextoffset = cramfs_list_inode (info, offset + inodeoffset); | |
337 | if (nextoffset == 0) | |
338 | break; | |
339 | inodeoffset += sizeof (struct cramfs_inode) + nextoffset; | |
340 | } | |
341 | ||
342 | return 1; | |
343 | } | |
344 | ||
345 | int cramfs_info (struct part_info *info) | |
346 | { | |
347 | if (cramfs_read_super (info)) | |
348 | return 0; | |
349 | ||
350 | printf ("size: 0x%x (%u)\n", super.size, super.size); | |
351 | ||
352 | if (super.flags != 0) { | |
353 | printf ("flags:\n"); | |
354 | if (super.flags & CRAMFS_FLAG_FSID_VERSION_2) | |
355 | printf ("\tFSID version 2\n"); | |
356 | if (super.flags & CRAMFS_FLAG_SORTED_DIRS) | |
357 | printf ("\tsorted dirs\n"); | |
358 | if (super.flags & CRAMFS_FLAG_HOLES) | |
359 | printf ("\tholes\n"); | |
360 | if (super.flags & CRAMFS_FLAG_SHIFTED_ROOT_OFFSET) | |
361 | printf ("\tshifted root offset\n"); | |
362 | } | |
363 | ||
364 | printf ("fsid:\n\tcrc: 0x%x\n\tedition: 0x%x\n", | |
365 | super.fsid.crc, super.fsid.edition); | |
366 | printf ("name: %16s\n", super.name); | |
367 | ||
368 | return 1; | |
369 | } | |
370 | ||
371 | int cramfs_check (struct part_info *info) | |
372 | { | |
700a0c64 WD |
373 | struct cramfs_super *sb; |
374 | ||
375 | if (info->dev->id->type != MTD_DEV_TYPE_NOR) | |
376 | return 0; | |
dd875c76 | 377 | |
700a0c64 | 378 | sb = (struct cramfs_super *) PART_OFFSET(info); |
dd875c76 WD |
379 | if (sb->magic != CRAMFS_32 (CRAMFS_MAGIC)) { |
380 | /* check at 512 byte offset */ | |
700a0c64 WD |
381 | sb = (struct cramfs_super *) (PART_OFFSET(info) + 512); |
382 | if (sb->magic != CRAMFS_32 (CRAMFS_MAGIC)) | |
dd875c76 | 383 | return 0; |
dd875c76 WD |
384 | } |
385 | return 1; | |
386 | } |