// spsh-mit-pipes.c
// Hans-Georg Esser, 10.06.2012

#include <sys/stat.h> // lstat()
#include <stdio.h>    // printf()
#include <time.h>     // localtime()
#include <pwd.h>      // getpwuid()
#include <grp.h>      // getgrgid()
#include <stdlib.h>   // exit()
#include <unistd.h>   // exec()
#include <string.h>   // strncpy()
#include <sys/wait.h> // wait()

void ls (char* filename) {
  struct stat s;
  struct passwd *pwd;
  struct group *grp;
  struct tm *time;
  char lnkfilename[280] = "";
  char rights[10];

  if ( lstat (filename, &s) == -1 ) {
    perror("spsh: ls");
    return;
  };
  // printable mode
  rights[0] = '?';
 
  if (S_ISREG(s.st_mode))  rights[0] = '-';
  if (S_ISDIR(s.st_mode))  rights[0] = 'd';
  if (S_ISCHR(s.st_mode))  rights[0] = 'c';
  if (S_ISBLK(s.st_mode))  rights[0] = 'b';
  if (S_ISFIFO(s.st_mode)) rights[0] = 'f';
  if (S_ISLNK(s.st_mode)) {
    rights[0] = 'l';
    strcpy ((char*)&lnkfilename, " ->  ");
    readlink (filename, (char*)(&lnkfilename) + 4 , 256);
  };
  if (S_ISSOCK(s.st_mode)) rights[0] = 's';

  rights[1] = (s.st_mode & 0400) ? 'r' : '-';  // -r-------- ?
  rights[2] = (s.st_mode & 0200) ? 'w' : '-';  // --w------- ?
  rights[3] = (s.st_mode & 0100) ? 'x' : '-';  // ---x------ ?
  rights[4] = (s.st_mode & 0040) ? 'r' : '-';  // ----r----- ?
  rights[5] = (s.st_mode & 0020) ? 'w' : '-';  // -----w---- ?
  rights[6] = (s.st_mode & 0010) ? 'x' : '-';  // ------x--- ?
  rights[7] = (s.st_mode & 0004) ? 'r' : '-';  // -------r-- ?
  rights[8] = (s.st_mode & 0002) ? 'w' : '-';  // --------w- ?
  rights[9] = (s.st_mode & 0001) ? 'x' : '-';  // ---------x ?
  rights[10] = '\0';
  if (s.st_mode & 04000)
    rights[3] = (s.st_mode & 0100) ? 's' : 'S'; // SetUID: 4000, 0100?
  if (s.st_mode & 02000) 
    rights[6] = (s.st_mode & 0010) ? 's' : 'S'; // SetGID: 2000, 0010?
  if (s.st_mode & 01000) 
    rights[9] = (s.st_mode & 0001) ? 't' : 'T'; // sticky: 1000, 0001?

  pwd = getpwuid(s.st_uid);
  grp = getgrgid(s.st_gid);
  time = localtime (&s.st_mtime);

  printf ("%d %s %d %s %s %d %04d/%02d/%02d %02d:%02d:%02d %s%s\n", 
    (int)s.st_ino, rights, (int)s.st_nlink,
    pwd->pw_name, grp->gr_name, (int)s.st_size,
    time->tm_year+1900, time->tm_mon+1, time->tm_mday,
    time->tm_hour, time->tm_min, time->tm_sec, 
    filename, lnkfilename);
  return;
};

int cmdno;
int pipe_fds[10][2];  // Pipe-Deskriptor-Paare

void logdup (int pid, int pip, int rw, int fd) {
  // return;
  // Funktion protokolliert dup2()-Aufrufe
  // ersten Befehl aktivieren, um Ausgabe zu unterdruecken
  printf ("process %d: dup2 (pipe_fds[%d][%d], %d)\n", 
    pid, pip, rw, fd);
  return; 
};

void close_all_pipes () {
  // schliesst alle Pipes
  int i;
  for (i=0; i<cmdno; i++) {
    close (pipe_fds[i][0]);
    close (pipe_fds[i][1]);
  };
  return;
};

int main () {
  char command[255];
  char seps[] = " \t";
  char *part;
  char *args[10][10];  // pro Kommando max. 10 Argument
  char *cmds[10];       // max. 10 Kommandos
  short no_args[10];    // Zahl der Argumente fuer die Kommandos
  int background, status, wpid;
  int pids[10];         // max. 10 Kind-PIDs

  while (1) {
    // Beendete Kindprozesse einsammeln
    wpid = waitpid (WAIT_ANY, &status, WNOHANG);
    while ( wpid > 0) {
      printf ("Process %d terminated.\n", wpid); 
      wpid = waitpid (WAIT_ANY, &status, WNOHANG);
    };
  
    printf ("spsh$ ");
    fgets (command, sizeof(command), stdin);
    // Aus Eingabe \n abschneiden
    command[strlen(command)-1] = (char) 0;

    cmdno = 0;
    no_args[0] = 0;
    background = 0;
    
    part = strtok (command, seps);
    while ( part != NULL ) {
      if (!strcmp(part,"|")) {
        // | gefunden
        cmdno++;
        no_args[cmdno] = 0;
      } else {
        // aktuelles Kommando weiter bearbeiten    
        args[cmdno][no_args[cmdno]] = part;
        no_args[cmdno]++;
      }
      part = strtok (NULL, seps);
    };

    // Test, ob letztes Argument ein "&" ist -> background
    if (!strcmp(args[cmdno][no_args[cmdno]-1],"&")) {
      no_args[cmdno]--;
      background = 1;
    };

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

    // internes Kommando?
    if (!strcmp(args[0][0],"exit")) {
      printf ("Terminating\n");
      exit(0);
    };
    if (!strcmp(args[0][0],"ls") && no_args[cmdno] == 2) {
      ls (args[cmdno][1]);
      continue;
    };
    
    // Test: Kommando ausgeben
    if (cmdno>0) {
      printf ("Creating pipe, %d commands:\n", cmdno+1);
      int i,j;
      for (i=0; i<cmdno+1; i++) {
        printf ("CMD %d: ", i);
        for (j=0; j<no_args[i]; j++) {
          printf ("'%s',", args[i][j]);
        }
        printf ("\n");
      }
    }

    // Pipes erzeugen
    // fuer n Prozesse brauche ich n-1 Pipes
    int i;
    for (i=0; i<cmdno; i++) {
      printf ("create pipe %d\n", i);
      pipe (pipe_fds[i]);
    };    
    
    // Kommando mit exec ausfuehren
    for (i=0; i<cmdno+1; i++)
      args[i][no_args[i]] = NULL;

    for (i=0; i<cmdno+1; i++) {
      pids[i] = fork();
      if ( pids[i] == 0 ) {
        // Kindprozess
        // Pipes bearbeiten
        if ((i==0) && (cmdno>0)) {
          // Fall 1: erster Prozess
          logdup (i, 0,1,1);
          dup2 (pipe_fds[0][1],1);  // Write-Ende von pipe 0
          close_all_pipes ();
        };
        if ((i>0) && (i<cmdno)) {
          // Fall 2: Prozess "in der Mitte"
          logdup (i, i-1,0,0);
          logdup (i, i,1,1);
          dup2 (pipe_fds[i-1][0],0);  // Read-Ende von pipe i-1
          dup2 (pipe_fds[i]  [1],1);  // Write-Ende von pipe i
          close_all_pipes ();
        };
        if ((i==cmdno) && (cmdno>0)) {
          // Fall 3: letzter Prozess
          logdup (i, i-1,0,0);
          dup2 (pipe_fds[i-1][0],0);  // Read-Ende von pipe i-1
          close_all_pipes ();
        };
        
        execvp (args[i][0], args[i]);
        // exec fehlgeschlagen?
        printf ("%s not found\n", args[i][0]);
        exit(0);
      };
    }; // ends for loop
    
    // folgender Code nur im Originalprozess
    close_all_pipes();
    printf ("Processes launched: ");
    for (i=0; i<cmdno+1; i++) {
      printf ("%d, ", pids[i]);
    };
    // Falls Vordergrund: Kind-Prozesse einsammeln
    if (background == 0) {
      printf ("foreground\n");
      for (i=0; i<cmdno+1; i++) 
        waitpid (pids[i], &status, 0);
    } else {
      printf ("background\n");
    };
    background = 0;  // wieder auf 0 setzen fuer naechstes Cmd.
  }
}

