xref: /openbmc/linux/fs/adfs/dir_fplus.c (revision 587065dc)
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 */
adfs_fplus_offset(const struct adfs_bigdirheader * h,unsigned int 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 
adfs_fplus_validate_header(const struct adfs_bigdirheader * h)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 
adfs_fplus_validate_tail(const struct adfs_bigdirheader * h,const struct adfs_bigdirtail * t)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 
adfs_fplus_checkbyte(struct adfs_dir * dir)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 
adfs_fplus_read(struct super_block * sb,u32 indaddr,unsigned int size,struct adfs_dir * dir)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
adfs_fplus_setpos(struct adfs_dir * dir,unsigned int fpos)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
adfs_fplus_getnext(struct adfs_dir * dir,struct object_info * obj)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 
adfs_fplus_iterate(struct adfs_dir * dir,struct dir_context * ctx)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 
adfs_fplus_update(struct adfs_dir * dir,struct object_info * obj)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 
adfs_fplus_commit(struct adfs_dir * dir)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 };
288