xref: /linux/fs/adfs/dir.c (revision 24c776355f4097316a763005434ffff716aa21a8)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  *  linux/fs/adfs/dir.c
4  *
5  *  Copyright (C) 1999-2000 Russell King
6  *
7  *  Common directory handling for ADFS
8  */
9 #include <linux/hex.h>
10 #include <linux/slab.h>
11 #include "adfs.h"
12 
13 /*
14  * For future.  This should probably be per-directory.
15  */
16 static DECLARE_RWSEM(adfs_dir_rwsem);
17 
18 int adfs_dir_copyfrom(void *dst, struct adfs_dir *dir, unsigned int offset,
19 		      size_t len)
20 {
21 	struct super_block *sb = dir->sb;
22 	unsigned int index, remain;
23 
24 	index = offset >> sb->s_blocksize_bits;
25 	offset &= sb->s_blocksize - 1;
26 	remain = sb->s_blocksize - offset;
27 	if (index + (remain < len) >= dir->nr_buffers)
28 		return -EINVAL;
29 
30 	if (remain < len) {
31 		memcpy(dst, dir->bhs[index]->b_data + offset, remain);
32 		dst += remain;
33 		len -= remain;
34 		index += 1;
35 		offset = 0;
36 	}
37 
38 	memcpy(dst, dir->bhs[index]->b_data + offset, len);
39 
40 	return 0;
41 }
42 
43 int adfs_dir_copyto(struct adfs_dir *dir, unsigned int offset, const void *src,
44 		    size_t len)
45 {
46 	struct super_block *sb = dir->sb;
47 	unsigned int index, remain;
48 
49 	index = offset >> sb->s_blocksize_bits;
50 	offset &= sb->s_blocksize - 1;
51 	remain = sb->s_blocksize - offset;
52 	if (index + (remain < len) >= dir->nr_buffers)
53 		return -EINVAL;
54 
55 	if (remain < len) {
56 		memcpy(dir->bhs[index]->b_data + offset, src, remain);
57 		src += remain;
58 		len -= remain;
59 		index += 1;
60 		offset = 0;
61 	}
62 
63 	memcpy(dir->bhs[index]->b_data + offset, src, len);
64 
65 	return 0;
66 }
67 
68 static void __adfs_dir_cleanup(struct adfs_dir *dir)
69 {
70 	dir->nr_buffers = 0;
71 
72 	if (dir->bhs != dir->bh)
73 		kfree(dir->bhs);
74 	dir->bhs = NULL;
75 	dir->sb = NULL;
76 }
77 
78 void adfs_dir_relse(struct adfs_dir *dir)
79 {
80 	unsigned int i;
81 
82 	for (i = 0; i < dir->nr_buffers; i++)
83 		brelse(dir->bhs[i]);
84 
85 	__adfs_dir_cleanup(dir);
86 }
87 
88 static void adfs_dir_forget(struct adfs_dir *dir)
89 {
90 	unsigned int i;
91 
92 	for (i = 0; i < dir->nr_buffers; i++)
93 		bforget(dir->bhs[i]);
94 
95 	__adfs_dir_cleanup(dir);
96 }
97 
98 int adfs_dir_read_buffers(struct super_block *sb, u32 indaddr,
99 			  unsigned int size, struct adfs_dir *dir)
100 {
101 	struct buffer_head **bhs;
102 	unsigned int i, num;
103 	int block;
104 
105 	num = ALIGN(size, sb->s_blocksize) >> sb->s_blocksize_bits;
106 	if (num > ARRAY_SIZE(dir->bh)) {
107 		/* We only allow one extension */
108 		if (dir->bhs != dir->bh)
109 			return -EINVAL;
110 
111 		bhs = kcalloc(num, sizeof(*bhs), GFP_KERNEL);
112 		if (!bhs)
113 			return -ENOMEM;
114 
115 		if (dir->nr_buffers)
116 			memcpy(bhs, dir->bhs, dir->nr_buffers * sizeof(*bhs));
117 
118 		dir->bhs = bhs;
119 	}
120 
121 	for (i = dir->nr_buffers; i < num; i++) {
122 		block = __adfs_block_map(sb, indaddr, i);
123 		if (!block) {
124 			adfs_error(sb, "dir %06x has a hole at offset %u",
125 				   indaddr, i);
126 			goto error;
127 		}
128 
129 		dir->bhs[i] = sb_bread(sb, block);
130 		if (!dir->bhs[i]) {
131 			adfs_error(sb,
132 				   "dir %06x failed read at offset %u, mapped block 0x%08x",
133 				   indaddr, i, block);
134 			goto error;
135 		}
136 
137 		dir->nr_buffers++;
138 	}
139 	return 0;
140 
141 error:
142 	adfs_dir_relse(dir);
143 
144 	return -EIO;
145 }
146 
147 static int adfs_dir_read(struct super_block *sb, u32 indaddr,
148 			 unsigned int size, struct adfs_dir *dir)
149 {
150 	dir->sb = sb;
151 	dir->bhs = dir->bh;
152 	dir->nr_buffers = 0;
153 
154 	return ADFS_SB(sb)->s_dir->read(sb, indaddr, size, dir);
155 }
156 
157 static int adfs_dir_read_inode(struct super_block *sb, struct inode *inode,
158 			       struct adfs_dir *dir)
159 {
160 	int ret;
161 
162 	ret = adfs_dir_read(sb, ADFS_I(inode)->indaddr, inode->i_size, dir);
163 	if (ret)
164 		return ret;
165 
166 	if (ADFS_I(inode)->parent_id != dir->parent_id) {
167 		adfs_error(sb,
168 			   "parent directory id changed under me! (%06x but got %06x)\n",
169 			   ADFS_I(inode)->parent_id, dir->parent_id);
170 		adfs_dir_relse(dir);
171 		ret = -EIO;
172 	}
173 
174 	return ret;
175 }
176 
177 static void adfs_dir_mark_dirty(struct adfs_dir *dir)
178 {
179 	unsigned int i;
180 
181 	/* Mark the buffers dirty */
182 	for (i = 0; i < dir->nr_buffers; i++)
183 		mark_buffer_dirty(dir->bhs[i]);
184 }
185 
186 static int adfs_dir_sync(struct adfs_dir *dir)
187 {
188 	int err = 0;
189 	int i;
190 
191 	for (i = dir->nr_buffers - 1; i >= 0; i--) {
192 		struct buffer_head *bh = dir->bhs[i];
193 		sync_dirty_buffer(bh);
194 		if (buffer_req(bh) && !buffer_uptodate(bh))
195 			err = -EIO;
196 	}
197 
198 	return err;
199 }
200 
201 void adfs_object_fixup(struct adfs_dir *dir, struct object_info *obj)
202 {
203 	unsigned int dots, i;
204 
205 	/*
206 	 * RISC OS allows the use of '/' in directory entry names, so we need
207 	 * to fix these up.  '/' is typically used for FAT compatibility to
208 	 * represent '.', so do the same conversion here.  In any case, '.'
209 	 * will never be in a RISC OS name since it is used as the pathname
210 	 * separator.  Handle the case where we may generate a '.' or '..'
211 	 * name, replacing the first character with '^' (the RISC OS "parent
212 	 * directory" character.)
213 	 */
214 	for (i = dots = 0; i < obj->name_len; i++)
215 		if (obj->name[i] == '/') {
216 			obj->name[i] = '.';
217 			dots++;
218 		}
219 
220 	if (obj->name_len <= 2 && dots == obj->name_len)
221 		obj->name[0] = '^';
222 
223 	/*
224 	 * If the object is a file, and the user requested the ,xyz hex
225 	 * filetype suffix to the name, check the filetype and append.
226 	 */
227 	if (!(obj->attr & ADFS_NDA_DIRECTORY) && ADFS_SB(dir->sb)->s_ftsuffix) {
228 		u16 filetype = adfs_filetype(obj->loadaddr);
229 
230 		if (filetype != ADFS_FILETYPE_NONE) {
231 			obj->name[obj->name_len++] = ',';
232 			obj->name[obj->name_len++] = hex_asc_lo(filetype >> 8);
233 			obj->name[obj->name_len++] = hex_asc_lo(filetype >> 4);
234 			obj->name[obj->name_len++] = hex_asc_lo(filetype >> 0);
235 		}
236 	}
237 }
238 
239 static int adfs_iterate(struct file *file, struct dir_context *ctx)
240 {
241 	struct inode *inode = file_inode(file);
242 	struct super_block *sb = inode->i_sb;
243 	const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir;
244 	struct adfs_dir dir;
245 	int ret;
246 
247 	down_read(&adfs_dir_rwsem);
248 	ret = adfs_dir_read_inode(sb, inode, &dir);
249 	if (ret)
250 		goto unlock;
251 
252 	if (ctx->pos == 0) {
253 		if (!dir_emit_dot(file, ctx))
254 			goto unlock_relse;
255 		ctx->pos = 1;
256 	}
257 	if (ctx->pos == 1) {
258 		if (!dir_emit(ctx, "..", 2, dir.parent_id, DT_DIR))
259 			goto unlock_relse;
260 		ctx->pos = 2;
261 	}
262 
263 	ret = ops->iterate(&dir, ctx);
264 
265 unlock_relse:
266 	up_read(&adfs_dir_rwsem);
267 	adfs_dir_relse(&dir);
268 	return ret;
269 
270 unlock:
271 	up_read(&adfs_dir_rwsem);
272 	return ret;
273 }
274 
275 int
276 adfs_dir_update(struct super_block *sb, struct object_info *obj, int wait)
277 {
278 	const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir;
279 	struct adfs_dir dir;
280 	int ret;
281 
282 	if (!IS_ENABLED(CONFIG_ADFS_FS_RW))
283 		return -EINVAL;
284 
285 	if (!ops->update)
286 		return -EINVAL;
287 
288 	down_write(&adfs_dir_rwsem);
289 	ret = adfs_dir_read(sb, obj->parent_id, 0, &dir);
290 	if (ret)
291 		goto unlock;
292 
293 	ret = ops->update(&dir, obj);
294 	if (ret)
295 		goto forget;
296 
297 	ret = ops->commit(&dir);
298 	if (ret)
299 		goto forget;
300 	up_write(&adfs_dir_rwsem);
301 
302 	adfs_dir_mark_dirty(&dir);
303 
304 	if (wait)
305 		ret = adfs_dir_sync(&dir);
306 
307 	adfs_dir_relse(&dir);
308 	return ret;
309 
310 	/*
311 	 * If the updated failed because the entry wasn't found, we can
312 	 * just release the buffers. If it was any other error, forget
313 	 * the dirtied buffers so they aren't written back to the media.
314 	 */
315 forget:
316 	if (ret == -ENOENT)
317 		adfs_dir_relse(&dir);
318 	else
319 		adfs_dir_forget(&dir);
320 unlock:
321 	up_write(&adfs_dir_rwsem);
322 
323 	return ret;
324 }
325 
326 static unsigned char adfs_tolower(unsigned char c)
327 {
328 	if (c >= 'A' && c <= 'Z')
329 		c += 'a' - 'A';
330 	return c;
331 }
332 
333 static int __adfs_compare(const unsigned char *qstr, u32 qlen,
334 			  const char *str, u32 len)
335 {
336 	u32 i;
337 
338 	if (qlen != len)
339 		return 1;
340 
341 	for (i = 0; i < qlen; i++)
342 		if (adfs_tolower(qstr[i]) != adfs_tolower(str[i]))
343 			return 1;
344 
345 	return 0;
346 }
347 
348 static int adfs_dir_lookup_byname(struct inode *inode, const struct qstr *qstr,
349 				  struct object_info *obj)
350 {
351 	struct super_block *sb = inode->i_sb;
352 	const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir;
353 	const unsigned char *name;
354 	struct adfs_dir dir;
355 	u32 name_len;
356 	int ret;
357 
358 	down_read(&adfs_dir_rwsem);
359 	ret = adfs_dir_read_inode(sb, inode, &dir);
360 	if (ret)
361 		goto unlock;
362 
363 	ret = ops->setpos(&dir, 0);
364 	if (ret)
365 		goto unlock_relse;
366 
367 	ret = -ENOENT;
368 	name = qstr->name;
369 	name_len = qstr->len;
370 	while (ops->getnext(&dir, obj) == 0) {
371 		if (!__adfs_compare(name, name_len, obj->name, obj->name_len)) {
372 			ret = 0;
373 			break;
374 		}
375 	}
376 	obj->parent_id = ADFS_I(inode)->indaddr;
377 
378 unlock_relse:
379 	up_read(&adfs_dir_rwsem);
380 	adfs_dir_relse(&dir);
381 	return ret;
382 
383 unlock:
384 	up_read(&adfs_dir_rwsem);
385 	return ret;
386 }
387 
388 const struct file_operations adfs_dir_operations = {
389 	.read		= generic_read_dir,
390 	.llseek		= generic_file_llseek,
391 	.iterate_shared	= adfs_iterate,
392 	.fsync		= generic_file_fsync,
393 };
394 
395 static int
396 adfs_hash(const struct dentry *parent, struct qstr *qstr)
397 {
398 	const unsigned char *name;
399 	unsigned long hash;
400 	u32 len;
401 
402 	if (qstr->len > ADFS_SB(parent->d_sb)->s_namelen)
403 		return -ENAMETOOLONG;
404 
405 	len = qstr->len;
406 	name = qstr->name;
407 	hash = init_name_hash(parent);
408 	while (len--)
409 		hash = partial_name_hash(adfs_tolower(*name++), hash);
410 	qstr->hash = end_name_hash(hash);
411 
412 	return 0;
413 }
414 
415 /*
416  * Compare two names, taking note of the name length
417  * requirements of the underlying filesystem.
418  */
419 static int adfs_compare(const struct dentry *dentry, unsigned int len,
420 			const char *str, const struct qstr *qstr)
421 {
422 	return __adfs_compare(qstr->name, qstr->len, str, len);
423 }
424 
425 const struct dentry_operations adfs_dentry_operations = {
426 	.d_hash		= adfs_hash,
427 	.d_compare	= adfs_compare,
428 };
429 
430 static struct dentry *
431 adfs_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags)
432 {
433 	struct inode *inode = NULL;
434 	struct object_info obj;
435 	int error;
436 
437 	error = adfs_dir_lookup_byname(dir, &dentry->d_name, &obj);
438 	if (error == 0) {
439 		/*
440 		 * This only returns NULL if get_empty_inode
441 		 * fails.
442 		 */
443 		inode = adfs_iget(dir->i_sb, &obj);
444 		if (!inode)
445 			inode = ERR_PTR(-EACCES);
446 	} else if (error != -ENOENT) {
447 		inode = ERR_PTR(error);
448 	}
449 	return d_splice_alias(inode, dentry);
450 }
451 
452 /*
453  * directories can handle most operations...
454  */
455 const struct inode_operations adfs_dir_inode_operations = {
456 	.lookup		= adfs_lookup,
457 	.setattr	= adfs_notify_change,
458 };
459