diff options
Diffstat (limited to 'fs/adfs')
-rw-r--r-- | fs/adfs/Makefile | 7 | ||||
-rw-r--r-- | fs/adfs/adfs.h | 127 | ||||
-rw-r--r-- | fs/adfs/dir.c | 302 | ||||
-rw-r--r-- | fs/adfs/dir_f.c | 460 | ||||
-rw-r--r-- | fs/adfs/dir_f.h | 65 | ||||
-rw-r--r-- | fs/adfs/dir_fplus.c | 179 | ||||
-rw-r--r-- | fs/adfs/dir_fplus.h | 45 | ||||
-rw-r--r-- | fs/adfs/file.c | 43 | ||||
-rw-r--r-- | fs/adfs/inode.c | 395 | ||||
-rw-r--r-- | fs/adfs/map.c | 296 | ||||
-rw-r--r-- | fs/adfs/super.c | 508 |
11 files changed, 2427 insertions, 0 deletions
diff --git a/fs/adfs/Makefile b/fs/adfs/Makefile new file mode 100644 index 000000000000..9b2d71a9a35c --- /dev/null +++ b/fs/adfs/Makefile | |||
@@ -0,0 +1,7 @@ | |||
1 | # | ||
2 | # Makefile for the linux adfs filesystem routines. | ||
3 | # | ||
4 | |||
5 | obj-$(CONFIG_ADFS_FS) += adfs.o | ||
6 | |||
7 | adfs-objs := dir.o dir_f.o dir_fplus.o file.o inode.o map.o super.o | ||
diff --git a/fs/adfs/adfs.h b/fs/adfs/adfs.h new file mode 100644 index 000000000000..63f5df9afb71 --- /dev/null +++ b/fs/adfs/adfs.h | |||
@@ -0,0 +1,127 @@ | |||
1 | /* Internal data structures for ADFS */ | ||
2 | |||
3 | #define ADFS_FREE_FRAG 0 | ||
4 | #define ADFS_BAD_FRAG 1 | ||
5 | #define ADFS_ROOT_FRAG 2 | ||
6 | |||
7 | #define ADFS_NDA_OWNER_READ (1 << 0) | ||
8 | #define ADFS_NDA_OWNER_WRITE (1 << 1) | ||
9 | #define ADFS_NDA_LOCKED (1 << 2) | ||
10 | #define ADFS_NDA_DIRECTORY (1 << 3) | ||
11 | #define ADFS_NDA_EXECUTE (1 << 4) | ||
12 | #define ADFS_NDA_PUBLIC_READ (1 << 5) | ||
13 | #define ADFS_NDA_PUBLIC_WRITE (1 << 6) | ||
14 | |||
15 | #include <linux/version.h> | ||
16 | #include "dir_f.h" | ||
17 | |||
18 | struct buffer_head; | ||
19 | |||
20 | /* | ||
21 | * Directory handling | ||
22 | */ | ||
23 | struct adfs_dir { | ||
24 | struct super_block *sb; | ||
25 | |||
26 | int nr_buffers; | ||
27 | struct buffer_head *bh[4]; | ||
28 | unsigned int pos; | ||
29 | unsigned int parent_id; | ||
30 | |||
31 | struct adfs_dirheader dirhead; | ||
32 | union adfs_dirtail dirtail; | ||
33 | }; | ||
34 | |||
35 | /* | ||
36 | * This is the overall maximum name length | ||
37 | */ | ||
38 | #define ADFS_MAX_NAME_LEN 256 | ||
39 | struct object_info { | ||
40 | __u32 parent_id; /* parent object id */ | ||
41 | __u32 file_id; /* object id */ | ||
42 | __u32 loadaddr; /* load address */ | ||
43 | __u32 execaddr; /* execution address */ | ||
44 | __u32 size; /* size */ | ||
45 | __u8 attr; /* RISC OS attributes */ | ||
46 | unsigned char name_len; /* name length */ | ||
47 | char name[ADFS_MAX_NAME_LEN];/* file name */ | ||
48 | }; | ||
49 | |||
50 | struct adfs_dir_ops { | ||
51 | int (*read)(struct super_block *sb, unsigned int id, unsigned int sz, struct adfs_dir *dir); | ||
52 | int (*setpos)(struct adfs_dir *dir, unsigned int fpos); | ||
53 | int (*getnext)(struct adfs_dir *dir, struct object_info *obj); | ||
54 | int (*update)(struct adfs_dir *dir, struct object_info *obj); | ||
55 | int (*create)(struct adfs_dir *dir, struct object_info *obj); | ||
56 | int (*remove)(struct adfs_dir *dir, struct object_info *obj); | ||
57 | void (*free)(struct adfs_dir *dir); | ||
58 | }; | ||
59 | |||
60 | struct adfs_discmap { | ||
61 | struct buffer_head *dm_bh; | ||
62 | __u32 dm_startblk; | ||
63 | unsigned int dm_startbit; | ||
64 | unsigned int dm_endbit; | ||
65 | }; | ||
66 | |||
67 | /* Inode stuff */ | ||
68 | struct inode *adfs_iget(struct super_block *sb, struct object_info *obj); | ||
69 | int adfs_write_inode(struct inode *inode,int unused); | ||
70 | int adfs_notify_change(struct dentry *dentry, struct iattr *attr); | ||
71 | |||
72 | /* map.c */ | ||
73 | extern int adfs_map_lookup(struct super_block *sb, unsigned int frag_id, unsigned int offset); | ||
74 | extern unsigned int adfs_map_free(struct super_block *sb); | ||
75 | |||
76 | /* Misc */ | ||
77 | void __adfs_error(struct super_block *sb, const char *function, | ||
78 | const char *fmt, ...); | ||
79 | #define adfs_error(sb, fmt...) __adfs_error(sb, __FUNCTION__, fmt) | ||
80 | |||
81 | /* super.c */ | ||
82 | |||
83 | /* | ||
84 | * Inodes and file operations | ||
85 | */ | ||
86 | |||
87 | /* dir_*.c */ | ||
88 | extern struct inode_operations adfs_dir_inode_operations; | ||
89 | extern struct file_operations adfs_dir_operations; | ||
90 | extern struct dentry_operations adfs_dentry_operations; | ||
91 | extern struct adfs_dir_ops adfs_f_dir_ops; | ||
92 | extern struct adfs_dir_ops adfs_fplus_dir_ops; | ||
93 | |||
94 | extern int adfs_dir_update(struct super_block *sb, struct object_info *obj); | ||
95 | |||
96 | /* file.c */ | ||
97 | extern struct inode_operations adfs_file_inode_operations; | ||
98 | extern struct file_operations adfs_file_operations; | ||
99 | |||
100 | extern inline __u32 signed_asl(__u32 val, signed int shift) | ||
101 | { | ||
102 | if (shift >= 0) | ||
103 | val <<= shift; | ||
104 | else | ||
105 | val >>= -shift; | ||
106 | return val; | ||
107 | } | ||
108 | |||
109 | /* | ||
110 | * Calculate the address of a block in an object given the block offset | ||
111 | * and the object identity. | ||
112 | * | ||
113 | * The root directory ID should always be looked up in the map [3.4] | ||
114 | */ | ||
115 | extern inline int | ||
116 | __adfs_block_map(struct super_block *sb, unsigned int object_id, | ||
117 | unsigned int block) | ||
118 | { | ||
119 | if (object_id & 255) { | ||
120 | unsigned int off; | ||
121 | |||
122 | off = (object_id & 255) - 1; | ||
123 | block += off << ADFS_SB(sb)->s_log2sharesize; | ||
124 | } | ||
125 | |||
126 | return adfs_map_lookup(sb, object_id >> 8, block); | ||
127 | } | ||
diff --git a/fs/adfs/dir.c b/fs/adfs/dir.c new file mode 100644 index 000000000000..0b4c3a028076 --- /dev/null +++ b/fs/adfs/dir.c | |||
@@ -0,0 +1,302 @@ | |||
1 | /* | ||
2 | * linux/fs/adfs/dir.c | ||
3 | * | ||
4 | * Copyright (C) 1999-2000 Russell King | ||
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 version 2 as | ||
8 | * published by the Free Software Foundation. | ||
9 | * | ||
10 | * Common directory handling for ADFS | ||
11 | */ | ||
12 | #include <linux/config.h> | ||
13 | #include <linux/errno.h> | ||
14 | #include <linux/fs.h> | ||
15 | #include <linux/adfs_fs.h> | ||
16 | #include <linux/time.h> | ||
17 | #include <linux/stat.h> | ||
18 | #include <linux/spinlock.h> | ||
19 | #include <linux/smp_lock.h> | ||
20 | #include <linux/buffer_head.h> /* for file_fsync() */ | ||
21 | |||
22 | #include "adfs.h" | ||
23 | |||
24 | /* | ||
25 | * For future. This should probably be per-directory. | ||
26 | */ | ||
27 | static DEFINE_RWLOCK(adfs_dir_lock); | ||
28 | |||
29 | static int | ||
30 | adfs_readdir(struct file *filp, void *dirent, filldir_t filldir) | ||
31 | { | ||
32 | struct inode *inode = filp->f_dentry->d_inode; | ||
33 | struct super_block *sb = inode->i_sb; | ||
34 | struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir; | ||
35 | struct object_info obj; | ||
36 | struct adfs_dir dir; | ||
37 | int ret = 0; | ||
38 | |||
39 | lock_kernel(); | ||
40 | |||
41 | if (filp->f_pos >> 32) | ||
42 | goto out; | ||
43 | |||
44 | ret = ops->read(sb, inode->i_ino, inode->i_size, &dir); | ||
45 | if (ret) | ||
46 | goto out; | ||
47 | |||
48 | switch ((unsigned long)filp->f_pos) { | ||
49 | case 0: | ||
50 | if (filldir(dirent, ".", 1, 0, inode->i_ino, DT_DIR) < 0) | ||
51 | goto free_out; | ||
52 | filp->f_pos += 1; | ||
53 | |||
54 | case 1: | ||
55 | if (filldir(dirent, "..", 2, 1, dir.parent_id, DT_DIR) < 0) | ||
56 | goto free_out; | ||
57 | filp->f_pos += 1; | ||
58 | |||
59 | default: | ||
60 | break; | ||
61 | } | ||
62 | |||
63 | read_lock(&adfs_dir_lock); | ||
64 | |||
65 | ret = ops->setpos(&dir, filp->f_pos - 2); | ||
66 | if (ret) | ||
67 | goto unlock_out; | ||
68 | while (ops->getnext(&dir, &obj) == 0) { | ||
69 | if (filldir(dirent, obj.name, obj.name_len, | ||
70 | filp->f_pos, obj.file_id, DT_UNKNOWN) < 0) | ||
71 | goto unlock_out; | ||
72 | filp->f_pos += 1; | ||
73 | } | ||
74 | |||
75 | unlock_out: | ||
76 | read_unlock(&adfs_dir_lock); | ||
77 | |||
78 | free_out: | ||
79 | ops->free(&dir); | ||
80 | |||
81 | out: | ||
82 | unlock_kernel(); | ||
83 | return ret; | ||
84 | } | ||
85 | |||
86 | int | ||
87 | adfs_dir_update(struct super_block *sb, struct object_info *obj) | ||
88 | { | ||
89 | int ret = -EINVAL; | ||
90 | #ifdef CONFIG_ADFS_FS_RW | ||
91 | struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir; | ||
92 | struct adfs_dir dir; | ||
93 | |||
94 | printk(KERN_INFO "adfs_dir_update: object %06X in dir %06X\n", | ||
95 | obj->file_id, obj->parent_id); | ||
96 | |||
97 | if (!ops->update) { | ||
98 | ret = -EINVAL; | ||
99 | goto out; | ||
100 | } | ||
101 | |||
102 | ret = ops->read(sb, obj->parent_id, 0, &dir); | ||
103 | if (ret) | ||
104 | goto out; | ||
105 | |||
106 | write_lock(&adfs_dir_lock); | ||
107 | ret = ops->update(&dir, obj); | ||
108 | write_unlock(&adfs_dir_lock); | ||
109 | |||
110 | ops->free(&dir); | ||
111 | out: | ||
112 | #endif | ||
113 | return ret; | ||
114 | } | ||
115 | |||
116 | static int | ||
117 | adfs_match(struct qstr *name, struct object_info *obj) | ||
118 | { | ||
119 | int i; | ||
120 | |||
121 | if (name->len != obj->name_len) | ||
122 | return 0; | ||
123 | |||
124 | for (i = 0; i < name->len; i++) { | ||
125 | char c1, c2; | ||
126 | |||
127 | c1 = name->name[i]; | ||
128 | c2 = obj->name[i]; | ||
129 | |||
130 | if (c1 >= 'A' && c1 <= 'Z') | ||
131 | c1 += 'a' - 'A'; | ||
132 | if (c2 >= 'A' && c2 <= 'Z') | ||
133 | c2 += 'a' - 'A'; | ||
134 | |||
135 | if (c1 != c2) | ||
136 | return 0; | ||
137 | } | ||
138 | return 1; | ||
139 | } | ||
140 | |||
141 | static int | ||
142 | adfs_dir_lookup_byname(struct inode *inode, struct qstr *name, struct object_info *obj) | ||
143 | { | ||
144 | struct super_block *sb = inode->i_sb; | ||
145 | struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir; | ||
146 | struct adfs_dir dir; | ||
147 | int ret; | ||
148 | |||
149 | ret = ops->read(sb, inode->i_ino, inode->i_size, &dir); | ||
150 | if (ret) | ||
151 | goto out; | ||
152 | |||
153 | if (ADFS_I(inode)->parent_id != dir.parent_id) { | ||
154 | adfs_error(sb, "parent directory changed under me! (%lx but got %lx)\n", | ||
155 | ADFS_I(inode)->parent_id, dir.parent_id); | ||
156 | ret = -EIO; | ||
157 | goto free_out; | ||
158 | } | ||
159 | |||
160 | obj->parent_id = inode->i_ino; | ||
161 | |||
162 | /* | ||
163 | * '.' is handled by reserved_lookup() in fs/namei.c | ||
164 | */ | ||
165 | if (name->len == 2 && name->name[0] == '.' && name->name[1] == '.') { | ||
166 | /* | ||
167 | * Currently unable to fill in the rest of 'obj', | ||
168 | * but this is better than nothing. We need to | ||
169 | * ascend one level to find it's parent. | ||
170 | */ | ||
171 | obj->name_len = 0; | ||
172 | obj->file_id = obj->parent_id; | ||
173 | goto free_out; | ||
174 | } | ||
175 | |||
176 | read_lock(&adfs_dir_lock); | ||
177 | |||
178 | ret = ops->setpos(&dir, 0); | ||
179 | if (ret) | ||
180 | goto unlock_out; | ||
181 | |||
182 | ret = -ENOENT; | ||
183 | while (ops->getnext(&dir, obj) == 0) { | ||
184 | if (adfs_match(name, obj)) { | ||
185 | ret = 0; | ||
186 | break; | ||
187 | } | ||
188 | } | ||
189 | |||
190 | unlock_out: | ||
191 | read_unlock(&adfs_dir_lock); | ||
192 | |||
193 | free_out: | ||
194 | ops->free(&dir); | ||
195 | out: | ||
196 | return ret; | ||
197 | } | ||
198 | |||
199 | struct file_operations adfs_dir_operations = { | ||
200 | .read = generic_read_dir, | ||
201 | .readdir = adfs_readdir, | ||
202 | .fsync = file_fsync, | ||
203 | }; | ||
204 | |||
205 | static int | ||
206 | adfs_hash(struct dentry *parent, struct qstr *qstr) | ||
207 | { | ||
208 | const unsigned int name_len = ADFS_SB(parent->d_sb)->s_namelen; | ||
209 | const unsigned char *name; | ||
210 | unsigned long hash; | ||
211 | int i; | ||
212 | |||
213 | if (qstr->len < name_len) | ||
214 | return 0; | ||
215 | |||
216 | /* | ||
217 | * Truncate the name in place, avoids | ||
218 | * having to define a compare function. | ||
219 | */ | ||
220 | qstr->len = i = name_len; | ||
221 | name = qstr->name; | ||
222 | hash = init_name_hash(); | ||
223 | while (i--) { | ||
224 | char c; | ||
225 | |||
226 | c = *name++; | ||
227 | if (c >= 'A' && c <= 'Z') | ||
228 | c += 'a' - 'A'; | ||
229 | |||
230 | hash = partial_name_hash(c, hash); | ||
231 | } | ||
232 | qstr->hash = end_name_hash(hash); | ||
233 | |||
234 | return 0; | ||
235 | } | ||
236 | |||
237 | /* | ||
238 | * Compare two names, taking note of the name length | ||
239 | * requirements of the underlying filesystem. | ||
240 | */ | ||
241 | static int | ||
242 | adfs_compare(struct dentry *parent, struct qstr *entry, struct qstr *name) | ||
243 | { | ||
244 | int i; | ||
245 | |||
246 | if (entry->len != name->len) | ||
247 | return 1; | ||
248 | |||
249 | for (i = 0; i < name->len; i++) { | ||
250 | char a, b; | ||
251 | |||
252 | a = entry->name[i]; | ||
253 | b = name->name[i]; | ||
254 | |||
255 | if (a >= 'A' && a <= 'Z') | ||
256 | a += 'a' - 'A'; | ||
257 | if (b >= 'A' && b <= 'Z') | ||
258 | b += 'a' - 'A'; | ||
259 | |||
260 | if (a != b) | ||
261 | return 1; | ||
262 | } | ||
263 | return 0; | ||
264 | } | ||
265 | |||
266 | struct dentry_operations adfs_dentry_operations = { | ||
267 | .d_hash = adfs_hash, | ||
268 | .d_compare = adfs_compare, | ||
269 | }; | ||
270 | |||
271 | static struct dentry * | ||
272 | adfs_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd) | ||
273 | { | ||
274 | struct inode *inode = NULL; | ||
275 | struct object_info obj; | ||
276 | int error; | ||
277 | |||
278 | dentry->d_op = &adfs_dentry_operations; | ||
279 | lock_kernel(); | ||
280 | error = adfs_dir_lookup_byname(dir, &dentry->d_name, &obj); | ||
281 | if (error == 0) { | ||
282 | error = -EACCES; | ||
283 | /* | ||
284 | * This only returns NULL if get_empty_inode | ||
285 | * fails. | ||
286 | */ | ||
287 | inode = adfs_iget(dir->i_sb, &obj); | ||
288 | if (inode) | ||
289 | error = 0; | ||
290 | } | ||
291 | unlock_kernel(); | ||
292 | d_add(dentry, inode); | ||
293 | return ERR_PTR(error); | ||
294 | } | ||
295 | |||
296 | /* | ||
297 | * directories can handle most operations... | ||
298 | */ | ||
299 | struct inode_operations adfs_dir_inode_operations = { | ||
300 | .lookup = adfs_lookup, | ||
301 | .setattr = adfs_notify_change, | ||
302 | }; | ||
diff --git a/fs/adfs/dir_f.c b/fs/adfs/dir_f.c new file mode 100644 index 000000000000..bbfc86259272 --- /dev/null +++ b/fs/adfs/dir_f.c | |||
@@ -0,0 +1,460 @@ | |||
1 | /* | ||
2 | * linux/fs/adfs/dir_f.c | ||
3 | * | ||
4 | * Copyright (C) 1997-1999 Russell King | ||
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 version 2 as | ||
8 | * published by the Free Software Foundation. | ||
9 | * | ||
10 | * E and F format directory handling | ||
11 | */ | ||
12 | #include <linux/errno.h> | ||
13 | #include <linux/fs.h> | ||
14 | #include <linux/adfs_fs.h> | ||
15 | #include <linux/time.h> | ||
16 | #include <linux/stat.h> | ||
17 | #include <linux/spinlock.h> | ||
18 | #include <linux/buffer_head.h> | ||
19 | #include <linux/string.h> | ||
20 | |||
21 | #include "adfs.h" | ||
22 | #include "dir_f.h" | ||
23 | |||
24 | static void adfs_f_free(struct adfs_dir *dir); | ||
25 | |||
26 | /* | ||
27 | * Read an (unaligned) value of length 1..4 bytes | ||
28 | */ | ||
29 | static inline unsigned int adfs_readval(unsigned char *p, int len) | ||
30 | { | ||
31 | unsigned int val = 0; | ||
32 | |||
33 | switch (len) { | ||
34 | case 4: val |= p[3] << 24; | ||
35 | case 3: val |= p[2] << 16; | ||
36 | case 2: val |= p[1] << 8; | ||
37 | default: val |= p[0]; | ||
38 | } | ||
39 | return val; | ||
40 | } | ||
41 | |||
42 | static inline void adfs_writeval(unsigned char *p, int len, unsigned int val) | ||
43 | { | ||
44 | switch (len) { | ||
45 | case 4: p[3] = val >> 24; | ||
46 | case 3: p[2] = val >> 16; | ||
47 | case 2: p[1] = val >> 8; | ||
48 | default: p[0] = val; | ||
49 | } | ||
50 | } | ||
51 | |||
52 | static inline int adfs_readname(char *buf, char *ptr, int maxlen) | ||
53 | { | ||
54 | char *old_buf = buf; | ||
55 | |||
56 | while (*ptr >= ' ' && maxlen--) { | ||
57 | if (*ptr == '/') | ||
58 | *buf++ = '.'; | ||
59 | else | ||
60 | *buf++ = *ptr; | ||
61 | ptr++; | ||
62 | } | ||
63 | *buf = '\0'; | ||
64 | |||
65 | return buf - old_buf; | ||
66 | } | ||
67 | |||
68 | #define ror13(v) ((v >> 13) | (v << 19)) | ||
69 | |||
70 | #define dir_u8(idx) \ | ||
71 | ({ int _buf = idx >> blocksize_bits; \ | ||
72 | int _off = idx - (_buf << blocksize_bits);\ | ||
73 | *(u8 *)(bh[_buf]->b_data + _off); \ | ||
74 | }) | ||
75 | |||
76 | #define dir_u32(idx) \ | ||
77 | ({ int _buf = idx >> blocksize_bits; \ | ||
78 | int _off = idx - (_buf << blocksize_bits);\ | ||
79 | *(__le32 *)(bh[_buf]->b_data + _off); \ | ||
80 | }) | ||
81 | |||
82 | #define bufoff(_bh,_idx) \ | ||
83 | ({ int _buf = _idx >> blocksize_bits; \ | ||
84 | int _off = _idx - (_buf << blocksize_bits);\ | ||
85 | (u8 *)(_bh[_buf]->b_data + _off); \ | ||
86 | }) | ||
87 | |||
88 | /* | ||
89 | * There are some algorithms that are nice in | ||
90 | * assembler, but a bitch in C... This is one | ||
91 | * of them. | ||
92 | */ | ||
93 | static u8 | ||
94 | adfs_dir_checkbyte(const struct adfs_dir *dir) | ||
95 | { | ||
96 | struct buffer_head * const *bh = dir->bh; | ||
97 | const int blocksize_bits = dir->sb->s_blocksize_bits; | ||
98 | union { __le32 *ptr32; u8 *ptr8; } ptr, end; | ||
99 | u32 dircheck = 0; | ||
100 | int last = 5 - 26; | ||
101 | int i = 0; | ||
102 | |||
103 | /* | ||
104 | * Accumulate each word up to the last whole | ||
105 | * word of the last directory entry. This | ||
106 | * can spread across several buffer heads. | ||
107 | */ | ||
108 | do { | ||
109 | last += 26; | ||
110 | do { | ||
111 | dircheck = le32_to_cpu(dir_u32(i)) ^ ror13(dircheck); | ||
112 | |||
113 | i += sizeof(u32); | ||
114 | } while (i < (last & ~3)); | ||
115 | } while (dir_u8(last) != 0); | ||
116 | |||
117 | /* | ||
118 | * Accumulate the last few bytes. These | ||
119 | * bytes will be within the same bh. | ||
120 | */ | ||
121 | if (i != last) { | ||
122 | ptr.ptr8 = bufoff(bh, i); | ||
123 | end.ptr8 = ptr.ptr8 + last - i; | ||
124 | |||
125 | do | ||
126 | dircheck = *ptr.ptr8++ ^ ror13(dircheck); | ||
127 | while (ptr.ptr8 < end.ptr8); | ||
128 | } | ||
129 | |||
130 | /* | ||
131 | * The directory tail is in the final bh | ||
132 | * Note that contary to the RISC OS PRMs, | ||
133 | * the first few bytes are NOT included | ||
134 | * in the check. All bytes are in the | ||
135 | * same bh. | ||
136 | */ | ||
137 | ptr.ptr8 = bufoff(bh, 2008); | ||
138 | end.ptr8 = ptr.ptr8 + 36; | ||
139 | |||
140 | do { | ||
141 | __le32 v = *ptr.ptr32++; | ||
142 | dircheck = le32_to_cpu(v) ^ ror13(dircheck); | ||
143 | } while (ptr.ptr32 < end.ptr32); | ||
144 | |||
145 | return (dircheck ^ (dircheck >> 8) ^ (dircheck >> 16) ^ (dircheck >> 24)) & 0xff; | ||
146 | } | ||
147 | |||
148 | /* | ||
149 | * Read and check that a directory is valid | ||
150 | */ | ||
151 | static int | ||
152 | adfs_dir_read(struct super_block *sb, unsigned long object_id, | ||
153 | unsigned int size, struct adfs_dir *dir) | ||
154 | { | ||
155 | const unsigned int blocksize_bits = sb->s_blocksize_bits; | ||
156 | int blk = 0; | ||
157 | |||
158 | /* | ||
159 | * Directories which are not a multiple of 2048 bytes | ||
160 | * are considered bad v2 [3.6] | ||
161 | */ | ||
162 | if (size & 2047) | ||
163 | goto bad_dir; | ||
164 | |||
165 | size >>= blocksize_bits; | ||
166 | |||
167 | dir->nr_buffers = 0; | ||
168 | dir->sb = sb; | ||
169 | |||
170 | for (blk = 0; blk < size; blk++) { | ||
171 | int phys; | ||
172 | |||
173 | phys = __adfs_block_map(sb, object_id, blk); | ||
174 | if (!phys) { | ||
175 | adfs_error(sb, "dir object %lX has a hole at offset %d", | ||
176 | object_id, blk); | ||
177 | goto release_buffers; | ||
178 | } | ||
179 | |||
180 | dir->bh[blk] = sb_bread(sb, phys); | ||
181 | if (!dir->bh[blk]) | ||
182 | goto release_buffers; | ||
183 | } | ||
184 | |||
185 | memcpy(&dir->dirhead, bufoff(dir->bh, 0), sizeof(dir->dirhead)); | ||
186 | memcpy(&dir->dirtail, bufoff(dir->bh, 2007), sizeof(dir->dirtail)); | ||
187 | |||
188 | if (dir->dirhead.startmasseq != dir->dirtail.new.endmasseq || | ||
189 | memcmp(&dir->dirhead.startname, &dir->dirtail.new.endname, 4)) | ||
190 | goto bad_dir; | ||
191 | |||
192 | if (memcmp(&dir->dirhead.startname, "Nick", 4) && | ||
193 | memcmp(&dir->dirhead.startname, "Hugo", 4)) | ||
194 | goto bad_dir; | ||
195 | |||
196 | if (adfs_dir_checkbyte(dir) != dir->dirtail.new.dircheckbyte) | ||
197 | goto bad_dir; | ||
198 | |||
199 | dir->nr_buffers = blk; | ||
200 | |||
201 | return 0; | ||
202 | |||
203 | bad_dir: | ||
204 | adfs_error(sb, "corrupted directory fragment %lX", | ||
205 | object_id); | ||
206 | release_buffers: | ||
207 | for (blk -= 1; blk >= 0; blk -= 1) | ||
208 | brelse(dir->bh[blk]); | ||
209 | |||
210 | dir->sb = NULL; | ||
211 | |||
212 | return -EIO; | ||
213 | } | ||
214 | |||
215 | /* | ||
216 | * convert a disk-based directory entry to a Linux ADFS directory entry | ||
217 | */ | ||
218 | static inline void | ||
219 | adfs_dir2obj(struct object_info *obj, struct adfs_direntry *de) | ||
220 | { | ||
221 | obj->name_len = adfs_readname(obj->name, de->dirobname, ADFS_F_NAME_LEN); | ||
222 | obj->file_id = adfs_readval(de->dirinddiscadd, 3); | ||
223 | obj->loadaddr = adfs_readval(de->dirload, 4); | ||
224 | obj->execaddr = adfs_readval(de->direxec, 4); | ||
225 | obj->size = adfs_readval(de->dirlen, 4); | ||
226 | obj->attr = de->newdiratts; | ||
227 | } | ||
228 | |||
229 | /* | ||
230 | * convert a Linux ADFS directory entry to a disk-based directory entry | ||
231 | */ | ||
232 | static inline void | ||
233 | adfs_obj2dir(struct adfs_direntry *de, struct object_info *obj) | ||
234 | { | ||
235 | adfs_writeval(de->dirinddiscadd, 3, obj->file_id); | ||
236 | adfs_writeval(de->dirload, 4, obj->loadaddr); | ||
237 | adfs_writeval(de->direxec, 4, obj->execaddr); | ||
238 | adfs_writeval(de->dirlen, 4, obj->size); | ||
239 | de->newdiratts = obj->attr; | ||
240 | } | ||
241 | |||
242 | /* | ||
243 | * get a directory entry. Note that the caller is responsible | ||
244 | * for holding the relevant locks. | ||
245 | */ | ||
246 | static int | ||
247 | __adfs_dir_get(struct adfs_dir *dir, int pos, struct object_info *obj) | ||
248 | { | ||
249 | struct super_block *sb = dir->sb; | ||
250 | struct adfs_direntry de; | ||
251 | int thissize, buffer, offset; | ||
252 | |||
253 | buffer = pos >> sb->s_blocksize_bits; | ||
254 | |||
255 | if (buffer > dir->nr_buffers) | ||
256 | return -EINVAL; | ||
257 | |||
258 | offset = pos & (sb->s_blocksize - 1); | ||
259 | thissize = sb->s_blocksize - offset; | ||
260 | if (thissize > 26) | ||
261 | thissize = 26; | ||
262 | |||
263 | memcpy(&de, dir->bh[buffer]->b_data + offset, thissize); | ||
264 | if (thissize != 26) | ||
265 | memcpy(((char *)&de) + thissize, dir->bh[buffer + 1]->b_data, | ||
266 | 26 - thissize); | ||
267 | |||
268 | if (!de.dirobname[0]) | ||
269 | return -ENOENT; | ||
270 | |||
271 | adfs_dir2obj(obj, &de); | ||
272 | |||
273 | return 0; | ||
274 | } | ||
275 | |||
276 | static int | ||
277 | __adfs_dir_put(struct adfs_dir *dir, int pos, struct object_info *obj) | ||
278 | { | ||
279 | struct super_block *sb = dir->sb; | ||
280 | struct adfs_direntry de; | ||
281 | int thissize, buffer, offset; | ||
282 | |||
283 | buffer = pos >> sb->s_blocksize_bits; | ||
284 | |||
285 | if (buffer > dir->nr_buffers) | ||
286 | return -EINVAL; | ||
287 | |||
288 | offset = pos & (sb->s_blocksize - 1); | ||
289 | thissize = sb->s_blocksize - offset; | ||
290 | if (thissize > 26) | ||
291 | thissize = 26; | ||
292 | |||
293 | /* | ||
294 | * Get the entry in total | ||
295 | */ | ||
296 | memcpy(&de, dir->bh[buffer]->b_data + offset, thissize); | ||
297 | if (thissize != 26) | ||
298 | memcpy(((char *)&de) + thissize, dir->bh[buffer + 1]->b_data, | ||
299 | 26 - thissize); | ||
300 | |||
301 | /* | ||
302 | * update it | ||
303 | */ | ||
304 | adfs_obj2dir(&de, obj); | ||
305 | |||
306 | /* | ||
307 | * Put the new entry back | ||
308 | */ | ||
309 | memcpy(dir->bh[buffer]->b_data + offset, &de, thissize); | ||
310 | if (thissize != 26) | ||
311 | memcpy(dir->bh[buffer + 1]->b_data, ((char *)&de) + thissize, | ||
312 | 26 - thissize); | ||
313 | |||
314 | return 0; | ||
315 | } | ||
316 | |||
317 | /* | ||
318 | * the caller is responsible for holding the necessary | ||
319 | * locks. | ||
320 | */ | ||
321 | static int | ||
322 | adfs_dir_find_entry(struct adfs_dir *dir, unsigned long object_id) | ||
323 | { | ||
324 | int pos, ret; | ||
325 | |||
326 | ret = -ENOENT; | ||
327 | |||
328 | for (pos = 5; pos < ADFS_NUM_DIR_ENTRIES * 26 + 5; pos += 26) { | ||
329 | struct object_info obj; | ||
330 | |||
331 | if (!__adfs_dir_get(dir, pos, &obj)) | ||
332 | break; | ||
333 | |||
334 | if (obj.file_id == object_id) { | ||
335 | ret = pos; | ||
336 | break; | ||
337 | } | ||
338 | } | ||
339 | |||
340 | return ret; | ||
341 | } | ||
342 | |||
343 | static int | ||
344 | adfs_f_read(struct super_block *sb, unsigned int id, unsigned int sz, struct adfs_dir *dir) | ||
345 | { | ||
346 | int ret; | ||
347 | |||
348 | if (sz != ADFS_NEWDIR_SIZE) | ||
349 | return -EIO; | ||
350 | |||
351 | ret = adfs_dir_read(sb, id, sz, dir); | ||
352 | if (ret) | ||
353 | adfs_error(sb, "unable to read directory"); | ||
354 | else | ||
355 | dir->parent_id = adfs_readval(dir->dirtail.new.dirparent, 3); | ||
356 | |||
357 | return ret; | ||
358 | } | ||
359 | |||
360 | static int | ||
361 | adfs_f_setpos(struct adfs_dir *dir, unsigned int fpos) | ||
362 | { | ||
363 | if (fpos >= ADFS_NUM_DIR_ENTRIES) | ||
364 | return -ENOENT; | ||
365 | |||
366 | dir->pos = 5 + fpos * 26; | ||
367 | return 0; | ||
368 | } | ||
369 | |||
370 | static int | ||
371 | adfs_f_getnext(struct adfs_dir *dir, struct object_info *obj) | ||
372 | { | ||
373 | unsigned int ret; | ||
374 | |||
375 | ret = __adfs_dir_get(dir, dir->pos, obj); | ||
376 | if (ret == 0) | ||
377 | dir->pos += 26; | ||
378 | |||
379 | return ret; | ||
380 | } | ||
381 | |||
382 | static int | ||
383 | adfs_f_update(struct adfs_dir *dir, struct object_info *obj) | ||
384 | { | ||
385 | struct super_block *sb = dir->sb; | ||
386 | int ret, i; | ||
387 | |||
388 | ret = adfs_dir_find_entry(dir, obj->file_id); | ||
389 | if (ret < 0) { | ||
390 | adfs_error(dir->sb, "unable to locate entry to update"); | ||
391 | goto out; | ||
392 | } | ||
393 | |||
394 | __adfs_dir_put(dir, ret, obj); | ||
395 | |||
396 | /* | ||
397 | * Increment directory sequence number | ||
398 | */ | ||
399 | dir->bh[0]->b_data[0] += 1; | ||
400 | dir->bh[dir->nr_buffers - 1]->b_data[sb->s_blocksize - 6] += 1; | ||
401 | |||
402 | ret = adfs_dir_checkbyte(dir); | ||
403 | /* | ||
404 | * Update directory check byte | ||
405 | */ | ||
406 | dir->bh[dir->nr_buffers - 1]->b_data[sb->s_blocksize - 1] = ret; | ||
407 | |||
408 | #if 1 | ||
409 | { | ||
410 | const unsigned int blocksize_bits = sb->s_blocksize_bits; | ||
411 | |||
412 | memcpy(&dir->dirhead, bufoff(dir->bh, 0), sizeof(dir->dirhead)); | ||
413 | memcpy(&dir->dirtail, bufoff(dir->bh, 2007), sizeof(dir->dirtail)); | ||
414 | |||
415 | if (dir->dirhead.startmasseq != dir->dirtail.new.endmasseq || | ||
416 | memcmp(&dir->dirhead.startname, &dir->dirtail.new.endname, 4)) | ||
417 | goto bad_dir; | ||
418 | |||
419 | if (memcmp(&dir->dirhead.startname, "Nick", 4) && | ||
420 | memcmp(&dir->dirhead.startname, "Hugo", 4)) | ||
421 | goto bad_dir; | ||
422 | |||
423 | if (adfs_dir_checkbyte(dir) != dir->dirtail.new.dircheckbyte) | ||
424 | goto bad_dir; | ||
425 | } | ||
426 | #endif | ||
427 | for (i = dir->nr_buffers - 1; i >= 0; i--) | ||
428 | mark_buffer_dirty(dir->bh[i]); | ||
429 | |||
430 | ret = 0; | ||
431 | out: | ||
432 | return ret; | ||
433 | #if 1 | ||
434 | bad_dir: | ||
435 | adfs_error(dir->sb, "whoops! I broke a directory!"); | ||
436 | return -EIO; | ||
437 | #endif | ||
438 | } | ||
439 | |||
440 | static void | ||
441 | adfs_f_free(struct adfs_dir *dir) | ||
442 | { | ||
443 | int i; | ||
444 | |||
445 | for (i = dir->nr_buffers - 1; i >= 0; i--) { | ||
446 | brelse(dir->bh[i]); | ||
447 | dir->bh[i] = NULL; | ||
448 | } | ||
449 | |||
450 | dir->nr_buffers = 0; | ||
451 | dir->sb = NULL; | ||
452 | } | ||
453 | |||
454 | struct adfs_dir_ops adfs_f_dir_ops = { | ||
455 | .read = adfs_f_read, | ||
456 | .setpos = adfs_f_setpos, | ||
457 | .getnext = adfs_f_getnext, | ||
458 | .update = adfs_f_update, | ||
459 | .free = adfs_f_free | ||
460 | }; | ||
diff --git a/fs/adfs/dir_f.h b/fs/adfs/dir_f.h new file mode 100644 index 000000000000..e4713404096c --- /dev/null +++ b/fs/adfs/dir_f.h | |||
@@ -0,0 +1,65 @@ | |||
1 | /* | ||
2 | * linux/fs/adfs/dir_f.h | ||
3 | * | ||
4 | * Copyright (C) 1999 Russell King | ||
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 version 2 as | ||
8 | * published by the Free Software Foundation. | ||
9 | * | ||
10 | * Structures of directories on the F format disk | ||
11 | */ | ||
12 | #ifndef ADFS_DIR_F_H | ||
13 | #define ADFS_DIR_F_H | ||
14 | |||
15 | /* | ||
16 | * Directory header | ||
17 | */ | ||
18 | struct adfs_dirheader { | ||
19 | unsigned char startmasseq; | ||
20 | unsigned char startname[4]; | ||
21 | }; | ||
22 | |||
23 | #define ADFS_NEWDIR_SIZE 2048 | ||
24 | #define ADFS_NUM_DIR_ENTRIES 77 | ||
25 | |||
26 | /* | ||
27 | * Directory entries | ||
28 | */ | ||
29 | struct adfs_direntry { | ||
30 | #define ADFS_F_NAME_LEN 10 | ||
31 | char dirobname[ADFS_F_NAME_LEN]; | ||
32 | __u8 dirload[4]; | ||
33 | __u8 direxec[4]; | ||
34 | __u8 dirlen[4]; | ||
35 | __u8 dirinddiscadd[3]; | ||
36 | __u8 newdiratts; | ||
37 | }; | ||
38 | |||
39 | /* | ||
40 | * Directory tail | ||
41 | */ | ||
42 | union adfs_dirtail { | ||
43 | struct { | ||
44 | unsigned char dirlastmask; | ||
45 | char dirname[10]; | ||
46 | unsigned char dirparent[3]; | ||
47 | char dirtitle[19]; | ||
48 | unsigned char reserved[14]; | ||
49 | unsigned char endmasseq; | ||
50 | unsigned char endname[4]; | ||
51 | unsigned char dircheckbyte; | ||
52 | } old; | ||
53 | struct { | ||
54 | unsigned char dirlastmask; | ||
55 | unsigned char reserved[2]; | ||
56 | unsigned char dirparent[3]; | ||
57 | char dirtitle[19]; | ||
58 | char dirname[10]; | ||
59 | unsigned char endmasseq; | ||
60 | unsigned char endname[4]; | ||
61 | unsigned char dircheckbyte; | ||
62 | } new; | ||
63 | }; | ||
64 | |||
65 | #endif | ||
diff --git a/fs/adfs/dir_fplus.c b/fs/adfs/dir_fplus.c new file mode 100644 index 000000000000..1ec644e32df9 --- /dev/null +++ b/fs/adfs/dir_fplus.c | |||
@@ -0,0 +1,179 @@ | |||
1 | /* | ||
2 | * linux/fs/adfs/dir_fplus.c | ||
3 | * | ||
4 | * Copyright (C) 1997-1999 Russell King | ||
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 version 2 as | ||
8 | * published by the Free Software Foundation. | ||
9 | */ | ||
10 | #include <linux/errno.h> | ||
11 | #include <linux/fs.h> | ||
12 | #include <linux/adfs_fs.h> | ||
13 | #include <linux/time.h> | ||
14 | #include <linux/stat.h> | ||
15 | #include <linux/spinlock.h> | ||
16 | #include <linux/buffer_head.h> | ||
17 | #include <linux/string.h> | ||
18 | |||
19 | #include "adfs.h" | ||
20 | #include "dir_fplus.h" | ||
21 | |||
22 | static int | ||
23 | adfs_fplus_read(struct super_block *sb, unsigned int id, unsigned int sz, struct adfs_dir *dir) | ||
24 | { | ||
25 | struct adfs_bigdirheader *h; | ||
26 | struct adfs_bigdirtail *t; | ||
27 | unsigned long block; | ||
28 | unsigned int blk, size; | ||
29 | int i, ret = -EIO; | ||
30 | |||
31 | dir->nr_buffers = 0; | ||
32 | |||
33 | block = __adfs_block_map(sb, id, 0); | ||
34 | if (!block) { | ||
35 | adfs_error(sb, "dir object %X has a hole at offset 0", id); | ||
36 | goto out; | ||
37 | } | ||
38 | |||
39 | dir->bh[0] = sb_bread(sb, block); | ||
40 | if (!dir->bh[0]) | ||
41 | goto out; | ||
42 | dir->nr_buffers += 1; | ||
43 | |||
44 | h = (struct adfs_bigdirheader *)dir->bh[0]->b_data; | ||
45 | size = le32_to_cpu(h->bigdirsize); | ||
46 | if (size != sz) { | ||
47 | printk(KERN_WARNING "adfs: adfs_fplus_read: directory header size\n" | ||
48 | " does not match directory size\n"); | ||
49 | } | ||
50 | |||
51 | if (h->bigdirversion[0] != 0 || h->bigdirversion[1] != 0 || | ||
52 | h->bigdirversion[2] != 0 || size & 2047 || | ||
53 | h->bigdirstartname != cpu_to_le32(BIGDIRSTARTNAME)) | ||
54 | goto out; | ||
55 | |||
56 | size >>= sb->s_blocksize_bits; | ||
57 | for (blk = 1; blk < size; blk++) { | ||
58 | block = __adfs_block_map(sb, id, blk); | ||
59 | if (!block) { | ||
60 | adfs_error(sb, "dir object %X has a hole at offset %d", id, blk); | ||
61 | goto out; | ||
62 | } | ||
63 | |||
64 | dir->bh[blk] = sb_bread(sb, block); | ||
65 | if (!dir->bh[blk]) | ||
66 | goto out; | ||
67 | dir->nr_buffers = blk; | ||
68 | } | ||
69 | |||
70 | t = (struct adfs_bigdirtail *)(dir->bh[size - 1]->b_data + (sb->s_blocksize - 8)); | ||
71 | |||
72 | if (t->bigdirendname != cpu_to_le32(BIGDIRENDNAME) || | ||
73 | t->bigdirendmasseq != h->startmasseq || | ||
74 | t->reserved[0] != 0 || t->reserved[1] != 0) | ||
75 | goto out; | ||
76 | |||
77 | dir->parent_id = le32_to_cpu(h->bigdirparent); | ||
78 | dir->sb = sb; | ||
79 | return 0; | ||
80 | out: | ||
81 | for (i = 0; i < dir->nr_buffers; i++) | ||
82 | brelse(dir->bh[i]); | ||
83 | dir->sb = NULL; | ||
84 | return ret; | ||
85 | } | ||
86 | |||
87 | static int | ||
88 | adfs_fplus_setpos(struct adfs_dir *dir, unsigned int fpos) | ||
89 | { | ||
90 | struct adfs_bigdirheader *h = (struct adfs_bigdirheader *)dir->bh[0]->b_data; | ||
91 | int ret = -ENOENT; | ||
92 | |||
93 | if (fpos <= le32_to_cpu(h->bigdirentries)) { | ||
94 | dir->pos = fpos; | ||
95 | ret = 0; | ||
96 | } | ||
97 | |||
98 | return ret; | ||
99 | } | ||
100 | |||
101 | static void | ||
102 | dir_memcpy(struct adfs_dir *dir, unsigned int offset, void *to, int len) | ||
103 | { | ||
104 | struct super_block *sb = dir->sb; | ||
105 | unsigned int buffer, partial, remainder; | ||
106 | |||
107 | buffer = offset >> sb->s_blocksize_bits; | ||
108 | offset &= sb->s_blocksize - 1; | ||
109 | |||
110 | partial = sb->s_blocksize - offset; | ||
111 | |||
112 | if (partial >= len) | ||
113 | memcpy(to, dir->bh[buffer]->b_data + offset, len); | ||
114 | else { | ||
115 | char *c = (char *)to; | ||
116 | |||
117 | remainder = len - partial; | ||
118 | |||
119 | memcpy(c, dir->bh[buffer]->b_data + offset, partial); | ||
120 | memcpy(c + partial, dir->bh[buffer + 1]->b_data, remainder); | ||
121 | } | ||
122 | } | ||
123 | |||
124 | static int | ||
125 | adfs_fplus_getnext(struct adfs_dir *dir, struct object_info *obj) | ||
126 | { | ||
127 | struct adfs_bigdirheader *h = (struct adfs_bigdirheader *)dir->bh[0]->b_data; | ||
128 | struct adfs_bigdirentry bde; | ||
129 | unsigned int offset; | ||
130 | int i, ret = -ENOENT; | ||
131 | |||
132 | if (dir->pos >= le32_to_cpu(h->bigdirentries)) | ||
133 | goto out; | ||
134 | |||
135 | offset = offsetof(struct adfs_bigdirheader, bigdirname); | ||
136 | offset += ((le32_to_cpu(h->bigdirnamelen) + 4) & ~3); | ||
137 | offset += dir->pos * sizeof(struct adfs_bigdirentry); | ||
138 | |||
139 | dir_memcpy(dir, offset, &bde, sizeof(struct adfs_bigdirentry)); | ||
140 | |||
141 | obj->loadaddr = le32_to_cpu(bde.bigdirload); | ||
142 | obj->execaddr = le32_to_cpu(bde.bigdirexec); | ||
143 | obj->size = le32_to_cpu(bde.bigdirlen); | ||
144 | obj->file_id = le32_to_cpu(bde.bigdirindaddr); | ||
145 | obj->attr = le32_to_cpu(bde.bigdirattr); | ||
146 | obj->name_len = le32_to_cpu(bde.bigdirobnamelen); | ||
147 | |||
148 | offset = offsetof(struct adfs_bigdirheader, bigdirname); | ||
149 | offset += ((le32_to_cpu(h->bigdirnamelen) + 4) & ~3); | ||
150 | offset += le32_to_cpu(h->bigdirentries) * sizeof(struct adfs_bigdirentry); | ||
151 | offset += le32_to_cpu(bde.bigdirobnameptr); | ||
152 | |||
153 | dir_memcpy(dir, offset, obj->name, obj->name_len); | ||
154 | for (i = 0; i < obj->name_len; i++) | ||
155 | if (obj->name[i] == '/') | ||
156 | obj->name[i] = '.'; | ||
157 | |||
158 | dir->pos += 1; | ||
159 | ret = 0; | ||
160 | out: | ||
161 | return ret; | ||
162 | } | ||
163 | |||
164 | static void | ||
165 | adfs_fplus_free(struct adfs_dir *dir) | ||
166 | { | ||
167 | int i; | ||
168 | |||
169 | for (i = 0; i < dir->nr_buffers; i++) | ||
170 | brelse(dir->bh[i]); | ||
171 | dir->sb = NULL; | ||
172 | } | ||
173 | |||
174 | struct adfs_dir_ops adfs_fplus_dir_ops = { | ||
175 | .read = adfs_fplus_read, | ||
176 | .setpos = adfs_fplus_setpos, | ||
177 | .getnext = adfs_fplus_getnext, | ||
178 | .free = adfs_fplus_free | ||
179 | }; | ||
diff --git a/fs/adfs/dir_fplus.h b/fs/adfs/dir_fplus.h new file mode 100644 index 000000000000..b55aa41a68fe --- /dev/null +++ b/fs/adfs/dir_fplus.h | |||
@@ -0,0 +1,45 @@ | |||
1 | /* | ||
2 | * linux/fs/adfs/dir_fplus.h | ||
3 | * | ||
4 | * Copyright (C) 1999 Russell King | ||
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 version 2 as | ||
8 | * published by the Free Software Foundation. | ||
9 | * | ||
10 | * Structures of directories on the F+ format disk | ||
11 | */ | ||
12 | |||
13 | #define ADFS_FPLUS_NAME_LEN 255 | ||
14 | |||
15 | #define BIGDIRSTARTNAME ('S' | 'B' << 8 | 'P' << 16 | 'r' << 24) | ||
16 | #define BIGDIRENDNAME ('o' | 'v' << 8 | 'e' << 16 | 'n' << 24) | ||
17 | |||
18 | struct adfs_bigdirheader { | ||
19 | __u8 startmasseq; | ||
20 | __u8 bigdirversion[3]; | ||
21 | __le32 bigdirstartname; | ||
22 | __le32 bigdirnamelen; | ||
23 | __le32 bigdirsize; | ||
24 | __le32 bigdirentries; | ||
25 | __le32 bigdirnamesize; | ||
26 | __le32 bigdirparent; | ||
27 | char bigdirname[1]; | ||
28 | }; | ||
29 | |||
30 | struct adfs_bigdirentry { | ||
31 | __le32 bigdirload; | ||
32 | __le32 bigdirexec; | ||
33 | __le32 bigdirlen; | ||
34 | __le32 bigdirindaddr; | ||
35 | __le32 bigdirattr; | ||
36 | __le32 bigdirobnamelen; | ||
37 | __le32 bigdirobnameptr; | ||
38 | }; | ||
39 | |||
40 | struct adfs_bigdirtail { | ||
41 | __le32 bigdirendname; | ||
42 | __u8 bigdirendmasseq; | ||
43 | __u8 reserved[2]; | ||
44 | __u8 bigdircheckbyte; | ||
45 | }; | ||
diff --git a/fs/adfs/file.c b/fs/adfs/file.c new file mode 100644 index 000000000000..afebbfde6968 --- /dev/null +++ b/fs/adfs/file.c | |||
@@ -0,0 +1,43 @@ | |||
1 | /* | ||
2 | * linux/fs/adfs/file.c | ||
3 | * | ||
4 | * Copyright (C) 1997-1999 Russell King | ||
5 | * from: | ||
6 | * | ||
7 | * linux/fs/ext2/file.c | ||
8 | * | ||
9 | * Copyright (C) 1992, 1993, 1994, 1995 | ||
10 | * Remy Card (card@masi.ibp.fr) | ||
11 | * Laboratoire MASI - Institut Blaise Pascal | ||
12 | * Universite Pierre et Marie Curie (Paris VI) | ||
13 | * | ||
14 | * from | ||
15 | * | ||
16 | * linux/fs/minix/file.c | ||
17 | * | ||
18 | * Copyright (C) 1991, 1992 Linus Torvalds | ||
19 | * | ||
20 | * adfs regular file handling primitives | ||
21 | */ | ||
22 | #include <linux/errno.h> | ||
23 | #include <linux/fs.h> | ||
24 | #include <linux/fcntl.h> | ||
25 | #include <linux/time.h> | ||
26 | #include <linux/stat.h> | ||
27 | #include <linux/buffer_head.h> /* for file_fsync() */ | ||
28 | #include <linux/adfs_fs.h> | ||
29 | |||
30 | #include "adfs.h" | ||
31 | |||
32 | struct file_operations adfs_file_operations = { | ||
33 | .llseek = generic_file_llseek, | ||
34 | .read = generic_file_read, | ||
35 | .mmap = generic_file_mmap, | ||
36 | .fsync = file_fsync, | ||
37 | .write = generic_file_write, | ||
38 | .sendfile = generic_file_sendfile, | ||
39 | }; | ||
40 | |||
41 | struct inode_operations adfs_file_inode_operations = { | ||
42 | .setattr = adfs_notify_change, | ||
43 | }; | ||
diff --git a/fs/adfs/inode.c b/fs/adfs/inode.c new file mode 100644 index 000000000000..a02802a30798 --- /dev/null +++ b/fs/adfs/inode.c | |||
@@ -0,0 +1,395 @@ | |||
1 | /* | ||
2 | * linux/fs/adfs/inode.c | ||
3 | * | ||
4 | * Copyright (C) 1997-1999 Russell King | ||
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 version 2 as | ||
8 | * published by the Free Software Foundation. | ||
9 | */ | ||
10 | #include <linux/errno.h> | ||
11 | #include <linux/fs.h> | ||
12 | #include <linux/adfs_fs.h> | ||
13 | #include <linux/time.h> | ||
14 | #include <linux/stat.h> | ||
15 | #include <linux/string.h> | ||
16 | #include <linux/mm.h> | ||
17 | #include <linux/smp_lock.h> | ||
18 | #include <linux/module.h> | ||
19 | #include <linux/buffer_head.h> | ||
20 | |||
21 | #include "adfs.h" | ||
22 | |||
23 | /* | ||
24 | * Lookup/Create a block at offset 'block' into 'inode'. We currently do | ||
25 | * not support creation of new blocks, so we return -EIO for this case. | ||
26 | */ | ||
27 | static int | ||
28 | adfs_get_block(struct inode *inode, sector_t block, struct buffer_head *bh, | ||
29 | int create) | ||
30 | { | ||
31 | if (block < 0) | ||
32 | goto abort_negative; | ||
33 | |||
34 | if (!create) { | ||
35 | if (block >= inode->i_blocks) | ||
36 | goto abort_toobig; | ||
37 | |||
38 | block = __adfs_block_map(inode->i_sb, inode->i_ino, block); | ||
39 | if (block) | ||
40 | map_bh(bh, inode->i_sb, block); | ||
41 | return 0; | ||
42 | } | ||
43 | /* don't support allocation of blocks yet */ | ||
44 | return -EIO; | ||
45 | |||
46 | abort_negative: | ||
47 | adfs_error(inode->i_sb, "block %d < 0", block); | ||
48 | return -EIO; | ||
49 | |||
50 | abort_toobig: | ||
51 | return 0; | ||
52 | } | ||
53 | |||
54 | static int adfs_writepage(struct page *page, struct writeback_control *wbc) | ||
55 | { | ||
56 | return block_write_full_page(page, adfs_get_block, wbc); | ||
57 | } | ||
58 | |||
59 | static int adfs_readpage(struct file *file, struct page *page) | ||
60 | { | ||
61 | return block_read_full_page(page, adfs_get_block); | ||
62 | } | ||
63 | |||
64 | static int adfs_prepare_write(struct file *file, struct page *page, unsigned int from, unsigned int to) | ||
65 | { | ||
66 | return cont_prepare_write(page, from, to, adfs_get_block, | ||
67 | &ADFS_I(page->mapping->host)->mmu_private); | ||
68 | } | ||
69 | |||
70 | static sector_t _adfs_bmap(struct address_space *mapping, sector_t block) | ||
71 | { | ||
72 | return generic_block_bmap(mapping, block, adfs_get_block); | ||
73 | } | ||
74 | |||
75 | static struct address_space_operations adfs_aops = { | ||
76 | .readpage = adfs_readpage, | ||
77 | .writepage = adfs_writepage, | ||
78 | .sync_page = block_sync_page, | ||
79 | .prepare_write = adfs_prepare_write, | ||
80 | .commit_write = generic_commit_write, | ||
81 | .bmap = _adfs_bmap | ||
82 | }; | ||
83 | |||
84 | static inline unsigned int | ||
85 | adfs_filetype(struct inode *inode) | ||
86 | { | ||
87 | unsigned int type; | ||
88 | |||
89 | if (ADFS_I(inode)->stamped) | ||
90 | type = (ADFS_I(inode)->loadaddr >> 8) & 0xfff; | ||
91 | else | ||
92 | type = (unsigned int) -1; | ||
93 | |||
94 | return type; | ||
95 | } | ||
96 | |||
97 | /* | ||
98 | * Convert ADFS attributes and filetype to Linux permission. | ||
99 | */ | ||
100 | static umode_t | ||
101 | adfs_atts2mode(struct super_block *sb, struct inode *inode) | ||
102 | { | ||
103 | unsigned int filetype, attr = ADFS_I(inode)->attr; | ||
104 | umode_t mode, rmask; | ||
105 | struct adfs_sb_info *asb = ADFS_SB(sb); | ||
106 | |||
107 | if (attr & ADFS_NDA_DIRECTORY) { | ||
108 | mode = S_IRUGO & asb->s_owner_mask; | ||
109 | return S_IFDIR | S_IXUGO | mode; | ||
110 | } | ||
111 | |||
112 | filetype = adfs_filetype(inode); | ||
113 | |||
114 | switch (filetype) { | ||
115 | case 0xfc0: /* LinkFS */ | ||
116 | return S_IFLNK|S_IRWXUGO; | ||
117 | |||
118 | case 0xfe6: /* UnixExec */ | ||
119 | rmask = S_IRUGO | S_IXUGO; | ||
120 | break; | ||
121 | |||
122 | default: | ||
123 | rmask = S_IRUGO; | ||
124 | } | ||
125 | |||
126 | mode = S_IFREG; | ||
127 | |||
128 | if (attr & ADFS_NDA_OWNER_READ) | ||
129 | mode |= rmask & asb->s_owner_mask; | ||
130 | |||
131 | if (attr & ADFS_NDA_OWNER_WRITE) | ||
132 | mode |= S_IWUGO & asb->s_owner_mask; | ||
133 | |||
134 | if (attr & ADFS_NDA_PUBLIC_READ) | ||
135 | mode |= rmask & asb->s_other_mask; | ||
136 | |||
137 | if (attr & ADFS_NDA_PUBLIC_WRITE) | ||
138 | mode |= S_IWUGO & asb->s_other_mask; | ||
139 | return mode; | ||
140 | } | ||
141 | |||
142 | /* | ||
143 | * Convert Linux permission to ADFS attribute. We try to do the reverse | ||
144 | * of atts2mode, but there is not a 1:1 translation. | ||
145 | */ | ||
146 | static int | ||
147 | adfs_mode2atts(struct super_block *sb, struct inode *inode) | ||
148 | { | ||
149 | umode_t mode; | ||
150 | int attr; | ||
151 | struct adfs_sb_info *asb = ADFS_SB(sb); | ||
152 | |||
153 | /* FIXME: should we be able to alter a link? */ | ||
154 | if (S_ISLNK(inode->i_mode)) | ||
155 | return ADFS_I(inode)->attr; | ||
156 | |||
157 | if (S_ISDIR(inode->i_mode)) | ||
158 | attr = ADFS_NDA_DIRECTORY; | ||
159 | else | ||
160 | attr = 0; | ||
161 | |||
162 | mode = inode->i_mode & asb->s_owner_mask; | ||
163 | if (mode & S_IRUGO) | ||
164 | attr |= ADFS_NDA_OWNER_READ; | ||
165 | if (mode & S_IWUGO) | ||
166 | attr |= ADFS_NDA_OWNER_WRITE; | ||
167 | |||
168 | mode = inode->i_mode & asb->s_other_mask; | ||
169 | mode &= ~asb->s_owner_mask; | ||
170 | if (mode & S_IRUGO) | ||
171 | attr |= ADFS_NDA_PUBLIC_READ; | ||
172 | if (mode & S_IWUGO) | ||
173 | attr |= ADFS_NDA_PUBLIC_WRITE; | ||
174 | |||
175 | return attr; | ||
176 | } | ||
177 | |||
178 | /* | ||
179 | * Convert an ADFS time to Unix time. ADFS has a 40-bit centi-second time | ||
180 | * referenced to 1 Jan 1900 (til 2248) | ||
181 | */ | ||
182 | static void | ||
183 | adfs_adfs2unix_time(struct timespec *tv, struct inode *inode) | ||
184 | { | ||
185 | unsigned int high, low; | ||
186 | |||
187 | if (ADFS_I(inode)->stamped == 0) | ||
188 | goto cur_time; | ||
189 | |||
190 | high = ADFS_I(inode)->loadaddr << 24; | ||
191 | low = ADFS_I(inode)->execaddr; | ||
192 | |||
193 | high |= low >> 8; | ||
194 | low &= 255; | ||
195 | |||
196 | /* Files dated pre 01 Jan 1970 00:00:00. */ | ||
197 | if (high < 0x336e996a) | ||
198 | goto too_early; | ||
199 | |||
200 | /* Files dated post 18 Jan 2038 03:14:05. */ | ||
201 | if (high >= 0x656e9969) | ||
202 | goto too_late; | ||
203 | |||
204 | /* discard 2208988800 (0x336e996a00) seconds of time */ | ||
205 | high -= 0x336e996a; | ||
206 | |||
207 | /* convert 40-bit centi-seconds to 32-bit seconds */ | ||
208 | tv->tv_sec = (((high % 100) << 8) + low) / 100 + (high / 100 << 8); | ||
209 | tv->tv_nsec = 0; | ||
210 | return; | ||
211 | |||
212 | cur_time: | ||
213 | *tv = CURRENT_TIME_SEC; | ||
214 | return; | ||
215 | |||
216 | too_early: | ||
217 | tv->tv_sec = tv->tv_nsec = 0; | ||
218 | return; | ||
219 | |||
220 | too_late: | ||
221 | tv->tv_sec = 0x7ffffffd; | ||
222 | tv->tv_nsec = 0; | ||
223 | return; | ||
224 | } | ||
225 | |||
226 | /* | ||
227 | * Convert an Unix time to ADFS time. We only do this if the entry has a | ||
228 | * time/date stamp already. | ||
229 | */ | ||
230 | static void | ||
231 | adfs_unix2adfs_time(struct inode *inode, unsigned int secs) | ||
232 | { | ||
233 | unsigned int high, low; | ||
234 | |||
235 | if (ADFS_I(inode)->stamped) { | ||
236 | /* convert 32-bit seconds to 40-bit centi-seconds */ | ||
237 | low = (secs & 255) * 100; | ||
238 | high = (secs / 256) * 100 + (low >> 8) + 0x336e996a; | ||
239 | |||
240 | ADFS_I(inode)->loadaddr = (high >> 24) | | ||
241 | (ADFS_I(inode)->loadaddr & ~0xff); | ||
242 | ADFS_I(inode)->execaddr = (low & 255) | (high << 8); | ||
243 | } | ||
244 | } | ||
245 | |||
246 | /* | ||
247 | * Fill in the inode information from the object information. | ||
248 | * | ||
249 | * Note that this is an inode-less filesystem, so we can't use the inode | ||
250 | * number to reference the metadata on the media. Instead, we use the | ||
251 | * inode number to hold the object ID, which in turn will tell us where | ||
252 | * the data is held. We also save the parent object ID, and with these | ||
253 | * two, we can locate the metadata. | ||
254 | * | ||
255 | * This does mean that we rely on an objects parent remaining the same at | ||
256 | * all times - we cannot cope with a cross-directory rename (yet). | ||
257 | */ | ||
258 | struct inode * | ||
259 | adfs_iget(struct super_block *sb, struct object_info *obj) | ||
260 | { | ||
261 | struct inode *inode; | ||
262 | |||
263 | inode = new_inode(sb); | ||
264 | if (!inode) | ||
265 | goto out; | ||
266 | |||
267 | inode->i_uid = ADFS_SB(sb)->s_uid; | ||
268 | inode->i_gid = ADFS_SB(sb)->s_gid; | ||
269 | inode->i_ino = obj->file_id; | ||
270 | inode->i_size = obj->size; | ||
271 | inode->i_nlink = 2; | ||
272 | inode->i_blksize = PAGE_SIZE; | ||
273 | inode->i_blocks = (inode->i_size + sb->s_blocksize - 1) >> | ||
274 | sb->s_blocksize_bits; | ||
275 | |||
276 | /* | ||
277 | * we need to save the parent directory ID so that | ||
278 | * write_inode can update the directory information | ||
279 | * for this file. This will need special handling | ||
280 | * for cross-directory renames. | ||
281 | */ | ||
282 | ADFS_I(inode)->parent_id = obj->parent_id; | ||
283 | ADFS_I(inode)->loadaddr = obj->loadaddr; | ||
284 | ADFS_I(inode)->execaddr = obj->execaddr; | ||
285 | ADFS_I(inode)->attr = obj->attr; | ||
286 | ADFS_I(inode)->stamped = ((obj->loadaddr & 0xfff00000) == 0xfff00000); | ||
287 | |||
288 | inode->i_mode = adfs_atts2mode(sb, inode); | ||
289 | adfs_adfs2unix_time(&inode->i_mtime, inode); | ||
290 | inode->i_atime = inode->i_mtime; | ||
291 | inode->i_ctime = inode->i_mtime; | ||
292 | |||
293 | if (S_ISDIR(inode->i_mode)) { | ||
294 | inode->i_op = &adfs_dir_inode_operations; | ||
295 | inode->i_fop = &adfs_dir_operations; | ||
296 | } else if (S_ISREG(inode->i_mode)) { | ||
297 | inode->i_op = &adfs_file_inode_operations; | ||
298 | inode->i_fop = &adfs_file_operations; | ||
299 | inode->i_mapping->a_ops = &adfs_aops; | ||
300 | ADFS_I(inode)->mmu_private = inode->i_size; | ||
301 | } | ||
302 | |||
303 | insert_inode_hash(inode); | ||
304 | |||
305 | out: | ||
306 | return inode; | ||
307 | } | ||
308 | |||
309 | /* | ||
310 | * Validate and convert a changed access mode/time to their ADFS equivalents. | ||
311 | * adfs_write_inode will actually write the information back to the directory | ||
312 | * later. | ||
313 | */ | ||
314 | int | ||
315 | adfs_notify_change(struct dentry *dentry, struct iattr *attr) | ||
316 | { | ||
317 | struct inode *inode = dentry->d_inode; | ||
318 | struct super_block *sb = inode->i_sb; | ||
319 | unsigned int ia_valid = attr->ia_valid; | ||
320 | int error; | ||
321 | |||
322 | lock_kernel(); | ||
323 | |||
324 | error = inode_change_ok(inode, attr); | ||
325 | |||
326 | /* | ||
327 | * we can't change the UID or GID of any file - | ||
328 | * we have a global UID/GID in the superblock | ||
329 | */ | ||
330 | if ((ia_valid & ATTR_UID && attr->ia_uid != ADFS_SB(sb)->s_uid) || | ||
331 | (ia_valid & ATTR_GID && attr->ia_gid != ADFS_SB(sb)->s_gid)) | ||
332 | error = -EPERM; | ||
333 | |||
334 | if (error) | ||
335 | goto out; | ||
336 | |||
337 | if (ia_valid & ATTR_SIZE) | ||
338 | error = vmtruncate(inode, attr->ia_size); | ||
339 | |||
340 | if (error) | ||
341 | goto out; | ||
342 | |||
343 | if (ia_valid & ATTR_MTIME) { | ||
344 | inode->i_mtime = attr->ia_mtime; | ||
345 | adfs_unix2adfs_time(inode, attr->ia_mtime.tv_sec); | ||
346 | } | ||
347 | /* | ||
348 | * FIXME: should we make these == to i_mtime since we don't | ||
349 | * have the ability to represent them in our filesystem? | ||
350 | */ | ||
351 | if (ia_valid & ATTR_ATIME) | ||
352 | inode->i_atime = attr->ia_atime; | ||
353 | if (ia_valid & ATTR_CTIME) | ||
354 | inode->i_ctime = attr->ia_ctime; | ||
355 | if (ia_valid & ATTR_MODE) { | ||
356 | ADFS_I(inode)->attr = adfs_mode2atts(sb, inode); | ||
357 | inode->i_mode = adfs_atts2mode(sb, inode); | ||
358 | } | ||
359 | |||
360 | /* | ||
361 | * FIXME: should we be marking this inode dirty even if | ||
362 | * we don't have any metadata to write back? | ||
363 | */ | ||
364 | if (ia_valid & (ATTR_SIZE | ATTR_MTIME | ATTR_MODE)) | ||
365 | mark_inode_dirty(inode); | ||
366 | out: | ||
367 | unlock_kernel(); | ||
368 | return error; | ||
369 | } | ||
370 | |||
371 | /* | ||
372 | * write an existing inode back to the directory, and therefore the disk. | ||
373 | * The adfs-specific inode data has already been updated by | ||
374 | * adfs_notify_change() | ||
375 | */ | ||
376 | int adfs_write_inode(struct inode *inode, int unused) | ||
377 | { | ||
378 | struct super_block *sb = inode->i_sb; | ||
379 | struct object_info obj; | ||
380 | int ret; | ||
381 | |||
382 | lock_kernel(); | ||
383 | obj.file_id = inode->i_ino; | ||
384 | obj.name_len = 0; | ||
385 | obj.parent_id = ADFS_I(inode)->parent_id; | ||
386 | obj.loadaddr = ADFS_I(inode)->loadaddr; | ||
387 | obj.execaddr = ADFS_I(inode)->execaddr; | ||
388 | obj.attr = ADFS_I(inode)->attr; | ||
389 | obj.size = inode->i_size; | ||
390 | |||
391 | ret = adfs_dir_update(sb, &obj); | ||
392 | unlock_kernel(); | ||
393 | return ret; | ||
394 | } | ||
395 | MODULE_LICENSE("GPL"); | ||
diff --git a/fs/adfs/map.c b/fs/adfs/map.c new file mode 100644 index 000000000000..92ab4fbc2031 --- /dev/null +++ b/fs/adfs/map.c | |||
@@ -0,0 +1,296 @@ | |||
1 | /* | ||
2 | * linux/fs/adfs/map.c | ||
3 | * | ||
4 | * Copyright (C) 1997-2002 Russell King | ||
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 version 2 as | ||
8 | * published by the Free Software Foundation. | ||
9 | */ | ||
10 | #include <linux/errno.h> | ||
11 | #include <linux/fs.h> | ||
12 | #include <linux/adfs_fs.h> | ||
13 | #include <linux/spinlock.h> | ||
14 | #include <linux/buffer_head.h> | ||
15 | |||
16 | #include <asm/unaligned.h> | ||
17 | |||
18 | #include "adfs.h" | ||
19 | |||
20 | /* | ||
21 | * The ADFS map is basically a set of sectors. Each sector is called a | ||
22 | * zone which contains a bitstream made up of variable sized fragments. | ||
23 | * Each bit refers to a set of bytes in the filesystem, defined by | ||
24 | * log2bpmb. This may be larger or smaller than the sector size, but | ||
25 | * the overall size it describes will always be a round number of | ||
26 | * sectors. A fragment id is always idlen bits long. | ||
27 | * | ||
28 | * < idlen > < n > <1> | ||
29 | * +---------+-------//---------+---+ | ||
30 | * | frag id | 0000....000000 | 1 | | ||
31 | * +---------+-------//---------+---+ | ||
32 | * | ||
33 | * The physical disk space used by a fragment is taken from the start of | ||
34 | * the fragment id up to and including the '1' bit - ie, idlen + n + 1 | ||
35 | * bits. | ||
36 | * | ||
37 | * A fragment id can be repeated multiple times in the whole map for | ||
38 | * large or fragmented files. The first map zone a fragment starts in | ||
39 | * is given by fragment id / ids_per_zone - this allows objects to start | ||
40 | * from any zone on the disk. | ||
41 | * | ||
42 | * Free space is described by a linked list of fragments. Each free | ||
43 | * fragment describes free space in the same way as the other fragments, | ||
44 | * however, the frag id specifies an offset (in map bits) from the end | ||
45 | * of this fragment to the start of the next free fragment. | ||
46 | * | ||
47 | * Objects stored on the disk are allocated object ids (we use these as | ||
48 | * our inode numbers.) Object ids contain a fragment id and an optional | ||
49 | * offset. This allows a directory fragment to contain small files | ||
50 | * associated with that directory. | ||
51 | */ | ||
52 | |||
53 | /* | ||
54 | * For the future... | ||
55 | */ | ||
56 | static DEFINE_RWLOCK(adfs_map_lock); | ||
57 | |||
58 | /* | ||
59 | * This is fun. We need to load up to 19 bits from the map at an | ||
60 | * arbitary bit alignment. (We're limited to 19 bits by F+ version 2). | ||
61 | */ | ||
62 | #define GET_FRAG_ID(_map,_start,_idmask) \ | ||
63 | ({ \ | ||
64 | unsigned char *_m = _map + (_start >> 3); \ | ||
65 | u32 _frag = get_unaligned((u32 *)_m); \ | ||
66 | _frag >>= (_start & 7); \ | ||
67 | _frag & _idmask; \ | ||
68 | }) | ||
69 | |||
70 | /* | ||
71 | * return the map bit offset of the fragment frag_id in the zone dm. | ||
72 | * Note that the loop is optimised for best asm code - look at the | ||
73 | * output of: | ||
74 | * gcc -D__KERNEL__ -O2 -I../../include -o - -S map.c | ||
75 | */ | ||
76 | static int | ||
77 | lookup_zone(const struct adfs_discmap *dm, const unsigned int idlen, | ||
78 | const unsigned int frag_id, unsigned int *offset) | ||
79 | { | ||
80 | const unsigned int mapsize = dm->dm_endbit; | ||
81 | const u32 idmask = (1 << idlen) - 1; | ||
82 | unsigned char *map = dm->dm_bh->b_data + 4; | ||
83 | unsigned int start = dm->dm_startbit; | ||
84 | unsigned int mapptr; | ||
85 | u32 frag; | ||
86 | |||
87 | do { | ||
88 | frag = GET_FRAG_ID(map, start, idmask); | ||
89 | mapptr = start + idlen; | ||
90 | |||
91 | /* | ||
92 | * find end of fragment | ||
93 | */ | ||
94 | { | ||
95 | __le32 *_map = (__le32 *)map; | ||
96 | u32 v = le32_to_cpu(_map[mapptr >> 5]) >> (mapptr & 31); | ||
97 | while (v == 0) { | ||
98 | mapptr = (mapptr & ~31) + 32; | ||
99 | if (mapptr >= mapsize) | ||
100 | goto error; | ||
101 | v = le32_to_cpu(_map[mapptr >> 5]); | ||
102 | } | ||
103 | |||
104 | mapptr += 1 + ffz(~v); | ||
105 | } | ||
106 | |||
107 | if (frag == frag_id) | ||
108 | goto found; | ||
109 | again: | ||
110 | start = mapptr; | ||
111 | } while (mapptr < mapsize); | ||
112 | return -1; | ||
113 | |||
114 | error: | ||
115 | printk(KERN_ERR "adfs: oversized fragment 0x%x at 0x%x-0x%x\n", | ||
116 | frag, start, mapptr); | ||
117 | return -1; | ||
118 | |||
119 | found: | ||
120 | { | ||
121 | int length = mapptr - start; | ||
122 | if (*offset >= length) { | ||
123 | *offset -= length; | ||
124 | goto again; | ||
125 | } | ||
126 | } | ||
127 | return start + *offset; | ||
128 | } | ||
129 | |||
130 | /* | ||
131 | * Scan the free space map, for this zone, calculating the total | ||
132 | * number of map bits in each free space fragment. | ||
133 | * | ||
134 | * Note: idmask is limited to 15 bits [3.2] | ||
135 | */ | ||
136 | static unsigned int | ||
137 | scan_free_map(struct adfs_sb_info *asb, struct adfs_discmap *dm) | ||
138 | { | ||
139 | const unsigned int mapsize = dm->dm_endbit + 32; | ||
140 | const unsigned int idlen = asb->s_idlen; | ||
141 | const unsigned int frag_idlen = idlen <= 15 ? idlen : 15; | ||
142 | const u32 idmask = (1 << frag_idlen) - 1; | ||
143 | unsigned char *map = dm->dm_bh->b_data; | ||
144 | unsigned int start = 8, mapptr; | ||
145 | u32 frag; | ||
146 | unsigned long total = 0; | ||
147 | |||
148 | /* | ||
149 | * get fragment id | ||
150 | */ | ||
151 | frag = GET_FRAG_ID(map, start, idmask); | ||
152 | |||
153 | /* | ||
154 | * If the freelink is null, then no free fragments | ||
155 | * exist in this zone. | ||
156 | */ | ||
157 | if (frag == 0) | ||
158 | return 0; | ||
159 | |||
160 | do { | ||
161 | start += frag; | ||
162 | |||
163 | /* | ||
164 | * get fragment id | ||
165 | */ | ||
166 | frag = GET_FRAG_ID(map, start, idmask); | ||
167 | mapptr = start + idlen; | ||
168 | |||
169 | /* | ||
170 | * find end of fragment | ||
171 | */ | ||
172 | { | ||
173 | __le32 *_map = (__le32 *)map; | ||
174 | u32 v = le32_to_cpu(_map[mapptr >> 5]) >> (mapptr & 31); | ||
175 | while (v == 0) { | ||
176 | mapptr = (mapptr & ~31) + 32; | ||
177 | if (mapptr >= mapsize) | ||
178 | goto error; | ||
179 | v = le32_to_cpu(_map[mapptr >> 5]); | ||
180 | } | ||
181 | |||
182 | mapptr += 1 + ffz(~v); | ||
183 | } | ||
184 | |||
185 | total += mapptr - start; | ||
186 | } while (frag >= idlen + 1); | ||
187 | |||
188 | if (frag != 0) | ||
189 | printk(KERN_ERR "adfs: undersized free fragment\n"); | ||
190 | |||
191 | return total; | ||
192 | error: | ||
193 | printk(KERN_ERR "adfs: oversized free fragment\n"); | ||
194 | return 0; | ||
195 | } | ||
196 | |||
197 | static int | ||
198 | scan_map(struct adfs_sb_info *asb, unsigned int zone, | ||
199 | const unsigned int frag_id, unsigned int mapoff) | ||
200 | { | ||
201 | const unsigned int idlen = asb->s_idlen; | ||
202 | struct adfs_discmap *dm, *dm_end; | ||
203 | int result; | ||
204 | |||
205 | dm = asb->s_map + zone; | ||
206 | zone = asb->s_map_size; | ||
207 | dm_end = asb->s_map + zone; | ||
208 | |||
209 | do { | ||
210 | result = lookup_zone(dm, idlen, frag_id, &mapoff); | ||
211 | |||
212 | if (result != -1) | ||
213 | goto found; | ||
214 | |||
215 | dm ++; | ||
216 | if (dm == dm_end) | ||
217 | dm = asb->s_map; | ||
218 | } while (--zone > 0); | ||
219 | |||
220 | return -1; | ||
221 | found: | ||
222 | result -= dm->dm_startbit; | ||
223 | result += dm->dm_startblk; | ||
224 | |||
225 | return result; | ||
226 | } | ||
227 | |||
228 | /* | ||
229 | * calculate the amount of free blocks in the map. | ||
230 | * | ||
231 | * n=1 | ||
232 | * total_free = E(free_in_zone_n) | ||
233 | * nzones | ||
234 | */ | ||
235 | unsigned int | ||
236 | adfs_map_free(struct super_block *sb) | ||
237 | { | ||
238 | struct adfs_sb_info *asb = ADFS_SB(sb); | ||
239 | struct adfs_discmap *dm; | ||
240 | unsigned int total = 0; | ||
241 | unsigned int zone; | ||
242 | |||
243 | dm = asb->s_map; | ||
244 | zone = asb->s_map_size; | ||
245 | |||
246 | do { | ||
247 | total += scan_free_map(asb, dm++); | ||
248 | } while (--zone > 0); | ||
249 | |||
250 | return signed_asl(total, asb->s_map2blk); | ||
251 | } | ||
252 | |||
253 | int | ||
254 | adfs_map_lookup(struct super_block *sb, unsigned int frag_id, | ||
255 | unsigned int offset) | ||
256 | { | ||
257 | struct adfs_sb_info *asb = ADFS_SB(sb); | ||
258 | unsigned int zone, mapoff; | ||
259 | int result; | ||
260 | |||
261 | /* | ||
262 | * map & root fragment is special - it starts in the center of the | ||
263 | * disk. The other fragments start at zone (frag / ids_per_zone) | ||
264 | */ | ||
265 | if (frag_id == ADFS_ROOT_FRAG) | ||
266 | zone = asb->s_map_size >> 1; | ||
267 | else | ||
268 | zone = frag_id / asb->s_ids_per_zone; | ||
269 | |||
270 | if (zone >= asb->s_map_size) | ||
271 | goto bad_fragment; | ||
272 | |||
273 | /* Convert sector offset to map offset */ | ||
274 | mapoff = signed_asl(offset, -asb->s_map2blk); | ||
275 | |||
276 | read_lock(&adfs_map_lock); | ||
277 | result = scan_map(asb, zone, frag_id, mapoff); | ||
278 | read_unlock(&adfs_map_lock); | ||
279 | |||
280 | if (result > 0) { | ||
281 | unsigned int secoff; | ||
282 | |||
283 | /* Calculate sector offset into map block */ | ||
284 | secoff = offset - signed_asl(mapoff, asb->s_map2blk); | ||
285 | return secoff + signed_asl(result, asb->s_map2blk); | ||
286 | } | ||
287 | |||
288 | adfs_error(sb, "fragment 0x%04x at offset %d not found in map", | ||
289 | frag_id, offset); | ||
290 | return 0; | ||
291 | |||
292 | bad_fragment: | ||
293 | adfs_error(sb, "invalid fragment 0x%04x (zone = %d, max = %d)", | ||
294 | frag_id, zone, asb->s_map_size); | ||
295 | return 0; | ||
296 | } | ||
diff --git a/fs/adfs/super.c b/fs/adfs/super.c new file mode 100644 index 000000000000..243963228d10 --- /dev/null +++ b/fs/adfs/super.c | |||
@@ -0,0 +1,508 @@ | |||
1 | /* | ||
2 | * linux/fs/adfs/super.c | ||
3 | * | ||
4 | * Copyright (C) 1997-1999 Russell King | ||
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 version 2 as | ||
8 | * published by the Free Software Foundation. | ||
9 | */ | ||
10 | #include <linux/module.h> | ||
11 | #include <linux/errno.h> | ||
12 | #include <linux/fs.h> | ||
13 | #include <linux/adfs_fs.h> | ||
14 | #include <linux/slab.h> | ||
15 | #include <linux/time.h> | ||
16 | #include <linux/stat.h> | ||
17 | #include <linux/string.h> | ||
18 | #include <linux/init.h> | ||
19 | #include <linux/buffer_head.h> | ||
20 | #include <linux/vfs.h> | ||
21 | #include <linux/parser.h> | ||
22 | #include <linux/bitops.h> | ||
23 | |||
24 | #include <asm/uaccess.h> | ||
25 | #include <asm/system.h> | ||
26 | |||
27 | #include <stdarg.h> | ||
28 | |||
29 | #include "adfs.h" | ||
30 | #include "dir_f.h" | ||
31 | #include "dir_fplus.h" | ||
32 | |||
33 | void __adfs_error(struct super_block *sb, const char *function, const char *fmt, ...) | ||
34 | { | ||
35 | char error_buf[128]; | ||
36 | va_list args; | ||
37 | |||
38 | va_start(args, fmt); | ||
39 | vsprintf(error_buf, fmt, args); | ||
40 | va_end(args); | ||
41 | |||
42 | printk(KERN_CRIT "ADFS-fs error (device %s)%s%s: %s\n", | ||
43 | sb->s_id, function ? ": " : "", | ||
44 | function ? function : "", error_buf); | ||
45 | } | ||
46 | |||
47 | static int adfs_checkdiscrecord(struct adfs_discrecord *dr) | ||
48 | { | ||
49 | int i; | ||
50 | |||
51 | /* sector size must be 256, 512 or 1024 bytes */ | ||
52 | if (dr->log2secsize != 8 && | ||
53 | dr->log2secsize != 9 && | ||
54 | dr->log2secsize != 10) | ||
55 | return 1; | ||
56 | |||
57 | /* idlen must be at least log2secsize + 3 */ | ||
58 | if (dr->idlen < dr->log2secsize + 3) | ||
59 | return 1; | ||
60 | |||
61 | /* we cannot have such a large disc that we | ||
62 | * are unable to represent sector offsets in | ||
63 | * 32 bits. This works out at 2.0 TB. | ||
64 | */ | ||
65 | if (le32_to_cpu(dr->disc_size_high) >> dr->log2secsize) | ||
66 | return 1; | ||
67 | |||
68 | /* idlen must be no greater than 19 v2 [1.0] */ | ||
69 | if (dr->idlen > 19) | ||
70 | return 1; | ||
71 | |||
72 | /* reserved bytes should be zero */ | ||
73 | for (i = 0; i < sizeof(dr->unused52); i++) | ||
74 | if (dr->unused52[i] != 0) | ||
75 | return 1; | ||
76 | |||
77 | return 0; | ||
78 | } | ||
79 | |||
80 | static unsigned char adfs_calczonecheck(struct super_block *sb, unsigned char *map) | ||
81 | { | ||
82 | unsigned int v0, v1, v2, v3; | ||
83 | int i; | ||
84 | |||
85 | v0 = v1 = v2 = v3 = 0; | ||
86 | for (i = sb->s_blocksize - 4; i; i -= 4) { | ||
87 | v0 += map[i] + (v3 >> 8); | ||
88 | v3 &= 0xff; | ||
89 | v1 += map[i + 1] + (v0 >> 8); | ||
90 | v0 &= 0xff; | ||
91 | v2 += map[i + 2] + (v1 >> 8); | ||
92 | v1 &= 0xff; | ||
93 | v3 += map[i + 3] + (v2 >> 8); | ||
94 | v2 &= 0xff; | ||
95 | } | ||
96 | v0 += v3 >> 8; | ||
97 | v1 += map[1] + (v0 >> 8); | ||
98 | v2 += map[2] + (v1 >> 8); | ||
99 | v3 += map[3] + (v2 >> 8); | ||
100 | |||
101 | return v0 ^ v1 ^ v2 ^ v3; | ||
102 | } | ||
103 | |||
104 | static int adfs_checkmap(struct super_block *sb, struct adfs_discmap *dm) | ||
105 | { | ||
106 | unsigned char crosscheck = 0, zonecheck = 1; | ||
107 | int i; | ||
108 | |||
109 | for (i = 0; i < ADFS_SB(sb)->s_map_size; i++) { | ||
110 | unsigned char *map; | ||
111 | |||
112 | map = dm[i].dm_bh->b_data; | ||
113 | |||
114 | if (adfs_calczonecheck(sb, map) != map[0]) { | ||
115 | adfs_error(sb, "zone %d fails zonecheck", i); | ||
116 | zonecheck = 0; | ||
117 | } | ||
118 | crosscheck ^= map[3]; | ||
119 | } | ||
120 | if (crosscheck != 0xff) | ||
121 | adfs_error(sb, "crosscheck != 0xff"); | ||
122 | return crosscheck == 0xff && zonecheck; | ||
123 | } | ||
124 | |||
125 | static void adfs_put_super(struct super_block *sb) | ||
126 | { | ||
127 | int i; | ||
128 | struct adfs_sb_info *asb = ADFS_SB(sb); | ||
129 | |||
130 | for (i = 0; i < asb->s_map_size; i++) | ||
131 | brelse(asb->s_map[i].dm_bh); | ||
132 | kfree(asb->s_map); | ||
133 | kfree(asb); | ||
134 | sb->s_fs_info = NULL; | ||
135 | } | ||
136 | |||
137 | enum {Opt_uid, Opt_gid, Opt_ownmask, Opt_othmask, Opt_err}; | ||
138 | |||
139 | static match_table_t tokens = { | ||
140 | {Opt_uid, "uid=%u"}, | ||
141 | {Opt_gid, "gid=%u"}, | ||
142 | {Opt_ownmask, "ownmask=%o"}, | ||
143 | {Opt_othmask, "othmask=%o"}, | ||
144 | {Opt_err, NULL} | ||
145 | }; | ||
146 | |||
147 | static int parse_options(struct super_block *sb, char *options) | ||
148 | { | ||
149 | char *p; | ||
150 | struct adfs_sb_info *asb = ADFS_SB(sb); | ||
151 | int option; | ||
152 | |||
153 | if (!options) | ||
154 | return 0; | ||
155 | |||
156 | while ((p = strsep(&options, ",")) != NULL) { | ||
157 | substring_t args[MAX_OPT_ARGS]; | ||
158 | int token; | ||
159 | if (!*p) | ||
160 | continue; | ||
161 | |||
162 | token = match_token(p, tokens, args); | ||
163 | switch (token) { | ||
164 | case Opt_uid: | ||
165 | if (match_int(args, &option)) | ||
166 | return -EINVAL; | ||
167 | asb->s_uid = option; | ||
168 | break; | ||
169 | case Opt_gid: | ||
170 | if (match_int(args, &option)) | ||
171 | return -EINVAL; | ||
172 | asb->s_gid = option; | ||
173 | break; | ||
174 | case Opt_ownmask: | ||
175 | if (match_octal(args, &option)) | ||
176 | return -EINVAL; | ||
177 | asb->s_owner_mask = option; | ||
178 | break; | ||
179 | case Opt_othmask: | ||
180 | if (match_octal(args, &option)) | ||
181 | return -EINVAL; | ||
182 | asb->s_other_mask = option; | ||
183 | break; | ||
184 | default: | ||
185 | printk("ADFS-fs: unrecognised mount option \"%s\" " | ||
186 | "or missing value\n", p); | ||
187 | return -EINVAL; | ||
188 | } | ||
189 | } | ||
190 | return 0; | ||
191 | } | ||
192 | |||
193 | static int adfs_remount(struct super_block *sb, int *flags, char *data) | ||
194 | { | ||
195 | *flags |= MS_NODIRATIME; | ||
196 | return parse_options(sb, data); | ||
197 | } | ||
198 | |||
199 | static int adfs_statfs(struct super_block *sb, struct kstatfs *buf) | ||
200 | { | ||
201 | struct adfs_sb_info *asb = ADFS_SB(sb); | ||
202 | |||
203 | buf->f_type = ADFS_SUPER_MAGIC; | ||
204 | buf->f_namelen = asb->s_namelen; | ||
205 | buf->f_bsize = sb->s_blocksize; | ||
206 | buf->f_blocks = asb->s_size; | ||
207 | buf->f_files = asb->s_ids_per_zone * asb->s_map_size; | ||
208 | buf->f_bavail = | ||
209 | buf->f_bfree = adfs_map_free(sb); | ||
210 | buf->f_ffree = (long)(buf->f_bfree * buf->f_files) / (long)buf->f_blocks; | ||
211 | |||
212 | return 0; | ||
213 | } | ||
214 | |||
215 | static kmem_cache_t *adfs_inode_cachep; | ||
216 | |||
217 | static struct inode *adfs_alloc_inode(struct super_block *sb) | ||
218 | { | ||
219 | struct adfs_inode_info *ei; | ||
220 | ei = (struct adfs_inode_info *)kmem_cache_alloc(adfs_inode_cachep, SLAB_KERNEL); | ||
221 | if (!ei) | ||
222 | return NULL; | ||
223 | return &ei->vfs_inode; | ||
224 | } | ||
225 | |||
226 | static void adfs_destroy_inode(struct inode *inode) | ||
227 | { | ||
228 | kmem_cache_free(adfs_inode_cachep, ADFS_I(inode)); | ||
229 | } | ||
230 | |||
231 | static void init_once(void * foo, kmem_cache_t * cachep, unsigned long flags) | ||
232 | { | ||
233 | struct adfs_inode_info *ei = (struct adfs_inode_info *) foo; | ||
234 | |||
235 | if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) == | ||
236 | SLAB_CTOR_CONSTRUCTOR) | ||
237 | inode_init_once(&ei->vfs_inode); | ||
238 | } | ||
239 | |||
240 | static int init_inodecache(void) | ||
241 | { | ||
242 | adfs_inode_cachep = kmem_cache_create("adfs_inode_cache", | ||
243 | sizeof(struct adfs_inode_info), | ||
244 | 0, SLAB_RECLAIM_ACCOUNT, | ||
245 | init_once, NULL); | ||
246 | if (adfs_inode_cachep == NULL) | ||
247 | return -ENOMEM; | ||
248 | return 0; | ||
249 | } | ||
250 | |||
251 | static void destroy_inodecache(void) | ||
252 | { | ||
253 | if (kmem_cache_destroy(adfs_inode_cachep)) | ||
254 | printk(KERN_INFO "adfs_inode_cache: not all structures were freed\n"); | ||
255 | } | ||
256 | |||
257 | static struct super_operations adfs_sops = { | ||
258 | .alloc_inode = adfs_alloc_inode, | ||
259 | .destroy_inode = adfs_destroy_inode, | ||
260 | .write_inode = adfs_write_inode, | ||
261 | .put_super = adfs_put_super, | ||
262 | .statfs = adfs_statfs, | ||
263 | .remount_fs = adfs_remount, | ||
264 | }; | ||
265 | |||
266 | static struct adfs_discmap *adfs_read_map(struct super_block *sb, struct adfs_discrecord *dr) | ||
267 | { | ||
268 | struct adfs_discmap *dm; | ||
269 | unsigned int map_addr, zone_size, nzones; | ||
270 | int i, zone; | ||
271 | struct adfs_sb_info *asb = ADFS_SB(sb); | ||
272 | |||
273 | nzones = asb->s_map_size; | ||
274 | zone_size = (8 << dr->log2secsize) - le16_to_cpu(dr->zone_spare); | ||
275 | map_addr = (nzones >> 1) * zone_size - | ||
276 | ((nzones > 1) ? ADFS_DR_SIZE_BITS : 0); | ||
277 | map_addr = signed_asl(map_addr, asb->s_map2blk); | ||
278 | |||
279 | asb->s_ids_per_zone = zone_size / (asb->s_idlen + 1); | ||
280 | |||
281 | dm = kmalloc(nzones * sizeof(*dm), GFP_KERNEL); | ||
282 | if (dm == NULL) { | ||
283 | adfs_error(sb, "not enough memory"); | ||
284 | return NULL; | ||
285 | } | ||
286 | |||
287 | for (zone = 0; zone < nzones; zone++, map_addr++) { | ||
288 | dm[zone].dm_startbit = 0; | ||
289 | dm[zone].dm_endbit = zone_size; | ||
290 | dm[zone].dm_startblk = zone * zone_size - ADFS_DR_SIZE_BITS; | ||
291 | dm[zone].dm_bh = sb_bread(sb, map_addr); | ||
292 | |||
293 | if (!dm[zone].dm_bh) { | ||
294 | adfs_error(sb, "unable to read map"); | ||
295 | goto error_free; | ||
296 | } | ||
297 | } | ||
298 | |||
299 | /* adjust the limits for the first and last map zones */ | ||
300 | i = zone - 1; | ||
301 | dm[0].dm_startblk = 0; | ||
302 | dm[0].dm_startbit = ADFS_DR_SIZE_BITS; | ||
303 | dm[i].dm_endbit = (le32_to_cpu(dr->disc_size_high) << (32 - dr->log2bpmb)) + | ||
304 | (le32_to_cpu(dr->disc_size) >> dr->log2bpmb) + | ||
305 | (ADFS_DR_SIZE_BITS - i * zone_size); | ||
306 | |||
307 | if (adfs_checkmap(sb, dm)) | ||
308 | return dm; | ||
309 | |||
310 | adfs_error(sb, NULL, "map corrupted"); | ||
311 | |||
312 | error_free: | ||
313 | while (--zone >= 0) | ||
314 | brelse(dm[zone].dm_bh); | ||
315 | |||
316 | kfree(dm); | ||
317 | return NULL; | ||
318 | } | ||
319 | |||
320 | static inline unsigned long adfs_discsize(struct adfs_discrecord *dr, int block_bits) | ||
321 | { | ||
322 | unsigned long discsize; | ||
323 | |||
324 | discsize = le32_to_cpu(dr->disc_size_high) << (32 - block_bits); | ||
325 | discsize |= le32_to_cpu(dr->disc_size) >> block_bits; | ||
326 | |||
327 | return discsize; | ||
328 | } | ||
329 | |||
330 | static int adfs_fill_super(struct super_block *sb, void *data, int silent) | ||
331 | { | ||
332 | struct adfs_discrecord *dr; | ||
333 | struct buffer_head *bh; | ||
334 | struct object_info root_obj; | ||
335 | unsigned char *b_data; | ||
336 | struct adfs_sb_info *asb; | ||
337 | struct inode *root; | ||
338 | |||
339 | sb->s_flags |= MS_NODIRATIME; | ||
340 | |||
341 | asb = kmalloc(sizeof(*asb), GFP_KERNEL); | ||
342 | if (!asb) | ||
343 | return -ENOMEM; | ||
344 | sb->s_fs_info = asb; | ||
345 | memset(asb, 0, sizeof(*asb)); | ||
346 | |||
347 | /* set default options */ | ||
348 | asb->s_uid = 0; | ||
349 | asb->s_gid = 0; | ||
350 | asb->s_owner_mask = S_IRWXU; | ||
351 | asb->s_other_mask = S_IRWXG | S_IRWXO; | ||
352 | |||
353 | if (parse_options(sb, data)) | ||
354 | goto error; | ||
355 | |||
356 | sb_set_blocksize(sb, BLOCK_SIZE); | ||
357 | if (!(bh = sb_bread(sb, ADFS_DISCRECORD / BLOCK_SIZE))) { | ||
358 | adfs_error(sb, "unable to read superblock"); | ||
359 | goto error; | ||
360 | } | ||
361 | |||
362 | b_data = bh->b_data + (ADFS_DISCRECORD % BLOCK_SIZE); | ||
363 | |||
364 | if (adfs_checkbblk(b_data)) { | ||
365 | if (!silent) | ||
366 | printk("VFS: Can't find an adfs filesystem on dev " | ||
367 | "%s.\n", sb->s_id); | ||
368 | goto error_free_bh; | ||
369 | } | ||
370 | |||
371 | dr = (struct adfs_discrecord *)(b_data + ADFS_DR_OFFSET); | ||
372 | |||
373 | /* | ||
374 | * Do some sanity checks on the ADFS disc record | ||
375 | */ | ||
376 | if (adfs_checkdiscrecord(dr)) { | ||
377 | if (!silent) | ||
378 | printk("VPS: Can't find an adfs filesystem on dev " | ||
379 | "%s.\n", sb->s_id); | ||
380 | goto error_free_bh; | ||
381 | } | ||
382 | |||
383 | brelse(bh); | ||
384 | if (sb_set_blocksize(sb, 1 << dr->log2secsize)) { | ||
385 | bh = sb_bread(sb, ADFS_DISCRECORD / sb->s_blocksize); | ||
386 | if (!bh) { | ||
387 | adfs_error(sb, "couldn't read superblock on " | ||
388 | "2nd try."); | ||
389 | goto error; | ||
390 | } | ||
391 | b_data = bh->b_data + (ADFS_DISCRECORD % sb->s_blocksize); | ||
392 | if (adfs_checkbblk(b_data)) { | ||
393 | adfs_error(sb, "disc record mismatch, very weird!"); | ||
394 | goto error_free_bh; | ||
395 | } | ||
396 | dr = (struct adfs_discrecord *)(b_data + ADFS_DR_OFFSET); | ||
397 | } else { | ||
398 | if (!silent) | ||
399 | printk(KERN_ERR "VFS: Unsupported blocksize on dev " | ||
400 | "%s.\n", sb->s_id); | ||
401 | goto error; | ||
402 | } | ||
403 | |||
404 | /* | ||
405 | * blocksize on this device should now be set to the ADFS log2secsize | ||
406 | */ | ||
407 | |||
408 | sb->s_magic = ADFS_SUPER_MAGIC; | ||
409 | asb->s_idlen = dr->idlen; | ||
410 | asb->s_map_size = dr->nzones | (dr->nzones_high << 8); | ||
411 | asb->s_map2blk = dr->log2bpmb - dr->log2secsize; | ||
412 | asb->s_size = adfs_discsize(dr, sb->s_blocksize_bits); | ||
413 | asb->s_version = dr->format_version; | ||
414 | asb->s_log2sharesize = dr->log2sharesize; | ||
415 | |||
416 | asb->s_map = adfs_read_map(sb, dr); | ||
417 | if (!asb->s_map) | ||
418 | goto error_free_bh; | ||
419 | |||
420 | brelse(bh); | ||
421 | |||
422 | /* | ||
423 | * set up enough so that we can read an inode | ||
424 | */ | ||
425 | sb->s_op = &adfs_sops; | ||
426 | |||
427 | dr = (struct adfs_discrecord *)(asb->s_map[0].dm_bh->b_data + 4); | ||
428 | |||
429 | root_obj.parent_id = root_obj.file_id = le32_to_cpu(dr->root); | ||
430 | root_obj.name_len = 0; | ||
431 | root_obj.loadaddr = 0; | ||
432 | root_obj.execaddr = 0; | ||
433 | root_obj.size = ADFS_NEWDIR_SIZE; | ||
434 | root_obj.attr = ADFS_NDA_DIRECTORY | ADFS_NDA_OWNER_READ | | ||
435 | ADFS_NDA_OWNER_WRITE | ADFS_NDA_PUBLIC_READ; | ||
436 | |||
437 | /* | ||
438 | * If this is a F+ disk with variable length directories, | ||
439 | * get the root_size from the disc record. | ||
440 | */ | ||
441 | if (asb->s_version) { | ||
442 | root_obj.size = le32_to_cpu(dr->root_size); | ||
443 | asb->s_dir = &adfs_fplus_dir_ops; | ||
444 | asb->s_namelen = ADFS_FPLUS_NAME_LEN; | ||
445 | } else { | ||
446 | asb->s_dir = &adfs_f_dir_ops; | ||
447 | asb->s_namelen = ADFS_F_NAME_LEN; | ||
448 | } | ||
449 | |||
450 | root = adfs_iget(sb, &root_obj); | ||
451 | sb->s_root = d_alloc_root(root); | ||
452 | if (!sb->s_root) { | ||
453 | int i; | ||
454 | iput(root); | ||
455 | for (i = 0; i < asb->s_map_size; i++) | ||
456 | brelse(asb->s_map[i].dm_bh); | ||
457 | kfree(asb->s_map); | ||
458 | adfs_error(sb, "get root inode failed\n"); | ||
459 | goto error; | ||
460 | } else | ||
461 | sb->s_root->d_op = &adfs_dentry_operations; | ||
462 | return 0; | ||
463 | |||
464 | error_free_bh: | ||
465 | brelse(bh); | ||
466 | error: | ||
467 | sb->s_fs_info = NULL; | ||
468 | kfree(asb); | ||
469 | return -EINVAL; | ||
470 | } | ||
471 | |||
472 | static struct super_block *adfs_get_sb(struct file_system_type *fs_type, | ||
473 | int flags, const char *dev_name, void *data) | ||
474 | { | ||
475 | return get_sb_bdev(fs_type, flags, dev_name, data, adfs_fill_super); | ||
476 | } | ||
477 | |||
478 | static struct file_system_type adfs_fs_type = { | ||
479 | .owner = THIS_MODULE, | ||
480 | .name = "adfs", | ||
481 | .get_sb = adfs_get_sb, | ||
482 | .kill_sb = kill_block_super, | ||
483 | .fs_flags = FS_REQUIRES_DEV, | ||
484 | }; | ||
485 | |||
486 | static int __init init_adfs_fs(void) | ||
487 | { | ||
488 | int err = init_inodecache(); | ||
489 | if (err) | ||
490 | goto out1; | ||
491 | err = register_filesystem(&adfs_fs_type); | ||
492 | if (err) | ||
493 | goto out; | ||
494 | return 0; | ||
495 | out: | ||
496 | destroy_inodecache(); | ||
497 | out1: | ||
498 | return err; | ||
499 | } | ||
500 | |||
501 | static void __exit exit_adfs_fs(void) | ||
502 | { | ||
503 | unregister_filesystem(&adfs_fs_type); | ||
504 | destroy_inodecache(); | ||
505 | } | ||
506 | |||
507 | module_init(init_adfs_fs) | ||
508 | module_exit(exit_adfs_fs) | ||