#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>

#if UINTPTR_MAX == 0xffffffff
# define BITS32 1
#elif UINTPTR_MAX == 0xffffffffffffffff
# define BITS64 1
#else
# error "unknown bits"
#endif

// docs:
// http://www.kernel.org/doc/html/latest/admin-guide/mm/idle_page_tracking.html
// http://www.kernel.org/doc/html/latest/admin-guide/mm/soft-dirty.html
// http://www.kernel.org/doc/html/latest/admin-guide/mm/pagemap.html
// http://github.com/spotify/linux/blob/master/Documentation/vm/pagemap.txt

typedef struct _Page_Flag
{
   unsigned short swapped : 1;
   unsigned short soft_dirty : 1;
   unsigned short dirty : 1;
   unsigned short exclusive : 1;
   unsigned short shared : 1;
   unsigned short present : 1;
   unsigned short huge : 1;
   unsigned short zero : 1;
   unsigned short active : 1;
   unsigned short writeback : 1;
   unsigned short priv : 1;
   unsigned short swapbacked : 1;
   unsigned short invalid : 1;
} Page_Flag;

typedef struct _Map
{
   struct {
      void *from, *to;
   } ptr;
   char *file;
   off_t offset;
   unsigned char read : 1;
   unsigned char write : 1;
   unsigned char execute : 1;
   unsigned char shared : 1;
   Page_Flag *pageflags;
} Map;

static unsigned long pgsize = 4096;

#define PM_PFN_MASK   0x7fffffffffffffull
#define PM_SOFT_DIRTY (1ull << 55)
#define PM_EXCLUSIVE  (1ull << 56)
#define PM_SHARED     (1ull << 61)
#define PM_SWAPPED    (1ull << 62)
#define PM_PRESENT    (1ull << 63)

#define KPF_LOCKED         (1ull << 0)
#define KPF_ERROR          (1ull << 1)
#define KPF_REFERENCED     (1ull << 2)
#define KPF_UPTODATE       (1ull << 3)
#define KPF_DIRTY          (1ull << 4)
#define KPF_LRU            (1ull << 5)
#define KPF_ACTIVE         (1ull << 6)
#define KPF_SLAB           (1ull << 7)
#define KPF_WRITEBACK      (1ull << 8)
#define KPF_RECLAIM        (1ull << 9)
#define KPF_BUDDY          (1ull << 10)
#define KPF_MMAP           (1ull << 11)
#define KPF_ANON           (1ull << 12)
#define KPF_SWAPCACHE      (1ull << 13)
#define KPF_SWAPBACKED     (1ull << 14)
#define KPF_COMPOUND_HEAD  (1ull << 15)
#define KPF_COMPOUND_TAIL  (1ull << 16)
#define KPF_HUGE           (1ull << 17)
#define KPF_UNEVICTABLE    (1ull << 18)
#define KPF_HWPOISON       (1ull << 19)
#define KPF_NOPAGE         (1ull << 20)
#define KPF_KSM            (1ull << 21)
#define KPF_THP            (1ull << 22)
#define KPF_BALLOON        (1ull << 23)
#define KPF_ZERO_PAGE      (1ull << 24)
#define KPF_IDLE           (1ull << 25)
#define KPF_RESERVED       (1ull << 26)
#define KPF_MLOCKED        (1ull << 27)
#define KPF_MAPPEDTODISK   (1ull << 28)
#define KPF_PRIVATE        (1ull << 30)
#define KPF_PRIVATE_2      (1ull << 31)
#define KPF_OWNER_PRIVATE  (1ull << 32)
#define KPF_ARCH           (1ull << 33)
#define KPF_UNCACHED       (1ull << 34)
#define KPF_SOFTDIRTY      (1ull << 35)
#define KPF_READAHEAD      (1ull << 36)
#define KPF_SLOB_FREE      (1ull << 37)
#define KPF_SLUB_FROZEN    (1ull << 38)
#define KPF_SLUB_DEBUG     (1ull << 39)
#define KPF_FILE           (1ull << 40)
#define KPF_SWAP           (1ull << 41)
#define KPF_MMAP_EXCLUSIVE (1ull << 42)

const int __endian = 1;
#define is_bigendian() ( (*(char *)&__endian) == 0 )

static inline unsigned long long
swap64(unsigned long long x)
{
   return (((x & 0xff00000000000000ull) >> 56) |
           ((x & 0x00ff000000000000ull) >> 40) |
           ((x & 0x0000ff0000000000ull) >> 24) |
           ((x & 0x000000ff00000000ull) >> 8)  |
           ((x & 0x00000000ff000000ull) << 8)  |
           ((x & 0x0000000000ff0000ull) << 24) |
           ((x & 0x000000000000ff00ull) << 40) |
           ((x & 0x00000000000000ffull) << 56));
}

static inline unsigned long long
endian_fix(unsigned long long x)
{
   if (is_bigendian()) return swap64(x);
   return x;
}

static Map *
_pages_append_line(Map *map, unsigned int *count, void *ptr_from, void *ptr_to,
                   const char *perms, off_t offset, const char *file)
{
   Map *tmp, *m;

   // skip empty mappings
   if (!strcmp(perms, "---p")) return map;

   (*count)++;
   tmp = realloc(map, *count * sizeof(Map));
   if (!tmp) return map;
   map = tmp;

   // m ptr for end item
   m = map + ((*count) - 1);

   // fill m entry at end
   m->ptr.from = ptr_from;
   m->ptr.to = ptr_to;
   m->offset = offset;
   m->file = strdup(file);
   m->pageflags = NULL;
   if (perms[0] == 'r') m->read = 1;
   else m->read = 0;
   if (perms[1] == 'w') m->write = 1;
   else m->write = 0;
   if (perms[2] == 'x') m->execute = 1;
   else m->execute = 0;
   if (perms[4] == 's') m->shared = 1;
   else m->shared = 0;
   return map;
}

static void
_pages_walk_page_flags(Map *m, FILE *pagemap, FILE *kpageflags)
{
   unsigned long long off;
   unsigned long long mem_from, mem_to;
   unsigned long long val, val2, pfn;
   unsigned char *p;
   Page_Flag *pf;

   mem_from = (uintptr_t)(unsigned char *)m->ptr.from;
   mem_to = (uintptr_t)(unsigned char *)m->ptr.to;
   off = mem_from / pgsize;
   pf = m->pageflags = calloc((mem_to - mem_from) / pgsize, sizeof(Page_Flag));
   if (!m->pageflags)
     {
        fprintf(stderr, "ERR allocating pageflags\n");
        return;
     }
   if (fseek(pagemap, off * 8, SEEK_SET) != 0)
     {
        fprintf(stderr, "ERR seeking to pagemap start at %llu\n", (unsigned long long)(off * 8));
        return;
     }
   for (p = (unsigned char *)m->ptr.from;
        p < (unsigned char *)m->ptr.to;
        p += pgsize)
     {
        if (fread(&val, 8, 1, pagemap) != 1)
          {
             pf->invalid = 1;
             continue;
          }
//        val = endian_fix(val);
        pfn = val & PM_PFN_MASK;
        if (val & PM_PRESENT)
          {
             if (fseek(kpageflags, pfn * 8, SEEK_SET) != 0)
               {
                  fprintf(stderr, "ERR seeking pageflags start\n");
                  return;
               }
             if (fread(&val2, 8, 1, kpageflags) != 1)
               {
                  fprintf(stderr, "ERR reading kpageflags\n");
                  return;
               }
//             val2 = endian_fix(val2);
             if (val & PM_SWAPPED)         pf->swapped = 1;
             if (val & PM_SOFT_DIRTY)      pf->soft_dirty = 1;
             if (val & PM_EXCLUSIVE)       pf->exclusive = 1;
             if (val & PM_SHARED)          pf->shared = 1;
             if (val & PM_PRESENT)         pf->present = 1;
             if (val2 & KPF_DIRTY)         pf->dirty = 1;
             if (val2 & KPF_HUGE)          pf->huge = 1;
             if (val2 & KPF_ZERO_PAGE)     pf->zero = 1;
             if (val2 & KPF_ACTIVE)        pf->active = 1;
             if (val2 & KPF_WRITEBACK)     pf->writeback = 1;
             if (val2 & KPF_PRIVATE)       pf->priv = 1;
             if (val2 & KPF_PRIVATE_2)     pf->priv = 1;
             if (val2 & KPF_OWNER_PRIVATE) pf->priv = 1;
             if (val2 & KPF_SWAPBACKED)    pf->swapbacked = 1;
          }
        pf++;
     }
}

static int
pages_load(pid_t pid)
{
   FILE *maps, *pagemap, *kpageflags;
   char buf[4096];
   char perms[10];
   char file[4096];
   void *ptr_from, *ptr_to, *offset;
   off_t off;
   unsigned int pcount, count = 0;
   Map *map = NULL, *m;

   pgsize = sysconf(_SC_PAGESIZE);
   snprintf(buf, sizeof(buf), "/proc/%i/maps", pid);
   maps = fopen(buf, "rb");
   if (!maps) return -1;
   snprintf(buf, sizeof(buf), "/proc/%i/pagemap", pid);
   pagemap = fopen(buf, "rb");
   if (!pagemap)
     {
        fprintf(stderr, "ERR can't open pagemap\n");
        fclose(maps);
        return -1;
     }
   if (!maps) return -1;
   kpageflags = fopen("/proc/kpageflags", "rb");
   if (!kpageflags)
     {
        fprintf(stderr, "ERR can't open kpageflags\n");
        fclose(maps);
        fclose(pagemap);
        return -1;
     }

   file[0] = 0;
   while (fgets(buf, sizeof(buf) - 1, maps))
     {
        file[0] = 0;
        if (sscanf(buf,
                   "%p-%p %9s %p %*s %*s %s\n",
                   &ptr_from, &ptr_to, perms, &offset, file) >= 4)
          {
             off = (off_t)offset;
             pcount = count;
             map = _pages_append_line(map, &count, ptr_from, ptr_to,
                                      perms, off, file);
             if (pcount != count)
               {
                  m = map + (count - 1);
                  _pages_walk_page_flags(m, pagemap, kpageflags);
                  printf("0x%016llx [%7lluKb] | %s | ",
                         (unsigned long long)(uintptr_t)m->ptr.from,
                         (unsigned long long)((unsigned char *)m->ptr.to - (unsigned char *)m->ptr.from) / 1024,
                         perms
                        );
                  unsigned char *p;
                  Page_Flag *pf = m->pageflags;
                  if (!pf) printf("X");
                  else
                  for (p = m->ptr.from; p < (unsigned char *)m->ptr.to; p += pgsize)
                    {
                       if      ((pf->dirty) && (pf->swapbacked)) printf("*");
                       else if (pf->swapbacked)                  printf("#");
                       else if (pf->dirty)                       printf("D");
//                       else if (pf->soft_dirty)                  printf("d");
                       else if (pf->zero)                        printf("z");
                       else if (pf->writeback)                   printf("w");
                       else if (pf->shared)                      printf("=");
                       else if (pf->active)                      printf("a");
                       else if (pf->present)                     printf("-");
                       else if (pf->invalid)                     printf("x");
                       else                                      printf("_");
                       pf++;
                    }
                  printf(" | %s\n", m->file);
               }
          }
     }
   fclose(maps);
   fclose(pagemap);
   fclose(kpageflags);
   return 0;
}

int
main(int argc, char **argv)
{
   if (argc < 2)
     {
        printf("usage: pages PID\n");
        return -1;
     }
   pid_t pid = atoi(argv[1]);
   pages_load(pid);
   return 0;
}
