// spsh-ramdisk.c
//
// Systemprogrammierung, SS 2013
// Hans-Georg Esser

// Shell mit eingebauter RAM-Disk

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

#define INODES 50
#define DIRENTRIES 50

int ramdisk_init = 0;

#define check_init { \
  if (!ramdisk_init) { \
    printf ("RAM-Disk nicht initialisiert\n"); \
    return; \
  } \
}

// inode-Felder uebernommen aus Linux-Manpage (man 2 stat)
typedef struct inode {
  mode_t    mode;    /* protection */
  nlink_t   nlink;   /* number of hard links */
  uid_t     uid;     /* user ID of owner */
  gid_t     gid;     /* group ID of owner */
  off_t     size;    /* total size, in bytes */
  time_t    atime;   /* time of last access */
  time_t    mtime;   /* time of last modification */
  time_t    ctime;   /* time of last status change */  
  void*     address; // Speicherbereich
} inode_t;

inode_t inode_table[INODES];        // die Inode-Tabelle

typedef struct direntry {
  char      filename[50];
  ino_t     st_ino;
} direntry_t;

direntry_t directory[DIRENTRIES];   // das Verzeichnis

void rdinit () {
  memset (inode_table, -1, sizeof(inode_table));
  memset (directory,   -1, sizeof(directory));
  ramdisk_init = 1;
  return;
}

int get_free_inode () {
  int i;
  for (i=0; i<INODES; i++) {
    // freie Eintraege haben address = -1
    if (inode_table[i].address == (void*)(-1)) return i;
  }
  return -1;
}

int get_free_direntry () {
  int i;
  for (i=0; i<DIRENTRIES; i++) {
    // freie Eintraege haben st_ino = -1
    if (directory[i].st_ino == -1) return i;
  }
  return -1;
}

void rdget (char *pfad, char *dateiname) {
  check_init;
  printf ("rdget %s %s\n", pfad, dateiname);
  
  int fd = open(pfad, O_RDONLY);
  if (fd == -1) {
    printf ("rdget: cannot open %s\n", pfad);
    return;
  }
  
  struct stat s;
  fstat (fd, &s);
  
  int size = s.st_size;

  // Erst Inode und DirEntry besorgen  
  int inodeno = get_free_inode ();
  if (inodeno == -1) {
    printf ("rdget: no ramdisk inode available\n");
    return;
  }
  
  int direntryno = get_free_direntry ();
  if (direntryno == -1) {
    printf ("rdget: no ramdisk direntry available\n");
    return;
  }
  
  // Inode mit Inhalt fuellen
  inode_t *ino;
  ino = &inode_table[inodeno];
  ino->address = malloc(size);
  ino->size = size;
  // ino->ino = inode_counter++;
  ino->mode = s.st_mode;
  ino->nlink = 1;
  ino->uid = s.st_uid;
  ino->gid = s.st_gid;
  ino->atime = s.st_atime;
  ino->mtime = s.st_mtime;
  ino->ctime = s.st_ctime;
  
  read (fd, ino->address, size);
  close (fd);
  
  // Verzeichniseintrag fuellen
  direntry_t *dir;
  dir = &directory[direntryno];
  dir->st_ino = inodeno;
  strcpy (dir->filename, dateiname);
  
  // Fertig :)
  return;
}

void rights_string (mode_t mode, char *s) {
  // aus vorheriger Uebung
  char rights[10];
  rights[0] = '?';
 
  if (S_ISREG(mode))  rights[0] = '-';
  if (S_ISDIR(mode))  rights[0] = 'd';
  if (S_ISCHR(mode))  rights[0] = 'c';
  if (S_ISBLK(mode))  rights[0] = 'b';
  if (S_ISFIFO(mode)) rights[0] = 'f';
  if (S_ISSOCK(mode)) rights[0] = 's';

  rights[1] = (mode & 0400) ? 'r' : '-';  // -r-------- ?
  rights[2] = (mode & 0200) ? 'w' : '-';  // --w------- ?
  rights[3] = (mode & 0100) ? 'x' : '-';  // ---x------ ?
  rights[4] = (mode & 0040) ? 'r' : '-';  // ----r----- ?
  rights[5] = (mode & 0020) ? 'w' : '-';  // -----w---- ?
  rights[6] = (mode & 0010) ? 'x' : '-';  // ------x--- ?
  rights[7] = (mode & 0004) ? 'r' : '-';  // -------r-- ?
  rights[8] = (mode & 0002) ? 'w' : '-';  // --------w- ?
  rights[9] = (mode & 0001) ? 'x' : '-';  // ---------x ?
  rights[10] = '\0';

  if (mode & 04000)
    rights[3] = (mode & 0100) ? 's' : 'S'; // SetUID: 4000, 0100?
  if (mode & 02000) 
    rights[6] = (mode & 0010) ? 's' : 'S'; // SetGID: 2000, 0010?
  if (mode & 01000) 
    rights[9] = (mode & 0001) ? 't' : 'T'; // sticky: 1000, 0001?

  strcpy (s, rights);
}

void rdls () {
  check_init;
  int i;
  char rights[10];
  printf ("INO RIGHTS     LC  UID  GID   SIZE NAME\n");
  for (i=0; i<DIRENTRIES; i++) {
    if (directory[i].st_ino != -1) {
      ino_t in = (ino_t)directory[i].st_ino;
      rights_string (inode_table[in].mode, rights);
      printf ("%3d %s %2d %4d %4d %6d %s\n", 
        (int)directory[i].st_ino, 
        rights,
        (int)inode_table[in].nlink,        
        (int)inode_table[in].uid,        
        (int)inode_table[in].gid,        
        (int)inode_table[in].size,        
        directory[i].filename
      );
    }
  }
  return;
}

int rdlookup (char* dateiname) {
  int i;
  for (i=0; i<DIRENTRIES; i++) {
    if (directory[i].st_ino != -1)
      if (!strcmp (directory[i].filename, dateiname))
        return i;
  }
  return -1;
}

void rdput (char *dateiname, char *pfad) {
  check_init;
  int dirno = rdlookup (dateiname);
  if (dirno == -1) {
    printf ("rdput: file not found, %s\n", dateiname);
    return;
  }
  
  // brauche Inode-Inhalt wegen CREATE-MODE
  ino_t in = (ino_t)directory[dirno].st_ino;
  int mode = inode_table[in].mode;
  
  int fd = creat (pfad, mode);  // mode aus Inode
  if (fd == -1) {
    printf ("rdput: cannot create output file, %s\n", pfad);
    return;
  }
  
  write (fd, inode_table[in].address, inode_table[in].size);
  close (fd);  
  return;
}

void free_inode (int ino) {
  // Inode freigeben
  memset (&inode_table[ino], -1, sizeof(inode_t));
}

void rdrm (char* dateiname) {
  check_init;
  int dirno = rdlookup (dateiname);
  if (dirno == -1) {
    printf ("rdrm: no such file, %s\n", dateiname);
    return;
  }
  // gefunden
  int ino = directory[dirno].st_ino;
  // Verzeichniseintrag loeschen
  memset (&directory[dirno], -1, sizeof(direntry_t));
  // Inode anpassen und ggf. loeschen
  inode_table[ino].nlink--;
  if (inode_table[ino].nlink == 0) {
    free_inode (ino);
  }
  return;
};

void rdln (char *dateiname, char *link) {
  check_init;
  printf ("rdln %s %s\n", dateiname, link);
  int dirno = rdlookup (dateiname);
  if (dirno == -1) {
    printf ("rdln: no such file, %s\n", dateiname);
    return;
  }

  // neuen Verzeichniseintrag suchen
  int newdirno = get_free_direntry ();
  if (newdirno == -1) {
    printf ("rdln: no ramdisk direntry available\n");
    return;
  }
  
  // Daten eintragen
  int ino = directory[dirno].st_ino;
  directory[newdirno].st_ino = ino;
  strcpy (directory[newdirno].filename, link);
  
  // Link-Count erhoehen
  inode_table[ino].nlink++;
  return;
}

void rdmv (char *alt, char *neu) {
  check_init;
  int dirno = rdlookup (alt);
  if (dirno == -1) {
    printf ("rdmv: no such file, %s\n", alt);
    return;
  }
  // pruefen, ob neuer Name schon existiert
  int checkdirno = rdlookup (neu);
  if (checkdirno != -1) {
    printf ("rdmv: file exists, %s\n", neu);
    return;
  }

  // neuen Namen setzen
  strcpy (directory[dirno].filename, neu);
  return;
}

void rdchmod (char *rechte, char *dateiname) {
  check_init;
  int dirno = rdlookup (dateiname);
  if (dirno == -1) {
    printf ("rdchmod: no such file, %s\n", dateiname);
    return;
  }
  int mode = strtol (rechte, NULL, 8);
  ino_t in = directory[dirno].st_ino;
  inode_table[in].mode = (inode_table[in].mode & ~0777) | mode;  
  return;
}

int main () {
  char command[255];
  char seps[] = " \t";
  char *part;
  char *args[10];
  short no_args;
  int status, pid;

  // remove this later!
  rdinit ();
  rdget ("a.out", "a.out");
  rdget ("spsh-ramdisk.c", "spsh-ramdisk.c");

  while (1) {
    printf ("spsh$ ");
    fgets (command, sizeof(command), stdin);
    // Aus Eingabe \n abschneiden
    command[strlen(command)-1] = (char) 0;

    no_args = 0;
    part = strtok (command, seps);
    while ( part != NULL ) {
      args[no_args] = part;
      no_args++;
      part = strtok (NULL, seps);
    };

    if (no_args == 0) continue;  // kein fork/exec

    // internes Kommando?
    if (!strcmp(args[0],"exit")) {
      printf ("Terminating\n");
      exit(0);
    } else if (!strcmp(args[0],"rdinit")) {
      rdinit ();
    } else if (!strcmp(args[0],"rdget")) {
      rdget (args[1], args[2]);
    } else if (!strcmp(args[0],"rdls")) {
      rdls ();
    } else if (!strcmp(args[0],"rdput")) {
      rdput (args[1], args[2]);
    } else if (!strcmp(args[0],"rdrm")) {
      rdrm (args[1]);
    } else if (!strcmp(args[0],"rdln")) {
      rdln (args[1], args[2]);
    } else if (!strcmp(args[0],"rdmv")) {
      rdmv (args[1], args[2]);
    } else if (!strcmp(args[0],"rdchmod")) {
      rdchmod (args[1], args[2]);
    } else {
      // Kommando mit exec ausfuehren
      args[no_args] = NULL;

      pid = fork();
      if ( pid == 0 ) {
        // Kindprozess
        execvp (args[0], args);
        // exec fehlgeschlagen?
        printf ("%s not found\n", args[0]);
        exit(0);
      } else {
        printf ("Process %d launched: \n", pid);
        // Vaterprozess
        waitpid (pid, &status, 0);
      }
    }
  }
}

