diff options
Diffstat (limited to 'fs/proc/task_mmu.c')
| -rw-r--r-- | fs/proc/task_mmu.c | 85 | 
1 files changed, 37 insertions, 48 deletions
diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c index 2d45889931f..caf0337dff7 100644 --- a/fs/proc/task_mmu.c +++ b/fs/proc/task_mmu.c @@ -407,6 +407,7 @@ static int show_smap(struct seq_file *m, void *v)  	memset(&mss, 0, sizeof mss);  	mss.vma = vma; +	/* mmap_sem is held in m_start */  	if (vma->vm_mm && !is_vm_hugetlb_page(vma))  		walk_page_range(vma->vm_start, vma->vm_end, &smaps_walk); @@ -553,7 +554,8 @@ const struct file_operations proc_clear_refs_operations = {  };  struct pagemapread { -	u64 __user *out, *end; +	int pos, len; +	u64 *buffer;  };  #define PM_ENTRY_BYTES      sizeof(u64) @@ -576,10 +578,8 @@ struct pagemapread {  static int add_to_pagemap(unsigned long addr, u64 pfn,  			  struct pagemapread *pm)  { -	if (put_user(pfn, pm->out)) -		return -EFAULT; -	pm->out++; -	if (pm->out >= pm->end) +	pm->buffer[pm->pos++] = pfn; +	if (pm->pos >= pm->len)  		return PM_END_OF_BUFFER;  	return 0;  } @@ -721,21 +721,20 @@ static int pagemap_hugetlb_range(pte_t *pte, unsigned long addr,   * determine which areas of memory are actually mapped and llseek to   * skip over unmapped regions.   */ +#define PAGEMAP_WALK_SIZE	(PMD_SIZE)  static ssize_t pagemap_read(struct file *file, char __user *buf,  			    size_t count, loff_t *ppos)  {  	struct task_struct *task = get_proc_task(file->f_path.dentry->d_inode); -	struct page **pages, *page; -	unsigned long uaddr, uend;  	struct mm_struct *mm;  	struct pagemapread pm; -	int pagecount;  	int ret = -ESRCH;  	struct mm_walk pagemap_walk = {};  	unsigned long src;  	unsigned long svpfn;  	unsigned long start_vaddr;  	unsigned long end_vaddr; +	int copied = 0;  	if (!task)  		goto out; @@ -758,35 +757,12 @@ static ssize_t pagemap_read(struct file *file, char __user *buf,  	if (!mm)  		goto out_task; - -	uaddr = (unsigned long)buf & PAGE_MASK; -	uend = (unsigned long)(buf + count); -	pagecount = (PAGE_ALIGN(uend) - uaddr) / PAGE_SIZE; -	ret = 0; -	if (pagecount == 0) -		goto out_mm; -	pages = kcalloc(pagecount, sizeof(struct page *), GFP_KERNEL); +	pm.len = PM_ENTRY_BYTES * (PAGEMAP_WALK_SIZE >> PAGE_SHIFT); +	pm.buffer = kmalloc(pm.len, GFP_TEMPORARY);  	ret = -ENOMEM; -	if (!pages) +	if (!pm.buffer)  		goto out_mm; -	down_read(¤t->mm->mmap_sem); -	ret = get_user_pages(current, current->mm, uaddr, pagecount, -			     1, 0, pages, NULL); -	up_read(¤t->mm->mmap_sem); - -	if (ret < 0) -		goto out_free; - -	if (ret != pagecount) { -		pagecount = ret; -		ret = -EFAULT; -		goto out_pages; -	} - -	pm.out = (u64 __user *)buf; -	pm.end = (u64 __user *)(buf + count); -  	pagemap_walk.pmd_entry = pagemap_pte_range;  	pagemap_walk.pte_hole = pagemap_pte_hole;  	pagemap_walk.hugetlb_entry = pagemap_hugetlb_range; @@ -808,23 +784,36 @@ static ssize_t pagemap_read(struct file *file, char __user *buf,  	 * user buffer is tracked in "pm", and the walk  	 * will stop when we hit the end of the buffer.  	 */ -	ret = walk_page_range(start_vaddr, end_vaddr, &pagemap_walk); -	if (ret == PM_END_OF_BUFFER) -		ret = 0; -	/* don't need mmap_sem for these, but this looks cleaner */ -	*ppos += (char __user *)pm.out - buf; -	if (!ret) -		ret = (char __user *)pm.out - buf; +	ret = 0; +	while (count && (start_vaddr < end_vaddr)) { +		int len; +		unsigned long end; -out_pages: -	for (; pagecount; pagecount--) { -		page = pages[pagecount-1]; -		if (!PageReserved(page)) -			SetPageDirty(page); -		page_cache_release(page); +		pm.pos = 0; +		end = start_vaddr + PAGEMAP_WALK_SIZE; +		/* overflow ? */ +		if (end < start_vaddr || end > end_vaddr) +			end = end_vaddr; +		down_read(&mm->mmap_sem); +		ret = walk_page_range(start_vaddr, end, &pagemap_walk); +		up_read(&mm->mmap_sem); +		start_vaddr = end; + +		len = min(count, PM_ENTRY_BYTES * pm.pos); +		if (copy_to_user(buf, pm.buffer, len) < 0) { +			ret = -EFAULT; +			goto out_free; +		} +		copied += len; +		buf += len; +		count -= len;  	} +	*ppos += copied; +	if (!ret || ret == PM_END_OF_BUFFER) +		ret = copied; +  out_free: -	kfree(pages); +	kfree(pm.buffer);  out_mm:  	mmput(mm);  out_task:  |