badf16621c
In order for the RCU to work, the file table array, sets and their sizes must be updated atomically. Instead of ensuring this through too many memory barriers, we put the arrays and their sizes in a separate structure. This patch takes the first step of putting the file table elements in a separate structure fdtable that is embedded withing files_struct. It also changes all the users to refer to the file table using files_fdtable() macro. Subsequent applciation of RCU becomes easier after this. Signed-off-by: Dipankar Sarma <dipankar@in.ibm.com> Signed-Off-By: David Howells <dhowells@redhat.com> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
263 lines
5.7 KiB
C
263 lines
5.7 KiB
C
/*
|
|
* linux/fs/file.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/time.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/file.h>
|
|
#include <linux/bitops.h>
|
|
|
|
|
|
/*
|
|
* Allocate an fd array, using kmalloc or vmalloc.
|
|
* 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
|
|
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 "free_fd_array: 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
|
|
vfree(array);
|
|
}
|
|
|
|
/*
|
|
* Expand the fd array in the files_struct. Called with the files
|
|
* spinlock held for write.
|
|
*/
|
|
|
|
static int expand_fd_array(struct files_struct *files, int nr)
|
|
__releases(files->file_lock)
|
|
__acquires(files->file_lock)
|
|
{
|
|
struct file **new_fds;
|
|
int error, nfds;
|
|
struct fdtable *fdt;
|
|
|
|
|
|
error = -EMFILE;
|
|
fdt = files_fdtable(files);
|
|
if (fdt->max_fds >= NR_OPEN || nr >= NR_OPEN)
|
|
goto out;
|
|
|
|
nfds = fdt->max_fds;
|
|
spin_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);
|
|
spin_lock(&files->file_lock);
|
|
if (!new_fds)
|
|
goto out;
|
|
|
|
/* Copy the existing array and install the new pointer */
|
|
fdt = files_fdtable(files);
|
|
|
|
if (nfds > fdt->max_fds) {
|
|
struct file **old_fds;
|
|
int i;
|
|
|
|
old_fds = xchg(&fdt->fd, new_fds);
|
|
i = xchg(&fdt->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 *));
|
|
|
|
spin_unlock(&files->file_lock);
|
|
free_fd_array(old_fds, i);
|
|
spin_lock(&files->file_lock);
|
|
}
|
|
} else {
|
|
/* Somebody expanded the array while we slept ... */
|
|
spin_unlock(&files->file_lock);
|
|
free_fd_array(new_fds, nfds);
|
|
spin_lock(&files->file_lock);
|
|
}
|
|
error = 0;
|
|
out:
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Allocate an fdset array, using kmalloc or vmalloc.
|
|
* 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
|
|
new_fdset = (fd_set *) vmalloc(size);
|
|
return new_fdset;
|
|
}
|
|
|
|
void free_fdset(fd_set *array, int num)
|
|
{
|
|
int size = num / 8;
|
|
|
|
if (num <= __FD_SETSIZE) /* Don't free an embedded fdset */
|
|
return;
|
|
else if (size <= PAGE_SIZE)
|
|
kfree(array);
|
|
else
|
|
vfree(array);
|
|
}
|
|
|
|
/*
|
|
* Expand the fdset in the files_struct. Called with the files spinlock
|
|
* held for write.
|
|
*/
|
|
static int expand_fdset(struct files_struct *files, int nr)
|
|
__releases(file->file_lock)
|
|
__acquires(file->file_lock)
|
|
{
|
|
fd_set *new_openset = NULL, *new_execset = NULL;
|
|
int error, nfds = 0;
|
|
struct fdtable *fdt;
|
|
|
|
error = -EMFILE;
|
|
fdt = files_fdtable(files);
|
|
if (fdt->max_fdset >= NR_OPEN || nr >= NR_OPEN)
|
|
goto out;
|
|
|
|
nfds = fdt->max_fdset;
|
|
spin_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);
|
|
spin_lock(&files->file_lock);
|
|
if (!new_openset || !new_execset)
|
|
goto out;
|
|
|
|
error = 0;
|
|
|
|
/* Copy the existing tables and install the new pointers */
|
|
fdt = files_fdtable(files);
|
|
if (nfds > fdt->max_fdset) {
|
|
int i = fdt->max_fdset / (sizeof(unsigned long) * 8);
|
|
int count = (nfds - fdt->max_fdset) / 8;
|
|
|
|
/*
|
|
* Don't copy the entire array if the current fdset is
|
|
* not yet initialised.
|
|
*/
|
|
if (i) {
|
|
memcpy (new_openset, fdt->open_fds, fdt->max_fdset/8);
|
|
memcpy (new_execset, fdt->close_on_exec, fdt->max_fdset/8);
|
|
memset (&new_openset->fds_bits[i], 0, count);
|
|
memset (&new_execset->fds_bits[i], 0, count);
|
|
}
|
|
|
|
nfds = xchg(&fdt->max_fdset, nfds);
|
|
new_openset = xchg(&fdt->open_fds, new_openset);
|
|
new_execset = xchg(&fdt->close_on_exec, new_execset);
|
|
spin_unlock(&files->file_lock);
|
|
free_fdset (new_openset, nfds);
|
|
free_fdset (new_execset, nfds);
|
|
spin_lock(&files->file_lock);
|
|
return 0;
|
|
}
|
|
/* Somebody expanded the array while we slept ... */
|
|
|
|
out:
|
|
spin_unlock(&files->file_lock);
|
|
if (new_openset)
|
|
free_fdset(new_openset, nfds);
|
|
if (new_execset)
|
|
free_fdset(new_execset, nfds);
|
|
spin_lock(&files->file_lock);
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
int expand_files(struct files_struct *files, int nr)
|
|
{
|
|
int err, expand = 0;
|
|
struct fdtable *fdt;
|
|
|
|
fdt = files_fdtable(files);
|
|
if (nr >= fdt->max_fdset) {
|
|
expand = 1;
|
|
if ((err = expand_fdset(files, nr)))
|
|
goto out;
|
|
}
|
|
if (nr >= fdt->max_fds) {
|
|
expand = 1;
|
|
if ((err = expand_fd_array(files, nr)))
|
|
goto out;
|
|
}
|
|
err = expand;
|
|
out:
|
|
return err;
|
|
}
|