/** * 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 (c == '?' || c == ':' || c == ';' || c == ',' || c == '#' || !(o = cstrchr(optionsp, c))) { if (optionsp[0] == ':') { go->buf[0] = c; go->optarg = go->buf; } else { warningf(true, "%s%s-%c: %s", (go->flags & GF_NONAME) ? "" : argv[0], (go->flags & GF_NONAME) ? "" : ": ", c, "unknown option"); if (go->flags & GF_ERROR) bi_errorfz(); } return ('?'); } /** * : 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 (':'); } warningf(true, "%s%s-%c: %s", (go->flags & GF_NONAME) ? "" : argv[0], (go->flags & GF_NONAME) ? "" : ": ", c, "requires an argument"); if (go->flags & GF_ERROR) bi_errorfz(); return ('?'); } 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 (ksh_isdigit(argv[go->optind - 1][go->p])) { go->optarg = argv[go->optind - 1] + go->p; go->p = 0; } else go->optarg = NULL; } else { if (argv[go->optind] && ksh_isdigit(argv[go->optind][0])) { go->optarg = argv[go->optind++]; go->p = 0; } else go->optarg = NULL; } } return (c); }
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("restricted shell - can't cd"); return (2); } pwd_s = global("PWD"); oldpwd_s = global("OLDPWD"); 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 */ strdupx(allocd, wp[0], ATEMP); if (ksh_isdash((dir = allocd))) { afree(allocd, ATEMP); allocd = NULL; dir = str_val(oldpwd_s); if (dir == null) { bi_errorf("no OLDPWD"); return (2); } printpath = true; } } 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("bad substitution"); 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("too many arguments"); return (2); } #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("%s: %s", dir, "bad directory"); else bi_errorf("%s: %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("%s\n", pwd); afree(allocd, ATEMP); Xfree(xs, xp); return (rv); }
/* * set up redirection, saving old fds in e->savefd */ static int iosetup(struct ioword *iop, struct tbl *tp) { int u = -1; char *cp = iop->ioname; int iotype = iop->ioflag & IOTYPE; bool do_open = true, do_close = false; int flags = 0; struct ioword iotmp; struct stat statb; if (iotype != IOHERE) cp = evalonestr(cp, DOTILDE|(Flag(FTALKING_I) ? DOGLOB : 0)); /* Used for tracing and error messages to print expanded cp */ iotmp = *iop; iotmp.ioname = (iotype == IOHERE) ? NULL : cp; iotmp.ioflag |= IONAMEXP; if (Flag(FXTRACE)) { change_xtrace(2, false); fptreef(shl_xtrace, 0, "%R", &iotmp); change_xtrace(1, false); } switch (iotype) { case IOREAD: flags = O_RDONLY; break; case IOCAT: flags = O_WRONLY | O_APPEND | O_CREAT; break; case IOWRITE: flags = O_WRONLY | O_CREAT | O_TRUNC; /* * The stat() is here to allow redirections to * things like /dev/null without error. */ if (Flag(FNOCLOBBER) && !(iop->ioflag & IOCLOB) && (stat(cp, &statb) < 0 || S_ISREG(statb.st_mode))) flags |= O_EXCL; break; case IORDWR: flags = O_RDWR | O_CREAT; break; case IOHERE: do_open = false; /* herein() returns -2 if error has been printed */ u = herein(iop, NULL); /* cp may have wrong name */ break; case IODUP: { const char *emsg; do_open = false; if (ksh_isdash(cp)) { /* prevent error return below */ u = 1009; do_close = true; } else if ((u = check_fd(cp, X_OK | ((iop->ioflag & IORDUP) ? R_OK : W_OK), &emsg)) < 0) { char *sp; warningf(true, "%s: %s", (sp = snptreef(NULL, 32, "%R", &iotmp)), emsg); afree(sp, ATEMP); return (-1); } if (u == (int)iop->unit) /* "dup from" == "dup to" */ return (0); break; } } if (do_open) { if (Flag(FRESTRICTED) && (flags & O_CREAT)) { warningf(true, "%s: %s", cp, "restricted"); return (-1); } u = binopen3(cp, flags, 0666); } if (u < 0) { /* herein() may already have printed message */ if (u == -1) { u = errno; warningf(true, "can't %s %s: %s", iotype == IODUP ? "dup" : (iotype == IOREAD || iotype == IOHERE) ? "open" : "create", cp, cstrerror(u)); } return (-1); } /* Do not save if it has already been redirected (i.e. "cat >x >y"). */ if (e->savefd[iop->unit] == 0) { /* If these are the same, it means unit was previously closed */ if (u == (int)iop->unit) e->savefd[iop->unit] = -1; else /* * c_exec() assumes e->savefd[fd] set for any * redirections. Ask savefd() not to close iop->unit; * this allows error messages to be seen if iop->unit * is 2; also means we can't lose the fd (eg, both * dup2 below and dup2 in restfd() failing). */ e->savefd[iop->unit] = savefd(iop->unit); } if (do_close) close(iop->unit); else if (u != (int)iop->unit) { if (ksh_dup2(u, iop->unit, true) < 0) { int eno; char *sp; eno = errno; warningf(true, "%s %s: %s", "can't finish (dup) redirection", (sp = snptreef(NULL, 32, "%R", &iotmp)), cstrerror(eno)); afree(sp, ATEMP); if (iotype != IODUP) close(u); return (-1); } if (iotype != IODUP) close(u); /* * Touching any co-process fd in an empty exec * causes the shell to close its copies */ else if (tp && tp->type == CSHELL && tp->val.f == c_exec) { if (iop->ioflag & IORDUP) /* possible exec <&p */ coproc_read_close(u); else /* possible exec >&p */ coproc_write_close(u); } } if (u == 2) /* Clear any write errors */ shf_reopen(2, SHF_WR, shl_out); return (0); }