Hi,
The patch below brings the large file descriptor set uptodate against
the 2.3.12-pre5 kernel.
The bulk of the patch is largely unchanged from the version that has
been in the 2.2-*ac patches for months, except that the fd manipulation
has been updated to deal with the new files spinlocking.
There is one other thing changed from the 2.2 version of the patch, and
I'd appreciate a glance over that. We have problems with the existing
dup2() in 2.3: there is no guarantee that it is atomic. dup2() drops
the file spinlock, calls sys_close(), then retakes the lock and performs
a dup(). There is no guarantee that the closed fd hasn't been reused in
the mean time: that is, "dup2(x,y)" may not actually return fd "y".
The solution I've used is to keep the fdset bit set during the entire
dup2(). Both the open and close code already have windows where the
fdset bit is set and the fd array is clear.
I've taken care to ensure that the fast paths don't keep dropping and
taking the files spinlocks, but a certain amount of extra locking is
inevitable if we end up growing the tables, and that necessarily grows
the code paths a little.
--Stephen
----------------------------------------------------------------
--- arch/alpha/kernel/process.c.~1~ Tue Jul 27 00:23:13 1999
+++ arch/alpha/kernel/process.c Tue Jul 27 00:25:47 1999
@@ -55,7 +55,6 @@
unsigned long init_user_stack[1024] = { STACK_MAGIC, };
static struct vm_area_struct init_mmap = INIT_MMAP;
static struct fs_struct init_fs = INIT_FS;
-static struct file * init_fd_array[NR_OPEN] = { NULL, };
static struct files_struct init_files = INIT_FILES;
static struct signal_struct init_signals = INIT_SIGNALS;
struct mm_struct init_mm = INIT_MM(init_mm);
--- arch/arm/kernel/init_task.c.~1~ Wed Jun 23 10:17:37 1999
+++ arch/arm/kernel/init_task.c Tue Jul 27 00:25:47 1999
@@ -6,7 +6,6 @@
static struct vm_area_struct init_mmap = INIT_MMAP;
static struct fs_struct init_fs = INIT_FS;
-static struct file * init_fd_array[NR_OPEN] = { NULL, };
static struct files_struct init_files = INIT_FILES;
static struct signal_struct init_signals = INIT_SIGNALS;
struct mm_struct init_mm = INIT_MM(init_mm);
--- arch/i386/kernel/init_task.c.~1~ Tue Jul 27 00:23:13 1999
+++ arch/i386/kernel/init_task.c Tue Jul 27 00:25:47 1999
@@ -8,7 +8,6 @@
static struct vm_area_struct init_mmap = INIT_MMAP;
static struct fs_struct init_fs = INIT_FS;
-static struct file * init_fd_array[NR_OPEN] = { NULL, };
static struct files_struct init_files = INIT_FILES;
static struct signal_struct init_signals = INIT_SIGNALS;
struct mm_struct init_mm = INIT_MM(init_mm);
--- arch/m68k/kernel/process.c.~1~ Tue Jul 27 00:24:45 1999
+++ arch/m68k/kernel/process.c Tue Jul 27 00:25:47 1999
@@ -40,7 +40,6 @@
*/
static struct vm_area_struct init_mmap = INIT_MMAP;
static struct fs_struct init_fs = INIT_FS;
-static struct file * init_fd_array[NR_OPEN] = { NULL, };
static struct files_struct init_files = INIT_FILES;
static struct signal_struct init_signals = INIT_SIGNALS;
struct mm_struct init_mm = INIT_MM(init_mm);
--- arch/mips/kernel/init_task.c.~1~ Wed Jul 14 19:08:21 1999
+++ arch/mips/kernel/init_task.c Tue Jul 27 00:25:47 1999
@@ -6,7 +6,6 @@
static struct vm_area_struct init_mmap = INIT_MMAP;
static struct fs_struct init_fs = INIT_FS;
-static struct files * init_fd_array[NR_OPEN] = { NULL, };
static struct files_struct init_files = INIT_FILES;
static struct signal_struct init_signals = INIT_SIGNALS;
struct mm_struct init_mm = INIT_MM(init_mm);
--- arch/ppc/kernel/process.c.~1~ Tue Jul 27 00:23:13 1999
+++ arch/ppc/kernel/process.c Tue Jul 27 00:25:47 1999
@@ -47,7 +47,6 @@
struct task_struct *last_task_used_math = NULL;
static struct vm_area_struct init_mmap = INIT_MMAP;
static struct fs_struct init_fs = INIT_FS;
-static struct file * init_fd_array[NR_OPEN] = { NULL, };
static struct files_struct init_files = INIT_FILES;
static struct signal_struct init_signals = INIT_SIGNALS;
struct mm_struct init_mm = INIT_MM(init_mm);
--- arch/sparc/kernel/init_task.c.~1~ Wed May 26 15:52:29 1999
+++ arch/sparc/kernel/init_task.c Tue Jul 27 00:25:47 1999
@@ -6,7 +6,6 @@
static struct vm_area_struct init_mmap = INIT_MMAP;
static struct fs_struct init_fs = INIT_FS;
-static struct file * init_fd_array[NR_OPEN] = { NULL, };
static struct files_struct init_files = INIT_FILES;
static struct signal_struct init_signals = INIT_SIGNALS;
struct mm_struct init_mm = INIT_MM(init_mm);
--- arch/sparc64/kernel/init_task.c.~1~ Wed May 26 15:52:29 1999
+++ arch/sparc64/kernel/init_task.c Tue Jul 27 00:25:47 1999
@@ -6,7 +6,6 @@
static struct vm_area_struct init_mmap = INIT_MMAP;
static struct fs_struct init_fs = INIT_FS;
-static struct file * init_fd_array[NR_OPEN] = { NULL, };
static struct files_struct init_files = INIT_FILES;
static struct signal_struct init_signals = INIT_SIGNALS;
struct mm_struct init_mm = INIT_MM(init_mm);
--- arch/sparc64/solaris/timod.c.~1~ Wed May 26 15:52:29 1999
+++ arch/sparc64/solaris/timod.c Tue Jul 27 00:25:47 1999
@@ -866,7 +866,7 @@
SOLD("entry");
lock_kernel();
- if(fd >= NR_OPEN) goto out;
+ if(fd >= current->files->max_fds) goto out;
filp = current->files->fd[fd];
if(!filp) goto out;
@@ -933,7 +933,7 @@
SOLD("entry");
lock_kernel();
- if(fd >= NR_OPEN) goto out;
+ if(fd >= current->files->max_fds) goto out;
filp = current->files->fd[fd];
if(!filp) goto out;
--- fs/Makefile.~1~ Wed Jul 14 19:08:30 1999
+++ fs/Makefile Tue Jul 27 00:25:47 1999
@@ -13,7 +13,7 @@
O_OBJS = open.o read_write.o devices.o file_table.o buffer.o \
super.o block_dev.o stat.o exec.o pipe.o namei.o fcntl.o \
ioctl.o readdir.o select.o fifo.o locks.o filesystems.o \
- dcache.o inode.o attr.o bad_inode.o $(BINFMTS)
+ dcache.o inode.o attr.o bad_inode.o file.o $(BINFMTS)
MOD_LIST_NAME := FS_MODULES
ALL_SUB_DIRS = coda minix ext2 fat msdos vfat proc isofs nfs umsdos ntfs \
--- fs/exec.c.~1~ Tue Jul 27 00:24:45 1999
+++ fs/exec.c Tue Jul 27 00:25:47 1999
@@ -449,9 +449,9 @@
unsigned long set, i;
i = j * __NFDBITS;
- if (i >= files->max_fds)
+ if (i >= files->max_fds || i >= files->max_fdset)
break;
- set = xchg(&files->close_on_exec.fds_bits[j], 0);
+ set = xchg(&files->close_on_exec->fds_bits[j], 0);
j++;
for ( ; set ; i++,set >>= 1) {
if (set & 1)
--- fs/fcntl.c.~1~ Tue Jul 27 00:23:14 1999
+++ fs/fcntl.c Tue Jul 27 00:25:47 1999
@@ -12,36 +12,89 @@
extern int sock_fcntl (struct file *, unsigned int cmd, unsigned long arg);
-static inline int dupfd(struct file *file, unsigned int arg)
+/*
+ * locate_fd finds a free file descriptor in the open_fds fdset,
+ * expanding the fd arrays if necessary. The files write lock will be
+ * held on exit to ensure that the fd can be entered atomically.
+ */
+
+static inline int locate_fd(struct files_struct *files,
+ struct file *file, int start)
{
- struct files_struct * files = current->files;
+ unsigned int newfd;
int error;
- error = -EMFILE;
write_lock(&files->file_lock);
- arg = find_next_zero_bit(&files->open_fds, NR_OPEN, arg);
- if (arg >= current->rlim[RLIMIT_NOFILE].rlim_cur)
- goto out_putf;
- FD_SET(arg, &files->open_fds);
- FD_CLR(arg, &files->close_on_exec);
- write_unlock(&files->file_lock);
- fd_install(arg, file);
- error = arg;
+
+repeat:
+ error = -EMFILE;
+ if (start < files->next_fd)
+ start = files->next_fd;
+ if (start >= files->max_fdset) {
+ expand:
+ error = expand_files(files, start);
+ if (error < 0)
+ goto out;
+ goto repeat;
+ }
+
+ newfd = find_next_zero_bit(files->open_fds->fds_bits,
+ files->max_fdset, start);
+
+ error = -EMFILE;
+ if (newfd >= current->rlim[RLIMIT_NOFILE].rlim_cur)
+ goto out;
+ if (newfd >= files->max_fdset)
+ goto expand;
+
+ error = expand_files(files, newfd);
+ if (error < 0)
+ goto out;
+ if (error) /* If we might have blocked, try again. */
+ goto repeat;
+
+ if (start <= files->next_fd)
+ files->next_fd = newfd + 1;
+
+ error = newfd;
+
out:
return error;
+}
+
+static inline void allocate_fd(struct files_struct *files,
+ struct file *file, int fd)
+{
+ FD_SET(fd, files->open_fds);
+ FD_CLR(fd, files->close_on_exec);
+ write_unlock(&files->file_lock);
+ fd_install(fd, file);
+}
+
+static int dupfd(struct file *file, int start)
+{
+ struct files_struct * files = current->files;
+ int ret;
+
+ ret = locate_fd(files, file, start);
+ if (ret < 0)
+ goto out_putf;
+ allocate_fd(files, file, ret);
+ return ret;
out_putf:
write_unlock(&files->file_lock);
fput(file);
- goto out;
+ return ret;
}
asmlinkage int sys_dup2(unsigned int oldfd, unsigned int newfd)
{
int err = -EBADF;
struct file * file;
+ struct files_struct * files = current->files;
- read_lock(¤t->files->file_lock);
+ write_lock(¤t->files->file_lock);
if (!(file = fcheck(oldfd)))
goto out_unlock;
err = newfd;
@@ -50,15 +103,33 @@
err = -EBADF;
if (newfd >= NR_OPEN)
goto out_unlock; /* following POSIX.1 6.2.1 */
- get_file(file);
- read_unlock(¤t->files->file_lock);
+ get_file(file); /* We are now finished with oldfd */
+
+ err = expand_files(files, newfd);
+ if (err < 0) {
+ write_unlock(&files->file_lock);
+ fput(file);
+ goto out;
+ }
+
+ /* To avoid races with open() and dup(), we will mark the fd as
+ * in-use in the open-file bitmap throughout the entire dup2()
+ * process. This is quite safe: do_close() uses the fd array
+ * entry, not the bitmap, to decide what work needs to be
+ * done. --sct */
+ FD_SET(newfd, files->open_fds);
+ write_unlock(&files->file_lock);
+
+ do_close(newfd, 0);
+
+ write_lock(&files->file_lock);
+ allocate_fd(files, file, newfd);
+ err = newfd;
- sys_close(newfd);
- err = dupfd(file, newfd);
out:
return err;
out_unlock:
- read_unlock(¤t->files->file_lock);
+ write_unlock(¤t->files->file_lock);
goto out;
}
@@ -66,6 +137,7 @@
{
int ret = -EBADF;
struct file * file = fget(fildes);
+
if (file)
ret = dupfd(file, 0);
return ret;
@@ -118,13 +190,13 @@
}
break;
case F_GETFD:
- err = FD_ISSET(fd, ¤t->files->close_on_exec);
+ err = FD_ISSET(fd, current->files->close_on_exec);
break;
case F_SETFD:
if (arg&1)
- FD_SET(fd, ¤t->files->close_on_exec);
+ FD_SET(fd, current->files->close_on_exec);
else
- FD_CLR(fd, ¤t->files->close_on_exec);
+ FD_CLR(fd, current->files->close_on_exec);
break;
case F_GETFL:
err = filp->f_flags;
@@ -152,7 +224,6 @@
err = filp->f_owner.pid;
break;
case F_SETOWN:
- err = 0;
filp->f_owner.pid = arg;
filp->f_owner.uid = current->uid;
filp->f_owner.euid = current->euid;
@@ -172,10 +243,9 @@
break;
default:
/* sockets need a few special fcntls. */
+ err = -EINVAL;
if (S_ISSOCK (filp->f_dentry->d_inode->i_mode))
err = sock_fcntl (filp, cmd, arg);
- else
- err = -EINVAL;
break;
}
fput(filp);
--- fs/file.c.~1~ Tue Jul 27 00:25:47 1999
+++ fs/file.c Tue Jul 27 00:25:47 1999
@@ -0,0 +1,240 @@
+/*
+ * linux/fs/open.c
+ *
+ * Copyright (C) 1998-1999, Stephen Tweedie and Bill Hawes
+ *
+ * Manage the dynamic fd arrays in the process files_struct.
+ */
+
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+#include <linux/malloc.h>
+#include <linux/vmalloc.h>
+
+#include <asm/bitops.h>
+
+
+/*
+ * Allocate an fd array, using get_free_page() if possible.
+ * Note: the array isn't cleared at allocation time.
+ */
+struct file ** alloc_fd_array(int num)
+{
+ struct file **new_fds;
+ int size = num * sizeof(struct file *);
+
+ if (size < PAGE_SIZE)
+ new_fds = (struct file **) kmalloc(size, GFP_KERNEL);
+ else if (size == PAGE_SIZE)
+ new_fds = (struct file **) __get_free_page(GFP_KERNEL);
+ else
+ new_fds = (struct file **) vmalloc(size);
+ return new_fds;
+}
+
+void free_fd_array(struct file **array, int num)
+{
+ int size = num * sizeof(struct file *);
+
+ if (!array) {
+ printk (KERN_ERR __FUNCTION__ "array = 0 (num = %d)\n", num);
+ return;
+ }
+
+ if (num <= NR_OPEN_DEFAULT) /* Don't free the embedded fd array! */
+ return;
+ else if (size < PAGE_SIZE)
+ kfree(array);
+ else if (size == PAGE_SIZE)
+ free_page((unsigned long) array);
+ else
+ vfree(array);
+}
+
+/*
+ * Expand the fd array in the files_struct. Called with the files
+ * spinlock held for write.
+ */
+
+int expand_fd_array(struct files_struct *files, int nr)
+{
+ struct file **new_fds;
+ int error, nfds;
+
+
+ error = -EMFILE;
+ if (files->max_fds >= NR_OPEN || nr > NR_OPEN)
+ goto out;
+
+ nfds = files->max_fds;
+ write_unlock(&files->file_lock);
+
+ /*
+ * Expand to the max in easy steps, and keep expanding it until
+ * we have enough for the requested fd array size.
+ */
+
+ do {
+#if NR_OPEN_DEFAULT < 256
+ if (nfds < 256)
+ nfds = 256;
+ else
+#endif
+ if (nfds < (PAGE_SIZE / sizeof(struct file *)))
+ nfds = PAGE_SIZE / sizeof(struct file *);
+ else {
+ nfds = nfds * 2;
+ if (nfds > NR_OPEN)
+ nfds = NR_OPEN;
+ }
+ } while (nfds < nr);
+
+ error = -ENOMEM;
+ new_fds = alloc_fd_array(nfds);
+ write_lock(&files->file_lock);
+ if (!new_fds)
+ goto out;
+
+ /* Copy the existing array and install the new pointer */
+
+ if (nfds > files->max_fds) {
+ struct file **old_fds;
+ int i;
+
+ old_fds = xchg(&files->fd, new_fds);
+ i = xchg(&files->max_fds, nfds);
+
+ /* Don't copy/clear the array if we are creating a new
+ fd array for fork() */
+ if (i) {
+ memcpy(new_fds, old_fds, i * sizeof(struct file *));
+ /* clear the remainder of the array */
+ memset(&new_fds[i], 0,
+ (nfds-i) * sizeof(struct file *));
+
+ write_unlock(&files->file_lock);
+ free_fd_array(old_fds, i);
+ write_lock(&files->file_lock);
+ }
+ } else {
+ /* Somebody expanded the array while we slept ... */
+ write_unlock(&files->file_lock);
+ free_fd_array(new_fds, nfds);
+ write_lock(&files->file_lock);
+ }
+ error = 0;
+out:
+ return error;
+}
+
+/*
+ * Allocate an fdset array, using get_free_page() if possible.
+ * Note: the array isn't cleared at allocation time.
+ */
+fd_set * alloc_fdset(int num)
+{
+ fd_set *new_fdset;
+ int size = num / 8;
+
+ if (size < PAGE_SIZE)
+ new_fdset = (fd_set *) kmalloc(size, GFP_KERNEL);
+ else if (size == PAGE_SIZE)
+ new_fdset = (fd_set *) __get_free_page(GFP_KERNEL);
+ else
+ new_fdset = (fd_set *) vmalloc(size);
+ return new_fdset;
+}
+
+void free_fdset(fd_set *array, int num)
+{
+ int size = num / 8;
+
+ if (!array) {
+ printk (KERN_ERR __FUNCTION__ "array = 0 (num = %d)\n", num);
+ return;
+ }
+
+ if (num <= __FD_SETSIZE) /* Don't free an embedded fdset */
+ return;
+ else if (size < PAGE_SIZE)
+ kfree(array);
+ else if (size == PAGE_SIZE)
+ free_page((unsigned long) array);
+ else
+ vfree(array);
+}
+
+/*
+ * Expand the fdset in the files_struct. Called with the files spinlock
+ * held for write.
+ */
+int expand_fdset(struct files_struct *files, int nr)
+{
+ fd_set *new_openset = 0, *new_execset = 0;
+ int error, nfds = 0;
+
+ error = -EMFILE;
+ if (files->max_fdset >= NR_OPEN || nr > NR_OPEN)
+ goto out;
+
+ nfds = files->max_fdset;
+ write_unlock(&files->file_lock);
+
+ /* Expand to the max in easy steps */
+ do {
+ if (nfds < (PAGE_SIZE * 8))
+ nfds = PAGE_SIZE * 8;
+ else {
+ nfds = nfds * 2;
+ if (nfds > NR_OPEN)
+ nfds = NR_OPEN;
+ }
+ } while (nfds < nr);
+
+ error = -ENOMEM;
+ new_openset = alloc_fdset(nfds);
+ new_execset = alloc_fdset(nfds);
+ write_lock(&files->file_lock);
+ if (!new_openset || !new_execset)
+ goto out;
+
+ error = 0;
+
+ /* Copy the existing tables and install the new pointers */
+ if (nfds > files->max_fdset) {
+ int i = files->max_fdset / (sizeof(unsigned long) * 8);
+ int count = (nfds - files->max_fdset) / 8;
+
+ /*
+ * Don't copy the entire array if the current fdset is
+ * not yet initialised.
+ */
+ if (i) {
+ memcpy (new_openset, files->open_fds, files->max_fdset/8);
+ memcpy (new_execset, files->close_on_exec, files->max_fdset/8);
+ memset (&new_openset->fds_bits[i], 0, count);
+ memset (&new_execset->fds_bits[i], 0, count);
+ }
+
+ nfds = xchg(&files->max_fdset, nfds);
+ new_openset = xchg(&files->open_fds, new_openset);
+ new_execset = xchg(&files->close_on_exec, new_execset);
+ write_unlock(&files->file_lock);
+ free_fdset (new_openset, nfds);
+ free_fdset (new_execset, nfds);
+ write_lock(&files->file_lock);
+ return 0;
+ }
+ /* Somebody expanded the array while we slept ... */
+
+out:
+ write_unlock(&files->file_lock);
+ if (new_openset)
+ free_fdset(new_openset, nfds);
+ if (new_execset)
+ free_fdset(new_execset, nfds);
+ write_lock(&files->file_lock);
+ return error;
+}
+
--- fs/ioctl.c.~1~ Wed Jul 14 19:09:29 1999
+++ fs/ioctl.c Tue Jul 27 00:25:47 1999
@@ -61,11 +61,11 @@
lock_kernel();
switch (cmd) {
case FIOCLEX:
- FD_SET(fd, ¤t->files->close_on_exec);
+ FD_SET(fd, current->files->close_on_exec);
break;
case FIONCLEX:
- FD_CLR(fd, ¤t->files->close_on_exec);
+ FD_CLR(fd, current->files->close_on_exec);
break;
case FIONBIO:
--- fs/open.c.~1~ Tue Jul 27 00:23:14 1999
+++ fs/open.c Tue Jul 27 00:25:47 1999
@@ -685,10 +685,14 @@
struct files_struct * files = current->files;
int fd, error;
- error = -EMFILE;
-
+ error = -EMFILE;
write_lock(&files->file_lock);
- fd = find_first_zero_bit(&files->open_fds, NR_OPEN);
+
+repeat:
+ fd = find_next_zero_bit(files->open_fds,
+ current->files->max_fdset,
+ files->next_fd);
+
/*
* N.B. For clone tasks sharing a files structure, this test
* will limit the total number of files that can be opened.
@@ -696,10 +700,31 @@
if (fd >= current->rlim[RLIMIT_NOFILE].rlim_cur)
goto out;
- /* Check here for fd > files->max_fds to do dynamic expansion */
+ /* Do we need to expand the fdset array? */
+ if (fd >= current->files->max_fdset) {
+ error = expand_fdset(files, 0);
+ if (!error) {
+ error = -EMFILE;
+ goto repeat;
+ }
+ goto out;
+ }
+
+ /*
+ * Check whether we need to expand the fd array.
+ */
+ if (fd >= files->max_fds) {
+ error = expand_fd_array(files, 0);
+ if (!error) {
+ error = -EMFILE;
+ goto repeat;
+ }
+ goto out;
+ }
- FD_SET(fd, &files->open_fds);
- FD_CLR(fd, &files->close_on_exec);
+ FD_SET(fd, files->open_fds);
+ FD_CLR(fd, files->close_on_exec);
+ files->next_fd = fd + 1;
#if 1
/* Sanity check */
if (files->fd[fd] != NULL) {
@@ -717,7 +742,9 @@
inline void put_unused_fd(unsigned int fd)
{
write_lock(¤t->files->file_lock);
- FD_CLR(fd, ¤t->files->open_fds);
+ FD_CLR(fd, current->files->open_fds);
+ if (fd < current->files->next_fd)
+ current->files->next_fd = fd;
write_unlock(¤t->files->file_lock);
}
@@ -790,8 +817,12 @@
* Careful here! We test whether the file pointer is NULL before
* releasing the fd. This ensures that one clone task can't release
* an fd while another clone is opening it.
+ *
+ * The "release" argument tells us whether or not to mark the fd as free
+ * or not in the open-files bitmap. dup2 uses this to retain the fd
+ * without races.
*/
-asmlinkage int sys_close(unsigned int fd)
+int do_close(unsigned int fd, int release)
{
int error;
struct file * filp;
@@ -802,9 +833,10 @@
filp = frip(fd);
if (!filp)
goto out_unlock;
- FD_CLR(fd, &files->close_on_exec);
+ FD_CLR(fd, files->close_on_exec);
write_unlock(&files->file_lock);
- put_unused_fd(fd);
+ if (release)
+ put_unused_fd(fd);
lock_kernel();
error = filp_close(filp, files);
unlock_kernel();
@@ -813,6 +845,11 @@
out_unlock:
write_unlock(&files->file_lock);
goto out;
+}
+
+asmlinkage int sys_close(unsigned int fd)
+{
+ return do_close(fd, 1);
}
/*
--- fs/proc/array.c.~1~ Tue Jul 27 00:24:45 1999
+++ fs/proc/array.c Tue Jul 27 00:25:47 1999
@@ -725,11 +725,13 @@
"PPid:\t%d\n"
"Uid:\t%d\t%d\t%d\t%d\n"
"Gid:\t%d\t%d\t%d\t%d\n"
+ "FDSize:\t%d\n"
"Groups:\t",
get_task_state(p),
p->pid, p->p_pptr->pid,
p->uid, p->euid, p->suid, p->fsuid,
- p->gid, p->egid, p->sgid, p->fsgid);
+ p->gid, p->egid, p->sgid, p->fsgid,
+ p->files ? p->files->max_fds : 0);
for (g = 0; g < p->ngroups; g++)
buffer += sprintf(buffer, "%d ", p->groups[g]);
--- fs/select.c.~1~ Wed Jul 14 19:09:30 1999
+++ fs/select.c Tue Jul 27 00:25:47 1999
@@ -106,7 +106,7 @@
/* handle last in-complete long-word first */
set = ~(~0UL << (n & (__NFDBITS-1)));
n /= __NFDBITS;
- open_fds = current->files->open_fds.fds_bits+n;
+ open_fds = current->files->open_fds->fds_bits+n;
max = 0;
if (set) {
set &= BITS(fds, n);
@@ -268,8 +268,8 @@
if (n < 0)
goto out_nofds;
- if (n > KFDS_NR)
- n = KFDS_NR;
+ if (n > current->files->max_fdset + 1)
+ n = current->files->max_fdset + 1;
/*
* We need 6 bitmaps (in/out/ex for both incoming and outgoing),
@@ -277,7 +277,7 @@
* long-words.
*/
ret = -ENOMEM;
- size = FDS_BYTES(n);
+ size = (n + 8 * sizeof(long) - 1) / (8 * sizeof(long)) * sizeof(long);
bits = kmalloc(6 * size, GFP_KERNEL);
if (!bits)
goto out_nofds;
@@ -380,7 +380,7 @@
lock_kernel();
/* Do a sanity check on nfds ... */
err = -EINVAL;
- if (nfds > NR_OPEN)
+ if (nfds > current->files->max_fds)
goto out;
if (timeout) {
--- include/asm-alpha/resource.h.~1~ Fri Aug 23 13:30:14 1996
+++ include/asm-alpha/resource.h Tue Jul 27 00:25:47 1999
@@ -28,7 +28,7 @@
{_STK_LIM, _STK_LIM}, /* RLIMIT_STACK */ \
{ 0, LONG_MAX}, /* RLIMIT_CORE */ \
{LONG_MAX, LONG_MAX}, /* RLIMIT_RSS */ \
- { NR_OPEN, NR_OPEN}, /* RLIMIT_NOFILE */ \
+ {INR_OPEN, INR_OPEN}, /* RLIMIT_NOFILE */ \
{LONG_MAX, LONG_MAX}, /* RLIMIT_AS */ \
{MAX_TASKS_PER_USER, MAX_TASKS_PER_USER}, /* RLIMIT_NPROC */ \
{LONG_MAX, LONG_MAX}, /* RLIMIT_MEMLOCK */ \
--- include/asm-arm/resource.h.~1~ Wed Jan 21 00:39:43 1998
+++ include/asm-arm/resource.h Tue Jul 27 00:25:47 1999
@@ -29,7 +29,7 @@
{ 0, LONG_MAX }, \
{ LONG_MAX, LONG_MAX }, \
{ MAX_TASKS_PER_USER, MAX_TASKS_PER_USER }, \
- { NR_OPEN, NR_OPEN }, \
+ { INR_OPEN, INR_OPEN }, \
{ LONG_MAX, LONG_MAX }, \
{ LONG_MAX, LONG_MAX }, \
}
--- include/asm-i386/resource.h.~1~ Tue Jul 27 00:23:14 1999
+++ include/asm-i386/resource.h Tue Jul 27 00:25:47 1999
@@ -29,7 +29,7 @@
{ 0, LONG_MAX }, \
{ LONG_MAX, LONG_MAX }, \
{ 0, 0 }, \
- { NR_OPEN, NR_OPEN }, \
+ { INR_OPEN, INR_OPEN }, \
{ LONG_MAX, LONG_MAX }, \
{ LONG_MAX, LONG_MAX }, \
}
--- include/asm-m68k/resource.h.~1~ Tue Jan 5 19:20:43 1999
+++ include/asm-m68k/resource.h Tue Jul 27 00:25:47 1999
@@ -29,7 +29,7 @@
{ 0, LONG_MAX}, \
{LONG_MAX, LONG_MAX}, \
{MAX_TASKS_PER_USER, MAX_TASKS_PER_USER}, \
- {NR_OPEN, NR_OPEN}, \
+ {INR_OPEN, INR_OPEN}, \
{LONG_MAX, LONG_MAX}, \
{LONG_MAX, LONG_MAX} \
}
--- include/asm-mips/resource.h.~1~ Wed Jul 14 19:08:36 1999
+++ include/asm-mips/resource.h Tue Jul 27 00:25:47 1999
@@ -34,7 +34,7 @@
{ LONG_MAX, LONG_MAX }, \
{ _STK_LIM, LONG_MAX }, \
{ 0, LONG_MAX }, \
- { NR_OPEN, NR_OPEN }, \
+ { INR_OPEN, INR_OPEN }, \
{ LONG_MAX, LONG_MAX }, \
{ LONG_MAX, LONG_MAX }, \
{ MAX_TASKS_PER_USER, MAX_TASKS_PER_USER }, \
--- include/asm-ppc/resource.h.~1~ Mon Dec 21 16:37:24 1998
+++ include/asm-ppc/resource.h Tue Jul 27 00:25:47 1999
@@ -25,7 +25,7 @@
{ 0, LONG_MAX}, /* RLIMIT_CORE */ \
{LONG_MAX, LONG_MAX}, /* RLIMIT_RSS */ \
{MAX_TASKS_PER_USER, MAX_TASKS_PER_USER}, /* RLIMIT_NPROC */ \
- { NR_OPEN, NR_OPEN}, /* RLIMIT_NOFILE */ \
+ {INR_OPEN, INR_OPEN}, /* RLIMIT_NOFILE */ \
{LONG_MAX, LONG_MAX}, /* RLIMIT_MEMLOCK */ \
{LONG_MAX, LONG_MAX}, /* RLIMIT_AS */ \
}
--- include/asm-sparc/resource.h.~1~ Tue Apr 20 22:33:11 1999
+++ include/asm-sparc/resource.h Tue Jul 27 00:25:47 1999
@@ -31,7 +31,7 @@
{LONG_MAX, LONG_MAX}, {LONG_MAX, LONG_MAX}, \
{LONG_MAX, LONG_MAX}, {_STK_LIM, LONG_MAX}, \
{ 0, LONG_MAX}, {LONG_MAX, LONG_MAX}, \
- {NR_OPEN, NR_OPEN}, {MAX_TASKS_PER_USER, MAX_TASKS_PER_USER}, \
+ {INR_OPEN, INR_OPEN}, {MAX_TASKS_PER_USER, MAX_TASKS_PER_USER}, \
{LONG_MAX, LONG_MAX}, {LONG_MAX, LONG_MAX} \
}
--- include/asm-sparc64/resource.h.~1~ Tue Apr 20 22:33:12 1999
+++ include/asm-sparc64/resource.h Tue Jul 27 00:25:47 1999
@@ -30,7 +30,7 @@
{LONG_MAX, LONG_MAX}, {LONG_MAX, LONG_MAX}, \
{LONG_MAX, LONG_MAX}, {_STK_LIM, LONG_MAX}, \
{ 0, LONG_MAX}, {LONG_MAX, LONG_MAX}, \
- {NR_OPEN, NR_OPEN}, {MAX_TASKS_PER_USER, MAX_TASKS_PER_USER}, \
+ {INR_OPEN, INR_OPEN}, {MAX_TASKS_PER_USER, MAX_TASKS_PER_USER}, \
{LONG_MAX, LONG_MAX}, {LONG_MAX, LONG_MAX} \
}
--- include/linux/file.h.~1~ Tue Jul 27 00:23:14 1999
+++ include/linux/file.h Tue Jul 27 00:25:47 1999
@@ -56,18 +56,6 @@
}
/*
- * Install a file pointer in the fd array.
- */
-extern inline void fd_install(unsigned int fd, struct file * file)
-{
- struct files_struct *files = current->files;
-
- write_lock(&files->file_lock);
- files->fd[fd] = file;
- write_unlock(&files->file_lock);
-}
-
-/*
* 23/12/1998 Marcin Dalecki <[EMAIL PROTECTED]>:
*
* Since those functions where calling other functions, it was compleatly
@@ -89,5 +77,27 @@
_fput(file);
}
extern void put_filp(struct file *);
+
+/*
+ * Install a file pointer in the fd array.
+ *
+ * The VFS is full of places where we drop the files lock between
+ * setting the open_fds bitmap and installing the file in the file
+ * array. At any such point, we are vulnerable to a dup2() race
+ * installing a file in the array before us. We need to detect this and
+ * fput() the struct file we are about to overwrite in this case.
+ */
+
+extern inline void fd_install(unsigned int fd, struct file * file)
+{
+ struct files_struct *files = current->files;
+ struct file * result;
+
+ write_lock(&files->file_lock);
+ result = xchg(&files->fd[fd], file);
+ write_unlock(&files->file_lock);
+ if (result)
+ fput(result);
+}
#endif /* __LINUX_FILE_H */
--- include/linux/fs.h.~1~ Wed Jul 14 19:09:31 1999
+++ include/linux/fs.h Tue Jul 27 00:26:35 1999
@@ -27,17 +27,19 @@
/*
- * It's silly to have NR_OPEN bigger than NR_FILE, but I'll fix
- * that later. Anyway, now the file code is no longer dependent
- * on bitmaps in unsigned longs, but uses the new fd_set structure..
+ * It's silly to have NR_OPEN bigger than NR_FILE, but you can change
+ * the file limit at runtime and only root can increase the per-process
+ * nr_file rlimit, so it's safe to set up a ridiculously high absolute
+ * upper limit on files-per-process.
*
* Some programs (notably those using select()) may have to be
- * recompiled to take full advantage of the new limits..
+ * recompiled to take full advantage of the new limits..
*/
/* Fixed constants first: */
#undef NR_OPEN
-#define NR_OPEN 1024
+#define NR_OPEN (1024*1024) /* Absolute upper limit on fd num */
+#define INR_OPEN 1024 /* Initial setting for nfile rlimits */
#define BLOCK_SIZE_BITS 10
#define BLOCK_SIZE (1<<BLOCK_SIZE_BITS)
@@ -704,6 +706,7 @@
asmlinkage int sys_open(const char *, int, int);
asmlinkage int sys_close(unsigned int); /* yes, it's really unsigned */
+extern int do_close(unsigned int, int); /* yes, it's really unsigned */
extern int do_truncate(struct dentry *, unsigned long);
extern int get_unused_fd(void);
extern void put_unused_fd(unsigned int);
--- include/linux/limits.h.~1~ Tue Jul 27 00:24:45 1999
+++ include/linux/limits.h Tue Jul 27 00:25:47 1999
@@ -1,7 +1,7 @@
#ifndef _LINUX_LIMITS_H
#define _LINUX_LIMITS_H
-#define NR_OPEN 1024
+#define NR_OPEN 1024
#define NGROUPS_MAX 32 /* supplemental group IDs are available */
#define ARG_MAX 131072 /* # bytes of args + environ for exec() */
--- include/linux/sched.h.~1~ Tue Jul 27 00:24:45 1999
+++ include/linux/sched.h Tue Jul 27 00:26:36 1999
@@ -128,24 +128,40 @@
asmlinkage void schedule(void);
/*
+ * The default fd array needs to be at least BITS_PER_LONG,
+ * as this is the granularity returned by copy_fdset().
+ */
+#define NR_OPEN_DEFAULT BITS_PER_LONG
+
+/*
* Open file table structure
*/
struct files_struct {
atomic_t count;
rwlock_t file_lock;
int max_fds;
+ int max_fdset;
+ int next_fd;
struct file ** fd; /* current fd array */
- fd_set close_on_exec;
- fd_set open_fds;
+ fd_set *close_on_exec;
+ fd_set *open_fds;
+ fd_set close_on_exec_init;
+ fd_set open_fds_init;
+ struct file * fd_array[NR_OPEN_DEFAULT];
};
#define INIT_FILES { \
ATOMIC_INIT(1), \
RW_LOCK_UNLOCKED, \
- NR_OPEN, \
- &init_fd_array[0], \
+ NR_OPEN_DEFAULT, \
+ __FD_SETSIZE, \
+ 0, \
+ &init_files.fd_array[0], \
+ &init_files.close_on_exec_init, \
+ &init_files.open_fds_init, \
+ { { 0, } }, \
{ { 0, } }, \
- { { 0, } } \
+ { NULL, } \
}
struct fs_struct {
@@ -629,6 +645,48 @@
extern void mmput(struct mm_struct *);
/* Remove the current tasks stale references to the old mm_struct */
extern void mm_release(void);
+
+/*
+ * Routines for handling the fd arrays
+ */
+extern struct file ** alloc_fd_array(int);
+extern int expand_fd_array(struct files_struct *, int nr);
+extern void free_fd_array(struct file **, int);
+
+extern fd_set *alloc_fdset(int);
+extern int expand_fdset(struct files_struct *, int nr);
+extern void free_fdset(fd_set *, int);
+
+/* Expand files. Return <0 on error; 0 nothing done; 1 files expanded,
+ * we may have blocked.
+ *
+ * Should be called with the files->file_lock spinlock held for write.
+ */
+static inline int expand_files(struct files_struct *files, int nr)
+{
+ int err, expand = 0;
+#ifdef FDSET_DEBUG
+ printk (KERN_ERR __FUNCTION__ " %d: nr = %d\n", current->pid, nr);
+#endif
+
+ if (nr >= files->max_fdset) {
+ expand = 1;
+ if ((err = expand_fdset(files, nr)))
+ goto out;
+ }
+ if (nr >= files->max_fds) {
+ expand = 1;
+ if ((err = expand_fd_array(files, nr)))
+ goto out;
+ }
+ err = expand;
+ out:
+#ifdef FDSET_DEBUG
+ if (err)
+ printk (KERN_ERR __FUNCTION__ " %d: return %d\n", current->pid, err);
+#endif
+ return err;
+}
extern int copy_thread(int, unsigned long, unsigned long, struct task_struct *,
struct pt_regs *);
extern void flush_thread(void);
--- kernel/exit.c.~1~ Tue Jul 27 00:24:45 1999
+++ kernel/exit.c Tue Jul 27 00:25:47 1999
@@ -1,3 +1,4 @@
+#define TRACELOCK
/*
* linux/kernel/exit.c
*
@@ -145,11 +146,11 @@
j = 0;
for (;;) {
- unsigned long set = files->open_fds.fds_bits[j];
+ unsigned long set;
i = j * __NFDBITS;
- j++;
- if (i >= files->max_fds)
+ if (i >= files->max_fdset || i >= files->max_fds)
break;
+ set = files->open_fds->fds_bits[j++];
while (set) {
if (set & 1) {
struct file * file = xchg(&files->fd[i], NULL);
@@ -172,12 +173,14 @@
if (atomic_dec_and_test(&files->count)) {
close_files(files);
/*
- * Free the fd array as appropriate ...
+ * Free the fd and fdset arrays if we expanded them.
*/
- if (NR_OPEN * sizeof(struct file *) == PAGE_SIZE)
- free_page((unsigned long) files->fd);
- else
- kfree(files->fd);
+ if (files->fd != &files->fd_array[0])
+ free_fd_array(files->fd, files->max_fds);
+ if (files->max_fdset > __FD_SETSIZE) {
+ free_fdset(files->open_fds, files->max_fdset);
+ free_fdset(files->close_on_exec, files->max_fdset);
+ }
kmem_cache_free(files_cachep, files);
}
}
--- kernel/fork.c.~1~ Tue Jul 27 00:24:45 1999
+++ kernel/fork.c Tue Jul 27 00:25:47 1999
@@ -433,32 +433,24 @@
return 0;
}
-/*
- * Copy a fd_set and compute the maximum fd it contains.
- */
-static inline int __copy_fdset(unsigned long *d, unsigned long *src)
+static int count_open_files(struct files_struct *files, int size)
{
- int i;
- unsigned long *p = src;
- unsigned long *max = src;
-
- for (i = __FDSET_LONGS; i; --i) {
- if ((*d++ = *p++) != 0)
- max = p;
+ int i;
+
+ /* Find the last open fd */
+ for (i = size/(8*sizeof(long)); i > 0; ) {
+ if (files->open_fds->fds_bits[--i])
+ break;
}
- return (max - src)*sizeof(long)*8;
-}
-
-static inline int copy_fdset(fd_set *dst, fd_set *src)
-{
- return __copy_fdset(dst->fds_bits, src->fds_bits);
+ i = (i+1) * 8 * sizeof(long);
+ return i;
}
static int copy_files(unsigned long clone_flags, struct task_struct * tsk)
{
struct files_struct *oldf, *newf;
struct file **old_fds, **new_fds;
- int size, i, error = 0;
+ int open_files, nfds, size, i, error = 0;
/*
* A background process may not have any files ...
@@ -478,43 +470,85 @@
if (!newf)
goto out;
- /*
- * Allocate the fd array, using get_free_page() if possible.
- * Eventually we want to make the array size variable ...
- */
- size = NR_OPEN * sizeof(struct file *);
- if (size == PAGE_SIZE)
- new_fds = (struct file **) __get_free_page(GFP_KERNEL);
- else
- new_fds = (struct file **) kmalloc(size, GFP_KERNEL);
- if (!new_fds)
- goto out_release;
-
- newf->file_lock = RW_LOCK_UNLOCKED;
atomic_set(&newf->count, 1);
- newf->max_fds = NR_OPEN;
- newf->fd = new_fds;
+
+ newf->file_lock = RW_LOCK_UNLOCKED;
+ newf->next_fd = 0;
+ newf->max_fds = NR_OPEN_DEFAULT;
+ newf->max_fdset = __FD_SETSIZE;
+ newf->close_on_exec = &newf->close_on_exec_init;
+ newf->open_fds = &newf->open_fds_init;
+ newf->fd = &newf->fd_array[0];
+
+ /* We don't yet have the oldf readlock, but even if the old
+ fdset gets grown now, we'll only copy up to "size" fds */
+ size = oldf->max_fdset;
+ if (size > __FD_SETSIZE) {
+ newf->max_fdset = 0;
+ write_lock(&newf->file_lock);
+ error = expand_fdset(newf, size);
+ write_unlock(&newf->file_lock);
+ if (error)
+ goto out_release;
+ }
read_lock(&oldf->file_lock);
- newf->close_on_exec = oldf->close_on_exec;
- i = copy_fdset(&newf->open_fds, &oldf->open_fds);
+
+ open_files = count_open_files(oldf, size);
+
+ /*
+ * Check whether we need to allocate a larger fd array.
+ * Note: we're not a clone task, so the open count won't
+ * change.
+ */
+ nfds = NR_OPEN_DEFAULT;
+ if (open_files > nfds) {
+ read_unlock(&oldf->file_lock);
+ newf->max_fds = 0;
+ write_lock(&newf->file_lock);
+ error = expand_fd_array(newf, open_files);
+ write_unlock(&newf->file_lock);
+ if (error)
+ goto out_release;
+ nfds = newf->max_fds;
+ read_lock(&oldf->file_lock);
+ }
old_fds = oldf->fd;
- for (; i != 0; i--) {
+ new_fds = newf->fd;
+
+ memcpy(newf->open_fds->fds_bits, oldf->open_fds->fds_bits, open_files/8);
+ memcpy(newf->close_on_exec->fds_bits, oldf->close_on_exec->fds_bits,
+open_files/8);
+
+ for (i = open_files; i != 0; i--) {
struct file *f = *old_fds++;
if (f)
get_file(f);
*new_fds++ = f;
}
read_unlock(&oldf->file_lock);
+
+ /* compute the remainder to be cleared */
+ size = (newf->max_fds - open_files) * sizeof(struct file *);
+
/* This is long word aligned thus could use a optimized version */
- memset(new_fds, 0, (char *)newf->fd + size - (char *)new_fds);
-
+ memset(new_fds, 0, size);
+
+ if (newf->max_fdset > open_files) {
+ int left = (newf->max_fdset-open_files)/8;
+ int start = open_files / (8 * sizeof(unsigned long));
+
+ memset(&newf->open_fds->fds_bits[start], 0, left);
+ memset(&newf->close_on_exec->fds_bits[start], 0, left);
+ }
+
tsk->files = newf;
error = 0;
out:
return error;
out_release:
+ free_fdset (newf->close_on_exec, newf->max_fdset);
+ free_fdset (newf->open_fds, newf->max_fdset);
kmem_cache_free(files_cachep, newf);
goto out;
}