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