void rtrimcl(char * const str) { char *p; assert(str); p = strchr(str, '\0'); while (--p >= str && isargdelim(*p)) ; p[1] = '\0'; }
char *ltrimcl(const char *str) { char c; assert(str); while ((c = *str++) != '\0' && isargdelim(c)) ; return (char *)str - 1; /* strip const */ }
char *skipQuoteArg(const char * const pp) { char *p; assert(pp); p = (char*)pp - 1; while(*(p = skipquote(p + 1)) != 0 && !isargdelim(*p)); return p; }
char *skipqword(const char *pp, const char * const stop) { size_t len; int quote = 0; len = stop? strlen(stop): 0; if(*pp) do { if(quote) { if(quote == *pp) quote = 0; } else if(strchr(QUOTE_STR, *pp)) quote = *pp; else if(len? (memcmp(pp, stop, len) == 0): isargdelim(*pp)) break; } while(*++pp); return (char *) pp; /* strip const */ }
int cmd_if(char *param) { #define X_EXEC 1 char *pp; int x_flag = 0; /* when set cause 'then' clause to be exec'ed */ int negate = 0; /* NOT keyword present */ int ignore_case = 0; /* /I option, case insensitive compare */ /* First check if param exists */ assert(param); /* check for options, note non-options must be treated as part of comparision */ if (matchtok(param, "/I")||matchtok(param, "/i")) ignore_case++; /* next check if param string begins with word 'not' */ if(matchtok(param, "not")) negate = X_EXEC; /* Remember 'NOT' */ /* Check for 'exist' form */ if(matchtok(param, "exist")) { struct dos_ffblk f; isr olderrhandler; if(!*param) { /* syntax error */ error_if_exist(); return 0; } pp = skip_word(param); *pp++ = '\0'; /* don't show abort/retry/fail if no disk in drive */ get_isr(0x24, olderrhandler); #ifdef XMS_SWAP set_isrfct(0x24, autofail_err_handler); /* always fails */ #else set_isrfct(0x24, dummy_criter_handler); /* always fails */ #endif if(dos_findfirst(param, &f, FA_NORMAL|FA_ARCH|FA_SYSTEM|FA_RDONLY|FA_HIDDEN) == 0) x_flag = X_EXEC; dos_findclose(&f); /* restore critical error handler */ set_isrfct(0x24, olderrhandler); } /* Check for 'errorlevel' form */ else if(matchtok(param, "errorlevel")) { int n = 0; #if 0 if(!isdigit(*param)) { error_if_errorlevel(); return 0; } pp = param; do n = n * 10 + (*pp - '0'); while (isdigit(*++pp)); if(*pp && !isargdelim(*pp)) { error_if_errorlevel_number(); return 0; } #else /* Add this COMMAND bug as someone tries to use: IF ERRORLEVEL H<upper-case_letter> -or- IF ERRORLEVEL x<lower-case_letter> to match the errorlevel against drive letters. NOT supported by 4dos or WinNT. HA --> maps to errorlevel 1 xa --> same HB & xb --> to 2 a.s.o. */ if(!*param) { error_if_errorlevel(); return 0; } pp = param; do n = n * 10 + (*pp - '0'); while(*++pp && !isargdelim(*pp)); n &= 255; dprintf( ("IF: checking for ERRORLEVEL >= %u\n", n) ); #endif if(errorlevel >= n) x_flag = X_EXEC; } /* Check that '==' is present, syntax error if not */ else { size_t len; char *r; /* right operand */ pp = skipqword(param, "=="); if(*pp != '=' || pp[1] != '=') { error_syntax(0); return 0; } *pp = '\0'; /* param[] points to the left operand */ /* skip over the '==' and subsquent spaces and assign the end of the right operator to pp */ pp = skipqword(r = ltrimcl(pp + 2), 0); /* now: param := beginning of the left operand r := beginning of the right operand pp := end of right operand */ rtrimcl(param); /* ensure that spurious whitespaces are ignored */ len = strlen(param); /* check if strings differ */ if ( ((pp - r) == len) && ((ignore_case && strnicmp(param, r, len) == 0) || (memcmp(param, r, len) == 0)) ) x_flag = X_EXEC; } if(x_flag ^ negate) /* perform the command */ if(!*(pp = ltrimcl(pp))) error_if_command(); else parsecommandline(pp, FALSE); return 0; }
/* * do the prompt/input/process loop * * If xflg is true, the function will not go interactive, but returns. * If commandline != NULL, this command is processed first. * * Return: 0: on success */ int process_input(int xflag, char *commandline) { /* Dimensionate parsedline that no sprintf() can overflow the buffer */ char parsedline[MAX_INTERNAL_COMMAND_SIZE + sizeof(errorlevel) * 8] , *readline; #if 0 /* Return the maximum pointer into parsedline to add 'numbytes' bytes */ #define parsedMax(numbytes) \ (parsedline + MAX_INTERNAL_COMMAND_SIZE - 1 - (numbytes)) char *evar; char *tp; char *cp; #endif char *ip; #if 0 char forvar; #endif int echothisline; int tracethisline; do { #ifdef FEATURE_LONG_FILENAMES if( toupper( *getEnv( "LFN" ) ) == 'N' ) __supportlfns = 0; else __supportlfns = 1; #endif interactive_command = 0; /* not directly entered by user */ echothisline = tracethisline = 0; if(commandline) { ip = commandline; readline = commandline = 0; } else { if ((readline = malloc(MAX_INTERNAL_COMMAND_SIZE + 1)) == 0) { error_out_of_memory(); return 1; } if (0 == (ip = readbatchline(&echothisline, readline, MAX_INTERNAL_COMMAND_SIZE))) { /* if no batch input then... */ if (xflag /* must not go interactive */ || (fdattr(0) & 0x84) == 0x84 /* input is NUL device */ || feof(stdin)) /* no further input */ { free(readline); break; } /* Go Interactive */ interactive_command = 1; /* directly entered by user */ /* Ensure the prompt starts at column #0 */ if(echo && (mywherex()>1)) outc('\n'); readcommand(ip = readline, MAX_INTERNAL_COMMAND_SIZE); tracemode = 0; /* reset trace mode */ } } /* Make sure there is no left-over from last run */ currCmdHelpScreen = 0; /* * The question mark '?' has a double meaning: * C:\> ? * ==> Display short help * * C:\> ? command arguments * ==> enable tracemode for just this line */ if(*(ip = ltrimcl(ip)) == '?') { ip = ltrimcl(ip + 1); if(!*ip) { /* is short help command */ #ifdef INCLUDE_CMD_QUESTION showcmds(ip); #endif free(readline); continue; } /* this-line-tracemode */ echothisline = 0; tracethisline = 1; } #if 0 /* The FOR hack If the line matches /^\s*for\s+\%[a-z]\s/, the FOR hack becomes active, because FOR requires the sequence "%<ch>" in its input. When the percent (%) expansion is made later on, any sequence "%<ch>" is retained. */ cp = ip; if(matchtok(cp, "for") && *cp == '%' && isalpha(cp[1]) && isargdelim(cp[2])) /* activate FOR hack */ forvar = toupper(cp[1]); else forvar = 0; #else if(cmd_for_hackery(ip)) { free(readline); continue; } #endif { int rc = expandEnvVars(ip, parsedline); free(readline); if(!rc) { error_line_too_long(); continue; } } if (echothisline) /* Echo batch file line */ { printprompt(); puts(parsedline); } if (*parsedline) { if(swapOnExec != ERROR) swapOnExec = defaultToSwap; if(tracethisline) ++tracemode; parsecommandline(parsedline, TRUE); if(tracethisline) --tracemode; } } while (!canexit || !exitflag); return 0; }
static void docommand(char *line) { /* * look through the internal commands and determine whether or not this * command is one of them. If it is, call the command. If not, call * execute to run it as an external program. * * line - the command line of the program to run */ char *cp; char *rest; /* pointer to the rest of the command line */ struct CMD *cmdptr = 0; #ifdef FEATURE_INSTALLABLE_COMMANDS /* Duplicate the command line into such buffer in order to allow Installable Commands to alter the command line. *line cannot be modified as pipes would be destroyed. */ /* Place both buffers immediately following each other in order to make sure the contents of args can be appended to com without any buffer overflow checks. *2 -> one buffer for com and one for args +2 -> max length byte of com + cur length of com +3 -> max length byte of args + cur length of args + additional '\0' */ char *buf = malloc(2+2*BUFFER_SIZE_MUX_AE+2+1); #define args (buf + 2) #define ARGS_BUFFER_SIZE (2 + BUFFER_SIZE_MUX_AE + 3) #define com (buf + ARGS_BUFFER_SIZE) #define BUFFER_SIZE BUFFER_SIZE_MUX_AE #else char *com = malloc(MAX_INTERNAL_COMMAND_SIZE); #define args line #define buf com #define BUFFER_SIZE MAX_INTERNAL_COMMAND_SIZE #endif assert(line); if(!buf) { error_out_of_memory(); return; } /* delete leading spaces, but keep trailing whitespaces */ line = ltrimcl(line); #ifdef FEATURE_INSTALLABLE_COMMANDS #if BUFFER_SIZE < MAX_INTERNAL_COMMAND_SIZE if(strlen(line) > BUFFER_SIZE) { error_line_too_long(); goto errRet; } #endif strcpy(args, line); #endif if (*(rest = args)) /* Anything to do ? */ { cp = com; /* Copy over 1st word as upper case */ /* Internal commands are constructed out of non-delimiter characters; ? had been parsed already */ while(*rest && is_fnchar(*rest) && !strchr(QUOTE_STR, *rest)) *cp++ = toupper(*rest++); if(*rest && strchr(QUOTE_STR, *rest)) /* If the first word is quoted, it is no internal command */ cp = com; /* invalidate it */ *cp = '\0'; /* Terminate first word */ if(*com) { #ifdef FEATURE_INSTALLABLE_COMMANDS int tryMUXAE; for(tryMUXAE = MUX_AE_MAX_REPEAT_CALL; tryMUXAE > 0; --tryMUXAE) { /* Check for installed COMMAND extension */ switch(runExtension(com, args)) { case 1: /* OK, done */ goto errRet; case 0: /* no extension */ tryMUXAE = 0; } /* reset the argument pointer */ rest = &args[(unsigned char)com[-1]]; dprintf( ("[Command on return of Installable Commands check: >%s]\n", com) ); #ifndef NDEBUG dprintf( ("[Command line: >") ); for(cp = args; cp < rest; ++cp) dprintf( ("%c", *cp) ); dprintf( ("|%s]\n", rest) ); #endif /* !defined(NDEBUG) */ #endif /* Scan internal command table */ for (cmdptr = internalCommands ; cmdptr->name && strcmp(com, cmdptr->name) != 0 ; cmdptr++); if(cmdptr && cmdptr->name) { /* internal command found */ #ifdef FEATURE_INSTALLABLE_COMMANDS cp = realloc(buf, ARGS_BUFFER_SIZE); #ifndef NDEBUG if(cp != buf) { dprintf( ("[INTERNAL error: realloc() returned wrong result]") ); buf = cp; } #endif #else free(buf); buf = 0; /* no further useage of this buffer */ #endif switch(cmdptr->flags & (CMD_SPECIAL_ALL | CMD_SPECIAL_DIR)) { case CMD_SPECIAL_ALL: /* pass everything into command */ break; case CMD_SPECIAL_DIR: /* pass '\\' & '.' too */ if(*rest == '\\' || *rest == '.' || *rest == ':') break; default: /* pass '/', ignore ',', ';' & '=' */ if(!*rest || *rest == '/') break; if(isargdelim(*rest)) { rest = ltrimcl(rest); break; } /* else syntax error */ error_syntax(0); goto errRet; } currCmdHelpScreen = cmdptr->help_id; /* JPP this will print help for any command */ if(memcmp(ltrimcl(rest), "/?", 2) == 0) { displayString(currCmdHelpScreen); } else { dprintf(("CMD '%s' : '%s'\n", cmdptr->name, rest)); cmdptr->func(rest); } goto errRet; } #ifdef FEATURE_INSTALLABLE_COMMANDS } #endif } free(buf); buf = 0; /* no longer used */ /* no internal command --> spawn an external one */ cp = unquote(line, rest = skip_word(line)); if(!cp) { error_out_of_memory(); goto errRet; } execute(cp, rest); free(cp); } #undef com #undef args #undef BUFFER_SIZE #undef ARGS_BUFFER_SIZE errRet: free(buf); }
int get_redirection(char *s, char **ifn, char **ofn, int *ofatt) { /* * Gets the redirection info from the command line and copies the * file names into ifn and ofn removing them from the command line. * The names are allocated here and passed back to the caller, on * malloc() failure, -1 is returned. These names are trimmed, * meaning they do not contain any leading or trailing whitespaces. * * Converts remaining command line into a series of null terminated * strings defined by the pipe char '|'. Each string corresponds * to a single executable command. A double null terminates the * command strings. * * Check for, but do not implement, output append redirect. * * Return number of command strings found. * */ int num = 1; int ch; char *dp = s; char *sp = s; assert(s); assert(ifn); assert(ofn); assert(ofatt); /* find and remove all the redirections first */ while ((ch = *dp++ = *sp++) != 0) switch (ch) { case '"': /* No redirects inside quotes */ /* case '\'': single quotes don't quote ska*/ { char *p; int len; /* If there is no closing quote, then go to end of line. */ if ((p = strchr(sp, ch)) == 0) { p = sp + strlen(sp) - 1; } /* closing quote found, move that area */ /* need memmove() because both areas overlap each other */ memmove(dp, sp, len = p - sp + 1); dp += len; sp += len; } break; case '<': case '>': { /* MS-DOS ignores multiple redirection symbols and uses the last */ /* redirection, so we'll emulate that and not check */ char **op = (ch == '<') ? ifn : ofn; char *p; if ((ch == '>') && (*sp == '>')) /* Append request ? */ { *ofatt = O_CREAT | O_APPEND | O_WRONLY; sp++; } p = sp = ltrimcl(sp); while (*sp && !is_redir(*sp) && !isargdelim(*sp)) ++sp; free(*op); /* ignore any previous one */ ch = *sp; *sp = '\0'; if ((*op = strdup(p)) == 0) { /* out of mem */ error_out_of_memory(); return -1; } *sp = ch; --dp; /* ignore the already copied '<' or '>' */ } break; case '|': dp[-1] = '\0'; /* overwrite the already copied '|' */ ++num; break; } /* end switch */ return num; }
/* * do the prompt/input/process loop * * If xflg is true, the function will not go interactive, but returns. * If commandline != NULL, this command is processed first. * * Return: 0: on success */ int process_input(int xflag, char *commandline) { /* Dimensionate parsedline that no sprintf() can overflow the buffer */ char parsedline[MAX_INTERNAL_COMMAND_SIZE + sizeof(errorlevel) * 8] , *readline; /* Return the maximum pointer into parsedline to add 'numbytes' bytes */ #define parsedMax(numbytes) \ (parsedline + MAX_INTERNAL_COMMAND_SIZE - 1 - (numbytes)) char *evar; char *tp; char *ip; char *cp; char forvar; int echothisline; int tracethisline; do { interactive_command = 0; /* not directly entered by user */ echothisline = tracethisline = 0; if(commandline) { ip = commandline; readline = commandline = 0; } else { if ((readline = malloc(MAX_INTERNAL_COMMAND_SIZE + 1)) == 0) { error_out_of_memory(); return 1; } if (0 == (ip = readbatchline(&echothisline, readline, MAX_INTERNAL_COMMAND_SIZE))) { /* if no batch input then... */ if (xflag /* must not go interactive */ || (fdattr(0) & 0x84) == 0x84 /* input is NUL device */ || feof(stdin)) /* no further input */ { free(readline); break; } /* Go Interactive */ interactive_command = 1; /* directly entered by user */ readcommand(ip = readline, MAX_INTERNAL_COMMAND_SIZE); tracemode = 0; /* reset trace mode */ } } /* * The question mark '?' has a double meaning: * C:\> ? * ==> Display short help * * C:\> ? command arguments * ==> enable tracemode for just this line */ if(*(ip = ltrimcl(ip)) == '?') { ip = ltrimcl(ip + 1); if(!*ip) { /* is short help command */ #ifdef INCLUDE_CMD_QUESTION showcmds(ip); #endif free(readline); continue; } /* this-line-tracemode */ echothisline = 0; tracethisline = 1; } /* The FOR hack If the line matches /^\s*for\s+\%[a-z]\s/, the FOR hack becomes active, because FOR requires the sequence "%<ch>" in its input. When the percent (%) expansion is made later on, any sequence "%<ch>" is retained. */ cp = ip; if(matchtok(cp, "for") && *cp == '%' && isalpha(cp[1]) && isargdelim(cp[2])) /* activate FOR hack */ forvar = toupper(cp[1]); else forvar = 0; cp = parsedline; while (*ip) { /* Assume that at least one character is added, place the test here to simplify the switch() statement */ if(cp >= parsedMax(1)) { cp = 0; /* error condition */ break; } if (*ip == '%') { switch (*++ip) { case '\0': /* FOR hack forvar == 0 if no FOR is active */ *cp++ = '%'; break; case '%': *cp++ = *ip++; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (0 != (tp = find_arg(*ip - '0'))) { if(cp >= parsedMax(strlen(tp))) { cp = 0; goto intBufOver; } cp = stpcpy(cp, tp); ip++; } else *cp++ = '%'; /* Let the digit be copied in the cycle */ break; case '?': /* overflow check: parsedline has that many character "on reserve" */ cp += sprintf(cp, "%u", errorlevel); ip++; break; default: if(forvar == toupper(*ip)) { /* FOR hack */ *cp++ = '%'; /* let the var be copied in next cycle */ break; } if ((tp = strchr(ip, '%')) != 0) { *tp = '\0'; if ((evar = getEnv(ip)) != 0) { if(cp >= parsedMax(strlen(evar))) { cp = 0; goto intBufOver; } cp = stpcpy(cp, evar); } ip = tp + 1; } break; } continue; } if (iscntrl(*ip)) { *cp++ = ' '; ++ip; } else *cp++ = *ip++; } intBufOver: free(readline); if(!cp) { /* internal buffer overflow */ error_line_too_long(); continue; } *cp = '\0'; /* terminate copied string */ if (echothisline) /* Echo batch file line */ { printprompt(); puts(parsedline); } if (*parsedline) { if(swapOnExec != ERROR) swapOnExec = defaultToSwap; if(tracethisline) ++tracemode; parsecommandline(parsedline); if(tracethisline) --tracemode; if (echothisline || echo) putchar('\n'); } } while (!canexit || !exitflag); return 0; }
static void docommand(char *line) { /* * look through the internal commands and determine whether or not this * command is one of them. If it is, call the command. If not, call * execute to run it as an external program. * * line - the command line of the program to run */ #ifdef FEATURE_INSTALLABLE_COMMANDS /* Duplicate the command line into such buffer in order to allow Installable Commands to alter the command line. *line cannot be modified as pipes would be destroyed. */ /* Place both buffers immediately following each other in order to make sure the contents of args can be appended to com without any buffer overflow checks. *2 -> one buffer for com and one for args +2 -> max length byte of com + cur length of com +3 -> max length byte of args + cur length of args + additional '\0' */ char buf[2+2*BUFFER_SIZE_MUX_AE+2+1]; #define com (buf + 2) #define args (buf + 2 + BUFFER_SIZE_MUX_AE + 2) #define BUFFER_SIZE BUFFER_SIZE_MUX_AE #else char com[MAX_INTERNAL_COMMAND_SIZE]; #define BUFFER_SIZE MAX_INTERNAL_COMMAND_SIZE #endif char *cp; char *rest; /* pointer to the rest of the command line */ struct CMD *cmdptr; assert(line); /* delete leading spaces, but keep trailing whitespaces */ line = ltrimcl(line); #ifdef FEATURE_INSTALLABLE_COMMANDS #if BUFFER_SIZE < MAX_INTERNAL_COMMAND_SIZE if(strlen(line) > BUFFER_SIZE) { error_line_too_long(); return; } #endif line = strcpy(args, line); #endif if (*(rest = line)) /* Anything to do ? */ { cp = com; /* Copy over 1st word as lower case */ /* Internal commands are constructed out of non-delimiter characters; ? had been parsed already */ while(*rest && is_fnchar(*rest) && !strchr(QUOTE_STR, *rest)) *cp++ = toupper(*rest++); if(*rest && strchr(QUOTE_STR, *rest)) /* If the first word is quoted, it is no internal command */ cp = com; /* invalidate it */ *cp = '\0'; /* Terminate first word */ if(*com) { #ifdef FEATURE_INSTALLABLE_COMMANDS /* Check for installed COMMAND extension */ if(runExtension(com, args)) return; /* OK, executed! */ dprintf( ("[Command on return of Installable Commands check: >%s<]\n", com) ); #endif /* Scan internal command table */ for (cmdptr = internalCommands ; cmdptr->name && strcmp(com, cmdptr->name) != 0 ; cmdptr++); } if(*com && cmdptr->name) { /* internal command found */ switch(cmdptr->flags & (CMD_SPECIAL_ALL | CMD_SPECIAL_DIR)) { case CMD_SPECIAL_ALL: /* pass everything into command */ break; case CMD_SPECIAL_DIR: /* pass '\\' & '.' too */ if(*rest == '\\' || *rest == '.' || *rest == ':') break; default: /* pass '/', ignore ',', ';' & '=' */ if(!*rest || *rest == '/') break; if(isargdelim(*rest)) { rest = ltrimcl(rest); break; } /* else syntax error */ error_syntax(0); return; } /* JPP this will print help for any command */ if (strstr(rest, "/?")) { displayString(cmdptr->help_id); } else { dprintf(("CMD '%s' : '%s'\n", com, rest)); cmdptr->func(rest); } } else { #ifdef FEATURE_INSTALLABLE_COMMANDS if(*com) { /* external command */ /* Installable Commands are allowed to change both: "com" and "args". Therefore, we may need to reconstruct the external command line */ /* Because com and *rest are located within the very same buffer and rest is definitely terminated with '\0', the followinf memmove() operation is fully robust against buffer overflows */ memmove(com + strlen(com), rest, strlen(rest) + 1); /* Unsave, but probably more efficient operation: strcat(com, rest); -- 2000/12/10 ska*/ line = com; } #endif /* no internal command --> spawn an external one */ cp = unquote(line, rest = skip_word(line)); if(!cp) { error_out_of_memory(); return; } execute(cp, ltrimsp(rest)); free(cp); } } #undef line #undef com #undef args #undef BUFFER_SIZE }