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 | */ | |
1da177e4 LT |
9 | #include "adfs.h" |
10 | ||
11 | /* | |
12 | * For future. This should probably be per-directory. | |
13 | */ | |
14 | static DEFINE_RWLOCK(adfs_dir_lock); | |
15 | ||
411c49bc RK |
16 | void adfs_object_fixup(struct adfs_dir *dir, struct object_info *obj) |
17 | { | |
fc722a04 | 18 | unsigned int dots, i; |
adb514a4 RK |
19 | |
20 | /* | |
21 | * RISC OS allows the use of '/' in directory entry names, so we need | |
22 | * to fix these up. '/' is typically used for FAT compatibility to | |
23 | * represent '.', so do the same conversion here. In any case, '.' | |
24 | * will never be in a RISC OS name since it is used as the pathname | |
fc722a04 RK |
25 | * separator. Handle the case where we may generate a '.' or '..' |
26 | * name, replacing the first character with '^' (the RISC OS "parent | |
27 | * directory" character.) | |
adb514a4 | 28 | */ |
fc722a04 RK |
29 | for (i = dots = 0; i < obj->name_len; i++) |
30 | if (obj->name[i] == '/') { | |
adb514a4 | 31 | obj->name[i] = '.'; |
fc722a04 RK |
32 | dots++; |
33 | } | |
34 | ||
35 | if (obj->name_len <= 2 && dots == obj->name_len) | |
36 | obj->name[0] = '^'; | |
adb514a4 | 37 | |
411c49bc | 38 | /* |
b4ed8f75 RK |
39 | * If the object is a file, and the user requested the ,xyz hex |
40 | * filetype suffix to the name, check the filetype and append. | |
411c49bc | 41 | */ |
b4ed8f75 RK |
42 | if (!(obj->attr & ADFS_NDA_DIRECTORY) && ADFS_SB(dir->sb)->s_ftsuffix) { |
43 | u16 filetype = adfs_filetype(obj->loadaddr); | |
5f8de487 | 44 | |
b4ed8f75 | 45 | if (filetype != ADFS_FILETYPE_NONE) { |
5f8de487 RK |
46 | obj->name[obj->name_len++] = ','; |
47 | obj->name[obj->name_len++] = hex_asc_lo(filetype >> 8); | |
48 | obj->name[obj->name_len++] = hex_asc_lo(filetype >> 4); | |
49 | obj->name[obj->name_len++] = hex_asc_lo(filetype >> 0); | |
50 | } | |
411c49bc RK |
51 | } |
52 | } | |
53 | ||
1da177e4 | 54 | static int |
2638ffba | 55 | adfs_readdir(struct file *file, struct dir_context *ctx) |
1da177e4 | 56 | { |
2638ffba | 57 | struct inode *inode = file_inode(file); |
1da177e4 | 58 | struct super_block *sb = inode->i_sb; |
0125f504 | 59 | const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir; |
1da177e4 LT |
60 | struct object_info obj; |
61 | struct adfs_dir dir; | |
62 | int ret = 0; | |
63 | ||
2638ffba AV |
64 | if (ctx->pos >> 32) |
65 | return 0; | |
1da177e4 LT |
66 | |
67 | ret = ops->read(sb, inode->i_ino, inode->i_size, &dir); | |
68 | if (ret) | |
2638ffba | 69 | return ret; |
1da177e4 | 70 | |
2638ffba AV |
71 | if (ctx->pos == 0) { |
72 | if (!dir_emit_dot(file, ctx)) | |
1da177e4 | 73 | goto free_out; |
2638ffba AV |
74 | ctx->pos = 1; |
75 | } | |
76 | if (ctx->pos == 1) { | |
77 | if (!dir_emit(ctx, "..", 2, dir.parent_id, DT_DIR)) | |
1da177e4 | 78 | goto free_out; |
2638ffba | 79 | ctx->pos = 2; |
1da177e4 LT |
80 | } |
81 | ||
82 | read_lock(&adfs_dir_lock); | |
83 | ||
2638ffba | 84 | ret = ops->setpos(&dir, ctx->pos - 2); |
1da177e4 LT |
85 | if (ret) |
86 | goto unlock_out; | |
87 | while (ops->getnext(&dir, &obj) == 0) { | |
2638ffba | 88 | if (!dir_emit(ctx, obj.name, obj.name_len, |
5ed70bb4 | 89 | obj.indaddr, DT_UNKNOWN)) |
2638ffba AV |
90 | break; |
91 | ctx->pos++; | |
1da177e4 LT |
92 | } |
93 | ||
94 | unlock_out: | |
95 | read_unlock(&adfs_dir_lock); | |
96 | ||
97 | free_out: | |
98 | ops->free(&dir); | |
1da177e4 LT |
99 | return ret; |
100 | } | |
101 | ||
102 | int | |
ffdc9064 | 103 | adfs_dir_update(struct super_block *sb, struct object_info *obj, int wait) |
1da177e4 LT |
104 | { |
105 | int ret = -EINVAL; | |
106 | #ifdef CONFIG_ADFS_FS_RW | |
0125f504 | 107 | const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir; |
1da177e4 LT |
108 | struct adfs_dir dir; |
109 | ||
5ed70bb4 RK |
110 | printk(KERN_INFO "adfs_dir_update: object %06x in dir %06x\n", |
111 | obj->indaddr, obj->parent_id); | |
1da177e4 LT |
112 | |
113 | if (!ops->update) { | |
114 | ret = -EINVAL; | |
115 | goto out; | |
116 | } | |
117 | ||
118 | ret = ops->read(sb, obj->parent_id, 0, &dir); | |
119 | if (ret) | |
120 | goto out; | |
121 | ||
122 | write_lock(&adfs_dir_lock); | |
123 | ret = ops->update(&dir, obj); | |
124 | write_unlock(&adfs_dir_lock); | |
125 | ||
ffdc9064 AV |
126 | if (wait) { |
127 | int err = ops->sync(&dir); | |
128 | if (!ret) | |
129 | ret = err; | |
130 | } | |
131 | ||
1da177e4 LT |
132 | ops->free(&dir); |
133 | out: | |
134 | #endif | |
135 | return ret; | |
136 | } | |
137 | ||
525715d0 RK |
138 | static unsigned char adfs_tolower(unsigned char c) |
139 | { | |
140 | if (c >= 'A' && c <= 'Z') | |
141 | c += 'a' - 'A'; | |
142 | return c; | |
143 | } | |
144 | ||
1e504cf8 RK |
145 | static int __adfs_compare(const unsigned char *qstr, u32 qlen, |
146 | const char *str, u32 len) | |
1da177e4 | 147 | { |
1e504cf8 | 148 | u32 i; |
1da177e4 | 149 | |
1e504cf8 RK |
150 | if (qlen != len) |
151 | return 1; | |
1da177e4 | 152 | |
525715d0 RK |
153 | for (i = 0; i < qlen; i++) |
154 | if (adfs_tolower(qstr[i]) != adfs_tolower(str[i])) | |
1e504cf8 | 155 | return 1; |
525715d0 | 156 | |
1e504cf8 | 157 | return 0; |
1da177e4 LT |
158 | } |
159 | ||
1e504cf8 RK |
160 | static int adfs_dir_lookup_byname(struct inode *inode, const struct qstr *qstr, |
161 | struct object_info *obj) | |
1da177e4 LT |
162 | { |
163 | struct super_block *sb = inode->i_sb; | |
0125f504 | 164 | const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir; |
1e504cf8 | 165 | const unsigned char *name; |
1da177e4 | 166 | struct adfs_dir dir; |
1e504cf8 | 167 | u32 name_len; |
1da177e4 LT |
168 | int ret; |
169 | ||
170 | ret = ops->read(sb, inode->i_ino, inode->i_size, &dir); | |
171 | if (ret) | |
172 | goto out; | |
173 | ||
174 | if (ADFS_I(inode)->parent_id != dir.parent_id) { | |
5ed70bb4 RK |
175 | adfs_error(sb, |
176 | "parent directory changed under me! (%06x but got %06x)\n", | |
1da177e4 LT |
177 | ADFS_I(inode)->parent_id, dir.parent_id); |
178 | ret = -EIO; | |
179 | goto free_out; | |
180 | } | |
181 | ||
182 | obj->parent_id = inode->i_ino; | |
183 | ||
1da177e4 LT |
184 | read_lock(&adfs_dir_lock); |
185 | ||
186 | ret = ops->setpos(&dir, 0); | |
187 | if (ret) | |
188 | goto unlock_out; | |
189 | ||
190 | ret = -ENOENT; | |
1e504cf8 RK |
191 | name = qstr->name; |
192 | name_len = qstr->len; | |
1da177e4 | 193 | while (ops->getnext(&dir, obj) == 0) { |
1e504cf8 | 194 | if (!__adfs_compare(name, name_len, obj->name, obj->name_len)) { |
1da177e4 LT |
195 | ret = 0; |
196 | break; | |
197 | } | |
198 | } | |
199 | ||
200 | unlock_out: | |
201 | read_unlock(&adfs_dir_lock); | |
202 | ||
203 | free_out: | |
204 | ops->free(&dir); | |
205 | out: | |
206 | return ret; | |
207 | } | |
208 | ||
4b6f5d20 | 209 | const struct file_operations adfs_dir_operations = { |
1da177e4 | 210 | .read = generic_read_dir, |
59af1584 | 211 | .llseek = generic_file_llseek, |
2638ffba | 212 | .iterate = adfs_readdir, |
1b061d92 | 213 | .fsync = generic_file_fsync, |
1da177e4 LT |
214 | }; |
215 | ||
216 | static int | |
da53be12 | 217 | adfs_hash(const struct dentry *parent, struct qstr *qstr) |
1da177e4 | 218 | { |
1da177e4 LT |
219 | const unsigned char *name; |
220 | unsigned long hash; | |
2eb0684f | 221 | u32 len; |
1da177e4 | 222 | |
2eb0684f RK |
223 | if (qstr->len > ADFS_SB(parent->d_sb)->s_namelen) |
224 | return -ENAMETOOLONG; | |
1da177e4 | 225 | |
2eb0684f | 226 | len = qstr->len; |
1da177e4 | 227 | name = qstr->name; |
8387ff25 | 228 | hash = init_name_hash(parent); |
2eb0684f | 229 | while (len--) |
525715d0 | 230 | hash = partial_name_hash(adfs_tolower(*name++), hash); |
1da177e4 LT |
231 | qstr->hash = end_name_hash(hash); |
232 | ||
233 | return 0; | |
234 | } | |
235 | ||
236 | /* | |
237 | * Compare two names, taking note of the name length | |
238 | * requirements of the underlying filesystem. | |
239 | */ | |
1e504cf8 RK |
240 | static int adfs_compare(const struct dentry *dentry, unsigned int len, |
241 | const char *str, const struct qstr *qstr) | |
1da177e4 | 242 | { |
1e504cf8 | 243 | return __adfs_compare(qstr->name, qstr->len, str, len); |
1da177e4 LT |
244 | } |
245 | ||
e16404ed | 246 | const struct dentry_operations adfs_dentry_operations = { |
1da177e4 LT |
247 | .d_hash = adfs_hash, |
248 | .d_compare = adfs_compare, | |
249 | }; | |
250 | ||
251 | static struct dentry * | |
00cd8dd3 | 252 | adfs_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags) |
1da177e4 LT |
253 | { |
254 | struct inode *inode = NULL; | |
255 | struct object_info obj; | |
256 | int error; | |
257 | ||
1da177e4 LT |
258 | error = adfs_dir_lookup_byname(dir, &dentry->d_name, &obj); |
259 | if (error == 0) { | |
1da177e4 LT |
260 | /* |
261 | * This only returns NULL if get_empty_inode | |
262 | * fails. | |
263 | */ | |
264 | inode = adfs_iget(dir->i_sb, &obj); | |
9a7dddca AV |
265 | if (!inode) |
266 | inode = ERR_PTR(-EACCES); | |
267 | } else if (error != -ENOENT) { | |
268 | inode = ERR_PTR(error); | |
1da177e4 | 269 | } |
9a7dddca | 270 | return d_splice_alias(inode, dentry); |
1da177e4 LT |
271 | } |
272 | ||
273 | /* | |
274 | * directories can handle most operations... | |
275 | */ | |
754661f1 | 276 | const struct inode_operations adfs_dir_inode_operations = { |
1da177e4 LT |
277 | .lookup = adfs_lookup, |
278 | .setattr = adfs_notify_change, | |
279 | }; |