plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

db.c (7590B)


      1 #include <u.h>
      2 #include <libc.h>
      3 #include <ip.h>
      4 #include <bio.h>
      5 #include <ndb.h>
      6 #include <ctype.h>
      7 #include "dat.h"
      8 
      9 /*
     10  *  format of a binding entry:
     11  *	char ipaddr[32];
     12  *	char id[32];
     13  *	char hwa[32];
     14  *	char otime[10];
     15  */
     16 Binding *bcache;
     17 uchar bfirst[IPaddrlen];
     18 char *binddir = nil;
     19 char *xbinddir = "#9/ndb/dhcp";
     20 
     21 /*
     22  *  convert a byte array to hex
     23  */
     24 static char
     25 hex(int x)
     26 {
     27 	if(x < 10)
     28 		return x + '0';
     29 	return x - 10 + 'a';
     30 }
     31 extern char*
     32 tohex(char *hdr, uchar *p, int len)
     33 {
     34 	char *s, *sp;
     35 	int hlen;
     36 
     37 	hlen = strlen(hdr);
     38 	s = malloc(hlen + 2*len + 1);
     39 	sp = s;
     40 	strcpy(sp, hdr);
     41 	sp += hlen;
     42 	for(; len > 0; len--){
     43 		*sp++ = hex(*p>>4);
     44 		*sp++ = hex(*p & 0xf);
     45 		p++;
     46 	}
     47 	*sp = 0;
     48 	return s;
     49 }
     50 
     51 /*
     52  *  convert a client id to a string.  If it's already
     53  *  ascii, leave it be.  Otherwise, convert it to hex.
     54  */
     55 extern char*
     56 toid(uchar *p, int n)
     57 {
     58 	int i;
     59 	char *s;
     60 
     61 	for(i = 0; i < n; i++)
     62 		if(!isprint(p[i]))
     63 			return tohex("id", p, n);
     64 	s = malloc(n + 1);
     65 	memmove(s, p, n);
     66 	s[n] = 0;
     67 	return s;
     68 }
     69 
     70 /*
     71  *  increment an ip address
     72  */
     73 static void
     74 incip(uchar *ip)
     75 {
     76 	int i, x;
     77 
     78 	for(i = IPaddrlen-1; i >= 0; i--){
     79 		x = ip[i];
     80 		x++;
     81 		ip[i] = x;
     82 		if((x & 0x100) == 0)
     83 			break;
     84 	}
     85 }
     86 
     87 /*
     88  *  find a binding for an id or hardware address
     89  */
     90 static int
     91 lockopen(char *file)
     92 {
     93 	char err[ERRMAX];
     94 	int fd, tries;
     95 
     96 	for(tries = 0; tries < 5; tries++){
     97 		fd = open(file, OLOCK|ORDWR);
     98 		if(fd >= 0)
     99 			return fd;
    100 		errstr(err, sizeof err);
    101 		if(strstr(err, "lock")){
    102 			/* wait for other process to let go of lock */
    103 			sleep(250);
    104 
    105 			/* try again */
    106 			continue;
    107 		}
    108 		if(strstr(err, "exist") || strstr(err, "No such")){
    109 			/* no file, create an exclusive access file */
    110 			fd = create(file, ORDWR, DMEXCL|0666);
    111 			chmod(file, 0666);
    112 			if(fd >= 0)
    113 				return fd;
    114 		}
    115 	}
    116 	return -1;
    117 }
    118 
    119 void
    120 setbinding(Binding *b, char *id, long t)
    121 {
    122 	if(b->boundto)
    123 		free(b->boundto);
    124 
    125 	b->boundto = strdup(id);
    126 	b->lease = t;
    127 }
    128 
    129 static void
    130 parsebinding(Binding *b, char *buf)
    131 {
    132 	long t;
    133 	char *id, *p;
    134 
    135 	/* parse */
    136 	t = atoi(buf);
    137 	id = strchr(buf, '\n');
    138 	if(id){
    139 		*id++ = 0;
    140 		p = strchr(id, '\n');
    141 		if(p)
    142 			*p = 0;
    143 	} else
    144 		id = "";
    145 
    146 	/* replace any past info */
    147 	setbinding(b, id, t);
    148 }
    149 
    150 static int
    151 writebinding(int fd, Binding *b)
    152 {
    153 	Dir *d;
    154 
    155 	seek(fd, 0, 0);
    156 	if(fprint(fd, "%ld\n%s\n", b->lease, b->boundto) < 0)
    157 		return -1;
    158 	d = dirfstat(fd);
    159 	if(d == nil)
    160 		return -1;
    161 	b->q.type = d->qid.type;
    162 	b->q.path = d->qid.path;
    163 	b->q.vers = d->qid.vers;
    164 	free(d);
    165 	return 0;
    166 }
    167 
    168 /*
    169  *  synchronize cached binding with file.  the file always wins.
    170  */
    171 int
    172 syncbinding(Binding *b, int returnfd)
    173 {
    174 	char buf[512];
    175 	int i, fd;
    176 	Dir *d;
    177 
    178 	if(binddir == nil)
    179 		binddir = unsharp(xbinddir);
    180 
    181 	snprint(buf, sizeof(buf), "%s/%I", binddir, b->ip);
    182 	fd = lockopen(buf);
    183 	if(fd < 0){
    184 		/* assume someone else is using it */
    185 		b->lease = time(0) + OfferTimeout;
    186 		return -1;
    187 	}
    188 
    189 	/* reread if changed */
    190 	d = dirfstat(fd);
    191 	if(d != nil)	/* BUG? */
    192 	if(d->qid.type != b->q.type || d->qid.path != b->q.path || d->qid.vers != b->q.vers){
    193 		i = read(fd, buf, sizeof(buf)-1);
    194 		if(i < 0)
    195 			i = 0;
    196 		buf[i] = 0;
    197 		parsebinding(b, buf);
    198 		b->lasttouched = d->mtime;
    199 		b->q.path = d->qid.path;
    200 		b->q.vers = d->qid.vers;
    201 	}
    202 
    203 	free(d);
    204 
    205 	if(returnfd)
    206 		return fd;
    207 
    208 	close(fd);
    209 	return 0;
    210 }
    211 
    212 extern int
    213 samenet(uchar *ip, Info *iip)
    214 {
    215 	uchar x[IPaddrlen];
    216 
    217 	maskip(iip->ipmask, ip, x);
    218 	return ipcmp(x, iip->ipnet) == 0;
    219 }
    220 
    221 /*
    222  *  create a record for each binding
    223  */
    224 extern void
    225 initbinding(uchar *first, int n)
    226 {
    227 	while(n-- > 0){
    228 		iptobinding(first, 1);
    229 		incip(first);
    230 	}
    231 }
    232 
    233 /*
    234  *  find a binding for a specific ip address
    235  */
    236 extern Binding*
    237 iptobinding(uchar *ip, int mk)
    238 {
    239 	Binding *b;
    240 
    241 	for(b = bcache; b; b = b->next){
    242 		if(ipcmp(b->ip, ip) == 0){
    243 			syncbinding(b, 0);
    244 			return b;
    245 		}
    246 	}
    247 
    248 	if(mk == 0)
    249 		return 0;
    250 	b = malloc(sizeof(*b));
    251 	memset(b, 0, sizeof(*b));
    252 	ipmove(b->ip, ip);
    253 	b->next = bcache;
    254 	bcache = b;
    255 	syncbinding(b, 0);
    256 	return b;
    257 }
    258 
    259 static void
    260 lognolease(Binding *b)
    261 {
    262 	/* renew the old binding, and hope it eventually goes away */
    263 	b->offer = 5*60;
    264 	commitbinding(b);
    265 
    266 	/* complain if we haven't in the last 5 minutes */
    267 	if(now - b->lastcomplained < 5*60)
    268 		return;
    269 	syslog(0, blog, "dhcp: lease for %I to %s ended at %ld but still in use\n",
    270 		b->ip, b->boundto != nil ? b->boundto : "?", b->lease);
    271 	b->lastcomplained = now;
    272 }
    273 
    274 /*
    275  *  find a free binding for a hw addr or id on the same network as iip
    276  */
    277 extern Binding*
    278 idtobinding(char *id, Info *iip, int ping)
    279 {
    280 	Binding *b, *oldest;
    281 	int oldesttime;
    282 
    283 	/*
    284 	 *  first look for an old binding that matches.  that way
    285 	 *  clients will tend to keep the same ip addresses.
    286 	 */
    287 	for(b = bcache; b; b = b->next){
    288 		if(b->boundto && strcmp(b->boundto, id) == 0){
    289 			if(!samenet(b->ip, iip))
    290 				continue;
    291 
    292 			/* check with the other servers */
    293 			syncbinding(b, 0);
    294 			if(strcmp(b->boundto, id) == 0)
    295 				return b;
    296 		}
    297 	}
    298 
    299 	/*
    300 	 *  look for oldest binding that we think is unused
    301 	 */
    302 	for(;;){
    303 		oldest = nil;
    304 		oldesttime = 0;
    305 		for(b = bcache; b; b = b->next){
    306 			if(b->tried != now)
    307 			if(b->lease < now && b->expoffer < now && samenet(b->ip, iip))
    308 			if(oldest == nil || b->lasttouched < oldesttime){
    309 				/* sync and check again */
    310 				syncbinding(b, 0);
    311 				if(b->lease < now && b->expoffer < now && samenet(b->ip, iip))
    312 				if(oldest == nil || b->lasttouched < oldesttime){
    313 					oldest = b;
    314 					oldesttime = b->lasttouched;
    315 				}
    316 			}
    317 		}
    318 		if(oldest == nil)
    319 			break;
    320 
    321 		/* make sure noone is still using it */
    322 		oldest->tried = now;
    323 		if(ping == 0 || icmpecho(oldest->ip) == 0)
    324 			return oldest;
    325 
    326 		lognolease(oldest);	/* sets lastcomplained */
    327 	}
    328 
    329 	/* try all bindings */
    330 	for(b = bcache; b; b = b->next){
    331 		syncbinding(b, 0);
    332 		if(b->tried != now)
    333 		if(b->lease < now && b->expoffer < now && samenet(b->ip, iip)){
    334 			b->tried = now;
    335 			if(ping == 0 || icmpecho(b->ip) == 0)
    336 				return b;
    337 
    338 			lognolease(b);
    339 		}
    340 	}
    341 
    342 	/* nothing worked, give up */
    343 	return 0;
    344 }
    345 
    346 /*
    347  *  create an offer
    348  */
    349 extern void
    350 mkoffer(Binding *b, char *id, long leasetime)
    351 {
    352 	if(leasetime <= 0){
    353 		if(b->lease > now + minlease)
    354 			leasetime = b->lease - now;
    355 		else
    356 			leasetime = minlease;
    357 	}
    358 	if(b->offeredto)
    359 		free(b->offeredto);
    360 	b->offeredto = strdup(id);
    361 	b->offer = leasetime;
    362 	b->expoffer = now + OfferTimeout;
    363 }
    364 
    365 /*
    366  *  find an offer for this id
    367  */
    368 extern Binding*
    369 idtooffer(char *id, Info *iip)
    370 {
    371 	Binding *b;
    372 
    373 	/* look for an offer to this id */
    374 	for(b = bcache; b; b = b->next){
    375 		if(b->offeredto && strcmp(b->offeredto, id) == 0 && samenet(b->ip, iip)){
    376 			/* make sure some other system hasn't stolen it */
    377 			syncbinding(b, 0);
    378 			if(b->lease < now
    379 			|| (b->boundto && strcmp(b->boundto, b->offeredto) == 0))
    380 				return b;
    381 		}
    382 	}
    383 	return 0;
    384 }
    385 
    386 /*
    387  *  commit a lease, this could fail
    388  */
    389 extern int
    390 commitbinding(Binding *b)
    391 {
    392 	int fd;
    393 	long now;
    394 
    395 	now = time(0);
    396 
    397 	if(b->offeredto == 0)
    398 		return -1;
    399 	fd = syncbinding(b, 1);
    400 	if(fd < 0)
    401 		return -1;
    402 	if(b->lease > now && b->boundto && strcmp(b->boundto, b->offeredto) != 0){
    403 		close(fd);
    404 		return -1;
    405 	}
    406 	setbinding(b, b->offeredto, now + b->offer);
    407 	b->lasttouched = now;
    408 
    409 	if(writebinding(fd, b) < 0){
    410 		close(fd);
    411 		return -1;
    412 	}
    413 	close(fd);
    414 	return 0;
    415 }
    416 
    417 /*
    418  *  commit a lease, this could fail
    419  */
    420 extern int
    421 releasebinding(Binding *b, char *id)
    422 {
    423 	int fd;
    424 	long now;
    425 
    426 	now = time(0);
    427 
    428 	fd = syncbinding(b, 1);
    429 	if(fd < 0)
    430 		return -1;
    431 	if(b->lease > now && b->boundto && strcmp(b->boundto, id) != 0){
    432 		close(fd);
    433 		return -1;
    434 	}
    435 	b->lease = 0;
    436 	b->expoffer = 0;
    437 
    438 	if(writebinding(fd, b) < 0){
    439 		close(fd);
    440 		return -1;
    441 	}
    442 	close(fd);
    443 	return 0;
    444 }