Some programs work well when they are structured as multiple flows of control. Other programs may show better performance when they are multithreaded allowing the multiple threads to be mapped to multiple processors when they are available.
GDS application programs can contain multiple threads of control. For example, a GDS application can need to query several GDS servers. This can be achieved more efficiently by using separate threads simultaneously to query the different servers.
GDS supports multithreaded applications. Writing multithreaded applications over GDS imposes new requirements on programmers: they must manage the threads, synchronize threads' access to global resources, and make choices about thread scheduling and priorities.
This chapter describes a simple GDS application that uses threads. (Refer to the (3thr) reference pages for more information on DCE threads.)
The XDS/XOM API calls do not change when they are making use of DCE threads in an application program. The service underneath XDS/XOM API is designed to be
Figure 8-1 shows an example of how an application can issue XDS/XOM calls from within different threads.
The order of thread completion is not defined; however, XDS/XOM has an inherent ordering. Multithreaded XDS applications must adhere to the following order of execution:
Multi-threaded XOM applications must adhere to the following order of execution:
The XDS/XOM API will return an appropriate error code if these sequences are not adhered to. For example the following errors are returned:
The sample program is called thradd. The thradd program is a multithreaded XDS application that adds entries to a GDS directory. Each thread performs a ds_add_entry() call. The information for each entry to be added is read from an input file.
The thradd program can also be used to reset the directory to its original state. This is achieved by invoking thradd with a -d command-line argument. In this case, thradd uses the same input file and calls ds_remove_entry() for each entry. The ds_remove_entry() calls are also done in separate threads.
To keep the program short and clear, it works with a fixed tree for the upper nodes (/C=it/O=sni/OU=ap), to which the entries described in the input file are added. This fixed upper tree is added to the directory by thradd. The input file contains the common name, the surname, and the phone number of each Organizational-Person entry to be added.
For simplicity, only pthread_join() is used for synchronization purposes; mutexes are not used.
The thradd program could be enhanced to satisfy the following scenarios :
The thradd program is called from the command line as follows :
thradd [-d] [-f file name]
The input file can contain any number of lines. Each line represents a directory entry of an organizational person. Each line must contain the following three attributes for each entry :
<common name> <surname> <phone number>
The attributes must be strings without space characters. Lines containing less than three strings are rejected by the program; any input on a line after the first three strings is ignored and can be used for comments. The attributes are separated by one or more space characters.
The input strings are not verified for their relevant attribute syntax. A wrong attribute syntax will result in either a ds_add_entry() error or a ds_remove_entry() error.
The following would be a valid input file for thradd :
Anna Meister Erwin Reiter Gerhard Schulz Gottfried Schmid Heidrun Blum Hermann Meier Josefa Fischer Jutta Arndt Leopold Huber Magdalena Schuster Margot Junge
The thradd program writes messages to stdout for every action done by a thread. The order of the output can differ from the order in the input file; it depends on the execution of the different threads.
Errors are reported to stderr.
The directory must be active before running thradd. If running thradd in ``adding'' mode then the directory should not contain a node /C=it. The thradd program should always be invoked twice with the same input file: first without and then with option -d. This guarantees that the directory is reset to its original state. The GDS administration program gdsditadm can be used to verify the directory contents after adding entries.
The thradd program has a similar structure to the sample XDS programs in the previous chapter. Therefore, only a short general outline of the program is given here. The thread specifics are described in detail in the next section.
The static descriptors for the fixed tree (that is, /C=it/O=sni/OU=ap) are declared in the thradd.h header file. Listings of both the thradd.h header file are included in later sections of this chapter.
The main routine scans the command-line options, initializes the XDS workspace and, if working in ``adding'' mode, binds to the default GDS server without credentials, adds the fixed tree of upper nodes, and then unbinds from the directory.
The program then binds to the default GDS server without credentials. Each line of the input file is processed in turn by a while loop (until the end of file is reached). The while loop contains two for loops. The first for loop creates a separate thread for each line of the input file, up to a maximum of MAX_THREAD_NO of threads.
The add_or_remove() procedure, which adds or removes an entry to/from the directory, is the starting point of each thread's processing.
The second for loop waits for termination of the threads and then releases the resources used by the threads.
When the entire input file has been processed, thradd closes the connection to the GDS server and, if working in ``removing'' mode, removes the fixed tree of upper nodes (that is, /C=it/O=sni/OU=ap).
Finally, the XDS workspace is closed.
Figure 8-2 shows the program flow.
The program consists of the following general steps:
The following paragraphs describe the corresponding step numbers from the program listing in the next section:
Step 1 includes the header file pthread.h which is required for thread programming.
Step 2 defines a parameter block structure type for the thread start routine. A thread start routine must have exactly one parameter. However, add_or_remove() requires three parameters (session object, input line and operating mode). The structure pb_add_or_remove is defined as the parameter block for these components. Therefore, the single parameter block contains the three parameters required by add_or_remove().
Step 3 declares arrays for thread handles and parameter blocks. The routine which creates the thread (main in this case) must maintain the following information for each thread:
Step 4 reads the input file line by line. A thread is created for each line. A maximum MAX_THREAD_NO of threads are created in parallel. The program then waits for the termination of the created threads so that it can release the resources used by these threads, allowing it to create new threads for remaining input lines (if any).
The absolute maximum number of threads working in parallel depends on system limits; for thradd.h), which is well below the maximum on most systems.
Step 5 updates the parameter block. For each thread a different element of the array of parameter blocks is used.
Step 6 creates the thread. The thread is created by using the function pthread_create(). The function has four parameters:
Step 7 waits for the termination of the thread. The pthread_join() routine is called with the thread handle as the input parameter. The program waits for the termination of the thread. If the thread has already terminated, then pthread_join() returns immediately. The second parameter of pthread_join() contains the return value of the start function; here it's a dummy value because add_or_remove() returns a void. add_or_remove() is designed as a void function because the calling routine does not have to deal with error cases. RThe add_or_remove() routine prints status messages itself to show the processing order of the threads. Normally a status should be returned to the application.
Step 8 releases the resources used by the thread. The thread handle is used as input for the function pthread_detach(), which releases the resources (for example, memory) used by the thread.
Step 9 defines the thread start routine. As previously mentioned, the thread start routine must have exactly one parameter. In this case, it is a pointer to the parameter block structure defined in Step
Step 10 declares local variables needed for descriptors for the objects read from the input file. These descriptors are variables and are declared as automatic because of the reentrancy requirement. In the previous sample programs, descriptors were generally declared static. For this example, this is only possible for the constant descriptors declared in thradd.h.
Of course this example shows only a small part of the possibilities of multithreaded XDS programming. For example, each thread could make its own bind which would be useful if more than one GDS server was involved.
The following code is a listing of the thradd.c program:
/*
* The program operates in two modes; it adds or removes entries of
* object type organizational person to/from a directory. The
* information about the entries is read from a file.
*
* The program requires that a tree exists in the directory.
* Therefore, each time the program runs, the following tree of 3
* entries is added to or removed from the directory, according
* to the operation mode.
*
* O C=it
* | (objectClass=Country)
* |
* O O=sni
* | (objectClass=Organization)
* |
* O OU=ap
* (objectClass=OrganizationalUnit)
*
* Information about the organizational persons to be added or
* removed is read from the input file. It may contain any number
* of lines, where each line must have the following syntax:
*
* <common name> <surname> <phone number>
* Each item must be a string without a space.
*
* Lines containing less than 3 strings are rejected by the
* program. The program does not check to see if the strings conform
* to the appropiate attribute syntax; i.e. a wrong attribute
* syntax will lead to a ds_add_entry error, or to a
* ds_remove_entry error.
*
*
* Usage: thradd [-d] [-f<file name>]
* -d If the option -d is set, the entries in the
* file and the tree described above are removed,
* otherwise they are added.
* -f<file name> The option -f specifies the name of the input
* file.If left out, the default "thradd.dat"
* is used.
*
*/
/* STEP 1 */
/*
* Header file for thread programming:
*/
#include <pthread.h>
#include <stdio.h>
#include <xom.h>
#include <xds.h>
#include <xdsbdcp.h>
#include <xdsgds.h>
#include <xdscds.h>
#include "thradd.h" /* static data structures. */
/* STEP 2 */
/*
* typedef for parameter block of function add_or_remove
* (this is necessary because the start function of a thread
* takes only 1 parameter). The following 3 parameters are
* passed to add_or_remove :
*
* Input - Session object from the ds_bind call
* Input - Buffer with the entry information
* Input - "adding" or "removing" mode ?
*/
typedef struct {
OM_private_object session;
char line[MAX_LINE_LEN+1];
int do_remove;
} pb_add_or_remove;
/*
* static constants:
*
* Default name for input file containing entry information.
*/
static char fn_default[] = "thradd.dat";
/*
* function declarations:
*/
char *own_fgets(char *s, int n, FILE *f);
void add_or_remove(pb_add_or_remove *pb);
int
main(
int argc,
char *argv[]
)
{
OM_workspace workspace; /* workspace for objects */
OM_private_object bound_session; /* Holds the Session */
/* returned by ds_bind() */
FILE *fp; /* pointer for input file*/
int do_remove = FALSE; /* "adding" or "removing"*/
int error = FALSE; /* error in options ? */
int is_eof = FALSE; /* EOF input file reached*/
int thread_count; /* no. of created threads*/
char *file_name; /* ptr to input file name*/
/* STEP 3 */
pthread_t threads[MAX_THREAD_NO]; /* thread table */
pb_add_or_remove param_block[MAX_THREAD_NO]; /* 1 param block*/
/* for start routine per thread */
int dummy;
int c;
int i;
extern char *optarg;/* external variable used by getopt */
extern int optind; /* external variable used by getopt */
/*
* scan options -d and -f
*/
file_name = fn_default;
while ((c=getopt(argc, argv, "df:")) != EOF)
{
switch (c)
{
case 'd':
do_remove = TRUE;
break;
case 'f':
file_name = optarg;
break;
default:
error = TRUE;
break;
}
}
if (error)
{
printf("usage: %s [-d] [-f<file name>]\en", argv[0]);
return(FAILURE);
}
if (( fp = fopen(file_name, "r")) == (FILE *) NULL)
{
printf("fopen() error, file name: %s\en", file_name);
return(FAILURE);
}
/*
* Initialize a directory workspace for use by XOM.
*/
if ((workspace = ds_initialize()) == (OM_workspace)0)
printf("ds_initialize() error\en");
/*
* Negotiate the use of the BDCP and GDS packages.
*/
if (ds_version(features, workspace) != DS_SUCCESS)
printf("ds_version() error\en");
/*
* Add the fixed tree of entries, if in adding mode
*/
if (!do_remove)
if (add_entries(workspace))
printf("add_entries() error\en");
/*
* Bind to the default GDS server.
* The returned session object is stored in the private
* object variable bound_session and is used for further
* XDS function calls.
*/
if (ds_bind(DS_DEFAULT_SESSION, workspace, &bound_session)
!= DS_SUCCESS)
printf("ds_bind() error\en");
/* STEP 4 */
/*
* Add or remove entries described in input file.
* This is done in parallel, creating up to MAX_THREAD_NO
* threads at a time.
*/
while (!is_eof)
{
for (thread_count=0; thread_count<MAX_THREAD_NO;
thread_count++)
{
/* STEP 5 */
/*
* Prepare parameter block:
*/
is_eof = (own_fgets(param_block[thread_count].line,
MAX_LINE_LEN, fp) == NULL);
if (is_eof)
break;
param_block[thread_count].session = bound_session;
param_block[thread_count].do_remove = do_remove;
/* STEP 6 */
/*
* Create thread with start routine add_or_remove:
*/
if (pthread_create(&threads[thread_count],
pthread_attr_default,
(pthread_startroutine_t) add_or_remove,
(pthread_addr_t) ¶m_block[thread_count])
!= SUCCESS)
printf("pthread_create() error\en");
} /* end for */
/*
* Wait for termination of the created threads and release
* resources:
*/
for (i=0; i<thread_count; i++)
{
/* STEP 7 */
/*
* Wait for termination of thread
* (If thread has terminated already, the function
* returns immediately):
*/
if (pthread_join(threads[i], (pthread_addr_t) &dummy)
!= SUCCESS)
printf("pthread_join() error\en");
/* STEP 8 */
/*
* Release resources used by the thread:
*/
if (pthread_detach(&threads[i]) != SUCCESS)
printf("pthread_detach() error\en");
} /* end for */
} /* end while */
/*
* Close the connection to the GDS server.
*/
if (ds_unbind(bound_session) != DS_SUCCESS)
printf("ds_unbind() error\en");
if (om_delete(bound_session) != OM_SUCCESS)
printf("om_delete() error\en");
/*
* Remove the tree from the directory, if removing mode
*/
if (do_remove)
if (remove_entries(workspace))
printf("remove_entries() error\en");
/*
* Close the directory workspace.
*/
if (ds_shutdown(workspace) != DS_SUCCESS)
printf("ds_shutdown() error\en");
fclose(fp);
return(SUCCESS);
} /* end main() */
/* STEP 9 */
/*
* Handle (add or remove) a directory entry
*/
void
add_or_remove(
pb_add_or_remove *pb /* parameter information */
)
{
/*
* further local variables:
*/
char common_name[MAX_AT_LEN+1];
char phone_num[MAX_AT_LEN+1];
char surname[MAX_AT_LEN+1];
OM_sint invoke_id;
/* STEP 10 */
/*
* local variables for descriptors for objects read from file
*/
OM_descriptor ava_genop[] = {
OM_OID_DESC(OM_CLASS, DS_C_AVA),
OM_OID_DESC(DS_ATTRIBUTE_TYPE, DS_A_COMMON_NAME),
OM_NULL_DESCRIPTOR, /* place holder */
OM_NULL_DESCRIPTOR
};
OM_descriptor rdn_genop[] = {
OM_OID_DESC(OM_CLASS, DS_C_DS_RDN),
OM_NULL_DESCRIPTOR, /* place holder */
OM_NULL_DESCRIPTOR
};
OM_descriptor dn_genop[] = {
OM_OID_DESC(OM_CLASS,DS_C_DS_DN),
{DS_RDNS,OM_S_OBJECT,{0,rdn_it}},
{DS_RDNS,OM_S_OBJECT,{0,rdn_sni}},
{DS_RDNS,OM_S_OBJECT,{0,rdn_ap}},
OM_NULL_DESCRIPTOR, /* place holder */
OM_NULL_DESCRIPTOR
};
OM_descriptor att_phone_num[] = {
OM_OID_DESC(OM_CLASS, DS_C_ATTRIBUTE),
OM_OID_DESC(DS_ATTRIBUTE_TYPE, DS_A_PHONE_NBR),
OM_NULL_DESCRIPTOR, /* place holder */
OM_NULL_DESCRIPTOR
};
OM_descriptor att_surname[] = {
OM_OID_DESC(OM_CLASS, DS_C_ATTRIBUTE),
OM_OID_DESC(DS_ATTRIBUTE_TYPE, DS_A_SURNAME),
OM_NULL_DESCRIPTOR, /* place holder */
OM_NULL_DESCRIPTOR
};
OM_descriptor alist_OP[] = {
OM_OID_DESC(OM_CLASS, DS_C_ATTRIBUTE_LIST),
{DS_ATTRIBUTES, OM_S_OBJECT, {0, obj_class_OP} },
OM_NULL_DESCRIPTOR, /* place holder */
OM_NULL_DESCRIPTOR, /* place holder */
OM_NULL_DESCRIPTOR
};
rdn_genop[1].type = DS_AVAS;
rdn_genop[1].syntax = OM_S_OBJECT;
rdn_genop[1].value.object.padding = 0;
rdn_genop[1].value.object.object = ava_genop;
dn_genop[4].type = DS_RDNS;
dn_genop[4].syntax = OM_S_OBJECT;
dn_genop[4].value.object.padding = 0;
dn_genop[4].value.object.object = rdn_genop;
alist_OP[2].type = DS_ATTRIBUTES;
alist_OP[2].syntax = OM_S_OBJECT;
alist_OP[2].value.object.padding = 0;
alist_OP[2].value.object.object = att_surname;
alist_OP[3].type = DS_ATTRIBUTES;
alist_OP[3].syntax = OM_S_OBJECT;
alist_OP[3].value.object.padding = 0;
alist_OP[3].value.object.object = att_phone_num;
if (sscanf(pb->line, "%s %s %s", common_name,
surname, phone_num) != 3)
{
printf("invalid input line: >%s<\en", pb->line);
return;
}
/*
* Fill descriptor for common name
*/
ava_genop[2].type = DS_ATTRIBUTE_VALUES;
ava_genop[2].syntax = OM_S_PRINTABLE_STRING;
ava_genop[2].value.string.length =
(OM_string_length)strlen(common_name);
ava_genop[2].value.string.elements = common_name;
if (!pb->do_remove) /* add */
{
/*
* Fill descriptors for surname and phone number
*/
att_surname[2].type = DS_ATTRIBUTE_VALUES;
att_surname[2].syntax = OM_S_TELETEX_STRING;
att_surname[2].value.string.length =
(OM_string_length)strlen(surname);
att_surname[2].value.string.elements = surname;
att_phone_num[2].type = DS_ATTRIBUTE_VALUES;
att_phone_num[2].syntax = OM_S_PRINTABLE_STRING;
att_phone_num[2].value.string.length =
(OM_string_length)strlen(phone_num);
att_phone_num[2].value.string.elements = phone_num;
/*
* add entry
*/
if (ds_add_entry(pb->session, DS_DEFAULT_CONTEXT, dn_genop,
alist_OP, &invoke_id) != DS_SUCCESS)
printf("ds_add_entry() error: %s %s %s\en",
common_name, surname, phone_num);
else
printf("entry added: %s %s %s\en",
common_name, surname, phone_num);
}
else /* remove */
{
/*
* remove entry
*/
if (ds_remove_entry(pb->session, DS_DEFAULT_CONTEXT,
dn_genop, &invoke_id) != DS_SUCCESS)
printf("ds_remove_entry() error: %s\en", common_name);
else
printf("entry removed: %s\en", common_name);
} /* end if */
} /* end add_or_remove() */
/*
* Add the tree of entries described above.
*/
int
add_entries(
OM_workspace workspace /* In--XDS workspace */
)
{
OM_private_object bound_session; /* Holds Session object */
/* returned by ds_bind() */
OM_sint invoke_id;
int error = FALSE;
/* Bind (without credentials) to the default GDS server */
if (ds_bind(DS_DEFAULT_SESSION, workspace, &bound_session)
!= DS_SUCCESS)
error = TRUE;
/* Add entries to the GDS server */
if (ds_add_entry(bound_session, DS_DEFAULT_CONTEXT, dn_it,
alist_C, &invoke_id) != DS_SUCCESS)
error = TRUE;
if (ds_add_entry(bound_session, DS_DEFAULT_CONTEXT, dn_sni,
alist_O, &invoke_id) != DS_SUCCESS)
error = TRUE;
if (ds_add_entry(bound_session, DS_DEFAULT_CONTEXT, dn_ap,
alist_OU, &invoke_id) != DS_SUCCESS)
error = TRUE;
/* Close the connection to the GDS server */
if (ds_unbind(bound_session) != DS_SUCCESS)
error = TRUE;
if (om_delete(bound_session) != OM_SUCCESS)
error = TRUE;
return (error);
}
/*
* Remove the tree of entries described above.
*/
int
remove_entries(
OM_workspace workspace /* In--XDS workspace */
)
{
OM_private_object bound_session; /* Holds Session object */
/* returned by ds_bind() */
OM_sint invoke_id;
int error = FALSE;
/* Bind to the default GDS server */
if (ds_bind(DS_DEFAULT_SESSION, workspace, &bound_session)
!= DS_SUCCESS)
error = TRUE;
/* Remove entries from the GDS server */
if (ds_remove_entry(bound_session, DS_DEFAULT_CONTEXT,
dn_ap, &invoke_id) != DS_SUCCESS)
error = TRUE;
if (ds_remove_entry(bound_session, DS_DEFAULT_CONTEXT,
dn_sni, &invoke_id) != DS_SUCCESS)
error = TRUE;
if (ds_remove_entry(bound_session, DS_DEFAULT_CONTEXT,
dn_it, &invoke_id) != DS_SUCCESS)
error = TRUE;
/* Close the connection to the GDS server */
if (ds_unbind(bound_session) != DS_SUCCESS)
error = TRUE;
if (om_delete(bound_session) != OM_SUCCESS)
error = TRUE;
return (error);
}
/*
* read one line with fgets and overwrite new line by
* a null character
*/
char *
own_fgets(
char *s, /* OUT--string read */
int n, /* IN---maximum number of chars to be read */
FILE *f /* IN---input file */
)
{
char *result;
int i = 0;
result = fgets(s, n, f);
if (result != NULL)
{
i = strlen(s);
if (s[i-1] == '\en')
s[i-1] = '\e0';
}
return (result);
}
#ifndef THRADD_H
#define THRADD_H
#ifndef TRUE
#define TRUE (1)
#endif
#ifndef FALSE
#define FALSE (0)
#endif
#define SUCCESS
#define FAILURE
#define MAX_LINE_LEN 100 /* max length of line in input file */
#define MAX_AT_LEN 100 /* max length of an attribute value */
#define MAX_THREAD_NO 10 /* max number of threads created */
/* The application must export the object
identfiers it requires.
*/
OM_EXPORT (DS_C_AVA)
OM_EXPORT (DS_C_DS_RDN)
OM_EXPORT (DS_C_DS_DN)
OM_EXPORT (DS_C_ATTRIBUTE)
OM_EXPORT (DS_C_ATTRIBUTE_LIST)
OM_EXPORT (DS_A_COUNTRY_NAME)
OM_EXPORT (DS_A_ORG_NAME)
OM_EXPORT (DS_A_ORG_UNIT_NAME)
OM_EXPORT (DS_A_COMMON_NAME)
OM_EXPORT (DS_A_OBJECT_CLASS)
OM_EXPORT (DS_A_PHONE_NBR)
OM_EXPORT (DS_A_SURNAME)
OM_EXPORT (DS_O_TOP)
OM_EXPORT (DS_O_COUNTRY)
OM_EXPORT (DS_O_ORG)
OM_EXPORT (DS_O_ORG_UNIT)
OM_EXPORT (DS_O_PERSON)
OM_EXPORT (DS_O_ORG_PERSON)
/* Build descriptor lists for the following */
/* distinguished names: */
/* root */
/* /C=it */
/* /C=it/O=sni */
/* /C=it/O=sni/OU=ap */
static OM_descriptor ava_it[] = {
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("it")},
OM_NULL_DESCRIPTOR
};
static OM_descriptor ava_sni[] = {
OM_OID_DESC(OM_CLASS, DS_C_AVA),
OM_OID_DESC(DS_ATTRIBUTE_TYPE, DS_A_ORG_NAME),
{DS_ATTRIBUTE_VALUES, OM_S_TELETEX_STRING, OM_STRING("sni")},
OM_NULL_DESCRIPTOR
};
static OM_descriptor ava_ap[] = {
OM_OID_DESC(OM_CLASS, DS_C_AVA),
OM_OID_DESC(DS_ATTRIBUTE_TYPE, DS_A_ORG_UNIT_NAME),
{DS_ATTRIBUTE_VALUES, OM_S_TELETEX_STRING, OM_STRING("ap")},
OM_NULL_DESCRIPTOR
};
static OM_descriptor rdn_it[] = {
OM_OID_DESC(OM_CLASS, DS_C_DS_RDN),
{DS_AVAS, OM_S_OBJECT, {0, ava_it}},
OM_NULL_DESCRIPTOR
};
static OM_descriptor rdn_sni[] = {
OM_OID_DESC(OM_CLASS, DS_C_DS_RDN),
{DS_AVAS, OM_S_OBJECT, {0, ava_sni}},
OM_NULL_DESCRIPTOR
};
static OM_descriptor rdn_ap[] = {
OM_OID_DESC(OM_CLASS, DS_C_DS_RDN),
{DS_AVAS, OM_S_OBJECT, {0, ava_ap}},
OM_NULL_DESCRIPTOR
};
static OM_descriptor dn_root[] = {
OM_OID_DESC(OM_CLASS,DS_C_DS_DN),
OM_NULL_DESCRIPTOR
};
static OM_descriptor dn_it[] = {
OM_OID_DESC(OM_CLASS,DS_C_DS_DN),
{DS_RDNS,OM_S_OBJECT,{0,rdn_it}},
OM_NULL_DESCRIPTOR
};
static OM_descriptor dn_sni[] = {
OM_OID_DESC(OM_CLASS,DS_C_DS_DN),
{DS_RDNS,OM_S_OBJECT,{0,rdn_it}},
{DS_RDNS,OM_S_OBJECT,{0,rdn_sni}},
OM_NULL_DESCRIPTOR
};
static OM_descriptor dn_ap[] = {
OM_OID_DESC(OM_CLASS,DS_C_DS_DN),
{DS_RDNS,OM_S_OBJECT,{0,rdn_it}},
{DS_RDNS,OM_S_OBJECT,{0,rdn_sni}},
{DS_RDNS,OM_S_OBJECT,{0,rdn_ap}},
OM_NULL_DESCRIPTOR
};
/* Build up an array of object identifiers for the */
/* attributes to be added to the directory. */
static OM_descriptor obj_class_C[] = {
OM_OID_DESC(OM_CLASS, DS_C_ATTRIBUTE),
OM_OID_DESC(DS_ATTRIBUTE_TYPE, DS_A_OBJECT_CLASS),
OM_OID_DESC(DS_ATTRIBUTE_VALUES, DS_O_TOP),
OM_OID_DESC(DS_ATTRIBUTE_VALUES, DS_O_COUNTRY),
OM_NULL_DESCRIPTOR
};
static OM_descriptor obj_class_O[] = {
OM_OID_DESC(OM_CLASS, DS_C_ATTRIBUTE),
OM_OID_DESC(DS_ATTRIBUTE_TYPE, DS_A_OBJECT_CLASS),
OM_OID_DESC(DS_ATTRIBUTE_VALUES, DS_O_TOP),
OM_OID_DESC(DS_ATTRIBUTE_VALUES, DS_O_ORG),
OM_NULL_DESCRIPTOR
};
static OM_descriptor obj_class_OU[] = {
OM_OID_DESC(OM_CLASS, DS_C_ATTRIBUTE),
OM_OID_DESC(DS_ATTRIBUTE_TYPE, DS_A_OBJECT_CLASS),
OM_OID_DESC(DS_ATTRIBUTE_VALUES, DS_O_TOP),
OM_OID_DESC(DS_ATTRIBUTE_VALUES, DS_O_ORG_UNIT),
OM_NULL_DESCRIPTOR
};
static OM_descriptor obj_class_OP[] = {
OM_OID_DESC(OM_CLASS, DS_C_ATTRIBUTE),
OM_OID_DESC(DS_ATTRIBUTE_TYPE, DS_A_OBJECT_CLASS),
OM_OID_DESC(DS_ATTRIBUTE_VALUES, DS_O_TOP),
OM_OID_DESC(DS_ATTRIBUTE_VALUES, DS_O_PERSON),
OM_OID_DESC(DS_ATTRIBUTE_VALUES, DS_O_ORG_PERSON),
OM_NULL_DESCRIPTOR
};
static OM_descriptor alist_C[] = {
OM_OID_DESC(OM_CLASS, DS_C_ATTRIBUTE_LIST),
{DS_ATTRIBUTES, OM_S_OBJECT, {0, obj_class_C} },
OM_NULL_DESCRIPTOR
};
static OM_descriptor alist_O[] = {
OM_OID_DESC(OM_CLASS, DS_C_ATTRIBUTE_LIST),
{DS_ATTRIBUTES, OM_S_OBJECT, {0, obj_class_O} },
OM_NULL_DESCRIPTOR
};
static OM_descriptor alist_OU[] = {
OM_OID_DESC(OM_CLASS, DS_C_ATTRIBUTE_LIST),
{DS_ATTRIBUTES, OM_S_OBJECT, {0, obj_class_OU} },
OM_NULL_DESCRIPTOR
};
/* Build up an array of object identifiers for the */
/* optional packages to be negotiated. */
static DS_feature features[] = {
{ OM_STRING(OMP_O_DS_BASIC_DIR_CONTENTS_PKG), OM_TRUE },
{ OM_STRING(OMP_O_DSX_GDS_PKG), OM_TRUE },
{ 0 }
};
#endif /* THRADD_H */