Linux-2.6.12-rc2
[GitHub/mt8127/android_kernel_alcatel_ttab.git] / fs / hfsplus / dir.c
1 /*
2 * linux/fs/hfsplus/dir.c
3 *
4 * Copyright (C) 2001
5 * Brad Boyer (flar@allandria.com)
6 * (C) 2003 Ardis Technologies <roman@ardistech.com>
7 *
8 * Handling of directories
9 */
10
11 #include <linux/errno.h>
12 #include <linux/fs.h>
13 #include <linux/sched.h>
14 #include <linux/slab.h>
15 #include <linux/random.h>
16 #include <linux/version.h>
17
18 #include "hfsplus_fs.h"
19 #include "hfsplus_raw.h"
20
21 static inline void hfsplus_instantiate(struct dentry *dentry,
22 struct inode *inode, u32 cnid)
23 {
24 dentry->d_fsdata = (void *)(unsigned long)cnid;
25 d_instantiate(dentry, inode);
26 }
27
28 /* Find the entry inside dir named dentry->d_name */
29 static struct dentry *hfsplus_lookup(struct inode *dir, struct dentry *dentry,
30 struct nameidata *nd)
31 {
32 struct inode *inode = NULL;
33 struct hfs_find_data fd;
34 struct super_block *sb;
35 hfsplus_cat_entry entry;
36 int err;
37 u32 cnid, linkid = 0;
38 u16 type;
39
40 sb = dir->i_sb;
41 dentry->d_fsdata = NULL;
42 hfs_find_init(HFSPLUS_SB(sb).cat_tree, &fd);
43 hfsplus_cat_build_key(sb, fd.search_key, dir->i_ino, &dentry->d_name);
44 again:
45 err = hfs_brec_read(&fd, &entry, sizeof(entry));
46 if (err) {
47 if (err == -ENOENT) {
48 hfs_find_exit(&fd);
49 /* No such entry */
50 inode = NULL;
51 goto out;
52 }
53 goto fail;
54 }
55 type = be16_to_cpu(entry.type);
56 if (type == HFSPLUS_FOLDER) {
57 if (fd.entrylength < sizeof(struct hfsplus_cat_folder)) {
58 err = -EIO;
59 goto fail;
60 }
61 cnid = be32_to_cpu(entry.folder.id);
62 dentry->d_fsdata = (void *)(unsigned long)cnid;
63 } else if (type == HFSPLUS_FILE) {
64 if (fd.entrylength < sizeof(struct hfsplus_cat_file)) {
65 err = -EIO;
66 goto fail;
67 }
68 cnid = be32_to_cpu(entry.file.id);
69 if (entry.file.user_info.fdType == cpu_to_be32(HFSP_HARDLINK_TYPE) &&
70 entry.file.user_info.fdCreator == cpu_to_be32(HFSP_HFSPLUS_CREATOR)) {
71 struct qstr str;
72 char name[32];
73
74 if (dentry->d_fsdata) {
75 err = -ENOENT;
76 inode = NULL;
77 goto out;
78 }
79 dentry->d_fsdata = (void *)(unsigned long)cnid;
80 linkid = be32_to_cpu(entry.file.permissions.dev);
81 str.len = sprintf(name, "iNode%d", linkid);
82 str.name = name;
83 hfsplus_cat_build_key(sb, fd.search_key, HFSPLUS_SB(sb).hidden_dir->i_ino, &str);
84 goto again;
85 } else if (!dentry->d_fsdata)
86 dentry->d_fsdata = (void *)(unsigned long)cnid;
87 } else {
88 printk("HFS+-fs: Illegal catalog entry type in lookup\n");
89 err = -EIO;
90 goto fail;
91 }
92 hfs_find_exit(&fd);
93 inode = iget(dir->i_sb, cnid);
94 if (!inode)
95 return ERR_PTR(-EACCES);
96 if (S_ISREG(inode->i_mode))
97 HFSPLUS_I(inode).dev = linkid;
98 out:
99 d_add(dentry, inode);
100 return NULL;
101 fail:
102 hfs_find_exit(&fd);
103 return ERR_PTR(err);
104 }
105
106 static int hfsplus_readdir(struct file *filp, void *dirent, filldir_t filldir)
107 {
108 struct inode *inode = filp->f_dentry->d_inode;
109 struct super_block *sb = inode->i_sb;
110 int len, err;
111 char strbuf[HFSPLUS_MAX_STRLEN + 1];
112 hfsplus_cat_entry entry;
113 struct hfs_find_data fd;
114 struct hfsplus_readdir_data *rd;
115 u16 type;
116
117 if (filp->f_pos >= inode->i_size)
118 return 0;
119
120 hfs_find_init(HFSPLUS_SB(sb).cat_tree, &fd);
121 hfsplus_cat_build_key(sb, fd.search_key, inode->i_ino, NULL);
122 err = hfs_brec_find(&fd);
123 if (err)
124 goto out;
125
126 switch ((u32)filp->f_pos) {
127 case 0:
128 /* This is completely artificial... */
129 if (filldir(dirent, ".", 1, 0, inode->i_ino, DT_DIR))
130 goto out;
131 filp->f_pos++;
132 /* fall through */
133 case 1:
134 hfs_bnode_read(fd.bnode, &entry, fd.entryoffset, fd.entrylength);
135 if (be16_to_cpu(entry.type) != HFSPLUS_FOLDER_THREAD) {
136 printk("HFS+-fs: bad catalog folder thread\n");
137 err = -EIO;
138 goto out;
139 }
140 if (fd.entrylength < HFSPLUS_MIN_THREAD_SZ) {
141 printk("HFS+-fs: truncated catalog thread\n");
142 err = -EIO;
143 goto out;
144 }
145 if (filldir(dirent, "..", 2, 1,
146 be32_to_cpu(entry.thread.parentID), DT_DIR))
147 goto out;
148 filp->f_pos++;
149 /* fall through */
150 default:
151 if (filp->f_pos >= inode->i_size)
152 goto out;
153 err = hfs_brec_goto(&fd, filp->f_pos - 1);
154 if (err)
155 goto out;
156 }
157
158 for (;;) {
159 if (be32_to_cpu(fd.key->cat.parent) != inode->i_ino) {
160 printk("HFS+-fs: walked past end of dir\n");
161 err = -EIO;
162 goto out;
163 }
164 hfs_bnode_read(fd.bnode, &entry, fd.entryoffset, fd.entrylength);
165 type = be16_to_cpu(entry.type);
166 len = HFSPLUS_MAX_STRLEN;
167 err = hfsplus_uni2asc(sb, &fd.key->cat.name, strbuf, &len);
168 if (err)
169 goto out;
170 if (type == HFSPLUS_FOLDER) {
171 if (fd.entrylength < sizeof(struct hfsplus_cat_folder)) {
172 printk("HFS+-fs: small dir entry\n");
173 err = -EIO;
174 goto out;
175 }
176 if (HFSPLUS_SB(sb).hidden_dir &&
177 HFSPLUS_SB(sb).hidden_dir->i_ino == be32_to_cpu(entry.folder.id))
178 goto next;
179 if (filldir(dirent, strbuf, len, filp->f_pos,
180 be32_to_cpu(entry.folder.id), DT_DIR))
181 break;
182 } else if (type == HFSPLUS_FILE) {
183 if (fd.entrylength < sizeof(struct hfsplus_cat_file)) {
184 printk("HFS+-fs: small file entry\n");
185 err = -EIO;
186 goto out;
187 }
188 if (filldir(dirent, strbuf, len, filp->f_pos,
189 be32_to_cpu(entry.file.id), DT_REG))
190 break;
191 } else {
192 printk("HFS+-fs: bad catalog entry type\n");
193 err = -EIO;
194 goto out;
195 }
196 next:
197 filp->f_pos++;
198 if (filp->f_pos >= inode->i_size)
199 goto out;
200 err = hfs_brec_goto(&fd, 1);
201 if (err)
202 goto out;
203 }
204 rd = filp->private_data;
205 if (!rd) {
206 rd = kmalloc(sizeof(struct hfsplus_readdir_data), GFP_KERNEL);
207 if (!rd) {
208 err = -ENOMEM;
209 goto out;
210 }
211 filp->private_data = rd;
212 rd->file = filp;
213 list_add(&rd->list, &HFSPLUS_I(inode).open_dir_list);
214 }
215 memcpy(&rd->key, fd.key, sizeof(struct hfsplus_cat_key));
216 out:
217 hfs_find_exit(&fd);
218 return err;
219 }
220
221 static int hfsplus_dir_release(struct inode *inode, struct file *file)
222 {
223 struct hfsplus_readdir_data *rd = file->private_data;
224 if (rd) {
225 list_del(&rd->list);
226 kfree(rd);
227 }
228 return 0;
229 }
230
231 static int hfsplus_create(struct inode *dir, struct dentry *dentry, int mode,
232 struct nameidata *nd)
233 {
234 struct inode *inode;
235 int res;
236
237 inode = hfsplus_new_inode(dir->i_sb, mode);
238 if (!inode)
239 return -ENOSPC;
240
241 res = hfsplus_create_cat(inode->i_ino, dir, &dentry->d_name, inode);
242 if (res) {
243 inode->i_nlink = 0;
244 hfsplus_delete_inode(inode);
245 iput(inode);
246 return res;
247 }
248 hfsplus_instantiate(dentry, inode, inode->i_ino);
249 mark_inode_dirty(inode);
250 return 0;
251 }
252
253 static int hfsplus_link(struct dentry *src_dentry, struct inode *dst_dir,
254 struct dentry *dst_dentry)
255 {
256 struct super_block *sb = dst_dir->i_sb;
257 struct inode *inode = src_dentry->d_inode;
258 struct inode *src_dir = src_dentry->d_parent->d_inode;
259 struct qstr str;
260 char name[32];
261 u32 cnid, id;
262 int res;
263
264 if (HFSPLUS_IS_RSRC(inode))
265 return -EPERM;
266
267 if (inode->i_ino == (u32)(unsigned long)src_dentry->d_fsdata) {
268 for (;;) {
269 get_random_bytes(&id, sizeof(cnid));
270 id &= 0x3fffffff;
271 str.name = name;
272 str.len = sprintf(name, "iNode%d", id);
273 res = hfsplus_rename_cat(inode->i_ino,
274 src_dir, &src_dentry->d_name,
275 HFSPLUS_SB(sb).hidden_dir, &str);
276 if (!res)
277 break;
278 if (res != -EEXIST)
279 return res;
280 }
281 HFSPLUS_I(inode).dev = id;
282 cnid = HFSPLUS_SB(sb).next_cnid++;
283 src_dentry->d_fsdata = (void *)(unsigned long)cnid;
284 res = hfsplus_create_cat(cnid, src_dir, &src_dentry->d_name, inode);
285 if (res)
286 /* panic? */
287 return res;
288 HFSPLUS_SB(sb).file_count++;
289 }
290 cnid = HFSPLUS_SB(sb).next_cnid++;
291 res = hfsplus_create_cat(cnid, dst_dir, &dst_dentry->d_name, inode);
292 if (res)
293 return res;
294
295 inode->i_nlink++;
296 hfsplus_instantiate(dst_dentry, inode, cnid);
297 atomic_inc(&inode->i_count);
298 inode->i_ctime = CURRENT_TIME_SEC;
299 mark_inode_dirty(inode);
300 HFSPLUS_SB(sb).file_count++;
301 sb->s_dirt = 1;
302
303 return 0;
304 }
305
306 static int hfsplus_unlink(struct inode *dir, struct dentry *dentry)
307 {
308 struct super_block *sb = dir->i_sb;
309 struct inode *inode = dentry->d_inode;
310 struct qstr str;
311 char name[32];
312 u32 cnid;
313 int res;
314
315 if (HFSPLUS_IS_RSRC(inode))
316 return -EPERM;
317
318 cnid = (u32)(unsigned long)dentry->d_fsdata;
319 if (inode->i_ino == cnid &&
320 atomic_read(&HFSPLUS_I(inode).opencnt)) {
321 str.name = name;
322 str.len = sprintf(name, "temp%lu", inode->i_ino);
323 res = hfsplus_rename_cat(inode->i_ino,
324 dir, &dentry->d_name,
325 HFSPLUS_SB(sb).hidden_dir, &str);
326 if (!res)
327 inode->i_flags |= S_DEAD;
328 return res;
329 }
330 res = hfsplus_delete_cat(cnid, dir, &dentry->d_name);
331 if (res)
332 return res;
333
334 inode->i_nlink--;
335 hfsplus_delete_inode(inode);
336 if (inode->i_ino != cnid && !inode->i_nlink) {
337 if (!atomic_read(&HFSPLUS_I(inode).opencnt)) {
338 res = hfsplus_delete_cat(inode->i_ino, HFSPLUS_SB(sb).hidden_dir, NULL);
339 if (!res)
340 hfsplus_delete_inode(inode);
341 } else
342 inode->i_flags |= S_DEAD;
343 }
344 inode->i_ctime = CURRENT_TIME_SEC;
345 mark_inode_dirty(inode);
346
347 return res;
348 }
349
350 static int hfsplus_mkdir(struct inode *dir, struct dentry *dentry, int mode)
351 {
352 struct inode *inode;
353 int res;
354
355 inode = hfsplus_new_inode(dir->i_sb, S_IFDIR | mode);
356 if (!inode)
357 return -ENOSPC;
358
359 res = hfsplus_create_cat(inode->i_ino, dir, &dentry->d_name, inode);
360 if (res) {
361 inode->i_nlink = 0;
362 hfsplus_delete_inode(inode);
363 iput(inode);
364 return res;
365 }
366 hfsplus_instantiate(dentry, inode, inode->i_ino);
367 mark_inode_dirty(inode);
368 return 0;
369 }
370
371 static int hfsplus_rmdir(struct inode *dir, struct dentry *dentry)
372 {
373 struct inode *inode;
374 int res;
375
376 inode = dentry->d_inode;
377 if (inode->i_size != 2)
378 return -ENOTEMPTY;
379 res = hfsplus_delete_cat(inode->i_ino, dir, &dentry->d_name);
380 if (res)
381 return res;
382 inode->i_nlink = 0;
383 inode->i_ctime = CURRENT_TIME_SEC;
384 hfsplus_delete_inode(inode);
385 mark_inode_dirty(inode);
386 return 0;
387 }
388
389 static int hfsplus_symlink(struct inode *dir, struct dentry *dentry,
390 const char *symname)
391 {
392 struct super_block *sb;
393 struct inode *inode;
394 int res;
395
396 sb = dir->i_sb;
397 inode = hfsplus_new_inode(sb, S_IFLNK | S_IRWXUGO);
398 if (!inode)
399 return -ENOSPC;
400
401 res = page_symlink(inode, symname, strlen(symname) + 1);
402 if (res) {
403 inode->i_nlink = 0;
404 hfsplus_delete_inode(inode);
405 iput(inode);
406 return res;
407 }
408
409 mark_inode_dirty(inode);
410 res = hfsplus_create_cat(inode->i_ino, dir, &dentry->d_name, inode);
411
412 if (!res) {
413 hfsplus_instantiate(dentry, inode, inode->i_ino);
414 mark_inode_dirty(inode);
415 }
416
417 return res;
418 }
419
420 static int hfsplus_mknod(struct inode *dir, struct dentry *dentry,
421 int mode, dev_t rdev)
422 {
423 struct super_block *sb;
424 struct inode *inode;
425 int res;
426
427 sb = dir->i_sb;
428 inode = hfsplus_new_inode(sb, mode);
429 if (!inode)
430 return -ENOSPC;
431
432 res = hfsplus_create_cat(inode->i_ino, dir, &dentry->d_name, inode);
433 if (res) {
434 inode->i_nlink = 0;
435 hfsplus_delete_inode(inode);
436 iput(inode);
437 return res;
438 }
439 init_special_inode(inode, mode, rdev);
440 hfsplus_instantiate(dentry, inode, inode->i_ino);
441 mark_inode_dirty(inode);
442
443 return 0;
444 }
445
446 static int hfsplus_rename(struct inode *old_dir, struct dentry *old_dentry,
447 struct inode *new_dir, struct dentry *new_dentry)
448 {
449 int res;
450
451 /* Unlink destination if it already exists */
452 if (new_dentry->d_inode) {
453 res = hfsplus_unlink(new_dir, new_dentry);
454 if (res)
455 return res;
456 }
457
458 res = hfsplus_rename_cat((u32)(unsigned long)old_dentry->d_fsdata,
459 old_dir, &old_dentry->d_name,
460 new_dir, &new_dentry->d_name);
461 if (!res)
462 new_dentry->d_fsdata = old_dentry->d_fsdata;
463 return res;
464 }
465
466 struct inode_operations hfsplus_dir_inode_operations = {
467 .lookup = hfsplus_lookup,
468 .create = hfsplus_create,
469 .link = hfsplus_link,
470 .unlink = hfsplus_unlink,
471 .mkdir = hfsplus_mkdir,
472 .rmdir = hfsplus_rmdir,
473 .symlink = hfsplus_symlink,
474 .mknod = hfsplus_mknod,
475 .rename = hfsplus_rename,
476 };
477
478 struct file_operations hfsplus_dir_operations = {
479 .read = generic_read_dir,
480 .readdir = hfsplus_readdir,
481 .ioctl = hfsplus_ioctl,
482 .llseek = generic_file_llseek,
483 .release = hfsplus_dir_release,
484 };