]>
Commit | Line | Data |
---|---|---|
d2912cb1 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
1da177e4 LT |
2 | /* |
3 | * linux/fs/adfs/dir.c | |
4 | * | |
5 | * Copyright (C) 1999-2000 Russell King | |
6 | * | |
1da177e4 LT |
7 | * Common directory handling for ADFS |
8 | */ | |
1dd9f5ba | 9 | #include <linux/slab.h> |
1da177e4 LT |
10 | #include "adfs.h" |
11 | ||
12 | /* | |
13 | * For future. This should probably be per-directory. | |
14 | */ | |
deed1bfd | 15 | static DECLARE_RWSEM(adfs_dir_rwsem); |
1da177e4 | 16 | |
a317120b RK |
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 | ||
f6075c79 | 67 | static void __adfs_dir_cleanup(struct adfs_dir *dir) |
1dd9f5ba | 68 | { |
1dd9f5ba RK |
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 | ||
f6075c79 RK |
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 | ||
419a6e5e RK |
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 | ||
95fbadbb RK |
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 | ||
90011c7a RK |
156 | static int adfs_dir_read_inode(struct super_block *sb, struct inode *inode, |
157 | struct adfs_dir *dir) | |
158 | { | |
159 | int ret; | |
160 | ||
25e5d4df | 161 | ret = adfs_dir_read(sb, ADFS_I(inode)->indaddr, inode->i_size, dir); |
90011c7a RK |
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 | ||
c3c8149b RK |
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 | ||
acf5f0be RK |
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 | ||
411c49bc RK |
200 | void adfs_object_fixup(struct adfs_dir *dir, struct object_info *obj) |
201 | { | |
fc722a04 | 202 | unsigned int dots, i; |
adb514a4 RK |
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 | |
fc722a04 RK |
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.) | |
adb514a4 | 212 | */ |
fc722a04 RK |
213 | for (i = dots = 0; i < obj->name_len; i++) |
214 | if (obj->name[i] == '/') { | |
adb514a4 | 215 | obj->name[i] = '.'; |
fc722a04 RK |
216 | dots++; |
217 | } | |
218 | ||
219 | if (obj->name_len <= 2 && dots == obj->name_len) | |
220 | obj->name[0] = '^'; | |
adb514a4 | 221 | |
411c49bc | 222 | /* |
b4ed8f75 RK |
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. | |
411c49bc | 225 | */ |
b4ed8f75 RK |
226 | if (!(obj->attr & ADFS_NDA_DIRECTORY) && ADFS_SB(dir->sb)->s_ftsuffix) { |
227 | u16 filetype = adfs_filetype(obj->loadaddr); | |
5f8de487 | 228 | |
b4ed8f75 | 229 | if (filetype != ADFS_FILETYPE_NONE) { |
5f8de487 RK |
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 | } | |
411c49bc RK |
235 | } |
236 | } | |
237 | ||
cdc46e99 | 238 | static int adfs_iterate(struct file *file, struct dir_context *ctx) |
1da177e4 | 239 | { |
2638ffba | 240 | struct inode *inode = file_inode(file); |
1da177e4 | 241 | struct super_block *sb = inode->i_sb; |
0125f504 | 242 | const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir; |
1da177e4 | 243 | struct adfs_dir dir; |
4287e4de | 244 | int ret; |
1da177e4 | 245 | |
deed1bfd | 246 | down_read(&adfs_dir_rwsem); |
90011c7a | 247 | ret = adfs_dir_read_inode(sb, inode, &dir); |
1da177e4 | 248 | if (ret) |
deed1bfd | 249 | goto unlock; |
1da177e4 | 250 | |
2638ffba AV |
251 | if (ctx->pos == 0) { |
252 | if (!dir_emit_dot(file, ctx)) | |
deed1bfd | 253 | goto unlock_relse; |
2638ffba AV |
254 | ctx->pos = 1; |
255 | } | |
256 | if (ctx->pos == 1) { | |
257 | if (!dir_emit(ctx, "..", 2, dir.parent_id, DT_DIR)) | |
deed1bfd | 258 | goto unlock_relse; |
2638ffba | 259 | ctx->pos = 2; |
1da177e4 LT |
260 | } |
261 | ||
4287e4de | 262 | ret = ops->iterate(&dir, ctx); |
1da177e4 | 263 | |
deed1bfd RK |
264 | unlock_relse: |
265 | up_read(&adfs_dir_rwsem); | |
1dd9f5ba | 266 | adfs_dir_relse(&dir); |
1da177e4 | 267 | return ret; |
deed1bfd RK |
268 | |
269 | unlock: | |
270 | up_read(&adfs_dir_rwsem); | |
271 | return ret; | |
1da177e4 LT |
272 | } |
273 | ||
274 | int | |
ffdc9064 | 275 | adfs_dir_update(struct super_block *sb, struct object_info *obj, int wait) |
1da177e4 | 276 | { |
0125f504 | 277 | const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir; |
1da177e4 | 278 | struct adfs_dir dir; |
4a0a88b6 | 279 | int ret; |
1da177e4 | 280 | |
4a0a88b6 RK |
281 | if (!IS_ENABLED(CONFIG_ADFS_FS_RW)) |
282 | return -EINVAL; | |
283 | ||
acf5f0be RK |
284 | if (!ops->update) |
285 | return -EINVAL; | |
1da177e4 | 286 | |
deed1bfd | 287 | down_write(&adfs_dir_rwsem); |
95fbadbb | 288 | ret = adfs_dir_read(sb, obj->parent_id, 0, &dir); |
1da177e4 | 289 | if (ret) |
deed1bfd | 290 | goto unlock; |
1da177e4 | 291 | |
1da177e4 | 292 | ret = ops->update(&dir, obj); |
aacc954c RK |
293 | if (ret) |
294 | goto forget; | |
295 | ||
296 | ret = ops->commit(&dir); | |
f6075c79 RK |
297 | if (ret) |
298 | goto forget; | |
deed1bfd | 299 | up_write(&adfs_dir_rwsem); |
1da177e4 | 300 | |
f6075c79 | 301 | adfs_dir_mark_dirty(&dir); |
c3c8149b | 302 | |
f6075c79 RK |
303 | if (wait) |
304 | ret = adfs_dir_sync(&dir); | |
ffdc9064 | 305 | |
1dd9f5ba | 306 | adfs_dir_relse(&dir); |
deed1bfd RK |
307 | return ret; |
308 | ||
f6075c79 RK |
309 | /* |
310 | * If the updated failed because the entry wasn't found, we can | |
311 | * just release the buffers. If it was any other error, forget | |
312 | * the dirtied buffers so they aren't written back to the media. | |
313 | */ | |
314 | forget: | |
315 | if (ret == -ENOENT) | |
316 | adfs_dir_relse(&dir); | |
317 | else | |
318 | adfs_dir_forget(&dir); | |
deed1bfd RK |
319 | unlock: |
320 | up_write(&adfs_dir_rwsem); | |
4a0a88b6 | 321 | |
1da177e4 LT |
322 | return ret; |
323 | } | |
324 | ||
525715d0 RK |
325 | static unsigned char adfs_tolower(unsigned char c) |
326 | { | |
327 | if (c >= 'A' && c <= 'Z') | |
328 | c += 'a' - 'A'; | |
329 | return c; | |
330 | } | |
331 | ||
1e504cf8 RK |
332 | static int __adfs_compare(const unsigned char *qstr, u32 qlen, |
333 | const char *str, u32 len) | |
1da177e4 | 334 | { |
1e504cf8 | 335 | u32 i; |
1da177e4 | 336 | |
1e504cf8 RK |
337 | if (qlen != len) |
338 | return 1; | |
1da177e4 | 339 | |
525715d0 RK |
340 | for (i = 0; i < qlen; i++) |
341 | if (adfs_tolower(qstr[i]) != adfs_tolower(str[i])) | |
1e504cf8 | 342 | return 1; |
525715d0 | 343 | |
1e504cf8 | 344 | return 0; |
1da177e4 LT |
345 | } |
346 | ||
1e504cf8 RK |
347 | static int adfs_dir_lookup_byname(struct inode *inode, const struct qstr *qstr, |
348 | struct object_info *obj) | |
1da177e4 LT |
349 | { |
350 | struct super_block *sb = inode->i_sb; | |
0125f504 | 351 | const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir; |
1e504cf8 | 352 | const unsigned char *name; |
1da177e4 | 353 | struct adfs_dir dir; |
1e504cf8 | 354 | u32 name_len; |
1da177e4 LT |
355 | int ret; |
356 | ||
deed1bfd | 357 | down_read(&adfs_dir_rwsem); |
90011c7a | 358 | ret = adfs_dir_read_inode(sb, inode, &dir); |
1da177e4 | 359 | if (ret) |
deed1bfd | 360 | goto unlock; |
1da177e4 LT |
361 | |
362 | ret = ops->setpos(&dir, 0); | |
363 | if (ret) | |
deed1bfd | 364 | goto unlock_relse; |
1da177e4 LT |
365 | |
366 | ret = -ENOENT; | |
1e504cf8 RK |
367 | name = qstr->name; |
368 | name_len = qstr->len; | |
1da177e4 | 369 | while (ops->getnext(&dir, obj) == 0) { |
1e504cf8 | 370 | if (!__adfs_compare(name, name_len, obj->name, obj->name_len)) { |
1da177e4 LT |
371 | ret = 0; |
372 | break; | |
373 | } | |
374 | } | |
25e5d4df | 375 | obj->parent_id = ADFS_I(inode)->indaddr; |
1da177e4 | 376 | |
deed1bfd RK |
377 | unlock_relse: |
378 | up_read(&adfs_dir_rwsem); | |
1dd9f5ba | 379 | adfs_dir_relse(&dir); |
deed1bfd RK |
380 | return ret; |
381 | ||
382 | unlock: | |
383 | up_read(&adfs_dir_rwsem); | |
1da177e4 LT |
384 | return ret; |
385 | } | |
386 | ||
4b6f5d20 | 387 | const struct file_operations adfs_dir_operations = { |
1da177e4 | 388 | .read = generic_read_dir, |
59af1584 | 389 | .llseek = generic_file_llseek, |
cdc46e99 | 390 | .iterate_shared = adfs_iterate, |
1b061d92 | 391 | .fsync = generic_file_fsync, |
1da177e4 LT |
392 | }; |
393 | ||
394 | static int | |
da53be12 | 395 | adfs_hash(const struct dentry *parent, struct qstr *qstr) |
1da177e4 | 396 | { |
1da177e4 LT |
397 | const unsigned char *name; |
398 | unsigned long hash; | |
2eb0684f | 399 | u32 len; |
1da177e4 | 400 | |
2eb0684f RK |
401 | if (qstr->len > ADFS_SB(parent->d_sb)->s_namelen) |
402 | return -ENAMETOOLONG; | |
1da177e4 | 403 | |
2eb0684f | 404 | len = qstr->len; |
1da177e4 | 405 | name = qstr->name; |
8387ff25 | 406 | hash = init_name_hash(parent); |
2eb0684f | 407 | while (len--) |
525715d0 | 408 | hash = partial_name_hash(adfs_tolower(*name++), hash); |
1da177e4 LT |
409 | qstr->hash = end_name_hash(hash); |
410 | ||
411 | return 0; | |
412 | } | |
413 | ||
414 | /* | |
415 | * Compare two names, taking note of the name length | |
416 | * requirements of the underlying filesystem. | |
417 | */ | |
1e504cf8 RK |
418 | static int adfs_compare(const struct dentry *dentry, unsigned int len, |
419 | const char *str, const struct qstr *qstr) | |
1da177e4 | 420 | { |
1e504cf8 | 421 | return __adfs_compare(qstr->name, qstr->len, str, len); |
1da177e4 LT |
422 | } |
423 | ||
e16404ed | 424 | const struct dentry_operations adfs_dentry_operations = { |
1da177e4 LT |
425 | .d_hash = adfs_hash, |
426 | .d_compare = adfs_compare, | |
427 | }; | |
428 | ||
429 | static struct dentry * | |
00cd8dd3 | 430 | adfs_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags) |
1da177e4 LT |
431 | { |
432 | struct inode *inode = NULL; | |
433 | struct object_info obj; | |
434 | int error; | |
435 | ||
1da177e4 LT |
436 | error = adfs_dir_lookup_byname(dir, &dentry->d_name, &obj); |
437 | if (error == 0) { | |
1da177e4 LT |
438 | /* |
439 | * This only returns NULL if get_empty_inode | |
440 | * fails. | |
441 | */ | |
442 | inode = adfs_iget(dir->i_sb, &obj); | |
9a7dddca AV |
443 | if (!inode) |
444 | inode = ERR_PTR(-EACCES); | |
445 | } else if (error != -ENOENT) { | |
446 | inode = ERR_PTR(error); | |
1da177e4 | 447 | } |
9a7dddca | 448 | return d_splice_alias(inode, dentry); |
1da177e4 LT |
449 | } |
450 | ||
451 | /* | |
452 | * directories can handle most operations... | |
453 | */ | |
754661f1 | 454 | const struct inode_operations adfs_dir_inode_operations = { |
1da177e4 LT |
455 | .lookup = adfs_lookup, |
456 | .setattr = adfs_notify_change, | |
457 | }; |