Novell is now a part of Micro Focus

Programming NDS with NetWare Loadable Modules, Part 3

Articles and Tips: article

Adapted from a DeveloperNet University Tutorial

01 Jun 2000


Parts 1 and 2 of this article, published in the April and May issues of Developer Notes respectively, covered general background information such as When to Write an NLM and Starting NLM Programming and also gave code samples with specific explanations.

For the online version of this tutorial, see http://developer.novell.com/education/tutorials/nlm_nds/L_NLM_OS_NDS1.html

This section, part 3, concludes the article and covers the following topics:

Adding an Attribute to a Field in NDS

// adds the third attribute to the existing class
void ModifyClass(NWDSContextHandle con)
{
    NWDSCCODE       cCode;
    pBuf_T                  classBuf;
    
    printf("Modifying Class Definition.\n");
    //delay(5000);
    if((cCode = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &classBuf)) != 0)
    {
         printf("NWDSAllocBuf failed:%d\n", cCode);
    }
    else
    {
         if((cCode = NWDSInitBuf(con, DSV_MODIFY_CLASS_DEF, classBuf)) != 0)
         {
              printf("NWDSInitBuf failed:%d\n", cCode);
    
         }
         if((cCode = NWDSPutAttrName(con, classBuf, "Test:Modify Class")) != 0)
    
         {
              printf("NWDSPutAttrName failed:%d\n", cCode);
         }
         if((cCode = NWDSModifyClassDef(con, "Test:Bookmark", classBuf)) != 0)
         {
              printf("Class Definition Modification failed:%d\n", cCode);
         }
         else
         {
              printf("Class Modification Successful.\n");
         }
         NWDSFreeBuf(classBuf);
    }
}

NWDSModifyClassDef is like NWDSDefineClass in that the program allocates memory, adds a field, and then updates NDS.

Creating Data in NDS

void CreateObject(NWDSContextHandle con)
{
	 NWDSCCODE       cCode;
	 pBuf_T  inBuf = NULL;
	 nuint32         syntaxID;

	 int x;
	 nstr8       strObjectName[MAX_DN_CHARS+1];

	 printf("Creating Objects.\n");
	 if((cCode = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &inBuf)) != 0)
	 {
	 	 printf("NWDSAllocBuf failed:%d\n", cCode);
	 	 dontCreate = 1;
	 	 ShutDownFlag = 1;
	 }
	 else
	 {
	 	 if((cCode = NWDSInitBuf(con, DSV_ADD_ENTRY, inBuf)) != 0)
	 	 {
	 	 	 printf("NWDSInitBuf failed:%d\n", cCode);
	 	 }

	 	 if((cCode = NWDSPutAttrName(con, inBuf, "Object Class")) != 0)
	 	 {
	 	 	 printf("Put Name in buffer failed:%d.\n", cCode);
	 	 }
	 	 if((cCode = NWDSGetSyntaxID(con, "Object Class", &syntaxID)) != 0)
	 	 {
	 	 	 printf("Get Syntax ID class failed:%d\n", cCode);
	 	 }
	 	 if((cCode = NWDSPutAttrVal(con, inBuf, syntaxID, "Test:Bookmark")) != 0)
	 	 {
	 	 	 printf("NWDSPutAttrVal failed:%d\n", cCode);
	 	 }
	 	 if((cCode = NWDSPutAttrName(con, inBuf, "Test:Profile Name")) != 0)
	 	 {
	 	 	 printf("Put attribute name in buffer failed:%d\n", cCode);
	 	 }
	 	 if((cCode = NWDSGetSyntaxID(con, "Test:Profile Name", &syntaxID)) != 0)
	 	 {
	 	 	 printf("Get Syntax ID name failed:%d\n", cCode);
	 	 }
	 	 if((cCode = NWDSPutAttrVal(con, inBuf, syntaxID, "Rob")) != 0)
	 	 {
	 	 	 printf("NWDSPutAttrVal failed:%d\n", cCode);
	 	 }
	 	 printf("Enter Context for Objects (e.g.  Novell)\nContext: ");
	 	 gets(strObjectContext);


	 	 for(x = 0; x < 5; x++)
	 	 {
	 	 	 printf("Enter Object Name (e.g.  admin)\nName: ");
	 	 	 gets(strObjectName);
	 	 	 strcat(strObjectName, ".");
	 	 	 strcat(strObjectName, strObjectContext);
	 	 	 printf("%s\n", strObjectName);
	 	 	 if((Names[x] = malloc(strlen(strObjectName)+1)) == NULL)
	 	 	 {
	 	 	 	 printf("Memory allocation failed.\n");
	 	 	 	 break;
	 	 	 }
	 	 	 else
	 	 	 {
	 	 	 	 strcpy(Names[x], strObjectName);
	 	 	 	 printf("%s\n", Names[x]);
	 	 	 }

	 	 	 if((cCode = NWDSAddObject(con, Names[x], NULL, 0, inBuf)) != 0)
	 	 	 {
	 	 	 	 printf("add Object failed:%d\n", cCode);
	 	 	 }
	 	 	 else
	 	 	 {
	 	 	 	 printf("Object Added.\n");
	 	 	 }
	 	 	 *strObjectName = '\0';
	 	 }
	 	 NWDSFreeBuf(inBuf);
	 }
}

NWDSAddObject requires a context to place the object. The allocated memory specifies the attributes that the object will have in it. The fifth parameter holds th data that will be added to the object.

Changing an Object Name

void ModifyObjectName(NWDSContextHandle con)
{
	 int x;
	 NWDSCCODE       cCode;
	 if(dontCreate != 1)
	 {
	 	 printf("Modifying Objects Names.\n");
	 	 for(x = 0; x < 5; x++)
	 	 {
	 	 	 if((New[x] = malloc(50)) == NULL)
	 	 	 {
	 	 	 	 printf("Memory allocation failed.\n");
	 	 	 	 ShutDownFlag = 1;
	 	 	 	 break;
	 	 	 }
	 	 	 else
	 	 	 {
	 	 	 	 switch(x)
	 	 	 	 	 {
	 	 	 	 	 case 0:         strcpy(New[x], "test");
	 	 	 	 	 	 break;
	 	 	 	 	 case 1:         strcpy(New[x], "test2");
	 	 	 	 	 	 break;
	 	 	 	 	 case 2:         strcpy(New[x], "test3");
	 	 	 	 	 	 break;
	 	 	 	 	 case 3:         strcpy(New[x], "test4");
	 	 	 	 	 	 break;
	 	 	 	 	 case 4:         strcpy(New[x], "test5");
	 	 	 	 	 	 break;
	 	 	 	 	 	 }
strcat(New[x], ".");
	 	 	 	 strcat(New[x], strObjectContext);
	 	 	 	 if((cCode = NWDSModifyRDN(con, Names[x], New[x], 1)) != 0)
	 	 	 	 {
	 	 	 	 	 printf("Change object name failed:%d\n", cCode);
	 	 	 	 }
	 	 	 	 else
	 	 	 	 {
	 	 	 	 	 printf("%s changed to %s.\n", Names[x], New[x]);
	 	 	 	 }
	 	 	 }
	 	 }
	 }
}

NWDSModifyRDN is used to change the relative distinguished name of an object in NDS. The first three paramaters are context, old object name, and new object name. The last parameter is set to TRUE. A TRUE deleted all attribute values of the object.

Changing an Object in NDS

void ModifyObject(NWDSContextHandle con)
{
	 NWDSCCODE       cCode;

	 int     x;
	 pBuf_T  inBuf, inBuf2, inBuf3, inBuf4;
	 nuint32         syntaxID;
	 nint32  iterHandle;

	 if(dontCreate != 1)
	 {
	 	 printf("Modifying Objects Values.\n");
	 	 if((cCode = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &inBuf)) != 0)
	 	 {
	 	 	 printf("NWDSAllocBuf failed:%d\n", cCode);
	 	 }
	 	 else
	 	 {
	 	 	 if((cCode = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &inBuf2)) != 0)
	 	 	 {
	 	 	 	 printf("NWDSAllocBuf failed:%d\n", cCode);
	 	 	 }
	 	 	 else
	 	 	 {
	 	 	 	 if((cCode = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &inBuf3)) != 0)
	 	 	 	 {
	 	 	 	 	 printf("NWDSAllocBuf failed:%d\n", cCode);
	 	 	 	 }
	 	 	 	 else
	 	 	 	 {
	 	 	 	 	 if((cCode = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &inBuf4)) !=
)
	 	 	 	 	 {
	 	 	 	 	 	 printf("NWDSAllocBuf failed:%d\n", cCode);
	 	 	 	 	 }
	 	 	 	 	 else
	 	 	 	 	 {
	 	 	 	 	 	 // add value for attribute Test:Bookmarks
	 	 	 	 	 	 if((cCode = NWDSInitBuf(con, DSV_MODIFY_ENTRY,
nBuf)) != 0)
	 	 	 	 	 	 {
	 	 	 	 	 	 	 printf("NWDSInitBuf failed:%d\n", cCode);
	 	 	 	 	 	 	 	 }
	 	 	 	 	 	 if((cCode = NWDSGetSyntaxID(con, "Test:Bookmarks",
syntaxID)) != 0)
	 	 	 	 	 	 {
	 	 	 	 	 	 	 printf("Get Syntax ID class failed:%d\n",
Code);
	 	 	 	 	 	 }
	 	 	 	 	 	 if((cCode = NWDSPutChangeAndVal(con, inBuf,
S_ADD_ATTRIBUTE, "Test:Bookmarks", syntaxID, "Yahoo")) !=0 )
	 	 	 	 	 	 {
	 	 	 	 	 	 	 printf("Put Add Value in buffer failed:%d\n",
Code);


	 	 	 	 	 	 }
	 	 	 	 	 	 // add value for attribute Test:modify Class
	 	 	 	 	 	 if((cCode = NWDSInitBuf(con, DSV_MODIFY_ENTRY,
nBuf3)) != 0)
	 	 	 	 	 	 {
	 	 	 	 	 	 	 printf("NWDSInitBuf failed:%d\n", cCode);
	 	 	 	 	 	 }
	 	 	 	 	 	 if((cCode = NWDSGetSyntaxID(con, "Test:Modify Class",
syntaxID)) != 0)
	 	 	 	 	 	 {
	 	 	 	 	 	 	 printf("Get Syntax ID class failed:%d\n",
Code);
	 	 	 	 	 	 }
	 	 	 	 	 	 if((cCode = NWDSPutChangeAndVal(con, inBuf,
S_ADD_ATTRIBUTE, "Test:Modify Class", syntaxID, "Netscape")) !=0 )
	 	 	 	 	 	 {
	 	 	 	 	 	 	 printf("Put Add Value in buffer failed:%d\n",
Code);
	 	 	 	 	 	 }
	 	 	 	 	 	 // modify Test:Profile name Value
	 	 	 	 	 	 if((cCode = NWDSInitBuf(con, DSV_MODIFY_ENTRY,
nBuf2)) != 0)
	 	 	 	 	 	 {
	 	 	 	 	 	 	 printf("NWDSInitBuf failed:%d\n", cCode);
	 	 	 	 	 	 }
	 	 	 	 	 if((cCode = NWDSGetSyntaxID(con, "Test:Profile Name",
syntaxID)) != 0)
	 	 	 	 	 	 {
	 	 	 	 	 	 	 printf("Get Syntax ID class failed:%d\n",
Code);
	 	 	 	 	 	 }
	 	 	 	 	 	 if((cCode = NWDSPutChangeAndVal(con, inBuf,
S_OVERWRITE_VALUE, "Test:Profile Name", syntaxID, "Larry")) !=0 )
	 	 	 	 	 	 {
	 	 	 	 	 	 	 printf("Put Overwrite Value in buffer
ailed:%d\n", cCode);
	 	 	 	 	 	 }
	 	 	 	 	 	 // remove and then add new Test:Profile Name
	 	 	 	 	 	 if((cCode = NWDSInitBuf(con, DSV_MODIFY_ENTRY,
nBuf4)) != 0)
	 	 	 	 	 	 {
	 	 	 	 	 	 	 printf("NWDSInitBuf failed:%d\n", cCode);
	 	 	 	 	 	 }
	 	 	 	 	 	 if((cCode = NWDSPutChangeAndVal(con, inBuf,
S_REMOVE_VALUE, "Test:Profile Name", syntaxID, "Larry")) !=0 )
	 	 	 	 	 	 {
	 	 	 	 	 	 	 printf("Put Remove Value in buffer 
failed:%d\n", cCode);
	 	 	 	 	 	 }
	 	 	 	 	 	 if((cCode = NWDSPutChangeAndVal(con, inBuf,
S_ADD_VALUE, "Test:Profile Name", syntaxID, "Superman")) !=0 )
	 	 	 	 	 	 {
	 	 	 	 	 	 	 printf("Put Add Value in buffer failed:%d\n",
Code);
	 	 	 	 	 	 }


	 	 	 	 	 	 for(x = 0; x < 5; x++)
	 	 	 	 	 	 {
	 	 	 	 	 	 	 iterHandle = -1;
	 	 	 	 	 	 	 if((cCode = NWDSModifyObject(con, New[x],
iterHandle, 1, inBuf)) != 0)
	 	 	 	 	 	 	 {
	 	 	 	 	 	 	 	 printf("Add Attribute value failed:%d\n",
Code);
	 	 	 	 	 	 	 }
	 	 	 	 	 	 	 else
	 	 	 	 	 	 	 {
	 	 	 	 	 	 	 	 printf("Add Attribute Value successful on
s.\n", New[x]);
	 	 	 	 	 	 	 }
	 	 	 	 	 	 	 if((cCode = NWDSModifyObject(con, New[x],
iterHandle, 1, inBuf3)) != 0)
	 	 	 	 	 	 	 {
	 	 	 	 	 	 	 	 printf("Add Attribute value failed:%d\n",
Code);
	 	 	 	 	 	 	 }

	 	 	 	 	 	 else

	 	 	 	 	 	 {
	 	 	 	 	 	 	 	 printf("Add Attribute Value successful on
s.\n", New[x]);
	 	 	 	 	 	 	 }
	 	 	 	 	 	 	 if((cCode = NWDSModifyObject(con, New[x],
iterHandle, 1, inBuf2)) != 0)
	 	 	 	 	 	 	 {
	 	 	 	 	 	 	 	 printf("Modify Value failed:%d\n",
Code);
	 	 	 	 	 	 	 }
	 	 	 	 	 	 	 else
	 	 	 	 	 	 	 {
	 	 	 	 	 	 	 	 printf("Modify Value successful on
s.\n", New[x]);
	 	 	 	 	 	 	 }
	 	 	 	 	 	 	 if((cCode = NWDSModifyObject(con, New[x],
iterHandle, 0, inBuf4)) != 0)
	 	 	 	 	 	 	 {
	 	 	 	 	 	 	 	 printf("Add/Remove Value failed:%d\n", 
cCode);
	 	 	 	 	 	 	 }
	 	 	 	 	 	 	 else
	 	 	 	 	 	 	 {
	 	 	 	 	 	 	 	 printf("Add/Remove Value successful on
s.\n", New[x]);
	 	 	 	 	 	 	 }

	 	 	 	 	 	 }
	 	 	 	 	 	 NWDSFreeBuf(inBuf4);
	 	 	 	 	 }
	 	 	 	 	 NWDSFreeBuf(inBuf3);
	 	 	 	 }
	 	 	 	 NWDSFreeBuf(inBuf2);
	 	 	 }
	 	 	 NWDSFreeBuf(inBuf);
	 	 }
	 }
}

NWDSModifyObject has five parameters that are used to change an NDS object. First, context is required. Second, the name of the object to be modified. Third, the information needed to position additional modifications of the NDS object. Fourth, the flags that will be used for the modification. Finally, the data that is going to be modified in NDS. NWDSModifyObject cannot change the reletive distinguished name of an object. Only attributes can be replaced, added or deleted from NDS.

Use ConsoleOne to verify that the objects have been changed.

Searching NDS

void Search(NWDSContextHandle con)
{
	 NWDSCCODE       cCode;
	 pBuf_T          searchFilter, objectInfo;
	 Filter_Cursor_T         *cur;
	 nint32  iterHandle;

	 printf("Performing Search.\n");
	 if((cCode = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &searchFilter)) != 0)
	 {
	 	 printf("buffer allocation failed:%d\n", cCode);
	 }
	 else
	 {
	 	 if((cCode = NWDSAllocFilter(&cur)) != 0)
	 	 {
	 	 	 printf("AllocFilter failed:%d\n", cCode);
	 	 }
	 	 else
	 	 {
	 	 	 if((cCode = NWDSInitBuf(con, DSV_SEARCH_FILTER, searchFilter)) != 0)
	 	 	 {
	 	 	 	 printf("InitBuf failed:%d\n", cCode);
	 	 	 }
	 	 	 if((cCode = NWDSAddFilterToken(cur, FTOK_ANAME, "Test:Modify Class",
)) != 0)
	 	 	 {
	 	 	 	 printf("add token failed:%d\n", cCode);
	 	 	 }
	 	 	 if((cCode = NWDSAddFilterToken(cur, FTOK_EQ, NULL, 0)) != 0)
	 	 	 {
	 	 	 	 printf("add token failed:%d\n", cCode);
	 	 	 }
	 	 	 if((cCode = NWDSAddFilterToken(cur, FTOK_AVAL, "Netscape",
YN_CI_STRING)) != 0)
	 	 	 {
	 	 	 	 printf("add token failed:%d\n", cCode);
	 	 	 }
	 	 	 if((cCode = NWDSAddFilterToken(cur, FTOK_AND, NULL, 0)) != 0)
	 	 	 {


	 	 	 	 printf("add token failed:%d\n", cCode);
	 	 	 }
	 	 	 if((cCode = NWDSAddFilterToken(cur, FTOK_ANAME, "Test:Bookmarks", 0))
= 0)
	 	 	 {
	 	 	 	 printf("add token failed:%d\n", cCode);
	 	 	 }
	 	 	 if((cCode = NWDSAddFilterToken(cur, FTOK_EQ, NULL, 0)) != 0)
	 	 	 {
	 	 	 	 printf("add token failed:%d\n", cCode);
	 	 	 }
	 	 	 if((cCode = NWDSAddFilterToken(cur, FTOK_AVAL, "Yahoo", SYN_CI_STRING))
= 0)
	 	 	 {
	 	 	 	 printf("add token failed:%d\n", cCode);
	 	 	 }
	 	 	 if((cCode = NWDSAddFilterToken(cur, FTOK_END, NULL, 0)) != 0)
	 	 	 {
	 	 	 	 printf("add token failed:%d\n", cCode);
	 	 	 }
	 	 	 if((cCode = NWDSPutFilter(con, searchFilter, cur, NULL)) != 0)
	 	 	 {
	 	 	 	 printf("PutFilter failed:%d\n", cCode);
	 	 	 }
	 	 	 else
	 	 	 {
	 	 	 	 printf("Search Expression Tree defined.\n");
	 	 	 }
	 	 	 iterHandle = -1;
	 	 	 if((cCode = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &objectInfo)) != 0)
	 	 	 {
	 	 	 	 printf("buffer allocation failed:%d\n", cCode);
	 	 	 }
	 	 	 else
	 	 {               
	 	 	 	 if((cCode = NWDSSearch(con, "[ROOT]", DS_SEARCH_SUBTREE, 1,
searchFilter, DS_ATTRIBUTE_VALUES, 1, NULL, &iterHandle, 0, 0, objectInfo)) != 0)
	 	 	 	 {
	 	 	 	 	 printf("Search failed:%d\n", cCode);
	 	 	 	 }
	 	 	 	 else
	 	 	 	 {
	 	 	 	 	 printf("Search successful.\n");
	 	 	 	 }
	 	 	 	 NWDSFreeBuf(objectInfo);
	 	 	 }
	 	 }
	 	 NWDSFreeBuf(searchFilter);
	 }
}

NWDSSearch has many parameters. The following list defines each.

Context is the location to start searching.

Base object is set the the ROOT of the subtree to be searched.

Depth of search is specified. Use DS_SEARCH_SUBTREE for all subordinates.

SearchAliases are dereferenced if TRUE.

Filter specified by NWDSAddFileterToken.

The type of information that is going to be returned.

All information about the attributes are returned when TRUE.

Names of the attributes for which information is to be returned.

Information needed to resume sebsequent iteration.

Number of objects for the server to search. Zero returns all.

Number of objects searched.

Pointer to the object along with any requested attribute values in search.

Removing an Object from NDS

void RemoveObject(NWDSContextHandle con)
{
	 NWDSCCODE       cCode;
	 int     x;

	 printf("Removing Objects.\n");
	 for(x = 0; x < 5; x++)
	 {
	 	 if(Names[x] != NULL)
	 	 {
	 	 	 if((cCode = NWDSRemoveObject(con, New[x])) != 0)
	 	 	 {
	 	 	 	 printf("Remove Object failed:%d\n", cCode);
	 	 	 }
	 	 	 else
	 	 	 {
	 	 	 	 printf("Object Removed.\n");
	 	 	 }
	 	 	 free(Names[x]);
	 	 	 free(New[x]);
	 	 }
	 }
}

// removes the new class
void RemoveClass(NWDSContextHandle con)
{
	 	 	 NWDSCCODE       cCode;
printf("Removing Class.\n");
	 //delay(5000);
	 if((cCode = NWDSRemoveClassDef(con, "Test:Bookmark")) != 0)
	 {


	 	 printf("Error removing class:%d\n", cCode);
	 }
	 else
	 {
	 	 printf("Class removed successfully.\n");
	 }
}


//  removes the attributes
void RemoveAttribute(NWDSContextHandle con)
{

	 NWDSCCODE       cCode;

	 printf("Removing Attribute Definitions.\n");
	 //delay(5000);
	 if((cCode = NWDSRemoveAttrDef(con, "Test:Profile Name")) != 0)
	 {
	 	 printf("Remove code:%d\n", cCode);
	 }
	 else
	 {
	 	 printf("Test:Profile Name successfully removed.\n");
	 }

	 if((cCode = NWDSRemoveAttrDef(con, "Test:Bookmarks")) != 0)
	 {
	 	 printf("Remove code:%d\n", cCode);
	 }
	 else
	 {
	 	 printf("Test:Bookmarks successfully removed.\n");
	 }
	 if((cCode = NWDSRemoveAttrDef(con, "Test:Modify Class")) != 0)
	 {
	 	 printf("Remove code:%d\n", cCode);
	 }
	 else
	 {
	 	 printf("Test:Modify Class successfully removed.\n");
	 }

}

NWDSRemoveAttrDef deletes the specified object from the specified location.

Clean-Up

Graceful NLM Demise

(This section is a reprint of a January 1996 Bullets article by Adam Jerome.)

In most environments, the program is finished when it runs as it should. However, an NLM is not finished until it can terminate successfully. Here are some rules to consider when writing an NLM.

Rule One: "Everyone put away your own things when you are finished with them."

If your NLM application allocates a resource, it must free it when no longer needed; but this rule does not stop there. If an NLM thread allocates a resource, it must free it when no longer needed.

Some NLM developers attempt to write an all purpose "clean up" procedure. Such a procedure is inherently very difficult because it attempts to free resources that it did not allocate. Don't do this. Having each thread free its own allocated resources is much more efficient.

Rule Two: Implement a signal(SIGTERM) handler.

Users can attempt to unload your NLM using the UNLOAD command. Your NLM must be prepared for this event. The best way to handle this is to use the signal() function to implement a SIGTERM signal handler. Then, do not allow your SIGTERM handler to "return" until your main() thread, and any other NLM threads, have terminated.

When I write an NLM, I instinctively create two global integer variables; NLM_exiting and NLM_threadCnt. Initially, NLM_exiting is set to FALSE (0), and NLM_threadCnt is set to zero (0). The first statement in my NLMs main() is ++NLM_threadCnt. The last statement before my NLM main()'s final "return" statement is --NLM_threadCnt. Further, the NLM_exiting variable is monitored by all loops within my NLM. If NLM_exiting is ever true, all threads free any allocated resources and then self terminate. Given this, my NLMs SIGTERM signal handler looks something like the following:

void NLM_SignalHandler(int sig)
{
switch(sig)
{
case SIGTERM:
NLM_exiting = TRUE;
while(NLM_threadCnt != 0)
ThreadSwitchWithDelay()
break;
}

return;
}

Many software engineers are tempted to use the AtUnload() or atexit() functions instead of a SIGTERM signal handler. Their success in doing so is somewhat limited. When NetWare executes the handler for these functions, all of the NLM's threads have already been summarily terminated, whether or not they have had a chance to clean up their resources. To be blunt, I do not use AtUnload() or atexit() in my NLMs.

Note that your SIGTERM handler will be executed by an OS Thread. (OS threads cannot take advantage of most of the functions offered in the NLM SDK.) In fact, this OS thread is the one and only Console Prompt thread. This thread is the one that accepts and executes commands from the file server's Console screen (or the colon prompt). When your NLM's SIGTERM handler captures control of this thread, it has literally captured the command prompt. Understand that the console command prompt will not be available again until your SIGTERM signal handler releases it. It is also important to note that your SIGTERM signal handler must not destroy this thread. Doing so would be destroying the console command prompt. Therefore, do not call exit() from your SIGTERM handler.

At times, your SIGTERM handler may need to call NLM SDK functions that require a full CLIB thread context. It is possible to borrow a CLIB context for the SIGTERM handler's OS thread, effectively converting it to CLIB Thread. To do this, you must first have the thread group ID of another an active thread within your NLM. Your NLM's main() thread is generally suitable for this purpose and you can get its thread group ID by calling GetThreadGroupID() and storing this ID in a global variable (such as NLM_mainThreadGroupID). Then your SIGTERM handler can temporarily assume the same context as your main() thread by calling the SetThreadGroupID() function. Before calling SetThreadGroupID() in your SIGTERM handler, call GetThreadGroupID() to obtain the handler's original context. You must restore the SIGTERM handler's original context before returning. The code below shows an example main() and SIGTERM handler that demonstrates the above verbiage.

	 	 	 int NLM_mainThreadGroupID;
int NLM_threadCnt = 0;
int NLM_exiting = FALSE;

void NLM_SignalHandler(int sig)
{
int handlerThreadGroupID;
switch(sig)
{
case SIGTERM:
NLM_exiting = TRUE;

handlerThreadGroupID = GetThreadGroupID();
SetThreadGroupID(NLM_mainThreadGroupID);

/* NLM SDK functions may be called here */
while(NLM_threadCnt != 0)
ThreadSwitchWithDelay();
SetThreadGroupID(handlerThreadGroupID);
break;
}
return;
}
void main(void)
{
++NLM_threadCnt;

NLM_mainThreadGroupID = GetThreadGroupID();
signal(SIGTERM, NLM_SignalHandler);

/* Body of main continues here... */

--NLM_threadCnt;
return;
}

Rule Three: Be aware of code that might be blocked or suspended when UNLOADed.

Assume that the body of main() above includes a statement such as getch(). The getch() function blocks (or suspends) the thread's execution until a character is received from the keyboard. It is highly probable that the console operator will attempt to UNLOAD our NLM while it is waiting for keyboard input. The SIGTERM handler is waiting for main() to decrement the NLM_threadCnt value to zero before proceeding, and main() will not do that until it receives a character. Therefore, it will appear to the console operator that the System Console screen is "hung" and that your NLM will not unload as requested.

The SIGTERM handler is responsible for waking up any blocked or suspended threads so that they can become aware of the NLM_exiting value. (Obviously then, it is the responsibility of each thread to check the NLM_exiting value as often as appropriate). The SIGTERM handler can help wake up a thread blocked on the getch() function by calling the ungetch() function, stuffing a character into the keyboard buffer. This will be read out of the keyboard buffer by the blocked getch() and execution can proceed.

Other blocking functions you should watch out for include gets(), t_snd(), NWSList(), NWSMenu(), SuspendThread(), delay(), etc.

Rule Four: Don't forget child threads and call-back routines.

As shown in the sample code, the first thing main() should do is increment the NLM_threadCnt; and the last thing is to decrement the value. If the NLM calls BeginThread(), or similar functions, the spawned thread should also increment and decrement the NLM_threadCnt just as main() does.

If your NLM sets up other call-back routines, each call-back routine must also increment and decrement the NLM_threadCnt. Call-back functions might include functions specified by NWAddFSMonitorHook(), NWRegisterNCPExtension(), RegisterForEvent(), etc.

Rule Five: Allow your NLM to terminate normally if appropriate.

Just as your SIGTERM handler waits for the NLM_threadCnt to go to zero, your main() thread should never terminate until the NLM_threadCnt is one (i.e.: only main is still running). This allows any thread in your application to shut down the NLM by setting NLM_exiting to true. This also forces main to stay alive until all other NLM threads have terminated. For example:

int NLM_mainThreadGroupID;
int NLM_threadCnt = 0;
int NLM_exiting = FALSE;

void NLM_SignalHandler(int sig)
{
int handlerThreadGroupID;
switch(sig)
{
case SIGTERM:

NLM_exiting = TRUE;

handlerThreadGroupID = GetThreadGroupID();
SetThreadGroupID(NLM_mainThreadGroupID);

/* NLM SDK functions may be called here */
while(NLM_threadCnt != 0)
ThreadSwitchWithDelay();

SetThreadGroupID(handlerThreadGroupID);
break;


case SIGINT:
signal(SIGINT, NLM_SignalHandler);
break;
}
return;
}

void main(void)
{
++NLM_threadCnt;

NLM_mainThreadGroupID = GetThreadGroupID();
signal(SIGTERM, NLM_SignalHandler);
signal(SIGINT, NLM_SignalHandler);

/* Body of main continues here... */

--NLM_threadCnt;
return;
}

You may also elect simply to call SetCtrlCharCheckMode() to disable CTRL-C's function.

Following the above rules will help your NLM find its way peacefully into that place where all good software goes after it has been duly executed. Help your NLM put its affairs in order so that it can rest peacefully and avoid an undue ABnormal END.

By now you should have a sound understanding of the basic concepts of NLMs and a clear understanding of NLM programming. Please visit the DNU Web site at: http://www.developer.novell.com/servlet/devnet/education/grafidx.html to go through this course and others like it. NLM courses will continue to be available through Developer Notes.

* Originally published in Novell AppNotes


Disclaimer

The origin of this information may be internal or external to Novell. While Novell makes all reasonable efforts to verify this information, Novell does not make explicit or implied claims to its validity.

© Copyright Micro Focus or one of its affiliates