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