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