Novell is now a part of Micro Focus

New Features of the Novell Kernel Services Programming Environment for NetWare 6 Programming

Articles and Tips: article

Russell Bateman
Senior Software Engineer
Server-library Development
rbateman@novell.com

01 Nov 2000


This AppNote is an update to a four-part series published last year in Novell Developer Notes, a statement of programming features and functionality of the Novell Kernel Services (NKS) programming environment for writing applications that run as NetWare-loadable Modules (NLMs) on NetWare.

Introduction

This is an update to a four-part series published last year (Novell Developer Notes, September--December 1999), a statement of programming features and functionality of the Novell Kernel Services (NKS) programming environment for writing applications that run as NetWare-loadable Modules (NLMs) on NetWare, Novell's network operating system. It could be named Part 5 of that series.

Part 1

http://support.novell.com/techcenter/articles/dnd19990905.html Explored the reasons for this programming environment in the NLM environment and examined in surface detail the various interfaces surfaced by NKS.NLM.

Part 2

http://support.novell.com/techcenter/articles/dnd19991004.html Focused more closely on the programming concepts in the Novell Kernel Services environment including discussions of multi-threaded programming correctness, latency and thread cancellation issues, and synchronization.

Part 3

http://support.novell.com/techcenter/articles/dnd19991103.html Finished the concepts started in part two by covering the remaining programming interfaces for writing NLMs including the NKS virtual machine, memory management, platform-dependent and debugging features, file and directory I/O, and time-outs.

Part 4

http://support.novell.com/techcenter/articles/dnd19991202.html Discussed how LibC sits atop NKS and made comments about programming at both levels. It also made a few remarks about differences in programming between LibC and CLib.

Part 5

This article exposes new features that have been added since the original series was published.

NKS for NLM Programming

In "The Future of Application Development on NetWare with NLMs" (Novell Developer Notes, Sept. 1999, p. 27, http://support.novell.com/techcenter/articles/dnd19990903.html), I explained how the ten-year-old CLib programming environment had reached a decision point in its attempt to continue to support existing NLMs coded to it and at the same time move forward to embrace support for future multi-threaded programming technologies. I also explained the reasons why we have frozen CLib in its current state (plus bug fixes) and instead moved ahead with a new environment based on Novell Kernel Services (NKS) plus a standard C library environment (LibC) loosely referred to as NKS/LibC.

Therefore, in the kick-off article that preceded this series on features ("The Future of Application Development on NetWare with NLMs"), we laid the foundation for a new API technology for NetWare. The main points were:

  • CLib had fundamental problems in supporting multi-threaded code in such a way that it scale across multiple processors. To remedy this problem would be to cease support for most NLMs ever coded to CLib. This would, of course, be unacceptable.

  • NKS offers the API power to interface with NetWare 5's multiprocessing kernel (MPK) and is the micro-kernel interface on Modesto, Novell's next- generation platform.

  • NKS offers the portability from NetWare to Modesto and, perhaps, toward other platforms endowing NLM programming with more potential for use elsewhere. NKS is the C binding to the Modesto platform.

Porting projects and the desire to keep the playing field between NetWare and Modesto as level and as functional as possible have forced us to deal with issues such as launching other applications and interprocess communication (IPC). We might have dealt with these issues in LibC, but the solutions defied using existing standards and we don't create the standard C runtime on some platforms where we might hope to take NKS in the future. Some APIs have been added and some have been changed when we thought that the NKS development community would clearly benefit.

Strings in NKS

Originally, we had decided that all strings in NKS would be in Unicode, which we thought to be a forward-looking strategy. By default this is still true, but we found that we could easily and usefully enhance all interfaces to accept either Unicode or ASCIIZ. We decided to add a flag to the NLM linker that will instruct NKS to use ASCIIZ strings in place of Unicode. The absence of any flag will default the strings expected by any NKS function that accepts a string to Unicode.

We created a new interface that yields information on what sort of strings are in use for the virtual machine (VM).

#include	 <nks/vm.h>

typedef enum	 	 	 { NX_STR_UNICODE, NX_STR_ASCII } NXStrType_t;

int NXVmGetStringType( NXStrType_t *type );

It will not be possible to change string types mid-stream. If an application is written as a server to heterogeneous clients (from a string point of view), it will be necessary to translate and there are functions available for this in a new Unicode library. The linker flag that will make the application use ASCII instead of Unicode strings is

0x02000000   ASCII_STRINGS

and can be set in CodeWarrior, for example, via the statement

FLAG_ON 0x02000000

As already noted, if this flag is not set explicitly, Unicode strings will be assumed by the library. All string-accepting interfaces in NKS have been changed to use void* instead of unicode_t*. This way, char* can be passed just as easily.

Virtual-machine Management

Because of a lack of application or process containment in NetWare, NKS created the virtual machine (VM) concept for hosting applications. The best programming model is to remain within NKS and its virtual machine. However, using foreign (non-NKS based) services is a frequent need in NLM programming. As a consequence, some transition handling is necessary. This was explained in Part 2 of this series ("Features of the NKS Programming Environment for NLMS: Part 2," Novell Developer Notes, October 1999, p. 43 http://support.novell.com/techcenter/articles/dnd19991004.html#l12pwjh) and involves the use of some NetWare-specific interfaces, nxCancelEnable and nxCancelDisable.

However, some applications, a good example of which might be a Web server, find a need to launch still others (or another copy of themselves) and communicate with them. NKS has been enhanced to deal with this instead of relying on the host platform's answer to fork/exec, which will always be very different depending on the platform. We decided to incorporate an NKS-supported method of spawning applications instead of looking to LibC for this feature. We had advertised that we would leave this to the higher-level library, ("Features of the NKS Programming Environment for NLMS: Part 3," Novell Developer Notes, November 1999, p. 35, second paragraph http://support.novell.com/techcenter/articles/dnd19991103.html#u12chou), but found that we were looking at too much variance in the way this would be done when moving from NetWare to Modesto let alone to other environments.

Spawning Applications

A new application may be spawned in another NKS virtual machine workspace. On NetWare, this is implemented in a separate, ring-3 address space if the caller is loaded in a protected address space itself. If spawned from the kernel (from a ring-0 NLM), it will be loaded in the kernel and the actual containment boundary will be as ineffectual as for any other pair of NLMs.

Spawning another application is done using NXVmSpawn:

#include <nks/vm.h>

#define NX_VM_CREATE_DETACHED

typedef struct
{
	 NXDHandle_t parent;
	 void	 	 	 	 *name;
} 	 NXNameSpec_t, NXStreamSpec_t;

typedef struct
{
	 int 	 	 	 	 	 argc;
	 void 	 	 	 	 	 **argv;
	 void 	 	 	 	 	 **env;
	 NXStreamSpec_t std_input;
	 NXStreamSpec_t std_output;
	 NXStreamSpec_t std_error;
} NXExecEnvSpec_t;

int NXVmSpawn( NXNameSpec_t *name, NXExecEnvSpec_t
	 	 	 *envSpec, unsigned long flags, NXVmId_t *newVm );

Calling NXVmSpawn is accomplished with a rather complete specification of the start-up environment of the new application including its arguments, passed as Unicode or ASCIIZ (depending on the application's native string type as already discussed), an environment and also passed as an array of pointers to strings. The new application's standard input, output, and error console wirings may be completely specified as suggested by the data structure. These may be files, the parent's console if the new application is launched as joinable, or the application's own console.

Without specifying env, the new application launched will simply not have any environment variables unless and until it makes use of environment variables, at which time it will merely get a copy of those currently active in the system (and therefore not necessarily those of the invoking application). A call to NXGetEnv, NXSetEnv, etc., or their more well-known LibC equivalents, is required before an application's environment is actually instantiated.

By default, applications are launched "joinable" just as by default a new thread is created "joinable." When the new application is joined via NXVmJoin, the calling thread blocks until that application finishes and unloads yielding a status. NXVmExit now takes an argument (it didn't before). This eliminates the need for an application to work through the C library or through some platform- specific mechanism to wait upon another application when application life is supposed to be known to and controlled by NKS and its concept of virtual machine.

#define NX_VM_JOIN_ANY
 void  NXVmExit( int status );
 int     NXVmJoin( NXVmId_t wait_for, NXVmId_t
 *departed_vm,
   int *status );

The define NX_VM_JOIN_ANY permits the calling application to wait for any of its (presumably more than one) applications, but not for any arbitrary application. Only the applications formally known to the waiting application by virtue of the newVm argument in NXVmSpawn can be joined and these values may not be communicated between applications. As soon as one of the joinable applications known to the caller finishes, its status is returned as well as its identity.

We are purposely tip-toeing around the notion of parent-child here. You may think of an application spawned by another as the latter's child if you wish, but nothing of the POSIX baggage of f ork/exec is present. File descriptors aren't shared and signals between the two aren't possible. For this reason, we refrain from using this terminology in our implementation.

It is also possible to terminate another application whose VM identity is known. The intent of the VM identity is that it be known only to the spawning application rather than being communicated via some means outside to NKS. Despite NXVmGetId, we provide no sanctioned discovery method and don't make any particular claims to behavior in the case where the identity is discovered and used by other than the spawning application.

int NXVmDestroy( NXVmId_t id );

By the way, NKS expects all strings in a certain format from an application. If argv and env are built using ASCIIZ strings, but the application being spawned requires Unicode, NKS will translate all strings into the new environment. So it is not necessary that the spawner know the string format of the spawnee or that the two even have identical string formats.

File and Directory I/O

The concept behind file and directory I/O for NKS was that the interfaces be portable, that is, common to all platforms, inherently multi-threadable (atomic) and succinct (reduced semantics).

There have been two important changes in file and directory I/O for NKS, neither of which are so intrusive as to require more than the simplest coding changes. The first change has already been described, that of softening the interfaces to accept ASCIIZ or Unicode strings indiscriminately. The path arguments of all the functions have been modified to void* or const void* so that they will readily accept char* or unicode_t*. (As expected, we find that neither Watcom nor CodeWarrior complain about this regardless of how high we set the compiler warning flag, however, some care will doubtless be necessary in using C++ string classes with these calls.)

The second, more substantial change has been to overload the file I/O functions to accomplish more than just file I/O. In order to support interprocess communication (IPC), we've instantiated the notion of named pipes (or FIFOs) directly in NKS. We've also thought about other application aspects we could overload on these APIs, like device I/O. So, we removed the suffix File from the file I/O signatures more or less shortening their names to the relevant, bare verbs:

#include <nks/fsio.h>

int	 NXClose(NXFHandle_t fileHandle);
int	 NXCreate(NXDHandle_t dirHandle, const void
*pathname,
	 	 NXMode_t mode, NXFHandle_t *fileHandle);
int	 NXOpen(NXDHandle_t dirHandle, const void
*pathname,
	 	 NXMode_t mode, NXFHandle_t *fileHandle);
int	 NXRemove(NXDHandle_t dirHandle, const void
*pathName);
int	 NXRename(NXDHandle_t dirHandle, const void
*oldname,
	 	 const void *newname );

These are distinguished from their directory counterparts by the latter retaining Dir as the object of the verb in the signature:

int  NXCloseDir(NXDHandle_t dirHandle);
 int  NXCreateDir(NXDHandle_t dirHandle, const void
     *pathname, NXDHandle_t  *newDirHandle);
 int   NXOpenDir(NXDHandle_t dirHandle, const void
 *pathname,
     NXDHandle_t *newDirHandle);
 int  NXRemoveDir(NXDHandle_t dirHandle, const void
     *pathName);

(Remember, NXOpenDir creates a new directory handle and NXCreateDir creates an actual directory in the file system.)

Because a few users have already coded to the original file I/O signatures, we've temporarily added some macros to permit them to continue on without being required to rename them:

#define      NXCreateFile NXCreate
 #define   NXOpenFile   NXOpen
 #define     NXRemoveFile NXRemove
 #define   NXRenameFile NXRename

File System and FIFO Roots

We found that we had some trouble "priming the pump" when writing to these interfaces and were going to leave to the library on the individual platform the solution to this problem. On NetWare, as noted in Part 3, page 41 (http://support.novell.com/techcenter/articles/dnd19991103.html#u12chou), this was going to be nxMakeDirhandle. However, the unification of the path name space between files, FIFOs and other, future types of I/O induce us to provide for discovery. To support entry to the name spaces, we've added definitions for those we want to support, including the console root which NXVmSpawn might consume in wiring a spawned application:

#define      NX_FILESYS_ROOT
 #define     NX_CONSOLE_ROOT
 #define     NX_FIFO_ROOT

Clearly, all problems are not solved when one contemplates the open-ended nature of NetWare with respect to file system support (near infinite volumes, infinite remote NetWare file systems, the DOS partition, etc.). It was the case, as described on page 40 of Part 3 of this series that 0 would be a valid directory handle in combination with a full path. Instead, NX_FILESYS_ROOT should be passed plus a full, perhaps UNC, path perhaps the now old-fashioned NetWare server-name, volume and path, perhaps just a known volume and path on the local server, etc. In this way, the pump is easily primed. The default on NetWare is volume SYS:. Thus, for an NLM to open a file on the system volume, it is as easy as this:

{
     NXMode_t       mode = NX_RDWR;
     NXFHandle_t         fileHandle;
     NXOpen(NX_FILESYS_ROOT, "System\\myfile.txt", mode,
          
fileHandle);

This would open "myfile.txt" in the system directory of the main, system volume ("SYS:\System\"). To the same name in a different directory "Users" on another volume, say "VOL1," we would need a volume-anchored path ("Vol1:\Users\ myfile.txt") or, if we were going to do much I/O there, we might create a directory handle as here:

{
     NXDHandle_t vol1Handle;
     NXOpenDir(NX_FILESYS_ROOT, "VOL1:\\Users",
 
vol1Handle);
     NXOpen(vol1Handle, "myfile.txt", mode, 
fH);

This is little different than before, except that experience has made us feel more at ease with how to do this in NKS. Here is a restatement of the two tables from the earlier part in this series.


When the directory handle indicates. . .
the name consists of. . .

nothing because it is NX_FILESYS_ROOT

the full path including the file name*

the immediate parent directory

only the file name

an arbitrarily-distant parent directory

the rest of the path including the file name


When the directory handle indicates. . .
the name consists of. . .

nothing because it is NX_FILESYS_ROOT

the full path including the directory name*

the immediate parent directory

only the directory name

an arbitrarily-distant parent directory

the rest of the path including the directoryname

the very directory itself

nothing; it is nil

Note: * It is useful to repeat that, while NetWare programming permits access of multiple volumes and remote NetWare file systems with multiple volumes, by default, the local system volume (SYS:) is supported as noted in the first of the two examples without prefixing the path with "SYS:" whereas other volume and other server paths require full specification, again, as demonstrated in the second example. This default brings the majority of applications in line with expectations on Modesto, where only one file system will exist and where remote (via NetWare-core Protocol) file system access will not.

FIFO System Roots

To create a FIFO, the FIFO root define need only be passed along with a name to NXCreate or to NXOpen just as for a file. FIFO names don't actually live in the file system. Their name space is flat and their names can be very long (up to 255 characters). They are valid across all NKS virtual machines though they are not preserved across boots. Applications are free, therefore, to create any sort of naming convention and, within the length of the name, pseudo-directory structure they wish.

For example, "WebServe/StreamPipe" might be a valid name that a Web server could open and use. Any other application that the Web server might launch in a separate VM might know this pipe by name, open and use it in cooperation with the "parent" application memory. The ends of the FIFO can be opened for read or write but not both. If both are needed, two separate FIFOs must be created. While it would also be possible for the creating application to open both ends, NKS does not guarantee the FIFO to be as efficient a means of inter-thread communication as merely sharing memory.

Directory Searching

We modified the rather difficult interface to NXSearchDir, breaking it into two pieces to make it easier to implement for us and somewhat less ambiguous to call as well.

#include <nks/fsio.h<< int NXSearchDirInit( NXDHandle_t dirHandle, const void<     *pattern, NXSeqCookie_t *sequence );< int NXSearchDir( NXSeqCookie_t *sequence, int entryCount,<     NXDirEntry_t entriesBuffer[], void *xEntriesBuffer,<     NXBufSize_t xEntriesBufferSize, int *entriesFound,<     int *entriesLeft );

Environment Variables

By reason of arranging for one NKS application to spawn another, NKS needed to begin providing for a complete environment including variables. The interfaces are simple ways to get an existing variable, set a new one (including overwriting an existing by the same name), and two functions exist for discovery.

#include <nks/env.h<< int     NXCopyEnv( void *env[], int length );< int     NXGetEnv( const void *name, void **value );< int   NXGetEnvCount( void );< int    NXSetEnv( const void *variable, const void *value, int<     overwrite );

Discovery is useful prior to spawning a new application that should have the same or, at least, partly the same environment as the parent. Calling NXGetEnvCount reveals how many variables exist at the time of the call. Obviously, this value is meaningless if the calling application has more than one thread and makes concurrent calls to NXSetEnv. After getting an idea of how many variables, NXCopyEnv can be called, with an appropriately-sized array of string pointers, to create a copy of the environment suitable for passing to NXVmSpawn.

Relationship of New Functions to LibC

This was the topic of the final (Part 4) of the article series last Fall. What has happened to NKS since then is that some important functionality that had been promised by LibC was pushed down into NKS because it became clear that applications written to NKS for NetWare would port badly to Modesto or other platforms if we didn't add these important functions. It also placed responsibility for these important bits of functionality where they could be best managed. We no longer need to solve the problem of missing fork/exec on every platform since it is solved uniformly in NKS.

Obviously, there are pieces of LibC that will sit directly atop NKS as noted previously in the article last Fall. LibC's getenv will call its NKS counterpart in the implementation of LibC.NLM. Other pieces will not find higher-level equivalents in LibC because any POSIX functions that might be logically based on them won't be implemented since with POSIX come POSIX expectations: we cannot easily support many POSIX semantics on NetWare. Even all the possible semantics of simple functions like open aren't supported. More difficult ones like mmap are very undoable. Therefore, our intent has been to find a more neutral, perhaps lowest- common denominator solution across platforms.

Sample Code

We have written some example code for common operations in NKS and published this code with the individual NKS function documentation on the Web in the SDK. These examples are continually updated as needed. The URL for the SDK is http://developer.novell.com/ndk.

Conclusion

These are the new and modified interfaces in Novell Kernel Services (NKS) with details especially oriented toward NetWare programming since that is the main thrust of the article series. Nevertheless, the details hold for all platforms since the NKS Feature Team represents Modesto as well.

* 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