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