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 */
hfsplus_lookup(struct inode * dir,struct dentry * dentry)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
hfsplus_readdir(struct file * filp,void * dirent,filldir_t filldir)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)
hfsplus_seek_dir(struct file * file,loff_t offset,int origin)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
hfsplus_dir_release(struct inode * inode,struct file * file)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
hfsplus_create(struct inode * dir,struct dentry * dentry,int mode)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
hfsplus_link(struct dentry * src_dentry,struct inode * dst_dir,struct dentry * dst_dentry)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
hfsplus_unlink(struct inode * dir,struct dentry * dentry)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
hfsplus_mkdir(struct inode * dir,struct dentry * dentry,int mode)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
hfsplus_rmdir(struct inode * dir,struct dentry * dentry)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
hfsplus_symlink(struct inode * dir,struct dentry * dentry,const char * symname)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)
hfsplus_mknod(struct inode * dir,struct dentry * dentry,int mode,dev_t rdev)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
hfsplus_rename(struct inode * old_dir,struct dentry * old_dentry,struct inode * new_dir,struct dentry * new_dentry)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 };
493