vserver 2.0 rc7
[linux-2.6.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 };