| /*  | 
 |  * This file is part of the UCB release of Plan 9. It is subject to the license | 
 |  * terms in the LICENSE file found in the top-level directory of this | 
 |  * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No | 
 |  * part of the UCB release of Plan 9, including this file, may be copied, | 
 |  * modified, propagated, or distributed except according to the terms contained | 
 |  * in the LICENSE file. | 
 |  */ | 
 | #include <stdlib.h> | 
 |  | 
 | #include <stdio.h> | 
 | #include <parlib/parlib.h> | 
 | #include <unistd.h> | 
 | #include <signal.h> | 
 | #include <fcntl.h> | 
 | #include <iplib/iplib.h> | 
 | #include <ndblib/ndb.h> | 
 | #include <ndblib/ndbhf.h> | 
 |  | 
 | enum { | 
 | 	Dptr,	/* pointer to database file */ | 
 | 	Cptr,	/* pointer to first chain entry */ | 
 | 	Cptr1,	/* pointer to second chain entry */ | 
 | }; | 
 |  | 
 | /* | 
 |  *  generate a hash value for an ascii string (val) given | 
 |  *  a hash table length (hlen) | 
 |  */ | 
 | uint32_t | 
 | ndbhash(char *vp, int hlen) | 
 | { | 
 | 	uint32_t hash; | 
 | 	uint8_t *val = (uint8_t*)vp; | 
 |  | 
 | 	for(hash = 0; *val; val++) | 
 | 		hash = (hash*13) + *val-'a'; | 
 | 	return hash % hlen; | 
 | } | 
 |  | 
 | /* | 
 |  *  read a hash file with buffering | 
 |  */ | 
 | static uint8_t* | 
 | hfread(struct ndbhf *hf, long off, int len) | 
 | { | 
 | 	if(off < hf->off || off + len > hf->off + hf->len){ | 
 | 		if(lseek(hf->fd, off, 0) < 0 | 
 | 		|| (hf->len = read(hf->fd, hf->buf, sizeof(hf->buf))) < len){ | 
 | 			hf->off = -1; | 
 | 			return 0; | 
 | 		} | 
 | 		hf->off = off; | 
 | 	} | 
 | 	return &hf->buf[off-hf->off]; | 
 | } | 
 |  | 
 | /* | 
 |  *  return an opened hash file if one exists for the | 
 |  *  attribute and if it is current vis-a-vis the data | 
 |  *  base file | 
 |  */ | 
 | static struct ndbhf* | 
 | hfopen(struct ndb *db, char *attr) | 
 | { | 
 | 	static int beenhere = 0; | 
 | 	struct ndbhf *hf; | 
 | 	char buf[sizeof(hf->attr)+sizeof(db->file)+2]; | 
 | 	uint8_t *p; | 
 | 	struct dir *d; | 
 |  | 
 | 	/* try opening the data base if it's closed */ | 
 | 	if(db->mtime==0 && ndbreopen(db) < 0) | 
 | 		return 0; | 
 |  | 
 | 	/* if the database has changed, throw out hash files and reopen db */ | 
 | #if 0 | 
 | 	if((d = dirfstat(Bfildes(&db->b))) == NULL || db->qid.path != d->qid.path | 
 | 	|| db->qid.vers != d->qid.vers){ | 
 | #else | 
 | 	if (! beenhere){ | 
 | #endif | 
 | 		if(ndbreopen(db) < 0){ | 
 | 			free(d); | 
 | 			return 0; | 
 | 		} | 
 | 		beenhere = 1; | 
 | 		db->mtime = 1; | 
 | 	} | 
 | 	free(d); | 
 |  | 
 | 	if(db->nohash) | 
 | 		return 0; | 
 |  | 
 | 	/* see if a hash file exists for this attribute */ | 
 | 	for(hf = db->hf; hf; hf= hf->next){ | 
 | 		if(strcmp(hf->attr, attr) == 0) | 
 | 			return hf; | 
 | 	} | 
 |  | 
 | 	/* create a new one */ | 
 | 	hf = (struct ndbhf*)calloc(sizeof(struct ndbhf), 1); | 
 | 	if(hf == 0) | 
 | 		return 0; | 
 | 	memset(hf, 0, sizeof(struct ndbhf)); | 
 |  | 
 | 	/* compare it to the database file */ | 
 | 	strncpy(hf->attr, attr, sizeof(hf->attr)-1); | 
 | 	sprintf(buf, "%s.%s", db->file, hf->attr); | 
 | 	hf->fd = open(buf, O_RDONLY); | 
 | 	if(hf->fd >= 0){ | 
 | 		hf->len = 0; | 
 | 		hf->off = 0; | 
 | 		p = hfread(hf, 0, 2*NDBULLEN); | 
 | 		if(p){ | 
 | 			hf->dbmtime = NDBGETUL(p); | 
 | 			hf->hlen = NDBGETUL(p+NDBULLEN); | 
 | 			if(hf->dbmtime == db->mtime){ | 
 | 				hf->next = db->hf; | 
 | 				db->hf = hf; | 
 | 				return hf; | 
 | 			} | 
 | 		} | 
 | 		close(hf->fd); | 
 | 	} | 
 |  | 
 | 	free(hf); | 
 | 	return 0; | 
 | } | 
 |  | 
 | /* | 
 |  *  return the first matching entry | 
 |  */ | 
 | struct ndbtuple* | 
 | ndbsearch(struct ndb *db, struct ndbs *s, char *attr, char *val) | 
 | { | 
 | 	uint8_t *p; | 
 | 	struct ndbtuple *t; | 
 | 	struct ndbhf *hf; | 
 | 	hf = hfopen(db, attr); | 
 |  | 
 | 	memset(s, 0, sizeof(*s)); | 
 | 	if(_ndbcachesearch(db, s, attr, val, &t) == 0){ | 
 | 		/* found in cache */ | 
 | 		if(t != NULL){ | 
 | 			ndbsetmalloctag(t, getcallerpc(&db)); | 
 | 			return t;	/* answer from this file */ | 
 | 		} | 
 | 		if(db->next == NULL) | 
 | 			return NULL; | 
 | 		t = ndbsearch(db->next, s, attr, val); | 
 | 		ndbsetmalloctag(t, getcallerpc(&db)); | 
 | 		return t; | 
 | 	} | 
 |  | 
 | 	s->db = db; | 
 | 	s->hf = hf; | 
 | 	if(s->hf){ | 
 | 		s->ptr = ndbhash(val, s->hf->hlen)*NDBPLEN; | 
 | 		p = hfread(s->hf, s->ptr+NDBHLEN, NDBPLEN); | 
 | 		if(p == 0){ | 
 | 			t = _ndbcacheadd(db, s, attr, val, NULL); | 
 | 			ndbsetmalloctag(t, getcallerpc(&db)); | 
 | 			return t; | 
 | 		} | 
 | 		s->ptr = NDBGETP(p); | 
 | 		s->type = Cptr1; | 
 | 	} else if(db->length > 128*1024){ | 
 | 		printf("Missing or out of date hash file %s.%s.\n", db->file, attr); | 
 | 		//syslog(0, "ndb", "Missing or out of date hash file %s.%s.", db->file, attr); | 
 |  | 
 | 		/* advance search to next db file */ | 
 | 		s->ptr = NDBNAP; | 
 | 		_ndbcacheadd(db, s, attr, val, NULL); | 
 | 		if(db->next == 0) | 
 | 			return NULL; | 
 | 		t = ndbsearch(db->next, s, attr, val); | 
 | 		ndbsetmalloctag(t, getcallerpc(&db)); | 
 | 		return t; | 
 | 	} else { | 
 | 		s->ptr = 0; | 
 | 		s->type = Dptr; | 
 | 	} | 
 | 	t = ndbsnext(s, attr, val); | 
 | 	_ndbcacheadd(db, s, attr, val, (t != NULL && s->db == db)?t:NULL); | 
 | 	ndbsetmalloctag(t, getcallerpc(&db)); | 
 | 	return t; | 
 | } | 
 |  | 
 | static struct ndbtuple* | 
 | match(struct ndbtuple *t, char *attr, char *val) | 
 | { | 
 | 	struct ndbtuple *nt; | 
 |  | 
 | 	for(nt = t; nt; nt = nt->entry) | 
 | 		if(strcmp(attr, nt->attr) == 0 | 
 | 		&& strcmp(val, nt->val) == 0) | 
 | 			return nt; | 
 | 	return 0; | 
 | } | 
 |  | 
 | /* | 
 |  *  return the next matching entry in the hash chain | 
 |  */ | 
 | struct ndbtuple* | 
 | ndbsnext(struct ndbs *s, char *attr, char *val) | 
 | { | 
 | 	struct ndbtuple *t; | 
 | 	struct ndb *db; | 
 | 	uint8_t *p; | 
 |  | 
 | 	db = s->db; | 
 | 	if(s->ptr == NDBNAP) | 
 | 		goto nextfile; | 
 |  | 
 | 	for(;;){ | 
 | 		if(s->type == Dptr){ | 
 | 			if(fseek(db->b, s->ptr, 0) < 0) | 
 | 				break; | 
 | 			t = ndbparse(db); | 
 | 			s->ptr = ftell(db->b); | 
 | 			if(t == 0) | 
 | 				break; | 
 | 			if(s->t = match(t, attr, val)){ | 
 | 				ndbsetmalloctag(t, getcallerpc(&s)); | 
 | 				return t; | 
 | 			} | 
 | 			ndbfree(t); | 
 | 		} else if(s->type == Cptr){ | 
 | 			if(fseek(db->b, s->ptr, 0) < 0) | 
 | 				break;  | 
 | 			s->ptr = s->ptr1; | 
 | 			s->type = Cptr1; | 
 | 			t = ndbparse(db); | 
 | 			if(t == 0) | 
 | 				break; | 
 | 			if(s->t = match(t, attr, val)){ | 
 | 				ndbsetmalloctag(t, getcallerpc(&s)); | 
 | 				return t; | 
 | 			} | 
 | 			ndbfree(t); | 
 | 		} else if(s->type == Cptr1){ | 
 | 			if(s->ptr & NDBCHAIN){	/* hash chain continuation */ | 
 | 				s->ptr &= ~NDBCHAIN; | 
 | 				p = hfread(s->hf, s->ptr+NDBHLEN, 2*NDBPLEN); | 
 | 				if(p == 0) | 
 | 					break; | 
 | 				s->ptr = NDBGETP(p); | 
 | 				s->ptr1 = NDBGETP(p+NDBPLEN); | 
 | 				s->type = Cptr; | 
 | 			} else {		/* end of hash chain */ | 
 | 				if(fseek(db->b, s->ptr, 0) < 0) | 
 | 					break;  | 
 | 				s->ptr = NDBNAP; | 
 | 				t = ndbparse(db); | 
 | 				if(t == 0) | 
 | 					break; | 
 | 				if(s->t = match(t, attr, val)){ | 
 | 					ndbsetmalloctag(t, getcallerpc(&s)); | 
 | 					return t; | 
 | 				} | 
 | 				ndbfree(t); | 
 | 				break; | 
 | 			} | 
 | 		} | 
 | 	} | 
 |  | 
 | nextfile: | 
 |  | 
 | 	/* nothing left to search? */ | 
 | 	s->ptr = NDBNAP; | 
 | 	if(db->next == 0) | 
 | 		return 0; | 
 |  | 
 | 	/* advance search to next db file */ | 
 | 	t = ndbsearch(db->next, s, attr, val); | 
 | 	ndbsetmalloctag(t, getcallerpc(&s)); | 
 | 	return t; | 
 | } |