/* * COMMAND.COM-compatible command processor for DOS. * * Copyright (C) 1997, CENTROID CORPORATION, HOWARD, PA 16841 * Copyright (C) Allen S. Cheung (RIP) * Copyright (C) 2005-2006 FreeDOS-32 project * Copyright (C) 2018-2024 stsp, dosemu2 project * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /* * comcom32 project note: * The use of GPLv3+ license for this code was confirmed by Centroid Corp here: * https://github.com/stsp/comcom32/issues/12#issuecomment-472004939 * * See also original copyrights below. */ /* * FILE NAME: * COMMAND.C * * PROGRAMMER(S): * Allen S. Cheung (allencheung@fastmail.ca) * * UPDATE: * 15-Apr-2002 * * LICENSE: * GNU General Public License * * COPYRIGHT (C) 1997 CENTROID CORPORATION, HOWARD, PA 16841 * ***/ /* WARNING: This is not the original version. * modified for FreeDOS-32 by Salvo Isaja and Hanzac Chen */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef DJ64 #include #endif #include "cmdbuf.h" #ifdef DJ64 #include #else #include "fmemcpy.h" #endif #include "asm.h" #include "mouse.h" #include "env.h" #include "psp.h" #include "umb.h" #include "ae0x.h" #include "compl.h" #include "clip.h" #include "command.h" /* * These declarations/definitions turn off some unwanted DJGPP features */ extern char **environ; #include int _crt0_startup_flags = _CRT0_FLAG_USE_DOS_SLASHES | // keep the backslashes _CRT0_FLAG_DISALLOW_RESPONSE_FILES | // no response files (i.e. `@gcc.rf') // _CRT0_FLAG_NO_LFN | // disable long file names _CRT0_FLAG_PRESERVE_FILENAME_CASE; // keep DOS names non-lowercased #define SHM_NOEXEC 1 #define SHM_EXCL 2 #define SHM_NEW_NS 4 #define SHM_NS 8 #define SHM_FLAGS0 (SHM_NOEXEC | SHM_EXCL | SHM_NEW_NS) #define SHM_FLAGS1 (SHM_NOEXEC | SHM_EXCL | SHM_NS) #define SHM_FLAGS (SHM_FLAGS0 | (SHM_FLAGS1 << 8)) #define __S(x) #x #define _S(x) __S(x) asm( ".globl _shm_flags\n" "_shm_flags = " _S(SHM_FLAGS) "\n" ); static const char *version = "0.3"; #define DP(s, o) (__dpmi_paddr){ .selector = s, .offset32 = o, } static int shell_mode = SHELL_NORMAL; static int shell_permanent; static int stepping; static int mouse_en; static int mouseopt_extctl; static int mouseopt_enabled; #define DEBUG 0 static __dpmi_raddr int0_vec; static int int0_wa; /* * Command parser defines/variables */ static char cmd_line[MAX_CMD_BUFLEN] = ""; // when this string is not "" it triggers command execution static char cmd[MAX_CMD_BUFLEN] = ""; static char cmd_arg[MAX_CMD_BUFLEN] = ""; static char cmd_switch[MAX_CMD_BUFLEN] = ""; static char cmd_args[MAX_CMD_BUFLEN] = ""; static char goto_label[MAX_CMD_BUFLEN] = ""; /* * Pipe variables and defines */ static char pipe_file[2][MAX_CMD_BUFLEN] = {"",""}; static int pipe_file_redir_count[2]; static char pipe_to_cmd[MAX_CMD_BUFLEN] = ""; static int pipe_to_cmd_redir_count; static FILE *bkp_stdin; /* * Command interpreter/executor defines/variables */ #define MAX_STACK_LEVEL 20 // Max number of batch file call stack levels #define MAX_BAT_ARGS 32 // Max number of batch file arguments static int need_to_crlf_at_next_prompt; static int stack_level = 0; static int echo_on[MAX_STACK_LEVEL]; static char bat_file_path[MAX_STACK_LEVEL][FILENAME_MAX]; // when this string is not "" it triggers batch file execution static char bat_arg[MAX_STACK_LEVEL][MAX_BAT_ARGS][MAX_CMD_BUFLEN]; static int bat_file_line_number[MAX_STACK_LEVEL]; static char pushd_stack[MAX_STACK_LEVEL][MAXPATH]; static int pushd_stack_level = 0; static unsigned error_level = 0; // Program execution return code static char for_var; static const char *for_val; static int exiting; static int break_on; static int break_enabled; /* * File attribute constants */ static const char attrib_letters[4] = {'R', 'A', 'S', 'H'}; static const unsigned attrib_values[4] = {_A_RDONLY, _A_ARCH, _A_SYSTEM, _A_HIDDEN}; /* * Some private prototypes */ static void perform_attrib(const char *arg); static void perform_break(const char *arg); static void perform_call(const char *arg); static void perform_cd(const char *arg); static void perform_choice(const char *arg); static void perform_clip(const char *arg); static void perform_cls(const char *arg); static void perform_copy(const char *arg); static void perform_ctty(const char *arg); static void perform_date(const char *arg); static void perform_delete(const char *arg); static void perform_deltree(const char *arg); static void perform_dir(const char *arg); static void perform_echo_dot(const char *arg); static void perform_echo(const char *arg); static void perform_elfexec(const char *arg); static void perform_exit(const char *arg); static void perform_for(const char *arg); static void perform_goto(const char *arg); static void perform_help(const char *arg); static void perform_loadhigh(const char *arg); static void perform_license(const char *arg); static void perform_loadfix(const char *arg); static void perform_md(const char *arg); static void perform_move(const char *arg); static void perform_more(const char *arg); static void perform_mouseopt(const char *arg); static void perform_path(const char *arg); static void perform_pause(const char *arg); static void perform_popd(const char *arg); static void perform_prompt(const char *arg); static void perform_pushd(const char *arg); static void perform_r200fix(const char *arg); static void perform_rd(const char *arg); static void perform_rename(const char *arg); static void perform_shift(const char *arg); static void perform_time(const char *arg); static void perform_timeout(const char *arg); static void perform_truename(const char *arg); static void perform_type(const char *arg); static void perform_ver(const char *arg); static void perform_xcopy(const char *arg); static void parse_cmd_line(void); static void perform_external_cmd(int call, int lh, char *ext_cmd); static void exec_cmd(int call); static void perform_set(const char *arg); static void list_cmds(void); //static void perform_unimplemented_cmd(void); static void set_break(int on); struct built_in_cmd cmd_table[] = { {"attrib", perform_attrib, "", "set file attributes"}, {"break", perform_break, "", "set ^Break handling"}, {"call", perform_call, "", "call batch file"}, {"cd", perform_cd, "", "change directory"}, {"chdir", perform_cd, "", "change directory"}, {"choice", perform_choice, "", "choice prompt sets ERRORLEVEL"}, {"clip", perform_clip, "", "clipboard operations"}, {"cls", perform_cls, "", "clear screen"}, {"copy", perform_copy, "", "copy file"}, {"ctty", perform_ctty, "", "change tty"}, {"date", perform_date, "", "display date"}, {"del", perform_delete, "", "delete file"}, {"deltree", perform_deltree, "", "delete directory recursively"}, {"erase", perform_delete, "", "delete file"}, {"dir", perform_dir, "", "directory listing"}, {"echo.", perform_echo_dot, "", "terminal output"}, // before normal echo {"echo", perform_echo, "", "terminal output"}, {"elfexec", perform_elfexec, "", "execute elf file"}, {"exit", perform_exit, "", "exit from interpreter"}, {"for", perform_for, "", "FOR loop"}, {"goto", perform_goto, "", "move to label"}, {"help", perform_help, "", "display this help"}, {"lh", perform_loadhigh, "", "load program to UMB"}, {"license", perform_license, "", "show copyright information"}, {"loadfix", perform_loadfix, "", "fix \"packed file is corrupt\""}, {"loadhigh", perform_loadhigh, "", "load program to UMB"}, {"md", perform_md, "", "create directory"}, {"mkdir", perform_md, "", "create directory"}, {"move", perform_move, "", "move file"}, {"more", perform_more, "", "scroll-pause long output"}, {"mouseopt", perform_mouseopt, "", "mouse options"}, {"path", perform_path, "", "set search path"}, {"pause", perform_pause, "", "wait for a keypress"}, {"popd", perform_popd, "", "pop dir from stack and cd"}, {"prompt", perform_prompt, "", "customize prompt string"}, {"pushd", perform_pushd, "", "push cwd to stack and cd"}, {"r200fix", perform_r200fix, "", "runtime error 200 fix"}, {"rd", perform_rd, "", "remove directory"}, {"rmdir", perform_rd, "", "remove directory"}, {"rename", perform_rename, "", "rename with wildcards"}, {"ren", perform_rename, "", "rename with wildcards"}, {"set", perform_set, "", "set/unset environment variables"}, {"shift", perform_shift, "", "shift arguments"}, {"time", perform_time, "", "display time"}, {"timeout", perform_timeout, "", "pause execution"}, {"truename", perform_truename, "", "path resolution"}, {"type", perform_type, "", "display file content"}, {"ver", perform_ver, " [/r]", "display version"}, {"xcopy", perform_xcopy, "", "copy large file"}, }; /* * Count of the number of valid commands */ const int CMD_TABLE_COUNT = sizeof(cmd_table) / sizeof(struct built_in_cmd); /*** * * FUNCTION: conv_unix_path_to_ms_dos * * PROGRAMMER: Allen S. Cheung * * UPDATE: 04-Jan-2001 * * PURPOSE: Force the given filepath into MS-DOS style * * CALL: void conv_unix_path_to_ms_dos(char *path) * * WHERE: *path is a file path either in Unix or MS-DOS style, * which will be converted to MS-dos style if * it was in Unix format. (I/R) * * RETURN: none * * NOTES: * ***/ static void conv_unix_path_to_ms_dos(char *path) { char *p = path; if (p != NULL) { while (*p != '\0') { if (*p == '/') *p = '\\'; // change slashes to backslashes /* *p = toupper(*p); change to uppercase */ p++; } } } static int is_drive_spec(char *s) // check for form "A:" { if (!isalpha(s[0])) return false; if (s[1] != ':') return false; if (s[2] != '\0') return false; return true; } static int is_drive_spec_with_slash(char *s) // check for form "C:\" { if (!isalpha(s[0])) return false; if (s[1] != ':') return false; if (s[2] != '\\') return false; if (s[3] != '\0') return false; return true; } static int has_trailing_slash(char *s) { if (*s == '\0') return false; s = strchr(s,'\0')-1; if (*s == '\\' || *s == '/') return true; return false; } static int has_wildcard(char *s) { if (strchr(s, '*') != NULL) return true; if (strchr(s, '?') != NULL) return true; return false; } static void reset_batfile_call_stack(void) { static int first_time = true; int ba; if (!first_time) { if (bat_file_path[stack_level][0] != '\0') { cprintf("Batch file aborted - %s, line %d\r\n", bat_file_path[stack_level], bat_file_line_number[stack_level]); } } first_time = false; // initialize stack for (stack_level = 0; stack_level < MAX_STACK_LEVEL; stack_level++) { bat_file_path[stack_level][0] = '\0'; for (ba = 0; ba < MAX_BAT_ARGS; ba++) bat_arg[stack_level][ba][0] = '\0'; bat_file_line_number[stack_level] = 0; echo_on[stack_level] = true; } stack_level = 0; } static void output_prompt(void) { char cur_drive_and_path[MAXPATH]; const char *promptvar = getenv("PROMPT"); char *cwd; if (need_to_crlf_at_next_prompt) { if (wherex() > 1) cputs("\r\n"); need_to_crlf_at_next_prompt = false; } if (promptvar == NULL) promptvar = "$p$g"; cwd = getcwd(cur_drive_and_path, MAXPATH); if (!cwd) strcpy(cur_drive_and_path, "invalid"); /* The disk letter is changed to upper-case */ cur_drive_and_path[0] = toupper(cur_drive_and_path[0]); conv_unix_path_to_ms_dos(cur_drive_and_path); while (*promptvar != '\0') { if (*promptvar == '$') { promptvar++; switch (toupper(*promptvar)) { case '\0': promptvar--; break; case 'Q': // = (equal sign) putch('='); break; case '$': // $ (dollar sign) putch('$'); break; case 'T': // Current time (TODO: emulate centisecond) { time_t t = time(NULL); struct tm *loctime = localtime (&t); cprintf("%2d:%02d:%02d", loctime->tm_hour, loctime->tm_min, loctime->tm_sec); break; } case 'D': // Current date { time_t t = time(NULL); struct tm *loctime = localtime (&t); cprintf("%02d-%02d-%04d", loctime->tm_mon+1, loctime->tm_mday, loctime->tm_year+1900); break; } case 'P': // Current drive and path { cputs(cur_drive_and_path); break; } case 'N': // Current drive { putch(*cur_drive_and_path); putch(':'); break; } case 'G': // > (greater-than sign) putch('>'); break; case 'L': // < (less-than sign) putch('<'); break; case 'B': // | (pipe) putch('|'); break; case '_': // ENTER-LINEFEED cputs("\r\n"); break; case 'E': // ASCII escape code putch(27); break; case 'H': // Backspace putch(8); break; default: putch('$'); putch(*promptvar); break; } } else putch(*promptvar); promptvar++; } } static void extract_args(char *src) { char *dest, *saved_src = src; // scout ahead to see if there are really any arguments while (*src == ' ' || *src == '\t') src++; if (*src == '\0' || *src == ';') { cmd_arg[0] = '\0'; cmd_switch[0] = '\0'; return; } // extract combined arguments src = saved_src; if (*src == ' ' || *src == '\t') src++; memmove(cmd_args, src, strlen(src)+1); // extract first occurring single argument src = cmd_args; while (*src == ' ' || *src == '\t') src++; dest = cmd_arg; while (*src != ' ' && *src != '\t' && *src != '\0') { *dest = *src; dest++; src++; if (*src == '/' || *src == ';') break; } *dest = '\0'; // copy the single argument to cmd_switch if it qualifies as a switch if (cmd_arg[0] == '/') strcpy(cmd_switch, cmd_arg); else cmd_switch[0] = '\0'; return; } static void advance_cmd_arg(void) { char *extr; extr = cmd_args; // skip over first argument while (*extr == ' ' || *extr == '\t') extr++; if (*extr == '\0') { cmd_args[0] = '\0'; goto NoArgs; } while (*extr != ' ' && *extr != '\t' && *extr != '\0') { extr++; if (*extr == '/' || *extr == ';') break; } if (*extr == '\0') { cmd_args[0] = '\0'; goto NoArgs; } if (*extr == ';') { memmove(cmd_args, extr, strlen(extr)+1); goto NoArgs; } // extract the rest extract_args(extr); return; NoArgs: cmd_arg[0] = '\0'; cmd_switch[0] = '\0'; return; } static unsigned short keyb_shift_states; static unsigned short keyb_get_rawcode(void) { unsigned short c = getch(); if (c == 0x00/* || c == 0xE0*/) c = getch()<<8; if (c == KEY_INSERT) keyb_shift_states ^= KEYB_FLAG_INSERT; return c; } static unsigned short keyb_get_shift_states(void) { return keyb_shift_states; } static void prompt_for_and_get_cmd(void) { int flag = 0, key = 0, len, len1, need_store, got_tab; char conbuf[MAX_CMD_BUFLEN+1]; output_prompt(); /* Console initialize */ flag = keyb_get_shift_states(); if (!(flag&KEYB_FLAG_INSERT)) _setcursortype(_NORMALCURSOR); else _setcursortype(_SOLIDCURSOR); need_store = 0; got_tab = 0; conbuf[0] = '\0'; do { /* Wait and get raw key code */ key = keyb_get_rawcode(); flag = keyb_get_shift_states(); // if (KEY_ASCII(key) == KEY_EXT) // key = KEY_EXTM(key); // else if (KEY_ASCII(key) != 0) key = KEY_ASCII(key); if (key != KEY_ENTER) need_store = 1; if (key != KEY_TAB) got_tab = 0; switch (key) { case 0: break; case 3: case 0x100: { int cur = cmdbuf_getcur(); int tail = cmdbuf_gettail(); if (tail > cur) cmdbuf_clreol(conbuf); else if (tail > 0) cmdbuf_clear(conbuf); break; } case KEY_ENTER: break; case KEY_BACKSPACE: if (cmdbuf_bksp(conbuf)) { putch(KEY_ASCII(KEY_BACKSPACE)); putch(' '); putch(KEY_ASCII(KEY_BACKSPACE)); } break; case KEY_DELETE: cmdbuf_delch(conbuf); break; case KEY_INSERT: if (!(flag&KEYB_FLAG_INSERT)) _setcursortype(_NORMALCURSOR); else _setcursortype(_SOLIDCURSOR); break; case KEY_UP: if (conbuf[0]) { cmdbuf_trunc(conbuf); cmdbuf_store_tmp(conbuf); cmdbuf_clear(conbuf); } cmdbuf_move(conbuf, UP); break; case KEY_LEFT: cmdbuf_move(conbuf, LEFT); break; case KEY_RIGHT: cmdbuf_move(conbuf, RIGHT); break; case KEY_DOWN: if (conbuf[0]) { cmdbuf_trunc(conbuf); cmdbuf_store_tmp(conbuf); cmdbuf_clear(conbuf); } cmdbuf_move(conbuf, DOWN); break; case KEY_PGUP: if (conbuf[0]) { cmdbuf_trunc(conbuf); cmdbuf_store_tmp(conbuf); cmdbuf_clear(conbuf); } cmdbuf_move(conbuf, PGUP); break; case KEY_PGDN: if (conbuf[0]) { cmdbuf_trunc(conbuf); cmdbuf_store_tmp(conbuf); cmdbuf_clear(conbuf); } cmdbuf_move(conbuf, PGDN); break; case KEY_HOME: cmdbuf_move(conbuf, HOME); break; case KEY_END: cmdbuf_move(conbuf, END); break; case KEY_TAB: { int rc, need_prn = got_tab, l = 0; char p[MAXPATH]; const char *p1; cmdbuf_trunc(conbuf); cmdbuf_clreol(conbuf); if (need_prn) putchar('\n'); if ((p1 = strrchr(conbuf, ' '))) { p1++; rc = compl_fname(p1, got_tab, &l, p); /* fixup for directories */ if (rc == 1) { char buf[MAXPATH]; struct stat sb; strcpy(buf, p1); strcat(buf, p); int err = stat(buf, &sb); if (!err && (sb.st_mode & S_IFMT) == S_IFDIR) { strcat(p, "\\"); l++; rc = 0; } } } else rc = compl_cmds(conbuf, got_tab, &l, p); if (need_prn) output_prompt(); got_tab = 0; switch (rc) { case -1: putchar('\a'); break; case 0: if (!need_prn && !l) putchar('\a'); if (strlen(conbuf)) got_tab++; if (l) { printf("%.*s", l, p); strncat(conbuf, p, l); cmdbuf_puts(conbuf); } break; case 1: printf("%s ", p); strcat(conbuf, p); strcat(conbuf, " "); cmdbuf_puts(conbuf); break; } if (need_prn) printf("%s", conbuf); break; } default: if (KEY_ASCII(key) != 0x00/* && KEY_ASCII(key) != 0xE0*/) { char c = cmdbuf_putch(conbuf, MAX_CMD_BUFLEN-2, KEY_ASCII(key), flag); if (c) putch(c); } break; } } while (key != KEY_ENTER); if (need_store) { cmdbuf_trunc(conbuf); cmdbuf_eol(); } else cmdbuf_reset(); strcpy(cmd_line, conbuf); /* Get the size of typed string */ len = strlen(conbuf); if (!len) { cputs("\r\n"); return; } len1 = strspn(cmd_line, "\r\n\t "); if (len1 >= len) { /* whole cmd_line contains only junk */ cputs("\r"); return; } if (len1) { /* part of cmd_line contains junk, skip it */ memmove(cmd_line, cmd_line + len1, len - len1 + 1); len -= len1; } if (need_store) cmdbuf_store(cmd_line); parse_cmd_line(); cputs("\r\n"); } static int get_choice(const char *choices) { int choice, key; // strupr(choices); do { key = getch(); if (key == 0) continue; } while (strchr(choices, toupper(key)) == NULL); choice = toupper(key); cprintf("%c", choice); cputs("\r\n"); return choice; } static void get_cmd_from_bat_file(void) { FILE *cmd_file; int line_num, c, ba; char *s; if (bat_file_line_number[stack_level] != MAXINT) bat_file_line_number[stack_level]++; cmd_file = fopen(bat_file_path[stack_level], "rt"); if (cmd_file == NULL) { cprintf("Cannot open %s\r\n", bat_file_path[stack_level]); goto ErrorDone; } for (line_num = 0; line_num < bat_file_line_number[stack_level]; line_num++) { /* input as much of the line as the buffer can hold */ s = fgets(cmd_line, MAX_CMD_BUFLEN, cmd_file); /* if s is null, investigate why */ if (s == NULL) { /* check for error */ if (ferror(cmd_file)) { cprintf("Read error: %s, line %d\r\n", bat_file_path[stack_level], line_num+1); goto ErrorDone; } /* line is unavailable because of end of file */ if (goto_label[0] != '\0') { cprintf("Label not found - %s\r\n", goto_label); goto_label[0] = '\0'; goto ErrorDone; } goto FileDone; } /* * Check for newline character; * If present, we have successfully reached end of line * If not present, continue getting line until newline or eof encountered */ s = strchr(cmd_line, '\n'); if (s != NULL) *s = '\0'; else { do { c = fgetc(cmd_file); } while (c != '\n' && c != EOF); // if eof occurs here, it needs // to be caught on the next iteration // but not now, and not here. if (ferror(cmd_file)) { cprintf("Read error: %s, line %d\r\n", bat_file_path[stack_level], line_num+1); goto ErrorDone; } } // check for goto arrival at labeled destination if (goto_label[0] != '\0') { s = cmd_line; while (*s == ' ' || *s == '\t') s++; if (*s == ':') { s++; if (strnicmp(goto_label, s, strlen(goto_label)) == 0) { s += strlen(goto_label); if (*s == ' ' || *s == '\t' || *s == '\0') { // we have arrived... set line number, erase goto label bat_file_line_number[stack_level] = line_num + 1; goto_label[0] = '\0'; break; } } } } } if (stepping) { int c; printf("%s [Y/N] ", cmd_line); c = getche(); puts(""); switch (c) { case 'Y': case 'y': break; case 'N': case 'n': return; case 0x1b: // ESC stepping = 0; break; default: goto ErrorDone; } } // parse command parse_cmd_line(); // deal with echo on/off and '@' at the beginning of the command line if (cmd[0] == '@') memmove(cmd, cmd+1, strlen(cmd)); else { if (echo_on[stack_level] && !stepping) { output_prompt(); cputs(cmd_line); cputs("\r\n"); } } goto RoutineDone; ErrorDone: reset_batfile_call_stack(); FileDone: cmd_line[0] = '\0'; parse_cmd_line(); // this clears cmd[], cmd_arg[], cmd_switch[], and cmd_args[] bat_file_path[stack_level][0] = '\0'; for (ba = 0; ba < MAX_BAT_ARGS; ba++) bat_arg[stack_level][ba][0] = '\0'; bat_file_line_number[stack_level] = 0; echo_on[stack_level] = true; if (stack_level > 0) stack_level--; if (stack_level == 0 && bat_file_path[stack_level][0] == '\0') { stepping = 0; /* send empty cmd to update window title of dosemu2 */ installable_command_check(cmd_line, ""); } RoutineDone: if (cmd_file != NULL) fclose(cmd_file); } static int ensure_dir_existence(char *dir) { char *c; size_t len; char dir_path[MAXPATH]; strcpy(dir_path, dir); len = strlen(dir_path); if (!len) return -1; if (dir_path[len - 1] == '\\') // take away ending backslash dir_path[len - 1] = '\0'; if (file_access(dir_path, D_OK) != 0) { c = strchr(dir_path, '\\'); while (c != NULL) { c = strchr(c+1, '\\'); if (c == NULL) printf("Creating directory - %s\\\n", dir_path); else *c = '\0'; if (_mkdir(dir_path) != 0 && c == NULL) { cprintf("Unable to create directory - %s\\\r\n", dir_path); return -1; } if (c != NULL) *c = '\\'; } } return 0; } static int copy_single_file(char *source_file, char *dest_file, int transfer_type, int append) { FILE *source_stream; FILE *dest_stream; char transfer_buffer[32768]; size_t byte_count; struct stat st; int err; if (stricmp(source_file, dest_file) == 0) { cprintf("Source and destination cannot match - %s\r\n", source_file); return -1; } err = lstat(source_file, &st); if (err) { cprintf("cannot stat %s\r\n", source_file); return -1; } if (st.st_mode & S_IFCHR) { source_stream = fopen(source_file, "rt"); setbuf(source_stream, NULL); __file_handle_set(fileno(source_stream), O_BINARY); } else { /* Open file for copy */ source_stream = fopen(source_file, "rb"); } if (source_stream == NULL) { cprintf("Unable to open source file - %s\r\n", source_file); return -1; } dest_stream = fopen(dest_file, append ? "ab" : "wb"); if (dest_stream == NULL) { cprintf("Unable to open destination file - %s\r\n", dest_file); fclose(source_stream); return -1; } /* Copy file contents*/ do { byte_count = 0; if (st.st_mode & S_IFCHR) { char c; c = fgetc(source_stream); if (!(c == EOF || c == 0x1a || c == 3 || c == 0)) { transfer_buffer[0] = c; byte_count = 1; } } else byte_count = fread(transfer_buffer, 1, 32768, source_stream); if (byte_count > 0) { if (fwrite(transfer_buffer, 1, byte_count, dest_stream) != byte_count) goto copy_error_close; } } while (byte_count > 0); /* Copy date and time */ fflush(dest_stream); if (file_copytime (fileno(dest_stream), fileno(source_stream)) != 0) goto copy_error_close; /* Close source and dest files */ fclose(source_stream); if (fclose(dest_stream) != 0) goto copy_error; return 0; /* * Error routine */ copy_error_close: fclose(source_stream); fclose(dest_stream); copy_error: remove(dest_file); // erase the unfinished file if (transfer_type == FILE_XFER_MOVE) cprintf("Error occurred while moving file - %s\r\n", source_file); else cprintf("Error occurred while copying to file - %s\r\n", dest_file); return -1; } /* copy_single_file */ static int verify_file(char *master_file, char *verify_file) { FILE *mstream; FILE *vstream; char mtransfer_buffer[32768]; char vtransfer_buffer[32768]; int b; size_t mbyte_count, vbyte_count; struct stat mfile_st, vfile_st; /* Open files */ mstream = fopen(master_file, "rb"); if (mstream == NULL) goto verify_error; vstream = fopen(verify_file, "rb"); if (vstream == NULL) { fclose(mstream); goto verify_error; } /* Verify file contents*/ do { mbyte_count = fread(mtransfer_buffer, 1, 32768, mstream); vbyte_count = fread(vtransfer_buffer, 1, 32768, vstream); if (mbyte_count != vbyte_count) goto verify_error_close; if (mbyte_count > 0) { for (b = 0; b < mbyte_count; b++) { if (mtransfer_buffer[b] != vtransfer_buffer[b]) goto verify_error_close; } } } while (mbyte_count > 0); /* verify date and time */ if (fstat(fileno(mstream), &mfile_st) != 0) goto verify_error_close; if (fstat(fileno(vstream), &vfile_st) != 0) goto verify_error_close; if (mfile_st.st_atime != vfile_st.st_atime || mfile_st.st_mtime != vfile_st.st_mtime) goto verify_error_close; /* Close source and dest files */ fclose(mstream); fclose(vstream); return 0; /* * Error routine */ verify_error_close: fclose(mstream); fclose(vstream); verify_error: cprintf("Verify failed - %s\r\n", verify_file); return -1; } static void expand_wildcard(char *spec, const char *fname, const char *fext) { char *p, *dot; const char *p1; int on_ext = 0; dot = strchr(spec, '.'); /* first convert * to ? */ while ((p = strchr(spec, '*'))) { int len = ((dot && p > dot) ? 3 - (p - (dot + 1)) : 8 - (p - spec)); int rem = strlen(p + 1) + 1; memmove(p + len, p + 1, rem); memset(p, '?', len); dot = strchr(spec, '.'); } /* now expand it */ p = spec; p1 = fname; while (*p) { if (*p == '?') { if (*p1) *p = *p1; else { memmove(p, p + 1, strlen(p + 1) + 1); p--; dot = strchr(spec, '.'); } } p++; if (*p1) p1++; if (!*p1 && p > dot && !on_ext) { on_ext++; p1 = fext; } } } static void general_file_transfer(int transfer_type, int append) { int xfer_count = 0; int ffrc; long ffhandle = 0; int traverse_subdirs = false; int copy_empty_subdirs = false; int do_file_verify; int s, subdir_level = 0; finddata_t ff[MAX_SUBDIR_LEVEL]; char dir_name[MAX_SUBDIR_LEVEL][MAXPATH]; int visitation_mode[MAX_SUBDIR_LEVEL]; // 4 = findfirst source_filespec for files; // 3 = findnext source_filespec for files; // 2 = findfirst *.* for subdirs; // 1 = findnext *.* for subdirs; // 0 = done unsigned attrib; char drivespec[MAXDRIVE], dirspec[MAXDIR], s_filespec[MAXFILE], s_extspec[MAXEXT]; char d_filespec[MAXFILE], d_extspec[MAXEXT]; char temp_path[MAXPATH]; char source_path[MAXPATH] = "", source_filespec[MAXPATH]; char dest_path[MAXPATH] = "", dest_filespec[MAXPATH]; char full_source_filespec[MAXPATH]; char full_dest_filespec[MAXPATH]; char full_dest_dirspec[MAXPATH]; if (transfer_type == FILE_XFER_MOVE) do_file_verify = true; else do_file_verify = false; while (*cmd_arg != '\0') { if (*cmd_switch == '\0') // if not a command switch ... { if (*source_path == '\0') { strncpy(source_path, cmd_arg, MAXPATH); source_path[MAXPATH-1] = '\0'; conv_unix_path_to_ms_dos(source_path); } else if (*dest_path == '\0') { strncpy(dest_path, cmd_arg, MAXPATH); dest_path[MAXPATH-1] = '\0'; conv_unix_path_to_ms_dos(dest_path); } else { cprintf("Too many parameters - %s\r\n", cmd_args); reset_batfile_call_stack(); return; } } else { if (stricmp(cmd_switch,"/v") == 0) { if (transfer_type == FILE_XFER_COPY || transfer_type == FILE_XFER_XCOPY) do_file_verify = true; else goto InvalidSwitch; } if (stricmp(cmd_switch,"/b") == 0) { /* ignore */ } else { if (transfer_type == FILE_XFER_XCOPY) { if (stricmp(cmd_switch,"/s") == 0) traverse_subdirs = true; else if (stricmp(cmd_switch,"/e") == 0) copy_empty_subdirs = true; else goto InvalidSwitch; } else goto InvalidSwitch; } } advance_cmd_arg(); } if (*source_path == '\0' || (transfer_type == FILE_XFER_MOVE && *dest_path == '\0')) { cputs("Required parameter missing\r\n"); reset_batfile_call_stack(); return; } if (*dest_path == '\0') strcpy(dest_path, "."); // prepare source for fnsplit() - // attach a file specification if specified source doesn't have one if (is_drive_spec(source_path) || has_trailing_slash(source_path)) strcat(source_path, "*.*"); else { // see if source exists and is a directory; if so, attach a file spec if (file_access(source_path, D_OK) == 0) strcat(source_path, "\\*.*"); } // parse source - create full source path and split into 2 components: path + file spec _fixpath(source_path, temp_path); fnsplit(temp_path, drivespec, dirspec, s_filespec, s_extspec); strcpy(source_path, drivespec); strcat(source_path, dirspec); conv_unix_path_to_ms_dos(source_path); strcpy(source_filespec, s_filespec); strcat(source_filespec, s_extspec); conv_unix_path_to_ms_dos(source_filespec); // prepare dest for fnsplit() - // attach a file specification if specified dest doesn't have one if (is_drive_spec(dest_path) || has_trailing_slash(dest_path)) strcat(dest_path, "*.*"); else { // see if dest exists and is a directory; if so, attach a file spec if (file_access(dest_path, D_OK) == 0) strcat(dest_path, "\\*.*"); else // else -- if dest does not exist or is not a directory... { if (transfer_type == FILE_XFER_XCOPY || transfer_type == FILE_XFER_MOVE) { // if source has a wildcard and dest does not, then treat dest as a dir ... if (has_wildcard(source_filespec) && !has_wildcard(dest_path)) strcat(dest_path, "\\*.*"); // dest is a directory; attach a file spec } else { if (transfer_type == FILE_XFER_XCOPY) // if we are doing xcopy, ask if target is a dir or a file { fnsplit(dest_path, NULL, NULL, d_filespec, d_extspec); cprintf("Does %s%s specify a file name\r\n", d_filespec, d_extspec); cputs("or directory name on the target\r\n"); cputs("(F = file, D = directory)?"); if (get_choice("FD") == 'D') strcat(dest_path, "\\*.*"); } } } } // parse dest - create full dest path and split into 2 components: path + file spec _fixpath(dest_path, temp_path); fnsplit(temp_path, drivespec, dirspec, d_filespec, d_extspec); strcpy(dest_path, drivespec); strcat(dest_path, dirspec); conv_unix_path_to_ms_dos(dest_path); strcpy(dest_filespec, d_filespec); strcat(dest_filespec, d_extspec); conv_unix_path_to_ms_dos(dest_filespec); if (has_wildcard(dest_filespec)) expand_wildcard(dest_filespec, s_filespec, s_extspec + 1); if (has_wildcard(dest_path)) { cputs("Illegal wildcard on destination\r\n"); reset_batfile_call_stack(); return; } // Stuff for the move command only if (transfer_type == FILE_XFER_MOVE) { // if source and dest are both full directories in the same // tree and on the same drive, and the dest directory does not exist, // then just rename source directory to dest if (strcmp(source_filespec, "*.*") == 0 && !is_drive_spec_with_slash(source_path) && strcmp(dest_filespec, "*.*") == 0 && !is_drive_spec_with_slash(dest_path)) { char source_dirspec[MAXPATH]; char dest_dirspec[MAXPATH]; int dest_dir_exists; char *sbs, *dbs; // backslash ptrs // check for both dirs to be at same tree and on the same drive strcpy(source_dirspec, source_path); strcpy(dest_dirspec, dest_path); *(strrchr(source_dirspec, '\\')) = '\0'; // get rid of trailing backslash *(strrchr(dest_dirspec, '\\')) = '\0'; // get rid of trailing backslash dest_dir_exists = (file_access(dest_dirspec, D_OK) == 0); sbs = strrchr(source_dirspec, '\\'); *sbs = '\0'; // chop off source dir name, leaving source tree dbs = strrchr(dest_dirspec, '\\'); *dbs = '\0'; // chop off dest dir name, leaving dest tree if (stricmp(source_dirspec, dest_dirspec) == 0) // if source tree == dest tree { if (!dest_dir_exists) // if dest dir does not exist.. { *sbs = '\\'; // put the backslash back *dbs = '\\'; // put the backslash back if (rename(source_dirspec, dest_dirspec) == 0) { printf("%s renamed to %s\n", source_dirspec, dbs+1); return; } } } } } // visit each directory; perform transfer visitation_mode[0] = 4; dir_name[0][0] = '\0'; while (subdir_level >= 0) { if (visitation_mode[subdir_level] == 4 || visitation_mode[subdir_level] == 2) { strcpy(full_source_filespec, source_path); for (s = 0; s <= subdir_level; s++) strcat(full_source_filespec, dir_name[s]); if (visitation_mode[subdir_level] == 4) { strcat(full_source_filespec, source_filespec); attrib = 0+FA_HIDDEN+FA_SYSTEM; } else { strcat(full_source_filespec, "*.*"); attrib = 0+FA_DIREC+FA_HIDDEN+FA_SYSTEM; } ffrc = findfirst_f(full_source_filespec, &(ff[subdir_level]), attrib, &ffhandle); visitation_mode[subdir_level]--; } else ffrc = findnext_f(&(ff[subdir_level]), ffhandle); if (ffrc == 0) { conv_unix_path_to_ms_dos(FINDDATA_T_FILENAME(ff[subdir_level])); strcpy(full_source_filespec, source_path); strcpy(full_dest_filespec, dest_path); strcpy(full_dest_dirspec, dest_path); for (s = 0; s <= subdir_level; s++) { strcat(full_source_filespec, dir_name[s]); strcat(full_dest_filespec, dir_name[s]); strcat(full_dest_dirspec, dir_name[s]); } strcat(full_source_filespec, FINDDATA_T_FILENAME(ff[subdir_level])); if (strcmp(dest_filespec, "*.*") == 0) strcat(full_dest_filespec, FINDDATA_T_FILENAME(ff[subdir_level])); else strcat(full_dest_filespec, dest_filespec); if ((FINDDATA_T_ATTRIB(ff[subdir_level])&FA_DIREC) != 0) { if (visitation_mode[subdir_level] <= 2 && traverse_subdirs && strcmp(FINDDATA_T_FILENAME(ff[subdir_level]),".") != 0 && strcmp(FINDDATA_T_FILENAME(ff[subdir_level]),"..") != 0) { subdir_level++; if (subdir_level >= MAX_SUBDIR_LEVEL) { cputs("Directory tree is too deep\r\n"); reset_batfile_call_stack(); goto ExitOperation; } if (copy_empty_subdirs) { if (ensure_dir_existence(full_dest_filespec) != 0) { reset_batfile_call_stack(); goto ExitOperation; } } visitation_mode[subdir_level] = 4; strcpy(dir_name[subdir_level], FINDDATA_T_FILENAME(ff[subdir_level-1])); strcat(dir_name[subdir_level], "\\"); } } else { if (visitation_mode[subdir_level] > 2) { if (transfer_type == FILE_XFER_XCOPY || transfer_type == FILE_XFER_MOVE) { if (ensure_dir_existence(full_dest_dirspec) != 0) { reset_batfile_call_stack(); goto ExitOperation; } } if (copy_single_file(full_source_filespec, full_dest_filespec, transfer_type, append) != 0) { reset_batfile_call_stack(); goto ExitOperation; } if (do_file_verify) { if (verify_file(full_source_filespec, full_dest_filespec) != 0) { reset_batfile_call_stack(); goto ExitOperation; } } if (transfer_type == FILE_XFER_MOVE) { if (remove(full_source_filespec) != 0) { remove(full_dest_filespec); cprintf("Unable to move file - %s\r\n", full_source_filespec); reset_batfile_call_stack(); goto ExitOperation; } } printf("%s %s to %s\n", FINDDATA_T_FILENAME(ff[subdir_level]), append ? "appended" : ( transfer_type == FILE_XFER_MOVE?"moved":"copied"), strcmp(dest_filespec, "*.*")==0?full_dest_dirspec:full_dest_filespec); xfer_count++; } } } else { if (traverse_subdirs) visitation_mode[subdir_level]--; else visitation_mode[subdir_level] = 0; if (visitation_mode[subdir_level] <= 0) subdir_level--; } } if (xfer_count == 0) printf("File(s) not found - %s%s\n", source_path, source_filespec); else { if (transfer_type == FILE_XFER_MOVE) printf("%9d file(s) moved\n", xfer_count); else printf("%9d file(s) copied\n", xfer_count); } ExitOperation: return; InvalidSwitch: cprintf("Invalid switch - %s\r\n", cmd_switch); reset_batfile_call_stack(); return; } static int get_set_file_attribute(char *full_path_filespec, unsigned req_attrib, unsigned attrib_mask) { int a; unsigned actual_attrib; if (attrib_mask == 0) { if (getfileattr(full_path_filespec, &actual_attrib) != 0) { cprintf("Cannot read attribute - %s\r\n", full_path_filespec); return -1; } } else { if (getfileattr(full_path_filespec, &actual_attrib) != 0) actual_attrib = 0; actual_attrib &= (~attrib_mask); actual_attrib |= (req_attrib & attrib_mask); if (setfileattr(full_path_filespec, actual_attrib) != 0) goto CantSetAttr; printf("Attribute set to "); } for (a = 0; a < 4; a++) { if ((actual_attrib&attrib_values[a]) == 0) printf(" -%c",tolower(attrib_letters[a])); else printf(" +%c",toupper(attrib_letters[a])); } printf(" - %s\n", full_path_filespec); return 0; CantSetAttr: cprintf("Cannot set attribute - %s\r\n", full_path_filespec); return -1; } /////////////////////////////////////////////////////////////////////////////////// static void perform_attrib(const char *arg) { long ffhandle = 0; int ffrc; int file_count = 0; int traverse_subdirs = false; int s, subdir_level = 0; finddata_t ff[MAX_SUBDIR_LEVEL]; char dir_name[MAX_SUBDIR_LEVEL][MAXPATH]; int visitation_mode[MAX_SUBDIR_LEVEL]; // 4 = findfirst source_filespec for files; // 3 = findnext source_filespec for files; // 2 = findfirst *.* for subdirs; // 1 = findnext *.* for subdirs; // 0 = done char drivespec[MAXDRIVE], dirspec[MAXDIR], filename[MAXFILE], extspec[MAXEXT]; char temp_path[MAXPATH]; char path[MAXPATH] = "", filespec[MAXPATH]; char full_path_filespec[MAXPATH]; int a; unsigned req_attrib = 0, attrib_mask = 0; unsigned search_attrib; while (*arg != '\0') { if (*cmd_switch == '\0') // if not a command switch ... { if (strlen(arg) == 2 && (arg[0] == '+' || arg[0] == '-')) { for (a = 0; a < 4; a++) { if (toupper(arg[1]) == toupper(attrib_letters[a])) { attrib_mask |= attrib_values[a]; if (arg[0] == '+') req_attrib |= attrib_values[a]; else req_attrib &= (~(attrib_values[a])); } } } else if (*path == '\0') { strncpy(path, arg, MAXPATH); path[MAXPATH-1] = '\0'; conv_unix_path_to_ms_dos(path); } else { cprintf("Too many parameters - %s\r\n", cmd_args); reset_batfile_call_stack(); return; } } else { if (stricmp(cmd_switch,"/s") == 0) traverse_subdirs = true; else { cprintf("Invalid switch - %s\r\n", cmd_switch); reset_batfile_call_stack(); return; } } advance_cmd_arg(); } if (*path == '\0') strcpy(path, "*.*"); // prepare path for fnsplit() - // attach a file specification if specified path doesn't have one if (is_drive_spec(path) || has_trailing_slash(path)) strcat(path, "*.*"); else { // see if path exists and is a directory; if so, attach a file spec if (file_access(path, D_OK) == 0) strcat(path, "\\*.*"); } // parse path - create full path and split into 2 components: path + file spec _fixpath(path, temp_path); fnsplit(temp_path, drivespec, dirspec, filename, extspec); strcpy(path, drivespec); strcat(path, dirspec); conv_unix_path_to_ms_dos(path); strcpy(filespec, filename); strcat(filespec, extspec); conv_unix_path_to_ms_dos(filespec); // visit each directory; perform attrib get/set visitation_mode[0] = 4; dir_name[0][0] = '\0'; while (subdir_level >= 0) { if (visitation_mode[subdir_level] == 4 || visitation_mode[subdir_level] == 2) { strcpy(full_path_filespec, path); for (s = 0; s <= subdir_level; s++) strcat(full_path_filespec, dir_name[s]); if (visitation_mode[subdir_level] == 4) { strcat(full_path_filespec, filespec); search_attrib = FA_RDONLY+FA_ARCH+FA_SYSTEM+FA_HIDDEN; } else { strcat(full_path_filespec, "*.*"); search_attrib = FA_DIREC+FA_RDONLY+FA_ARCH+FA_SYSTEM+FA_HIDDEN; } ffrc = findfirst_f(full_path_filespec, &(ff[subdir_level]), search_attrib, &ffhandle); visitation_mode[subdir_level]--; } else ffrc = findnext_f(&(ff[subdir_level]), ffhandle); if (ffrc == 0) { conv_unix_path_to_ms_dos(FINDDATA_T_FILENAME(ff[subdir_level])); strcpy(full_path_filespec, path); for (s = 0; s <= subdir_level; s++) strcat(full_path_filespec, dir_name[s]); strcat(full_path_filespec, FINDDATA_T_FILENAME(ff[subdir_level])); if ((FINDDATA_T_ATTRIB(ff[subdir_level])&FA_DIREC) != 0) { if (visitation_mode[subdir_level] <= 2 && traverse_subdirs && strcmp(FINDDATA_T_FILENAME(ff[subdir_level]),".") != 0 && strcmp(FINDDATA_T_FILENAME(ff[subdir_level]),"..") != 0) { subdir_level++; if (subdir_level >= MAX_SUBDIR_LEVEL) { cputs("Directory tree is too deep\r\n"); reset_batfile_call_stack(); return; } visitation_mode[subdir_level] = 4; strcpy(dir_name[subdir_level], FINDDATA_T_FILENAME(ff[subdir_level-1])); strcat(dir_name[subdir_level], "\\"); } } else { if (visitation_mode[subdir_level] > 2) { if (get_set_file_attribute(full_path_filespec, req_attrib, attrib_mask) != 0) { reset_batfile_call_stack(); return; } file_count++; } } } else { if (traverse_subdirs) visitation_mode[subdir_level]--; else visitation_mode[subdir_level] = 0; if (visitation_mode[subdir_level] <= 0) subdir_level--; } } if (file_count == 0) printf("File(s) not found - %s%s\n", path, filespec); } static void perform_call(const char *arg) { while (*cmd_switch) // skip switches advance_cmd_arg(); strcpy(cmd, arg); advance_cmd_arg(); exec_cmd(true); } static void perform_license(const char *arg) { const char *license = "comcom64 - COMMAND.COM-compatible command processor for DOS.\n\n" "Copyright (C) 1997, CENTROID CORPORATION, HOWARD, PA 16841\n" "Copyright (C) Allen S. Cheung (allencheung@fastmail.ca)\n" "Copyright (C) 2005, Hanzac Chen\n" "Copyright (C) 2019, C. Masloch \n" "Copyright (C) 2018-2024, @stsp \n" "\n" "This program is free software: you can redistribute it and/or modify\n" "it under the terms of the GNU General Public License as published by\n" "the Free Software Foundation, either version 3 of the License, or\n" "(at your option) any later version.\n" "\n" "This program is distributed in the hope that it will be useful,\n" "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" "GNU General Public License for more details.\n"; printf("%s\n", license); } static void perform_loadhigh(const char *arg) { if (!*arg) { cprintf("loadhigh: command name missing\r\n"); reset_batfile_call_stack(); return; } while (*cmd_switch) // skip switches advance_cmd_arg(); strcpy(cmd, arg); advance_cmd_arg(); perform_external_cmd(false, true, cmd); /* Should we set this to true? Only affects batch files anyway, * which shouldn't be loaded with LOADHIGH to begin with. */ } #define LOADFIX_NUMBLOCKS 16 static const int loadfix_numblocks = LOADFIX_NUMBLOCKS; static unsigned short loadfix_allocations[LOADFIX_NUMBLOCKS]; static int loadfix_ii; static unsigned loadfix_initialised; static void loadfix_init(void) { int allocated; unsigned short allocation, to_64kib, max, size; __dpmi_regs r = {}; loadfix_ii = 0; do { r.h.ah = 0x48; r.x.bx = 1; __dpmi_int(0x21, &r); /* allocate one-paragraph block */ if ((r.x.flags & 1) == 0) /* if NC */ { allocated = 1; allocation = r.x.ax; #if DEBUG printf("LOADFIX: allocated block at %04Xh\n", allocation); #endif if (allocation >= 0x1000) /* does it start above 64 KiB ? */ { r.h.ah = 0x49; r.x.es = allocation; __dpmi_int(0x21, &r); /* free */ #if DEBUG printf("LOADFIX: too high, freeing block at %04Xh\n", allocation); #endif break; /* and done */ } if (loadfix_ii >= loadfix_numblocks) { printf("LOADFIX: too many blocks allocated!\n"); break; } loadfix_allocations[loadfix_ii] = allocation; ++loadfix_ii; r.h.ah = 0x4A; r.x.bx = -1; r.x.es = allocation; __dpmi_int(0x21, &r); /* resize and get maximum block size */ /* Note that this expands the block to the maximum * available size. */ max = r.x.bx; to_64kib = 0x1000 - allocation; /* note: does not underflow */ size = to_64kib < max ? to_64kib : max; r.x.bx = size; r.h.ah = 0x4A; __dpmi_int(0x21, &r); /* resize */ /* If to_64kib is the lower value, this shortens the block * to that size. Else it does nothing. */ #if DEBUG printf("LOADFIX: resizing block at %04Xh to %04Xh paragraphs (%u bytes)\n", allocation, (int)size, (int)size * 16); #endif } else { #if DEBUG printf("LOADFIX: could not allocate another block\n"); #endif allocated = 0; } } while (allocated); link_umb(0); } static void loadfix_exit(void) { __dpmi_regs r = {}; unlink_umb(); while (loadfix_ii != 0) { --loadfix_ii; r.h.ah = 0x49; r.x.es = loadfix_allocations[loadfix_ii]; __dpmi_int(0x21, &r); /* free */ #if DEBUG printf("LOADFIX: afterwards freeing block at %04Xh\n", loadfix_allocations[loadfix_ii]); #endif } } static void perform_loadfix(const char *arg) { if (!*arg) { cprintf("loadfix: command name missing\r\n"); reset_batfile_call_stack(); return; } strcpy(cmd, arg); advance_cmd_arg(); loadfix_init(); loadfix_initialised = 1; perform_external_cmd(false, false, cmd); /* Should we set this to true? Only affects batch files anyway, * which shouldn't be loaded with LOADFIX to begin with. */ loadfix_exit(); loadfix_initialised = 0; } static void perform_cd(const char *arg) { while (*cmd_switch) // skip switches advance_cmd_arg(); if (*arg) { unsigned cur_drive, dummy; int rc; if (arg[1] == ':') getdrive(&cur_drive); rc = chdir(arg); if (arg[1] == ':' && toupper(arg[0]) != 'A' + cur_drive - 1) setdrive(cur_drive, &dummy); if (rc != 0) { cprintf("Directory does not exist - %s\r\n",arg); error_level = 1; return; } } else { char cur_drive_and_path[MAXPATH]; getcwd(cur_drive_and_path, MAXPATH); conv_unix_path_to_ms_dos(cur_drive_and_path); puts(cur_drive_and_path); } } static void perform_change_drive(void) { char cur_drive_and_path[MAXPATH]; unsigned int drive_set, cur_drive = 0, old_drive, dummy; drive_set = toupper(cmd[0])-'A'+1; getdrive(&old_drive); setdrive(drive_set, &dummy); getdrive(&cur_drive); if (cur_drive != drive_set) { cprintf("Invalid drive specification - %s\r\n", cmd); error_level = 1; return; } if (!getcwd(cur_drive_and_path, MAXPATH)) { cprintf("Drive not ready - %s\r\n", cmd); setdrive(old_drive, &dummy); error_level = 1; return; } } static void perform_choice(const char *arg) { const char *choices = "YN"; // Y,N are the default choices const char *text = ""; int supress_prompt = false; int choice; while (*arg != '\0') { if (*cmd_switch == '\0') // if not a command switch ... { text = cmd_args; break; } else { if (strnicmp(cmd_switch,"/c:", 3) == 0 && strlen(cmd_switch) > 3) choices = cmd_switch+3; else if (stricmp(cmd_switch,"/n") == 0) supress_prompt = true; else { cprintf("Invalid switch - %s\r\n", cmd_switch); reset_batfile_call_stack(); return; } } advance_cmd_arg(); } cputs(text); if (!supress_prompt) { int first = true; const char *c; putch('['); c = choices; while (*c != '\0') { if (first) first = false; else putch(','); putch(toupper(*c)); c++; } cputs("]?"); } choice = get_choice(choices); error_level = strchr(choices, choice) - choices + 1; return; } static int expand_pluses(void) { char cmd_args_bkp[MAX_CMD_BUFLEN]; int len; char *p, *p2, *last_arg; strcpy(cmd_args_bkp, cmd_args); last_arg = strrchr(cmd_args_bkp, ' '); if (!last_arg) { cprintf("syntax error\n"); return -1; } len = 0; cmd_args[0] = '\0'; for (p2 = cmd_args_bkp, p = strchr(p2, '+'); p; p2 = p + 1, p = strchr(p2, '+')) { len += snprintf(cmd_args + len, sizeof(cmd_args) - len, "%.*s %s;", (int)(p - p2), p2, last_arg); } strlcat(cmd_args, p2, sizeof(cmd_args)); return 0; } static void perform_copy(const char *arg) { int err = expand_pluses(); if (err) return; general_file_transfer(FILE_XFER_COPY, 0); /* expand_pluses() delimited the arg groups with ; */ while (cmd_args[0] == ';') { cmd_args[0] = ' '; extract_args(cmd_args); if (cmd_arg[0] != '\0') general_file_transfer(FILE_XFER_COPY, 1); } } static void perform_xcopy(const char *arg) { general_file_transfer(FILE_XFER_XCOPY, 0); } #define IS_CHRDEV(d) ((d) & _DEV_CDEV) static void perform_ctty(const char *arg) { __dpmi_regs r = {}; int dinfo; int fd; if (*arg == '\0') { cprintf("ctty: device name missing\r\n"); return; } fd = open(arg, O_RDWR | O_TEXT); if (fd == -1) { cprintf("ctty: cannot open %s\r\n", arg); return; } dinfo = _get_dev_info(fd); if (dinfo == -1) { cprintf("ctty: %s is not a device\r\n", arg); goto err_close; } if (!IS_CHRDEV(dinfo)) { cprintf("ctty: %s is not a char device\r\n", arg); goto err_close; } if ((dinfo & 3) != 0) { cprintf("ctty: %s is already a tty\r\n", arg); goto err_close; } if ((dinfo & 0x3c) != 0) { cprintf("ctty: %s has wrong type %x\r\n", arg, r.x.dx); goto err_close; } r.x.ax = 0x4401; r.x.bx = fd; r.x.dx = (dinfo & 0xff) | (_DEV_STDIN | _DEV_STDOUT); // set ctty __dpmi_int(0x21, &r); if (r.x.flags & 1) { cprintf("ctty: failed to set ctty on %s\r\n", arg); goto err_close; } dinfo = _get_dev_info(0); if (dinfo != -1) { r.x.ax = 0x4401; r.x.bx = 0; r.x.dx = dinfo & 0xff & ~(_DEV_STDIN | _DEV_STDOUT); // clear ctty from old __dpmi_int(0x21, &r); if (r.x.flags & 1) cprintf("ctty: stdin clear ctty failed with %x\r\n", r.x.ax); } else cprintf("ctty: stdin ioctl failed\r\n"); close(0); close(1); close(2); dup(fd); dup(fd); dup(fd); err_close: close(fd); } static void perform_date(const char *arg) { time_t t = time(NULL); struct tm loctime; const char *day_of_week[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; localtime_r(&t, &loctime); if (*arg != '\0') { struct timeval tv; unsigned int m, d, y; int rc = sscanf(arg, "%d-%d-%d", &m, &d, &y); if (rc != 3 || m == 0 || m > 12 || d == 0 || d > 31) { cprintf("Invalid date\r\n"); reset_batfile_call_stack(); return; } if (y < 100) { if (y < 80) y += 2000; else y += 1900; } else if (y < 1900) { cprintf("Invalid year\r\n"); reset_batfile_call_stack(); return; } if (y >= 2038) { cprintf("Invalid year: Y2K38\r\n"); reset_batfile_call_stack(); return; } loctime.tm_year = y - 1900; loctime.tm_mon = m - 1; loctime.tm_mday = d; tv.tv_sec = mktime(&loctime); tv.tv_usec = 0; settimeofday(&tv, NULL); } else printf("Current date is %s %02d-%02d-%04d\n", day_of_week[loctime.tm_wday], loctime.tm_mon+1, loctime.tm_mday, loctime.tm_year+1900); } static void perform_delete(const char *arg) { long fhandle; finddata_t ff; char filespec[MAXPATH] = ""; char full_filespec[MAXPATH] = ""; char drive[MAXDRIVE], dir[MAXDIR]; int done = 0; while (*arg != '\0') { if (*cmd_switch == '\0') // if not a command switch ... { if (*filespec == '\0') { strncpy(filespec, arg, MAXPATH); filespec[MAXPATH-1] = '\0'; } else { cprintf("Too many parameters - %s\r\n", cmd_args); reset_batfile_call_stack(); return; } } advance_cmd_arg(); } if (*filespec == '\0') { cprintf("filespec not specified\r\n"); reset_batfile_call_stack(); return; } _fixpath(filespec, full_filespec); fnsplit(full_filespec, drive, dir, NULL, NULL); conv_unix_path_to_ms_dos(drive); conv_unix_path_to_ms_dos(dir); if (findfirst_f(full_filespec, &ff, 0, &fhandle) != 0) { printf("File(s) not found - %s\n", filespec); // informational msg; not an error return; } while (!done) { char individual_filespec[MAXPATH]; conv_unix_path_to_ms_dos(FINDDATA_T_FILENAME(ff)); strcpy(individual_filespec, drive); strcat(individual_filespec, dir); strcat(individual_filespec, FINDDATA_T_FILENAME(ff)); done = (findnext_f(&ff, fhandle) != 0); if (remove(individual_filespec) == 0) printf("%s erased\n", individual_filespec); else { cprintf("Access denied - %s\r\n", individual_filespec); error_level = 1; return; } } } static void perform_deltree(const char *arg) { long ffhandle = 0; int ffrc; int file_count = 0, dir_count = 0; int s, subdir_level = 0; int confirm_before_delete = true; finddata_t ff[MAX_SUBDIR_LEVEL]; char dir_name[MAX_SUBDIR_LEVEL][MAXPATH]; int remove_level1_dir; int visitation_mode[MAX_SUBDIR_LEVEL]; // 4 = findfirst source_filespec for files; // 3 = findnext source_filespec for files; // 2 = findfirst *.* for subdirs; // 1 = findnext *.* for subdirs; // 0 = done char drivespec[MAXDRIVE], dirspec[MAXDIR], filename[MAXFILE], extspec[MAXEXT]; char temp_path[MAXPATH]; char path[MAXPATH] = "", filespec[MAXPATH]; char full_path_filespec[MAXPATH]; int choice; unsigned search_attrib; while (*arg != '\0') { if (*cmd_switch == '\0') // if not a command switch ... { if (*path == '\0') { strncpy(path, arg, MAXPATH); path[MAXPATH-1] = '\0'; conv_unix_path_to_ms_dos(path); } else { cprintf("Too many parameters - %s\r\n", cmd_args); reset_batfile_call_stack(); return; } } else { if (stricmp(cmd_switch,"/Y") == 0) confirm_before_delete = false; else { cprintf("Invalid switch - %s\r\n", cmd_switch); reset_batfile_call_stack(); return; } } advance_cmd_arg(); } if (*path == '\0') { cprintf("filespec not specified\r\n"); reset_batfile_call_stack(); return; } // prepare path for fnsplit() - // attach a file specification if specified path doesn't have one if (is_drive_spec(path) || has_trailing_slash(path)) strcat(path, "*.*"); // parse path - create full path and split into 2 components: path + file spec _fixpath(path, temp_path); fnsplit(temp_path, drivespec, dirspec, filename, extspec); strcpy(path, drivespec); strcat(path, dirspec); conv_unix_path_to_ms_dos(path); strcpy(filespec, filename); strcat(filespec, extspec); conv_unix_path_to_ms_dos(filespec); // visit each directory; delete files and subdirs visitation_mode[0] = 4; dir_name[0][0] = '\0'; remove_level1_dir = false; while (subdir_level >= 0) { if (visitation_mode[subdir_level] == 4 || visitation_mode[subdir_level] == 2) { strcpy(full_path_filespec, path); for (s = 0; s <= subdir_level; s++) strcat(full_path_filespec, dir_name[s]); if (subdir_level == 0) strcat(full_path_filespec, filespec); else strcat(full_path_filespec, "*.*"); if (visitation_mode[subdir_level] == 4) search_attrib = FA_RDONLY+FA_ARCH+FA_SYSTEM+FA_HIDDEN; else search_attrib = FA_DIREC+FA_RDONLY+FA_ARCH+FA_SYSTEM+FA_HIDDEN; ffrc = findfirst_f(full_path_filespec, &(ff[subdir_level]), search_attrib, &ffhandle); visitation_mode[subdir_level]--; } else ffrc = findnext_f(&(ff[subdir_level]), ffhandle); if (ffrc == 0) { conv_unix_path_to_ms_dos(FINDDATA_T_FILENAME(ff[subdir_level])); strcpy(full_path_filespec, path); for (s = 0; s <= subdir_level; s++) strcat(full_path_filespec, dir_name[s]); strcat(full_path_filespec, FINDDATA_T_FILENAME(ff[subdir_level])); if ((FINDDATA_T_ATTRIB(ff[subdir_level])&FA_DIREC) != 0) { if (visitation_mode[subdir_level] <= 2 && strcmp(FINDDATA_T_FILENAME(ff[subdir_level]),".") != 0 && strcmp(FINDDATA_T_FILENAME(ff[subdir_level]),"..") != 0) { if (subdir_level == 0) { if (confirm_before_delete) { cprintf("Delete directory %s and all its subdirectories? [Y/N] ", full_path_filespec); remove_level1_dir = (get_choice("YN") == 'Y'); } else remove_level1_dir = true; } if (subdir_level > 0 || remove_level1_dir) { subdir_level++; if (subdir_level >= MAX_SUBDIR_LEVEL) { cputs("Directory tree is too deep\r\n"); reset_batfile_call_stack(); return; } visitation_mode[subdir_level] = 4; strcpy(dir_name[subdir_level], FINDDATA_T_FILENAME(ff[subdir_level-1])); strcat(dir_name[subdir_level], "\\"); } } } else { if (visitation_mode[subdir_level] > 2) { if (confirm_before_delete && subdir_level == 0) { cprintf("Delete file %s ? [Y/N] ", full_path_filespec); choice = get_choice("YN"); } else choice = 'Y'; if (choice == 'Y') { if (remove(full_path_filespec) != 0) { cprintf("Unable to delete file - %s\r\n", full_path_filespec); error_level = 1; return; } if (subdir_level == 0) printf("%s deleted\n", full_path_filespec); file_count++; } } } } else { visitation_mode[subdir_level]--; if (visitation_mode[subdir_level] <= 0) { if (subdir_level > 0) { strcpy(full_path_filespec, path); for (s = 0; s <= subdir_level; s++) strcat(full_path_filespec, dir_name[s]); *(strrchr(full_path_filespec,'\\')) = '\0'; if (subdir_level > 1 || remove_level1_dir) { if (rmdir(full_path_filespec) != 0) { cprintf("Unable to remove directory - %s\\\r\n", full_path_filespec); error_level = 1; return; } if (subdir_level == 1) printf("%s removed\n", full_path_filespec); dir_count++; } } subdir_level--; if (subdir_level >= 0) visitation_mode[subdir_level] = 4; // restart from findfirst } } } printf("%9d file(s) deleted, ", file_count); if (dir_count == 1) printf("%9d directory removed\n", dir_count); else printf("%9d (sub)directories removed\n", dir_count); } static void perform_dir(const char *arg) { long ffhandle; int ffrc; int wide_column_countdown = -1; int use_pause = 0; unsigned long long avail; //was double avail; --Salvo finddata_t ff; struct statvfs sf; int rc; unsigned int attrib = FA_DIREC+FA_RDONLY+FA_ARCH+FA_SYSTEM+FA_HIDDEN, first; unsigned long filecount = 0, dircount = 0, bytecount = 0; char dirspec[MAXPATH]; char volspec[7] = "X:\\*.*"; char full_filespec[MAXPATH]; char filespec[MAXPATH] = ""; struct text_info txinfo; gettextinfo(&txinfo); while (*arg != '\0') { if (*cmd_switch == '\0') // if not a command switch ... { if (*filespec == '\0') { strncpy(filespec, arg, MAXPATH); filespec[MAXPATH-1] = '\0'; conv_unix_path_to_ms_dos(filespec); } else { cprintf("Too many parameters - %s\r\n", cmd_args); reset_batfile_call_stack(); return; } } else { if (stricmp(cmd_switch,"/w")==0) wide_column_countdown = 5; if (stricmp(cmd_switch,"/p")==0) { use_pause = 1; clrscr(); } } advance_cmd_arg(); } if (!has_trailing_slash(filespec) && !has_wildcard(filespec) && findfirst_f(filespec, &ff, FA_DIREC, NULL) == 0) strcat(filespec, "\\"); if (*filespec == '\0' || is_drive_spec(filespec) || has_trailing_slash(filespec)) strcat(filespec, "*.*"); _fixpath(filespec, full_filespec); conv_unix_path_to_ms_dos(full_filespec); volspec[0] = full_filespec[0]; if (findfirst_f(volspec, &ff, FA_LABEL, NULL) == 0) { char *p = strchr(FINDDATA_T_FILENAME(ff), '.'); if (p) memmove(p, p + 1, strlen(p + 1) + 1); printf(" Volume in drive %c is %s\n", volspec[0], FINDDATA_T_FILENAME(ff)); } else { struct _DOSERROR derr; int err = _dosexterr(&derr); if (err == 3) { puts("Invalid drive specification"); return; } puts(" Volume has no label"); } fnsplit (full_filespec, NULL, dirspec, NULL, NULL); printf(" Directory of %c:%s\n\n", full_filespec[0], dirspec); first = true; for (;;) { if (use_pause && wherey() == txinfo.winbottom) { printf("Press any key to continue..."); getch(); clrscr(); } if (first) { if ((ffrc = findfirst_f(full_filespec, &ff, attrib, &ffhandle)) != 0) { puts("File not found"); // informational message -- not an error return; } first = false; } else { if ((ffrc = findnext_f(&ff, ffhandle)) != 0) break; } conv_unix_path_to_ms_dos(FINDDATA_T_FILENAME(ff)); if (wide_column_countdown < 0) { printf("%04d-%02d-%02d ", FINDDATA_T_WDATE_YEAR(ff), FINDDATA_T_WDATE_MON(ff), FINDDATA_T_WDATE_DAY(ff)); printf("%02d:%02d ", FINDDATA_T_WTIME_HOUR(ff), FINDDATA_T_WTIME_MIN(ff)); if ((FINDDATA_T_ATTRIB(ff)&FA_DIREC) == 0) printf("%13u", FINDDATA_T_SIZE(ff)); else printf("%8s", ""); printf(" %s\n", FINDDATA_T_FILENAME(ff)); } else { if ((FINDDATA_T_ATTRIB(ff)&FA_DIREC) == 0) printf("%-14s", FINDDATA_T_FILENAME(ff)); else { int len = strlen(FINDDATA_T_FILENAME(ff)) + 2; printf("[%s]", FINDDATA_T_FILENAME(ff)); while (len < 14) { printf(" "); len++; } } wide_column_countdown--; if (wide_column_countdown == 0) { puts(""); wide_column_countdown = 5; } else printf(" "); } if ((FINDDATA_T_ATTRIB(ff)&FA_DIREC) == 0) { filecount++; bytecount += FINDDATA_T_SIZE(ff); } else dircount++; } if (wide_column_countdown >= 0 && wide_column_countdown < 5) puts(""); printf("%10lu file(s) %14lu bytes\n", filecount, bytecount); printf("%10lu dir(s) ", dircount); rc = statvfs(full_filespec, &sf); if (rc == 0) { avail = (unsigned long long)sf.f_bavail * sf.f_bsize; if (avail < 1048576) printf("%15lli byte(s) free\n", avail); else if (avail < 1073741824) printf("%15lli KB free\n", avail / 1024); else if (avail < 2147483648ULL) printf("%15lli MB free\n", avail / 1024 / 1024); else printf("%15.1f GB free\n", avail / 1024.0 / 1024.0 / 1024.0); } else { printf("statvfs() failed\n"); } } static void perform_echo(const char *arg) { if (stricmp(arg, "off") == 0) echo_on[stack_level] = false; else if (stricmp(arg, "on") == 0) echo_on[stack_level] = true; else if (arg[0] == '\0') { if (echo_on[stack_level]) puts("ECHO is on"); else puts("ECHO is off"); } else puts(cmd_args); } static void perform_elfexec(const char *arg) { #ifdef DJ64 int rc; #endif if (!arg || !arg[0]) { cprintf("Syntax error\r\n"); reset_batfile_call_stack(); return; } #ifdef DJ64 rc = elfexec(arg, 0, NULL); if (rc == -1) printf("elfexec failed\n"); else if (rc & (1 << 15)) printf("elfexec: unsupported ELF format or not an ELF file\n"); else { char el[16]; snprintf(el, sizeof(el), "%d", rc); setenv("ERRORLEVEL", el, 1); } #else printf("elfexec unsupported\n"); #endif } static void perform_break(const char *arg) { if (stricmp(arg, "off") == 0) break_on = false; else if (stricmp(arg, "on") == 0) break_on = true; else if (arg[0] == '\0') printf("BREAK is %s\n", break_on ? "on" : "off"); else { cprintf("Syntax error\r\n"); reset_batfile_call_stack(); } } static void perform_echo_dot(const char *arg) { if (arg[0] == '\0') puts(""); else puts(cmd_args); } static void perform_exit(const char *arg) { int ba; int is_bat = bat_file_path[stack_level][0]; bat_file_path[stack_level][0] = '\0'; for (ba = 0; ba < MAX_BAT_ARGS; ba++) bat_arg[stack_level][ba][0] = '\0'; bat_file_line_number[stack_level] = 0; echo_on[stack_level] = true; if (stack_level > 0) stack_level--; else { if (!shell_permanent || (getenv("SHELL_ALLOW_EXIT") && !is_bat)) { exiting++; if (arg) { if (arg[0]) error_level = atoi(arg); else error_level = 0; } } } } struct for_iter { char *token; int glob_idx; int glob_state; glob_t gl; const char *end; char *sptr; }; static void advance_iter(struct for_iter *iter) { char *tok = strtok_r(NULL, " )", &iter->sptr); iter->token = ((tok && tok < iter->end) ? tok : NULL); } static const char *extract_token(struct for_iter *iter) { const char *tok; if (iter->glob_state == 2) { globfree(&iter->gl); iter->glob_state = 0; } again: if (!iter->token) return NULL; // no more tokens if (iter->glob_state) { tok = iter->gl.gl_pathv[iter->glob_idx++]; if (iter->glob_idx >= iter->gl.gl_pathc) { iter->glob_state = 2; advance_iter(iter); } return tok; } if (!has_wildcard(iter->token)) { tok = iter->token; advance_iter(iter); return tok; } if (glob(iter->token, 0, NULL, &iter->gl)) { advance_iter(iter); goto again; } tok = iter->gl.gl_pathv[0]; if (iter->gl.gl_pathc > 1) { iter->glob_state = 1; iter->glob_idx = 1; } else { iter->glob_state = 2; advance_iter(iter); } return tok; } static void perform_for(const char *arg) { const char *tok; char cmd_args2[MAX_CMD_BUFLEN]; struct for_iter iter = {}; char *p, *p1, *d0, *d1, *d, *c; const char *v; strcpy(cmd_args2, cmd_args); p = strchr(cmd_args2, '('); p1 = strchr(cmd_args2, ')'); d0 = strstr(cmd_args2, " DO "); d1 = strstr(cmd_args2, " do "); d = d0 ?: d1; if (!p || !p1 || p1 < p || !d) { cprintf("Syntax error\r\n"); reset_batfile_call_stack(); return; } v = arg; if (*v == '%') v++; for_var = *v; p++; iter.token = strtok_r(p, " )", &iter.sptr); iter.end = p1; c = d + 4; while (*c == ' ') c++; while ((tok = extract_token(&iter))) { strlcpy(cmd_line, c, sizeof(cmd_line)); for_val = tok; parse_cmd_line(); exec_cmd(false); } for_var = '\0'; } #define VIDADDR(r,c) (0xb8000 + 2*(((r) * txinfo.screenwidth) + (c))) #if 0 static void reset_text_attrs(void) { char attr = 7; struct text_info txinfo; int row, col; gettextinfo(&txinfo); for (row=txinfo.wintop-1; row < txinfo.winbottom; row++) { for (col=0; col < txinfo.winright - txinfo.winleft + 1; col++) dosmemput(&attr, 1, VIDADDR(row, txinfo.winleft - 1 + col) + 1); } } #endif static int orig_strat, orig_umblink; static void loadhigh_init(void) { __dpmi_regs r = {}; r.x.ax = 0x5800; __dpmi_int(0x21, &r); orig_strat = r.x.ax; r.x.ax = 0x5802; __dpmi_int(0x21, &r); orig_umblink = r.h.al; } static void loadhigh_done(void) { __dpmi_regs r = {}; r.x.ax = 0x5801; r.x.bx = orig_strat; __dpmi_int(0x21, &r); r.x.ax = 0x5803; r.x.bx = orig_umblink; __dpmi_int(0x21, &r); } static int is_HMA_enabled(void) { __dpmi_regs r = {}; r.x.ax = 0x3306; __dpmi_int(0x21, &r); return !!(r.h.dh & 0x10); } static void perform_external_cmd(int call, int lh, char *ext_cmd) { finddata_t ff; long ffhandle; char cmd_name[MAX_CMD_BUFLEN]; char *pathvar, pathlist[200]; char full_cmd[MAXPATH+MAX_CMD_BUFLEN] = ""; char temp_cmd[MAXPATH+MAX_CMD_BUFLEN]; int rc; int exec_type, e, ba; const char *exec_ext[3] = {".COM",".EXE",".BAT"}; char *s; // No wildcards allowed -- reject them if (has_wildcard(ext_cmd)) goto BadCommand; // Assemble a path list, and also extract the command name without the path. // For commands that already specify a path, the path list will consist of // only that path. s = strrchr(ext_cmd, '\\'); if (s != NULL) { s++; strncpy(pathlist, ext_cmd, s-ext_cmd); pathlist[s-ext_cmd] = '\0'; } else { s = strchr(ext_cmd, ':'); if (s != NULL) { s++; strncpy(pathlist, ext_cmd, s-ext_cmd); pathlist[s-ext_cmd] = '\0'; } else { strcpy(pathlist, ".\\;"); s = ext_cmd; pathvar = getenv("PATH"); if(pathvar != NULL) { strncat(pathlist, pathvar, 200 - strlen(pathlist) - 1); pathlist[sizeof(pathlist)-1] = '\0'; } } } strcpy(cmd_name, s); // Search for the command exec_type = -1; pathvar = strtok(pathlist, "; "); while (pathvar != NULL) { // start to build full command name (sort of, because it still could be missing .exe, .com, or .bat) strcpy(full_cmd, pathvar); s = strchr(full_cmd, '\0'); if (strlen(full_cmd) > 0) { if(*(s-1)!=':'&&*(s-1)!='\\') { *s = '\\'; s++; *s = '\0'; } } if (stricmp(full_cmd,".\\") == 0) full_cmd[0] = '\0'; strcat(full_cmd, cmd_name); _fixpath(full_cmd, temp_cmd); strcpy(full_cmd, temp_cmd); conv_unix_path_to_ms_dos(full_cmd); // check validity for each executable type for (e = 0; e < 3; e++) { s = strchr(cmd_name, '.'); if (s == NULL) // no file type mentioned { s = strchr(full_cmd, '\0'); // save position of the nul terminator strcat(full_cmd, exec_ext[e]); if (findfirst_f(full_cmd, &ff, 0, &ffhandle) == 0) { exec_type = e; break; } *s = '\0'; // restore nul terminator } else { if (stricmp(s, exec_ext[e]) == 0) { if (findfirst_f(full_cmd, &ff, 0, &ffhandle) == 0) { exec_type = e; break; } } } } if (exec_type < 0) // if exec file not found yet... pathvar = strtok(NULL, "; "); // try next path in path list else pathvar = NULL; // command found... } if (exec_type < 0) goto BadCommand; strupr(full_cmd); if (exec_type == 2) // if command is a batch file { if (call || getenv("SHELL_CALL_DEFAULT")) { stack_level++; if (stack_level >= MAX_STACK_LEVEL) goto StackOverflow; } else bat_file_line_number[stack_level] = 0; strcpy(bat_file_path[stack_level], full_cmd); ba = 0; /* keep last entry empty to simplify shifting */ while (ba < MAX_BAT_ARGS - 1 && *cmd_arg != '\0') { strcpy(bat_arg[stack_level][ba], cmd_arg); advance_cmd_arg(); ba++; } } else { unsigned do_auto_loadfix = 0; char el[16]; int alen; FILE * exefile; char *lh_d; if (mouse_en && !mouseopt_extctl) mouse_disable(); #if SYNC_ENV /* the below is disabled because it seems we don't need * to update our copy of env. djgpp creates the env segment * for the child process from the prot-mode environment anyway. * Disabling allows to pass much fewer memory to /E. * But we still need the /E because some programs (msetenv) * may set the env strings on their parent (shell) to make * them permanent. */ put_env(); #else set_env("PATH", getenv("PATH")); #endif _control87(0x033f, 0xffff); #ifdef __DJGPP__ __djgpp_exception_toggle(); #endif set_env_seg(); /* prepend command tail with space */ alen = strlen(cmd_args); if (alen) { alen++; // \0 if (alen >= MAX_CMD_BUFLEN) { alen = MAX_CMD_BUFLEN - 1; cmd_args[alen - 1] = '\0'; } memmove(cmd_args + 1, cmd_args, alen); cmd_args[0] = ' '; } if (!loadfix_initialised && is_HMA_enabled() && (exefile = fopen(full_cmd,"rb"))) { /* from https://github.com/dosemu2/comcom32/issues/59#issuecomment-1179566783 */ unsigned char exebuffer[256] = { 0 }; unsigned is_mz_exe = 0; fread(exebuffer, 1, 256, exefile); if (exebuffer[0] == 'M' && exebuffer[1] == 'Z') is_mz_exe = 1; if (exebuffer[0] == 'Z' && exebuffer[1] == 'M') is_mz_exe = 1; if (is_mz_exe && exebuffer[16] == 128 && exebuffer[17] == 0 && (exebuffer[20] == 16 || exebuffer[20] == 18) && exebuffer[21] == 0) { unsigned headersize = (exebuffer[8] + exebuffer[9] * 256UL) * 16UL; short codesegment = exebuffer[22] + exebuffer[23] * 256UL; unsigned checkoffset = headersize + ((int)codesegment * 16UL); unsigned char entrybuffer[18] = { 0 }; fseek(exefile, checkoffset, SEEK_SET); fread(entrybuffer, 1, 18, exefile); if (entrybuffer[exebuffer[20] - 2UL] == 'R' && entrybuffer[exebuffer[20] - 1UL] == 'B') { do_auto_loadfix = 1; } } if (is_mz_exe && !memcmp(&exebuffer[30], "PKLITE", 6)) do_auto_loadfix = 1; else if (is_mz_exe && !memcmp(&exebuffer[30], "PKlite", 6)) do_auto_loadfix = 1; else if (!is_mz_exe && !memcmp(&exebuffer[46], "PKLITE", 6)) do_auto_loadfix = 1; else if (!is_mz_exe && !memcmp(&exebuffer[48], "PKLITE", 6)) do_auto_loadfix = 1; else if (!is_mz_exe && !memcmp(&exebuffer[46], "PKlite", 6)) do_auto_loadfix = 1; else if (!is_mz_exe && !memcmp(&exebuffer[48], "PKlite", 6)) do_auto_loadfix = 1; else if (!is_mz_exe && !memcmp(&exebuffer[38], "PK Copyr", 8)) do_auto_loadfix = 1; fclose(exefile); exefile = NULL; } if (do_auto_loadfix) loadfix_init(); lh_d = getenv("SHELL_LOADHIGH_DEFAULT"); if ((lh_d && lh_d[0] == '1')) lh++; if (lh_d) unsetenv("SHELL_LOADHIGH_DEFAULT"); if (lh) link_umb(0x80); set_break(break_on); #ifdef HAVE_DOS_EXEC5 rc = _dos_exec5(full_cmd, cmd_args, environ, NULL, lh ? 0x80 : 0); #else rc = _dos_exec(full_cmd, cmd_args, environ, NULL); #endif set_break(0); if (rc == -1) cprintf("Error: unable to execute %s\r\n", full_cmd); else error_level = rc & 0xff; if (lh) unlink_umb(); if (do_auto_loadfix) loadfix_exit(); set_env_sel(); get_env(); #ifdef __DJGPP__ __djgpp_exception_toggle(); #endif _control87(0x033f, 0xffff); _clear87(); _fpreset(); // reset_text_attrs(); gppconio_init(); /* video mode could change */ if (mouse_en && mouseopt_enabled) mouse_enable(); /* re-read history in case it was altered by another comcom instance */ if (shell_mode != SHELL_SINGLE_CMD) cmdbuf_init(); sprintf(el, "%d", error_level); setenv("ERRORLEVEL", el, 1); } return; BadCommand: cprintf("Bad command or file name - %s\r\n", ext_cmd); //reset_batfile_call_stack(); -- even if error becuase the command is not found, don't clean up return; StackOverflow: cputs("Call stack overflow\r\n"); reset_batfile_call_stack(); return; } static void perform_goto(const char *arg) { if (bat_file_path[stack_level][0] != '\0') { strcpy(goto_label, arg); bat_file_line_number[stack_level] = MAXINT; } else cputs("Goto not valid in immediate mode.\r\n"); } static void perform_help(const char *arg) { list_cmds(); } static void perform_if(void) { long ffhandle; int not_flag = false; int condition_fulfilled = false; if (cmd_arg[0] == '\0') goto SyntaxError; if (stricmp(cmd_arg, "not") == 0) { not_flag = true; advance_cmd_arg(); } if (stricmp(cmd_arg, "exist") == 0) // conditional is "exist " { char *s; finddata_t ff; advance_cmd_arg(); s = strrchr(cmd_arg, '\\'); if (s != NULL) { if (stricmp(s,"\\nul") == 0) *s = '\0'; } if (file_access(cmd_arg, F_OK) == 0 || file_access(cmd_arg, D_OK) == 0) condition_fulfilled = true; else if (findfirst_f(cmd_arg, &ff, 0, &ffhandle) == 0) { findclose_f(ffhandle); condition_fulfilled = true; } } else if (strnicmp(cmd_args, "errorlevel", 10) == 0) //conditional is "errolevel x" { char *s; unsigned ecomp; s = cmd_args+10; if (*s != ' ' && *s != '\t' && *s != '=') goto SyntaxError; while (*s == ' ' || *s == '\t' || *s == '=') { *s = 'x'; s++; } if (sscanf(s, "%u", &ecomp) == 1) { if (error_level >= ecomp) { while (*s != ' ' && *s != '\t') { *s = 'x'; s++; } condition_fulfilled = true; } } } else // assume the conditional is in the form " xxxxxxxx == yyyyy " { // op1^ op1end^ ^eq ^op2 ^op2end int len; char *op1, *op1end, *eq, *op2, *op2end; op1 = cmd_args; while (*op1 == ' ' || *op1 == '\t') op1++; if (op1[0] == '\"') { op1end = strchr(op1 + 1, '\"'); if (op1end) op1end++; else goto SyntaxError; } else { op1end = op1; while (*op1end != ' ' && *op1end != '\t' && *op1end != '\0' && *op1end != '=') op1end++; } if (*op1end == '\0') goto SyntaxError; len = op1end - op1; if (len == 0) goto SyntaxError; eq = op1end; while (*eq != '\0' && *eq != '=') eq++; if (*eq != '=') goto SyntaxError; eq++; if (*eq != '=') goto SyntaxError; while (*eq == '=') eq++; op2 = eq; while (*op2 == ' ' || *op2 == '\t') op2++; if (op2[0] == '\"') { op2end = strchr(op2 + 1, '\"'); if (op2end) op2end++; else goto SyntaxError; } else { op2end = op2; while (*op2end != ' ' && *op2end != '\t' && *op2end != '\0') op2end++; } if (op2 == op2end) goto SyntaxError; if (len == (op2end - op2)) { if (strnicmp(op1, op2, len) == 0) condition_fulfilled = true; } while (op1 != op2end) { *op1 = 'x'; op1++; } } advance_cmd_arg(); if ((not_flag && !condition_fulfilled) || (!not_flag && condition_fulfilled)) { strcpy(cmd, cmd_arg); advance_cmd_arg(); } else cmd[0] = '\0'; return; SyntaxError: cputs("Syntax error\r\n"); reset_batfile_call_stack(); cmd_line[0] = '\0'; parse_cmd_line(); // this clears cmd[], cmd_arg[], cmd_switch[], and cmd_args[] return; } static void perform_md(const char *arg) { while (*cmd_switch) // skip switches advance_cmd_arg(); if (*arg) { if (file_access(arg, D_OK) != 0) { if (_mkdir(arg) != 0) { cprintf("Could not create directory - %s\r\n", arg); error_level = 1; } } else { cprintf("Directory already exists - %s\r\n", arg); error_level = 1; } } else { cputs("Required parameter missing"); reset_batfile_call_stack(); } } static void perform_more(const char *arg) { struct text_info txinfo; gettextinfo(&txinfo); int c, cnt = 0; while ((c = getchar()) != EOF) { putchar(c); if (c == '\n') { if (++cnt == txinfo.winbottom - 1) { cnt = 0; printf("--More--"); fgetc(bkp_stdin); } } } } static void perform_mouseopt(const char *arg) { if (arg[0] != '\0') { int opt = 1; if (isdigit(arg[2])) opt = arg[2] - '0'; if (strnicmp(arg, "/C", 2) == 0 && mouseopt_extctl != opt) mouseopt_extctl = opt; if (strnicmp(arg, "/E", 2) == 0 && mouseopt_enabled != opt) { if (mouse_en) { if (opt) { mouse_enable(); mouseopt_enabled = 1; } else { mouseopt_enabled = 0; mouse_disable(); } } } if (stricmp(arg, "/M") == 0 && opt == 1) { if (!mouse_en) mouse_en = mouse_init(); } } else { printf("mouseopt [/M] [/C[0|1]] [/E[1|0]]\n\n"); printf("mouse initialized (/M):\t\t%i\n", mouse_en); printf("mouse enabled (/E):\t\t%i\n", mouseopt_enabled); printf("mouse external control (/C):\t%i\n", mouseopt_extctl); } } static void perform_move(const char *arg) { general_file_transfer(FILE_XFER_MOVE, 0); } static void perform_null_cmd(void) { } static void perform_path(const char *arg) { int off = 0; if (*cmd_args == '\0') { char *pathvar = getenv("PATH"); printf("PATH="); if (pathvar == NULL) puts(""); else puts(pathvar); } else { if (*cmd_args == '=') /* support PATH= syntax */ off++; memmove(cmd_args+5, cmd_args + off, strlen(cmd_args)+1); memcpy(cmd_args, "PATH=", 5); perform_set(cmd_args); } } static void perform_pause(const char *arg) { cputs("Press any key to continue . . .\r\n"); getch(); } static void perform_popd(const char *arg) { if (pushd_stack_level == 0) { printf("pushd stack empty\n"); reset_batfile_call_stack(); return; } if (chdir(pushd_stack[--pushd_stack_level]) != 0) { cprintf("Directory does not exist - %s\r\n",arg); error_level = 1; return; } } static void perform_prompt(const char *arg) { if (!arg[0]) { char *promptvar = getenv("PROMPT"); if (promptvar) printf("%s\n", promptvar); return; } memmove(cmd_args+7, cmd_args, strlen(cmd_args)+1); memcpy(cmd_args, "PROMPT=", 7); perform_set(arg); } static void perform_pushd(const char *arg) { if (pushd_stack_level >= MAX_STACK_LEVEL) { printf("pushd stack overflow\n"); reset_batfile_call_stack(); return; } getcwd(pushd_stack[pushd_stack_level], MAXPATH); if (arg && arg[0]) { if (chdir(arg) != 0) { cprintf("Directory does not exist - %s\r\n",arg); error_level = 1; return; } } pushd_stack_level++; } static void perform_r200fix(const char *arg) { if (!*arg) { cprintf("r200fix: command name missing\r\n"); reset_batfile_call_stack(); return; } strcpy(cmd, arg); advance_cmd_arg(); int0_wa = 1; perform_external_cmd(false, false, cmd); int0_wa = 0; } static void perform_rd(const char *arg) { while (*cmd_switch) // skip switches advance_cmd_arg(); if (*cmd_arg) { if (rmdir(arg) != 0) { cprintf("Could not remove directory - %s\r\n", arg); error_level = 1; } } else { cputs("Required parameter missing"); reset_batfile_call_stack(); } } static void perform_rename(const char *arg) { long ffhandle; int ffrc; int first, zfill; finddata_t ff; unsigned attrib = FA_RDONLY+FA_ARCH+FA_SYSTEM; char from_path[MAXPATH] = "", to_path[MAXPATH] = ""; char from_drive[MAXDRIVE], to_drive[MAXDRIVE]; char from_dir[MAXDIR], to_dir[MAXDIR]; char from_name[MAXFILE], to_name[MAXFILE], new_to_name[MAXFILE]; char from_ext[MAXEXT], to_ext[MAXEXT], new_to_ext[MAXEXT]; char full_from_filespec[MAXPATH], full_to_filespec[MAXPATH]; char *w; while (*arg != '\0') { if (*cmd_switch == '\0') // if not a command switch ... { if (*from_path == '\0') { strncpy(from_path, arg, MAXPATH); from_path[MAXPATH-1] = '\0'; conv_unix_path_to_ms_dos(from_path); } else if (*to_path == '\0') { strncpy(to_path, arg, MAXPATH); to_path[MAXPATH-1] = '\0'; conv_unix_path_to_ms_dos(to_path); } else { cprintf("Too many parameters - %s\r\n", cmd_args); reset_batfile_call_stack(); return; } } else { cprintf("Invalid switch - %s\r\n", cmd_args); reset_batfile_call_stack(); return; } advance_cmd_arg(); } _fixpath(from_path, full_from_filespec); conv_unix_path_to_ms_dos(full_from_filespec); fnsplit(full_from_filespec, from_drive, from_dir, from_name, from_ext); if (has_wildcard(from_dir) || has_trailing_slash(from_path)) { cprintf("Invalid parameter - %s\r\n", from_path); reset_batfile_call_stack(); return; } _fixpath(to_path, full_to_filespec); conv_unix_path_to_ms_dos(full_to_filespec); fnsplit(full_to_filespec, to_drive, to_dir, to_name, to_ext); if (stricmp(from_drive, to_drive) != 0 || stricmp(from_dir, to_dir) != 0 || has_trailing_slash(to_path)) { cprintf("Invalid parameter - %s\r\n", to_path); reset_batfile_call_stack(); return; } first = true; for (;;) { if (first) { if ((ffrc = findfirst_f(full_from_filespec, &ff, attrib, &ffhandle)) != 0) { cprintf("File not found - %s\r\n", from_path); error_level = 1; return; } first = false; } else { if ((ffrc = findnext_f(&ff, ffhandle)) != 0) break; } strcpy(full_from_filespec, from_drive); strcat(full_from_filespec, from_dir); strcat(full_from_filespec, FINDDATA_T_FILENAME(ff)); conv_unix_path_to_ms_dos(full_from_filespec); fnsplit(full_from_filespec, NULL, NULL, from_name, from_ext); for (zfill = strlen(from_name); zfill < MAXFILE; zfill++) from_name[zfill] = '\0'; for (zfill = strlen(from_ext); zfill < MAXEXT; zfill++) from_ext[zfill] = '\0'; strcpy(full_to_filespec, from_drive); strcat(full_to_filespec, from_dir); strcpy(new_to_name, to_name); strcpy(new_to_ext, to_ext); while ((w = strchr(new_to_name, '?')) != NULL) *w = from_name[w-new_to_name]; if ((w = strchr(new_to_name, '*')) != NULL) strcpy(w, &(from_name[w-new_to_name])); while ((w = strchr(new_to_ext, '?')) != NULL) *w = from_ext[w-new_to_ext]; if ((w = strchr(new_to_ext, '*')) != NULL) strcpy(w, &(from_ext[w-new_to_ext])); fnmerge(full_to_filespec, to_drive, to_dir, new_to_name, new_to_ext); conv_unix_path_to_ms_dos(full_to_filespec); if (stricmp(full_from_filespec, full_to_filespec) != 0) { conv_unix_path_to_ms_dos(new_to_name); conv_unix_path_to_ms_dos(new_to_ext); if (rename(full_from_filespec, full_to_filespec) == 0) printf("%s renamed to %s%s\n", full_from_filespec, new_to_name, new_to_ext); else { cprintf("Unable to rename %s to %s%s\n", full_from_filespec, new_to_name, new_to_ext); error_level = 1; return; } } } } static void perform_set(const char *arg) { const char *var_name; char *vname; int err; if (*arg == '\0') { int i = 0; while (environ[i]) printf("%s\n", environ[i++]); } else { char *s; int is_p = 0; if (strnicmp(cmd_switch,"/p", 2) == 0) { is_p++; advance_cmd_arg(); } var_name = cmd_args; if (strlen(var_name) == 0) { cputs("Syntax error\r\n"); reset_batfile_call_stack(); return; } vname = strdup(var_name); s = strchr(vname, '='); if (s) { *s = '\0'; s++; } strupr(vname); if (is_p) { char buf[128]; char *p; cputs(s); p = fgets(buf, sizeof(buf), stdin); if (p) { p = strpbrk(buf, "\r\n"); if (p) *p = '\0'; err = setenv(vname, buf, 1); } else err = -1; } else { if (!s || !*s) err = unsetenv(vname); else err = setenv(vname, s, 1); } free(vname); if (err != 0) { cprintf("Error setting environment variable - %s\r\n", var_name); error_level = 1; return; } } } static void perform_shift(const char *arg) { int i; for (i = 0; i < MAX_BAT_ARGS - 1; i++) { strcpy(bat_arg[stack_level][i], bat_arg[stack_level][i + 1]); /* check _after_ copy to zero out last entry */ if (!bat_arg[stack_level][i + 1][0]) break; } } static void perform_time(const char *arg) { time_t t = time(NULL); struct tm loctime; unsigned int hour, min, sec = 0; int rc, exp_rc; localtime_r(&t, &loctime); if (*arg != '\0') { struct timeval tv; char *p1 = strchr(arg, ':'), *p2 = strrchr(arg, ':'); if (!p1) { cprintf("Invalid time format\r\n"); reset_batfile_call_stack(); return; } if (p1 == p2) { rc = sscanf(arg, "%d:%d", &hour, &min); exp_rc = 2; } else { rc = sscanf(arg, "%d:%d:%d", &hour, &min, &sec); exp_rc = 3; } if (rc != exp_rc || hour > 23 || min > 59 || sec > 59) { cprintf("Invalid time\r\n"); reset_batfile_call_stack(); return; } loctime.tm_hour = hour; loctime.tm_min = min; loctime.tm_sec = sec; tv.tv_sec = mktime(&loctime); tv.tv_usec = 0; settimeofday(&tv, NULL); } else printf("Current time is %d:%02d:%02d\n", loctime.tm_hour, loctime.tm_min, loctime.tm_sec); } static void perform_timeout(const char *arg) { int t = 0; while (*arg != '\0') { if (stricmp(cmd_switch, "/t") == 0) // just ignore { advance_cmd_arg(); continue; } t = atoi(arg); advance_cmd_arg(); } if (t) sleep(t); } static void perform_truename(const char *arg) { char truebuf[FILENAME_MAX]; char buf2[FILENAME_MAX]; char *s = NULL;; finddata_t ff; long ffh; if (arg[0] == '\0') { cputs("Required parameter missing\r\n"); reset_batfile_call_stack(); return; } if (findfirst_f(arg, &ff, 0, &ffh) == 0) { findclose(ffh); s = _truename(FINDDATA_T_FILENAME(ff), truebuf); } if (!s && !strchr(arg, '.')) { const char *elst[] = { "com", "exe", "bat", NULL }; const char **p; for (p = elst; *p; p++) { snprintf(buf2, sizeof(buf2), "%s.%s", arg, *p); if (findfirst_f(buf2, &ff, 0, &ffh) == 0) { findclose(ffh); s = _truename(FINDDATA_T_FILENAME(ff), truebuf); if (s) break; } } } if (!s) s = _truename(arg, truebuf); if (!s) { cprintf("path not found\r\n"); return; } puts(s); } static void perform_type(const char *arg) { FILE *textfile; char filespec[MAXPATH] = ""; int c; while (*arg != '\0') { if (*cmd_switch == '\0') // if not a command switch ... { if (*filespec == '\0') { strncpy(filespec, arg, MAXPATH); filespec[MAXPATH-1] = '\0'; conv_unix_path_to_ms_dos(filespec); } else { cprintf("Too many parameters - %s\r\n", cmd_args); reset_batfile_call_stack(); return; } } else { } advance_cmd_arg(); } /* HACK: open in text mode for dos, but then set binary mode for djgpp. * djgpp otherwise doesn't pass 0x1a to us (at least from device). */ textfile = fopen(filespec,"rt"); setbuf(textfile, NULL); __file_handle_set(fileno(textfile), O_BINARY); if (textfile != NULL) { while (1) { c = fgetc(textfile); if (c == EOF || c == 0x1a || c == 3 || c == 0) break; putchar(c); } fclose(textfile); } else { cprintf("Unable to open file - %s\r\n", filespec); error_level = 1; } } static int is_blank(const char cc) { if (cc == 32 || cc == 9 || cc == 13 || cc == 10) { return 1; } else { return 0; } } static void perform_ver(const char *arg) { int is_r = 0; while (*arg != '\0') { if (*cmd_switch == '\0') // if not a command switch ... { cprintf("Invalid parameter - %s\r\n", cmd_args); reset_batfile_call_stack(); return; } else { if (stricmp(cmd_switch,"/r")==0) { is_r = 1; } else { cprintf("Invalid switch - %s\r\n", cmd_switch); reset_batfile_call_stack(); return; } } advance_cmd_arg(); } printf("comcom" #ifdef DJ64 "64" #else "32" #endif " v%s, %.16s\n", version, _stubinfo->magic); #ifdef DJ64 printf(" stub version %i, loader version %i\n", _stubinfo->stubinfo_ver >> 16, _stubinfo->stubinfo_ver & 0xffff); #endif if (REV_ID[0]) printf(" Source ID: %s\n", REV_ID); if (is_r) { const int buffersize = 256; int ver_major, ver_minor, true_ver_major, true_ver_minor, oem_id; unsigned ver_string, ii; char *pc, *buffer = malloc(buffersize); __dpmi_regs r = {}; r.x.ax = 0x3000; __dpmi_int(0x21, &r); ver_major = r.h.al; ver_minor = r.h.ah; oem_id = r.h.bh; printf("\nReported DOS version (Int21.3000): %u.%02u OEM: %02Xh\n", ver_major, ver_minor, oem_id); r.x.ax = 0x3306; r.x.bx = 0; __dpmi_int(0x21, &r); if (! r.x.bx) { printf("Reported true DOS version (Int21.3306): (none)\n"); } else { true_ver_major = r.h.bl; true_ver_minor = r.h.bh; printf("Reported true DOS version (Int21.3306): %u.%02u\n", true_ver_major, true_ver_minor); } r.x.ax = 0x33FF; r.x.dx = 0; __dpmi_int(0x21, &r); if (! r.x.dx) { printf("Version string (Int21.33FF): (none)\n"); } else { if (! buffer) { printf("Version string (Int21.33FF): (buffer allocation failure)\n"); } else { ver_string = (r.x.dx << 4) + r.x.ax; dosmemget(ver_string, buffersize - 1, buffer); buffer[buffersize - 1] = 0; pc = buffer; while (is_blank(*pc)) { ++pc; } ii = strlen(pc); while (ii > 1 && is_blank(pc[ii - 1])) { --ii; } pc[ii] = 0; printf("Version string (Int21.33FF): %s\n", pc); } } free(buffer); } } static void cl_write(const char *buf, int len) { if (len && buf[len - 1] == '\0') len--; if (len) write(STDOUT_FILENO, buf, len); } static void perform_clip(const char *arg) { int rc = clip_read(7, cl_write); if (rc == -1) { cprintf("clipboard read failed\r\n"); return; } } static void perform_cls(const char *arg) { clrscr(); } #if 0 static void perform_unimplemented_cmd(void) { cputs("Command not implemented\r\n"); reset_batfile_call_stack(); } #endif ////////////////////////////////////////////////////////////////////////////////// static void list_cmds(void) { int i, j; printf("\tAvailable commands:\n"); for (i = 0; i < CMD_TABLE_COUNT; i++) { int num = printf("%s%s - %s", cmd_table[i].cmd_name, cmd_table[i].opts, cmd_table[i].help); if (!(i & 1)) { for (j = num; j < 40; j++) printf(" "); } else printf("\n"); } printf("\n"); } static bool is_valid_DOS_char(int c) { unsigned char u=(unsigned char)c; /* convert to ascii */ if (!u) return false; if (u >= 128 || isalnum(u)) return true; /* now we add some extra special chars */ if(strchr("_^$~!#%&-{}()@'`",c)!=0) return true; /* general for any codepage */ /* no match is found, then */ return false; } static void parse_cmd_line(void) { int c, cmd_len, *pipe_count_addr; char *extr, *dest, *saved_extr, *delim; char new_cmd_line[MAX_CMD_BUFLEN], *end; const char *v; int quoting; // substitute in variable values before parsing extr = strchr(cmd_line, '%'); if (extr != NULL) { dest = new_cmd_line; extr = cmd_line; v = NULL; while ((v != NULL || *extr != '\0') && dest < new_cmd_line+MAX_CMD_BUFLEN-1) { if (v == NULL) { if (*extr == '%') { extr++; if (*extr == '0') // '%0' { extr++; v = bat_file_path[stack_level]; continue; } if (*extr >= '1' && *extr <= '9') // '%1' to '%9' { v = bat_arg[stack_level][(*extr)-'1']; extr++; continue; } end = strchr(extr, '%'); // find ending '%' delim = strchr(extr, ' '); if (end == NULL || (delim && end > delim)) // if '%' found, but no ending '%' ... { if (*extr && *extr == for_var) { v = for_val; extr++; continue; } else { *dest = '%'; dest++; continue; } } else // else ending '%' is found too { if (extr == end) // if "%%", replace with single '%' { extr++; *dest = '%'; dest++; continue; } else { *end = '\0'; strupr(extr); v = getenv(extr); extr = end + 1; } } } else { *dest = *extr; dest++; extr++; } } else { if (*v != '\0') { *dest = *v; dest++; v++; } if (*v == '\0') v = NULL; } } *dest = '\0'; strcpy(cmd_line, new_cmd_line); } // extract pipe specs.... pipe_file[STDIN_INDEX][0] = '\0'; // < pipe_file_redir_count[STDIN_INDEX] = 0; // count of '<' characters pipe_file[STDOUT_INDEX][0] = '\0'; // > or >> pipe_file_redir_count[STDOUT_INDEX] = 0; // count of '>' characters pipe_to_cmd[0] = '\0'; // | pipe_to_cmd_redir_count = 0; // count of '|' characters quoting = 0; extr = cmd_line; while (*extr != '\0') { c = *extr; switch (*extr) { case '\"': quoting ^= 1; c = 0; break; case '<': if (quoting) goto quot; dest = pipe_file[STDIN_INDEX]; pipe_count_addr = &(pipe_file_redir_count[STDIN_INDEX]); break; case '>': if (quoting) goto quot; dest = pipe_file[STDOUT_INDEX]; pipe_count_addr = &(pipe_file_redir_count[STDOUT_INDEX]); break; case '|': if (quoting) goto quot; dest = pipe_to_cmd; pipe_count_addr = &pipe_to_cmd_redir_count; break; default: quot: c = 0; break; } if (c == 0) extr++; else { // count redirection characters saved_extr = extr; while (*extr == c) { (*pipe_count_addr)++; extr++; } // skip over spaces while (*extr == ' ' || *extr == '\t') extr++; // extract pipe destinations if (c == '|') // "pipe to" command { while (*extr != '\0') { *dest = *extr; dest++; extr++; } } else // pipe in or out file { while (*extr != ' ' && *extr != '\t' && *extr != '\0') { *dest = *extr; dest++; extr++; } } *dest = '\0'; // snip out pipe spec from the cmd_line[] string memmove(saved_extr, extr, strlen(extr)+1); extr = saved_extr; } } conv_unix_path_to_ms_dos(pipe_file[STDIN_INDEX]); conv_unix_path_to_ms_dos(pipe_file[STDOUT_INDEX]); // done with variables and pipes -- now, skip leading spaces extr = cmd_line; while (*extr == ' ' || *extr == '\t') extr++; if (*extr == '\0') { cmd[0] = '\0'; cmd_arg[0] = '\0'; cmd_switch[0] = '\0'; cmd_args[0] = '\0'; return; } // extract built-in command if command line contains one for (c = 0; c < CMD_TABLE_COUNT; c++) { cmd_len = strlen(cmd_table[c].cmd_name); if (strnicmp(extr, cmd_table[c].cmd_name, cmd_len) == 0) { delim = extr+cmd_len; if (!is_valid_DOS_char(delim[0]) || !is_valid_DOS_char(delim[-1])) { // ok, we have a built-in command; extract it strcpy(cmd, cmd_table[c].cmd_name); extr = delim; break; } } } // if not built-in command, extract as an external command if (c >= CMD_TABLE_COUNT) { dest = cmd; while (*extr != ' ' && *extr != '\t' && *extr != '/' && *extr != '\0') { *dest = *extr; dest++; extr++; } *dest = '\0'; } // extract the rest as arguments cmd_args[0] = '\0'; extract_args(extr); return; } static void exec_cmd(int call) { int c; int pipe_index, pipe_fno[2], old_std_fno[2], redir_result[2]; for (pipe_index = 0; pipe_index < 2; pipe_index++) { pipe_fno[pipe_index] = -1; old_std_fno[pipe_index] = -1; redir_result[pipe_index] = -1; } if (pipe_to_cmd_redir_count > 0) { pipe(pipe_fno); /* cputs("Piping between 2 commands is not supported\r\n"); reset_batfile_call_stack(); goto Exit; */ } else // open the pipe file { if (pipe_file_redir_count[STDIN_INDEX] > 0) pipe_fno[STDIN_INDEX] = open(pipe_file[STDIN_INDEX], O_TEXT|O_RDONLY, S_IRUSR); if (pipe_file_redir_count[STDOUT_INDEX] > 1) pipe_fno[STDOUT_INDEX] = open(pipe_file[STDOUT_INDEX], O_BINARY|O_WRONLY|O_APPEND|O_CREAT, S_IRUSR | S_IWUSR); // open for append else if (pipe_file_redir_count[STDOUT_INDEX] == 1) pipe_fno[STDOUT_INDEX] = open(pipe_file[STDOUT_INDEX], O_BINARY|O_WRONLY|O_TRUNC|O_CREAT, S_IRUSR | S_IWUSR); // open as new file /* check for error if (pipe_fno[pipe_index] < 0 || old_std_fno[pipe_index] == -1 || redir_result[pipe_index] == -1) { if (pipe_index == pipe_index) cprintf("Unable to pipe standard input from file - %s\r\n", pipe_file[pipe_index]); else cprintf("Unable to pipe standard output to file - %s\r\n", pipe_file[pipe_index]); reset_batfile_call_stack(); goto Exit; } */ } for (pipe_index = 0; pipe_index < 2; pipe_index++) { // save a reference to the old standard in/out file handle if (pipe_fno[pipe_index] >= 0) old_std_fno[pipe_index] = dup(pipe_index); // redirect pipe file to standard in/out if (pipe_fno[pipe_index] >= 0 && old_std_fno[pipe_index] != -1) redir_result[pipe_index] = dup2(pipe_fno[pipe_index], pipe_index); // close pipe file handle if (pipe_fno[pipe_index] >= 0) { close(pipe_fno[pipe_index]); pipe_fno[pipe_index] = -1; } } if (old_std_fno[STDIN_INDEX] >= 0) bkp_stdin = fdopen(old_std_fno[STDIN_INDEX], "r"); while (cmd[0] != '\0') { if (stricmp(cmd, "if") == 0) { perform_if(); continue; } else if (strnicmp(cmd, "rem", 3) == 0) // rem statement perform_null_cmd(); else if (cmd[0] == ':') // goto label perform_null_cmd(); else if (is_drive_spec(cmd) || is_drive_spec_with_slash(cmd)) // drive letter perform_change_drive(); else { if (!call && installable_command_check(cmd, cmd_args) == 0) { cmd[0] = '\0'; break; } for (c = 0; c < CMD_TABLE_COUNT; c++) { if (stricmp(cmd, cmd_table[c].cmd_name) == 0) { cmd_table[c].cmd_fn(cmd_arg); break; } } if (c >= CMD_TABLE_COUNT) { need_to_crlf_at_next_prompt = true; perform_external_cmd(call, false, cmd); } } cmd[0] = '\0'; } /* Recover the stdout stream */ if (redir_result[STDOUT_INDEX] != -1) { dup2(old_std_fno[STDOUT_INDEX], STDOUT_INDEX); close(old_std_fno[STDOUT_INDEX]); clearerr(stdout); } if (pipe_to_cmd_redir_count > 0) { strcpy(cmd_line, pipe_to_cmd); parse_cmd_line(); exec_cmd(true); } /* Exit: */ cmd_line[0] = '\0'; if (redir_result[STDIN_INDEX] != -1) { dup2(old_std_fno[STDIN_INDEX], STDIN_INDEX); fclose(bkp_stdin); // closes also fd clearerr(stdin); } } int do_int23(void) { return break_enabled; } static void set_break(int on) { __dpmi_regs r = {}; r.x.ax = 0x3301; // set break handling r.x.dx = on; // to "on" __dpmi_int(0x21, &r); break_enabled = on; } static void setup_break_handling(void) { __dpmi_paddr pa; __djgpp_set_ctrl_c(0); // disable SIGINT on ^C _go32_want_ctrl_break(1); // disable SIGINT on ^Break set_break(0); pa.selector = _my_cs(); pa.offset32 = (uintptr_t)my_int23_handler; __dpmi_set_protected_mode_interrupt_vector(0x23, &pa); break_on = true; } static int break_pressed(void) { int ret = _farpeekb(_dos_ds, 0x471); _farpokeb(_dos_ds, 0x471, ret & ~0x80); return (ret & 0x80); } static void setup_int0_handling(void) { __dpmi_paddr pa; __dpmi_get_real_mode_interrupt_vector(0, &int0_vec); __dpmi_get_extended_exception_handler_vector_rm(0, &pa); _prev0_eip = pa.offset32; _prev0_cs = pa.selector; pa.selector = _my_cs(); pa.offset32 = (uintptr_t)my_int0_handler; __dpmi_set_extended_exception_handler_vector_rm(0, &pa); } void do_int0(void) { if (int0_wa) __dpmi_set_real_mode_interrupt_vector(0, &int0_vec); } int main(int argc, const char *argv[], const char *envp[]) { int a; char *cmd_path, *v; int disable_autoexec = 0; int inited = 0; // initialize the cmd data ... // reset fpu _clear87(); _fpreset(); _ds = _my_ds(); loadhigh_init(); // save initial umb link and strat unlink_umb(); // in case we loaded with shellhigh or lh set_env_size(); // reset_text_attrs(); #ifdef __spawn_leak_workaround __spawn_flags &= ~__spawn_leak_workaround; #endif if (_osmajor < 7) _osmajor = 7; // fake _osmajor to enable extended functionality setup_break_handling(); setup_int0_handling(); // unbuffer stdin and stdout setbuf(stdin, NULL); setbuf(stdout, NULL); // init bat file stack reset_batfile_call_stack(); cmd_path = strdup(argv[0]); strupr(cmd_path); conv_unix_path_to_ms_dos(cmd_path); setenv("COMSPEC", cmd_path, 1); free(cmd_path); setenv("COMCOM_VER", version, 1); setenv("ERRORLEVEL", "0", 1); setenv("TERM", "djgpp", 0); // process arguments for (a = 1; a < argc; a++) { // check for permanent shell if (stricmp(argv[a], "/P") == 0) { shell_permanent = 1; } if (strnicmp(argv[a], "/E:", 3) == 0) { unsigned new_size, old_size = get_env_size(); if (argv[a][3] == '+') new_size = old_size + atoi(argv[a] + 4); else new_size = atoi(argv[a] + 3); new_size &= ~0xf; if (new_size > old_size) { int err = realloc_env(new_size); if (err) return EXIT_FAILURE; } } if (stricmp(argv[a], "/D") == 0) { disable_autoexec = 1; } if (stricmp(argv[a], "/Y") == 0) { stepping = 1; } if (strnicmp(argv[a], "/M", 2) == 0) { int opt = 1; char copt[2]; if (isdigit(argv[a][2])) opt = argv[a][2] - '0'; copt[0] = opt + '0'; copt[1] = '\0'; unsetenv("COMCOM_MOUSE"); switch (opt) { case 0: break; default: mouse_en = mouse_init(); if (mouse_en) { mouseopt_enabled = 1; mouseopt_extctl = (opt == 2); setenv("COMCOM_MOUSE", copt, 1); } break; } } // check for command in arguments if (stricmp(argv[a], "/K") == 0) { shell_mode = SHELL_STARTUP_WITH_CMD; a++; strncat(cmd_line, argv[a], MAX_CMD_BUFLEN-1); parse_cmd_line(); } if (stricmp(argv[a], "/C") == 0) { int cmd_buf_remaining; shell_mode = SHELL_SINGLE_CMD; // build command from rest of the arguments a++; cmd_buf_remaining = MAX_CMD_BUFLEN-1; while (a < argc) { strncat(cmd_line, argv[a], cmd_buf_remaining); cmd_buf_remaining -= strlen(argv[a]); if (cmd_buf_remaining < 0) cmd_buf_remaining = 0; strncat(cmd_line, " ", cmd_buf_remaining); cmd_buf_remaining--; if (cmd_buf_remaining < 0) cmd_buf_remaining = 0; a++; } parse_cmd_line(); } } if (shell_permanent) { set_psp_parent(); #if !SYNC_ENV /* some progs (Word Perfect) look for COMSPEC in parent env, rather * than their own. We need to sync it down to DOS. */ sync_env(); #endif } if (!mouse_en && (v = getenv("COMCOM_MOUSE")) && v[0] >= '1') { mouse_en = mouse_init(); if (mouse_en) { mouseopt_enabled = 1; if (v[0] == '2') mouseopt_extctl = 1; } } if (shell_permanent && !disable_autoexec) { unsigned int drive; strcpy(bat_file_path[0], "X:\\AUTOEXEC.BAT"); // trigger execution of autoexec.bat getdrive(&drive); drive += ('A' - 1); bat_file_path[0][0] = drive; // no arguments for batch file } // Main command parsing/interpretation/execution loop while (!exiting) { if (cmd_line[0] == '\0') { if (bat_file_path[stack_level][0] == '\0') { if (shell_mode == SHELL_SINGLE_CMD) { perform_exit(NULL); continue; } if (!inited) { inited++; /* this depends on TEMP var being set, so is done that late */ cmdbuf_init(); } set_env_seg(); prompt_for_and_get_cmd(); set_env_sel(); } else { if (break_on && break_pressed()) reset_batfile_call_stack(); else get_cmd_from_bat_file(); } } exec_cmd(false); } loadhigh_done(); if (mouse_en) mouse_done(); if (shell_permanent) restore_psp_parent(); return error_level; }