summaryrefslogtreecommitdiff
path: root/fs/file.c
diff options
context:
space:
mode:
authorAl Viro <viro@zeniv.linux.org.uk>2012-08-21 11:48:11 -0400
committerAl Viro <viro@zeniv.linux.org.uk>2012-09-26 21:09:57 -0400
commitfe17f22d7fd0e344ef6447238f799bb49f670c6f (patch)
tree793facbd992c674e55790793ffa912927ae7a766 /fs/file.c
parent6a6d27de340c89c5323565b49f7851362619925d (diff)
downloadlwn-fe17f22d7fd0e344ef6447238f799bb49f670c6f.tar.gz
lwn-fe17f22d7fd0e344ef6447238f799bb49f670c6f.zip
take purely descriptor-related stuff from fcntl.c to file.c
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Diffstat (limited to 'fs/file.c')
-rw-r--r--fs/file.c132
1 files changed, 132 insertions, 0 deletions
diff --git a/fs/file.c b/fs/file.c
index 92197dd9fdc8..7f29544755d0 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;
+}