DOSBox-X
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
src/shell/shell_misc.cpp
00001 /*
00002  *  Copyright (C) 2002-2019  The DOSBox Team
00003  *
00004  *  This program is free software; you can redistribute it and/or modify
00005  *  it under the terms of the GNU General Public License as published by
00006  *  the Free Software Foundation; either version 2 of the License, or
00007  *  (at your option) any later version.
00008  *
00009  *  This program is distributed in the hope that it will be useful,
00010  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  *  GNU General Public License for more details.
00013  *
00014  *  You should have received a copy of the GNU General Public License
00015  *  along with this program; if not, write to the Free Software
00016  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA.
00017  */
00018 
00019 #include <assert.h>
00020 #include <stdlib.h>
00021 #include <string.h>
00022 #include <algorithm> //std::copy
00023 #include <iterator>  //std::front_inserter
00024 #include "shell.h"
00025 #include "timer.h"
00026 #include "bios.h"
00027 #include "control.h"
00028 #include "regs.h"
00029 #include "callback.h"
00030 #include "support.h"
00031 #include "../ints/int10.h"
00032 #ifdef WIN32
00033 #include "../dos/cdrom.h"
00034 #endif 
00035 
00036 #ifdef _MSC_VER
00037 # define MIN(a,b) ((a) < (b) ? (a) : (b))
00038 # define MAX(a,b) ((a) > (b) ? (a) : (b))
00039 #else
00040 # define MIN(a,b) std::min(a,b)
00041 # define MAX(a,b) std::max(a,b)
00042 #endif
00043 
00044 void DOS_Shell::ShowPrompt(void) {
00045         char dir[DOS_PATHLENGTH];
00046         dir[0] = 0; //DOS_GetCurrentDir doesn't always return something. (if drive is messed up)
00047         DOS_GetCurrentDir(0,dir);
00048         std::string line;
00049         const char * promptstr = "\0";
00050 
00051         if(GetEnvStr("PROMPT",line)) {
00052                 std::string::size_type idx = line.find('=');
00053                 std::string value=line.substr(idx +1 , std::string::npos);
00054                 line = std::string(promptstr) + value;
00055                 promptstr = line.c_str();
00056         }
00057 
00058         while (*promptstr) {
00059                 if (!strcasecmp(promptstr,"$"))
00060                         WriteOut("\0");
00061                 else if(*promptstr != '$')
00062                         WriteOut("%c",*promptstr);
00063                 else switch (toupper(*++promptstr)) {
00064                         case 'A': WriteOut("&"); break;
00065                         case 'B': WriteOut("|"); break;
00066                         case 'C': WriteOut("("); break;
00067                         case 'D': WriteOut("%02d-%02d-%04d",dos.date.day,dos.date.month,dos.date.year); break;
00068                         case 'E': WriteOut("%c",27);  break;
00069                         case 'F': WriteOut(")");  break;
00070                         case 'G': WriteOut(">"); break;
00071                         case 'H': WriteOut("\b");   break;
00072                         case 'L': WriteOut("<"); break;
00073                         case 'N': WriteOut("%c",DOS_GetDefaultDrive()+'A'); break;
00074                         case 'P': WriteOut("%c:\\%s",DOS_GetDefaultDrive()+'A',dir); break;
00075                         case 'Q': WriteOut("="); break;
00076                         case 'S': WriteOut(" "); break;
00077                         case 'T': {
00078                                 Bitu ticks=(Bitu)(((65536.0 * 100.0)/(double)PIT_TICK_RATE)* mem_readd(BIOS_TIMER));
00079                                 reg_dl=(Bit8u)((Bitu)ticks % 100);
00080                                 ticks/=100;
00081                                 reg_dh=(Bit8u)((Bitu)ticks % 60);
00082                                 ticks/=60;
00083                                 reg_cl=(Bit8u)((Bitu)ticks % 60);
00084                                 ticks/=60;
00085                                 reg_ch=(Bit8u)((Bitu)ticks % 24);
00086                                 WriteOut("%2d:%02d:%02d.%02d",reg_ch,reg_cl,reg_dh,reg_dl);
00087                                 break;
00088                         }
00089                         case 'V': WriteOut("DOSBox version %s. Reported DOS version %d.%d.",VERSION,dos.version.major,dos.version.minor); break;
00090                         case '$': WriteOut("$"); break;
00091                         case '_': WriteOut("\n"); break;
00092                         case 'M': break;
00093                         case '+': break;
00094                 }
00095                 promptstr++;
00096         }
00097 }
00098 
00099 static void outc(Bit8u c) {
00100         Bit16u n=1;
00101         DOS_WriteFile(STDOUT,&c,&n);
00102 }
00103 
00105 void MoveCaretBackwards()
00106 {
00107         Bit8u col, row;
00108         const Bit8u page(0);
00109         INT10_GetCursorPos(&row, &col, page);
00110 
00111         if (col != 0) 
00112                 return;
00113 
00114         Bit16u cols;
00115         INT10_GetScreenColumns(&cols);
00116         INT10_SetCursorPos(row - 1, static_cast<Bit8u>(cols), page);
00117 }
00118 
00119 /* NTS: buffer pointed to by "line" must be at least CMD_MAXLINE+1 large */
00120 void DOS_Shell::InputCommand(char * line) {
00121         Bitu size=CMD_MAXLINE-2; //lastcharacter+0
00122         Bit8u c;Bit16u n=1;
00123         Bit16u str_len=0;Bit16u str_index=0;
00124         Bit16u len=0;
00125         bool current_hist=false; // current command stored in history?
00126     Bit16u cr;
00127 
00128     input_eof = false;
00129         line[0] = '\0';
00130 
00131         std::list<std::string>::iterator it_history = l_history.begin(), it_completion = l_completion.begin();
00132 
00133         while (size) {
00134                 dos.echo=false;
00135                 if (!DOS_ReadFile(input_handle,&c,&n)) {
00136             LOG(LOG_MISC,LOG_ERROR)("SHELL: Lost the input handle, dropping shell input loop");
00137             n = 0;
00138         }
00139                 if (!n) {
00140             input_eof = true;
00141                         size=0;                 //Kill the while loop
00142                         continue;
00143                 }
00144 
00145         if (input_handle != STDIN) { /* FIXME: Need DOS_IsATTY() or somesuch */
00146             cr = (Bit16u)c; /* we're not reading from the console */
00147         }
00148         else if (IS_PC98_ARCH) {
00149             extern Bit16u last_int16_code;
00150 
00151             /* shift state is needed for some key combinations not directly supported by CON driver.
00152              * bit 4 = CTRL
00153              * bit 3 = GRPH/ALT
00154              * bit 2 = kana
00155              * bit 1 = caps
00156              * bit 0 = SHIFT */
00157             uint8_t shiftstate = mem_readb(0x52A + 0x0E);
00158 
00159             /* NTS: PC-98 keyboards lack the US layout HOME / END keys, therefore there is no mapping here */
00160 
00161             /* NTS: Since left arrow and backspace map to the same byte value, PC-98 treats it the same at the DOS prompt.
00162              *      However the PC-98 version of DOSKEY seems to be able to differentiate the two anyway and let the left
00163              *      arrow move the cursor back (perhaps it's calling INT 18h directly then?) */
00164                  if (c == 0x0B)
00165                 cr = 0x4800;    /* IBM extended code up arrow */
00166             else if (c == 0x0A)
00167                 cr = 0x5000;    /* IBM extended code down arrow */
00168                  else if (c == 0x0C) {
00169                      if (shiftstate & 0x10/*CTRL*/)
00170                          cr = 0x7400;    /* IBM extended code CTRL + right arrow */
00171                      else
00172                          cr = 0x4D00;    /* IBM extended code right arrow */
00173                  }
00174             else if (c == 0x08) {
00175                 /* IBM extended code left arrow OR backspace. use last scancode to tell which as DOSKEY apparently can. */
00176                 if (last_int16_code == 0x3B00) {
00177                     if (shiftstate & 0x10/*CTRL*/)
00178                         cr = 0x7300; /* CTRL + left arrow */
00179                     else
00180                         cr = 0x4B00; /* left arrow */
00181                 }
00182                 else {
00183                     cr = 0x08; /* backspace */
00184                 }
00185             }
00186             else if (c == 0x1B) { /* escape */
00187                 /* Either it really IS the ESC key, or an ANSI code */
00188                 if (last_int16_code != 0x001B) {
00189                     DOS_ReadFile(input_handle,&c,&n);
00190                          if (c == 0x44)  // DEL
00191                         cr = 0x5300;
00192                     else if (c == 0x50)  // INS
00193                         cr = 0x5200;
00194                     else if (c == 0x53)  // F1
00195                         cr = 0x3B00;
00196                     else if (c == 0x54)  // F2
00197                         cr = 0x3C00;
00198                     else if (c == 0x55)  // F3
00199                         cr = 0x3D00;
00200                     else if (c == 0x56)  // F4
00201                         cr = 0x3E00;
00202                     else if (c == 0x57)  // F5
00203                         cr = 0x3F00;
00204                     else if (c == 0x45)  // F6
00205                         cr = 0x4000;
00206                     else if (c == 0x4A)  // F7
00207                         cr = 0x4100;
00208                     else if (c == 0x50)  // F8
00209                         cr = 0x4200;
00210                     else if (c == 0x51)  // F9
00211                         cr = 0x4300;
00212                     else if (c == 0x5A)  // F10
00213                         cr = 0x4400;
00214                     else
00215                         cr = 0;
00216                 }
00217                 else {
00218                     cr = (Bit16u)c;
00219                 }
00220             }
00221             else {
00222                 cr = (Bit16u)c;
00223             }
00224         }
00225         else {
00226             if (c == 0) {
00227                                 DOS_ReadFile(input_handle,&c,&n);
00228                 cr = (Bit16u)c << (Bit16u)8;
00229             }
00230             else {
00231                 cr = (Bit16u)c;
00232             }
00233         }
00234 
00235         switch (cr) {
00236             case 0x3d00:        /* F3 */
00237                 if (!l_history.size()) break;
00238                 it_history = l_history.begin();
00239                 if (it_history != l_history.end() && it_history->length() > str_len) {
00240                     const char *reader = &(it_history->c_str())[str_len];
00241                     while ((c = (Bit8u)(*reader++))) {
00242                         line[str_index ++] = (char)c;
00243                         DOS_WriteFile(STDOUT,&c,&n);
00244                     }
00245                     str_len = str_index = (Bit16u)it_history->length();
00246                     size = (unsigned int)CMD_MAXLINE - str_index - 2u;
00247                     line[str_len] = 0;
00248                 }
00249                 break;
00250 
00251             case 0x4B00:        /* LEFT */
00252                 if (str_index) {
00253                     outc(8);
00254                     str_index --;
00255                         MoveCaretBackwards();
00256                 }
00257                 break;
00258 
00259                         case 0x7400: /*CTRL + RIGHT : cmd.exe-like next word*/
00260                                 {
00261                                         auto pos = line + str_index;
00262                                         auto spc = *pos == ' ';
00263                                         const auto end = line + str_len;
00264 
00265                                         while (pos < end) {
00266                                                 if (spc && *pos != ' ')
00267                                                         break;
00268                                                 if (*pos == ' ')
00269                                                         spc = true;
00270                                                 pos++;
00271                                         }
00272                                         
00273                                         const auto lgt = MIN(pos, end) - (line + str_index);
00274                                         
00275                                         for (auto i = 0; i < lgt; i++)
00276                                                 outc(static_cast<Bit8u>(line[str_index++]));
00277                                 }       
00278                         break;
00279                         case 0x7300: /*CTRL + LEFT : cmd.exe-like previous word*/
00280                                 {
00281                                         auto pos = line + str_index - 1;
00282                                         const auto beg = line;
00283                                         const auto spc = *pos == ' ';
00284 
00285                                         if (spc) {
00286                                                 while(*pos == ' ') pos--;
00287                                                 while(*pos != ' ') pos--;
00288                                                 pos++;
00289                                         }
00290                                         else {
00291                                                 while(*pos != ' ') pos--;
00292                                                 pos++;
00293                                         }
00294                                         
00295                                         const auto lgt = abs(MAX(pos, beg) - (line + str_index));
00296                                         
00297                                         for (auto i = 0; i < lgt; i++) {
00298                                                 outc(8);
00299                                                 str_index--;
00300                                                 MoveCaretBackwards();
00301                                         }
00302                                 }       
00303                         break;
00304             case 0x4D00:        /* RIGHT */
00305                 if (str_index < str_len) {
00306                     outc((Bit8u)line[str_index++]);
00307                 }
00308                 break;
00309 
00310             case 0x4700:        /* HOME */
00311                 while (str_index) {
00312                     outc(8);
00313                     str_index--;
00314                 }
00315                 break;
00316 
00317             case 0x5200:    /* INS */
00318                 if (IS_PC98_ARCH) { // INS state handled by IBM PC/AT BIOS, faked for PC-98 mode
00319                     extern bool pc98_doskey_insertmode;
00320 
00321                     // NTS: No visible change to the cursor, just like DOSKEY on PC-98 MS-DOS
00322                     pc98_doskey_insertmode = !pc98_doskey_insertmode;
00323                 }
00324                 break;
00325 
00326             case 0x4F00:        /* END */
00327                 while (str_index < str_len) {
00328                     outc((Bit8u)line[str_index++]);
00329                 }
00330                 break;
00331 
00332             case 0x4800:        /* UP */
00333                 if (l_history.empty() || it_history == l_history.end()) break;
00334 
00335                 // store current command in history if we are at beginning
00336                 if (it_history == l_history.begin() && !current_hist) {
00337                     current_hist=true;
00338                     l_history.push_front(line);
00339                 }
00340 
00341                 for (;str_index>0; str_index--) {
00342                     // removes all characters
00343                     outc(8); outc(' '); outc(8);
00344                 }
00345                 strcpy(line, it_history->c_str());
00346                 len = (Bit16u)it_history->length();
00347                 str_len = str_index = len;
00348                 size = (unsigned int)CMD_MAXLINE - str_index - 2u;
00349                 DOS_WriteFile(STDOUT, (Bit8u *)line, &len);
00350                 it_history ++;
00351                 break;
00352 
00353             case 0x5000:        /* DOWN */
00354                 if (l_history.empty() || it_history == l_history.begin()) break;
00355 
00356                 // not very nice but works ..
00357                 it_history --;
00358                 if (it_history == l_history.begin()) {
00359                     // no previous commands in history
00360                     it_history ++;
00361 
00362                     // remove current command from history
00363                     if (current_hist) {
00364                         current_hist=false;
00365                         l_history.pop_front();
00366                     }
00367                     break;
00368                 } else it_history --;
00369 
00370                 for (;str_index>0; str_index--) {
00371                     // removes all characters
00372                     outc(8); outc(' '); outc(8);
00373                 }
00374                 strcpy(line, it_history->c_str());
00375                 len = (Bit16u)it_history->length();
00376                 str_len = str_index = len;
00377                 size = (unsigned int)CMD_MAXLINE - str_index - 2u;
00378                 DOS_WriteFile(STDOUT, (Bit8u *)line, &len);
00379                 it_history ++;
00380 
00381                 break;
00382             case 0x5300:/* DELETE */
00383                 {
00384                     if(str_index>=str_len) break;
00385                     Bit16u a=str_len-str_index-1;
00386                     Bit8u* text=reinterpret_cast<Bit8u*>(&line[str_index+1]);
00387                     DOS_WriteFile(STDOUT,text,&a);//write buffer to screen
00388                     outc(' ');outc(8);
00389                     for(Bitu i=str_index;i<(str_len-1u);i++) {
00390                         line[i]=line[i+1u];
00391                         outc(8);
00392                     }
00393                     line[--str_len]=0;
00394                     size++;
00395                 }
00396                 break;
00397             case 0x0F00:        /* Shift-Tab */
00398                 if (l_completion.size()) {
00399                     if (it_completion == l_completion.begin()) it_completion = l_completion.end (); 
00400                     it_completion--;
00401 
00402                     if (it_completion->length()) {
00403                         for (;str_index > completion_index; str_index--) {
00404                             // removes all characters
00405                             outc(8); outc(' '); outc(8);
00406                         }
00407 
00408                         strcpy(&line[completion_index], it_completion->c_str());
00409                         len = (Bit16u)it_completion->length();
00410                         str_len = str_index = (Bitu)(completion_index + len);
00411                         size = (unsigned int)CMD_MAXLINE - str_index - 2u;
00412                         DOS_WriteFile(STDOUT, (Bit8u *)it_completion->c_str(), &len);
00413                     }
00414                 }
00415                 break;
00416             case 0x08:                          /* BackSpace */
00417                 if (str_index) {
00418                     outc(8);
00419                     Bit32u str_remain=(Bit32u)(str_len - str_index);
00420                     size++;
00421                     if (str_remain) {
00422                         memmove(&line[str_index-1],&line[str_index],str_remain);
00423                         line[--str_len]=0;
00424                         str_index --;
00425                         /* Go back to redraw */
00426                         for (Bit16u i=str_index; i < str_len; i++)
00427                             outc((Bit8u)line[i]);
00428                     } else {
00429                         line[--str_index] = '\0';
00430                         str_len--;
00431                     }
00432                     outc(' ');  outc(8);
00433                     // moves the cursor left
00434                     while (str_remain--) outc(8);
00435                 }
00436                 if (l_completion.size()) l_completion.clear();
00437                 break;
00438             case 0x0a:                          /* Give a new Line */
00439                 outc('\n');
00440                 break;
00441             case '': // FAKE CTRL-C
00442                 outc(94); outc('C');
00443                 *line = 0;      // reset the line.
00444                 if (l_completion.size()) l_completion.clear(); //reset the completion list.
00445                 if(!echo) outc('\n');
00446                 size = 0;       // stop the next loop
00447                 str_len = 0;    // prevent multiple adds of the same line
00448                 break;
00449             case 0x0d:                          /* Don't care, and return */
00450                 if(!echo) { outc('\r'); outc('\n'); }
00451                 size=0;                 //Kill the while loop
00452                 break;
00453             case'\t':
00454                 {
00455                     if (l_completion.size()) {
00456                         it_completion ++;
00457                         if (it_completion == l_completion.end()) it_completion = l_completion.begin();
00458                     } else {
00459                         // build new completion list
00460                         // Lines starting with CD will only get directories in the list
00461                         bool dir_only = (strncasecmp(line,"CD ",3)==0);
00462 
00463                         // get completion mask
00464                         char *p_completion_start = strrchr(line, ' ');
00465 
00466                         if (p_completion_start) {
00467                             p_completion_start ++;
00468                             completion_index = (Bit16u)(str_len - strlen(p_completion_start));
00469                         } else {
00470                             p_completion_start = line;
00471                             completion_index = 0;
00472                         }
00473 
00474                         char *path;
00475                         if ((path = strrchr(line+completion_index,'\\'))) completion_index = (Bit16u)(path-line+1);
00476                         if ((path = strrchr(line+completion_index,'/'))) completion_index = (Bit16u)(path-line+1);
00477 
00478                         // build the completion list
00479                         char mask[DOS_PATHLENGTH] = {0};
00480                         if (strlen(p_completion_start) + 3 >= DOS_PATHLENGTH) {
00481                             //Beep;
00482                             break;
00483                         }
00484                         if (p_completion_start) {
00485                             safe_strncpy(mask, p_completion_start,DOS_PATHLENGTH);
00486                             char* dot_pos=strrchr(mask,'.');
00487                             char* bs_pos=strrchr(mask,'\\');
00488                             char* fs_pos=strrchr(mask,'/');
00489                             char* cl_pos=strrchr(mask,':');
00490                             // not perfect when line already contains wildcards, but works
00491                             if ((dot_pos-bs_pos>0) && (dot_pos-fs_pos>0) && (dot_pos-cl_pos>0))
00492                                 strncat(mask, "*",DOS_PATHLENGTH - 1);
00493                             else strncat(mask, "*.*",DOS_PATHLENGTH - 1);
00494                         } else {
00495                             strcpy(mask, "*.*");
00496                         }
00497 
00498                         RealPt save_dta=dos.dta();
00499                         dos.dta(dos.tables.tempdta);
00500 
00501                         bool res = DOS_FindFirst(mask, 0xffff & ~DOS_ATTR_VOLUME);
00502                         if (!res) {
00503                             dos.dta(save_dta);
00504                             break;      // TODO: beep
00505                         }
00506 
00507                         DOS_DTA dta(dos.dta());
00508                         char name[DOS_NAMELENGTH_ASCII];Bit32u sz;Bit16u date;Bit16u time;Bit8u att;
00509 
00510                         std::list<std::string> executable;
00511                         while (res) {
00512                             dta.GetResult(name,sz,date,time,att);
00513                             // add result to completion list
00514 
00515                             char *ext;  // file extension
00516                             if (strcmp(name, ".") && strcmp(name, "..")) {
00517                                 if (dir_only) { //Handle the dir only case different (line starts with cd)
00518                                     if(att & DOS_ATTR_DIRECTORY) l_completion.push_back(name);
00519                                 } else {
00520                                     ext = strrchr(name, '.');
00521                                     if (ext && (strcmp(ext, ".BAT") == 0 || strcmp(ext, ".COM") == 0 || strcmp(ext, ".EXE") == 0))
00522                                         // we add executables to the a seperate list and place that list infront of the normal files
00523                                         executable.push_front(name);
00524                                     else
00525                                         l_completion.push_back(name);
00526                                 }
00527                             }
00528                             res=DOS_FindNext();
00529                         }
00530                         /* Add executable list to front of completion list. */
00531                         std::copy(executable.begin(),executable.end(),std::front_inserter(l_completion));
00532                         it_completion = l_completion.begin();
00533                         dos.dta(save_dta);
00534                     }
00535 
00536                     if (l_completion.size() && it_completion->length()) {
00537                         for (;str_index > completion_index; str_index--) {
00538                             // removes all characters
00539                             outc(8); outc(' '); outc(8);
00540                         }
00541 
00542                         strcpy(&line[completion_index], it_completion->c_str());
00543                         len = (Bit16u)it_completion->length();
00544                         str_len = str_index = (Bitu)(completion_index + len);
00545                         size = (unsigned int)CMD_MAXLINE - str_index - 2u;
00546                         DOS_WriteFile(STDOUT, (Bit8u *)it_completion->c_str(), &len);
00547                     }
00548                 }
00549                 break;
00550             case 0x1b:   /* ESC */
00551                 // NTS: According to real PC-98 DOS:
00552                 //      If DOSKEY is loaded, ESC clears the prompt
00553                 //      If DOSKEY is NOT loaded, ESC does nothing. In fact, after ESC,
00554                 //      the next character input is thrown away before resuming normal keyboard input.
00555                 //
00556                 //      DOSBox / DOSBox-X have always acted as if DOSKEY is loaded in a fashion, so
00557                 //      we'll emulate the PC-98 DOSKEY behavior here.
00558                 //
00559                 //      DOSKEY on PC-98 is able to clear the whole prompt and even bring the cursor
00560                 //      back up to the first line if the input crosses multiple lines.
00561 
00562                 // NTS: According to real IBM/Microsoft PC/AT DOS:
00563                 //      If DOSKEY is loaded, ESC clears the prompt
00564                 //      If DOSKEY is NOT loaded, ESC prints a backslash and goes to the next line.
00565                 //      The Windows 95 version of DOSKEY puts the cursor at a horizontal position
00566                 //      that matches the DOS prompt (not emulated here).
00567                 //
00568                 //      DOSBox / DOSBox-X have always acted as if DOSKEY is loaded in a fashion, so
00569                 //      we'll emulate DOSKEY behavior here.
00570 
00571                 while (str_index < str_len) {
00572                     outc(' ');
00573                     str_index++;
00574                 }
00575                 while (str_index > 0) {
00576                     outc(8);
00577                     outc(' ');
00578                     outc(8);
00579                     MoveCaretBackwards();
00580                     str_index--;
00581                 }
00582 
00583                 *line = 0;      // reset the line.
00584                 if (l_completion.size()) l_completion.clear(); //reset the completion list.
00585                 str_index = 0;
00586                 str_len = 0;
00587                 break;
00588             default:
00589                 if (cr >= 0x100) break;
00590                 if (l_completion.size()) l_completion.clear();
00591                 if(str_index < str_len && !INT10_GetInsertState()) { //mem_readb(BIOS_KEYBOARD_FLAGS1)&0x80) dev_con.h ?
00592                     outc(' ');//move cursor one to the right.
00593                     Bit16u a = str_len - str_index;
00594                     Bit8u* text=reinterpret_cast<Bit8u*>(&line[str_index]);
00595                     DOS_WriteFile(STDOUT,text,&a);//write buffer to screen
00596                     outc(8);//undo the cursor the right.
00597                     for(Bitu i=str_len;i>str_index;i--) {
00598                         line[i]=line[i-1]; //move internal buffer
00599                         outc(8); //move cursor back (from write buffer to screen)
00600                     }
00601                     line[++str_len]=0;//new end (as the internal buffer moved one place to the right
00602                     size--;
00603                 }
00604 
00605                 line[str_index]=(char)(cr&0xFF);
00606                 str_index ++;
00607                 if (str_index > str_len){ 
00608                     line[str_index] = '\0';
00609                     str_len++;
00610                     size--;
00611                 }
00612                 DOS_WriteFile(STDOUT,&c,&n);
00613                 break;
00614         }
00615     }
00616 
00617         if (!str_len) return;
00618         str_len++;
00619 
00620         // remove current command from history if it's there
00621         if (current_hist) {
00622                 current_hist=false;
00623                 l_history.pop_front();
00624         }
00625 
00626         // add command line to history. Win95 behavior with DOSKey suggests
00627         // that the original string is preserved, not the expanded string.
00628         l_history.push_front(line); it_history = l_history.begin();
00629         if (l_completion.size()) l_completion.clear();
00630 
00631         /* DOS %variable% substitution */
00632         ProcessCmdLineEnvVarStitution(line);
00633 }
00634 
00635 
00636 /* WARNING: Substitution is carried out in-place!
00637  * Buffer pointed to by "line" must be at least CMD_MAXLINE+1 bytes long! */
00638 void DOS_Shell::ProcessCmdLineEnvVarStitution(char *line) {
00639         char temp[CMD_MAXLINE]; /* <- NTS: Currently 4096 which is very generous indeed! */
00640         char *w=temp,*wf=temp+sizeof(temp)-1;
00641         char *r=line;
00642 
00643         /* initial scan: is there anything to substitute? */
00644         /* if not, then return without modifying "line" */
00645         while (*r != 0 && *r != '%') r++;
00646         if (*r != '%') return;
00647 
00648         /* if the incoming string is already too long, then that's a problem too! */
00649         if (((size_t)(r+1-line)) >= CMD_MAXLINE) {
00650                 LOG_MSG("DOS_Shell::ProcessCmdLineEnvVarStitution WARNING incoming string to substitute is already too long!\n");
00651                 goto overflow;
00652         }
00653 
00654         /* copy the string down up to that point */
00655         for (char *c=line;c < r;) {
00656                 assert(w < wf);
00657                 *w++ = *c++;
00658         }
00659 
00660         /* begin substitution process */
00661         while (*r != 0) {
00662                 if (*r == '%') {
00663                         r++;
00664                         if (*r == '%' || *r == 0) {
00665                                 /* %% or leaving a trailing % at the end (Win95 behavior) becomes a single '%' */
00666                                 if (w >= wf) goto overflow;
00667                                 *w++ = '%';
00668                                 if (*r != 0) r++;
00669                                 else break;
00670                         }
00671                         else {
00672                                 char *name = r; /* store pointer, 'r' is first char of the name following '%' */
00673                                 int spaces = 0,chars = 0;
00674 
00675                                 /* continue scanning for the ending '%'. variable names are apparently meant to be
00676                                  * alphanumeric, start with a letter, without spaces (if Windows 95 COMMAND.COM is
00677                                  * any good example). If it doesn't end in '%' or is broken by space or starts with
00678                                  * a number, substitution is not carried out. In the middle of the variable name
00679                                  * it seems to be acceptable to use hyphens.
00680                                  *
00681                                  * since spaces break up a variable name to prevent substitution, these commands
00682                                  * act differently from one another:
00683                                  *
00684                                  * C:>echo %PATH%
00685                                  * C:\DOS;C:\WINDOWS
00686                                  *
00687                                  * C:>echo %PATH %
00688                                  * %PATH % */
00689                                 if (isalpha(*r) || *r == ' ') { /* must start with a letter. space is apparently valid too. (Win95) */
00690                                         if (*r == ' ') spaces++;
00691                                         else if (isalpha(*r)) chars++;
00692 
00693                                         r++;
00694                                         while (*r != 0 && *r != '%') {
00695                                                 if (*r == ' ') spaces++;
00696                                                 else chars++;
00697                                                 r++;
00698                                         }
00699                                 }
00700 
00701                                 /* Win95 testing:
00702                                  *
00703                                  * "%" = "%"
00704                                  * "%%" = "%"
00705                                  * "% %" = ""
00706                                  * "%  %" = ""
00707                                  * "% % %" = " %"
00708                                  *
00709                                  * ^ WTF?
00710                                  *
00711                                  * So the below code has funny conditions to match Win95's weird rules on what
00712                                  * consitutes valid or invalid %variable% names. */
00713                                 if (*r == '%' && ((spaces > 0 && chars == 0) || (spaces == 0 && chars > 0))) {
00714                                         std::string temp;
00715 
00716                                         /* valid name found. substitute */
00717                                         *r++ = 0; /* ASCIIZ snip */
00718                                         if (GetEnvStr(name,temp)) {
00719                                                 size_t equ_pos = temp.find_first_of('=');
00720                                                 if (equ_pos != std::string::npos) {
00721                                                         const char *base = temp.c_str();
00722                                                         const char *value = base + equ_pos + 1;
00723                                                         const char *fence = base + temp.length();
00724                                                         assert(value >= base && value <= fence);
00725                                                         size_t len = (size_t)(fence-value);
00726 
00727                                                         if ((w+len) > wf) goto overflow;
00728                                                         memcpy(w,value,len);
00729                                                         w += len;
00730                                                 }
00731                                         }
00732                                 }
00733                                 else {
00734                                         /* nope. didn't find a valid name */
00735 
00736                                         while (*r != 0 && *r == ' ') r++; /* skip spaces */
00737                                         name--; /* step "name" back to cover the first '%' we found */
00738 
00739                                         for (char *c=name;c < r;) {
00740                                                 if (w >= wf) goto overflow;
00741                                                 *w++ = *c++;
00742                                         }
00743                                 }
00744                         }
00745                 }
00746                 else {
00747                         if (w >= wf) goto overflow;
00748                         *w++ = *r++;
00749                 }
00750         }
00751 
00752         /* complete the C-string */
00753         assert(w <= wf);
00754         *w = 0;
00755 
00756         /* copy the string back over the buffer pointed to by line */
00757         {
00758                 size_t out_len = (size_t)(w+1-temp); /* length counting the NUL too */
00759                 assert(out_len <= CMD_MAXLINE);
00760                 memcpy(line,temp,out_len);
00761         }
00762 
00763         /* success */
00764         return;
00765 overflow:
00766         *line = 0; /* clear string (C-string chop with NUL) */
00767         WriteOut("Command input error: string expansion overflow\n");
00768 }
00769 
00770 std::string full_arguments = "";
00771 bool DOS_Shell::Execute(char * name,char * args) {
00772 /* return true  => don't check for hardware changes in do_command 
00773  * return false =>       check for hardware changes in do_command */
00774         char fullname[DOS_PATHLENGTH+4]; //stores results from Which
00775         char* p_fullname;
00776         char line[CMD_MAXLINE];
00777         if(strlen(args)!= 0){
00778                 if(*args != ' '){ //put a space in front
00779                         line[0]=' ';line[1]=0;
00780                         strncat(line,args,CMD_MAXLINE-2);
00781                         line[CMD_MAXLINE-1]=0;
00782                 }
00783                 else
00784                 {
00785                         safe_strncpy(line,args,CMD_MAXLINE);
00786                 }
00787         }else{
00788                 line[0]=0;
00789         }
00790 
00791         /* check for a drive change */
00792         if (((strcmp(name + 1, ":") == 0) || (strcmp(name + 1, ":\\") == 0)) && isalpha(*name))
00793         {
00794                 if (strrchr(name,'\\')) { WriteOut(MSG_Get("SHELL_EXECUTE_ILLEGAL_COMMAND"),name); return true; }
00795                 if (!DOS_SetDrive(toupper(name[0])-'A')) {
00796 #ifdef WIN32
00797                         Section_prop * sec=0; sec=static_cast<Section_prop *>(control->GetSection("dos"));
00798                         if(!sec->Get_bool("automount")) { WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_NOT_FOUND"),toupper(name[0])); return true; }
00799                         // automount: attempt direct letter to drive map.
00800                         if((GetDriveType(name)==3) && (strcasecmp(name,"c:")==0)) WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_ACCESS_WARNING_WIN"));
00801 first_1:
00802                         if(GetDriveType(name)==5) WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_ACCESS_CDROM"),toupper(name[0]));
00803                         else if(GetDriveType(name)==2) WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_ACCESS_FLOPPY"),toupper(name[0]));
00804                         else if((GetDriveType(name)==3)||(GetDriveType(name)==4)||(GetDriveType(name)==6)) WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_ACCESS_FIXED"),toupper(name[0]));
00805                         else { WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_NOT_FOUND"),toupper(name[0])); return true; }
00806 
00807 first_2:
00808                 Bit8u c;Bit16u n=1;
00809                 DOS_ReadFile (STDIN,&c,&n);
00810                 do switch (c) {
00811                         case 'n':                       case 'N':
00812                         {
00813                                 DOS_WriteFile (STDOUT,&c, &n);
00814                                 DOS_ReadFile (STDIN,&c,&n);
00815                                 do switch (c) {
00816                                         case 0xD: WriteOut("\n\n"); WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_NOT_FOUND"),toupper(name[0])); return true;
00817                                         case 0x08: WriteOut("\b \b"); goto first_2;
00818                                 } while (DOS_ReadFile (STDIN,&c,&n));
00819                         }
00820                         case 'y':                       case 'Y':
00821                         {
00822                                 DOS_WriteFile (STDOUT,&c, &n);
00823                                 DOS_ReadFile (STDIN,&c,&n);
00824                                 do switch (c) {
00825                                         case 0xD: WriteOut("\n"); goto continue_1;
00826                                         case 0x08: WriteOut("\b \b"); goto first_2;
00827                                 } while (DOS_ReadFile (STDIN,&c,&n));
00828                         }
00829                         case 0xD: WriteOut("\n"); goto first_1;
00830                         case '\t': case 0x08: goto first_2;
00831                         default:
00832                         {
00833                                 DOS_WriteFile (STDOUT,&c, &n);
00834                                 DOS_ReadFile (STDIN,&c,&n);
00835                                 do switch (c) {
00836                                         case 0xD: WriteOut("\n");goto first_1;
00837                                         case 0x08: WriteOut("\b \b"); goto first_2;
00838                                 } while (DOS_ReadFile (STDIN,&c,&n));
00839                                 goto first_2;
00840                         }
00841                 } while (DOS_ReadFile (STDIN,&c,&n));
00842 
00843 continue_1:
00844 
00845                         char mountstring[DOS_PATHLENGTH+CROSS_LEN+20];
00846                         sprintf(mountstring,"MOUNT %s ",name);
00847 
00848                         if(GetDriveType(name)==5) strcat(mountstring,"-t cdrom ");
00849                         else if(GetDriveType(name)==2) strcat(mountstring,"-t floppy ");
00850                         strcat(mountstring,name);
00851                         strcat(mountstring,"\\");
00852 //                      if(GetDriveType(name)==5) strcat(mountstring," -ioctl");
00853                         
00854                         this->ParseLine(mountstring);
00855 //failed:
00856                         if (!DOS_SetDrive(toupper(name[0])-'A'))
00857 #endif
00858                         WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_NOT_FOUND"),toupper(name[0]));
00859                 }
00860                 return true;
00861         }
00862         /* Check for a full name */
00863         p_fullname = Which(name);
00864         if (!p_fullname) return false;
00865         strcpy(fullname,p_fullname);
00866         const char* extension = strrchr(fullname,'.');
00867         
00868         /*always disallow files without extension from being executed. */
00869         /*only internal commands can be run this way and they never get in this handler */
00870         if(extension == 0)
00871         {
00872                 //Check if the result will fit in the parameters. Else abort
00873                 if(strlen(fullname) >( DOS_PATHLENGTH - 1) ) return false;
00874                 char temp_name[DOS_PATHLENGTH+4],* temp_fullname;
00875                 //try to add .com, .exe and .bat extensions to filename
00876                 
00877                 strcpy(temp_name,fullname);
00878                 strcat(temp_name,".COM");
00879                 temp_fullname=Which(temp_name);
00880                 if (temp_fullname) { extension=".com";strcpy(fullname,temp_fullname); }
00881 
00882                 else 
00883                 {
00884                         strcpy(temp_name,fullname);
00885                         strcat(temp_name,".EXE");
00886                         temp_fullname=Which(temp_name);
00887                         if (temp_fullname) { extension=".exe";strcpy(fullname,temp_fullname);}
00888 
00889                         else 
00890                         {
00891                                 strcpy(temp_name,fullname);
00892                                 strcat(temp_name,".BAT");
00893                                 temp_fullname=Which(temp_name);
00894                                 if (temp_fullname) { extension=".bat";strcpy(fullname,temp_fullname);}
00895 
00896                                 else  
00897                                 {
00898                                         return false;
00899                                 }
00900                         
00901                         }       
00902                 }
00903         }
00904         
00905         if (strcasecmp(extension, ".bat") == 0) 
00906         {       /* Run the .bat file */
00907                 /* delete old batch file if call is not active*/
00908                 bool temp_echo=echo; /*keep the current echostate (as delete bf might change it )*/
00909                 if(bf && !call) delete bf;
00910                 bf=new BatchFile(this,fullname,name,line);
00911                 echo=temp_echo; //restore it.
00912         } 
00913         else 
00914         {       /* only .bat .exe .com extensions maybe be executed by the shell */
00915                 if(strcasecmp(extension, ".com") !=0) 
00916                 {
00917                         if(strcasecmp(extension, ".exe") !=0) return false;
00918                 }
00919                 /* Run the .exe or .com file from the shell */
00920                 /* Allocate some stack space for tables in physical memory */
00921                 reg_sp-=0x200;
00922                 //Add Parameter block
00923                 DOS_ParamBlock block(SegPhys(ss)+reg_sp);
00924                 block.Clear();
00925                 //Add a filename
00926                 RealPt file_name=RealMakeSeg(ss,reg_sp+0x20);
00927                 MEM_BlockWrite(Real2Phys(file_name),fullname,(Bitu)(strlen(fullname)+1));
00928 
00929                 /* HACK: Store full commandline for mount and imgmount */
00930                 full_arguments.assign(line);
00931 
00932                 /* Fill the command line */
00933                 CommandTail cmdtail;
00934                 cmdtail.count = 0;
00935                 memset(&cmdtail.buffer,0,127); //Else some part of the string is unitialized (valgrind)
00936                 if (strlen(line)>126) line[126]=0;
00937                 cmdtail.count=(Bit8u)strlen(line);
00938                 memcpy(cmdtail.buffer,line,strlen(line));
00939                 cmdtail.buffer[strlen(line)]=0xd;
00940                 /* Copy command line in stack block too */
00941                 MEM_BlockWrite(SegPhys(ss)+reg_sp+0x100,&cmdtail,128);
00942                 
00943                 /* Split input line up into parameters, using a few special rules, most notable the one for /AAA => A\0AA
00944                  * Qbix: It is extremly messy, but this was the only way I could get things like /:aa and :/aa to work correctly */
00945                 
00946                 //Prepare string first
00947                 char parseline[258] = { 0 };
00948                 for(char *pl = line,*q = parseline; *pl ;pl++,q++) {
00949                         if (*pl == '=' || *pl == ';' || *pl ==',' || *pl == '\t' || *pl == ' ') *q = 0; else *q = *pl; //Replace command seperators with 0.
00950                 } //No end of string \0 needed as parseline is larger than line
00951 
00952                 for(char* p = parseline; (p-parseline) < 250 ;p++) { //Stay relaxed within boundaries as we have plenty of room
00953                         if (*p == '/') { //Transform /Hello into H\0ello
00954                                 *p = 0;
00955                                 p++;
00956                                 while ( *p == 0 && (p-parseline) < 250) p++; //Skip empty fields
00957                                 if ((p-parseline) < 250) { //Found something. Lets get the first letter and break it up
00958                                         p++;
00959                                         memmove(static_cast<void*>(p + 1),static_cast<void*>(p),(250u-(unsigned int)(p-parseline)));
00960                                         if ((p-parseline) < 250) *p = 0;
00961                                 }
00962                         }
00963                 }
00964                 parseline[255] = parseline[256] = parseline[257] = 0; //Just to be safe.
00965 
00966                 /* Parse FCB (first two parameters) and put them into the current DOS_PSP */
00967                 Bit8u add;
00968                 Bit16u skip = 0;
00969                 //find first argument, we end up at parseline[256] if there is only one argument (similar for the second), which exists and is 0.
00970                 while(skip < 256 && parseline[skip] == 0) skip++;
00971                 FCB_Parsename(dos.psp(),0x5C,0x01,parseline + skip,&add);
00972                 skip += add;
00973                 
00974                 //Move to next argument if it exists
00975                 while(parseline[skip] != 0) skip++;  //This is safe as there is always a 0 in parseline at the end.
00976                 while(skip < 256 && parseline[skip] == 0) skip++; //Which is higher than 256
00977                 FCB_Parsename(dos.psp(),0x6C,0x01,parseline + skip,&add);
00978 
00979                 block.exec.fcb1=RealMake(dos.psp(),0x5C);
00980                 block.exec.fcb2=RealMake(dos.psp(),0x6C);
00981                 /* Set the command line in the block and save it */
00982                 block.exec.cmdtail=RealMakeSeg(ss,reg_sp+0x100);
00983                 block.SaveData();
00984 #if 0
00985                 /* Save CS:IP to some point where i can return them from */
00986                 Bit32u oldeip=reg_eip;
00987                 Bit16u oldcs=SegValue(cs);
00988                 RealPt newcsip=CALLBACK_RealPointer(call_shellstop);
00989                 SegSet16(cs,RealSeg(newcsip));
00990                 reg_ip=RealOff(newcsip);
00991 #endif
00992                 /* Start up a dos execute interrupt */
00993                 reg_ax=0x4b00;
00994                 //Filename pointer
00995                 SegSet16(ds,SegValue(ss));
00996                 reg_dx=RealOff(file_name);
00997                 //Paramblock
00998                 SegSet16(es,SegValue(ss));
00999                 reg_bx=reg_sp;
01000                 SETFLAGBIT(IF,false);
01001                 CALLBACK_RunRealInt(0x21);
01002                 /* Restore CS:IP and the stack */
01003                 reg_sp+=0x200;
01004 #if 0
01005                 reg_eip=oldeip;
01006                 SegSet16(cs,oldcs);
01007 #endif
01008         }
01009         return true; //Executable started
01010 }
01011 
01012 
01013 
01014 
01015 static const char * bat_ext=".BAT";
01016 static const char * com_ext=".COM";
01017 static const char * exe_ext=".EXE";
01018 static char which_ret[DOS_PATHLENGTH+4];
01019 
01020 char * DOS_Shell::Which(char * name) {
01021         size_t name_len = strlen(name);
01022         if(name_len >= DOS_PATHLENGTH) return 0;
01023 
01024         /* Parse through the Path to find the correct entry */
01025         /* Check if name is already ok but just misses an extension */
01026 
01027         if (DOS_FileExists(name)) return name;
01028         /* try to find .com .exe .bat */
01029         strcpy(which_ret,name);
01030         strcat(which_ret,com_ext);
01031         if (DOS_FileExists(which_ret)) return which_ret;
01032         strcpy(which_ret,name);
01033         strcat(which_ret,exe_ext);
01034         if (DOS_FileExists(which_ret)) return which_ret;
01035         strcpy(which_ret,name);
01036         strcat(which_ret,bat_ext);
01037         if (DOS_FileExists(which_ret)) return which_ret;
01038 
01039 
01040         /* No Path in filename look through path environment string */
01041         char path[DOS_PATHLENGTH];std::string temp;
01042         if (!GetEnvStr("PATH",temp)) return 0;
01043         const char * pathenv=temp.c_str();
01044         if (!pathenv) return 0;
01045         pathenv = strchr(pathenv,'=');
01046         if (!pathenv) return 0;
01047         pathenv++;
01048         Bitu i_path = 0;
01049         while (*pathenv) {
01050                 /* remove ; and ;; at the beginning. (and from the second entry etc) */
01051                 while(*pathenv == ';')
01052                         pathenv++;
01053 
01054                 /* get next entry */
01055                 i_path = 0; /* reset writer */
01056                 while(*pathenv && (*pathenv !=';') && (i_path < DOS_PATHLENGTH) )
01057                         path[i_path++] = *pathenv++;
01058 
01059                 if(i_path == DOS_PATHLENGTH) {
01060                         /* If max size. move till next ; and terminate path */
01061                         while(*pathenv && (*pathenv != ';')) 
01062                                 pathenv++;
01063                         path[DOS_PATHLENGTH - 1] = 0;
01064                 } else path[i_path] = 0;
01065 
01066 
01067                 /* check entry */
01068                 if(size_t len = strlen(path)){
01069                         if(len >= (DOS_PATHLENGTH - 2)) continue;
01070 
01071                         if(path[len - 1] != '\\') {
01072                                 strcat(path,"\\"); 
01073                                 len++;
01074                         }
01075 
01076                         //If name too long =>next
01077                         if((name_len + len + 1) >= DOS_PATHLENGTH) continue;
01078                         strcat(path,name);
01079 
01080                         strcpy(which_ret,path);
01081                         if (DOS_FileExists(which_ret)) return which_ret;
01082                         strcpy(which_ret,path);
01083                         strcat(which_ret,com_ext);
01084                         if (DOS_FileExists(which_ret)) return which_ret;
01085                         strcpy(which_ret,path);
01086                         strcat(which_ret,exe_ext);
01087                         if (DOS_FileExists(which_ret)) return which_ret;
01088                         strcpy(which_ret,path);
01089                         strcat(which_ret,bat_ext);
01090                         if (DOS_FileExists(which_ret)) return which_ret;
01091                 }
01092         }
01093         return 0;
01094 }