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

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