DOSBox-X
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator
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 #ifdef WIN32
00032 #include "../dos/cdrom.h"
00033 #endif 
00034 
00035 void DOS_Shell::ShowPrompt(void) {
00036         char dir[DOS_PATHLENGTH];
00037         dir[0] = 0; //DOS_GetCurrentDir doesn't always return something. (if drive is messed up)
00038         DOS_GetCurrentDir(0,dir);
00039         std::string line;
00040         const char * promptstr = "\0";
00041 
00042         if(GetEnvStr("PROMPT",line)) {
00043                 std::string::size_type idx = line.find('=');
00044                 std::string value=line.substr(idx +1 , std::string::npos);
00045                 line = std::string(promptstr) + value;
00046                 promptstr = line.c_str();
00047         }
00048 
00049         while (*promptstr) {
00050                 if (!strcasecmp(promptstr,"$"))
00051                         WriteOut("\0");
00052                 else if(*promptstr != '$')
00053                         WriteOut("%c",*promptstr);
00054                 else switch (toupper(*++promptstr)) {
00055                         case 'A': WriteOut("&"); break;
00056                         case 'B': WriteOut("|"); break;
00057                         case 'C': WriteOut("("); break;
00058                         case 'D': WriteOut("%02d-%02d-%04d",dos.date.day,dos.date.month,dos.date.year); break;
00059                         case 'E': WriteOut("%c",27);  break;
00060                         case 'F': WriteOut(")");  break;
00061                         case 'G': WriteOut(">"); break;
00062                         case 'H': WriteOut("\b");   break;
00063                         case 'L': WriteOut("<"); break;
00064                         case 'N': WriteOut("%c",DOS_GetDefaultDrive()+'A'); break;
00065                         case 'P': WriteOut("%c:\\%s",DOS_GetDefaultDrive()+'A',dir); break;
00066                         case 'Q': WriteOut("="); break;
00067                         case 'S': WriteOut(" "); break;
00068                         case 'T': {
00069                                 Bitu ticks=(Bitu)(((65536.0 * 100.0)/(double)PIT_TICK_RATE)* mem_readd(BIOS_TIMER));
00070                                 reg_dl=(Bit8u)((Bitu)ticks % 100);
00071                                 ticks/=100;
00072                                 reg_dh=(Bit8u)((Bitu)ticks % 60);
00073                                 ticks/=60;
00074                                 reg_cl=(Bit8u)((Bitu)ticks % 60);
00075                                 ticks/=60;
00076                                 reg_ch=(Bit8u)((Bitu)ticks % 24);
00077                                 WriteOut("%2d:%02d:%02d.%02d",reg_ch,reg_cl,reg_dh,reg_dl);
00078                                 break;
00079                         }
00080                         case 'V': WriteOut("DOSBox version %s. Reported DOS version %d.%d.",VERSION,dos.version.major,dos.version.minor); break;
00081                         case '$': WriteOut("$"); break;
00082                         case '_': WriteOut("\n"); break;
00083                         case 'M': break;
00084                         case '+': break;
00085                 }
00086                 promptstr++;
00087         }
00088 }
00089 
00090 static void outc(Bit8u c) {
00091         Bit16u n=1;
00092         DOS_WriteFile(STDOUT,&c,&n);
00093 }
00094 
00095 /* NTS: buffer pointed to by "line" must be at least CMD_MAXLINE+1 large */
00096 void DOS_Shell::InputCommand(char * line) {
00097         Bitu size=CMD_MAXLINE-2; //lastcharacter+0
00098         Bit8u c;Bit16u n=1;
00099         Bitu str_len=0;Bitu str_index=0;
00100         Bit16u len=0;
00101         bool current_hist=false; // current command stored in history?
00102     Bit16u cr;
00103 
00104     input_eof = false;
00105         line[0] = '\0';
00106 
00107         std::list<std::string>::iterator it_history = l_history.begin(), it_completion = l_completion.begin();
00108 
00109         while (size) {
00110                 dos.echo=false;
00111                 if (!DOS_ReadFile(input_handle,&c,&n)) {
00112             LOG(LOG_MISC,LOG_ERROR)("SHELL: Lost the input handle, dropping shell input loop");
00113             n = 0;
00114         }
00115                 if (!n) {
00116             input_eof = true;
00117                         size=0;                 //Kill the while loop
00118                         continue;
00119                 }
00120 
00121         if (input_handle != STDIN) { /* FIXME: Need DOS_IsATTY() or somesuch */
00122             cr = (Bit16u)c; /* we're not reading from the console */
00123         }
00124         else if (IS_PC98_ARCH) {
00125             extern Bit16u last_int16_code;
00126 
00127             /* NTS: Since left arrow and backspace map to the same byte value, PC-98 treats it the same at the DOS prompt.
00128              *      However the PC-98 version of DOSKEY seems to be able to differentiate the two anyway and let the left
00129              *      arrow move the cursor back (perhaps it's calling INT 18h directly then?) */
00130                  if (c == 0x0B)
00131                 cr = 0x4800;    /* IBM extended code up arrow */
00132             else if (c == 0x0A)
00133                 cr = 0x5000;    /* IBM extended code down arrow */
00134             else if (c == 0x0C)
00135                 cr = 0x4D00;    /* IBM extended code right arrow */
00136             else if (c == 0x08) {
00137                 /* IBM extended code left arrow OR backspace. use last scancode to tell which as DOSKEY apparently can. */
00138                 if (last_int16_code == 0x3B00)
00139                     cr = 0x4B00; /* left arrow */
00140                 else
00141                     cr = 0x08; /* backspace */
00142             }
00143             else if (c == 0x1B) { /* escape */
00144                                 DOS_ReadFile(input_handle,&c,&n);
00145                      if (c == 0x44)  // DEL
00146                     cr = 0x5300;
00147                 else if (c == 0x53)  // F1
00148                     cr = 0x3B00;
00149                 else if (c == 0x54)  // F2
00150                     cr = 0x3C00;
00151                 else if (c == 0x55)  // F3
00152                     cr = 0x3D00;
00153                 else if (c == 0x56)  // F4
00154                     cr = 0x3E00;
00155                 else if (c == 0x57)  // F5
00156                     cr = 0x3F00;
00157                 else if (c == 0x45)  // F6
00158                     cr = 0x4000;
00159                 else if (c == 0x4A)  // F7
00160                     cr = 0x4100;
00161                 else if (c == 0x50)  // F8
00162                     cr = 0x4200;
00163                 else if (c == 0x51)  // F9
00164                     cr = 0x4300;
00165                 else if (c == 0x5A)  // F10
00166                     cr = 0x4400;
00167                 else
00168                     cr = 0;
00169             }
00170             else
00171                 cr = (Bit16u)c;
00172         }
00173         else {
00174             if (c == 0) {
00175                                 DOS_ReadFile(input_handle,&c,&n);
00176                 cr = (Bit16u)c << (Bit16u)8;
00177             }
00178             else {
00179                 cr = (Bit16u)c;
00180             }
00181         }
00182 
00183         switch (cr) {
00184             case 0x3d00:        /* F3 */
00185                 if (!l_history.size()) break;
00186                 it_history = l_history.begin();
00187                 if (it_history != l_history.end() && it_history->length() > str_len) {
00188                     const char *reader = &(it_history->c_str())[str_len];
00189                     while ((c = (Bit8u)(*reader++))) {
00190                         line[str_index ++] = (char)c;
00191                         DOS_WriteFile(STDOUT,&c,&n);
00192                     }
00193                     str_len = str_index = (Bitu)it_history->length();
00194                     size = CMD_MAXLINE - str_index - 2;
00195                     line[str_len] = 0;
00196                 }
00197                 break;
00198 
00199             case 0x4B00:        /* LEFT */
00200                 if (str_index) {
00201                     outc(8);
00202                     str_index --;
00203                 }
00204                 break;
00205 
00206             case 0x4D00:        /* RIGHT */
00207                 if (str_index < str_len) {
00208                     outc((Bit8u)line[str_index++]);
00209                 }
00210                 break;
00211 
00212             case 0x4700:        /* HOME */
00213                 while (str_index) {
00214                     outc(8);
00215                     str_index--;
00216                 }
00217                 break;
00218 
00219             case 0x4F00:        /* END */
00220                 while (str_index < str_len) {
00221                     outc((Bit8u)line[str_index++]);
00222                 }
00223                 break;
00224 
00225             case 0x4800:        /* UP */
00226                 if (l_history.empty() || it_history == l_history.end()) break;
00227 
00228                 // store current command in history if we are at beginning
00229                 if (it_history == l_history.begin() && !current_hist) {
00230                     current_hist=true;
00231                     l_history.push_front(line);
00232                 }
00233 
00234                 for (;str_index>0; str_index--) {
00235                     // removes all characters
00236                     outc(8); outc(' '); outc(8);
00237                 }
00238                 strcpy(line, it_history->c_str());
00239                 len = (Bit16u)it_history->length();
00240                 str_len = str_index = len;
00241                 size = CMD_MAXLINE - str_index - 2;
00242                 DOS_WriteFile(STDOUT, (Bit8u *)line, &len);
00243                 it_history ++;
00244                 break;
00245 
00246             case 0x5000:        /* DOWN */
00247                 if (l_history.empty() || it_history == l_history.begin()) break;
00248 
00249                 // not very nice but works ..
00250                 it_history --;
00251                 if (it_history == l_history.begin()) {
00252                     // no previous commands in history
00253                     it_history ++;
00254 
00255                     // remove current command from history
00256                     if (current_hist) {
00257                         current_hist=false;
00258                         l_history.pop_front();
00259                     }
00260                     break;
00261                 } else it_history --;
00262 
00263                 for (;str_index>0; str_index--) {
00264                     // removes all characters
00265                     outc(8); outc(' '); outc(8);
00266                 }
00267                 strcpy(line, it_history->c_str());
00268                 len = (Bit16u)it_history->length();
00269                 str_len = str_index = len;
00270                 size = CMD_MAXLINE - str_index - 2;
00271                 DOS_WriteFile(STDOUT, (Bit8u *)line, &len);
00272                 it_history ++;
00273 
00274                 break;
00275             case 0x5300:/* DELETE */
00276                 {
00277                     if(str_index>=str_len) break;
00278                     Bit16u a=str_len-str_index-1;
00279                     Bit8u* text=reinterpret_cast<Bit8u*>(&line[str_index+1]);
00280                     DOS_WriteFile(STDOUT,text,&a);//write buffer to screen
00281                     outc(' ');outc(8);
00282                     for(Bitu i=str_index;i<str_len-1;i++) {
00283                         line[i]=line[i+1];
00284                         outc(8);
00285                     }
00286                     line[--str_len]=0;
00287                     size++;
00288                 }
00289                 break;
00290             case 0x0F00:        /* Shift-Tab */
00291                 if (l_completion.size()) {
00292                     if (it_completion == l_completion.begin()) it_completion = l_completion.end (); 
00293                     it_completion--;
00294 
00295                     if (it_completion->length()) {
00296                         for (;str_index > completion_index; str_index--) {
00297                             // removes all characters
00298                             outc(8); outc(' '); outc(8);
00299                         }
00300 
00301                         strcpy(&line[completion_index], it_completion->c_str());
00302                         len = (Bit16u)it_completion->length();
00303                         str_len = str_index = (Bitu)(completion_index + len);
00304                         size = CMD_MAXLINE - str_index - 2;
00305                         DOS_WriteFile(STDOUT, (Bit8u *)it_completion->c_str(), &len);
00306                     }
00307                 }
00308                 break;
00309             case 0x08:                          /* BackSpace */
00310                 if (str_index) {
00311                     outc(8);
00312                     Bit32u str_remain=str_len - str_index;
00313                     size++;
00314                     if (str_remain) {
00315                         memmove(&line[str_index-1],&line[str_index],str_remain);
00316                         line[--str_len]=0;
00317                         str_index --;
00318                         /* Go back to redraw */
00319                         for (Bit16u i=str_index; i < str_len; i++)
00320                             outc((Bit8u)line[i]);
00321                     } else {
00322                         line[--str_index] = '\0';
00323                         str_len--;
00324                     }
00325                     outc(' ');  outc(8);
00326                     // moves the cursor left
00327                     while (str_remain--) outc(8);
00328                 }
00329                 if (l_completion.size()) l_completion.clear();
00330                 break;
00331             case 0x0a:                          /* Give a new Line */
00332                 outc('\n');
00333                 break;
00334             case '': // FAKE CTRL-C
00335                 outc(94); outc('C');
00336                 *line = 0;      // reset the line.
00337                 if (l_completion.size()) l_completion.clear(); //reset the completion list.
00338                 if(!echo) outc('\n');
00339                 size = 0;       // stop the next loop
00340                 str_len = 0;    // prevent multiple adds of the same line
00341                 break;
00342             case 0x0d:                          /* Don't care, and return */
00343                 if(!echo) outc('\n');
00344                 size=0;                 //Kill the while loop
00345                 break;
00346             case'\t':
00347                 {
00348                     if (l_completion.size()) {
00349                         it_completion ++;
00350                         if (it_completion == l_completion.end()) it_completion = l_completion.begin();
00351                     } else {
00352                         // build new completion list
00353                         // Lines starting with CD will only get directories in the list
00354                         bool dir_only = (strncasecmp(line,"CD ",3)==0);
00355 
00356                         // get completion mask
00357                         char *p_completion_start = strrchr(line, ' ');
00358 
00359                         if (p_completion_start) {
00360                             p_completion_start ++;
00361                             completion_index = (Bit16u)(str_len - strlen(p_completion_start));
00362                         } else {
00363                             p_completion_start = line;
00364                             completion_index = 0;
00365                         }
00366 
00367                         char *path;
00368                         if ((path = strrchr(line+completion_index,'\\'))) completion_index = (Bit16u)(path-line+1);
00369                         if ((path = strrchr(line+completion_index,'/'))) completion_index = (Bit16u)(path-line+1);
00370 
00371                         // build the completion list
00372                         char mask[DOS_PATHLENGTH];
00373                         if (p_completion_start) {
00374                             strcpy(mask, p_completion_start);
00375                             char* dot_pos=strrchr(mask,'.');
00376                             char* bs_pos=strrchr(mask,'\\');
00377                             char* fs_pos=strrchr(mask,'/');
00378                             char* cl_pos=strrchr(mask,':');
00379                             // not perfect when line already contains wildcards, but works
00380                             if ((dot_pos-bs_pos>0) && (dot_pos-fs_pos>0) && (dot_pos-cl_pos>0))
00381                                 strcat(mask, "*");
00382                             else strcat(mask, "*.*");
00383                         } else {
00384                             strcpy(mask, "*.*");
00385                         }
00386 
00387                         RealPt save_dta=dos.dta();
00388                         dos.dta(dos.tables.tempdta);
00389 
00390                         bool res = DOS_FindFirst(mask, 0xffff & ~DOS_ATTR_VOLUME);
00391                         if (!res) {
00392                             dos.dta(save_dta);
00393                             break;      // TODO: beep
00394                         }
00395 
00396                         DOS_DTA dta(dos.dta());
00397                         char name[DOS_NAMELENGTH_ASCII];Bit32u sz;Bit16u date;Bit16u time;Bit8u att;
00398 
00399                         std::list<std::string> executable;
00400                         while (res) {
00401                             dta.GetResult(name,sz,date,time,att);
00402                             // add result to completion list
00403 
00404                             char *ext;  // file extension
00405                             if (strcmp(name, ".") && strcmp(name, "..")) {
00406                                 if (dir_only) { //Handle the dir only case different (line starts with cd)
00407                                     if(att & DOS_ATTR_DIRECTORY) l_completion.push_back(name);
00408                                 } else {
00409                                     ext = strrchr(name, '.');
00410                                     if (ext && (strcmp(ext, ".BAT") == 0 || strcmp(ext, ".COM") == 0 || strcmp(ext, ".EXE") == 0))
00411                                         // we add executables to the a seperate list and place that list infront of the normal files
00412                                         executable.push_front(name);
00413                                     else
00414                                         l_completion.push_back(name);
00415                                 }
00416                             }
00417                             res=DOS_FindNext();
00418                         }
00419                         /* Add executable list to front of completion list. */
00420                         std::copy(executable.begin(),executable.end(),std::front_inserter(l_completion));
00421                         it_completion = l_completion.begin();
00422                         dos.dta(save_dta);
00423                     }
00424 
00425                     if (l_completion.size() && it_completion->length()) {
00426                         for (;str_index > completion_index; str_index--) {
00427                             // removes all characters
00428                             outc(8); outc(' '); outc(8);
00429                         }
00430 
00431                         strcpy(&line[completion_index], it_completion->c_str());
00432                         len = (Bit16u)it_completion->length();
00433                         str_len = str_index = (Bitu)(completion_index + len);
00434                         size = CMD_MAXLINE - str_index - 2;
00435                         DOS_WriteFile(STDOUT, (Bit8u *)it_completion->c_str(), &len);
00436                     }
00437                 }
00438                 break;
00439             case 0x1b:   /* ESC */
00440                 if (IS_PC98_ARCH) {
00441                     //TODO: Either different behavior or none at all
00442                 }
00443                 else {
00444                     //write a backslash and return to the next line
00445                     outc('\\');
00446                     outc('\n');
00447                     *line = 0;      // reset the line.
00448                     if (l_completion.size()) l_completion.clear(); //reset the completion list.
00449                     this->InputCommand(line);   //Get the NEW line.
00450                     size = 0;       // stop the next loop
00451                     str_len = 0;    // prevent multiple adds of the same line
00452                 }
00453                 break;
00454             default:
00455                 if (cr >= 0x100) break;
00456                 if (l_completion.size()) l_completion.clear();
00457                 if(str_index < str_len && true) { //mem_readb(BIOS_KEYBOARD_FLAGS1)&0x80) dev_con.h ?
00458                     outc(' ');//move cursor one to the right.
00459                     Bit16u a = str_len - str_index;
00460                     Bit8u* text=reinterpret_cast<Bit8u*>(&line[str_index]);
00461                     DOS_WriteFile(STDOUT,text,&a);//write buffer to screen
00462                     outc(8);//undo the cursor the right.
00463                     for(Bitu i=str_len;i>str_index;i--) {
00464                         line[i]=line[i-1]; //move internal buffer
00465                         outc(8); //move cursor back (from write buffer to screen)
00466                     }
00467                     line[++str_len]=0;//new end (as the internal buffer moved one place to the right
00468                     size--;
00469                 };
00470 
00471                 line[str_index]=(char)(cr&0xFF);
00472                 str_index ++;
00473                 if (str_index > str_len){ 
00474                     line[str_index] = '\0';
00475                     str_len++;
00476                     size--;
00477                 }
00478                 DOS_WriteFile(STDOUT,&c,&n);
00479                 break;
00480         }
00481     }
00482 
00483         if (!str_len) return;
00484         str_len++;
00485 
00486         // remove current command from history if it's there
00487         if (current_hist) {
00488                 current_hist=false;
00489                 l_history.pop_front();
00490         }
00491 
00492         // add command line to history. Win95 behavior with DOSKey suggests
00493         // that the original string is preserved, not the expanded string.
00494         l_history.push_front(line); it_history = l_history.begin();
00495         if (l_completion.size()) l_completion.clear();
00496 
00497         /* DOS %variable% substitution */
00498         ProcessCmdLineEnvVarStitution(line);
00499 }
00500 
00501 
00502 /* WARNING: Substitution is carried out in-place!
00503  * Buffer pointed to by "line" must be at least CMD_MAXLINE+1 bytes long! */
00504 void DOS_Shell::ProcessCmdLineEnvVarStitution(char *line) {
00505         char temp[CMD_MAXLINE]; /* <- NTS: Currently 4096 which is very generous indeed! */
00506         char *w=temp,*wf=temp+sizeof(temp)-1;
00507         char *r=line;
00508 
00509         /* initial scan: is there anything to substitute? */
00510         /* if not, then return without modifying "line" */
00511         while (*r != 0 && *r != '%') r++;
00512         if (*r != '%') return;
00513 
00514         /* if the incoming string is already too long, then that's a problem too! */
00515         if (((size_t)(r+1-line)) >= CMD_MAXLINE) {
00516                 LOG_MSG("DOS_Shell::ProcessCmdLineEnvVarStitution WARNING incoming string to substitute is already too long!\n");
00517                 goto overflow;
00518         }
00519 
00520         /* copy the string down up to that point */
00521         for (char *c=line;c < r;) {
00522                 assert(w < wf);
00523                 *w++ = *c++;
00524         }
00525 
00526         /* begin substitution process */
00527         while (*r != 0) {
00528                 if (*r == '%') {
00529                         r++;
00530                         if (*r == '%' || *r == 0) {
00531                                 /* %% or leaving a trailing % at the end (Win95 behavior) becomes a single '%' */
00532                                 if (w >= wf) goto overflow;
00533                                 *w++ = '%';
00534                                 if (*r != 0) r++;
00535                                 else break;
00536                         }
00537                         else {
00538                                 char *name = r; /* store pointer, 'r' is first char of the name following '%' */
00539                                 int spaces = 0,chars = 0;
00540 
00541                                 /* continue scanning for the ending '%'. variable names are apparently meant to be
00542                                  * alphanumeric, start with a letter, without spaces (if Windows 95 COMMAND.COM is
00543                                  * any good example). If it doesn't end in '%' or is broken by space or starts with
00544                                  * a number, substitution is not carried out. In the middle of the variable name
00545                                  * it seems to be acceptable to use hyphens.
00546                                  *
00547                                  * since spaces break up a variable name to prevent substitution, these commands
00548                                  * act differently from one another:
00549                                  *
00550                                  * C:>echo %PATH%
00551                                  * C:\DOS;C:\WINDOWS
00552                                  *
00553                                  * C:>echo %PATH %
00554                                  * %PATH % */
00555                                 if (isalpha(*r) || *r == ' ') { /* must start with a letter. space is apparently valid too. (Win95) */
00556                                         if (*r == ' ') spaces++;
00557                                         else if (isalpha(*r)) chars++;
00558 
00559                                         r++;
00560                                         while (*r != 0 && *r != '%') {
00561                                                 if (*r == ' ') spaces++;
00562                                                 else chars++;
00563                                                 r++;
00564                                         }
00565                                 }
00566 
00567                                 /* Win95 testing:
00568                                  *
00569                                  * "%" = "%"
00570                                  * "%%" = "%"
00571                                  * "% %" = ""
00572                                  * "%  %" = ""
00573                                  * "% % %" = " %"
00574                                  *
00575                                  * ^ WTF?
00576                                  *
00577                                  * So the below code has funny conditions to match Win95's weird rules on what
00578                                  * consitutes valid or invalid %variable% names. */
00579                                 if (*r == '%' && ((spaces > 0 && chars == 0) || (spaces == 0 && chars > 0))) {
00580                                         std::string temp;
00581 
00582                                         /* valid name found. substitute */
00583                                         *r++ = 0; /* ASCIIZ snip */
00584                                         if (GetEnvStr(name,temp)) {
00585                                                 size_t equ_pos = temp.find_first_of('=');
00586                                                 if (equ_pos != std::string::npos) {
00587                                                         const char *base = temp.c_str();
00588                                                         const char *value = base + equ_pos + 1;
00589                                                         const char *fence = base + temp.length();
00590                                                         assert(value >= base && value <= fence);
00591                                                         size_t len = (size_t)(fence-value);
00592 
00593                                                         if ((w+len) > wf) goto overflow;
00594                                                         memcpy(w,value,len);
00595                                                         w += len;
00596                                                 }
00597                                         }
00598                                 }
00599                                 else {
00600                                         /* nope. didn't find a valid name */
00601 
00602                                         while (*r != 0 && *r == ' ') r++; /* skip spaces */
00603                                         name--; /* step "name" back to cover the first '%' we found */
00604 
00605                                         for (char *c=name;c < r;) {
00606                                                 if (w >= wf) goto overflow;
00607                                                 *w++ = *c++;
00608                                         }
00609                                 }
00610                         }
00611                 }
00612                 else {
00613                         if (w >= wf) goto overflow;
00614                         *w++ = *r++;
00615                 }
00616         }
00617 
00618         /* complete the C-string */
00619         assert(w <= wf);
00620         *w = 0;
00621 
00622         /* copy the string back over the buffer pointed to by line */
00623         {
00624                 size_t out_len = (size_t)(w+1-temp); /* length counting the NUL too */
00625                 assert(out_len <= CMD_MAXLINE);
00626                 memcpy(line,temp,out_len);
00627         }
00628 
00629         /* success */
00630         return;
00631 overflow:
00632         *line = 0; /* clear string (C-string chop with NUL) */
00633         WriteOut("Command input error: string expansion overflow\n");
00634 }
00635 
00636 std::string full_arguments = "";
00637 bool DOS_Shell::Execute(char * name,char * args) {
00638 /* return true  => don't check for hardware changes in do_command 
00639  * return false =>       check for hardware changes in do_command */
00640         char fullname[DOS_PATHLENGTH+4]; //stores results from Which
00641         char* p_fullname;
00642         char line[CMD_MAXLINE];
00643         if(strlen(args)!= 0){
00644                 if(*args != ' '){ //put a space in front
00645                         line[0]=' ';line[1]=0;
00646                         strncat(line,args,CMD_MAXLINE-2);
00647                         line[CMD_MAXLINE-1]=0;
00648                 }
00649                 else
00650                 {
00651                         safe_strncpy(line,args,CMD_MAXLINE);
00652                 }
00653         }else{
00654                 line[0]=0;
00655         };
00656 
00657         /* check for a drive change */
00658         if (((strcmp(name + 1, ":") == 0) || (strcmp(name + 1, ":\\") == 0)) && isalpha(*name))
00659         {
00660                 if (strrchr(name,'\\')) { WriteOut(MSG_Get("SHELL_EXECUTE_ILLEGAL_COMMAND"),name); return true; }
00661                 if (!DOS_SetDrive(toupper(name[0])-'A')) {
00662 #ifdef WIN32
00663                         Section_prop * sec=0; sec=static_cast<Section_prop *>(control->GetSection("dos"));
00664                         if(!sec->Get_bool("automount")) { WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_NOT_FOUND"),toupper(name[0])); return true; }
00665                         // automount: attempt direct letter to drive map.
00666                         if((GetDriveType(name)==3) && (strcasecmp(name,"c:")==0)) WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_ACCESS_WARNING_WIN"));
00667 first_1:
00668                         if(GetDriveType(name)==5) WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_ACCESS_CDROM"),toupper(name[0]));
00669                         else if(GetDriveType(name)==2) WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_ACCESS_FLOPPY"),toupper(name[0]));
00670                         else if((GetDriveType(name)==3)||(GetDriveType(name)==4)||(GetDriveType(name)==6)) WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_ACCESS_FIXED"),toupper(name[0]));
00671                         else { WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_NOT_FOUND"),toupper(name[0])); return true; }
00672 
00673 first_2:
00674                 Bit8u c;Bit16u n=1;
00675                 DOS_ReadFile (STDIN,&c,&n);
00676                 do switch (c) {
00677                         case 'n':                       case 'N':
00678                         {
00679                                 DOS_WriteFile (STDOUT,&c, &n);
00680                                 DOS_ReadFile (STDIN,&c,&n);
00681                                 do switch (c) {
00682                                         case 0xD: WriteOut("\n\n"); WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_NOT_FOUND"),toupper(name[0])); return true;
00683                                         case 0x08: WriteOut("\b \b"); goto first_2;
00684                                 } while (DOS_ReadFile (STDIN,&c,&n));
00685                         }
00686                         case 'y':                       case 'Y':
00687                         {
00688                                 DOS_WriteFile (STDOUT,&c, &n);
00689                                 DOS_ReadFile (STDIN,&c,&n);
00690                                 do switch (c) {
00691                                         case 0xD: WriteOut("\n"); goto continue_1;
00692                                         case 0x08: WriteOut("\b \b"); goto first_2;
00693                                 } while (DOS_ReadFile (STDIN,&c,&n));
00694                         }
00695                         case 0xD: WriteOut("\n"); goto first_1;
00696                         case '\t': case 0x08: goto first_2;
00697                         default:
00698                         {
00699                                 DOS_WriteFile (STDOUT,&c, &n);
00700                                 DOS_ReadFile (STDIN,&c,&n);
00701                                 do switch (c) {
00702                                         case 0xD: WriteOut("\n");goto first_1;
00703                                         case 0x08: WriteOut("\b \b"); goto first_2;
00704                                 } while (DOS_ReadFile (STDIN,&c,&n));
00705                                 goto first_2;
00706                         }
00707                 } while (DOS_ReadFile (STDIN,&c,&n));
00708 
00709 continue_1:
00710 
00711                         char mountstring[DOS_PATHLENGTH+CROSS_LEN+20];
00712                         sprintf(mountstring,"MOUNT %s ",name);
00713 
00714                         if(GetDriveType(name)==5) strcat(mountstring,"-t cdrom ");
00715                         else if(GetDriveType(name)==2) strcat(mountstring,"-t floppy ");
00716                         strcat(mountstring,name);
00717                         strcat(mountstring,"\\");
00718 //                      if(GetDriveType(name)==5) strcat(mountstring," -ioctl");
00719                         
00720                         this->ParseLine(mountstring);
00721 //failed:
00722                         if (!DOS_SetDrive(toupper(name[0])-'A'))
00723 #endif
00724                         WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_NOT_FOUND"),toupper(name[0]));
00725                 }
00726                 return true;
00727         }
00728         /* Check for a full name */
00729         p_fullname = Which(name);
00730         if (!p_fullname) return false;
00731         strcpy(fullname,p_fullname);
00732         const char* extension = strrchr(fullname,'.');
00733         
00734         /*always disallow files without extension from being executed. */
00735         /*only internal commands can be run this way and they never get in this handler */
00736         if(extension == 0)
00737         {
00738                 //Check if the result will fit in the parameters. Else abort
00739                 if(strlen(fullname) >( DOS_PATHLENGTH - 1) ) return false;
00740                 char temp_name[DOS_PATHLENGTH+4],* temp_fullname;
00741                 //try to add .com, .exe and .bat extensions to filename
00742                 
00743                 strcpy(temp_name,fullname);
00744                 strcat(temp_name,".COM");
00745                 temp_fullname=Which(temp_name);
00746                 if (temp_fullname) { extension=".com";strcpy(fullname,temp_fullname); }
00747 
00748                 else 
00749                 {
00750                         strcpy(temp_name,fullname);
00751                         strcat(temp_name,".EXE");
00752                         temp_fullname=Which(temp_name);
00753                         if (temp_fullname) { extension=".exe";strcpy(fullname,temp_fullname);}
00754 
00755                         else 
00756                         {
00757                                 strcpy(temp_name,fullname);
00758                                 strcat(temp_name,".BAT");
00759                                 temp_fullname=Which(temp_name);
00760                                 if (temp_fullname) { extension=".bat";strcpy(fullname,temp_fullname);}
00761 
00762                                 else  
00763                                 {
00764                                         return false;
00765                                 }
00766                         
00767                         }       
00768                 }
00769         }
00770         
00771         if (strcasecmp(extension, ".bat") == 0) 
00772         {       /* Run the .bat file */
00773                 /* delete old batch file if call is not active*/
00774                 bool temp_echo=echo; /*keep the current echostate (as delete bf might change it )*/
00775                 if(bf && !call) delete bf;
00776                 bf=new BatchFile(this,fullname,name,line);
00777                 echo=temp_echo; //restore it.
00778         } 
00779         else 
00780         {       /* only .bat .exe .com extensions maybe be executed by the shell */
00781                 if(strcasecmp(extension, ".com") !=0) 
00782                 {
00783                         if(strcasecmp(extension, ".exe") !=0) return false;
00784                 }
00785                 /* Run the .exe or .com file from the shell */
00786                 /* Allocate some stack space for tables in physical memory */
00787                 reg_sp-=0x200;
00788                 //Add Parameter block
00789                 DOS_ParamBlock block(SegPhys(ss)+reg_sp);
00790                 block.Clear();
00791                 //Add a filename
00792                 RealPt file_name=RealMakeSeg(ss,reg_sp+0x20);
00793                 MEM_BlockWrite(Real2Phys(file_name),fullname,(Bitu)(strlen(fullname)+1));
00794 
00795                 /* HACK: Store full commandline for mount and imgmount */
00796                 full_arguments.assign(line);
00797 
00798                 /* Fill the command line */
00799                 CommandTail cmdtail;
00800                 cmdtail.count = 0;
00801                 memset(&cmdtail.buffer,0,127); //Else some part of the string is unitialized (valgrind)
00802                 if (strlen(line)>126) line[126]=0;
00803                 cmdtail.count=(Bit8u)strlen(line);
00804                 memcpy(cmdtail.buffer,line,strlen(line));
00805                 cmdtail.buffer[strlen(line)]=0xd;
00806                 /* Copy command line in stack block too */
00807                 MEM_BlockWrite(SegPhys(ss)+reg_sp+0x100,&cmdtail,128);
00808                 /* Parse FCB (first two parameters) and put them into the current DOS_PSP */
00809                 Bit8u add;
00810                 FCB_Parsename(dos.psp(),0x5C,0x00,cmdtail.buffer,&add);
00811                 FCB_Parsename(dos.psp(),0x6C,0x00,&cmdtail.buffer[add],&add);
00812                 block.exec.fcb1=RealMake(dos.psp(),0x5C);
00813                 block.exec.fcb2=RealMake(dos.psp(),0x6C);
00814                 /* Set the command line in the block and save it */
00815                 block.exec.cmdtail=RealMakeSeg(ss,reg_sp+0x100);
00816                 block.SaveData();
00817 #if 0
00818                 /* Save CS:IP to some point where i can return them from */
00819                 Bit32u oldeip=reg_eip;
00820                 Bit16u oldcs=SegValue(cs);
00821                 RealPt newcsip=CALLBACK_RealPointer(call_shellstop);
00822                 SegSet16(cs,RealSeg(newcsip));
00823                 reg_ip=RealOff(newcsip);
00824 #endif
00825                 /* Start up a dos execute interrupt */
00826                 reg_ax=0x4b00;
00827                 //Filename pointer
00828                 SegSet16(ds,SegValue(ss));
00829                 reg_dx=RealOff(file_name);
00830                 //Paramblock
00831                 SegSet16(es,SegValue(ss));
00832                 reg_bx=reg_sp;
00833                 SETFLAGBIT(IF,false);
00834                 CALLBACK_RunRealInt(0x21);
00835                 /* Restore CS:IP and the stack */
00836                 reg_sp+=0x200;
00837 #if 0
00838                 reg_eip=oldeip;
00839                 SegSet16(cs,oldcs);
00840 #endif
00841         }
00842         return true; //Executable started
00843 }
00844 
00845 
00846 
00847 
00848 static const char * bat_ext=".BAT";
00849 static const char * com_ext=".COM";
00850 static const char * exe_ext=".EXE";
00851 static char which_ret[DOS_PATHLENGTH+4];
00852 
00853 char * DOS_Shell::Which(char * name) {
00854         size_t name_len = strlen(name);
00855         if(name_len >= DOS_PATHLENGTH) return 0;
00856 
00857         /* Parse through the Path to find the correct entry */
00858         /* Check if name is already ok but just misses an extension */
00859 
00860         if (DOS_FileExists(name)) return name;
00861         /* try to find .com .exe .bat */
00862         strcpy(which_ret,name);
00863         strcat(which_ret,com_ext);
00864         if (DOS_FileExists(which_ret)) return which_ret;
00865         strcpy(which_ret,name);
00866         strcat(which_ret,exe_ext);
00867         if (DOS_FileExists(which_ret)) return which_ret;
00868         strcpy(which_ret,name);
00869         strcat(which_ret,bat_ext);
00870         if (DOS_FileExists(which_ret)) return which_ret;
00871 
00872 
00873         /* No Path in filename look through path environment string */
00874         char path[DOS_PATHLENGTH];std::string temp;
00875         if (!GetEnvStr("PATH",temp)) return 0;
00876         const char * pathenv=temp.c_str();
00877         if (!pathenv) return 0;
00878         pathenv = strchr(pathenv,'=');
00879         if (!pathenv) return 0;
00880         pathenv++;
00881         Bitu i_path = 0;
00882         while (*pathenv) {
00883                 /* remove ; and ;; at the beginning. (and from the second entry etc) */
00884                 while(*pathenv == ';')
00885                         pathenv++;
00886 
00887                 /* get next entry */
00888                 i_path = 0; /* reset writer */
00889                 while(*pathenv && (*pathenv !=';') && (i_path < DOS_PATHLENGTH) )
00890                         path[i_path++] = *pathenv++;
00891 
00892                 if(i_path == DOS_PATHLENGTH) {
00893                         /* If max size. move till next ; and terminate path */
00894                         while(*pathenv && (*pathenv != ';')) 
00895                                 pathenv++;
00896                         path[DOS_PATHLENGTH - 1] = 0;
00897                 } else path[i_path] = 0;
00898 
00899 
00900                 /* check entry */
00901                 if(size_t len = strlen(path)){
00902                         if(len >= (DOS_PATHLENGTH - 2)) continue;
00903 
00904                         if(path[len - 1] != '\\') {
00905                                 strcat(path,"\\"); 
00906                                 len++;
00907                         }
00908 
00909                         //If name too long =>next
00910                         if((name_len + len + 1) >= DOS_PATHLENGTH) continue;
00911                         strcat(path,name);
00912 
00913                         strcpy(which_ret,path);
00914                         if (DOS_FileExists(which_ret)) return which_ret;
00915                         strcpy(which_ret,path);
00916                         strcat(which_ret,com_ext);
00917                         if (DOS_FileExists(which_ret)) return which_ret;
00918                         strcpy(which_ret,path);
00919                         strcat(which_ret,exe_ext);
00920                         if (DOS_FileExists(which_ret)) return which_ret;
00921                         strcpy(which_ret,path);
00922                         strcat(which_ret,bat_ext);
00923                         if (DOS_FileExists(which_ret)) return which_ret;
00924                 }
00925         }
00926         return 0;
00927 }