Skip Navigation Links | |
Exit Print View | |
Remote Administration Daemon Developer Guide Oracle Solaris 11.1 Information Library |
3. Abstract Data Representation
Accessing Simple adr_data_t Values
Manipulating Derived Type adr_data_t
Manipulating Array adr_data_t Values
Consumers of the ADR data management routines should include the rad/adr.h header file:
#include <rad/adr.h>
This file contains definitions for the two fundamental data management types, adr_type_t and adr_data_t, as well as prototypes for data allocation, access, and validation routines.
Each data type is represented by a adr_type_t type, whether it is just a base type or a complex of nested structures and arrays. The adr_type_t contains all the information necessary to understand the structure of the type. libadr provides statically-allocated singleton of adr_type_t type for the base types. These singleton types are more than a convenience: they must be used when referencing the base types.
The base types and their corresponding array types are listed in the following table.
Table 4-1 Base and Array Types
|
The adr_type_t for a derived type should also be unique, but obviously they cannot be defined by libadr. Although technically adr_type_t could be dynamically allocated, at the moment, the only supported way of defining a adr_type_t is to generate a definition using the ADR IDL and radadrgen.
The most frequently used type defined by rad/adr.h is adr_data_t. A adr_data_t object represents a unit of typed data. It could be of a base type, such as an integer (“1”) or string (“banana”), or of a derived type like a structure or an array. Each adr_data_t maintains a pointer to its adr_type_t.
A few common traits simplify access to adr_data_t objects. The first is that, except for the structure and array derived types (not enumerations), all adr_data_t values are immutable. They are assigned a value when they are created, and may not be changed thereafter.
Another is that all adr_data_t values are reference counted. Sometimes data structures need to be used by multiple consumers simultaneously, or simply retained for subsequent use. Reference counting is a cheap way to cut down on the cost of copying large data structures and the complexity of handling allocation failures. Though the reference counting is thread-safe, there is no other locking, which is not a problem for an immutable adr_data_t. Though the value of a non-immutable adr_data_t may be modified post-creation, the convention used throughout rad and its associated libraries is that once visibility of a adr_data_t has spread past its creator, it may no longer be modified. This eliminates the need for additional synchronization.
adr_data_t *adr_data_ref(adr_data_t *data); void adr_data_free(adr_data_t *data);
The reference count on the adr_data_t data is incremented with adr_data_ref. For convenience, adr_data_ref returns data. Symmetrically, the reference count on the adr_data_t data is decremented with adr_data_free. As the name implies, this may result in data being freed; after calling adr_data_free the caller must not access data in any way. Neither adr_data_ref nor adr_data_free can fail.
A third trait is that interfaces that accept adr_data_t values take ownership of the caller's reference on the adr_data_t. If the caller needs to refer to the adr_data_t after passing a pointer to it to a libadr interface, it must first secure an additional reference with adr_data_ref. Interfaces that return adr_data_t that are referenced by other adr_data_t do not increase the reference count on the returned adr_data_t. The returned value is guaranteed to persist only as long as the caller retains a reference on the referring adr_data_t, or if the caller uses adr_data_ref to acquire its own reference on the returned adr_data_t. The net result is that in the common case where a adr_data_t does not have multiple simultaneous consumers, libadr consumers need not perform any explicit reference counting at all. They can naively allocate and free adr_data_t values as if they were any other data structure. Note also that the adr_data_t implementation can therefore optimize for the case where the reference count is 1.
Lastly, many adr_data_t management routines rely on dynamic memory allocation, which means that proper error handling is essential. To increase the clarity and maintainability of adr_data_t consumers, and reduce the likelihood of mishandling errors, libadr interfaces explicitly accept NULL adr_data_t inputs and fail in sympathy. This means that a libadr consumer can perform a large number of operations on the instances of adr_data_t, checking only the final result for failure. Additionally, if a libadr routine is going to fail for any reason, references to a non-NULL adr_data_t passed to the routine is released. In other words, no special clean-up is needed when a libadr routine fails.
The first phase in the lifecycle of a adr_data_t is allocation. For each ADR type, there is at least one allocation routine. The arguments to an allocation routine depend on the type. In the case of mandatorily immutable types, allocation implies initialization, and their allocation routines take as arguments the value the adr_data_t is to have. Structures and arrays each have a single generic allocation routine that takes a adr_type_t* specifying the type of the structure or array. A adr_data_t is assigned values using a separate set of routines.
All allocation routines return a non-NULL adr_data_t * on success, or NULL on failure.
Note - The allocation and initialization routines for immutable types may elect to return a reference to a shared adr_data_t for a commonly used value, for example, boolean true or false. This substitution should be undetectable by adr_data_t consumers who correctly manage adr_data_t reference counts and respect the immutability of these types.
adr_data_t *adr_data_new_string(const char *s, lifetype_t lifetime);
Allocates a new string adr_data_t, initializing it to the NUL-terminated string pointed to by s. If s is NULL, adr_data_new_string will fail.
The value of the lifetime determines how the string s is to be used.
|
If lifetime is LT_FREE and adr_data_new_string fails for any reason, s will automatically be freed.
adr_data_t *adr_data_new_fstring (const char *format, ...);
Allocates a new string adr_data_t, initializing it to the string generated by calling sprintf on format and any additional arguments provided.
adr_data_t *adr_data_new_nstring (const char *s, int count);
Allocates a new string adr_data_t, initializing it to the first count bytes of s.
adr_data_t *adr_data_new_boolean (boolean_t b);
Allocates a new boolean adr_data_t, initializing it to the boolean value specified by b.
adr_data_t *adr_data_new_integer (int i); adr_data_t *adr_data_new_long (long long l); adr_data_t *adr_data_new_uinteger (unsigned int ui); adr_data_t *adr_data_new_ulong (unsigned long ul); adr_data_t *adr_data_new_float (float f); adr_data_t *adr_data_new_double (double d);
Allocates a new integer, (int), long, uinteger, ulong, float, or double adr_data_t, respectively, initializing it to the value of the single argument provided.
adr_data_t *adr_data_new_time (long long sec, int nano); adr_data_t *data_new_time_ts (time_t t); adr_data_t *adr_data_new_time_now (void );
Allocates a new time adr_data_t, initializing it to the argument, if any, provided.
adr_data_t *adr_data_new_opaque (void *buffer, int length, lifetime_t lifetime);
Allocates a new opaque adr_data_t, initializing it to the length bytes found at buffer. How adr_data_new_opaque uses buffer depends on lifetime, which takes on the same meanings as it does when used with adr_data_new_string.
adr_data_t *data_new_password (const char *p);
Allocates a new secret adr_data_t, initializing it to the contents of the NULL-terminated 8-bit character array pointed to by p. The secret type is used to hold sensitive data such as passwords. Client/server implementations may take additional steps to protect the content of password data, for example, zeroing buffers after use.
adr_data_t *adr_data_new_name (adr_name_t *name);
Allocates a new name adr_data_t, initializing it to the value of name. adr_name_t types are reference counted; the reference on name held by the caller is transferred to the resulting adr_data_t by the call to adr_data_new_name. A caller that needs to continue using name should secure an additional reference to it before calling adr_data_new_name. If adr_data_new_name fails for any reason, the caller's reference to name will be released.
adr_data_t *adr_data_new_enum (adr_type_t *type, int value); adr_data_t *adr_data_new_enum_byname (adr_type_t *type, const char * name);
The two ways to allocate an enumeration adr_data_t both require that the adr_type_t of the enumeration be specified. The first form, adr_data_new_enum, takes a scalar value as an argument and initializes the enumeration adr_data_t to the enumerated value that was assigned (implicitly or explicitly) that scalar value. The second form, adr_data_new_enum_byname, takes a pointer to a string as an argument and initializes the enumeration adr_data_t to the enumerated value that has that name. If value does not correspond to an assigned scalar value or name does not correspond to an enumerated value name, the respective allocation routine fails.
The nature of an enumeration is that all possible values are known. Enumerated types generated by radadrgen have singleton adr_data_t values that will be returned by adr_data_new_enum and adr_data_new_enum_byname. For efficiency and to reduce the error handling that needs to be performed at runtime, these values have defined symbols that may be referenced directly.
The value of type must be an enumeration data-type.
adr_data_t *adr_data_new_union (adr_type_t *uniontype, adr_data_t *discriminator, adr_data_t *value);
Allocates a new union adr_data_t, initializing it with the discriminator and arm data value provided.
adr_data_t *adr_data_new_struct (adr_type_t *type);
Allocates an uninitialized structure adr_data_t of type type. Any post-allocation initialization that occurs must be consistent with type.
The value of type must be a structured type.
adr_data_t *adr_data_new_array (adr_type_t *type, int size);
Allocates an empty array adr_data_t of type type. Arrays will automatically adjust their size to fit the amount of data placed in them. size can be used to initialize the size of the array if it is known beforehand.
The value of type must be an array type.
rad/adr.h defines macros that behave like the following prototypes:
const char *adr_data_to_string(adr_data_t *data); int adr_data_to_integer(adr_data_t *data); unsigned int adr_data_to_uinteger(adr_data_t *data); long long adr_data_to_longint(adr_data_t *data); unsigned long long adr_data_to_ulongint(adr_data_t *data); boolean_t adr_data_to_boolean(adr_data_t *data); adr_name_t *adr_data_to_name(adr_data_t *data); const char *adr_data_to_secret(adr_data_t *data); float adr_data_to_float(adr_data_t *data); double adr_data_to_double(adr_data_t *data); const char * adr_data_to_opaque(adr_data_t *data); long long adr_data_to_time_secs(adr_data_t *data); int adr_data_to_time_nsecs(adr_data_t *data);
In all cases, pointer return values will point to data that is guaranteed to exist only as long as the caller retains their reference to the data parameter.
Additionally, the following functions are provided for interpreting enumeration values:
const char *adr_enum_tostring(adr_data_t *data); int adr_enum_tovalue(adr_data_t *data);
adr_enum_tostring maps data to the value's string name. adr_enum_tovalue maps data to its scalar value.
The behavior is undefined if a macro or function is called on a adr_data_t of the wrong type.
The following functions are used to access union data:
adr_data_t *adr_union_get(adr_data_t *uniondata); adr_data_t *adr_union_get_arm(adr_data_t *uniondata);
adr_union_get returns the data stored in the arm selected by the discriminator. adr_union_get_arm returns the value of the discriminator.
Structure and array derived types are assigned no value when they are allocated. As a best practice, you should assign some value to them before use; in the case of structured types with non-nullable fields, it is required. In either case, once a reference to a derived type is shared, it may no longer be modified.
rad/adr.h defines array-access macros that behave like the following prototypes:
int adr_array_size(adr_data_t *array); adr_data_t *adr_array_get(adr_data_t *array, int index);
adr_array_size returns the number of elements in array. adr_array_get returns the index element of array. The adr_data_t returned by adr_array_get is valid as long as the caller retains its reference to array; if it is needed longer, the caller should take a hold on the adr_data_t (see adr_data_t Type). If the index element of array has not been set, the behavior of adr_array_get is undefined.
The following functions modify arrays:
int adr_array_add(adr_data_t *array, adr_data_t * value);
adr_array_add adds value to the end of array. As described in adr_data_t Type, the caller's reference to value is transferred to the array. adr_array_add might need to allocate memory and can therefore fail. When adr_array_add succeeds, it returns 0. When adr_array_add fails, it will return 1 and array will be marked invalid. For more information, see Validating adr_data_t Values.
void adr_array_remove(adr_data_t *array, int index);
adr_array_remove removes the index element from array. The array's reference count on the element at index is released, possibly resulting in its deallocation. All elements following index in array are shifted to the next lower position in the array, for example, element index+1 is moved to index. The behavior of adr_array_remove is undefined if index is greater than or equal to the size of array as returned by adr_array_size.
int adr_array_vset(adr_data_t *array, int index, adr_data_t * value);
adr_array_vset sets the index element of array to value. If an element was previously at index, the reference on that element held by the array is released. adr_array_vset may need to allocate memory and can therefore fail. When adr_array_vset succeeds, it returns 0. When adr_array_vset fails, it will return 1 and array will be marked invalid. For more information, see Validating adr_data_t Values.
The primary interface for accessing a adr_data_t structure is adr_struct_get:
adr_data_t *adr_struct_get(adr_data_t *struct, const char *field);
adr_struct_get returns the value of the field named field. If the field is nullable and has no value or if the field hasn't been given a value (that is the structure was incompletely initialized), adr_struct_get returns NULL. The adr_data_t returned by adr_struct_get is valid as long as the caller retains its reference to struct. If it is needed longer the caller should take a hold on the adr_data_t. If struct does not have a field named field, the behavior of adr_struct_get is undefined.
The primary interface for writing to a adr_data_t structure is adr_struct_set:
void adr_struct_set(adr_data_t *struct, const char *field, adr_data_t *value);
adr_struct_set writes value to the field named field. It If field previously had a value, the reference on that value held by the structure is released. If struct does not have a field named field, or if the type of value does not match that of the specified field the behavior of adr_struct_set is undefined.
libadr provides a rich environment for examining and manipulating typed data. However, unlike C's native typing system, the compiler is unaware of libadr type relationships and is therefore unable to perform static type-checking at compile time. All type checking must be performed at runtime.
The most useful of the type-checking tools provided by libadr is adr_data_verify:
boolean_t adr_data_verify(adr_data_t *data, adr_type_t *type, boolean_t recursive);
adr_data_verify takes a adr_data_t to type-check and a adr_type_t to type-check against, H can be instructed to check only the adr_data_t data or data and the transitive closure of every adr_data_t it references. adr_data_verify returns B_TRUE if data matches type, and B_FALSE if not. If type is NULL, data is tested against the type it claims to be. Although this method is not a good idea for input validation, it can be useful for error handling.
In order for data to be verified as type type, the following must be true:
data must not be NULL.
data must claim to be of type type.
If type is an enumeration, data must be a value in that enumeration.
If data is an array, it must be not have been marked invalid by a failed adr_array_add or adr_array_vset operation.
If data is an array, it must have no NULL elements.
If data is an array and recursive is true, each element of the array must satisfy these criteria given the array's element type.
If data is a structure, every non-nullable field must have a value, that is, be non-NULL.
If data is a structure and recursive is true, every non-NULL field value must satisfy these criteria considering the field's type.
Obviously, adr_data_verify is useful when validating input from an untrusted source. A second, less obvious application of adr_data_verify is as a powerful error-handling tool. Suppose you are writing a function that needs to return a complex data value. A traditional way of implementing it would be to check each call for failure individually, as shown in the following example.
Example 4-1 Traditional Error Handling
adr_data_t *tmp, *name, *result; if ((name = adr_data_new_struct(name_type)) == NULL) { /* handle failure */ } if ((tmp = adr_data_new_string("Jack")) == NULL) { /* handle failure */ } adr_struct_set(name, "first", tmp); if ((tmp = adr_data_new_string("O'Neill")) == NULL) { /* handle failure */ } adr_struct_set(name, "last", tmp); if ((record = adr_data_new_struct(record_type)) == NULL) { /* handle failure */ } adr_struct_set(record, "name", name); /* ...and so on */
This approach is difficult to implement and difficult to maintain. It is more likely to have a flaw in it than the allocations it is testing are to fail. Instead, using adr_data_verify and the error handling behaviors described in adr_data_t Type, the entire non-truncated function can be reduced to the method shown in the following example.
Example 4-2 Error Handling With adr_data_verify
adr_data_t *name = adr_data_new_struct(name_type); adr_struct_set(name, "first", adr_data_new_string("Jack")); adr_struct_set(name, "last", adr_data_new_string("O'Neill")); adr_data_t *record = adr_data_new_struct(record_type); adr_struct_set(record, "name", name); adr_struct_set(record, "rank", adr_data_new_enum_byname("COLONEL")); adr_struct_set(record, "l_count", adr_data_new_integer(2)); if (!adr_data_verify(record, NULL, B_TRUE)) { /* Recursive type check */ adr_data_free(record); return (NULL); /* NULL means something failed */ } return (record); /* Non-NULL means success */
An important limitation to this technique is the possibility for structure fields to be nullable, and the NULL indicating that the field has no value is indistinguishable from the NULL that indicates that the allocation of that field's value failed. In such cases, explicitly testing each nullable value's allocation is necessary. Even with such explicit checks, however, the net savings in complexity can be substantial.