File Index
Files and directories
Sign in
FILE

dir.c

File preview with syntax highlighting.

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