/*
|---------------------------------------------------|
|             CipShell versiunea 1.00               |
|                                                   |
|Interpretor simplu de comenzi ce permite:          |
| - tratarea semnalelor de oprire a aplicatiei;     |
| - executarea de comenzi simple;                   |
| - executare de comenzi compuse cu un singur pipe; |
|Programator: Zavoianu Ciprian                      |
|             ciprian_zavoianu@yahoo.com            |
|             www.info.uvt.ro/~zavoianu.ciprian     |
|---------------------------------------------------|
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>


#define STANDARD_IN 0
#define STANDARD_OUT 1
#define CAP_CITIRE 0
#define CAP_SCRIERE 1

/*
  functia imparte_in_argumente imparte comanda data in argumente pe care le plaseaza
  in vectorul sig_arg[]
*/
//------------------------------------------------------------------------------
void imparte_in_argumente(char *comanda, char *sir_arg[], int max_arg)
{
     int i=1;
     sir_arg[0]=strtok(comanda," ");
     while((sir_arg[i++]=strtok(NULL," "))!=NULL && (i<max_arg-1));
}
//------------------------------------------------------------------------------

/*
  functie ce executa o comanda simpla (fork()) cu ajutorul unui fisier pipe
  anonim
*/
//------------------------------------------------------------------------------
void executa_comanda_simpla(char *comanda)
{
    int copy_stdout;
    int pa[2]; //pipe-ul anonim
    pid_t pid;
    
    //se salveaza valorea originala pentru stdout
    copy_stdout=dup(STANDARD_OUT);
    
    //se incearca crearea pipe-ului anonim
    if (pipe(pa)!=0)
    {
          perror("Eroare la crearea PIPE-ului ANONIM!\n");
          exit(EXIT_FAILURE);
    }   
    
    //apelul fork
    if ((pid=fork())<0)
    {
          perror("Erore in fuctia fork()!");
          exit(EXIT_FAILURE);
    }
    //codul pentru procesul fiu
    if (pid==0)
    {
          close(pa[CAP_CITIRE]); //se inchide capatul de citire (nu este nevoie de el)
          //se incearca redirectarea lui stadout catre capatul de scriere al pipe-ului
          if (dup2(pa[CAP_SCRIERE],STANDARD_OUT)==-1)
          {
                 perror("Eroare la redirectare stdout!");
                 exit(EXIT_FAILURE);                                    
          }
          //se imparte comanda in argumentele componenete
          char *sir_argumente[25]; //sir ce va retine argumentele componenete ale comenzii
          imparte_in_argumente(comanda,sir_argumente,25); //se imparte comanda in argumente
          
          //se incearca executia comenzii
          execvp(sir_argumente[0],sir_argumente);
          //in caz de eroare se afiseaza un mesaj corespunzator
          printf("Eroare la executia comenzii: %s\n",sir_argumente[0]);
          exit(EXIT_FAILURE);     
    }    
    //codul pentru procesul parinte
    else
    {
          close(pa[CAP_SCRIERE]); //se inchide capatul de scriere (nu e nevoie de el)
          int x;
          wait(&x); //se asteapta terminarea procesului fiu
          
          // se restaureaza valoarea initiala pentru stdout
          dup2(copy_stdout, STANDARD_OUT);
          
          //se citeste din pipe informatia si se afiseaza pe ecran 
          char buffer[128];
          int nrb; //numarul de bytes cititi in buffer
          while ((nrb=read(pa[CAP_CITIRE],buffer,127))>0)
          {
                buffer[nrb]='\0';
                printf("%s",buffer);
          }
          
          //se inchide si capatul de citire al pipe-ului
          close(pa[CAP_CITIRE]);          
    }
}
//------------------------------------------------------------------------------

/*
  functie ce executa o comanda compusa cu un singur pipe
*/
//------------------------------------------------------------------------------
void executa_comanda_compusa(char *comanda1, char *comanda2)
{
    int copy_stdin,copy_stdout;
    int pa1[2],pa2[2]; //pipe-uri anonime
    pid_t pid1,pid2;
    
    //se salveaza valorile originale pentru stdin si stdout
    copy_stdin=dup(STANDARD_IN);
    copy_stdout=dup(STANDARD_OUT);
    
    //se incearca crearea pipe-urilor anonime
    if (pipe(pa1)!=0)
    {
          perror("Eroare la crearea PIPE-ului ANONIM!\n");
          exit(EXIT_FAILURE);
    }
    if (pipe(pa2)!=0)
    {
          perror("Eroare la crearea PIPE-ului ANONIM!\n");
          exit(EXIT_FAILURE);
    }
    
    //apelul fork pentru prima comanda
    if ((pid1=fork())<0)
    {
          perror("Erore in fuctia fork()!");
          exit(EXIT_FAILURE);
    }
    //codul pentru procesul fiu 1
    if (pid1==0)
    { 
          //se inchid capetele de citire de care nu este nevoie
          close(pa2[CAP_CITIRE]);
          close(pa2[CAP_SCRIERE]);      
          close(pa1[CAP_CITIRE]); 
          //se incearca redirectarea lui stadout catre capatul de scriere al pipe-ului 1
          if (dup2(pa1[CAP_SCRIERE],STANDARD_OUT)==-1)
          {
                 perror("Eroare la redirectare stdout!");
                 exit(EXIT_FAILURE);                                    
          }
          //se imparte comanda in argumentele componenete 1
          char *sir_argumente[25]; //sir ce va retine argumentele componenete ale comenzii 1
          imparte_in_argumente(comanda1,sir_argumente,25); //se imparte efectiv comanda 1 in argumente
          
          //se incearca executia comenzii 1
          execvp(sir_argumente[0],sir_argumente);
          //in caz de eroare se afiseaza un mesaj corespunzator
          printf("Eroare la executia comenzii: %s\n",sir_argumente[0]);
          exit(EXIT_FAILURE);     
    }    
    
    //apelul fork pentru cea de-a doua comanda
    if ((pid2=fork())<0)
    {
          perror("Erore in fuctia fork()!");
          exit(EXIT_FAILURE);
    }
    //codul pentru procesul fiu 2
    if (pid2==0)
    { 
          //se inchid capetele de citire de care nu este nevoie
          close(pa2[CAP_CITIRE]);    
          close(pa1[CAP_SCRIERE]); 
          //se incearca redirectarea lui stadin catre capatul de citire al pipe-ului 1
          if (dup2(pa1[CAP_CITIRE],STANDARD_IN)==-1)
          {
                 perror("Eroare la redirectare stdin!");
                 exit(EXIT_FAILURE);                                    
          }
          //se incearca redirectarea lui stdout catre capatul de scriere al pipe-ului 2
          if (dup2(pa2[CAP_SCRIERE],STANDARD_OUT)==-1)
          {
                 perror("Eroare la redirectare stdout!");
                 exit(EXIT_FAILURE);                                    
          }
          //se imparte comanda 2 in argumentele componenete
          char *sir_argumente[25]; //sir ce va retine argumentele componenete ale comenzii 2
          imparte_in_argumente(comanda2,sir_argumente,25); //se imparte efectiv comanda 2 in argumente
          
          //se incearca executia comenzii 2
          execvp(sir_argumente[0],sir_argumente);
          //in caz de eroare se afiseaza un mesaj corespunzator
          printf("Eroare la executia comenzii: %s\n",sir_argumente[0]);
          exit(EXIT_FAILURE);     
    }
  
  //urmeza codul pentru procesul parinte
  
  //se inchid capetele de pipe de care nu mai este nevoie
  close(pa1[CAP_CITIRE]);
  close(pa1[CAP_SCRIERE]);
  close(pa2[CAP_SCRIERE]);
  
  //se asteapta terminarea proceselor fiu
  int x;
  wait(&x);
  wait(&x);
  
  //se restaureaza valorile initiale pentru stdin si stdout
  dup2(copy_stdin, STANDARD_IN);
  dup2(copy_stdout, STANDARD_OUT); 
  
  //se citeste din pipe-ul 2 informatia si se afiseaza pe ecran 
  char buffer[128];
  int nrb; //numarul de bytes cititi in buffer
  while ((nrb=read(pa2[CAP_CITIRE],buffer,127))>0)
  {
       buffer[nrb]='\0';
       printf("%s",buffer);
  }
          
  //se inchide si capatul de citire al pipe-ului
  close(pa2[CAP_CITIRE]); 
}
//------------------------------------------------------------------------------

/*
  functia parseaza_si_executa parseaza comanda primita si incearca sa o execute
  fie ca o comada simpla fie ca o comanda compusa.
  daca avem o comanda multipla (are mai mult de un pipe), executia este oprita.
*/
//------------------------------------------------------------------------------
void parseaza_si_executa(char *comanda)
{
    int rezultat_executie,i,nr=0;
    char *comanda1,*comanda2;
    
    //se testeaza daca avem o comanda cu mai mult de un pipe
    for(i=0;i<strlen(comanda);i++)
      if (comanda[i]=='|') nr++;
    if (nr>1)
    {
       printf("Se pot executa comenzi compuse ce au CEL MULT UN PIPE!\n");
       exit(EXIT_FAILURE);      
    } 
    
    comanda2=strtok(comanda,"|:\n");//se extrage prima comanda
    comanda1=comanda2;              //se atribuie valoare extrasa pointerului comanda1
    comanda2=strtok(NULL,"|:\n");   //se extrage a doua comanda daca aceasta exista 
    
    if (comanda2==NULL) executa_comanda_simpla(comanda1);
    else executa_comanda_compusa(comanda1,comanda2); 
}
//------------------------------------------------------------------------------
 
/*
  urmatorul set de 5 functii este folosit pentru tratarea semnalelor "prinse"
  in cadrul functie test_signals.
  tratarea efectiva a semnalelor de terminarea a aplicatiei se reduce la un simplu
  dialog cu utilizatorul (implementat in functia terminate_shell_dialog).
*/
//------------------------------------------------------------------------------
void terminate_shell_dialog()
{
     /*
       are loc un dialog cu utilizatorul in care se cere confirmare pentru iesirea
       din program
     */       
     char raspuns[100];
     strcpy(raspuns,"default");
     while ((strncmp(raspuns, "y", 1) != 0) &&
            (strncmp(raspuns, "Y", 1) != 0) &&
            (strncmp(raspuns, "n", 1) != 0) &&
            (strncmp(raspuns, "N", 1) != 0)) 
     {
           printf("Doriti sa iesiti din CipShell?(Y|N)\n");
           
           if(fgets(raspuns, sizeof(raspuns), stdin) == NULL)
		   {
			      strcpy(raspuns,"default");
		   }
           
           if ((strncmp(raspuns, "y", 1) == 0) ||
               (strncmp(raspuns, "Y", 1) == 0))
           {
                  exit(EXIT_SUCCESS);
		   }
       }  
}

void SIGINTmess()
{
     printf("Se va trata semnalul SIGINT (CTRL-C):\n");
     terminate_shell_dialog();
}

void SIGTERMmess()
{
      printf("Se va trata semnalul SIGTERM (kill aplicat pe shell):\n");
     terminate_shell_dialog();
}

void SIGQUITmess()
{
     printf("Se va trata semnalul SIGQUIT (CTRL-\\):\n");
     terminate_shell_dialog();
}

void SIGTSTPmess()
{
     printf("Se va trata semnalul SIGTSTP (CTRL-Z):\n");
     terminate_shell_dialog();
}
//------------------------------------------------------------------------------

/*
  functia test_signals "prinde" semnalele: 
        1.  SIGINT   <---> (CTRL-C), 
        2.  SIGTERM  <---> (apel de forma <kill PID_CipShell>), 
        3.  SIGQUIT  <---> (CTRL-\),  
        4.  SIGTSTP  <---> (CTRL-Z); 
*/
//------------------------------------------------------------------------------   
void test_signals()
{
    if(signal(SIGINT, SIGINTmess) == SIG_ERR)
	{
		perror("EROARE LA SEMNALUL SIGINT!");
		exit(EXIT_FAILURE);
	}
	if(signal(SIGTERM, SIGTERMmess) == SIG_ERR)
	{
		perror("EROARE LA SEMNALUL SIGTERM!");
		exit(EXIT_FAILURE);
	}
	if(signal(SIGQUIT, SIGQUITmess) == SIG_ERR)
	{
		perror("EROARE LA SEMNALUL SIGQUIT!");
		exit(EXIT_FAILURE);
	}
	if(signal(SIGTSTP, SIGTSTPmess) == SIG_ERR)
	{
		perror("EROARE LA SEMNALUL SIGTSTP!");
		exit(EXIT_FAILURE);
	}
}
//------------------------------------------------------------------------------

//functia exec_loop cicleaza la infinit asteptand inputul utilizatorului
/*
  pentru a iesi din ciclare (si din program): - fie se apasa  CTRL-D (comanda vida) 
                                              - fie se introduce comanda "exitCS" 
*/
//------------------------------------------------------------------------------
void exec_loop()
{
    char comanda[100];
    int rezultat_executie;
    
    printf("|--------------------------------------------|\n");
    printf("|  Interpretor de comezi CipShell ver 1.00   |\n");
    printf("|Comanda de iesire din aplicatie este: exitcs|\n");
    printf("|--------------------------------------------|\n");
    printf("\n");
    
    while(0!=1)
    {
        printf("\nCipShell 1.00>> ");       
        //testul de CTRL-D       
        if(fgets(comanda, sizeof(comanda), stdin) == NULL)
		{
			perror("Linie vida! Se va iesi din consola!");
			exit(EXIT_FAILURE);
		}
		//testul pt comanda de iesire din shell
		if ((strncmp(comanda, "exitcs", 6) == 0) ||
            (strncmp(comanda, "exitCS", 6) == 0) ||
            (strncmp(comanda, "EXITCS", 6) == 0))
        {
            printf("|--------------------------------------------|\n");
            printf("|    Va multumim ca ati folosit CipShell!    |\n");
            printf("|--------------------------------------------|\n");    
			exit(EXIT_SUCCESS);
		}
		else //se incearca parsarea si executia comenzii primite
		{
           parseaza_si_executa(comanda); 
        }
    }
}
//------------------------------------------------------------------------------

/*
  main-ul contine doar functia de testare a semnalelor si o functia de cicilare 
  infinita in interiorul careia se citesc comenzile si se incearca executarea
  acestora
*/
//------------------------------------------------------------------------------
int main(int argc, char **argv)
{
    test_signals();
    exec_loop();
    return EXIT_SUCCESS;
}
//------------------------------------------------------------------------------ 
