/*
  This program is a little test to show how we can trap seg faults and
  control protections on pages of memory.  For more information, check
  the man pages for all the calls used
*/
#include <sys/mman.h>

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

#include <unistd.h>

/*
  The amount of space to be allocated will be VM-architecture
  specific; on Intel, this will end up being 16K (since pages are 4K)
*/
#define NUMPAGES 4

void crash(char mess[])
{
    perror(mess);
    exit(1);
}

/*
  The handler just changes the protections on the page that caused the
  fault.  When the code goes back and re-executes the faulting
  instruction, it will succeed.  You can comment out the call to
  mprotect() to watch it go into a hard loop at the faulting address
  without this
*/
void segvhandler(int signum, siginfo_t *info, void *extra)
{
    /*
      just to play a little bit with static variables:  I init it to
      0, but then it gets set to an actual value the first time the
      handler is called.  That way I don't have to keep making an
      extra system call every time the handler is invoked.

      Also, this handler would work just fine if it just made the byte
      that faulted readable -- this would end up making the whole page
      readable.  But most applications that actually care about
      messing with page protections will want to be able to manipulate
      the whole page, so this example shows how to map from an
      arbitrary byte on the page to the entire page.
    */
    static int pagesize = 0;
    static unsigned int pagemask = 0;
    
    printf("segv at %08x\n", info->si_addr);
    if (pagesize == 0) {
        pagesize = getpagesize();
        printf("pagesize = %d\n", pagesize);
        pagemask = 0xffffffff ^ (pagesize-1);
    }
    
    if (mprotect((void *)(((int)info->si_addr) & pagemask), pagesize, PROT_READ) != 0)
        crash("mprotect 2");

    return;
}

int main()
{
    char *buffer, temp;
    int i;
    struct sigaction act, oldact;

    int bufsize = getpagesize() * NUMPAGES;
    
    /*
      allocate the buffer and fill it with data corresponding to
      offset in the buffer
    */
    buffer = mmap(NULL, bufsize, PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
    if (buffer == NULL)
        crash("mmap");

    printf("buffer is at %08x\n", buffer);
    
    for (i = 0; i < bufsize; i++)
        buffer[i] = i & 0xff;

    /*
      mark the buffer inaccessible
    */
    if (mprotect(buffer, bufsize, PROT_NONE) !=  0)
        crash("mprotect");

    /*
      set up a signal handler for SEGV
    */
    act.sa_sigaction = segvhandler;
    
    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask, SIGSEGV);
    
    act.sa_flags = SA_SIGINFO;
    act.sa_restorer = NULL;

    if (sigaction(SIGSEGV, &act, &oldact) != 0)
        crash("sigaction");

    /*
      Now go through the whole array printing its contents.  I'm going
      through it backwards so I'll get a fault on the last byte of the
      page instead of the first.  You can also manipulate this order
      to see what happens.
    */
    for (i = bufsize-1; i != 0; i--) {
        printf("%5d:\n", i);
        temp = buffer[i];
        printf("      = %02x\n", temp & 0xff);
    }

    exit(0);
}

