summary refs log tree commit diff
path: root/src/var.c
blob: cc6f7f2a910a9959aafb24ace6c651beb941fedf (plain) (blame)
1
2009-12-12
|\|
| * CGIT 0.8.3.1Lars Hjemli2009-12-12
| * Fix segfault on ppc when browsing treeMartins Polakovs2009-12-12
* | Add .tar.xz-snapshot supportAndreas Wiese2009-12-08
* | Merge branch 'lh/remote-branches'Lars Hjemli2009-12-08
|\ \
| * | Add support for remote branchesLars Hjemli2009-11-07
* | | Merge branch 'ro/ssdiff'Lars Hjemli2009-12-08
|\ \ \
| * | | In side-by-side diff, add support for marking individual characters.Ragnar Ouchterlony2009-11-07
| * | | Fixed side-by-side diff bugs related to binary diff and more.Ragnar Ouchterlony2009-09-16
| * | | Polishing of how the side-by-side diff looks.Ragnar Ouchterlony2009-09-16
| * | | Add possibility to switch between unidiff and side-by-side-diff.Ragnar Ouchterlony2009-09-16
| * | | First version of side-by-side diff.Ragnar Ouchterlony2009-09-16
* | | | Merge branch 'master' of http://op-co.de/cgitLars Hjemli2009-12-08
|\ \ \ \
| * | | | "max-blob-size" config var to limit generated HTML sizeGeorg Lukas2009-11-28
| * | | | cgit.css: highlight directories in treeGeorg Lukas2009-11-28
| * | | | syntax highlighting for all formats supported by "highlight"Georg Lukas2009-11-19
| | |/ / | |/| |
* | | | Merge branch 'stable'Lars Hjemli2009-12-08
|\ \ \ \ | |/ / / |/| | / | | |/ | |/|
| * | Don't crash when a repo-specific readme file is usedSami Kyöstilä2009-12-08
* | | Merge branch 'stable'Lars Hjemli2009-11-07
|\| |
| * | shared.c: return original errnoLars Hjemli2009-11-07
* | | Add NO_OPENSSL optionMikhail Gusarov2009-11-07
* | | Merge branch 'stable'Lars Hjemli2009-11-07
|\| |
| * | Close fd on error in readfile()Rys Sommefeldt2009-11-07
| * | Nov is the correct abbreviationDanijel Tašov2009-11-07
* | | ui-shared.c: prettify download links when generated from tag pageLars Hjemli2009-10-16
* | | ui-tag: make output more similar to commit viewLars Hjemli2009-10-06
* | | ui-tag: add snapshot linksAlexey Nezhdanov2009-10-06
* | | Skip leading "/" in url querystring valueStefan Bühler2009-10-06
|/ /
* | Fix repolist search links with virtual rootGeoff Johnstone2009-09-20
* | cgitrc.5.txt: Change repo.group to section in example config.Loui Chang2009-09-14
* | cgitrc.5.txt: Add mansource and manmanual.Loui Chang2009-09-14
|/
* CGIT 0.8.3Lars Hjemli2009-09-13
* Merge branch 'stable'Lars Hjemli2009-09-13
|\
| * CGIT 0.8.2.2Lars Hjemli2009-09-13
* | Merge branch 'lh/repo-scan'Lars Hjemli2009-09-13
|\ \
| * | cgit.c: respect repo-local 'snapshots' option for --scan-pathLars Hjemli2009-08-24
| * | cgit.c: only print first line of repo.desc in print_repo()Lars Hjemli2009-08-24
| * | Add and use cgit_find_stats_periodname() in print_repo()Lars Hjemli2009-08-24
| * | cgit.c: generate repo.snapshots in print_repo()Lars Hjemli2009-08-24
| * | cgit.c: add missing options to print_repo()Lars Hjemli2009-08-24
| * | shared.c: initialize cgit_repo structs properlyLars Hjemli2009-08-24
| * | Add config option 'enable-filter-overrides'Lars Hjemli2009-08-24
| * | cgitrc.5.txt: fix markup errorsLars Hjemli2009-08-24
| * | ui-repolist: handle empty sections similar to NULL sectionsLars Hjemli2009-08-24
| * | Add support for repo-local cgitrc fileLars Hjemli2009-08-24
| * | cgit.c: refactor repo_config() from config_cb()Lars Hjemli2009-08-24
| * | ui-repolist.c: sort by section name, repo name as defaultLars Hjemli2009-08-24
| * | Add config option 'repo.section'Lars Hjemli2009-08-24
| * | Introduce 'section' as canonical spelling for 'repo.group'Lars Hjemli2009-08-24
| * | Add support for --scan-path command line optionLars Hjemli2009-08-24
d='n577' href='#n577'>577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713
/*-
 * Copyright (c) 1991, 1993
 *	The Regents of the University of California.  All rights reserved.
 * Copyright (c) 1997-2005
 *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Kenneth Almquist.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_PATHS_H
#include <paths.h>
#endif

/*
 * Shell variables.
 */

#include "shell.h"
#include "output.h"
#include "expand.h"
#include "nodes.h"	/* for other headers */
#include "exec.h"
#include "syntax.h"
#include "options.h"
#include "mail.h"
#include "var.h"
#include "memalloc.h"
#include "error.h"
#include "mystring.h"
#include "parser.h"
#include "show.h"
#ifndef SMALL
#include "myhistedit.h"
#endif
#include "system.h"


#define VTABSIZE 39


struct localvar_list {
	struct localvar_list *next;
	struct localvar *lv;
};

MKINIT struct localvar_list *localvar_stack;

const char defpathvar[] =
	"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
#ifdef IFS_BROKEN
const char defifsvar[] = "IFS= \t\n";
#else
const char defifs[] = " \t\n";
#endif
MKINIT char defoptindvar[] = "OPTIND=1";

int lineno;
char linenovar[sizeof("LINENO=")+sizeof(int)*CHAR_BIT/3+1] = "LINENO=";

/* Some macros in var.h depend on the order, add new variables to the end. */
struct var varinit[] = {
#if ATTY
	{ 0,	VSTRFIXED|VTEXTFIXED|VUNSET,	"ATTY\0",	0 },
#endif
#ifdef IFS_BROKEN
	{ 0,	VSTRFIXED|VTEXTFIXED,		defifsvar,	0 },
#else
	{ 0,	VSTRFIXED|VTEXTFIXED|VUNSET,	"IFS\0",	0 },
#endif
	{ 0,	VSTRFIXED|VTEXTFIXED|VUNSET,	"MAIL\0",	changemail },
	{ 0,	VSTRFIXED|VTEXTFIXED|VUNSET,	"MAILPATH\0",	changemail },
	{ 0,	VSTRFIXED|VTEXTFIXED,		defpathvar,	changepath },
	{ 0,	VSTRFIXED|VTEXTFIXED,		"PS1=$ ",	0 },
	{ 0,	VSTRFIXED|VTEXTFIXED,		"PS2=> ",	0 },
	{ 0,	VSTRFIXED|VTEXTFIXED,		"PS4=+ ",	0 },
	{ 0,	VSTRFIXED|VTEXTFIXED,		defoptindvar,	getoptsreset },
#ifdef WITH_LINENO
	{ 0,	VSTRFIXED|VTEXTFIXED,		linenovar,	0 },
#endif
#ifndef SMALL
	{ 0,	VSTRFIXED|VTEXTFIXED|VUNSET,	"TERM\0",	0 },
	{ 0,	VSTRFIXED|VTEXTFIXED|VUNSET,	"HISTSIZE\0",	sethistsize },
#endif
};

STATIC struct var *vartab[VTABSIZE];

STATIC struct var **hashvar(const char *);
STATIC int vpcmp(const void *, const void *);
STATIC struct var **findvar(struct var **, const char *);

/*
 * Initialize the varable symbol tables and import the environment
 */

#ifdef mkinit
INCLUDE <unistd.h>
INCLUDE <sys/types.h>
INCLUDE <sys/stat.h>
INCLUDE "cd.h"
INCLUDE "output.h"
INCLUDE "var.h"
MKINIT char **environ;
INIT {
	char **envp;
	static char ppid[32] = "PPID=";
	const char *p;
	struct stat st1, st2;

	initvar();
	for (envp = environ ; *envp ; envp++) {
		p = endofname(*envp);
		if (p != *envp && *p == '=') {
			setvareq(*envp, VEXPORT|VTEXTFIXED);
		}
	}

	setvareq(defoptindvar, VTEXTFIXED);

	fmtstr(ppid + 5, sizeof(ppid) - 5, "%ld", (long) getppid());
	setvareq(ppid, VTEXTFIXED);

	p = lookupvar("PWD");
	if (p)
		if (*p != '/' || stat(p, &st1) || stat(".", &st2) ||
		    st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino)
			p = 0;
	setpwd(p, 0);
}

RESET {
	unwindlocalvars(0);
}
#endif


/*
 * This routine initializes the builtin variables.  It is called when the
 * shell is initialized.
 */

void
initvar(void)
{
	struct var *vp;
	struct var *end;
	struct var **vpp;

	vp = varinit;
	end = vp + sizeof(varinit) / sizeof(varinit[0]);
	do {
		vpp = hashvar(vp->text);
		vp->next = *vpp;
		*vpp = vp;
	} while (++vp < end);
	/*
	 * PS1 depends on uid
	 */
	if (!geteuid())
		vps1.text = "PS1=# ";
}

/*
 * Set the value of a variable.  The flags argument is ored with the
 * flags of the variable.  If val is NULL, the variable is unset.
 */

struct var *setvar(const char *name, const char *val, int flags)
{
	char *p, *q;
	size_t namelen;
	char *nameeq;
	size_t vallen;
	struct var *vp;

	q = endofname(name);
	p = strchrnul(q, '=');
	namelen = p - name;
	if (!namelen || p != q)
		sh_error("%.*s: bad variable name", namelen, name);
	vallen = 0;
	if (val == NULL) {
		flags |= VUNSET;
	} else {
		vallen = strlen(val);
	}
	INTOFF;
	p = mempcpy(nameeq = ckmalloc(namelen + vallen + 2), name, namelen);
	if (val) {
		*p++ = '=';
		p = mempcpy(p, val, vallen);
	}
	*p = '\0';
	vp = setvareq(nameeq, flags | VNOSAVE);
	INTON;

	return vp;
}

/*
 * Set the given integer as the value of a variable.  The flags argument is
 * ored with the flags of the variable.
 */

intmax_t setvarint(const char *name, intmax_t val, int flags)
{
	int len = max_int_length(sizeof(val));
	char buf[len];

	fmtstr(buf, len, "%" PRIdMAX, val);
	setvar(name, buf, flags);
	return val;
}



/*
 * Same as setvar except that the variable and value are passed in
 * the first argument as name=value.  Since the first argument will
 * be actually stored in the table, it should not be a string that
 * will go away.
 * Called with interrupts off.
 */

struct var *setvareq(char *s, int flags)
{
	struct var *vp, **vpp;

	vpp = hashvar(s);
	flags |= (VEXPORT & (((unsigned) (1 - aflag)) - 1));
	vpp = findvar(vpp, s);
	vp = *vpp;
	if (vp) {
		if (vp->flags & VREADONLY) {
			const char *n;

			if (flags & VNOSAVE)
				free(s);
			n = vp->text;
			sh_error("%.*s: is read only", strchrnul(n, '=') - n,
				 n);
		}

		if (flags & VNOSET)
			goto out;

		if (vp->func && (flags & VNOFUNC) == 0)
			(*vp->func)(strchrnul(s, '=') + 1);

		if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
			ckfree(vp->text);

		if (((flags & (VEXPORT|VREADONLY|VSTRFIXED|VUNSET)) |
		     (vp->flags & VSTRFIXED)) == VUNSET) {
			*vpp = vp->next;
			ckfree(vp);
out_free:
			if ((flags & (VTEXTFIXED|VSTACK|VNOSAVE)) == VNOSAVE)
				ckfree(s);
			goto out;
		}

		flags |= vp->flags & ~(VTEXTFIXED|VSTACK|VNOSAVE|VUNSET);
	} else {
		if (flags & VNOSET)
			goto out;
		if ((flags & (VEXPORT|VREADONLY|VSTRFIXED|VUNSET)) == VUNSET)
			goto out_free;
		/* not found */
		vp = ckmalloc(sizeof (*vp));
		vp->next = *vpp;
		vp->func = NULL;
		*vpp = vp;
	}
	if (!(flags & (VTEXTFIXED|VSTACK|VNOSAVE)))
		s = savestr(s);
	vp->text = s;
	vp->flags = flags;

out:
	return vp;
}



/*
 * Process a linked list of variable assignments.
 */

void
listsetvar(struct strlist *list, int flags)
{
	struct strlist *lp;

	lp = list;
	if (!lp)
		return;
	INTOFF;
	do {
		setvareq(lp->text, flags);
	} while ((lp = lp->next));
	INTON;
}


/*
 * Find the value of a variable.  Returns NULL if not set.
 */

char *
lookupvar(const char *name)
{
	struct var *v;

	if ((v = *findvar(hashvar(name), name)) && !(v->flags & VUNSET)) {
#ifdef WITH_LINENO
		if (v == &vlineno && v->text == linenovar) {
			fmtstr(linenovar+7, sizeof(linenovar)-7, "%d", lineno);
		}
#endif
		return strchrnul(v->text, '=') + 1;
	}
	return NULL;
}

intmax_t lookupvarint(const char *name)
{
	return atomax(lookupvar(name) ?: nullstr, 0);
}



/*
 * Generate a list of variables satisfying the given conditions.
 */

char **
listvars(int on, int off, char ***end)
{
	struct var **vpp;
	struct var *vp;
	char **ep;
	int mask;

	STARTSTACKSTR(ep);
	vpp = vartab;
	mask = on | off;
	do {
		for (vp = *vpp ; vp ; vp = vp->next)
			if ((vp->flags & mask) == on) {
				if (ep == stackstrend())
					ep = growstackstr();
				*ep++ = (char *) vp->text;
			}
	} while (++vpp < vartab + VTABSIZE);
	if (ep == stackstrend())
		ep = growstackstr();
	if (end)
		*end = ep;
	*ep++ = NULL;
	return grabstackstr(ep);
}



/*
 * POSIX requires that 'set' (but not export or readonly) output the
 * variables in lexicographic order - by the locale's collating order (sigh).
 * Maybe we could keep them in an ordered balanced binary tree
 * instead of hashed lists.
 * For now just roll 'em through qsort for printing...
 */

int
showvars(const char *prefix, int on, int off)
{
	const char *sep;
	char **ep, **epend;

	ep = listvars(on, off, &epend);
	qsort(ep, epend - ep, sizeof(char *), vpcmp);

	sep = *prefix ? spcstr : prefix;

	for (; ep < epend; ep++) {
		const char *p;
		const char *q;

		p = strchrnul(*ep, '=');
		q = nullstr;
		if (*p)
			q = single_quote(++p);

		out1fmt("%s%s%.*s%s\n", prefix, sep, (int)(p - *ep), *ep, q);
	}

	return 0;
}



/*
 * The export and readonly commands.
 */

int
exportcmd(int argc, char **argv)
{
	struct var *vp;
	char *name;
	const char *p;
	char **aptr;
	int flag = argv[0][0] == 'r'? VREADONLY : VEXPORT;
	int notp;

	notp = nextopt("p") - 'p';
	if (notp && ((name = *(aptr = argptr)))) {
		do {
			if ((p = strchr(name, '=')) != NULL) {
				p++;
			} else {
				if ((vp = *findvar(hashvar(name), name))) {
					vp->flags |= flag;
					continue;
				}
			}
			setvar(name, p, flag);
		} while ((name = *++aptr) != NULL);
	} else {
		showvars(argv[0], flag, 0);
	}
	return 0;
}


/*
 * The "local" command.
 */

int
localcmd(int argc, char **argv)
{
	char *name;

	if (!localvar_stack)
		sh_error("not in a function");

	argv = argptr;
	while ((name = *argv++) != NULL) {
		mklocal(name);
	}
	return 0;
}


/*
 * Make a variable a local variable.  When a variable is made local, it's
 * value and flags are saved in a localvar structure.  The saved values
 * will be restored when the shell function returns.  We handle the name
 * "-" as a special case.
 */

void mklocal(char *name)
{
	struct localvar *lvp;
	struct var **vpp;
	struct var *vp;

	INTOFF;
	lvp = ckmalloc(sizeof (struct localvar));
	if (name[0] == '-' && name[1] == '\0') {
		char *p;
		p = ckmalloc(sizeof(optlist));
		lvp->text = memcpy(p, optlist, sizeof(optlist));
		vp = NULL;
	} else {
		char *eq;

		vpp = hashvar(name);
		vp = *findvar(vpp, name);
		eq = strchr(name, '=');
		if (vp == NULL) {
			if (eq)
				vp = setvareq(name, VSTRFIXED);
			else
				vp = setvar(name, NULL, VSTRFIXED);
			lvp->flags = VUNSET;
		} else {
			lvp->text = vp->text;
			lvp->flags = vp->flags;
			vp->flags |= VSTRFIXED|VTEXTFIXED;
			if (eq)
				setvareq(name, 0);
		}
	}
	lvp->vp = vp;
	lvp->next = localvar_stack->lv;
	localvar_stack->lv = lvp;
	INTON;
}


/*
 * Called after a function returns.
 * Interrupts must be off.
 */

void
poplocalvars(int keep)
{
	struct localvar_list *ll;
	struct localvar *lvp, *next;
	struct var *vp;

	INTOFF;
	ll = localvar_stack;
	localvar_stack = ll->next;

	next = ll->lv;
	ckfree(ll);

	while ((lvp = next) != NULL) {
		next = lvp->next;
		vp = lvp->vp;
		TRACE(("poplocalvar %s\n", vp ? vp->text : "-"));
		if (keep) {
			int bits = VSTRFIXED;

			if (lvp->flags != VUNSET) {
				if (vp->text == lvp->text)
					bits |= VTEXTFIXED;
				else if (!(lvp->flags & (VTEXTFIXED|VSTACK)))
					ckfree(lvp->text);
			}

			vp->flags &= ~bits;
			vp->flags |= (lvp->flags & bits);

			if ((vp->flags &
			     (VEXPORT|VREADONLY|VSTRFIXED|VUNSET)) == VUNSET)
				unsetvar(vp->text);
		} else if (vp == NULL) {	/* $- saved */
			memcpy(optlist, lvp->text, sizeof(optlist));
			ckfree(lvp->text);
			optschanged();
		} else if (lvp->flags == VUNSET) {
			vp->flags &= ~(VSTRFIXED|VREADONLY);
			unsetvar(vp->text);
		} else {
			if (vp->func)
				(*vp->func)(strchrnul(lvp->text, '=') + 1);
			if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
				ckfree(vp->text);
			vp->flags = lvp->flags;
			vp->text = lvp->text;
		}
		ckfree(lvp);
	}
	INTON;
}


/*
 * Create a new localvar environment.
 */
struct localvar_list *pushlocalvars(void)
{
	struct localvar_list *ll;

	INTOFF;
	ll = ckmalloc(sizeof(*ll));
	ll->lv = NULL;
	ll->next = localvar_stack;
	localvar_stack = ll;
	INTON;

	return ll->next;
}


void unwindlocalvars(struct localvar_list *stop)
{
	while (localvar_stack != stop)
		poplocalvars(0);
}


/*
 * The unset builtin command.  We unset the function before we unset the
 * variable to allow a function to be unset when there is a readonly variable
 * with the same name.
 */

int
unsetcmd(int argc, char **argv)
{
	char **ap;
	int i;
	int flag = 0;

	while ((i = nextopt("vf")) != '\0') {
		flag = i;
	}

	for (ap = argptr; *ap ; ap++) {
		if (flag != 'f') {
			unsetvar(*ap);
			continue;
		}
		if (flag != 'v')
			unsetfunc(*ap);
	}
	return 0;
}


/*
 * Unset the specified variable.
 */

void unsetvar(const char *s)
{
	setvar(s, 0, 0);
}



/*
 * Find the appropriate entry in the hash table from the name.
 */

STATIC struct var **
hashvar(const char *p)
{
	unsigned int hashval;

	hashval = ((unsigned char) *p) << 4;
	while (*p && *p != '=')
		hashval += (unsigned char) *p++;
	return &vartab[hashval % VTABSIZE];
}



/*
 * Compares two strings up to the first = or '\0'.  The first
 * string must be terminated by '='; the second may be terminated by
 * either '=' or '\0'.
 */

int
varcmp(const char *p, const char *q)
{
	int c, d;

	while ((c = *p) == (d = *q)) {
		if (!c || c == '=')
			goto out;
		p++;
		q++;
	}
	if (c == '=')
		c = 0;
	if (d == '=')
		d = 0;
out:
	return c - d;
}

STATIC int
vpcmp(const void *a, const void *b)
{
	return varcmp(*(const char **)a, *(const char **)b);
}

STATIC struct var **
findvar(struct var **vpp, const char *name)
{
	for (; *vpp; vpp = &(*vpp)->next) {
		if (varequal((*vpp)->text, name)) {
			break;
		}
	}
	return vpp;
}