#include <linux/highmem.h>
#include <linux/init.h>
#include <linux/string.h>
+#include <linux/capability.h>
#include <linux/backing-dev.h>
#include <linux/hugetlb.h>
#include <linux/pagevec.h>
static struct backing_dev_info hugetlbfs_backing_dev_info = {
.ra_pages = 0, /* No readahead */
- .memory_backed = 1, /* Does not contribute to dirty memory */
+ .capabilities = BDI_CAP_NO_ACCT_DIRTY | BDI_CAP_NO_WRITEBACK,
};
+int sysctl_hugetlb_shm_group;
+
+static void huge_pagevec_release(struct pagevec *pvec)
+{
+ int i;
+
+ for (i = 0; i < pagevec_count(pvec); ++i)
+ put_page(pvec->pages[i]);
+
+ pagevec_reinit(pvec);
+}
+
+/*
+ * huge_pages_needed tries to determine the number of new huge pages that
+ * will be required to fully populate this VMA. This will be equal to
+ * the size of the VMA in huge pages minus the number of huge pages
+ * (covered by this VMA) that are found in the page cache.
+ *
+ * Result is in bytes to be compatible with is_hugepage_mem_enough()
+ */
+static unsigned long
+huge_pages_needed(struct address_space *mapping, struct vm_area_struct *vma)
+{
+ int i;
+ struct pagevec pvec;
+ unsigned long start = vma->vm_start;
+ unsigned long end = vma->vm_end;
+ unsigned long hugepages = (end - start) >> HPAGE_SHIFT;
+ pgoff_t next = vma->vm_pgoff >> (HPAGE_SHIFT - PAGE_SHIFT);
+ pgoff_t endpg = next + hugepages;
+
+ pagevec_init(&pvec, 0);
+ while (next < endpg) {
+ if (!pagevec_lookup(&pvec, mapping, next, PAGEVEC_SIZE))
+ break;
+ for (i = 0; i < pagevec_count(&pvec); i++) {
+ struct page *page = pvec.pages[i];
+ if (page->index > next)
+ next = page->index;
+ if (page->index >= endpg)
+ break;
+ next++;
+ hugepages--;
+ }
+ huge_pagevec_release(&pvec);
+ }
+ return hugepages << HPAGE_SHIFT;
+}
+
static int hugetlbfs_file_mmap(struct file *file, struct vm_area_struct *vma)
{
struct inode *inode = file->f_dentry->d_inode;
struct address_space *mapping = inode->i_mapping;
+ unsigned long bytes;
loff_t len, vma_len;
int ret;
+ if (vma->vm_pgoff & (HPAGE_SIZE / PAGE_SIZE - 1))
+ return -EINVAL;
+
if (vma->vm_start & ~HPAGE_MASK)
return -EINVAL;
if (vma->vm_end - vma->vm_start < HPAGE_SIZE)
return -EINVAL;
+ bytes = huge_pages_needed(mapping, vma);
+ if (!is_hugepage_mem_enough(bytes))
+ return -ENOMEM;
+
vma_len = (loff_t)(vma->vm_end - vma->vm_start);
- down(&inode->i_sem);
+ mutex_lock(&inode->i_mutex);
file_accessed(file);
vma->vm_flags |= VM_HUGETLB | VM_RESERVED;
vma->vm_ops = &hugetlb_vm_ops;
- ret = hugetlb_prefault(mapping, vma);
- len = vma_len + ((loff_t)vma->vm_pgoff << PAGE_SHIFT);
- if (ret == 0 && inode->i_size < len)
+
+ ret = -ENOMEM;
+ len = vma_len + ((loff_t)vma->vm_pgoff << PAGE_SHIFT);
+ if (!(vma->vm_flags & VM_WRITE) && len > inode->i_size)
+ goto out;
+
+ ret = 0;
+ hugetlb_prefault_arch_hook(vma->vm_mm);
+ if (inode->i_size < len)
inode->i_size = len;
- up(&inode->i_sem);
+out:
+ mutex_unlock(&inode->i_mutex);
return ret;
}
/*
- * Called under down_write(mmap_sem), page_table_lock is not held
+ * Called under down_write(mmap_sem).
*/
#ifdef HAVE_ARCH_HUGETLB_UNMAPPED_AREA
{
struct mm_struct *mm = current->mm;
struct vm_area_struct *vma;
+ unsigned long start_addr;
if (len & ~HPAGE_MASK)
return -EINVAL;
return addr;
}
- addr = ALIGN(mm->free_area_cache, HPAGE_SIZE);
+ start_addr = mm->free_area_cache;
+
+ if (len <= mm->cached_hole_size)
+ start_addr = TASK_UNMAPPED_BASE;
+
+full_search:
+ addr = ALIGN(start_addr, HPAGE_SIZE);
for (vma = find_vma(mm, addr); ; vma = vma->vm_next) {
/* At this point: (!vma || addr < vma->vm_end). */
- if (TASK_SIZE - len < addr)
+ if (TASK_SIZE - len < addr) {
+ /*
+ * Start a new search - just in case we missed
+ * some holes.
+ */
+ if (start_addr != TASK_UNMAPPED_BASE) {
+ start_addr = TASK_UNMAPPED_BASE;
+ goto full_search;
+ }
return -ENOMEM;
+ }
+
if (!vma || addr + len <= vma->vm_start)
return addr;
addr = ALIGN(vma->vm_end, HPAGE_SIZE);
return -EINVAL;
}
-void huge_pagevec_release(struct pagevec *pvec)
-{
- int i;
-
- for (i = 0; i < pagevec_count(pvec); ++i)
- put_page(pvec->pages[i]);
-
- pagevec_reinit(pvec);
-}
-
-void truncate_huge_page(struct page *page)
+static void truncate_huge_page(struct page *page)
{
clear_page_dirty(page);
ClearPageUptodate(page);
put_page(page);
}
-void truncate_hugepages(struct address_space *mapping, loff_t lstart)
+static void truncate_hugepages(struct address_space *mapping, loff_t lstart)
{
const pgoff_t start = lstart >> HPAGE_SHIFT;
struct pagevec pvec;
static void hugetlbfs_delete_inode(struct inode *inode)
{
- struct hugetlbfs_sb_info *sbinfo = HUGETLBFS_SB(inode->i_sb);
-
- hlist_del_init(&inode->i_hash);
- list_del_init(&inode->i_list);
- inode->i_state |= I_FREEING;
- inodes_stat.nr_inodes--;
- spin_unlock(&inode_lock);
-
if (inode->i_data.nrpages)
truncate_hugepages(&inode->i_data, 0);
-
- security_inode_delete(inode);
-
- if (sbinfo->free_inodes >= 0) {
- spin_lock(&sbinfo->stat_lock);
- sbinfo->free_inodes++;
- spin_unlock(&sbinfo->stat_lock);
- }
-
clear_inode(inode);
- destroy_inode(inode);
}
static void hugetlbfs_forget_inode(struct inode *inode)
{
- struct super_block *super_block = inode->i_sb;
- struct hugetlbfs_sb_info *sbinfo = HUGETLBFS_SB(super_block);
-
- if (hlist_unhashed(&inode->i_hash))
- goto out_truncate;
+ struct super_block *sb = inode->i_sb;
- if (!(inode->i_state & (I_DIRTY|I_LOCK))) {
- list_del(&inode->i_list);
- list_add(&inode->i_list, &inode_unused);
- }
- inodes_stat.nr_unused++;
- if (!super_block || (super_block->s_flags & MS_ACTIVE)) {
+ if (!hlist_unhashed(&inode->i_hash)) {
+ if (!(inode->i_state & (I_DIRTY|I_LOCK)))
+ list_move(&inode->i_list, &inode_unused);
+ inodes_stat.nr_unused++;
+ if (!sb || (sb->s_flags & MS_ACTIVE)) {
+ spin_unlock(&inode_lock);
+ return;
+ }
+ inode->i_state |= I_WILL_FREE;
spin_unlock(&inode_lock);
- return;
+ /*
+ * write_inode_now is a noop as we set BDI_CAP_NO_WRITEBACK
+ * in our backing_dev_info.
+ */
+ write_inode_now(inode, 1);
+ spin_lock(&inode_lock);
+ inode->i_state &= ~I_WILL_FREE;
+ inodes_stat.nr_unused--;
+ hlist_del_init(&inode->i_hash);
}
-
- /* write_inode_now() ? */
- inodes_stat.nr_unused--;
- hlist_del_init(&inode->i_hash);
-out_truncate:
list_del_init(&inode->i_list);
+ list_del_init(&inode->i_sb_list);
inode->i_state |= I_FREEING;
inodes_stat.nr_inodes--;
spin_unlock(&inode_lock);
if (inode->i_data.nrpages)
truncate_hugepages(&inode->i_data, 0);
-
- if (sbinfo->free_inodes >= 0) {
- spin_lock(&sbinfo->stat_lock);
- sbinfo->free_inodes++;
- spin_unlock(&sbinfo->stat_lock);
- }
-
clear_inode(inode);
destroy_inode(inode);
}
static void hugetlbfs_drop_inode(struct inode *inode)
{
if (!inode->i_nlink)
- hugetlbfs_delete_inode(inode);
+ generic_delete_inode(inode);
else
hugetlbfs_forget_inode(inode);
}
static inline void
hugetlb_vmtruncate_list(struct prio_tree_root *root, unsigned long h_pgoff)
{
- struct vm_area_struct *vma = NULL;
+ struct vm_area_struct *vma;
struct prio_tree_iter iter;
- while ((vma = vma_prio_tree_next(vma, root, &iter,
- h_pgoff, ULONG_MAX)) != NULL) {
+ vma_prio_tree_foreach(vma, &iter, root, h_pgoff, ULONG_MAX) {
unsigned long h_vm_pgoff;
- unsigned long v_length;
unsigned long v_offset;
- h_vm_pgoff = vma->vm_pgoff << (HPAGE_SHIFT - PAGE_SHIFT);
- v_length = vma->vm_end - vma->vm_start;
+ h_vm_pgoff = vma->vm_pgoff >> (HPAGE_SHIFT - PAGE_SHIFT);
v_offset = (h_pgoff - h_vm_pgoff) << HPAGE_SHIFT;
-
/*
* Is this VMA fully outside the truncation point?
*/
if (h_vm_pgoff >= h_pgoff)
v_offset = 0;
- zap_hugepage_range(vma,
- vma->vm_start + v_offset,
- v_length - v_offset);
+ unmap_hugepage_range(vma,
+ vma->vm_start + v_offset, vma->vm_end);
}
}
if (error)
goto out;
- error = security_inode_setattr(dentry, attr);
- if (error)
- goto out;
if (ia_valid & ATTR_SIZE) {
error = -EINVAL;
if (!(attr->ia_size & ~HPAGE_MASK))
gid_t gid, int mode, dev_t dev)
{
struct inode *inode;
- struct hugetlbfs_sb_info *sbinfo = HUGETLBFS_SB(sb);
-
- if (sbinfo->free_inodes >= 0) {
- spin_lock(&sbinfo->stat_lock);
- if (!sbinfo->free_inodes) {
- spin_unlock(&sbinfo->stat_lock);
- return NULL;
- }
- sbinfo->free_inodes--;
- spin_unlock(&sbinfo->stat_lock);
- }
inode = new_inode(sb);
if (inode) {
inode->i_mapping->backing_dev_info =&hugetlbfs_backing_dev_info;
inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
info = HUGETLBFS_I(inode);
- mpol_shared_policy_init(&info->policy);
+ mpol_shared_policy_init(&info->policy, MPOL_DEFAULT, NULL);
switch (mode & S_IFMT) {
default:
init_special_inode(inode, mode, dev);
/*
* For direct-IO reads into hugetlb pages
*/
-int hugetlbfs_set_page_dirty(struct page *page)
+static int hugetlbfs_set_page_dirty(struct page *page)
{
return 0;
}
buf->f_bsize = HPAGE_SIZE;
if (sbinfo) {
spin_lock(&sbinfo->stat_lock);
- buf->f_blocks = sbinfo->max_blocks;
- buf->f_bavail = buf->f_bfree = sbinfo->free_blocks;
- buf->f_files = sbinfo->max_inodes;
- buf->f_ffree = sbinfo->free_inodes;
+ /* If no limits set, just report 0 for max/free/used
+ * blocks, like simple_statfs() */
+ if (sbinfo->max_blocks >= 0) {
+ buf->f_blocks = sbinfo->max_blocks;
+ buf->f_bavail = buf->f_bfree = sbinfo->free_blocks;
+ buf->f_files = sbinfo->max_inodes;
+ buf->f_ffree = sbinfo->free_inodes;
+ }
spin_unlock(&sbinfo->stat_lock);
}
buf->f_namelen = NAME_MAX;
}
}
+static inline int hugetlbfs_dec_free_inodes(struct hugetlbfs_sb_info *sbinfo)
+{
+ if (sbinfo->free_inodes >= 0) {
+ spin_lock(&sbinfo->stat_lock);
+ if (unlikely(!sbinfo->free_inodes)) {
+ spin_unlock(&sbinfo->stat_lock);
+ return 0;
+ }
+ sbinfo->free_inodes--;
+ spin_unlock(&sbinfo->stat_lock);
+ }
+
+ return 1;
+}
+
+static void hugetlbfs_inc_free_inodes(struct hugetlbfs_sb_info *sbinfo)
+{
+ if (sbinfo->free_inodes >= 0) {
+ spin_lock(&sbinfo->stat_lock);
+ sbinfo->free_inodes++;
+ spin_unlock(&sbinfo->stat_lock);
+ }
+}
+
+
static kmem_cache_t *hugetlbfs_inode_cachep;
static struct inode *hugetlbfs_alloc_inode(struct super_block *sb)
{
+ struct hugetlbfs_sb_info *sbinfo = HUGETLBFS_SB(sb);
struct hugetlbfs_inode_info *p;
+ if (unlikely(!hugetlbfs_dec_free_inodes(sbinfo)))
+ return NULL;
p = kmem_cache_alloc(hugetlbfs_inode_cachep, SLAB_KERNEL);
- if (!p)
+ if (unlikely(!p)) {
+ hugetlbfs_inc_free_inodes(sbinfo);
return NULL;
+ }
return &p->vfs_inode;
}
-static void init_once(void *foo, kmem_cache_t *cachep, unsigned long flags)
-{
- struct hugetlbfs_inode_info *ei = (struct hugetlbfs_inode_info *)foo;
-
- if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) ==
- SLAB_CTOR_CONSTRUCTOR)
- inode_init_once(&ei->vfs_inode);
-}
-
static void hugetlbfs_destroy_inode(struct inode *inode)
{
+ hugetlbfs_inc_free_inodes(HUGETLBFS_SB(inode->i_sb));
mpol_free_shared_policy(&HUGETLBFS_I(inode)->policy);
kmem_cache_free(hugetlbfs_inode_cachep, HUGETLBFS_I(inode));
}
.set_page_dirty = hugetlbfs_set_page_dirty,
};
+
+static void init_once(void *foo, kmem_cache_t *cachep, unsigned long flags)
+{
+ struct hugetlbfs_inode_info *ei = (struct hugetlbfs_inode_info *)foo;
+
+ if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) ==
+ SLAB_CTOR_CONSTRUCTOR)
+ inode_init_once(&ei->vfs_inode);
+}
+
struct file_operations hugetlbfs_file_operations = {
.mmap = hugetlbfs_file_mmap,
.fsync = simple_sync_file,
.alloc_inode = hugetlbfs_alloc_inode,
.destroy_inode = hugetlbfs_destroy_inode,
.statfs = hugetlbfs_statfs,
+ .delete_inode = hugetlbfs_delete_inode,
.drop_inode = hugetlbfs_drop_inode,
.put_super = hugetlbfs_put_super,
};
sbinfo->free_blocks = config.nr_blocks;
sbinfo->max_inodes = config.nr_inodes;
sbinfo->free_inodes = config.nr_inodes;
+ sb->s_maxbytes = MAX_LFS_FILESIZE;
sb->s_blocksize = HPAGE_SIZE;
sb->s_blocksize_bits = HPAGE_SHIFT;
sb->s_magic = HUGETLBFS_MAGIC;
sb->s_op = &hugetlbfs_ops;
+ sb->s_time_gran = 1;
inode = hugetlbfs_get_inode(sb, config.uid, config.gid,
S_IFDIR | config.mode, 0);
if (!inode)
*/
static unsigned long hugetlbfs_counter(void)
{
- static spinlock_t lock = SPIN_LOCK_UNLOCKED;
+ static DEFINE_SPINLOCK(lock);
static unsigned long counter;
unsigned long ret;
return ret;
}
+static int can_do_hugetlb_shm(void)
+{
+ return likely(capable(CAP_IPC_LOCK) ||
+ in_group_p(sysctl_hugetlb_shm_group) ||
+ can_do_mlock());
+}
+
struct file *hugetlb_zero_setup(size_t size)
{
- int error;
+ int error = -ENOMEM;
struct file *file;
struct inode *inode;
struct dentry *dentry, *root;
struct qstr quick_string;
char buf[16];
- if (!can_do_mlock())
+ if (!can_do_hugetlb_shm())
return ERR_PTR(-EPERM);
if (!is_hugepage_mem_enough(size))
return ERR_PTR(-ENOMEM);
+ if (!user_shm_lock(size, current->user))
+ return ERR_PTR(-ENOMEM);
+
root = hugetlbfs_vfsmount->mnt_root;
snprintf(buf, 16, "%lu", hugetlbfs_counter());
quick_string.name = buf;
quick_string.hash = 0;
dentry = d_alloc(root, &quick_string);
if (!dentry)
- return ERR_PTR(-ENOMEM);
+ goto out_shm_unlock;
error = -ENFILE;
file = get_empty_filp();
put_filp(file);
out_dentry:
dput(dentry);
+out_shm_unlock:
+ user_shm_unlock(size, current->user);
return ERR_PTR(error);
}
hugetlbfs_inode_cachep = kmem_cache_create("hugetlbfs_inode_cache",
sizeof(struct hugetlbfs_inode_info),
- 0, SLAB_RECLAIM_ACCOUNT,
- init_once, NULL);
+ 0, 0, init_once, NULL);
if (hugetlbfs_inode_cachep == NULL)
return -ENOMEM;