/*
*
* Instantatiate a new terminal shell.
*
* Author:
*
* WORK: fernando.ruiz@ctv.es
* HOME: correo@fernando-ruiz.com
*
* Thanks at:
* Chris John
*
* $Id$
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <rtems.h>
#include <rtems/error.h>
#include <rtems/libio.h>
#include <rtems/libio_.h>
#include <rtems/system.h>
#include <rtems/shell.h>
#include <termios.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <pwd.h>
/* ----------------------------------------------- *
* This is a stupidity but is cute.
* ----------------------------------------------- */
rtems_unsigned32 new_rtems_name(char * rtems_name) {
static char b[5];
sprintf(b,"%-4.4s",rtems_name);
return rtems_build_name(b[0],b[1],b[2],b[3]);
}
/* **************************************************************
* common linked list of shell commands.
* Because the help report is very long
* I have a topic for each command.
* Help list the topics
* help [topic] list the commands for the topic
* help [command] help for the command
* Can you see help rtems monitor report?
* ************************************************************** */
struct shell_topic_tt;
typedef struct shell_topic_tt shell_topic_t;
struct shell_topic_tt {
char * topic;
shell_topic_t * next;
};
static shell_cmd_t * shell_first_cmd;
static shell_topic_t * shell_first_topic;
/* ----------------------------------------------- *
* Using Chain I can reuse the rtems code.
* I am more comfortable with this, sorry.
* ----------------------------------------------- */
shell_topic_t * shell_lookup_topic(char * topic) {
shell_topic_t * shell_topic;
shell_topic=shell_first_topic;
while (shell_topic) {
if (!strcmp(shell_topic->topic,topic)) return shell_topic;
shell_topic=shell_topic->next;
};
return (shell_topic_t *) NULL;
}
/* ----------------------------------------------- */
shell_topic_t * shell_add_topic(char * topic) {
shell_topic_t * current,*aux;
if (!shell_first_topic) {
aux=malloc(sizeof(shell_topic_t));
aux->topic=topic;
aux->next=(shell_topic_t*)NULL;
return shell_first_topic=aux;
} else {
current=shell_first_topic;
if (!strcmp(topic,current->topic)) return current;
while (current->next) {
if (!strcmp(topic,current->next->topic)) return current->next;
current=current->next;
};
aux=malloc(sizeof(shell_topic_t));
aux->topic=topic;
aux->next=(shell_topic_t*)NULL;
current->next=aux;
return aux;
};
}
/* ----------------------------------------------- */
shell_cmd_t * shell_lookup_cmd(char * cmd) {
shell_cmd_t * shell_cmd;
shell_cmd=shell_first_cmd;
while (shell_cmd) {
if (!strcmp(shell_cmd->name,cmd)) return shell_cmd;
shell_cmd=shell_cmd->next;
};
return (shell_cmd_t *) NULL;
}
/* ----------------------------------------------- */
shell_cmd_t * shell_add_cmd(char * cmd,
char * topic,
char * usage,
shell_command_t command) {
int shell_help(int argc,char * argv[]);
shell_cmd_t * shell_cmd,*shell_pvt;
if (!shell_first_cmd) {
shell_first_cmd=(shell_cmd_t *) malloc(sizeof(shell_cmd_t));
shell_first_cmd->name ="help";
shell_first_cmd->topic ="help";
shell_first_cmd->usage ="help [topic] # list of usage of commands";
shell_first_cmd->command=shell_help;
shell_first_cmd->alias =(shell_cmd_t *) NULL;
shell_first_cmd->next =(shell_cmd_t *) NULL;
shell_add_topic(shell_first_cmd->topic);
register_cmds();
};
if (!cmd) return (shell_cmd_t *) NULL;
if (!command) return (shell_cmd_t *) NULL;
shell_cmd=(shell_cmd_t *) malloc(sizeof(shell_cmd_t));
shell_cmd->name =cmd;
shell_cmd->topic =topic;
shell_cmd->usage =usage;
shell_cmd->command=command;
shell_cmd->alias =(shell_cmd_t *) NULL;
shell_cmd->next =(shell_cmd_t *) NULL;
shell_add_topic(shell_cmd->topic);
shell_pvt=shell_first_cmd;
while (shell_pvt->next) shell_pvt=shell_pvt->next;
return shell_pvt->next=shell_cmd;
}
/* ----------------------------------------------- *
* you can make an alias for every command.
* ----------------------------------------------- */
shell_cmd_t * shell_alias_cmd(char * cmd, char * alias) {
shell_cmd_t * shell_cmd,* shell_aux;
shell_aux=(shell_cmd_t *) NULL;
if (alias) {
if ((shell_aux=shell_lookup_cmd(alias))!=NULL) {
return NULL;
};
if ((shell_cmd=shell_lookup_cmd(cmd))!=NULL) {
shell_aux=shell_add_cmd(alias,shell_cmd->topic,
shell_cmd->usage,shell_cmd->command);
if (shell_aux) shell_aux->alias=shell_cmd;
};
};
return shell_aux;
}
/* ----------------------------------------------- *
* Poor but enough..
* TODO: Redirection capture. "" evaluate, ... C&S welcome.
* ----------------------------------------------- */
int shell_make_args(char * cmd,
int * pargc,
char * argv[]) {
int argc=0;
while ((cmd=strtok(cmd," \t\r\n"))!=NULL) {
argv[argc++]=cmd;
cmd=(char*)NULL;
};
argv[argc]=(char*)NULL;
return *pargc=argc;
}
/* ----------------------------------------------- *
* show the help for one command.
* ----------------------------------------------- */
int shell_help_cmd(shell_cmd_t * shell_cmd) {
char * pc;
int col,line;
printf("%-10.10s -",shell_cmd->name);
col=12;
line=1;
if (shell_cmd->alias) {
printf("is an <alias> for command '%s'",shell_cmd->alias->name);
} else
if (shell_cmd->usage) {
pc=shell_cmd->usage;
while (*pc) {
switch(*pc) {
case '\r':break;
case '\n':putchar('\n');
col=0;
break;
default :putchar(*pc);
col++;
break;
};
pc++;
if(col>78) { /* What daring... 78?*/
if (*pc) {
putchar('\n');
col=0;
};
};
if (!col && *pc) {
printf(" ");
col=12;line++;
};
};
};
puts("");
return line;
}
/* ----------------------------------------------- *
* show the help. The first command implemented.
* Can you see the header of routine? Known?
* The same with all the commands....
* ----------------------------------------------- */
int shell_help(int argc,char * argv[]) {
int col,line,arg;
shell_topic_t *topic;
shell_cmd_t * shell_cmd=shell_first_cmd;
if (argc<2) {
printf("help: ('r' repeat last cmd - 'e' edit last cmd)\n"
" TOPIC? The topics are\n");
topic=shell_first_topic;
col=0;
while (topic) {
if (!col){
col=printf(" %s",topic->topic);
} else {
if ((col+strlen(topic->topic)+2)>78){
printf("\n");
col=printf(" %s",topic->topic);
} else {
col+=printf(", %s",topic->topic);
};
};
topic=topic->next;
};
printf("\n");
return 1;
};
line=0;
for (arg=1;arg<argc;arg++) {
if (line>16) {
printf("Press any key to continue...");getchar();
printf("\n");
line=0;
};
topic=shell_lookup_topic(argv[arg]);
if (!topic){
if ((shell_cmd=shell_lookup_cmd(argv[arg]))==NULL) {
printf("help: topic or cmd '%s' not found. Try <help> alone for a list\n",argv[arg]);
line++;
} else {
line+=shell_help_cmd(shell_cmd);
}
continue;
};
printf("help: list for the topic '%s'\n",argv[arg]);
line++;
while (shell_cmd) {
if (!strcmp(topic->topic,shell_cmd->topic))
line+=shell_help_cmd(shell_cmd);
if (line>16) {
printf("Press any key to continue...");getchar();
printf("\n");
line=0;
};
shell_cmd=shell_cmd->next;
};
};
puts("");
return 0;
}
/* ----------------------------------------------- *
* TODO: Add improvements. History, edit vi or emacs, ...
* ----------------------------------------------- */
int shell_scanline(char * line,int size,FILE * in,FILE * out) {
int c,col;
col=0;
if (*line) {
col=strlen(line);
if (out) fprintf(out,"%s",line);
};
tcdrain(fileno(in ));
if (out) tcdrain(fileno(out));
for (;;) {
line[col]=0;
c=fgetc(in);
switch (c) {
case 0x04:/*Control-d*/
if (col) break;
case EOF :return 0;
case '\n':break;
case '\f':if (out) fputc('\f',out);
case 0x03:/*Control-C*/
line[0]=0;
case '\r':if (out) fputc('\n',out);
return 1;
case 127:
case '\b':if (col) {
if (out) {
fputc('\b',out);
fputc(' ',out);
fputc('\b',out);
};
col--;
} else {
if (out) fputc('\a',out);
};
break;
default :if (!iscntrl(c)) {
if (col<size-1) {
line[col++]=c;
if (out) fputc(c,out);
} else {
if (out) fputc('\a',out);
};
} else {
if (out)
if (c=='\a') fputc('\a',out);
};
break;
};
};
}
/* ----------------------------------------------- *
* - The shell TASK
* Poor but enough..
* TODO: Redirection. Tty Signals. ENVVARs. Shell language.
* ----------------------------------------------- */
shell_env_t global_shell_env ,
* current_shell_env=&global_shell_env;
extern char **environ;
void cat_file(FILE * out,char * name) {
FILE * fd;
int c;
if (out) {
fd=fopen(name,"r");
if (fd) {
while ((c=fgetc(fd))!=EOF) fputc(c,out);
fclose(fd);
};
};
}
void write_file(char * name,char * content) {
FILE * fd;
fd=fopen(name,"w");
if (fd) {
fwrite(content,1,strlen(content),fd);
fclose(fd);
};
}
void init_issue(void) {
static char issue_inited=FALSE;
struct stat buf;
if (issue_inited) return;
issue_inited=TRUE;
getpwnam("root"); /* dummy call to init /etc dir */
if (stat("/etc/issue",&buf))
write_file("/etc/issue",
"Welcome to @V\\n"
"Login into @S(@L)\\n");
if (stat("/etc/issue.net",&buf))
write_file("/etc/issue.net",
"Welcome to %v\n"
"running on %m\n");
}
int shell_login(FILE * in,FILE * out) {
FILE * fd;
int c;
time_t t;
int times;
char name[128];
char pass[128];
struct passwd * passwd;
init_issue();
setuid(0);
setgid(0);
rtems_current_user_env->euid=
rtems_current_user_env->egid=0;
if (out) {
if((current_shell_env->devname[5]!='p')||
(current_shell_env->devname[6]!='t')||
(current_shell_env->devname[7]!='y')) {
fd=fopen("/etc/issue","r");
if (fd) {
while ((c=fgetc(fd))!=EOF) {
if (c=='@') {
switch(c=fgetc(fd)) {
case 'L':fprintf(out,"%s",current_shell_env->devname);
break;
case 'B':fprintf(out,"0");
break;
case 'T':
case 'D':time(&t);
fprintf(out,"%s",ctime(&t));
break;
case 'S':fprintf(out,"RTEMS");
break;
case 'V':fprintf(out,"%s\n%s",_RTEMS_version,_Copyright_Notice);
break;
case '@':fprintf(out,"@");
break;
default :fprintf(out,"@%c",c);
break;
};
} else
if (c=='\\') {
switch(c=fgetc(fd)) {
case '\\':fprintf(out,"\\");
break;
case 'b':fprintf(out,"\b"); break;
case 'f':fprintf(out,"\f"); break;
case 'n':fprintf(out,"\n"); break;
case 'r':fprintf(out,"\r"); break;
case 's':fprintf(out," "); break;
case 't':fprintf(out,"\t"); break;
case '@':fprintf(out,"@"); break;
};
} else {
fputc(c,out);
};
};
fclose(fd);
}
} else {
fd=fopen("/etc/issue.net","r");
if (fd) {
while ((c=fgetc(fd))!=EOF) {
if (c=='%') {
switch(c=fgetc(fd)) {
case 't':fprintf(out,"%s",current_shell_env->devname);
break;
case 'h':fprintf(out,"0");
break;
case 'D':fprintf(out," ");
break;
case 'd':time(&t);
fprintf(out,"%s",ctime(&t));
break;
case 's':fprintf(out,"RTEMS");
break;
case 'm':fprintf(out,"(" CPU_NAME "/" CPU_MODEL_NAME ")");
break;
case 'r':fprintf(out,_RTEMS_version);
break;
case 'v':fprintf(out,"%s\n%s",_RTEMS_version,_Copyright_Notice);
break;
case '%':fprintf(out,"%%");
break;
default :fprintf(out,"%%%c",c);
break;
};
} else {
fputc(c,out);
};
};
fclose(fd);
}
};
};
times=0;
strcpy(name,"");
strcpy(pass,"");
for (;;) {
times++;
if (times>3) break;
if (out) fprintf(out,"\nlogin: ");
if (!shell_scanline(name,sizeof(name),in,out )) break;
if (out) fprintf(out,"Password: ");
if (!shell_scanline(pass,sizeof(pass),in,NULL)) break;
if (out) fprintf(out,"\n");
if ((passwd=getpwnam(name))) {
if (strcmp(passwd->pw_passwd,"!")) { /* valid user */
setuid(passwd->pw_uid);
setgid(passwd->pw_gid);
rtems_current_user_env->euid=
rtems_current_user_env->egid=0;
chown(current_shell_env->devname,passwd->pw_uid,0);
rtems_current_user_env->euid=passwd->pw_uid;
rtems_current_user_env->egid=passwd->pw_gid;
if (!strcmp(passwd->pw_passwd,"*")) {
/* /etc/shadow */
return 0;
} else {
/* crypt() */
return 0;
};
};
};
if (out) fprintf(out,"Login incorrect\n");
strcpy(name,"");
strcpy(pass,"");
};
return -1;
}
rtems_task shell_shell(rtems_task_argument task_argument) {
shell_env_t * shell_env =(shell_env_t*) task_argument;
shell_cmd_t * shell_cmd;
rtems_status_code sc;
struct termios term;
char * devname;
char curdir[256];
char cmd[256];
char last_cmd[256]; /* to repeat 'r' */
int argc;
char * argv[128];
sc=rtems_task_variable_add(RTEMS_SELF,(void*)¤t_shell_env,free);
if (sc!=RTEMS_SUCCESSFUL) {
rtems_error(sc,"rtems_task_variable_add(current_shell_env):");
rtems_task_delete(RTEMS_SELF);
};
current_shell_env=shell_env;
sc=rtems_libio_set_private_env();
if (sc!=RTEMS_SUCCESSFUL) {
rtems_error(sc,"rtems_libio_set_private_env():");
rtems_task_delete(RTEMS_SELF);
};
devname=shell_env->devname;
setuid(0);
setgid(0);
rtems_current_user_env->euid=
rtems_current_user_env->egid=0;
stdin =fopen(devname,"r+");
if (!stdin) {
fprintf(stderr,"shell:unable to open stdin.%s:%s\n",devname,strerror(errno));
rtems_task_delete(RTEMS_SELF);
};
setvbuf(stdin,NULL,_IONBF,0); /* Not buffered*/
/* make a raw terminal,Linux MANuals */
if (tcgetattr (fileno(stdin), &term)>=0) {
term.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
term.c_oflag &= ~OPOST;
term.c_oflag |= (OPOST|ONLCR); /* But with cr+nl on output */
term.c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
term.c_cflag = CLOCAL | CREAD |(shell_env->tcflag);
term.c_cc[VMIN] = 1;
term.c_cc[VTIME] = 0;
if (tcsetattr (fileno(stdin), TCSADRAIN, &term) < 0) {
fprintf(stderr,"shell:cannot set terminal attributes(%s)\n",devname);
};
stdout=fopen(devname,"r+");
if (!stdout) {
fprintf(stderr,"shell:unable to open stdout.%s:%s\n",devname,strerror(errno));
};
setvbuf(stdout,NULL,_IONBF,0); /* Not buffered*/
stderr=fopen(devname,"r+");
if (!stderr) {
printf("shell:unable to open stderr.%s:%s\n",devname,strerror(errno));
};
/* when the future user environment runs ok
* a freopen() reopens the terminals. Now this don't work
* (sorry but you can't use because FILENO_STDIN!=0. Better fileno(stdin))
*/
};
shell_add_cmd(NULL,NULL,NULL,NULL); /* init the chain list*/
do {
/* Set again root user and root filesystem, side effect of set_priv..*/
sc=rtems_libio_set_private_env();
if (sc!=RTEMS_SUCCESSFUL) {
rtems_error(sc,"rtems_libio_set_private_env():");
rtems_task_delete(RTEMS_SELF);
};
if (!shell_login(stdin,stdout)) {
cat_file(stdout,"/etc/motd");
strcpy(last_cmd,"");
strcpy(cmd,"");
printf("\n"
"RTEMS SHELL (Ver.1.0-FRC):%s. "__DATE__". 'help' to list commands.\n",devname);
chdir("/"); /* XXX: chdir to getpwent homedir */
shell_env->exit_shell=FALSE;
for (;;) {
/* Prompt section */
/* XXX: show_prompt user adjustable */
getcwd(curdir,sizeof(curdir));
printf("%s [%s] %c ",shell_env->taskname,curdir,geteuid()?'$':'#');
/* getcmd section */
if (!shell_scanline(cmd,sizeof(cmd),stdin,stdout)) break; /*EOF*/
/* evaluate cmd section */
if (!strcmp(cmd,"e")) { /* edit last command */
strcpy(cmd,last_cmd);
continue;
} else
if (!strcmp(cmd,"r")) { /* repeat last command */
strcpy(cmd,last_cmd);
} else
if (strcmp(cmd,"")) { /* only for get a new prompt */
strcpy(last_cmd,cmd);
};
/* exec cmd section */
/* TODO:
* To avoid user crash catch the signals.
* Open a new stdio files with posibility of redirection *
* Run in a new shell task background. (unix &)
* Resuming. A little bash.
*/
if (shell_make_args(cmd,&argc,argv)) {
if ((shell_cmd=shell_lookup_cmd(argv[0]))!=NULL) {
shell_env->errorlevel=shell_cmd->command(argc,argv);
} else {
printf("shell:%s command not found\n",argv[0]);
shell_env->errorlevel=-1;
};
};
/* end exec cmd section */
if (shell_env->exit_shell) break;
cmd[0]=0;
};
printf("\nGoodbye from RTEMS SHELL :-(\n");
};
} while (shell_env->forever);
fclose(stdin );
fclose(stdout);
fclose(stderr);
rtems_task_delete(RTEMS_SELF);
}
/* ----------------------------------------------- */
rtems_status_code shell_init (char * task_name,
rtems_unsigned32 task_stacksize,
rtems_task_priority task_priority,
char * devname,
tcflag_t tcflag,
int forever) {
rtems_id task_id;
rtems_status_code sc;
shell_env_t * shell_env;
sc=rtems_task_create(new_rtems_name(task_name),
task_priority,
task_stacksize?task_stacksize:RTEMS_MINIMUM_STACK_SIZE,
RTEMS_DEFAULT_MODES,
RTEMS_DEFAULT_ATTRIBUTES,
&task_id);
if (sc!=RTEMS_SUCCESSFUL) {
rtems_error(sc,"creating task %s in shell_init()",task_name);
return sc;
};
shell_env=malloc(sizeof(shell_env_t));
if (!shell_env) {
rtems_task_delete(task_id);
sc=RTEMS_NO_MEMORY;
rtems_error(sc,"allocating shell_env %s in shell_init()",task_name);
return sc;
};
if (global_shell_env.magic!=new_rtems_name("SENV")) {
global_shell_env.magic =new_rtems_name("SENV");
global_shell_env.devname ="/dev/console";
global_shell_env.taskname ="GLOBAL";
global_shell_env.tcflag =0;
global_shell_env.exit_shell=0;
global_shell_env.forever =TRUE;
};
shell_env->magic =global_shell_env.magic;
shell_env->devname =devname;
shell_env->taskname =task_name;
shell_env->tcflag =tcflag;
shell_env->exit_shell=FALSE;
shell_env->forever =forever;
return rtems_task_start(task_id,shell_shell,(rtems_task_argument) shell_env);
}