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