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