/* * Plan 9 versions of system-specific functions * By convention, exported routines herein have names beginning with an * upper case letter. */ #include #include #include #undef IN #include "rc.h" #include "exec.h" #include "io.h" #include "fns.h" #include "getflags.h" enum { Maxshebang = 1024 }; struct { int e; char *s; } Winerrs[] = { /* some more common errors translated to more friendly messages */ { ERROR_HANDLE_EOF, "end of file" }, { ERROR_INVALID_HANDLE, "invalid handle" }, { ERROR_SHARING_VIOLATION, "sharing violation" }, { ERROR_FILE_NOT_FOUND, "file does not exist" }, { ERROR_PATH_NOT_FOUND, "path does not exist" }, { ERROR_BAD_PATHNAME, "bad path" }, { ERROR_TOO_MANY_OPEN_FILES, "too many open files" }, { ERROR_ACCESS_DENIED, "permission denied" }, { ERROR_INVALID_NAME, "filename syntax" }, { ERROR_OUTOFMEMORY, "out of memory" }, { ERROR_NOT_ENOUGH_MEMORY, "not enough emmory" }, { ERROR_WRITE_PROTECT, "read only" }, { ERROR_BROKEN_PIPE, "broken pipe" }, { ERROR_NO_MORE_SEARCH_HANDLES, "too many open directories" }, { ERROR_ALREADY_EXISTS, "already exists" }, { ERROR_BAD_EXE_FORMAT, "bad executable format" }, { ERROR_SEEK_ON_DEVICE, "seek on device" }, { ERROR_GEN_FAILURE, "general failure" }, { ERROR_NOT_SUPPORTED, "not supported" }, { ERROR_NOT_READY, "device not ready" }, { ERROR_BAD_NETPATH, "network path not found" }, { ERROR_BAD_NET_NAME, "host not known" }, { ERROR_HANDLE_DISK_FULL, "disk full" }, { 0, nil }, }; char *Signame[] = { "sigexit", "sighup", "sigint", "sigquit", "sigalrm", "sigkill", "sigfpe", "sigterm", 0 }; char *Rcmain = "undefined"; void execfinit(void); builtin Builtin[] = { "cd", execcd, "whatis", execwhatis, "eval", execeval, "exec", execexec, /* but with popword first */ "exit", execexit, "shift", execshift, "wait", execwait, ".", execdot, "finit", execfinit, "flag", execflag, 0 }; #define SEP ';' char *xenviron; /* * Windows preserves case but ignores it, even in environment variables. * We need to be able to find these vars so we force their * case to that below as they are parsed from the environment. */ char *Force[] = { "path", "pathext", nil }; static struct { int pid; HANDLE hand; } Child[10]; static int cistrcmp(char *s1, char *s2) { int c1, c2; while(*s1){ c1 = *(uchar*)s1++; c2 = *(uchar*)s2++; if(c1 == c2) continue; if(c1 >= 'A' && c1 <= 'Z') c1 -= 'A' - 'a'; if(c2 >= 'A' && c2 <= 'Z') c2 -= 'A' - 'a'; if(c1 != c2) return c1 - c2; } return -*s2; } struct word* enval(char *s) { char *t, c; struct word *v; for(t = s;*t && *t != SEP;t++) continue; c=*t; *t='\0'; v = newword(s, c=='\0'?(struct word *)0:enval(t+1)); *t = c; return v; } static void reslash(char *s, char new) { char *p; for(p = s; *p; p++) if(*p == '\\' || *p == '/') *p = new; } int cmpvar(const void *aa, const void *ab) { struct var * const *a = aa, * const *b = ab; return strcmp((*a)->name, (*b)->name); } char * exportenv(void) { char *env, *p, *q; struct var **h, *v, **idx; struct word *a; int nvar = 0, nchr = 0, sep, i; int x; /* * Slightly kludgy loops look at locals then globals. * locals no longer exist - geoff */ for(h = gvar-1; h != &gvar[NVAR]; h++) for(v = h >= gvar? *h: runq->local; v ;v = v->next){ if(v==vlook(v->name) && v->val){ nvar++; nchr+=strlen(v->name)+1; for(a = v->val;a;a = a->next) nchr+=strlen(a->word)+1; } if(v->fn){ nvar++; nchr+=strlen(v->name)+strlen(v->fn[v->pc-1].s)+8; } } idx = (struct var **)emalloc(nvar * sizeof(struct var *)); i = 0; for(h = gvar-1; h != &gvar[NVAR]; h++) for(v = h >= gvar? *h: runq->local; v ;v = v->next){ if((v==vlook(v->name) && v->val) || v->fn) idx[i++] = v; } qsort((void *)idx, nvar, sizeof idx[0], cmpvar); env = (char *)emalloc(nvar+nchr+1); p = env; for(i = 0; i < nvar; i++){ v = idx[i]; if((v==vlook(v->name)) && v->val){ q = v->name; while(*q) *p++=*q++; sep='='; for(a = v->val;a;a = a->next){ *p++=sep; sep = SEP; q = a->word; while(*q) *p++=*q++; } *p++='\0'; } if(v->fn){ *p++='f'; *p++='n'; *p++='#'; q = v->name; while(*q) *p++=*q++; *p++='='; q = v->fn[v->pc-1].s; while(*q) *p++=*q++; *p++='\0'; } } *p = 0; return env; } /* * had to change the parsing from plan9/unix code as windows * tends to use braces in variables and variable names with inpunity. * FIXME: should handle UTF really */ void Vinit(void) { int a; struct word *w; char *p, *s, **f, *env; static char buf[MAX_PATH]; env = GetEnvironmentStrings(); xenviron = env; for(; env && *env; env = strchr(env, 0)+1){ if((s = strchr(env, '=')) == NULL) continue; if(strncmp(env, "fn#", 3) == 0) /* ignore functions */ continue; *s='\0'; for(f = Force; *f; f++) if(cistrcmp(*f, env) == 0) break; if(*f){ reslash(s+1, '/'); setvar(*f, enval(s+1)); }else{ setvar(env, enval(s+1)); } *s='='; } /* look for rcmain in $path */ for(w = vlook("path")->val; w; w = w->next){ snprintf(buf, sizeof(buf), "%s/rcmain", w->word); a = GetFileAttributes(buf); if(a != -1 && a != FILE_ATTRIBUTE_DIRECTORY) break; } if(w == nil){ pfmt(err, "rc: cannot find 'rcmain' in $path :\n"); exit(0); } Rcmain = buf; } char *xenv; /* * called once per func read from env, returns after each, * but calls Xreturn() before returning when the list is empty */ void Xrdfn(void) { int len; char *s, *nxt; for(; xenv && *xenv; xenv = nxt){ nxt = strchr(xenv, 0) +1; if(strncmp(xenv, "fn#", 3) != 0) /* ignore variables */ continue; if((s = strchr(xenv, '=')) == NULL) continue; len = strlen(xenv); *s=' '; xenv[2]=' '; xenv[len]='\n'; execcmds(opencore(xenv, len+1)); xenv[len]='\0'; xenv[2]='#'; *s='='; xenv = nxt; return; } Xreturn(); } union code rdfns[4]; void execfinit(void) { static int first = 1; if(first){ rdfns[0].i = 1; rdfns[1].f = Xrdfn; rdfns[2].f = Xjump; rdfns[3].i = 1; first = 0; } Xpopm(); xenv = xenviron; start(rdfns, 1, runq->local); } static int addchild(int pid, HANDLE hand) { int i; for(i = 0; i < nelem(Child); i++) { if(Child[i].hand == 0) { Child[i].pid = pid; Child[i].hand = hand; return 1; } } pfmt(err, "adchild: child table full\n"); return 0; } /* NB: return -1 only on interrupt, may cause a retry */ int Waitfor(int pid, int persist) { int i; HANDLE h; ulong status; char buf[32]; if(pid == 0) return 0; for(i = 0; i < nelem(Child); i++) if(Child[i].pid == pid){ h = Child[i].hand; break; } /* we don't know about this one - let the system try to find it */ if(h == nil){ h = OpenProcess(PROCESS_ALL_ACCESS, 0, pid); if(h == nil){ pfmt(err, "%d cannot open process\n", pid); return 0; } } status = 1; WaitForSingleObject(h, INFINITE); GetExitCodeProcess(h, &status); CloseHandle(h); for(i = 0; i < nelem(Child); i++) if(Child[i].pid == pid){ Child[i].pid = 0; Child[i].hand = nil; break; } if(status){ inttoascii(buf, status); setstatus(buf); return 0; } setstatus(""); return 0; } void Updenv(void) { } void Execute(word *args, word *path) { pfmt(err, "rc: exec not supported\n"); } int Abspath(char *w) { if(strncmp(w, "./", 2)==0) return 1; if(strncmp(w, ".\\", 2)==0) return 1; if(strncmp(w, "../", 3)==0) return 1; if(strncmp(w, "..\\", 3)==0) return 1; if(strncmp(w, "/", 1)==0) return 1; if(strncmp(w, "\\", 1)==0) return 1; if(isalpha(w[0]) && w[1] == ':') return 1; return 0; } int Globsize(char *p) { int isglob, globlen; isglob = 0; globlen = FILENAME_MAX+1; for(; *p; p++){ if(*p == GLOB){ p++; if(*p != GLOB) isglob++; if(*p == '*') globlen += FILENAME_MAX; else globlen += 1; } else globlen++; } if(isglob) return globlen; return 0; } enum { Ndirs = 50 }; typedef struct { HANDLE h; int first; WIN32_FIND_DATA data; } Dir; static Dir Dirs[Ndirs]; int Opendir(char *path) { long n; Dir *dp; char fullpath[MAX_PATH]; for(dp = Dirs; dp < &Dirs[Ndirs]; dp++) if(dp == nil){ snprintf(fullpath, MAX_PATH, "%s\\*.*", path); dp->h = FindFirstFile(fullpath, &dp->data); if(dp->h == INVALID_HANDLE_VALUE){ dp->h = 0; /* paranoia */ return -1; } dp->first = 1; return dp-Dirs; } return -1; } static int validfile(WIN32_FIND_DATA *wfd, int onlydirs) { char *s; s = wfd->cFileName; if(! (wfd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && onlydirs) return 0; if(s[0] == '.' && s[1] == 0) return 0; if(s[0] == '.' && s[1] == '.' && s[2] == 0) return 0; return 1; } /* * onlydirs is advisory -- it means you only * need to return the directories. it's okay to * return files too (e.g., on unix where you can't * tell during the readdir), but that just makes * the globber work harder. */ int Readdir(int f, void *p, int onlydirs) { int n; WIN32_FIND_DATA *wfd; if(f < 0 || f >= Ndirs) return -1; wfd = &Dirs[f].data; if(Dirs[f].first){ Dirs[f].first = 0; if(validfile(wfd, onlydirs)){ strcpy(p, wfd->cFileName); return 1; } } while(FindNextFile(Dirs[f].h, wfd) != 0){ if(! validfile(wfd, onlydirs)) continue; strcpy(p, wfd->cFileName); return 1; } return 0; } void Closedir(int f) { if(f < 0 || f >= Ndirs) return; FindClose(Dirs[f].h); Dirs[f].h = 0; } int interrupted = 0; static BOOL WINAPI gettrap(DWORD code) { int i; switch(code){ case CTRL_C_EVENT: trap[SIGINT]++; break; case CTRL_BREAK_EVENT: trap[SIGQUIT]++; break; case CTRL_CLOSE_EVENT: /* window close or "End Task" task manager */ case CTRL_LOGOFF_EVENT: case CTRL_SHUTDOWN_EVENT: trap[SIGTERM]++; break; default: pfmt(err, "rc: %d unexpected trap code\n", code); break; } ntrap++; if(ntrap >= NSIG){ pfmt(err, "rc: Too many traps (trap %d), aborting\n", code); return 0; /* default action (exit) */ } interrupted = 1; for(i = 0; i < nelem(Child); i++) if(Child[i].hand != nil) GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, Child[i].pid); return 1; /* continue */ } void Trapinit(void) { SetConsoleCtrlHandler(gettrap, TRUE); } void Unlink(char *name) { remove(name); } long Write(int fd, void *buf, long cnt) { return write(fd, buf, cnt); } long Read(int fd, void *buf, long cnt) { int n; n = read(fd, buf, cnt); if(interrupted) return -1; return n; } long Seek(int fd, long cnt, long whence) { return lseek(fd, cnt, whence); } /* used by whatis only */ int Executable(char *file) { int a; FILE *fp; ulong type; struct word *w; char *ext, buf[4]; if(GetBinaryType(file, &type)) return 1; if((ext = strrchr(file, '.')) != nil) for(w = vlook("pathext")->val; w; w = w->next) if(cistrcmp(ext, w->word) == 0) return 1; if((fp = fopen(file, "r")) == nil) return 0; if(fread(buf, 1, sizeof(buf), fp) <= 0){ fclose(fp); return 0; } fclose(fp); if(strncmp(buf, "#!", 2) == 0) return 1; return 0; } int Creat(char *file) { return creat(file, 0644); } int Dup(int a, int b) { return dup2(a, b); } int Dup1(int a) { return dup(a); } void Exit(char *stat) { int n = 0; while(*stat){ if(*stat != '|'){ if(*stat < '0' || '9' < *stat) exit(1); else n = n*10 + *stat - '0'; } stat++; } exit(n); } int Eintr(void) { return interrupted; } void Noerror(void) { interrupted = 0; } int Isatty(int fd) { switch(GetFileType((HANDLE *)_get_osfhandle(fd))){ case FILE_TYPE_CHAR: /* console or maybe a uart */ case FILE_TYPE_PIPE: /* ssh session */ return 1; case FILE_TYPE_DISK: /* redirection */ case FILE_TYPE_REMOTE: /* unused they say */ case FILE_TYPE_UNKNOWN: default: return 0; } } void Abort(void) { pfmt(err, "aborting\n"); flush(err); exit(2); } void Memcpy(void *a, void *b, long n) { memmove(a, b, n); } void* Malloc(ulong n) { return malloc(n); } int needsrcquote(int c) { if(c <= ' ') return 1; if(strchr("`^#*[]=|\\?${}()'<>&;", c)) return 1; return 0; } void errstr(char *buf, int len) { int e, i; char *p, *q; e = GetLastError(); if(e == ERROR_SUCCESS){ *buf = 0; return; } for(i = 0; Winerrs[i].e; i++) if(Winerrs[i].e == e){ strncpy(buf, Winerrs[i].s, len); buf[len -1] = 0; return; } FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, nil, e, 0, buf, len, 0); for(p=q=buf; *p; p++) { if(*p == '\r') continue; if(*p == '\n') *q++ = ' '; else *q++ = *p; } } char ** mkargv(struct word *a) { int n; char **argv, **argp; n = count(a)+2; if(n < 8) /* plenty of room for shebangs */ n = 8; argv = (char **)emalloc(n*sizeof(char *)); memset(argv, 0, n*sizeof(char *)); argp = argv+1; /* leave one at front for runcoms */ for(;a;a = a->next) *argp++=a->word; *argp = 0; return argv; } static int setpath(char *path, char *file) { char *p, *last, tmp[MAX_PATH+1]; int n; if(strlen(file) >= MAX_PATH){ pfmt(err, "%s: file name too long", file); return -1; } strcpy(tmp, file); for(p=tmp; *p; p++) { if(*p == '/') *p = '\\'; } if(tmp[0] != 0 && tmp[1] == ':') { if(tmp[2] == 0) { tmp[2] = '\\'; tmp[3] = 0; } else if(tmp[2] != '\\') { /* don't allow c:foo - only c:\foo */ pfmt(err, "%s: illegal file name", file); return -1; } } path[0] = 0; n = GetFullPathName(tmp, MAX_PATH, path, &last); if(n >= MAX_PATH) { pfmt(err, "%s: expanded path too long", file); return -1; } if(n == 0 && tmp[0] == '\\' && tmp[1] == '\\' && tmp[2] != 0) { strcpy(path, tmp); return -1; } if(n == 0) { pfmt(err, "%s: bad file name", tmp); return -1; } for(p=path; *p; p++) { if(*p < 32 || *p == '*' || *p == '?') { pfmt(err, "%s: wildcards in path", path); return -1; } } /* get rid of trailling \ */ if(path[n-1] == '\\') { if(n <= 2) { pfmt(err, "%s: illegal path", path); return -1; } path[n-1] = 0; n--; } if(path[1] == ':' && path[2] == 0) { path[2] = '\\'; path[3] = '.'; path[4] = 0; return -1; } if(path[0] != '\\' || path[1] != '\\') return 0; for(p=path+2,n=0; *p; p++) if(*p == '\\') n++; if(n == 0) return -1; if(n == 1) return -1; return 0; } static int shargs(char *s, int n, char **ap) { int i; s += 2; n -= 2; /* skip #! */ for(i=0; s[i]!='\n'; i++) if(i == n-1) return 0; s[i] = 0; if(flag['p']) pfmt(err, "got %s\n", s); *ap = 0; i = 0; for(;;) { while(*s==' ' || *s=='\t') s++; if(*s == 0) break; i++; *ap++ = s; *ap = 0; while(*s && *s!=' ' && *s!='\t') s++; if(*s == 0) break; else *s++ = 0; } return i; } static int exetype(char *path, char *file, char **bangv) { int a, n, hasext; FILE *fp; ulong type; struct word *w; char *ext, *p; static char line[Maxshebang]; /* has it a valid looking extension? */ hasext = 0; if((ext = strrchr(file, '.')) != 0){ for(w = vlook("pathext")->val; w; w = w->next) if(cistrcmp(ext, w->word) == 0){ hasext++; snprintf(path, MAX_PATH, "%s", file); if(flag['p']) pfmt(err, "srch: %s good ext\n", path); a = GetFileAttributes(path); if(a != -1 && a != FILE_ATTRIBUTE_DIRECTORY) return 0; } } /* is it an rc script, or a windows executable? */ snprintf(path, MAX_PATH, "%s", file); if(flag['p']) pfmt(err, "srch: %s raw name\n", path); if((fp = fopen(path, "r")) != nil) if((n = fread(line, 1, sizeof(line), fp)) > 0){ fclose(fp); if(strncmp(line, "MZ", 2) == 0) return 0; if(strncmp(line, "#!", 2) == 0) if(shargs(line, n, bangv) > 0){ if(strcmp(bangv[0], "/bin/rc") == 0) bangv[0] = argv0; snprintf(path, MAX_PATH, "%s", bangv[0]); return 0; } } /* try appending a known extensions (.BAT files etc)*/ if(! hasext) for(w = vlook("pathext")->val; w; w = w->next){ snprintf(path, MAX_PATH, "%s%s", file, w->word); if(flag['p']) pfmt(err, "srch: %s add ext\n", path); a = GetFileAttributes(path); if(a != -1 && a != FILE_ATTRIBUTE_DIRECTORY) return 0; } return -1; } /* * windows quoting rules - I think * Words are seperated by space or tab * Words containing a space or tab can be quoted using " * 2N backslashes + " ==> N backslashes and end quote * 2N+1 backslashes + " ==> N backslashes + literal " * N backslashes not followed by " ==> N backslashes */ static char * dblquote(char *cmd, char *s) { int nb; char *p; for(p=s; *p; p++) if(*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r' || *p == '"') break; if(p == s){ /* empty arg */ strcpy(cmd, "\"\""); return cmd+2; } if(*p == 0){ /* easy case */ strcpy(cmd, s); return cmd+(p-s); } *cmd++ = '"'; for(;;) { for(nb=0; *s=='\\'; nb++) *cmd++ = *s++; if(*s == 0) { /* trailing backslashes -> 2N */ while(nb-- > 0) *cmd++ = '\\'; break; } if(*s == '"') { /* literal quote -> 2N+1 backslashes */ while(nb-- > 0) *cmd++ = '\\'; *cmd++ = '\\'; /* escape the quote */ } *cmd++ = *s++; } *cmd++ = '"'; *cmd = 0; return cmd; } static char * proccmd(char **bangv, char **argv) { int i, n; char *cmd, *p; /* conservatively calculate length of command; * backslash expansion can cause growth in dblquote(). */ n = 0; for(i=0; bangv[i]; i++) n += (bangv[i])? 2*strlen(bangv[i]): 2; for(i=0; argv[i]; i++) n += (argv[i])? 2*strlen(argv[i]): 2; n++; cmd = emalloc(n); p = cmd; for(i=0; bangv[i]; i++){ p = dblquote(p, bangv[i]); *p++ = ' '; } for(i=0; argv[i]; i++){ p = dblquote(p, argv[i]); *p++ = ' '; } if(p != cmd) p--; *p = 0; return cmd; } int pipe(int *fd) { /* * If you want binary pipes in to/out of * rc then you will probably want to change * the definition of ifs in rcmain to include * a carriage return. I cannot see why you would * want to do this, but perhaps its a lack of vision. * -Steve */ return _pipe(fd, 8192, _O_TEXT); } static HANDLE fdexport(int fd) { HANDLE h, r; if(fd < 0) return INVALID_HANDLE_VALUE; h = (HANDLE)_get_osfhandle(fd); if(h < 0) return INVALID_HANDLE_VALUE; if(!DuplicateHandle(GetCurrentProcess(), h, GetCurrentProcess(), &r, DUPLICATE_SAME_ACCESS, 1, DUPLICATE_SAME_ACCESS)) return INVALID_HANDLE_VALUE; return r; } int ForkExecute(char *name, char **argv, int sin, int sout, int serr) { int r; STARTUPINFO si; PROCESS_INFORMATION pi; char path[MAX_PATH], *bangv[1024], *cmd, *env; bangv[0] = 0; if(exetype(path, name, bangv) == -1) return -1; memset(&si, 0, sizeof(si)); si.cb = sizeof(si); si.dwFlags = STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES; si.wShowWindow = SW_SHOW; si.hStdInput = fdexport(sin); si.hStdOutput = fdexport(sout); si.hStdError = fdexport(serr); env = exportenv(); cmd = proccmd(bangv, argv); if(flag['d']) pfmt(err, "proc: path='%s' cmd='%s'\n", path, cmd); r = CreateProcess(path, cmd, nil, nil, TRUE, CREATE_NEW_PROCESS_GROUP, env, nil, &si, &pi); /* allow child to run */ Sleep(0); free(cmd); free(env); CloseHandle(si.hStdInput); CloseHandle(si.hStdOutput); CloseHandle(si.hStdError); if(!r){ setstatus("cannot create process"); return 0; } CloseHandle(pi.hThread); if(addchild(pi.dwProcessId, pi.hProcess) == 0) return 0; return pi.dwProcessId; }