]> Pileus Git - ~andy/linux/blobdiff - fs/proc/task_mmu.c
d_path: Make seq_path() use a struct path argument
[~andy/linux] / fs / proc / task_mmu.c
index 308fc5451e432bdc96b111000c719598454b3e46..49958cffbd8da91d4505a55ee5cbb9a353555c1b 100644 (file)
@@ -5,14 +5,18 @@
 #include <linux/highmem.h>
 #include <linux/ptrace.h>
 #include <linux/pagemap.h>
+#include <linux/ptrace.h>
 #include <linux/mempolicy.h>
+#include <linux/swap.h>
+#include <linux/swapops.h>
+#include <linux/seq_file.h>
 
 #include <asm/elf.h>
 #include <asm/uaccess.h>
 #include <asm/tlbflush.h>
 #include "internal.h"
 
-char *task_mem(struct mm_struct *mm, char *buffer)
+void task_mem(struct seq_file *m, struct mm_struct *mm)
 {
        unsigned long data, text, lib;
        unsigned long hiwater_vm, total_vm, hiwater_rss, total_rss;
@@ -34,7 +38,7 @@ char *task_mem(struct mm_struct *mm, char *buffer)
        data = mm->total_vm - mm->shared_vm - mm->stack_vm;
        text = (PAGE_ALIGN(mm->end_code) - (mm->start_code & PAGE_MASK)) >> 10;
        lib = (mm->exec_vm << (PAGE_SHIFT-10)) - text;
-       buffer += sprintf(buffer,
+       seq_printf(m,
                "VmPeak:\t%8lu kB\n"
                "VmSize:\t%8lu kB\n"
                "VmLck:\t%8lu kB\n"
@@ -53,7 +57,6 @@ char *task_mem(struct mm_struct *mm, char *buffer)
                data << (PAGE_SHIFT-10),
                mm->stack_vm << (PAGE_SHIFT-10), text, lib,
                (PTRS_PER_PTE*sizeof(pte_t)*mm->nr_ptes) >> 10);
-       return buffer;
 }
 
 unsigned long task_vsize(struct mm_struct *mm)
@@ -72,7 +75,7 @@ int task_statm(struct mm_struct *mm, int *shared, int *text,
        return mm->total_vm;
 }
 
-int proc_exe_link(struct inode *inode, struct dentry **dentry, struct vfsmount **mnt)
+int proc_exe_link(struct inode *inode, struct path *path)
 {
        struct vm_area_struct * vma;
        int result = -ENOENT;
@@ -95,8 +98,8 @@ int proc_exe_link(struct inode *inode, struct dentry **dentry, struct vfsmount *
        }
 
        if (vma) {
-               *mnt = mntget(vma->vm_file->f_path.mnt);
-               *dentry = dget(vma->vm_file->f_path.dentry);
+               *path = vma->vm_file->f_path;
+               path_get(&vma->vm_file->f_path);
                result = 0;
        }
 
@@ -213,7 +216,7 @@ static void m_stop(struct seq_file *m, void *v)
 }
 
 static int do_maps_open(struct inode *inode, struct file *file,
-                       struct seq_operations *ops)
+                       const struct seq_operations *ops)
 {
        struct proc_maps_private *priv;
        int ret = -ENOMEM;
@@ -268,7 +271,7 @@ static int show_map(struct seq_file *m, void *v)
         */
        if (file) {
                pad_len_spaces(m, len);
-               seq_path(m, file->f_path.mnt, file->f_path.dentry, "\n");
+               seq_path(m, &file->f_path, "\n");
        } else {
                const char *name = arch_vma_name(vma);
                if (!name) {
@@ -296,7 +299,7 @@ static int show_map(struct seq_file *m, void *v)
        return 0;
 }
 
-static struct seq_operations proc_pid_maps_op = {
+static const struct seq_operations proc_pid_maps_op = {
        .start  = m_start,
        .next   = m_next,
        .stop   = m_stop,
@@ -334,6 +337,7 @@ const struct file_operations proc_maps_operations = {
  */
 #define PSS_SHIFT 12
 
+#ifdef CONFIG_PROC_PAGE_MONITOR
 struct mem_size_stats
 {
        struct vm_area_struct *vma;
@@ -430,7 +434,7 @@ static int show_smap(struct seq_file *m, void *v)
        return ret;
 }
 
-static struct seq_operations proc_pid_smaps_op = {
+static const struct seq_operations proc_pid_smaps_op = {
        .start  = m_start,
        .next   = m_next,
        .stop   = m_stop,
@@ -519,6 +523,203 @@ const struct file_operations proc_clear_refs_operations = {
        .write          = clear_refs_write,
 };
 
+struct pagemapread {
+       char __user *out, *end;
+};
+
+#define PM_ENTRY_BYTES sizeof(u64)
+#define PM_RESERVED_BITS    3
+#define PM_RESERVED_OFFSET  (64 - PM_RESERVED_BITS)
+#define PM_RESERVED_MASK    (((1LL<<PM_RESERVED_BITS)-1) << PM_RESERVED_OFFSET)
+#define PM_SPECIAL(nr)      (((nr) << PM_RESERVED_OFFSET) | PM_RESERVED_MASK)
+#define PM_NOT_PRESENT      PM_SPECIAL(1LL)
+#define PM_SWAP             PM_SPECIAL(2LL)
+#define PM_END_OF_BUFFER    1
+
+static int add_to_pagemap(unsigned long addr, u64 pfn,
+                         struct pagemapread *pm)
+{
+       /*
+        * Make sure there's room in the buffer for an
+        * entire entry.  Otherwise, only copy part of
+        * the pfn.
+        */
+       if (pm->out + PM_ENTRY_BYTES >= pm->end) {
+               if (copy_to_user(pm->out, &pfn, pm->end - pm->out))
+                       return -EFAULT;
+               pm->out = pm->end;
+               return PM_END_OF_BUFFER;
+       }
+
+       if (put_user(pfn, pm->out))
+               return -EFAULT;
+       pm->out += PM_ENTRY_BYTES;
+       return 0;
+}
+
+static int pagemap_pte_hole(unsigned long start, unsigned long end,
+                               void *private)
+{
+       struct pagemapread *pm = private;
+       unsigned long addr;
+       int err = 0;
+       for (addr = start; addr < end; addr += PAGE_SIZE) {
+               err = add_to_pagemap(addr, PM_NOT_PRESENT, pm);
+               if (err)
+                       break;
+       }
+       return err;
+}
+
+u64 swap_pte_to_pagemap_entry(pte_t pte)
+{
+       swp_entry_t e = pte_to_swp_entry(pte);
+       return PM_SWAP | swp_type(e) | (swp_offset(e) << MAX_SWAPFILES_SHIFT);
+}
+
+static int pagemap_pte_range(pmd_t *pmd, unsigned long addr, unsigned long end,
+                            void *private)
+{
+       struct pagemapread *pm = private;
+       pte_t *pte;
+       int err = 0;
+
+       for (; addr != end; addr += PAGE_SIZE) {
+               u64 pfn = PM_NOT_PRESENT;
+               pte = pte_offset_map(pmd, addr);
+               if (is_swap_pte(*pte))
+                       pfn = swap_pte_to_pagemap_entry(*pte);
+               else if (pte_present(*pte))
+                       pfn = pte_pfn(*pte);
+               /* unmap so we're not in atomic when we copy to userspace */
+               pte_unmap(pte);
+               err = add_to_pagemap(addr, pfn, pm);
+               if (err)
+                       return err;
+       }
+
+       cond_resched();
+
+       return err;
+}
+
+static struct mm_walk pagemap_walk = {
+       .pmd_entry = pagemap_pte_range,
+       .pte_hole = pagemap_pte_hole
+};
+
+/*
+ * /proc/pid/pagemap - an array mapping virtual pages to pfns
+ *
+ * For each page in the address space, this file contains one 64-bit
+ * entry representing the corresponding physical page frame number
+ * (PFN) if the page is present. If there is a swap entry for the
+ * physical page, then an encoding of the swap file number and the
+ * page's offset into the swap file are returned. If no page is
+ * present at all, PM_NOT_PRESENT is returned. This allows determining
+ * precisely which pages are mapped (or in swap) and comparing mapped
+ * pages between processes.
+ *
+ * Efficient users of this interface will use /proc/pid/maps to
+ * determine which areas of memory are actually mapped and llseek to
+ * skip over unmapped regions.
+ */
+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;
+
+       if (!task)
+               goto out;
+
+       ret = -EACCES;
+       if (!ptrace_may_attach(task))
+               goto out;
+
+       ret = -EINVAL;
+       /* file position must be aligned */
+       if (*ppos % PM_ENTRY_BYTES)
+               goto out;
+
+       ret = 0;
+       mm = get_task_mm(task);
+       if (!mm)
+               goto out;
+
+       ret = -ENOMEM;
+       uaddr = (unsigned long)buf & PAGE_MASK;
+       uend = (unsigned long)(buf + count);
+       pagecount = (PAGE_ALIGN(uend) - uaddr) / PAGE_SIZE;
+       pages = kmalloc(pagecount * sizeof(struct page *), GFP_KERNEL);
+       if (!pages)
+               goto out_task;
+
+       down_read(&current->mm->mmap_sem);
+       ret = get_user_pages(current, current->mm, uaddr, pagecount,
+                            1, 0, pages, NULL);
+       up_read(&current->mm->mmap_sem);
+
+       if (ret < 0)
+               goto out_free;
+
+       pm.out = buf;
+       pm.end = buf + count;
+
+       if (!ptrace_may_attach(task)) {
+               ret = -EIO;
+       } else {
+               unsigned long src = *ppos;
+               unsigned long svpfn = src / PM_ENTRY_BYTES;
+               unsigned long start_vaddr = svpfn << PAGE_SHIFT;
+               unsigned long end_vaddr = TASK_SIZE_OF(task);
+
+               /* watch out for wraparound */
+               if (svpfn > TASK_SIZE_OF(task) >> PAGE_SHIFT)
+                       start_vaddr = end_vaddr;
+
+               /*
+                * The odds are that this will stop walking way
+                * before end_vaddr, because the length of the
+                * user buffer is tracked in "pm", and the walk
+                * will stop when we hit the end of the buffer.
+                */
+               ret = walk_page_range(mm, start_vaddr, end_vaddr,
+                                       &pagemap_walk, &pm);
+               if (ret == PM_END_OF_BUFFER)
+                       ret = 0;
+               /* don't need mmap_sem for these, but this looks cleaner */
+               *ppos += pm.out - buf;
+               if (!ret)
+                       ret = pm.out - buf;
+       }
+
+       for (; pagecount; pagecount--) {
+               page = pages[pagecount-1];
+               if (!PageReserved(page))
+                       SetPageDirty(page);
+               page_cache_release(page);
+       }
+       mmput(mm);
+out_free:
+       kfree(pages);
+out_task:
+       put_task_struct(task);
+out:
+       return ret;
+}
+
+const struct file_operations proc_pagemap_operations = {
+       .llseek         = mem_lseek, /* borrow this */
+       .read           = pagemap_read,
+};
+#endif /* CONFIG_PROC_PAGE_MONITOR */
+
 #ifdef CONFIG_NUMA
 extern int show_numa_map(struct seq_file *m, void *v);
 
@@ -533,7 +734,7 @@ static int show_numa_map_checked(struct seq_file *m, void *v)
        return show_numa_map(m, v);
 }
 
-static struct seq_operations proc_pid_numa_maps_op = {
+static const struct seq_operations proc_pid_numa_maps_op = {
         .start  = m_start,
         .next   = m_next,
         .stop   = m_stop,
@@ -552,4 +753,3 @@ const struct file_operations proc_numa_maps_operations = {
        .release        = seq_release_private,
 };
 #endif
-