Tried to fix whole execution system (Shell, Unnamed Pipes)
[meinos.git] / apps / sh / main.c
blob138f1bd8caaf2c4fb7ad707f4bbb4fb6b6d2795a
1 /*
2 meinOS - A unix-like x86 microkernel operating system
3 Copyright (C) 2008 Janosch Gräf <janosch.graef@gmx.net>
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <sys/wait.h>
22 #include <string.h>
23 #include <stdlib.h>
24 #include <unistd.h>
25 #include <stdio.h>
26 #include <sys/utsname.h>
27 #include <pwd.h>
28 #include <dirent.h>
29 #include <errno.h>
30 #include <llist.h>
31 #include <misc.h>
33 #include <readline/readline.h>
34 #include <readline/history.h>
36 #define TERMINAL_DEVICE "/dev/console"
38 typedef struct {
39 const char *cmd;
40 int (*func)(char **argv);
41 } shell_builtin_cmd_t;
43 typedef struct {
44 pid_t pid;
45 char *path;
46 char **argv;
47 char *stdin;
48 char *stdout;
49 char *stderr;
50 } shell_job_t;
52 struct utsname utsname;
53 struct passwd *passwd;
54 llist_t joblist;
56 static void usage(char *cmd,int err) {
57 FILE *out = err?stderr:stdout;
58 fprintf(out,"Usage: %s\n");
59 exit(err);
62 /**
63 * Gets command from shell
64 * @return Command line
66 static char *shell_get_command() {
67 char *cwd = getcwd(NULL,0);
68 char *prompt;
69 char *input;
71 asprintf(&prompt,"%s@%s:%s> ",passwd!=NULL?passwd->pw_name:"nobody",utsname.nodename,cwd);
72 input = readline(prompt);
73 if (input!=NULL) {
74 if (input[0]!=0) add_history(input);
76 else return NULL;
78 free(prompt);
79 free(cwd);
80 return input;
83 /**
84 * Converts a string with an octal number to an integer
85 * @param string String holding octal number
86 * @return String as integer
88 static int octal2num(char *str) {
89 char buf[4];
90 memcpy(buf,str,3);
91 buf[3] = 0;
92 int num = strtoul(buf,NULL,8);
93 return num;
96 /**
97 * Parses an command line argument
98 * @param arg Command line argument
99 * @param len Length of argument
100 * @return Parse arguments
102 static char *shell_parse_arg(char *arg,size_t len) {
103 char *new = malloc(len+1);
104 size_t i,j;
106 for (i=0,j=0;i<len;i++,j++) {
107 if (arg[i]=='\\' && i+1<len) { // escape code
108 i++;
109 if (arg[i]=='\\') new[j] = '\\'; // backslash
110 else if (arg[i]=='\"') new[j] = '\"'; // quotation mark
111 else if (arg[i]=='a') new[j] = '\a'; // alert
112 else if (arg[i]=='b') new[j] = '\b'; // backspace
113 else if (arg[i]=='f') new[j] = '\f'; // form feed
114 else if (arg[i]=='n') new[j] = '\n'; // new line
115 else if (arg[i]=='r') new[j] = '\r'; // carriage return
116 else if (arg[i]=='t') new[j] = '\t'; // horizontal tab
117 else if (arg[i]=='v') new[j] = '\v'; // vertical tab
118 else if (arg[i]=='0' && arg[i+1]!=0 && arg[i+2]!=0 && arg[i+3]!=0) {
119 // character in octal
120 new[j] = octal2num(arg+i+1);
123 else new[j] = arg[i];
126 new[j] = 0;
128 return new;
132 * Parses a command line string
133 * @param cmd Command line string
134 * @return Argument vector
136 static char **shell_parse_cmd(char *cmd) {
137 int quote = 0;
138 char *cur = cmd;
139 char *next;
140 char **argv = NULL;
141 int argc = 0;
143 do {
144 // skip spaces
145 while (*cur==' ') cur++;
146 if (*cur==0) break;
148 // find next space (or quotation mark)
149 next = strchr(cur,quote?'\"':' ');
150 if (next==NULL) next = cmd+strlen(cmd);
152 // if next character is a quotation mark, activate qoutation mode
153 //if (*next=='\"') qoute = 1;
155 // add current to argv
156 argv = realloc(argv,(argc+1)*sizeof(char*));
157 argv[argc++] = shell_parse_arg(cur,next-cur);
158 cur = next+1;
159 } while (*next!=0);
161 if (argc>0) {
162 argv = realloc(argv,(argc+1)*sizeof(char*));
163 argv[argc] = NULL;
164 return argv;
166 else return NULL;
169 static int shell_builtin_exit(char **argv) {
170 return 1;
173 static int shell_builtin_help(char **argv) {
174 printf("Built-in commands:\n"
175 " exit Exit shell\n"
176 " cd DIR Change working directory to DIR\n"
177 " ls [DIR] List content of DIR\n"
178 " help Show this help dialog\n"
179 " version Show version\n");
180 return 0;
183 static int shell_builtin_version(char **argv) {
184 printf("%s %s\n",utsname.sysname,utsname.version);
185 return 0;
188 static int shell_builtin_cd(char **argv) {
189 if (argv[1]!=NULL) {
190 if (chdir(argv[1])==-1) fprintf(stderr,"sh: cd: %s: %s\n",argv[1],strerror(errno));
192 return 0;
195 static char ls_filetype(mode_t mode) {
196 if (S_ISBLK(mode)) return 'b';
197 else if (S_ISCHR(mode)) return 'c';
198 else if (S_ISDIR(mode)) return 'd';
199 else if (S_ISFIFO(mode)) return 'f';
200 else if (S_ISLNK(mode)) return 'l';
201 else if (S_ISSOCK(mode)) return 's';
202 else return '-';
205 static char *ls_perm(mode_t mode) {
206 static char buf[10];
207 snprintf(buf,10,"%c%c%c%c%c%c%c%c%c",mode&S_IRUSR?'r':'-',mode&S_IWUSR?'w':'-',mode&S_IXUSR?'x':'-',mode&S_IRGRP?'r':'-',mode&S_IWGRP?'w':'-',mode&S_IXGRP?'x':'-',mode&S_IROTH?'r':'-',mode&S_IWOTH?'w':'-',mode&S_IXOTH?'x':'-');
208 return buf;
211 static int shell_builtin_ls(char **argv) {
212 char *path = argv[1]==NULL?".":argv[1];
214 DIR *dir = opendir(path);
215 if (dir!=NULL) {
216 struct dirent *dirent;
217 do {
218 dirent = readdir(dir);
219 if (dirent!=NULL) {
220 if (strcmp(dirent->d_name,".")!=0 && strcmp(dirent->d_name,"..")!=0) {
221 struct stat stbuf;
222 struct passwd *owner;
223 char *file;
224 asprintf(&file,"%s/%s",path,dirent->d_name);
225 if (stat(file,&stbuf)==-1) printf("Error (%d): %s\n",errno,strerror(errno));
226 owner = getpwuid(stbuf.st_uid);
227 printf("%c%s %s % 5d %s\n",ls_filetype(stbuf.st_mode),ls_perm(stbuf.st_mode),owner!=NULL?owner->pw_name:"root",stbuf.st_size,dirent->d_name);
230 } while (dirent!=NULL);
231 return 0;
233 else return 1;
236 int shell_builtin_cat(char **argv) {
237 FILE *in;
239 if (argv[1]==NULL) in = stdin;
240 else {
241 in = fopen(argv[1],"r");
242 if (in==NULL) return 1;
245 while (!feof(in)) {
246 char buf[512];
247 size_t size = fread(buf,1,512,in);
248 fwrite(buf,1,size,stdout);
251 return 0;
254 /*int shell_builtin_echo(char **argv) {
255 size_t i;
256 for (i=1;argv[i]!=NULL;i++) printf("%s%s",argv[i],argv[i+1]!=NULL?" ":"");
257 putchar('\n');
258 return 0;
261 int shell_builtin_test(char **argv) {
262 char buf[32] = {0};
263 ssize_t n;
265 printf("Creating FIFO...\n");
266 mknod("foobar",S_IFIFO|0755,0);
268 printf("Opening end A (w)...\n");
269 FILE *enda = fopen("foobar","w");
270 if (enda==NULL) return 1;
272 printf("Opening end B (r)...\n");
273 FILE *endb = fopen("foobar","r");
274 if (endb==NULL) return 1;
276 printf("Opening end C (r)...\n");
277 FILE *endc = fopen("foobar","r");
278 if (endc==NULL) return 1;
280 printf("Opening end D (w)...\n");
281 FILE *endd = fopen("foobar","w");
282 if (endd==NULL) return 1;
284 printf("Writing to A...\n");
285 n = fputs("Hello World\n",enda);
286 if (n==-1) return 1;
287 printf("Written %d bytes\n",n);
289 printf("Reading from B...\n");
290 if (fgets(buf,32,endb)==NULL) return 1;
291 printf("\"%s\"\n",buf);
293 printf("Reading from C...\n");
294 if (fgets(buf,32,endc)==NULL) return 1;
295 printf("\"%s\"\n",buf);
297 printf("Writing to D...\n");
298 n = fputs("Hallo Welt\n",endd);
299 if (n==-1) return 1;
300 printf("Written %d bytes\n",n);
302 printf("Reading from B...\n");
303 if (fgets(buf,32,endb)==NULL) return 1;
304 printf("\"%s\"\n",buf);
306 printf("Reading from C...\n");
307 if (fgets(buf,32,endc)==NULL) return 1;
308 printf("\"%s\"\n",buf);
310 return 0;
313 /// @todo Sourcecode von utils/echo.c cat.c (und ls.c) hierher und main() in shell_builtin_* umbennen.
314 static int shell_run_builtin(char **argv) {
315 shell_builtin_cmd_t shell_builtin_cmds[] = {
316 { .cmd = "exit", .func = shell_builtin_exit },
317 { .cmd = "help", .func = shell_builtin_help },
318 { .cmd = "version", .func = shell_builtin_version },
319 { .cmd = "cd", .func = shell_builtin_cd },
320 { .cmd = "ls", .func = shell_builtin_ls },
321 { .cmd = "cat", .func = shell_builtin_cat },
322 //{ .cmd = "echo", .func = shell_builtin_echo },
323 { .cmd = "test", .func = shell_builtin_test }
325 size_t i;
327 for (i=0;i<sizeof(shell_builtin_cmds)/sizeof(shell_builtin_cmd_t);i++) {
328 if (strcmp(argv[0],shell_builtin_cmds[i].cmd)==0) return shell_builtin_cmds[i].func(argv);
331 return -1;
334 static char *shell_find_path(char *cmd) {
335 char *path;
336 /// @todo read from PATH enviroment variable
337 asprintf(&path,"/boot/bin/%s",cmd);
338 return path;
341 static int shell_run_binary(char **argv,int background) {
342 shell_job_t *job = malloc(sizeof(shell_job_t));
343 job->path = shell_find_path(argv[0]);
344 job->argv = argv;
345 job->stdin = TERMINAL_DEVICE;
346 job->stdout = TERMINAL_DEVICE;
347 job->stderr = TERMINAL_DEVICE;
348 job->pid = execute(job->path,argv,job->stdin,job->stdout,job->stderr);
350 if (job->pid==-1) {
351 free(job->path);
352 free(job);
353 return -1;
356 if (background) {
357 llist_push(joblist,job);
358 printf("+[%d]\n",job->pid);
360 else {
361 int status;
362 waitpid(job->pid,&status,0);
364 free(job->path);
365 free(job);
367 if (status!=0) fprintf(stderr,"sh: %s: returned with %d\n",argv[0],status);
370 return 0;
374 * Runs shell interactive
376 static void shell_interactive() {
377 int status = 0;
379 while (status!=1) {
380 size_t i;
381 char **argv;
382 char *cmd;
384 if ((cmd = shell_get_command())==NULL) break;
386 if ((argv = shell_parse_cmd(cmd))!=NULL) {
387 if ((status = shell_run_builtin(argv))==-1) {
388 if (shell_run_binary(argv,0)==-1) fprintf(stderr,"sh: %s: command not found\n",argv[0]);
391 for (i=0;argv[i];i++) free(argv[i]);
392 free(argv);
395 free(cmd);
399 int main(int argc,char *argv[]) {
400 int c;
402 FILE *terminal = fopen(TERMINAL_DEVICE,"r+");
403 if (terminal==NULL) return 1;
405 FILE *stdin_bak = stdin;
406 FILE *stdout_bak = stdout;
407 FILE *stderr_bak = stderr;
408 stdin = terminal;
409 stdout = terminal;
410 stderr = terminal;
412 while ((c = getopt(argc,argv,":hv"))!=-1) {
413 switch(c) {
414 case 'h':
415 usage(argv[0],0);
416 break;
417 case 'v':
418 printf("sh v0.1\n(c) 2008 Janosch Graef\n");
419 return 0;
420 break;
421 case '?':
422 fprintf(stderr,"Unrecognized option: -%c\n", optopt);
423 usage(argv[0],1);
424 break;
428 uname(&utsname);
429 passwd = getpwuid(getuid());
430 shell_interactive();
432 stdin = stdin_bak;
433 stdout = stdout_bak;
434 stderr = stderr_bak;
435 fclose(terminal);
437 return 0;