diff options
Diffstat (limited to 'fs/file.c')
| -rw-r--r-- | fs/file.c | 132 | 
1 files changed, 132 insertions, 0 deletions
diff --git a/fs/file.c b/fs/file.c index 92197dd9fdc..7f29544755d 100644 --- a/fs/file.c +++ b/fs/file.c @@ -6,6 +6,7 @@   *  Manage the dynamic fd arrays in the process files_struct.   */ +#include <linux/syscalls.h>  #include <linux/export.h>  #include <linux/fs.h>  #include <linux/mm.h> @@ -794,3 +795,134 @@ struct file *fget_raw_light(unsigned int fd, int *fput_needed)  	return file;  } + +void set_close_on_exec(unsigned int fd, int flag) +{ +	struct files_struct *files = current->files; +	struct fdtable *fdt; +	spin_lock(&files->file_lock); +	fdt = files_fdtable(files); +	if (flag) +		__set_close_on_exec(fd, fdt); +	else +		__clear_close_on_exec(fd, fdt); +	spin_unlock(&files->file_lock); +} + +bool get_close_on_exec(unsigned int fd) +{ +	struct files_struct *files = current->files; +	struct fdtable *fdt; +	bool res; +	rcu_read_lock(); +	fdt = files_fdtable(files); +	res = close_on_exec(fd, fdt); +	rcu_read_unlock(); +	return res; +} + +SYSCALL_DEFINE3(dup3, unsigned int, oldfd, unsigned int, newfd, int, flags) +{ +	int err = -EBADF; +	struct file * file, *tofree; +	struct files_struct * files = current->files; +	struct fdtable *fdt; + +	if ((flags & ~O_CLOEXEC) != 0) +		return -EINVAL; + +	if (newfd >= rlimit(RLIMIT_NOFILE)) +		return -EMFILE; + +	spin_lock(&files->file_lock); +	err = expand_files(files, newfd); +	file = fcheck(oldfd); +	if (unlikely(!file)) +		goto Ebadf; +	if (unlikely(err < 0)) { +		if (err == -EMFILE) +			goto Ebadf; +		goto out_unlock; +	} +	/* +	 * We need to detect attempts to do dup2() over allocated but still +	 * not finished descriptor.  NB: OpenBSD avoids that at the price of +	 * extra work in their equivalent of fget() - they insert struct +	 * file immediately after grabbing descriptor, mark it larval if +	 * more work (e.g. actual opening) is needed and make sure that +	 * fget() treats larval files as absent.  Potentially interesting, +	 * but while extra work in fget() is trivial, locking implications +	 * and amount of surgery on open()-related paths in VFS are not. +	 * FreeBSD fails with -EBADF in the same situation, NetBSD "solution" +	 * deadlocks in rather amusing ways, AFAICS.  All of that is out of +	 * scope of POSIX or SUS, since neither considers shared descriptor +	 * tables and this condition does not arise without those. +	 */ +	err = -EBUSY; +	fdt = files_fdtable(files); +	tofree = fdt->fd[newfd]; +	if (!tofree && fd_is_open(newfd, fdt)) +		goto out_unlock; +	get_file(file); +	rcu_assign_pointer(fdt->fd[newfd], file); +	__set_open_fd(newfd, fdt); +	if (flags & O_CLOEXEC) +		__set_close_on_exec(newfd, fdt); +	else +		__clear_close_on_exec(newfd, fdt); +	spin_unlock(&files->file_lock); + +	if (tofree) +		filp_close(tofree, files); + +	return newfd; + +Ebadf: +	err = -EBADF; +out_unlock: +	spin_unlock(&files->file_lock); +	return err; +} + +SYSCALL_DEFINE2(dup2, unsigned int, oldfd, unsigned int, newfd) +{ +	if (unlikely(newfd == oldfd)) { /* corner case */ +		struct files_struct *files = current->files; +		int retval = oldfd; + +		rcu_read_lock(); +		if (!fcheck_files(files, oldfd)) +			retval = -EBADF; +		rcu_read_unlock(); +		return retval; +	} +	return sys_dup3(oldfd, newfd, 0); +} + +SYSCALL_DEFINE1(dup, unsigned int, fildes) +{ +	int ret = -EBADF; +	struct file *file = fget_raw(fildes); + +	if (file) { +		ret = get_unused_fd(); +		if (ret >= 0) +			fd_install(ret, file); +		else +			fput(file); +	} +	return ret; +} + +int f_dupfd(unsigned int from, struct file *file, unsigned flags) +{ +	int err; +	if (from >= rlimit(RLIMIT_NOFILE)) +		return -EINVAL; +	err = alloc_fd(from, flags); +	if (err >= 0) { +		get_file(file); +		fd_install(err, file); +	} +	return err; +}  |