DOSBox-X
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
src/shell/shell_misc.cpp
00001 /*
00002  *  Copyright (C) 2002-2015  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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         Bitu str_len=0;Bitu 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 = (Bitu)it_history->length();
00246                     size = CMD_MAXLINE - str_index - 2;
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 = CMD_MAXLINE - str_index - 2;
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 = CMD_MAXLINE - str_index - 2;
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-1;i++) {
00390                         line[i]=line[i+1];
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 = CMD_MAXLINE - str_index - 2;
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=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('\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];
00480                         if (p_completion_start) {
00481                             strcpy(mask, p_completion_start);
00482                             char* dot_pos=strrchr(mask,'.');
00483                             char* bs_pos=strrchr(mask,'\\');
00484                             char* fs_pos=strrchr(mask,'/');
00485                             char* cl_pos=strrchr(mask,':');
00486                             // not perfect when line already contains wildcards, but works
00487                             if ((dot_pos-bs_pos>0) && (dot_pos-fs_pos>0) && (dot_pos-cl_pos>0))
00488                                 strcat(mask, "*");
00489                             else strcat(mask, "*.*");
00490                         } else {
00491                             strcpy(mask, "*.*");
00492                         }
00493 
00494                         RealPt save_dta=dos.dta();
00495                         dos.dta(dos.tables.tempdta);
00496 
00497                         bool res = DOS_FindFirst(mask, 0xffff & ~DOS_ATTR_VOLUME);
00498                         if (!res) {
00499                             dos.dta(save_dta);
00500                             break;      // TODO: beep
00501                         }
00502 
00503                         DOS_DTA dta(dos.dta());
00504                         char name[DOS_NAMELENGTH_ASCII];Bit32u sz;Bit16u date;Bit16u time;Bit8u att;
00505 
00506                         std::list<std::string> executable;
00507                         while (res) {
00508                             dta.GetResult(name,sz,date,time,att);
00509                             // add result to completion list
00510 
00511                             char *ext;  // file extension
00512                             if (strcmp(name, ".") && strcmp(name, "..")) {
00513                                 if (dir_only) { //Handle the dir only case different (line starts with cd)
00514                                     if(att & DOS_ATTR_DIRECTORY) l_completion.push_back(name);
00515                                 } else {
00516                                     ext = strrchr(name, '.');
00517                                     if (ext && (strcmp(ext, ".BAT") == 0 || strcmp(ext, ".COM") == 0 || strcmp(ext, ".EXE") == 0))
00518                                         // we add executables to the a seperate list and place that list infront of the normal files
00519                                         executable.push_front(name);
00520                                     else
00521                                         l_completion.push_back(name);
00522                                 }
00523                             }
00524                             res=DOS_FindNext();
00525                         }
00526                         /* Add executable list to front of completion list. */
00527                         std::copy(executable.begin(),executable.end(),std::front_inserter(l_completion));
00528                         it_completion = l_completion.begin();
00529                         dos.dta(save_dta);
00530                     }
00531 
00532                     if (l_completion.size() && it_completion->length()) {
00533                         for (;str_index > completion_index; str_index--) {
00534                             // removes all characters
00535                             outc(8); outc(' '); outc(8);
00536                         }
00537 
00538                         strcpy(&line[completion_index], it_completion->c_str());
00539                         len = (Bit16u)it_completion->length();
00540                         str_len = str_index = (Bitu)(completion_index + len);
00541                         size = CMD_MAXLINE - str_index - 2;
00542                         DOS_WriteFile(STDOUT, (Bit8u *)it_completion->c_str(), &len);
00543                     }
00544                 }
00545                 break;
00546             case 0x1b:   /* ESC */
00547                 // NTS: According to real PC-98 DOS:
00548                 //      If DOSKEY is loaded, ESC clears the prompt
00549                 //      If DOSKEY is NOT loaded, ESC does nothing. In fact, after ESC,
00550                 //      the next character input is thrown away before resuming normal keyboard input.
00551                 //
00552                 //      DOSBox / DOSBox-X have always acted as if DOSKEY is loaded in a fashion, so
00553                 //      we'll emulate the PC-98 DOSKEY behavior here.
00554                 //
00555                 //      DOSKEY on PC-98 is able to clear the whole prompt and even bring the cursor
00556                 //      back up to the first line if the input crosses multiple lines.
00557 
00558                 // NTS: According to real IBM/Microsoft PC/AT DOS:
00559                 //      If DOSKEY is loaded, ESC clears the prompt
00560                 //      If DOSKEY is NOT loaded, ESC prints a backslash and goes to the next line.
00561                 //      The Windows 95 version of DOSKEY puts the cursor at a horizontal position
00562                 //      that matches the DOS prompt (not emulated here).
00563                 //
00564                 //      DOSBox / DOSBox-X have always acted as if DOSKEY is loaded in a fashion, so
00565                 //      we'll emulate DOSKEY behavior here.
00566 
00567                 while (str_index < str_len) {
00568                     outc(' ');
00569                     str_index++;
00570                 }
00571                 while (str_index > 0) {
00572                     outc(8);
00573                     outc(' ');
00574                     outc(8);
00575                     MoveCaretBackwards();
00576                     str_index--;
00577                 }
00578 
00579                 *line = 0;      // reset the line.
00580                 if (l_completion.size()) l_completion.clear(); //reset the completion list.
00581                 str_index = 0;
00582                 str_len = 0;
00583                 break;
00584             default:
00585                 if (cr >= 0x100) break;
00586                 if (l_completion.size()) l_completion.clear();
00587                 if(str_index < str_len && !INT10_GetInsertState()) { //mem_readb(BIOS_KEYBOARD_FLAGS1)&0x80) dev_con.h ?
00588                     outc(' ');//move cursor one to the right.
00589                     Bit16u a = str_len - str_index;
00590                     Bit8u* text=reinterpret_cast<Bit8u*>(&line[str_index]);
00591                     DOS_WriteFile(STDOUT,text,&a);//write buffer to screen
00592                     outc(8);//undo the cursor the right.
00593                     for(Bitu i=str_len;i>str_index;i--) {
00594                         line[i]=line[i-1]; //move internal buffer
00595                         outc(8); //move cursor back (from write buffer to screen)
00596                     }
00597                     line[++str_len]=0;//new end (as the internal buffer moved one place to the right
00598                     size--;
00599                 };
00600 
00601                 line[str_index]=(char)(cr&0xFF);
00602                 str_index ++;
00603                 if (str_index > str_len){ 
00604                     line[str_index] = '\0';
00605                     str_len++;
00606                     size--;
00607                 }
00608                 DOS_WriteFile(STDOUT,&c,&n);
00609                 break;
00610         }
00611     }
00612 
00613         if (!str_len) return;
00614         str_len++;
00615 
00616         // remove current command from history if it's there
00617         if (current_hist) {
00618                 current_hist=false;
00619                 l_history.pop_front();
00620         }
00621 
00622         // add command line to history. Win95 behavior with DOSKey suggests
00623         // that the original string is preserved, not the expanded string.
00624         l_history.push_front(line); it_history = l_history.begin();
00625         if (l_completion.size()) l_completion.clear();
00626 
00627         /* DOS %variable% substitution */
00628         ProcessCmdLineEnvVarStitution(line);
00629 }
00630 
00631 
00632 /* WARNING: Substitution is carried out in-place!
00633  * Buffer pointed to by "line" must be at least CMD_MAXLINE+1 bytes long! */
00634 void DOS_Shell::ProcessCmdLineEnvVarStitution(char *line) {
00635         char temp[CMD_MAXLINE]; /* <- NTS: Currently 4096 which is very generous indeed! */
00636         char *w=temp,*wf=temp+sizeof(temp)-1;
00637         char *r=line;
00638 
00639         /* initial scan: is there anything to substitute? */
00640         /* if not, then return without modifying "line" */
00641         while (*r != 0 && *r != '%') r++;
00642         if (*r != '%') return;
00643 
00644         /* if the incoming string is already too long, then that's a problem too! */
00645         if (((size_t)(r+1-line)) >= CMD_MAXLINE) {
00646                 LOG_MSG("DOS_Shell::ProcessCmdLineEnvVarStitution WARNING incoming string to substitute is already too long!\n");
00647                 goto overflow;
00648         }
00649 
00650         /* copy the string down up to that point */
00651         for (char *c=line;c < r;) {
00652                 assert(w < wf);
00653                 *w++ = *c++;
00654         }
00655 
00656         /* begin substitution process */
00657         while (*r != 0) {
00658                 if (*r == '%') {
00659                         r++;
00660                         if (*r == '%' || *r == 0) {
00661                                 /* %% or leaving a trailing % at the end (Win95 behavior) becomes a single '%' */
00662                                 if (w >= wf) goto overflow;
00663                                 *w++ = '%';
00664                                 if (*r != 0) r++;
00665                                 else break;
00666                         }
00667                         else {
00668                                 char *name = r; /* store pointer, 'r' is first char of the name following '%' */
00669                                 int spaces = 0,chars = 0;
00670 
00671                                 /* continue scanning for the ending '%'. variable names are apparently meant to be
00672                                  * alphanumeric, start with a letter, without spaces (if Windows 95 COMMAND.COM is
00673                                  * any good example). If it doesn't end in '%' or is broken by space or starts with
00674                                  * a number, substitution is not carried out. In the middle of the variable name
00675                                  * it seems to be acceptable to use hyphens.
00676                                  *
00677                                  * since spaces break up a variable name to prevent substitution, these commands
00678                                  * act differently from one another:
00679                                  *
00680                                  * C:>echo %PATH%
00681                                  * C:\DOS;C:\WINDOWS
00682                                  *
00683                                  * C:>echo %PATH %
00684                                  * %PATH % */
00685                                 if (isalpha(*r) || *r == ' ') { /* must start with a letter. space is apparently valid too. (Win95) */
00686                                         if (*r == ' ') spaces++;
00687                                         else if (isalpha(*r)) chars++;
00688 
00689                                         r++;
00690                                         while (*r != 0 && *r != '%') {
00691                                                 if (*r == ' ') spaces++;
00692                                                 else chars++;
00693                                                 r++;
00694                                         }
00695                                 }
00696 
00697                                 /* Win95 testing:
00698                                  *
00699                                  * "%" = "%"
00700                                  * "%%" = "%"
00701                                  * "% %" = ""
00702                                  * "%  %" = ""
00703                                  * "% % %" = " %"
00704                                  *
00705                                  * ^ WTF?
00706                                  *
00707                                  * So the below code has funny conditions to match Win95's weird rules on what
00708                                  * consitutes valid or invalid %variable% names. */
00709                                 if (*r == '%' && ((spaces > 0 && chars == 0) || (spaces == 0 && chars > 0))) {
00710                                         std::string temp;
00711 
00712                                         /* valid name found. substitute */
00713                                         *r++ = 0; /* ASCIIZ snip */
00714                                         if (GetEnvStr(name,temp)) {
00715                                                 size_t equ_pos = temp.find_first_of('=');
00716                                                 if (equ_pos != std::string::npos) {
00717                                                         const char *base = temp.c_str();
00718                                                         const char *value = base + equ_pos + 1;
00719                                                         const char *fence = base + temp.length();
00720                                                         assert(value >= base && value <= fence);
00721                                                         size_t len = (size_t)(fence-value);
00722 
00723                                                         if ((w+len) > wf) goto overflow;
00724                                                         memcpy(w,value,len);
00725                                                         w += len;
00726                                                 }
00727                                         }
00728                                 }
00729                                 else {
00730                                         /* nope. didn't find a valid name */
00731 
00732                                         while (*r != 0 && *r == ' ') r++; /* skip spaces */
00733                                         name--; /* step "name" back to cover the first '%' we found */
00734 
00735                                         for (char *c=name;c < r;) {
00736                                                 if (w >= wf) goto overflow;
00737                                                 *w++ = *c++;
00738                                         }
00739                                 }
00740                         }
00741                 }
00742                 else {
00743                         if (w >= wf) goto overflow;
00744                         *w++ = *r++;
00745                 }
00746         }
00747 
00748         /* complete the C-string */
00749         assert(w <= wf);
00750         *w = 0;
00751 
00752         /* copy the string back over the buffer pointed to by line */
00753         {
00754                 size_t out_len = (size_t)(w+1-temp); /* length counting the NUL too */
00755                 assert(out_len <= CMD_MAXLINE);
00756                 memcpy(line,temp,out_len);
00757         }
00758 
00759         /* success */
00760         return;
00761 overflow:
00762         *line = 0; /* clear string (C-string chop with NUL) */
00763         WriteOut("Command input error: string expansion overflow\n");
00764 }
00765 
00766 std::string full_arguments = "";
00767 bool DOS_Shell::Execute(char * name,char * args) {
00768 /* return true  => don't check for hardware changes in do_command 
00769  * return false =>       check for hardware changes in do_command */
00770         char fullname[DOS_PATHLENGTH+4]; //stores results from Which
00771         char* p_fullname;
00772         char line[CMD_MAXLINE];
00773         if(strlen(args)!= 0){
00774                 if(*args != ' '){ //put a space in front
00775                         line[0]=' ';line[1]=0;
00776                         strncat(line,args,CMD_MAXLINE-2);
00777                         line[CMD_MAXLINE-1]=0;
00778                 }
00779                 else
00780                 {
00781                         safe_strncpy(line,args,CMD_MAXLINE);
00782                 }
00783         }else{
00784                 line[0]=0;
00785         };
00786 
00787         /* check for a drive change */
00788         if (((strcmp(name + 1, ":") == 0) || (strcmp(name + 1, ":\\") == 0)) && isalpha(*name))
00789         {
00790                 if (strrchr(name,'\\')) { WriteOut(MSG_Get("SHELL_EXECUTE_ILLEGAL_COMMAND"),name); return true; }
00791                 if (!DOS_SetDrive(toupper(name[0])-'A')) {
00792 #ifdef WIN32
00793                         Section_prop * sec=0; sec=static_cast<Section_prop *>(control->GetSection("dos"));
00794                         if(!sec->Get_bool("automount")) { WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_NOT_FOUND"),toupper(name[0])); return true; }
00795                         // automount: attempt direct letter to drive map.
00796                         if((GetDriveType(name)==3) && (strcasecmp(name,"c:")==0)) WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_ACCESS_WARNING_WIN"));
00797 first_1:
00798                         if(GetDriveType(name)==5) WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_ACCESS_CDROM"),toupper(name[0]));
00799                         else if(GetDriveType(name)==2) WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_ACCESS_FLOPPY"),toupper(name[0]));
00800                         else if((GetDriveType(name)==3)||(GetDriveType(name)==4)||(GetDriveType(name)==6)) WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_ACCESS_FIXED"),toupper(name[0]));
00801                         else { WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_NOT_FOUND"),toupper(name[0])); return true; }
00802 
00803 first_2:
00804                 Bit8u c;Bit16u n=1;
00805                 DOS_ReadFile (STDIN,&c,&n);
00806                 do switch (c) {
00807                         case 'n':                       case 'N':
00808                         {
00809                                 DOS_WriteFile (STDOUT,&c, &n);
00810                                 DOS_ReadFile (STDIN,&c,&n);
00811                                 do switch (c) {
00812                                         case 0xD: WriteOut("\n\n"); WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_NOT_FOUND"),toupper(name[0])); return true;
00813                                         case 0x08: WriteOut("\b \b"); goto first_2;
00814                                 } while (DOS_ReadFile (STDIN,&c,&n));
00815                         }
00816                         case 'y':                       case 'Y':
00817                         {
00818                                 DOS_WriteFile (STDOUT,&c, &n);
00819                                 DOS_ReadFile (STDIN,&c,&n);
00820                                 do switch (c) {
00821                                         case 0xD: WriteOut("\n"); goto continue_1;
00822                                         case 0x08: WriteOut("\b \b"); goto first_2;
00823                                 } while (DOS_ReadFile (STDIN,&c,&n));
00824                         }
00825                         case 0xD: WriteOut("\n"); goto first_1;
00826                         case '\t': case 0x08: goto first_2;
00827                         default:
00828                         {
00829                                 DOS_WriteFile (STDOUT,&c, &n);
00830                                 DOS_ReadFile (STDIN,&c,&n);
00831                                 do switch (c) {
00832                                         case 0xD: WriteOut("\n");goto first_1;
00833                                         case 0x08: WriteOut("\b \b"); goto first_2;
00834                                 } while (DOS_ReadFile (STDIN,&c,&n));
00835                                 goto first_2;
00836                         }
00837                 } while (DOS_ReadFile (STDIN,&c,&n));
00838 
00839 continue_1:
00840 
00841                         char mountstring[DOS_PATHLENGTH+CROSS_LEN+20];
00842                         sprintf(mountstring,"MOUNT %s ",name);
00843 
00844                         if(GetDriveType(name)==5) strcat(mountstring,"-t cdrom ");
00845                         else if(GetDriveType(name)==2) strcat(mountstring,"-t floppy ");
00846                         strcat(mountstring,name);
00847                         strcat(mountstring,"\\");
00848 //                      if(GetDriveType(name)==5) strcat(mountstring," -ioctl");
00849                         
00850                         this->ParseLine(mountstring);
00851 //failed:
00852                         if (!DOS_SetDrive(toupper(name[0])-'A'))
00853 #endif
00854                         WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_NOT_FOUND"),toupper(name[0]));
00855                 }
00856                 return true;
00857         }
00858         /* Check for a full name */
00859         p_fullname = Which(name);
00860         if (!p_fullname) return false;
00861         strcpy(fullname,p_fullname);
00862         const char* extension = strrchr(fullname,'.');
00863         
00864         /*always disallow files without extension from being executed. */
00865         /*only internal commands can be run this way and they never get in this handler */
00866         if(extension == 0)
00867         {
00868                 //Check if the result will fit in the parameters. Else abort
00869                 if(strlen(fullname) >( DOS_PATHLENGTH - 1) ) return false;
00870                 char temp_name[DOS_PATHLENGTH+4],* temp_fullname;
00871                 //try to add .com, .exe and .bat extensions to filename
00872                 
00873                 strcpy(temp_name,fullname);
00874                 strcat(temp_name,".COM");
00875                 temp_fullname=Which(temp_name);
00876                 if (temp_fullname) { extension=".com";strcpy(fullname,temp_fullname); }
00877 
00878                 else 
00879                 {
00880                         strcpy(temp_name,fullname);
00881                         strcat(temp_name,".EXE");
00882                         temp_fullname=Which(temp_name);
00883                         if (temp_fullname) { extension=".exe";strcpy(fullname,temp_fullname);}
00884 
00885                         else 
00886                         {
00887                                 strcpy(temp_name,fullname);
00888                                 strcat(temp_name,".BAT");
00889                                 temp_fullname=Which(temp_name);
00890                                 if (temp_fullname) { extension=".bat";strcpy(fullname,temp_fullname);}
00891 
00892                                 else  
00893                                 {
00894                                         return false;
00895                                 }
00896                         
00897                         }       
00898                 }
00899         }
00900         
00901         if (strcasecmp(extension, ".bat") == 0) 
00902         {       /* Run the .bat file */
00903                 /* delete old batch file if call is not active*/
00904                 bool temp_echo=echo; /*keep the current echostate (as delete bf might change it )*/
00905                 if(bf && !call) delete bf;
00906                 bf=new BatchFile(this,fullname,name,line);
00907                 echo=temp_echo; //restore it.
00908         } 
00909         else 
00910         {       /* only .bat .exe .com extensions maybe be executed by the shell */
00911                 if(strcasecmp(extension, ".com") !=0) 
00912                 {
00913                         if(strcasecmp(extension, ".exe") !=0) return false;
00914                 }
00915                 /* Run the .exe or .com file from the shell */
00916                 /* Allocate some stack space for tables in physical memory */
00917                 reg_sp-=0x200;
00918                 //Add Parameter block
00919                 DOS_ParamBlock block(SegPhys(ss)+reg_sp);
00920                 block.Clear();
00921                 //Add a filename
00922                 RealPt file_name=RealMakeSeg(ss,reg_sp+0x20);
00923                 MEM_BlockWrite(Real2Phys(file_name),fullname,(Bitu)(strlen(fullname)+1));
00924 
00925                 /* HACK: Store full commandline for mount and imgmount */
00926                 full_arguments.assign(line);
00927 
00928                 /* Fill the command line */
00929                 CommandTail cmdtail;
00930                 cmdtail.count = 0;
00931                 memset(&cmdtail.buffer,0,127); //Else some part of the string is unitialized (valgrind)
00932                 if (strlen(line)>126) line[126]=0;
00933                 cmdtail.count=(Bit8u)strlen(line);
00934                 memcpy(cmdtail.buffer,line,strlen(line));
00935                 cmdtail.buffer[strlen(line)]=0xd;
00936                 /* Copy command line in stack block too */
00937                 MEM_BlockWrite(SegPhys(ss)+reg_sp+0x100,&cmdtail,128);
00938                 /* Parse FCB (first two parameters) and put them into the current DOS_PSP */
00939                 Bit8u add;
00940                 FCB_Parsename(dos.psp(),0x5C,0x00,cmdtail.buffer,&add);
00941                 FCB_Parsename(dos.psp(),0x6C,0x00,&cmdtail.buffer[add],&add);
00942                 block.exec.fcb1=RealMake(dos.psp(),0x5C);
00943                 block.exec.fcb2=RealMake(dos.psp(),0x6C);
00944                 /* Set the command line in the block and save it */
00945                 block.exec.cmdtail=RealMakeSeg(ss,reg_sp+0x100);
00946                 block.SaveData();
00947 #if 0
00948                 /* Save CS:IP to some point where i can return them from */
00949                 Bit32u oldeip=reg_eip;
00950                 Bit16u oldcs=SegValue(cs);
00951                 RealPt newcsip=CALLBACK_RealPointer(call_shellstop);
00952                 SegSet16(cs,RealSeg(newcsip));
00953                 reg_ip=RealOff(newcsip);
00954 #endif
00955                 /* Start up a dos execute interrupt */
00956                 reg_ax=0x4b00;
00957                 //Filename pointer
00958                 SegSet16(ds,SegValue(ss));
00959                 reg_dx=RealOff(file_name);
00960                 //Paramblock
00961                 SegSet16(es,SegValue(ss));
00962                 reg_bx=reg_sp;
00963                 SETFLAGBIT(IF,false);
00964                 CALLBACK_RunRealInt(0x21);
00965                 /* Restore CS:IP and the stack */
00966                 reg_sp+=0x200;
00967 #if 0
00968                 reg_eip=oldeip;
00969                 SegSet16(cs,oldcs);
00970 #endif
00971         }
00972         return true; //Executable started
00973 }
00974 
00975 
00976 
00977 
00978 static const char * bat_ext=".BAT";
00979 static const char * com_ext=".COM";
00980 static const char * exe_ext=".EXE";
00981 static char which_ret[DOS_PATHLENGTH+4];
00982 
00983 char * DOS_Shell::Which(char * name) {
00984         size_t name_len = strlen(name);
00985         if(name_len >= DOS_PATHLENGTH) return 0;
00986 
00987         /* Parse through the Path to find the correct entry */
00988         /* Check if name is already ok but just misses an extension */
00989 
00990         if (DOS_FileExists(name)) return name;
00991         /* try to find .com .exe .bat */
00992         strcpy(which_ret,name);
00993         strcat(which_ret,com_ext);
00994         if (DOS_FileExists(which_ret)) return which_ret;
00995         strcpy(which_ret,name);
00996         strcat(which_ret,exe_ext);
00997         if (DOS_FileExists(which_ret)) return which_ret;
00998         strcpy(which_ret,name);
00999         strcat(which_ret,bat_ext);
01000         if (DOS_FileExists(which_ret)) return which_ret;
01001 
01002 
01003         /* No Path in filename look through path environment string */
01004         char path[DOS_PATHLENGTH];std::string temp;
01005         if (!GetEnvStr("PATH",temp)) return 0;
01006         const char * pathenv=temp.c_str();
01007         if (!pathenv) return 0;
01008         pathenv = strchr(pathenv,'=');
01009         if (!pathenv) return 0;
01010         pathenv++;
01011         Bitu i_path = 0;
01012         while (*pathenv) {
01013                 /* remove ; and ;; at the beginning. (and from the second entry etc) */
01014                 while(*pathenv == ';')
01015                         pathenv++;
01016 
01017                 /* get next entry */
01018                 i_path = 0; /* reset writer */
01019                 while(*pathenv && (*pathenv !=';') && (i_path < DOS_PATHLENGTH) )
01020                         path[i_path++] = *pathenv++;
01021 
01022                 if(i_path == DOS_PATHLENGTH) {
01023                         /* If max size. move till next ; and terminate path */
01024                         while(*pathenv && (*pathenv != ';')) 
01025                                 pathenv++;
01026                         path[DOS_PATHLENGTH - 1] = 0;
01027                 } else path[i_path] = 0;
01028 
01029 
01030                 /* check entry */
01031                 if(size_t len = strlen(path)){
01032                         if(len >= (DOS_PATHLENGTH - 2)) continue;
01033 
01034                         if(path[len - 1] != '\\') {
01035                                 strcat(path,"\\"); 
01036                                 len++;
01037                         }
01038 
01039                         //If name too long =>next
01040                         if((name_len + len + 1) >= DOS_PATHLENGTH) continue;
01041                         strcat(path,name);
01042 
01043                         strcpy(which_ret,path);
01044                         if (DOS_FileExists(which_ret)) return which_ret;
01045                         strcpy(which_ret,path);
01046                         strcat(which_ret,com_ext);
01047                         if (DOS_FileExists(which_ret)) return which_ret;
01048                         strcpy(which_ret,path);
01049                         strcat(which_ret,exe_ext);
01050                         if (DOS_FileExists(which_ret)) return which_ret;
01051                         strcpy(which_ret,path);
01052                         strcat(which_ret,bat_ext);
01053                         if (DOS_FileExists(which_ret)) return which_ret;
01054                 }
01055         }
01056         return 0;
01057 }