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