/* * utils.c - miscellaneous utilities * * This file is part of zsh, the Z shell. * * Copyright (c) 1992-1997 Paul Falstad * All rights reserved. * * Permission is hereby granted, without written agreement and without * license or royalty fees, to use, copy, modify, and distribute this * software and to distribute modified versions of this software for any * purpose, provided that the above copyright notice and the following * two paragraphs appear in all copies of this software. * * In no event shall Paul Falstad or the Zsh Development Group be liable * to any party for direct, indirect, special, incidental, or consequential * damages arising out of the use of this software and its documentation, * even if Paul Falstad and the Zsh Development Group have been advised of * the possibility of such damage. * * Paul Falstad and the Zsh Development Group specifically disclaim any * warranties, including, but not limited to, the implied warranties of * merchantability and fitness for a particular purpose. The software * provided hereunder is on an "as is" basis, and Paul Falstad and the * Zsh Development Group have no obligation to provide maintenance, * support, updates, enhancements, or modifications. * */ #include "zsh.mdh" #include "utils.pro" /* name of script being sourced */ /**/ char *scriptname; /* Print an error */ /**/ mod_export void zerr(const char *fmt, const char *str, int num) { if (errflag || noerrs) { if (noerrs < 2) errflag = 1; return; } zwarn(fmt, str, num); errflag = 1; } /**/ mod_export void zerrnam(const char *cmd, const char *fmt, const char *str, int num) { if (errflag || noerrs) return; zwarnnam(cmd, fmt, str, num); errflag = 1; } /**/ mod_export void zwarn(const char *fmt, const char *str, int num) { if (errflag || noerrs) return; if (isatty(2)) trashzle(); /* * scriptname is set when sourcing scripts, so that we get the * correct name instead of the generic name of whatever * program/script is running. It's also set in shell functions, * so test locallevel, too. */ nicezputs((isset(SHINSTDIN) && !locallevel) ? "zsh" : scriptname ? scriptname : argzero, stderr); fputc((unsigned char)':', stderr); zerrmsg(fmt, str, num); } /**/ mod_export void zwarnnam(const char *cmd, const char *fmt, const char *str, int num) { if (errflag || noerrs) return; trashzle(); if (unset(SHINSTDIN) || locallevel) { nicezputs(scriptname ? scriptname : argzero, stderr); fputc((unsigned char)':', stderr); } if (cmd) { nicezputs(cmd, stderr); fputc((unsigned char)':', stderr); } zerrmsg(fmt, str, num); } /**/ void zerrmsg(const char *fmt, const char *str, int num) { if ((unset(SHINSTDIN) || locallevel) && lineno) fprintf(stderr, "%ld: ", (long)lineno); else fputc((unsigned char)' ', stderr); while (*fmt) if (*fmt == '%') { fmt++; switch (*fmt++) { case 's': nicezputs(str, stderr); break; case 'l': { char *s; num = metalen(str, num); s = zhalloc(num + 1); memcpy(s, str, num); s[num] = '\0'; nicezputs(s, stderr); break; } case 'd': fprintf(stderr, "%d", num); break; case '%': putc('%', stderr); break; case 'c': fputs(nicechar(num), stderr); break; case 'e': /* print the corresponding message for this errno */ if (num == EINTR) { fputs("interrupt\n", stderr); errflag = 1; return; } /* If the message is not about I/O problems, it looks better * * if we uncapitalize the first letter of the message */ if (num == EIO) fputs(strerror(num), stderr); else { char *errmsg = strerror(num); fputc(tulower(errmsg[0]), stderr); fputs(errmsg + 1, stderr); } break; } } else { putc(*fmt == Meta ? *++fmt ^ 32 : *fmt, stderr); fmt++; } putc('\n', stderr); fflush(stderr); } /* Output a single character, for the termcap routines. * * This is used instead of putchar since it can be a macro. */ /**/ mod_export int putraw(int c) { putc(c, stdout); return 0; } /* Output a single character, for the termcap routines. */ /**/ mod_export int putshout(int c) { putc(c, shout); return 0; } /* Turn a character into a visible representation thereof. The visible * * string is put together in a static buffer, and this function returns * * a pointer to it. Printable characters stand for themselves, DEL is * * represented as "^?", newline and tab are represented as "\n" and * * "\t", and normal control characters are represented in "^C" form. * * Characters with bit 7 set, if unprintable, are represented as "\M-" * * followed by the visible representation of the character with bit 7 * * stripped off. Tokens are interpreted, rather than being treated as * * literal characters. */ /**/ mod_export char * nicechar(int c) { static char buf[6]; char *s = buf; c &= 0xff; if (isprint(c)) goto done; if (c & 0x80) { if (isset(PRINTEIGHTBIT)) goto done; *s++ = '\\'; *s++ = 'M'; *s++ = '-'; c &= 0x7f; if(isprint(c)) goto done; } if (c == 0x7f) { *s++ = '^'; c = '?'; } else if (c == '\n') { *s++ = '\\'; c = 'n'; } else if (c == '\t') { *s++ = '\\'; c = 't'; } else if (c < 0x20) { *s++ = '^'; c += 0x40; } done: *s++ = c; *s = 0; return buf; } /* Output a string's visible representation. */ #if 0 /**/ void nicefputs(char *s, FILE *f) { for (; *s; s++) fputs(nicechar(STOUC(*s)), f); } #endif /* Return the length of the visible representation of a string. */ /**/ size_t nicestrlen(char *s) { size_t l = 0; for (; *s; s++) l += strlen(nicechar(STOUC(*s))); return l; } /* get a symlink-free pathname for s relative to PWD */ /**/ char * findpwd(char *s) { char *t; if (*s == '/') return xsymlink(s); s = tricat((pwd[1]) ? pwd : "", "/", s); t = xsymlink(s); zsfree(s); return t; } /* Check whether a string contains the * * name of the present directory. */ /**/ int ispwd(char *s) { struct stat sbuf, tbuf; if (stat(unmeta(s), &sbuf) == 0 && stat(".", &tbuf) == 0) if (sbuf.st_dev == tbuf.st_dev && sbuf.st_ino == tbuf.st_ino) return 1; return 0; } static char xbuf[PATH_MAX*2]; /**/ static char ** slashsplit(char *s) { char *t, **r, **q; int t0; if (!*s) return (char **) zshcalloc(sizeof(char **)); for (t = s, t0 = 0; *t; t++) if (*t == '/') t0++; q = r = (char **) zalloc(sizeof(char **) * (t0 + 2)); while ((t = strchr(s, '/'))) { *q++ = ztrduppfx(s, t - s); while (*t == '/') t++; if (!*t) { *q = NULL; return r; } s = t; } *q++ = ztrdup(s); *q = NULL; return r; } /* expands symlinks and .. or . expressions */ /* if flag = 0, only expand .. and . expressions */ /**/ static int xsymlinks(char *s) { char **pp, **opp; char xbuf2[PATH_MAX*2], xbuf3[PATH_MAX*2]; int t0, ret = 0; opp = pp = slashsplit(s); for (; *pp; pp++) { if (!strcmp(*pp, ".")) { zsfree(*pp); continue; } if (!strcmp(*pp, "..")) { char *p; zsfree(*pp); if (!strcmp(xbuf, "/")) continue; p = xbuf + strlen(xbuf); while (*--p != '/'); *p = '\0'; continue; } sprintf(xbuf2, "%s/%s", xbuf, *pp); t0 = readlink(unmeta(xbuf2), xbuf3, PATH_MAX); if (t0 == -1) { strcat(xbuf, "/"); strcat(xbuf, *pp); zsfree(*pp); } else { ret = 1; metafy(xbuf3, t0, META_NOALLOC); if (*xbuf3 == '/') { strcpy(xbuf, ""); xsymlinks(xbuf3 + 1); } else xsymlinks(xbuf3); zsfree(*pp); } } free(opp); return ret; } /* * expand symlinks in s, and remove other weird things: * note that this always expands symlinks. */ /**/ char * xsymlink(char *s) { if (*s != '/') return NULL; *xbuf = '\0'; xsymlinks(s + 1); if (!*xbuf) return ztrdup("/"); return ztrdup(xbuf); } /**/ void print_if_link(char *s) { if (*s == '/') { *xbuf = '\0'; if (xsymlinks(s + 1)) printf(" -> "), zputs(*xbuf ? xbuf : "/", stdout); } } /* print a directory */ /**/ void fprintdir(char *s, FILE *f) { Nameddir d = finddir(s); if (!d) fputs(unmeta(s), f); else { putc('~', f); fputs(unmeta(d->nam), f); fputs(unmeta(s + strlen(d->dir)), f); } } /* Returns the current username. It caches the username * * and uid to try to avoid requerying the password files * * or NIS/NIS+ database. */ /**/ uid_t cached_uid; /**/ char *cached_username; /**/ char * get_username(void) { #ifdef HAVE_GETPWUID struct passwd *pswd; uid_t current_uid; current_uid = getuid(); if (current_uid != cached_uid) { cached_uid = current_uid; zsfree(cached_username); if ((pswd = getpwuid(current_uid))) cached_username = ztrdup(pswd->pw_name); else cached_username = ztrdup(""); } #else /* !HAVE_GETPWUID */ cached_uid = getuid(); #endif /* !HAVE_GETPWUID */ return cached_username; } /* static variables needed by finddir(). */ static char *finddir_full; static Nameddir finddir_last; static int finddir_best; /* ScanFunc used by finddir(). */ /**/ static void finddir_scan(HashNode hn, int flags) { Nameddir nd = (Nameddir) hn; if(nd->diff > finddir_best && !dircmp(nd->dir, finddir_full) && !(nd->flags & ND_NOABBREV)) { finddir_last=nd; finddir_best=nd->diff; } } /* See if a path has a named directory as its prefix. * * If passed a NULL argument, it will invalidate any * * cached information. */ /**/ Nameddir finddir(char *s) { static struct nameddir homenode = { NULL, "", 0, NULL, 0 }; static int ffsz; /* Invalidate directory cache if argument is NULL. This is called * * whenever a node is added to or removed from the hash table, and * * whenever the value of $HOME changes. (On startup, too.) */ if (!s) { homenode.dir = home; homenode.diff = strlen(home); if(homenode.diff==1) homenode.diff = 0; if(!finddir_full) finddir_full = zalloc(ffsz = PATH_MAX); finddir_full[0] = 0; return finddir_last = NULL; } if(!strcmp(s, finddir_full) && *finddir_full) return finddir_last; if(strlen(s) >= ffsz) { free(finddir_full); finddir_full = zalloc(ffsz = strlen(s) * 2); } strcpy(finddir_full, s); finddir_best=0; finddir_last=NULL; finddir_scan((HashNode)&homenode, 0); scanhashtable(nameddirtab, 0, 0, 0, finddir_scan, 0); return finddir_last; } /* add a named directory */ /**/ mod_export void adduserdir(char *s, char *t, int flags, int always) { Nameddir nd; /* We don't maintain a hash table in non-interactive shells. */ if (!interact) return; /* The ND_USERNAME flag means that this possible hash table * * entry is derived from a passwd entry. Such entries are * * subordinate to explicitly generated entries. */ if ((flags & ND_USERNAME) && nameddirtab->getnode2(nameddirtab, s)) return; /* Normal parameter assignments generate calls to this function, * * with always==0. Unless the AUTO_NAME_DIRS option is set, we * * don't let such assignments actually create directory names. * * Instead, a reference to the parameter as a directory name can * * cause the actual creation of the hash table entry. */ if (!always && unset(AUTONAMEDIRS) && !nameddirtab->getnode2(nameddirtab, s)) return; if (!t || *t != '/' || strlen(t) >= PATH_MAX) { /* We can't use this value as a directory, so simply remove * * the corresponding entry in the hash table, if any. */ HashNode hn = nameddirtab->removenode(nameddirtab, s); if(hn) nameddirtab->freenode(hn); return; } /* add the name */ nd = (Nameddir) zshcalloc(sizeof *nd); nd->flags = flags; nd->dir = ztrdup(t); /* The variables PWD and OLDPWD are not to be displayed as ~PWD etc. */ if (!strcmp(s, "PWD") || !strcmp(s, "OLDPWD")) nd->flags |= ND_NOABBREV; nameddirtab->addnode(nameddirtab, ztrdup(s), nd); } /* Get a named directory: this function can cause a directory name * * to be added to the hash table, if it isn't there already. */ /**/ char * getnameddir(char *name) { Param pm; char *str; Nameddir nd; /* Check if it is already in the named directory table */ if ((nd = (Nameddir) nameddirtab->getnode(nameddirtab, name))) return dupstring(nd->dir); /* Check if there is a scalar parameter with this name whose value * * begins with a `/'. If there is, add it to the hash table and * * return the new value. */ if ((pm = (Param) paramtab->getnode(paramtab, name)) && (PM_TYPE(pm->flags) == PM_SCALAR) && (str = getsparam(name)) && *str == '/') { pm->flags |= PM_NAMEDDIR; adduserdir(name, str, 0, 1); return str; } #ifdef HAVE_GETPWNAM { /* Retrieve an entry from the password table/database for this user. */ struct passwd *pw; if ((pw = getpwnam(name))) { char *dir = isset(CHASELINKS) ? xsymlink(pw->pw_dir) : ztrdup(pw->pw_dir); adduserdir(name, dir, ND_USERNAME, 1); str = dupstring(dir); zsfree(dir); return str; } } #endif /* HAVE_GETPWNAM */ /* There are no more possible sources of directory names, so give up. */ return NULL; } /**/ static int dircmp(char *s, char *t) { if (s) { for (; *s == *t; s++, t++) if (!*s) return 0; if (!*s && *t == '/') return 0; } return 1; } /* extra functions to call before displaying the prompt */ /**/ mod_export LinkList prepromptfns; /* the last time we checked mail */ /**/ time_t lastmailcheck; /* the last time we checked the people in the WATCH variable */ /**/ time_t lastwatch; /* do pre-prompt stuff */ /**/ void preprompt(void) { static time_t lastperiodic; LinkNode ln; Eprog prog; int period = getiparam("PERIOD"); int mailcheck = getiparam("MAILCHECK"); /* If NOTIFY is not set, then check for completed * * jobs before we print the prompt. */ if (unset(NOTIFY)) scanjobs(); if (errflag) return; /* If a shell function named "precmd" exists, * * then execute it. */ if ((prog = getshfunc("precmd")) != &dummy_eprog) { /* * Save stopmsg, since user doesn't get a chance to respond * to a list of jobs generated in precmd. */ int osc = sfcontext, osm = stopmsg; sfcontext = SFC_HOOK; doshfunc("precmd", prog, NULL, 0, 1); sfcontext = osc; stopmsg = osm; } if (errflag) return; /* If 1) the parameter PERIOD exists, 2) the shell function * * "periodic" exists, 3) it's been greater than PERIOD since we * * executed "periodic", then execute it now. */ if (period && (time(NULL) > lastperiodic + period) && (prog = getshfunc("periodic")) != &dummy_eprog) { int osc = sfcontext; sfcontext = SFC_HOOK; doshfunc("periodic", prog, NULL, 0, 1); sfcontext = osc; lastperiodic = time(NULL); } if (errflag) return; /* If WATCH is set, then check for the * * specified login/logout events. */ if (watch) { if ((int) difftime(time(NULL), lastwatch) > getiparam("LOGCHECK")) { dowatch(); lastwatch = time(NULL); } } if (errflag) return; /* Check mail */ if (mailcheck && (int) difftime(time(NULL), lastmailcheck) > mailcheck) { char *mailfile; if (mailpath && *mailpath && **mailpath) checkmailpath(mailpath); else { queue_signals(); if ((mailfile = getsparam("MAIL")) && *mailfile) { char *x[2]; x[0] = mailfile; x[1] = NULL; checkmailpath(x); } unqueue_signals(); } lastmailcheck = time(NULL); } /* Some people have claimed that C performs type * * checking, but they were later found to be lying. */ for(ln = firstnode(prepromptfns); ln; ln = nextnode(ln)) (**(void (**) _((void)))getdata(ln))(); } /**/ static void checkmailpath(char **s) { struct stat st; char *v, *u, c; while (*s) { for (v = *s; *v && *v != '?'; v++); c = *v; *v = '\0'; if (c != '?') u = NULL; else u = v + 1; if (**s == 0) { *v = c; zerr("empty MAILPATH component: %s", *s, 0); } else if (mailstat(unmeta(*s), &st) == -1) { if (errno != ENOENT) zerr("%e: %s", *s, errno); } else if (S_ISDIR(st.st_mode)) { LinkList l; DIR *lock = opendir(unmeta(*s)); char buf[PATH_MAX * 2], **arr, **ap; int ct = 1; if (lock) { char *fn; pushheap(); l = newlinklist(); while ((fn = zreaddir(lock, 1)) && !errflag) { if (u) sprintf(buf, "%s/%s?%s", *s, fn, u); else sprintf(buf, "%s/%s", *s, fn); addlinknode(l, dupstring(buf)); ct++; } closedir(lock); ap = arr = (char **) zhalloc(ct * sizeof(char *)); while ((*ap++ = (char *)ugetnode(l))); checkmailpath(arr); popheap(); } } else { if (st.st_size && st.st_atime <= st.st_mtime && st.st_mtime > lastmailcheck) { if (!u) { fprintf(shout, "You have new mail.\n"); fflush(shout); } else { VARARR(char, usav, underscoreused); memcpy(usav, underscore, underscoreused); setunderscore(*s); u = dupstring(u); if (! parsestr(u)) { singsub(&u); zputs(u, shout); fputc('\n', shout); fflush(shout); } setunderscore(usav); } } if (isset(MAILWARNING) && st.st_atime > st.st_mtime && st.st_atime > lastmailcheck && st.st_size) { fprintf(shout, "The mail in %s has been read.\n", unmeta(*s)); fflush(shout); } } *v = c; s++; } } /* This prints the XTRACE prompt. */ /**/ FILE *xtrerr = 0; /**/ void printprompt4(void) { if (!xtrerr) xtrerr = stderr; if (prompt4) { int l, t = opts[XTRACE]; char *s = dupstring(prompt4); opts[XTRACE] = 0; unmetafy(s, &l); s = unmetafy(promptexpand(metafy(s, l, META_NOALLOC), 0, NULL, NULL), &l); opts[XTRACE] = t; fprintf(xtrerr, "%s", s); free(s); } } /**/ mod_export void freestr(void *a) { zsfree(a); } /**/ void gettyinfo(struct ttyinfo *ti) { if (SHTTY != -1) { #ifdef HAVE_TERMIOS_H # ifdef HAVE_TCGETATTR if (tcgetattr(SHTTY, &ti->tio) == -1) # else if (ioctl(SHTTY, TCGETS, &ti->tio) == -1) # endif zerr("bad tcgets: %e", NULL, errno); #else # ifdef HAVE_TERMIO_H ioctl(SHTTY, TCGETA, &ti->tio); # else ioctl(SHTTY, TIOCGETP, &ti->sgttyb); ioctl(SHTTY, TIOCLGET, &ti->lmodes); ioctl(SHTTY, TIOCGETC, &ti->tchars); ioctl(SHTTY, TIOCGLTC, &ti->ltchars); # endif #endif } } /**/ mod_export void settyinfo(struct ttyinfo *ti) { if (SHTTY != -1) { #ifdef HAVE_TERMIOS_H # ifdef HAVE_TCGETATTR # ifndef TCSADRAIN # define TCSADRAIN 1 /* XXX Princeton's include files are screwed up */ # endif tcsetattr(SHTTY, TCSADRAIN, &ti->tio); /* if (tcsetattr(SHTTY, TCSADRAIN, &ti->tio) == -1) */ # else ioctl(SHTTY, TCSETS, &ti->tio); /* if (ioctl(SHTTY, TCSETS, &ti->tio) == -1) */ # endif /* zerr("settyinfo: %e",NULL,errno)*/ ; #else # ifdef HAVE_TERMIO_H ioctl(SHTTY, TCSETA, &ti->tio); # else ioctl(SHTTY, TIOCSETN, &ti->sgttyb); ioctl(SHTTY, TIOCLSET, &ti->lmodes); ioctl(SHTTY, TIOCSETC, &ti->tchars); ioctl(SHTTY, TIOCSLTC, &ti->ltchars); # endif #endif } } /* the default tty state */ /**/ mod_export struct ttyinfo shttyinfo; /* != 0 if we need to call resetvideo() */ /**/ mod_export int resetneeded; #ifdef TIOCGWINSZ /* window size changed */ /**/ mod_export int winchanged; #endif static int adjustlines(int signalled) { int oldlines = lines; #ifdef TIOCGWINSZ if (signalled || lines <= 0) lines = shttyinfo.winsize.ws_row; else shttyinfo.winsize.ws_row = lines; #endif /* TIOCGWINSZ */ if (lines <= 0) { DPUTS(signalled, "BUG: Impossible TIOCGWINSZ rows"); lines = tclines > 0 ? tclines : 24; } if (lines > 2) termflags &= ~TERM_SHORT; else termflags |= TERM_SHORT; return (lines != oldlines); } static int adjustcolumns(int signalled) { int oldcolumns = columns; #ifdef TIOCGWINSZ if (signalled || columns <= 0) columns = shttyinfo.winsize.ws_col; else shttyinfo.winsize.ws_col = columns; #endif /* TIOCGWINSZ */ if (columns <= 0) { DPUTS(signalled, "BUG: Impossible TIOCGWINSZ cols"); columns = tccolumns > 0 ? tccolumns : 80; } if (columns > 2) termflags &= ~TERM_NARROW; else termflags |= TERM_NARROW; return (columns != oldcolumns); } /* check the size of the window and adjust if necessary. * * The value of from: * * 0: called from update_job or setupvals * * 1: called from the SIGWINCH handler * * 2: called from the LINES parameter callback * * 3: called from the COLUMNS parameter callback */ /**/ void adjustwinsize(int from) { static int getwinsz = 1; int ttyrows = shttyinfo.winsize.ws_row; int ttycols = shttyinfo.winsize.ws_col; int resetzle = 0; if (getwinsz || from == 1) { #ifdef TIOCGWINSZ if (SHTTY == -1) return; if (ioctl(SHTTY, TIOCGWINSZ, (char *)&shttyinfo.winsize) == 0) { resetzle = (ttyrows != shttyinfo.winsize.ws_row || ttycols != shttyinfo.winsize.ws_col); if (from == 0 && resetzle && ttyrows && ttycols) from = 1; /* Signal missed while a job owned the tty? */ ttyrows = shttyinfo.winsize.ws_row; ttycols = shttyinfo.winsize.ws_col; } else { /* Set to unknown on failure */ shttyinfo.winsize.ws_row = 0; shttyinfo.winsize.ws_col = 0; resetzle = 1; } #else resetzle = from == 1; #endif /* TIOCGWINSZ */ } /* else return; */ switch (from) { case 0: case 1: getwinsz = 0; /* Calling setiparam() here calls this function recursively, but * * because we've already called adjustlines() and adjustcolumns() * * here, recursive calls are no-ops unless a signal intervenes. * * The commented "else return;" above might be a safe shortcut, * * but I'm concerned about what happens on race conditions; e.g., * * suppose the user resizes his xterm during `eval $(resize)'? */ if (adjustlines(from) && zgetenv("LINES")) setiparam("LINES", lines); if (adjustcolumns(from) && zgetenv("COLUMNS")) setiparam("COLUMNS", columns); getwinsz = 1; break; case 2: resetzle = adjustlines(0); break; case 3: resetzle = adjustcolumns(0); break; } #ifdef TIOCGWINSZ if (interact && from >= 2 && (shttyinfo.winsize.ws_row != ttyrows || shttyinfo.winsize.ws_col != ttycols)) { /* shttyinfo.winsize is already set up correctly */ ioctl(SHTTY, TIOCSWINSZ, (char *)&shttyinfo.winsize); } #endif /* TIOCGWINSZ */ if (zleactive && resetzle) { #ifdef TIOCGWINSZ winchanged = #endif /* TIOCGWINSZ */ resetneeded = 1; zrefresh(); } } /* Move a fd to a place >= 10 and mark the new fd in fdtable. If the fd * * is already >= 10, it is not moved. If it is invalid, -1 is returned. */ /**/ mod_export int movefd(int fd) { if(fd != -1 && fd < 10) { #ifdef F_DUPFD int fe = fcntl(fd, F_DUPFD, 10); #else int fe = movefd(dup(fd)); #endif zclose(fd); fd = fe; } if(fd != -1) { if (fd > max_zsh_fd) { while (fd >= fdtable_size) fdtable = zrealloc(fdtable, (fdtable_size *= 2)); max_zsh_fd = fd; } fdtable[fd] = 1; } return fd; } /* Move fd x to y. If x == -1, fd y is closed. */ /**/ mod_export void redup(int x, int y) { if(x < 0) zclose(y); else if (x != y) { while (y >= fdtable_size) fdtable = zrealloc(fdtable, (fdtable_size *= 2)); dup2(x, y); if ((fdtable[y] = fdtable[x]) && y > max_zsh_fd) max_zsh_fd = y; zclose(x); } } /* Close the given fd, and clear it from fdtable. */ /**/ mod_export int zclose(int fd) { if (fd >= 0) { fdtable[fd] = 0; while (max_zsh_fd > 0 && !fdtable[max_zsh_fd]) max_zsh_fd--; if (fd == coprocin) coprocin = -1; if (fd == coprocout) coprocout = -1; } return close(fd); } /* Get a file name relative to $TMPPREFIX which * * is unique, for use as a temporary file. */ /**/ mod_export char * gettempname(void) { char *s, *ret; queue_signals(); if (!(s = getsparam("TMPPREFIX"))) s = DEFAULT_TMPPREFIX; #ifdef HAVE__MKTEMP /* Zsh uses mktemp() safely, so silence the warnings */ ret = ((char *) _mktemp(dyncat(unmeta(s), "XXXXXX"))); #else ret = ((char *) mktemp(dyncat(unmeta(s), "XXXXXX"))); #endif unqueue_signals(); return ret; } /* Check if a string contains a token */ /**/ mod_export int has_token(const char *s) { while(*s) if(itok(*s++)) return 1; return 0; } /* Delete a character in a string */ /**/ mod_export void chuck(char *str) { while ((str[0] = str[1])) str++; } /**/ mod_export int tulower(int c) { c &= 0xff; return (isupper(c) ? tolower(c) : c); } /**/ mod_export int tuupper(int c) { c &= 0xff; return (islower(c) ? toupper(c) : c); } /* copy len chars from t into s, and null terminate */ /**/ void ztrncpy(char *s, char *t, int len) { while (len--) *s++ = *t++; *s = '\0'; } /* copy t into *s and update s */ /**/ mod_export void strucpy(char **s, char *t) { char *u = *s; while ((*u++ = *t++)); *s = u - 1; } /**/ mod_export void struncpy(char **s, char *t, int n) { char *u = *s; while (n--) *u++ = *t++; *s = u; *u = '\0'; } /* Return the number of elements in an array of pointers. * * It doesn't count the NULL pointer at the end. */ /**/ mod_export int arrlen(char **s) { int count; for (count = 0; *s; s++, count++); return count; } /* Skip over a balanced pair of parenthesis. */ /**/ mod_export int skipparens(char inpar, char outpar, char **s) { int level; if (**s != inpar) return -1; for (level = 1; *++*s && level;) if (**s == inpar) ++level; else if (**s == outpar) --level; return level; } /* Convert string to zlong (see zsh.h). This function (without the z) * * is contained in the ANSI standard C library, but a lot of them seem * * to be broken. */ /**/ mod_export zlong zstrtol(const char *s, char **t, int base) { zlong ret = 0; int neg; while (inblank(*s)) s++; if ((neg = (*s == '-'))) s++; else if (*s == '+') s++; if (!base) { if (*s != '0') base = 10; else if (*++s == 'x' || *s == 'X') base = 16, s++; else base = 8; } if (base <= 10) for (; *s >= '0' && *s < ('0' + base); s++) ret = ret * base + *s - '0'; else for (; idigit(*s) || (*s >= 'a' && *s < ('a' + base - 10)) || (*s >= 'A' && *s < ('A' + base - 10)); s++) ret = ret * base + (idigit(*s) ? (*s - '0') : (*s & 0x1f) + 9); if (t) *t = (char *)s; return neg ? -ret : ret; } /**/ int setblock_fd(int turnonblocking, int fd, long *modep) { #ifdef O_NDELAY # ifdef O_NONBLOCK # define NONBLOCK (O_NDELAY|O_NONBLOCK) # else /* !O_NONBLOCK */ # define NONBLOCK O_NDELAY # endif /* !O_NONBLOCK */ #else /* !O_NDELAY */ # ifdef O_NONBLOCK # define NONBLOCK O_NONBLOCK # else /* !O_NONBLOCK */ # define NONBLOCK 0 # endif /* !O_NONBLOCK */ #endif /* !O_NDELAY */ #if NONBLOCK struct stat st; if (!fstat(fd, &st) && !S_ISREG(st.st_mode)) { *modep = fcntl(fd, F_GETFL, 0); if (*modep != -1) { if (!turnonblocking) { /* We want to know if blocking was off */ if ((*modep & NONBLOCK) || !fcntl(fd, F_SETFL, *modep | NONBLOCK)) return 1; } else if ((*modep & NONBLOCK) && !fcntl(fd, F_SETFL, *modep & ~NONBLOCK)) { /* Here we want to know if the state changed */ return 1; } } } else #endif /* NONBLOCK */ *modep = -1; return 0; #undef NONBLOCK } /**/ int setblock_stdin(void) { long mode; return setblock_fd(1, 0, &mode); } /* * Check for pending input on fd. If polltty is set, we may need to * use termio to look for input. As a final resort, go to non-blocking * input and try to read a character, which in this case will be * returned in *readchar. * * Note that apart from setting (and restoring) non-blocking input, * this function does not change the input mode. The calling function * should have set cbreak mode if necessary. */ /**/ mod_export int read_poll(int fd, int *readchar, int polltty) { int ret = -1; long mode = -1; char c; #ifdef HAVE_SELECT fd_set foofd; struct timeval expire_tv; #else #ifdef FIONREAD int val; #endif #endif #ifdef HAS_TIO struct ttyinfo ti; #endif #if defined(HAS_TIO) && !defined(__CYGWIN__) /* * Under Solaris, at least, reading from the terminal in non-canonical * mode requires that we use the VMIN mechanism to poll. Any attempt * to check any other way, or to set the terminal to non-blocking mode * and poll that way, fails; it will just for canonical mode input. * We should probably use this mechanism if the user has set non-canonical * mode, in which case testing here for isatty() and ~ICANON would be * better than testing whether bin_read() set it, but for now we've got * enough problems. * * Under Cygwin, you won't be surprised to here, this mechanism, * although present, doesn't work, and we *have* to use ordinary * non-blocking reads to find out if there is a character present * in non-canonical mode. * * I am assuming Solaris is nearer the UNIX norm. This is not necessarily * as plausible as it sounds, but it seems the right way to guess. * pws 2000/06/26 */ if (polltty) { gettyinfo(&ti); if ((polltty = ti.tio.c_cc[VMIN])) { ti.tio.c_cc[VMIN] = 0; settyinfo(&ti); } } #else polltty = 0; #endif #ifdef HAVE_SELECT expire_tv.tv_sec = expire_tv.tv_usec = 0; FD_ZERO(&foofd); FD_SET(fd, &foofd); ret = select(fd+1, (SELECT_ARG_2_T) &foofd, NULL, NULL, &expire_tv); #else #ifdef FIONREAD if (ioctl(fd, FIONREAD, (char *) &val) == 0) ret = (val > 0); #endif #endif if (ret < 0) { /* * Final attempt: set non-blocking read and try to read a character. * Praise Bill, this works under Cygwin (nothing else seems to). */ if ((polltty || setblock_fd(0, fd, &mode)) && read(fd, &c, 1) > 0) { *readchar = STOUC(c); ret = 1; } if (mode != -1) fcntl(fd, F_SETFL, mode); } #ifdef HAS_TIO if (polltty) { ti.tio.c_cc[VMIN] = 1; settyinfo(&ti); } #endif return (ret > 0); } /**/ int checkrmall(char *s) { if (!shout) return 1; fprintf(shout, "zsh: sure you want to delete all the files in "); if (*s != '/') { nicezputs(pwd[1] ? unmeta(pwd) : "", shout); fputc('/', shout); } nicezputs(s, shout); if(isset(RMSTARWAIT)) { fputs("? (waiting ten seconds)", shout); fflush(shout); zbeep(); sleep(10); fputc('\n', shout); } fputs(" [yn]? ", shout); fflush(shout); zbeep(); return (getquery("ny", 1) == 'y'); } /**/ int read1char(void) { char c; while (read(SHTTY, &c, 1) != 1) { if (errno != EINTR || errflag || retflag || breaks || contflag) return -1; } return STOUC(c); } /**/ mod_export int noquery(int purge) { int val = 0; char c; #ifdef FIONREAD ioctl(SHTTY, FIONREAD, (char *)&val); if (purge) { for (; val; val--) read(SHTTY, &c, 1); } #endif return val; } /**/ int getquery(char *valid_chars, int purge) { int c, d; int isem = !strcmp(term, "emacs"); attachtty(mypgrp); if (!isem) setcbreak(); if (noquery(purge)) { if (!isem) settyinfo(&shttyinfo); write(SHTTY, "n\n", 2); return 'n'; } while ((c = read1char()) >= 0) { if (c == 'Y') c = 'y'; else if (c == 'N') c = 'n'; if (!valid_chars) break; if (c == '\n') { c = *valid_chars; break; } if (strchr(valid_chars, c)) { write(SHTTY, "\n", 1); break; } zbeep(); if (icntrl(c)) write(SHTTY, "\b \b", 3); write(SHTTY, "\b \b", 3); } if (isem) { if (c != '\n') while ((d = read1char()) >= 0 && d != '\n'); } else { settyinfo(&shttyinfo); if (c != '\n' && !valid_chars) write(SHTTY, "\n", 1); } return c; } static int d; static char *guess, *best; /**/ static void spscan(HashNode hn, int scanflags) { int nd; nd = spdist(hn->nam, guess, (int) strlen(guess) / 4 + 1); if (nd <= d) { best = hn->nam; d = nd; } } /* spellcheck a word */ /* fix s ; if hist is nonzero, fix the history list too */ /**/ mod_export void spckword(char **s, int hist, int cmd, int ask) { char *t, *u; int x; char ic = '\0'; int ne; int preflen = 0; if ((histdone & HISTFLAG_NOEXEC) || **s == '-' || **s == '%') return; if (!strcmp(*s, "in")) return; if (!(*s)[0] || !(*s)[1]) return; if (shfunctab->getnode(shfunctab, *s) || builtintab->getnode(builtintab, *s) || cmdnamtab->getnode(cmdnamtab, *s) || aliastab->getnode(aliastab, *s) || reswdtab->getnode(reswdtab, *s)) return; else if (isset(HASHLISTALL)) { cmdnamtab->filltable(cmdnamtab); if (cmdnamtab->getnode(cmdnamtab, *s)) return; } t = *s; if (*t == Tilde || *t == Equals || *t == String) t++; for (; *t; t++) if (itok(*t)) return; best = NULL; for (t = *s; *t; t++) if (*t == '/') break; if (**s == Tilde && !*t) return; if (**s == String && !*t) { guess = *s + 1; if (*t || !ialpha(*guess)) return; ic = String; d = 100; scanhashtable(paramtab, 1, 0, 0, spscan, 0); } else if (**s == Equals) { if (*t) return; if (hashcmd(guess = *s + 1, pathchecked)) return; d = 100; ic = Equals; scanhashtable(aliastab, 1, 0, 0, spscan, 0); scanhashtable(cmdnamtab, 1, 0, 0, spscan, 0); } else { guess = *s; if (*guess == Tilde || *guess == String) { ic = *guess; if (!*++t) return; guess = dupstring(guess); ne = noerrs; noerrs = 2; singsub(&guess); noerrs = ne; if (!guess) return; preflen = strlen(guess) - strlen(t); } if (access(unmeta(guess), F_OK) == 0) return; if ((u = spname(guess)) != guess) best = u; if (!*t && cmd) { if (hashcmd(guess, pathchecked)) return; d = 100; scanhashtable(reswdtab, 1, 0, 0, spscan, 0); scanhashtable(aliastab, 1, 0, 0, spscan, 0); scanhashtable(shfunctab, 1, 0, 0, spscan, 0); scanhashtable(builtintab, 1, 0, 0, spscan, 0); scanhashtable(cmdnamtab, 1, 0, 0, spscan, 0); } } if (errflag) return; if (best && (int)strlen(best) > 1 && strcmp(best, guess)) { if (ic) { if (preflen) { /* do not correct the result of an expansion */ if (strncmp(guess, best, preflen)) return; /* replace the temporarily expanded prefix with the original */ u = (char *) hcalloc(t - *s + strlen(best + preflen) + 1); strncpy(u, *s, t - *s); strcpy(u + (t - *s), best + preflen); } else { u = (char *) hcalloc(strlen(best) + 2); strcpy(u + 1, best); } best = u; guess = *s; *guess = *best = ztokens[ic - Pound]; } if (ask) { if (noquery(0)) { x = 'n'; } else { char *pptbuf; pptbuf = promptexpand(sprompt, 0, best, guess); zputs(pptbuf, shout); free(pptbuf); fflush(shout); zbeep(); x = getquery("nyae \t", 0); } } else x = 'y'; if (x == 'y' || x == ' ' || x == '\t') { *s = dupstring(best); if (hist) hwrep(best); } else if (x == 'a') { histdone |= HISTFLAG_NOEXEC; } else if (x == 'e') { histdone |= HISTFLAG_NOEXEC | HISTFLAG_RECALL; } if (ic) **s = ic; } } /* * Helper for ztrftime. Called with a pointer to the length left * in the buffer, and a new string length to decrement from that. * Returns 0 if the new length fits, 1 otherwise. We assume a terminating * NUL and return 1 if that doesn't fit. */ /**/ static int ztrftimebuf(int *bufsizeptr, int decr) { if (*bufsizeptr <= decr) return 1; *bufsizeptr -= decr; return 0; } /* * Like the system function, this returns the number of characters * copied, not including the terminating NUL. This may be zero * if the string didn't fit. */ /**/ mod_export int ztrftime(char *buf, int bufsize, char *fmt, struct tm *tm) { int hr12, decr; #ifndef HAVE_STRFTIME static char *astr[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; static char *estr[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; #endif char *origbuf = buf; char tmp[3]; tmp[0] = '%'; tmp[2] = '\0'; while (*fmt) if (*fmt == '%') { fmt++; /* * Assume this format will take up at least two * characters. Not always true, but if that matters * we are so close to the edge it's not a big deal. * Fix up some longer cases specially when we get to them. */ if (ztrftimebuf(&bufsize, 2)) return 0; switch (*fmt++) { case 'd': *buf++ = '0' + tm->tm_mday / 10; *buf++ = '0' + tm->tm_mday % 10; break; case 'e': case 'f': if (tm->tm_mday > 9) *buf++ = '0' + tm->tm_mday / 10; else if (fmt[-1] == 'e') *buf++ = ' '; *buf++ = '0' + tm->tm_mday % 10; break; case 'k': case 'K': if (tm->tm_hour > 9) *buf++ = '0' + tm->tm_hour / 10; else if (fmt[-1] == 'k') *buf++ = ' '; *buf++ = '0' + tm->tm_hour % 10; break; case 'l': case 'L': hr12 = tm->tm_hour % 12; if (hr12 == 0) hr12 = 12; if (hr12 > 9) *buf++ = '1'; else if (fmt[-1] == 'l') *buf++ = ' '; *buf++ = '0' + (hr12 % 10); break; case 'm': *buf++ = '0' + (tm->tm_mon + 1) / 10; *buf++ = '0' + (tm->tm_mon + 1) % 10; break; case 'M': *buf++ = '0' + tm->tm_min / 10; *buf++ = '0' + tm->tm_min % 10; break; case 'S': *buf++ = '0' + tm->tm_sec / 10; *buf++ = '0' + tm->tm_sec % 10; break; case 'y': *buf++ = '0' + (tm->tm_year / 10) % 10; *buf++ = '0' + tm->tm_year % 10; break; case '\0': /* Guard against premature end of string */ *buf++ = '%'; fmt--; break; #ifndef HAVE_STRFTIME case 'a': if (ztrftimebuf(&bufsize, strlen(astr[tm->tm_wday]) - 2)) return 0; strucpy(&buf, astr[tm->tm_wday]); break; case 'b': if (ztrftimebuf(&bufsize, strlen(estr[tm->tm_mon]) - 2)) return 0; strucpy(&buf, estr[tm->tm_mon]); break; case 'p': *buf++ = (tm->tm_hour > 11) ? 'p' : 'a'; *buf++ = 'm'; break; default: *buf++ = '%'; if (fmt[-1] != '%') *buf++ = fmt[-1]; #else default: /* * Remember we've already allowed for two characters * in the accounting in bufsize (but nowhere else). */ *buf = '\0'; tmp[1] = fmt[-1]; if (!strftime(buf, bufsize + 2, tmp, tm)) return 0; decr = strlen(buf); buf += decr; bufsize -= decr - 2; #endif break; } } else { if (ztrftimebuf(&bufsize, 1)) return 0; *buf++ = *fmt++; } *buf = '\0'; return buf - origbuf; } /**/ mod_export char * zjoin(char **arr, int delim, int heap) { int len = 0; char **s, *ret, *ptr; for (s = arr; *s; s++) len += strlen(*s) + 1; if (!len) return heap? "" : ztrdup(""); ptr = ret = (heap ? (char *) hcalloc(len) : (char *) zshcalloc(len)); for (s = arr; *s; s++) { strucpy(&ptr, *s); if (delim) *ptr++ = delim; } ptr[-1] = '\0'; return ret; } /* Split a string containing a colon separated list * * of items into an array of strings. */ /**/ char ** colonsplit(char *s, int uniq) { int ct; char *t, **ret, **ptr, **p; for (t = s, ct = 0; *t; t++) /* count number of colons */ if (*t == ':') ct++; ptr = ret = (char **) zalloc(sizeof(char **) * (ct + 2)); t = s; do { s = t; /* move t to point at next colon */ for (; *t && *t != ':'; t++); if (uniq) for (p = ret; p < ptr; p++) if (strlen(*p) == t - s && ! strncmp(*p, s, t - s)) goto cont; *ptr = (char *) zalloc((t - s) + 1); ztrncpy(*ptr++, s, t - s); cont: ; } while (*t++); *ptr = NULL; return ret; } /**/ static int skipwsep(char **s) { char *t = *s; int i = 0; while (*t && iwsep(*t == Meta ? t[1] ^ 32 : *t)) { if (*t == Meta) t++; t++; i++; } *s = t; return i; } /* see findsep() below for handling of `quote' argument */ /**/ mod_export char ** spacesplit(char *s, int allownull, int heap, int quote) { char *t, **ret, **ptr; int l = sizeof(*ret) * (wordcount(s, NULL, -!allownull) + 1); char *(*dup)(const char *) = (heap ? dupstring : ztrdup); ptr = ret = (heap ? (char **) hcalloc(l) : (char **) zshcalloc(l)); if (quote) { /* * we will be stripping quoted separators by hacking string, * so make sure it's hackable. */ s = dupstring(s); } t = s; skipwsep(&s); if (*s && isep(*s == Meta ? s[1] ^ 32 : *s)) *ptr++ = dup(allownull ? "" : nulstring); else if (!allownull && t != s) *ptr++ = dup(""); while (*s) { if (isep(*s == Meta ? s[1] ^ 32 : *s) || (quote && *s == '\\')) { if (*s == Meta) s++; s++; skipwsep(&s); } t = s; findsep(&s, NULL, quote); if (s > t || allownull) { *ptr = (heap ? (char *) hcalloc((s - t) + 1) : (char *) zshcalloc((s - t) + 1)); ztrncpy(*ptr++, t, s - t); } else *ptr++ = dup(nulstring); t = s; skipwsep(&s); } if (!allownull && t != s) *ptr++ = dup(""); *ptr = NULL; return ret; } /**/ static int findsep(char **s, char *sep, int quote) { /* * *s is the string we are looking along, which will be updated * to the point we have got to. * * sep is a possibly multicharacter separator to look for. If NULL, * use normal separator characters. * * quote is a flag that '\' should not be treated as a separator. * in this case we need to be able to strip the backslash directly * in the string, so the calling function must have sent us something * modifiable. currently this only works for sep == NULL. also in * in this case only, we need to turn \\ into \. */ int i; char *t, *tt; if (!sep) { for (t = *s; *t; t++) { if (quote && *t == '\\' && (isep(t[1] == Meta ? (t[2] ^ 32) : t[1]) || t[1] == '\\')) { chuck(t); if (*t == Meta) t++; continue; } if (*t == Meta) { if (isep(t[1] ^ 32)) break; t++; } else if (isep(*t)) break; } i = t - *s; *s = t; return i; } if (!sep[0]) { if (**s) { if (**s == Meta) *s += 2; else ++*s; return 1; } return -1; } for (i = 0; **s; i++) { for (t = sep, tt = *s; *t && *tt && *t == *tt; t++, tt++); if (!*t) return i; if (*(*s)++ == Meta) { #ifdef DEBUG if (! *(*s)++) fprintf(stderr, "BUG: unexpected end of string in findsep()\n"); #else (*s)++; #endif } } return -1; } /**/ char * findword(char **s, char *sep) { char *r, *t; int sl; if (!**s) return NULL; if (sep) { sl = strlen(sep); r = *s; while (! findsep(s, sep, 0)) { r = *s += sl; } return r; } for (t = *s; *t; t++) { if (*t == Meta) { if (! isep(t[1] ^ 32)) break; t++; } else if (! isep(*t)) break; } *s = t; findsep(s, sep, 0); return t; } /**/ int wordcount(char *s, char *sep, int mul) { int r, sl, c; if (sep) { r = 1; sl = strlen(sep); for (; (c = findsep(&s, sep, 0)) >= 0; s += sl) if ((c && *(s + sl)) || mul) r++; } else { char *t = s; r = 0; if (mul <= 0) skipwsep(&s); if ((*s && isep(*s == Meta ? s[1] ^ 32 : *s)) || (mul < 0 && t != s)) r++; for (; *s; r++) { if (isep(*s == Meta ? s[1] ^ 32 : *s)) { if (*s == Meta) s++; s++; if (mul <= 0) skipwsep(&s); } findsep(&s, NULL, 0); t = s; if (mul <= 0) skipwsep(&s); } if (mul < 0 && t != s) r++; } return r; } /**/ mod_export char * sepjoin(char **s, char *sep, int heap) { char *r, *p, **t; int l, sl; char sepbuf[3]; if (!*s) return heap ? "" : ztrdup(""); if (!sep) { p = sep = sepbuf; if (ifs) { *p++ = *ifs; *p++ = *ifs == Meta ? ifs[1] ^ 32 : '\0'; } else { *p++ = ' '; } *p = '\0'; } sl = strlen(sep); for (t = s, l = 1 - sl; *t; l += strlen(*t) + sl, t++); r = p = (heap ? (char *) hcalloc(l) : (char *) zshcalloc(l)); t = s; while (*t) { strucpy(&p, *t); if (*++t) strucpy(&p, sep); } *p = '\0'; return r; } /**/ char ** sepsplit(char *s, char *sep, int allownull, int heap) { int n, sl; char *t, *tt, **r, **p; if (!sep) return spacesplit(s, allownull, heap, 0); sl = strlen(sep); n = wordcount(s, sep, 1); r = p = (heap ? (char **) hcalloc((n + 1) * sizeof(char *)) : (char **) zshcalloc((n + 1) * sizeof(char *))); for (t = s; n--;) { tt = t; findsep(&t, sep, 0); *p = (heap ? (char *) hcalloc(t - tt + 1) : (char *) zshcalloc(t - tt + 1)); strncpy(*p, tt, t - tt); (*p)[t - tt] = '\0'; p++; t += sl; } *p = NULL; return r; } /* Get the definition of a shell function */ /**/ mod_export Eprog getshfunc(char *nam) { Shfunc shf; if (!(shf = (Shfunc) shfunctab->getnode(shfunctab, nam))) return &dummy_eprog; return shf->funcdef; } /**/ char ** mkarray(char *s) { char **t = (char **) zalloc((s) ? (2 * sizeof s) : (sizeof s)); if ((*t = s)) t[1] = NULL; return t; } /**/ mod_export void zbeep(void) { char *vb; queue_signals(); if ((vb = getsparam("ZBEEP"))) { int len; vb = getkeystring(vb, &len, 2, NULL); write(SHTTY, vb, len); } else if (isset(BEEP)) write(SHTTY, "\07", 1); unqueue_signals(); } /**/ mod_export void freearray(char **s) { char **t = s; DPUTS(!s, "freearray() with zero argument"); while (*s) zsfree(*s++); free(t); } /**/ int equalsplit(char *s, char **t) { for (; *s && *s != '='; s++); if (*s == '=') { *s++ = '\0'; *t = s; return 1; } return 0; } /* the ztypes table */ /**/ mod_export short int typtab[256]; /* initialize the ztypes table */ /**/ void inittyptab(void) { int t0; char *s; for (t0 = 0; t0 != 256; t0++) typtab[t0] = 0; for (t0 = 0; t0 != 32; t0++) typtab[t0] = typtab[t0 + 128] = ICNTRL; typtab[127] = ICNTRL; for (t0 = '0'; t0 <= '9'; t0++) typtab[t0] = IDIGIT | IALNUM | IWORD | IIDENT | IUSER; for (t0 = 'a'; t0 <= 'z'; t0++) typtab[t0] = typtab[t0 - 'a' + 'A'] = IALPHA | IALNUM | IIDENT | IUSER | IWORD; for (t0 = 0240; t0 != 0400; t0++) typtab[t0] = IALPHA | IALNUM | IIDENT | IUSER | IWORD; typtab['_'] = IIDENT | IUSER; typtab['-'] = IUSER; typtab[' '] |= IBLANK | INBLANK; typtab['\t'] |= IBLANK | INBLANK; typtab['\n'] |= INBLANK; typtab['\0'] |= IMETA; typtab[STOUC(Meta) ] |= IMETA; typtab[STOUC(Marker)] |= IMETA; for (t0 = (int)STOUC(Pound); t0 <= (int)STOUC(Nularg); t0++) typtab[t0] |= ITOK | IMETA; for (s = ifs ? ifs : DEFAULT_IFS; *s; s++) { if (inblank(*s)) { if (s[1] == *s) s++; else typtab[STOUC(*s)] |= IWSEP; } typtab[STOUC(*s == Meta ? *++s ^ 32 : *s)] |= ISEP; } for (s = wordchars ? wordchars : DEFAULT_WORDCHARS; *s; s++) typtab[STOUC(*s == Meta ? *++s ^ 32 : *s)] |= IWORD; for (s = SPECCHARS; *s; s++) typtab[STOUC(*s)] |= ISPECIAL; if (isset(BANGHIST) && bangchar && interact && isset(SHINSTDIN)) typtab[bangchar] |= ISPECIAL; } /**/ mod_export char ** arrdup(char **s) { char **x, **y; y = x = (char **) zhalloc(sizeof(char *) * (arrlen(s) + 1)); while ((*x++ = dupstring(*s++))); return y; } /**/ mod_export char ** zarrdup(char **s) { char **x, **y; y = x = (char **) zalloc(sizeof(char *) * (arrlen(s) + 1)); while ((*x++ = ztrdup(*s++))); return y; } /**/ static char * spname(char *oldname) { char *p, spnameguess[PATH_MAX + 1], spnamebest[PATH_MAX + 1]; static char newname[PATH_MAX + 1]; char *new = newname, *old; int bestdist = 200, thisdist; old = oldname; for (;;) { while (*old == '/') *new++ = *old++; *new = '\0'; if (*old == '\0') return newname; p = spnameguess; for (; *old != '/' && *old != '\0'; old++) if (p < spnameguess + PATH_MAX) *p++ = *old; *p = '\0'; if ((thisdist = mindist(newname, spnameguess, spnamebest)) >= 3) { if (bestdist < 3) { strcpy(new, spnameguess); strcat(new, old); return newname; } else return NULL; } else bestdist = thisdist; for (p = spnamebest; (*new = *p++);) new++; } } /**/ static int mindist(char *dir, char *mindistguess, char *mindistbest) { int mindistd, nd; DIR *dd; char *fn; char buf[PATH_MAX]; if (dir[0] == '\0') dir = "."; mindistd = 100; sprintf(buf, "%s/%s", dir, mindistguess); if (access(unmeta(buf), F_OK) == 0) { strcpy(mindistbest, mindistguess); return 0; } if (!(dd = opendir(unmeta(dir)))) return mindistd; while ((fn = zreaddir(dd, 0))) { nd = spdist(fn, mindistguess, (int)strlen(mindistguess) / 4 + 1); if (nd <= mindistd) { strcpy(mindistbest, fn); mindistd = nd; if (mindistd == 0) break; } } closedir(dd); return mindistd; } /**/ static int spdist(char *s, char *t, int thresh) { char *p, *q; const char qwertykeymap[] = "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\ \t1234567890-=\t\ \tqwertyuiop[]\t\ \tasdfghjkl;'\n\t\ \tzxcvbnm,./\t\t\t\ \n\n\n\n\n\n\n\n\n\n\n\n\n\n\ \t!@#$%^&*()_+\t\ \tQWERTYUIOP{}\t\ \tASDFGHJKL:\"\n\t\ \tZXCVBNM<>?\n\n\t\ \n\n\n\n\n\n\n\n\n\n\n\n\n\n"; const char dvorakkeymap[] = "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\ \t1234567890[]\t\ \t',.pyfgcrl/=\t\ \taoeuidhtns-\n\t\ \t;qjkxbmwvz\t\t\t\ \n\n\n\n\n\n\n\n\n\n\n\n\n\n\ \t!@#$%^&*(){}\t\ \t\"<>PYFGCRL?+\t\ \tAOEUIDHTNS_\n\t\ \t:QJKXBMWVZ\n\n\t\ \n\n\n\n\n\n\n\n\n\n\n\n\n\n"; const char *keymap; if ( isset( DVORAK ) ) keymap = dvorakkeymap; else keymap = qwertykeymap; if (!strcmp(s, t)) return 0; /* any number of upper/lower mistakes allowed (dist = 1) */ for (p = s, q = t; *p && tulower(*p) == tulower(*q); p++, q++); if (!*p && !*q) return 1; if (!thresh) return 200; for (p = s, q = t; *p && *q; p++, q++) if (*p == *q) continue; /* don't consider "aa" transposed, ash */ else if (p[1] == q[0] && q[1] == p[0]) /* transpositions */ return spdist(p + 2, q + 2, thresh - 1) + 1; else if (p[1] == q[0]) /* missing letter */ return spdist(p + 1, q + 0, thresh - 1) + 2; else if (p[0] == q[1]) /* missing letter */ return spdist(p + 0, q + 1, thresh - 1) + 2; else if (*p != *q) break; if ((!*p && strlen(q) == 1) || (!*q && strlen(p) == 1)) return 2; for (p = s, q = t; *p && *q; p++, q++) if (p[0] != q[0] && p[1] == q[1]) { int t0; char *z; /* mistyped letter */ if (!(z = strchr(keymap, p[0])) || *z == '\n' || *z == '\t') return spdist(p + 1, q + 1, thresh - 1) + 1; t0 = z - keymap; if (*q == keymap[t0 - 15] || *q == keymap[t0 - 14] || *q == keymap[t0 - 13] || *q == keymap[t0 - 1] || *q == keymap[t0 + 1] || *q == keymap[t0 + 13] || *q == keymap[t0 + 14] || *q == keymap[t0 + 15]) return spdist(p + 1, q + 1, thresh - 1) + 2; return 200; } else if (*p != *q) break; return 200; } /* set cbreak mode, or the equivalent */ /**/ void setcbreak(void) { struct ttyinfo ti; ti = shttyinfo; #ifdef HAS_TIO ti.tio.c_lflag &= ~ICANON; ti.tio.c_cc[VMIN] = 1; ti.tio.c_cc[VTIME] = 0; #else ti.sgttyb.sg_flags |= CBREAK; #endif settyinfo(&ti); } /* give the tty to some process */ /**/ mod_export void attachtty(pid_t pgrp) { static int ep = 0; if (jobbing) { #ifdef HAVE_TCSETPGRP if (SHTTY != -1 && tcsetpgrp(SHTTY, pgrp) == -1 && !ep) #else # if ardent if (SHTTY != -1 && setpgrp() == -1 && !ep) # else int arg = pgrp; if (SHTTY != -1 && ioctl(SHTTY, TIOCSPGRP, &arg) == -1 && !ep) # endif #endif { if (pgrp != mypgrp && kill(-pgrp, 0) == -1) attachtty(mypgrp); else { if (errno != ENOTTY) { zwarn("can't set tty pgrp: %e", NULL, errno); fflush(stderr); } opts[MONITOR] = 0; ep = 1; } } } } /* get the process group associated with the tty */ /**/ pid_t gettygrp(void) { pid_t arg; if (SHTTY == -1) return -1; #ifdef HAVE_TCSETPGRP arg = tcgetpgrp(SHTTY); #else ioctl(SHTTY, TIOCGPGRP, &arg); #endif return arg; } /* Return the output baudrate */ #ifdef HAVE_SELECT /**/ long getbaudrate(struct ttyinfo *shttyinfo) { long speedcode; #ifdef HAS_TIO # if defined(HAVE_TCGETATTR) && defined(HAVE_TERMIOS_H) speedcode = cfgetospeed(&shttyinfo->tio); # else speedcode = shttyinfo->tio.c_cflag & CBAUD; # endif #else speedcode = shttyinfo->sgttyb.sg_ospeed; #endif switch (speedcode) { case B0: return (0L); case B50: return (50L); case B75: return (75L); case B110: return (110L); case B134: return (134L); case B150: return (150L); case B200: return (200L); case B300: return (300L); case B600: return (600L); #ifdef _B900 case _B900: return (900L); #endif case B1200: return (1200L); case B1800: return (1800L); case B2400: return (2400L); #ifdef _B3600 case _B3600: return (3600L); #endif case B4800: return (4800L); #ifdef _B7200 case _B7200: return (7200L); #endif case B9600: return (9600L); #ifdef B19200 case B19200: return (19200L); #else # ifdef EXTA case EXTA: return (19200L); # endif #endif #ifdef B38400 case B38400: return (38400L); #else # ifdef EXTB case EXTB: return (38400L); # endif #endif #ifdef B57600 case B57600: return (57600L); #endif #ifdef B115200 case B115200: return (115200L); #endif #ifdef B230400 case B230400: return (230400L); #endif #ifdef B460800 case B460800: return (460800L); #endif default: if (speedcode >= 100) return speedcode; break; } return (0L); } #endif /* Escape tokens and null characters. Buf is the string which should be * * escaped. len is the length of the string. If len is -1, buf should be * * null terminated. If len is non-negative and the third paramerer is not * * META_DUP, buf should point to an at least len+1 long memory area. The * * return value points to the quoted string. If the given string does not * * contain any special character which should be quoted and the third * * parameter is not META_(HEAP|)DUP, buf is returned unchanged (a * * terminating null character is appended to buf if necessary). Otherwise * * the third `heap' argument determines the method used to allocate space * * for the result. It can have the following values: * * META_REALLOC: use zrealloc on buf * * META_HREALLOC: use hrealloc on buf * * META_USEHEAP: get memory from the heap. This leaves buf unchanged. * * META_NOALLOC: buf points to a memory area which is long enough to hold * * the quoted form, just quote it and return buf. * * META_STATIC: store the quoted string in a static area. The original * * string should be at most PATH_MAX long. * * META_ALLOC: allocate memory for the new string with zalloc(). * * META_DUP: leave buf unchanged and allocate space for the return * * value even if buf does not contains special characters * * META_HEAPDUP: same as META_DUP, but uses the heap */ /**/ mod_export char * metafy(char *buf, int len, int heap) { int meta = 0; char *t, *p, *e; static char mbuf[PATH_MAX*2+1]; if (len == -1) { for (e = buf, len = 0; *e; len++) if (imeta(*e++)) meta++; } else for (e = buf; e < buf + len;) if (imeta(*e++)) meta++; if (meta || heap == META_DUP || heap == META_HEAPDUP) { switch (heap) { case META_REALLOC: buf = zrealloc(buf, len + meta + 1); break; case META_HREALLOC: buf = hrealloc(buf, len, len + meta + 1); break; case META_ALLOC: case META_DUP: buf = memcpy(zalloc(len + meta + 1), buf, len); break; case META_USEHEAP: case META_HEAPDUP: buf = memcpy(zhalloc(len + meta + 1), buf, len); break; case META_STATIC: #ifdef DEBUG if (len > PATH_MAX) { fprintf(stderr, "BUG: len = %d > PATH_MAX in metafy\n", len); fflush(stderr); } #endif buf = memcpy(mbuf, buf, len); break; #ifdef DEBUG case META_NOALLOC: break; default: fprintf(stderr, "BUG: metafy called with invalid heap value\n"); fflush(stderr); break; #endif } p = buf + len; e = t = buf + len + meta; while (meta) { if (imeta(*--t = *--p)) { *t-- ^= 32; *t = Meta; meta--; } } } *e = '\0'; return buf; } /**/ mod_export char * unmetafy(char *s, int *len) { char *p, *t; for (p = s; *p && *p != Meta; p++); for (t = p; (*t = *p++);) if (*t++ == Meta) t[-1] = *p++ ^ 32; if (len) *len = t - s; return s; } /* Return the character length of a metafied substring, given the * * unmetafied substring length. */ /**/ mod_export int metalen(const char *s, int len) { int mlen = len; while (len--) { if (*s++ == Meta) { mlen++; s++; } } return mlen; } /* This function converts a zsh internal string to a form which can be * * passed to a system call as a filename. The result is stored in a * * single static area. NULL returned if the result is longer than * * 4 * PATH_MAX. */ /**/ mod_export char * unmeta(const char *file_name) { static char fn[4 * PATH_MAX]; char *p; const char *t; for (t = file_name, p = fn; *t && p < fn + 4 * PATH_MAX - 1; p++) if ((*p = *t++) == Meta) *p = *t++ ^ 32; if (*t) return NULL; if (p - fn == t - file_name) return (char *) file_name; *p = '\0'; return fn; } /* Unmetafy and compare two strings, with unsigned characters. * * "a\0" sorts after "a". */ /**/ int ztrcmp(unsigned char const *s1, unsigned char const *s2) { int c1, c2; while(*s1 && *s1 == *s2) { s1++; s2++; } if(!(c1 = *s1)) c1 = -1; else if(c1 == STOUC(Meta)) c1 = *++s1 ^ 32; if(!(c2 = *s2)) c2 = -1; else if(c2 == STOUC(Meta)) c2 = *++s2 ^ 32; if(c1 == c2) return 0; else if(c1 < c2) return -1; else return 1; } /* Return zero if the metafied string s and the non-metafied, * * len-long string r are the same. Return -1 if r is a prefix * * of s. Return 1 if r is the lowercase version of s. Return * * 2 is r is the lowercase prefix of s and return 3 otherwise. */ /**/ mod_export int metadiffer(char const *s, char const *r, int len) { int l = len; while (l-- && *s && *r++ == (*s == Meta ? *++s ^ 32 : *s)) s++; if (*s && l < 0) return -1; if (l < 0) return 0; if (!*s) return 3; s -= len - l - 1; r -= len - l; while (len-- && *s && *r++ == tulower(*s == Meta ? *++s ^ 32 : *s)) s++; if (*s && len < 0) return 2; if (len < 0) return 1; return 3; } /* Return the unmetafied length of a metafied string. */ /**/ mod_export int ztrlen(char const *s) { int l; for (l = 0; *s; l++) if (*s++ == Meta) { #ifdef DEBUG if (! *s) fprintf(stderr, "BUG: unexpected end of string in ztrlen()\n"); else #endif s++; } return l; } /* Subtract two pointers in a metafied string. */ /**/ mod_export int ztrsub(char const *t, char const *s) { int l = t - s; while (s != t) if (*s++ == Meta) { #ifdef DEBUG if (! *s || s == t) fprintf(stderr, "BUG: substring ends in the middle of a metachar in ztrsub()\n"); else #endif s++; l--; } return l; } /**/ mod_export char * zreaddir(DIR *dir, int ignoredots) { struct dirent *de; do { de = readdir(dir); if(!de) return NULL; } while(ignoredots && de->d_name[0] == '.' && (!de->d_name[1] || (de->d_name[1] == '.' && !de->d_name[2]))); return metafy(de->d_name, -1, META_STATIC); } /* Unmetafy and output a string. Tokens are skipped. */ /**/ mod_export int zputs(char const *s, FILE *stream) { int c; while (*s) { if (*s == Meta) c = *++s ^ 32; else if(itok(*s)) { s++; continue; } else c = *s; s++; if (fputc(c, stream) < 0) return EOF; } return 0; } /* Create a visibly-represented duplicate of a string. */ /**/ static char * nicedup(char const *s, int heap) { int c, len = strlen(s) * 5; VARARR(char, buf, len); char *p = buf, *n; while ((c = *s++)) { if (itok(c)) { if (c <= Comma) c = ztokens[c - Pound]; else continue; } if (c == Meta) c = *s++ ^ 32; n = nicechar(c); while(*n) *p++ = *n++; } return metafy(buf, p - buf, (heap ? META_HEAPDUP : META_DUP)); } /**/ mod_export char * niceztrdup(char const *s) { return nicedup(s, 0); } /**/ mod_export char * nicedupstring(char const *s) { return nicedup(s, 1); } /* Unmetafy and output a string, displaying special characters readably. */ /**/ mod_export int nicezputs(char const *s, FILE *stream) { int c; while ((c = *s++)) { if (itok(c)) { if (c <= Comma) c = ztokens[c - Pound]; else continue; } if (c == Meta) c = *s++ ^ 32; if(fputs(nicechar(c), stream) < 0) return EOF; } return 0; } /* Return the length of the visible representation of a metafied string. */ /**/ mod_export size_t niceztrlen(char const *s) { size_t l = 0; int c; while ((c = *s++)) { if (itok(c)) { if (c <= Comma) c = ztokens[c - Pound]; else continue; } if (c == Meta) c = *s++ ^ 32; l += strlen(nicechar(STOUC(c))); } return l; } /* check for special characters in the string */ /**/ mod_export int hasspecial(char const *s) { for (; *s; s++) if (ispecial(*s == Meta ? *++s ^ 32 : *s)) return 1; return 0; } /* Quote the string s and return the result. If e is non-zero, the * * pointer it points to may point to a position in s and in e the position * * of the corresponding character in the quoted string is returned. * * The last argument should be zero if this is to be used outside a string, * * one if it is to be quoted for the inside of a single quoted string, and * * two if it is for the inside of double quoted string. * * The string may be metafied and contain tokens. */ /**/ mod_export char * bslashquote(const char *s, char **e, int instring) { const char *u, *tt; char *v; char *buf = hcalloc(4 * strlen(s) + 1); int sf = 0; tt = v = buf; u = s; for (; *u; u++) { if (e && *e == u) *e = v, sf = 1; if (instring == 3) { int c = *u; if (c == Meta) { c = *++u ^ 32; } c &= 0xff; if(isprint(c)) { switch (c) { case '\\': case '\'': *v++ = '\\'; *v++ = c; break; default: if(imeta(c)) { *v++ = Meta; *v++ = c ^ 32; } else { if (isset(BANGHIST) && c == bangchar) { *v++ = '\\'; } *v++ = c; } break; } } else { switch (c) { case '\0': *v++ = '\\'; *v++ = '0'; if ('0' <= u[1] && u[1] <= '7') { *v++ = '0'; *v++ = '0'; } break; case '\007': *v++ = '\\'; *v++ = 'a'; break; case '\b': *v++ = '\\'; *v++ = 'b'; break; case '\f': *v++ = '\\'; *v++ = 'f'; break; case '\n': *v++ = '\\'; *v++ = 'n'; break; case '\r': *v++ = '\\'; *v++ = 'r'; break; case '\t': *v++ = '\\'; *v++ = 't'; break; case '\v': *v++ = '\\'; *v++ = 'v'; break; default: *v++ = '\\'; *v++ = '0' + ((c >> 6) & 7); *v++ = '0' + ((c >> 3) & 7); *v++ = '0' + (c & 7); break; } } continue; } else if (*u == Tick || *u == Qtick) { char c = *u++; *v++ = c; while (*u && *u != c) *v++ = *u++; *v++ = c; if (!*u) u--; continue; } else if ((*u == String || *u == Qstring) && (u[1] == Inpar || u[1] == Inbrack || u[1] == Inbrace)) { char c = (u[1] == Inpar ? Outpar : (u[1] == Inbrace ? Outbrace : Outbrack)); char beg = *u; int level = 0; *v++ = *u++; *v++ = *u++; while (*u && (*u != c || level)) { if (*u == beg) level++; else if (*u == c) level--; *v++ = *u++; } if (*u) *v++ = *u; else u--; continue; } else if (ispecial(*u) && ((*u != '=' && *u != '~') || u == s || (isset(MAGICEQUALSUBST) && (u[-1] == '=' || u[-1] == ':')) || (*u == '~' && isset(EXTENDEDGLOB))) && (!instring || (isset(BANGHIST) && *u == (char)bangchar && instring != 1) || (instring == 2 && (*u == '$' || *u == '`' || *u == '\"' || *u == '\\')) || (instring == 1 && *u == '\''))) { if (*u == '\n' || (instring == 1 && *u == '\'')) { if (unset(RCQUOTES)) { *v++ = '\''; if (*u == '\'') *v++ = '\\'; *v++ = *u; *v++ = '\''; } else if (*u == '\n') *v++ = '"', *v++ = '\n', *v++ = '"'; else *v++ = '\'', *v++ = '\''; continue; } else *v++ = '\\'; } if(*u == Meta) *v++ = *u++; *v++ = *u; } *v = '\0'; if (e && *e == u) *e = v, sf = 1; DPUTS(e && !sf, "BUG: Wild pointer *e in bslashquote()"); return buf; } /* Unmetafy and output a string, quoted if it contains special characters. */ /**/ mod_export int quotedzputs(char const *s, FILE *stream) { int inquote = 0, c; /* check for empty string */ if(!*s) return fputs("''", stream); if (!hasspecial(s)) return zputs(s, stream); if (isset(RCQUOTES)) { /* use rc-style quotes-within-quotes for the whole string */ if(fputc('\'', stream) < 0) return EOF; while(*s) { if (*s == Meta) c = *++s ^ 32; else c = *s; s++; if (c == '\'') { if(fputc('\'', stream) < 0) return EOF; } else if(c == '\n' && isset(CSHJUNKIEQUOTES)) { if(fputc('\\', stream) < 0) return EOF; } if(fputc(c, stream) < 0) return EOF; } if(fputc('\'', stream) < 0) return EOF; } else { /* use Bourne-style quoting, avoiding empty quoted strings */ while(*s) { if (*s == Meta) c = *++s ^ 32; else c = *s; s++; if (c == '\'') { if(inquote) { if(fputc('\'', stream) < 0) return EOF; inquote=0; } if(fputs("\\'", stream) < 0) return EOF; } else { if (!inquote) { if(fputc('\'', stream) < 0) return EOF; inquote=1; } if(c == '\n' && isset(CSHJUNKIEQUOTES)) { if(fputc('\\', stream) < 0) return EOF; } if(fputc(c, stream) < 0) return EOF; } } if (inquote) { if(fputc('\'', stream) < 0) return EOF; } } return 0; } /* Double-quote a metafied string. */ /**/ mod_export char * dquotedztrdup(char const *s) { int len = strlen(s) * 4 + 2; char *buf = zalloc(len); char *p = buf, *ret; if(isset(CSHJUNKIEQUOTES)) { int inquote = 0; while(*s) { int c = *s++; if (c == Meta) c = *s++ ^ 32; switch(c) { case '"': case '$': case '`': if(inquote) { *p++ = '"'; inquote = 0; } *p++ = '\\'; *p++ = c; break; default: if(!inquote) { *p++ = '"'; inquote = 1; } if(c == '\n') *p++ = '\\'; *p++ = c; break; } } if (inquote) *p++ = '"'; } else { int pending = 0; *p++ = '"'; while(*s) { int c = *s++; if (c == Meta) c = *s++ ^ 32; switch(c) { case '\\': if(pending) *p++ = '\\'; *p++ = '\\'; pending = 1; break; case '"': case '$': case '`': if(pending) *p++ = '\\'; *p++ = '\\'; /* fall through */ default: *p++ = c; pending = 0; break; } } if(pending) *p++ = '\\'; *p++ = '"'; } ret = metafy(buf, p - buf, META_DUP); zfree(buf, len); return ret; } /* Unmetafy and output a string, double quoting it in its entirety. */ #if 0 /**/ int dquotedzputs(char const *s, FILE *stream) { char *d = dquotedztrdup(s); int ret = zputs(d, stream); zsfree(d); return ret; } #endif /* * Decode a key string, turning it into the literal characters. * The length is returned in len. * fromwhere determines how the processing works. * 0: Don't handle keystring, just print-like escapes. * Expects misc to be present. * 1: Handle Emacs-like \C-X arguments etc., but not ^X * Expects misc to be present. * 2: Handle ^X as well as emacs-like keys; don't handle \c * for no newlines. * 3: As 1, but don't handle \c. * 4: Do $'...' quoting. Overwrites the existing string instead of * zhalloc'ing * 5: As 2, but \- is special. Expects misc to be defined. * 6: As 2, but parses only one character and returns end-pointer * and parsed character in *misc */ /**/ mod_export char * getkeystring(char *s, int *len, int fromwhere, int *misc) { char *buf, tmp[1]; char *t, *u = NULL; char svchar = '\0'; int meta = 0, control = 0; if (fromwhere == 6) t = buf = tmp; else if (fromwhere != 4) t = buf = zhalloc(strlen(s) + 1); else { t = buf = s; s += 2; } for (; *s; s++) { if (*s == '\\' && s[1]) { switch (*++s) { case 'a': #ifdef __STDC__ *t++ = '\a'; #else *t++ = '\07'; #endif break; case 'n': *t++ = '\n'; break; case 'b': *t++ = '\b'; break; case 't': *t++ = '\t'; break; case 'v': *t++ = '\v'; break; case 'f': *t++ = '\f'; break; case 'r': *t++ = '\r'; break; case 'E': if (!fromwhere) { *t++ = '\\', s--; continue; } case 'e': *t++ = '\033'; break; case 'M': if (fromwhere) { if (s[1] == '-') s++; meta = 1 + control; /* preserve the order of ^ and meta */ } else *t++ = '\\', s--; continue; case 'C': if (fromwhere) { if (s[1] == '-') s++; control = 1; } else *t++ = '\\', s--; continue; case Meta: *t++ = '\\', s--; break; case '-': if (fromwhere == 5) { *misc = 1; break; } goto def; case 'c': if (fromwhere < 2) { *misc = 1; break; } default: def: if ((idigit(*s) && *s < '8') || *s == 'x') { if (!fromwhere) { if (*s == '0') s++; else if (*s != 'x') { *t++ = '\\', s--; continue; } } if (s[1] && s[2] && s[3]) { svchar = s[3]; s[3] = '\0'; u = s; } *t++ = zstrtol(s + (*s == 'x'), &s, (*s == 'x') ? 16 : 8); if (svchar) { u[3] = svchar; svchar = '\0'; } s--; } else { if (!fromwhere && *s != '\\') *t++ = '\\'; *t++ = *s; } break; } } else if (fromwhere == 4 && *s == Snull) { for (u = t; (*u++ = *s++);); return t + 1; } else if (*s == '^' && !control && (fromwhere == 2 || fromwhere == 5 || fromwhere == 6)) { control = 1; continue; } else if (*s == Meta) *t++ = *++s ^ 32; else *t++ = *s; if (meta == 2) { t[-1] |= 0x80; meta = 0; } if (control) { if (t[-1] == '?') t[-1] = 0x7f; else t[-1] &= 0x9f; control = 0; } if (meta) { t[-1] |= 0x80; meta = 0; } if (fromwhere == 4 && imeta(t[-1])) { *t = t[-1] ^ 32; t[-1] = Meta; t++; } if (fromwhere == 6 && t != tmp) { *misc = STOUC(tmp[0]); return s + 1; } } DPUTS(fromwhere == 4, "BUG: unterminated $' substitution"); *t = '\0'; if (fromwhere == 6) *misc = 0; else *len = t - buf; return buf; } /* Return non-zero if s is a prefix of t. */ /**/ mod_export int strpfx(char *s, char *t) { while (*s && *s == *t) s++, t++; return !*s; } /* Return non-zero if s is a suffix of t. */ /**/ mod_export int strsfx(char *s, char *t) { int ls = strlen(s), lt = strlen(t); if (ls <= lt) return !strcmp(t + lt - ls, s); return 0; } /**/ static int upchdir(int n) { char buf[PATH_MAX]; char *s; int err = -1; while (n > 0) { for (s = buf; s < buf + PATH_MAX - 4 && n--; ) *s++ = '.', *s++ = '.', *s++ = '/'; s[-1] = '\0'; if (chdir(buf)) return err; err = -2; } return 0; } /* Change directory, without following symlinks. Returns 0 on success, -1 * * on failure. Sets errno to ENOTDIR if any symlinks are encountered. If * * fchdir() fails, or the current directory is unreadable, we might end up * * in an unwanted directory in case of failure. */ /**/ mod_export int lchdir(char const *path, struct dirsav *d, int hard) { char const *pptr; int level; struct stat st1; struct dirsav ds; #ifdef HAVE_LSTAT char buf[PATH_MAX + 1], *ptr; int err; struct stat st2; #endif if (!d) { ds.ino = ds.dev = 0; ds.dirname = NULL; ds.dirfd = -1; d = &ds; } #ifdef HAVE_LSTAT if ((*path == '/' || !hard) && (d != &ds || hard)){ #else if (*path == '/') { #endif level = -1; #ifdef HAVE_FCHDIR if (d->dirfd < 0 && (d->dirfd = open(".", O_RDONLY | O_NOCTTY)) < 0 && zgetdir(d) && *d->dirname != '/') d->dirfd = open("..", O_RDONLY | O_NOCTTY); #else if (!d->dirname) zgetdir(d); #endif } else { level = 0; if (!d->dev && !d->ino) { stat(".", &st1); d->dev = st1.st_dev; d->ino = st1.st_ino; } } #ifdef HAVE_LSTAT if (!hard) #endif { if (d != &ds) { for (pptr = path; *pptr; level++) { while (*pptr && *pptr++ != '/'); while (*pptr == '/') pptr++; } d->level = level; } return zchdir((char *) path); } #ifdef HAVE_LSTAT if (*path == '/') chdir("/"); for(;;) { while(*path == '/') path++; if(!*path) { if (d == &ds) { zsfree(ds.dirname); if (ds.dirfd >=0) close(ds.dirfd); } else d->level = level; return 0; } for(pptr = path; *++pptr && *pptr != '/'; ) ; if(pptr - path > PATH_MAX) { err = ENAMETOOLONG; break; } for(ptr = buf; path != pptr; ) *ptr++ = *path++; *ptr = 0; if(lstat(buf, &st1)) { err = errno; break; } if(!S_ISDIR(st1.st_mode)) { err = ENOTDIR; break; } if(chdir(buf)) { err = errno; break; } if (level >= 0) level++; if(lstat(".", &st2)) { err = errno; break; } if(st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino) { err = ENOTDIR; break; } } if (restoredir(d)) { if (d == &ds) { zsfree(ds.dirname); if (ds.dirfd >=0) close(ds.dirfd); } errno = err; return -2; } if (d == &ds) { zsfree(ds.dirname); if (ds.dirfd >=0) close(ds.dirfd); } errno = err; return -1; #endif /* HAVE_LSTAT */ } /**/ mod_export int restoredir(struct dirsav *d) { int err = 0; struct stat sbuf; if (d->dirname && *d->dirname == '/') return chdir(d->dirname); #ifdef HAVE_FCHDIR if (d->dirfd >= 0) { if (!fchdir(d->dirfd)) { if (!d->dirname) { return 0; } else if (chdir(d->dirname)) { close(d->dirfd); d->dirfd = -1; err = -2; } } else { close(d->dirfd); d->dirfd = err = -1; } } else #endif if (d->level > 0) err = upchdir(d->level); else if (d->level < 0) err = -1; if (d->dev || d->ino) { stat(".", &sbuf); if (sbuf.st_ino != d->ino || sbuf.st_dev != d->dev) err = -2; } return err; } /* Get a signal number from a string */ /**/ mod_export int getsignum(char *s) { int x, i; /* check for a signal specified by number */ x = atoi(s); if (idigit(*s) && x >= 0 && x < VSIGCOUNT) return x; /* search for signal by name */ for (i = 0; i < VSIGCOUNT; i++) if (!strcmp(s, sigs[i])) return i; /* no matching signal */ return -1; } /* Check whether the shell is running with privileges in effect. * * This is the case if EITHER the euid is zero, OR (if the system * * supports POSIX.1e (POSIX.6) capability sets) the process' * * Effective or Inheritable capability sets are non-empty. */ /**/ int privasserted(void) { if(!geteuid()) return 1; #ifdef HAVE_CAP_GET_PROC { cap_t caps = cap_get_proc(); if(caps) { /* POSIX doesn't define a way to test whether a capability set * * is empty or not. Typical. I hope this is conforming... */ cap_flag_value_t val; cap_value_t n; for(n = 0; !cap_get_flag(caps, n, CAP_EFFECTIVE, &val); n++) if(val) { cap_free(caps); return 1; } cap_free(caps); } } #endif /* HAVE_CAP_GET_PROC */ return 0; } #ifdef DEBUG /**/ mod_export void dputs(char *message) { fprintf(stderr, "%s\n", message); fflush(stderr); } #endif /* DEBUG */ /**/ mod_export int mode_to_octal(mode_t mode) { int m = 0; if(mode & S_ISUID) m |= 04000; if(mode & S_ISGID) m |= 02000; if(mode & S_ISVTX) m |= 01000; if(mode & S_IRUSR) m |= 00400; if(mode & S_IWUSR) m |= 00200; if(mode & S_IXUSR) m |= 00100; if(mode & S_IRGRP) m |= 00040; if(mode & S_IWGRP) m |= 00020; if(mode & S_IXGRP) m |= 00010; if(mode & S_IROTH) m |= 00004; if(mode & S_IWOTH) m |= 00002; if(mode & S_IXOTH) m |= 00001; return m; } #ifdef MAILDIR_SUPPORT /* * Stat a file. If it's a maildir, check all messages * in the maildir and present the grand total as a file. * The fields in the 'struct stat' are from the mail directory. * The following fields are emulated: * * st_nlink always 1 * st_size total number of bytes in all files * st_blocks total number of messages * st_atime access time of newest file in maildir * st_mtime modify time of newest file in maildir * st_mode S_IFDIR changed to S_IFREG * * This is good enough for most mail-checking applications. */ /**/ int mailstat(char *path, struct stat *st) { DIR *dd; struct dirent *fn; struct stat st_ret, st_tmp; static struct stat st_new_last, st_ret_last; char *dir, *file = 0; int i; time_t atime = 0, mtime = 0; size_t plen = strlen(path), dlen; /* First see if it's a directory. */ if ((i = stat(path, st)) != 0 || !S_ISDIR(st->st_mode)) return i; st_ret = *st; st_ret.st_nlink = 1; st_ret.st_size = 0; st_ret.st_blocks = 0; st_ret.st_mode &= ~S_IFDIR; st_ret.st_mode |= S_IFREG; /* See if cur/ is present */ dir = appstr(ztrdup(path), "/cur"); if (stat(dir, &st_tmp) || !S_ISDIR(st_tmp.st_mode)) return 0; st_ret.st_atime = st_tmp.st_atime; /* See if tmp/ is present */ dir[plen] = 0; dir = appstr(dir, "/tmp"); if (stat(dir, &st_tmp) || !S_ISDIR(st_tmp.st_mode)) return 0; st_ret.st_mtime = st_tmp.st_mtime; /* And new/ */ dir[plen] = 0; dir = appstr(dir, "/new"); if (stat(dir, &st_tmp) || !S_ISDIR(st_tmp.st_mode)) return 0; st_ret.st_mtime = st_tmp.st_mtime; /* Optimization - if new/ didn't change, nothing else did. */ if (st_tmp.st_dev == st_new_last.st_dev && st_tmp.st_ino == st_new_last.st_ino && st_tmp.st_atime == st_new_last.st_atime && st_tmp.st_mtime == st_new_last.st_mtime) { *st = st_ret_last; return 0; } st_new_last = st_tmp; /* Loop over new/ and cur/ */ for (i = 0; i < 2; i++) { dir[plen] = 0; dir = appstr(dir, i ? "/cur" : "/new"); if ((dd = opendir(dir)) == NULL) { zsfree(file); zsfree(dir); return 0; } dlen = strlen(dir) + 1; /* include the "/" */ while ((fn = readdir(dd)) != NULL) { if (fn->d_name[0] == '.') continue; if (file) { file[dlen] = 0; file = appstr(file, fn->d_name); } else { file = tricat(dir, "/", fn->d_name); } if (stat(file, &st_tmp) != 0) continue; st_ret.st_size += st_tmp.st_size; st_ret.st_blocks++; if (st_tmp.st_atime != st_tmp.st_mtime && st_tmp.st_atime > atime) atime = st_tmp.st_atime; if (st_tmp.st_mtime > mtime) mtime = st_tmp.st_mtime; } closedir(dd); } zsfree(file); zsfree(dir); if (atime) st_ret.st_atime = atime; if (mtime) st_ret.st_mtime = mtime; *st = st_ret_last = st_ret; return 0; } #endif