Description
Simple shell for Linux.
Simple shell for Linux.
/* * "sish.c" (C) Davide Francesco "HdS619" Merico ( hds619@gmail.com ) * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #define _BSD_SOURCE #define _GNU_SOURCE #define SISH_IN 5 #define SISH_SIN 3 #include <stdio.h> #include <ctype.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <signal.h> #include <stdarg.h> #include <sys/types.h> #include <sys/wait.h> #include <getopt.h> struct user_info { char *username; char hostname[64]; char *cpath; char **paths; } *user_info = NULL; typedef enum { false, true } bool; void get_and_do_command (char *); char **getpaths (char *); char **depure_command_string (char *, int *); bool search_program (char **); bool internal_command (char **); bool check_internal_command (char *); bool check_is_env_var (char *); bool adjust_env_var (char **); void load_config_file (char *); char *cmds_join (char **, int, int); char *remove_facoltative_space (char *); void logging_command (char **); int pipe_action (char **, int *); void sish_exec (char ***); void redirect_input (char ***, int); void redirect_output (char ***, int); void process_or (char ***, int); void process_and (char ***, int); static unsigned int quiet = 1; static struct logging { char *filename; bool logging; } *log; static struct alias { char *command; char *alias; } *alias; static char *internal[SISH_IN] = { "quit", "exit", "sish", "cd", "alias" }; static char *s_internal[SISH_SIN] = { "info", "load_config", "logging" }; void sish_err (const char *fmtstr, ...) { va_list list; va_start (list, fmtstr); vfprintf (stderr, fmtstr, list); va_end (list); } void SISH_SIG_IGN (int num) { return; } void SISH_EXIT (int num) { sish_err ("\nSegnale Ricevuto. Uscita con codice: %d\n", num); exit (num); } int main (int argc, char *argv[]) { int opt, opt_index; char *config_file = NULL, *command = NULL; bool chk_config = true; struct option sish_long_option[] = { { "command", required_argument, 0, 'c' }, { "profile", optional_argument, 0, 'P' }, { "logging", optional_argument, 0, 'L' }, { "noprofile", no_argument, 0, 'p' }, { 0, 0, 0, 0 } }; user_info = (struct user_info *) malloc (sizeof (struct user_info)); user_info->username = getenv ("USER"); gethostname (user_info->hostname, 64); user_info->cpath = getcwd (NULL, 0); user_info->paths = getpaths (getenv ("PATH")); alias = (struct alias *) malloc (sizeof (struct alias)); alias->command = NULL; alias->alias = NULL; log = (struct logging *) malloc (sizeof (struct logging)); log->logging = false; log->filename = NULL; signal (SIGINT, SISH_SIG_IGN); signal (SIGPIPE, SISH_SIG_IGN); signal (SIGCHLD, SISH_SIG_IGN); signal (SIGQUIT, SISH_EXIT); for ( opt_index = 0; (opt = getopt_long (argc, argv, "plc:P:", sish_long_option, &opt_index)) != -1; opt_index = 0 ) { switch ( opt ) { case 'P': if ( optarg ) config_file = strdup (optarg); break; case 'L': log->logging = true; if ( optarg ) log->filename = strdup (optarg); break; case 'c': if ( optarg ) command = strdup (optarg); break; case 'p': chk_config = false; break; } } if ( chk_config ) load_config_file (config_file); if ( config_file ) free (config_file); if ( command ) { get_and_do_command (command); free (command); return 0; } while ( quiet ) { fprintf (stdout, "%s@%s:%s%c ", user_info->username, user_info->hostname, user_info->cpath, geteuid () ? '$' : '#'); get_and_do_command (NULL); user_info->cpath = getcwd (NULL, 0); } return 0; } char **getpaths (char *str) { char *ptr; char **paths = NULL; int mem = 1; paths = (char **) malloc (mem * sizeof (char *)); if ( !(ptr = strtok (str, ":")) ) { paths[mem - 1] = NULL; return paths; } paths[mem - 1] = strdup (ptr); paths = (char **) realloc (paths, ++mem * sizeof (char *)); while ((ptr = strtok (NULL, ":"))) { paths[mem - 1] = strdup (ptr); paths = (char **) realloc (paths, ++mem * sizeof (char *)); } paths[mem - 1] = NULL; return paths; } void get_and_do_command (char *o_cmd) { char *cmd = NULL, **commands; int mem = 1, i, ncmd, c, status; bool chk = false; pid_t pid; if ( !o_cmd ) { cmd = (char *) malloc (mem * sizeof (char)); while ((cmd[mem - 1] = getchar ())) { if ( cmd[mem - 1] == 0x0A && !chk ) break; if ( cmd[mem - 1] == '"' || cmd[mem - 1] == '\'' ) chk = !chk; cmd = (char *) realloc (cmd, ++mem * sizeof (char)); if ( cmd[mem - 2] == 0x0A ) sish_err ("> "); } cmd[mem-1] = 0x00; commands = depure_command_string (cmd, &ncmd); free (cmd); } else commands = depure_command_string (o_cmd, &ncmd); if ( !*commands || !**commands) { free (commands); return; } for ( i = 0; alias[i].command; i++ ) { if ( !strcmp (alias[i].command, *commands) ) { free (*commands); *commands = strdup (alias[i].alias); cmd = cmds_join (commands, -1, -1); for ( c = 0; commands[c]; c++ ) free (commands[c]); free (commands); commands = depure_command_string (cmd, &ncmd); free (cmd); } } if ( !*commands || !**commands) { free (commands); return; } if ( internal_command (commands) ) { for ( i = 0; commands[i]; i++ ) free (commands[i]); free (commands); return; } if ( !search_program (&*commands) ) { sish_err ("sish: %s: comando non trovato\n", *commands); for ( i = 0; commands[i]; i++ ) free (commands[i]); free (commands); return; } if ( access (*commands, X_OK) ) { sish_err ("sish: %s: file non eseguibile\n", *commands); for ( i = 0; commands[i]; i++ ) free (commands[i]); free (commands); return; } if ( (pid = fork ()) < 0 ) { sish_err ("sish: impossibile eseguire il programma.\n"); for ( i = 0; commands[i]; i++ ) free (commands[i]); free (commands); return; } if ( !pid ) { sish_exec (&commands); exit (0); } else { if ( log->logging ) logging_command (commands); waitpid (pid, &status, 0); } for ( i = 0; commands[i]; i++ ) free (commands[i]); free (commands); } char **depure_command_string (char *_cstring, int *cnum) { char **cmds = NULL, *cstring; int mem = 1, len, i = 0; bool chk = false; cstring = remove_facoltative_space (_cstring); cmds = (char **) malloc ((*cnum = 1) * sizeof (char *)); len = strlen (cstring); if ( !*cstring ) { *cmds = NULL; *cnum = 0; return cmds; } do { if ( (cstring[i] == 0x20 || cstring[i] == 0x00 || cstring[i] == 0x0A) && !chk ) { if ( check_is_env_var (cmds[*cnum - 1]) ) { if ( !adjust_env_var (&cmds[*cnum - 1]) ) { free (cmds[*cnum - 1]); cmds = (char **) realloc (cmds, --(*cnum) * sizeof (char *)); } } cmds = (char **) realloc (cmds, ++(*cnum) * sizeof (char *)); mem = 1; continue; } if ( (cstring[i] == '"' || cstring[i] == '\'') ) { chk = !chk; continue; } if ( mem == 1 ) cmds[*cnum - 1] = (char *) malloc (mem * sizeof (char)); cmds[*cnum - 1][mem - 1] = cstring[i]; cmds[*cnum - 1] = (char *) realloc (cmds[*cnum - 1], ++mem * sizeof (char)); cmds[*cnum - 1][mem - 1] = 0x00; } while ( i++ < len ); cmds[*cnum - 1] = NULL; (*cnum)--; free (cstring); return cmds; } bool search_program (char **prg) { int i, len = strlen (*prg), plen; char *tmp; for ( i = 0; user_info->paths[i]; i++ ) { plen = 2 + len + strlen (user_info->paths[i]); tmp = (char *) malloc (plen * sizeof (char)); snprintf (tmp, plen, "%s/%s", user_info->paths[i], *prg); if ( !access (tmp, F_OK) ) { free (*prg); *prg = strdup (tmp); free (tmp); return true; } free (tmp); } if ( !access (*prg, F_OK) ) return true; return false; } bool check_internal_command (char *cmd) { int i; for ( i = 0; i < SISH_IN; i++ ) if ( !strcmp (cmd, internal[i]) ) return true; return false; } bool internal_command (char **cmds) { int i; for ( i = 0; i < SISH_IN; i++ ) { if ( !strcmp (*cmds, internal[i]) ) break; if ( i == (SISH_IN - 1) ) { i = -1; break; } } if ( i < 0 ) return false; switch ( i ) { case 0: case 1: sish_err ("exit.\n"); exit (0); break; case 2: if ( !cmds[1] ) { sish_err ("sish: argomento mancante.\n"); break; } for ( i = 0; i < SISH_SIN; i++ ) { if ( !strcmp (cmds[1], s_internal[i]) ) break; if ( i == (SISH_SIN - 1) ) { i = -1; break; } } if ( i < 0 ) { sish_err ("sish: argomento non valido.\n"); break; } switch ( i ) { case 0: printf ("SiSh ( Simple Shell ) è una piccolissima shell " "creata per puro divertimento e piccolo esercizio " "di HdS619. Può essere usata per piccoli compiti ma " "di certo il suo obiettivo non è quello di diventare " "una shell alternativa ad altre già esistenti.\n" "Divertitevi ;)\n"); break; case 1: load_config_file (cmds[2]); break; case 2: if ( !cmds[2] ) { sish_err ("sish: logging: argomento mancante.\n"); break; } if ( !strcmp (cmds[2], "on") ) { log->logging = true; if ( cmds[3] ) log->filename = strdup (cmds[3]); else { if ( log->filename ) free (log->filename); log->filename = NULL; } } else if ( !strcmp (cmds[2], "off") ) { log->logging = false; if ( log->filename ) free (log->filename); log->filename = NULL; } break; } break; case 3: if ( !cmds[1] ) { sish_err ("sish: cd: argomento mancante.\n"); break; } if ( chdir (cmds[1]) ) sish_err ("sish: cd: posizione non valida.\n"); break; case 4: if ( !cmds[1] ) { sish_err ("sish: alias: argomenti mancanti.\n"); break; } for ( i = 0; alias[i].command; i++ ) { if ( !strcmp (alias[i].command, cmds[1]) ) { free (alias[i].alias); alias[i].alias = strdup (cmds[2] ? cmds[2] : " "); i = -1; break; } } if ( i < 0 ) break; alias[i].command = strdup (cmds[1]); alias[i].alias = strdup (cmds[2] ? cmds[2] : " "); alias = (struct alias *) realloc (alias, (i + 2) * sizeof (struct alias)); alias[i+1].command = NULL; alias[i+1].alias = NULL; break; } return true; } bool check_is_env_var (char *str) { int i, len; bool chk = false; if ( !str ) return false; len = strlen (str); for ( i = 0; i < len; i++ ) { if ( str[i] == '=' && i > 0 && !chk) chk = true; if ( !isalnum (str[i]) && i > 0 && !chk && str[i] != '_' ) return false; } if ( (!chk && *str != '$') || ( *str == '$' && len < 2 ) ) return false; return true; } bool adjust_env_var (char **env_var) { char *evn, *evv; int mem = 1, len = strlen (*env_var), i; evn = (char *) malloc (mem * sizeof (char)); if ( **env_var == '$' ) { for ( i = 1; i < len && ( isalnum((*env_var)[i]) || (*env_var)[i] == '_' ); i++ ) { evn[mem - 1] = (*env_var)[i]; evn = (char *) realloc (evn, ++mem * sizeof (char)); } evn[mem - 1] = 0x00; free (*env_var); *env_var = strdup (getenv (evn) ? getenv (evn) : "\0"); free (evn); return **env_var ? true : false; } for ( i = 0; i < len && (*env_var)[i] != '='; i++ ) { evn[mem - 1] = (*env_var)[i]; evn = (char *) realloc (evn, ++mem * sizeof (char)); } evn[mem - 1] = 0x00; evv = (char *) malloc ((mem = 1) * sizeof (char)); for ( i++; i < len; i++ ) { evv[mem - 1] = (*env_var)[i]; evv = (char *) realloc (evv, ++mem * sizeof (char)); } evv[mem - 1] = 0x00; if ( !(*evv) ) unsetenv (evn); else setenv (evn, evv, 1); free (evv); free (evn); return false; } void load_config_file (char *file) { unsigned char *data; char *filename, *t; int len, i, c; bool chk = false; FILE *of; if ( !(t = getenv ("HOME")) ) { sish_err ("sish: impossibile caricare il file di configurazione.\n"); return; } if ( !file ) { len = strlen (t) + 14; filename = (char *) malloc (len * sizeof (char)); snprintf (filename, len, "%s/.sish-config", t); } else filename = strdup (file); if ( access (filename, F_OK | R_OK) ) { sish_err ("sish: impossibile caricare il file di configurazione.\n"); return; } if ( !(of = fopen (filename, "rb")) ) { sish_err ("sish: impossibile caricare il file di configurazione.\n"); return; } free (filename); fseek (of, 0, SEEK_END); data = (unsigned char *) malloc ((len = ftell (of)) * sizeof (unsigned char)); rewind (of); fread (data, sizeof (unsigned char), len, of); fclose (of); for ( i = c = 0; i < len; i++ ) { if ( data[i] == '"' || data[i] == '\'' ) chk = !chk; if ( (data[i] == 0x0A || i == (len - 1)) && !chk ) { t = strndup ((char *) data + c, i - c); c = i + 1; if ( *t != 0x0A ) get_and_do_command (t); free (t); } } if ( chk ) sish_err ("sish: file di configurazione caricato non correttamente: " "mancata chiusura di \" o '.\n"); free (data); } char *cmds_join (char **cmds, int start, int end) { int i, len = 0, n; char *str; str = (char *) malloc ((len = strlen (cmds[(start > 0) ? start : 0])) + 1); strncpy (str, cmds[(start > 0) ? start : 0], len); str[len] = ' '; for ( n = (start > 0) ? start + 1 : 1, i = len + 1; cmds[n]; n++, i += len ) { if ( end > 0 && !(n < end) ) break; str = (char *) realloc (str, (len = strlen (cmds[n])) + i + 1); strncpy (str + i, cmds[n], len); str[i + len] = ' '; i++; } str[i - 1] = 0x00; return str; } char *remove_facoltative_space (char *str) { int i, len = strlen (str ? str : ""), mem = 1; bool chk = false, s = false; char *l_str; l_str = (char *) malloc (mem * sizeof (char)); for ( i = 0; i < len; i++ ) { if ( str[i] == '"' || str[i] == '\'' ) { chk = true; s = false; } if ( str[i] == 0x20 && !i ) s = true; if ( str[i] == 0x20 && !s ) { l_str[mem - 1] = str[i]; l_str = (char *) realloc (l_str, ++mem * sizeof (char)); s = true; } if ( str[i] != 0x20 ) { l_str[mem - 1] = str[i]; l_str = (char *) realloc (l_str, ++mem * sizeof (char)); s = false; } } l_str [mem - ((l_str[mem - 2] == 0x20) ? 2 : 1)] = 0x00; return l_str; } void logging_command (char **commands) { char *t; int len; FILE *of; if ( !log->filename ) { t = getenv ("HOME"); len = strlen (t ? t : "") + 15; log->filename = (char *) malloc (len * sizeof (char)); snprintf (log->filename, len, "%s/.sish-history", t); } if ( !(of = fopen(log->filename, "a")) ) { sish_err ("sish: logging: impossibile aprire il file `%s' di log.\n", log->filename); return; } for (len = 0; commands[len]; len++ ) fprintf (of, "%s ", commands[len]); putc (0x0A, of); fclose (of); return; } void sish_exec (char ***commands) { int i, chk = 0; i = pipe_action (*commands, &chk); switch ( chk ) { case 0: execv (**commands, *commands); break; case 1: process_or (commands, i); break; case 2: process_and (commands, i); break; case 3: redirect_output (commands, i); break; case 4: redirect_input (commands, i); break; } } int pipe_action (char **commands, int *chk) { int i; *chk = 0; for ( i = 0; commands[i]; i++ ) { if ( !strcmp (commands[i], "|") ) *chk = 1; if ( !strcmp (commands[i], "&") ) *chk = 2; if ( !strcmp (commands[i], ">") ) *chk = 3; if ( !strcmp (commands[i], "<") ) *chk = 4; if ( *chk ) break; } return i; } void redirect_input (char ***commands, int _i) { int len, c, i = _i; char *t; unsigned char *data = NULL; FILE *in, *out; if ( !(*commands)[i + 1] ) { sish_err ("sish: redirect dell'input non valido: " "nessun input specificato.\n"); return; } if ( access ((*commands)[i + 1], F_OK) ) { sish_err ("sish: il file `%s' passato come input non esiste.\n", (*commands)[i + 1]); return; } if ( !(in = fopen ((*commands)[i + 1], "rb")) ) { sish_err ("sish: impossibile aprire il file `%s' per la lettura.\n", (*commands)[i + 1]); return; } fseek (in, 0, SEEK_END); data = (unsigned char *) malloc ((len = ftell (in)) * sizeof (unsigned char)); rewind (in); fread (data, sizeof (unsigned char), len, in); fclose (in); free ((*commands)[i]); (*commands)[i] = NULL; free ((*commands)[i + 1]); (*commands)[i + 1] = NULL; for ( c = i + 2; (*commands)[c]; c++ ) { (*commands)[i++] = strdup ((*commands)[c]); free ((*commands)[c]); (*commands)[c] = NULL; } t = cmds_join (*commands, -1, -1); if ( !(out = popen (t, "w")) ) { sish_err ("sish: impossibile aprire la pipe in scrittura.\n"); free (t); free (data); return; } free (t); for ( i = 0; i < len; i++ ) putc ((int) data[i], out); pclose (out); free (data); } void redirect_output (char ***commands, int _i) { char *t; int chk, c, i = _i; FILE *in, *out; if ( !(*commands)[i + 1] ) { sish_err ("sish: redirect dell'output non valido: " "nessun output specificato.\n"); return; } if ( !access ((*commands)[i + 1], F_OK) ) { sish_err ("sish: il file scelto in cui reindirizzare l'output esiste " "già . Quale azione eseguire [(A)NNULLA/(S)ovrascrivi] ? "); chk = getchar (); while (getchar () != 0x0A); if ( chk != 'S' && chk != 's' ) return; } if ( !(out = fopen ((*commands)[i + 1], "w")) ) { sish_err ("sish: impossibile aprire il file `%s' per la scrittura.\n", (*commands)[i + 1]); return; } free ((*commands)[i]); (*commands)[i] = NULL; free ((*commands)[i + 1]); (*commands)[i + 1] = NULL; for ( c = i + 2; (*commands)[c]; c++ ) { (*commands)[i++] = strdup ((*commands)[c]); free ((*commands)[c]); (*commands)[c] = NULL; } t = cmds_join (*commands, -1, -1); if ( !(in = popen (t, "r")) ) { sish_err ("sish: impossibile aprire la pipe in lettura.\n"); free (t); fclose (out); return; } free (t); while ((c = getc (in)) != EOF) putc (c, out); pclose (in); fclose (out); } void process_or (char ***commands, int _i) { int c, r, s, i = _i; char *t, **cmds; unsigned char *data; FILE *in, *out; t = cmds_join (*commands, -1, i); if ( !(in = popen (t, "r")) ) { sish_err ("sish: impossibile aprire la pipe in lettura.\n"); free (t); return; } free (t); data = (unsigned char *) malloc ((c = 1) * sizeof (unsigned char)); while ((r = getc (in)) != EOF) { data[c - 1] = (unsigned char) r; data = (unsigned char *) realloc (data, ++c * sizeof (unsigned char)); } data[c - 1] = 0x00; pclose (in); if ( !(*commands)[i + 1] ) { sish_err ("sish: pipe rotta.\n"); free (data); return; } for ( r = 0; alias[r].command; r++ ) { if ( !strcmp (alias[r].command, (*commands)[i + 1]) ) { free ((*commands)[i + 1]); (*commands)[i + 1] = strdup (alias[r].alias); t = cmds_join (*commands, i + 1, -1); for ( s = i + 1; (*commands)[s]; s++ ) free ((*commands)[s]); cmds = depure_command_string (t, &s); free (t); (*commands) = (char **) realloc (*commands, (s + i + 1) * sizeof (char *)); for ( s = 0; cmds[s]; s++ ) { (*commands)[s + i + 1] = strdup (cmds[s]); free (cmds[s]); } free (cmds); } } if ( check_internal_command ((*commands)[i + 1]) ) { sish_err ("sish: impossibile aprire una pipe con un comando interno.\n"); free (data); return; } if ( !search_program (&((*commands)[i + 1])) ) { sish_err ("sish: %s: comando non trovato\n", (*commands)[i + 1]); free (data); return; } t = cmds_join ((*commands), i + 1, -1); if ( !(out = popen (t, "w")) ) { sish_err ("sish: impossibile aprire la pipe in scrittura.\n"); free (t); free (data); return; } free (t); for ( i = 0, --c; i < c; i++ ) putc ((int) data[i], out); pclose (out); free (data); } void process_and (char ***commands, int i) { int c, status; pid_t pid, subp; for ( c = i; (*commands)[c]; c++ ) free ((*commands)[c]); (*commands)[i] = NULL; if ( (pid = fork ()) < 0 ) { sish_err ("sish: impossibile eseguire il programma.\n"); return; } if ( !pid ) { if ( (subp = fork ()) < 0 ) { sish_err ("sish: impossibile eseguire il programma.\n"); exit (0); } if ( !subp ) { execv (**commands, *commands); exit (0); } else { waitpid (subp, &status, 0); sish_err ("\nsish: %s: esecuzione terminata.", **commands); exit (0); } } }