/*
       * linux/kernel/ptrace.c
       *
       * (C) Copyright 1999 Linus Torvalds
       *
       * Common interfaces for "ptrace()" which we do not want
       * to continually duplicate across every architecture.
       */
      
      #include <linux/sched.h>
      #include <linux/errno.h>
      #include <linux/mm.h>
      #include <linux/highmem.h>
      #include <linux/smp_lock.h>
      
      #include <asm/pgtable.h>
      #include <asm/uaccess.h>
      
      /*
       * Access another process' address space, one page at a time.
       */
      static int access_one_page(struct mm_struct * mm, struct vm_area_struct * vma, unsigned long addr, void *buf, int len, int write)
      {
      	pgd_t * pgdir;
      	pmd_t * pgmiddle;
      	pte_t * pgtable;
      	char *maddr; 
      	struct page *page;
      
      repeat:
      	pgdir = pgd_offset(vma->vm_mm, addr);
      	if (pgd_none(*pgdir))
      		goto fault_in_page;
      	if (pgd_bad(*pgdir))
      		goto bad_pgd;
      	pgmiddle = pmd_offset(pgdir, addr);
      	if (pmd_none(*pgmiddle))
      		goto fault_in_page;
      	if (pmd_bad(*pgmiddle))
      		goto bad_pmd;
      	pgtable = pte_offset(pgmiddle, addr);
      	if (!pte_present(*pgtable))
      		goto fault_in_page;
      	if (write && (!pte_write(*pgtable) || !pte_dirty(*pgtable)))
      		goto fault_in_page;
      	page = pte_page(*pgtable);
  47  
      	/* ZERO_PAGE is special: reads from it are ok even though it's marked reserved */
      	if (page != ZERO_PAGE(addr) || write) {
      		if ((!VALID_PAGE(page)) || PageReserved(page))
      			return 0;
      	}
  53  	flush_cache_page(vma, addr);
      
      	if (write) {
      		maddr = kmap(page);
      		memcpy(maddr + (addr & ~PAGE_MASK), buf, len);
      		flush_page_to_ram(page);
      		flush_icache_page(vma, page);
      		kunmap(page);
      	} else {
  62  		maddr = kmap(page);
      		memcpy(buf, maddr + (addr & ~PAGE_MASK), len);
      		flush_page_to_ram(page);
      		kunmap(page);
      	}
      	return len;
      
      fault_in_page:
  70  	/* -1: out of memory. 0 - unmapped page */
      	if (handle_mm_fault(mm, vma, addr, write) > 0)
      		goto repeat;
  73  	return 0;
      
      bad_pgd:
  76  	pgd_ERROR(*pgdir);
  77  	return 0;
  78  
  79  bad_pmd:
      	pmd_ERROR(*pgmiddle);
  81  	return 0;
  82  }
  83  
  84  static int access_mm(struct mm_struct *mm, struct vm_area_struct * vma, unsigned long addr, void *buf, int len, int write)
      {
  86  	int copied = 0;
  87  
  88  	for (;;) {
  89  		unsigned long offset = addr & ~PAGE_MASK;
  90  		int this_len = PAGE_SIZE - offset;
      		int retval;
  92  
  93  		if (this_len > len)
  94  			this_len = len;
  95  		retval = access_one_page(mm, vma, addr, buf, this_len, write);
  96  		copied += retval;
      		if (retval != this_len)
  98  			break;
  99  
      		len -= retval;
      		if (!len)
 102  			break;
      
 104  		addr += retval;
      		buf += retval;
      
 107  		if (addr < vma->vm_end)
      			continue;	
      		if (!vma->vm_next)
 110  			break;
      		if (vma->vm_next->vm_start != vma->vm_end)
      			break;
      	
      		vma = vma->vm_next;
 115  	}
 116  	return copied;
      }
 118  
 119  int access_process_vm(struct task_struct *tsk, unsigned long addr, void *buf, int len, int write)
      {
 121  	int copied;
 122  	struct mm_struct *mm;
 123  	struct vm_area_struct * vma;
 124  
 125  	/* Worry about races with exit() */
      	task_lock(tsk);
      	mm = tsk->mm;
 128  	if (mm)
 129  		atomic_inc(&mm->mm_users);
      	task_unlock(tsk);
      	if (!mm)
      		return 0;
      
 134  	down(&mm->mmap_sem);
      	vma = find_extend_vma(mm, addr);
      	copied = 0;
 137  	if (vma)
      		copied = access_mm(mm, vma, addr, buf, len, write);
      
      	up(&mm->mmap_sem);
      	mmput(mm);
      	return copied;
 143  }
      
 145  int ptrace_readdata(struct task_struct *tsk, unsigned long src, char *dst, int len)
      {
 147  	int copied = 0;
 148  
      	while (len > 0) {
      		char buf[128];
      		int this_len, retval;
 152  
      		this_len = (len > sizeof(buf)) ? sizeof(buf) : len;
      		retval = access_process_vm(tsk, src, buf, this_len, 0);
      		if (!retval) {
      			if (copied)
 157  				break;
      			return -EIO;
 159  		}
 160  		if (copy_to_user(dst, buf, retval))
 161  			return -EFAULT;
      		copied += retval;
      		src += retval;
 164  		dst += retval;
 165  		len -= retval;			
      	}
 167  	return copied;
 168  }
 169  
      int ptrace_writedata(struct task_struct *tsk, char * src, unsigned long dst, int len)
      {
      	int copied = 0;
      
      	while (len > 0) {
      		char buf[128];
      		int this_len, retval;
 177  
 178  		this_len = (len > sizeof(buf)) ? sizeof(buf) : len;
      		if (copy_from_user(buf, src, this_len))
 180  			return -EFAULT;
 181  		retval = access_process_vm(tsk, dst, buf, this_len, 1);
      		if (!retval) {
      			if (copied)
 184  				break;
 185  			return -EIO;
 186  		}
      		copied += retval;
 188  		src += retval;
      		dst += retval;
 190  		len -= retval;			
      	}
      	return copied;
      }