11da177e4SLinus Torvalds /* 21da177e4SLinus Torvalds * mm/mremap.c 31da177e4SLinus Torvalds * 41da177e4SLinus Torvalds * (C) Copyright 1996 Linus Torvalds 51da177e4SLinus Torvalds * 6046c6884SAlan Cox * Address space accounting code <alan@lxorguk.ukuu.org.uk> 71da177e4SLinus Torvalds * (C) Copyright 2002 Red Hat Inc, All Rights Reserved 81da177e4SLinus Torvalds */ 91da177e4SLinus Torvalds 101da177e4SLinus Torvalds #include <linux/mm.h> 111da177e4SLinus Torvalds #include <linux/hugetlb.h> 121da177e4SLinus Torvalds #include <linux/slab.h> 131da177e4SLinus Torvalds #include <linux/shm.h> 141ff82995SHugh Dickins #include <linux/ksm.h> 151da177e4SLinus Torvalds #include <linux/mman.h> 161da177e4SLinus Torvalds #include <linux/swap.h> 17c59ede7bSRandy.Dunlap #include <linux/capability.h> 181da177e4SLinus Torvalds #include <linux/fs.h> 191da177e4SLinus Torvalds #include <linux/highmem.h> 201da177e4SLinus Torvalds #include <linux/security.h> 211da177e4SLinus Torvalds #include <linux/syscalls.h> 22cddb8a5cSAndrea Arcangeli #include <linux/mmu_notifier.h> 231da177e4SLinus Torvalds 241da177e4SLinus Torvalds #include <asm/uaccess.h> 251da177e4SLinus Torvalds #include <asm/cacheflush.h> 261da177e4SLinus Torvalds #include <asm/tlbflush.h> 271da177e4SLinus Torvalds 28ba470de4SRik van Riel #include "internal.h" 29ba470de4SRik van Riel 307be7a546SHugh Dickins static pmd_t *get_old_pmd(struct mm_struct *mm, unsigned long addr) 311da177e4SLinus Torvalds { 321da177e4SLinus Torvalds pgd_t *pgd; 331da177e4SLinus Torvalds pud_t *pud; 341da177e4SLinus Torvalds pmd_t *pmd; 351da177e4SLinus Torvalds 361da177e4SLinus Torvalds pgd = pgd_offset(mm, addr); 371da177e4SLinus Torvalds if (pgd_none_or_clear_bad(pgd)) 381da177e4SLinus Torvalds return NULL; 391da177e4SLinus Torvalds 401da177e4SLinus Torvalds pud = pud_offset(pgd, addr); 411da177e4SLinus Torvalds if (pud_none_or_clear_bad(pud)) 421da177e4SLinus Torvalds return NULL; 431da177e4SLinus Torvalds 441da177e4SLinus Torvalds pmd = pmd_offset(pud, addr); 451da177e4SLinus Torvalds if (pmd_none_or_clear_bad(pmd)) 461da177e4SLinus Torvalds return NULL; 471da177e4SLinus Torvalds 487be7a546SHugh Dickins return pmd; 491da177e4SLinus Torvalds } 501da177e4SLinus Torvalds 517be7a546SHugh Dickins static pmd_t *alloc_new_pmd(struct mm_struct *mm, unsigned long addr) 521da177e4SLinus Torvalds { 531da177e4SLinus Torvalds pgd_t *pgd; 541da177e4SLinus Torvalds pud_t *pud; 55c74df32cSHugh Dickins pmd_t *pmd; 561da177e4SLinus Torvalds 571da177e4SLinus Torvalds pgd = pgd_offset(mm, addr); 581da177e4SLinus Torvalds pud = pud_alloc(mm, pgd, addr); 591da177e4SLinus Torvalds if (!pud) 60c74df32cSHugh Dickins return NULL; 617be7a546SHugh Dickins 621da177e4SLinus Torvalds pmd = pmd_alloc(mm, pud, addr); 637be7a546SHugh Dickins if (!pmd) 64c74df32cSHugh Dickins return NULL; 657be7a546SHugh Dickins 661bb3630eSHugh Dickins if (!pmd_present(*pmd) && __pte_alloc(mm, pmd, addr)) 67c74df32cSHugh Dickins return NULL; 68c74df32cSHugh Dickins 697be7a546SHugh Dickins return pmd; 701da177e4SLinus Torvalds } 711da177e4SLinus Torvalds 727be7a546SHugh Dickins static void move_ptes(struct vm_area_struct *vma, pmd_t *old_pmd, 737be7a546SHugh Dickins unsigned long old_addr, unsigned long old_end, 747be7a546SHugh Dickins struct vm_area_struct *new_vma, pmd_t *new_pmd, 757be7a546SHugh Dickins unsigned long new_addr) 761da177e4SLinus Torvalds { 771da177e4SLinus Torvalds struct address_space *mapping = NULL; 781da177e4SLinus Torvalds struct mm_struct *mm = vma->vm_mm; 797be7a546SHugh Dickins pte_t *old_pte, *new_pte, pte; 804c21e2f2SHugh Dickins spinlock_t *old_ptl, *new_ptl; 81cddb8a5cSAndrea Arcangeli unsigned long old_start; 821da177e4SLinus Torvalds 83cddb8a5cSAndrea Arcangeli old_start = old_addr; 84cddb8a5cSAndrea Arcangeli mmu_notifier_invalidate_range_start(vma->vm_mm, 85cddb8a5cSAndrea Arcangeli old_start, old_end); 861da177e4SLinus Torvalds if (vma->vm_file) { 871da177e4SLinus Torvalds /* 881da177e4SLinus Torvalds * Subtle point from Rajesh Venkatasubramanian: before 8925d9e2d1Snpiggin@suse.de * moving file-based ptes, we must lock truncate_pagecache 9025d9e2d1Snpiggin@suse.de * out, since it might clean the dst vma before the src vma, 911da177e4SLinus Torvalds * and we propagate stale pages into the dst afterward. 921da177e4SLinus Torvalds */ 931da177e4SLinus Torvalds mapping = vma->vm_file->f_mapping; 941da177e4SLinus Torvalds spin_lock(&mapping->i_mmap_lock); 951da177e4SLinus Torvalds if (new_vma->vm_truncate_count && 961da177e4SLinus Torvalds new_vma->vm_truncate_count != vma->vm_truncate_count) 971da177e4SLinus Torvalds new_vma->vm_truncate_count = 0; 981da177e4SLinus Torvalds } 991da177e4SLinus Torvalds 1004c21e2f2SHugh Dickins /* 1014c21e2f2SHugh Dickins * We don't have to worry about the ordering of src and dst 1024c21e2f2SHugh Dickins * pte locks because exclusive mmap_sem prevents deadlock. 1034c21e2f2SHugh Dickins */ 104c74df32cSHugh Dickins old_pte = pte_offset_map_lock(mm, old_pmd, old_addr, &old_ptl); 1057be7a546SHugh Dickins new_pte = pte_offset_map_nested(new_pmd, new_addr); 1064c21e2f2SHugh Dickins new_ptl = pte_lockptr(mm, new_pmd); 1074c21e2f2SHugh Dickins if (new_ptl != old_ptl) 108f20dc5f7SIngo Molnar spin_lock_nested(new_ptl, SINGLE_DEPTH_NESTING); 1096606c3e0SZachary Amsden arch_enter_lazy_mmu_mode(); 1108b1f3124SNick Piggin 1117be7a546SHugh Dickins for (; old_addr < old_end; old_pte++, old_addr += PAGE_SIZE, 1127be7a546SHugh Dickins new_pte++, new_addr += PAGE_SIZE) { 1137be7a546SHugh Dickins if (pte_none(*old_pte)) 1147be7a546SHugh Dickins continue; 1157be7a546SHugh Dickins pte = ptep_clear_flush(vma, old_addr, old_pte); 1167be7a546SHugh Dickins pte = move_pte(pte, new_vma->vm_page_prot, old_addr, new_addr); 1177be7a546SHugh Dickins set_pte_at(mm, new_addr, new_pte, pte); 1181da177e4SLinus Torvalds } 1197be7a546SHugh Dickins 1206606c3e0SZachary Amsden arch_leave_lazy_mmu_mode(); 1214c21e2f2SHugh Dickins if (new_ptl != old_ptl) 1224c21e2f2SHugh Dickins spin_unlock(new_ptl); 1237be7a546SHugh Dickins pte_unmap_nested(new_pte - 1); 124c74df32cSHugh Dickins pte_unmap_unlock(old_pte - 1, old_ptl); 1251da177e4SLinus Torvalds if (mapping) 1261da177e4SLinus Torvalds spin_unlock(&mapping->i_mmap_lock); 127cddb8a5cSAndrea Arcangeli mmu_notifier_invalidate_range_end(vma->vm_mm, old_start, old_end); 1281da177e4SLinus Torvalds } 1291da177e4SLinus Torvalds 1307be7a546SHugh Dickins #define LATENCY_LIMIT (64 * PAGE_SIZE) 1317be7a546SHugh Dickins 132b6a2fea3SOllie Wild unsigned long move_page_tables(struct vm_area_struct *vma, 1331da177e4SLinus Torvalds unsigned long old_addr, struct vm_area_struct *new_vma, 1341da177e4SLinus Torvalds unsigned long new_addr, unsigned long len) 1351da177e4SLinus Torvalds { 1367be7a546SHugh Dickins unsigned long extent, next, old_end; 1377be7a546SHugh Dickins pmd_t *old_pmd, *new_pmd; 1381da177e4SLinus Torvalds 1397be7a546SHugh Dickins old_end = old_addr + len; 1407be7a546SHugh Dickins flush_cache_range(vma, old_addr, old_end); 1411da177e4SLinus Torvalds 1427be7a546SHugh Dickins for (; old_addr < old_end; old_addr += extent, new_addr += extent) { 1431da177e4SLinus Torvalds cond_resched(); 1447be7a546SHugh Dickins next = (old_addr + PMD_SIZE) & PMD_MASK; 1457be7a546SHugh Dickins if (next - 1 > old_end) 1467be7a546SHugh Dickins next = old_end; 1477be7a546SHugh Dickins extent = next - old_addr; 1487be7a546SHugh Dickins old_pmd = get_old_pmd(vma->vm_mm, old_addr); 1497be7a546SHugh Dickins if (!old_pmd) 1507be7a546SHugh Dickins continue; 1517be7a546SHugh Dickins new_pmd = alloc_new_pmd(vma->vm_mm, new_addr); 1527be7a546SHugh Dickins if (!new_pmd) 1537be7a546SHugh Dickins break; 1547be7a546SHugh Dickins next = (new_addr + PMD_SIZE) & PMD_MASK; 1557be7a546SHugh Dickins if (extent > next - new_addr) 1567be7a546SHugh Dickins extent = next - new_addr; 1577be7a546SHugh Dickins if (extent > LATENCY_LIMIT) 1587be7a546SHugh Dickins extent = LATENCY_LIMIT; 1597be7a546SHugh Dickins move_ptes(vma, old_pmd, old_addr, old_addr + extent, 1607be7a546SHugh Dickins new_vma, new_pmd, new_addr); 1611da177e4SLinus Torvalds } 1627be7a546SHugh Dickins 1637be7a546SHugh Dickins return len + old_addr - old_end; /* how much done */ 1641da177e4SLinus Torvalds } 1651da177e4SLinus Torvalds 1661da177e4SLinus Torvalds static unsigned long move_vma(struct vm_area_struct *vma, 1671da177e4SLinus Torvalds unsigned long old_addr, unsigned long old_len, 1681da177e4SLinus Torvalds unsigned long new_len, unsigned long new_addr) 1691da177e4SLinus Torvalds { 1701da177e4SLinus Torvalds struct mm_struct *mm = vma->vm_mm; 1711da177e4SLinus Torvalds struct vm_area_struct *new_vma; 1721da177e4SLinus Torvalds unsigned long vm_flags = vma->vm_flags; 1731da177e4SLinus Torvalds unsigned long new_pgoff; 1741da177e4SLinus Torvalds unsigned long moved_len; 1751da177e4SLinus Torvalds unsigned long excess = 0; 176365e9c87SHugh Dickins unsigned long hiwater_vm; 1771da177e4SLinus Torvalds int split = 0; 1787103ad32SHugh Dickins int err; 1791da177e4SLinus Torvalds 1801da177e4SLinus Torvalds /* 1811da177e4SLinus Torvalds * We'd prefer to avoid failure later on in do_munmap: 1821da177e4SLinus Torvalds * which may split one vma into three before unmapping. 1831da177e4SLinus Torvalds */ 1841da177e4SLinus Torvalds if (mm->map_count >= sysctl_max_map_count - 3) 1851da177e4SLinus Torvalds return -ENOMEM; 1861da177e4SLinus Torvalds 1871ff82995SHugh Dickins /* 1881ff82995SHugh Dickins * Advise KSM to break any KSM pages in the area to be moved: 1891ff82995SHugh Dickins * it would be confusing if they were to turn up at the new 1901ff82995SHugh Dickins * location, where they happen to coincide with different KSM 1911ff82995SHugh Dickins * pages recently unmapped. But leave vma->vm_flags as it was, 1921ff82995SHugh Dickins * so KSM can come around to merge on vma and new_vma afterwards. 1931ff82995SHugh Dickins */ 1947103ad32SHugh Dickins err = ksm_madvise(vma, old_addr, old_addr + old_len, 1957103ad32SHugh Dickins MADV_UNMERGEABLE, &vm_flags); 1967103ad32SHugh Dickins if (err) 1977103ad32SHugh Dickins return err; 1981ff82995SHugh Dickins 1991da177e4SLinus Torvalds new_pgoff = vma->vm_pgoff + ((old_addr - vma->vm_start) >> PAGE_SHIFT); 2001da177e4SLinus Torvalds new_vma = copy_vma(&vma, new_addr, new_len, new_pgoff); 2011da177e4SLinus Torvalds if (!new_vma) 2021da177e4SLinus Torvalds return -ENOMEM; 2031da177e4SLinus Torvalds 2041da177e4SLinus Torvalds moved_len = move_page_tables(vma, old_addr, new_vma, new_addr, old_len); 2051da177e4SLinus Torvalds if (moved_len < old_len) { 2061da177e4SLinus Torvalds /* 2071da177e4SLinus Torvalds * On error, move entries back from new area to old, 2081da177e4SLinus Torvalds * which will succeed since page tables still there, 2091da177e4SLinus Torvalds * and then proceed to unmap new area instead of old. 2101da177e4SLinus Torvalds */ 2111da177e4SLinus Torvalds move_page_tables(new_vma, new_addr, vma, old_addr, moved_len); 2121da177e4SLinus Torvalds vma = new_vma; 2131da177e4SLinus Torvalds old_len = new_len; 2141da177e4SLinus Torvalds old_addr = new_addr; 2151da177e4SLinus Torvalds new_addr = -ENOMEM; 2161da177e4SLinus Torvalds } 2171da177e4SLinus Torvalds 2181da177e4SLinus Torvalds /* Conceal VM_ACCOUNT so old reservation is not undone */ 2191da177e4SLinus Torvalds if (vm_flags & VM_ACCOUNT) { 2201da177e4SLinus Torvalds vma->vm_flags &= ~VM_ACCOUNT; 2211da177e4SLinus Torvalds excess = vma->vm_end - vma->vm_start - old_len; 2221da177e4SLinus Torvalds if (old_addr > vma->vm_start && 2231da177e4SLinus Torvalds old_addr + old_len < vma->vm_end) 2241da177e4SLinus Torvalds split = 1; 2251da177e4SLinus Torvalds } 2261da177e4SLinus Torvalds 22771799062SKirill Korotaev /* 228365e9c87SHugh Dickins * If we failed to move page tables we still do total_vm increment 229365e9c87SHugh Dickins * since do_munmap() will decrement it by old_len == new_len. 230365e9c87SHugh Dickins * 231365e9c87SHugh Dickins * Since total_vm is about to be raised artificially high for a 232365e9c87SHugh Dickins * moment, we need to restore high watermark afterwards: if stats 233365e9c87SHugh Dickins * are taken meanwhile, total_vm and hiwater_vm appear too high. 234365e9c87SHugh Dickins * If this were a serious issue, we'd add a flag to do_munmap(). 23571799062SKirill Korotaev */ 236365e9c87SHugh Dickins hiwater_vm = mm->hiwater_vm; 23771799062SKirill Korotaev mm->total_vm += new_len >> PAGE_SHIFT; 238ab50b8edSHugh Dickins vm_stat_account(mm, vma->vm_flags, vma->vm_file, new_len>>PAGE_SHIFT); 23971799062SKirill Korotaev 2401da177e4SLinus Torvalds if (do_munmap(mm, old_addr, old_len) < 0) { 2411da177e4SLinus Torvalds /* OOM: unable to split vma, just get accounts right */ 2421da177e4SLinus Torvalds vm_unacct_memory(excess >> PAGE_SHIFT); 2431da177e4SLinus Torvalds excess = 0; 2441da177e4SLinus Torvalds } 245365e9c87SHugh Dickins mm->hiwater_vm = hiwater_vm; 2461da177e4SLinus Torvalds 2471da177e4SLinus Torvalds /* Restore VM_ACCOUNT if one or two pieces of vma left */ 2481da177e4SLinus Torvalds if (excess) { 2491da177e4SLinus Torvalds vma->vm_flags |= VM_ACCOUNT; 2501da177e4SLinus Torvalds if (split) 2511da177e4SLinus Torvalds vma->vm_next->vm_flags |= VM_ACCOUNT; 2521da177e4SLinus Torvalds } 2531da177e4SLinus Torvalds 2541da177e4SLinus Torvalds if (vm_flags & VM_LOCKED) { 2551da177e4SLinus Torvalds mm->locked_vm += new_len >> PAGE_SHIFT; 2561da177e4SLinus Torvalds if (new_len > old_len) 257ba470de4SRik van Riel mlock_vma_pages_range(new_vma, new_addr + old_len, 2581da177e4SLinus Torvalds new_addr + new_len); 2591da177e4SLinus Torvalds } 2601da177e4SLinus Torvalds 2611da177e4SLinus Torvalds return new_addr; 2621da177e4SLinus Torvalds } 2631da177e4SLinus Torvalds 26454f5de70SAl Viro static struct vm_area_struct *vma_to_resize(unsigned long addr, 26554f5de70SAl Viro unsigned long old_len, unsigned long new_len, unsigned long *p) 26654f5de70SAl Viro { 26754f5de70SAl Viro struct mm_struct *mm = current->mm; 26854f5de70SAl Viro struct vm_area_struct *vma = find_vma(mm, addr); 26954f5de70SAl Viro 27054f5de70SAl Viro if (!vma || vma->vm_start > addr) 27154f5de70SAl Viro goto Efault; 27254f5de70SAl Viro 27354f5de70SAl Viro if (is_vm_hugetlb_page(vma)) 27454f5de70SAl Viro goto Einval; 27554f5de70SAl Viro 27654f5de70SAl Viro /* We can't remap across vm area boundaries */ 27754f5de70SAl Viro if (old_len > vma->vm_end - addr) 27854f5de70SAl Viro goto Efault; 27954f5de70SAl Viro 28054f5de70SAl Viro if (vma->vm_flags & (VM_DONTEXPAND | VM_PFNMAP)) { 28154f5de70SAl Viro if (new_len > old_len) 28254f5de70SAl Viro goto Efault; 28354f5de70SAl Viro } 28454f5de70SAl Viro 28554f5de70SAl Viro if (vma->vm_flags & VM_LOCKED) { 28654f5de70SAl Viro unsigned long locked, lock_limit; 28754f5de70SAl Viro locked = mm->locked_vm << PAGE_SHIFT; 28859e99e5bSJiri Slaby lock_limit = rlimit(RLIMIT_MEMLOCK); 28954f5de70SAl Viro locked += new_len - old_len; 29054f5de70SAl Viro if (locked > lock_limit && !capable(CAP_IPC_LOCK)) 29154f5de70SAl Viro goto Eagain; 29254f5de70SAl Viro } 29354f5de70SAl Viro 29454f5de70SAl Viro if (!may_expand_vm(mm, (new_len - old_len) >> PAGE_SHIFT)) 29554f5de70SAl Viro goto Enomem; 29654f5de70SAl Viro 29754f5de70SAl Viro if (vma->vm_flags & VM_ACCOUNT) { 29854f5de70SAl Viro unsigned long charged = (new_len - old_len) >> PAGE_SHIFT; 29954f5de70SAl Viro if (security_vm_enough_memory(charged)) 30054f5de70SAl Viro goto Efault; 30154f5de70SAl Viro *p = charged; 30254f5de70SAl Viro } 30354f5de70SAl Viro 30454f5de70SAl Viro return vma; 30554f5de70SAl Viro 30654f5de70SAl Viro Efault: /* very odd choice for most of the cases, but... */ 30754f5de70SAl Viro return ERR_PTR(-EFAULT); 30854f5de70SAl Viro Einval: 30954f5de70SAl Viro return ERR_PTR(-EINVAL); 31054f5de70SAl Viro Enomem: 31154f5de70SAl Viro return ERR_PTR(-ENOMEM); 31254f5de70SAl Viro Eagain: 31354f5de70SAl Viro return ERR_PTR(-EAGAIN); 31454f5de70SAl Viro } 31554f5de70SAl Viro 316ecc1a899SAl Viro static unsigned long mremap_to(unsigned long addr, 317ecc1a899SAl Viro unsigned long old_len, unsigned long new_addr, 318ecc1a899SAl Viro unsigned long new_len) 319ecc1a899SAl Viro { 320ecc1a899SAl Viro struct mm_struct *mm = current->mm; 321ecc1a899SAl Viro struct vm_area_struct *vma; 322ecc1a899SAl Viro unsigned long ret = -EINVAL; 323ecc1a899SAl Viro unsigned long charged = 0; 324097eed10SAl Viro unsigned long map_flags; 325ecc1a899SAl Viro 326ecc1a899SAl Viro if (new_addr & ~PAGE_MASK) 327ecc1a899SAl Viro goto out; 328ecc1a899SAl Viro 329ecc1a899SAl Viro if (new_len > TASK_SIZE || new_addr > TASK_SIZE - new_len) 330ecc1a899SAl Viro goto out; 331ecc1a899SAl Viro 332ecc1a899SAl Viro /* Check if the location we're moving into overlaps the 333ecc1a899SAl Viro * old location at all, and fail if it does. 334ecc1a899SAl Viro */ 335ecc1a899SAl Viro if ((new_addr <= addr) && (new_addr+new_len) > addr) 336ecc1a899SAl Viro goto out; 337ecc1a899SAl Viro 338ecc1a899SAl Viro if ((addr <= new_addr) && (addr+old_len) > new_addr) 339ecc1a899SAl Viro goto out; 340ecc1a899SAl Viro 341ecc1a899SAl Viro ret = security_file_mmap(NULL, 0, 0, 0, new_addr, 1); 342ecc1a899SAl Viro if (ret) 343ecc1a899SAl Viro goto out; 344ecc1a899SAl Viro 345ecc1a899SAl Viro ret = do_munmap(mm, new_addr, new_len); 346ecc1a899SAl Viro if (ret) 347ecc1a899SAl Viro goto out; 348ecc1a899SAl Viro 349ecc1a899SAl Viro if (old_len >= new_len) { 350ecc1a899SAl Viro ret = do_munmap(mm, addr+new_len, old_len - new_len); 351ecc1a899SAl Viro if (ret && old_len != new_len) 352ecc1a899SAl Viro goto out; 353ecc1a899SAl Viro old_len = new_len; 354ecc1a899SAl Viro } 355ecc1a899SAl Viro 356ecc1a899SAl Viro vma = vma_to_resize(addr, old_len, new_len, &charged); 357ecc1a899SAl Viro if (IS_ERR(vma)) { 358ecc1a899SAl Viro ret = PTR_ERR(vma); 359ecc1a899SAl Viro goto out; 360ecc1a899SAl Viro } 361ecc1a899SAl Viro 362097eed10SAl Viro map_flags = MAP_FIXED; 363097eed10SAl Viro if (vma->vm_flags & VM_MAYSHARE) 364097eed10SAl Viro map_flags |= MAP_SHARED; 3659206de95SAl Viro 366097eed10SAl Viro ret = get_unmapped_area(vma->vm_file, new_addr, new_len, vma->vm_pgoff + 367097eed10SAl Viro ((addr - vma->vm_start) >> PAGE_SHIFT), 368097eed10SAl Viro map_flags); 369ecc1a899SAl Viro if (ret & ~PAGE_MASK) 370097eed10SAl Viro goto out1; 371097eed10SAl Viro 372097eed10SAl Viro ret = move_vma(vma, addr, old_len, new_len, new_addr); 373097eed10SAl Viro if (!(ret & ~PAGE_MASK)) 374097eed10SAl Viro goto out; 375097eed10SAl Viro out1: 376ecc1a899SAl Viro vm_unacct_memory(charged); 377ecc1a899SAl Viro 378ecc1a899SAl Viro out: 379ecc1a899SAl Viro return ret; 380ecc1a899SAl Viro } 381ecc1a899SAl Viro 3821a0ef85fSAl Viro static int vma_expandable(struct vm_area_struct *vma, unsigned long delta) 3831a0ef85fSAl Viro { 384f106af4eSAl Viro unsigned long end = vma->vm_end + delta; 3859206de95SAl Viro if (end < vma->vm_end) /* overflow */ 3861a0ef85fSAl Viro return 0; 3879206de95SAl Viro if (vma->vm_next && vma->vm_next->vm_start < end) /* intersection */ 388f106af4eSAl Viro return 0; 389f106af4eSAl Viro if (get_unmapped_area(NULL, vma->vm_start, end - vma->vm_start, 390f106af4eSAl Viro 0, MAP_FIXED) & ~PAGE_MASK) 391f106af4eSAl Viro return 0; 3921a0ef85fSAl Viro return 1; 3931a0ef85fSAl Viro } 3941a0ef85fSAl Viro 3951da177e4SLinus Torvalds /* 3961da177e4SLinus Torvalds * Expand (or shrink) an existing mapping, potentially moving it at the 3971da177e4SLinus Torvalds * same time (controlled by the MREMAP_MAYMOVE flag and available VM space) 3981da177e4SLinus Torvalds * 3991da177e4SLinus Torvalds * MREMAP_FIXED option added 5-Dec-1999 by Benjamin LaHaise 4001da177e4SLinus Torvalds * This option implies MREMAP_MAYMOVE. 4011da177e4SLinus Torvalds */ 4021da177e4SLinus Torvalds unsigned long do_mremap(unsigned long addr, 4031da177e4SLinus Torvalds unsigned long old_len, unsigned long new_len, 4041da177e4SLinus Torvalds unsigned long flags, unsigned long new_addr) 4051da177e4SLinus Torvalds { 406d0de32d9SHugh Dickins struct mm_struct *mm = current->mm; 4071da177e4SLinus Torvalds struct vm_area_struct *vma; 4081da177e4SLinus Torvalds unsigned long ret = -EINVAL; 4091da177e4SLinus Torvalds unsigned long charged = 0; 4101da177e4SLinus Torvalds 4111da177e4SLinus Torvalds if (flags & ~(MREMAP_FIXED | MREMAP_MAYMOVE)) 4121da177e4SLinus Torvalds goto out; 4131da177e4SLinus Torvalds 4141da177e4SLinus Torvalds if (addr & ~PAGE_MASK) 4151da177e4SLinus Torvalds goto out; 4161da177e4SLinus Torvalds 4171da177e4SLinus Torvalds old_len = PAGE_ALIGN(old_len); 4181da177e4SLinus Torvalds new_len = PAGE_ALIGN(new_len); 4191da177e4SLinus Torvalds 4201da177e4SLinus Torvalds /* 4211da177e4SLinus Torvalds * We allow a zero old-len as a special case 4221da177e4SLinus Torvalds * for DOS-emu "duplicate shm area" thing. But 4231da177e4SLinus Torvalds * a zero new-len is nonsensical. 4241da177e4SLinus Torvalds */ 4251da177e4SLinus Torvalds if (!new_len) 4261da177e4SLinus Torvalds goto out; 4271da177e4SLinus Torvalds 4281da177e4SLinus Torvalds if (flags & MREMAP_FIXED) { 429ecc1a899SAl Viro if (flags & MREMAP_MAYMOVE) 430ecc1a899SAl Viro ret = mremap_to(addr, old_len, new_addr, new_len); 4311da177e4SLinus Torvalds goto out; 4321da177e4SLinus Torvalds } 4331da177e4SLinus Torvalds 4341da177e4SLinus Torvalds /* 4351da177e4SLinus Torvalds * Always allow a shrinking remap: that just unmaps 4361da177e4SLinus Torvalds * the unnecessary pages.. 4371da177e4SLinus Torvalds * do_munmap does all the needed commit accounting 4381da177e4SLinus Torvalds */ 4391da177e4SLinus Torvalds if (old_len >= new_len) { 440d0de32d9SHugh Dickins ret = do_munmap(mm, addr+new_len, old_len - new_len); 4411da177e4SLinus Torvalds if (ret && old_len != new_len) 4421da177e4SLinus Torvalds goto out; 4431da177e4SLinus Torvalds ret = addr; 4441da177e4SLinus Torvalds goto out; 4451da177e4SLinus Torvalds } 4461da177e4SLinus Torvalds 4471da177e4SLinus Torvalds /* 448ecc1a899SAl Viro * Ok, we need to grow.. 4491da177e4SLinus Torvalds */ 45054f5de70SAl Viro vma = vma_to_resize(addr, old_len, new_len, &charged); 45154f5de70SAl Viro if (IS_ERR(vma)) { 45254f5de70SAl Viro ret = PTR_ERR(vma); 4531da177e4SLinus Torvalds goto out; 4541da177e4SLinus Torvalds } 4551da177e4SLinus Torvalds 4561da177e4SLinus Torvalds /* old_len exactly to the end of the area.. 4571da177e4SLinus Torvalds */ 458ecc1a899SAl Viro if (old_len == vma->vm_end - addr) { 4591da177e4SLinus Torvalds /* can we just expand the current mapping? */ 4601a0ef85fSAl Viro if (vma_expandable(vma, new_len - old_len)) { 4611da177e4SLinus Torvalds int pages = (new_len - old_len) >> PAGE_SHIFT; 4621da177e4SLinus Torvalds 463*5beb4930SRik van Riel if (vma_adjust(vma, vma->vm_start, addr + new_len, 464*5beb4930SRik van Riel vma->vm_pgoff, NULL)) { 465*5beb4930SRik van Riel ret = -ENOMEM; 466*5beb4930SRik van Riel goto out; 467*5beb4930SRik van Riel } 4681da177e4SLinus Torvalds 469d0de32d9SHugh Dickins mm->total_vm += pages; 470d0de32d9SHugh Dickins vm_stat_account(mm, vma->vm_flags, vma->vm_file, pages); 4711da177e4SLinus Torvalds if (vma->vm_flags & VM_LOCKED) { 472d0de32d9SHugh Dickins mm->locked_vm += pages; 473ba470de4SRik van Riel mlock_vma_pages_range(vma, addr + old_len, 4741da177e4SLinus Torvalds addr + new_len); 4751da177e4SLinus Torvalds } 4761da177e4SLinus Torvalds ret = addr; 4771da177e4SLinus Torvalds goto out; 4781da177e4SLinus Torvalds } 4791da177e4SLinus Torvalds } 4801da177e4SLinus Torvalds 4811da177e4SLinus Torvalds /* 4821da177e4SLinus Torvalds * We weren't able to just expand or shrink the area, 4831da177e4SLinus Torvalds * we need to create a new one and move it.. 4841da177e4SLinus Torvalds */ 4851da177e4SLinus Torvalds ret = -ENOMEM; 4861da177e4SLinus Torvalds if (flags & MREMAP_MAYMOVE) { 4871da177e4SLinus Torvalds unsigned long map_flags = 0; 4881da177e4SLinus Torvalds if (vma->vm_flags & VM_MAYSHARE) 4891da177e4SLinus Torvalds map_flags |= MAP_SHARED; 4901da177e4SLinus Torvalds 4911da177e4SLinus Torvalds new_addr = get_unmapped_area(vma->vm_file, 0, new_len, 49293587414SAl Viro vma->vm_pgoff + 49393587414SAl Viro ((addr - vma->vm_start) >> PAGE_SHIFT), 49493587414SAl Viro map_flags); 495ed032189SEric Paris if (new_addr & ~PAGE_MASK) { 4961da177e4SLinus Torvalds ret = new_addr; 497ed032189SEric Paris goto out; 498ed032189SEric Paris } 499ed032189SEric Paris 500c80544dcSStephen Hemminger ret = security_file_mmap(NULL, 0, 0, 0, new_addr, 1); 501ed032189SEric Paris if (ret) 5021da177e4SLinus Torvalds goto out; 5031da177e4SLinus Torvalds ret = move_vma(vma, addr, old_len, new_len, new_addr); 5041da177e4SLinus Torvalds } 5051da177e4SLinus Torvalds out: 5061da177e4SLinus Torvalds if (ret & ~PAGE_MASK) 5071da177e4SLinus Torvalds vm_unacct_memory(charged); 5081da177e4SLinus Torvalds return ret; 5091da177e4SLinus Torvalds } 5101da177e4SLinus Torvalds 5116a6160a7SHeiko Carstens SYSCALL_DEFINE5(mremap, unsigned long, addr, unsigned long, old_len, 5126a6160a7SHeiko Carstens unsigned long, new_len, unsigned long, flags, 5136a6160a7SHeiko Carstens unsigned long, new_addr) 5141da177e4SLinus Torvalds { 5151da177e4SLinus Torvalds unsigned long ret; 5161da177e4SLinus Torvalds 5171da177e4SLinus Torvalds down_write(¤t->mm->mmap_sem); 5181da177e4SLinus Torvalds ret = do_mremap(addr, old_len, new_len, flags, new_addr); 5191da177e4SLinus Torvalds up_write(¤t->mm->mmap_sem); 5201da177e4SLinus Torvalds return ret; 5211da177e4SLinus Torvalds } 522