Misc changes, working on the VTerm's VT100 support and CLIShell's input
[tpg/acess2.git] / Usermode / Applications / CLIShell_src / main.c
1 /*\r
2  * AcessOS Shell Version 3\r
3  */\r
4 #define USE_READLINE    1\r
5 #include <acess/sys.h>\r
6 #include <stdlib.h>\r
7 #include <stdio.h>\r
8 #include <string.h>\r
9 #include "header.h"\r
10 \r
11 #if USE_READLINE\r
12 # include "readline.h"\r
13 #endif\r
14 \r
15 #define _stdin  0\r
16 #define _stdout 1\r
17 #define _stderr 2\r
18 \r
19 // ==== PROTOTYPES ====\r
20 char    *ReadCommandLine(int *Length);\r
21 void    Parse_Args(char *str, char **dest);\r
22 void    CallCommand(char **Args);\r
23 void    Command_Logout(int argc, char **argv);\r
24 void    Command_Clear(int argc, char **argv);\r
25 void    Command_Help(int argc, char **argv);\r
26 void    Command_Cd(int argc, char **argv);\r
27 void    Command_Dir(int argc, char **argv);\r
28 \r
29 // ==== CONSTANT GLOBALS ====\r
30 struct  {\r
31         char    *name;\r
32         void    (*fcn)(int argc, char **argv);\r
33 }       cBUILTINS[] = {\r
34         {"exit", Command_Logout},       {"logout", Command_Logout},\r
35         {"help", Command_Help}, {"clear", Command_Clear},\r
36         {"cd", Command_Cd}, {"dir", Command_Dir}\r
37 };\r
38 static char     *cDEFAULT_PATH[] = {"/Acess/Bin"};\r
39 #define BUILTIN_COUNT   (sizeof(cBUILTINS)/sizeof(cBUILTINS[0]))\r
40 \r
41 // ==== LOCAL VARIABLES ====\r
42  int    giNumPathDirs = 1;\r
43 char    **gasPathDirs = cDEFAULT_PATH;\r
44 char    **gasEnvironment;\r
45 char    gsCommandBuffer[1024];\r
46 char    *gsCurrentDirectory = NULL;\r
47 char    **gasCommandHistory;\r
48  int    giLastCommand = 0;\r
49  int    giCommandSpace = 0;\r
50 \r
51 // ==== CODE ====\r
52 int main(int argc, char *argv[], char *envp[])\r
53 {\r
54         char    *sCommandStr;\r
55         char    *saArgs[32] = {0};\r
56          int    length = 0;\r
57          int    i;\r
58          int    iArgCount = 0;\r
59         #if !USE_READLINE\r
60          int    bCached = 1;\r
61         #else\r
62         tReadline       readline_state = {0};\r
63         #endif\r
64         \r
65         #if USE_READLINE\r
66         readline_state.UseHistory = 1;\r
67         #endif\r
68         \r
69         gasEnvironment = envp;\r
70         \r
71         Command_Clear(0, NULL);\r
72         \r
73         {\r
74                 char    *tmp = getenv("CWD");\r
75                 if(tmp) {\r
76                         gsCurrentDirectory = malloc(strlen(tmp)+1);\r
77                         strcpy(gsCurrentDirectory, tmp);\r
78                 } else {\r
79                         gsCurrentDirectory = malloc(2);\r
80                         strcpy(gsCurrentDirectory, "/");\r
81                 }\r
82         }       \r
83         \r
84         write(_stdout, 22, "Acess Shell Version 3\n");\r
85         write(_stdout,  2, "\n");\r
86         for(;;)\r
87         {\r
88                 // Free last command & arguments\r
89                 if(saArgs[0])   free(saArgs);\r
90                 #if !USE_READLINE\r
91                 if(!bCached)    free(sCommandStr);\r
92                 bCached = 0;\r
93                 #endif\r
94                 \r
95                 write(_stdout, strlen(gsCurrentDirectory), gsCurrentDirectory);\r
96                 write(_stdout, 2, "$ ");\r
97                 \r
98                 // Read Command line\r
99                 #if USE_READLINE\r
100                 sCommandStr = Readline( &readline_state );\r
101                 length = strlen(sCommandStr);\r
102                 #else\r
103                 sCommandStr = ReadCommandLine( &length );\r
104                 \r
105                 if(!sCommandStr) {\r
106                         write(_stdout, 25, "PANIC: Out of heap space\n");\r
107                         return -1;\r
108                 }\r
109                 \r
110                 // Check if the command should be cached\r
111                 if(gasCommandHistory == NULL || strcmp(sCommandStr, gasCommandHistory[giLastCommand]) != 0)\r
112                 {\r
113                         if(giLastCommand >= giCommandSpace) {\r
114                                 giCommandSpace += 12;\r
115                                 gasCommandHistory = realloc(gasCommandHistory, giCommandSpace*sizeof(char*));\r
116                         }\r
117                         giLastCommand ++;\r
118                         gasCommandHistory[ giLastCommand ] = sCommandStr;\r
119                         bCached = 1;\r
120                 }\r
121                 #endif\r
122                 \r
123                 // Parse Command Line into arguments\r
124                 Parse_Args(sCommandStr, saArgs);\r
125                 \r
126                 // Count Arguments\r
127                 iArgCount = 0;\r
128                 while(saArgs[iArgCount])        iArgCount++;\r
129                 \r
130                 // Silently Ignore all empty commands\r
131                 if(saArgs[1][0] == '\0')        continue;\r
132                 \r
133                 // Check Built-In Commands\r
134                 for( i = 0; i < BUILTIN_COUNT; i++ )\r
135                 {\r
136                         if( strcmp(saArgs[1], cBUILTINS[i].name) == 0 )\r
137                         {\r
138                                 cBUILTINS[i].fcn(iArgCount-1, &saArgs[1]);\r
139                                 break;\r
140                         }\r
141                 }\r
142                 if(i != BUILTIN_COUNT)  continue;\r
143                 \r
144                 // Shall we?\r
145                 CallCommand( &saArgs[1] );\r
146                 \r
147                 #if USE_READLINE\r
148                 free( sCommandStr );\r
149                 #endif\r
150         }\r
151 }\r
152 \r
153 /**\r
154  * \fn char *ReadCommandLine(int *Length)\r
155  * \brief Read from the command line\r
156  */\r
157 char *ReadCommandLine(int *Length)\r
158 {\r
159         char    *ret;\r
160          int    len, pos, space = 1023;\r
161         char    ch;\r
162         #if 0\r
163          int    scrollbackPos = giLastCommand;\r
164         #endif\r
165          \r
166         // Preset Variables\r
167         ret = malloc( space+1 );\r
168         if(!ret)        return NULL;\r
169         len = 0;        pos = 0;\r
170                 \r
171         // Read In Command Line\r
172         do {\r
173                 read(_stdin, 1, &ch);   // Read Character from stdin (read is a blocking call)\r
174                 \r
175                 if(ch == '\n')  break;\r
176                 \r
177                 switch(ch)\r
178                 {\r
179                 // Control characters\r
180                 case '\x1B':\r
181                         read(_stdin, 1, &ch);   // Read control character\r
182                         switch(ch)\r
183                         {\r
184                         //case 'D':     if(pos) pos--;  break;\r
185                         //case 'C':     if(pos<len)     pos++;  break;\r
186                         case '[':\r
187                                 read(_stdin, 1, &ch);   // Read control character\r
188                                 switch(ch)\r
189                                 {\r
190                                 #if 0\r
191                                 case 'A':       // Up\r
192                                         {\r
193                                                  int    oldLen = len;\r
194                                                 if( scrollbackPos > 0 ) break;\r
195                                                 \r
196                                                 free(ret);\r
197                                                 ret = strdup( gasCommandHistory[--scrollbackPos] );\r
198                                                 \r
199                                                 len = strlen(ret);\r
200                                                 while(pos--)    write(_stdout, 3, "\x1B[D");\r
201                                                 write(_stdout, len, ret);       pos = len;\r
202                                                 while(pos++ < oldLen)   write(_stdout, 1, " ");\r
203                                         }\r
204                                         break;\r
205                                 case 'B':       // Down\r
206                                         {\r
207                                                  int    oldLen = len;\r
208                                                 if( scrollbackPos < giLastCommand-1 )   break;\r
209                                                 \r
210                                                 free(ret);\r
211                                                 ret = strdup( gasCommandHistory[++scrollbackPos] );\r
212                                                 \r
213                                                 len = strlen(ret);\r
214                                                 while(pos--)    write(_stdout, 3, "\x1B[D");\r
215                                                 write(_stdout, len, ret);       pos = len;\r
216                                                 while(pos++ < oldLen)   write(_stdout, 1, " ");\r
217                                         }\r
218                                         break;\r
219                                 #endif\r
220                                 case 'D':       // Left\r
221                                         if(pos == 0)    break;\r
222                                         pos --;\r
223                                         write(_stdout, 3, "\x1B[D");\r
224                                         break;\r
225                                 case 'C':       // Right\r
226                                         if(pos == len)  break;\r
227                                         pos++;\r
228                                         write(_stdout, 3, "\x1B[C");\r
229                                         break;\r
230                                 }\r
231                         }\r
232                         break;\r
233                 \r
234                 // Backspace\r
235                 case '\b':\r
236                         if(len <= 0)            break;  // Protect against underflows\r
237                         write(_stdout, 1, &ch);\r
238                         if(pos == len) {        // Simple case of end of string\r
239                                 len --;\r
240                                 pos--;\r
241                         }\r
242                         else {\r
243                                 char    buf[7] = "\x1B[000D";\r
244                                 buf[2] += ((len-pos+1)/100) % 10;\r
245                                 buf[3] += ((len-pos+1)/10) % 10;\r
246                                 buf[4] += (len-pos+1) % 10;\r
247                                 write(_stdout, len-pos, &ret[pos]);     // Move Text\r
248                                 ch = ' ';       write(_stdout, 1, &ch); ch = '\b';      // Clear deleted character\r
249                                 write(_stdout, 7, buf); // Update Cursor\r
250                                 // Alter Buffer\r
251                                 memmove(&ret[pos-1], &ret[pos], len-pos);\r
252                                 pos --;\r
253                                 len --;\r
254                         }\r
255                         break;\r
256                 \r
257                 // Tab\r
258                 case '\t':\r
259                         //TODO: Implement Tab-Completion\r
260                         //Currently just ignore tabs\r
261                         break;\r
262                 \r
263                 default:                \r
264                         // Expand Buffer\r
265                         if(len+1 > space) {\r
266                                 space += 256;\r
267                                 ret = realloc(ret, space+1);\r
268                                 if(!ret)        return NULL;\r
269                         }\r
270                         \r
271                         // Editing inside the buffer\r
272                         if(pos != len) {\r
273                                 char    buf[7] = "\x1B[000D";\r
274                                 buf[2] += ((len-pos)/100) % 10;\r
275                                 buf[3] += ((len-pos)/10) % 10;\r
276                                 buf[4] += (len-pos) % 10;\r
277                                 write(_stdout, 1, &ch); // Print new character\r
278                                 write(_stdout, len-pos, &ret[pos]);     // Move Text\r
279                                 write(_stdout, 7, buf); // Update Cursor\r
280                                 memmove( &ret[pos+1], &ret[pos], len-pos );\r
281                         }\r
282                         else {\r
283                                 write(_stdout, 1, &ch);\r
284                         }\r
285                         ret[pos++] = ch;\r
286                         len ++;\r
287                         break;\r
288                 }\r
289         } while(ch != '\n');\r
290         \r
291         // Cap String\r
292         ret[len] = '\0';\r
293         printf("\n");\r
294         \r
295         // Return length\r
296         if(Length)      *Length = len;\r
297         \r
298         return ret;\r
299 }\r
300 \r
301 /**\r
302  * \fn void Parse_Args(char *str, char **dest)\r
303  * \brief Parse a string into an argument array\r
304  */\r
305 void Parse_Args(char *str, char **dest)\r
306 {\r
307          int    i = 1;\r
308         char    *buf = malloc( strlen(str) + 1 );\r
309         \r
310         if(buf == NULL) {\r
311                 dest[0] = NULL;\r
312                 Print("Parse_Args: Out of heap space!\n");\r
313                 return ;\r
314         }\r
315         \r
316         strcpy(buf, str);\r
317         dest[0] = buf;\r
318         \r
319         // Trim leading whitespace\r
320         while(*buf == ' ' && *buf)      buf++;\r
321         \r
322         for(;;)\r
323         {\r
324                 dest[i] = buf;  // Save start of string\r
325                 i++;\r
326                 while(*buf && *buf != ' ')\r
327                 {\r
328                         if(*buf++ == '"')\r
329                         {\r
330                                 while(*buf && !(*buf == '"' && buf[-1] != '\\'))\r
331                                         buf++;\r
332                         }\r
333                 }\r
334                 if(*buf == '\0')        break;\r
335                 *buf = '\0';\r
336                 while(*++buf == ' ' && *buf);\r
337                 if(*buf == '\0')        break;\r
338         }\r
339         dest[i] = NULL;\r
340         if(i == 1) {\r
341                 free(buf);\r
342                 dest[0] = NULL;\r
343         }\r
344 }\r
345 \r
346 /**\r
347  * \fn void CallCommand(char **Args)\r
348  */\r
349 void CallCommand(char **Args)\r
350 {\r
351         t_sysFInfo      info;\r
352          int    pid = -1;\r
353          int    fd = 0;\r
354         char    sTmpBuffer[1024];\r
355         char    *exefile = Args[0];\r
356         \r
357         if(exefile[0] == '/'\r
358         || (exefile[0] == '.' && exefile[1] == '/')\r
359         || (exefile[0] == '.' && exefile[1] == '.' && exefile[2] == '/')\r
360                 )\r
361         {\r
362                 GeneratePath(exefile, gsCurrentDirectory, sTmpBuffer);\r
363                 // Check file existence\r
364                 fd = open(sTmpBuffer, OPENFLAG_EXEC);\r
365                 if(fd == -1) {\r
366                         Print("Unknown Command: `");Print(Args[0]);Print("'\n");        // Error Message\r
367                         return ;\r
368                 }\r
369                 \r
370                 // Get File info and close file\r
371                 finfo( fd, &info, 0 );\r
372                 close( fd );\r
373                 \r
374                 // Check if the file is a directory\r
375                 if(info.flags & FILEFLAG_DIRECTORY) {\r
376                         Print("`");Print(sTmpBuffer);   // Error Message\r
377                         Print("' is a directory.\n");\r
378                         return ;\r
379                 }\r
380         }\r
381         else\r
382         {\r
383                  int    i;\r
384                 \r
385                 // Check all components of $PATH\r
386                 for( i = 0; i < giNumPathDirs; i++ )\r
387                 {\r
388                         GeneratePath(exefile, gasPathDirs[i], sTmpBuffer);\r
389                         fd = open(sTmpBuffer, OPENFLAG_EXEC);\r
390                         if(fd == -1)    continue;\r
391                         finfo( fd, &info, 0 );\r
392                         close( fd );\r
393                         if(info.flags & FILEFLAG_DIRECTORY)     continue;\r
394                         // Woohoo! We found a valid command\r
395                         break;\r
396                 }\r
397                 \r
398                 // Exhausted path directories\r
399                 if( i == giNumPathDirs ) {\r
400                         Print("Unknown Command: `");Print(exefile);Print("'\n");\r
401                         return ;\r
402                 }\r
403         }\r
404         \r
405         // Create new process\r
406         pid = clone(CLONE_VM, 0);\r
407         // Start Task\r
408         if(pid == 0)\r
409                 execve(sTmpBuffer, Args, gasEnvironment);\r
410         if(pid <= 0) {\r
411                 Print("Unablt to create process: `");Print(sTmpBuffer);Print("'\n");    // Error Message\r
412         }\r
413         else {\r
414                  int    status;\r
415                 waittid(pid, &status);\r
416         }\r
417 }\r
418 \r
419 /**\r
420  * \fn void Command_Logout(int argc, char **argv)\r
421  * \brief Exit the shell, logging the user out\r
422  */\r
423 void Command_Logout(int argc, char **argv)\r
424 {\r
425         exit(0);\r
426 }\r
427 \r
428 /**\r
429  * \fn void Command_Colour(int argc, char **argv)\r
430  * \brief Displays the help screen\r
431  */\r
432 void Command_Help(int argc, char **argv)\r
433 {\r
434         Print("Acess 2 Command Line Interface\n");\r
435         Print(" By John Hodge (thePowersGang / [TPG])\n");\r
436         Print("\n");\r
437         Print("Builtin Commands:\n");\r
438         Print(" logout: Return to the login prompt\n");\r
439         Print(" exit:   Same\n");\r
440         Print(" help:   Display this message\n");\r
441         Print(" clear:  Clear the screen\n");\r
442         Print(" cd:     Change the current directory\n");\r
443         Print(" dir:    Print the contents of the current directory\n");\r
444         //Print("\n");\r
445         return;\r
446 }\r
447 \r
448 /**\r
449  * \fn void Command_Clear(int argc, char **argv)\r
450  * \brief Clear the screen\r
451  */\r
452 void Command_Clear(int argc, char **argv)\r
453 {\r
454         write(_stdout, 4, "\x1B[2J");   //Clear Screen\r
455 }\r
456 \r
457 /**\r
458  * \fn void Command_Cd(int argc, char **argv)\r
459  * \brief Change directory\r
460  */\r
461 void Command_Cd(int argc, char **argv)\r
462 {\r
463         char    tmpPath[1024];\r
464         int             fp;\r
465         t_sysFInfo      stats;\r
466         \r
467         if(argc < 2)\r
468         {\r
469                 Print(gsCurrentDirectory);Print("\n");\r
470                 return;\r
471         }\r
472         \r
473         GeneratePath(argv[1], gsCurrentDirectory, tmpPath);\r
474         \r
475         fp = open(tmpPath, 0);\r
476         if(fp == -1) {\r
477                 write(_stdout, 26, "Directory does not exist\n");\r
478                 return;\r
479         }\r
480         finfo(fp, &stats, 0);\r
481         close(fp);\r
482         \r
483         if( !(stats.flags & FILEFLAG_DIRECTORY) ) {\r
484                 write(_stdout, 17, "Not a Directory\n");\r
485                 return;\r
486         }\r
487         \r
488         free(gsCurrentDirectory);\r
489         gsCurrentDirectory = malloc(strlen(tmpPath)+1);\r
490         strcpy(gsCurrentDirectory, tmpPath);\r
491         \r
492         // Register change with kernel\r
493         chdir( gsCurrentDirectory );\r
494 }\r
495 \r
496 /**\r
497  * \fn void Command_Dir(int argc, char **argv)\r
498  * \brief Print the contents of a directory\r
499  */\r
500 void Command_Dir(int argc, char **argv)\r
501 {\r
502          int    dp, fp, dirLen;\r
503         char    modeStr[11] = "RWXrwxRWX ";\r
504         char    tmpPath[1024];\r
505         char    *fileName;\r
506         t_sysFInfo      info;\r
507         t_sysACL        acl;\r
508         \r
509         // Generate Directory Path\r
510         if(argc > 1)\r
511                 dirLen = GeneratePath(argv[1], gsCurrentDirectory, tmpPath);\r
512         else\r
513         {\r
514                 strcpy(tmpPath, gsCurrentDirectory);\r
515         }\r
516         dirLen = strlen(tmpPath);\r
517         \r
518         // Open Directory\r
519         dp = open(tmpPath, OPENFLAG_READ);\r
520         // Check if file opened\r
521         if(dp == -1)\r
522         {\r
523                 printf("Unable to open directory `%s', File cannot be found\n", tmpPath);\r
524                 return;\r
525         }\r
526         // Get File Stats\r
527         if( finfo(dp, &info, 0) == -1 )\r
528         {\r
529                 close(dp);\r
530                 write(_stdout, 34, "stat Failed, Bad File Descriptor\n");\r
531                 return;\r
532         }\r
533         // Check if it's a directory\r
534         if(!(info.flags & FILEFLAG_DIRECTORY))\r
535         {\r
536                 close(dp);\r
537                 write(_stdout, 27, "Unable to open directory `");\r
538                 write(_stdout, strlen(tmpPath)+1, tmpPath);\r
539                 write(_stdout, 20, "', Not a directory\n");\r
540                 return;\r
541         }\r
542         \r
543         // Append Shash for file paths\r
544         if(tmpPath[dirLen-1] != '/')\r
545         {\r
546                 tmpPath[dirLen++] = '/';\r
547                 tmpPath[dirLen] = '\0';\r
548         }\r
549         \r
550         fileName = (char*)(tmpPath+dirLen);\r
551         // Read Directory Content\r
552         while( (fp = readdir(dp, fileName)) )\r
553         {\r
554                 if(fp < 0)\r
555                 {\r
556                         if(fp == -3)\r
557                                 write(_stdout, 42, "Invalid Permissions to traverse directory\n");\r
558                         break;\r
559                 }\r
560                 // Open File\r
561                 fp = open(tmpPath, 0);\r
562                 if(fp == -1)    continue;\r
563                 // Get File Stats\r
564                 finfo(fp, &info, 0);\r
565                 \r
566                 if(info.flags & FILEFLAG_DIRECTORY)\r
567                         write(_stdout, 1, "d");\r
568                 else\r
569                         write(_stdout, 1, "-");\r
570                 \r
571                 // Print Mode\r
572                 // - Owner\r
573                 acl.group = 0;  acl.id = info.uid;\r
574                 _SysGetACL(fp, &acl);\r
575                 if(acl.perms & 1)       modeStr[0] = 'r';       else    modeStr[0] = '-';\r
576                 if(acl.perms & 2)       modeStr[1] = 'w';       else    modeStr[1] = '-';\r
577                 if(acl.perms & 8)       modeStr[2] = 'x';       else    modeStr[2] = '-';\r
578                 // - Group\r
579                 acl.group = 1;  acl.id = info.gid;\r
580                 _SysGetACL(fp, &acl);\r
581                 if(acl.perms & 1)       modeStr[3] = 'r';       else    modeStr[3] = '-';\r
582                 if(acl.perms & 2)       modeStr[4] = 'w';       else    modeStr[4] = '-';\r
583                 if(acl.perms & 8)       modeStr[5] = 'x';       else    modeStr[5] = '-';\r
584                 // - World\r
585                 acl.group = 1;  acl.id = -1;\r
586                 _SysGetACL(fp, &acl);\r
587                 if(acl.perms & 1)       modeStr[6] = 'r';       else    modeStr[6] = '-';\r
588                 if(acl.perms & 2)       modeStr[7] = 'w';       else    modeStr[7] = '-';\r
589                 if(acl.perms & 8)       modeStr[8] = 'x';       else    modeStr[8] = '-';\r
590                 write(_stdout, 10, modeStr);\r
591                 close(fp);\r
592                 \r
593                 // Colour Code\r
594                 if(info.flags & FILEFLAG_DIRECTORY)     // Directory: Green\r
595                         write(_stdout, 6, "\x1B[32m");\r
596                 // Default: White\r
597                 \r
598                 // Print Name\r
599                 write(_stdout, strlen(fileName), fileName);\r
600                 \r
601                 // Print slash if applicable\r
602                 if(info.flags & FILEFLAG_DIRECTORY)\r
603                         write(_stdout, 1, "/");\r
604                 \r
605                 // Revert Colour\r
606                 write(_stdout, 6, "\x1B[37m");\r
607                 \r
608                 // Newline!\r
609                 write(_stdout, 1, "\n");\r
610         }\r
611         // Close Directory\r
612         close(dp);\r
613 }\r

UCC git Repository :: git.ucc.asn.au