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