=================================================================== RCS file: /cvs/djgpp/djgpp/src/libc/dos/process/dosexec.c,v retrieving revision 1.1 retrieving revision 1.2 diff -p -u -r1.1 -r1.2 --- djgpp/src/libc/dos/process/dosexec.c 1995/11/30 06:58:08 1.1 +++ /cvs/djgpp/djgpp/src/libc/dos/process/dosexec.c 1996/10/05 20:14:04 1.2 @@ -1,18 +1,27 @@ +/* Copyright (C) 1996 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details */ #include #include #include #include #include +#include +#include #include #include #include #include #include #include +#include #include #include #include +#include + +/* FIXME: this is not LFN-clean. Win95 has a way to + pass long command lines, but we don't support it here. */ +#define CMDLEN_LIMIT 125 extern char **environ; @@ -30,80 +39,282 @@ typedef struct { static Execp parm; -static unsigned long tbuf; +static unsigned long tbuf_ptr; +static unsigned long tbuf_beg; +static unsigned long tbuf_end; +static unsigned long tbuf_len; +#if 0 +static int tbuf_selector; +#endif + +static int script_exec(const char *, char **, char **); +/* Allocate AMT bytes off the transfer buffer. */ static unsigned long talloc(size_t amt) { - unsigned long rv = tbuf; - tbuf += amt; + unsigned long rv = tbuf_ptr; + tbuf_ptr += amt; return rv; } -static int direct_exec_tail(const char *program, const char *args, char * const envp[]) +/* Make sure we can allocate AMT bytes off the transfer buffer + without overflowing it. Return non-zero if we can, zero otherwise. + + WARNING: This can relocate the data already in the transfer buffer, + so all linear addresses which use it should be relative to + TBUF_BEG! */ +static int check_talloc(size_t amt) +{ + int retval = 1; + + if (tbuf_ptr + amt > tbuf_end) + { +#if 0 + /* Code that reallocs the transfer buffer; currently disabled. */ + unsigned long new_tb; + unsigned long min_len = tbuf_len + amt; + unsigned long max_len = 0x10000; /* 64KB */ + int old_selector = tbuf_selector; + int max_avail; + int e = errno; + + errno = E2BIG; + + /* Try to allocate new, larger DOS buffer, upto 64KB. */ + if (min_len > max_len) + { + retval = 0; + goto done; + } + while (tbuf_len <= max_len && tbuf_len < min_len) + tbuf_len *= 2; + if (tbuf_len < min_len) + { + retval = 0; + goto done; + } + + tbuf_len = (tbuf_len + 15) & 0xffff0; /* round to nearest paragraph */ + + if ((new_tb = + __dpmi_allocate_dos_memory(tbuf_len/16, &max_avail)) == -1) + { + if (max_avail*16 < min_len + || (new_tb = + __dpmi_allocate_dos_memory(max_avail, &tbuf_selector)) == -1) + { + retval = 0; + goto done; + } + tbuf_len = max_avail*16; + } + else + tbuf_selector = max_avail; + + new_tb *= 16; /* convert to linear address */ + movedata (_dos_ds, tbuf_beg, _dos_ds, new_tb, tbuf_ptr - tbuf_beg); + tbuf_ptr = new_tb + tbuf_ptr - tbuf_beg; + tbuf_beg = new_tb; + tbuf_end = tbuf_beg + tbuf_len - 1; + + errno = e; + + done: + /* Assume caller will return immediately in case of + failure to reallocate, so they won't need the old data. */ + if (!retval) + tbuf_selector = 0; + if (old_selector) + __dpmi_free_dos_memory(old_selector); +#else + errno = E2BIG; + retval = 0; +#endif + } + + return retval; +} + +extern char __PROXY[]; /* defined on crt0/crt1.c */ +extern size_t __PROXY_LEN; + +/* Functions that call `direct_exec_tail' after they've put + some data into the transfer buffer, should set LFN parameter + to either 0 (no LFN support) or 1 (LFN supported), but NOT 2! + if LFN is 2, there is a possiblity that the contents of the + transfer buffer will be overrun! */ +static int +direct_exec_tail(const char *program, const char *args, + char * const envp[], const char *proxy, int lfn) { __dpmi_regs r; unsigned long program_la; unsigned long arg_la; unsigned long parm_la; unsigned long env_la, env_e_la; + size_t proxy_len = proxy ? strlen(proxy)+1 : 0; + int seen_proxy = 0; char arg_header[3]; char short_name[FILENAME_MAX]; const char *progname; unsigned proglen; int i; + unsigned long fcb1_la, fcb2_la, fname_la; sync(); + if (lfn == 2) /* don't know yet */ + lfn = _USE_LFN; + + /* The pathname of the executable to run. */ proglen = strlen(program)+1; - if(_USE_LFN) { - dosmemput(program, proglen, tbuf); + if (!check_talloc(proglen)) + return -1; + if(lfn) { + dosmemput(program, proglen, tbuf_ptr); r.x.ax = 0x7160; /* Truename */ r.x.cx = 1; /* Get short name */ - r.x.ds = r.x.es = tbuf / 16; - r.x.si = r.x.di = tbuf & 15; + r.x.ds = r.x.es = tbuf_ptr / 16; + r.x.si = r.x.di = tbuf_ptr & 15; __dpmi_int(0x21, &r); if (r.x.flags & 1) { errno = __doserr_to_errno(r.x.ax); return -1; } - dosmemget(tbuf, FILENAME_MAX, short_name); + dosmemget(tbuf_ptr, FILENAME_MAX, short_name); progname = short_name; proglen = strlen(short_name)+1; } else progname = program; - + + if (!check_talloc(proglen + strlen(args) + 3 + sizeof(Execp) + 48)) + return -1; program_la = talloc(proglen); - arg_la = talloc(strlen(args)+3); - parm_la = talloc(sizeof(Execp)); + arg_la = talloc(strlen(args)+3); + parm_la = talloc(sizeof(Execp)); dosmemput(progname, proglen, program_la); - + + /* The command-line tail. */ arg_header[0] = strlen(args); arg_header[1] = '\r'; dosmemput(arg_header, 1, arg_la); dosmemput(args, strlen(args), arg_la+1); dosmemput(arg_header+1, 1, arg_la+1+strlen(args)); + /* The 2 FCBs. Some programs (like XCOPY from DOS 6.x) need them. */ + fcb1_la = talloc(16); /* allocate space for 1st FCB */ + fname_la = arg_la + 1; /* first character of command tail */ + r.x.ax = 0x2901; /* AL = 1 means skip leading separators */ + r.x.ds = fname_la / 16; /* pointer to 1st cmd argument */ + r.x.si = fname_la & 15; + r.x.es = fcb1_la / 16; /* pointer to FCB buffer */ + r.x.di = fcb1_la & 15; + __dpmi_int (0x21, &r); + + /* We cannot be sure that Int 21h/AX=2901h parsed the entire + first command-line argument (it might not be a filename + at all!). We need to get to the next command-line arg + before calling 2901 again. 2901 returns the pointer to + first unparsed character in DS:SI. + + Note that, in case there is no second command-line argument, + the following loop is terminated by the trailing CR which + ends the command-line tail. */ + for (_farsetsel(_dos_ds), fname_la = ((unsigned)r.x.ds) * 16 + r.x.si; + !isspace(_farnspeekb(fname_la)); + fname_la++) + ; + + fcb2_la = talloc(16); + r.x.ax = 0x2901; + r.x.ds = fname_la / 16; /* begin parsing 2nd arg from here */ + r.x.si = fname_la & 15; + r.x.es = fcb2_la / 16; + r.x.di = fcb2_la & 15; + __dpmi_int (0x21, &r); + + /* The environment must be on a segment boundary, so get + to the first location in the transfer buffer whose + linear address is divisable by 16. */ do { env_la = talloc(1); } while (env_la & 15); talloc(-1); + +#if 0 + /* Convert to relative, since `check_talloc' may relocate. */ + arg_la -= tbuf_beg; + env_la -= tbuf_beg; + fcb1_la -= tbuf_beg; + fcb2_la -= tbuf_beg; + parm_la -= tbuf_beg; + program_la -= tbuf_beg; +#endif + + /* The environment. Replace the !proxy variable, if there is + one (for nested programs) if we are called from `system', + or skip it, if we are called from `spawnXX'. */ for (i=0; envp[i]; i++) { - env_e_la = talloc(strlen(envp[i])+1); - dosmemput(envp[i], strlen(envp[i])+1, env_e_la); + const char *ep = envp[i]; + size_t env_len = strlen(ep)+1; + + if (strncmp(ep, __PROXY, __PROXY_LEN) == 0 && ep[__PROXY_LEN] == '=') + { + seen_proxy = 1; + if (proxy) + { + ep = proxy; + env_len = proxy_len; + } + else + continue; + } + if (!check_talloc(env_len)) + return -1; + env_e_la = talloc(env_len); + dosmemput(ep, env_len, env_e_la); + } + + /* If no !proxy variable was found, create one. */ + if (proxy && !seen_proxy) + { + if (!check_talloc(proxy_len)) + return -1; + env_e_la = talloc(proxy_len); + dosmemput(proxy, proxy_len, env_e_la); } + + /* Terminate by an extra NULL char. */ arg_header[0] = 0; - arg_header[1] = 1; + + /* The name of the program that owns the environment. */ + arg_header[1] = 1; /* the number of strings (1, little-endian) */ arg_header[2] = 0; + if (!check_talloc(3 + proglen)) + return -1; dosmemput(arg_header, 3, talloc(3)); env_e_la = talloc(proglen); dosmemput(progname, proglen, env_e_la); - parm.eseg = env_la / 16; - parm.argseg = arg_la / 16; - parm.argoff = arg_la & 15; + /* Prepare the parameter block and call Int 21h/AX=4B00h. */ +#if 0 + arg_la += tbuf_beg; + env_la += tbuf_beg; + fcb1_la += tbuf_beg; + fcb2_la += tbuf_beg; + parm_la += tbuf_beg; + program_la += tbuf_beg; +#endif + parm.eseg = env_la / 16; + parm.argseg = arg_la / 16; + parm.argoff = arg_la & 15; + parm.fcb1_seg = fcb1_la / 16; + parm.fcb1_off = fcb1_la & 15; + parm.fcb2_seg = fcb2_la / 16; + parm.fcb2_off = fcb2_la & 15; dosmemput(&parm, sizeof(parm), parm_la); r.x.ax = 0x4b00; @@ -112,6 +323,11 @@ static int direct_exec_tail(const char * r.x.es = parm_la / 16; r.x.bx = parm_la & 15; __dpmi_int(0x21, &r); +#if 0 + if (tbuf_selector) + __dpmi_free_dos_memory (tbuf_selector); + tbuf_selector = 0; +#endif if (r.x.flags & 1) { errno = __doserr_to_errno(r.x.ax); @@ -126,46 +342,161 @@ static int direct_exec_tail(const char * errno = __doserr_to_errno(r.x.ax); return -1; } - return r.x.ax; + + /* AH holds the ``system exit code'' which is non-zero if the + child was aborted by Ctrl-C, or Critical Device error (also + if the child installs itself as a TSR). */ + if (r.h.ah && r.h.ah != 3) /* 3 means it exited as TSR (is it ``normal''?) */ + { + errno = EINTR; /* what else can we put in `errno'? */ + return ( ((r.h.ah == 1 ? SIGINT : SIGABRT) << 8) | r.h.al ); + } + return r.h.al; /* AL holds the child exit code */ } int _dos_exec(const char *program, const char *args, char * const envp[]) { - tbuf = __tb; - return direct_exec_tail(program, args, envp); + tbuf_beg = tbuf_ptr = __tb; + tbuf_len = _go32_info_block.size_of_transfer_buffer; + tbuf_end = tbuf_beg + tbuf_len - 1; + return direct_exec_tail(program, args, envp, 0, 2); +} + +static char GO32_V2_STRING[] = "go32-v2.exe"; +static char GO32_STRING[] = "go32.exe"; + +/* A list of known shells which require we DON'T quote command + lines that are passed to them with the /c or -c switch. */ +static const char *shell_brokets[] = { + "COMMAND.COM", + "4DOS.COM", + "NDOS.COM", + 0 +}; + +/* A list of Unix-like shells and other non-DJGPP programs + which treat single quote specially. */ +static const char *unix_shells[] = { + "SH.EXE", + "SH16.EXE", + "SH32.EXE", + "TCSH.EXE", + "BASH.EXE", + 0 +}; + +static int +list_member (const char *program, const char *program_list[]) +{ + const char *p = program, *ptail = program; + int i; + + while (*p) + { + if (*p == '/' || *p == ':' || *p == '\\') + ptail = p + 1; + p++; + } + + for (i = 0; program_list[i]; i++) + if (!stricmp (ptail, program_list[i])) + return 1; + + return 0; +} + +int +_is_unixy_shell (const char *shellpath) +{ + return list_member (shellpath, unix_shells); } -static char GO32_STRING[] = "go32.exe"; +int +_is_dos_shell (const char *shellpath) +{ + return list_member (shellpath, shell_brokets); +} static int direct_exec(const char *program, char **argv, char **envp) { int i, arglen; char *args, *argp; + int need_quote = !__dosexec_in_system; + int unescape_quote = __dosexec_in_system; - tbuf = __tb; + /* PROGRAM can be a shell which expects a single argument + (beyond the /c or -c switch) that is the entire command + line. With some shells, we must NOT quote that command + line, because that will confuse the shell. + + The hard problem is to know when PROGRAM names a shell + that doesn't like its command line quoted... */ + + if (need_quote + && argv[1] && !strcmp (argv[1], "/c") + && argv[2] && !argv[3] + && _is_dos_shell (program)) + need_quote = 0; + + if (unescape_quote && _is_unixy_shell (program)) + unescape_quote = 0; arglen = 0; for (i=1; argv[i]; i++) - arglen += strlen(argv[i]) + 1; + arglen += 2*strlen(argv[i]) + 1 + 2; + args = (char *)alloca(arglen+1); argp = args; for (i=1; argv[i]; i++) { + int quoted = 0; const char *p = argv[i]; - if (argp - args > 125) + + if (argp - args > CMDLEN_LIMIT) break; *argp++ = ' '; + /* If invoked by `spawnXX' or `execXX' functions, we need to + quote arguments which include whitespace, so they end up + as a single argument on the child side. + We will invoke PROGRAM directly by DOS Exec function (not + through COMMAND.COM), therefore no need to quote characters + special only to COMMAND.COM. + We also assume that DJGPP programs aren't invoked through + here, so a single quote `\'' is also not special. The only + programs other than DJGPP that treat a single quote specially + are Unix-like shells, but whoever uses them should know to + escape the quotes himself. */ + if (need_quote && strpbrk(p, " \t") != 0) + { + *argp++ = '"'; + quoted = 1; + } while (*p) { - if (argp - args > 125) + if (argp - args > CMDLEN_LIMIT) break; + if (*p == '"' && (quoted || need_quote)) + *argp++ = '\\'; + /* Most non-DJGPP programs don't treat `\'' specially, + but our `system' requires we always escape it, so + we should undo the quoting here. */ + else if (*p == '\\' && p[1] == '\'' && unescape_quote) + p++; *argp++ = *p++; } + if (quoted && argp - args <= CMDLEN_LIMIT) + *argp++ = '"'; } *argp = 0; + + if (argp - args > CMDLEN_LIMIT) + errno = E2BIG; - return direct_exec_tail(program, args, envp); + tbuf_beg = tbuf_ptr = __tb; + tbuf_len = _go32_info_block.size_of_transfer_buffer; + tbuf_end = tbuf_beg + tbuf_len - 1; + return direct_exec_tail(program, args, envp, 0, 2); } typedef struct { @@ -178,40 +509,58 @@ typedef struct { static int go32_exec(const char *program, char **argv, char **envp) { char *save_argv0; - int is_stubbed = 0; + int is_stubbed = 0, is_coff = 0; int found_si = 0; StubInfo si; unsigned short header[3]; int pf, i; char *go32, *sip=0; - char rpath[80]; - int stub_offset, argc; + char rpath[FILENAME_MAX]; + int stub_offset, argc=0; int v2_0 = 0; - int si_la=0, rm_la, rm_seg; - short *rm_argv; - char cmdline[34]; - - if (__dosexec_in_system) - return direct_exec(program, argv, envp); + int si_la=0, si_off=0, rm_off, argv_off; + char cmdline[CMDLEN_LIMIT+2], *cmdp = cmdline; + char *pcmd = cmdline, *pproxy = 0, *proxy_cmdline = 0; + int retval; + int lfn = 2; /* means don't know yet */ pf = open(program, O_RDONLY|O_BINARY); read(pf, header, sizeof(header)); if (header[0] == 0x010b || header[0] == 0x014c) { - is_stubbed = 1; + unsigned char firstbytes[1]; + unsigned long coffhdr[40]; + + /* Seems to be an unstubbed COFF. See what the first opcode + is to determine if it's v1.x or v2 COFF (or an impostor). + + FIXME: the code here assumes that any COFF that's not a V1 + can only be V2. What about other compilers that use COFF? */ + is_coff = 1; + if (lseek(pf, 2, 1) < 0 + || read(pf, coffhdr, sizeof(coffhdr)) != sizeof(coffhdr) + || lseek(pf, coffhdr[10 + 5], 0) < 0 + || read(pf, firstbytes, 1) != 1) /* scnptr */ + is_coff = 0; /* "Aha! An impostor!" (The Adventure game) */ + else if (firstbytes[0] != 0xa3) /* opcode of movl %eax, 0x12345678 (V1) */ + v2_0 = 1; } - else if (header[0] == 0x5a4d) + else if (header[0] == 0x5a4d) /* "MZ" */ { int header_offset = (long)header[2]*512L; + is_stubbed = 1; if (header[1]) header_offset += (long)header[1] - 512L; lseek(pf, 512, 0); read(pf, cmdline, 8); cmdline[8] = 0; if (strcmp(cmdline, "go32stub") == 0) + { v2_0 = 1; + is_coff = 1; + } else { lseek(pf, header_offset - 4, 0); @@ -219,16 +568,23 @@ static int go32_exec(const char *program header[0] = 0; read(pf, header, sizeof(header)); if (header[0] == 0x010b) - is_stubbed = 1; + is_coff = 1; if (header[0] == 0x014c) - is_stubbed = 1; + is_coff = 1; lseek(pf, stub_offset, 0); read(pf, &si, sizeof(si)); if (memcmp(STUB_INFO_MAGIC, si.magic, 16) == 0) found_si = 1; } } - if (!is_stubbed && !v2_0) + else if (header[0] == 0x2123) /* "#!" */ + { + close(pf); + return script_exec(program, argv, envp); + } + + /* Non-DJGPP programs cannot be run by !proxy. */ + if (!is_coff) { close(pf); return direct_exec(program, argv, envp); @@ -236,18 +592,22 @@ static int go32_exec(const char *program if (found_si) go32 = si.go32; + else if (v2_0 && !is_stubbed) + go32 = GO32_V2_STRING; else go32 = GO32_STRING; - if (v2_0) + if (v2_0 && is_stubbed) { strcpy(rpath, program); } else { + int e = errno; if (!__dosexec_find_on_path(go32, envp, rpath)) { close(pf); + errno = e; return direct_exec(program, argv, envp); /* give up and just run it */ } @@ -260,40 +620,129 @@ static int go32_exec(const char *program } close(pf); + /* V2.0 programs invoked by `system' must be run via + `direct_exec', because otherwise the command-line arguments + won't be globbed correctly by the child. Only v2.01 and + later knows how to get long command lines from `system' AND + glob them correctly. But we don't want to check with which + version was the child compiled, so we need to create both the + usual DOS command line and the !proxy one (which will be put + into the environment). Sigh... */ save_argv0 = argv[0]; argv[0] = unconst(program, char *); /* since that's where we really found it */ + /* Construct the DOS command tail */ + for (argc=0; argv[argc]; argc++); - tbuf = __tb; - - if (found_si) + if (__dosexec_in_system && v2_0) { - si_la = talloc(si.struct_length); - dosmemput(sip, si.struct_length, si_la); - } - - for (argc=0; argv[argc]; argc++); - rm_la = talloc(2*(argc+1)); - rm_seg = (__tb >> 4) & 0xffff; - rm_argv = (short *)alloca((argc+1) * sizeof(short)); - for (i=0; i CMDLEN_LIMIT) + break; + *cmdp++ = ' '; + while (*p) + { + if (cmdp - cmdline > CMDLEN_LIMIT) + break; + *cmdp++ = *p++; + } + } + *cmdp = '\0'; } - rm_argv[i] = 0; - dosmemput(rm_argv, 2*(argc+1), rm_la); - - sprintf(cmdline, " !proxy %04x %04x %04x %04x %04x", - argc, rm_seg, (rm_la - (rm_seg<<4))&0xffff, - rm_seg, (si_la - (rm_seg<<4))&0xffff); - if (!found_si) - cmdline[22] = 0; /* remove stub information */ - argv[0] = save_argv0; + lfn = _USE_LFN; + + /* Can't call any functions that use the transfer buffer beyond + this point: they will overwrite the data already in __tb. */ + tbuf_beg = tbuf_ptr = __tb; + tbuf_len = _go32_info_block.size_of_transfer_buffer; + tbuf_end = tbuf_ptr + tbuf_len - 1; + + /* If called from `system' and we have a command line shorter + than the DOS limit, we don't need to use !proxy at all. + Note that v1.x programs are always run through !proxy, + to prevent go32.exe from printing its copyright line. */ + if (!__dosexec_in_system || !v2_0 || cmdp - cmdline > CMDLEN_LIMIT) + { + if (!check_talloc(found_si ? + si.struct_length : 0 + + (argc+1)*sizeof(short))) + { + argv[0] = save_argv0; + return -1; + } + if (found_si) + { + si_la = talloc(si.struct_length); + si_off = si_la - tbuf_beg; + dosmemput(sip, si.struct_length, si_la); + } + + rm_off = argv_off = talloc((argc+1) * sizeof(short)) - tbuf_beg; +#if 0 + /* `alloca' could be dangerous with long command lines. We + will instead move the offsets one by one with `_farpokew'. */ + rm_argv = (short *)alloca((argc+1) * sizeof(short)); +#endif + + for (i=0; i> 4), rm_off & 0xffff, + (unsigned)(tbuf_beg >> 4), si_off & 0xffff); + if (!found_si) + proxy_cmdline[22] = 0; /* remove stubinfo information */ + + if (__dosexec_in_system && v2_0) + pproxy = proxy_cmdline; + else + { + /* `proxy_cmdline looks like an environment variable " !proxy=value". + This is used as the REAL command line specification by 2.01 + and later executables when called by `system'. But if that's + not the case, we need a blank instead of the `='. */ + proxy_cmdline[__PROXY_LEN] = ' '; + pcmd = proxy_cmdline; + } + } + else + argv[0] = save_argv0; - return direct_exec_tail(rpath, cmdline, envp); + retval = direct_exec_tail(rpath, pcmd, envp, pproxy, lfn); + if (proxy_cmdline) + free(proxy_cmdline); + return retval; } int @@ -301,30 +750,83 @@ __dosexec_command_exec(const char *progr { const char *comspec=0; char *cmdline; - char *newargs[3]; int cmdlen; int i; - - cmdlen = strlen(program) + 4; + int was_quoted = 0; /* was the program name quoted? */ + + /* Add spare space for possible quote characters. */ + cmdlen = strlen(program) + 4 + 2; for (i=0; argv[i]; i++) - cmdlen += strlen(argv[i]) + 1; + cmdlen += 2*strlen(argv[i]) + 1; cmdline = (char *)alloca(cmdlen); - + + /* FIXME: is this LFN-clean? What special characters can + the program name have and how should they be quoted? */ strcpy(cmdline, "/c "); - for (i=0; program[i] > ' '; i++) + if (strchr(program, ' ') || strchr(program, '\t')) + { + was_quoted = 1; + cmdline[3] = '"'; + } + for (i = 0; program[i] > ' '; i++) { + /* COMMAND.COM cannot grok program names with forward slashes. */ if (program[i] == '/') - cmdline[i+3] = '\\'; + cmdline[i+3+was_quoted] = '\\'; else - cmdline[i+3] = program[i]; + cmdline[i+3+was_quoted] = program[i]; } for (; program[i]; i++) - cmdline[i+3] = program[i]; - cmdline[i+3] = 0; + cmdline[i+3+was_quoted] = program[i]; + if (was_quoted) + { + cmdline[i+3+was_quoted] = '"'; + i++; + } + cmdline[i+3+was_quoted] = 0; for (i=1; argv[i]; i++) { strcat(cmdline, " "); - strcat(cmdline, argv[i]); + /* If called by `spawnXX' or `execXX' functions, must quote + arguments that have embedded whitespace or characters which + are special to COMMAND.COM and its ilk. We don't quote all + the arguments so the command line won't grow larger than + the 126-char limit, if it doesn't have to. */ + if (!__dosexec_in_system && strpbrk(argv[i], " \t<>|'\"%") != 0) + { + char *d = cmdline + strlen(cmdline); + char *s = argv[i]; + /* COMMAND.COM doesn't understand escaped quotes, so we must + insert additional quotes around redirection characters if + it would seem to COMMAND.COM we're outside of quoted part. + This variable keeps track of whether we are in- or outside + quotes as far as COMMAND.COM is concerned. */ + int outside_quote = 0; + + *d++ = '"'; + while (*s) + { + if (*s == '"') + { + outside_quote = !outside_quote; + *d++ = '\\'; + } + else if (outside_quote && (*s == '|' || *s == '<' || *s == '>')) + { + *d++ = '"'; + *d++ = *s++; + *d++ = '"'; + continue; + } + else if (*s == '%') + *d++ = '%'; + *d++ = *s++; + } + *d++ = '"'; + *d++ = '\0'; + } + else + strcat(cmdline, argv[i]); } for (i=0; envp[i]; i++) if (strncmp(envp[i], "COMSPEC=", 8) == 0) @@ -335,19 +837,31 @@ __dosexec_command_exec(const char *progr comspec = environ[i]+8; if (!comspec) comspec = "c:\\command.com"; - newargs[0] = unconst(comspec, char *); - newargs[1] = cmdline; - newargs[2] = 0; - i = direct_exec(comspec, newargs, envp); + + /* FIXME: 126-char limit below isn't LFN-clean. */ + if (strlen(cmdline) > CMDLEN_LIMIT + 1) + { + cmdline[CMDLEN_LIMIT+1] = '\0'; + errno = E2BIG; + } + + tbuf_beg = tbuf_ptr = __tb; + tbuf_len = _go32_info_block.size_of_transfer_buffer; + tbuf_end = tbuf_ptr + tbuf_len - 1; + i = direct_exec_tail(comspec, cmdline, envp, 0, 2); return i; } static int script_exec(const char *program, char **argv, char **envp) { - char line[130], interp[80], iargs[130]; + char line[130], interp[FILENAME_MAX], iargs[130]; FILE *f; char **newargs; int i, hasargs=0; + char *base, *p; + int has_extension = 0, has_drive = 0; + char pinterp[FILENAME_MAX]; + int (*spawnfunc)(int, const char *, char *const [], char *const []); f = fopen(program, "rt"); if (!f) @@ -355,11 +869,22 @@ static int script_exec(const char *progr errno = ENOENT; return -1; } - fgets(line, 130, f); + fgets(line, sizeof(line), f); fclose(f); + + if (strncmp(line, "#!", 2)) /* prevent infinite loop */ + return go32_exec(program, argv, envp); + + /* Paranoia: is this at all a text file? */ + for (i=0; i < sizeof(line)-1 && line[i] != '\0'; i++) + if (line[i] < 7 && line[i] >= 0) + return direct_exec(program, argv, envp); + iargs[0] = 0; interp[0] = 0; sscanf(line, "#! %s %[^\n]", interp, iargs); + + /* If no interpreter, invoke the default shell in $COMSPEC. */ if (interp[0] == 0) return __dosexec_command_exec(program, argv, envp); /* it couldn't be .exe or .com if here */ if (iargs[0]) @@ -370,14 +895,48 @@ static int script_exec(const char *progr for (i=0; argv[i]; i++) newargs[i+1+hasargs] = unconst(argv[i], char *); newargs[i+1+hasargs] = 0; - newargs[0] = unconst(argv[0], char *); /* it might work right, if not in system() */ + /* Some interpreters might have their own ideas about $PATH. + Therefore, pass them the full pathname of the script. */ + newargs[0] = newargs[1+hasargs] = unconst(program, char *); if (hasargs) newargs[1] = iargs; - i = spawnvpe(P_WAIT, interp, newargs, envp); + /* If INTERP is a Unix-style pathname, like "/bin/sh", we will try + it with the usual extensions and, if that fails, will further + search for the basename of the shell along the PATH; this + allows to run Unix shell scripts without editing their first line. */ + for (base=p=interp; *p; p++) + { + if (*p == '.') + has_extension = 1; + if (*p == '/' || *p == '\\' || *p == ':') + { + if (*p == ':') + has_drive = 1; + has_extension = 0; + base = p + 1; + } + } + + if (has_drive || has_extension) + { + strcpy (pinterp, interp); + spawnfunc = spawnvpe; + } + else if (__dosexec_find_on_path(interp, (char **)0, pinterp) + || __dosexec_find_on_path(base, envp, pinterp)) + spawnfunc = spawnve; /* no need to search on PATH: we've found it */ + else + return -1; + + i = (*spawnfunc)(P_WAIT, pinterp, newargs, envp); return i; } +/* Note: the following list is not supposed to mention *every* + possible extension of an executable file. It only mentions + those extensions that can be *omitted* when you invoke the + executable from one of the shells used on MSDOS. */ static struct { const char *extension; int (*interp)(const char *, char **, char **); @@ -385,11 +944,19 @@ static struct { { ".com", direct_exec }, { ".exe", go32_exec }, { ".bat", __dosexec_command_exec }, + { ".btm", __dosexec_command_exec }, + { ".sh", script_exec }, /* for compatibility with ms_sh */ + { ".ksh", script_exec }, + { ".pl", script_exec }, /* Perl */ + { ".sed", script_exec }, { "", go32_exec }, - { 0, script_exec }, + { 0, script_exec }, /* every extension not mentioned above calls it */ { 0, 0 }, }; -#define INTERP_NO_EXT 3 + +/* This is the index into the above array of the interpreter + which is called when the program filename has no extension. */ +#define INTERP_NO_EXT (sizeof(interpreters)/sizeof(interpreters[0]) - 3) /*-------------------------------------------------*/ @@ -400,9 +967,15 @@ __dosexec_find_on_path(const char *progr const char *ptr; int i, hasdot=0, haspath=0; int tried_dot = 0; + int e = errno, blen = strlen(program); - strcpy(buf, program); - rp = buf + strlen(buf); + if (blen > FILENAME_MAX - 1) + { + errno = ENAMETOOLONG; + return 0; + } + strncpy(buf, program, blen + 1); + rp = buf + blen; for (ptr=program; *ptr; ptr++) { @@ -418,17 +991,27 @@ __dosexec_find_on_path(const char *progr if (hasdot) { if (access(buf, 0) == 0 && access(buf, D_OK)) + { + errno = e; return buf; + } } else for (i=0; interpreters[i].extension; i++) { strcpy(rp, interpreters[i].extension); if (access(buf, 0) == 0 && access(buf, D_OK)) + { + /* If some of the `access' calls failed, `errno' will hold + the reason for the failure which is irrelevant to the + caller (we *did* find the execuatble). Restore the value + `errno' had when we were called. */ + errno = e; return buf; + } } - if (haspath) + if (haspath || !envp) return 0; *rp = 0; @@ -463,7 +1046,10 @@ __dosexec_find_on_path(const char *progr if (hasdot) { if (access(buf, 0) == 0 && access(buf, D_OK)) + { + errno = e; return buf; + } } else { @@ -471,7 +1057,10 @@ __dosexec_find_on_path(const char *progr { strcpy(rp, interpreters[i].extension); if (access(buf, 0) == 0 && access(buf, D_OK)) + { + errno = e; return buf; + } } } if (*pe == 0) @@ -486,7 +1075,19 @@ int __spawnve(int mode, const char *path int i = -1; char **argvp; char **envpp; - char rpath[80], *rp, *rd=0; + char rpath[FILENAME_MAX], *rp, *rd=0; + int e = errno; + + if (path == 0 || argv[0] == 0) + { + errno = EINVAL; + return -1; + } + if (strlen(path) > FILENAME_MAX - 1) + { + errno = ENAMETOOLONG; + return -1; + } u.x = argv; argvp = u.p; u.x = envp; envpp = u.p; @@ -516,6 +1117,7 @@ int __spawnve(int mode, const char *path } strcpy(rp, interpreters[i].extension); } + errno = e; if (i == -1) i = INTERP_NO_EXT; i = interpreters[i].interp(rpath, argvp, envpp);