151d913115
main.c: In function 'main': main.c:208: warning: cast discards qualifiers from pointer target type main.c:329: warning: cast discards qualifiers from pointer target type no warnings at autoconf time left either; will take care of these two later (might revisit changes from this commit), maybe change declararion for the builtins to have their argv[] be const strings, and go through strict type and qualifier checking again. this'll further improve stability. XXX these changes might have introduced (more?) memory leaks, XXX someone who knows about these tools should verify with XXX automatic memory usage analysers (valgrind?) still passes testsuite
1371 lines
30 KiB
C
1371 lines
30 KiB
C
/* $OpenBSD: misc.c,v 1.30 2006/03/12 00:26:58 deraadt Exp $ */
|
|
/* $OpenBSD: path.c,v 1.12 2005/03/30 17:16:37 deraadt Exp $ */
|
|
|
|
#include "sh.h"
|
|
#if HAVE_SETGROUPS
|
|
#include <grp.h>
|
|
#endif
|
|
|
|
__RCSID("$MirOS: src/bin/mksh/misc.c,v 1.42 2006/11/12 14:58:15 tg Exp $\t"
|
|
MKSH_SH_H_ID);
|
|
|
|
#undef USE_CHVT
|
|
#if defined(TIOCSCTTY) && !defined(MKSH_SMALL)
|
|
#define USE_CHVT
|
|
#endif
|
|
|
|
unsigned char chtypes[UCHAR_MAX + 1]; /* type bits for unsigned char */
|
|
|
|
static int do_gmatch(const unsigned char *, const unsigned char *,
|
|
const unsigned char *, const unsigned char *);
|
|
static const unsigned char *cclass(const unsigned char *, int);
|
|
#ifdef USE_CHVT
|
|
static void parse_T(char *);
|
|
#endif
|
|
static char *do_phys_path(XString *, char *, const char *);
|
|
|
|
/*
|
|
* Fast character classes
|
|
*/
|
|
void
|
|
setctypes(const char *s, int t)
|
|
{
|
|
unsigned i;
|
|
|
|
if (t & C_IFS) {
|
|
for (i = 0; i < UCHAR_MAX+1; i++)
|
|
chtypes[i] &= ~C_IFS;
|
|
chtypes[0] |= C_IFS; /* include \0 in C_IFS */
|
|
}
|
|
while (*s != 0)
|
|
chtypes[(unsigned char) *s++] |= t;
|
|
}
|
|
|
|
void
|
|
initctypes(void)
|
|
{
|
|
int c;
|
|
|
|
for (c = 'a'; c <= 'z'; c++)
|
|
chtypes[c] |= C_ALPHA;
|
|
for (c = 'A'; c <= 'Z'; c++)
|
|
chtypes[c] |= C_ALPHA;
|
|
chtypes['_'] |= C_ALPHA;
|
|
setctypes("0123456789", C_DIGIT);
|
|
setctypes(" \t\n|&;<>()", C_LEX1); /* \0 added automatically */
|
|
setctypes("*@#!$-?", C_VAR1);
|
|
setctypes(" \t\n", C_IFSWS);
|
|
setctypes("=-+?", C_SUBOP1);
|
|
setctypes(" \n\t\"#$&'()*;<>?[]\\`|", C_QUOTE);
|
|
}
|
|
|
|
/* Allocate a string of size n+1 and copy upto n characters from the possibly
|
|
* null terminated string s into it. Always returns a null terminated string
|
|
* (unless n < 0).
|
|
*/
|
|
char *
|
|
str_nsave(const char *s, int n, Area *ap)
|
|
{
|
|
char *ns = NULL;
|
|
|
|
if (n >= 0 && s)
|
|
strlcpy(ns = alloc(n + 1, ap), s, n + 1);
|
|
return (ns);
|
|
}
|
|
|
|
#ifdef MKSH_SMALL
|
|
char *
|
|
str_save(const char *s, Area *ap)
|
|
{
|
|
return (str_nsave(s, s ? strlen(s) : 0, ap));
|
|
}
|
|
#endif
|
|
|
|
/* called from XcheckN() to grow buffer */
|
|
char *
|
|
Xcheck_grow_(XString *xsp, char *xp, unsigned more)
|
|
{
|
|
char *old_beg = xsp->beg;
|
|
|
|
xsp->len += more > xsp->len ? more : xsp->len;
|
|
xsp->beg = aresize(xsp->beg, xsp->len + 8, xsp->areap);
|
|
xsp->end = xsp->beg + xsp->len;
|
|
return xsp->beg + (xp - old_beg);
|
|
}
|
|
|
|
const struct option options[] = {
|
|
/* Special cases (see parse_args()): -A, -o, -s.
|
|
* Options are sorted by their longnames - the order of these
|
|
* entries MUST match the order of sh_flag F* enumerations in sh.h.
|
|
*/
|
|
{ "allexport", 'a', OF_ANY },
|
|
{ "braceexpand", 0, OF_ANY }, /* non-standard */
|
|
{ "bgnice", 0, OF_ANY },
|
|
{ NULL, 'c', OF_CMDLINE },
|
|
{ "emacs", 0, OF_ANY },
|
|
{ "emacs-usemeta", 0, OF_ANY }, /* non-standard */
|
|
{ "errexit", 'e', OF_ANY },
|
|
{ "gmacs", 0, OF_ANY },
|
|
{ "ignoreeof", 0, OF_ANY },
|
|
{ "interactive",'i', OF_CMDLINE },
|
|
{ "keyword", 'k', OF_ANY },
|
|
{ "login", 'l', OF_CMDLINE },
|
|
{ "markdirs", 'X', OF_ANY },
|
|
{ "monitor", 'm', OF_ANY },
|
|
{ "noclobber", 'C', OF_ANY },
|
|
{ "noexec", 'n', OF_ANY },
|
|
{ "noglob", 'f', OF_ANY },
|
|
{ "nohup", 0, OF_ANY },
|
|
{ "nolog", 0, OF_ANY }, /* no effect */
|
|
{ "notify", 'b', OF_ANY },
|
|
{ "nounset", 'u', OF_ANY },
|
|
{ "physical", 0, OF_ANY }, /* non-standard */
|
|
{ "posix", 0, OF_ANY }, /* non-standard */
|
|
{ "privileged", 'p', OF_ANY },
|
|
{ "restricted", 'r', OF_CMDLINE },
|
|
{ "sh", 0, OF_ANY }, /* non-standard */
|
|
{ "stdin", 's', OF_CMDLINE }, /* pseudo non-standard */
|
|
{ "trackall", 'h', OF_ANY },
|
|
{ "utf8-hack", 'U', OF_ANY }, /* non-standard */
|
|
{ "verbose", 'v', OF_ANY },
|
|
{ "vi", 0, OF_ANY },
|
|
{ "viraw", 0, OF_ANY }, /* no effect */
|
|
{ "vi-show8", 0, OF_ANY }, /* non-standard */
|
|
{ "vi-tabcomplete", 0, OF_ANY }, /* non-standard */
|
|
{ "vi-esccomplete", 0, OF_ANY }, /* non-standard */
|
|
{ "xtrace", 'x', OF_ANY },
|
|
/* Anonymous flags: used internally by shell only
|
|
* (not visible to user)
|
|
*/
|
|
{ NULL, 0, OF_INTERNAL }, /* FTALKING_I */
|
|
};
|
|
|
|
/*
|
|
* translate -o option into F* constant (also used for test -o option)
|
|
*/
|
|
int
|
|
option(const char *n)
|
|
{
|
|
unsigned i;
|
|
|
|
for (i = 0; i < NELEM(options); i++)
|
|
if (options[i].name && strcmp(options[i].name, n) == 0)
|
|
return i;
|
|
|
|
return -1;
|
|
}
|
|
|
|
struct options_info {
|
|
int opt_width;
|
|
int opts[NELEM(options)];
|
|
};
|
|
|
|
static char *options_fmt_entry(const void *arg, int, char *, int);
|
|
static void printoptions(int);
|
|
|
|
/* format a single select menu item */
|
|
static char *
|
|
options_fmt_entry(const void *arg, int i, char *buf, int buflen)
|
|
{
|
|
const struct options_info *oi = (const struct options_info *)arg;
|
|
|
|
shf_snprintf(buf, buflen, "%-*s %s",
|
|
oi->opt_width, options[oi->opts[i]].name,
|
|
Flag(oi->opts[i]) ? "on" : "off");
|
|
return buf;
|
|
}
|
|
|
|
static void
|
|
printoptions(int verbose)
|
|
{
|
|
unsigned i;
|
|
|
|
if (verbose) {
|
|
struct options_info oi;
|
|
int n, len;
|
|
|
|
/* verbose version */
|
|
shprintf("Current option settings\n");
|
|
|
|
for (i = n = oi.opt_width = 0; i < NELEM(options); i++)
|
|
if (options[i].name) {
|
|
len = strlen(options[i].name);
|
|
oi.opts[n++] = i;
|
|
if (len > oi.opt_width)
|
|
oi.opt_width = len;
|
|
}
|
|
print_columns(shl_stdout, n, options_fmt_entry, &oi,
|
|
oi.opt_width + 5, 1);
|
|
} else {
|
|
/* short version ala ksh93 */
|
|
shprintf("set");
|
|
for (i = 0; i < NELEM(options); i++)
|
|
if (Flag(i) && options[i].name)
|
|
shprintf(" -o %s", options[i].name);
|
|
shprintf(newline);
|
|
}
|
|
}
|
|
|
|
char *
|
|
getoptions(void)
|
|
{
|
|
unsigned i;
|
|
char m[(int) FNFLAGS + 1];
|
|
char *cp = m;
|
|
|
|
for (i = 0; i < NELEM(options); i++)
|
|
if (options[i].c && Flag(i))
|
|
*cp++ = options[i].c;
|
|
*cp = 0;
|
|
return str_save(m, ATEMP);
|
|
}
|
|
|
|
/* change a Flag(*) value; takes care of special actions */
|
|
void
|
|
change_flag(enum sh_flag f,
|
|
int what, /* flag to change */
|
|
int newval) /* what is changing the flag (command line vs set) */
|
|
{
|
|
int oldval;
|
|
|
|
/* limited pdksh compatibility (FSH stays always off) */
|
|
if (f == FSH)
|
|
return;
|
|
|
|
oldval = Flag(f);
|
|
Flag(f) = newval;
|
|
if (f == FMONITOR) {
|
|
if (what != OF_CMDLINE && newval != oldval)
|
|
j_change();
|
|
} else if ((f == FVI || f == FEMACS || f == FGMACS) && newval) {
|
|
Flag(FVI) = Flag(FEMACS) = Flag(FGMACS) = 0;
|
|
Flag(f) = newval;
|
|
} else if (f == FPRIVILEGED && oldval && !newval) {
|
|
/* Turning off -p? */
|
|
#if HAVE_SETRESUGID
|
|
gid_t kshegid = getgid();
|
|
|
|
setresgid(kshegid, kshegid, kshegid);
|
|
#if HAVE_SETGROUPS
|
|
setgroups(1, &kshegid);
|
|
#endif
|
|
setresuid(ksheuid, ksheuid, ksheuid);
|
|
#else
|
|
#error setresid/setresgid required at the moment
|
|
#endif
|
|
} else if (f == FPOSIX && newval) {
|
|
Flag(FBRACEEXPAND) = 0;
|
|
}
|
|
/* Changing interactive flag? */
|
|
if (f == FTALKING) {
|
|
if ((what == OF_CMDLINE || what == OF_SET) && procpid == kshpid)
|
|
Flag(FTALKING_I) = newval;
|
|
}
|
|
}
|
|
|
|
/* parse command line & set command arguments. returns the index of
|
|
* non-option arguments, -1 if there is an error.
|
|
*/
|
|
int
|
|
parse_args(char **argv,
|
|
int what, /* OF_CMDLINE or OF_SET */
|
|
int *setargsp)
|
|
{
|
|
static char cmd_opts[NELEM(options) + 5]; /* o:T:\0 */
|
|
static char set_opts[NELEM(options) + 6]; /* A:o;s\0 */
|
|
char *opts;
|
|
char *array = NULL;
|
|
Getopt go;
|
|
int optc, set, sortargs = 0, arrayset = 0;
|
|
unsigned i;
|
|
|
|
/* First call? Build option strings... */
|
|
if (cmd_opts[0] == '\0') {
|
|
char *p = cmd_opts, *q = set_opts;
|
|
|
|
/* see cmd_opts[] declaration */
|
|
*p++ = 'o';
|
|
*p++ = ':';
|
|
*p++ = 'T';
|
|
*p++ = ':';
|
|
/* see set_opts[] declaration */
|
|
*q++ = 'A';
|
|
*q++ = ':';
|
|
*q++ = 'o';
|
|
*q++ = ';';
|
|
*q++ = 's';
|
|
|
|
for (i = 0; i < NELEM(options); i++) {
|
|
if (options[i].c) {
|
|
if (options[i].flags & OF_CMDLINE)
|
|
*p++ = options[i].c;
|
|
if (options[i].flags & OF_SET)
|
|
*q++ = options[i].c;
|
|
}
|
|
}
|
|
*p = '\0';
|
|
*q = '\0';
|
|
}
|
|
|
|
if (what == OF_CMDLINE) {
|
|
char *p;
|
|
/* Set FLOGIN before parsing options so user can clear
|
|
* flag using +l.
|
|
*/
|
|
Flag(FLOGIN) = (argv[0][0] == '-' ||
|
|
((p = strrchr(argv[0], '/')) && *++p == '-'));
|
|
opts = cmd_opts;
|
|
} else if (what == OF_FIRSTTIME) {
|
|
opts = cmd_opts;
|
|
} else
|
|
opts = set_opts;
|
|
ksh_getopt_reset(&go, GF_ERROR|GF_PLUSOPT);
|
|
while ((optc = ksh_getopt(argv, &go, opts)) != -1) {
|
|
set = (go.info & GI_PLUS) ? 0 : 1;
|
|
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).
|
|
*/
|
|
printoptions(set);
|
|
break;
|
|
}
|
|
i = option(go.optarg);
|
|
if (i != (unsigned)-1 && set == 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 != (unsigned)-1 && (options[i].flags & what))
|
|
change_flag((enum sh_flag) i, what, set);
|
|
else {
|
|
bi_errorf("%s: bad option", go.optarg);
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case 'T':
|
|
#ifndef MKSH_SMALL
|
|
if (what != OF_FIRSTTIME)
|
|
break;
|
|
#ifndef USE_CHVT
|
|
errorf("no TIOCSCTTY ioctl");
|
|
#else
|
|
change_flag(FTALKING, OF_CMDLINE, 1);
|
|
parse_T(go.optarg);
|
|
#endif
|
|
#endif
|
|
break;
|
|
|
|
case '?':
|
|
return -1;
|
|
|
|
default:
|
|
if (what == OF_FIRSTTIME)
|
|
break;
|
|
/* -s: sort positional params (at&t ksh stupidity) */
|
|
if (what == OF_SET && optc == 's') {
|
|
sortargs = 1;
|
|
break;
|
|
}
|
|
for (i = 0; i < NELEM(options); i++)
|
|
if (optc == options[i].c &&
|
|
(what & options[i].flags)) {
|
|
change_flag((enum sh_flag) i, what,
|
|
set);
|
|
break;
|
|
}
|
|
if (i == NELEM(options)) {
|
|
internal_errorf(1, "parse_args: '%c'", optc);
|
|
return -1; /* not reached */
|
|
}
|
|
}
|
|
}
|
|
if (!(go.info & GI_MINUSMINUS) && argv[go.optind] &&
|
|
(argv[go.optind][0] == '-' || argv[go.optind][0] == '+') &&
|
|
argv[go.optind][1] == '\0') {
|
|
/* lone - clears -v and -x flags */
|
|
if (argv[go.optind][0] == '-')
|
|
Flag(FVERBOSE) = Flag(FXTRACE) = 0;
|
|
/* 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 && (!*array || *skip_varname(array, false))) {
|
|
bi_errorf("%s: is not an identifier", array);
|
|
return -1;
|
|
}
|
|
if (sortargs) {
|
|
for (i = go.optind; argv[i]; i++)
|
|
;
|
|
qsort(&argv[go.optind], i - go.optind, sizeof (void *),
|
|
xstrcmp);
|
|
}
|
|
if (arrayset) {
|
|
set_array(array, arrayset, argv + go.optind);
|
|
for (; argv[go.optind]; 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)
|
|
{
|
|
int i, c, rv = 0;
|
|
bool neg = false;
|
|
|
|
do {
|
|
c = *s++;
|
|
} while (ksh_isspace(c));
|
|
if (c == '-') {
|
|
neg = true;
|
|
c = *s++;
|
|
} else if (c == '+')
|
|
c = *s++;
|
|
*ai = i = 0;
|
|
do {
|
|
if (!ksh_isdigit(c))
|
|
goto getn_out;
|
|
i *= 10;
|
|
if (i < *ai)
|
|
/* overflow */
|
|
goto getn_out;
|
|
i += c - '0';
|
|
*ai = i;
|
|
} while ((c = *s++));
|
|
rv = 1;
|
|
|
|
getn_out:
|
|
if (neg)
|
|
*ai = -*ai;
|
|
return (rv);
|
|
}
|
|
|
|
/* getn() that prints error */
|
|
int
|
|
bi_getn(const char *as, int *ai)
|
|
{
|
|
int rv = getn(as, ai);
|
|
|
|
if (!rv)
|
|
bi_errorf("%s: bad number", as);
|
|
return rv;
|
|
}
|
|
|
|
/* -------- 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, int isfile)
|
|
{
|
|
const char *se, *pe;
|
|
|
|
if (s == NULL || p == NULL)
|
|
return 0;
|
|
se = s + strlen(s);
|
|
pe = p + strlen(p);
|
|
/* isfile is false iff no syntax check has been done on
|
|
* the pattern. If check fails, just to a strcmp().
|
|
*/
|
|
if (!isfile && !has_globbing(p, pe)) {
|
|
size_t len = pe - p + 1;
|
|
char tbuf[64];
|
|
char *t = len <= sizeof(tbuf) ? tbuf :
|
|
(char *) alloc(len, ATEMP);
|
|
debunk(t, p, len);
|
|
return !strcmp(t, s);
|
|
}
|
|
return do_gmatch((const unsigned char *) s, (const unsigned char *) se,
|
|
(const unsigned char *) p, (const unsigned char *) pe);
|
|
}
|
|
|
|
/* 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 ?
|
|
*/
|
|
int
|
|
has_globbing(const char *xp, const char *xpe)
|
|
{
|
|
const unsigned char *p = (const unsigned char *) xp;
|
|
const unsigned char *pe = (const unsigned char *) xpe;
|
|
int c;
|
|
int nest = 0, bnest = 0;
|
|
int saw_glob = 0;
|
|
int in_bracket = 0; /* inside [...] */
|
|
|
|
for (; p < pe; p++) {
|
|
if (!ISMAGIC(*p))
|
|
continue;
|
|
if ((c = *++p) == '*' || c == '?')
|
|
saw_glob = 1;
|
|
else if (c == '[') {
|
|
if (!in_bracket) {
|
|
saw_glob = 1;
|
|
in_bracket = 1;
|
|
if (ISMAGIC(p[1]) && p[2] == NOT)
|
|
p += 2;
|
|
if (ISMAGIC(p[1]) && p[2] == ']')
|
|
p += 2;
|
|
}
|
|
/* XXX Do we need to check ranges here? POSIX Q */
|
|
} else if (c == ']') {
|
|
if (in_bracket) {
|
|
if (bnest) /* [a*(b]) */
|
|
return 0;
|
|
in_bracket = 0;
|
|
}
|
|
} else if ((c & 0x80) && strchr("*+?@! ", c & 0x7f)) {
|
|
saw_glob = 1;
|
|
if (in_bracket)
|
|
bnest++;
|
|
else
|
|
nest++;
|
|
} else if (c == '|') {
|
|
if (in_bracket && !bnest) /* *(a[foo|bar]) */
|
|
return 0;
|
|
} else if (c == /*(*/ ')') {
|
|
if (in_bracket) {
|
|
if (!bnest--) /* *(a[b)c] */
|
|
return 0;
|
|
} else if (nest)
|
|
nest--;
|
|
}
|
|
/* else must be a MAGIC-MAGIC, or MAGIC-!, MAGIC--, MAGIC-]
|
|
MAGIC-{, MAGIC-,, MAGIC-} */
|
|
}
|
|
return saw_glob && !in_bracket && !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)
|
|
{
|
|
int sc, pc;
|
|
const unsigned char *prest, *psub, *pnext;
|
|
const unsigned char *srest;
|
|
|
|
if (s == NULL || p == NULL)
|
|
return 0;
|
|
while (p < pe) {
|
|
pc = *p++;
|
|
sc = s < se ? *s : '\0';
|
|
s++;
|
|
if (!ISMAGIC(pc)) {
|
|
if (sc != pc)
|
|
return 0;
|
|
continue;
|
|
}
|
|
switch (*p++) {
|
|
case '[':
|
|
if (sc == 0 || (p = cclass(p, sc)) == NULL)
|
|
return 0;
|
|
break;
|
|
|
|
case '?':
|
|
if (sc == 0)
|
|
return 0;
|
|
break;
|
|
|
|
case '*':
|
|
if (p == pe)
|
|
return 1;
|
|
s--;
|
|
do {
|
|
if (do_gmatch(s, se, p, pe))
|
|
return 1;
|
|
} while (s++ < se);
|
|
return 0;
|
|
|
|
/*
|
|
* [*+?@!](pattern|pattern|..)
|
|
* This is also needed for ${..%..}, etc.
|
|
*/
|
|
case 0x80|'+': /* matches one or more times */
|
|
case 0x80|'*': /* matches zero or more times */
|
|
if (!(prest = pat_scan(p, pe, 0)))
|
|
return 0;
|
|
s--;
|
|
/* take care of zero matches */
|
|
if (p[-1] == (0x80 | '*') &&
|
|
do_gmatch(s, se, prest, pe))
|
|
return 1;
|
|
for (psub = p; ; psub = pnext) {
|
|
pnext = pat_scan(psub, pe, 1);
|
|
for (srest = s; srest <= se; srest++) {
|
|
if (do_gmatch(s, srest, psub, pnext - 2) &&
|
|
(do_gmatch(srest, se, prest, pe) ||
|
|
(s != srest && do_gmatch(srest,
|
|
se, p - 2, pe))))
|
|
return 1;
|
|
}
|
|
if (pnext == prest)
|
|
break;
|
|
}
|
|
return 0;
|
|
|
|
case 0x80|'?': /* matches zero or once */
|
|
case 0x80|'@': /* matches one of the patterns */
|
|
case 0x80|' ': /* simile for @ */
|
|
if (!(prest = pat_scan(p, pe, 0)))
|
|
return 0;
|
|
s--;
|
|
/* Take care of zero matches */
|
|
if (p[-1] == (0x80 | '?') &&
|
|
do_gmatch(s, se, prest, pe))
|
|
return 1;
|
|
for (psub = p; ; psub = pnext) {
|
|
pnext = pat_scan(psub, pe, 1);
|
|
srest = prest == pe ? se : s;
|
|
for (; srest <= se; srest++) {
|
|
if (do_gmatch(s, srest, psub, pnext - 2) &&
|
|
do_gmatch(srest, se, prest, pe))
|
|
return 1;
|
|
}
|
|
if (pnext == prest)
|
|
break;
|
|
}
|
|
return 0;
|
|
|
|
case 0x80|'!': /* matches none of the patterns */
|
|
if (!(prest = pat_scan(p, pe, 0)))
|
|
return 0;
|
|
s--;
|
|
for (srest = s; srest <= se; srest++) {
|
|
int matched = 0;
|
|
|
|
for (psub = p; ; psub = pnext) {
|
|
pnext = pat_scan(psub, pe, 1);
|
|
if (do_gmatch(s, srest, psub,
|
|
pnext - 2)) {
|
|
matched = 1;
|
|
break;
|
|
}
|
|
if (pnext == prest)
|
|
break;
|
|
}
|
|
if (!matched &&
|
|
do_gmatch(srest, se, prest, pe))
|
|
return 1;
|
|
}
|
|
return 0;
|
|
|
|
default:
|
|
if (sc != p[-1])
|
|
return 0;
|
|
break;
|
|
}
|
|
}
|
|
return s == se;
|
|
}
|
|
|
|
static const unsigned char *
|
|
cclass(const unsigned char *p, int sub)
|
|
{
|
|
int c, d, not, found = 0;
|
|
const unsigned char *orig_p = p;
|
|
|
|
if ((not = (ISMAGIC(*p) && *++p == NOT)))
|
|
p++;
|
|
do {
|
|
c = *p++;
|
|
if (ISMAGIC(c)) {
|
|
c = *p++;
|
|
if ((c & 0x80) && !ISMAGIC(c)) {
|
|
c &= 0x7f;/* extended pattern matching: *+?@! */
|
|
/* XXX the ( char isn't handled as part of [] */
|
|
if (c == ' ') /* simile for @: plain (..) */
|
|
c = '(' /*)*/;
|
|
}
|
|
}
|
|
if (c == '\0')
|
|
/* No closing ] - act as if the opening [ was quoted */
|
|
return sub == '[' ? orig_p : NULL;
|
|
if (ISMAGIC(p[0]) && p[1] == '-' &&
|
|
(!ISMAGIC(p[2]) || p[3] != ']')) {
|
|
p += 2; /* MAGIC- */
|
|
d = *p++;
|
|
if (ISMAGIC(d)) {
|
|
d = *p++;
|
|
if ((d & 0x80) && !ISMAGIC(d))
|
|
d &= 0x7f;
|
|
}
|
|
/* POSIX says this is an invalid expression */
|
|
if (c > d)
|
|
return NULL;
|
|
} else
|
|
d = c;
|
|
if (c == sub || (c <= sub && sub <= d))
|
|
found = 1;
|
|
} while (!(ISMAGIC(p[0]) && p[1] == ']'));
|
|
|
|
return (found != not) ? p+2 : NULL;
|
|
}
|
|
|
|
/* Look for next ) or | (if match_sep) in *(foo|bar) pattern */
|
|
const unsigned char *
|
|
pat_scan(const unsigned char *p, const unsigned char *pe, int match_sep)
|
|
{
|
|
int nest = 0;
|
|
|
|
for (; p < pe; p++) {
|
|
if (!ISMAGIC(*p))
|
|
continue;
|
|
if ((*++p == /*(*/ ')' && nest-- == 0) ||
|
|
(*p == '|' && match_sep && nest == 0))
|
|
return ++p;
|
|
if ((*p & 0x80) && strchr("*+?@! ", *p & 0x7f))
|
|
nest++;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int
|
|
xstrcmp(const void *p1, const void *p2)
|
|
{
|
|
return (strcmp(*(char * const *)p1, *(char * const *)p2));
|
|
}
|
|
|
|
/* 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(char **argv, Getopt *go, const char *optionsp)
|
|
{
|
|
char c;
|
|
char *o;
|
|
|
|
if (go->p == 0 || (c = argv[go->optind - 1][go->p]) == '\0') {
|
|
char *arg = argv[go->optind], flag = arg ? *arg : '\0';
|
|
|
|
go->p = 1;
|
|
if (flag == '-' && arg[1] == '-' && arg[2] == '\0') {
|
|
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 = strchr(optionsp, c))) {
|
|
if (optionsp[0] == ':') {
|
|
go->buf[0] = c;
|
|
go->optarg = go->buf;
|
|
} else {
|
|
warningf(true, "%s%s-%c: unknown option",
|
|
(go->flags & GF_NONAME) ? "" : argv[0],
|
|
(go->flags & GF_NONAME) ? "" : ": ", c);
|
|
if (go->flags & GF_ERROR)
|
|
bi_errorf(null);
|
|
}
|
|
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' requires argument",
|
|
(go->flags & GF_NONAME) ? "" : argv[0],
|
|
(go->flags & GF_NONAME) ? "" : ": ", c);
|
|
if (go->flags & GF_ERROR)
|
|
bi_errorf(null);
|
|
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;
|
|
}
|
|
|
|
/* 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(const char *s)
|
|
{
|
|
const char *p;
|
|
int inquote = 0;
|
|
|
|
/* Test if any quotes are needed */
|
|
for (p = s; *p; p++)
|
|
if (ctype(*p, C_QUOTE))
|
|
break;
|
|
if (!*p) {
|
|
shprintf("%s", s);
|
|
return;
|
|
}
|
|
for (p = s; *p; p++) {
|
|
if (*p == '\'') {
|
|
shprintf("'\\'" + 1 - inquote);
|
|
inquote = 0;
|
|
} else {
|
|
if (!inquote) {
|
|
shprintf("'");
|
|
inquote = 1;
|
|
}
|
|
shf_putc(*p, shl_stdout);
|
|
}
|
|
}
|
|
if (inquote)
|
|
shprintf("'");
|
|
}
|
|
|
|
/* Print things in columns and rows - func() is called to format the ith
|
|
* element
|
|
*/
|
|
void
|
|
print_columns(struct shf *shf, int n,
|
|
char *(*func) (const void *, int, char *, int),
|
|
const void *arg, int max_width, int prefcol)
|
|
{
|
|
char *str = (char *) alloc(max_width + 1, ATEMP);
|
|
int i;
|
|
int r, c;
|
|
int rows, cols;
|
|
int nspace;
|
|
|
|
/* max_width + 1 for the space. Note that no space
|
|
* is printed after the last column to avoid problems
|
|
* with terminals that have auto-wrap.
|
|
*/
|
|
cols = x_cols / (max_width + 1);
|
|
if (!cols)
|
|
cols = 1;
|
|
rows = (n + cols - 1) / cols;
|
|
if (prefcol && n && cols > rows) {
|
|
int tmp = rows;
|
|
|
|
rows = cols;
|
|
cols = tmp;
|
|
if (rows > n)
|
|
rows = n;
|
|
}
|
|
|
|
nspace = (x_cols - max_width * cols) / cols;
|
|
if (nspace <= 0)
|
|
nspace = 1;
|
|
for (r = 0; r < rows; r++) {
|
|
for (c = 0; c < cols; c++) {
|
|
i = c * rows + r;
|
|
if (i < n) {
|
|
shf_fprintf(shf, "%-*s",
|
|
max_width,
|
|
(*func)(arg, i, str, max_width + 1));
|
|
if (c + 1 < cols)
|
|
shf_fprintf(shf, "%*s", nspace, null);
|
|
}
|
|
}
|
|
shf_putchar('\n', shf);
|
|
}
|
|
afree(str, ATEMP);
|
|
}
|
|
|
|
/* Strip any nul bytes from buf - returns new length (nbytes - # of nuls) */
|
|
void
|
|
strip_nuls(char *buf, int nbytes)
|
|
{
|
|
char *dst;
|
|
|
|
/* nbytes check because some systems (older FreeBSDs) have a buggy
|
|
* memchr()
|
|
*/
|
|
if (nbytes && (dst = memchr(buf, '\0', nbytes))) {
|
|
char *end = buf + nbytes;
|
|
char *p, *q;
|
|
|
|
for (p = dst; p < end; p = q) {
|
|
/* skip a block of nulls */
|
|
while (++p < end && *p == '\0')
|
|
;
|
|
/* find end of non-null block */
|
|
if (!(q = memchr(p, '\0', end - p)))
|
|
q = end;
|
|
memmove(dst, p, q - p);
|
|
dst += q - p;
|
|
}
|
|
*dst = '\0';
|
|
}
|
|
}
|
|
|
|
/* Like read(2), but if read fails due to non-blocking flag, resets flag
|
|
* and restarts read.
|
|
*/
|
|
int
|
|
blocking_read(int fd, char *buf, int nbytes)
|
|
{
|
|
int ret;
|
|
int tried_reset = 0;
|
|
|
|
while ((ret = read(fd, buf, nbytes)) < 0) {
|
|
if (!tried_reset && errno == EAGAIN) {
|
|
if (reset_nonblock(fd) > 0) {
|
|
tried_reset = 1;
|
|
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;
|
|
}
|
|
|
|
|
|
/* Like getcwd(), except bsize is ignored if buf is 0 (PATH_MAX is used) */
|
|
char *
|
|
ksh_get_wd(size_t *dlen)
|
|
{
|
|
char *ret, *b;
|
|
size_t len = 1;
|
|
|
|
if ((ret = getcwd((b = alloc(PATH_MAX + 1, ATEMP)), PATH_MAX)))
|
|
ret = aresize(b, len = (strlen(b) + 1), ATEMP);
|
|
else
|
|
afree(b, ATEMP);
|
|
|
|
if (dlen)
|
|
*dlen = len;
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
int
|
|
make_path(const char *cwd, const char *file,
|
|
char **cdpathp, /* & of : separated list */
|
|
XString *xsp,
|
|
int *phys_pathp)
|
|
{
|
|
int rval = 0;
|
|
int use_cdpath = 1;
|
|
char *plist;
|
|
int len;
|
|
int plen = 0;
|
|
char *xp = Xstring(*xsp, xp);
|
|
|
|
if (!file)
|
|
file = null;
|
|
|
|
if (file[0] == '/') {
|
|
*phys_pathp = 0;
|
|
use_cdpath = 0;
|
|
} else {
|
|
if (file[0] == '.') {
|
|
char c = file[1];
|
|
|
|
if (c == '.')
|
|
c = file[2];
|
|
if (c == '/' || c == '\0')
|
|
use_cdpath = 0;
|
|
}
|
|
|
|
plist = *cdpathp;
|
|
if (!plist)
|
|
use_cdpath = 0;
|
|
else if (use_cdpath) {
|
|
char *pend;
|
|
|
|
for (pend = plist; *pend && *pend != ':'; pend++)
|
|
;
|
|
plen = pend - plist;
|
|
*cdpathp = *pend ? ++pend : NULL;
|
|
}
|
|
|
|
if ((use_cdpath == 0 || !plen || plist[0] != '/') &&
|
|
(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;
|
|
}
|
|
|
|
/*
|
|
* Simplify pathnames containing "." and ".." entries.
|
|
* ie, simplify_path("/a/b/c/./../d/..") returns "/a/b"
|
|
*/
|
|
void
|
|
simplify_path(char *pathl)
|
|
{
|
|
char *cur;
|
|
char *t;
|
|
int isrooted;
|
|
char *very_start = pathl;
|
|
char *start;
|
|
|
|
if (!*pathl)
|
|
return;
|
|
|
|
if ((isrooted = pathl[0] == '/'))
|
|
very_start++;
|
|
|
|
/* Before After
|
|
* /foo/ /foo
|
|
* /foo/../../bar /bar
|
|
* /foo/./blah/.. /foo
|
|
* . .
|
|
* .. ..
|
|
* ./foo foo
|
|
* foo/../../../bar ../../bar
|
|
*/
|
|
|
|
for (cur = t = start = very_start; ; ) {
|
|
/* treat multiple '/'s as one '/' */
|
|
while (*t == '/')
|
|
t++;
|
|
|
|
if (*t == '\0') {
|
|
if (cur == pathl)
|
|
/* convert empty path to dot */
|
|
*cur++ = '.';
|
|
*cur = '\0';
|
|
break;
|
|
}
|
|
|
|
if (t[0] == '.') {
|
|
if (!t[1] || t[1] == '/') {
|
|
t += 1;
|
|
continue;
|
|
} else if (t[1] == '.' && (!t[2] || t[2] == '/')) {
|
|
if (!isrooted && cur == start) {
|
|
if (cur != very_start)
|
|
*cur++ = '/';
|
|
*cur++ = '.';
|
|
*cur++ = '.';
|
|
start = cur;
|
|
} else if (cur != start)
|
|
while (--cur > start && *cur != '/')
|
|
;
|
|
t += 2;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (cur != very_start)
|
|
*cur++ = '/';
|
|
|
|
/* find/copy next component of pathname */
|
|
while (*t && *t != '/')
|
|
*cur++ = *t++;
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
set_current_wd(char *pathl)
|
|
{
|
|
size_t len = 1;
|
|
char *p = pathl;
|
|
|
|
if (p == NULL) {
|
|
if ((p = ksh_get_wd(&len)) == NULL)
|
|
p = null;
|
|
} else
|
|
len = strlen(p) + 1;
|
|
|
|
if (len > current_wd_size)
|
|
current_wd = aresize(current_wd, current_wd_size = len, APERM);
|
|
memcpy(current_wd, p, len);
|
|
if (p != pathl && p != null)
|
|
afree(p, ATEMP);
|
|
}
|
|
|
|
char *
|
|
get_phys_path(const char *pathl)
|
|
{
|
|
XString xs;
|
|
char *xp;
|
|
|
|
Xinit(xs, xp, strlen(pathl) + 1, ATEMP);
|
|
|
|
xp = do_phys_path(&xs, xp, pathl);
|
|
|
|
if (!xp)
|
|
return NULL;
|
|
|
|
if (Xlength(xs, xp) == 0)
|
|
Xput(xs, xp, '/');
|
|
Xput(xs, xp, '\0');
|
|
|
|
return Xclose(xs, xp);
|
|
}
|
|
|
|
static char *
|
|
do_phys_path(XString *xsp, char *xp, const char *pathl)
|
|
{
|
|
const char *p, *q;
|
|
int len, llen;
|
|
int savepos;
|
|
char lbuf[PATH_MAX];
|
|
|
|
Xcheck(*xsp, xp);
|
|
for (p = pathl; p; p = q) {
|
|
while (*p == '/')
|
|
p++;
|
|
if (!*p)
|
|
break;
|
|
len = (q = strchr(p, '/')) ? q - p : (int)strlen(p);
|
|
if (len == 1 && p[0] == '.')
|
|
continue;
|
|
if (len == 2 && p[0] == '.' && p[1] == '.') {
|
|
while (xp > Xstring(*xsp, xp)) {
|
|
xp--;
|
|
if (*xp == '/')
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
savepos = Xsavepos(*xsp, xp);
|
|
Xput(*xsp, xp, '/');
|
|
XcheckN(*xsp, xp, len + 1);
|
|
memcpy(xp, p, len);
|
|
xp += len;
|
|
*xp = '\0';
|
|
|
|
llen = readlink(Xstring(*xsp, xp), lbuf, sizeof(lbuf) - 1);
|
|
if (llen < 0) {
|
|
/* EINVAL means it wasn't a symlink... */
|
|
if (errno != EINVAL)
|
|
return NULL;
|
|
continue;
|
|
}
|
|
lbuf[llen] = '\0';
|
|
|
|
/* If absolute path, start from scratch.. */
|
|
xp = lbuf[0] == '/' ? Xstring(*xsp, xp) :
|
|
Xrestpos(*xsp, xp, savepos);
|
|
if (!(xp = do_phys_path(xsp, xp, lbuf)))
|
|
return NULL;
|
|
}
|
|
return xp;
|
|
}
|
|
|
|
#ifdef USE_CHVT
|
|
static void
|
|
parse_T(char *fn)
|
|
{
|
|
char dv[20];
|
|
struct stat sb;
|
|
int fd;
|
|
|
|
if (stat(fn, &sb)) {
|
|
memcpy(dv, "/dev/ttyC", 9);
|
|
strlcpy(dv + 9, fn, 20 - 9);
|
|
if (stat(dv, &sb)) {
|
|
memmove(dv + 8, dv + 9, 20 - 9);
|
|
if (stat(dv, &sb))
|
|
errorf("chvt: can't find tty %s", fn);
|
|
}
|
|
fn = dv;
|
|
}
|
|
if (!(sb.st_mode & S_IFCHR))
|
|
errorf("chvt: not a char device: %s", fn);
|
|
if ((sb.st_uid != 0) && chown(fn, 0, 0))
|
|
warningf(false, "chvt: cannot chown root %s", fn);
|
|
if (((sb.st_mode & 07777) != 0600) && chmod(fn, 0600))
|
|
warningf(false, "chvt: cannot chmod 0600 %s", fn);
|
|
#if !defined(__sun__) && !defined(__gnu_linux__) && !defined(__INTERIX)
|
|
if (revoke(fn))
|
|
warningf(false, "chvt: cannot revoke %s", fn);
|
|
#endif
|
|
|
|
if ((fd = open(fn, O_RDWR)) == -1) {
|
|
sleep(1);
|
|
if ((fd = open(fn, O_RDWR)) == -1)
|
|
errorf("chvt: cannot open %s", fn);
|
|
}
|
|
switch (fork()) {
|
|
case -1:
|
|
errorf("fork failed");
|
|
case 0:
|
|
break;
|
|
default:
|
|
exit(0);
|
|
}
|
|
if (setsid() == -1)
|
|
errorf("chvt: setsid failed");
|
|
if (ioctl(fd, TIOCSCTTY, NULL) == -1)
|
|
errorf("chvt: TIOCSCTTY failed");
|
|
dup2(fd, 0);
|
|
dup2(fd, 1);
|
|
dup2(fd, 2);
|
|
if (fd > 2)
|
|
close(fd);
|
|
}
|
|
#endif
|