Merge tag 'gvt-fixes-2021-08-10' of https://github.com/intel/gvt-linux into drm-intel...
[platform/kernel/linux-starfive.git] / fs / adfs / dir_fplus.c
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  *  linux/fs/adfs/dir_fplus.c
4  *
5  *  Copyright (C) 1997-1999 Russell King
6  */
7 #include "adfs.h"
8 #include "dir_fplus.h"
9
10 /* Return the byte offset to directory entry pos */
11 static unsigned int adfs_fplus_offset(const struct adfs_bigdirheader *h,
12                                       unsigned int pos)
13 {
14         return offsetof(struct adfs_bigdirheader, bigdirname) +
15                ALIGN(le32_to_cpu(h->bigdirnamelen), 4) +
16                pos * sizeof(struct adfs_bigdirentry);
17 }
18
19 static int adfs_fplus_validate_header(const struct adfs_bigdirheader *h)
20 {
21         unsigned int size = le32_to_cpu(h->bigdirsize);
22         unsigned int len;
23
24         if (h->bigdirversion[0] != 0 || h->bigdirversion[1] != 0 ||
25             h->bigdirversion[2] != 0 ||
26             h->bigdirstartname != cpu_to_le32(BIGDIRSTARTNAME) ||
27             !size || size & 2047 || size > SZ_4M)
28                 return -EIO;
29
30         size -= sizeof(struct adfs_bigdirtail) +
31                 offsetof(struct adfs_bigdirheader, bigdirname);
32
33         /* Check that bigdirnamelen fits within the directory */
34         len = ALIGN(le32_to_cpu(h->bigdirnamelen), 4);
35         if (len > size)
36                 return -EIO;
37
38         size -= len;
39
40         /* Check that bigdirnamesize fits within the directory */
41         len = le32_to_cpu(h->bigdirnamesize);
42         if (len > size)
43                 return -EIO;
44
45         size -= len;
46
47         /*
48          * Avoid division, we know that absolute maximum number of entries
49          * can not be so large to cause overflow of the multiplication below.
50          */
51         len = le32_to_cpu(h->bigdirentries);
52         if (len > SZ_4M / sizeof(struct adfs_bigdirentry) ||
53             len * sizeof(struct adfs_bigdirentry) > size)
54                 return -EIO;
55
56         return 0;
57 }
58
59 static int adfs_fplus_validate_tail(const struct adfs_bigdirheader *h,
60                                     const struct adfs_bigdirtail *t)
61 {
62         if (t->bigdirendname != cpu_to_le32(BIGDIRENDNAME) ||
63             t->bigdirendmasseq != h->startmasseq ||
64             t->reserved[0] != 0 || t->reserved[1] != 0)
65                 return -EIO;
66
67         return 0;
68 }
69
70 static u8 adfs_fplus_checkbyte(struct adfs_dir *dir)
71 {
72         struct adfs_bigdirheader *h = dir->bighead;
73         struct adfs_bigdirtail *t = dir->bigtail;
74         unsigned int end, bs, bi, i;
75         __le32 *bp;
76         u32 dircheck;
77
78         end = adfs_fplus_offset(h, le32_to_cpu(h->bigdirentries)) +
79                 le32_to_cpu(h->bigdirnamesize);
80
81         /* Accumulate the contents of the header, entries and names */
82         for (dircheck = 0, bi = 0; end; bi++) {
83                 bp = (void *)dir->bhs[bi]->b_data;
84                 bs = dir->bhs[bi]->b_size;
85                 if (bs > end)
86                         bs = end;
87
88                 for (i = 0; i < bs; i += sizeof(u32))
89                         dircheck = ror32(dircheck, 13) ^ le32_to_cpup(bp++);
90
91                 end -= bs;
92         }
93
94         /* Accumulate the contents of the tail except for the check byte */
95         dircheck = ror32(dircheck, 13) ^ le32_to_cpu(t->bigdirendname);
96         dircheck = ror32(dircheck, 13) ^ t->bigdirendmasseq;
97         dircheck = ror32(dircheck, 13) ^ t->reserved[0];
98         dircheck = ror32(dircheck, 13) ^ t->reserved[1];
99
100         return dircheck ^ dircheck >> 8 ^ dircheck >> 16 ^ dircheck >> 24;
101 }
102
103 static int adfs_fplus_read(struct super_block *sb, u32 indaddr,
104                            unsigned int size, struct adfs_dir *dir)
105 {
106         struct adfs_bigdirheader *h;
107         struct adfs_bigdirtail *t;
108         unsigned int dirsize;
109         int ret;
110
111         /* Read first buffer */
112         ret = adfs_dir_read_buffers(sb, indaddr, sb->s_blocksize, dir);
113         if (ret)
114                 return ret;
115
116         dir->bighead = h = (void *)dir->bhs[0]->b_data;
117         ret = adfs_fplus_validate_header(h);
118         if (ret) {
119                 adfs_error(sb, "dir %06x has malformed header", indaddr);
120                 goto out;
121         }
122
123         dirsize = le32_to_cpu(h->bigdirsize);
124         if (size && dirsize != size) {
125                 adfs_msg(sb, KERN_WARNING,
126                          "dir %06x header size %X does not match directory size %X",
127                          indaddr, dirsize, size);
128         }
129
130         /* Read remaining buffers */
131         ret = adfs_dir_read_buffers(sb, indaddr, dirsize, dir);
132         if (ret)
133                 return ret;
134
135         dir->bigtail = t = (struct adfs_bigdirtail *)
136                 (dir->bhs[dir->nr_buffers - 1]->b_data + (sb->s_blocksize - 8));
137
138         ret = adfs_fplus_validate_tail(h, t);
139         if (ret) {
140                 adfs_error(sb, "dir %06x has malformed tail", indaddr);
141                 goto out;
142         }
143
144         if (adfs_fplus_checkbyte(dir) != t->bigdircheckbyte) {
145                 adfs_error(sb, "dir %06x checkbyte mismatch\n", indaddr);
146                 goto out;
147         }
148
149         dir->parent_id = le32_to_cpu(h->bigdirparent);
150         return 0;
151
152 out:
153         adfs_dir_relse(dir);
154
155         return ret;
156 }
157
158 static int
159 adfs_fplus_setpos(struct adfs_dir *dir, unsigned int fpos)
160 {
161         int ret = -ENOENT;
162
163         if (fpos <= le32_to_cpu(dir->bighead->bigdirentries)) {
164                 dir->pos = fpos;
165                 ret = 0;
166         }
167
168         return ret;
169 }
170
171 static int
172 adfs_fplus_getnext(struct adfs_dir *dir, struct object_info *obj)
173 {
174         struct adfs_bigdirheader *h = dir->bighead;
175         struct adfs_bigdirentry bde;
176         unsigned int offset;
177         int ret;
178
179         if (dir->pos >= le32_to_cpu(h->bigdirentries))
180                 return -ENOENT;
181
182         offset = adfs_fplus_offset(h, dir->pos);
183
184         ret = adfs_dir_copyfrom(&bde, dir, offset,
185                                 sizeof(struct adfs_bigdirentry));
186         if (ret)
187                 return ret;
188
189         obj->loadaddr = le32_to_cpu(bde.bigdirload);
190         obj->execaddr = le32_to_cpu(bde.bigdirexec);
191         obj->size     = le32_to_cpu(bde.bigdirlen);
192         obj->indaddr  = le32_to_cpu(bde.bigdirindaddr);
193         obj->attr     = le32_to_cpu(bde.bigdirattr);
194         obj->name_len = le32_to_cpu(bde.bigdirobnamelen);
195
196         offset = adfs_fplus_offset(h, le32_to_cpu(h->bigdirentries));
197         offset += le32_to_cpu(bde.bigdirobnameptr);
198
199         ret = adfs_dir_copyfrom(obj->name, dir, offset, obj->name_len);
200         if (ret)
201                 return ret;
202
203         adfs_object_fixup(dir, obj);
204
205         dir->pos += 1;
206
207         return 0;
208 }
209
210 static int adfs_fplus_iterate(struct adfs_dir *dir, struct dir_context *ctx)
211 {
212         struct object_info obj;
213
214         if ((ctx->pos - 2) >> 32)
215                 return 0;
216
217         if (adfs_fplus_setpos(dir, ctx->pos - 2))
218                 return 0;
219
220         while (!adfs_fplus_getnext(dir, &obj)) {
221                 if (!dir_emit(ctx, obj.name, obj.name_len,
222                               obj.indaddr, DT_UNKNOWN))
223                         break;
224                 ctx->pos++;
225         }
226
227         return 0;
228 }
229
230 static int adfs_fplus_update(struct adfs_dir *dir, struct object_info *obj)
231 {
232         struct adfs_bigdirheader *h = dir->bighead;
233         struct adfs_bigdirentry bde;
234         int offset, end, ret;
235
236         offset = adfs_fplus_offset(h, 0) - sizeof(bde);
237         end = adfs_fplus_offset(h, le32_to_cpu(h->bigdirentries));
238
239         do {
240                 offset += sizeof(bde);
241                 if (offset >= end) {
242                         adfs_error(dir->sb, "unable to locate entry to update");
243                         return -ENOENT;
244                 }
245                 ret = adfs_dir_copyfrom(&bde, dir, offset, sizeof(bde));
246                 if (ret) {
247                         adfs_error(dir->sb, "error reading directory entry");
248                         return -ENOENT;
249                 }
250         } while (le32_to_cpu(bde.bigdirindaddr) != obj->indaddr);
251
252         bde.bigdirload    = cpu_to_le32(obj->loadaddr);
253         bde.bigdirexec    = cpu_to_le32(obj->execaddr);
254         bde.bigdirlen     = cpu_to_le32(obj->size);
255         bde.bigdirindaddr = cpu_to_le32(obj->indaddr);
256         bde.bigdirattr    = cpu_to_le32(obj->attr);
257
258         return adfs_dir_copyto(dir, offset, &bde, sizeof(bde));
259 }
260
261 static int adfs_fplus_commit(struct adfs_dir *dir)
262 {
263         int ret;
264
265         /* Increment directory sequence number */
266         dir->bighead->startmasseq += 1;
267         dir->bigtail->bigdirendmasseq += 1;
268
269         /* Update directory check byte */
270         dir->bigtail->bigdircheckbyte = adfs_fplus_checkbyte(dir);
271
272         /* Make sure the directory still validates correctly */
273         ret = adfs_fplus_validate_header(dir->bighead);
274         if (ret == 0)
275                 ret = adfs_fplus_validate_tail(dir->bighead, dir->bigtail);
276
277         return ret;
278 }
279
280 const struct adfs_dir_ops adfs_fplus_dir_ops = {
281         .read           = adfs_fplus_read,
282         .iterate        = adfs_fplus_iterate,
283         .setpos         = adfs_fplus_setpos,
284         .getnext        = adfs_fplus_getnext,
285         .update         = adfs_fplus_update,
286         .commit         = adfs_fplus_commit,
287 };