Linux-libre 3.18.62-gnu
[librecmc/linux-libre.git] / fs / adfs / dir_fplus.c
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/buffer_head.h>
11 #include <linux/slab.h>
12 #include "adfs.h"
13 #include "dir_fplus.h"
14
15 static int
16 adfs_fplus_read(struct super_block *sb, unsigned int id, unsigned int sz, struct adfs_dir *dir)
17 {
18         struct adfs_bigdirheader *h;
19         struct adfs_bigdirtail *t;
20         unsigned long block;
21         unsigned int blk, size;
22         int i, ret = -EIO;
23
24         dir->nr_buffers = 0;
25
26         /* start off using fixed bh set - only alloc for big dirs */
27         dir->bh_fplus = &dir->bh[0];
28
29         block = __adfs_block_map(sb, id, 0);
30         if (!block) {
31                 adfs_error(sb, "dir object %X has a hole at offset 0", id);
32                 goto out;
33         }
34
35         dir->bh_fplus[0] = sb_bread(sb, block);
36         if (!dir->bh_fplus[0])
37                 goto out;
38         dir->nr_buffers += 1;
39
40         h = (struct adfs_bigdirheader *)dir->bh_fplus[0]->b_data;
41         size = le32_to_cpu(h->bigdirsize);
42         if (size != sz) {
43                 printk(KERN_WARNING "adfs: adfs_fplus_read:"
44                                         " directory header size %X\n"
45                                         " does not match directory size %X\n",
46                                         size, sz);
47         }
48
49         if (h->bigdirversion[0] != 0 || h->bigdirversion[1] != 0 ||
50             h->bigdirversion[2] != 0 || size & 2047 ||
51             h->bigdirstartname != cpu_to_le32(BIGDIRSTARTNAME)) {
52                 printk(KERN_WARNING "adfs: dir object %X has"
53                                         " malformed dir header\n", id);
54                 goto out;
55         }
56
57         size >>= sb->s_blocksize_bits;
58         if (size > ARRAY_SIZE(dir->bh)) {
59                 /* this directory is too big for fixed bh set, must allocate */
60                 struct buffer_head **bh_fplus =
61                         kcalloc(size, sizeof(struct buffer_head *),
62                                 GFP_KERNEL);
63                 if (!bh_fplus) {
64                         adfs_error(sb, "not enough memory for"
65                                         " dir object %X (%d blocks)", id, size);
66                         goto out;
67                 }
68                 dir->bh_fplus = bh_fplus;
69                 /* copy over the pointer to the block that we've already read */
70                 dir->bh_fplus[0] = dir->bh[0];
71         }
72
73         for (blk = 1; blk < size; blk++) {
74                 block = __adfs_block_map(sb, id, blk);
75                 if (!block) {
76                         adfs_error(sb, "dir object %X has a hole at offset %d", id, blk);
77                         goto out;
78                 }
79
80                 dir->bh_fplus[blk] = sb_bread(sb, block);
81                 if (!dir->bh_fplus[blk]) {
82                         adfs_error(sb,  "dir object %x failed read for offset %d, mapped block %lX",
83                                    id, blk, block);
84                         goto out;
85                 }
86
87                 dir->nr_buffers += 1;
88         }
89
90         t = (struct adfs_bigdirtail *)
91                 (dir->bh_fplus[size - 1]->b_data + (sb->s_blocksize - 8));
92
93         if (t->bigdirendname != cpu_to_le32(BIGDIRENDNAME) ||
94             t->bigdirendmasseq != h->startmasseq ||
95             t->reserved[0] != 0 || t->reserved[1] != 0) {
96                 printk(KERN_WARNING "adfs: dir object %X has "
97                                         "malformed dir end\n", id);
98                 goto out;
99         }
100
101         dir->parent_id = le32_to_cpu(h->bigdirparent);
102         dir->sb = sb;
103         return 0;
104
105 out:
106         if (dir->bh_fplus) {
107                 for (i = 0; i < dir->nr_buffers; i++)
108                         brelse(dir->bh_fplus[i]);
109
110                 if (&dir->bh[0] != dir->bh_fplus)
111                         kfree(dir->bh_fplus);
112
113                 dir->bh_fplus = NULL;
114         }
115
116         dir->nr_buffers = 0;
117         dir->sb = NULL;
118         return ret;
119 }
120
121 static int
122 adfs_fplus_setpos(struct adfs_dir *dir, unsigned int fpos)
123 {
124         struct adfs_bigdirheader *h =
125                 (struct adfs_bigdirheader *) dir->bh_fplus[0]->b_data;
126         int ret = -ENOENT;
127
128         if (fpos <= le32_to_cpu(h->bigdirentries)) {
129                 dir->pos = fpos;
130                 ret = 0;
131         }
132
133         return ret;
134 }
135
136 static void
137 dir_memcpy(struct adfs_dir *dir, unsigned int offset, void *to, int len)
138 {
139         struct super_block *sb = dir->sb;
140         unsigned int buffer, partial, remainder;
141
142         buffer = offset >> sb->s_blocksize_bits;
143         offset &= sb->s_blocksize - 1;
144
145         partial = sb->s_blocksize - offset;
146
147         if (partial >= len)
148                 memcpy(to, dir->bh_fplus[buffer]->b_data + offset, len);
149         else {
150                 char *c = (char *)to;
151
152                 remainder = len - partial;
153
154                 memcpy(c,
155                         dir->bh_fplus[buffer]->b_data + offset,
156                         partial);
157
158                 memcpy(c + partial,
159                         dir->bh_fplus[buffer + 1]->b_data,
160                         remainder);
161         }
162 }
163
164 static int
165 adfs_fplus_getnext(struct adfs_dir *dir, struct object_info *obj)
166 {
167         struct adfs_bigdirheader *h =
168                 (struct adfs_bigdirheader *) dir->bh_fplus[0]->b_data;
169         struct adfs_bigdirentry bde;
170         unsigned int offset;
171         int i, ret = -ENOENT;
172
173         if (dir->pos >= le32_to_cpu(h->bigdirentries))
174                 goto out;
175
176         offset = offsetof(struct adfs_bigdirheader, bigdirname);
177         offset += ((le32_to_cpu(h->bigdirnamelen) + 4) & ~3);
178         offset += dir->pos * sizeof(struct adfs_bigdirentry);
179
180         dir_memcpy(dir, offset, &bde, sizeof(struct adfs_bigdirentry));
181
182         obj->loadaddr = le32_to_cpu(bde.bigdirload);
183         obj->execaddr = le32_to_cpu(bde.bigdirexec);
184         obj->size     = le32_to_cpu(bde.bigdirlen);
185         obj->file_id  = le32_to_cpu(bde.bigdirindaddr);
186         obj->attr     = le32_to_cpu(bde.bigdirattr);
187         obj->name_len = le32_to_cpu(bde.bigdirobnamelen);
188
189         offset = offsetof(struct adfs_bigdirheader, bigdirname);
190         offset += ((le32_to_cpu(h->bigdirnamelen) + 4) & ~3);
191         offset += le32_to_cpu(h->bigdirentries) * sizeof(struct adfs_bigdirentry);
192         offset += le32_to_cpu(bde.bigdirobnameptr);
193
194         dir_memcpy(dir, offset, obj->name, obj->name_len);
195         for (i = 0; i < obj->name_len; i++)
196                 if (obj->name[i] == '/')
197                         obj->name[i] = '.';
198
199         obj->filetype = -1;
200
201         /*
202          * object is a file and is filetyped and timestamped?
203          * RISC OS 12-bit filetype is stored in load_address[19:8]
204          */
205         if ((0 == (obj->attr & ADFS_NDA_DIRECTORY)) &&
206                 (0xfff00000 == (0xfff00000 & obj->loadaddr))) {
207                 obj->filetype = (__u16) ((0x000fff00 & obj->loadaddr) >> 8);
208
209                 /* optionally append the ,xyz hex filetype suffix */
210                 if (ADFS_SB(dir->sb)->s_ftsuffix)
211                         obj->name_len +=
212                                 append_filetype_suffix(
213                                         &obj->name[obj->name_len],
214                                         obj->filetype);
215         }
216
217         dir->pos += 1;
218         ret = 0;
219 out:
220         return ret;
221 }
222
223 static int
224 adfs_fplus_sync(struct adfs_dir *dir)
225 {
226         int err = 0;
227         int i;
228
229         for (i = dir->nr_buffers - 1; i >= 0; i--) {
230                 struct buffer_head *bh = dir->bh_fplus[i];
231                 sync_dirty_buffer(bh);
232                 if (buffer_req(bh) && !buffer_uptodate(bh))
233                         err = -EIO;
234         }
235
236         return err;
237 }
238
239 static void
240 adfs_fplus_free(struct adfs_dir *dir)
241 {
242         int i;
243
244         if (dir->bh_fplus) {
245                 for (i = 0; i < dir->nr_buffers; i++)
246                         brelse(dir->bh_fplus[i]);
247
248                 if (&dir->bh[0] != dir->bh_fplus)
249                         kfree(dir->bh_fplus);
250
251                 dir->bh_fplus = NULL;
252         }
253
254         dir->nr_buffers = 0;
255         dir->sb = NULL;
256 }
257
258 struct adfs_dir_ops adfs_fplus_dir_ops = {
259         .read           = adfs_fplus_read,
260         .setpos         = adfs_fplus_setpos,
261         .getnext        = adfs_fplus_getnext,
262         .sync           = adfs_fplus_sync,
263         .free           = adfs_fplus_free
264 };