1 /*
2     OWFS -- One-Wire filesystem
3     OWHTTPD -- One-Wire Web Server
4     Written 2003 Paul H Alfille
5     email: paul.alfille@gmail.com
6     Released under the GPL
7     See the header file: ow.h for full attribution
8     1wire/iButton system from Dallas Semiconductor
9 */
10 
11 // 8/18/2004 -- applied Serg Oskin's correction
12 // 8/20/2004 -- changed everything, specifically no longer using db, tsearch instead!
13 #include <config.h>
14 #include "owfs_config.h"
15 #include "ow.h"
16 #include "ow_counters.h"
17 #include "ow_connection.h"
18 
19 //#define CACHE_DEBUG
20 
21 #include <limits.h>
22 
23 #define EXTENSION_INTERNAL  -2
24 
25 // Directories are bus-specific, but buses are dynamic
26 // The numbering is sequential, however, so use that and an arbitrary
27 // generic unique address for directories.
28 int DirMarkerLoc ;
29 void * Directory_Marker = &DirMarkerLoc ;
30 int AuxDirMarkerLoc ;
31 void * AuxDirectory_Marker = &AuxDirMarkerLoc ;
32 int MainDirMarkerLoc ;
33 void * MainDirectory_Marker = &MainDirMarkerLoc ;
34 
35 // Devices are sn-based
36 // generic unique address for devices.
37 int DevMarkerLoc ;
38 void * Device_Marker = &DevMarkerLoc ;
39 
40 // Aliases are sn-based
41 // generic unique address for devices.
42 int AliasMarkerLoc ;
43 void * Alias_Marker = &AliasMarkerLoc ;
44 
45 
46 /* Put the globals into a struct to declutter the namespace */
47 struct cache_data {
48 	void *temporary_tree_new;			// current cache database
49 	void *temporary_tree_old;			// older cache database
50 	void *persistent_tree;				// persistent database
51 	void *temporary_alias_tree_new;		// current cache database
52 	void *temporary_alias_tree_old;		// older cache database
53 	void *persistent_alias_tree;		// persistent database
54 	size_t old_ram_size;				// cache size
55 	size_t new_ram_size;				// cache size
56 	time_t time_retired;				// start time of older
57 	time_t time_to_kill;				// deathtime of older
58 	time_t retired_lifespan;			// lifetime of older
59 	UINT added;					// items added
60 };
61 static struct cache_data cache;
62 
63 /* Cache elements are placed in a Red/Black binary tree
64 	-- standard glibc implementation
65 	-- use gnu tdestroy extension
66 	-- compatibility implementation included
67   Cache key has 3 components: (struct tree_key)
68 	sn -- 8 byte serial number of 1-wire slave
69 	p -- pointer to internal structure like filetype (guaranteed unique if non-portable)
70 	extension -- integer used for array elements
71   Cache node is the entire node not including data payload (struct key_node)
72 	key -- sorted component as above
73 	expires -- the time that the element is no longer valid
74 	dsize -- length in bytes of trailing data
75   Cache data is the actual data
76 	allocated at same call as cache node
77 	access via macro TREE_DATA
78 	freed when cache node is freed
79   Note: This means that cache data must be a copy of program data
80         both on creation and retrieval
81 */
82 
83 /* Key used for sorting/retrieving cache data
84    sn is for device serial number
85    p is a pointer to filetype, or other things (guaranteed unique and fast lookup
86    extension is used for indexed array properties
87 */
88 struct tree_key {
89 	BYTE sn[8];
90 	void *p;
91 	int extension;
92 };
93 
94 /* How we organize the data in the binary tree used for cache storage
95    A key (see above)
96    An expiration time
97    And a size in bytes
98    Actaully size bytes follows with the data
99 */
100 struct tree_node {
101 	struct tree_key tk;
102 	time_t expires;
103 	size_t dsize;
104 };
105 
106 struct alias_tree_node {
107 	size_t size;
108 	time_t expires;
109 	union {
110 		INDEX_OR_ERROR bus;
111 		BYTE sn[SERIAL_NUMBER_SIZE];
112 	} ;
113 };
114 
115 /* Bad bad C library */
116 /* implementation of tfind, tsearch returns an opaque structure */
117 /* you have to know that the first element is a pointer to your data */
118 struct tree_opaque {
119 	struct tree_node *key;
120 	void *other;
121 };
122 
123 #define TREE_DATA(tn)    ( (BYTE *)(tn) + sizeof(struct tree_node) )
124 #define CONST_TREE_DATA(tn)    ( (const BYTE *)(tn) + sizeof(struct tree_node) )
125 
126 #define ALIAS_TREE_DATA(atn)    ( (ASCII *)(atn) + sizeof(struct alias_tree_node) )
127 #define CONST_ALIAS_TREE_DATA(atn)    ( (const ASCII *)(atn) + sizeof(struct alias_tree_node) )
128 
129 enum cache_task_return { ctr_ok, ctr_not_found, ctr_expired, ctr_size_mismatch, } ;
130 
131 static void FlipTree( void ) ;
132 
133 static int IsThisPersistent( const struct parsedname * pn ) ;
134 
135 static GOOD_OR_BAD Cache_Add(const void *data, const size_t datasize, const struct parsedname *pn);
136 static GOOD_OR_BAD Cache_Add_Common(struct tree_node *tn);
137 static GOOD_OR_BAD Cache_Add_Persistent(struct tree_node *tn);
138 
139 static enum cache_task_return Cache_Get_Common(void *data, size_t * dsize, time_t * duration, const struct tree_node *tn);
140 static enum cache_task_return Cache_Get_Common_Dir(struct dirblob *db, time_t * duration, const struct tree_node *tn);
141 static enum cache_task_return Cache_Get_Persistent(void *data, size_t * dsize, time_t * duration, const struct tree_node *tn);
142 
143 static GOOD_OR_BAD Cache_Get_Simultaneous(const struct internal_prop *ip, struct one_wire_query *owq) ;
144 static GOOD_OR_BAD Cache_Get_Internal(void *data, size_t * dsize, const struct internal_prop *ip, const struct parsedname *pn);
145 static GOOD_OR_BAD Cache_Get_Strict(void *data, size_t dsize, const struct parsedname *pn);
146 
147 static void Cache_Del(const struct parsedname *pn) ;
148 static GOOD_OR_BAD Cache_Del_Common(const struct tree_node *tn);
149 static GOOD_OR_BAD Cache_Del_Persistent(const struct tree_node *tn);
150 
151 static void Cache_Add_Alias_Common(struct alias_tree_node *atn);
152 static INDEX_OR_ERROR Cache_Get_Alias_Common( struct alias_tree_node * atn) ;
153 
154 static void Cache_Add_Alias_SN(const ASCII * alias_name, const BYTE * sn) ;
155 static void Cache_Del_Alias_SN(const ASCII * alias_name) ;
156 
157 static void Cache_Add_Alias_Persistent(struct alias_tree_node *atn);
158 static GOOD_OR_BAD Cache_Get_Alias_Persistent( BYTE * sn, struct alias_tree_node * atn);
159 static void Cache_Del_Alias_Persistent( struct alias_tree_node * atn) ;
160 
161 static GOOD_OR_BAD Add_Stat(struct cache_stats *scache, GOOD_OR_BAD result);
162 static GOOD_OR_BAD Get_Stat(struct cache_stats *scache, const enum cache_task_return result);
163 static void Del_Stat(struct cache_stats *scache, const int result);
164 
165 static int tree_compare(const void *a, const void *b);
166 static time_t TimeOut(const enum fc_change change);
167 static void Aliaslistaction(const void *node, const VISIT which, const int depth) ;
168 static void LoadTK( const BYTE * sn, void * p, int extension, struct tree_node * tn ) ;
169 
170 /* used for the sort/search b-tree routines */
171 /* big voodoo pointer fuss to just do a standard memory compare of the "key" */
tree_compare(const void * a,const void * b)172 static int tree_compare(const void *a, const void *b)
173 {
174 	return memcmp(&((const struct tree_node *) a)->tk, &((const struct tree_node *) b)->tk, sizeof(struct tree_key));
175 }
176 
177 /* used for the sort/search b-tree routines */
178 /* big voodoo pointer fuss to just do a standard memory compare of the "key" */
alias_tree_compare(const void * a,const void * b)179 static int alias_tree_compare(const void *a, const void *b)
180 {
181 	int da = ((const struct alias_tree_node *) a)->size ;
182 	int d = da - ((const struct alias_tree_node *) b)->size ;
183 
184 	if ( d != 0 ) {
185 		return d ;
186 	}
187 	return memcmp( CONST_ALIAS_TREE_DATA((const struct alias_tree_node *) a), CONST_ALIAS_TREE_DATA((const struct alias_tree_node *) b), da);
188 }
189 
190 /* Gives the delay for a given property type */
191 /* Values in seconds (as defined in Globals structure and modified by command line and "settings") */
TimeOut(const enum fc_change change)192 static time_t TimeOut(const enum fc_change change)
193 {
194 	switch (change) {
195 	case fc_second:
196 	case fc_persistent:		/* arbitrary non-zero */
197 		return 1;
198 	case fc_volatile:
199 	case fc_simultaneous_temperature:
200 	case fc_simultaneous_voltage:
201 		return Globals.timeout_volatile;
202 	case fc_stable:
203 	case fc_read_stable:
204 		return Globals.timeout_stable;
205 	case fc_presence:
206 		return Globals.timeout_presence;
207 	case fc_directory:
208 		return Globals.timeout_directory;
209 	case fc_link:
210 	case fc_page:
211 	case fc_subdir:
212 	default:					/* static or statistic */
213 		return 0;
214 	}
215 }
216 
217 #ifdef CACHE_DEBUG
218 /* debug routine -- shows a table */
219 /* Run it as twalk(dababase, tree_show ) */
node_show(struct tree_node * tn)220 static void node_show(struct tree_node *tn)
221 {
222 	int i;
223 	char b[26];
224 	ctime_r(&tn->expires, b);
225 	fprintf(stderr,"\tNode " SNformat
226 		   " pointer=%p extension=%d length=%d start=%p expires=%s", SNvar(tn->tk.sn), tn->tk.p, tn->tk.extension, tn->dsize, tn, b ? b : "null\n");
227 	for (i = 0; i < sizeof(struct tree_key); ++i) {
228 		fprintf(stderr,"%.2X ", ((uint8_t *) tn)[i]);
229 	}
230 	fprintf(stderr,"\n");
231 }
232 
tree_show(const void * node,const VISIT which,const int depth)233 static void tree_show(const void *node, const VISIT which, const int depth)
234 {
235 	const struct tree_node *tn = *(struct tree_node * *) node;
236 	(void) depth;
237 	if (node) {
238 		switch (which) {
239 		case leaf:
240 		case postorder:
241 			node_show(tn);
242 			// fall through
243 		default:
244 			break;
245 		}
246 	} else {
247 		fprintf(stderr,"Node empty\n");
248 	}
249 }
new_tree(void)250 static void new_tree(void)
251 {
252 	fprintf(stderr,"Walk the new tree:\n");
253 	twalk(cache.temporary_tree_new, tree_show);
254 }
255 #else							/* CACHE_DEBUG */
256 #define new_tree()
257 #define node_show(tn)
258 #endif							/* CACHE_DEBUG */
259 
IsThisPersistent(const struct parsedname * pn)260 static int IsThisPersistent( const struct parsedname * pn )
261 {
262 	return ( (pn->selected_filetype->change==fc_persistent) || get_busmode(pn->selected_connection)==bus_mock ) ;
263 }
264 
265 /* DB cache creation code */
266 /* Note: done in single-threaded mode so locking not yet needed */
Cache_Open(void)267 void Cache_Open(void)
268 {
269 	memset(&cache, 0, sizeof(struct cache_data));
270 
271 	cache.retired_lifespan = TimeOut(fc_stable);
272 	if (cache.retired_lifespan > 3600) {
273 		cache.retired_lifespan = 3600;	/* 1 hour tops */
274 	}
275 
276 	// Flip once (at start) to set up old tree.
277 	FlipTree() ;
278 }
279 
280 /* Note: done in a simgle single thread mode so locking not needed */
Cache_Close(void)281 void Cache_Close(void)
282 {
283 	Cache_Clear() ;
284 	SAFETDESTROY( cache.persistent_tree, owfree_func);
285 	SAFETDESTROY( cache.persistent_alias_tree, owfree_func);
286 }
287 
288 /* Moves new to old tree, initializes new tree, and clears former old tree location */
FlipTree(void)289 static void FlipTree( void )
290 {
291 	void * flip = cache.temporary_tree_old; // old old saved for later clearing
292 	void * flip_alias = cache.temporary_alias_tree_old; // old old saved for later clearing
293 
294 	/* Flip caches! old = new. New truncated, reset time and counters and flag */
295 	LEVEL_DEBUG("Flipping cache tree (purging timed-out data)");
296 
297 	// move "new" pointers to "old"
298 	cache.temporary_tree_old = cache.temporary_tree_new;
299 	cache.old_ram_size = cache.new_ram_size;
300 	cache.temporary_alias_tree_old = cache.temporary_alias_tree_new;
301 
302 	// New cache setup
303 	cache.temporary_tree_new = NULL;
304 	cache.temporary_alias_tree_new = NULL;
305 	cache.new_ram_size = 0;
306 	cache.added = 0;
307 
308 	// set up "old" cache times
309 	cache.time_retired = NOW_TIME;
310 	cache.time_to_kill = cache.time_retired + cache.retired_lifespan;
311 
312 	// delete really old tree
313 	LEVEL_DEBUG("flip cache. tdestroy() will be called.");
314 	SAFETDESTROY( flip, owfree_func);
315 	SAFETDESTROY( flip_alias, owfree_func);
316 	STATLOCK;
317 	++cache_flips;			/* statistics */
318 	memcpy(&old_avg, &new_avg, sizeof(struct average));
319 	AVERAGE_CLEAR(&new_avg);
320 	STATUNLOCK;
321 }
322 
323 /* Clear the cache (a change was made that might give stale information) */
Cache_Clear(void)324 void Cache_Clear(void)
325 {
326 	CACHE_WLOCK;
327 	FlipTree() ;
328 	FlipTree() ;
329 	CACHE_WUNLOCK;
330 }
331 
332 /* Wrapper to perform a cache function and add statistics */
Add_Stat(struct cache_stats * scache,GOOD_OR_BAD result)333 static GOOD_OR_BAD Add_Stat(struct cache_stats *scache, GOOD_OR_BAD result)
334 {
335 	if ( GOOD(result) ) {
336 		STAT_ADD1(scache->adds);
337 	}
338 	return result;
339 }
340 
341 /* Higher level add of a one-wire-query object */
OWQ_Cache_Add(const struct one_wire_query * owq)342 GOOD_OR_BAD OWQ_Cache_Add(const struct one_wire_query *owq)
343 {
344 	const struct parsedname *pn = PN(owq);
345 	if (pn->extension == EXTENSION_ALL) {
346 		switch (pn->selected_filetype->format) {
347 		case ft_ascii:
348 		case ft_vascii:
349 		case ft_alias:
350 		case ft_binary:
351 			return gbBAD;			// cache of string arrays not supported
352 		case ft_integer:
353 		case ft_unsigned:
354 		case ft_yesno:
355 		case ft_date:
356 		case ft_float:
357 		case ft_pressure:
358 		case ft_temperature:
359 		case ft_tempgap:
360 			LEVEL_DEBUG("Adding data for %s", SAFESTRING(pn->path) );
361 			return Cache_Add(OWQ_array(owq), (pn->selected_filetype->ag->elements) * sizeof(union value_object), pn);
362 		default:
363 			return gbBAD;
364 		}
365 	} else {
366 		switch (pn->selected_filetype->format) {
367 		case ft_ascii:
368 		case ft_vascii:
369 		case ft_alias:
370 		case ft_binary:
371 			if (OWQ_offset(owq) > 0) {
372 				return gbBAD;
373 			}
374 			LEVEL_DEBUG("Adding data for %s", SAFESTRING(pn->path) );
375 			return Cache_Add(OWQ_buffer(owq), OWQ_length(owq), pn);
376 		case ft_integer:
377 		case ft_unsigned:
378 		case ft_yesno:
379 		case ft_date:
380 		case ft_float:
381 		case ft_pressure:
382 		case ft_temperature:
383 		case ft_tempgap:
384 			LEVEL_DEBUG("Adding data for %s", SAFESTRING(pn->path) );
385 			return Cache_Add(&OWQ_val(owq), sizeof(union value_object), pn);
386 		default:
387 			return gbBAD;
388 		}
389 	}
390 }
391 
392 /* Add an item to the cache */
393 /* return 0 if good, 1 if not */
Cache_Add(const void * data,const size_t datasize,const struct parsedname * pn)394 static GOOD_OR_BAD Cache_Add(const void *data, const size_t datasize, const struct parsedname *pn)
395 {
396 	struct tree_node *tn;
397 	time_t duration;
398 	int persistent ;
399 
400 	if (!pn || IsAlarmDir(pn)) {
401 		return gbGOOD;				// do check here to avoid needless processing
402 	}
403 
404 	// Special handling of Mock
405 	persistent = IsThisPersistent(pn) ;
406 	if ( persistent ) {
407 		duration = 1 ;
408 	} else {
409 		duration = TimeOut(pn->selected_filetype->change);
410 		if (duration <= 0) {
411 			return gbGOOD;				/* in case timeout set to 0 */
412 		}
413 	}
414 
415 	// allocate space for the node and data
416 	tn = (struct tree_node *) owmalloc(sizeof(struct tree_node) + datasize);
417 	if (!tn) {
418 		return gbBAD;
419 	}
420 
421 	LEVEL_DEBUG(SNformat " size=%d", SNvar(pn->sn), (int) datasize);
422 
423 	// populate the node structure with data
424 	LoadTK( pn->sn, pn->selected_filetype, pn->extension, tn );
425 	tn->expires = duration + NOW_TIME;
426 	tn->dsize = datasize;
427 	if (datasize) {
428 		memcpy(TREE_DATA(tn), data, datasize);
429 	}
430 	return persistent ?
431 		Add_Stat(&cache_pst, Cache_Add_Persistent(tn)) :
432 		Add_Stat(&cache_ext, Cache_Add_Common(tn)) ;
433 }
434 
435 /* Add a directory entry to the cache */
436 /* return 0 if good, 1 if not */
Cache_Add_Dir(const struct dirblob * db,const struct parsedname * pn)437 GOOD_OR_BAD Cache_Add_Dir(const struct dirblob *db, const struct parsedname *pn)
438 {
439 	time_t duration = TimeOut(fc_directory);
440 	struct tree_node *tn;
441 	size_t size = DirblobElements(db) * SERIAL_NUMBER_SIZE;
442 	struct parsedname pn_directory;
443 
444 	if (pn==NO_PARSEDNAME || pn->selected_connection==NO_CONNECTION) {
445 		return gbGOOD;				// do check here to avoid needless processing
446 	}
447 
448 	switch ( get_busmode(pn->selected_connection) ) {
449 		case bus_fake:
450 		case bus_tester:
451 		case bus_mock:
452 		case bus_w1:
453 		case bus_bad:
454 		case bus_unknown:
455 			return gbGOOD ;
456 		default:
457 			break ;
458 	}
459 
460 	if (duration <= 0) {
461 		return 0;				/* in case timeout set to 0 */
462 	}
463 
464 	if ( DirblobElements(db) < 1 ) {
465 		// only cache long directories.
466 		// zero (or one?) entry is possibly an error and needs to be repeated more quickly
467 		LEVEL_DEBUG("Won\'t cache empty directory");
468 		Cache_Del_Dir( pn ) ;
469 		return gbGOOD ;
470 	}
471 
472 	// allocate space for the node and data
473 	tn = (struct tree_node *) owmalloc(sizeof(struct tree_node) + size);
474 	if (!tn) {
475 		return gbBAD;
476 	}
477 
478 	LEVEL_DEBUG("Adding directory for " SNformat " elements=%d", SNvar(pn->sn), DirblobElements(db));
479 
480 	// populate node with directory name and dirblob
481 	FS_LoadDirectoryOnly(&pn_directory, pn);
482 	LoadTK( pn_directory.sn, Directory_Marker, pn->selected_connection->index, tn );
483 	tn->expires = duration + NOW_TIME;
484 	tn->dsize = size;
485 	if (size) {
486 		memcpy(TREE_DATA(tn), db->snlist, size);
487 	}
488 	return Add_Stat(&cache_dir, Cache_Add_Common(tn));
489 }
490 
491 /* Add a Simultaneous entry to the cache */
492 /* return 0 if good, 1 if not */
Cache_Add_Simul(const struct internal_prop * ip,const struct parsedname * pn)493 GOOD_OR_BAD Cache_Add_Simul(const struct internal_prop *ip, const struct parsedname *pn)
494 {
495 	// Note: pn already points to directory
496 	time_t duration = TimeOut(ip->change);
497 	struct tree_node *tn;
498 
499 	if (pn==NO_PARSEDNAME || pn->selected_connection==NO_CONNECTION) {
500 		return gbGOOD;				// do check here to avoid needless processing
501 	}
502 
503 	// Already removed inappropriate busmodes (like fake and usb_monitor)
504 	// Now test requested time
505 	if (duration <= 0) {
506 		return gbGOOD;				/* in case timeout set to 0 */
507 	}
508 
509 	// allocate space for the node and data
510 	LEVEL_DEBUG("Adding for conversion time for "SNformat, SNvar(pn->sn));
511 	tn = (struct tree_node *) owmalloc(sizeof(struct tree_node));
512 	if (!tn) {
513 		return gbBAD;
514 	}
515 
516 	LEVEL_DEBUG(SNformat, SNvar(pn->sn));
517 
518 	// populate node with directory name and dirblob
519 	LoadTK( pn->sn, ip->name, 0, tn) ;
520 	LEVEL_DEBUG("Simultaneous add type=%s",ip->name);
521 	tn->expires = duration + NOW_TIME;
522 	tn->dsize = 0;
523 	return Add_Stat(&cache_dir, Cache_Add_Common(tn));
524 }
525 
526 /* Add a device entry to the cache */
527 /* return 0 if good, 1 if not */
Cache_Add_Device(const int bus_nr,const BYTE * sn)528 GOOD_OR_BAD Cache_Add_Device(const int bus_nr, const BYTE * sn)
529 {
530 	time_t duration = TimeOut(fc_presence);
531 	struct tree_node *tn;
532 
533 	if (duration <= 0) {
534 		return gbGOOD;				/* in case timeout set to 0 */
535 	}
536 
537 	if ( sn[0] == 0 ) { //bad serial number
538 		return gbGOOD ;
539 	}
540 
541 	tn = (struct tree_node *) owmalloc(sizeof(struct tree_node) + sizeof(int));
542 	if (!tn) {
543 		return gbBAD;
544 	}
545 
546 	LEVEL_DEBUG("Adding device location " SNformat " bus=%d", SNvar(sn), (int) bus_nr);
547 	LoadTK(sn, Device_Marker, 0, tn );
548 	tn->expires = duration + NOW_TIME;
549 	tn->dsize = sizeof(int);
550 	memcpy(TREE_DATA(tn), &bus_nr, sizeof(int));
551 	return Add_Stat(&cache_dev, Cache_Add_Common(tn));
552 }
553 
554 /* What do we cache?
555 Type       sn             extension             p         What
556 directory  0=root         0                     *in       dirblob
557            DS2409_branch  0                     *in       dirblob
558 device     device sn      EXTENSION_DEVICE=-1   NULL      bus_nr
559 internal   device sn      EXTENSION_INTERNAL=-2 ip->name  binary data
560 property   device sn      extension             *ft       binary data
561 */
562 
563 /* Add an item to the cache */
564 /* return 0 if good, 1 if not */
Cache_Add_SlaveSpecific(const void * data,const size_t datasize,const struct internal_prop * ip,const struct parsedname * pn)565 GOOD_OR_BAD Cache_Add_SlaveSpecific(const void *data, const size_t datasize, const struct internal_prop *ip, const struct parsedname *pn)
566 {
567 	struct tree_node *tn;
568 	time_t duration;
569 	//printf("Cache_Add_SlaveSpecific\n");
570 	if (!pn) {
571 		return gbGOOD;				// do check here to avoid needless processing
572 	}
573 
574 	duration = TimeOut(ip->change);
575 	if (duration <= 0) {
576 		return gbGOOD;				/* in case timeout set to 0 */
577 	}
578 
579 	tn = (struct tree_node *) owmalloc(sizeof(struct tree_node) + datasize);
580 	if (!tn) {
581 		return gbBAD;
582 	}
583 
584 	LEVEL_DEBUG("Adding internal data for "SNformat " size=%d", SNvar(pn->sn), (int) datasize);
585 	LoadTK( pn->sn, ip->name, EXTENSION_INTERNAL, tn );
586 	tn->expires = duration + NOW_TIME;
587 	tn->dsize = datasize;
588 	if (datasize) {
589 		memcpy(TREE_DATA(tn), data, datasize);
590 	}
591 	//printf("ADD INTERNAL name= %s size=%d \n",tn->tk.p.nm,tn->dsize);
592 	//printf("  ADD INTERNAL data[0]=%d size=%d \n",((BYTE *)data)[0],datasize);
593 	switch (ip->change) {
594 	case fc_persistent:
595 		return Add_Stat(&cache_pst, Cache_Add_Persistent(tn));
596 	default:
597 		return Add_Stat(&cache_int, Cache_Add_Common(tn));
598 	}
599 }
600 
601 /* Add an item to the cache */
602 /* return 0 if good, 1 if not */
Cache_Add_Alias(const ASCII * name,const BYTE * sn)603 GOOD_OR_BAD Cache_Add_Alias(const ASCII *name, const BYTE * sn)
604 {
605 	struct tree_node *tn;
606 	size_t size = strlen(name) ;
607 
608 	if ( size == 0 ) {
609 		return gbGOOD ;
610 	}
611 
612 	tn = (struct tree_node *) owmalloc(sizeof(struct tree_node) + size + 1 );
613 	if (!tn) {
614 		return gbBAD;
615 	}
616 
617 	LEVEL_DEBUG("Adding alias for " SNformat " = %s", SNvar(sn), name);
618 	LoadTK( sn, Alias_Marker, 0, tn );
619 	tn->expires = NOW_TIME;
620 	tn->dsize = size;
621 	memcpy((ASCII *)TREE_DATA(tn), name, size+1 ); // includes NULL
622 	Cache_Add_Alias_SN( name, sn ) ;
623 	return Add_Stat(&cache_pst, Cache_Add_Persistent(tn));
624 }
625 
626 /* Add an item to the cache */
627 /* retire the cache (flip) if too old, and start a new one (keep the old one for a while) */
628 /* return 0 if good, 1 if not */
Cache_Add_Common(struct tree_node * tn)629 static GOOD_OR_BAD Cache_Add_Common(struct tree_node *tn)
630 {
631 	struct tree_opaque *opaque;
632 	enum { no_add, yes_add, just_update } state = no_add;
633 
634 	node_show(tn);
635 	LEVEL_DEBUG("Add to cache sn " SNformat " pointer=%p index=%d size=%d", SNvar(tn->tk.sn), tn->tk.p, tn->tk.extension, tn->dsize);
636 	CACHE_WLOCK;
637 	if (cache.time_to_kill < NOW_TIME) {	// old database has timed out
638 		FlipTree() ;
639 	}
640 	if (Globals.cache_size && (cache.old_ram_size + cache.new_ram_size > Globals.cache_size)) {
641 		// failed size test
642 		owfree(tn);
643 	} else if ((opaque = tsearch(tn, &cache.temporary_tree_new, tree_compare))) {
644 		//printf("Cache_Add_Common to %p\n",opaque);
645 		if (tn != opaque->key) {
646 			cache.new_ram_size += sizeof(tn) - sizeof(opaque->key);
647 			owfree(opaque->key);
648 			opaque->key = tn;
649 			state = just_update;
650 		} else {
651 			state = yes_add;
652 			cache.new_ram_size += sizeof(tn);
653 		}
654 	} else {					// nothing found or added?!? free our memory segment
655 		owfree(tn);
656 	}
657 	CACHE_WUNLOCK;
658 	/* Added or updated, update statistics */
659 	switch (state) {
660 		case yes_add: // add new entry
661 			STATLOCK;
662 			AVERAGE_IN(&new_avg);
663 			++cache_adds;			/* statistics */
664 			STATUNLOCK;
665 			return gbGOOD;
666 		case just_update: // update the time mark and data
667 			STATLOCK;
668 			AVERAGE_MARK(&new_avg);
669 			++cache_adds;			/* statistics */
670 			STATUNLOCK;
671 			return gbGOOD;
672 		default: // unable to add
673 			return gbBAD;
674 	}
675 }
676 
677 /* Add an item to the cache */
678 /* retire the cache (flip) if too old, and start a new one (keep the old one for a while) */
679 /* return 0 if good, 1 if not */
Cache_Add_Persistent(struct tree_node * tn)680 static GOOD_OR_BAD Cache_Add_Persistent(struct tree_node *tn)
681 {
682 	struct tree_opaque *opaque;
683 	enum { no_add, yes_add, just_update } state = no_add;
684 	LEVEL_DEBUG("Adding data to permanent store");
685 
686 	PERSISTENT_WLOCK;
687 	opaque = tsearch(tn, &cache.persistent_tree, tree_compare) ;
688 	if ( opaque != NULL ) {
689 		//printf("CACHE ADD pointer=%p, key=%p\n",tn,opaque->key);
690 		if (tn != opaque->key) {
691 			owfree(opaque->key);
692 			opaque->key = tn;
693 			state = just_update;
694 		} else {
695 			state = yes_add;
696 		}
697 	} else {					// nothing found or added?!? free our memory segment
698 		owfree(tn);
699 	}
700 	PERSISTENT_WUNLOCK;
701 
702 	switch (state) {
703 	case yes_add:
704 		STATLOCK;
705 		AVERAGE_IN(&store_avg);
706 		STATUNLOCK;
707 		return gbGOOD;
708 	case just_update:
709 		STATLOCK;
710 		AVERAGE_MARK(&store_avg);
711 		STATUNLOCK;
712 		return gbGOOD;
713 	default:
714 		return gbBAD;
715 	}
716 }
717 
Get_Stat(struct cache_stats * scache,const enum cache_task_return result)718 static GOOD_OR_BAD Get_Stat(struct cache_stats *scache, const enum cache_task_return result)
719 {
720 	GOOD_OR_BAD gbret = gbBAD ; // default
721 
722 	STATLOCK;
723 	++scache->tries;
724 	switch ( result ) {
725 		case ctr_expired:
726 			++scache->expires;
727 			break ;
728 		case ctr_ok:
729 			++scache->hits;
730 			gbret = gbGOOD ;
731 			break ;
732 		default:
733 			break ;
734 	}
735 	STATUNLOCK;
736 	return gbret ;
737 }
738 
739 /* Does cache get, but doesn't allow play in data size */
Cache_Get_Strict(void * data,size_t dsize,const struct parsedname * pn)740 static GOOD_OR_BAD Cache_Get_Strict(void *data, size_t dsize, const struct parsedname *pn)
741 {
742 	size_t size = dsize;
743 	RETURN_BAD_IF_BAD( Cache_Get(data, &size, pn) ) ;
744 	return ( dsize == size) ? gbGOOD : gbBAD ;
745 }
746 
747 
OWQ_Cache_Get(struct one_wire_query * owq)748 GOOD_OR_BAD OWQ_Cache_Get(struct one_wire_query *owq)
749 {
750 	struct parsedname *pn = PN(owq);
751 
752 	// do check here to avoid needless processing
753 	if (IsUncachedDir(pn) || IsAlarmDir(pn)) {
754 		return gbBAD;
755 	}
756 
757 	switch (pn->selected_filetype->change) {
758 	case fc_simultaneous_temperature:
759 		return Cache_Get_Simultaneous(SlaveSpecificTag(S_T), owq) ;
760 	case fc_simultaneous_voltage:
761 		return Cache_Get_Simultaneous(SlaveSpecificTag(S_T), owq) ;
762 	default:
763 		break ;
764 	}
765 
766 	if (pn->extension == EXTENSION_ALL) {
767 		switch (pn->selected_filetype->format) {
768 		case ft_ascii:
769 		case ft_vascii:
770 		case ft_alias:
771 		case ft_binary:
772 			return gbBAD;			// string arrays not supported
773 		case ft_integer:
774 		case ft_unsigned:
775 		case ft_yesno:
776 		case ft_date:
777 		case ft_float:
778 		case ft_pressure:
779 		case ft_temperature:
780 		case ft_tempgap:
781 			return Cache_Get_Strict(OWQ_array(owq), (pn->selected_filetype->ag->elements) * sizeof(union value_object), pn);
782 		default:
783 			return gbBAD;
784 		}
785 	} else {
786 		switch (pn->selected_filetype->format) {
787 		case ft_ascii:
788 		case ft_vascii:
789 		case ft_alias:
790 		case ft_binary:
791 			if (OWQ_offset(owq) > 0) {
792 				return gbBAD;
793 			}
794 			OWQ_length(owq) = OWQ_size(owq);
795 			return Cache_Get(OWQ_buffer(owq), &OWQ_length(owq), pn);
796 		case ft_integer:
797 		case ft_unsigned:
798 		case ft_yesno:
799 		case ft_date:
800 		case ft_float:
801 		case ft_pressure:
802 		case ft_temperature:
803 		case ft_tempgap:
804 			return Cache_Get_Strict(&OWQ_val(owq), sizeof(union value_object), pn);
805 		default:
806 			return gbBAD;
807 		}
808 	}
809 }
810 
811 /* Look in caches, 0=found and valid, 1=not or uncachable in the first place */
Cache_Get(void * data,size_t * dsize,const struct parsedname * pn)812 GOOD_OR_BAD Cache_Get(void *data, size_t * dsize, const struct parsedname *pn)
813 {
814 	time_t duration;
815 	struct tree_node tn;
816 	int persistent ;
817 
818 	// do check here to avoid needless processing
819 	if (IsUncachedDir(pn) || IsAlarmDir(pn)) {
820 		return gbBAD;
821 	}
822 
823 	// Special handling of Mock
824 	persistent = IsThisPersistent(pn) ;
825 	if ( persistent ) {
826 		duration = 1 ;
827 	} else {
828 		duration = TimeOut(pn->selected_filetype->change);
829 		if (duration <= 0) {
830 			return gbBAD;				/* in case timeout set to 0 */
831 		}
832 	}
833 
834 
835 	LEVEL_DEBUG(SNformat " size=%d IsUncachedDir=%d", SNvar(pn->sn), (int) dsize[0], IsUncachedDir(pn));
836 	LoadTK( pn->sn, pn->selected_filetype, pn->extension, &tn );
837 	return persistent ?
838 		Get_Stat(&cache_pst, Cache_Get_Persistent(data, dsize, &duration, &tn)) :
839 		Get_Stat(&cache_ext, Cache_Get_Common(data, dsize, &duration, &tn));
840 }
841 
842 /* Look in caches, 0=found and valid, 1=not or uncachable in the first place */
Cache_Get_Dir(struct dirblob * db,const struct parsedname * pn)843 GOOD_OR_BAD Cache_Get_Dir(struct dirblob *db, const struct parsedname *pn)
844 {
845 	time_t duration = TimeOut(fc_directory);
846 	struct tree_node tn;
847 	struct parsedname pn_directory;
848 	DirblobInit(db);
849 	if (duration <= 0) {
850 		return gbBAD;
851 	}
852 
853 	LEVEL_DEBUG("Looking for directory "SNformat, SNvar(pn->sn));
854 	FS_LoadDirectoryOnly(&pn_directory, pn);
855 	LoadTK( pn_directory.sn, Directory_Marker, pn->selected_connection->index, &tn) ;
856 	return Get_Stat(&cache_dir, Cache_Get_Common_Dir(db, &duration, &tn));
857 }
858 
859 /* Look in caches, 0=found and valid, 1=not or uncachable in the first place */
Cache_Get_Common_Dir(struct dirblob * db,time_t * duration,const struct tree_node * tn)860 static enum cache_task_return Cache_Get_Common_Dir(struct dirblob *db, time_t * duration, const struct tree_node *tn)
861 {
862 	enum cache_task_return ctr_ret;
863 	time_t now = NOW_TIME;
864 	size_t size;
865 	struct tree_opaque *opaque;
866 	LEVEL_DEBUG("Get from cache sn " SNformat " pointer=%p extension=%d", SNvar(tn->tk.sn), tn->tk.p, tn->tk.extension);
867 	CACHE_RLOCK;
868 	opaque = tfind(tn, &cache.temporary_tree_new, tree_compare) ;
869 	if ( opaque == NULL ) {
870 		// not found in new tree
871 		if ( cache.time_retired + duration[0] > now ) {
872 			// old tree could be new enough
873 			opaque = tfind(tn, &cache.temporary_tree_old, tree_compare) ;
874 		}
875 	}
876 	if ( opaque != NULL ) {
877 		duration[0] = opaque->key->expires - now ;
878 		if (duration[0] >= 0) {
879 			LEVEL_DEBUG("Dir found in cache");
880 			size = opaque->key->dsize;
881 			if (DirblobRecreate(TREE_DATA(opaque->key), size, db) == 0) {
882 				//printf("Cache: snlist=%p, devices=%lu, size=%lu\n",*snlist,devices[0],size) ;
883 				ctr_ret = ctr_ok;
884 			} else {
885 				ctr_ret = ctr_size_mismatch;
886 			}
887 		} else {
888 			//char b[26];
889 			//printf("GOT DEAD now:%s",ctime_r(&now,b)) ;
890 			//printf("        then:%s",ctime_r(&opaque->key->expires,b)) ;
891 			LEVEL_DEBUG("Dir expired in cache");
892 			ctr_ret = ctr_expired;
893 		}
894 	} else {
895 		LEVEL_DEBUG("Dir not found in cache");
896 		ctr_ret = ctr_not_found;
897 	}
898 	CACHE_RUNLOCK;
899 	return ctr_ret;
900 }
901 
902 /* Look in caches, 0=found and valid, 1=not or uncachable in the first place */
Cache_Get_Device(void * bus_nr,const struct parsedname * pn)903 GOOD_OR_BAD Cache_Get_Device(void *bus_nr, const struct parsedname *pn)
904 {
905 	time_t duration = TimeOut(fc_presence);
906 	size_t size = sizeof(int);
907 	struct tree_node tn;
908 	if (duration <= 0) {
909 		return gbBAD;
910 	}
911 
912 	LEVEL_DEBUG("Looking for device "SNformat, SNvar(pn->sn));
913 	LoadTK( pn->sn, Device_Marker, 0, &tn ) ;
914 	return Get_Stat(&cache_dev, Cache_Get_Common(bus_nr, &size, &duration, &tn));
915 }
916 
917 /* Does cache get, but doesn't allow play in data size */
Cache_Get_SlaveSpecific(void * data,size_t dsize,const struct internal_prop * ip,const struct parsedname * pn)918 GOOD_OR_BAD Cache_Get_SlaveSpecific(void *data, size_t dsize, const struct internal_prop *ip, const struct parsedname *pn)
919 {
920 	size_t size = dsize;
921 	RETURN_BAD_IF_BAD( Cache_Get_Internal(data, &size, ip, pn) ) ;
922 
923 	return ( dsize == size) ? gbGOOD : gbBAD ;
924 }
925 
926 /* Look in caches, 0=found and valid, 1=not or uncachable in the first place */
Cache_Get_Internal(void * data,size_t * dsize,const struct internal_prop * ip,const struct parsedname * pn)927 static GOOD_OR_BAD Cache_Get_Internal(void *data, size_t * dsize, const struct internal_prop *ip, const struct parsedname *pn)
928 {
929 	struct tree_node tn;
930 	time_t duration;
931 	//printf("Cache_Get_Internal");
932 	if (!pn) {
933 		return gbBAD;				// do check here to avoid needless processing
934 	}
935 
936 	duration = TimeOut(ip->change);
937 	if (duration <= 0) {
938 		return gbBAD;				/* in case timeout set to 0 */
939 	}
940 
941 	LEVEL_DEBUG(SNformat " size=%d", SNvar(pn->sn), (int) dsize[0]);
942 	LoadTK( pn->sn, ip->name, EXTENSION_INTERNAL, &tn) ;
943 	switch (ip->change) {
944 		case fc_persistent:
945 			return Get_Stat(&cache_pst, Cache_Get_Persistent(data, dsize, &duration, &tn));
946 		default:
947 			return Get_Stat(&cache_int, Cache_Get_Common(data, dsize, &duration, &tn));
948 	}
949 }
950 
951 /* Test for a  simultaneous property
952 If the property isn't independently cached, return false (1)
953 If the simultaneous conversion is more recent, return false (1)
954 Else return the cached value and true (0)
955 */
Cache_Get_Simul_Time(const struct internal_prop * ip,time_t * dwell_time,const struct parsedname * pn)956 GOOD_OR_BAD Cache_Get_Simul_Time(const struct internal_prop *ip, time_t * dwell_time, const struct parsedname * pn)
957 {
958 	// valid cached primary data -- see if a simultaneous conversion should be used instead
959 	struct tree_node tn;
960 	time_t duration ;
961 	size_t dsize_simul = 0 ;
962 	struct parsedname pn_directory ;
963 
964 	duration = TimeOut(ip->change); // time allocated this conversion
965 	if ( duration <= 0) {
966 		// uncachable
967 		return gbBAD;
968 	}
969 
970 	LEVEL_DEBUG("Looking for conversion time "SNformat, SNvar(pn->sn));
971 
972 	FS_LoadDirectoryOnly(&pn_directory, pn);
973 	LoadTK(pn_directory.sn, ip->name, 0, &tn ) ;
974 	if ( Get_Stat(&cache_int, Cache_Get_Common(NULL, &dsize_simul, &duration, &tn)) ) {
975 		return gbBAD ;
976 	}
977 	// duration_simul is time left
978 	// duration is time allocated
979 	// back-compute dwell time
980 	dwell_time[0] = TimeOut(ip->change) - duration ;
981 	return gbGOOD ;
982 }
983 
984 /* Test for a simultaneous property
985  * return true if simultaneous is the prefered method
986  * bad if no simultaneous, or it's not the best
987 */
Cache_Get_Simultaneous(const struct internal_prop * ip,struct one_wire_query * owq)988 static GOOD_OR_BAD Cache_Get_Simultaneous(const struct internal_prop *ip, struct one_wire_query *owq)
989 {
990 	struct tree_node tn;
991 	time_t duration ;
992 	time_t time_left ;
993 	time_t dwell_time_simul ;
994 	struct parsedname * pn = PN(owq) ;
995 	size_t dsize = sizeof(union value_object) ;
996 
997 	time_left = duration = TimeOut(pn->selected_filetype->change);
998 	if (duration <= 0) {
999 		// probably "uncached" requested
1000 		return gbBAD;
1001 	}
1002 
1003 	LoadTK( pn->sn, pn->selected_filetype, pn->extension, &tn ) ;
1004 
1005 	if ( Get_Stat(&cache_ext, Cache_Get_Common( &OWQ_val(owq), &dsize, &time_left, &tn)) == 0 ) {
1006 		// valid cached primary data -- see if a simultaneous conversion should be used instead
1007 		time_t dwell_time_data = duration - time_left ;
1008 
1009 		if ( BAD( Cache_Get_Simul_Time( ip, &dwell_time_simul, pn)) ) {
1010 			// Simul not found or timed out
1011 			LEVEL_DEBUG("Simultaneous conversion not found.") ;
1012 			OWQ_SIMUL_CLR(owq) ;
1013 			return gbGOOD ;
1014 		}
1015 		if ( dwell_time_simul < dwell_time_data ) {
1016 			LEVEL_DEBUG("Simultaneous conversion is newer than previous reading.") ;
1017 			OWQ_SIMUL_SET(owq) ;
1018 			return gbBAD ; // Simul is newer
1019 		}
1020 		// Cached data is newer, so use it
1021 		OWQ_SIMUL_CLR(owq) ;
1022 		return gbGOOD ;
1023 	}
1024 	// fall through -- no cached primary data
1025 	if ( BAD( Cache_Get_Simul_Time( ip, &dwell_time_simul, pn)) ) {
1026 		// no simultaneous either
1027 		OWQ_SIMUL_CLR(owq) ;
1028 		return gbBAD ;
1029 	}
1030 	OWQ_SIMUL_SET(owq) ;
1031 	return gbBAD ; // Simul is newer
1032 }
1033 
1034 /* Look in caches, 0=found and valid, 1=not or uncachable in the first place */
1035 /* space allocated, needs to be owfree-d */
1036 /* returns null-terminated string */
Cache_Get_Alias(const BYTE * sn)1037 ASCII * Cache_Get_Alias(const BYTE * sn)
1038 {
1039 	struct tree_node tn;
1040 	struct tree_opaque *opaque;
1041 	ASCII * alias_name = NULL ;
1042 
1043 
1044 	LoadTK(sn, Alias_Marker, 0, &tn ) ;
1045 
1046 	PERSISTENT_RLOCK;
1047 	opaque = tfind(&tn, &cache.persistent_tree, tree_compare) ;
1048 	if ( opaque != NULL ) {
1049 		alias_name = owmalloc( opaque->key->dsize + 1 ) ;
1050 		if ( alias_name != NULL ) {
1051 			memcpy( alias_name, (ASCII *)TREE_DATA(opaque->key), opaque->key->dsize+1 ) ;
1052 			LEVEL_DEBUG("Retrieving " SNformat " alias=%s", SNvar(sn), alias_name );
1053 		}
1054 	}
1055 	PERSISTENT_RUNLOCK;
1056 	return alias_name ;
1057 }
1058 
1059 /* Look in caches */
1060 /* duration is time left */
1061 /* inputs: dsize, duration, tn
1062  * outputs: return value, data, dsize (updated), duration (updated)
1063  * */
Cache_Get_Common(void * data,size_t * dsize,time_t * duration,const struct tree_node * tn)1064 static enum cache_task_return Cache_Get_Common(void *data, size_t * dsize, time_t * duration, const struct tree_node *tn)
1065 {
1066 	enum cache_task_return ctr_ret;
1067 	time_t now = NOW_TIME;
1068 	struct tree_opaque *opaque;
1069 
1070 	LEVEL_DEBUG("Search in cache sn " SNformat " pointer=%p index=%d size=%d", SNvar(tn->tk.sn), tn->tk.p, tn->tk.extension, (int) dsize[0]);
1071 	//node_show(tn);
1072 	//new_tree();
1073 	CACHE_RLOCK;
1074 	opaque = tfind(tn, &cache.temporary_tree_new, tree_compare) ;
1075 	if ( opaque == NULL ) {
1076 		// not found in new tree
1077 		if ( cache.time_retired + duration[0] > now ) {
1078 			// retired time isn't too old for this data item
1079 			opaque = tfind(tn, &cache.temporary_tree_old, tree_compare) ;
1080 		}
1081 	}
1082 	if ( opaque != NULL ) {
1083 		// modify duration to time left (can be negative if expired)
1084 		duration[0] = opaque->key->expires - now ;
1085 		if (duration[0] > 0) {
1086 			LEVEL_DEBUG("Value found in cache. Remaining life: %d seconds.",duration[0]);
1087 			// Compared with >= before, but fc_second(1) always cache for 2 seconds in that case.
1088 			// Very noticable when reading time-data like "/26.80A742000000/date" for example.
1089 			if ( dsize[0] >= opaque->key->dsize) {
1090 				// lower data size if stored value is shorter
1091 				dsize[0] = opaque->key->dsize;
1092 				//tree_show(opaque,leaf,0);
1093 				if (dsize[0] > 0) {
1094 					memcpy(data, TREE_DATA(opaque->key), dsize[0]);
1095 				}
1096 				ctr_ret = ctr_ok;
1097 				//twalk(cache.temporary_tree_new,tree_show) ;
1098 			} else {
1099 				ctr_ret = ctr_size_mismatch;
1100 			}
1101 		} else {
1102 			LEVEL_DEBUG("Value found in cache, but expired by %d seconds.",-duration[0]);
1103 			ctr_ret = ctr_expired;
1104 		}
1105 	} else {
1106 		LEVEL_DEBUG("Value not found in cache");
1107 		ctr_ret = ctr_not_found;
1108 	}
1109 	CACHE_RUNLOCK;
1110 	return ctr_ret;
1111 }
1112 
1113 /* Look in caches, 0=found and valid, 1=not or uncachable in the first place */
Cache_Get_Persistent(void * data,size_t * dsize,time_t * duration,const struct tree_node * tn)1114 static enum cache_task_return Cache_Get_Persistent(void *data, size_t * dsize, time_t * duration, const struct tree_node *tn)
1115 {
1116 	struct tree_opaque *opaque;
1117 	enum cache_task_return ctr_ret;
1118 	(void) duration; // ignored -- no timeout
1119 	PERSISTENT_RLOCK;
1120 	opaque = tfind(tn, &cache.persistent_tree, tree_compare) ;
1121 	if ( opaque != NULL ) {
1122 		if ( dsize[0] >= opaque->key->dsize) {
1123 			dsize[0] = opaque->key->dsize;
1124 			if (dsize[0] > 0) {
1125 				memcpy(data, TREE_DATA(opaque->key), dsize[0]);
1126 			}
1127 			ctr_ret = ctr_ok;
1128 		} else {
1129 			ctr_ret = ctr_size_mismatch;
1130 		}
1131 	} else {
1132 		ctr_ret = ctr_not_found;
1133 	}
1134 	PERSISTENT_RUNLOCK;
1135 	return ctr_ret;
1136 }
1137 
Del_Stat(struct cache_stats * scache,const int result)1138 static void Del_Stat(struct cache_stats *scache, const int result)
1139 {
1140 	if ( GOOD( result)) {
1141 		STAT_ADD1(scache->deletes);
1142 	}
1143 }
1144 
OWQ_Cache_Del(struct one_wire_query * owq)1145 void OWQ_Cache_Del(struct one_wire_query *owq)
1146 {
1147 	Cache_Del(PN(owq));
1148 }
1149 
OWQ_Cache_Del_ALL(struct one_wire_query * owq)1150 void OWQ_Cache_Del_ALL(struct one_wire_query *owq)
1151 {
1152 	struct parsedname * pn = PN(owq) ; // convenience
1153 	int extension = pn->extension ; // store extension
1154 
1155 	pn->extension = EXTENSION_ALL ; // temporary assignment
1156 	Cache_Del(pn);
1157 	pn->extension = extension ; // restore extension
1158 }
1159 
OWQ_Cache_Del_BYTE(struct one_wire_query * owq)1160 void OWQ_Cache_Del_BYTE(struct one_wire_query *owq)
1161 {
1162 	struct parsedname * pn = PN(owq) ; // convenience
1163 	int extension = pn->extension ; // store extension
1164 
1165 	pn->extension = EXTENSION_BYTE ; // temporary assignment
1166 	Cache_Del(pn);
1167 	pn->extension = extension ; // restore extension
1168 }
1169 
OWQ_Cache_Del_parts(struct one_wire_query * owq)1170 void OWQ_Cache_Del_parts(struct one_wire_query *owq)
1171 {
1172 	struct parsedname * pn = PN(owq) ; // convenience
1173 
1174 	if ( pn->selected_filetype->ag != NON_AGGREGATE ) {
1175 		int extension = pn->extension ; // store extension
1176 		int extension_index ;
1177 		for ( extension_index = pn->selected_filetype->ag->elements - 1 ; extension_index >= 0  ; -- extension_index ) {
1178 			pn->extension = extension_index ; // temporary assignment
1179 			Cache_Del(pn);
1180 		}
1181 		pn->extension = extension ; // restore extension
1182 	} else {
1183 		Cache_Del(pn);
1184 	}
1185 }
1186 
1187 // Delete a serial number's alias
1188 // Safe to call if no alias exists
1189 // Also deletes from linked alias_sn file
Cache_Del_Alias(const BYTE * sn)1190 void Cache_Del_Alias(const BYTE * sn)
1191 {
1192 	ASCII * alias_name ;
1193 	struct tree_node *tn;
1194 	size_t size ;
1195 
1196 	alias_name = Cache_Get_Alias( sn ) ;
1197 	if ( alias_name == NULL ) {
1198 		// doesn't exist
1199 		// (or a memory error -- unlikely)
1200 		return ;
1201 	}
1202 
1203 	LEVEL_DEBUG("Deleting alias %s from "SNformat, alias_name, SNvar(sn)) ;
1204 	size = strlen( alias_name ) ;
1205 	tn = (struct tree_node *) owmalloc(sizeof(struct tree_node) + size + 1 );
1206 	if ( tn != NULL ) {
1207 		tn->expires = NOW_TIME;
1208 		tn->dsize = size;
1209 		memcpy((ASCII *)TREE_DATA(tn), alias_name, size+1); // includes NULL
1210 		LoadTK( sn, Alias_Marker, 0, tn ) ;
1211 		Del_Stat(&cache_pst, Cache_Del_Persistent(tn));
1212 		Cache_Del_Alias_SN( alias_name ) ;
1213 	}
1214 	owfree( alias_name ) ;
1215 }
1216 
Cache_Del(const struct parsedname * pn)1217 static void Cache_Del(const struct parsedname *pn)
1218 {
1219 	struct tree_node tn;
1220 	time_t duration;
1221 
1222 	//printf("Cache_Del\n") ;
1223 	//printf("Cache_Del\n") ;
1224 	if (!pn) {
1225 		return;	// do check here to avoid needless processing
1226 	}
1227 
1228 	duration = TimeOut(pn->selected_filetype->change);
1229 	if (duration <= 0) {
1230 		return;				/* in case timeout set to 0 */
1231 	}
1232 
1233 	LoadTK( pn->sn, pn->selected_filetype, pn->extension, &tn ) ;
1234 	switch (pn->selected_filetype->change) {
1235 		case fc_persistent:
1236 			Del_Stat(&cache_pst, Cache_Del_Persistent(&tn));
1237 			break ;
1238 		default:
1239 			Del_Stat(&cache_ext, Cache_Del_Common(&tn));
1240 			break ;
1241 	}
1242 }
1243 
Cache_Del_Mixed_Individual(const struct parsedname * pn)1244 void Cache_Del_Mixed_Individual(const struct parsedname *pn)
1245 {
1246 	struct tree_node tn;
1247 	time_t duration;
1248 	//printf("Cache_Del\n") ;
1249 	if (!pn) {
1250 		return;				// do check here to avoid needless processing
1251 	}
1252 	if (pn->selected_filetype->ag==NON_AGGREGATE || pn->selected_filetype->ag->combined!=ag_mixed) {
1253 		return ;
1254 	}
1255 	duration = TimeOut(pn->selected_filetype->change);
1256 	if (duration <= 0) {
1257 		return;				/* in case timeout set to 0 */
1258 	}
1259 
1260 	LoadTK( pn->sn, pn->selected_filetype, 0, &tn) ;
1261 	for ( tn.tk.extension = pn->selected_filetype->ag->elements-1 ; tn.tk.extension >= 0 ; --tn.tk.extension ) {
1262 		switch (pn->selected_filetype->change) {
1263 			case fc_persistent:
1264 				Del_Stat(&cache_pst, Cache_Del_Persistent(&tn));
1265 				break ;
1266 			default:
1267 				Del_Stat(&cache_ext, Cache_Del_Common(&tn));
1268 				break ;
1269 		}
1270 	}
1271 }
1272 
Cache_Del_Mixed_Aggregate(const struct parsedname * pn)1273 void Cache_Del_Mixed_Aggregate(const struct parsedname *pn)
1274 {
1275 	struct tree_node tn;
1276 	time_t duration;
1277 	//printf("Cache_Del\n") ;
1278 	if (!pn) {
1279 		return;				// do check here to avoid needless processing
1280 	}
1281 	if (pn->selected_filetype->ag==NON_AGGREGATE || pn->selected_filetype->ag->combined!=ag_mixed) {
1282 		return ;
1283 	}
1284 	duration = TimeOut(pn->selected_filetype->change);
1285 	if (duration <= 0) {
1286 		return;				/* in case timeout set to 0 */
1287 	}
1288 
1289 	LoadTK( pn->sn, pn->selected_filetype, EXTENSION_ALL, &tn) ;
1290 	switch (pn->selected_filetype->change) {
1291 		case fc_persistent:
1292 			Del_Stat(&cache_pst, Cache_Del_Persistent(&tn));
1293 			break ;
1294 		default:
1295 			Del_Stat(&cache_ext, Cache_Del_Common(&tn));
1296 			break ;
1297 	}
1298 }
1299 
Cache_Del_Dir(const struct parsedname * pn)1300 void Cache_Del_Dir(const struct parsedname *pn)
1301 {
1302 	struct tree_node tn;
1303 	struct parsedname pn_directory;
1304 
1305 	FS_LoadDirectoryOnly(&pn_directory, pn);
1306 	LoadTK( pn_directory.sn, Directory_Marker, pn->selected_connection->index, &tn ) ;
1307 	Del_Stat(&cache_dir, Cache_Del_Common(&tn));
1308 }
1309 
Cache_Del_Simul(const struct internal_prop * ip,const struct parsedname * pn)1310 void Cache_Del_Simul(const struct internal_prop *ip, const struct parsedname *pn)
1311 {
1312 	struct tree_node tn;
1313 	struct parsedname pn_directory;
1314 
1315 	FS_LoadDirectoryOnly(&pn_directory, pn);
1316 	LoadTK(pn_directory.sn, ip->name, 0, &tn );
1317 	Del_Stat(&cache_dir, Cache_Del_Common(&tn));
1318 }
1319 
Cache_Del_Device(const struct parsedname * pn)1320 void Cache_Del_Device(const struct parsedname *pn)
1321 {
1322 	struct tree_node tn;
1323 	time_t duration = TimeOut(fc_presence);
1324 	if (duration <= 0) {
1325 		return;
1326 	}
1327 
1328 	LoadTK(pn->sn, Device_Marker, 0, &tn) ;
1329 	Del_Stat(&cache_dev, Cache_Del_Common(&tn));
1330 }
1331 
Cache_Del_Internal(const struct internal_prop * ip,const struct parsedname * pn)1332 void Cache_Del_Internal(const struct internal_prop *ip, const struct parsedname *pn)
1333 {
1334 	struct tree_node tn;
1335 	time_t duration;
1336 	//printf("Cache_Del_Internal\n") ;
1337 	if (!pn) {
1338 		return;				// do check here to avoid needless processing
1339 	}
1340 
1341 	duration = TimeOut(ip->change);
1342 	if (duration <= 0) {
1343 		return;				/* in case timeout set to 0 */
1344 	}
1345 
1346 	LoadTK(pn->sn, ip->name, 0, &tn);
1347 	switch (ip->change) {
1348 	case fc_persistent:
1349 		Del_Stat(&cache_pst, Cache_Del_Persistent(&tn));
1350 		break;
1351 	default:
1352 		Del_Stat(&cache_int, Cache_Del_Common(&tn));
1353 		break;
1354 	}
1355 }
1356 
Cache_Del_Common(const struct tree_node * tn)1357 static GOOD_OR_BAD Cache_Del_Common(const struct tree_node *tn)
1358 {
1359 	struct tree_opaque *opaque;
1360 	time_t now = NOW_TIME;
1361 	GOOD_OR_BAD ret = gbBAD;
1362 	LEVEL_DEBUG("Delete from cache sn " SNformat " in=%p index=%d", SNvar(tn->tk.sn), tn->tk.p, tn->tk.extension);
1363 
1364 	CACHE_WLOCK;
1365 	opaque = tfind(tn, &cache.temporary_tree_new, tree_compare) ;
1366 	if ( opaque == NULL ) {
1367 		// not in new tree
1368 		if ( cache.time_to_kill > now ) {
1369 			// old tree still alive
1370 			opaque = tfind(tn, &cache.temporary_tree_old, tree_compare) ;
1371 		}
1372 	}
1373 	if ( opaque != NULL ) {
1374 		opaque->key->expires = now - 1;
1375 		ret = gbGOOD;
1376 	}
1377 	CACHE_WUNLOCK;
1378 
1379 	return ret;
1380 }
1381 
Cache_Del_Persistent(const struct tree_node * tn)1382 static GOOD_OR_BAD Cache_Del_Persistent(const struct tree_node *tn)
1383 {
1384 	struct tree_opaque *opaque;
1385 	struct tree_node *tn_found = NULL;
1386 
1387 	PERSISTENT_WLOCK;
1388 	opaque = tfind(tn, &cache.persistent_tree, tree_compare) ;
1389 	if ( opaque != NULL ) {
1390 		tn_found = opaque->key;
1391 		tdelete(tn, &cache.persistent_tree, tree_compare);
1392 	}
1393 	PERSISTENT_WUNLOCK;
1394 
1395 	if ( tn_found == NULL ) {
1396 		return gbBAD;
1397 	}
1398 
1399 	owfree(tn_found);
1400 	STATLOCK;
1401 	AVERAGE_OUT(&store_avg);
1402 	STATUNLOCK;
1403 	return gbGOOD;
1404 }
1405 
LoadTK(const BYTE * sn,void * p,int extension,struct tree_node * tn)1406 static void LoadTK( const BYTE * sn, void * p, int extension, struct tree_node * tn )
1407 {
1408 	memset(&(tn->tk), 0, sizeof(struct tree_key));
1409 	memcpy(tn->tk.sn, sn, SERIAL_NUMBER_SIZE);
1410 	tn->tk.p = p;
1411 	tn->tk.extension = extension;
1412 }
1413 
1414 // Alias list from persistent cache
1415 // formatted as an alias file:
1416 // NNNNNNNNNNNN=alias_name\n
1417 //
1418 // Need to protect a global variable (aliaslist_cb) since twalk has no way of sending user data.
1419 
1420 struct memblob * aliaslist_mb ;
1421 
Aliaslistaction(const void * node,const VISIT which,const int depth)1422 static void Aliaslistaction(const void *node, const VISIT which, const int depth)
1423 {
1424 	const struct tree_node *p = *(struct tree_node * const *) node;
1425 	(void) depth;
1426 	char SN_address[SERIAL_NUMBER_SIZE*2] ;
1427 
1428 	switch (which) {
1429 	case leaf:
1430 	case postorder:
1431 		if ( p->tk.p != Alias_Marker ) {
1432 			return ;
1433 		}
1434 		// Add sn address
1435 		bytes2string(SN_address, p->tk.sn, SERIAL_NUMBER_SIZE);
1436 		MemblobAdd( (BYTE *) SN_address, SERIAL_NUMBER_SIZE*2, aliaslist_mb ) ;
1437 		// Add '='
1438 		MemblobAdd( (BYTE *) "=", 1, aliaslist_mb ) ;
1439 		// Add alias name
1440 		MemblobAdd( (const BYTE *)CONST_TREE_DATA(p), p->dsize, aliaslist_mb ) ;
1441 		// Add <CR>
1442 		MemblobAdd( (BYTE *) "\x0D\x0A", 2, aliaslist_mb ) ;
1443 		return ;
1444 	case preorder:
1445 	case endorder:
1446 		break;
1447 	}
1448 }
1449 
Aliaslist(struct memblob * mb)1450 void Aliaslist( struct memblob * mb  )
1451 {
1452 	PERSISTENT_RLOCK ;
1453 	ALIASLISTLOCK ;
1454 	aliaslist_mb = mb ;
1455 	twalk(cache.persistent_tree, Aliaslistaction);
1456 	ALIASLISTUNLOCK ;
1457 	PERSISTENT_RUNLOCK ;
1458 }
1459 
1460 /* Add an alias to the temporary database of name->bus */
1461 /* alias_name is a null-terminated string */
Cache_Add_Alias_Bus(const ASCII * alias_name,INDEX_OR_ERROR bus)1462 void Cache_Add_Alias_Bus(const ASCII * alias_name, INDEX_OR_ERROR bus)
1463 {
1464 	// allocate space for the node and data
1465 	size_t datasize = strlen(alias_name) ;
1466 	struct alias_tree_node *atn = (struct alias_tree_node *) owmalloc(sizeof(struct alias_tree_node) + datasize + 1 );
1467 	time_t duration = TimeOut(fc_presence);
1468 
1469 	if (atn==NULL) {
1470 		return ;
1471 	}
1472 
1473 	if (datasize==0) {
1474 		owfree(atn) ;
1475 		return ;
1476 	}
1477 
1478 	// populate the node structure with data
1479 	atn->expires = duration + NOW_TIME;
1480 	atn->size = datasize ;
1481 	atn->bus = bus ;
1482 	memcpy( ALIAS_TREE_DATA(atn), alias_name, datasize + 1 ) ;
1483 
1484 	Cache_Add_Alias_Common( atn ) ;
1485 }
1486 
1487 /* Add an item to the alias cache */
1488 /* retire the cache (flip) if too old, and start a new one (keep the old one for a while) */
Cache_Add_Alias_Common(struct alias_tree_node * atn)1489 static void Cache_Add_Alias_Common(struct alias_tree_node *atn)
1490 {
1491 	struct tree_opaque *opaque;
1492 
1493 	CACHE_WLOCK;
1494 	if (cache.time_to_kill < NOW_TIME) {	// old database has timed out
1495 		FlipTree() ;
1496 	}
1497 	if (Globals.cache_size && (cache.old_ram_size + cache.new_ram_size > Globals.cache_size)) {
1498 		// failed size test
1499 		owfree(atn);
1500 	} else if ((opaque = tsearch(atn, &cache.temporary_alias_tree_new, alias_tree_compare))) {
1501 		if ( (void *)atn != (void *) (opaque->key) ) {
1502 			cache.new_ram_size += sizeof(atn) - sizeof(opaque->key);
1503 			owfree(opaque->key);
1504 			opaque->key = (void *) atn;
1505 		} else {
1506 			cache.new_ram_size += sizeof(atn);
1507 		}
1508 	} else {					// nothing found or added?!? free our memory segment
1509 		owfree(atn);
1510 	}
1511 	CACHE_WUNLOCK;
1512 }
1513 
1514 /* Add an alias/sn to the persistent database of name->sn */
1515 /* Alias name must be a null-terminated string */
Cache_Add_Alias_SN(const ASCII * alias_name,const BYTE * sn)1516 static void Cache_Add_Alias_SN(const ASCII * alias_name, const BYTE * sn)
1517 {
1518 	// allocate space for the node and data
1519 	size_t datasize = strlen(alias_name) ;
1520 	struct alias_tree_node *atn = (struct alias_tree_node *) owmalloc(sizeof(struct alias_tree_node) + datasize + 1);
1521 
1522 	if (atn==NULL) {
1523 		return ;
1524 	}
1525 
1526 	if (datasize==0) {
1527 		owfree(atn) ;
1528 		return ;
1529 	}
1530 
1531 	// populate the node structure with data
1532 	atn->expires = NOW_TIME;
1533 	atn->size = datasize;
1534 	memcpy( atn->sn, sn, SERIAL_NUMBER_SIZE ) ;
1535 	memcpy( ALIAS_TREE_DATA(atn), alias_name, datasize+1 ) ;
1536 
1537 	Cache_Add_Alias_Persistent( atn ) ;
1538 }
1539 
Cache_Add_Alias_Persistent(struct alias_tree_node * atn)1540 static void Cache_Add_Alias_Persistent(struct alias_tree_node *atn)
1541 {
1542 	struct tree_opaque *opaque;
1543 
1544 	PERSISTENT_WLOCK;
1545 	opaque = tsearch(atn, &cache.persistent_alias_tree, alias_tree_compare) ;
1546 	if ( opaque != NULL ) {
1547 		if ( (void *) atn != (void *) (opaque->key) ) {
1548 			owfree(opaque->key);
1549 			opaque->key = (void *) atn;
1550 		}
1551 	} else {					// nothing found or added?!? free our memory segment
1552 		owfree(atn);
1553 	}
1554 	PERSISTENT_WUNLOCK;
1555 }
1556 
1557 /* Find bus from alias name */
1558 /* Alias name must be a null-terminated string */
Cache_Get_Alias_Bus(const ASCII * alias_name)1559 INDEX_OR_ERROR Cache_Get_Alias_Bus(const ASCII * alias_name)
1560 {
1561 	// allocate space for the node and data
1562 	size_t datasize = strlen(alias_name) ;
1563 	struct alias_tree_node *atn = (struct alias_tree_node *) owmalloc(sizeof(struct alias_tree_node) + datasize + 1);
1564 
1565 	if (atn==NULL) {
1566 		return INDEX_BAD ;
1567 	}
1568 
1569 	if (datasize==0) {
1570 		owfree(atn) ;
1571 		return INDEX_BAD ;
1572 	}
1573 
1574 	// populate the node structure with data
1575 	atn->size = datasize;
1576 	memcpy( ALIAS_TREE_DATA(atn), alias_name, datasize+1 ) ;
1577 
1578 	return Cache_Get_Alias_Common( atn ) ;
1579 }
1580 
Cache_Get_Alias_Common(struct alias_tree_node * atn)1581 static INDEX_OR_ERROR Cache_Get_Alias_Common( struct alias_tree_node * atn)
1582 {
1583 	INDEX_OR_ERROR bus = INDEX_BAD;
1584 	time_t now = NOW_TIME;
1585 	struct tree_opaque *opaque;
1586 
1587 	CACHE_RLOCK;
1588 	opaque = tfind(atn, &cache.temporary_alias_tree_new, alias_tree_compare) ;
1589 	if ( opaque == NULL ) {
1590 		// try old tree
1591 		opaque = tfind(atn, &cache.temporary_alias_tree_old, alias_tree_compare) ;
1592 	}
1593 	if ( opaque != NULL ) {
1594 		// test expiration
1595 		if ( ((struct alias_tree_node *)(opaque->key))->expires > now) {
1596 			bus = ((struct alias_tree_node *)(opaque->key))->bus ;
1597 			LEVEL_DEBUG("Found %s on bus.%d",ALIAS_TREE_DATA(atn),bus) ;
1598 		}
1599 	}
1600 	CACHE_RUNLOCK;
1601 	LEVEL_DEBUG("Finding %s unsuccessful",ALIAS_TREE_DATA(atn)) ;
1602 	owfree(atn) ;
1603 	return bus;
1604 }
1605 
1606 /* sn must point to an 8 byte buffer */
1607 /* Alias name must be a null-terminated string */
Cache_Get_Alias_SN(const ASCII * alias_name,BYTE * sn)1608 GOOD_OR_BAD Cache_Get_Alias_SN(const ASCII * alias_name, BYTE * sn )
1609 {
1610 	// allocate space for the node and data
1611 	size_t datasize = strlen(alias_name) ;
1612 	struct alias_tree_node *atn ;
1613 
1614 	if (datasize==0) {
1615 		return gbBAD ;
1616 	}
1617 
1618 	atn = (struct alias_tree_node *) owmalloc(sizeof(struct alias_tree_node) + datasize+1);
1619 	if (atn==NULL) {
1620 		return gbBAD ;
1621 	}
1622 
1623 	// populate the node structure with data
1624 	atn->size = datasize;
1625 	memcpy( ALIAS_TREE_DATA(atn), alias_name, datasize+1 ) ;
1626 
1627 	return Cache_Get_Alias_Persistent( sn, atn ) ;
1628 }
1629 
1630 /* look in persistent alias->sn tree */
Cache_Get_Alias_Persistent(BYTE * sn,struct alias_tree_node * atn)1631 static GOOD_OR_BAD Cache_Get_Alias_Persistent( BYTE * sn, struct alias_tree_node * atn)
1632 {
1633 	struct tree_opaque *opaque;
1634 	GOOD_OR_BAD ret = gbBAD ;
1635 
1636 	PERSISTENT_RLOCK;
1637 	opaque = tfind(atn, &cache.persistent_alias_tree, alias_tree_compare) ;
1638 	if ( opaque != NULL ) {
1639 		memcpy( sn, ((struct alias_tree_node *)(opaque->key))->sn, SERIAL_NUMBER_SIZE ) ;
1640 		LEVEL_DEBUG("Lookup of %s gives "SNformat, CONST_ALIAS_TREE_DATA(atn), SNvar(sn) ) ;
1641 		ret = gbGOOD ;
1642 	} else {
1643 		LEVEL_DEBUG("Lookup of %s unsuccessful",CONST_ALIAS_TREE_DATA(atn)) ;
1644 	}
1645 	PERSISTENT_RUNLOCK;
1646 	owfree(atn) ;
1647 	return ret;
1648 }
1649 
1650 /* Alias name must be a null-terminated string */
Cache_Del_Alias_SN(const ASCII * alias_name)1651 static void Cache_Del_Alias_SN(const ASCII * alias_name)
1652 {
1653 	// allocate space for the node and data
1654 	size_t datasize = strlen(alias_name) ;
1655 	struct alias_tree_node *atn = (struct alias_tree_node *) owmalloc(sizeof(struct alias_tree_node) + datasize + 1);
1656 
1657 	if (atn==NULL) {
1658 		return ;
1659 	}
1660 
1661 	// populate the node structure with data
1662 	atn->expires = NOW_TIME;
1663 	atn->size = datasize;
1664 	memcpy( ALIAS_TREE_DATA(atn), alias_name, datasize+1 ) ;
1665 
1666 	Cache_Del_Alias_Persistent( atn ) ;
1667 }
1668 
1669 /* look in persistent alias->sn tree */
Cache_Del_Alias_Persistent(struct alias_tree_node * atn)1670 static void Cache_Del_Alias_Persistent( struct alias_tree_node * atn)
1671 {
1672 	struct tree_opaque *opaque;
1673 	struct alias_tree_node *atn_found = NULL;
1674 
1675 	PERSISTENT_RLOCK;
1676 	opaque = tfind(atn, &cache.persistent_alias_tree, alias_tree_compare) ;
1677 	if ( opaque != NULL ) {
1678 		atn_found = (struct alias_tree_node *) (opaque->key);
1679 	}
1680 	PERSISTENT_RUNLOCK;
1681 	owfree(atn_found) ;
1682 }
1683 
1684 /* Delete bus from alias name */
1685 /* Alias name must be a null-terminated string */
Cache_Del_Alias_Bus(const ASCII * alias_name)1686 void Cache_Del_Alias_Bus(const ASCII * alias_name)
1687 {
1688 	// Cheat -- just change to a bad bus value
1689 	LEVEL_DEBUG("Hide %s",alias_name) ;
1690 	Cache_Add_Alias_Bus( alias_name, INDEX_BAD ) ;
1691 }
1692