- pre5:
[davej-history.git] / fs / adfs / dir_f.c
blob35564c3b15e1b9674ba2bc662091a9e66417c3f9
1 /*
2 * linux/fs/adfs/dir_f.c
4 * Copyright (C) 1997-1999 Russell King
6 * E and F format directory handling
7 */
8 #include <linux/version.h>
9 #include <linux/errno.h>
10 #include <linux/fs.h>
11 #include <linux/adfs_fs.h>
12 #include <linux/sched.h>
13 #include <linux/stat.h>
14 #include <linux/spinlock.h>
16 #include "adfs.h"
17 #include "dir_f.h"
19 static void adfs_f_free(struct adfs_dir *dir);
22 * Read an (unaligned) value of length 1..4 bytes
24 static inline unsigned int adfs_readval(unsigned char *p, int len)
26 unsigned int val = 0;
28 switch (len) {
29 case 4: val |= p[3] << 24;
30 case 3: val |= p[2] << 16;
31 case 2: val |= p[1] << 8;
32 default: val |= p[0];
34 return val;
37 static inline void adfs_writeval(unsigned char *p, int len, unsigned int val)
39 switch (len) {
40 case 4: p[3] = val >> 24;
41 case 3: p[2] = val >> 16;
42 case 2: p[1] = val >> 8;
43 default: p[0] = val;
47 static inline int adfs_readname(char *buf, char *ptr, int maxlen)
49 char *old_buf = buf;
51 while (*ptr >= ' ' && maxlen--) {
52 if (*ptr == '/')
53 *buf++ = '.';
54 else
55 *buf++ = *ptr;
56 ptr++;
58 *buf = '\0';
60 return buf - old_buf;
63 static inline void adfs_writename(char *to, char *from, int maxlen)
65 int i;
67 for (i = 0; i < maxlen; i++) {
68 if (from[i] == '\0')
69 break;
70 if (from[i] == '.')
71 to[i] = '/';
72 else
73 to[i] = from[i];
76 for (; i < maxlen; i++)
77 to[i] = '\0';
80 #define ror13(v) ((v >> 13) | (v << 19))
82 #define dir_u8(idx) \
83 ({ int _buf = idx >> blocksize_bits; \
84 int _off = idx - (_buf << blocksize_bits);\
85 *(u8 *)(bh[_buf]->b_data + _off); \
88 #define dir_u32(idx) \
89 ({ int _buf = idx >> blocksize_bits; \
90 int _off = idx - (_buf << blocksize_bits);\
91 *(u32 *)(bh[_buf]->b_data + _off); \
94 #define bufoff(_bh,_idx) \
95 ({ int _buf = _idx >> blocksize_bits; \
96 int _off = _idx - (_buf << blocksize_bits);\
97 (u8 *)(_bh[_buf]->b_data + _off); \
101 * There are some algorithms that are nice in
102 * assembler, but a bitch in C... This is one
103 * of them.
105 static u8
106 adfs_dir_checkbyte(const struct adfs_dir *dir)
108 struct buffer_head * const *bh = dir->bh;
109 const int blocksize_bits = dir->sb->s_blocksize_bits;
110 union { u32 *ptr32; u8 *ptr8; } ptr, end;
111 u32 dircheck = 0;
112 int last = 5 - 26;
113 int i = 0;
116 * Accumulate each word up to the last whole
117 * word of the last directory entry. This
118 * can spread across several buffer heads.
120 do {
121 last += 26;
122 do {
123 dircheck = cpu_to_le32(dir_u32(i)) ^ ror13(dircheck);
125 i += sizeof(u32);
126 } while (i < (last & ~3));
127 } while (dir_u8(last) != 0);
130 * Accumulate the last few bytes. These
131 * bytes will be within the same bh.
133 if (i != last) {
134 ptr.ptr8 = bufoff(bh, i);
135 end.ptr8 = ptr.ptr8 + last - i;
138 dircheck = *ptr.ptr8++ ^ ror13(dircheck);
139 while (ptr.ptr8 < end.ptr8);
143 * The directory tail is in the final bh
144 * Note that contary to the RISC OS PRMs,
145 * the first few bytes are NOT included
146 * in the check. All bytes are in the
147 * same bh.
149 ptr.ptr8 = bufoff(bh, 2008);
150 end.ptr8 = ptr.ptr8 + 36;
152 do {
153 unsigned int v = *ptr.ptr32++;
154 dircheck = cpu_to_le32(v) ^ ror13(dircheck);
155 } while (ptr.ptr32 < end.ptr32);
157 return (dircheck ^ (dircheck >> 8) ^ (dircheck >> 16) ^ (dircheck >> 24)) & 0xff;
161 * Read and check that a directory is valid
164 adfs_dir_read(struct super_block *sb, unsigned long object_id,
165 unsigned int size, struct adfs_dir *dir)
167 const unsigned int blocksize_bits = sb->s_blocksize_bits;
168 int blk = 0;
171 * Directories which are not a multiple of 2048 bytes
172 * are considered bad v2 [3.6]
174 if (size & 2047)
175 goto bad_dir;
177 size >>= blocksize_bits;
179 dir->nr_buffers = 0;
180 dir->sb = sb;
182 for (blk = 0; blk < size; blk++) {
183 int phys;
185 phys = __adfs_block_map(sb, object_id, blk);
186 if (!phys) {
187 adfs_error(sb, "dir object %lX has a hole at offset %d",
188 object_id, blk);
189 goto release_buffers;
192 dir->bh[blk] = bread(sb->s_dev, phys, sb->s_blocksize);
193 if (!dir->bh[blk])
194 goto release_buffers;
197 memcpy(&dir->dirhead, bufoff(dir->bh, 0), sizeof(dir->dirhead));
198 memcpy(&dir->dirtail, bufoff(dir->bh, 2007), sizeof(dir->dirtail));
200 if (dir->dirhead.startmasseq != dir->dirtail.new.endmasseq ||
201 memcmp(&dir->dirhead.startname, &dir->dirtail.new.endname, 4))
202 goto bad_dir;
204 if (memcmp(&dir->dirhead.startname, "Nick", 4) &&
205 memcmp(&dir->dirhead.startname, "Hugo", 4))
206 goto bad_dir;
208 if (adfs_dir_checkbyte(dir) != dir->dirtail.new.dircheckbyte)
209 goto bad_dir;
211 dir->nr_buffers = blk;
213 return 0;
215 bad_dir:
216 adfs_error(sb, "corrupted directory fragment %lX",
217 object_id);
218 release_buffers:
219 for (blk -= 1; blk >= 0; blk -= 1)
220 brelse(dir->bh[blk]);
222 dir->sb = NULL;
224 return -EIO;
228 * convert a disk-based directory entry to a Linux ADFS directory entry
230 static inline void
231 adfs_dir2obj(struct object_info *obj, struct adfs_direntry *de)
233 obj->name_len = adfs_readname(obj->name, de->dirobname, ADFS_F_NAME_LEN);
234 obj->file_id = adfs_readval(de->dirinddiscadd, 3);
235 obj->loadaddr = adfs_readval(de->dirload, 4);
236 obj->execaddr = adfs_readval(de->direxec, 4);
237 obj->size = adfs_readval(de->dirlen, 4);
238 obj->attr = de->newdiratts;
242 * convert a Linux ADFS directory entry to a disk-based directory entry
244 static inline void
245 adfs_obj2dir(struct adfs_direntry *de, struct object_info *obj)
247 adfs_writeval(de->dirinddiscadd, 3, obj->file_id);
248 adfs_writeval(de->dirload, 4, obj->loadaddr);
249 adfs_writeval(de->direxec, 4, obj->execaddr);
250 adfs_writeval(de->dirlen, 4, obj->size);
251 de->newdiratts = obj->attr;
255 * get a directory entry. Note that the caller is responsible
256 * for holding the relevent locks.
259 __adfs_dir_get(struct adfs_dir *dir, int pos, struct object_info *obj)
261 struct super_block *sb = dir->sb;
262 struct adfs_direntry de;
263 int thissize, buffer, offset;
265 buffer = pos >> sb->s_blocksize_bits;
267 if (buffer > dir->nr_buffers)
268 return -EINVAL;
270 offset = pos & (sb->s_blocksize - 1);
271 thissize = sb->s_blocksize - offset;
272 if (thissize > 26)
273 thissize = 26;
275 memcpy(&de, dir->bh[buffer]->b_data + offset, thissize);
276 if (thissize != 26)
277 memcpy(((char *)&de) + thissize, dir->bh[buffer + 1]->b_data,
278 26 - thissize);
280 if (!de.dirobname[0])
281 return -ENOENT;
283 adfs_dir2obj(obj, &de);
285 return 0;
289 __adfs_dir_put(struct adfs_dir *dir, int pos, struct object_info *obj)
291 struct super_block *sb = dir->sb;
292 struct adfs_direntry de;
293 int thissize, buffer, offset;
295 buffer = pos >> sb->s_blocksize_bits;
297 if (buffer > dir->nr_buffers)
298 return -EINVAL;
300 offset = pos & (sb->s_blocksize - 1);
301 thissize = sb->s_blocksize - offset;
302 if (thissize > 26)
303 thissize = 26;
306 * Get the entry in total
308 memcpy(&de, dir->bh[buffer]->b_data + offset, thissize);
309 if (thissize != 26)
310 memcpy(((char *)&de) + thissize, dir->bh[buffer + 1]->b_data,
311 26 - thissize);
314 * update it
316 adfs_obj2dir(&de, obj);
319 * Put the new entry back
321 memcpy(dir->bh[buffer]->b_data + offset, &de, thissize);
322 if (thissize != 26)
323 memcpy(dir->bh[buffer + 1]->b_data, ((char *)&de) + thissize,
324 26 - thissize);
326 return 0;
330 * the caller is responsible for holding the necessary
331 * locks.
333 static int
334 adfs_dir_find_entry(struct adfs_dir *dir, unsigned long object_id)
336 int pos, ret;
338 ret = -ENOENT;
340 for (pos = 5; pos < ADFS_NUM_DIR_ENTRIES * 26 + 5; pos += 26) {
341 struct object_info obj;
343 if (!__adfs_dir_get(dir, pos, &obj))
344 break;
346 if (obj.file_id == object_id) {
347 ret = pos;
348 break;
352 return ret;
355 static int
356 adfs_f_read(struct super_block *sb, unsigned int id, unsigned int sz, struct adfs_dir *dir)
358 int ret;
360 if (sz != ADFS_NEWDIR_SIZE)
361 return -EIO;
363 ret = adfs_dir_read(sb, id, sz, dir);
364 if (ret)
365 adfs_error(sb, "unable to read directory");
366 else
367 dir->parent_id = adfs_readval(dir->dirtail.new.dirparent, 3);
369 return ret;
372 static int
373 adfs_f_setpos(struct adfs_dir *dir, unsigned int fpos)
375 if (fpos >= ADFS_NUM_DIR_ENTRIES)
376 return -ENOENT;
378 dir->pos = 5 + fpos * 26;
379 return 0;
382 static int
383 adfs_f_getnext(struct adfs_dir *dir, struct object_info *obj)
385 unsigned int ret;
387 ret = __adfs_dir_get(dir, dir->pos, obj);
388 if (ret == 0)
389 dir->pos += 26;
391 return ret;
394 static int
395 adfs_f_update(struct adfs_dir *dir, struct object_info *obj)
397 struct super_block *sb = dir->sb;
398 int ret, i;
400 ret = adfs_dir_find_entry(dir, obj->file_id);
401 if (ret < 0) {
402 adfs_error(dir->sb, "unable to locate entry to update");
403 goto out;
406 __adfs_dir_put(dir, ret, obj);
409 * Increment directory sequence number
411 dir->bh[0]->b_data[0] += 1;
412 dir->bh[dir->nr_buffers - 1]->b_data[sb->s_blocksize - 6] += 1;
414 ret = adfs_dir_checkbyte(dir);
416 * Update directory check byte
418 dir->bh[dir->nr_buffers - 1]->b_data[sb->s_blocksize - 1] = ret;
420 #if 1
422 const unsigned int blocksize_bits = sb->s_blocksize_bits;
424 memcpy(&dir->dirhead, bufoff(dir->bh, 0), sizeof(dir->dirhead));
425 memcpy(&dir->dirtail, bufoff(dir->bh, 2007), sizeof(dir->dirtail));
427 if (dir->dirhead.startmasseq != dir->dirtail.new.endmasseq ||
428 memcmp(&dir->dirhead.startname, &dir->dirtail.new.endname, 4))
429 goto bad_dir;
431 if (memcmp(&dir->dirhead.startname, "Nick", 4) &&
432 memcmp(&dir->dirhead.startname, "Hugo", 4))
433 goto bad_dir;
435 if (adfs_dir_checkbyte(dir) != dir->dirtail.new.dircheckbyte)
436 goto bad_dir;
438 #endif
439 for (i = dir->nr_buffers - 1; i >= 0; i--)
440 mark_buffer_dirty(dir->bh[i]);
442 ret = 0;
443 out:
444 return ret;
445 #if 1
446 bad_dir:
447 adfs_error(dir->sb, "whoops! I broke a directory!");
448 return -EIO;
449 #endif
452 static void
453 adfs_f_free(struct adfs_dir *dir)
455 int i;
457 for (i = dir->nr_buffers - 1; i >= 0; i--) {
458 brelse(dir->bh[i]);
459 dir->bh[i] = NULL;
462 dir->nr_buffers = 0;
463 dir->sb = NULL;
466 struct adfs_dir_ops adfs_f_dir_ops = {
467 adfs_f_read,
468 adfs_f_setpos,
469 adfs_f_getnext,
470 adfs_f_update,
471 NULL,
472 NULL,
473 adfs_f_free