blob: 1bab896918ed1d71e26d6cc2cb80a0cf9de25e79 [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
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 */
Linus Torvalds1da177e2005-04-16 15:20:36 -070012#include <linux/buffer_head.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070013#include "adfs.h"
14#include "dir_f.h"
15
16static void adfs_f_free(struct adfs_dir *dir);
17
18/*
19 * Read an (unaligned) value of length 1..4 bytes
20 */
21static inline unsigned int adfs_readval(unsigned char *p, int len)
22{
23 unsigned int val = 0;
24
25 switch (len) {
26 case 4: val |= p[3] << 24;
27 case 3: val |= p[2] << 16;
28 case 2: val |= p[1] << 8;
29 default: val |= p[0];
30 }
31 return val;
32}
33
34static inline void adfs_writeval(unsigned char *p, int len, unsigned int val)
35{
36 switch (len) {
37 case 4: p[3] = val >> 24;
38 case 3: p[2] = val >> 16;
39 case 2: p[1] = val >> 8;
40 default: p[0] = val;
41 }
42}
43
44static inline int adfs_readname(char *buf, char *ptr, int maxlen)
45{
46 char *old_buf = buf;
47
James Bursa3223ea82007-01-05 16:36:28 -080048 while ((unsigned char)*ptr >= ' ' && maxlen--) {
Linus Torvalds1da177e2005-04-16 15:20:36 -070049 if (*ptr == '/')
50 *buf++ = '.';
51 else
52 *buf++ = *ptr;
53 ptr++;
54 }
Linus Torvalds1da177e2005-04-16 15:20:36 -070055
56 return buf - old_buf;
57}
58
59#define ror13(v) ((v >> 13) | (v << 19))
60
61#define dir_u8(idx) \
62 ({ int _buf = idx >> blocksize_bits; \
63 int _off = idx - (_buf << blocksize_bits);\
64 *(u8 *)(bh[_buf]->b_data + _off); \
65 })
66
67#define dir_u32(idx) \
68 ({ int _buf = idx >> blocksize_bits; \
69 int _off = idx - (_buf << blocksize_bits);\
70 *(__le32 *)(bh[_buf]->b_data + _off); \
71 })
72
73#define bufoff(_bh,_idx) \
74 ({ int _buf = _idx >> blocksize_bits; \
75 int _off = _idx - (_buf << blocksize_bits);\
76 (u8 *)(_bh[_buf]->b_data + _off); \
77 })
78
79/*
80 * There are some algorithms that are nice in
81 * assembler, but a bitch in C... This is one
82 * of them.
83 */
84static u8
85adfs_dir_checkbyte(const struct adfs_dir *dir)
86{
87 struct buffer_head * const *bh = dir->bh;
88 const int blocksize_bits = dir->sb->s_blocksize_bits;
89 union { __le32 *ptr32; u8 *ptr8; } ptr, end;
90 u32 dircheck = 0;
91 int last = 5 - 26;
92 int i = 0;
93
94 /*
95 * Accumulate each word up to the last whole
96 * word of the last directory entry. This
97 * can spread across several buffer heads.
98 */
99 do {
100 last += 26;
101 do {
102 dircheck = le32_to_cpu(dir_u32(i)) ^ ror13(dircheck);
103
104 i += sizeof(u32);
105 } while (i < (last & ~3));
106 } while (dir_u8(last) != 0);
107
108 /*
109 * Accumulate the last few bytes. These
110 * bytes will be within the same bh.
111 */
112 if (i != last) {
113 ptr.ptr8 = bufoff(bh, i);
114 end.ptr8 = ptr.ptr8 + last - i;
115
Harvey Harrisone5949052008-04-29 00:58:41 -0700116 do {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700117 dircheck = *ptr.ptr8++ ^ ror13(dircheck);
Harvey Harrisone5949052008-04-29 00:58:41 -0700118 } while (ptr.ptr8 < end.ptr8);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700119 }
120
121 /*
122 * The directory tail is in the final bh
123 * Note that contary to the RISC OS PRMs,
124 * the first few bytes are NOT included
125 * in the check. All bytes are in the
126 * same bh.
127 */
128 ptr.ptr8 = bufoff(bh, 2008);
129 end.ptr8 = ptr.ptr8 + 36;
130
131 do {
132 __le32 v = *ptr.ptr32++;
133 dircheck = le32_to_cpu(v) ^ ror13(dircheck);
134 } while (ptr.ptr32 < end.ptr32);
135
136 return (dircheck ^ (dircheck >> 8) ^ (dircheck >> 16) ^ (dircheck >> 24)) & 0xff;
137}
138
139/*
140 * Read and check that a directory is valid
141 */
142static int
143adfs_dir_read(struct super_block *sb, unsigned long object_id,
144 unsigned int size, struct adfs_dir *dir)
145{
146 const unsigned int blocksize_bits = sb->s_blocksize_bits;
147 int blk = 0;
148
149 /*
150 * Directories which are not a multiple of 2048 bytes
151 * are considered bad v2 [3.6]
152 */
153 if (size & 2047)
154 goto bad_dir;
155
156 size >>= blocksize_bits;
157
158 dir->nr_buffers = 0;
159 dir->sb = sb;
160
161 for (blk = 0; blk < size; blk++) {
162 int phys;
163
164 phys = __adfs_block_map(sb, object_id, blk);
165 if (!phys) {
166 adfs_error(sb, "dir object %lX has a hole at offset %d",
167 object_id, blk);
168 goto release_buffers;
169 }
170
171 dir->bh[blk] = sb_bread(sb, phys);
172 if (!dir->bh[blk])
173 goto release_buffers;
174 }
175
176 memcpy(&dir->dirhead, bufoff(dir->bh, 0), sizeof(dir->dirhead));
177 memcpy(&dir->dirtail, bufoff(dir->bh, 2007), sizeof(dir->dirtail));
178
179 if (dir->dirhead.startmasseq != dir->dirtail.new.endmasseq ||
180 memcmp(&dir->dirhead.startname, &dir->dirtail.new.endname, 4))
181 goto bad_dir;
182
183 if (memcmp(&dir->dirhead.startname, "Nick", 4) &&
184 memcmp(&dir->dirhead.startname, "Hugo", 4))
185 goto bad_dir;
186
187 if (adfs_dir_checkbyte(dir) != dir->dirtail.new.dircheckbyte)
188 goto bad_dir;
189
190 dir->nr_buffers = blk;
191
192 return 0;
193
194bad_dir:
195 adfs_error(sb, "corrupted directory fragment %lX",
196 object_id);
197release_buffers:
198 for (blk -= 1; blk >= 0; blk -= 1)
199 brelse(dir->bh[blk]);
200
201 dir->sb = NULL;
202
203 return -EIO;
204}
205
206/*
207 * convert a disk-based directory entry to a Linux ADFS directory entry
208 */
209static inline void
Stuart Swalesda23ef02011-03-22 16:35:06 -0700210adfs_dir2obj(struct adfs_dir *dir, struct object_info *obj,
211 struct adfs_direntry *de)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700212{
213 obj->name_len = adfs_readname(obj->name, de->dirobname, ADFS_F_NAME_LEN);
214 obj->file_id = adfs_readval(de->dirinddiscadd, 3);
215 obj->loadaddr = adfs_readval(de->dirload, 4);
216 obj->execaddr = adfs_readval(de->direxec, 4);
217 obj->size = adfs_readval(de->dirlen, 4);
218 obj->attr = de->newdiratts;
Stuart Swalesda23ef02011-03-22 16:35:06 -0700219
Russell King411c49b2019-03-24 12:57:32 +0000220 adfs_object_fixup(dir, obj);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700221}
222
223/*
224 * convert a Linux ADFS directory entry to a disk-based directory entry
225 */
226static inline void
227adfs_obj2dir(struct adfs_direntry *de, struct object_info *obj)
228{
229 adfs_writeval(de->dirinddiscadd, 3, obj->file_id);
230 adfs_writeval(de->dirload, 4, obj->loadaddr);
231 adfs_writeval(de->direxec, 4, obj->execaddr);
232 adfs_writeval(de->dirlen, 4, obj->size);
233 de->newdiratts = obj->attr;
234}
235
236/*
237 * get a directory entry. Note that the caller is responsible
238 * for holding the relevant locks.
239 */
240static int
241__adfs_dir_get(struct adfs_dir *dir, int pos, struct object_info *obj)
242{
243 struct super_block *sb = dir->sb;
244 struct adfs_direntry de;
245 int thissize, buffer, offset;
246
247 buffer = pos >> sb->s_blocksize_bits;
248
249 if (buffer > dir->nr_buffers)
250 return -EINVAL;
251
252 offset = pos & (sb->s_blocksize - 1);
253 thissize = sb->s_blocksize - offset;
254 if (thissize > 26)
255 thissize = 26;
256
257 memcpy(&de, dir->bh[buffer]->b_data + offset, thissize);
258 if (thissize != 26)
259 memcpy(((char *)&de) + thissize, dir->bh[buffer + 1]->b_data,
260 26 - thissize);
261
262 if (!de.dirobname[0])
263 return -ENOENT;
264
Stuart Swalesda23ef02011-03-22 16:35:06 -0700265 adfs_dir2obj(dir, obj, &de);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700266
267 return 0;
268}
269
270static int
271__adfs_dir_put(struct adfs_dir *dir, int pos, struct object_info *obj)
272{
273 struct super_block *sb = dir->sb;
274 struct adfs_direntry de;
275 int thissize, buffer, offset;
276
277 buffer = pos >> sb->s_blocksize_bits;
278
279 if (buffer > dir->nr_buffers)
280 return -EINVAL;
281
282 offset = pos & (sb->s_blocksize - 1);
283 thissize = sb->s_blocksize - offset;
284 if (thissize > 26)
285 thissize = 26;
286
287 /*
288 * Get the entry in total
289 */
290 memcpy(&de, dir->bh[buffer]->b_data + offset, thissize);
291 if (thissize != 26)
292 memcpy(((char *)&de) + thissize, dir->bh[buffer + 1]->b_data,
293 26 - thissize);
294
295 /*
296 * update it
297 */
298 adfs_obj2dir(&de, obj);
299
300 /*
301 * Put the new entry back
302 */
303 memcpy(dir->bh[buffer]->b_data + offset, &de, thissize);
304 if (thissize != 26)
305 memcpy(dir->bh[buffer + 1]->b_data, ((char *)&de) + thissize,
306 26 - thissize);
307
308 return 0;
309}
310
311/*
312 * the caller is responsible for holding the necessary
313 * locks.
314 */
315static int
316adfs_dir_find_entry(struct adfs_dir *dir, unsigned long object_id)
317{
318 int pos, ret;
319
320 ret = -ENOENT;
321
322 for (pos = 5; pos < ADFS_NUM_DIR_ENTRIES * 26 + 5; pos += 26) {
323 struct object_info obj;
324
325 if (!__adfs_dir_get(dir, pos, &obj))
326 break;
327
328 if (obj.file_id == object_id) {
329 ret = pos;
330 break;
331 }
332 }
333
334 return ret;
335}
336
337static int
338adfs_f_read(struct super_block *sb, unsigned int id, unsigned int sz, struct adfs_dir *dir)
339{
340 int ret;
341
342 if (sz != ADFS_NEWDIR_SIZE)
343 return -EIO;
344
345 ret = adfs_dir_read(sb, id, sz, dir);
346 if (ret)
347 adfs_error(sb, "unable to read directory");
348 else
349 dir->parent_id = adfs_readval(dir->dirtail.new.dirparent, 3);
350
351 return ret;
352}
353
354static int
355adfs_f_setpos(struct adfs_dir *dir, unsigned int fpos)
356{
357 if (fpos >= ADFS_NUM_DIR_ENTRIES)
358 return -ENOENT;
359
360 dir->pos = 5 + fpos * 26;
361 return 0;
362}
363
364static int
365adfs_f_getnext(struct adfs_dir *dir, struct object_info *obj)
366{
367 unsigned int ret;
368
369 ret = __adfs_dir_get(dir, dir->pos, obj);
370 if (ret == 0)
371 dir->pos += 26;
372
373 return ret;
374}
375
376static int
377adfs_f_update(struct adfs_dir *dir, struct object_info *obj)
378{
379 struct super_block *sb = dir->sb;
380 int ret, i;
381
382 ret = adfs_dir_find_entry(dir, obj->file_id);
383 if (ret < 0) {
384 adfs_error(dir->sb, "unable to locate entry to update");
385 goto out;
386 }
387
388 __adfs_dir_put(dir, ret, obj);
389
390 /*
391 * Increment directory sequence number
392 */
393 dir->bh[0]->b_data[0] += 1;
394 dir->bh[dir->nr_buffers - 1]->b_data[sb->s_blocksize - 6] += 1;
395
396 ret = adfs_dir_checkbyte(dir);
397 /*
398 * Update directory check byte
399 */
400 dir->bh[dir->nr_buffers - 1]->b_data[sb->s_blocksize - 1] = ret;
401
402#if 1
403 {
404 const unsigned int blocksize_bits = sb->s_blocksize_bits;
405
406 memcpy(&dir->dirhead, bufoff(dir->bh, 0), sizeof(dir->dirhead));
407 memcpy(&dir->dirtail, bufoff(dir->bh, 2007), sizeof(dir->dirtail));
408
409 if (dir->dirhead.startmasseq != dir->dirtail.new.endmasseq ||
410 memcmp(&dir->dirhead.startname, &dir->dirtail.new.endname, 4))
411 goto bad_dir;
412
413 if (memcmp(&dir->dirhead.startname, "Nick", 4) &&
414 memcmp(&dir->dirhead.startname, "Hugo", 4))
415 goto bad_dir;
416
417 if (adfs_dir_checkbyte(dir) != dir->dirtail.new.dircheckbyte)
418 goto bad_dir;
419 }
420#endif
421 for (i = dir->nr_buffers - 1; i >= 0; i--)
422 mark_buffer_dirty(dir->bh[i]);
423
424 ret = 0;
425out:
426 return ret;
427#if 1
428bad_dir:
429 adfs_error(dir->sb, "whoops! I broke a directory!");
430 return -EIO;
431#endif
432}
433
Al Viroffdc9062009-06-08 00:44:42 -0400434static int
435adfs_f_sync(struct adfs_dir *dir)
436{
437 int err = 0;
438 int i;
439
440 for (i = dir->nr_buffers - 1; i >= 0; i--) {
441 struct buffer_head *bh = dir->bh[i];
442 sync_dirty_buffer(bh);
443 if (buffer_req(bh) && !buffer_uptodate(bh))
444 err = -EIO;
445 }
446
447 return err;
448}
449
Linus Torvalds1da177e2005-04-16 15:20:36 -0700450static void
451adfs_f_free(struct adfs_dir *dir)
452{
453 int i;
454
455 for (i = dir->nr_buffers - 1; i >= 0; i--) {
456 brelse(dir->bh[i]);
457 dir->bh[i] = NULL;
458 }
459
460 dir->nr_buffers = 0;
461 dir->sb = NULL;
462}
463
Julia Lawall0125f502015-11-21 16:15:37 +0100464const struct adfs_dir_ops adfs_f_dir_ops = {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700465 .read = adfs_f_read,
466 .setpos = adfs_f_setpos,
467 .getnext = adfs_f_getnext,
468 .update = adfs_f_update,
Al Viroffdc9062009-06-08 00:44:42 -0400469 .sync = adfs_f_sync,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700470 .free = adfs_f_free
471};