]>
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, <linux-development@auerswald.de> | |
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> | |
29 | ||
dd60d122 | 30 | #if defined(CONFIG_CMD_JFFS2) |
dd875c76 WD |
31 | |
32 | #include <asm/byteorder.h> | |
33 | #include <linux/stat.h> | |
180d3f74 | 34 | #include <jffs2/jffs2.h> |
dd875c76 | 35 | #include <jffs2/load_kernel.h> |
180d3f74 | 36 | #include <cramfs/cramfs_fs.h> |
dd875c76 WD |
37 | |
38 | /* These two macros may change in future, to provide better st_ino | |
39 | semantics. */ | |
40 | #define CRAMINO(x) (CRAMFS_GET_OFFSET(x) ? CRAMFS_GET_OFFSET(x)<<2 : 1) | |
41 | #define OFFSET(x) ((x)->i_ino) | |
42 | ||
43 | struct cramfs_super super; | |
44 | ||
700a0c64 WD |
45 | /* CPU address space offset calculation macro, struct part_info offset is |
46 | * device address space offset, so we need to shift it by a device start address. */ | |
e6f2e902 | 47 | extern flash_info_t flash_info[]; |
700a0c64 WD |
48 | #define PART_OFFSET(x) (x->offset + flash_info[x->dev->id->num].start[0]) |
49 | ||
dd875c76 WD |
50 | static int cramfs_read_super (struct part_info *info) |
51 | { | |
52 | unsigned long root_offset; | |
53 | ||
54 | /* Read the first block and get the superblock from it */ | |
700a0c64 | 55 | memcpy (&super, (void *) PART_OFFSET(info), sizeof (super)); |
dd875c76 WD |
56 | |
57 | /* Do sanity checks on the superblock */ | |
58 | if (super.magic != CRAMFS_32 (CRAMFS_MAGIC)) { | |
59 | /* check at 512 byte offset */ | |
700a0c64 | 60 | memcpy (&super, (void *) PART_OFFSET(info) + 512, sizeof (super)); |
dd875c76 WD |
61 | if (super.magic != CRAMFS_32 (CRAMFS_MAGIC)) { |
62 | printf ("cramfs: wrong magic\n"); | |
63 | return -1; | |
64 | } | |
65 | } | |
66 | ||
67 | /* flags is reused several times, so swab it once */ | |
68 | super.flags = CRAMFS_32 (super.flags); | |
69 | super.size = CRAMFS_32 (super.size); | |
70 | ||
71 | /* get feature flags first */ | |
72 | if (super.flags & ~CRAMFS_SUPPORTED_FLAGS) { | |
73 | printf ("cramfs: unsupported filesystem features\n"); | |
74 | return -1; | |
75 | } | |
76 | ||
77 | /* Check that the root inode is in a sane state */ | |
78 | if (!S_ISDIR (CRAMFS_16 (super.root.mode))) { | |
79 | printf ("cramfs: root is not a directory\n"); | |
80 | return -1; | |
81 | } | |
82 | root_offset = CRAMFS_GET_OFFSET (&(super.root)) << 2; | |
83 | if (root_offset == 0) { | |
84 | printf ("cramfs: empty filesystem"); | |
85 | } else if (!(super.flags & CRAMFS_FLAG_SHIFTED_ROOT_OFFSET) && | |
86 | ((root_offset != sizeof (struct cramfs_super)) && | |
87 | (root_offset != 512 + sizeof (struct cramfs_super)))) { | |
88 | printf ("cramfs: bad root offset %lu\n", root_offset); | |
89 | return -1; | |
90 | } | |
91 | ||
92 | return 0; | |
93 | } | |
94 | ||
700a0c64 | 95 | static unsigned long cramfs_resolve (unsigned long begin, unsigned long offset, |
dd875c76 WD |
96 | unsigned long size, int raw, |
97 | char *filename) | |
98 | { | |
99 | unsigned long inodeoffset = 0, nextoffset; | |
100 | ||
101 | while (inodeoffset < size) { | |
102 | struct cramfs_inode *inode; | |
103 | char *name; | |
104 | int namelen; | |
105 | ||
106 | inode = (struct cramfs_inode *) (begin + offset + | |
107 | inodeoffset); | |
108 | ||
109 | /* | |
110 | * Namelengths on disk are shifted by two | |
111 | * and the name padded out to 4-byte boundaries | |
112 | * with zeroes. | |
113 | */ | |
114 | namelen = CRAMFS_GET_NAMELEN (inode) << 2; | |
115 | name = (char *) inode + sizeof (struct cramfs_inode); | |
116 | ||
117 | nextoffset = | |
118 | inodeoffset + sizeof (struct cramfs_inode) + namelen; | |
119 | ||
120 | for (;;) { | |
121 | if (!namelen) | |
122 | return -1; | |
123 | if (name[namelen - 1]) | |
124 | break; | |
125 | namelen--; | |
126 | } | |
127 | ||
128 | if (!strncmp (filename, name, namelen)) { | |
129 | char *p = strtok (NULL, "/"); | |
130 | ||
131 | if (raw && (p == NULL || *p == '\0')) | |
132 | return offset + inodeoffset; | |
133 | ||
134 | if (S_ISDIR (CRAMFS_16 (inode->mode))) { | |
135 | return cramfs_resolve (begin, | |
136 | CRAMFS_GET_OFFSET | |
137 | (inode) << 2, | |
138 | CRAMFS_24 (inode-> | |
139 | size), raw, | |
140 | p); | |
141 | } else if (S_ISREG (CRAMFS_16 (inode->mode))) { | |
142 | return offset + inodeoffset; | |
143 | } else { | |
144 | printf ("%*.*s: unsupported file type (%x)\n", | |
145 | namelen, namelen, name, | |
146 | CRAMFS_16 (inode->mode)); | |
147 | return 0; | |
148 | } | |
149 | } | |
150 | ||
151 | inodeoffset = nextoffset; | |
152 | } | |
153 | ||
154 | printf ("can't find corresponding entry\n"); | |
155 | return 0; | |
156 | } | |
157 | ||
700a0c64 | 158 | static int cramfs_uncompress (unsigned long begin, unsigned long offset, |
dd875c76 WD |
159 | unsigned long loadoffset) |
160 | { | |
161 | struct cramfs_inode *inode = (struct cramfs_inode *) (begin + offset); | |
162 | unsigned long *block_ptrs = (unsigned long *) | |
163 | (begin + (CRAMFS_GET_OFFSET (inode) << 2)); | |
164 | unsigned long curr_block = (CRAMFS_GET_OFFSET (inode) + | |
165 | (((CRAMFS_24 (inode->size)) + | |
166 | 4095) >> 12)) << 2; | |
167 | int size, total_size = 0; | |
168 | int i; | |
169 | ||
170 | cramfs_uncompress_init (); | |
171 | ||
172 | for (i = 0; i < ((CRAMFS_24 (inode->size) + 4095) >> 12); i++) { | |
173 | size = cramfs_uncompress_block ((void *) loadoffset, | |
174 | (void *) (begin + curr_block), | |
175 | (CRAMFS_32 (block_ptrs[i]) - | |
176 | curr_block)); | |
177 | if (size < 0) | |
178 | return size; | |
179 | loadoffset += size; | |
180 | total_size += size; | |
181 | curr_block = CRAMFS_32 (block_ptrs[i]); | |
182 | } | |
183 | ||
184 | cramfs_uncompress_exit (); | |
185 | return total_size; | |
186 | } | |
187 | ||
188 | int cramfs_load (char *loadoffset, struct part_info *info, char *filename) | |
189 | { | |
190 | unsigned long offset; | |
191 | ||
192 | if (cramfs_read_super (info)) | |
193 | return -1; | |
194 | ||
700a0c64 | 195 | offset = cramfs_resolve (PART_OFFSET(info), |
dd875c76 WD |
196 | CRAMFS_GET_OFFSET (&(super.root)) << 2, |
197 | CRAMFS_24 (super.root.size), 0, | |
198 | strtok (filename, "/")); | |
199 | ||
200 | if (offset <= 0) | |
201 | return offset; | |
202 | ||
700a0c64 | 203 | return cramfs_uncompress (PART_OFFSET(info), offset, |
dd875c76 WD |
204 | (unsigned long) loadoffset); |
205 | } | |
206 | ||
dd875c76 WD |
207 | static int cramfs_list_inode (struct part_info *info, unsigned long offset) |
208 | { | |
209 | struct cramfs_inode *inode = (struct cramfs_inode *) | |
700a0c64 | 210 | (PART_OFFSET(info) + offset); |
dd875c76 WD |
211 | char *name, str[20]; |
212 | int namelen, nextoff; | |
213 | ||
214 | /* | |
215 | * Namelengths on disk are shifted by two | |
216 | * and the name padded out to 4-byte boundaries | |
217 | * with zeroes. | |
218 | */ | |
219 | namelen = CRAMFS_GET_NAMELEN (inode) << 2; | |
220 | name = (char *) inode + sizeof (struct cramfs_inode); | |
221 | nextoff = namelen; | |
222 | ||
223 | for (;;) { | |
224 | if (!namelen) | |
225 | return namelen; | |
226 | if (name[namelen - 1]) | |
227 | break; | |
228 | namelen--; | |
229 | } | |
230 | ||
231 | printf (" %s %8d %*.*s", mkmodestr (CRAMFS_16 (inode->mode), str), | |
232 | CRAMFS_24 (inode->size), namelen, namelen, name); | |
233 | ||
234 | if ((CRAMFS_16 (inode->mode) & S_IFMT) == S_IFLNK) { | |
235 | /* symbolic link. | |
236 | * Unpack the link target, trusting in the inode's size field. | |
237 | */ | |
238 | unsigned long size = CRAMFS_24 (inode->size); | |
239 | char *link = malloc (size); | |
240 | ||
700a0c64 | 241 | if (link != NULL && cramfs_uncompress (PART_OFFSET(info), offset, |
dd875c76 WD |
242 | (unsigned long) link) |
243 | == size) | |
244 | printf (" -> %*.*s\n", (int) size, (int) size, link); | |
245 | else | |
246 | printf (" [Error reading link]\n"); | |
247 | if (link) | |
248 | free (link); | |
249 | } else | |
250 | printf ("\n"); | |
251 | ||
252 | return nextoff; | |
253 | } | |
254 | ||
255 | int cramfs_ls (struct part_info *info, char *filename) | |
256 | { | |
257 | struct cramfs_inode *inode; | |
258 | unsigned long inodeoffset = 0, nextoffset; | |
259 | unsigned long offset, size; | |
260 | ||
261 | if (cramfs_read_super (info)) | |
262 | return -1; | |
263 | ||
264 | if (strlen (filename) == 0 || !strcmp (filename, "/")) { | |
265 | /* Root directory. Use root inode in super block */ | |
266 | offset = CRAMFS_GET_OFFSET (&(super.root)) << 2; | |
267 | size = CRAMFS_24 (super.root.size); | |
268 | } else { | |
269 | /* Resolve the path */ | |
700a0c64 | 270 | offset = cramfs_resolve (PART_OFFSET(info), |
dd875c76 WD |
271 | CRAMFS_GET_OFFSET (&(super.root)) << |
272 | 2, CRAMFS_24 (super.root.size), 1, | |
273 | strtok (filename, "/")); | |
274 | ||
275 | if (offset <= 0) | |
276 | return offset; | |
277 | ||
278 | /* Resolving was successful. Examine the inode */ | |
700a0c64 | 279 | inode = (struct cramfs_inode *) (PART_OFFSET(info) + offset); |
dd875c76 WD |
280 | if (!S_ISDIR (CRAMFS_16 (inode->mode))) { |
281 | /* It's not a directory - list it, and that's that */ | |
282 | return (cramfs_list_inode (info, offset) > 0); | |
283 | } | |
284 | ||
285 | /* It's a directory. List files within */ | |
286 | offset = CRAMFS_GET_OFFSET (inode) << 2; | |
287 | size = CRAMFS_24 (inode->size); | |
288 | } | |
289 | ||
290 | /* List the given directory */ | |
291 | while (inodeoffset < size) { | |
700a0c64 | 292 | inode = (struct cramfs_inode *) (PART_OFFSET(info) + offset + |
dd875c76 WD |
293 | inodeoffset); |
294 | ||
295 | nextoffset = cramfs_list_inode (info, offset + inodeoffset); | |
296 | if (nextoffset == 0) | |
297 | break; | |
298 | inodeoffset += sizeof (struct cramfs_inode) + nextoffset; | |
299 | } | |
300 | ||
301 | return 1; | |
302 | } | |
303 | ||
304 | int cramfs_info (struct part_info *info) | |
305 | { | |
306 | if (cramfs_read_super (info)) | |
307 | return 0; | |
308 | ||
309 | printf ("size: 0x%x (%u)\n", super.size, super.size); | |
310 | ||
311 | if (super.flags != 0) { | |
312 | printf ("flags:\n"); | |
313 | if (super.flags & CRAMFS_FLAG_FSID_VERSION_2) | |
314 | printf ("\tFSID version 2\n"); | |
315 | if (super.flags & CRAMFS_FLAG_SORTED_DIRS) | |
316 | printf ("\tsorted dirs\n"); | |
317 | if (super.flags & CRAMFS_FLAG_HOLES) | |
318 | printf ("\tholes\n"); | |
319 | if (super.flags & CRAMFS_FLAG_SHIFTED_ROOT_OFFSET) | |
320 | printf ("\tshifted root offset\n"); | |
321 | } | |
322 | ||
323 | printf ("fsid:\n\tcrc: 0x%x\n\tedition: 0x%x\n", | |
324 | super.fsid.crc, super.fsid.edition); | |
325 | printf ("name: %16s\n", super.name); | |
326 | ||
327 | return 1; | |
328 | } | |
329 | ||
330 | int cramfs_check (struct part_info *info) | |
331 | { | |
700a0c64 WD |
332 | struct cramfs_super *sb; |
333 | ||
334 | if (info->dev->id->type != MTD_DEV_TYPE_NOR) | |
335 | return 0; | |
dd875c76 | 336 | |
700a0c64 | 337 | sb = (struct cramfs_super *) PART_OFFSET(info); |
dd875c76 WD |
338 | if (sb->magic != CRAMFS_32 (CRAMFS_MAGIC)) { |
339 | /* check at 512 byte offset */ | |
700a0c64 WD |
340 | sb = (struct cramfs_super *) (PART_OFFSET(info) + 512); |
341 | if (sb->magic != CRAMFS_32 (CRAMFS_MAGIC)) | |
dd875c76 | 342 | return 0; |
dd875c76 WD |
343 | } |
344 | return 1; | |
345 | } | |
346 | ||
347 | #endif /* CFG_FS_CRAMFS */ |