/*
 * Copyright 2002 by Albert Cahalan; all rights reserved.
 * This file may be used subject to the terms and conditions of the
 * GNU Library General Public License Version 2, or any later version
 * at your option, as published by the Free Software Foundation.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Library General Public License for more details.
 */

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

#include <sys/ipc.h>
#include <sys/shm.h>

#include "proc/readproc.h"
#include "proc/version.h"
#include "proc/escape.h"

static void usage(void) NORETURN;
static void usage(void){
  fprintf(stderr,
    "Usage: pmap [-x | -d] [-q] pid...\n"
    "-x  show details\n"
    "-d  show offset and device number\n"
    "-q  quiet; less header/footer info\n"
    "-V  show the version number\n"
  );
  exit(1);
}


static int V_option;
static int r_option;  // ignored -- for SunOS compatibility
static int x_option;
static int d_option;
static int q_option;


static unsigned shm_minor = ~0u;

static void discover_shm_minor(void){
  void *addr;
  int shmid;
  char mapbuf[256];

  if(!freopen("/proc/self/maps", "r", stdin)) return;

  // create
  shmid = shmget(IPC_PRIVATE, 42, IPC_CREAT | 0666);
  if(shmid==-1) return; // failed; oh well
  // attach
  addr = shmat(shmid, NULL, SHM_RDONLY);
  if(addr==(void*)-1) goto out_destroy;

  while(fgets(mapbuf, sizeof mapbuf, stdin)){
    char flags[32];
    char *tmp; // to clean up unprintables
    unsigned KLONG start, end;
    unsigned long long file_offset, inode;
    unsigned dev_major, dev_minor;
    sscanf(mapbuf,"%"KLF"x-%"KLF"x %31s %Lx %x:%x %Lu", &start, &end, flags, &file_offset, &dev_major, &dev_minor, &inode);
    tmp = strchr(mapbuf,'\n');
    if(tmp) *tmp='\0';
    tmp = mapbuf;
    while(*tmp){
      if(!isprint(*tmp)) *tmp='?';
      tmp++;
    }
    if(start > (unsigned long)addr) continue;
    if(dev_major) continue;
    if(flags[3] != 's') continue;
    if(strstr(mapbuf,"/SYSV")){
      shm_minor = dev_minor;
      break;
    }
  }

  if(shmdt(addr)) perror("shmdt");

out_destroy:
  if(shmctl(shmid, IPC_RMID, NULL)) perror("IPC_RMID");

  return;
}


static const char *mapping_name(proc_t *p, unsigned KLONG addr, unsigned KLONG len, const char *mapbuf, unsigned advance, unsigned dev_major, unsigned dev_minor, unsigned long long inode){
  const char *cp;

  if(!dev_major && dev_minor==shm_minor && strstr(mapbuf,"/SYSV")){
    static char shmbuf[64];
    snprintf(shmbuf, sizeof shmbuf, "  [ shmid=0x%Lx ]", inode);
    return shmbuf;
  }

  cp = strrchr(mapbuf,'/');
  if(cp){
    if(cp[1]) cp += advance;
    return cp;
  }

  cp = "  [ anon ]";
  if( (p->start_stack >= addr) && (p->start_stack <= addr+len) )  cp = "  [ stack ]";
  return cp;
}


// Overkill, but who knows what is proper? The "w" prog
// uses the tty width to determine this.
#define maxcmd 0xfffff

static int one_proc(proc_t *p){
  char buf[32];
  char mapbuf[9600];
  char cmdbuf[512];
  unsigned long total_shared = 0ul;
  unsigned long total_private_readonly = 0ul;
  unsigned long total_private_writeable = 0ul;

  sprintf(buf,"/proc/%u/maps",p->tgid);
  if(!freopen(buf, "r", stdin)) return 1;

  escape_command(cmdbuf, p, sizeof cmdbuf, maxcmd, ESC_ARGS|ESC_BRACKETS);
  printf("%u:   %s\n", p->tgid, cmdbuf);

  if(!q_option && (x_option|d_option)){
    if(x_option){
      if(sizeof(KLONG)==4) printf("Address   Kbytes     RSS    Anon  Locked Mode   Mapping\n");
      else         printf("Address           Kbytes     RSS    Anon  Locked Mode   Mapping\n");
    }
    if(d_option){
      if(sizeof(KLONG)==4) printf("Address   Kbytes Mode  Offset           Device    Mapping\n");
      else         printf("Address           Kbytes Mode  Offset           Device    Mapping\n");
    }
  }

  while(fgets(mapbuf,sizeof mapbuf,stdin)){
    char flags[32];
    char *tmp; // to clean up unprintables
    unsigned KLONG start, end, diff;
    unsigned long long file_offset, inode;
    unsigned dev_major, dev_minor;
    sscanf(mapbuf,"%"KLF"x-%"KLF"x %31s %Lx %x:%x %Lu", &start, &end, flags, &file_offset, &dev_major, &dev_minor, &inode);
    tmp = strchr(mapbuf,'\n');
    if(tmp) *tmp='\0';
    tmp = mapbuf;
    while(*tmp){
      if(!isprint(*tmp)) *tmp='?';
      tmp++;
    }
    
    diff = end-start;
    if(flags[3]=='s') total_shared  += diff;
    if(flags[3]=='p'){
      flags[3] = '-';
      if(flags[1]=='w') total_private_writeable += diff;
      else              total_private_readonly  += diff;
    }

    // format used by Solaris 9 and procps-3.2.0+
    // an 'R' if swap not reserved (MAP_NORESERVE, SysV ISM shared mem, etc.)
    flags[4] = '-';
    flags[5] = '\0';

    if(x_option){
      const char *cp = mapping_name(p, start, diff, mapbuf, 1, dev_major, dev_minor, inode);
      printf(
        (sizeof(KLONG)==8)
          ? "%016"KLF"x %7lu       -       -       - %s  %s\n"
          :      "%08lx %7lu       -       -       - %s  %s\n",
        start,
        (unsigned long)(diff>>10),
        flags,
        cp
      );
    }
    if(d_option){
      const char *cp = mapping_name(p, start, diff, mapbuf, 1, dev_major, dev_minor, inode);
      printf(
        (sizeof(KLONG)==8)
          ? "%016"KLF"x %7lu %s %016Lx %03x:%05x %s\n"
          :      "%08lx %7lu %s %016Lx %03x:%05x %s\n",
        start,
        (unsigned long)(diff>>10),
        flags,
        file_offset,
        dev_major, dev_minor,
        cp
      );
    }
    if(!x_option && !d_option){
      const char *cp = mapping_name(p, start, diff, mapbuf, 0, dev_major, dev_minor, inode);
      printf(
        (sizeof(KLONG)==8)
          ? "%016"KLF"x %6luK %s  %s\n"
          :      "%08lx %6luK %s  %s\n",
        start,
        (unsigned long)(diff>>10),
        flags,
        cp
      );
    }
    
  }

  if(!q_option){
    if(x_option){
      if(sizeof(KLONG)==8){
        printf("----------------  ------  ------  ------  ------\n");
        printf(
          "total kB %15ld       -       -       -\n",
          (total_shared + total_private_writeable + total_private_readonly) >> 10
        );
      }else{
        printf("-------- ------- ------- ------- -------\n");
        printf(
          "total kB %7ld       -       -       -\n",
          (total_shared + total_private_writeable + total_private_readonly) >> 10
        );
      }
    }
    if(d_option){
        printf(
          "mapped: %ldK    writeable/private: %ldK    shared: %ldK\n",
          (total_shared + total_private_writeable + total_private_readonly) >> 10,
          total_private_writeable >> 10,
          total_shared >> 10
        );
    }
    if(!x_option && !d_option){
      if(sizeof(KLONG)==8) printf(" total %16ldK\n", (total_shared + total_private_writeable + total_private_readonly) >> 10);
      else                 printf(" total %8ldK\n",  (total_shared + total_private_writeable + total_private_readonly) >> 10);
    }
  }

  return 0;
}


int main(int argc, char *argv[]){
  unsigned *pidlist;
  unsigned count = 0;
  PROCTAB* PT;
  proc_t p;
  int ret = 0;

  if(argc<2) usage();
  pidlist = malloc(sizeof(unsigned)*argc);  // a bit more than needed perhaps

  while(*++argv){
    if(!strcmp("--version",*argv)){
      V_option++;
      continue;
    }
    if(**argv=='-'){
      char *walk = *argv;
      if(!walk[1]) usage();
      while(*++walk){
        switch(*walk){
        case 'V':
          V_option++;
          break;
        case 'x':
          x_option++;
          break;
        case 'r':
          r_option++;
          break;
        case 'd':
          d_option++;
          break;
        case 'q':
          q_option++;
          break;
        default:
          usage();
        }
      }
    }else{
      char *walk = *argv;
      char *endp;
      unsigned long pid;
      if(!strncmp("/proc/",walk,6)){
        walk += 6;
        // user allowed to do: pmap /proc/*
        if(*walk<'0' || *walk>'9') continue;
      }
      if(*walk<'0' || *walk>'9') usage();
      pid = strtoul(walk, &endp, 0);
      if(pid<1ul || pid>0x7ffffffful || *endp) usage();
      pidlist[count++] = pid;
    }
  }

  if( (x_option|V_option|r_option|d_option|q_option) >> 1 ) usage(); // dupes
  if(V_option){
    if(count|x_option|r_option|d_option|q_option) usage();
    fprintf(stdout, "pmap (%s)\n", procps_version);
    return 0;
  }
  if(count<1) usage();   // no processes
  if(d_option && x_option) usage();

  discover_shm_minor();

  pidlist[count] = 0;  // old libproc interface is zero-terminated
  PT = openproc(PROC_FILLSTAT|PROC_FILLARG|PROC_PID, pidlist);
  while(readproc(PT, &p)){
    ret |= one_proc(&p);
    if(p.cmdline) free((void*)*p.cmdline);
    count--;
  }
  closeproc(PT);

  if(count) ret |= 42;  // didn't find all processes asked for
  return ret;
}
