diff options
Diffstat (limited to 'fs/affs/namei.c')
-rw-r--r-- | fs/affs/namei.c | 443 |
1 files changed, 443 insertions, 0 deletions
diff --git a/fs/affs/namei.c b/fs/affs/namei.c new file mode 100644 index 000000000000..d4c2d636c479 --- /dev/null +++ b/fs/affs/namei.c | |||
@@ -0,0 +1,443 @@ | |||
1 | /* | ||
2 | * linux/fs/affs/namei.c | ||
3 | * | ||
4 | * (c) 1996 Hans-Joachim Widmaier - Rewritten | ||
5 | * | ||
6 | * (C) 1993 Ray Burr - Modified for Amiga FFS filesystem. | ||
7 | * | ||
8 | * (C) 1991 Linus Torvalds - minix filesystem | ||
9 | */ | ||
10 | |||
11 | #include "affs.h" | ||
12 | |||
13 | typedef int (*toupper_t)(int); | ||
14 | |||
15 | static int affs_toupper(int ch); | ||
16 | static int affs_hash_dentry(struct dentry *, struct qstr *); | ||
17 | static int affs_compare_dentry(struct dentry *, struct qstr *, struct qstr *); | ||
18 | static int affs_intl_toupper(int ch); | ||
19 | static int affs_intl_hash_dentry(struct dentry *, struct qstr *); | ||
20 | static int affs_intl_compare_dentry(struct dentry *, struct qstr *, struct qstr *); | ||
21 | |||
22 | struct dentry_operations affs_dentry_operations = { | ||
23 | .d_hash = affs_hash_dentry, | ||
24 | .d_compare = affs_compare_dentry, | ||
25 | }; | ||
26 | |||
27 | static struct dentry_operations affs_intl_dentry_operations = { | ||
28 | .d_hash = affs_intl_hash_dentry, | ||
29 | .d_compare = affs_intl_compare_dentry, | ||
30 | }; | ||
31 | |||
32 | |||
33 | /* Simple toupper() for DOS\1 */ | ||
34 | |||
35 | static int | ||
36 | affs_toupper(int ch) | ||
37 | { | ||
38 | return ch >= 'a' && ch <= 'z' ? ch -= ('a' - 'A') : ch; | ||
39 | } | ||
40 | |||
41 | /* International toupper() for DOS\3 ("international") */ | ||
42 | |||
43 | static int | ||
44 | affs_intl_toupper(int ch) | ||
45 | { | ||
46 | return (ch >= 'a' && ch <= 'z') || (ch >= 0xE0 | ||
47 | && ch <= 0xFE && ch != 0xF7) ? | ||
48 | ch - ('a' - 'A') : ch; | ||
49 | } | ||
50 | |||
51 | static inline toupper_t | ||
52 | affs_get_toupper(struct super_block *sb) | ||
53 | { | ||
54 | return AFFS_SB(sb)->s_flags & SF_INTL ? affs_intl_toupper : affs_toupper; | ||
55 | } | ||
56 | |||
57 | /* | ||
58 | * Note: the dentry argument is the parent dentry. | ||
59 | */ | ||
60 | static inline int | ||
61 | __affs_hash_dentry(struct dentry *dentry, struct qstr *qstr, toupper_t toupper) | ||
62 | { | ||
63 | const u8 *name = qstr->name; | ||
64 | unsigned long hash; | ||
65 | int i; | ||
66 | |||
67 | i = affs_check_name(qstr->name,qstr->len); | ||
68 | if (i) | ||
69 | return i; | ||
70 | |||
71 | hash = init_name_hash(); | ||
72 | i = min(qstr->len, 30u); | ||
73 | for (; i > 0; name++, i--) | ||
74 | hash = partial_name_hash(toupper(*name), hash); | ||
75 | qstr->hash = end_name_hash(hash); | ||
76 | |||
77 | return 0; | ||
78 | } | ||
79 | |||
80 | static int | ||
81 | affs_hash_dentry(struct dentry *dentry, struct qstr *qstr) | ||
82 | { | ||
83 | return __affs_hash_dentry(dentry, qstr, affs_toupper); | ||
84 | } | ||
85 | static int | ||
86 | affs_intl_hash_dentry(struct dentry *dentry, struct qstr *qstr) | ||
87 | { | ||
88 | return __affs_hash_dentry(dentry, qstr, affs_intl_toupper); | ||
89 | } | ||
90 | |||
91 | static inline int | ||
92 | __affs_compare_dentry(struct dentry *dentry, struct qstr *a, struct qstr *b, toupper_t toupper) | ||
93 | { | ||
94 | const u8 *aname = a->name; | ||
95 | const u8 *bname = b->name; | ||
96 | int len; | ||
97 | |||
98 | /* 'a' is the qstr of an already existing dentry, so the name | ||
99 | * must be valid. 'b' must be validated first. | ||
100 | */ | ||
101 | |||
102 | if (affs_check_name(b->name,b->len)) | ||
103 | return 1; | ||
104 | |||
105 | /* If the names are longer than the allowed 30 chars, | ||
106 | * the excess is ignored, so their length may differ. | ||
107 | */ | ||
108 | len = a->len; | ||
109 | if (len >= 30) { | ||
110 | if (b->len < 30) | ||
111 | return 1; | ||
112 | len = 30; | ||
113 | } else if (len != b->len) | ||
114 | return 1; | ||
115 | |||
116 | for (; len > 0; len--) | ||
117 | if (toupper(*aname++) != toupper(*bname++)) | ||
118 | return 1; | ||
119 | |||
120 | return 0; | ||
121 | } | ||
122 | |||
123 | static int | ||
124 | affs_compare_dentry(struct dentry *dentry, struct qstr *a, struct qstr *b) | ||
125 | { | ||
126 | return __affs_compare_dentry(dentry, a, b, affs_toupper); | ||
127 | } | ||
128 | static int | ||
129 | affs_intl_compare_dentry(struct dentry *dentry, struct qstr *a, struct qstr *b) | ||
130 | { | ||
131 | return __affs_compare_dentry(dentry, a, b, affs_intl_toupper); | ||
132 | } | ||
133 | |||
134 | /* | ||
135 | * NOTE! unlike strncmp, affs_match returns 1 for success, 0 for failure. | ||
136 | */ | ||
137 | |||
138 | static inline int | ||
139 | affs_match(struct dentry *dentry, const u8 *name2, toupper_t toupper) | ||
140 | { | ||
141 | const u8 *name = dentry->d_name.name; | ||
142 | int len = dentry->d_name.len; | ||
143 | |||
144 | if (len >= 30) { | ||
145 | if (*name2 < 30) | ||
146 | return 0; | ||
147 | len = 30; | ||
148 | } else if (len != *name2) | ||
149 | return 0; | ||
150 | |||
151 | for (name2++; len > 0; len--) | ||
152 | if (toupper(*name++) != toupper(*name2++)) | ||
153 | return 0; | ||
154 | return 1; | ||
155 | } | ||
156 | |||
157 | int | ||
158 | affs_hash_name(struct super_block *sb, const u8 *name, unsigned int len) | ||
159 | { | ||
160 | toupper_t toupper = affs_get_toupper(sb); | ||
161 | int hash; | ||
162 | |||
163 | hash = len = min(len, 30u); | ||
164 | for (; len > 0; len--) | ||
165 | hash = (hash * 13 + toupper(*name++)) & 0x7ff; | ||
166 | |||
167 | return hash % AFFS_SB(sb)->s_hashsize; | ||
168 | } | ||
169 | |||
170 | static struct buffer_head * | ||
171 | affs_find_entry(struct inode *dir, struct dentry *dentry) | ||
172 | { | ||
173 | struct super_block *sb = dir->i_sb; | ||
174 | struct buffer_head *bh; | ||
175 | toupper_t toupper = affs_get_toupper(sb); | ||
176 | u32 key; | ||
177 | |||
178 | pr_debug("AFFS: find_entry(\"%.*s\")\n", (int)dentry->d_name.len, dentry->d_name.name); | ||
179 | |||
180 | bh = affs_bread(sb, dir->i_ino); | ||
181 | if (!bh) | ||
182 | return ERR_PTR(-EIO); | ||
183 | |||
184 | key = be32_to_cpu(AFFS_HEAD(bh)->table[affs_hash_name(sb, dentry->d_name.name, dentry->d_name.len)]); | ||
185 | |||
186 | for (;;) { | ||
187 | affs_brelse(bh); | ||
188 | if (key == 0) | ||
189 | return NULL; | ||
190 | bh = affs_bread(sb, key); | ||
191 | if (!bh) | ||
192 | return ERR_PTR(-EIO); | ||
193 | if (affs_match(dentry, AFFS_TAIL(sb, bh)->name, toupper)) | ||
194 | return bh; | ||
195 | key = be32_to_cpu(AFFS_TAIL(sb, bh)->hash_chain); | ||
196 | } | ||
197 | } | ||
198 | |||
199 | struct dentry * | ||
200 | affs_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd) | ||
201 | { | ||
202 | struct super_block *sb = dir->i_sb; | ||
203 | struct buffer_head *bh; | ||
204 | struct inode *inode = NULL; | ||
205 | |||
206 | pr_debug("AFFS: lookup(\"%.*s\")\n",(int)dentry->d_name.len,dentry->d_name.name); | ||
207 | |||
208 | affs_lock_dir(dir); | ||
209 | bh = affs_find_entry(dir, dentry); | ||
210 | affs_unlock_dir(dir); | ||
211 | if (IS_ERR(bh)) { | ||
212 | return ERR_PTR(PTR_ERR(bh)); | ||
213 | } | ||
214 | if (bh) { | ||
215 | u32 ino = bh->b_blocknr; | ||
216 | |||
217 | /* store the real header ino in d_fsdata for faster lookups */ | ||
218 | dentry->d_fsdata = (void *)(long)ino; | ||
219 | switch (be32_to_cpu(AFFS_TAIL(sb, bh)->stype)) { | ||
220 | //link to dirs disabled | ||
221 | //case ST_LINKDIR: | ||
222 | case ST_LINKFILE: | ||
223 | ino = be32_to_cpu(AFFS_TAIL(sb, bh)->original); | ||
224 | } | ||
225 | affs_brelse(bh); | ||
226 | inode = iget(sb, ino); | ||
227 | if (!inode) { | ||
228 | return ERR_PTR(-EACCES); | ||
229 | } | ||
230 | } | ||
231 | dentry->d_op = AFFS_SB(sb)->s_flags & SF_INTL ? &affs_intl_dentry_operations : &affs_dentry_operations; | ||
232 | d_add(dentry, inode); | ||
233 | return NULL; | ||
234 | } | ||
235 | |||
236 | int | ||
237 | affs_unlink(struct inode *dir, struct dentry *dentry) | ||
238 | { | ||
239 | pr_debug("AFFS: unlink(dir=%d, \"%.*s\")\n", (u32)dir->i_ino, | ||
240 | (int)dentry->d_name.len, dentry->d_name.name); | ||
241 | |||
242 | return affs_remove_header(dentry); | ||
243 | } | ||
244 | |||
245 | int | ||
246 | affs_create(struct inode *dir, struct dentry *dentry, int mode, struct nameidata *nd) | ||
247 | { | ||
248 | struct super_block *sb = dir->i_sb; | ||
249 | struct inode *inode; | ||
250 | int error; | ||
251 | |||
252 | pr_debug("AFFS: create(%lu,\"%.*s\",0%o)\n",dir->i_ino,(int)dentry->d_name.len, | ||
253 | dentry->d_name.name,mode); | ||
254 | |||
255 | inode = affs_new_inode(dir); | ||
256 | if (!inode) | ||
257 | return -ENOSPC; | ||
258 | |||
259 | inode->i_mode = mode; | ||
260 | mode_to_prot(inode); | ||
261 | mark_inode_dirty(inode); | ||
262 | |||
263 | inode->i_op = &affs_file_inode_operations; | ||
264 | inode->i_fop = &affs_file_operations; | ||
265 | inode->i_mapping->a_ops = (AFFS_SB(sb)->s_flags & SF_OFS) ? &affs_aops_ofs : &affs_aops; | ||
266 | error = affs_add_entry(dir, inode, dentry, ST_FILE); | ||
267 | if (error) { | ||
268 | inode->i_nlink = 0; | ||
269 | iput(inode); | ||
270 | return error; | ||
271 | } | ||
272 | return 0; | ||
273 | } | ||
274 | |||
275 | int | ||
276 | affs_mkdir(struct inode *dir, struct dentry *dentry, int mode) | ||
277 | { | ||
278 | struct inode *inode; | ||
279 | int error; | ||
280 | |||
281 | pr_debug("AFFS: mkdir(%lu,\"%.*s\",0%o)\n",dir->i_ino, | ||
282 | (int)dentry->d_name.len,dentry->d_name.name,mode); | ||
283 | |||
284 | inode = affs_new_inode(dir); | ||
285 | if (!inode) | ||
286 | return -ENOSPC; | ||
287 | |||
288 | inode->i_mode = S_IFDIR | mode; | ||
289 | mode_to_prot(inode); | ||
290 | |||
291 | inode->i_op = &affs_dir_inode_operations; | ||
292 | inode->i_fop = &affs_dir_operations; | ||
293 | |||
294 | error = affs_add_entry(dir, inode, dentry, ST_USERDIR); | ||
295 | if (error) { | ||
296 | inode->i_nlink = 0; | ||
297 | mark_inode_dirty(inode); | ||
298 | iput(inode); | ||
299 | return error; | ||
300 | } | ||
301 | return 0; | ||
302 | } | ||
303 | |||
304 | int | ||
305 | affs_rmdir(struct inode *dir, struct dentry *dentry) | ||
306 | { | ||
307 | pr_debug("AFFS: rmdir(dir=%u, \"%.*s\")\n", (u32)dir->i_ino, | ||
308 | (int)dentry->d_name.len, dentry->d_name.name); | ||
309 | |||
310 | return affs_remove_header(dentry); | ||
311 | } | ||
312 | |||
313 | int | ||
314 | affs_symlink(struct inode *dir, struct dentry *dentry, const char *symname) | ||
315 | { | ||
316 | struct super_block *sb = dir->i_sb; | ||
317 | struct buffer_head *bh; | ||
318 | struct inode *inode; | ||
319 | char *p; | ||
320 | int i, maxlen, error; | ||
321 | char c, lc; | ||
322 | |||
323 | pr_debug("AFFS: symlink(%lu,\"%.*s\" -> \"%s\")\n",dir->i_ino, | ||
324 | (int)dentry->d_name.len,dentry->d_name.name,symname); | ||
325 | |||
326 | maxlen = AFFS_SB(sb)->s_hashsize * sizeof(u32) - 1; | ||
327 | inode = affs_new_inode(dir); | ||
328 | if (!inode) | ||
329 | return -ENOSPC; | ||
330 | |||
331 | inode->i_op = &affs_symlink_inode_operations; | ||
332 | inode->i_data.a_ops = &affs_symlink_aops; | ||
333 | inode->i_mode = S_IFLNK | 0777; | ||
334 | mode_to_prot(inode); | ||
335 | |||
336 | error = -EIO; | ||
337 | bh = affs_bread(sb, inode->i_ino); | ||
338 | if (!bh) | ||
339 | goto err; | ||
340 | i = 0; | ||
341 | p = (char *)AFFS_HEAD(bh)->table; | ||
342 | lc = '/'; | ||
343 | if (*symname == '/') { | ||
344 | while (*symname == '/') | ||
345 | symname++; | ||
346 | while (AFFS_SB(sb)->s_volume[i]) /* Cannot overflow */ | ||
347 | *p++ = AFFS_SB(sb)->s_volume[i++]; | ||
348 | } | ||
349 | while (i < maxlen && (c = *symname++)) { | ||
350 | if (c == '.' && lc == '/' && *symname == '.' && symname[1] == '/') { | ||
351 | *p++ = '/'; | ||
352 | i++; | ||
353 | symname += 2; | ||
354 | lc = '/'; | ||
355 | } else if (c == '.' && lc == '/' && *symname == '/') { | ||
356 | symname++; | ||
357 | lc = '/'; | ||
358 | } else { | ||
359 | *p++ = c; | ||
360 | lc = c; | ||
361 | i++; | ||
362 | } | ||
363 | if (lc == '/') | ||
364 | while (*symname == '/') | ||
365 | symname++; | ||
366 | } | ||
367 | *p = 0; | ||
368 | mark_buffer_dirty_inode(bh, inode); | ||
369 | affs_brelse(bh); | ||
370 | mark_inode_dirty(inode); | ||
371 | |||
372 | error = affs_add_entry(dir, inode, dentry, ST_SOFTLINK); | ||
373 | if (error) | ||
374 | goto err; | ||
375 | |||
376 | return 0; | ||
377 | |||
378 | err: | ||
379 | inode->i_nlink = 0; | ||
380 | mark_inode_dirty(inode); | ||
381 | iput(inode); | ||
382 | return error; | ||
383 | } | ||
384 | |||
385 | int | ||
386 | affs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry) | ||
387 | { | ||
388 | struct inode *inode = old_dentry->d_inode; | ||
389 | |||
390 | pr_debug("AFFS: link(%u, %u, \"%.*s\")\n", (u32)inode->i_ino, (u32)dir->i_ino, | ||
391 | (int)dentry->d_name.len,dentry->d_name.name); | ||
392 | |||
393 | return affs_add_entry(dir, inode, dentry, ST_LINKFILE); | ||
394 | } | ||
395 | |||
396 | int | ||
397 | affs_rename(struct inode *old_dir, struct dentry *old_dentry, | ||
398 | struct inode *new_dir, struct dentry *new_dentry) | ||
399 | { | ||
400 | struct super_block *sb = old_dir->i_sb; | ||
401 | struct buffer_head *bh = NULL; | ||
402 | int retval; | ||
403 | |||
404 | pr_debug("AFFS: rename(old=%u,\"%*s\" to new=%u,\"%*s\")\n", | ||
405 | (u32)old_dir->i_ino, (int)old_dentry->d_name.len, old_dentry->d_name.name, | ||
406 | (u32)new_dir->i_ino, (int)new_dentry->d_name.len, new_dentry->d_name.name); | ||
407 | |||
408 | retval = affs_check_name(new_dentry->d_name.name,new_dentry->d_name.len); | ||
409 | if (retval) | ||
410 | return retval; | ||
411 | |||
412 | /* Unlink destination if it already exists */ | ||
413 | if (new_dentry->d_inode) { | ||
414 | retval = affs_remove_header(new_dentry); | ||
415 | if (retval) | ||
416 | return retval; | ||
417 | } | ||
418 | |||
419 | retval = -EIO; | ||
420 | bh = affs_bread(sb, old_dentry->d_inode->i_ino); | ||
421 | if (!bh) | ||
422 | goto done; | ||
423 | |||
424 | /* Remove header from its parent directory. */ | ||
425 | affs_lock_dir(old_dir); | ||
426 | retval = affs_remove_hash(old_dir, bh); | ||
427 | affs_unlock_dir(old_dir); | ||
428 | if (retval) | ||
429 | goto done; | ||
430 | |||
431 | /* And insert it into the new directory with the new name. */ | ||
432 | affs_copy_name(AFFS_TAIL(sb, bh)->name, new_dentry); | ||
433 | affs_fix_checksum(sb, bh); | ||
434 | affs_lock_dir(new_dir); | ||
435 | retval = affs_insert_hash(new_dir, bh); | ||
436 | affs_unlock_dir(new_dir); | ||
437 | /* TODO: move it back to old_dir, if error? */ | ||
438 | |||
439 | done: | ||
440 | mark_buffer_dirty_inode(bh, retval ? old_dir : new_dir); | ||
441 | affs_brelse(bh); | ||
442 | return retval; | ||
443 | } | ||