blob: e886ac8460d375ccda11f4a005aefffe34b60bcd [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
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>
Linus Torvalds1da177e2005-04-16 15:20:36 -070016
17#include "hfsplus_fs.h"
18#include "hfsplus_raw.h"
19
20static 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 */
28static 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);
43again:
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) &&
Roman Zippelaf8c85b2006-01-18 17:43:10 -080069 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) {
Linus Torvalds1da177e2005-04-16 15:20:36 -070073 struct qstr str;
74 char name[32];
75
76 if (dentry->d_fsdata) {
Roman Zippelaf8c85b2006-01-18 17:43:10 -080077 /*
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;
Linus Torvalds1da177e2005-04-16 15:20:36 -070090 }
Linus Torvalds1da177e2005-04-16 15:20:36 -070091 } else if (!dentry->d_fsdata)
92 dentry->d_fsdata = (void *)(unsigned long)cnid;
93 } else {
Roman Zippel634725a2006-01-18 17:43:05 -080094 printk(KERN_ERR "hfs: invalid catalog entry type in lookup\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -070095 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;
104out:
105 d_add(dentry, inode);
106 return NULL;
107fail:
108 hfs_find_exit(&fd);
109 return ERR_PTR(err);
110}
111
112static int hfsplus_readdir(struct file *filp, void *dirent, filldir_t filldir)
113{
Josef Sipekf44ea032006-12-08 02:37:04 -0800114 struct inode *inode = filp->f_path.dentry->d_inode;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700115 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) {
Roman Zippel634725a2006-01-18 17:43:05 -0800142 printk(KERN_ERR "hfs: bad catalog folder thread\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700143 err = -EIO;
144 goto out;
145 }
146 if (fd.entrylength < HFSPLUS_MIN_THREAD_SZ) {
Roman Zippel634725a2006-01-18 17:43:05 -0800147 printk(KERN_ERR "hfs: truncated catalog thread\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700148 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) {
Roman Zippel634725a2006-01-18 17:43:05 -0800166 printk(KERN_ERR "hfs: walked past end of dir\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700167 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)) {
Roman Zippel634725a2006-01-18 17:43:05 -0800178 printk(KERN_ERR "hfs: small dir entry\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700179 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)) {
Roman Zippel634725a2006-01-18 17:43:05 -0800190 printk(KERN_ERR "hfs: small file entry\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700191 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 {
Roman Zippel634725a2006-01-18 17:43:05 -0800198 printk(KERN_ERR "hfs: bad catalog entry type\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700199 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));
222out:
223 hfs_find_exit(&fd);
224 return err;
225}
226
227static 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
237static 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
259static 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
Dave Hansend8c76e62006-09-30 23:29:04 -0700301 inc_nlink(inode);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700302 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
312static 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
Roman Zippelaf8c85b2006-01-18 17:43:10 -0800340 if (inode->i_nlink > 0)
Dave Hansen9a53c3a2006-09-30 23:29:03 -0700341 drop_nlink(inode);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700342 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;
Roman Zippelaf8c85b2006-01-18 17:43:10 -0800350 } else
Dave Hansence71ec32006-09-30 23:29:06 -0700351 clear_nlink(inode);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700352 inode->i_ctime = CURRENT_TIME_SEC;
353 mark_inode_dirty(inode);
354
355 return res;
356}
357
358static 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
379static 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;
Dave Hansence71ec32006-09-30 23:29:06 -0700390 clear_nlink(inode);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700391 inode->i_ctime = CURRENT_TIME_SEC;
392 hfsplus_delete_inode(inode);
393 mark_inode_dirty(inode);
394 return 0;
395}
396
397static 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
428static 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
454static 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
474struct 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
Arjan van de Ven4b6f5d22006-03-28 01:56:42 -0800486const struct file_operations hfsplus_dir_operations = {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700487 .read = generic_read_dir,
488 .readdir = hfsplus_readdir,
489 .ioctl = hfsplus_ioctl,
490 .llseek = generic_file_llseek,
491 .release = hfsplus_dir_release,
492};