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