| /* $OpenBSD: misc.c,v 1.41 2015/09/10 22:48:58 nicm Exp $ */ |
| /* $OpenBSD: path.c,v 1.13 2015/09/05 09:47:08 jsg Exp $ */ |
| |
| /*- |
| * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, |
| * 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019, |
| * 2020 |
| * mirabilos <[email protected]> |
| * Copyright (c) 2015 |
| * Daniel Richard G. <[email protected]> |
| * |
| * Provided that these terms and disclaimer and all copyright notices |
| * are retained or reproduced in an accompanying document, permission |
| * is granted to deal in this work without restriction, including un- |
| * limited rights to use, publicly perform, distribute, sell, modify, |
| * merge, give away, or sublicence. |
| * |
| * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to |
| * the utmost extent permitted by applicable law, neither express nor |
| * implied; without malicious intent or gross negligence. In no event |
| * may a licensor, author or contributor be held liable for indirect, |
| * direct, other damage, loss, or other issues arising in any way out |
| * of dealing in the work, even if advised of the possibility of such |
| * damage or existence of a defect, except proven that it results out |
| * of said person's immediate fault when using the work as intended. |
| */ |
| |
| #include "sh.h" |
| #if !HAVE_GETRUSAGE |
| #include <sys/times.h> |
| #endif |
| #if HAVE_GRP_H |
| #include <grp.h> |
| #endif |
| |
| __RCSID("$MirOS: src/bin/mksh/misc.c,v 1.302 2020/08/27 19:52:45 tg Exp $"); |
| |
| #define KSH_CHVT_FLAG |
| #ifdef MKSH_SMALL |
| #undef KSH_CHVT_FLAG |
| #endif |
| #ifdef TIOCSCTTY |
| #define KSH_CHVT_CODE |
| #define KSH_CHVT_FLAG |
| #endif |
| |
| /* type bits for unsigned char */ |
| unsigned char chtypes[UCHAR_MAX + 1]; |
| |
| static const unsigned char *pat_scan(const unsigned char *, |
| const unsigned char *, bool) MKSH_A_PURE; |
| static int do_gmatch(const unsigned char *, const unsigned char *, |
| const unsigned char *, const unsigned char *, |
| const unsigned char *) MKSH_A_PURE; |
| static const unsigned char *gmatch_cclass(const unsigned char *, unsigned char) |
| MKSH_A_PURE; |
| #ifdef KSH_CHVT_CODE |
| static void chvt(const Getopt *); |
| #endif |
| |
| /*XXX this should go away */ |
| static int make_path(const char *, const char *, char **, XString *, int *); |
| |
| #ifdef SETUID_CAN_FAIL_WITH_EAGAIN |
| /* we don't need to check for other codes, EPERM won't happen */ |
| #define DO_SETUID(func,argvec) do { \ |
| if ((func argvec) && errno == EAGAIN) \ |
| errorf("%s failed with EAGAIN, probably due to a" \ |
| " too low process limit; aborting", #func); \ |
| } while (/* CONSTCOND */ 0) |
| #else |
| #define DO_SETUID(func,argvec) func argvec |
| #endif |
| |
| |
| /* called from XcheckN() to grow buffer */ |
| char * |
| Xcheck_grow(XString *xsp, const char *xp, size_t more) |
| { |
| const char *old_beg = xsp->beg; |
| |
| if (more < xsp->len) |
| more = xsp->len; |
| /* (xsp->len + X_EXTRA) never overflows */ |
| checkoktoadd(more, xsp->len + X_EXTRA); |
| xsp->beg = aresize(xsp->beg, (xsp->len += more) + X_EXTRA, xsp->areap); |
| xsp->end = xsp->beg + xsp->len; |
| return (xsp->beg + (xp - old_beg)); |
| } |
| |
| |
| #define SHFLAGS_DEFNS |
| #define FN(sname,cname,flags,ochar) \ |
| static const struct { \ |
| /* character flag (if any) */ \ |
| char c; \ |
| /* OF_* */ \ |
| unsigned char optflags; \ |
| /* long name of option */ \ |
| char name[sizeof(sname)]; \ |
| } shoptione_ ## cname = { \ |
| ochar, flags, sname \ |
| }; |
| #include "sh_flags.gen" |
| |
| #define OFC(i) (options[i][-2]) |
| #define OFF(i) (((const unsigned char *)options[i])[-1]) |
| #define OFN(i) (options[i]) |
| |
| const char * const options[] = { |
| #define SHFLAGS_ITEMS |
| #include "sh_flags.gen" |
| }; |
| |
| /* |
| * translate -o option into F* constant (also used for test -o option) |
| */ |
| size_t |
| option(const char *n) |
| { |
| size_t i = 0; |
| |
| if (ctype(n[0], C_MINUS | C_PLUS) && n[1] && !n[2]) |
| while (i < NELEM(options)) { |
| if (OFC(i) == n[1]) |
| return (i); |
| ++i; |
| } |
| else |
| while (i < NELEM(options)) { |
| if (!strcmp(OFN(i), n)) |
| return (i); |
| ++i; |
| } |
| |
| return ((size_t)-1); |
| } |
| |
| struct options_info { |
| int opt_width; |
| int opts[NELEM(options)]; |
| }; |
| |
| static void options_fmt_entry(char *, size_t, unsigned int, const void *); |
| static int printoptions(bool); |
| static int printoption(size_t); |
| |
| /* format a single select menu item */ |
| static void |
| options_fmt_entry(char *buf, size_t buflen, unsigned int i, const void *arg) |
| { |
| const struct options_info *oi = (const struct options_info *)arg; |
| |
| shf_snprintf(buf, buflen, "%-*s %s", |
| oi->opt_width, OFN(oi->opts[i]), |
| Flag(oi->opts[i]) ? "on" : "off"); |
| } |
| |
| static int |
| printoption(size_t i) |
| { |
| if (Flag(i) == baseline_flags[i]) |
| return (0); |
| if (!OFN(i)[0]) { |
| #if !defined(MKSH_SMALL) || defined(DEBUG) |
| bi_errorf(Tf_sd, "change in unnamed option", (int)i); |
| #endif |
| return (1); |
| } |
| if (Flag(i) != 0 && Flag(i) != 1) { |
| #if !defined(MKSH_SMALL) || defined(DEBUG) |
| bi_errorf(Tf_s_sD_s, Tdo, OFN(i), "not 0 or 1"); |
| #endif |
| return (1); |
| } |
| shprintf(Tf__s_s, Flag(i) ? Tdo : Tpo, OFN(i)); |
| return (0); |
| } |
| |
| static int |
| printoptions(bool verbose) |
| { |
| size_t i = 0; |
| int rv = 0; |
| |
| if (verbose) { |
| size_t n = 0, len, octs = 0; |
| struct options_info oi; |
| struct columnise_opts co; |
| |
| /* verbose version */ |
| shf_puts("Current option settings\n", shl_stdout); |
| |
| oi.opt_width = 0; |
| while (i < NELEM(options)) { |
| if ((len = strlen(OFN(i)))) { |
| oi.opts[n++] = i; |
| if (len > octs) |
| octs = len; |
| len = utf_mbswidth(OFN(i)); |
| if ((int)len > oi.opt_width) |
| oi.opt_width = (int)len; |
| } |
| ++i; |
| } |
| co.shf = shl_stdout; |
| co.linesep = '\n'; |
| co.prefcol = co.do_last = true; |
| print_columns(&co, n, options_fmt_entry, &oi, |
| octs + 4, oi.opt_width + 4); |
| } else { |
| /* short version like AT&T ksh93 */ |
| shf_puts(Tset, shl_stdout); |
| shf_puts(To_o_reset, shl_stdout); |
| printoption(FSH); |
| printoption(FPOSIX); |
| while (i < FNFLAGS) { |
| if (i != FSH && i != FPOSIX) |
| rv |= printoption(i); |
| ++i; |
| } |
| shf_putc('\n', shl_stdout); |
| } |
| return (rv); |
| } |
| |
| char * |
| getoptions(void) |
| { |
| size_t i = 0; |
| char c, m[(int)FNFLAGS + 1]; |
| char *cp = m; |
| |
| while (i < NELEM(options)) { |
| if ((c = OFC(i)) && Flag(i)) |
| *cp++ = c; |
| ++i; |
| } |
| strndupx(cp, m, cp - m, ATEMP); |
| return (cp); |
| } |
| |
| /* change a Flag(*) value; takes care of special actions */ |
| void |
| change_flag(enum sh_flag f, int what, bool newset) |
| { |
| unsigned char oldval = Flag(f); |
| unsigned char newval = (newset ? 1 : 0); |
| |
| if (f == FXTRACE) { |
| change_xtrace(newval, true); |
| return; |
| } else if (f == FPRIVILEGED) { |
| if (!oldval) |
| /* no getting back dropped privs */ |
| return; |
| else if (!newval) { |
| /* turning off -p */ |
| kshegid = kshgid; |
| ksheuid = kshuid; |
| } else if (oldval != 3) |
| /* nor going full sugid */ |
| goto change_flag; |
| |
| /* +++ set group IDs +++ */ |
| #if HAVE_SETRESUGID |
| DO_SETUID(setresgid, (kshegid, kshegid, kshgid)); |
| #else /* !HAVE_SETRESUGID */ |
| /* setgid, setegid don't EAGAIN on Linux */ |
| setgid(kshegid); |
| #ifndef MKSH__NO_SETEUGID |
| setegid(kshegid); |
| #endif /* !MKSH__NO_SETEUGID */ |
| #endif /* !HAVE_SETRESUGID */ |
| |
| /* +++ wipe groups vector +++ */ |
| #if HAVE_SETGROUPS |
| /* setgroups doesn't EAGAIN on Linux */ |
| setgroups(0, NULL); |
| #endif /* HAVE_SETGROUPS */ |
| |
| /* +++ set user IDs +++ */ |
| #if HAVE_SETRESUGID |
| DO_SETUID(setresuid, (ksheuid, ksheuid, kshuid)); |
| #else /* !HAVE_SETRESUGID */ |
| /* seteuid doesn't EAGAIN on Linux */ |
| DO_SETUID(setuid, (ksheuid)); |
| #ifndef MKSH__NO_SETEUGID |
| seteuid(ksheuid); |
| #endif /* !MKSH__NO_SETEUGID */ |
| #endif /* !HAVE_SETRESUGID */ |
| |
| /* +++ privs changed +++ */ |
| } else if ((f == FPOSIX || f == FSH) && newval) { |
| /* Turning on -o posix? */ |
| if (f == FPOSIX) |
| /* C locale required for compliance */ |
| UTFMODE = 0; |
| /* Turning on -o posix or -o sh? */ |
| Flag(FBRACEEXPAND) = 0; |
| #ifndef MKSH_NO_CMDLINE_EDITING |
| } else if ((f == FEMACS || |
| #if !MKSH_S_NOVI |
| f == FVI || |
| #endif |
| f == FGMACS) && newval) { |
| #if !MKSH_S_NOVI |
| Flag(FVI) = 0; |
| #endif |
| Flag(FEMACS) = Flag(FGMACS) = 0; |
| #endif |
| } |
| |
| change_flag: |
| Flag(f) = newval; |
| |
| if (f == FTALKING) { |
| /* Changing interactive flag? */ |
| if ((what == OF_CMDLINE || what == OF_SET) && procpid == kshpid) |
| Flag(FTALKING_I) = newval; |
| #ifndef MKSH_UNEMPLOYED |
| } else if (f == FMONITOR) { |
| if (what != OF_CMDLINE && newval != oldval) |
| j_change(); |
| #endif |
| } |
| } |
| |
| void |
| change_xtrace(unsigned char newval, bool dosnapshot) |
| { |
| static bool in_xtrace; |
| |
| if (in_xtrace) |
| return; |
| |
| if (!dosnapshot && newval == Flag(FXTRACE)) |
| return; |
| |
| if (Flag(FXTRACE) == 2) { |
| shf_putc('\n', shl_xtrace); |
| Flag(FXTRACE) = 1; |
| shf_flush(shl_xtrace); |
| } |
| |
| if (!dosnapshot && Flag(FXTRACE) == 1) |
| switch (newval) { |
| case 1: |
| return; |
| case 2: |
| goto changed_xtrace; |
| } |
| |
| shf_flush(shl_xtrace); |
| if (shl_xtrace->fd != 2) |
| close(shl_xtrace->fd); |
| if (!newval || (shl_xtrace->fd = savefd(2)) == -1) |
| shl_xtrace->fd = 2; |
| |
| changed_xtrace: |
| if ((Flag(FXTRACE) = newval) == 2) { |
| in_xtrace = true; |
| Flag(FXTRACE) = 0; |
| shf_puts(substitute(str_val(global("PS4")), 0), shl_xtrace); |
| Flag(FXTRACE) = 2; |
| in_xtrace = false; |
| } |
| } |
| |
| /* |
| * Parse command line and set command arguments. Returns the index of |
| * non-option arguments, -1 if there is an error. |
| */ |
| int |
| parse_args(const char **argv, |
| /* OF_FIRSTTIME, OF_CMDLINE, or OF_SET */ |
| int what, |
| bool *setargsp) |
| { |
| static const char cmd_opts[] = |
| #define SHFLAGS_NOT_SET |
| #define SHFLAGS_OPTCS |
| #include "sh_flags.gen" |
| #undef SHFLAGS_NOT_SET |
| ; |
| static const char set_opts[] = |
| #define SHFLAGS_NOT_CMD |
| #define SHFLAGS_OPTCS |
| #include "sh_flags.gen" |
| #undef SHFLAGS_NOT_CMD |
| ; |
| bool set; |
| const char *opts = what == OF_CMDLINE || what == OF_FIRSTTIME ? |
| cmd_opts : set_opts; |
| const char *array = NULL; |
| Getopt go; |
| size_t i; |
| int optc, arrayset = 0; |
| bool sortargs = false; |
| bool fcompatseen = false; |
| |
| ksh_getopt_reset(&go, GF_ERROR|GF_PLUSOPT); |
| while ((optc = ksh_getopt(argv, &go, opts)) != -1) { |
| set = tobool(!(go.info & GI_PLUS)); |
| switch (optc) { |
| case 'A': |
| if (what == OF_FIRSTTIME) |
| break; |
| arrayset = set ? 1 : -1; |
| array = go.optarg; |
| break; |
| |
| case 'o': |
| if (what == OF_FIRSTTIME) |
| break; |
| if (go.optarg == NULL) { |
| /* |
| * lone -o: print options |
| * |
| * Note that on the command line, -o requires |
| * an option (ie, can't get here if what is |
| * OF_CMDLINE). |
| */ |
| #if !defined(MKSH_SMALL) || defined(DEBUG) |
| if (!set && !baseline_flags[(int)FNFLAGS]) { |
| bi_errorf(Tf_s_s, "too early", |
| Tset_po); |
| return (-1); |
| } |
| #endif |
| if (printoptions(set)) |
| return (-1); |
| break; |
| } |
| i = option(go.optarg); |
| if ((i == FPOSIX || i == FSH) && set && !fcompatseen) { |
| /* |
| * If running 'set -o posix' or |
| * 'set -o sh', turn off the other; |
| * if running 'set -o posix -o sh' |
| * allow both to be set though. |
| */ |
| Flag(FPOSIX) = 0; |
| Flag(FSH) = 0; |
| fcompatseen = true; |
| } |
| if ((i != (size_t)-1) && (set ? 1U : 0U) == Flag(i)) |
| /* |
| * Don't check the context if the flag |
| * isn't changing - makes "set -o interactive" |
| * work if you're already interactive. Needed |
| * if the output of "set +o" is to be used. |
| */ |
| ; |
| else if ((i != (size_t)-1) && (OFF(i) & what)) |
| change_flag((enum sh_flag)i, what, set); |
| else if (!strcmp(go.optarg, To_reset)) { |
| #if !defined(MKSH_SMALL) || defined(DEBUG) |
| if (!baseline_flags[(int)FNFLAGS]) { |
| bi_errorf(Tf_ss, "too early", |
| To_o_reset); |
| return (-1); |
| } |
| #endif |
| /* |
| * ordering, with respect to side effects, |
| * was ensured above by printoptions |
| */ |
| for (i = 0; i < FNFLAGS; ++i) |
| if (Flag(i) != baseline_flags[i]) |
| change_flag((enum sh_flag)i, |
| what, baseline_flags[i]); |
| } else { |
| bi_errorf(Tf_sD_s, go.optarg, |
| Tunknown_option); |
| return (-1); |
| } |
| break; |
| |
| #ifdef KSH_CHVT_FLAG |
| case 'T': |
| if (what != OF_FIRSTTIME) |
| break; |
| #ifndef KSH_CHVT_CODE |
| errorf("no TIOCSCTTY ioctl"); |
| #else |
| change_flag(FTALKING, OF_CMDLINE, true); |
| chvt(&go); |
| break; |
| #endif |
| #endif |
| |
| case '?': |
| return (-1); |
| |
| default: |
| if (what == OF_FIRSTTIME) |
| break; |
| /* -s: sort positional params (AT&T ksh stupidity) */ |
| if (what == OF_SET && optc == 's') { |
| sortargs = true; |
| break; |
| } |
| for (i = 0; i < NELEM(options); i++) |
| if (optc == OFC(i) && |
| (what & OFF(i))) { |
| change_flag((enum sh_flag)i, what, set); |
| break; |
| } |
| if (i == NELEM(options)) |
| internal_errorf("parse_args: '%c'", optc); |
| } |
| } |
| if (!(go.info & GI_MINUSMINUS) && argv[go.optind] && |
| ctype(argv[go.optind][0], C_MINUS | C_PLUS) && |
| argv[go.optind][1] == '\0') { |
| /* lone - clears -v and -x flags */ |
| if (argv[go.optind][0] == '-') { |
| Flag(FVERBOSE) = 0; |
| change_xtrace(0, false); |
| } |
| /* set skips lone - or + option */ |
| go.optind++; |
| } |
| if (setargsp) |
| /* -- means set $#/$* even if there are no arguments */ |
| *setargsp = !arrayset && ((go.info & GI_MINUSMINUS) || |
| argv[go.optind]); |
| |
| if (arrayset) { |
| const char *ccp = NULL; |
| |
| if (array && *array) |
| ccp = skip_varname(array, false); |
| if (!ccp || !(!ccp[0] || (ccp[0] == '+' && !ccp[1]))) { |
| bi_errorf(Tf_sD_s, array, Tnot_ident); |
| return (-1); |
| } |
| } |
| if (sortargs) { |
| for (i = go.optind; argv[i]; i++) |
| ; |
| qsort(&argv[go.optind], i - go.optind, sizeof(void *), |
| ascpstrcmp); |
| } |
| if (arrayset) |
| go.optind += set_array(array, tobool(arrayset > 0), |
| argv + go.optind); |
| |
| return (go.optind); |
| } |
| |
| /* parse a decimal number: returns 0 if string isn't a number, 1 otherwise */ |
| int |
| getn(const char *s, int *ai) |
| { |
| char c; |
| mksh_ari_u num; |
| bool neg = false; |
| |
| num.u = 0; |
| |
| do { |
| c = *s++; |
| } while (ctype(c, C_SPACE)); |
| |
| switch (c) { |
| case '-': |
| neg = true; |
| /* FALLTHROUGH */ |
| case '+': |
| c = *s++; |
| break; |
| } |
| |
| do { |
| if (!ctype(c, C_DIGIT)) |
| /* not numeric */ |
| return (0); |
| if (num.u > 214748364U) |
| /* overflow on multiplication */ |
| return (0); |
| num.u = num.u * 10U + (unsigned int)ksh_numdig(c); |
| /* now: num.u <= 2147483649U */ |
| } while ((c = *s++)); |
| |
| if (num.u > (neg ? 2147483648U : 2147483647U)) |
| /* overflow for signed 32-bit int */ |
| return (0); |
| |
| if (neg) |
| num.u = -num.u; |
| *ai = num.i; |
| return (1); |
| } |
| |
| /** |
| * pattern simplifications: |
| * - @(x) -> x (not @(x|y) though) |
| * - ** -> * |
| */ |
| static void * |
| simplify_gmatch_pattern(const unsigned char *sp) |
| { |
| uint8_t c; |
| unsigned char *cp, *dp; |
| const unsigned char *ps, *se; |
| |
| cp = alloc(strlen((const void *)sp) + 1, ATEMP); |
| goto simplify_gmatch_pat1a; |
| |
| /* foo@(b@(a)r)b@(a|a)z -> foobarb@(a|a)z */ |
| simplify_gmatch_pat1: |
| sp = cp; |
| simplify_gmatch_pat1a: |
| dp = cp; |
| se = strnul(sp); |
| while ((c = *sp++)) { |
| if (!ISMAGIC(c)) { |
| *dp++ = c; |
| continue; |
| } |
| switch ((c = *sp++)) { |
| case 0x80|'@': |
| /* simile for @ */ |
| case 0x80|' ': |
| /* check whether it has only one clause */ |
| ps = pat_scan(sp, se, true); |
| if (!ps || ps[-1] != /*(*/ ')') |
| /* nope */ |
| break; |
| /* copy inner clause until matching close */ |
| ps -= 2; |
| while ((const unsigned char *)sp < ps) |
| *dp++ = *sp++; |
| /* skip MAGIC and closing parenthesis */ |
| sp += 2; |
| /* copy the rest of the pattern */ |
| memmove(dp, sp, strlen((const void *)sp) + 1); |
| /* redo from start */ |
| goto simplify_gmatch_pat1; |
| } |
| *dp++ = MAGIC; |
| *dp++ = c; |
| } |
| *dp = '\0'; |
| |
| /* collapse adjacent asterisk wildcards */ |
| sp = dp = cp; |
| while ((c = *sp++)) { |
| if (!ISMAGIC(c)) { |
| *dp++ = c; |
| continue; |
| } |
| switch ((c = *sp++)) { |
| case '*': |
| while (ISMAGIC(sp[0]) && sp[1] == c) |
| sp += 2; |
| break; |
| } |
| *dp++ = MAGIC; |
| *dp++ = c; |
| } |
| *dp = '\0'; |
| |
| /* return the result, allocated from ATEMP */ |
| return (cp); |
| } |
| |
| /* -------- gmatch.c -------- */ |
| |
| /* |
| * int gmatch(string, pattern) |
| * char *string, *pattern; |
| * |
| * Match a pattern as in sh(1). |
| * pattern character are prefixed with MAGIC by expand. |
| */ |
| int |
| gmatchx(const char *s, const char *p, bool isfile) |
| { |
| const char *se, *pe; |
| char *pnew; |
| int rv; |
| |
| if (s == NULL || p == NULL) |
| return (0); |
| |
| pe = strnul(p); |
| /* |
| * isfile is false iff no syntax check has been done on |
| * the pattern. If check fails, just do a strcmp(). |
| */ |
| if (!isfile && !has_globbing(p)) { |
| size_t len = pe - p + 1; |
| char tbuf[64]; |
| char *t = len <= sizeof(tbuf) ? tbuf : alloc(len, ATEMP); |
| debunk(t, p, len); |
| return (!strcmp(t, s)); |
| } |
| se = strnul(s); |
| |
| /* |
| * since the do_gmatch() engine sucks so much, we must do some |
| * pattern simplifications |
| */ |
| pnew = simplify_gmatch_pattern((const unsigned char *)p); |
| pe = strnul(pnew); |
| |
| rv = do_gmatch((const unsigned char *)s, (const unsigned char *)se, |
| (const unsigned char *)pnew, (const unsigned char *)pe, |
| (const unsigned char *)s); |
| afree(pnew, ATEMP); |
| return (rv); |
| } |
| |
| /** |
| * Returns if p is a syntacticly correct globbing pattern, false |
| * if it contains no pattern characters or if there is a syntax error. |
| * Syntax errors are: |
| * - [ with no closing ] |
| * - imbalanced $(...) expression |
| * - [...] and *(...) not nested (eg, @(a[b|)c], *(a[b|c]d)) |
| */ |
| /*XXX |
| * - if no magic, |
| * if dest given, copy to dst |
| * return ? |
| * - if magic && (no globbing || syntax error) |
| * debunk to dst |
| * return ? |
| * - return ? |
| */ |
| bool |
| has_globbing(const char *pat) |
| { |
| unsigned char c, subc; |
| bool saw_glob = false; |
| unsigned int nest = 0; |
| const unsigned char *p = (const unsigned char *)pat; |
| const unsigned char *s; |
| |
| while ((c = *p++)) { |
| /* regular character? ok. */ |
| if (!ISMAGIC(c)) |
| continue; |
| /* MAGIC + NUL? abort. */ |
| if (!(c = *p++)) |
| return (false); |
| /* some specials */ |
| if (ord(c) == ORD('*') || ord(c) == ORD('?')) { |
| /* easy glob, accept */ |
| saw_glob = true; |
| } else if (ord(c) == ORD('[')) { |
| /* bracket expression; eat negation and initial ] */ |
| if (ISMAGIC(p[0]) && ord(p[1]) == ORD('!')) |
| p += 2; |
| if (ISMAGIC(p[0]) && ord(p[1]) == ORD(']')) |
| p += 2; |
| /* check next string part */ |
| s = p; |
| while ((c = *s++)) { |
| /* regular chars are ok */ |
| if (!ISMAGIC(c)) |
| continue; |
| /* MAGIC + NUL cannot happen */ |
| if (!(c = *s++)) |
| return (false); |
| /* terminating bracket? */ |
| if (ord(c) == ORD(']')) { |
| /* accept and continue */ |
| p = s; |
| saw_glob = true; |
| break; |
| } |
| /* sub-bracket expressions */ |
| if (ord(c) == ORD('[') && ( |
| /* collating element? */ |
| ord(*s) == ORD('.') || |
| /* equivalence class? */ |
| ord(*s) == ORD('=') || |
| /* character class? */ |
| ord(*s) == ORD(':'))) { |
| /* must stop with exactly the same c */ |
| subc = *s++; |
| /* arbitrarily many chars in betwixt */ |
| while ((c = *s++)) |
| /* but only this sequence... */ |
| if (c == subc && ISMAGIC(*s) && |
| ord(s[1]) == ORD(']')) { |
| /* accept, terminate */ |
| s += 2; |
| break; |
| } |
| /* EOS without: reject bracket expr */ |
| if (!c) |
| break; |
| /* continue; */ |
| } |
| /* anything else just goes on */ |
| } |
| } else if ((c & 0x80) && ctype(c & 0x7F, C_PATMO | C_SPC)) { |
| /* opening pattern */ |
| saw_glob = true; |
| ++nest; |
| } else if (ord(c) == ORD(/*(*/ ')')) { |
| /* closing pattern */ |
| if (nest) |
| --nest; |
| } |
| } |
| return (saw_glob && !nest); |
| } |
| |
| /* Function must return either 0 or 1 (assumed by code for 0x80|'!') */ |
| static int |
| do_gmatch(const unsigned char *s, const unsigned char *se, |
| const unsigned char *p, const unsigned char *pe, |
| const unsigned char *smin) |
| { |
| unsigned char sc, pc, sl = 0; |
| const unsigned char *prest, *psub, *pnext; |
| const unsigned char *srest; |
| |
| if (s == NULL || p == NULL) |
| return (0); |
| if (s > smin && s <= se) |
| sl = s[-1]; |
| while (p < pe) { |
| pc = *p++; |
| sc = s < se ? *s : '\0'; |
| s++; |
| if (!ISMAGIC(pc)) { |
| if (sc != pc) |
| return (0); |
| sl = sc; |
| continue; |
| } |
| switch (ord(*p++)) { |
| case ORD('['): |
| /* BSD cclass extension? */ |
| if (ISMAGIC(p[0]) && ord(p[1]) == ORD('[') && |
| ord(p[2]) == ORD(':') && |
| ctype((pc = p[3]), C_ANGLE) && |
| ord(p[4]) == ORD(':') && |
| ISMAGIC(p[5]) && ord(p[6]) == ORD(']') && |
| ISMAGIC(p[7]) && ord(p[8]) == ORD(']')) { |
| /* zero-length match */ |
| --s; |
| p += 9; |
| /* word begin? */ |
| if (ord(pc) == ORD('<') && |
| !ctype(sl, C_ALNUX) && |
| ctype(sc, C_ALNUX)) |
| break; |
| /* word end? */ |
| if (ord(pc) == ORD('>') && |
| ctype(sl, C_ALNUX) && |
| !ctype(sc, C_ALNUX)) |
| break; |
| /* neither */ |
| return (0); |
| } |
| if (sc == 0 || (p = gmatch_cclass(p, sc)) == NULL) |
| return (0); |
| break; |
| |
| case ORD('?'): |
| if (sc == 0) |
| return (0); |
| if (UTFMODE) { |
| --s; |
| s += utf_ptradj((const void *)s); |
| } |
| break; |
| |
| case ORD('*'): |
| if (p == pe) |
| return (1); |
| s--; |
| do { |
| if (do_gmatch(s, se, p, pe, smin)) |
| return (1); |
| } while (s++ < se); |
| return (0); |
| |
| /** |
| * [+*?@!](pattern|pattern|..) |
| * This is also needed for ${..%..}, etc. |
| */ |
| |
| /* matches one or more times */ |
| case ORD('+') | 0x80: |
| /* matches zero or more times */ |
| case ORD('*') | 0x80: |
| if (!(prest = pat_scan(p, pe, false))) |
| return (0); |
| s--; |
| /* take care of zero matches */ |
| if (ord(p[-1]) == (0x80 | ORD('*')) && |
| do_gmatch(s, se, prest, pe, smin)) |
| return (1); |
| for (psub = p; ; psub = pnext) { |
| pnext = pat_scan(psub, pe, true); |
| for (srest = s; srest <= se; srest++) { |
| if (do_gmatch(s, srest, psub, pnext - 2, smin) && |
| (do_gmatch(srest, se, prest, pe, smin) || |
| (s != srest && |
| do_gmatch(srest, se, p - 2, pe, smin)))) |
| return (1); |
| } |
| if (pnext == prest) |
| break; |
| } |
| return (0); |
| |
| /* matches zero or once */ |
| case ORD('?') | 0x80: |
| /* matches one of the patterns */ |
| case ORD('@') | 0x80: |
| /* simile for @ */ |
| case ORD(' ') | 0x80: |
| if (!(prest = pat_scan(p, pe, false))) |
| return (0); |
| s--; |
| /* Take care of zero matches */ |
| if (ord(p[-1]) == (0x80 | ORD('?')) && |
| do_gmatch(s, se, prest, pe, smin)) |
| return (1); |
| for (psub = p; ; psub = pnext) { |
| pnext = pat_scan(psub, pe, true); |
| srest = prest == pe ? se : s; |
| for (; srest <= se; srest++) { |
| if (do_gmatch(s, srest, psub, pnext - 2, smin) && |
| do_gmatch(srest, se, prest, pe, smin)) |
| return (1); |
| } |
| if (pnext == prest) |
| break; |
| } |
| return (0); |
| |
| /* matches none of the patterns */ |
| case ORD('!') | 0x80: |
| if (!(prest = pat_scan(p, pe, false))) |
| return (0); |
| s--; |
| for (srest = s; srest <= se; srest++) { |
| int matched = 0; |
| |
| for (psub = p; ; psub = pnext) { |
| pnext = pat_scan(psub, pe, true); |
| if (do_gmatch(s, srest, psub, |
| pnext - 2, smin)) { |
| matched = 1; |
| break; |
| } |
| if (pnext == prest) |
| break; |
| } |
| if (!matched && |
| do_gmatch(srest, se, prest, pe, smin)) |
| return (1); |
| } |
| return (0); |
| |
| default: |
| if (sc != p[-1]) |
| return (0); |
| break; |
| } |
| sl = sc; |
| } |
| return (s == se); |
| } |
| |
| /*XXX this is a prime example for bsearch or a const hashtable */ |
| static const struct cclass { |
| const char *name; |
| uint32_t value; |
| } cclasses[] = { |
| /* POSIX */ |
| { "alnum", C_ALNUM }, |
| { "alpha", C_ALPHA }, |
| { "blank", C_BLANK }, |
| { "cntrl", C_CNTRL }, |
| { "digit", C_DIGIT }, |
| { "graph", C_GRAPH }, |
| { "lower", C_LOWER }, |
| { "print", C_PRINT }, |
| { "punct", C_PUNCT }, |
| { "space", C_SPACE }, |
| { "upper", C_UPPER }, |
| { "xdigit", C_SEDEC }, |
| /* BSD */ |
| /* "<" and ">" are handled inline */ |
| /* GNU bash */ |
| { "ascii", C_ASCII }, |
| { "word", C_ALNUX }, |
| /* mksh */ |
| { "sh_alias", C_ALIAS }, |
| { "sh_edq", C_EDQ }, |
| { "sh_ifs", C_IFS }, |
| { "sh_ifsws", C_IFSWS }, |
| { "sh_nl", C_NL }, |
| { "sh_quote", C_QUOTE }, |
| /* sentinel */ |
| { NULL, 0 } |
| }; |
| |
| static const unsigned char * |
| gmatch_cclass(const unsigned char *pat, unsigned char sc) |
| { |
| unsigned char c, subc, lc; |
| const unsigned char *p = pat, *s; |
| bool found = false; |
| bool negated = false; |
| char *subp; |
| |
| /* check for negation */ |
| if (ISMAGIC(p[0]) && ord(p[1]) == ORD('!')) { |
| p += 2; |
| negated = true; |
| } |
| /* make initial ] non-MAGIC */ |
| if (ISMAGIC(p[0]) && ord(p[1]) == ORD(']')) |
| ++p; |
| /* iterate over bracket expression, debunk()ing on the fly */ |
| while ((c = *p++)) { |
| nextc: |
| /* non-regular character? */ |
| if (ISMAGIC(c)) { |
| /* MAGIC + NUL cannot happen */ |
| if (!(c = *p++)) |
| break; |
| /* terminating bracket? */ |
| if (ord(c) == ORD(']')) { |
| /* accept and return */ |
| return (found != negated ? p : NULL); |
| } |
| /* sub-bracket expressions */ |
| if (ord(c) == ORD('[') && ( |
| /* collating element? */ |
| ord(*p) == ORD('.') || |
| /* equivalence class? */ |
| ord(*p) == ORD('=') || |
| /* character class? */ |
| ord(*p) == ORD(':'))) { |
| /* must stop with exactly the same c */ |
| subc = *p++; |
| /* save away start of substring */ |
| s = p; |
| /* arbitrarily many chars in betwixt */ |
| while ((c = *p++)) |
| /* but only this sequence... */ |
| if (c == subc && ISMAGIC(*p) && |
| ord(p[1]) == ORD(']')) { |
| /* accept, terminate */ |
| p += 2; |
| break; |
| } |
| /* EOS without: reject bracket expr */ |
| if (!c) |
| break; |
| /* debunk substring */ |
| strndupx(subp, s, p - s - 3, ATEMP); |
| debunk(subp, subp, p - s - 3 + 1); |
| cclass_common: |
| /* whither subexpression */ |
| if (ord(subc) == ORD(':')) { |
| const struct cclass *cls = cclasses; |
| |
| /* search for name in cclass list */ |
| while (cls->name) |
| if (!strcmp(subp, cls->name)) { |
| /* found, match? */ |
| if (ctype(sc, |
| cls->value)) |
| found = true; |
| /* break either way */ |
| break; |
| } else |
| ++cls; |
| /* that's all here */ |
| afree(subp, ATEMP); |
| continue; |
| } |
| /* collating element or equivalence class */ |
| /* Note: latter are treated as former */ |
| if (ctype(subp[0], C_ASCII) && !subp[1]) |
| /* [.a.] where a is one ASCII char */ |
| c = subp[0]; |
| else |
| /* force no match */ |
| c = 0; |
| /* no longer needed */ |
| afree(subp, ATEMP); |
| } else if (!ISMAGIC(c) && (c & 0x80)) { |
| /* 0x80|' ' is plain (...) */ |
| if ((c &= 0x7F) != ' ') { |
| /* check single match NOW */ |
| if (sc == c) |
| found = true; |
| /* next character is (...) */ |
| } |
| c = '(' /*)*/; |
| } |
| } |
| /* range expression? */ |
| if (!(ISMAGIC(p[0]) && ord(p[1]) == ORD('-') && |
| /* not terminating bracket? */ |
| (!ISMAGIC(p[2]) || ord(p[3]) != ORD(']')))) { |
| /* no, check single match */ |
| if (sc == c) |
| /* note: sc is never NUL */ |
| found = true; |
| /* do the next "first" character */ |
| continue; |
| } |
| /* save lower range bound */ |
| lc = c; |
| /* skip over the range operator */ |
| p += 2; |
| /* do the same shit as above... almost */ |
| subc = 0; |
| if (!(c = *p++)) |
| break; |
| /* non-regular character? */ |
| if (ISMAGIC(c)) { |
| /* MAGIC + NUL cannot happen */ |
| if (!(c = *p++)) |
| break; |
| /* sub-bracket expressions */ |
| if (ord(c) == ORD('[') && ( |
| /* collating element? */ |
| ord(*p) == ORD('.') || |
| /* equivalence class? */ |
| ord(*p) == ORD('=') || |
| /* character class? */ |
| ord(*p) == ORD(':'))) { |
| /* must stop with exactly the same c */ |
| subc = *p++; |
| /* save away start of substring */ |
| s = p; |
| /* arbitrarily many chars in betwixt */ |
| while ((c = *p++)) |
| /* but only this sequence... */ |
| if (c == subc && ISMAGIC(*p) && |
| ord(p[1]) == ORD(']')) { |
| /* accept, terminate */ |
| p += 2; |
| break; |
| } |
| /* EOS without: reject bracket expr */ |
| if (!c) |
| break; |
| /* debunk substring */ |
| strndupx(subp, s, p - s - 3, ATEMP); |
| debunk(subp, subp, p - s - 3 + 1); |
| /* whither subexpression */ |
| if (ord(subc) == ORD(':')) { |
| /* oops, not a range */ |
| |
| /* match single previous char */ |
| if (lc && (sc == lc)) |
| found = true; |
| /* match hyphen-minus */ |
| if (ord(sc) == ORD('-')) |
| found = true; |
| /* handle cclass common part */ |
| goto cclass_common; |
| } |
| /* collating element or equivalence class */ |
| /* Note: latter are treated as former */ |
| if (ctype(subp[0], C_ASCII) && !subp[1]) |
| /* [.a.] where a is one ASCII char */ |
| c = subp[0]; |
| else |
| /* force no match */ |
| c = 0; |
| /* no longer needed */ |
| afree(subp, ATEMP); |
| /* other meaning below */ |
| subc = 0; |
| } else if (c == (0x80 | ' ')) { |
| /* 0x80|' ' is plain (...) */ |
| c = '(' /*)*/; |
| } else if (!ISMAGIC(c) && (c & 0x80)) { |
| c &= 0x7F; |
| subc = '(' /*)*/; |
| } |
| } |
| /* now do the actual range match check */ |
| if (lc != 0 /* && c != 0 */ && |
| asciibetical(lc) <= asciibetical(sc) && |
| asciibetical(sc) <= asciibetical(c)) |
| found = true; |
| /* forced next character? */ |
| if (subc) { |
| c = subc; |
| goto nextc; |
| } |
| /* otherwise, just go on with the pattern string */ |
| } |
| /* if we broke here, the bracket expression was invalid */ |
| if (ord(sc) == ORD('[')) |
| /* initial opening bracket as literal match */ |
| return (pat); |
| /* or rather no match */ |
| return (NULL); |
| } |
| |
| /* Look for next ) or | (if match_sep) in *(foo|bar) pattern */ |
| static const unsigned char * |
| pat_scan(const unsigned char *p, const unsigned char *pe, bool match_sep) |
| { |
| int nest = 0; |
| |
| for (; p < pe; p++) { |
| if (!ISMAGIC(*p)) |
| continue; |
| if ((*++p == /*(*/ ')' && nest-- == 0) || |
| (*p == '|' && match_sep && nest == 0)) |
| return (p + 1); |
| if ((*p & 0x80) && ctype(*p & 0x7F, C_PATMO | C_SPC)) |
| nest++; |
| } |
| return (NULL); |
| } |
| |
| int |
| ascstrcmp(const void *s1, const void *s2) |
| { |
| const uint8_t *cp1 = s1, *cp2 = s2; |
| |
| while (*cp1 == *cp2) { |
| if (*cp1++ == '\0') |
| return (0); |
| ++cp2; |
| } |
| return ((int)asciibetical(*cp1) - (int)asciibetical(*cp2)); |
| } |
| |
| int |
| ascpstrcmp(const void *pstr1, const void *pstr2) |
| { |
| return (ascstrcmp(*(const char * const *)pstr1, |
| *(const char * const *)pstr2)); |
| } |
| |
| /* Initialise a Getopt structure */ |
| void |
| ksh_getopt_reset(Getopt *go, int flags) |
| { |
| go->optind = 1; |
| go->optarg = NULL; |
| go->p = 0; |
| go->flags = flags; |
| go->info = 0; |
| go->buf[1] = '\0'; |
| } |
| |
| |
| /** |
| * getopt() used for shell built-in commands, the getopts command, and |
| * command line options. |
| * A leading ':' in options means don't print errors, instead return '?' |
| * or ':' and set go->optarg to the offending option character. |
| * If GF_ERROR is set (and option doesn't start with :), errors result in |
| * a call to bi_errorf(). |
| * |
| * Non-standard features: |
| * - ';' is like ':' in options, except the argument is optional |
| * (if it isn't present, optarg is set to 0). |
| * Used for 'set -o'. |
| * - ',' is like ':' in options, except the argument always immediately |
| * follows the option character (optarg is set to the null string if |
| * the option is missing). |
| * Used for 'read -u2', 'print -u2' and fc -40. |
| * - '#' is like ':' in options, expect that the argument is optional |
| * and must start with a digit. If the argument doesn't start with a |
| * digit, it is assumed to be missing and normal option processing |
| * continues (optarg is set to 0 if the option is missing). |
| * Used for 'typeset -LZ4'. |
| * - accepts +c as well as -c IF the GF_PLUSOPT flag is present. If an |
| * option starting with + is accepted, the GI_PLUS flag will be set |
| * in go->info. |
| */ |
| int |
| ksh_getopt(const char **argv, Getopt *go, const char *optionsp) |
| { |
| char c; |
| const char *o; |
| |
| if (go->p == 0 || (c = argv[go->optind - 1][go->p]) == '\0') { |
| const char *arg = argv[go->optind], flag = arg ? *arg : '\0'; |
| |
| go->p = 1; |
| if (flag == '-' && ksh_isdash(arg + 1)) { |
| go->optind++; |
| go->p = 0; |
| go->info |= GI_MINUSMINUS; |
| return (-1); |
| } |
| if (arg == NULL || |
| ((flag != '-' ) && |
| /* neither a - nor a + (if + allowed) */ |
| (!(go->flags & GF_PLUSOPT) || flag != '+')) || |
| (c = arg[1]) == '\0') { |
| go->p = 0; |
| return (-1); |
| } |
| go->optind++; |
| go->info &= ~(GI_MINUS|GI_PLUS); |
| go->info |= flag == '-' ? GI_MINUS : GI_PLUS; |
| } |
| go->p++; |
| if (ctype(c, C_QUEST | C_COLON | C_HASH) || c == ';' || c == ',' || |
| !(o = cstrchr(optionsp, c))) { |
| if (optionsp[0] == ':') { |
| go->buf[0] = c; |
| go->optarg = go->buf; |
| } else { |
| warningf(true, Tf_optfoo, |
| (go->flags & GF_NONAME) ? "" : argv[0], |
| (go->flags & GF_NONAME) ? "" : Tcolsp, |
| c, Tunknown_option); |
| if (go->flags & GF_ERROR) |
| bi_errorfz(); |
| } |
| return (ORD('?')); |
| } |
| /** |
| * : means argument must be present, may be part of option argument |
| * or the next argument |
| * ; same as : but argument may be missing |
| * , means argument is part of option argument, and may be null. |
| */ |
| if (*++o == ':' || *o == ';') { |
| if (argv[go->optind - 1][go->p]) |
| go->optarg = argv[go->optind - 1] + go->p; |
| else if (argv[go->optind]) |
| go->optarg = argv[go->optind++]; |
| else if (*o == ';') |
| go->optarg = NULL; |
| else { |
| if (optionsp[0] == ':') { |
| go->buf[0] = c; |
| go->optarg = go->buf; |
| return (ORD(':')); |
| } |
| warningf(true, Tf_optfoo, |
| (go->flags & GF_NONAME) ? "" : argv[0], |
| (go->flags & GF_NONAME) ? "" : Tcolsp, |
| c, Treq_arg); |
| if (go->flags & GF_ERROR) |
| bi_errorfz(); |
| return (ORD('?')); |
| } |
| go->p = 0; |
| } else if (*o == ',') { |
| /* argument is attached to option character, even if null */ |
| go->optarg = argv[go->optind - 1] + go->p; |
| go->p = 0; |
| } else if (*o == '#') { |
| /* |
| * argument is optional and may be attached or unattached |
| * but must start with a digit. optarg is set to 0 if the |
| * argument is missing. |
| */ |
| if (argv[go->optind - 1][go->p]) { |
| if (ctype(argv[go->optind - 1][go->p], C_DIGIT)) { |
| go->optarg = argv[go->optind - 1] + go->p; |
| go->p = 0; |
| } else |
| go->optarg = NULL; |
| } else { |
| if (argv[go->optind] && |
| ctype(argv[go->optind][0], C_DIGIT)) { |
| go->optarg = argv[go->optind++]; |
| go->p = 0; |
| } else |
| go->optarg = NULL; |
| } |
| } |
| return (ord(c)); |
| } |
| |
| /* |
| * print variable/alias value using necessary quotes |
| * (POSIX says they should be suitable for re-entry...) |
| * No trailing newline is printed. |
| */ |
| void |
| print_value_quoted(struct shf *shf, const char *s) |
| { |
| unsigned char c; |
| const unsigned char *p = (const unsigned char *)s; |
| bool inquote = true; |
| |
| /* first, special-case empty strings (for re-entrancy) */ |
| if (!*s) { |
| shf_putc('\'', shf); |
| shf_putc('\'', shf); |
| return; |
| } |
| |
| /* non-empty; check whether any quotes are needed */ |
| while (rtt2asc(c = *p++) >= 32) |
| if (ctype(c, C_QUOTE | C_SPC)) |
| inquote = false; |
| |
| p = (const unsigned char *)s; |
| if (c == 0) { |
| if (inquote) { |
| /* nope, use the shortcut */ |
| shf_puts(s, shf); |
| return; |
| } |
| |
| /* otherwise, quote nicely via state machine */ |
| while ((c = *p++) != 0) { |
| if (c == '\'') { |
| /* |
| * multiple single quotes or any of them |
| * at the beginning of a string look nicer |
| * this way than when simply substituting |
| */ |
| if (inquote) { |
| shf_putc('\'', shf); |
| inquote = false; |
| } |
| shf_putc('\\', shf); |
| } else if (!inquote) { |
| shf_putc('\'', shf); |
| inquote = true; |
| } |
| shf_putc(c, shf); |
| } |
| } else { |
| unsigned int wc; |
| size_t n; |
| |
| /* use $'...' quote format */ |
| shf_putc('$', shf); |
| shf_putc('\'', shf); |
| while ((c = *p) != 0) { |
| #ifndef MKSH_EBCDIC |
| if (c >= 0xC2) { |
| n = utf_mbtowc(&wc, (const char *)p); |
| if (n != (size_t)-1) { |
| p += n; |
| shf_fprintf(shf, "\\u%04X", wc); |
| continue; |
| } |
| } |
| #endif |
| ++p; |
| switch (c) { |
| /* see unbksl() in this file for comments */ |
| case KSH_BEL: |
| c = 'a'; |
| if (0) |
| /* FALLTHROUGH */ |
| case '\b': |
| c = 'b'; |
| if (0) |
| /* FALLTHROUGH */ |
| case '\f': |
| c = 'f'; |
| if (0) |
| /* FALLTHROUGH */ |
| case '\n': |
| c = 'n'; |
| if (0) |
| /* FALLTHROUGH */ |
| case '\r': |
| c = 'r'; |
| if (0) |
| /* FALLTHROUGH */ |
| case '\t': |
| c = 't'; |
| if (0) |
| /* FALLTHROUGH */ |
| case KSH_VTAB: |
| c = 'v'; |
| if (0) |
| /* FALLTHROUGH */ |
| case KSH_ESC: |
| /* take E not e because \e is \ in *roff */ |
| c = 'E'; |
| /* FALLTHROUGH */ |
| case '\\': |
| shf_putc('\\', shf); |
| |
| if (0) |
| /* FALLTHROUGH */ |
| default: |
| #if defined(MKSH_EBCDIC) || defined(MKSH_FAUX_EBCDIC) |
| if (ksh_isctrl(c)) |
| #else |
| if (!ctype(c, C_PRINT)) |
| #endif |
| { |
| /* FALLTHROUGH */ |
| case '\'': |
| shf_fprintf(shf, "\\%03o", c); |
| break; |
| } |
| |
| shf_putc(c, shf); |
| break; |
| } |
| } |
| inquote = true; |
| } |
| if (inquote) |
| shf_putc('\'', shf); |
| } |
| |
| /* |
| * Print things in columns and rows - func() is called to format |
| * the i-th element |
| */ |
| void |
| print_columns(struct columnise_opts *opts, unsigned int n, |
| void (*func)(char *, size_t, unsigned int, const void *), |
| const void *arg, size_t max_oct, size_t max_colz) |
| { |
| unsigned int i, r = 0, c, rows, cols, nspace, max_col; |
| char *str; |
| |
| if (!n) |
| return; |
| |
| if (max_colz > 2147483646) { |
| #ifndef MKSH_SMALL |
| internal_warningf("print_columns called with %s=%zu >= INT_MAX", |
| "max_col", max_colz); |
| #endif |
| return; |
| } |
| max_col = (unsigned int)max_colz; |
| |
| if (max_oct > 2147483646) { |
| #ifndef MKSH_SMALL |
| internal_warningf("print_columns called with %s=%zu >= INT_MAX", |
| "max_oct", max_oct); |
| #endif |
| return; |
| } |
| ++max_oct; |
| str = alloc(max_oct, ATEMP); |
| |
| /* |
| * We use (max_col + 2) to consider the separator space. |
| * Note that no spaces are printed after the last column |
| * to avoid problems with terminals that have auto-wrap, |
| * but we need to also take this into account in x_cols. |
| */ |
| cols = (x_cols + 1) / (max_col + 2); |
| |
| /* if we can only print one column anyway, skip the goo */ |
| if (cols < 2) { |
| goto prcols_easy; |
| while (r < n) { |
| shf_putc(opts->linesep, opts->shf); |
| prcols_easy: |
| (*func)(str, max_oct, r++, arg); |
| shf_puts(str, opts->shf); |
| } |
| goto out; |
| } |
| |
| rows = (n + cols - 1) / cols; |
| if (opts->prefcol && cols > rows) { |
| cols = rows; |
| rows = (n + cols - 1) / cols; |
| } |
| |
| nspace = (x_cols - max_col * cols) / cols; |
| if (nspace < 2) |
| nspace = 2; |
| max_col = -max_col; |
| goto prcols_hard; |
| while (r < rows) { |
| shf_putchar(opts->linesep, opts->shf); |
| prcols_hard: |
| for (c = 0; c < cols; c++) { |
| if ((i = c * rows + r) >= n) |
| break; |
| (*func)(str, max_oct, i, arg); |
| if (i + rows >= n) |
| shf_puts(str, opts->shf); |
| else |
| shf_fprintf(opts->shf, "%*s%*s", |
| (int)max_col, str, (int)nspace, null); |
| } |
| ++r; |
| } |
| out: |
| if (opts->do_last) |
| shf_putchar(opts->linesep, opts->shf); |
| afree(str, ATEMP); |
| } |
| |
| /* strip all NUL bytes from buf; output is NUL-terminated if stripped */ |
| void |
| strip_nuls(char *buf, size_t len) |
| { |
| char *cp, *dp, *ep; |
| |
| if (!len || !(dp = memchr(buf, '\0', len))) |
| return; |
| |
| ep = buf + len; |
| cp = dp; |
| |
| cp_has_nul_byte: |
| while (cp++ < ep && *cp == '\0') |
| ; /* nothing */ |
| while (cp < ep && *cp != '\0') |
| *dp++ = *cp++; |
| if (cp < ep) |
| goto cp_has_nul_byte; |
| |
| *dp = '\0'; |
| } |
| |
| /* |
| * Like read(2), but if read fails due to non-blocking flag, |
| * resets flag and restarts read. |
| */ |
| ssize_t |
| blocking_read(int fd, char *buf, size_t nbytes) |
| { |
| ssize_t ret; |
| bool tried_reset = false; |
| |
| while ((ret = read(fd, buf, nbytes)) < 0) { |
| if (!tried_reset && errno == EAGAIN) { |
| if (reset_nonblock(fd) > 0) { |
| tried_reset = true; |
| continue; |
| } |
| errno = EAGAIN; |
| } |
| break; |
| } |
| return (ret); |
| } |
| |
| /* |
| * Reset the non-blocking flag on the specified file descriptor. |
| * Returns -1 if there was an error, 0 if non-blocking wasn't set, |
| * 1 if it was. |
| */ |
| int |
| reset_nonblock(int fd) |
| { |
| int flags; |
| |
| if ((flags = fcntl(fd, F_GETFL, 0)) < 0) |
| return (-1); |
| if (!(flags & O_NONBLOCK)) |
| return (0); |
| flags &= ~O_NONBLOCK; |
| if (fcntl(fd, F_SETFL, flags) < 0) |
| return (-1); |
| return (1); |
| } |
| |
| /* getcwd(3) equivalent, allocates from ATEMP but doesn't resize */ |
| char * |
| ksh_get_wd(void) |
| { |
| #ifdef MKSH__NO_PATH_MAX |
| char *rv, *cp; |
| |
| if ((cp = get_current_dir_name())) { |
| strdupx(rv, cp, ATEMP); |
| free_gnu_gcdn(cp); |
| } else |
| rv = NULL; |
| #else |
| char *rv; |
| |
| if (!getcwd((rv = alloc(PATH_MAX + 1, ATEMP)), PATH_MAX)) { |
| afree(rv, ATEMP); |
| rv = NULL; |
| } |
| #endif |
| |
| return (rv); |
| } |
| |
| #ifndef ELOOP |
| #define ELOOP E2BIG |
| #endif |
| |
| char * |
| do_realpath(const char *upath) |
| { |
| char *xp, *ip, *tp, *ipath, *ldest = NULL; |
| XString xs; |
| size_t pos, len; |
| int llen; |
| struct stat sb; |
| #ifdef MKSH__NO_PATH_MAX |
| size_t ldestlen = 0; |
| #define pathlen sb.st_size |
| #define pathcnd (ldestlen < (pathlen + 1)) |
| #else |
| #define pathlen PATH_MAX |
| #define pathcnd (!ldest) |
| #endif |
| /* max. recursion depth */ |
| int symlinks = 32; |
| |
| if (mksh_abspath(upath)) { |
| /* upath is an absolute pathname */ |
| strdupx(ipath, upath, ATEMP); |
| #ifdef MKSH_DOSPATH |
| } else if (mksh_drvltr(upath)) { |
| /* upath is a drive-relative pathname */ |
| if (getdrvwd(&ldest, ord(*upath))) |
| return (NULL); |
| /* A:foo -> A:/cwd/foo; A: -> A:/cwd */ |
| strpathx(ipath, ldest, upath + 2, 0); |
| #endif |
| } else { |
| /* upath is a relative pathname, prepend cwd */ |
| if ((tp = ksh_get_wd()) == NULL || !mksh_abspath(tp)) |
| return (NULL); |
| strpathx(ipath, tp, upath, 1); |
| afree(tp, ATEMP); |
| } |
| |
| /* ipath and upath are in memory at the same time -> unchecked */ |
| Xinit(xs, xp, strlen(ip = ipath) + 1, ATEMP); |
| |
| /* now jump into the deep of the loop */ |
| goto beginning_of_a_pathname; |
| |
| while (*ip) { |
| /* skip slashes in input */ |
| while (mksh_cdirsep(*ip)) |
| ++ip; |
| if (!*ip) |
| break; |
| |
| /* get next pathname component from input */ |
| tp = ip; |
| while (*ip && !mksh_cdirsep(*ip)) |
| ++ip; |
| len = ip - tp; |
| |
| /* check input for "." and ".." */ |
| if (tp[0] == '.') { |
| if (len == 1) |
| /* just continue with the next one */ |
| continue; |
| else if (len == 2 && tp[1] == '.') { |
| /* strip off last pathname component */ |
| /*XXX consider a rooted pathname */ |
| while (xp > Xstring(xs, xp)) |
| if (mksh_cdirsep(*--xp)) |
| break; |
| /* then continue with the next one */ |
| continue; |
| } |
| } |
| |
| /* store output position away, then append slash to output */ |
| pos = Xsavepos(xs, xp); |
| /* 1 for the '/' and len + 1 for tp and the NUL from below */ |
| XcheckN(xs, xp, 1 + len + 1); |
| Xput(xs, xp, '/'); |
| |
| /* append next pathname component to output */ |
| memcpy(xp, tp, len); |
| xp += len; |
| *xp = '\0'; |
| |
| /* lstat the current output, see if it's a symlink */ |
| if (mksh_lstat(Xstring(xs, xp), &sb)) { |
| /* lstat failed */ |
| if (errno == ENOENT) { |
| /* because the pathname does not exist */ |
| while (mksh_cdirsep(*ip)) |
| /* skip any trailing slashes */ |
| ++ip; |
| /* no more components left? */ |
| if (!*ip) |
| /* we can still return successfully */ |
| break; |
| /* more components left? fall through */ |
| } |
| /* not ENOENT or not at the end of ipath */ |
| goto notfound; |
| } |
| |
| /* check if we encountered a symlink? */ |
| if (S_ISLNK(sb.st_mode)) { |
| #ifndef MKSH__NO_SYMLINK |
| /* reached maximum recursion depth? */ |
| if (!symlinks--) { |
| /* yep, prevent infinite loops */ |
| errno = ELOOP; |
| goto notfound; |
| } |
| |
| /* get symlink(7) target */ |
| if (pathcnd) { |
| #ifdef MKSH__NO_PATH_MAX |
| if (notoktoadd(pathlen, 1)) { |
| errno = ENAMETOOLONG; |
| goto notfound; |
| } |
| #endif |
| ldest = aresize(ldest, pathlen + 1, ATEMP); |
| } |
| llen = readlink(Xstring(xs, xp), ldest, pathlen); |
| if (llen < 0) |
| /* oops... */ |
| goto notfound; |
| ldest[llen] = '\0'; |
| |
| /* |
| * restart if symlink target is an absolute path, |
| * otherwise continue with currently resolved prefix |
| */ |
| #ifdef MKSH_DOSPATH |
| assemble_symlink: |
| #endif |
| /* append rest of current input path to link target */ |
| strpathx(tp, ldest, ip, 0); |
| afree(ipath, ATEMP); |
| ip = ipath = tp; |
| if (!mksh_abspath(ipath)) { |
| #ifdef MKSH_DOSPATH |
| /* symlink target might be drive-relative */ |
| if (mksh_drvltr(ipath)) { |
| if (getdrvwd(&ldest, ord(*ipath))) |
| goto notfound; |
| ip += 2; |
| goto assemble_symlink; |
| } |
| #endif |
| /* symlink target is a relative path */ |
| xp = Xrestpos(xs, xp, pos); |
| } else |
| #endif |
| { |
| /* symlink target is an absolute path */ |
| xp = Xstring(xs, xp); |
| beginning_of_a_pathname: |
| /* assert: mksh_abspath(ip == ipath) */ |
| /* assert: xp == xs.beg => start of path */ |
| |
| /* exactly two leading slashes? (SUSv4 3.266) */ |
| if (ip[1] == ip[0] && !mksh_cdirsep(ip[2])) { |
| /* keep them, e.g. for UNC pathnames */ |
| Xput(xs, xp, '/'); |
| } |
| #ifdef MKSH_DOSPATH |
| /* drive letter? */ |
| if (mksh_drvltr(ip)) { |
| /* keep it */ |
| Xput(xs, xp, *ip++); |
| Xput(xs, xp, *ip++); |
| } |
| #endif |
| } |
| } |
| /* otherwise (no symlink) merely go on */ |
| } |
| |
| /* |
| * either found the target and successfully resolved it, |
| * or found its parent directory and may create it |
| */ |
| if (Xlength(xs, xp) == 0) |
| /* |
| * if the resolved pathname is "", make it "/", |
| * otherwise do not add a trailing slash |
| */ |
| Xput(xs, xp, '/'); |
| Xput(xs, xp, '\0'); |
| |
| /* |
| * if source path had a trailing slash, check if target path |
| * is not a non-directory existing file |
| */ |
| if (ip > ipath && mksh_cdirsep(ip[-1])) { |
| if (stat(Xstring(xs, xp), &sb)) { |
| if (errno != ENOENT) |
| goto notfound; |
| } else if (!S_ISDIR(sb.st_mode)) { |
| errno = ENOTDIR; |
| goto notfound; |
| } |
| /* target now either does not exist or is a directory */ |
| } |
| |
| /* return target path */ |
| afree(ldest, ATEMP); |
| afree(ipath, ATEMP); |
| return (Xclose(xs, xp)); |
| |
| notfound: |
| /* save; freeing memory might trash it */ |
| llen = errno; |
| afree(ldest, ATEMP); |
| afree(ipath, ATEMP); |
| Xfree(xs, xp); |
| errno = llen; |
| return (NULL); |
| |
| #undef pathlen |
| #undef pathcnd |
| } |
| |
| /** |
| * Makes a filename into result using the following algorithm. |
| * - make result NULL |
| * - if file starts with '/', append file to result & set cdpathp to NULL |
| * - if file starts with ./ or ../ append cwd and file to result |
| * and set cdpathp to NULL |
| * - if the first element of cdpathp doesn't start with a '/' xx or '.' xx |
| * then cwd is appended to result. |
| * - the first element of cdpathp is appended to result |
| * - file is appended to result |
| * - cdpathp is set to the start of the next element in cdpathp (or NULL |
| * if there are no more elements. |
| * The return value indicates whether a non-null element from cdpathp |
| * was appended to result. |
| */ |
| static int |
| make_path(const char *cwd, const char *file, |
| /* pointer to colon-separated list */ |
| char **cdpathp, |
| XString *xsp, |
| int *phys_pathp) |
| { |
| int rval = 0; |
| bool use_cdpath = true; |
| char *plist; |
| size_t len, plen = 0; |
| char *xp = Xstring(*xsp, xp); |
| |
| if (!file) |
| file = null; |
| |
| if (mksh_abspath(file)) { |
| *phys_pathp = 0; |
| use_cdpath = false; |
| } else { |
| if (file[0] == '.') { |
| char c = file[1]; |
| |
| if (c == '.') |
| c = file[2]; |
| if (mksh_cdirsep(c) || c == '\0') |
| use_cdpath = false; |
| } |
| |
| plist = *cdpathp; |
| if (!plist) |
| use_cdpath = false; |
| else if (use_cdpath) { |
| char *pend = plist; |
| |
| while (*pend && *pend != MKSH_PATHSEPC) |
| ++pend; |
| plen = pend - plist; |
| *cdpathp = *pend ? pend + 1 : NULL; |
| } |
| |
| if ((!use_cdpath || !plen || !mksh_abspath(plist)) && |
| (cwd && *cwd)) { |
| len = strlen(cwd); |
| XcheckN(*xsp, xp, len); |
| memcpy(xp, cwd, len); |
| xp += len; |
| if (mksh_cdirsep(xp[-1])) |
| xp--; |
| *xp++ = '/'; |
| } |
| *phys_pathp = Xlength(*xsp, xp); |
| if (use_cdpath && plen) { |
| XcheckN(*xsp, xp, plen); |
| memcpy(xp, plist, plen); |
| xp += plen; |
| if (mksh_cdirsep(xp[-1])) |
| xp--; |
| *xp++ = '/'; |
| rval = 1; |
| } |
| } |
| |
| len = strlen(file) + 1; |
| XcheckN(*xsp, xp, len); |
| memcpy(xp, file, len); |
| |
| if (!use_cdpath) |
| *cdpathp = NULL; |
| |
| return (rval); |
| } |
| |
| /*- |
| * Simplify pathnames containing "." and ".." entries. |
| * |
| * simplify_path(this) = that |
| * /a/b/c/./../d/.. /a/b |
| * //./C/foo/bar/../baz //C/foo/baz |
| * /foo/ /foo |
| * /foo/../../bar /bar |
| * /foo/./blah/.. /foo |
| * . . |
| * .. .. |
| * ./foo foo |
| * foo/../../../bar ../../bar |
| * C:/foo/../.. C:/ |
| * C:. C: |
| * C:.. C:.. |
| * C:foo/../../blah C:../blah |
| * |
| * XXX consider a rooted pathname: we cannot really 'cd ..' for |
| * pathnames like: '/', 'c:/', '//foo', '//foo/', '/@unixroot/' |
| * (no effect), 'c:', 'c:.' (effect is retaining the '../') but |
| * we need to honour this throughout the shell |
| */ |
| void |
| simplify_path(char *p) |
| { |
| char *dp, *ip, *sp, *tp; |
| size_t len; |
| bool needslash; |
| #ifdef MKSH_DOSPATH |
| bool needdot = true; |
| |
| /* keep drive letter */ |
| if (mksh_drvltr(p)) { |
| p += 2; |
| needdot = false; |
| } |
| #else |
| #define needdot true |
| #endif |
| |
| switch (*p) { |
| case 0: |
| return; |
| case '/': |
| #ifdef MKSH_DOSPATH |
| case '\\': |
| #endif |
| /* exactly two leading slashes? (SUSv4 3.266) */ |
| if (p[1] == p[0] && !mksh_cdirsep(p[2])) { |
| /* keep them, e.g. for UNC pathnames */ |
| #ifdef MKSH_DOSPATH |
| *p++ = '/'; |
| #else |
| ++p; |
| #endif |
| } |
| needslash = true; |
| break; |
| default: |
| needslash = false; |
| } |
| dp = ip = sp = p; |
| |
| while (*ip) { |
| /* skip slashes in input */ |
| while (mksh_cdirsep(*ip)) |
| ++ip; |
| if (!*ip) |
| break; |
| |
| /* get next pathname component from input */ |
| tp = ip; |
| while (*ip && !mksh_cdirsep(*ip)) |
| ++ip; |
| len = ip - tp; |
| |
| /* check input for "." and ".." */ |
| if (tp[0] == '.') { |
| if (len == 1) |
| /* just continue with the next one */ |
| continue; |
| else if (len == 2 && tp[1] == '.') { |
| /* parent level, but how? (see above) */ |
| if (mksh_abspath(p)) |
| /* absolute path, only one way */ |
| goto strip_last_component; |
| else if (dp > sp) { |
| /* relative path, with subpaths */ |
| needslash = false; |
| strip_last_component: |
| /* strip off last pathname component */ |
| while (dp > sp) |
| if (mksh_cdirsep(*--dp)) |
| break; |
| } else { |
| /* relative path, at its beginning */ |
| if (needslash) |
| /* or already dotdot-slash'd */ |
| *dp++ = '/'; |
| /* keep dotdot-slash if not absolute */ |
| *dp++ = '.'; |
| *dp++ = '.'; |
| needslash = true; |
| sp = dp; |
| } |
| /* then continue with the next one */ |
| continue; |
| } |
| } |
| |
| if (needslash) |
| *dp++ = '/'; |
| |
| /* append next pathname component to output */ |
| memmove(dp, tp, len); |
| dp += len; |
| |
| /* append slash if we continue */ |
| needslash = true; |
| /* try next component */ |
| } |
| if (dp == p) { |
| /* empty path -> dot (or slash, when absolute) */ |
| if (needslash) |
| *dp++ = '/'; |
| else if (needdot) |
| *dp++ = '.'; |
| } |
| *dp = '\0'; |
| #undef needdot |
| } |
| |
| void |
| set_current_wd(const char *nwd) |
| { |
| char *allocd = NULL; |
| |
| if (nwd == NULL) { |
| allocd = ksh_get_wd(); |
| nwd = allocd ? allocd : null; |
| } |
| |
| afree(current_wd, APERM); |
| strdupx(current_wd, nwd, APERM); |
| |
| afree(allocd, ATEMP); |
| } |
| |
| int |
| c_cd(const char **wp) |
| { |
| int optc, rv, phys_path; |
| bool physical = tobool(Flag(FPHYSICAL)); |
| /* was a node from cdpath added in? */ |
| int cdnode; |
| /* show where we went?, error for $PWD */ |
| bool printpath = false, eflag = false; |
| struct tbl *pwd_s, *oldpwd_s; |
| XString xs; |
| char *dir, *allocd = NULL, *tryp, *pwd, *cdpath; |
| |
| while ((optc = ksh_getopt(wp, &builtin_opt, "eLP")) != -1) |
| switch (optc) { |
| case 'e': |
| eflag = true; |
| break; |
| case 'L': |
| physical = false; |
| break; |
| case 'P': |
| physical = true; |
| break; |
| case '?': |
| return (2); |
| } |
| wp += builtin_opt.optind; |
| |
| if (Flag(FRESTRICTED)) { |
| bi_errorf(Tcant_cd); |
| return (2); |
| } |
| |
| pwd_s = global(TPWD); |
| oldpwd_s = global(TOLDPWD); |
| |
| if (!wp[0]) { |
| /* no arguments; go home */ |
| if ((dir = str_val(global("HOME"))) == null) { |
| bi_errorf("no home directory (HOME not set)"); |
| return (2); |
| } |
| } else if (!wp[1]) { |
| /* one argument: - or dir */ |
| if (ksh_isdash(wp[0])) { |
| dir = str_val(oldpwd_s); |
| if (dir == null) { |
| bi_errorf(Tno_OLDPWD); |
| return (2); |
| } |
| printpath = true; |
| } else { |
| strdupx(allocd, wp[0], ATEMP); |
| dir = allocd; |
| } |
| } else if (!wp[2]) { |
| /* two arguments; substitute arg1 in PWD for arg2 */ |
| size_t ilen, olen, nlen, elen; |
| char *cp; |
| |
| if (!current_wd[0]) { |
| bi_errorf("can't determine current directory"); |
| return (2); |
| } |
| /* |
| * Substitute arg1 for arg2 in current path. If the first |
| * substitution fails because the cd fails we could try to |
| * find another substitution. For now, we don't. |
| */ |
| if ((cp = strstr(current_wd, wp[0])) == NULL) { |
| bi_errorf(Tbadsubst); |
| return (2); |
| } |
| /*- |
| * ilen = part of current_wd before wp[0] |
| * elen = part of current_wd after wp[0] |
| * because current_wd and wp[1] need to be in memory at the |
| * same time beforehand the addition can stay unchecked |
| */ |
| ilen = cp - current_wd; |
| olen = strlen(wp[0]); |
| nlen = strlen(wp[1]); |
| elen = strlen(current_wd + ilen + olen) + 1; |
| dir = allocd = alloc(ilen + nlen + elen, ATEMP); |
| memcpy(dir, current_wd, ilen); |
| memcpy(dir + ilen, wp[1], nlen); |
| memcpy(dir + ilen + nlen, current_wd + ilen + olen, elen); |
| printpath = true; |
| } else { |
| bi_errorf(Ttoo_many_args); |
| return (2); |
| } |
| |
| #ifdef MKSH_DOSPATH |
| tryp = NULL; |
| if (mksh_drvltr(dir) && !mksh_cdirsep(dir[2]) && |
| !getdrvwd(&tryp, ord(*dir))) { |
| strpathx(dir, tryp, dir + 2, 0); |
| afree(tryp, ATEMP); |
| afree(allocd, ATEMP); |
| allocd = dir; |
| } |
| #endif |
| |
| #ifdef MKSH__NO_PATH_MAX |
| /* only a first guess; make_path will enlarge xs if necessary */ |
| XinitN(xs, 1024, ATEMP); |
| #else |
| XinitN(xs, PATH_MAX, ATEMP); |
| #endif |
| |
| cdpath = str_val(global("CDPATH")); |
| do { |
| cdnode = make_path(current_wd, dir, &cdpath, &xs, &phys_path); |
| if (physical) |
| rv = chdir(tryp = Xstring(xs, xp) + phys_path); |
| else { |
| simplify_path(Xstring(xs, xp)); |
| rv = chdir(tryp = Xstring(xs, xp)); |
| } |
| } while (rv < 0 && cdpath != NULL); |
| |
| if (rv < 0) { |
| if (cdnode) |
| bi_errorf(Tf_sD_s, dir, "bad directory"); |
| else |
| bi_errorf(Tf_sD_s, tryp, cstrerror(errno)); |
| afree(allocd, ATEMP); |
| Xfree(xs, xp); |
| return (2); |
| } |
| |
| rv = 0; |
| |
| /* allocd (above) => dir, which is no longer used */ |
| afree(allocd, ATEMP); |
| allocd = NULL; |
| |
| /* Clear out tracked aliases with relative paths */ |
| flushcom(false); |
| |
| /* |
| * Set OLDPWD (note: unsetting OLDPWD does not disable this |
| * setting in AT&T ksh) |
| */ |
| if (current_wd[0]) |
| /* Ignore failure (happens if readonly or integer) */ |
| setstr(oldpwd_s, current_wd, KSH_RETURN_ERROR); |
| |
| if (!mksh_abspath(Xstring(xs, xp))) { |
| pwd = NULL; |
| } else if (!physical) { |
| goto norealpath_PWD; |
| } else if ((pwd = allocd = do_realpath(Xstring(xs, xp))) == NULL) { |
| if (eflag) |
| rv = 1; |
| norealpath_PWD: |
| pwd = Xstring(xs, xp); |
| } |
| |
| /* Set PWD */ |
| if (pwd) { |
| char *ptmp = pwd; |
| |
| set_current_wd(ptmp); |
| /* Ignore failure (happens if readonly or integer) */ |
| setstr(pwd_s, ptmp, KSH_RETURN_ERROR); |
| } else { |
| set_current_wd(null); |
| pwd = Xstring(xs, xp); |
| /* XXX unset $PWD? */ |
| if (eflag) |
| rv = 1; |
| } |
| if (printpath || cdnode) |
| shprintf(Tf_sN, pwd); |
| |
| afree(allocd, ATEMP); |
| Xfree(xs, xp); |
| return (rv); |
| } |
| |
| |
| #ifdef KSH_CHVT_CODE |
| extern void chvt_reinit(void); |
| |
| static void |
| chvt(const Getopt *go) |
| { |
| const char *dv = go->optarg; |
| char *cp = NULL; |
| int fd; |
| |
| switch (*dv) { |
| case '-': |
| dv = "/dev/null"; |
| break; |
| case '!': |
| ++dv; |
| /* FALLTHROUGH */ |
| default: { |
| struct stat sb; |
| |
| if (stat(dv, &sb)) { |
| cp = shf_smprintf("/dev/ttyC%s", dv); |
| dv = cp; |
| if (stat(dv, &sb)) { |
| memmove(cp + 1, cp, /* /dev/tty */ 8); |
| dv = cp + 1; |
| if (stat(dv, &sb)) { |
| errorf(Tf_sD_sD_s, "chvt", |
| "can't find tty", go->optarg); |
| } |
| } |
| } |
| if (!(sb.st_mode & S_IFCHR)) |
| errorf(Tf_sD_sD_s, "chvt", "not a char device", dv); |
| #ifndef MKSH_DISABLE_REVOKE_WARNING |
| #if HAVE_REVOKE |
| if (revoke(dv)) |
| #endif |
| warningf(false, Tf_sD_s_s, "chvt", |
| "new shell is potentially insecure, can't revoke", |
| dv); |
| #endif |
| } |
| } |
| if ((fd = binopen2(dv, O_RDWR)) < 0) { |
| sleep(1); |
| if ((fd = binopen2(dv, O_RDWR)) < 0) { |
| errorf(Tf_sD_s_s, "chvt", Tcant_open, dv); |
| } |
| } |
| if (go->optarg[0] != '!') { |
| switch (fork()) { |
| case -1: |
| errorf(Tf_sD_s_s, "chvt", "fork", "failed"); |
| case 0: |
| break; |
| default: |
| exit(0); |
| } |
| } |
| if (setsid() == -1) |
| errorf(Tf_sD_s_s, "chvt", "setsid", "failed"); |
| if (go->optarg[0] != '-') { |
| if (ioctl(fd, TIOCSCTTY, NULL) == -1) |
| errorf(Tf_sD_s_s, "chvt", "TIOCSCTTY", "failed"); |
| if (tcflush(fd, TCIOFLUSH)) |
| errorf(Tf_sD_s_s, "chvt", "TCIOFLUSH", "failed"); |
| } |
| ksh_dup2(fd, 0, false); |
| ksh_dup2(fd, 1, false); |
| ksh_dup2(fd, 2, false); |
| if (fd > 2) |
| close(fd); |
| rndset((unsigned long)chvt_rndsetup(go, sizeof(Getopt))); |
| chvt_reinit(); |
| } |
| #endif |
| |
| #ifdef DEBUG |
| char * |
| strchr(char *p, int ch) |
| { |
| for (;; ++p) { |
| if (*p == ch) |
| return (p); |
| if (!*p) |
| return (NULL); |
| } |
| /* NOTREACHED */ |
| } |
| |
| char * |
| strstr(char *b, const char *l) |
| { |
| char first, c; |
| size_t n; |
| |
| if ((first = *l++) == '\0') |
| return (b); |
| n = strlen(l); |
| strstr_look: |
| while ((c = *b++) != first) |
| if (c == '\0') |
| return (NULL); |
| if (strncmp(b, l, n)) |
| goto strstr_look; |
| return (b - 1); |
| } |
| #endif |
| |
| #if defined(MKSH_SMALL) && !defined(MKSH_SMALL_BUT_FAST) |
| char * |
| strndup_i(const char *src, size_t len, Area *ap) |
| { |
| char *dst = NULL; |
| |
| if (src != NULL) { |
| dst = alloc(len + 1, ap); |
| memcpy(dst, src, len); |
| dst[len] = '\0'; |
| } |
| return (dst); |
| } |
| |
| char * |
| strdup_i(const char *src, Area *ap) |
| { |
| return (src == NULL ? NULL : strndup_i(src, strlen(src), ap)); |
| } |
| #endif |
| |
| #if !HAVE_GETRUSAGE |
| #define INVTCK(r,t) do { \ |
| r.tv_usec = ((t) % (1000000 / CLK_TCK)) * (1000000 / CLK_TCK); \ |
| r.tv_sec = (t) / CLK_TCK; \ |
| } while (/* CONSTCOND */ 0) |
| |
| int |
| getrusage(int what, struct rusage *ru) |
| { |
| struct tms tms; |
| clock_t u, s; |
| |
| if (/* ru == NULL || */ times(&tms) == (clock_t)-1) |
| return (-1); |
| |
| switch (what) { |
| case RUSAGE_SELF: |
| u = tms.tms_utime; |
| s = tms.tms_stime; |
| break; |
| case RUSAGE_CHILDREN: |
| u = tms.tms_cutime; |
| s = tms.tms_cstime; |
| break; |
| default: |
| errno = EINVAL; |
| return (-1); |
| } |
| INVTCK(ru->ru_utime, u); |
| INVTCK(ru->ru_stime, s); |
| return (0); |
| } |
| #endif |
| |
| /* |
| * process the string available via fg (get a char) |
| * and fp (put back a char) for backslash escapes, |
| * assuming the first call to *fg gets the char di- |
| * rectly after the backslash; return the character |
| * (0..0xFF), UCS (wc + 0x100), or -1 if no known |
| * escape sequence was found |
| */ |
| int |
| unbksl(bool cstyle, int (*fg)(void), void (*fp)(int)) |
| { |
| int wc, i, c, fc, n; |
| |
| fc = (*fg)(); |
| switch (fc) { |
| case 'a': |
| wc = KSH_BEL; |
| break; |
| case 'b': |
| wc = '\b'; |
| break; |
| case 'c': |
| if (!cstyle) |
| goto unknown_escape; |
| c = (*fg)(); |
| wc = ksh_toctrl(c); |
| break; |
| case 'E': |
| case 'e': |
| wc = KSH_ESC; |
| break; |
| case 'f': |
| wc = '\f'; |
| break; |
| case 'n': |
| wc = '\n'; |
| break; |
| case 'r': |
| wc = '\r'; |
| break; |
| case 't': |
| wc = '\t'; |
| break; |
| case 'v': |
| wc = KSH_VTAB; |
| break; |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| if (!cstyle) |
| goto unknown_escape; |
| /* FALLTHROUGH */ |
| case '0': |
| if (cstyle) |
| (*fp)(fc); |
| /* |
| * look for an octal number with up to three |
| * digits, not counting the leading zero; |
| * convert it to a raw octet |
| */ |
| wc = 0; |
| i = 3; |
| while (i--) |
| if (ctype((c = (*fg)()), C_OCTAL)) |
| wc = (wc << 3) + ksh_numdig(c); |
| else { |
| (*fp)(c); |
| break; |
| } |
| break; |
| case 'U': |
| i = 8; |
| if (/* CONSTCOND */ 0) |
| /* FALLTHROUGH */ |
| case 'u': |
| i = 4; |
| if (/* CONSTCOND */ 0) |
| /* FALLTHROUGH */ |
| case 'x': |
| i = cstyle ? -1 : 2; |
| /** |
| * x: look for a hexadecimal number with up to |
| * two (C style: arbitrary) digits; convert |
| * to raw octet (C style: UCS if >0xFF) |
| * u/U: look for a hexadecimal number with up to |
| * four (U: eight) digits; convert to UCS |
| */ |
| wc = 0; |
| n = 0; |
| while (n < i || i == -1) { |
| wc <<= 4; |
| if (!ctype((c = (*fg)()), C_SEDEC)) { |
| wc >>= 4; |
| (*fp)(c); |
| break; |
| } |
| if (ctype(c, C_DIGIT)) |
| wc += ksh_numdig(c); |
| else if (ctype(c, C_UPPER)) |
| wc += ksh_numuc(c) + 10; |
| else |
| wc += ksh_numlc(c) + 10; |
| ++n; |
| } |
| if (!n) |
| goto unknown_escape; |
| if ((cstyle && wc > 0xFF) || fc != 'x') |
| /* UCS marker */ |
| wc += 0x100; |
| break; |
| case '\'': |
| if (!cstyle) |
| goto unknown_escape; |
| wc = '\''; |
| break; |
| case '\\': |
| wc = '\\'; |
| break; |
| default: |
| unknown_escape: |
| (*fp)(fc); |
| return (-1); |
| } |
| |
| return (wc); |
| } |