mirror of
https://github.com/zsh-users/zsh
synced 2024-11-02 09:47:59 +00:00
1617 lines
39 KiB
C
1617 lines
39 KiB
C
/*
|
|
* hashtable.c - hash tables
|
|
*
|
|
* This file is part of zsh, the Z shell.
|
|
*
|
|
* Copyright (c) 1992-1997 Paul Falstad
|
|
* All rights reserved.
|
|
*
|
|
* Permission is hereby granted, without written agreement and without
|
|
* license or royalty fees, to use, copy, modify, and distribute this
|
|
* software and to distribute modified versions of this software for any
|
|
* purpose, provided that the above copyright notice and the following
|
|
* two paragraphs appear in all copies of this software.
|
|
*
|
|
* In no event shall Paul Falstad or the Zsh Development Group be liable
|
|
* to any party for direct, indirect, special, incidental, or consequential
|
|
* damages arising out of the use of this software and its documentation,
|
|
* even if Paul Falstad and the Zsh Development Group have been advised of
|
|
* the possibility of such damage.
|
|
*
|
|
* Paul Falstad and the Zsh Development Group specifically disclaim any
|
|
* warranties, including, but not limited to, the implied warranties of
|
|
* merchantability and fitness for a particular purpose. The software
|
|
* provided hereunder is on an "as is" basis, and Paul Falstad and the
|
|
* Zsh Development Group have no obligation to provide maintenance,
|
|
* support, updates, enhancements, or modifications.
|
|
*
|
|
*/
|
|
|
|
#include "../config.h"
|
|
|
|
#ifdef ZSH_HASH_DEBUG
|
|
# define HASHTABLE_DEBUG_MEMBERS \
|
|
/* Members of struct hashtable used for debugging hash tables */ \
|
|
HashTable next, last; /* linked list of all hash tables */ \
|
|
char *tablename; /* string containing name of the hash table */ \
|
|
PrintTableStats printinfo; /* pointer to function to print table stats */
|
|
#else /* !ZSH_HASH_DEBUG */
|
|
# define HASHTABLE_DEBUG_MEMBERS
|
|
#endif /* !ZSH_HASH_DEBUG */
|
|
|
|
#define HASHTABLE_INTERNAL_MEMBERS \
|
|
ScanStatus scan; /* status of a scan over this hashtable */ \
|
|
HASHTABLE_DEBUG_MEMBERS
|
|
|
|
typedef struct scanstatus *ScanStatus;
|
|
|
|
#include "zsh.mdh"
|
|
#include "hashtable.pro"
|
|
|
|
/* Structure for recording status of a hashtable scan in progress. When a *
|
|
* scan starts, the .scan member of the hashtable structure points to one *
|
|
* of these. That member being non-NULL disables resizing of the *
|
|
* hashtable (when adding elements). When elements are deleted, the *
|
|
* contents of this structure is used to make sure the scan won't stumble *
|
|
* into the deleted element. */
|
|
|
|
struct scanstatus {
|
|
int sorted;
|
|
union {
|
|
struct {
|
|
HashNode *hashtab;
|
|
int ct;
|
|
} s;
|
|
HashNode u;
|
|
} u;
|
|
};
|
|
|
|
/********************************/
|
|
/* Generic Hash Table functions */
|
|
/********************************/
|
|
|
|
#ifdef ZSH_HASH_DEBUG
|
|
static HashTable firstht, lastht;
|
|
#endif /* ZSH_HASH_DEBUG */
|
|
|
|
/* Generic hash function */
|
|
|
|
/**/
|
|
mod_export unsigned
|
|
hasher(const char *str)
|
|
{
|
|
unsigned hashval = 0, c;
|
|
|
|
while ((c = *((unsigned char *) str++)))
|
|
hashval += (hashval << 5) + c;
|
|
|
|
return hashval;
|
|
}
|
|
|
|
/* Get a new hash table */
|
|
|
|
/**/
|
|
mod_export HashTable
|
|
newhashtable(int size, UNUSED(char const *name), UNUSED(PrintTableStats printinfo))
|
|
{
|
|
HashTable ht;
|
|
|
|
ht = (HashTable) zshcalloc(sizeof *ht);
|
|
#ifdef ZSH_HASH_DEBUG
|
|
ht->next = NULL;
|
|
if(!firstht)
|
|
firstht = ht;
|
|
ht->last = lastht;
|
|
if(lastht)
|
|
lastht->next = ht;
|
|
lastht = ht;
|
|
ht->printinfo = printinfo ? printinfo : printhashtabinfo;
|
|
ht->tablename = ztrdup(name);
|
|
#endif /* ZSH_HASH_DEBUG */
|
|
ht->nodes = (HashNode *) zshcalloc(size * sizeof(HashNode));
|
|
ht->hsize = size;
|
|
ht->ct = 0;
|
|
ht->scan = NULL;
|
|
ht->scantab = NULL;
|
|
return ht;
|
|
}
|
|
|
|
/* Delete a hash table. After this function has been used, any *
|
|
* existing pointers to the hash table are invalid. */
|
|
|
|
/**/
|
|
mod_export void
|
|
deletehashtable(HashTable ht)
|
|
{
|
|
ht->emptytable(ht);
|
|
#ifdef ZSH_HASH_DEBUG
|
|
if(ht->next)
|
|
ht->next->last = ht->last;
|
|
else
|
|
lastht = ht->last;
|
|
if(ht->last)
|
|
ht->last->next = ht->next;
|
|
else
|
|
firstht = ht->next;
|
|
zsfree(ht->tablename);
|
|
#endif /* ZSH_HASH_DEBUG */
|
|
zfree(ht->nodes, ht->hsize * sizeof(HashNode));
|
|
zfree(ht, sizeof(*ht));
|
|
}
|
|
|
|
/* Add a node to a hash table. *
|
|
* nam is the key to use in hashing. nodeptr points *
|
|
* to the node to add. If there is already a node in *
|
|
* the table with the same key, it is first freed, and *
|
|
* then the new node is added. If the number of nodes *
|
|
* is now greater than twice the number of hash values, *
|
|
* the table is then expanded. */
|
|
|
|
/**/
|
|
mod_export void
|
|
addhashnode(HashTable ht, char *nam, void *nodeptr)
|
|
{
|
|
HashNode oldnode = addhashnode2(ht, nam, nodeptr);
|
|
if (oldnode)
|
|
ht->freenode(oldnode);
|
|
}
|
|
|
|
/* Add a node to a hash table, returning the old node on replacement. */
|
|
|
|
/**/
|
|
HashNode
|
|
addhashnode2(HashTable ht, char *nam, void *nodeptr)
|
|
{
|
|
unsigned hashval;
|
|
HashNode hn, hp, hq;
|
|
|
|
hn = (HashNode) nodeptr;
|
|
hn->nam = nam;
|
|
|
|
hashval = ht->hash(hn->nam) % ht->hsize;
|
|
hp = ht->nodes[hashval];
|
|
|
|
/* check if this is the first node for this hash value */
|
|
if (!hp) {
|
|
hn->next = NULL;
|
|
ht->nodes[hashval] = hn;
|
|
if (++ht->ct >= ht->hsize * 2 && !ht->scan)
|
|
expandhashtable(ht);
|
|
return NULL;
|
|
}
|
|
|
|
/* else check if the first node contains the same key */
|
|
if (ht->cmpnodes(hp->nam, hn->nam) == 0) {
|
|
ht->nodes[hashval] = hn;
|
|
replacing:
|
|
hn->next = hp->next;
|
|
if(ht->scan) {
|
|
if(ht->scan->sorted) {
|
|
HashNode *hashtab = ht->scan->u.s.hashtab;
|
|
int i;
|
|
for(i = ht->scan->u.s.ct; i--; )
|
|
if(hashtab[i] == hp)
|
|
hashtab[i] = hn;
|
|
} else if(ht->scan->u.u == hp)
|
|
ht->scan->u.u = hn;
|
|
}
|
|
return hp;
|
|
}
|
|
|
|
/* else run through the list and check all the keys */
|
|
hq = hp;
|
|
hp = hp->next;
|
|
for (; hp; hq = hp, hp = hp->next) {
|
|
if (ht->cmpnodes(hp->nam, hn->nam) == 0) {
|
|
hq->next = hn;
|
|
goto replacing;
|
|
}
|
|
}
|
|
|
|
/* else just add it at the front of the list */
|
|
hn->next = ht->nodes[hashval];
|
|
ht->nodes[hashval] = hn;
|
|
if (++ht->ct >= ht->hsize * 2 && !ht->scan)
|
|
expandhashtable(ht);
|
|
return NULL;
|
|
}
|
|
|
|
/* Get an enabled entry in a hash table. *
|
|
* If successful, it returns a pointer to *
|
|
* the hashnode. If the node is DISABLED *
|
|
* or isn't found, it returns NULL */
|
|
|
|
/**/
|
|
mod_export HashNode
|
|
gethashnode(HashTable ht, const char *nam)
|
|
{
|
|
unsigned hashval;
|
|
HashNode hp;
|
|
|
|
hashval = ht->hash(nam) % ht->hsize;
|
|
for (hp = ht->nodes[hashval]; hp; hp = hp->next) {
|
|
if (ht->cmpnodes(hp->nam, nam) == 0) {
|
|
if (hp->flags & DISABLED)
|
|
return NULL;
|
|
else
|
|
return hp;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Get an entry in a hash table. It will *
|
|
* ignore the DISABLED flag and return a *
|
|
* pointer to the hashnode if found, else *
|
|
* it returns NULL. */
|
|
|
|
/**/
|
|
mod_export HashNode
|
|
gethashnode2(HashTable ht, const char *nam)
|
|
{
|
|
unsigned hashval;
|
|
HashNode hp;
|
|
|
|
hashval = ht->hash(nam) % ht->hsize;
|
|
for (hp = ht->nodes[hashval]; hp; hp = hp->next) {
|
|
if (ht->cmpnodes(hp->nam, nam) == 0)
|
|
return hp;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Remove an entry from a hash table. *
|
|
* If successful, it removes the node from the *
|
|
* table and returns a pointer to it. If there *
|
|
* is no such node, then it returns NULL */
|
|
|
|
/**/
|
|
mod_export HashNode
|
|
removehashnode(HashTable ht, const char *nam)
|
|
{
|
|
unsigned hashval;
|
|
HashNode hp, hq;
|
|
|
|
hashval = ht->hash(nam) % ht->hsize;
|
|
hp = ht->nodes[hashval];
|
|
|
|
/* if no nodes at this hash value, return NULL */
|
|
if (!hp)
|
|
return NULL;
|
|
|
|
/* else check if the key in the first one matches */
|
|
if (ht->cmpnodes(hp->nam, nam) == 0) {
|
|
ht->nodes[hashval] = hp->next;
|
|
gotit:
|
|
ht->ct--;
|
|
if(ht->scan) {
|
|
if(ht->scan->sorted) {
|
|
HashNode *hashtab = ht->scan->u.s.hashtab;
|
|
int i;
|
|
for(i = ht->scan->u.s.ct; i--; )
|
|
if(hashtab[i] == hp)
|
|
hashtab[i] = NULL;
|
|
} else if(ht->scan->u.u == hp)
|
|
ht->scan->u.u = hp->next;
|
|
}
|
|
return hp;
|
|
}
|
|
|
|
/* else run through the list and check the rest of the keys */
|
|
hq = hp;
|
|
hp = hp->next;
|
|
for (; hp; hq = hp, hp = hp->next) {
|
|
if (ht->cmpnodes(hp->nam, nam) == 0) {
|
|
hq->next = hp->next;
|
|
goto gotit;
|
|
}
|
|
}
|
|
|
|
/* else it is not in the list, so return NULL */
|
|
return NULL;
|
|
}
|
|
|
|
/* Disable a node in a hash table */
|
|
|
|
/**/
|
|
void
|
|
disablehashnode(HashNode hn, UNUSED(int flags))
|
|
{
|
|
hn->flags |= DISABLED;
|
|
}
|
|
|
|
/* Enable a node in a hash table */
|
|
|
|
/**/
|
|
void
|
|
enablehashnode(HashNode hn, UNUSED(int flags))
|
|
{
|
|
hn->flags &= ~DISABLED;
|
|
}
|
|
|
|
/* Compare two hash table entries by name */
|
|
|
|
/**/
|
|
static int
|
|
hnamcmp(const void *ap, const void *bp)
|
|
{
|
|
HashNode a = *(HashNode *)ap;
|
|
HashNode b = *(HashNode *)bp;
|
|
return ztrcmp(a->nam, b->nam);
|
|
}
|
|
|
|
/* Scan the nodes in a hash table and execute scanfunc on nodes based on
|
|
* the flags that are set/unset. scanflags is passed unchanged to
|
|
* scanfunc (if executed).
|
|
*
|
|
* If sorted != 0, then sort entries of hash table before scanning.
|
|
* If flags1 > 0, then execute scanfunc on a node only if at least one of
|
|
* these flags is set.
|
|
* If flags2 > 0, then execute scanfunc on a node only if all of
|
|
* these flags are NOT set.
|
|
* The conditions above for flags1/flags2 must both be true.
|
|
*
|
|
* It is safe to add, remove or replace hash table elements from within
|
|
* the scanfunc. Replaced elements will appear in the scan exactly once,
|
|
* the new version if it was not scanned before the replacement was made.
|
|
* Added elements might or might not appear in the scan.
|
|
*
|
|
* pprog, if non-NULL, is a pattern that must match the name
|
|
* of the node.
|
|
*
|
|
* The function returns the number of matches, as reduced by pprog, flags1
|
|
* and flags2.
|
|
*/
|
|
|
|
/**/
|
|
mod_export int
|
|
scanmatchtable(HashTable ht, Patprog pprog, int sorted,
|
|
int flags1, int flags2, ScanFunc scanfunc, int scanflags)
|
|
{
|
|
int match = 0;
|
|
struct scanstatus st;
|
|
|
|
/*
|
|
* scantab is currently only used by modules to scan
|
|
* tables where the contents are generated on the fly from
|
|
* other objects. Note the fact that in this case pprog,
|
|
* sorted, flags1 and flags2 are ignore.
|
|
*/
|
|
if (!pprog && ht->scantab) {
|
|
ht->scantab(ht, scanfunc, scanflags);
|
|
return ht->ct;
|
|
}
|
|
if (sorted) {
|
|
int i, ct = ht->ct;
|
|
VARARR(HashNode, hnsorttab, ct);
|
|
HashNode *htp, hn;
|
|
|
|
/*
|
|
* Because the structure might change under our feet,
|
|
* we can't apply the flags and the pattern before sorting,
|
|
* tempting though that is.
|
|
*/
|
|
for (htp = hnsorttab, i = 0; i < ht->hsize; i++)
|
|
for (hn = ht->nodes[i]; hn; hn = hn->next)
|
|
*htp++ = hn;
|
|
qsort((void *)hnsorttab, ct, sizeof(HashNode), hnamcmp);
|
|
|
|
st.sorted = 1;
|
|
st.u.s.hashtab = hnsorttab;
|
|
st.u.s.ct = ct;
|
|
ht->scan = &st;
|
|
|
|
for (htp = hnsorttab, i = 0; i < ct; i++, htp++) {
|
|
if ((!flags1 || ((*htp)->flags & flags1)) &&
|
|
!((*htp)->flags & flags2) &&
|
|
(!pprog || pattry(pprog, (*htp)->nam))) {
|
|
match++;
|
|
scanfunc(*htp, scanflags);
|
|
}
|
|
}
|
|
|
|
ht->scan = NULL;
|
|
} else {
|
|
int i, hsize = ht->hsize;
|
|
HashNode *nodes = ht->nodes;
|
|
|
|
st.sorted = 0;
|
|
ht->scan = &st;
|
|
|
|
for (i = 0; i < hsize; i++)
|
|
for (st.u.u = nodes[i]; st.u.u; ) {
|
|
HashNode hn = st.u.u;
|
|
st.u.u = st.u.u->next;
|
|
if ((!flags1 || (hn->flags & flags1)) && !(hn->flags & flags2)
|
|
&& (!pprog || pattry(pprog, hn->nam))) {
|
|
match++;
|
|
scanfunc(hn, scanflags);
|
|
}
|
|
}
|
|
|
|
ht->scan = NULL;
|
|
}
|
|
|
|
return match;
|
|
}
|
|
|
|
|
|
/**/
|
|
mod_export int
|
|
scanhashtable(HashTable ht, int sorted, int flags1, int flags2,
|
|
ScanFunc scanfunc, int scanflags)
|
|
{
|
|
return scanmatchtable(ht, NULL, sorted, flags1, flags2,
|
|
scanfunc, scanflags);
|
|
}
|
|
|
|
/* Expand hash tables when they get too many entries. *
|
|
* The new size is 4 times the previous size. */
|
|
|
|
/**/
|
|
static void
|
|
expandhashtable(HashTable ht)
|
|
{
|
|
struct hashnode **onodes, **ha, *hn, *hp;
|
|
int i, osize;
|
|
|
|
osize = ht->hsize;
|
|
onodes = ht->nodes;
|
|
|
|
ht->hsize = osize * 4;
|
|
ht->nodes = (HashNode *) zshcalloc(ht->hsize * sizeof(HashNode));
|
|
ht->ct = 0;
|
|
|
|
/* scan through the old list of nodes, and *
|
|
* rehash them into the new list of nodes */
|
|
for (i = 0, ha = onodes; i < osize; i++, ha++) {
|
|
for (hn = *ha; hn;) {
|
|
hp = hn->next;
|
|
ht->addnode(ht, hn->nam, hn);
|
|
hn = hp;
|
|
}
|
|
}
|
|
zfree(onodes, osize * sizeof(HashNode));
|
|
}
|
|
|
|
/* Empty the hash table and resize it if necessary */
|
|
|
|
/**/
|
|
static void
|
|
resizehashtable(HashTable ht, int newsize)
|
|
{
|
|
struct hashnode **ha, *hn, *hp;
|
|
int i;
|
|
|
|
/* free all the hash nodes */
|
|
ha = ht->nodes;
|
|
for (i = 0; i < ht->hsize; i++, ha++) {
|
|
for (hn = *ha; hn;) {
|
|
hp = hn->next;
|
|
ht->freenode(hn);
|
|
hn = hp;
|
|
}
|
|
}
|
|
|
|
/* If new size desired is different from current size, *
|
|
* we free it and allocate a new nodes array. */
|
|
if (ht->hsize != newsize) {
|
|
zfree(ht->nodes, ht->hsize * sizeof(HashNode));
|
|
ht->nodes = (HashNode *) zshcalloc(newsize * sizeof(HashNode));
|
|
ht->hsize = newsize;
|
|
} else {
|
|
/* else we just re-zero the current nodes array */
|
|
memset(ht->nodes, 0, newsize * sizeof(HashNode));
|
|
}
|
|
|
|
ht->ct = 0;
|
|
}
|
|
|
|
/* Generic method to empty a hash table */
|
|
|
|
/**/
|
|
mod_export void
|
|
emptyhashtable(HashTable ht)
|
|
{
|
|
resizehashtable(ht, ht->hsize);
|
|
}
|
|
|
|
/**/
|
|
#ifdef ZSH_HASH_DEBUG
|
|
|
|
/* Print info about hash table */
|
|
|
|
#define MAXDEPTH 7
|
|
|
|
/**/
|
|
static void
|
|
printhashtabinfo(HashTable ht)
|
|
{
|
|
HashNode hn;
|
|
int chainlen[MAXDEPTH + 1];
|
|
int i, tmpcount, total;
|
|
|
|
printf("name of table : %s\n", ht->tablename);
|
|
printf("size of nodes[] : %d\n", ht->hsize);
|
|
printf("number of nodes : %d\n\n", ht->ct);
|
|
|
|
memset(chainlen, 0, sizeof(chainlen));
|
|
|
|
/* count the number of nodes just to be sure */
|
|
total = 0;
|
|
for (i = 0; i < ht->hsize; i++) {
|
|
tmpcount = 0;
|
|
for (hn = ht->nodes[i]; hn; hn = hn->next)
|
|
tmpcount++;
|
|
if (tmpcount >= MAXDEPTH)
|
|
chainlen[MAXDEPTH]++;
|
|
else
|
|
chainlen[tmpcount]++;
|
|
total += tmpcount;
|
|
}
|
|
|
|
for (i = 0; i < MAXDEPTH; i++)
|
|
printf("number of hash values with chain of length %d : %4d\n", i, chainlen[i]);
|
|
printf("number of hash values with chain of length %d+ : %4d\n", MAXDEPTH, chainlen[MAXDEPTH]);
|
|
printf("total number of nodes : %4d\n", total);
|
|
}
|
|
|
|
/**/
|
|
int
|
|
bin_hashinfo(UNUSED(char *nam), UNUSED(char **args), UNUSED(Options ops), UNUSED(int func))
|
|
{
|
|
HashTable ht;
|
|
|
|
printf("----------------------------------------------------\n");
|
|
queue_signals();
|
|
for(ht = firstht; ht; ht = ht->next) {
|
|
ht->printinfo(ht);
|
|
printf("----------------------------------------------------\n");
|
|
}
|
|
unqueue_signals();
|
|
return 0;
|
|
}
|
|
|
|
/**/
|
|
#endif /* ZSH_HASH_DEBUG */
|
|
|
|
/********************************/
|
|
/* Command Hash Table Functions */
|
|
/********************************/
|
|
|
|
/* hash table containing external commands */
|
|
|
|
/**/
|
|
mod_export HashTable cmdnamtab;
|
|
|
|
/* how far we've hashed the PATH so far */
|
|
|
|
/**/
|
|
mod_export char **pathchecked;
|
|
|
|
/* Create a new command hash table */
|
|
|
|
/**/
|
|
void
|
|
createcmdnamtable(void)
|
|
{
|
|
cmdnamtab = newhashtable(201, "cmdnamtab", NULL);
|
|
|
|
cmdnamtab->hash = hasher;
|
|
cmdnamtab->emptytable = emptycmdnamtable;
|
|
cmdnamtab->filltable = fillcmdnamtable;
|
|
cmdnamtab->cmpnodes = strcmp;
|
|
cmdnamtab->addnode = addhashnode;
|
|
cmdnamtab->getnode = gethashnode2;
|
|
cmdnamtab->getnode2 = gethashnode2;
|
|
cmdnamtab->removenode = removehashnode;
|
|
cmdnamtab->disablenode = NULL;
|
|
cmdnamtab->enablenode = NULL;
|
|
cmdnamtab->freenode = freecmdnamnode;
|
|
cmdnamtab->printnode = printcmdnamnode;
|
|
|
|
pathchecked = path;
|
|
}
|
|
|
|
/**/
|
|
static void
|
|
emptycmdnamtable(HashTable ht)
|
|
{
|
|
emptyhashtable(ht);
|
|
pathchecked = path;
|
|
}
|
|
|
|
/* Add all commands in a given directory *
|
|
* to the command hashtable. */
|
|
|
|
/**/
|
|
void
|
|
hashdir(char **dirp)
|
|
{
|
|
Cmdnam cn;
|
|
DIR *dir;
|
|
char *fn, *unmetadir, *pathbuf, *pathptr;
|
|
int dirlen;
|
|
#if defined(_WIN32) || defined(__CYGWIN__)
|
|
char *exe;
|
|
#endif /* _WIN32 || _CYGWIN__ */
|
|
|
|
if (isrelative(*dirp))
|
|
return;
|
|
unmetadir = unmeta(*dirp);
|
|
if (!(dir = opendir(unmetadir)))
|
|
return;
|
|
|
|
dirlen = strlen(unmetadir);
|
|
pathbuf = (char *)zalloc(dirlen + PATH_MAX + 2);
|
|
sprintf(pathbuf, "%s/", unmetadir);
|
|
pathptr = pathbuf + dirlen + 1;
|
|
|
|
while ((fn = zreaddir(dir, 1))) {
|
|
if (!cmdnamtab->getnode(cmdnamtab, fn)) {
|
|
char *fname = ztrdup(fn);
|
|
struct stat statbuf;
|
|
int add = 0, dummylen;
|
|
|
|
unmetafy(fn, &dummylen);
|
|
if (strlen(fn) > PATH_MAX) {
|
|
/* Too heavy to do all the allocation */
|
|
add = 1;
|
|
} else {
|
|
strcpy(pathptr, fn);
|
|
/*
|
|
* This is the same test as for the glob qualifier for
|
|
* executable plain files.
|
|
*/
|
|
if (unset(HASHEXECUTABLESONLY) ||
|
|
(access(pathbuf, X_OK) == 0 &&
|
|
stat(pathbuf, &statbuf) == 0 &&
|
|
S_ISREG(statbuf.st_mode) && (statbuf.st_mode & S_IXUGO)))
|
|
add = 1;
|
|
}
|
|
if (add) {
|
|
cn = (Cmdnam) zshcalloc(sizeof *cn);
|
|
cn->node.flags = 0;
|
|
cn->u.name = dirp;
|
|
cmdnamtab->addnode(cmdnamtab, fname, cn);
|
|
} else
|
|
zsfree(fname);
|
|
}
|
|
#if defined(_WIN32) || defined(__CYGWIN__)
|
|
/* Hash foo.exe as foo, since when no real foo exists, foo.exe
|
|
will get executed by DOS automatically. This quiets
|
|
spurious corrections when CORRECT or CORRECT_ALL is set. */
|
|
if ((exe = strrchr(fn, '.')) &&
|
|
(exe[1] == 'E' || exe[1] == 'e') &&
|
|
(exe[2] == 'X' || exe[2] == 'x') &&
|
|
(exe[3] == 'E' || exe[3] == 'e') && exe[4] == 0) {
|
|
*exe = 0;
|
|
if (!cmdnamtab->getnode(cmdnamtab, fn)) {
|
|
cn = (Cmdnam) zshcalloc(sizeof *cn);
|
|
cn->node.flags = 0;
|
|
cn->u.name = dirp;
|
|
cmdnamtab->addnode(cmdnamtab, ztrdup(fn), cn);
|
|
}
|
|
}
|
|
#endif /* _WIN32 || __CYGWIN__ */
|
|
}
|
|
closedir(dir);
|
|
zfree(pathbuf, dirlen + PATH_MAX + 2);
|
|
}
|
|
|
|
/* Go through user's PATH and add everything to *
|
|
* the command hashtable. */
|
|
|
|
/**/
|
|
static void
|
|
fillcmdnamtable(UNUSED(HashTable ht))
|
|
{
|
|
char **pq;
|
|
|
|
for (pq = pathchecked; *pq; pq++)
|
|
hashdir(pq);
|
|
|
|
pathchecked = pq;
|
|
}
|
|
|
|
/**/
|
|
static void
|
|
freecmdnamnode(HashNode hn)
|
|
{
|
|
Cmdnam cn = (Cmdnam) hn;
|
|
|
|
zsfree(cn->node.nam);
|
|
if (cn->node.flags & HASHED)
|
|
zsfree(cn->u.cmd);
|
|
|
|
zfree(cn, sizeof(struct cmdnam));
|
|
}
|
|
|
|
/* Print an element of the cmdnamtab hash table (external command) */
|
|
|
|
/**/
|
|
static void
|
|
printcmdnamnode(HashNode hn, int printflags)
|
|
{
|
|
Cmdnam cn = (Cmdnam) hn;
|
|
|
|
if (printflags & PRINT_WHENCE_WORD) {
|
|
printf("%s: %s\n", cn->node.nam, (cn->node.flags & HASHED) ?
|
|
"hashed" : "command");
|
|
return;
|
|
}
|
|
|
|
if ((printflags & PRINT_WHENCE_CSH) || (printflags & PRINT_WHENCE_SIMPLE)) {
|
|
if (cn->node.flags & HASHED) {
|
|
zputs(cn->u.cmd, stdout);
|
|
putchar('\n');
|
|
} else {
|
|
zputs(*(cn->u.name), stdout);
|
|
putchar('/');
|
|
zputs(cn->node.nam, stdout);
|
|
putchar('\n');
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (printflags & PRINT_WHENCE_VERBOSE) {
|
|
if (cn->node.flags & HASHED) {
|
|
nicezputs(cn->node.nam, stdout);
|
|
printf(" is hashed to ");
|
|
nicezputs(cn->u.cmd, stdout);
|
|
putchar('\n');
|
|
} else {
|
|
nicezputs(cn->node.nam, stdout);
|
|
printf(" is ");
|
|
nicezputs(*(cn->u.name), stdout);
|
|
putchar('/');
|
|
nicezputs(cn->node.nam, stdout);
|
|
putchar('\n');
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (printflags & PRINT_LIST) {
|
|
printf("hash ");
|
|
|
|
if(cn->node.nam[0] == '-')
|
|
printf("-- ");
|
|
}
|
|
|
|
if (cn->node.flags & HASHED) {
|
|
quotedzputs(cn->node.nam, stdout);
|
|
putchar('=');
|
|
quotedzputs(cn->u.cmd, stdout);
|
|
putchar('\n');
|
|
} else {
|
|
quotedzputs(cn->node.nam, stdout);
|
|
putchar('=');
|
|
quotedzputs(*(cn->u.name), stdout);
|
|
putchar('/');
|
|
quotedzputs(cn->node.nam, stdout);
|
|
putchar('\n');
|
|
}
|
|
}
|
|
|
|
/***************************************/
|
|
/* Shell Function Hash Table Functions */
|
|
/***************************************/
|
|
|
|
/* hash table containing the shell functions */
|
|
|
|
/**/
|
|
mod_export HashTable shfunctab;
|
|
|
|
/**/
|
|
void
|
|
createshfunctable(void)
|
|
{
|
|
shfunctab = newhashtable(7, "shfunctab", NULL);
|
|
|
|
shfunctab->hash = hasher;
|
|
shfunctab->emptytable = NULL;
|
|
shfunctab->filltable = NULL;
|
|
shfunctab->cmpnodes = strcmp;
|
|
shfunctab->addnode = addhashnode;
|
|
shfunctab->getnode = gethashnode;
|
|
shfunctab->getnode2 = gethashnode2;
|
|
shfunctab->removenode = removeshfuncnode;
|
|
shfunctab->disablenode = disableshfuncnode;
|
|
shfunctab->enablenode = enableshfuncnode;
|
|
shfunctab->freenode = freeshfuncnode;
|
|
shfunctab->printnode = printshfuncnode;
|
|
}
|
|
|
|
/* Remove an entry from the shell function hash table. *
|
|
* It checks if the function is a signal trap and if so, *
|
|
* it will disable the trapping of that signal. */
|
|
|
|
/**/
|
|
static HashNode
|
|
removeshfuncnode(UNUSED(HashTable ht), const char *nam)
|
|
{
|
|
HashNode hn;
|
|
int signum;
|
|
|
|
if (!strncmp(nam, "TRAP", 4) && (signum = getsignum(nam + 4)) != -1)
|
|
hn = removetrap(signum);
|
|
else
|
|
hn = removehashnode(shfunctab, nam);
|
|
|
|
return hn;
|
|
}
|
|
|
|
/* Disable an entry in the shell function hash table. *
|
|
* It checks if the function is a signal trap and if so, *
|
|
* it will disable the trapping of that signal. */
|
|
|
|
/**/
|
|
static void
|
|
disableshfuncnode(HashNode hn, UNUSED(int flags))
|
|
{
|
|
hn->flags |= DISABLED;
|
|
if (!strncmp(hn->nam, "TRAP", 4)) {
|
|
int signum = getsignum(hn->nam + 4);
|
|
if (signum != -1) {
|
|
sigtrapped[signum] &= ~ZSIG_FUNC;
|
|
unsettrap(signum);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Re-enable an entry in the shell function hash table. *
|
|
* It checks if the function is a signal trap and if so, *
|
|
* it will re-enable the trapping of that signal. */
|
|
|
|
/**/
|
|
static void
|
|
enableshfuncnode(HashNode hn, UNUSED(int flags))
|
|
{
|
|
Shfunc shf = (Shfunc) hn;
|
|
|
|
shf->node.flags &= ~DISABLED;
|
|
if (!strncmp(shf->node.nam, "TRAP", 4)) {
|
|
int signum = getsignum(shf->node.nam + 4);
|
|
if (signum != -1) {
|
|
settrap(signum, NULL, ZSIG_FUNC);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**/
|
|
static void
|
|
freeshfuncnode(HashNode hn)
|
|
{
|
|
Shfunc shf = (Shfunc) hn;
|
|
|
|
zsfree(shf->node.nam);
|
|
if (shf->funcdef)
|
|
freeeprog(shf->funcdef);
|
|
if (shf->redir)
|
|
freeeprog(shf->redir);
|
|
dircache_set(&shf->filename, NULL);
|
|
if (shf->sticky) {
|
|
if (shf->sticky->n_on_opts)
|
|
zfree(shf->sticky->on_opts,
|
|
shf->sticky->n_on_opts * sizeof(*shf->sticky->on_opts));
|
|
if (shf->sticky->n_off_opts)
|
|
zfree(shf->sticky->off_opts,
|
|
shf->sticky->n_off_opts * sizeof(*shf->sticky->off_opts));
|
|
zfree(shf->sticky, sizeof(*shf->sticky));
|
|
}
|
|
zfree(shf, sizeof(struct shfunc));
|
|
}
|
|
|
|
/* Print a shell function */
|
|
|
|
/**/
|
|
static void
|
|
printshfuncnode(HashNode hn, int printflags)
|
|
{
|
|
Shfunc f = (Shfunc) hn;
|
|
char *t = 0;
|
|
|
|
if ((printflags & PRINT_NAMEONLY) ||
|
|
((printflags & PRINT_WHENCE_SIMPLE) &&
|
|
!(printflags & PRINT_WHENCE_FUNCDEF))) {
|
|
zputs(f->node.nam, stdout);
|
|
putchar('\n');
|
|
return;
|
|
}
|
|
|
|
if ((printflags & (PRINT_WHENCE_VERBOSE|PRINT_WHENCE_WORD)) &&
|
|
!(printflags & PRINT_WHENCE_FUNCDEF)) {
|
|
nicezputs(f->node.nam, stdout);
|
|
printf((printflags & PRINT_WHENCE_WORD) ? ": function" :
|
|
(f->node.flags & PM_UNDEFINED) ?
|
|
" is an autoload shell function" :
|
|
" is a shell function");
|
|
if ((printflags & PRINT_WHENCE_VERBOSE) && f->filename) {
|
|
printf(" from ");
|
|
quotedzputs(f->filename, stdout);
|
|
if (f->node.flags & PM_LOADDIR) {
|
|
printf("/");
|
|
quotedzputs(f->node.nam, stdout);
|
|
}
|
|
}
|
|
putchar('\n');
|
|
return;
|
|
}
|
|
|
|
quotedzputs(f->node.nam, stdout);
|
|
if (f->funcdef || f->node.flags & PM_UNDEFINED) {
|
|
printf(" () {\n");
|
|
zoutputtab(stdout);
|
|
if (f->node.flags & PM_UNDEFINED) {
|
|
printf("%c undefined\n", hashchar);
|
|
zoutputtab(stdout);
|
|
} else
|
|
t = getpermtext(f->funcdef, NULL, 1);
|
|
if (f->node.flags & (PM_TAGGED|PM_TAGGED_LOCAL)) {
|
|
printf("%c traced\n", hashchar);
|
|
zoutputtab(stdout);
|
|
}
|
|
if (!t) {
|
|
char *fopt = "UtTkzc";
|
|
int flgs[] = {
|
|
PM_UNALIASED, PM_TAGGED, PM_TAGGED_LOCAL,
|
|
PM_KSHSTORED, PM_ZSHSTORED, PM_CUR_FPATH, 0
|
|
};
|
|
int fl;;
|
|
|
|
zputs("builtin autoload -X", stdout);
|
|
for (fl=0;fopt[fl];fl++)
|
|
if (f->node.flags & flgs[fl]) putchar(fopt[fl]);
|
|
if (f->filename && (f->node.flags & PM_LOADDIR)) {
|
|
putchar(' ');
|
|
zputs(f->filename, stdout);
|
|
}
|
|
} else {
|
|
zputs(t, stdout);
|
|
zsfree(t);
|
|
if (f->funcdef->flags & EF_RUN) {
|
|
printf("\n");
|
|
zoutputtab(stdout);
|
|
quotedzputs(f->node.nam, stdout);
|
|
printf(" \"$@\"");
|
|
}
|
|
}
|
|
printf("\n}");
|
|
} else {
|
|
printf(" () { }");
|
|
}
|
|
if (f->redir) {
|
|
t = getpermtext(f->redir, NULL, 1);
|
|
if (t) {
|
|
zputs(t, stdout);
|
|
zsfree(t);
|
|
}
|
|
}
|
|
|
|
putchar('\n');
|
|
}
|
|
|
|
/*
|
|
* Wrap scanmatchtable for shell functions with optional
|
|
* expansion of leading tabs.
|
|
* expand = 0 is standard: use hard tabs.
|
|
* expand > 0 uses that many spaces.
|
|
* expand < 0 uses no indentation.
|
|
*
|
|
* Note this function and the following two are called with
|
|
* interrupts queued, so saving and restoring text_expand_tabs
|
|
* is safe.
|
|
*/
|
|
|
|
/**/
|
|
mod_export int
|
|
scanmatchshfunc(Patprog pprog, int sorted, int flags1, int flags2,
|
|
ScanFunc scanfunc, int scanflags, int expand)
|
|
{
|
|
int ret, save_expand;
|
|
|
|
save_expand = text_expand_tabs;
|
|
text_expand_tabs = expand;
|
|
ret = scanmatchtable(shfunctab, pprog, sorted, flags1, flags2,
|
|
scanfunc, scanflags);
|
|
text_expand_tabs = save_expand;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Wrap scanhashtable to expand tabs for shell functions */
|
|
|
|
/**/
|
|
mod_export int
|
|
scanshfunc(int sorted, int flags1, int flags2,
|
|
ScanFunc scanfunc, int scanflags, int expand)
|
|
{
|
|
return scanmatchshfunc(NULL, sorted, flags1, flags2,
|
|
scanfunc, scanflags, expand);
|
|
}
|
|
|
|
/* Wrap shfunctab->printnode to expand tabs */
|
|
|
|
/**/
|
|
mod_export void
|
|
printshfuncexpand(HashNode hn, int printflags, int expand)
|
|
{
|
|
int save_expand;
|
|
|
|
save_expand = text_expand_tabs;
|
|
text_expand_tabs = expand;
|
|
shfunctab->printnode(hn, printflags);
|
|
text_expand_tabs = save_expand;
|
|
}
|
|
|
|
/*
|
|
* Get a heap-duplicated name of the shell function, for
|
|
* use in tracing.
|
|
*/
|
|
|
|
/**/
|
|
mod_export char *
|
|
getshfuncfile(Shfunc shf)
|
|
{
|
|
if (shf->node.flags & PM_LOADDIR) {
|
|
return zhtricat(shf->filename, "/", shf->node.nam);
|
|
} else if (shf->filename) {
|
|
return dupstring(shf->filename);
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
/* Reserved Word Hash Table Functions */
|
|
/**************************************/
|
|
|
|
/* Nodes for reserved word hash table */
|
|
|
|
static struct reswd reswds[] = {
|
|
{{NULL, "!", 0}, BANG},
|
|
{{NULL, "[[", 0}, DINBRACK},
|
|
{{NULL, "{", 0}, INBRACE},
|
|
{{NULL, "}", 0}, OUTBRACE},
|
|
{{NULL, "case", 0}, CASE},
|
|
{{NULL, "coproc", 0}, COPROC},
|
|
{{NULL, "declare", 0}, TYPESET},
|
|
{{NULL, "do", 0}, DOLOOP},
|
|
{{NULL, "done", 0}, DONE},
|
|
{{NULL, "elif", 0}, ELIF},
|
|
{{NULL, "else", 0}, ELSE},
|
|
{{NULL, "end", 0}, ZEND},
|
|
{{NULL, "esac", 0}, ESAC},
|
|
{{NULL, "export", 0}, TYPESET},
|
|
{{NULL, "fi", 0}, FI},
|
|
{{NULL, "float", 0}, TYPESET},
|
|
{{NULL, "for", 0}, FOR},
|
|
{{NULL, "foreach", 0}, FOREACH},
|
|
{{NULL, "function", 0}, FUNC},
|
|
{{NULL, "if", 0}, IF},
|
|
{{NULL, "integer", 0}, TYPESET},
|
|
{{NULL, "local", 0}, TYPESET},
|
|
{{NULL, "nocorrect", 0}, NOCORRECT},
|
|
{{NULL, "readonly", 0}, TYPESET},
|
|
{{NULL, "repeat", 0}, REPEAT},
|
|
{{NULL, "select", 0}, SELECT},
|
|
{{NULL, "then", 0}, THEN},
|
|
{{NULL, "time", 0}, TIME},
|
|
{{NULL, "typeset", 0}, TYPESET},
|
|
{{NULL, "until", 0}, UNTIL},
|
|
{{NULL, "while", 0}, WHILE},
|
|
{{NULL, NULL, 0}, 0}
|
|
};
|
|
|
|
/* hash table containing the reserved words */
|
|
|
|
/**/
|
|
mod_export HashTable reswdtab;
|
|
|
|
/* Build the hash table containing zsh's reserved words. */
|
|
|
|
/**/
|
|
void
|
|
createreswdtable(void)
|
|
{
|
|
Reswd rw;
|
|
|
|
reswdtab = newhashtable(23, "reswdtab", NULL);
|
|
|
|
reswdtab->hash = hasher;
|
|
reswdtab->emptytable = NULL;
|
|
reswdtab->filltable = NULL;
|
|
reswdtab->cmpnodes = strcmp;
|
|
reswdtab->addnode = addhashnode;
|
|
reswdtab->getnode = gethashnode;
|
|
reswdtab->getnode2 = gethashnode2;
|
|
reswdtab->removenode = NULL;
|
|
reswdtab->disablenode = disablehashnode;
|
|
reswdtab->enablenode = enablehashnode;
|
|
reswdtab->freenode = NULL;
|
|
reswdtab->printnode = printreswdnode;
|
|
|
|
for (rw = reswds; rw->node.nam; rw++)
|
|
reswdtab->addnode(reswdtab, rw->node.nam, rw);
|
|
}
|
|
|
|
/* Print a reserved word */
|
|
|
|
/**/
|
|
static void
|
|
printreswdnode(HashNode hn, int printflags)
|
|
{
|
|
Reswd rw = (Reswd) hn;
|
|
|
|
if (printflags & PRINT_WHENCE_WORD) {
|
|
printf("%s: reserved\n", rw->node.nam);
|
|
return;
|
|
}
|
|
|
|
if (printflags & PRINT_WHENCE_CSH) {
|
|
printf("%s: shell reserved word\n", rw->node.nam);
|
|
return;
|
|
}
|
|
|
|
if (printflags & PRINT_WHENCE_VERBOSE) {
|
|
printf("%s is a reserved word\n", rw->node.nam);
|
|
return;
|
|
}
|
|
|
|
/* default is name only */
|
|
printf("%s\n", rw->node.nam);
|
|
}
|
|
|
|
/********************************/
|
|
/* Aliases Hash Table Functions */
|
|
/********************************/
|
|
|
|
/* hash table containing the aliases */
|
|
|
|
/**/
|
|
mod_export HashTable aliastab;
|
|
|
|
/* has table containing suffix aliases */
|
|
|
|
/**/
|
|
mod_export HashTable sufaliastab;
|
|
|
|
/* Create new hash tables for aliases */
|
|
|
|
/**/
|
|
void
|
|
createaliastable(HashTable ht)
|
|
{
|
|
ht->hash = hasher;
|
|
ht->emptytable = NULL;
|
|
ht->filltable = NULL;
|
|
ht->cmpnodes = strcmp;
|
|
ht->addnode = addhashnode;
|
|
ht->getnode = gethashnode;
|
|
ht->getnode2 = gethashnode2;
|
|
ht->removenode = removehashnode;
|
|
ht->disablenode = disablehashnode;
|
|
ht->enablenode = enablehashnode;
|
|
ht->freenode = freealiasnode;
|
|
ht->printnode = printaliasnode;
|
|
}
|
|
|
|
/**/
|
|
void
|
|
createaliastables(void)
|
|
{
|
|
/* Table for regular and global aliases */
|
|
|
|
aliastab = newhashtable(23, "aliastab", NULL);
|
|
|
|
createaliastable(aliastab);
|
|
|
|
/* add the default aliases */
|
|
aliastab->addnode(aliastab, ztrdup("run-help"), createaliasnode(ztrdup("man"), 0));
|
|
aliastab->addnode(aliastab, ztrdup("which-command"), createaliasnode(ztrdup("whence"), 0));
|
|
|
|
|
|
/* Table for suffix aliases --- make this smaller */
|
|
|
|
sufaliastab = newhashtable(11, "sufaliastab", NULL);
|
|
|
|
createaliastable(sufaliastab);
|
|
}
|
|
|
|
/* Create a new alias node */
|
|
|
|
/**/
|
|
mod_export Alias
|
|
createaliasnode(char *txt, int flags)
|
|
{
|
|
Alias al;
|
|
|
|
al = (Alias) zshcalloc(sizeof *al);
|
|
al->node.flags = flags;
|
|
al->text = txt;
|
|
al->inuse = 0;
|
|
return al;
|
|
}
|
|
|
|
/**/
|
|
static void
|
|
freealiasnode(HashNode hn)
|
|
{
|
|
Alias al = (Alias) hn;
|
|
|
|
zsfree(al->node.nam);
|
|
zsfree(al->text);
|
|
zfree(al, sizeof(struct alias));
|
|
}
|
|
|
|
/* Print an alias */
|
|
|
|
/**/
|
|
static void
|
|
printaliasnode(HashNode hn, int printflags)
|
|
{
|
|
Alias a = (Alias) hn;
|
|
|
|
if (printflags & PRINT_NAMEONLY) {
|
|
zputs(a->node.nam, stdout);
|
|
putchar('\n');
|
|
return;
|
|
}
|
|
|
|
if (printflags & PRINT_WHENCE_WORD) {
|
|
if (a->node.flags & ALIAS_SUFFIX)
|
|
printf("%s: suffix alias\n", a->node.nam);
|
|
else if (a->node.flags & ALIAS_GLOBAL)
|
|
printf("%s: global alias\n", a->node.nam);
|
|
else
|
|
printf("%s: alias\n", a->node.nam);
|
|
return;
|
|
}
|
|
|
|
if (printflags & PRINT_WHENCE_SIMPLE) {
|
|
zputs(a->text, stdout);
|
|
putchar('\n');
|
|
return;
|
|
}
|
|
|
|
if (printflags & PRINT_WHENCE_CSH) {
|
|
nicezputs(a->node.nam, stdout);
|
|
printf(": ");
|
|
if (a->node.flags & ALIAS_SUFFIX)
|
|
printf("suffix ");
|
|
else if (a->node.flags & ALIAS_GLOBAL)
|
|
printf("globally ");
|
|
printf ("aliased to ");
|
|
nicezputs(a->text, stdout);
|
|
putchar('\n');
|
|
return;
|
|
}
|
|
|
|
if (printflags & PRINT_WHENCE_VERBOSE) {
|
|
nicezputs(a->node.nam, stdout);
|
|
printf(" is a");
|
|
if (a->node.flags & ALIAS_SUFFIX)
|
|
printf(" suffix");
|
|
else if (a->node.flags & ALIAS_GLOBAL)
|
|
printf(" global");
|
|
else
|
|
printf("n");
|
|
printf(" alias for ");
|
|
nicezputs(a->text, stdout);
|
|
putchar('\n');
|
|
return;
|
|
}
|
|
|
|
if (printflags & PRINT_LIST) {
|
|
/* Fast fail on unrepresentable values. */
|
|
if (strchr(a->node.nam, '=')) {
|
|
zwarn("invalid alias '%s' encountered while printing aliases",
|
|
a->node.nam);
|
|
/* ### TODO: Return an error status to the C caller */
|
|
return;
|
|
}
|
|
|
|
/* Normal path. */
|
|
printf("alias ");
|
|
if (a->node.flags & ALIAS_SUFFIX)
|
|
printf("-s ");
|
|
else if (a->node.flags & ALIAS_GLOBAL)
|
|
printf("-g ");
|
|
|
|
/* If an alias begins with `-' or `+', then we must output `-- '
|
|
* first, so that it is not interpreted as an option. */
|
|
if(a->node.nam[0] == '-' || a->node.nam[0] == '+')
|
|
printf("-- ");
|
|
}
|
|
|
|
quotedzputs(a->node.nam, stdout);
|
|
putchar('=');
|
|
quotedzputs(a->text, stdout);
|
|
|
|
putchar('\n');
|
|
}
|
|
|
|
/*************************************/
|
|
/* History Line Hash Table Functions */
|
|
/*************************************/
|
|
|
|
/**/
|
|
void
|
|
createhisttable(void)
|
|
{
|
|
histtab = newhashtable(599, "histtab", NULL);
|
|
|
|
histtab->hash = histhasher;
|
|
histtab->emptytable = emptyhisttable;
|
|
histtab->filltable = NULL;
|
|
histtab->cmpnodes = histstrcmp;
|
|
histtab->addnode = addhistnode;
|
|
histtab->getnode = gethashnode2;
|
|
histtab->getnode2 = gethashnode2;
|
|
histtab->removenode = removehashnode;
|
|
histtab->disablenode = NULL;
|
|
histtab->enablenode = NULL;
|
|
histtab->freenode = freehistnode;
|
|
histtab->printnode = NULL;
|
|
}
|
|
|
|
/**/
|
|
unsigned
|
|
histhasher(const char *str)
|
|
{
|
|
unsigned hashval = 0;
|
|
|
|
while (inblank(*str)) str++;
|
|
|
|
while (*str) {
|
|
if (inblank(*str)) {
|
|
do str++; while (inblank(*str));
|
|
if (*str)
|
|
hashval += (hashval << 5) + ' ';
|
|
}
|
|
else
|
|
hashval += (hashval << 5) + *(unsigned char *)str++;
|
|
}
|
|
return hashval;
|
|
}
|
|
|
|
/**/
|
|
void
|
|
emptyhisttable(HashTable ht)
|
|
{
|
|
emptyhashtable(ht);
|
|
if (hist_ring)
|
|
histremovedups();
|
|
}
|
|
|
|
/* Compare two strings with normalized white-space */
|
|
|
|
/**/
|
|
int
|
|
histstrcmp(const char *str1, const char *str2)
|
|
{
|
|
while (inblank(*str1)) str1++;
|
|
while (inblank(*str2)) str2++;
|
|
while (*str1 && *str2) {
|
|
if (inblank(*str1)) {
|
|
if (!inblank(*str2))
|
|
break;
|
|
do str1++; while (inblank(*str1));
|
|
do str2++; while (inblank(*str2));
|
|
}
|
|
else {
|
|
if (*str1 != *str2)
|
|
break;
|
|
str1++;
|
|
str2++;
|
|
}
|
|
}
|
|
return *str1 - *str2;
|
|
}
|
|
|
|
/**/
|
|
void
|
|
addhistnode(HashTable ht, char *nam, void *nodeptr)
|
|
{
|
|
HashNode oldnode = addhashnode2(ht, nam, nodeptr);
|
|
Histent he = (Histent)nodeptr;
|
|
if (oldnode && oldnode != (HashNode)nodeptr) {
|
|
if (he->node.flags & HIST_MAKEUNIQUE
|
|
|| (he->node.flags & HIST_FOREIGN && (Histent)oldnode == he->up)) {
|
|
(void) addhashnode2(ht, oldnode->nam, oldnode); /* restore hash */
|
|
he->node.flags |= HIST_DUP;
|
|
he->node.flags &= ~HIST_MAKEUNIQUE;
|
|
}
|
|
else {
|
|
oldnode->flags |= HIST_DUP;
|
|
if (hist_ignore_all_dups)
|
|
freehistnode(oldnode); /* Remove the old dup */
|
|
}
|
|
}
|
|
else
|
|
he->node.flags &= ~HIST_MAKEUNIQUE;
|
|
}
|
|
|
|
/**/
|
|
void
|
|
freehistnode(HashNode nodeptr)
|
|
{
|
|
freehistdata((Histent)nodeptr, 1);
|
|
zfree(nodeptr, sizeof (struct histent));
|
|
}
|
|
|
|
/**/
|
|
void
|
|
freehistdata(Histent he, int unlink)
|
|
{
|
|
if (!he)
|
|
return;
|
|
|
|
if (he == &curline)
|
|
return;
|
|
|
|
if (!(he->node.flags & (HIST_DUP | HIST_TMPSTORE)))
|
|
removehashnode(histtab, he->node.nam);
|
|
|
|
zsfree(he->node.nam);
|
|
if (he->nwords)
|
|
zfree(he->words, he->nwords*2*sizeof(short));
|
|
|
|
if (unlink) {
|
|
if (!--histlinect)
|
|
hist_ring = NULL;
|
|
else {
|
|
if (he == hist_ring)
|
|
hist_ring = hist_ring->up;
|
|
he->up->down = he->down;
|
|
he->down->up = he->up;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* Directory name cache mechanism
|
|
*
|
|
* The idea of this is that there are various shell structures,
|
|
* notably functions, that record the directories with which they
|
|
* are associated. Rather than store the full string each time,
|
|
* we store a pointer to the same location and count the references.
|
|
* This is optimised so that retrieval is quick at the expense of
|
|
* searching the list when setting up the structure, which is a much
|
|
* rarer operation.
|
|
*
|
|
* There is nothing special about the fact that the strings are
|
|
* directories, except for the assumptions for efficiency that many
|
|
* structures will point to the same one, and that there are not too
|
|
* many different directories associated with the shell.
|
|
**********************************************************************/
|
|
|
|
struct dircache_entry
|
|
{
|
|
/* Name of directory in cache */
|
|
char *name;
|
|
/* Number of references to it */
|
|
int refs;
|
|
};
|
|
|
|
/*
|
|
* dircache is the cache, of length dircache_size.
|
|
* dircache_lastentry is the last entry used, an optimisation
|
|
* for multiple references to the same directory, e.g
|
|
* "autoload /blah/blah/\*".
|
|
*/
|
|
static struct dircache_entry *dircache, *dircache_lastentry;
|
|
static int dircache_size;
|
|
|
|
/*
|
|
* Set *name to point to a cached version of value.
|
|
* value is copied so may come from any source.
|
|
*
|
|
* If value is NULL, look for the existing value of *name (safe if this
|
|
* too is NULL) and remove a reference to it from the cache. If it's
|
|
* not found in the cache, it's assumed to be an allocated string and
|
|
* freed --- this currently occurs for a shell function that's been
|
|
* loaded as the filename is now a full path, not just a directory,
|
|
* though we may one day optimise this to a cached directory plus a
|
|
* name, too. Note --- the function does *not* otherwise check
|
|
* if *name points to something already cached, so this is
|
|
* necessary any time *name may already be in the cache.
|
|
*/
|
|
|
|
/**/
|
|
mod_export void
|
|
dircache_set(char **name, char *value)
|
|
{
|
|
struct dircache_entry *dcptr, *dcnew;
|
|
|
|
if (!value) {
|
|
if (!*name)
|
|
return;
|
|
if (!dircache_size) {
|
|
zsfree(*name);
|
|
*name = NULL;
|
|
return;
|
|
}
|
|
|
|
for (dcptr = dircache; dcptr < dircache + dircache_size; dcptr++)
|
|
{
|
|
/* Must be a pointer much, not a string match */
|
|
if (*name == dcptr->name)
|
|
{
|
|
--dcptr->refs;
|
|
if (!dcptr->refs) {
|
|
ptrdiff_t ind = dcptr - dircache;
|
|
zsfree(dcptr->name);
|
|
--dircache_size;
|
|
|
|
if (!dircache_size) {
|
|
zfree(dircache, sizeof(*dircache));
|
|
dircache = NULL;
|
|
dircache_lastentry = NULL;
|
|
*name = NULL;
|
|
return;
|
|
}
|
|
dcnew = (struct dircache_entry *)
|
|
zalloc(dircache_size * sizeof(*dcnew));
|
|
if (ind)
|
|
memcpy(dcnew, dircache, ind * sizeof(*dcnew));
|
|
if (ind < dircache_size)
|
|
memcpy(dcnew + ind, dcptr + 1,
|
|
(dircache_size - ind) * sizeof(*dcnew));
|
|
zfree(dircache, (dircache_size+1)*sizeof(*dcnew));
|
|
dircache = dcnew;
|
|
dircache_lastentry = NULL;
|
|
}
|
|
*name = NULL;
|
|
return;
|
|
}
|
|
}
|
|
zsfree(*name);
|
|
*name = NULL;
|
|
} else {
|
|
/*
|
|
* As the function path has been resolved to a particular
|
|
* location, we'll store it as an absolute path.
|
|
*/
|
|
if (*value != '/') {
|
|
value = zhtricat(metafy(zgetcwd(), -1, META_HEAPDUP),
|
|
"/", value);
|
|
value = xsymlink(value, 1);
|
|
}
|
|
/*
|
|
* We'll maintain the cache at exactly the right size rather
|
|
* than overallocating. The rationale here is that typically
|
|
* we'll get a lot of functions in a small number of directories
|
|
* so the complexity overhead of maintaining a separate count
|
|
* isn't really matched by the efficiency gain.
|
|
*/
|
|
if (dircache_lastentry &&
|
|
!strcmp(value, dircache_lastentry->name)) {
|
|
*name = dircache_lastentry->name;
|
|
++dircache_lastentry->refs;
|
|
return;
|
|
} else if (!dircache_size) {
|
|
dircache_size = 1;
|
|
dcptr = dircache =
|
|
(struct dircache_entry *)zalloc(sizeof(*dircache));
|
|
} else {
|
|
for (dcptr = dircache; dcptr < dircache + dircache_size; dcptr++)
|
|
{
|
|
if (!strcmp(value, dcptr->name)) {
|
|
*name = dcptr->name;
|
|
++dcptr->refs;
|
|
return;
|
|
}
|
|
}
|
|
++dircache_size;
|
|
dircache = (struct dircache_entry *)
|
|
zrealloc(dircache, sizeof(*dircache) * dircache_size);
|
|
dcptr = dircache + dircache_size - 1;
|
|
}
|
|
dcptr->name = ztrdup(value);
|
|
*name = dcptr->name;
|
|
dcptr->refs = 1;
|
|
dircache_lastentry = dcptr;
|
|
}
|
|
}
|