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