1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
21da177e4SLinus Torvalds /*
31da177e4SLinus Torvalds * linux/fs/adfs/dir_fplus.c
41da177e4SLinus Torvalds *
51da177e4SLinus Torvalds * Copyright (C) 1997-1999 Russell King
61da177e4SLinus Torvalds */
71da177e4SLinus Torvalds #include "adfs.h"
81da177e4SLinus Torvalds #include "dir_fplus.h"
91da177e4SLinus Torvalds
100db35a02SRussell King /* Return the byte offset to directory entry pos */
adfs_fplus_offset(const struct adfs_bigdirheader * h,unsigned int pos)110db35a02SRussell King static unsigned int adfs_fplus_offset(const struct adfs_bigdirheader *h,
120db35a02SRussell King unsigned int pos)
130db35a02SRussell King {
140db35a02SRussell King return offsetof(struct adfs_bigdirheader, bigdirname) +
150db35a02SRussell King ALIGN(le32_to_cpu(h->bigdirnamelen), 4) +
160db35a02SRussell King pos * sizeof(struct adfs_bigdirentry);
170db35a02SRussell King }
180db35a02SRussell King
adfs_fplus_validate_header(const struct adfs_bigdirheader * h)196674ecabSRussell King static int adfs_fplus_validate_header(const struct adfs_bigdirheader *h)
206674ecabSRussell King {
216674ecabSRussell King unsigned int size = le32_to_cpu(h->bigdirsize);
22aa3d4e01SRussell King unsigned int len;
236674ecabSRussell King
246674ecabSRussell King if (h->bigdirversion[0] != 0 || h->bigdirversion[1] != 0 ||
256674ecabSRussell King h->bigdirversion[2] != 0 ||
266674ecabSRussell King h->bigdirstartname != cpu_to_le32(BIGDIRSTARTNAME) ||
27aa3d4e01SRussell King !size || size & 2047 || size > SZ_4M)
28aa3d4e01SRussell King return -EIO;
29aa3d4e01SRussell King
30aa3d4e01SRussell King size -= sizeof(struct adfs_bigdirtail) +
31aa3d4e01SRussell King offsetof(struct adfs_bigdirheader, bigdirname);
32aa3d4e01SRussell King
33aa3d4e01SRussell King /* Check that bigdirnamelen fits within the directory */
34aa3d4e01SRussell King len = ALIGN(le32_to_cpu(h->bigdirnamelen), 4);
35aa3d4e01SRussell King if (len > size)
36aa3d4e01SRussell King return -EIO;
37aa3d4e01SRussell King
38aa3d4e01SRussell King size -= len;
39aa3d4e01SRussell King
40aa3d4e01SRussell King /* Check that bigdirnamesize fits within the directory */
41aa3d4e01SRussell King len = le32_to_cpu(h->bigdirnamesize);
42aa3d4e01SRussell King if (len > size)
43aa3d4e01SRussell King return -EIO;
44aa3d4e01SRussell King
45aa3d4e01SRussell King size -= len;
46aa3d4e01SRussell King
47aa3d4e01SRussell King /*
48aa3d4e01SRussell King * Avoid division, we know that absolute maximum number of entries
49aa3d4e01SRussell King * can not be so large to cause overflow of the multiplication below.
50aa3d4e01SRussell King */
51aa3d4e01SRussell King len = le32_to_cpu(h->bigdirentries);
52aa3d4e01SRussell King if (len > SZ_4M / sizeof(struct adfs_bigdirentry) ||
53aa3d4e01SRussell King len * sizeof(struct adfs_bigdirentry) > size)
546674ecabSRussell King return -EIO;
556674ecabSRussell King
566674ecabSRussell King return 0;
576674ecabSRussell King }
586674ecabSRussell King
adfs_fplus_validate_tail(const struct adfs_bigdirheader * h,const struct adfs_bigdirtail * t)596674ecabSRussell King static int adfs_fplus_validate_tail(const struct adfs_bigdirheader *h,
606674ecabSRussell King const struct adfs_bigdirtail *t)
616674ecabSRussell King {
626674ecabSRussell King if (t->bigdirendname != cpu_to_le32(BIGDIRENDNAME) ||
636674ecabSRussell King t->bigdirendmasseq != h->startmasseq ||
646674ecabSRussell King t->reserved[0] != 0 || t->reserved[1] != 0)
656674ecabSRussell King return -EIO;
666674ecabSRussell King
676674ecabSRussell King return 0;
686674ecabSRussell King }
696674ecabSRussell King
adfs_fplus_checkbyte(struct adfs_dir * dir)70d79288b4SRussell King static u8 adfs_fplus_checkbyte(struct adfs_dir *dir)
71d79288b4SRussell King {
72d79288b4SRussell King struct adfs_bigdirheader *h = dir->bighead;
73d79288b4SRussell King struct adfs_bigdirtail *t = dir->bigtail;
74d79288b4SRussell King unsigned int end, bs, bi, i;
75d79288b4SRussell King __le32 *bp;
76d79288b4SRussell King u32 dircheck;
77d79288b4SRussell King
78d79288b4SRussell King end = adfs_fplus_offset(h, le32_to_cpu(h->bigdirentries)) +
79d79288b4SRussell King le32_to_cpu(h->bigdirnamesize);
80d79288b4SRussell King
81d79288b4SRussell King /* Accumulate the contents of the header, entries and names */
82d79288b4SRussell King for (dircheck = 0, bi = 0; end; bi++) {
83d79288b4SRussell King bp = (void *)dir->bhs[bi]->b_data;
84d79288b4SRussell King bs = dir->bhs[bi]->b_size;
85d79288b4SRussell King if (bs > end)
86d79288b4SRussell King bs = end;
87d79288b4SRussell King
88d79288b4SRussell King for (i = 0; i < bs; i += sizeof(u32))
89d79288b4SRussell King dircheck = ror32(dircheck, 13) ^ le32_to_cpup(bp++);
90d79288b4SRussell King
91d79288b4SRussell King end -= bs;
92d79288b4SRussell King }
93d79288b4SRussell King
94d79288b4SRussell King /* Accumulate the contents of the tail except for the check byte */
95d79288b4SRussell King dircheck = ror32(dircheck, 13) ^ le32_to_cpu(t->bigdirendname);
96d79288b4SRussell King dircheck = ror32(dircheck, 13) ^ t->bigdirendmasseq;
97d79288b4SRussell King dircheck = ror32(dircheck, 13) ^ t->reserved[0];
98d79288b4SRussell King dircheck = ror32(dircheck, 13) ^ t->reserved[1];
99d79288b4SRussell King
100d79288b4SRussell King return dircheck ^ dircheck >> 8 ^ dircheck >> 16 ^ dircheck >> 24;
101d79288b4SRussell King }
102d79288b4SRussell King
adfs_fplus_read(struct super_block * sb,u32 indaddr,unsigned int size,struct adfs_dir * dir)103419a6e5eSRussell King static int adfs_fplus_read(struct super_block *sb, u32 indaddr,
104419a6e5eSRussell King unsigned int size, struct adfs_dir *dir)
1051da177e4SLinus Torvalds {
1061da177e4SLinus Torvalds struct adfs_bigdirheader *h;
1071da177e4SLinus Torvalds struct adfs_bigdirtail *t;
108419a6e5eSRussell King unsigned int dirsize;
109419a6e5eSRussell King int ret;
1101da177e4SLinus Torvalds
111419a6e5eSRussell King /* Read first buffer */
112419a6e5eSRussell King ret = adfs_dir_read_buffers(sb, indaddr, sb->s_blocksize, dir);
113419a6e5eSRussell King if (ret)
114419a6e5eSRussell King return ret;
1151da177e4SLinus Torvalds
116016936b3SRussell King dir->bighead = h = (void *)dir->bhs[0]->b_data;
117*587065dcSDan Carpenter ret = adfs_fplus_validate_header(h);
118*587065dcSDan Carpenter if (ret) {
1196674ecabSRussell King adfs_error(sb, "dir %06x has malformed header", indaddr);
1206674ecabSRussell King goto out;
1216674ecabSRussell King }
1226674ecabSRussell King
123419a6e5eSRussell King dirsize = le32_to_cpu(h->bigdirsize);
124a464152fSRussell King if (size && dirsize != size) {
125ceb3b106SRussell King adfs_msg(sb, KERN_WARNING,
126419a6e5eSRussell King "dir %06x header size %X does not match directory size %X",
127419a6e5eSRussell King indaddr, dirsize, size);
1281da177e4SLinus Torvalds }
1291da177e4SLinus Torvalds
130419a6e5eSRussell King /* Read remaining buffers */
131419a6e5eSRussell King ret = adfs_dir_read_buffers(sb, indaddr, dirsize, dir);
132419a6e5eSRussell King if (ret)
133419a6e5eSRussell King return ret;
1342f09719aSStuart Swales
135016936b3SRussell King dir->bigtail = t = (struct adfs_bigdirtail *)
136419a6e5eSRussell King (dir->bhs[dir->nr_buffers - 1]->b_data + (sb->s_blocksize - 8));
1371da177e4SLinus Torvalds
1386674ecabSRussell King ret = adfs_fplus_validate_tail(h, t);
1396674ecabSRussell King if (ret) {
140419a6e5eSRussell King adfs_error(sb, "dir %06x has malformed tail", indaddr);
1411da177e4SLinus Torvalds goto out;
1422f09719aSStuart Swales }
1431da177e4SLinus Torvalds
144d79288b4SRussell King if (adfs_fplus_checkbyte(dir) != t->bigdircheckbyte) {
145d79288b4SRussell King adfs_error(sb, "dir %06x checkbyte mismatch\n", indaddr);
146d79288b4SRussell King goto out;
147d79288b4SRussell King }
148d79288b4SRussell King
1491da177e4SLinus Torvalds dir->parent_id = le32_to_cpu(h->bigdirparent);
1501da177e4SLinus Torvalds return 0;
1512f09719aSStuart Swales
1521da177e4SLinus Torvalds out:
1531dd9f5baSRussell King adfs_dir_relse(dir);
1542f09719aSStuart Swales
1551da177e4SLinus Torvalds return ret;
1561da177e4SLinus Torvalds }
1571da177e4SLinus Torvalds
1581da177e4SLinus Torvalds static int
adfs_fplus_setpos(struct adfs_dir * dir,unsigned int fpos)1591da177e4SLinus Torvalds adfs_fplus_setpos(struct adfs_dir *dir, unsigned int fpos)
1601da177e4SLinus Torvalds {
1611da177e4SLinus Torvalds int ret = -ENOENT;
1621da177e4SLinus Torvalds
163016936b3SRussell King if (fpos <= le32_to_cpu(dir->bighead->bigdirentries)) {
1641da177e4SLinus Torvalds dir->pos = fpos;
1651da177e4SLinus Torvalds ret = 0;
1661da177e4SLinus Torvalds }
1671da177e4SLinus Torvalds
1681da177e4SLinus Torvalds return ret;
1691da177e4SLinus Torvalds }
1701da177e4SLinus Torvalds
1711da177e4SLinus Torvalds static int
adfs_fplus_getnext(struct adfs_dir * dir,struct object_info * obj)1721da177e4SLinus Torvalds adfs_fplus_getnext(struct adfs_dir *dir, struct object_info *obj)
1731da177e4SLinus Torvalds {
174016936b3SRussell King struct adfs_bigdirheader *h = dir->bighead;
1751da177e4SLinus Torvalds struct adfs_bigdirentry bde;
1761da177e4SLinus Torvalds unsigned int offset;
177a317120bSRussell King int ret;
1781da177e4SLinus Torvalds
1791da177e4SLinus Torvalds if (dir->pos >= le32_to_cpu(h->bigdirentries))
180a317120bSRussell King return -ENOENT;
1811da177e4SLinus Torvalds
1820db35a02SRussell King offset = adfs_fplus_offset(h, dir->pos);
1831da177e4SLinus Torvalds
184a317120bSRussell King ret = adfs_dir_copyfrom(&bde, dir, offset,
185a317120bSRussell King sizeof(struct adfs_bigdirentry));
186a317120bSRussell King if (ret)
187a317120bSRussell King return ret;
1881da177e4SLinus Torvalds
1891da177e4SLinus Torvalds obj->loadaddr = le32_to_cpu(bde.bigdirload);
1901da177e4SLinus Torvalds obj->execaddr = le32_to_cpu(bde.bigdirexec);
1911da177e4SLinus Torvalds obj->size = le32_to_cpu(bde.bigdirlen);
1925ed70bb4SRussell King obj->indaddr = le32_to_cpu(bde.bigdirindaddr);
1931da177e4SLinus Torvalds obj->attr = le32_to_cpu(bde.bigdirattr);
1941da177e4SLinus Torvalds obj->name_len = le32_to_cpu(bde.bigdirobnamelen);
1951da177e4SLinus Torvalds
1960db35a02SRussell King offset = adfs_fplus_offset(h, le32_to_cpu(h->bigdirentries));
1971da177e4SLinus Torvalds offset += le32_to_cpu(bde.bigdirobnameptr);
1981da177e4SLinus Torvalds
199a317120bSRussell King ret = adfs_dir_copyfrom(obj->name, dir, offset, obj->name_len);
200a317120bSRussell King if (ret)
201a317120bSRussell King return ret;
202a317120bSRussell King
203411c49bcSRussell King adfs_object_fixup(dir, obj);
204da23ef05SStuart Swales
2051da177e4SLinus Torvalds dir->pos += 1;
206a317120bSRussell King
207a317120bSRussell King return 0;
2081da177e4SLinus Torvalds }
2091da177e4SLinus Torvalds
adfs_fplus_iterate(struct adfs_dir * dir,struct dir_context * ctx)2104287e4deSRussell King static int adfs_fplus_iterate(struct adfs_dir *dir, struct dir_context *ctx)
2114287e4deSRussell King {
2124287e4deSRussell King struct object_info obj;
2134287e4deSRussell King
2144287e4deSRussell King if ((ctx->pos - 2) >> 32)
2154287e4deSRussell King return 0;
2164287e4deSRussell King
2174287e4deSRussell King if (adfs_fplus_setpos(dir, ctx->pos - 2))
2184287e4deSRussell King return 0;
2194287e4deSRussell King
2204287e4deSRussell King while (!adfs_fplus_getnext(dir, &obj)) {
2214287e4deSRussell King if (!dir_emit(ctx, obj.name, obj.name_len,
2224287e4deSRussell King obj.indaddr, DT_UNKNOWN))
2234287e4deSRussell King break;
2244287e4deSRussell King ctx->pos++;
2254287e4deSRussell King }
2264287e4deSRussell King
2274287e4deSRussell King return 0;
2284287e4deSRussell King }
2294287e4deSRussell King
adfs_fplus_update(struct adfs_dir * dir,struct object_info * obj)230a464152fSRussell King static int adfs_fplus_update(struct adfs_dir *dir, struct object_info *obj)
231a464152fSRussell King {
232a464152fSRussell King struct adfs_bigdirheader *h = dir->bighead;
233a464152fSRussell King struct adfs_bigdirentry bde;
234a464152fSRussell King int offset, end, ret;
235a464152fSRussell King
236a464152fSRussell King offset = adfs_fplus_offset(h, 0) - sizeof(bde);
237a464152fSRussell King end = adfs_fplus_offset(h, le32_to_cpu(h->bigdirentries));
238a464152fSRussell King
239a464152fSRussell King do {
240a464152fSRussell King offset += sizeof(bde);
241a464152fSRussell King if (offset >= end) {
242a464152fSRussell King adfs_error(dir->sb, "unable to locate entry to update");
243a464152fSRussell King return -ENOENT;
244a464152fSRussell King }
245a464152fSRussell King ret = adfs_dir_copyfrom(&bde, dir, offset, sizeof(bde));
246a464152fSRussell King if (ret) {
247a464152fSRussell King adfs_error(dir->sb, "error reading directory entry");
248a464152fSRussell King return -ENOENT;
249a464152fSRussell King }
250a464152fSRussell King } while (le32_to_cpu(bde.bigdirindaddr) != obj->indaddr);
251a464152fSRussell King
252a464152fSRussell King bde.bigdirload = cpu_to_le32(obj->loadaddr);
253a464152fSRussell King bde.bigdirexec = cpu_to_le32(obj->execaddr);
254a464152fSRussell King bde.bigdirlen = cpu_to_le32(obj->size);
255a464152fSRussell King bde.bigdirindaddr = cpu_to_le32(obj->indaddr);
256a464152fSRussell King bde.bigdirattr = cpu_to_le32(obj->attr);
257a464152fSRussell King
258a464152fSRussell King return adfs_dir_copyto(dir, offset, &bde, sizeof(bde));
259a464152fSRussell King }
260a464152fSRussell King
adfs_fplus_commit(struct adfs_dir * dir)261a464152fSRussell King static int adfs_fplus_commit(struct adfs_dir *dir)
262a464152fSRussell King {
263a464152fSRussell King int ret;
264a464152fSRussell King
265a464152fSRussell King /* Increment directory sequence number */
266a464152fSRussell King dir->bighead->startmasseq += 1;
267a464152fSRussell King dir->bigtail->bigdirendmasseq += 1;
268a464152fSRussell King
269a464152fSRussell King /* Update directory check byte */
270a464152fSRussell King dir->bigtail->bigdircheckbyte = adfs_fplus_checkbyte(dir);
271a464152fSRussell King
272a464152fSRussell King /* Make sure the directory still validates correctly */
273a464152fSRussell King ret = adfs_fplus_validate_header(dir->bighead);
274a464152fSRussell King if (ret == 0)
275a464152fSRussell King ret = adfs_fplus_validate_tail(dir->bighead, dir->bigtail);
276a464152fSRussell King
277a464152fSRussell King return ret;
278a464152fSRussell King }
279a464152fSRussell King
2800125f504SJulia Lawall const struct adfs_dir_ops adfs_fplus_dir_ops = {
2811da177e4SLinus Torvalds .read = adfs_fplus_read,
2824287e4deSRussell King .iterate = adfs_fplus_iterate,
2831da177e4SLinus Torvalds .setpos = adfs_fplus_setpos,
2841da177e4SLinus Torvalds .getnext = adfs_fplus_getnext,
285a464152fSRussell King .update = adfs_fplus_update,
286a464152fSRussell King .commit = adfs_fplus_commit,
2871da177e4SLinus Torvalds };
288