xref: /openbmc/linux/fs/readdir.c (revision 2903ff019b346ab8d36ebbf54853c3aaf6590608)
11da177e4SLinus Torvalds /*
21da177e4SLinus Torvalds  *  linux/fs/readdir.c
31da177e4SLinus Torvalds  *
41da177e4SLinus Torvalds  *  Copyright (C) 1995  Linus Torvalds
51da177e4SLinus Torvalds  */
61da177e4SLinus Torvalds 
785c9fe8fSKevin Winchester #include <linux/stddef.h>
8022a1692SMilind Arun Choudhary #include <linux/kernel.h>
9630d9c47SPaul Gortmaker #include <linux/export.h>
101da177e4SLinus Torvalds #include <linux/time.h>
111da177e4SLinus Torvalds #include <linux/mm.h>
121da177e4SLinus Torvalds #include <linux/errno.h>
131da177e4SLinus Torvalds #include <linux/stat.h>
141da177e4SLinus Torvalds #include <linux/file.h>
151da177e4SLinus Torvalds #include <linux/fs.h>
161da177e4SLinus Torvalds #include <linux/dirent.h>
171da177e4SLinus Torvalds #include <linux/security.h>
181da177e4SLinus Torvalds #include <linux/syscalls.h>
191da177e4SLinus Torvalds #include <linux/unistd.h>
201da177e4SLinus Torvalds 
211da177e4SLinus Torvalds #include <asm/uaccess.h>
221da177e4SLinus Torvalds 
231da177e4SLinus Torvalds int vfs_readdir(struct file *file, filldir_t filler, void *buf)
241da177e4SLinus Torvalds {
250f7fc9e4SJosef "Jeff" Sipek 	struct inode *inode = file->f_path.dentry->d_inode;
261da177e4SLinus Torvalds 	int res = -ENOTDIR;
271da177e4SLinus Torvalds 	if (!file->f_op || !file->f_op->readdir)
281da177e4SLinus Torvalds 		goto out;
291da177e4SLinus Torvalds 
301da177e4SLinus Torvalds 	res = security_file_permission(file, MAY_READ);
311da177e4SLinus Torvalds 	if (res)
321da177e4SLinus Torvalds 		goto out;
331da177e4SLinus Torvalds 
34da784511SLiam R. Howlett 	res = mutex_lock_killable(&inode->i_mutex);
35da784511SLiam R. Howlett 	if (res)
36da784511SLiam R. Howlett 		goto out;
37da784511SLiam R. Howlett 
381da177e4SLinus Torvalds 	res = -ENOENT;
391da177e4SLinus Torvalds 	if (!IS_DEADDIR(inode)) {
401da177e4SLinus Torvalds 		res = file->f_op->readdir(file, buf, filler);
411da177e4SLinus Torvalds 		file_accessed(file);
421da177e4SLinus Torvalds 	}
431b1dcc1bSJes Sorensen 	mutex_unlock(&inode->i_mutex);
441da177e4SLinus Torvalds out:
451da177e4SLinus Torvalds 	return res;
461da177e4SLinus Torvalds }
471da177e4SLinus Torvalds 
481da177e4SLinus Torvalds EXPORT_SYMBOL(vfs_readdir);
491da177e4SLinus Torvalds 
501da177e4SLinus Torvalds /*
511da177e4SLinus Torvalds  * Traditional linux readdir() handling..
521da177e4SLinus Torvalds  *
531da177e4SLinus Torvalds  * "count=1" is a special case, meaning that the buffer is one
541da177e4SLinus Torvalds  * dirent-structure in size and that the code can't handle more
551da177e4SLinus Torvalds  * anyway. Thus the special "fillonedir()" function for that
561da177e4SLinus Torvalds  * case (the low-level handlers don't need to care about this).
571da177e4SLinus Torvalds  */
581da177e4SLinus Torvalds 
591da177e4SLinus Torvalds #ifdef __ARCH_WANT_OLD_READDIR
601da177e4SLinus Torvalds 
611da177e4SLinus Torvalds struct old_linux_dirent {
621da177e4SLinus Torvalds 	unsigned long	d_ino;
631da177e4SLinus Torvalds 	unsigned long	d_offset;
641da177e4SLinus Torvalds 	unsigned short	d_namlen;
651da177e4SLinus Torvalds 	char		d_name[1];
661da177e4SLinus Torvalds };
671da177e4SLinus Torvalds 
681da177e4SLinus Torvalds struct readdir_callback {
691da177e4SLinus Torvalds 	struct old_linux_dirent __user * dirent;
701da177e4SLinus Torvalds 	int result;
711da177e4SLinus Torvalds };
721da177e4SLinus Torvalds 
731da177e4SLinus Torvalds static int fillonedir(void * __buf, const char * name, int namlen, loff_t offset,
74afefdbb2SDavid Howells 		      u64 ino, unsigned int d_type)
751da177e4SLinus Torvalds {
761da177e4SLinus Torvalds 	struct readdir_callback * buf = (struct readdir_callback *) __buf;
771da177e4SLinus Torvalds 	struct old_linux_dirent __user * dirent;
78afefdbb2SDavid Howells 	unsigned long d_ino;
791da177e4SLinus Torvalds 
801da177e4SLinus Torvalds 	if (buf->result)
811da177e4SLinus Torvalds 		return -EINVAL;
82afefdbb2SDavid Howells 	d_ino = ino;
838f3f655dSAl Viro 	if (sizeof(d_ino) < sizeof(ino) && d_ino != ino) {
848f3f655dSAl Viro 		buf->result = -EOVERFLOW;
85afefdbb2SDavid Howells 		return -EOVERFLOW;
868f3f655dSAl Viro 	}
871da177e4SLinus Torvalds 	buf->result++;
881da177e4SLinus Torvalds 	dirent = buf->dirent;
891da177e4SLinus Torvalds 	if (!access_ok(VERIFY_WRITE, dirent,
901da177e4SLinus Torvalds 			(unsigned long)(dirent->d_name + namlen + 1) -
911da177e4SLinus Torvalds 				(unsigned long)dirent))
921da177e4SLinus Torvalds 		goto efault;
93afefdbb2SDavid Howells 	if (	__put_user(d_ino, &dirent->d_ino) ||
941da177e4SLinus Torvalds 		__put_user(offset, &dirent->d_offset) ||
951da177e4SLinus Torvalds 		__put_user(namlen, &dirent->d_namlen) ||
961da177e4SLinus Torvalds 		__copy_to_user(dirent->d_name, name, namlen) ||
971da177e4SLinus Torvalds 		__put_user(0, dirent->d_name + namlen))
981da177e4SLinus Torvalds 		goto efault;
991da177e4SLinus Torvalds 	return 0;
1001da177e4SLinus Torvalds efault:
1011da177e4SLinus Torvalds 	buf->result = -EFAULT;
1021da177e4SLinus Torvalds 	return -EFAULT;
1031da177e4SLinus Torvalds }
1041da177e4SLinus Torvalds 
105d4e82042SHeiko Carstens SYSCALL_DEFINE3(old_readdir, unsigned int, fd,
106d4e82042SHeiko Carstens 		struct old_linux_dirent __user *, dirent, unsigned int, count)
1071da177e4SLinus Torvalds {
1081da177e4SLinus Torvalds 	int error;
109*2903ff01SAl Viro 	struct fd f = fdget(fd);
1101da177e4SLinus Torvalds 	struct readdir_callback buf;
1111da177e4SLinus Torvalds 
112*2903ff01SAl Viro 	if (!f.file)
113863ced7fSAl Viro 		return -EBADF;
1141da177e4SLinus Torvalds 
1151da177e4SLinus Torvalds 	buf.result = 0;
1161da177e4SLinus Torvalds 	buf.dirent = dirent;
1171da177e4SLinus Torvalds 
118*2903ff01SAl Viro 	error = vfs_readdir(f.file, fillonedir, &buf);
11953c9c5c0SAl Viro 	if (buf.result)
1201da177e4SLinus Torvalds 		error = buf.result;
1211da177e4SLinus Torvalds 
122*2903ff01SAl Viro 	fdput(f);
1231da177e4SLinus Torvalds 	return error;
1241da177e4SLinus Torvalds }
1251da177e4SLinus Torvalds 
1261da177e4SLinus Torvalds #endif /* __ARCH_WANT_OLD_READDIR */
1271da177e4SLinus Torvalds 
1281da177e4SLinus Torvalds /*
1291da177e4SLinus Torvalds  * New, all-improved, singing, dancing, iBCS2-compliant getdents()
1301da177e4SLinus Torvalds  * interface.
1311da177e4SLinus Torvalds  */
1321da177e4SLinus Torvalds struct linux_dirent {
1331da177e4SLinus Torvalds 	unsigned long	d_ino;
1341da177e4SLinus Torvalds 	unsigned long	d_off;
1351da177e4SLinus Torvalds 	unsigned short	d_reclen;
1361da177e4SLinus Torvalds 	char		d_name[1];
1371da177e4SLinus Torvalds };
1381da177e4SLinus Torvalds 
1391da177e4SLinus Torvalds struct getdents_callback {
1401da177e4SLinus Torvalds 	struct linux_dirent __user * current_dir;
1411da177e4SLinus Torvalds 	struct linux_dirent __user * previous;
1421da177e4SLinus Torvalds 	int count;
1431da177e4SLinus Torvalds 	int error;
1441da177e4SLinus Torvalds };
1451da177e4SLinus Torvalds 
1461da177e4SLinus Torvalds static int filldir(void * __buf, const char * name, int namlen, loff_t offset,
147afefdbb2SDavid Howells 		   u64 ino, unsigned int d_type)
1481da177e4SLinus Torvalds {
1491da177e4SLinus Torvalds 	struct linux_dirent __user * dirent;
1501da177e4SLinus Torvalds 	struct getdents_callback * buf = (struct getdents_callback *) __buf;
151afefdbb2SDavid Howells 	unsigned long d_ino;
15285c9fe8fSKevin Winchester 	int reclen = ALIGN(offsetof(struct linux_dirent, d_name) + namlen + 2,
15385c9fe8fSKevin Winchester 		sizeof(long));
1541da177e4SLinus Torvalds 
1551da177e4SLinus Torvalds 	buf->error = -EINVAL;	/* only used if we fail.. */
1561da177e4SLinus Torvalds 	if (reclen > buf->count)
1571da177e4SLinus Torvalds 		return -EINVAL;
158afefdbb2SDavid Howells 	d_ino = ino;
1598f3f655dSAl Viro 	if (sizeof(d_ino) < sizeof(ino) && d_ino != ino) {
1608f3f655dSAl Viro 		buf->error = -EOVERFLOW;
161afefdbb2SDavid Howells 		return -EOVERFLOW;
1628f3f655dSAl Viro 	}
1631da177e4SLinus Torvalds 	dirent = buf->previous;
1641da177e4SLinus Torvalds 	if (dirent) {
1651da177e4SLinus Torvalds 		if (__put_user(offset, &dirent->d_off))
1661da177e4SLinus Torvalds 			goto efault;
1671da177e4SLinus Torvalds 	}
1681da177e4SLinus Torvalds 	dirent = buf->current_dir;
169afefdbb2SDavid Howells 	if (__put_user(d_ino, &dirent->d_ino))
1701da177e4SLinus Torvalds 		goto efault;
1711da177e4SLinus Torvalds 	if (__put_user(reclen, &dirent->d_reclen))
1721da177e4SLinus Torvalds 		goto efault;
1731da177e4SLinus Torvalds 	if (copy_to_user(dirent->d_name, name, namlen))
1741da177e4SLinus Torvalds 		goto efault;
1751da177e4SLinus Torvalds 	if (__put_user(0, dirent->d_name + namlen))
1761da177e4SLinus Torvalds 		goto efault;
1771da177e4SLinus Torvalds 	if (__put_user(d_type, (char __user *) dirent + reclen - 1))
1781da177e4SLinus Torvalds 		goto efault;
1791da177e4SLinus Torvalds 	buf->previous = dirent;
1801da177e4SLinus Torvalds 	dirent = (void __user *)dirent + reclen;
1811da177e4SLinus Torvalds 	buf->current_dir = dirent;
1821da177e4SLinus Torvalds 	buf->count -= reclen;
1831da177e4SLinus Torvalds 	return 0;
1841da177e4SLinus Torvalds efault:
1851da177e4SLinus Torvalds 	buf->error = -EFAULT;
1861da177e4SLinus Torvalds 	return -EFAULT;
1871da177e4SLinus Torvalds }
1881da177e4SLinus Torvalds 
18920f37034SHeiko Carstens SYSCALL_DEFINE3(getdents, unsigned int, fd,
19020f37034SHeiko Carstens 		struct linux_dirent __user *, dirent, unsigned int, count)
1911da177e4SLinus Torvalds {
192*2903ff01SAl Viro 	struct fd f;
1931da177e4SLinus Torvalds 	struct linux_dirent __user * lastdirent;
1941da177e4SLinus Torvalds 	struct getdents_callback buf;
1951da177e4SLinus Torvalds 	int error;
1961da177e4SLinus Torvalds 
1971da177e4SLinus Torvalds 	if (!access_ok(VERIFY_WRITE, dirent, count))
198863ced7fSAl Viro 		return -EFAULT;
1991da177e4SLinus Torvalds 
200*2903ff01SAl Viro 	f = fdget(fd);
201*2903ff01SAl Viro 	if (!f.file)
202863ced7fSAl Viro 		return -EBADF;
2031da177e4SLinus Torvalds 
2041da177e4SLinus Torvalds 	buf.current_dir = dirent;
2051da177e4SLinus Torvalds 	buf.previous = NULL;
2061da177e4SLinus Torvalds 	buf.count = count;
2071da177e4SLinus Torvalds 	buf.error = 0;
2081da177e4SLinus Torvalds 
209*2903ff01SAl Viro 	error = vfs_readdir(f.file, filldir, &buf);
21053c9c5c0SAl Viro 	if (error >= 0)
2111da177e4SLinus Torvalds 		error = buf.error;
2121da177e4SLinus Torvalds 	lastdirent = buf.previous;
2131da177e4SLinus Torvalds 	if (lastdirent) {
214*2903ff01SAl Viro 		if (put_user(f.file->f_pos, &lastdirent->d_off))
2151da177e4SLinus Torvalds 			error = -EFAULT;
2161da177e4SLinus Torvalds 		else
2171da177e4SLinus Torvalds 			error = count - buf.count;
2181da177e4SLinus Torvalds 	}
219*2903ff01SAl Viro 	fdput(f);
2201da177e4SLinus Torvalds 	return error;
2211da177e4SLinus Torvalds }
2221da177e4SLinus Torvalds 
2231da177e4SLinus Torvalds struct getdents_callback64 {
2241da177e4SLinus Torvalds 	struct linux_dirent64 __user * current_dir;
2251da177e4SLinus Torvalds 	struct linux_dirent64 __user * previous;
2261da177e4SLinus Torvalds 	int count;
2271da177e4SLinus Torvalds 	int error;
2281da177e4SLinus Torvalds };
2291da177e4SLinus Torvalds 
2301da177e4SLinus Torvalds static int filldir64(void * __buf, const char * name, int namlen, loff_t offset,
231afefdbb2SDavid Howells 		     u64 ino, unsigned int d_type)
2321da177e4SLinus Torvalds {
2331da177e4SLinus Torvalds 	struct linux_dirent64 __user *dirent;
2341da177e4SLinus Torvalds 	struct getdents_callback64 * buf = (struct getdents_callback64 *) __buf;
23585c9fe8fSKevin Winchester 	int reclen = ALIGN(offsetof(struct linux_dirent64, d_name) + namlen + 1,
23685c9fe8fSKevin Winchester 		sizeof(u64));
2371da177e4SLinus Torvalds 
2381da177e4SLinus Torvalds 	buf->error = -EINVAL;	/* only used if we fail.. */
2391da177e4SLinus Torvalds 	if (reclen > buf->count)
2401da177e4SLinus Torvalds 		return -EINVAL;
2411da177e4SLinus Torvalds 	dirent = buf->previous;
2421da177e4SLinus Torvalds 	if (dirent) {
2431da177e4SLinus Torvalds 		if (__put_user(offset, &dirent->d_off))
2441da177e4SLinus Torvalds 			goto efault;
2451da177e4SLinus Torvalds 	}
2461da177e4SLinus Torvalds 	dirent = buf->current_dir;
2471da177e4SLinus Torvalds 	if (__put_user(ino, &dirent->d_ino))
2481da177e4SLinus Torvalds 		goto efault;
2491da177e4SLinus Torvalds 	if (__put_user(0, &dirent->d_off))
2501da177e4SLinus Torvalds 		goto efault;
2511da177e4SLinus Torvalds 	if (__put_user(reclen, &dirent->d_reclen))
2521da177e4SLinus Torvalds 		goto efault;
2531da177e4SLinus Torvalds 	if (__put_user(d_type, &dirent->d_type))
2541da177e4SLinus Torvalds 		goto efault;
2551da177e4SLinus Torvalds 	if (copy_to_user(dirent->d_name, name, namlen))
2561da177e4SLinus Torvalds 		goto efault;
2571da177e4SLinus Torvalds 	if (__put_user(0, dirent->d_name + namlen))
2581da177e4SLinus Torvalds 		goto efault;
2591da177e4SLinus Torvalds 	buf->previous = dirent;
2601da177e4SLinus Torvalds 	dirent = (void __user *)dirent + reclen;
2611da177e4SLinus Torvalds 	buf->current_dir = dirent;
2621da177e4SLinus Torvalds 	buf->count -= reclen;
2631da177e4SLinus Torvalds 	return 0;
2641da177e4SLinus Torvalds efault:
2651da177e4SLinus Torvalds 	buf->error = -EFAULT;
2661da177e4SLinus Torvalds 	return -EFAULT;
2671da177e4SLinus Torvalds }
2681da177e4SLinus Torvalds 
26920f37034SHeiko Carstens SYSCALL_DEFINE3(getdents64, unsigned int, fd,
27020f37034SHeiko Carstens 		struct linux_dirent64 __user *, dirent, unsigned int, count)
2711da177e4SLinus Torvalds {
272*2903ff01SAl Viro 	struct fd f;
2731da177e4SLinus Torvalds 	struct linux_dirent64 __user * lastdirent;
2741da177e4SLinus Torvalds 	struct getdents_callback64 buf;
2751da177e4SLinus Torvalds 	int error;
2761da177e4SLinus Torvalds 
2771da177e4SLinus Torvalds 	if (!access_ok(VERIFY_WRITE, dirent, count))
278863ced7fSAl Viro 		return -EFAULT;
2791da177e4SLinus Torvalds 
280*2903ff01SAl Viro 	f = fdget(fd);
281*2903ff01SAl Viro 	if (!f.file)
282863ced7fSAl Viro 		return -EBADF;
2831da177e4SLinus Torvalds 
2841da177e4SLinus Torvalds 	buf.current_dir = dirent;
2851da177e4SLinus Torvalds 	buf.previous = NULL;
2861da177e4SLinus Torvalds 	buf.count = count;
2871da177e4SLinus Torvalds 	buf.error = 0;
2881da177e4SLinus Torvalds 
289*2903ff01SAl Viro 	error = vfs_readdir(f.file, filldir64, &buf);
29053c9c5c0SAl Viro 	if (error >= 0)
2911da177e4SLinus Torvalds 		error = buf.error;
2921da177e4SLinus Torvalds 	lastdirent = buf.previous;
2931da177e4SLinus Torvalds 	if (lastdirent) {
294*2903ff01SAl Viro 		typeof(lastdirent->d_off) d_off = f.file->f_pos;
2951da177e4SLinus Torvalds 		if (__put_user(d_off, &lastdirent->d_off))
29653c9c5c0SAl Viro 			error = -EFAULT;
29753c9c5c0SAl Viro 		else
2981da177e4SLinus Torvalds 			error = count - buf.count;
2991da177e4SLinus Torvalds 	}
300*2903ff01SAl Viro 	fdput(f);
3011da177e4SLinus Torvalds 	return error;
3021da177e4SLinus Torvalds }
303