This chapter describes the use of the XDS programming interface when accessing the CDS namespace. The first section provides an introduction to using XDS in the CDS namespace. Section 3.2 describes XDS objects and how they are used to access CDS data. Section 3.3 provides a step-by-step procedure for writing an XDS program to access CDS. Section 3.4 provides examples of using the XOM interface to manipulate objects. Section 3.5 provides details of the structure of XDS/CDS objects. Finally, Section 3.6 provides translation tables between XDS and CDS for attributes and data types.
Outside of the DCE cells and their separate namespaces is the global namespace in which the cell names themselves are entered, and where all intercell references are resolved. Two directory services participate in the global namespace. The first is the X.500-compliant Global Directory Service (GDS) supplied with DCE. The second is the Domain Name Service (DNS), which DCE interacts with, but is not a part of DCE.
The global and cell directory services are accessed implicitly by RPC applications using the NSI interface. GDS and CDS can also be accessed explicitly using the X/Open Directory Service (XDS) interface. With XDS, application programmers can gain access to GDS, a powerful general-purpose distributed database service, which can be used for many other things besides intercell binding. XDS can also be used to access the cell namespace directly, as this chapter describes.
An XDS application looks very different from the RPC-based DCE applications. This is partly because there is no dependency in XDS on the DCE RPC interface, although you can use both interfaces in the same application. Also, XDS is a generalized directory interface, oriented more toward performing large database operations than toward fine-tuning the contents of RPC entries. Nevertheless, XDS can be used as a general access mechanism on the CDS namespace.
Complete descriptions of all the XDS and XOM functions used in CDS operations can be found in the the OSF DCE Application Development Reference, which you should have beside you as you read through the examples in this chapter. In particular, refer to those chapters for information about XDS error objects, which are not discussed in this chapter.
Complete descriptions for all objects required as input parameters by XDS functions when accessing a CDS namespace can be found in Section 3.5. Abbreviated definitions for these same objects can be found with all the others in Part 4. XOM functions are general-purpose utility routines that operate on objects of any class, and take the rest of their input in conventional form.
Slightly less detailed descriptions of the output objects you can expect to receive when accessing CDS through XDS are also given in Section 3.5. You do not have to construct objects of these classes yourself; you just have to know their general structure so that you can disassemble them using XOM routines.
No information is given in this chapter about the DS_status error objects that are returned by unsuccessful XDS functions; a description of all the subclasses of DS_status requires a chapter in itself. Code for a rudimentary general-purpose DS_status-handling routine can be found in Chapter 7 of this guide.
XDS allows you to perform general operations on CDS entry attributes, something which you cannot do through the DCE RPC NSI interface. However, there are certain things you cannot do to cell directory entries even through XDS:
If you are planning to use XDS to access the cell namespace in a one-cell environment (that is, your cell does not need to communicate with other DCE cells), you do not need to set up a cell entry in GDS for your cell because the XDS functions simply call the appropriate statically linked CDS routines to access the cell namespace. In these circumstances, XDS always tries to access the local cell when given an untyped (non-X.500) name.
For XDS to be able to access any nonlocal cell namespace, that cell must be registered (that is, have an entry) in the global namespace.
For information on setting up your cell name, see the Transarc DCE Administration Guide.
The XDS interface differs from the other DCE component interfaces in that it is ``object oriented.'' The following subsections explain two things: first, what object-oriented programming means in terms of using XDS; and second, how to use XDS to access the Cell Directory Service.
Imagine a generalized data structure that always has the same data type, and yet can contain any kind of data, and any amount of it. Functions could pass these structures back and forth in the same way all the time, and yet they could use the same structures for any kind of data they wanted to store or transfer. Such a data structure, if it existed, would be a true object. Programming language constructs allow interfaces to pretend that they use objects, although the realities of implementation are not usually so simple.
XDS is such an interface. For the most part, XDS functions neither accept nor return values in any form but as objects. The objects themselves are indeed always the same data type; namely, pointers to arrays of object descriptor (C struct) elements. Contained within these OM_descriptor element structures are unions that can actually accommodate all the different kinds of values an object can be called on to hold. In order to allow the interface to make sense of the unions, each OM_descriptor also contains a syntax field, which indicates the data type of that descriptor's union. There is also a record of what the descriptor's value actually is; for example, whether it is a name, a number, an address, a list, and so on. This information is held in the descriptor's type field.
These OM_descriptor elements, which are referred to as ``object descriptors'' or ``descriptors,'' are the basic building blocks of all XDS objects; every actual XDS object reduces to arrays of them. Each descriptor contains three items:
Figure 3-1 illustrates one such object descriptor.
Note that, from an abstract point of view, syntax is just an implementation detail. The scheme really consists only of a type/value pair. The type gives an identity to the object (something like CDS entry attribute, CDS entry name, or DUA access point), and the value is some data associated with that identity, just as a variable has a name that gives meaning to the value it holds, and the value itself.
In order to make the representation of objects as logical and as flexible as possible, these two logical components of every object, type and value, are themselves each represented by separate object descriptors. Thus, the first element of every complete object descriptor array is a descriptor whose type field identifies its value field as containing the name of the kind (or class) of this object, and the syntax field indicates how that name value should be read. Next is usually one (or more, if the object is multivalued) object descriptor whose type field identifies its value field as containing some value appropriate for this class of object. Finally, every complete object descriptor array ends with a descriptor element that is identified by its fields as being a NULL-terminating element.
Thus, a minimum-size descriptor array consists of just two elements: the first contains its class identity, and the second is a NULL (it is legitimate for objects not to have values). When an object does have a value, it is held in the value field of a descriptor whose type field communicates the value's meaning.
Figure 3-2 illustrates the arrangement of a complete object descriptor array.
The generic term for any object value is attribute. In this sense, an object is nothing but a collection of attributes, and every object descriptor describes one attribute. The first attribute's value identifies the object's class, and this determines all the other attributes the object is supposed to have. One or more other attributes follow, which contain the object's working values. The NULL object descriptor at the end is an implementation detail, and is not a part of the object.
Note that, depending on the attribute it represents, a descriptor's value field can contain a pointer to another array of object descriptors. In other words, an object's value can be another object.
Figure 3-3 shows a three-layer compound object: the top-level superobject, dn_object, contains the subobject rdn1, which in turn contains the subobject ava1.
GDS is comprised of objects; these are directory objects, and reflect the X.500 design. The XDS interface also works with objects. However, there is a big difference between directory and XDS objects. Programmers do not work directly with the directory objects; they are composed of attributes that make up the directory service's implementation of entries.
Programmers work with XDS objects. XDS objects have explicit data representations that can be directly manipulated with programming language operators. Some of these techniques are described in this chapter; others can be found in Chapter 7.
XDS and GDS terminology sometimes suggests that XDS objects are somehow direct representations of the directory objects to which they communicate information. This is not the case, however. You never directly see or manipulate the directory objects; the XDS interface objects are used only to pass parameters to the XDS calls, which in turn request GDS (or CDS) to perform operations on the directory objects. The XDS objects are therefore somewhat arbitrary structures defined by the interface.
Figure 3-4 illustrates the relationship between XDS (also called interface) objects and directory objects. The figure shows an application passing several properly initialized XDS objects to some XDS function; it then takes some action, which affects the attribute contents of certain directory objects. The application never works with the directory objects; it works with the XDS interface objects.
A side effect of the existence of a separate XDS interface and GDS or CDS directory objects is the existence of attributes for both kinds of objects as well. Because the purpose of XDS objects is to feed data into and extract data from directory objects, programmers work with XDS objects whose attributes have directory object attributes as their values. You should keep in mind the distinction between directory objects and interface objects.
The GDS namespace is a hierarchical collection of entries. The name of each of these entries is an attribute of a directory object. The object is accessed through XDS by stating its name attribute.
Figure 3-5 shows the relationship of entry names in the GDS namespace to the GDS directory objects to which they refer.
There are many different classes of objects defined for the XDS interface; still more are defined by the X.500 standard for general directory use. But only a small number of classes are needed for XDS/CDS operations, and only those classes are discussed in this chapter. Information about other classes can be found in Part 4 of this guide.
The class that an object belongs to determines what sort of information the object can contain. Each object class consists of a list of attributes that objects must have. For example, you would expect an object in the directory entry name class to be required to have an attribute to hold the entry name string. However, it is not sufficient to simply place a string like:
/.../C=US/O=OSF/OU=DCE/hosts/tamburlaine/self
into an object descriptor.
A full directory entry name such as the preceding one is called in XDS a Distinguished Name (DN), meaning that the entry name is fully qualified (distinct) from root to entry name. To properly represent the entry name in an object, you must look up the definition of the XDS distinguished name object class and build an object that has the set of attributes that the definition prescribes.
Complete definitions for all the object classes required as input for XDS functions can be found in Section 3.5. Among them is the class for distinguished name objects, called DS_C_DS_DN. There you will learn that this class of object has two attributes: its class attribute, which identifies it as a DS_C_DS_DN object, and a second attribute, which occurs multiple times in the object. Each instance of this attribute contains as its value one piece of the full name; for example, the directory name hosts.
The DS_C_DS_DN attribute that holds the entry name piece, or Relative Distinguished Name, is defined by the class rules to hold, not a string, but another object of the Relative Distinguished Name class (DS_C_DS_RDN).
Thus, a static declaration of the descriptor array representing the DS_C_DS_DN object would look like the following:
static OM_descriptor Full_Entry_Name_Object[] = {
OM_OID_DESC(OM_CLASS, DS_C_DS_DN),
/* ^^^^^^^^^^^ */
/* Macro to put an "OID string" in a descrip- */
/* tor's type field and fill its other */
/* fields with appropriate values. */
{DS_RDNS, OM_S_OBJECT, {0, Country_RDN}},
/* ^^^^^^^ ^^^^^^^^^^^ ^^^^^^^^^^^ */
/* type syntax value */
/* */
/* (the "value" union is in fact here a */
/* structure; the 0 fills a pad field in */
/* that structure.) */
{DS_RDNS, OM_S_OBJECT, {0, Organization_RDN}},
{DS_RDNS, OM_S_OBJECT, {0, Org_Unit_RDN}},
{DS_RDNS, OM_S_OBJECT, {0, Hosts_Dir_RDN}},
{DS_RDNS, OM_S_OBJECT, {0, Tamburlaine_Dir_RDN}},
{DS_RDNS, OM_S_OBJECT, {0, Self_Entry_RDN}},
OM_NULL_DESCRIPTOR
/* ^^^^^^^^^^^^^^^^^^ */
/* Macro to fill a descriptor with proper */
/* NULL values. */
};
The use of the OM_NULL_DESCRIPTOR and OM_NULL_DESCRIPTOR macros slightly obscures the layout of this declaration. However, each line contains code to initialize exactly one OM_descriptor object; the array consists of eight objects.
The names (such as Country_RDN) in the descriptors' value fields refer to the other descriptor arrays, which separately represent the relative name objects. (The order of the C declaration in the source file is opposite to the order described here.) Since DS_C_DS_RDN objects are now called for, the next step is to look at what attributes that class requires.
The definition for DS_C_DS_RDN can be found in Section 3.5.2.6. This class object is defined, like DS_C_DS_DN, to have only one attribute (with the exception of the OM_Object attribute, which is mandatory for all objects). The one attribute, DS_AVAS, holds the value of one relative name. The syntax of this value is OM_S_OBJECT, meaning that DS_AVAS's value is a pointer to yet another object descriptor array:
static OM_descriptor Country_RDN[] = {
OM_OID_DESC(OM_CLASS, DS_C_DS_RDN),
{DS_AVAS, OM_S_OBJECT, {0, Country_Value}},
OM_NULL_DESCRIPTOR
};
Note that there should also be five other similar declarations, one for each of the other DS_C_DS_DN objects held in the DS_C_DS_DN.
The declarations have the same meanings as they did in the previous example. Country_Value is the name of the descriptor array that represents the object of class DS_C_AVA, which we are now about to look up.
The rules for the DS_C_AVA class can be found in this chapter just after DS_C_DS_DN. They tell us that DS_C_AVA objects have two attributes aside from the omnipresent OM_Object; namely:
This attribute holds the object's value.
This attribute gives the meaning of the object's value.
In this instance, the meaning of the string US is that it is a country name. There is a particular directory service attribute value for this; it is identified by an OID that is associated with the label DS_A_COUNTRY_NAME (the OIDs held in objects are represented in string form). Accordingly, we make that OID the value of DS_ATTRIBUTE_TYPE, and we make the name string itself the value of DS_ATTRIBUTE_VALUES:
static OM_descriptor Country_Value[] = {
OM_OID_DESC(OM_CLASS, DS_C_AVA),
OM_OID_DESC(DS_ATTRIBUTE_TYPE, DS_A_COUNTRY_NAME),
{DS_ATTRIBUTE_VALUES, OM_S_PRINTABLE_STRING, OM_STRING("US")},
/* ^^^^^^^^^^^^^^^ */
/* Macro to properly */
/* fill the "value" union with the NULL-terminated string. */
OM_NULL_DESCRIPTOR
};
There are also five other DS_C_AVA declarations, one for each of the five other separate name piece objects referred to in the DS_C_DS_RDN superobjects.
The previous sections described how an object is created: you look up the rules for the object class you require, and then add the attributes called for in the definition. Whenever some attribute is defined to have an object as its value, you have to look up the class rules for the new object and declare a further descriptor array for it. In this way you continue working down through layers of subobjects until you reach an object class that contains no subobjects as values; at that point, you are finished.
Normally, you do not statically declare objects in real applications. The steps outlined in the preceding text are given as a method for determining what an object looks like. Once you have done that, you can then write routines to create the objects dynamically. An example of how to do this can be found in the teldir.c example application in app_gs_ds_7 of this guide.
The process of object building is somewhat easier than it sounds. There are only five different object classes needed for input to XDS functions when accessing CDS, and only one of those, the DS_C_DS_DN class, has more than one level of subobjects. The rules for all five of these classes can be found in Part 4 of this guide. In order to use the GDS references, you should know a few things about class hierarchy.
Object classes are hierarchically organized so that some classes may be located above some classes in the hierarchy and below others in the hierarchy. In any such system of subordinate classes, each next lower class inherits all the attributes prescribed for the class immediately above it, plus whatever attributes are defined peculiarly for it alone. If the hierarchy continues further down, cumulative collection of attributes continues to accumulate. If there were a class for every letter of the alphabet, starting at the highest level with A and continuing down to the lowest level with Z, and if each succeeding letter was a subclass of its predecessor, the Z class would possess all the attributes of all the other letters, as well as its own, while the A class would possess only the A class attributes.
XDS/XOM classes are seldom nested more than two or at most three layers. All inherited attributes are explicitly listed in the object descriptions that follow, so you do not have to worry about class hierarchies here. However, the complete descriptions of XDS/XOM objects in Part 4 of this guide rely on statements of class inheritance to fill out their attribute lists for the different classes. Refer to Part for information about the classes of objects that can be returned by XDS calls in order to be able to handle those returned objects.
Note that class hierarchy is different from object structure. Object structure is the layering of object arrays that was previously described in the DS_C_DS_DN declaration in Section 3.2.5. It occurs when one object contains another object as the value of one or more of its attributes.
This is what is meant by recursive objects: one object can point to another object as one of its attribute values. The layering of subobjects below superobjects in this way is described repeatedly in Section 3.5.
The only practical significance of class hierarchy is in determining all the attributes a certain object class must have. Once you have done this, you may find that a certain attribute requires as its value some other object. The result is a compound object, but this is completely determined by the attributes for the particular class you are looking at.
In Section 3.2.5, you saw how a multilevel XDS object can be statically declared in C code. Now imagine that you have written an application that contains such a static DS_C_DS_DN object declaration. From the point of view of your application, that object is nothing but a series of arrays, and you can manipulate them with all the normal programming operators, just as you can any other data type. Nevertheless, the object is syntactically perfectly acceptable to any XDS (or XOM) function that is prepared to receive a DS_C_DS_DN object.
Objects are also created by the XDS functions themselves; this is the way they usually return information to callers. However, there is a difference between objects generated by the XDS interface and objects that are explicitly declared by the application: you cannot access the former, private, objects in the direct way that you can the latter, public, objects.
These two kinds of objects are the same as far as their classes and attributes are concerned. The only difference between them is in the way they are accessed. The public objects that an application explicitly creates or declares in its own memory area are just as accessible as any of the other data storage it uses. However, private objects are created and held in the XDS interface's own system memory. Applications get handles to private objects, and in order to access the private objects' contents, they have to pass the handles to Object Management functions. The Object Management (XOM) functions make up a sort of all-purpose companion interface to XDS. Whereas XDS functions typically require some specific class object as input, the XOM functions accept objects of any class and perform useful operations on them.
If a private object needs to be manipulated, one of the XOM functions, om_get(), can be called to make a public copy of the private object. Then, calling the XOM om_create() function allows applications to generate private objects manipulable by om_get(). The main significance of private as opposed to public objects is that they do not have to be explicitly operated on; instead, you can access them cleanly through the XOM interface and let it do most of the work. You still have to know something about the objects' logical representation, however, to use XOM.
Except for a few more details, which will be mentioned as needed, this is practically all there is to XDS object representation.
To call an XDS library function, do the following:
Almost all data returned to you by an XDS function is enclosed in objects, which you must parse to recover the information that you want. This task is made almost automatic by a library function supplied with the companion X/Open OSI-Abstract-Data Manipulation (XOM) interface.
With XDS, the programmer has to perform a lot of call parameter management, but in other respects the interface is easy to use. The XDS functions' dependence on objects makes them easy to call, once you have the objects themselves correctly set up.
You now know all that you need to know to work with a cell namespace through XDS. The following subsections provide a walk-through of the steps of some typical XDS/CDS operations. They describe what is involved in using XDS to access existing CDS attributes. They then describe how you can create and access new CDS entry attributes.
Suppose that you want to use XDS to read some information from the following CDS entry:
/.../C=US/O=OSF/OU=DCE/hosts/tamburlaine/self
As explained in the Transarc DCE Administration Guide, the /.:/hosts/hostname/self entry, which is created at the time of cell configuration, contains binding information for the machine hostname. Since this is a simple RPC NSI entry, there is not very much in the entry that is interesting to read, but this entry is used as an example anyway as a simple demonstration.
Following are the header inclusions and general data declarations.
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <xom.h> #include <xds.h> #include <xdsbdcp.h> #include <xdscds.h>
Note that the xom.h and xds.h header files must be included in the order shown in the preceding example. Also note that the xdscds.h header file is brought in for the sake of DSX_TYPELESS_RDN. This file is where the CDS-significant OIDs are defined. The xdsbdcp.h file contains information necessary to the Basic Directory Contents Package, which is the basic version of the XDS interface you can use in this program.
The XDS/XOM interface defines numerous object identifier string constants, which are used to identify the many object classes, parts, and pieces (among other things) that it needs to know about. In order to make sure that these OID constants do not collide with any other constants, the interface refers to them with the string OMP_O_ prefixed to the user-visible form; for example, DS_C_DS_DN becomes OMP_O_DS_C_DS_DN internally. In order to make application instances consistent with the internal form, use OM_EXPORT to import all XDS-defined or XOM-defined OID constants used in your application.
OM_EXPORT( DS_A_COUNTRY_NAME ) OM_EXPORT( DS_A_OBJECT_CLASS ) OM_EXPORT( DS_A_ORG_UNIT_NAME ) OM_EXPORT( DS_A_ORG_NAME ) OM_EXPORT( DS_C_ATTRIBUTE ) OM_EXPORT( DS_C_ATTRIBUTE_LIST ) OM_EXPORT( DS_C_AVA ) OM_EXPORT( DS_C_DS_DN ) OM_EXPORT( DS_C_DS_RDN ) OM_EXPORT( DS_C_ENTRY_INFO_SELECTION ) OM_EXPORT( DSX_TYPELESS_RDN ) /* ...Special OID for an untyped (i.e., non-X.500) "Relative */ /* Distinguished Name". Defined in xdscds.h header. */
A further important effect of OM_EXPORT is that it builds an OM_string structure to hold the exported Object Identifier hexadecimal string. As explained in the previous chapter, OIDs are not numeric values, but strings. Comparisons and similar operations on OIDs must access them as strings. Once an OID has been exported, you can access it using its declared name. For example, the hexadecimal string representation of DS_C_ATTRIBUTE is contained in DS_C_ATTRIBUTE.elements, and the length of this string is contained in DS_C_ATTRIBUTE.length.
Next are the static declarations for the lowest layer of objects that make up the global name (Distinguished Name) of the CDS directory entry you want to read. These lowest-level objects contain the string values for each part of the name. Remember that the first three parts of the name (excluding the global prefix /.../, which is not represented):
/C=US/O=OSF/OU=DCE/
constitute the cell name. In this example, assume that GDS is being used as the cell's global directory service, so the cell name is represented in X.500 format, and each part of it is typed in the object representation; for example, DS_A_COUNTRY_NAME is the DS_ATTRIBUTE_TYPE in the Country_String_Object. If you were using DNS, and the cell name were something like:
osf.org.dce
then the entire string osf.org.dce would be held in a single object whose DS_ATTRIBUTE_TYPE would be DSX_TYPELESS_RDN.
DSX_TYPELESS_RDN is a special type that marks a name piece as not residing in an X.500 namespace. If the object resides under a typed X.500 name, as is the case in the declared object structures, then it serves as a delimiter for the end of the cell name GDS looks up, and the beginning of the name that is passed to a CDS server in that cell, assuming that the cell has access to GDS; if not, such a name cannot be resolved. If the untyped portion of the name is at the beginning, as would be the case with the name:
/.../osf.org.dce/hosts/zenocrate/self
then the name is passed immediately by XDS via the local CDS (and the GDA) to DNS for resolution of the cell name. Thus, the typing of entry names determines which directory service a global directory entry name is sent to for resolution.
The following are the static declarations you need:
/*****************************************************************/
/* Here are the objects that contain the string values for each */
/* part of the CDS entry's global name... */
static OM_descriptor Country_String_Object[] = {
OM_OID_DESC(OM_CLASS, DS_C_AVA),
OM_OID_DESC(DS_ATTRIBUTE_TYPE, DS_A_COUNTRY_NAME),
{DS_ATTRIBUTE_VALUES, OM_S_PRINTABLE_STRING, OM_STRING("US")},
OM_NULL_DESCRIPTOR
};
static OM_descriptor Organization_String_Object[] = {
OM_OID_DESC(OM_CLASS, DS_C_AVA),
OM_OID_DESC(DS_ATTRIBUTE_TYPE, DS_A_ORG_NAME),
{DS_ATTRIBUTE_VALUES, OM_S_PRINTABLE_STRING, OM_STRING("OSF")},
OM_NULL_DESCRIPTOR
};
static OM_descriptor Org_Unit_String_Object[] = {
OM_OID_DESC(OM_CLASS, DS_C_AVA),
OM_OID_DESC(DS_ATTRIBUTE_TYPE, DS_A_ORG_UNIT_NAME),
{DS_ATTRIBUTE_VALUES, OM_S_PRINTABLE_STRING, OM_STRING("DCE")},
OM_NULL_DESCRIPTOR
};
static OM_descriptor Hosts_Dir_String_Object[] = {
OM_OID_DESC(OM_CLASS, DS_C_AVA),
OM_OID_DESC(DS_ATTRIBUTE_TYPE, DSX_TYPELESS_RDN),
{DS_ATTRIBUTE_VALUES, OM_S_PRINTABLE_STRING, OM_STRING("hosts")},
OM_NULL_DESCRIPTOR
};
static OM_descriptor Tamburlaine_Dir_String_Object[] = {
OM_OID_DESC(OM_CLASS, DS_C_AVA),
OM_OID_DESC(DS_ATTRIBUTE_TYPE, DSX_TYPELESS_RDN),
{DS_ATTRIBUTE_VALUES, OM_S_PRINTABLE_STRING, OM_STRING("tamburlaine")},
OM_NULL_DESCRIPTOR
};
static OM_descriptor Self_Entry_String_Object[] = {
OM_OID_DESC(OM_CLASS, DS_C_AVA),
OM_OID_DESC(DS_ATTRIBUTE_TYPE, DSX_TYPELESS_RDN),
{DS_ATTRIBUTE_VALUES, OM_S_PRINTABLE_STRING, OM_STRING("self")},
OM_NULL_DESCRIPTOR
};
The string objects are contained by a next-higher level of objects that identify the strings as being pieces (RDNs) of a fully qualified directory entry name (DN). Thus, the Country_RDN object contains Country_String_Object as the value of its DS_AVAS attribute; the Organization_RDN contains Organization_String_Object, and so on.
/*****************************************************************/
/* Here are the "Relative Distinguished Name" objects.
static OM_descriptor Country_RDN[] = {
OM_OID_DESC(OM_CLASS, DS_C_DS_RDN),
{DS_AVAS, OM_S_OBJECT, {0, Country_String_Object}},
OM_NULL_DESCRIPTOR
};
static OM_descriptor Organization_RDN[] = {
OM_OID_DESC(OM_CLASS, DS_C_DS_RDN),
{DS_AVAS, OM_S_OBJECT, {0, Organization_String_Object}},
OM_NULL_DESCRIPTOR
};
static OM_descriptor Org_Unit_RDN[] = {
OM_OID_DESC(OM_CLASS, DS_C_DS_RDN),
{DS_AVAS, OM_S_OBJECT, {0, Org_Unit_String_Object}},
OM_NULL_DESCRIPTOR
};
static OM_descriptor Hosts_Dir_RDN[] = {
OM_OID_DESC(OM_CLASS, DS_C_DS_RDN),
{DS_AVAS, OM_S_OBJECT, {0, Hosts_Dir_String_Object}},
OM_NULL_DESCRIPTOR
};
static OM_descriptor Tamburlaine_Dir_RDN[] = {
OM_OID_DESC(OM_CLASS, DS_C_DS_RDN),
{DS_AVAS, OM_S_OBJECT, {0, Tamburlaine_Dir_String_Object}},
OM_NULL_DESCRIPTOR
};
static OM_descriptor Self_Entry_RDN[] = {
OM_OID_DESC(OM_CLASS, DS_C_DS_RDN),
{DS_AVAS, OM_S_OBJECT, {0, Self_Entry_String_Object}},
OM_NULL_DESCRIPTOR
};
At the highest level, all the subobjects are gathered together in the DN object named Full_Entry_Name_Object.
/*****************************************************************/
static OM_descriptor Full_Entry_Name_Object[] = {
OM_OID_DESC(OM_CLASS, DS_C_DS_DN),
{DS_RDNS, OM_S_OBJECT, {0, Country_RDN}},
{DS_RDNS, OM_S_OBJECT, {0, Organization_RDN}},
{DS_RDNS, OM_S_OBJECT, {0, Org_Unit_RDN}},
{DS_RDNS, OM_S_OBJECT, {0, Hosts_Dir_RDN}},
{DS_RDNS, OM_S_OBJECT, {0, Tamburlaine_Dir_RDN}},
{DS_RDNS, OM_S_OBJECT, {0, Self_Entry_RDN}},
OM_NULL_DESCRIPTOR
};
The ds_read() procedure takes requests in the form of a DS_C_ENTRY_INFO_SELECTION class object. However, if you refer to the recipe for this object class in Section 3.5, you will find that it is much simpler than the name object; it contains no subobjects, and its declaration is straightforward.
The value of the DS_ALL_ATTRIBUTES attribute specifies that all attributes be read from the CDS entry, which is specified in the Full_Entry_Name_Object variable.
Note that the term ``attribute'' is used slightly differently in CDS and XDS contexts. In XDS, attributes describe the values that can be held by various object classes; they can be thought of as ``object fields.'' In CDS, attributes describe the values that can be associated with a directory entry. The following code fragment shows the definition of a DS_C_ENTRY_INFO_SELECTION object.
static OM_descriptor Entry_Info_Select_Object[] = {
OM_OID_DESC(OM_CLASS, DS_C_ENTRY_INFO_SELECTION),
{DS_ALL_ATTRIBUTES, OM_S_BOOLEAN, OM_TRUE},
{DS_INFO_TYPE, OM_S_ENUMERATION, DS_TYPES_AND_VALUES},
OM_NULL_DESCRIPTOR
};
The following are declarations for miscellaneous variables:
OM_workspace xdsWorkspace;
/* ...will contain handle to our "workspace" */
DS_feature featureList[] = {
{ OM_STRING(OMP_O_DS_BASIC_DIR_CONTENTS_PKG), OM_TRUE },
{ 0 }
};
/* ...list of service "packages" we will want from XDS */
OM_private_object session;
/* ...will contain handle to a bound-to directory session */
DS_status dsStatus;
/* ...status return from XDS calls */
OM_return_code omStatus;
/* ...status return from XOM calls */
OM_sint dummy;
/* ...for unsupported ds_read() argument */
OM_private_object readResultObject;
/* ...to receive entry information read from CDS by "ds_read()" */
OM_type I_want_entry_object[] = {DS_ENTRY, OM_NO_MORE_TYPES};
OM_type I_want_attribute_list[] = {DS_ATTRIBUTES, OM_NO_MORE_TYPES};
OM_type I_want_attribute_value[] = {DS_ATTRIBUTE_VALUES, OM_NO_MORE_TYPES};
/* ...arrays to pass to "om_get()" to extract subobjects */
/* from the result object returned by "ds_read()" */
OM_value_position number_of_descriptors;
/* ...to hold number of attribute descriptors returned */
/* by "om_get() */
OM_public_object entry;
/* ...to hold public object returned by "om_get()" */
This section describes the main program. Three calls usually precede any use of XDS.
First, ds_initialize() is called to set up a workspace. A workspace is a memory area in which XDS can generate objects that will be used to pass information to the application. If the call is successful, it returns a handle that must be saved for the ds_shutdown() call. If the call is unsuccessful, it returns NULL, but this example does not check for errors.
xdsWorkspace = ds_initialize();
If GDS is being used as the global directory service, the service packages are specified next. Packages consist of groups of objects, together with the associated supporting interface functionality, designed to be used for some specific end. For example, to access the (X.500) Global Directory, specify DSX_GDS_PKG. This example uses the basic XDS service so DS_BASIC_DIR_CONTENTS_PKG is specified. The featureList parameter to ds_version() is an array, not an object, since packages are not being handled yet:
dsStatus = ds_version(featureList, xdsWorkspace);
Note that if you are not using GDS as your global directory service (in other words, if you are using XDS by itself), then you should not call ds_version().
From this point on, status is returned by XDS functions via a DS_status variable. DS_status is a handle to a private object, whose value is DS_SUCCESS (this is NULL) if the call was successful. If something went wrong, the information in the (possibly complex) private error object has to be analyzed through calls to om_get(), which is one of the general-purpose object management functions that belongs to XDS's companion interface XOM. Usage of om_get() is demonstrated later on in this program, but return status is not checked in this example.
The third necessary call is to ds_bind(). This call brings up the directory service, which binds to a Directory System Agent (DSA), the GDS server, through a Directory User Agent (DUA), the GDS client. The DS_DEFAULT_SESSION parameter calls for a default session. The alternative is to build and fill out your own DS_C_SESSION object, specifying such things as DSA addresses, and pass that. The default is used in this example:
dsStatus = ds_bind(DS_DEFAULT_SESSION, xdsWorkspace, &session);
At this point, you can read a set of object attributes from the cell namespace entry. Call ds_read() with the two objects that specify the entry to be read and the specific entry attribute you want:
dsStatus = ds_read(session, DS_DEFAULT_CONTEXT, Full_Entry_Name_Object,
Entry_Info_Select_Object, &readResultObject, &dummy);
The DS_DEFAULT_CONTEXT parameter could be substituted with a DS_C_CONTEXT object, which would typically be reused during a series of related XDS calls. This object specifies and records how GDS should perform the operation, how much progress has been made in resolving a name, and so on.
If the call succeeds, the private object readResultObject contains a series of DS_C_ATTRIBUTE subobjects, one for each attribute read from the cell name entry. A complete recipe for the DS_C_READ_RESULT object can be found in Chapter 11, but the following is a skeletal outline of the object's structure:
DS_C_READ_RESULT
DS_ENTRY: object(DS_C_ENTRY_INFO)
DS_ALIAS_DEREFERENCED: OM_S_BOOLEAN
DS_PERFORMER: object(DS_C_NAME)
DS_C_ENTRY_INFO
DS_FROM_ENTRY: OM_S_BOOLEAN
DS_OBJECT_NAME: object(DS_C_NAME)
DS_ATTRIBUTES: one or more object(DS_C_ATTRIBUTE)
DS_C_NAME == DS_C_DS_DN
DS_RDNS: object(DS_C_DS_RDN)
DS_C_DS_RDN
DS_AVAS: object(DS_C_AVA)
DS_C_AVA
DS_ATTRIBUTE_TYPE: OID string
DS_ATTRIBUTE_VALUES: anything
DS_C_ATTRIBUTE --one for each attribute read
DS_ATTRIBUTE_TYPE: OID string
DS_ATTRIBUTE_VALUES: anything
DS_C_ATTRIBUTE
DS_ATTRIBUTE_TYPE: OID string
DS_ATTRIBUTE_VALUES: anything
Figure 3-6 illustrates the general object structure of a DS_C_READ_RESULT, showing only the object-valued attributes, and only one DS_C_ATTRIBUTE subobject.
The next goal is to extract the instances of the DS_C_ATTRIBUTE subsubclass, one for each attribute read, from the returned object. The first step is to make a public copy of readResultObject, which is a private object, and therefore does not allow access to the object descriptors themselves. Using the XOM om_get() function, you can make a public copy of readResultObject, and at the same time specify that only the relevant parts of it be preserved in the copy. Then with a couple of calls to om_get(), you can reduce the object to manageable size, leaving a superobject whose immediate subobjects are fairly easily accessed.
The om_get() function takes as its third input parameter an OM_type_list, which is an array of OM_type. Possible parameters are DS_ATTRIBUTES, DS_ENTRY, DS_ATTRIBUTE_VALUES, and anything that can legitimately appear in an object descriptor's type field. The types specified in this parameter are interpreted according to the options specified in the preceding parameter. For example, the relevent attribute from the read result is DS_ENTRY. It contains the DS_C_ENTRY_INFO object, which in turn contains the DS_C_ATTRIBUTE objects. The DS_C_ATTRIBUTE objects contain the data read from the cell directory name entry. Therefore, you should specify the OM_EXCLUDE_ALL_BUT_THESE_TYPES option, which has the effect of excluding everything but the contents of the object's DS_ENTRY type attribute.
The OM_EXCLUDE_SUBOBJECTS option is also ORed into the parameter. Why would you not preserve the subobjects of DS_C_ENTRY_INFO? Because om_get() works only on private, not on public, objects. If you were to use om_get() on the entire object substructure, you would not be able to continue getting the subobjects, and instead you would have to follow the object pointers down to the DS_C_ATTRIBUTEs. However, when om_get() excludes subobjects from a copy, it does not really leave them out; it merely leaves the subobjects private, with a handle to the private objects where pointers would have been. This allows you to continue to call om_get() as long as there are more subobjects.
The following is the first call:
/* The DS_C_READ_RESULT object that ds_read() returns has */
/* one subobject, DS_C_ENTRY_INFO; it in turn has two sub- */
/* objects, i.e. a DS_C_NAME which holds the object's di- */
/* stinguished name (which we don't care about here), and */
/* a DS_C_ATTRIBUTE which contains the attribute info we */
/* read; that one we want. So we climb down to it... */
/* This om_get() will "return" the entry-info object... */
omStatus = om_get(readResultObject,
OM_EXCLUDE_ALL_BUT_THESE_TYPES +
OM_EXCLUDE_SUBOBJECTS,
I_want_entry_object,
OM_FALSE,
OM_ALL_VALUES,
OM_ALL_VALUES,
&entry,
&number_of_descriptors);
The number_of_descriptors parameter contains the number of attribute descriptors returned in the public copy, not in any excluded subobjects.
If an XOM function is successful, it returns an OM_SUCCESS code. Unsuccessful calls to XOM functions do not return error objects, but rather return simple error codes. The interface assumes that if the XOM function does not accept your object, then you will not be able to get much information from any further objects. The return status is not checked in this example.
The return parameter entry should now contain a pointer to the DS_C_ENTRY_INFO object with the following immediate structure. (The number of instances of DS_ATTRIBUTES depends on the number of attributes read from the entry.)
DS_C_ENTRY_INFO
DS_FROM_ENTRY: OM_S_BOOLEAN
DS_OBJECT_NAME: object(DS_C_NAME)
DS_ATTRIBUTES: object(DS_C_ATTRIBUTE)
DS_C_ATTRIBUTE
DS_ATTRIBUTE_TYPE: OID string
DS_ATTRIBUTE_VALUES: anything
DS_ATTRIBUTES: object(DS_C_ATTRIBUTE) object(DS_C_ATTRIBUTE) DS_C_ATTRIBUTE DS_ATTRIBUTE_TYPE: OID string DS_ATTRIBUTE_VALUES: anything
The italics indicate private subobjects. Figure 3-7 shows the DS_C_ENTRY_INFO object. Only one instance of a DS_C_ATTRIBUTE subobject is shown in the figure; usually there are several such subobjects, all at the same level, each containing information about one of the attributes read from the entry. These subobjects are represented in DS_C_ENTRY_INFO as a series of descriptors of type DS_ATTRIBUTES, each of which has as its value a separate DS_C_ATTRIBUTE subobject.
Now extract the separate attribute values of the entry that was read. These were returned as separate object values of DS_ATTRIBUTES; each one has an object class of DS_C_ATTRIBUTE. To return any one of these subobjects, a second call to om_get() is necessary, as follows.
/* The second om_get() returns one selected sub-object */
/* from the DS_C_ENTRY_INFO subobject we just got. The */
/* contents of "entry" as we enter this call is the pri- */
/* vate subobject which is the value of DS_ATTRIBUTES. If */
/* we were to make the following call with the */
/* OM_EXCLUDE_SUBOBJECTS and without the */
/* OM_EXCLUDE_ALL_BUT_THESE_VALUES flags, we would get */
/* back an object consisting of six private subobjects, */
/* one for each of the attributes returned. Note the val- */
/* ues for initial and limiting position: "2" specifies */
/* that we want only the third DS_C_ATTRIBUTE subobject */
/* to be gotten (the subobjects are numbered from 0, not */
/* from one), and the "3" specifies that we want no more */
/* than that-- in other words, the limiting value must al- */
/* ways be one more than the initial value if the latter */
/* is to have any effect. OM_EXCLUDE_ALL_BUT_THESE_VALUES */
/* is likewise required for the initial and limiting val- */
/* ues to have any effect... */
omStatus = om_get(entry->value.object.object,
OM_EXCLUDE_ALL_BUT_THESE_TYPES
+ OM_EXCLUDE_SUBOBJECTS
+ OM_EXCLUDE_ALL_BUT_THESE_VALUES,
I_want_attribute_list,
OM_FALSE,
((OM_value_position) 2),
((OM_value_position) 3),
&entry,
&number_of_descriptors);
Note the value that is passed as the first parameter. Since om_get() does not work on public objects, pass it the handle of the private subobject explicitly. To do this you have to know the arrangement of the descriptor's value union, which is defined in xom.h.
The following is the layout of the object field in a descriptor's value union:
typedef struct {
OM_uint32 padding;
OM_object object;
} OM_padded_object;
The following is the layout of the value union itself:
typedef union OM_value_union {
OM_string string;
OM_boolean boolean;
OM_enumeration enumeration;
OM_integer integer;
OM_padded_object object;
} OM_value;
The following is the layout of the descriptor itself:
typedef struct OM_descriptor_struct {
OM_type type;
OM_syntax syntax;
union OM_value_union value;
} OM_descriptor;
Thus, if entry is a pointer to the DS_C_ENTRY_INFO object, then entry->value.object.object is the private handle to the DS_C_ATTRIBUTE object that you want next.
The last call yielded one separate DS_C_ATTRIBUTE subsubobject from the original returned result object:
DS_C_ATTRIBUTE DS_ATTRIBUTE_TYPE: OID string DS_ATTRIBUTE_VALUES: anything
Figure 3-8 illustrates what is left.
A final call to om_get() returns the single object descriptor that contains the actual value of the single attribute you selected from the returned object:
omStatus = om_get(entry->value.object.object,
OM_EXCLUDE_ALL_BUT_THESE_TYPES,
I_want_attribute_value,
OM_FALSE,
OM_ALL_VALUES,
OM_ALL_VALUES,
&entry,
&number_of_descriptors);
At this point, the value of entry is the base address of an object descriptor whose entry->type is DS_ATTRIBUTE_VALUES. Depending on the value found in entry->syntax, the value of the attribute can be read from entry->value.string, entry->value.integer, entry->value.boolean, or entry->value.enumeration. For example, suppose the value of entry->syntax is OM_S_OCTET_STRING. The attribute value, represented as an octet string (not terminated by a NULL), is found in entry->value.string.elements; its length is found in entry->value.string.length.
You can check any attribute value against the value you get from the cdscp command by entering:
cdscp show object /.:/hosts/tamburlaine/self
For further information on cdscp, see the Transarc DCE Command Reference.
Note that you can always call om_get() to get the entire returned object from an XDS call. This yields a full structure of object descriptors that you can manipulate like any other data structure. To do this with the ds_read() return object would have required the following call:
/* make a public copy of ENTIRE object... */
omStatus = om_get(readResultObject,
OM_NO_EXCLUSIONS,
((OM_type_list) 0),
OM_FALSE,
((OM_value_position) 0),
((OM_value_position) 0),
&entry,
&number_of_descriptors);
At the end of every XDS session you have to unbind from the GDS, and then deallocate the XDS and XOM structures and other storage. You must also explicitly deallocate any service-generated objects, whether public or private, with calls to om_delete(), as follows:
/* delete service-generated public or private objects... */ omStatus = om_delete(readResultObject); omStatus = om_delete(entry); /* unbind from the GDS... */ dsStatus = ds_unbind(session); /* close down the workspace... */ dsStatus = ds_shutdown(xdsWorkspace); exit();
The following subsections provide the procedure and some code examples for creating new CDS entry attributes.
To create new attributes of your own on cell namespace entries, you must do the following:
For example, the following shows the xdscds.h definition for the CDS CDS_Class attribute:
#define OMP_O_DSX_A_CDS_Class "\\x2B\\x16\\x01\\x03\\x0F"
Note the XDS internal form of the name. This is what DSX_A_CDS_Class looks like when it has been exported using OM_EXPORT in an application, as all OIDs must be. Thus, if you wanted to create a CDS attribute called CDS_Brave_New_Attrib, you would obtain an OID from your administrator and add the following line to xdscds.h:
#define OMP_O_DSX_A_CDS_Brave_New_Attrib "your_OID"
In the following code fragments a set of declarations similar to those in the previous examples is assumed.
The ds_modify_entry() function, which is called to add new attributes to an entry or to write new values into existing attributes, requires a DS_C_ENTRY_MOD_LIST input object whose contents specify the attributes and values to be written to the entry. The name, as always, is specified in a DS_C_DS_DN object. The following is a static declaration of such a list, which consists of two attributes:
static OM_descriptor Entry_Modification_Object_1[] = {
OM_OID_DESC(OM_CLASS, DS_C_ENTRY_MOD),
OM_OID_DESC(DS_ATTRIBUTE_TYPE, DSX_A_CDS_Brave_New_Attrib),
{DS_ATTRIBUTE_VALUES, OM_S_PRINTABLE_STRING,
OM_STRING("O brave new attribute")},
{DS_MOD_TYPE, OM_S_ENUMERATION, DS_ADD_ATTRIBUTE},
OM_NULL_DESCRIPTOR
};
static OM_descriptor Entry_Modification_Object_2[] = {
OM_OID_DESC(OM_CLASS, DS_C_ENTRY_MOD),
OM_OID_DESC(DS_ATTRIBUTE_TYPE, DSX_A_CDS_Class),
{DS_ATTRIBUTE_VALUES, OM_S_PRINTABLE_STRING, OM_STRING("Miscellaneous")},
{DS_MOD_TYPE, OM_S_ENUMERATION, DS_ADD_ATTRIBUTE},
OM_NULL_DESCRIPTOR
};
static OM_descriptor Entry_Modification_List_Object[] = {
OM_OID_DESC(OM_CLASS, DS_C_ENTRY_MOD_LIST),
{DS_CHANGES, OM_S_OBJECT, {0, Entry_Modification_Object_1}},
{DS_CHANGES, OM_S_OBJECT, {0, Entry_Modification_Object_2}},
OM_NULL_DESCRIPTOR
};
A full description of this object can be found in Section 3.5. There could be any number of additional attribute changes in the list; this would mean additional DS_C_ENTRY_MOD objects declared, and an additional DS_CHANGES descriptor declared and initialized in the DS_C_ENTRY_MOD_LIST object.
With the DS_C_ENTRY_MOD_LIST class object having been declared as shown previously, the following code fragment illustrates how to call XDS to write a new attribute value (actually two new values since two attributes are contained in the list object). Note that any of the attributes may be new, although the entry itself must already exist.
dsStatus = ds_modify_entry(session, /* Directory session from "ds_bind()" */
DS_DEFAULT_CONTEXT, /* Usual directory context */
Full_Entry_Name_Object, /* Entry name object */
Entry_Modification_List_Object, /* Entry Modifi- */
/* cation object */
&dummy); /* Unsupported argument */
If the entire entry is new, you must call ds_add_entry(). This function requires an input object of class DS_C_ATTRIBUTE_LIST, whose contents specify the attributes (and values) to be attached to the new entry. Following is the static declaration for an attribute list that contains three attributes:
static OM_descriptor Class_Attribute_Object[] = {
OM_OID_DESC(OM_CLASS, DS_C_ATTRIBUTE),
OM_OID_DESC(DS_ATTRIBUTE_TYPE, DSX_A_CDS_Class),
{DS_ATTRIBUTE_VALUES, OM_S_PRINTABLE_STRING, OM_STRING("Printer")},
OM_NULL_DESCRIPTOR
};
static OM_descriptor ClassVersion_Attribute_Object[] = {
OM_OID_DESC(OM_CLASS, DS_C_ATTRIBUTE),
OM_OID_DESC(DS_ATTRIBUTE_TYPE, DSX_A_CDS_ClassVersion),
{DS_ATTRIBUTE_VALUES, OM_S_PRINTABLE_STRING, OM_STRING("1.0")},
OM_NULL_DESCRIPTOR
};
static OM_descriptor My_Own_Attribute_Object[] = {
OM_OID_DESC(OM_CLASS, DS_C_ATTRIBUTE),
OM_OID_DESC(DS_ATTRIBUTE_TYPE, DSX_A_CDS_My_OwnAttribute),
{DS_ATTRIBUTE_VALUES, OM_S_PRINTABLE_STRING, OM_STRING("zorro")},
OM_NULL_DESCRIPTOR
};
static OM_descriptor Attribute_List_Object[] = {
OM_OID_DESC(OM_CLASS, DS_C_ATTRIBUTE_LIST),
{DS_ATTRIBUTES, OM_S_OBJECT, {0, Class_Attribute_Object}},
{DS_ATTRIBUTES, OM_S_OBJECT, {0, ClassVersion_Attribute_Object}},
{DS_ATTRIBUTES, OM_S_OBJECT, {0, My_Own_Attribute_Object}},
OM_NULL_DESCRIPTOR
};
The ds_add_entry() function also requires a DS_C_DS_DN class object containing the new entry's full name, for example:
/.../osf.org.dce/subsys/doc/my_bookwhere every member of the name exists except for the last one, my_book. Assuming that Full_Entry_Name_Object is a DS_C_DS_DN object, the following code shows what the call would look like:
dsStatus = ds_add_entry(session, /* Directory session from "ds_bind()" */
DS_DEFAULT_CONTEXT, /* Usual directory context */
Full_Entry_Name_Object, /* Name of new entry */
Attribute_List_Object, /* Attributes to be attached */
/* to new entry, with values */
&dummy); /* Unsupported argument */
The following subsections describe the use of XOM and discuss dynamic object creation.
The following code fragments demonstrate an alternative way to set up the entry modification object for a ds_modify_entry() call, mainly for the sake of showing how the om_put() and om_write() functions are used.
The following technique is used to initialize the modification object:
The following new declarations are necessary:
OM_private_object newAttributeMod_priv;
/* ...handle to a private object to "om_put()" to */
OM_public_object newAttributeMod_pub;
/* ...to hold public object from "om_get()" */
OM_type types_to_include[] = {DS_ATTRIBUTE_TYPE, DS_ATTRIBUTE_VALUES,
DS_MOD_TYPE, OM_NO_MORE_TYPES};
/* ...i.e., all attribute values of the Entry Modification */
/* object. For "om_put()" and "om_get()" */
char *my_string = "O brave new attribute";
/* ...value I want to write into attribute */
OM_value_position number_of_descriptors;
/* ...to hold value returned by "om_get()" */
First, use XOM to generate a private object of the desired class:
omStatus = om_create(DS_C_ENTRY_MOD, /* Class of object */
OM_TRUE, /* Initialize attributes per defaults */
xdsWorkspace, /* Our workspace handle */
&newAttributeMod_priv); /* Created object handle */
Next, copy the public object's attributes into the private object:
omStatus = om_put(newAttributeMod_priv, /* Private object to copy */
/* attributes into */
OM_REPLACE_ALL, /* Which attributes to replace in */
/* destination object */
Entry_Modification_Object, /* Source object to copy */
/* attributes from */
types_to_include, /* List of attribute types we want */
/* copied */
0, 0); /* Start-stop index for multivalued attri- */
/* butes; ignored with OM_REPLACE_ALL */
Since om_put() ignores the class of the source object (the object from which attributes are being copied), it is not necessary to declare class descriptors for the source objects. In other words, the static declarations could have omitted the OM_CLASS initializations if this technique were being used, for example:
static OM_descriptor Entry_Modification_Object_2[] = {
/* OM_OID_DESC(OM_CLASS, DS_C_ENTRY_MOD), */
/* Not needed for "om_put()"... */
OM_OID_DESC(DS_ATTRIBUTE_TYPE, DSX_A_CDS_Class),
{DS_ATTRIBUTE_VALUES, OM_S_PRINTABLE_STRING, OM_STRING("Miscellaneous")},
{DS_MOD_TYPE, OM_S_ENUMERATION, DS_ADD_ATTRIBUTE},
OM_NULL_DESCRIPTOR
};
The OM_CLASS was already properly initialized by om_create().
Next, write the attribute value string into the private object:
omStatus = om_write(newAttributeMod_priv,/* Private object to write to */
DS_ATTRIBUTE_VALUES, /* Attribute type whose value */
/* we're writing */
0, /* Descriptor index if attribute is multivalued */
OM_S_PRINTABLE_STRING, /* Syntax of value */
0, /* Offset in source string to write from */
my_string); /* Source string to write from */
Now make the whole thing public again:
omStatus = om_get(newAttributeMod_priv, /* Private object to get */
0, /* Get everything */
types_to_include, /* All attribute types */
0, /* Unsupported argument */
0, 0, /* Start-stop descriptor index for multi-val- */
/* ued attributes; ignored in this case */
&newAttributeMod_pub, /* Pointer to returned copy */
&number_of_descriptors); /* Number of attribute */
/* descriptors returned */
Finally, insert the address of the subobject into its superobject:
Entry_Modification_List_Object[1].value.object.object = newAttributeMod_pub;
Objects can be completely dynamically allocated and initialized; however, you have to implement the routines to do this yourself. The examples in this section are code fragments; for complete examples, see Chapter 7.
Initialization of object structures can be automated by declaring macros or functions to do this. For example, the following macro initializes one object descriptor with a full set of appropriate values:
/* Put a C-style (NULL-terminated) string into an object, and */
/* set all the other descriptor fields to requested values... */
#define FILL_OMD_STRING( desc, index, typ, syntx, val )
desc[index].type = typ;
desc[index].syntax = syntx;
desc[index].value.string.length = (OM_element_position)strlen(val);
desc[index].value.string.elements = val;
When generating objects, use malloc() to allocate space for the number of objects desired, and then use macros (or functions) such as the preceding one to initialize the descriptors. The following code fragment shows how this can be done for the top-level object of a DS_C_DS_DN, such as the one described near the beginning of this chapter. Recall that the DS_C_DS_DN has a separate DS_RDNS descriptor for each name piece in the full name.
/* Calculate number of "DS_RDNS" attributes there should be... */
numberOfPieces = number_of_name_pieces;
/* Allocate space for that many descriptors, plus one for the */
/* object class at the front, and a NULL descriptor at the */
/* back... */
Name_Object = (OM_object)malloc((numberOfPieces + 2) * sizeof(OM_descriptor));
if(Name_Object == NULL) /* "malloc()" failed */
return OM_MEMORY_INSUFFICIENT;
/* Initialize it as a DS_C_DS_DN object by placing that class */
/* identifier in the first position... */
FILL_OMD_XOM_STRING(Name_Object, 0, OM_CLASS,
OM_S_OBJECT_IDENTIFIER_STRING, DS_C_DS_DN)
Note that all of these steps would have to be repeated for each of the DS_C_DS_RDN objects required as attribute values of the DS_C_DS_DN. Then a tier of DS_C_AVA objects would have to be created in the same way, since each of the DS_C_DS_RDNs requires one of them as its attribute value.
You could now use om_create() and om_put() to generate a private copy of this object, if so desired.
The application is responsible for managing the memory it allocates for such dynamic object creation.
The following subsections contain shorthand for object classes. For example, if you look at the reference pages for the ds_...() functions, you will see that an object of class DS_C_NAME is required to hold entry names you want to pass to the call, not DS_C_DS_DN as is stated in this chapter. However, DS_C_NAME is in fact an abstract class with only one subclass, DS_C_DS_DN, so in this chapter, DS_C_DS_DN is used.
In general, the objects you work with in an XDS/CDS application fall into two categories:
This section describes only the first category, since you have to construct these input objects yourself.
Table 3-1 shows XDS functions and the objects given to them as input parameters.
Only items significant to CDS are listed in the table. DS_C_SESSION and DS_C_CONTEXT are ignored. DS_C_SESSION is returned by ds_bind(), which usually receives the DS_DEFAULT_SESSION constant as input. DS_C_CONTEXT is usually substituted by the DS_DEFAULT_CONTEXT constant.
Note: DS_C_NAME is an abstract class that has the single subclass DS_C_DS_DN. Therefore, DS_C_NAME is practically the same thing as DS_C_DS_DN.
| Function | Input Object |
|---|---|
| ds_add_entry() | DS_C_NAME DS_C_ATTRIBUTE_LIST |
| ds_bind() | None |
| ds_compare() | DS_C_NAME DS_C_AVA |
| ds_initialize() | None |
| ds_list() | DS_C_NAME |
| ds_modify_entry() | DS_C_NAME DS_C_ENTRY_MOD_LIST |
| ds_read() | DS_C_NAME DS_C_ENTRY_INFO_SELECTION |
| ds_remove_entry() | DS_C_NAME |
| ds_shutdown() | None |
| ds_unbind() | None |
| ds_version() | None |
The following subsections contain information about all the object types required as input to any of the XDS functions that can be used to access the CDS. In order to use these functions successfully, you must be able to construct and modify the objects that the functions expect as their input parameters. XDS functions require most of their input parameters to be wrapped in a nested series of data structures that represent objects, and these functions deliver their output returns to callers in the same object form.
Objects that are returned to you by the interface are not difficult to manipulate because the om_get() function allows you to go through them and retrieve only the value parts you are interested in, and discard the parts of data structures you are not interested in. However, any objects you are required to supply as input to an XDS or XOM function are another matter: you must build and initialize these object structures yourself.
The basics of object building have already been explained earlier in this chapter. Each object described in the following subsections is accompanied by a static declaration in C of a very simple instance of that object class. The objects in an application are usually built dynamically (this technique was demonstrated earlier in this chapter). The static declarations that follow give a simple example of what the objects look like.
An object's properties, such as what sort of values it can hold, how many of them it can hold, and so on, are determined by the class the object belongs to. Each class consists of one or more attributes that an object can have. The attributes hold whatever values the object contains. Thus, the objects are data structures that all look the same (and can be handled in the same way) from the outside, but whose specific data fields are determined by the class each object belongs to. At the abstract level, objects consist of attributes, just as structures consist fields.
Following is a list of all the object types that are described in the following subsections. Most of these objects are object structures; that is, compounds consisting of superobjects that contain subobjects as some of their values. These latter may in turn contain other objects, and so on. Subobjects are indicated by indentation. A DS_C_DS_DN object contains at least one DS_C_DS_RDN object, and each of the latter contains one DS_C_AVA object. Note that subobjects can, and often do, exist by themselves, depending on what object class is called for by a given function. This list contains all the possible kinds of objects that can be required as input for any XDS/CDS operation.
In each section, information is provided for the described object's attributes. All of its attributes are listed.
The illustrations in the following sections can be compared to the same object classes' tabular definitions leter in this guide.
A DS_C_ATTRIBUTE_LIST class object is required as input to ds_add_entry(). The object contains a list of the directory attributes you want associated with the entry that is to be added.
Its general structure is as follows:
Thus, a DS_C_ATTRIBUTE_LIST object containing one attribute consists of two object descriptor arrays because each additional attribute in the list requires an additional descriptor array to represent it. The subobject arrays' names (that is, addresses) are the contents of the value fields in the DS_ATTRIBUTES object descriptors.
Figure 3-9 shows the attributes of the DS_C_ATTRIBUTE_LIST object.
An object of this class can be an attribute of a DS_C_ATTRIBUTE_LIST object (see Section 3.5.2.2).
The following code fragment is a definition of a DS_C_ATTRIBUTE_LIST object.
static OM_descriptor Single_Attribute_Object[] = {
OM_OID_DESC(OM_CLASS, DS_C_ATTRIBUTE),
OM_OID_DESC(DS_ATTRIBUTE_TYPE, DSX_A_CDS_Class),
{DS_ATTRIBUTE_VALUES, OM_S_PRINTABLE_STRING, OM_STRING("Printer")},
OM_NULL_DESCRIPTOR
};
static OM_descriptor Attribute_List_Object[] = {
OM_OID_DESC(OM_CLASS, DS_C_ATTRIBUTE_LIST),
{DS_ATTRIBUTES, OM_S_OBJECT, {0, Single_Attribute_Object}},
OM_NULL_DESCRIPTOR
};
DS_C_DS_DN class objects are used to hold the full names of directory entries (Distinguished Names). You need an object of this class to pass directory entry names to the following XDS functions:
Figure 3-10 shows the attributes of a DS_C_DS_DN object.
There are as many DS_RDNS attributes in a DS_C_DS_DN object as there are separate name components in the full directory entry name. For example, to represent the following CDS entry name:
/.../C=US/O=OSF/OU=DCE/hosts/brazil/self
a total of six instances of the DS_RDNS attribute are required in the DS_C_DS_DN object. The /.../ (global root prefix) is not represented. This means that another six object descriptor arrays are required to hold the Relative Distinguished Name objects, as well as six object descriptors in the present object, one to hold (as the value of a DS_RDNS attribute) a pointer to each array.
Note that the order of these DS_RDNS attributes is significant; that is, the first DS_RDNS should contain as its value a pointer to the array representing the C=US part of the name; the next DS_RDNS should contain as its value a pointer to the array representing the O=OSF part, and so on. The root part of the name is not represented at all.
DS_C_DS_RDN class objects are required as values for the DS_RDNS attributes of DS_C_DS_DN objects. (For an illustration of its structure, see Figure 3-10.) RDN refers to the X.500 term Relative Distinguished Name that is used to signify a part of a full entry name. Separate objects of this class are not usually required as input to XDS functions.
The standard permits multiple AVAs in an RDN, but the DCE Directory and XDS API restrict an RDN to one AVA.
Note that there can only be one instance of this attribute in the DS_C_RDN object. The object descriptor array describing this object always consists of three object descriptor structures: the first describes the object's class, the second describes the DS_AVAS attribute, and the third descriptor is the terminating NULL.
The DS_C_AVA class object is used to hold an actual value. The value is usually in the form of one of the many different XOM string types. (For an illustration of its structure, see Figure 3-10.)
In calls to ds_compare(), an object of this type is required to hold the type and value of the attribute that you want compared with those in the entry you specify. It holds the type and value in a separate DS_C_DS_DN object.
DS_C_AVA is also included here because it is a required subsubobject of DS_C_DS_DN itself. DS_C_AVA is the subobject in which the name part's actual literal value is held.
If the DS_C_AVA object is a subobject of DS_C_DS_RDN (and therefore also of DS_C_DS_DN), then the value is a string representing the part of the directory entry name represented by this object. For example, if the DS_C_DS_RDN object contains the O=OSF part of an entry name, then the string OSF is the value of the DS_ATTRIBUTE_VALUES attribute, and DS_A_COUNTRY_NAME is the value of the DS_ATTRIBUTE_TYPE attribute.
On the other hand, if DS_C_AVA contains an entry attribute type and value to be passed to ds_compare(), then DS_ATTRIBUTE_TYPE identifies the type of the attribute, and DS_ATTRIBUTE_VALUES contains a value, which is appropriate for the attribute type, to be compared with the entry value.
For example, suppose you wanted to compare a certain value with a CDS entry's CDS_Class attribute's value. The identifiers for all the valid CDS entry attributes are found in the /.:/opt/dcelocal/etc/cds_attributes file. The value of DS_ATTRIBUTE_TYPE would be CDS_Class, which is the label of an object identifier string, and DS_ATTRIBUTE_VALUES would contain some desired value, in the correct syntax for CDS_Class. The syntax also is found in the cds_attributes file; for CDS_Class it is byte; that is, a character string.
The following code fragment shows an example definition for a DS_C_DS_DN object.
static OM_descriptor Entry_String_Object[] = {
OM_OID_DESC(OM_CLASS, DS_C_AVA),
OM_OID_DESC(DS_ATTRIBUTE_TYPE, DSX_TYPELESS_RDN),
{DS_ATTRIBUTE_VALUES, OM_S_PRINTABLE_STRING, OM_STRING("brazil")},
OM_NULL_DESCRIPTOR
};
static OM_descriptor Entry_Part_Object[] = {
OM_OID_DESC(OM_CLASS, DS_C_DS_RDN),
{DS_AVAS, OM_S_OBJECT, {0, Entry_String_Object}},
OM_NULL_DESCRIPTOR
};
static OM_descriptor Entry_Name_Object[] = {
OM_OID_DESC(OM_CLASS, DS_C_DS_DN),
{DS_RDNS, OM_S_OBJECT, {0, Entry_Part_Object}},
OM_NULL_DESCRIPTOR
};
DS_C_ENTRY_MOD_LIST class objects, which contain a list of changes to be made to some directory entry, must be passed to ds_modify_entry(). DS_C_ENTRY_MOD_LIST objects have the attributes shown in Figure 3-11.
Note that there can be one or more instances of this attribute in the object, which is why it is called _LIST. Each attribute contains one separate entry modification. To learn how the modification itself is specified, see Section 3.5.2.10. The order of multiple instances of this attribute is significant because if more than one modification is specified, the modifications are performed by ds_modify_entry() in the order in which the DS_CHANGES attributes appear in the DS_C_ENTRY_MOD_LIST object.
The DS_C_ENTRY_MOD class object holds the information associated with a directory entry modification. (For an illustration of its structure, see Figure 3-11.) Each DS_C_ENTRY_MOD object describes one modification. To create a list of modifications suitable to be passed to a ds_modify_entry() call, describe each modification in a separate DS_C_ENTRY_MOD object, and then insert these objects as multiple instances of the DS_CHANGES attribute in a DS_C_ENTRY_MOD_LIST object (see Section 3.5.2.9).
The following code fragment is an example definition of a DS_C_ENTRY_MOD_LIST object.
OM_string my_uuid;
static OM_descriptor Entry_Mod_Object[] = {
OM_OID_DESC(OM_CLASS, DS_C_ENTRY_MOD),
OM_OID_DESC(DS_ATTRIBUTE_TYPE, DSX_UUID),
{DS_ATTRIBUTE_VALUES, OM_S_OCTET_STRING, my_uuid},
{DS_MOD_TYPE, OM_S_ENUMERATION, DS_ADD_ATTRIBUTE},
OM_NULL_DESCRIPTOR
};
static OM_descriptor Entry_Mod_List_Object[] = {
OM_OID_DESC(OM_CLASS, DS_C_ENTRY_MOD_LIST),
{DS_CHANGES, OM_S_OBJECT, {0, Entry_Mod_Object}},
OM_NULL_DESCRIPTOR
};
When you call ds_read() to read one or more attributes from a CDS entry, you specify in the DS_C_ENTRY_INFO_SELECTION object the entry attributes you want to read.
The DS_C_ENTRY_INFO_SELECTION object contains the attributes shown in Figure 3-12.
Note that this object class has no subobjects.
Note also that there are multiple instances of this attribute if more than one attribute, but not all of them, is to be selected for reading. Each separate instance of DS_ATTRIBUTES_SELECTED has as its value an OID string that identifies one directory entry attribute to be read. If DS_ATTRIBUTES_SELECTED is present but does not have a value, ds_read() reads the entry but does not return any attribute data; this technique can be used to verify the existence of a directory entry.
The following code fragment provides an example definition of a DS_C_ENTRY_INFO_SELECTION object.
static OM_descriptor Entry_Info_Select_Object[] = {
OM_OID_DESC(OM_CLASS, DS_C_ENTRY_INFO_SELECTION),
OM_OID_DESC(DS_ATTRIBUTES_SELECTED, DSX_A_CDS_Class),
{DS_ALL_ATTRIBUTES, OM_S_BOOLEAN, OM_FALSE},
{DS_INFO_TYPE, OM_S_ENUMERATION, DS_TYPES_AND_VALUES},
OM_NULL_DESCRIPTOR
};
This section provides translations between CDS and XDS for attributes and data types. Table 3-2 provides the OM syntax for CDS attributes. Table 3-3 provides the OM syntax for CDS data types. Table 3-4 defines the mapping of the CDS Data Types to OM Syntaxes.
| CDS Attribute | OM Syntax |
|---|---|
| CDS_CTS | OM_S_OCTET_STRING |
| CDS_UTS | OM_S_OCTET_STRING |
| CDS_Class | OM_S_OCTET_STRING |
| CDS_ClassVersion | OM_S_INTEGER |
| CDS_ObjectUID | OM_S_OCTET_STRING |
| CDS_AllUpTo | OM_S_OCTET_STRING |
| CDS_Convergence | OM_S_INTEGER |
| CDS_InCHName | OM_S_INTEGER |
| CDS_DirectoryVersion | OM_S_INTEGER |
| CDS_UpgradeTo | OM_S_INTEGER |
| CDS_LinkTimeout | OM_S_INTEGER |
| CDS_Towers | OM_S_OCTET_STRING |
| OM Syntax | CDS Data Type |
|---|---|
| OM_S_TELETEX_STRING | cds_char |
| OM_S_OBJECT_IDENTIFIER_STRING | cds_byte |
| OM_S_OCTET_STRING | cds_byte |
| OM_S_PRINTABLE_STRING | cds_char |
| OM_S_NUMERIC_STRING | cds_char |
| OM_S_BOOLEAN | cds_long |
| OM_S_INTEGER | cds_long |
| OM_S_UTC_TIME_STRING | cds_char |
| OM_S_ENCODING_STRING | cds_byte |
| CDS Data Type | OM Syntax |
|---|---|
| cds_none | OM_S_NULL |
| cds_long | OM_S_INTEGER |
| cds_short | OM_S_INTEGER |
| cds_small | OM_S_INTEGER |
| cds_uuid | OM_S_OCTET_STRING |
| cds_Timestamp | OM_S_OCTET_STRING |
| cds_Version | OM_S_PRINTABLE_STRING |
| cds_char | OM_S_TELETEX_STRING |
| cds_byte | OM_S_OCTET_STRING |