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