/* jrd's version of the "system()" function. system(command_line) -> return-code System deals with finding the executable you're talking about, and firing it off. It also deals with any redirections. It returns the completion status of the executed program. In addition, it sets up the environment of the child 'task' such that if it's using our CRT0, it'll pick up the complete arglist. The comments in CRT0 claim that it's using MWC format; maybe so, I can't tell as I don't use MWC. At any rate, it seems to work. Places we look for executables: 1. See if there's an env var named what you want; it's expected to be the name of the program to run. 2. See if there's an env var called PATH. If so, it's expected to be a list of directories. Look thru them all for an executable matching the command name. 3. Look in the current directory for something that matches the command name. This thing requires an entry point called "barf", for reporting errors. See the code. The compile-time switch BUILTINS enables searching of a list of builtin command names, supplied by the application that uses this. The compile-time switch CLI causes it to try to use a CLI to exec the command, if it fails to figure out how to do it any other way. Compile-time switch GULAM makes it insist on gulam. */ #include #include #include #define LOCAL static #define CLI #define GULAM #ifdef GULAM #define GULAM_HAS_BUGS #endif extern char * getenv(); extern char * malloc(); extern struct basepage * _base; extern char ** environ; #ifdef CLI #define NO_CLI -65536 /* couldn't find CLI to exec */ #define _shell_p 0x04F6 /* dereference _shell_p. run this in super mode */ LOCAL long kludge = 0; /* will contain *_shell_p */ void deref_shell_p() { kludge = *((long * )_shell_p); } #ifdef GULAM /* return the gulam magic number */ long gulam_magic() { long * gulam_base; if (!kludge) Supexec(deref_shell_p); if (!kludge) return(0); gulam_base = (long * )kludge; return(*((long * )((int )gulam_base - 10))); } #endif /* dereference _shell_p if necessary */ long cli_entry_vector() { long cli_base; if (!kludge) Supexec(deref_shell_p); return(kludge); } #ifdef GULAM_HAS_BUGS /* El disgusto. Gulam has some kind of horrible bug that trashes the stack. Save a copy of the stack here, and save regs. NB!!! this makes us *NOT* reentrant!!! */ extern int _stktop; /* top of stack set by crt0 */ LOCAL long regs[16]; /* all regs */ LOCAL char * stack_buffer; /* use this as stack */ LOCAL char * current_stack_pointer; LOCAL int stack_nbytes; /* how big the stack is */ LOCAL inline bcopy(from, to, n) char * from; char * to; int n; { while (n-- > 0) { *to++ = *from++; } } LOCAL inline save_context() { /* clone the stack */ asm volatile ("movel sp,d0"); asm volatile ("movel d0,_current_stack_pointer"); asm volatile ("movel __stktop,d1"); asm volatile ("subl d0,d1"); asm volatile ("movel d1,_stack_nbytes"); stack_buffer = (char * )malloc(stack_nbytes); bcopy(current_stack_pointer, stack_buffer, stack_nbytes); /* save all registers */ asm volatile ("moveml #0xFFFF,_regs"); asm volatile ("clrl d0" : : : "d0"); asm volatile ("clrl d1" : : : "d1"); asm volatile ("clrl d2" : : : "d2"); asm volatile ("clrl d3" : : : "d3"); asm volatile ("clrl d4" : : : "d4"); asm volatile ("clrl d5" : : : "d5"); asm volatile ("clrl d6" : : : "d6"); asm volatile ("clrl d7" : : : "d7"); asm volatile ("movel d0,a0" : : : "a0"); asm volatile ("movel d0,a1" : : : "a1"); asm volatile ("movel d0,a2" : : : "a2"); asm volatile ("movel d0,a3" : : : "a3"); asm volatile ("movel d0,a4" : : : "a4"); asm volatile ("movel d0,a5" : : : "a5"); } LOCAL inline restore_context() { asm volatile ("moveml _regs,#0xFFFF" : : : "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "a0", "a1", "a2", "a3", "a4", "a5", "a6", "sp"); bcopy(stack_buffer, current_stack_pointer, stack_nbytes); free(stack_buffer); } LOCAL char * gulam_command_string; LOCAL int (* callgulam)(); LOCAL int gulam_result; /* this prevents compiler from outsmarting us by optimizing things onto the stack after we've trashed regs. DON'T INLINE! */ LOCAL void call_gulam_by_hand() { asm volatile ("movel _gulam_command_string,sp@-"); asm volatile ("movel _callgulam,a0"); asm volatile ("jsr a0@"); asm volatile ("movel d0,_gulam_result"); } #endif int exec_via_cli(str) char * str; { #ifndef GULAM_HAS_BUGS int (* callcli)(); #endif #ifdef GULAM if (gulam_magic() != 0x00420135) return(NO_CLI); #endif #ifdef GULAM_HAS_BUGS if ((long )callgulam = cli_entry_vector()) { gulam_command_string = str; save_context(); call_gulam_by_hand(); restore_context(); return(gulam_result); } #else if ((long)callcli = cli_entry_vector()) return((*callcli)(str)); #endif else return(NO_CLI); } #endif /* CLI */ /* random utils */ LOCAL char * copy_until(to, from, pred) char * to; char * from; int (* pred)(); /* predicate, tells us when to stop */ { while (*from && !(*pred)(*from)) *to++ = *from++; *to = '\0'; return(from); } /* predicates for above */ LOCAL int char_white_p(ch) { return(isspace(ch)); } /* stuff for resolving program to run when a command gets typed */ /* probe for a file, return T if found */ LOCAL int probe_file(name) char * name; { return((Fattrib(name, 0, 0) & 0x1F) == 0); } /* tack 'name' onto 'base_buf' and try adding permutations til til find one that's there. 'extensions' is a string containing a comma_separated list of pathname extensions to try. 'extensions' NIL means just probe for the base name. return T and altered base_buf if found. */ LOCAL int search_internal(base_buf, name, extensions) char * base_buf; char * name; char * extensions; { char * ext_ptr; /* pointer into 'extensions' */ char * tail_ptr; /* ptr to tail part of base_buf */ char * p; /* see if there's a trailing backslash in the base buffer */ if ((strlen(base_buf) > 0) && (base_buf[strlen(base_buf) - 1] != '\\')) strcat(base_buf, "\\"); /* nope, add one */ tail_ptr = base_buf + strlen(base_buf); strcat(base_buf, name); /* add the name */ /* skip any leading dots etc in name */ while ((*tail_ptr == '.') || (*tail_ptr == '\\')) tail_ptr++; /* if no extensions specified, just probe */ if (!extensions) return(probe_file(base_buf)); /* if the name's got a dot in it, just probe and go home */ if (index(tail_ptr, '.')) /* already has extension? */ return(probe_file(base_buf)); /* permute base buf by extensions til find a match */ while (*tail_ptr) tail_ptr++; /* inc tail to eos */ for (ext_ptr = extensions ; (ext_ptr && *ext_ptr) ; ) { for (p = tail_ptr ; (*ext_ptr && (*ext_ptr != ',')) ; ) *p++ = *ext_ptr++; *p = '\0'; if (*ext_ptr == ',') ext_ptr++; if (probe_file(base_buf)) return(1); } return(0); } /* handed a comma-separated list of directories, a name, and a comma-separated list of extensions, complete the pathname if possible */ LOCAL char complete_name[256]; LOCAL char * search_directories(dirs, name, extensions) char * dirs; char * name; char * extensions; { char * p; char * dir_ptr = dirs; if ((*name != '.') && (*name != '\\') && (name[1] != ':')) /* not abs pathname? */ for ( ; (dir_ptr && *dir_ptr) ; ) { for (p = &complete_name[0] ; (*dir_ptr && (*dir_ptr != ',')) ; ) *p++ = *dir_ptr++; *p = '\0'; if (*dir_ptr == ',') dir_ptr++; if (search_internal(complete_name, name, extensions)) return(&complete_name[0]); } /* not found? ok, try probing with no dir, ie current dir */ complete_name[0] = '\0'; if (search_internal(complete_name, name, extensions)) return(&complete_name[0]); return(NULL); /* not found anyplace */ } /* handed the name of a command, find the executable to run */ LOCAL char * find_executable(name) char * name; { char * real_name; if ((name[1] != ':') && (name[0] != '.') && (name[0] != '\\') && (real_name = getenv(name))) return(real_name); return(search_directories(getenv("PATH"), name, ".ttp,.tos,.prg,.app")); } /* stuff for building child environments */ LOCAL char * child_env; LOCAL int child_env_nbytes; LOCAL int my_env_nbytes(my_env) char ** my_env; { int nbytes = 0; for ( ; (my_env && *my_env) ; my_env++) nbytes += strlen(*my_env) + 1; return(nbytes); } LOCAL char * clone_my_env(my_env, child_env) char ** my_env; char * child_env; /* Pexec format... */ { for ( ; (my_env && *my_env) ; my_env++) { strcpy(child_env, *my_env); child_env += strlen(child_env) + 1; } return(child_env); } /* parse command line and build child env out of it. return remaining command line, if not all used. */ LOCAL char * make_child_env(line) char * line; { short argstart[64]; /* bytepos in line being parsed */ short argend[64]; char ch; char * p; int i, ii; int n_args = 0; int done = 0; #ifdef DEBUG fprintf(stderr, "system: make-child-env('%s')\n", line); #endif for (i = 0 ; ((!done) && line[i]) ; ) { while (line[i] && isspace(line[i])) i++; if (line[i] == '\0') break; switch (ch = line[i]) { case '\'': case '"': i++; argstart[n_args] = i; while (line[i] != ch) i++; argend[n_args] = i - 1; i++; n_args++; break; case '>': case '<': done = 1; break; default: argstart[n_args] = i; while (line[i] && !isspace(line[i])) i++; argend[n_args] = i; n_args++; break; } } child_env_nbytes = my_env_nbytes(environ); #ifdef DEBUG fprintf(stderr, "system: my env %d bytes\n", child_env_nbytes); #endif child_env_nbytes += strlen("ARGV=here") + 1; /* count arg lengths */ for (ii = 0 ; ii < n_args ; ii++) child_env_nbytes += (argend[ii] - argstart[ii]) + 1; child_env_nbytes += 1; /* final nul */ /* build the child env */ child_env = malloc(child_env_nbytes); #ifdef DEBUG fprintf(stderr, "system: bought child env at %08X %d bytes total\n", child_env, child_env_nbytes); #endif p = clone_my_env(environ, child_env); #ifdef DEBUG fprintf(stderr, "system: new ptr at %08X\n", p); #endif strcpy(p, "ARGV=here"); /* magic flag */ p += strlen("ARGV=here") + 1; for (ii = 0 ; ii < n_args ; ii++) { #ifdef DEBUG fprintf(stderr, "system: next arg at %08X '%s'\n", p, line + argstart[ii]); #endif strncpy(p, line + argstart[ii], (argend[ii] - argstart[ii])); p += (argend[ii] - argstart[ii]) + 1; } *p = '\0'; /* if there's more of the command line unused, return it */ if (line[i]) return(line + i); else return(NULL); } /* stuff for redirecting handles 0 and 1 */ LOCAL int original_handle_0, new_handle_0, handle_0_redirected = 0; LOCAL int original_handle_1, new_handle_1, handle_1_redirected = 0; LOCAL redirect_0(pathname) char * pathname; { int force; original_handle_0 = Fdup(0); /* save old */ new_handle_0 = Fopen(pathname, 0); /* open readonly */ force = Fforce(0, new_handle_0); /* force 0 to the new one */ handle_0_redirected = 1; /* zzz should probly deal with errors in here */ } LOCAL redirect_1(pathname, append_p) char * pathname; int append_p; { int force; original_handle_1 = Fdup(1); /* save old */ new_handle_1 = Fopen(pathname, 1); /* try to open writable */ if (new_handle_1 < -32) /* lose? */ { new_handle_1 = Fcreate(pathname, 0); /* ok, create it */ append_p = 0; /* don't bother */ } /* if append, try to find file and seek to end */ if (append_p) Fseek(0, new_handle_1, 2); /* eof please */ force = Fforce(1, new_handle_1); /* force 1 to the new one */ handle_1_redirected = 1; } LOCAL restore_redirects() { #ifdef DEBUG fprintf(stderr, "system: restore redirects\n"); #endif if (handle_0_redirected) { Fclose(0); Fclose(new_handle_0); /* necessary? free up handle? */ Fforce(0, original_handle_0); /* restore old setting */ Fclose(original_handle_0); /* is this right??? */ handle_0_redirected = 0; } if (handle_1_redirected) { Fclose(1); Fclose(new_handle_1); /* necessary? free up handle? */ Fforce(1, original_handle_1); /* restore old setting */ Fclose(original_handle_1); /* is this right??? */ handle_1_redirected = 0; } } LOCAL process_redirects(str) char * str; { char buf[64]; char * p; int append_p = 0; #ifdef DEBUG fprintf(stderr, "system: process redirects '%s'\n", str); #endif while (*str) { switch (*str) { case '<': str++; /* skip it */ while (isspace(*str)) str++; if (*str) str = copy_until(&buf, str, char_white_p); if (strlen(buf) > 0) redirect_0(buf); break; case '>': str++; /* skip it */ if (*str == '>') { str++; append_p = 1; } while (isspace(*str)) str++; if (*str) str = copy_until(&buf, str, char_white_p); if (strlen(buf) > 0) redirect_1(buf, append_p); break; default: str++; break; } } } /* externally visible entry points */ /* execute a command line. grok out the command name, figure out what it means. construct the string passed to Pexec, and the child environment. Do it, and free up everything. return status from the pexec */ int execute_command(line) char * line; { char pexec_string[130]; char cmd[32]; char * executable; char * redirects; int i, cmdstart; i = 0; while(line[i] && isspace(line[i])) i++; /* skip leading white */ cmdstart = i; while(line[i] && !isspace(line[i])) i++; /* skip nonwhite */ strncpy(cmd, line + cmdstart, i - cmdstart); /* pull out command name */ /* be more forgiving.... if (strlen(cmd) == 0) { barf("No command?!?"); return(-999); } */ executable = find_executable(cmd); #ifdef DEBUG fprintf(stderr, "system: found executable '%s'\n", executable); #endif if (!executable) { #ifdef CLI /* try to use a CLI to execute it */ int cli_result = exec_via_cli(line); if (cli_result != NO_CLI) return(cli_result); else #endif barf("I don't grok '%s'", cmd); return(-999); } while(line[i] && isspace(line[i])) i++; /* skip more whitespace */ strncpy(pexec_string, line + i, 126); /* make the pexec string */ redirects = make_child_env(line); /* in case child is smart */ #ifdef DEBUG fprintf(stderr, "system: made child env\n"); #endif if (redirects) process_redirects(redirects); #ifdef DEBUG fprintf(stderr, "system: Pexec('%s', '%s')\n", executable, pexec_string); #endif i = Pexec(PE_LOADGO, executable, pexec_string, child_env); if (redirects) restore_redirects(); free(child_env); child_env = NULL; return(i); } int system(command_line) char * command_line; { #ifdef DEBUG fprintf(stderr, "system('%s')\n", command_line); #endif return(execute_command(command_line)); } #ifndef barf barf(string, arg1, arg2, arg3) char * string; int arg1, arg2, arg3; { fprintf(stderr, string, arg1, arg2, arg3); } #endif /* that's all! */