1 /*
2  * Copyright 2020 University Corporation for Atmospheric Research
3  *
4  * This file is part of the UDUNITS-2 package.  See the file COPYRIGHT
5  * in the top-level source-directory of the package for copying and
6  * redistribution conditions.
7  */
8 /*
9  * Identifier-to-unit map.
10  */
11 
12 /*LINTLIBRARY*/
13 
14 #include "config.h"
15 
16 #include "udunits2.h"
17 #include "unitAndId.h"
18 #include "systemMap.h"
19 
20 #include <assert.h>
21 #ifdef _MSC_VER
22 #include "tsearch.h"
23 #else
24 #include <search.h>
25 #endif
26 
27 #include <stdlib.h>
28 
29 #include <string.h>
30 #ifndef _MSC_VER
31 #include <strings.h>
32 #endif
33 
34 typedef struct {
35     int			(*compare)(const void*, const void*);
36     void*		tree;
37 } IdToUnitMap;
38 
39 static SystemMap*	systemToNameToUnit;
40 static SystemMap*	systemToSymbolToUnit;
41 
42 
43 static int
sensitiveCompare(const void * const node1,const void * const node2)44 sensitiveCompare(
45     const void* const	node1,
46     const void* const	node2)
47 {
48     return strcmp(((const UnitAndId*)node1)->id,
49 	((const UnitAndId*)node2)->id);
50 }
51 
52 
53 static int
insensitiveCompare(const void * const node1,const void * const node2)54 insensitiveCompare(
55     const void* const	node1,
56     const void* const	node2)
57 {
58     return strcasecmp(((const UnitAndId*)node1)->id,
59 	((const UnitAndId*)node2)->id);
60 }
61 
62 
63 static IdToUnitMap*
itumNew(int (* compare)(const void *,const void *))64 itumNew(
65     int		(*compare)(const void*, const void*))
66 {
67     IdToUnitMap*	map = (IdToUnitMap*)malloc(sizeof(IdToUnitMap));
68 
69     if (map != NULL) {
70 	map->tree = NULL;
71 	map->compare = compare;
72     }
73 
74     return map;
75 }
76 
77 
78 /*
79  * Frees an identifier-to-unit map.  All entries are freed.
80  *
81  * Arguments:
82  *	map		Pointer to the identifier-to-unit map.
83  * Returns:
84  */
85 static void
itumFree(IdToUnitMap * map)86 itumFree(
87     IdToUnitMap*	map)
88 {
89     if (map != NULL) {
90 	while (map->tree != NULL) {
91 	    UnitAndId*	uai = *(UnitAndId**)map->tree;
92 
93 	    (void)tdelete(uai, &map->tree, map->compare);
94 	    uaiFree(uai);
95 	}
96 
97 	free(map);
98     }					/* valid arguments */
99 }
100 
101 
102 /*
103  * Adds an entry to an identifier-to-unit map.
104  *
105  * Arguments:
106  *	map		The database.
107  *	id		The identifier.  May be freed upon return.
108  *	unit		The unit.  May be freed upon return.
109  * Returns:
110  *	UT_OS		Operating-system error.  See "errno".
111  *	UT_EXISTS	"id" already maps to a different unit.
112  *	UT_SUCCESS	Success.
113  */
114 static ut_status
itumAdd(IdToUnitMap * map,const char * const id,const ut_unit * const unit)115 itumAdd(
116     IdToUnitMap*		map,
117     const char* const		id,
118     const ut_unit* const	unit)
119 {
120     ut_status		status;
121     UnitAndId*		targetEntry;
122 
123     assert(map != NULL);
124     assert(id != NULL);
125     assert(unit != NULL);
126 
127     targetEntry = uaiNew(unit, id);
128 
129     if (targetEntry == NULL) {
130         status = ut_get_status();
131     }
132     else {
133 	UnitAndId**	treeEntry = tsearch(targetEntry, &map->tree,
134 	    map->compare);
135 
136 	if (treeEntry == NULL) {
137 	    uaiFree(targetEntry);
138 	    status = UT_OS;
139 	}
140 	else {
141 	    if (ut_compare((*treeEntry)->unit, unit) == 0) {
142 		status = UT_SUCCESS;
143 	    }
144 	    else {
145 		status = UT_EXISTS;
146                 ut_set_status(status);
147 		ut_handle_error_message(
148 		    "\"%s\" already maps to existing but different unit", id);
149 	    }
150 
151             if (targetEntry != *treeEntry)
152                 uaiFree(targetEntry);
153 	}				/* found entry */
154     }					/* "targetEntry" allocated */
155 
156     return status;
157 }
158 
159 
160 /*
161  * Removes an entry to an identifier-to-unit map.
162  *
163  * Arguments:
164  *	map		The database.
165  *	id		The identifier.  May be freed upon return.
166  * Returns:
167  *	UT_SUCCESS	Success.
168  */
169 static ut_status
itumRemove(IdToUnitMap * map,const char * const id)170 itumRemove(
171     IdToUnitMap*	map,
172     const char* const	id)
173 {
174     UnitAndId		targetEntry;
175     UnitAndId**		treeEntry;
176 
177     assert(map != NULL);
178     assert(id != NULL);
179 
180     targetEntry.id = (char*)id;
181     treeEntry = tfind(&targetEntry, &map->tree, map->compare);
182 
183     if (treeEntry != NULL) {
184 	UnitAndId*	uai = *treeEntry;
185 
186 	(void)tdelete(uai, &map->tree, map->compare);
187 	uaiFree(uai);
188     }
189 
190     return UT_SUCCESS;
191 }
192 
193 
194 /*
195  * Finds the entry in an identifier-to-unit map that corresponds to an
196  * identifer.
197  *
198  * Arguments:
199  *	map	The identifier-to-unit map.
200  *	id	The identifier to be used as the key in the search.
201  * Returns:
202  *	NULL	Failure.  "map" doesn't contain an entry that corresponds
203  *		to "id".
204  *	else	Pointer to the entry corresponding to "id".
205  */
206 static const UnitAndId*
itumFind(IdToUnitMap * map,const char * const id)207 itumFind(
208     IdToUnitMap*	map,
209     const char* const	id)
210 {
211     UnitAndId*		entry = NULL;	/* failure */
212     UnitAndId		targetEntry;
213     UnitAndId**		treeEntry;
214 
215     assert(map != NULL);
216     assert(id != NULL);
217 
218     targetEntry.id = (char*)id;
219     treeEntry = tfind(&targetEntry, &map->tree, map->compare);
220 
221     if (treeEntry != NULL)
222 	entry = *treeEntry;
223 
224     return entry;
225 }
226 
227 
228 /*
229  * Adds to a particular unit-system a mapping from an identifier to a unit.
230  *
231  * Arguments:
232  *	systemMap	Address of the pointer to the system-map.
233  *	id		Pointer to the identifier.  May be freed upon return.
234  *	unit		Pointer to the unit.  May be freed upon return.
235  *	compare		Pointer to comparison function for unit-identifiers.
236  * Returns:
237  *	UT_BAD_ARG	"id" is NULL or "unit" is NULL.
238  *	UT_OS		Operating-sytem failure.  See "errno".
239  *	UT_SUCCESS	Success.
240  */
241 static ut_status
mapIdToUnit(SystemMap ** const systemMap,const char * const id,const ut_unit * const unit,int (* compare)(const void *,const void *))242 mapIdToUnit(
243     SystemMap** const		systemMap,
244     const char* const		id,
245     const ut_unit* const	unit,
246     int				(*compare)(const void*, const void*))
247 {
248     ut_status		status = UT_SUCCESS;
249 
250     if (id == NULL) {
251 	status = UT_BAD_ARG;
252     }
253     else if (unit == NULL) {
254 	status = UT_BAD_ARG;
255     }
256     else {
257 	ut_system*	system = ut_get_system(unit);
258 
259 	if (*systemMap == NULL) {
260 	    *systemMap = smNew();
261 
262 	    if (*systemMap == NULL)
263 		status = UT_OS;
264 	}
265 
266 	if (*systemMap != NULL) {
267 	    IdToUnitMap** const	idToUnit =
268 		(IdToUnitMap**)smSearch(*systemMap, system);
269 
270 	    if (idToUnit == NULL) {
271 		status = UT_OS;
272 	    }
273 	    else {
274 		if (*idToUnit == NULL) {
275 		    *idToUnit = itumNew(compare);
276 
277 		    if (*idToUnit == NULL)
278 			status = UT_OS;
279 		}
280 
281 		if (*idToUnit != NULL)
282 		    status = itumAdd(*idToUnit, id, unit);
283 	    }				/* have system-map entry */
284 	}				/* have system-map */
285     }					/* valid arguments */
286 
287     return status;
288 }
289 
290 
291 /*
292  * Removes the mapping from an identifier to a unit.
293  *
294  * Arguments:
295  *	systemMap	Address of the pointer to the system-map.
296  *	id		Pointer to the identifier.  May be freed upon return.
297  *	system		Pointer to the unit-system associated with the mapping.
298  * Returns:
299  *	UT_BAD_ARG	"id" is NULL, "system" is NULL, or "compare" is NULL.
300  *	UT_SUCCESS	Success.
301  */
302 static ut_status
unmapId(SystemMap * const systemMap,const char * const id,ut_system * system)303 unmapId(
304     SystemMap* const	systemMap,
305     const char* const	id,
306     ut_system*		system)
307 {
308     ut_status		status;
309 
310     if (systemMap == NULL || id == NULL || system == NULL) {
311 	status = UT_BAD_ARG;
312     }
313     else {
314 	IdToUnitMap** const	idToUnit =
315 	    (IdToUnitMap**)smFind(systemMap, system);
316 
317 	status =
318 	    (idToUnit == NULL || *idToUnit == NULL)
319 		? UT_SUCCESS
320 		: itumRemove(*idToUnit, id);
321     }					/* valid arguments */
322 
323     return status;
324 }
325 
326 
327 /*
328  * Adds a mapping from a name to a unit.
329  *
330  * Arguments:
331  *	name		Pointer to the name to be mapped to "unit".  May be
332  *			freed upon return.
333  *      encoding        The character encoding of "name".
334  *	unit		Pointer to the unit to be mapped-to by "name".  May be
335  *			freed upon return.
336  * Returns:
337  *	UT_BAD_ARG	"name" or "unit" is NULL.
338  *	UT_OS		Operating-system error.  See "errno".
339  *	UT_EXISTS	"name" already maps to a different unit.
340  *	UT_SUCCESS	Success.
341  */
342 ut_status
ut_map_name_to_unit(const char * const name,const ut_encoding encoding,const ut_unit * const unit)343 ut_map_name_to_unit(
344     const char* const		name,
345     const ut_encoding		encoding,
346     const ut_unit* const	unit)
347 {
348     ut_set_status(
349 	mapIdToUnit(&systemToNameToUnit, name, unit, insensitiveCompare));
350 
351     return ut_get_status();
352 }
353 
354 
355 /*
356  * Removes a mapping from a name to a unit.  After this function,
357  * ut_get_unit_by_name(system,name) will no longer return a unit.
358  *
359  * Arguments:
360  *	system		The unit-system to which the unit belongs.
361  *	name		The name of the unit.
362  *      encoding        The character encoding of "name".
363  * Returns:
364  *	UT_SUCCESS	Success.
365  *	UT_BAD_ARG	"system" or "name" is NULL.
366  */
367 ut_status
ut_unmap_name_to_unit(ut_system * system,const char * const name,const ut_encoding encoding)368 ut_unmap_name_to_unit(
369     ut_system*		system,
370     const char* const	name,
371     const ut_encoding   encoding)
372 {
373     ut_set_status(unmapId(systemToNameToUnit, name, system));
374 
375     return ut_get_status();
376 }
377 
378 
379 /*
380  * Adds a mapping from a symbol to a unit.
381  *
382  * Arguments:
383  *	symbol		Pointer to the symbol to be mapped to "unit".  May be
384  *			freed upon return.
385  *      encoding        The character encoding of "symbol".
386  *	unit		Pointer to the unit to be mapped-to by "symbol".  May
387  *			be freed upon return.
388  * Returns:
389  *	UT_BAD_ARG	"symbol" or "unit" is NULL.
390  *	UT_OS		Operating-system error.  See "errno".
391  *	UT_EXISTS	"symbol" already maps to a different unit.
392  *	UT_SUCCESS	Success.
393  */
394 ut_status
ut_map_symbol_to_unit(const char * const symbol,const ut_encoding encoding,const ut_unit * const unit)395 ut_map_symbol_to_unit(
396     const char* const		symbol,
397     const ut_encoding		encoding,
398     const ut_unit* const	unit)
399 {
400     ut_set_status(
401 	mapIdToUnit(&systemToSymbolToUnit, symbol, unit, sensitiveCompare));
402 
403     return ut_get_status();
404 }
405 
406 
407 /*
408  * Removes a mapping from a symbol to a unit.  After this function,
409  * ut_get_unit_by_symbol(system,symbol) will no longer return a unit.
410  *
411  * Arguments:
412  *	system		The unit-system to which the unit belongs.
413  *	symbol		The symbol of the unit.
414  *      encoding        The character encoding of "symbol".
415  * Returns:
416  *	UT_SUCCESS	Success.
417  *	UT_BAD_ARG	"system" or "symbol" is NULL.
418  */
419 ut_status
ut_unmap_symbol_to_unit(ut_system * system,const char * const symbol,const ut_encoding encoding)420 ut_unmap_symbol_to_unit(
421     ut_system*		system,
422     const char* const	symbol,
423     const ut_encoding   encoding)
424 {
425     ut_set_status(unmapId(systemToSymbolToUnit, symbol, system));
426 
427     return ut_get_status();
428 }
429 
430 
431 /*
432  * Returns the unit to which an identifier maps in a particular unit-system.
433  *
434  * Arguments:
435  *	systemMap	NULL or pointer to the system-map.  If NULL, then
436  *			NULL will be returned.
437  *	system		Pointer to the unit-system.
438  *	id		Pointer to the identifier.
439  * Returns:
440  *	NULL	Failure.  "ut_get_status()" will be:
441  *		    UT_BAD_ARG	        "system" is NULL or "id" is NULL.
442  *	else	Pointer to the unit in "system" with the identifier "id".
443  *		Should be passed to ut_free() when no longer needed.
444  */
445 static ut_unit*
getUnitById(const SystemMap * const systemMap,const ut_system * const system,const char * const id)446 getUnitById(
447     const SystemMap* const	systemMap,
448     const ut_system* const	system,
449     const char* const		id)
450 {
451     ut_unit*	unit = NULL;		/* failure */
452 
453     if (system == NULL) {
454 	ut_set_status(UT_BAD_ARG);
455 	ut_handle_error_message("getUnitById(): NULL unit-system argument");
456     }
457     else if (id == NULL) {
458 	ut_set_status(UT_BAD_ARG);
459 	ut_handle_error_message("getUnitById(): NULL identifier argument");
460     }
461     else if (systemMap != NULL) {
462 	IdToUnitMap** const	idToUnit =
463 	    (IdToUnitMap**)smFind(systemMap, system);
464 
465 	if (idToUnit != NULL) {
466 	    const UnitAndId*	uai = itumFind(*idToUnit, id);
467 
468 	    if (uai != NULL)
469 		unit = ut_clone(uai->unit);
470 	}
471     }					/* valid arguments */
472 
473     return unit;
474 }
475 
476 
477 /*
478  * Returns the unit with a given name from a unit-system.  Name comparisons
479  * are case-insensitive.
480  *
481  * Arguments:
482  *	system	Pointer to the unit-system.
483  *	name	Pointer to the name of the unit to be returned.
484  * Returns:
485  *	NULL	Failure.  "ut_get_status()" will be
486  *		    UT_SUCCESS		"name" doesn't map to a unit of
487  *					"system".
488  *		    UT_BAD_ARG		"system" or "name" is NULL.
489  *	else	Pointer to the unit of the unit-system with the given name.
490  *		The pointer should be passed to ut_free() when the unit is
491  *		no longer needed.
492  */
493 ut_unit*
ut_get_unit_by_name(const ut_system * const system,const char * const name)494 ut_get_unit_by_name(
495     const ut_system* const	system,
496     const char* const		name)
497 {
498     ut_set_status(UT_SUCCESS);
499 
500     return getUnitById(systemToNameToUnit, system, name);
501 }
502 
503 
504 /*
505  * Returns the unit with a given symbol from a unit-system.  Symbol
506  * comparisons are case-sensitive.
507  *
508  * Arguments:
509  *	system		Pointer to the unit-system.
510  *	symbol		Pointer to the symbol associated with the unit to be
511  *			returned.
512  * Returns:
513  *	NULL	Failure.  "ut_get_status()" will be
514  *		    UT_SUCCESS		"symbol" doesn't map to a unit of
515  *					"system".
516  *		    UT_BAD_ARG		"system" or "symbol" is NULL.
517  *	else	Pointer to the unit in the unit-system with the given symbol.
518  *		The pointer should be passed to ut_free() when the unit is no
519  *		longer needed.
520  */
521 ut_unit*
ut_get_unit_by_symbol(const ut_system * const system,const char * const symbol)522 ut_get_unit_by_symbol(
523     const ut_system* const	system,
524     const char* const		symbol)
525 {
526     ut_set_status(UT_SUCCESS);
527 
528     return getUnitById(systemToSymbolToUnit, system, symbol);
529 }
530 
531 
532 /*
533  * Frees resources associated with a unit-system.
534  *
535  * Arguments:
536  *	system		Pointer to the unit-system to have its associated
537  *			resources freed.
538  */
539 void
itumFreeSystem(ut_system * system)540 itumFreeSystem(
541     ut_system*	system)
542 {
543     if (system != NULL) {
544 	SystemMap*	systemMaps[2];
545 	int		i;
546 
547 	systemMaps[0] = systemToNameToUnit;
548 	systemMaps[1] = systemToSymbolToUnit;
549 
550 	for (i = 0; i < 2; i++) {
551 	    if (systemMaps[i] != NULL) {
552 		IdToUnitMap** const	idToUnit =
553 		    (IdToUnitMap**)smFind(systemMaps[i], system);
554 
555 		if (idToUnit != NULL)
556 		    itumFree(*idToUnit);
557 
558 		smRemove(systemMaps[i], system);
559 	    }
560 	}
561     }					/* valid arguments */
562 }
563