void initio(void) { #ifdef DF const char *lfp; #endif /* force buffer allocation */ shf_fdopen(1, SHF_WR, shl_stdout); shf_fdopen(2, SHF_WR, shl_out); shf_fdopen(2, SHF_WR, shl_xtrace); #ifdef DF if ((lfp = getenv("SDMKSH_PATH")) == NULL) { if ((lfp = getenv("HOME")) == NULL || !mksh_abspath(lfp)) errorf("can't get home directory"); lfp = shf_smprintf(Tf_sSs, lfp, "mksh-dbg.txt"); } if ((shl_dbg_fd = open(lfp, O_WRONLY | O_APPEND | O_CREAT, 0600)) < 0) errorf("can't open debug output file %s", lfp); if (shl_dbg_fd < FDBASE) { int nfd; nfd = fcntl(shl_dbg_fd, F_DUPFD, FDBASE); close(shl_dbg_fd); if ((shl_dbg_fd = nfd) == -1) errorf("can't dup debug output file"); } fcntl(shl_dbg_fd, F_SETFD, FD_CLOEXEC); shf_fdopen(shl_dbg_fd, SHF_WR, shl_dbg); DF("=== open ==="); #endif initio_done = true; }
/* * flush executable commands with relative paths * (just relative or all?) */ void flushcom(bool all) { struct tbl *tp; struct tstate ts; for (ktwalk(&ts, &taliases); (tp = ktnext(&ts)) != NULL; ) if ((tp->flag&ISSET) && (all || !mksh_abspath(tp->val.s))) { if (tp->flag&ALLOC) { tp->flag &= ~(ALLOC|ISSET); afree(tp->val.s, APERM); } tp->flag &= ~ISSET; } }
static int main_init(int argc, const char *argv[], Source **sp, struct block **lp) { int argi, i; Source *s = NULL; struct block *l; unsigned char restricted_shell, errexit, utf_flag; char *cp; const char *ccp, **wp; struct tbl *vp; struct stat s_stdin; #if !defined(_PATH_DEFPATH) && defined(_CS_PATH) ssize_t k; #endif #if defined(MKSH_EBCDIC) || defined(MKSH_FAUX_EBCDIC) ebcdic_init(); #endif set_ifs(TC_IFSWS); #ifdef __OS2__ os2_init(&argc, &argv); #endif /* do things like getpgrp() et al. */ chvt_reinit(); /* make sure argv[] is sane, for weird OSes */ if (!*argv) { argv = empty_argv; argc = 1; } kshname = argv[0]; /* initialise permanent Area */ ainit(&aperm); /* max. name length: -2147483648 = 11 (+ NUL) */ vtemp = alloc(offsetof(struct tbl, name[0]) + 12, APERM); /* set up base environment */ env.type = E_NONE; ainit(&env.area); /* set up global l->vars and l->funs */ newblock(); /* Do this first so output routines (eg, errorf, shellf) can work */ initio(); /* determine the basename (without '-' or path) of the executable */ ccp = kshname; goto begin_parsing_kshname; while ((i = ccp[argi++])) { if (mksh_cdirsep(i)) { ccp += argi; begin_parsing_kshname: argi = 0; if (*ccp == '-') ++ccp; } } if (!*ccp) ccp = empty_argv[0]; /* * Turn on nohup by default. (AT&T ksh does not have a nohup * option - it always sends the hup). */ Flag(FNOHUP) = 1; /* * Turn on brace expansion by default. AT&T kshs that have * alternation always have it on. */ Flag(FBRACEEXPAND) = 1; /* * Turn on "set -x" inheritance by default. */ Flag(FXTRACEREC) = 1; /* define built-in commands and see if we were called as one */ ktinit(APERM, &builtins, /* currently up to 54 builtins: 75% of 128 = 2^7 */ 7); for (i = 0; mkshbuiltins[i].name != NULL; i++) if (!strcmp(ccp, builtin(mkshbuiltins[i].name, mkshbuiltins[i].func))) Flag(FAS_BUILTIN) = 1; if (!Flag(FAS_BUILTIN)) { /* check for -T option early */ argi = parse_args(argv, OF_FIRSTTIME, NULL); if (argi < 0) return (1); #if defined(MKSH_BINSHPOSIX) || defined(MKSH_BINSHREDUCED) /* are we called as -sh or /bin/sh or so? */ if (!strcmp(ccp, "sh" MKSH_EXE_EXT)) { /* either also turns off braceexpand */ #ifdef MKSH_BINSHPOSIX /* enable better POSIX conformance */ change_flag(FPOSIX, OF_FIRSTTIME, true); #endif #ifdef MKSH_BINSHREDUCED /* enable kludge/compat mode */ change_flag(FSH, OF_FIRSTTIME, true); #endif } #endif } initvar(); inittraps(); coproc_init(); /* set up variable and command dictionaries */ ktinit(APERM, &taliases, 0); ktinit(APERM, &aliases, 0); #ifndef MKSH_NOPWNAM ktinit(APERM, &homedirs, 0); #endif /* define shell keywords */ initkeywords(); init_histvec(); /* initialise tty size before importing environment */ change_winsz(); #ifdef _PATH_DEFPATH def_path = _PATH_DEFPATH; #else #ifdef _CS_PATH if ((k = confstr(_CS_PATH, NULL, 0)) > 0 && confstr(_CS_PATH, cp = alloc(k + 1, APERM), k + 1) == k + 1) def_path = cp; else #endif /* * this is uniform across all OSes unless it * breaks somewhere hard; don't try to optimise, * e.g. add stuff for Interix or remove /usr * for HURD, because e.g. Debian GNU/HURD is * "keeping a regular /usr"; this is supposed * to be a sane 'basic' default PATH */ def_path = MKSH_UNIXROOT "/bin" MKSH_PATHSEPS MKSH_UNIXROOT "/usr/bin" MKSH_PATHSEPS MKSH_UNIXROOT "/sbin" MKSH_PATHSEPS MKSH_UNIXROOT "/usr/sbin"; #endif /* * Set PATH to def_path (will set the path global variable). * (import of environment below will probably change this setting). */ vp = global(TPATH); /* setstr can't fail here */ setstr(vp, def_path, KSH_RETURN_ERROR); #ifndef MKSH_NO_CMDLINE_EDITING /* * Set edit mode to emacs by default, may be overridden * by the environment or the user. Also, we want tab completion * on in vi by default. */ change_flag(FEMACS, OF_SPECIAL, true); #if !MKSH_S_NOVI Flag(FVITABCOMPLETE) = 1; #endif #endif /* import environment */ init_environ(); /* override default PATH regardless of environment */ #ifdef MKSH_DEFPATH_OVERRIDE vp = global(TPATH); setstr(vp, MKSH_DEFPATH_OVERRIDE, KSH_RETURN_ERROR); #endif /* for security */ typeset(TinitIFS, 0, 0, 0, 0); /* assign default shell variable values */ typeset("PATHSEP=" MKSH_PATHSEPS, 0, 0, 0, 0); substitute(initsubs, 0); /* Figure out the current working directory and set $PWD */ vp = global(TPWD); cp = str_val(vp); /* Try to use existing $PWD if it is valid */ set_current_wd((mksh_abspath(cp) && test_eval(NULL, TO_FILEQ, cp, Tdot, true)) ? cp : NULL); if (current_wd[0]) simplify_path(current_wd); /* Only set pwd if we know where we are or if it had a bogus value */ if (current_wd[0] || *cp) /* setstr can't fail here */ setstr(vp, current_wd, KSH_RETURN_ERROR); for (wp = initcoms; *wp != NULL; wp++) { c_builtin(wp); while (*wp != NULL) wp++; } setint_n(global("OPTIND"), 1, 10); kshuid = getuid(); kshgid = getgid(); kshegid = getegid(); safe_prompt = ksheuid ? "$ " : "# "; vp = global("PS1"); /* Set PS1 if unset or we are root and prompt doesn't contain a # */ if (!(vp->flag & ISSET) || (!ksheuid && !strchr(str_val(vp), '#'))) /* setstr can't fail here */ setstr(vp, safe_prompt, KSH_RETURN_ERROR); setint_n((vp = global("BASHPID")), 0, 10); vp->flag |= INT_U; setint_n((vp = global("PGRP")), (mksh_uari_t)kshpgrp, 10); vp->flag |= INT_U; setint_n((vp = global("PPID")), (mksh_uari_t)kshppid, 10); vp->flag |= INT_U; setint_n((vp = global("USER_ID")), (mksh_uari_t)ksheuid, 10); vp->flag |= INT_U; setint_n((vp = global("KSHUID")), (mksh_uari_t)kshuid, 10); vp->flag |= INT_U; setint_n((vp = global("KSHEGID")), (mksh_uari_t)kshegid, 10); vp->flag |= INT_U; setint_n((vp = global("KSHGID")), (mksh_uari_t)kshgid, 10); vp->flag |= INT_U; setint_n((vp = global("RANDOM")), rndsetup(), 10); vp->flag |= INT_U; setint_n((vp_pipest = global("PIPESTATUS")), 0, 10); /* Set this before parsing arguments */ Flag(FPRIVILEGED) = (kshuid != ksheuid || kshgid != kshegid) ? 2 : 0; /* this to note if monitor is set on command line (see below) */ #ifndef MKSH_UNEMPLOYED Flag(FMONITOR) = 127; #endif /* this to note if utf-8 mode is set on command line (see below) */ UTFMODE = 2; if (!Flag(FAS_BUILTIN)) { argi = parse_args(argv, OF_CMDLINE, NULL); if (argi < 0) return (1); } /* process this later only, default to off (hysterical raisins) */ utf_flag = UTFMODE; UTFMODE = 0; if (Flag(FAS_BUILTIN)) { /* auto-detect from environment variables, always */ utf_flag = 3; } else if (Flag(FCOMMAND)) { s = pushs(SSTRINGCMDLINE, ATEMP); if (!(s->start = s->str = argv[argi++])) errorf(Tf_optfoo, "", "", 'c', Treq_arg); while (*s->str) { if (ctype(*s->str, C_QUOTE)) break; s->str++; } if (!*s->str) s->flags |= SF_MAYEXEC; s->str = s->start; #ifdef MKSH_MIDNIGHTBSD01ASH_COMPAT /* compatibility to MidnightBSD 0.1 /bin/sh (kludge) */ if (Flag(FSH) && argv[argi] && !strcmp(argv[argi], "--")) ++argi; #endif if (argv[argi]) kshname = argv[argi++]; } else if (argi < argc && !Flag(FSTDIN)) { s = pushs(SFILE, ATEMP); #ifdef __OS2__ /* * A bug in OS/2 extproc (like shebang) handling makes * it not pass the full pathname of a script, so we need * to search for it. This changes the behaviour of a * simple "mksh foo", but can't be helped. */ s->file = argv[argi++]; if (search_access(s->file, X_OK) != 0) s->file = search_path(s->file, path, X_OK, NULL); if (!s->file || !*s->file) s->file = argv[argi - 1]; #else s->file = argv[argi++]; #endif s->u.shf = shf_open(s->file, O_RDONLY, 0, SHF_MAPHI | SHF_CLEXEC); if (s->u.shf == NULL) { shl_stdout_ok = false; warningf(true, Tf_sD_s, s->file, cstrerror(errno)); /* mandated by SUSv4 */ exstat = 127; unwind(LERROR); } kshname = s->file; } else { Flag(FSTDIN) = 1; s = pushs(SSTDIN, ATEMP); s->file = "<stdin>"; s->u.shf = shf_fdopen(0, SHF_RD | can_seek(0), NULL); if (isatty(0) && isatty(2)) { Flag(FTALKING) = Flag(FTALKING_I) = 1; /* The following only if isatty(0) */ s->flags |= SF_TTY; s->u.shf->flags |= SHF_INTERRUPT; s->file = NULL; } } /* this bizarreness is mandated by POSIX */ if (fstat(0, &s_stdin) >= 0 && S_ISCHR(s_stdin.st_mode) && Flag(FTALKING)) reset_nonblock(0); /* initialise job control */ j_init(); /* do this after j_init() which calls tty_init_state() */ if (Flag(FTALKING)) { if (utf_flag == 2) { #ifndef MKSH_ASSUME_UTF8 /* auto-detect from locale or environment */ utf_flag = 4; #else /* this may not be an #elif */ #if MKSH_ASSUME_UTF8 utf_flag = 1; #else /* always disable UTF-8 (for interactive) */ utf_flag = 0; #endif #endif } #ifndef MKSH_NO_CMDLINE_EDITING x_init(); #endif } #ifdef SIGWINCH sigtraps[SIGWINCH].flags |= TF_SHELL_USES; setsig(&sigtraps[SIGWINCH], x_sigwinch, SS_RESTORE_ORIG|SS_FORCE|SS_SHTRAP); #endif l = e->loc; if (Flag(FAS_BUILTIN)) { l->argc = argc; l->argv = argv; l->argv[0] = ccp; } else { l->argc = argc - argi; /* * allocate a new array because otherwise, when we modify * it in-place, ps(1) output changes; the meaning of argc * here is slightly different as it excludes kshname, and * we add a trailing NULL sentinel as well */ l->argv = alloc2(l->argc + 2, sizeof(void *), APERM); l->argv[0] = kshname; memcpy(&l->argv[1], &argv[argi], l->argc * sizeof(void *)); l->argv[l->argc + 1] = NULL; getopts_reset(1); } /* divine the initial state of the utf8-mode Flag */ ccp = null; switch (utf_flag) { /* auto-detect from locale or environment */ case 4: #if HAVE_SETLOCALE_CTYPE ccp = setlocale(LC_CTYPE, ""); #if HAVE_LANGINFO_CODESET if (!isuc(ccp)) ccp = nl_langinfo(CODESET); #endif if (!isuc(ccp)) ccp = null; #endif /* FALLTHROUGH */ /* auto-detect from environment */ case 3: /* these were imported from environ earlier */ if (ccp == null) ccp = str_val(global("LC_ALL")); if (ccp == null) ccp = str_val(global("LC_CTYPE")); if (ccp == null) ccp = str_val(global("LANG")); UTFMODE = isuc(ccp); break; /* not set on command line, not FTALKING */ case 2: /* unknown values */ default: utf_flag = 0; /* FALLTHROUGH */ /* known values */ case 1: case 0: UTFMODE = utf_flag; break; } /* Disable during .profile/ENV reading */ restricted_shell = Flag(FRESTRICTED); Flag(FRESTRICTED) = 0; errexit = Flag(FERREXIT); Flag(FERREXIT) = 0; /* * Do this before profile/$ENV so that if it causes problems in them, * user will know why things broke. */ if (!current_wd[0] && Flag(FTALKING)) warningf(false, "can't determine current directory"); if (Flag(FLOGIN)) include(MKSH_SYSTEM_PROFILE, 0, NULL, true); if (!Flag(FPRIVILEGED)) { if (Flag(FLOGIN)) include(substitute("$HOME/.profile", 0), 0, NULL, true); if (Flag(FTALKING)) { cp = substitute("${ENV:-" MKSHRC_PATH "}", DOTILDE); if (cp[0] != '\0') include(cp, 0, NULL, true); } } else { include(MKSH_SUID_PROFILE, 0, NULL, true); /* turn off -p if not set explicitly */ if (Flag(FPRIVILEGED) != 1) change_flag(FPRIVILEGED, OF_INTERNAL, false); } if (restricted_shell) { c_builtin(restr_com); /* After typeset command... */ Flag(FRESTRICTED) = 1; } Flag(FERREXIT) = errexit; if (Flag(FTALKING) && s) hist_init(s); else /* set after ENV */ Flag(FTRACKALL) = 1; alarm_init(); *sp = s; *lp = l; return (0); }
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); }
/*- * 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 */ void simplify_path(char *p) { char *dp, *ip, *sp, *tp; size_t len; bool needslash; switch (*p) { case 0: return; case '/': /* exactly two leading slashes? (SUSv4 3.266) */ if (p[1] == '/' && p[2] != '/') /* keep them, e.g. for UNC pathnames */ ++p; needslash = true; break; default: needslash = false; } dp = ip = sp = p; while (*ip) { /* skip slashes in input */ while (*ip == '/') ++ip; if (!*ip) break; /* get next pathname component from input */ tp = ip; while (*ip && *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? */ 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 (*--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 */ *dp++ = needslash ? '/' : '.'; *dp = '\0'; }
/** * 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 doesnt 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 (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 (cwd[len - 1] != '/') Xput(*xsp, xp, '/'); } *phys_pathp = Xlength(*xsp, xp); if (use_cdpath && plen) { XcheckN(*xsp, xp, plen); memcpy(xp, plist, plen); xp += plen; if (plist[plen - 1] != '/') Xput(*xsp, xp, '/'); rval = 1; } } len = strlen(file) + 1; XcheckN(*xsp, xp, len); memcpy(xp, file, len); if (!use_cdpath) *cdpathp = NULL; return (rval); }
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); } else { /* upath is a relative pathname, prepend cwd */ if ((tp = ksh_get_wd()) == NULL || !mksh_abspath(tp)) return (NULL); ipath = shf_smprintf("%s%s%s", tp, "/", upath); 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 (*ip == '/') ++ip; if (!*ip) break; /* get next pathname component from input */ tp = ip; while (*ip && *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 */ while (xp > Xstring(xs, xp)) if (*--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 (*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 */ /* append rest of current input path to link target */ tp = shf_smprintf("%s%s%s", ldest, *ip ? "/" : "", ip); afree(ipath, ATEMP); ip = ipath = tp; if (!mksh_abspath(ldest)) { /* 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: (ip == ipath)[0] == '/' */ /* assert: xp == xs.beg => start of path */ /* exactly two leading slashes? (SUSv4 3.266) */ if (ip[1] == '/' && ip[2] != '/') { /* keep them, e.g. for UNC pathnames */ Xput(xs, xp, '/'); } } } /* 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 && 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 */ if (ldest != NULL) afree(ldest, ATEMP); afree(ipath, ATEMP); return (Xclose(xs, xp)); notfound: /* save; freeing memory might trash it */ llen = errno; if (ldest != NULL) afree(ldest, ATEMP); afree(ipath, ATEMP); Xfree(xs, xp); errno = llen; return (NULL); #undef pathlen #undef pathcnd }
static void setspec(struct tbl *vp) { mksh_ari_u num; char *s; int st; switch ((st = special(vp->name))) { #if HAVE_PERSISTENT_HISTORY case V_HISTFILE: sethistfile(str_val(vp)); return; #endif case V_IFS: setctypes(s = str_val(vp), C_IFS); ifs0 = *s; return; case V_PATH: if (path) afree(path, APERM); s = str_val(vp); strdupx(path, s, APERM); /* clear tracked aliases */ flushcom(true); return; case V_TMPDIR: if (tmpdir) { afree(tmpdir, APERM); tmpdir = NULL; } /* * Use tmpdir iff it is an absolute path, is writable * and searchable and is a directory... */ { struct stat statb; s = str_val(vp); /* LINTED use of access */ if (mksh_abspath(s) && access(s, W_OK|X_OK) == 0 && stat(s, &statb) == 0 && S_ISDIR(statb.st_mode)) strdupx(tmpdir, s, APERM); } return; /* common sub-cases */ case V_COLUMNS: case V_LINES: if (vp->flag & IMPORT) { /* do not touch */ unspecial(vp->name); vp->flag &= ~SPECIAL; return; } /* FALLTHROUGH */ case V_HISTSIZE: case V_LINENO: case V_OPTIND: case V_RANDOM: case V_SECONDS: case V_TMOUT: vp->flag &= ~SPECIAL; if (getint(vp, &num, false) == -1) { s = str_val(vp); if (st != V_RANDOM) errorf("%s: %s: %s", vp->name, "bad number", s); num.u = hash(s); } vp->flag |= SPECIAL; break; default: /* do nothing, do not touch vp at all */ return; } /* process the singular parts of the common cases */ switch (st) { case V_COLUMNS: if (num.i >= MIN_COLS) x_cols = num.i; break; case V_HISTSIZE: sethistsize(num.i); break; case V_LINENO: /* The -1 is because line numbering starts at 1. */ user_lineno = num.u - (mksh_uari_t)current_lineno - 1; break; case V_LINES: if (num.i >= MIN_LINS) x_lins = num.i; break; case V_OPTIND: getopts_reset((int)num.i); break; case V_RANDOM: /* * mksh R39d+ no longer has the traditional repeatability * of $RANDOM sequences, but always retains state */ rndset((unsigned long)num.u); break; case V_SECONDS: { struct timeval tv; mksh_TIME(tv); seconds = tv.tv_sec - num.i; } break; case V_TMOUT: ksh_tmout = num.i >= 0 ? num.i : 0; break; } }