• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
adfs_object_fixup(struct adfs_dir * dir,struct object_info * obj)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
adfs_readdir(struct file * file,struct dir_context * ctx)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
adfs_dir_update(struct super_block * sb,struct object_info * obj,int wait)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 
adfs_tolower(unsigned char c)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 
__adfs_compare(const unsigned char * qstr,u32 qlen,const char * str,u32 len)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 
adfs_dir_lookup_byname(struct inode * inode,const struct qstr * qstr,struct object_info * obj)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
adfs_hash(const struct dentry * parent,struct qstr * qstr)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  */
adfs_compare(const struct dentry * dentry,unsigned int len,const char * str,const struct qstr * qstr)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 *
adfs_lookup(struct inode * dir,struct dentry * dentry,unsigned int flags)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 };
280