Merge tag 'sound-5.3-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai...
[linux-2.6-microblaze.git] / fs / adfs / dir.c
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 "adfs.h"
10
11 /*
12  * For future.  This should probably be per-directory.
13  */
14 static DEFINE_RWLOCK(adfs_dir_lock);
15
16 void adfs_object_fixup(struct adfs_dir *dir, struct object_info *obj)
17 {
18         unsigned int dots, i;
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
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.)
28          */
29         for (i = dots = 0; i < obj->name_len; i++)
30                 if (obj->name[i] == '/') {
31                         obj->name[i] = '.';
32                         dots++;
33                 }
34
35         if (obj->name_len <= 2 && dots == obj->name_len)
36                 obj->name[0] = '^';
37
38         /*
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.
41          */
42         if (!(obj->attr & ADFS_NDA_DIRECTORY) && ADFS_SB(dir->sb)->s_ftsuffix) {
43                 u16 filetype = adfs_filetype(obj->loadaddr);
44
45                 if (filetype != ADFS_FILETYPE_NONE) {
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                 }
51         }
52 }
53
54 static int
55 adfs_readdir(struct file *file, struct dir_context *ctx)
56 {
57         struct inode *inode = file_inode(file);
58         struct super_block *sb = inode->i_sb;
59         const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir;
60         struct object_info obj;
61         struct adfs_dir dir;
62         int ret = 0;
63
64         if (ctx->pos >> 32)
65                 return 0;
66
67         ret = ops->read(sb, inode->i_ino, inode->i_size, &dir);
68         if (ret)
69                 return ret;
70
71         if (ctx->pos == 0) {
72                 if (!dir_emit_dot(file, ctx))
73                         goto free_out;
74                 ctx->pos = 1;
75         }
76         if (ctx->pos == 1) {
77                 if (!dir_emit(ctx, "..", 2, dir.parent_id, DT_DIR))
78                         goto free_out;
79                 ctx->pos = 2;
80         }
81
82         read_lock(&adfs_dir_lock);
83
84         ret = ops->setpos(&dir, ctx->pos - 2);
85         if (ret)
86                 goto unlock_out;
87         while (ops->getnext(&dir, &obj) == 0) {
88                 if (!dir_emit(ctx, obj.name, obj.name_len,
89                               obj.indaddr, DT_UNKNOWN))
90                         break;
91                 ctx->pos++;
92         }
93
94 unlock_out:
95         read_unlock(&adfs_dir_lock);
96
97 free_out:
98         ops->free(&dir);
99         return ret;
100 }
101
102 int
103 adfs_dir_update(struct super_block *sb, struct object_info *obj, int wait)
104 {
105         int ret = -EINVAL;
106 #ifdef CONFIG_ADFS_FS_RW
107         const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir;
108         struct adfs_dir dir;
109
110         printk(KERN_INFO "adfs_dir_update: object %06x in dir %06x\n",
111                  obj->indaddr, obj->parent_id);
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
126         if (wait) {
127                 int err = ops->sync(&dir);
128                 if (!ret)
129                         ret = err;
130         }
131
132         ops->free(&dir);
133 out:
134 #endif
135         return ret;
136 }
137
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
145 static int __adfs_compare(const unsigned char *qstr, u32 qlen,
146                           const char *str, u32 len)
147 {
148         u32 i;
149
150         if (qlen != len)
151                 return 1;
152
153         for (i = 0; i < qlen; i++)
154                 if (adfs_tolower(qstr[i]) != adfs_tolower(str[i]))
155                         return 1;
156
157         return 0;
158 }
159
160 static int adfs_dir_lookup_byname(struct inode *inode, const struct qstr *qstr,
161                                   struct object_info *obj)
162 {
163         struct super_block *sb = inode->i_sb;
164         const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir;
165         const unsigned char *name;
166         struct adfs_dir dir;
167         u32 name_len;
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) {
175                 adfs_error(sb,
176                            "parent directory changed under me! (%06x but got %06x)\n",
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
184         read_lock(&adfs_dir_lock);
185
186         ret = ops->setpos(&dir, 0);
187         if (ret)
188                 goto unlock_out;
189
190         ret = -ENOENT;
191         name = qstr->name;
192         name_len = qstr->len;
193         while (ops->getnext(&dir, obj) == 0) {
194                 if (!__adfs_compare(name, name_len, obj->name, obj->name_len)) {
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
209 const struct file_operations adfs_dir_operations = {
210         .read           = generic_read_dir,
211         .llseek         = generic_file_llseek,
212         .iterate        = adfs_readdir,
213         .fsync          = generic_file_fsync,
214 };
215
216 static int
217 adfs_hash(const struct dentry *parent, struct qstr *qstr)
218 {
219         const unsigned char *name;
220         unsigned long hash;
221         u32 len;
222
223         if (qstr->len > ADFS_SB(parent->d_sb)->s_namelen)
224                 return -ENAMETOOLONG;
225
226         len = qstr->len;
227         name = qstr->name;
228         hash = init_name_hash(parent);
229         while (len--)
230                 hash = partial_name_hash(adfs_tolower(*name++), hash);
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  */
240 static int adfs_compare(const struct dentry *dentry, unsigned int len,
241                         const char *str, const struct qstr *qstr)
242 {
243         return __adfs_compare(qstr->name, qstr->len, str, len);
244 }
245
246 const struct dentry_operations adfs_dentry_operations = {
247         .d_hash         = adfs_hash,
248         .d_compare      = adfs_compare,
249 };
250
251 static struct dentry *
252 adfs_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags)
253 {
254         struct inode *inode = NULL;
255         struct object_info obj;
256         int error;
257
258         error = adfs_dir_lookup_byname(dir, &dentry->d_name, &obj);
259         if (error == 0) {
260                 /*
261                  * This only returns NULL if get_empty_inode
262                  * fails.
263                  */
264                 inode = adfs_iget(dir->i_sb, &obj);
265                 if (!inode)
266                         inode = ERR_PTR(-EACCES);
267         } else if (error != -ENOENT) {
268                 inode = ERR_PTR(error);
269         }
270         return d_splice_alias(inode, dentry);
271 }
272
273 /*
274  * directories can handle most operations...
275  */
276 const struct inode_operations adfs_dir_inode_operations = {
277         .lookup         = adfs_lookup,
278         .setattr        = adfs_notify_change,
279 };