Novell is now a part of Micro Focus

How to Port an ANSI C/POSIX Application to NKS/LibC

Articles and Tips: article

Ulrich Neumann
Technical Director
GNE - Grebe Neumann Gliwa GmbH, Germany
Novell DeveloperNet SysOp
u_neumann@gne.de

01 Nov 2002


Thanks to Russel Bateman, the LibC Team, and the Developer Support Team for their cooperation and support in writing this AppNote.

Previous AppNotes about NKS and LibC on NetWare describe the goals of LibC, such as threading and virtual machines, and provide a description of header files. This AppNote describes the differences in this library compared to ANSI C, POSIX, and other UNIX systems. This is very useful if you want to port your software to LibC.


Topics

application development tools, NLM development, server applications

Products

NetWare 5.1, NetWare 6

Audience

developers

Level

intermediate

Prerequisite Skills

familiarity with C programming language

Operating System

NetWare 5.1, NetWare 6

Tools

MetroWerks CodeWarrior 7.1 with PDK4

Sample Code

yes

Introduction

Several years ago, Novell made the decision to develop a new library for developers. This library covers multi-processing, threading, and a containment model, and it is as close as possible to ANSI C, POSIX, and other standards such as BSD, SVID, UNIX95, and UNIX98 (as well as "de facto standards" often used in open-source projects).

Several Developer Notes and AppNotes have been written which describe aspects of LibC. During the years LibC has been changed several times to deliver new functionality or to better serve developers. Here is a list of the most important articles:

Structure of the LibC SDK

The LibC SDK can be downloaded at http://developer.novell.com/ndk/libc.htm. By default the SDK is installed at C:\NOVELL\NDK\LIBC with the following directories: imports, include, lib, and tables.

  • imports - Contains object files, that need to be linked in your application, along with several import files for different compilers listed in the following table.


    Files in imports directory
    Explanation

    libcpre.o

    Prelude object file for MetroWerks CodeWarrior Compiler

    libcpre.obj

    Prelude object file for Watcom Compiler

    libcpre.gcc.o

    Prelude object file for GNU Compiler

    libcaux.lib

    Prelude file if you want to use your NLM on NetWare 5.1SP4

    libc.ali

    Import file for Watcom Compiler

    libc.wmp

    Import file for Watcom Compiler

    libc.imp

    Import file for MetroWerks CodeWarrior and GNU Compilers

    netware.imp

    Import file for MetroWerks CodeWarrior and GNU Compilers

    ws2nlm.imp

    Import file for MetroWerks CodeWarrior and GNU Compiler

  • include - Contains the C header files for all compilers.

  • lib - Contains the libraries as NLMs in a debug version. Be sure to copy these files to your local C:\NWSERVER directory if you want to use them.

  • tables - Contains several language and codepage specific files.

Compilers for NetWare

Today there are several compilers available for NetWare development. The most popular ones are listed in the following table.


Compilers for NetWare
Explanation

MetroWerks CodeWarrior

A commercial compiler with a build in source level debugger. For more information see http://www.metrowerks.com.

Watcom

A free compiler and the first compiler that supported NLM development. It is impossible to build LibC C++ Applications with Watcom. For more information see http://www.openwatcom.org.

GNU C/C++

A widely used compiler on many UNIX/LINUX systems and now supported to build NetWare NLMs. For more information see http://gcc.gnu.org.

Hello World with CodeWarrior

The following "Hello World" code helps to describe the settings, how to use the prelude, import and other files, along with how to prepare an NLM for debugging.

#include <stdlib.h>     /* enable standard C library */
#include <nksapi.h>     /* enable all NKS specific functions */
  
int
main (int argc, char **argv)
{
    printf ("Hello World");     /* Output to the actual screen */
    consoleprintf ("Hello World");  /* Output to the system console */
}

Let us create a new project in the IDE.

  1. To create this project, choose File > New. You will see the screen shown in Figure 1.

    Figure 1: Creating a new project in the IDE.

  2. Give your project a new name and a location where your files will be stored. In this example, the project is called "HelloWorld" and is stored at C:\HelloWorld.

  3. Choose "NetWare Stationery" and click on OK.

  4. The next window (see Figure 2) gives you the choice of a template. A template is a small project with all the settings predefined to make life a little bit easier.

    Figure 2: Project stationery or template.

    There are five different templates you can use for NKS/LibC:


    Template
    Explanation

    Consumer Application

    Template for a C Application that uses a library NLM

    Generic C

    Template for a C Application

    Generic C++

    Template for a C++ Application

    Library

    Template for a C Library Application

    UI with NUT

    Template for a C Application with Novell's "blue" style user interface (NWSNUT)

    Most of the time you will use Generic C or C++.

  5. In the next screen (see Figure 3), you need to change several things in order to build up-to-date applications.

    Figure 3: The HelloWorld.mcp window.

    Here are the things you need to change:

    • Delete mwcrtl.lib, mwcrtld.lib, and libcpre.o from your project directory. Mwcrtl.lib and mwcrtld.lib are MetroWerks files which you don't need to build a C application. Libcpre.o handles the startup and shutdown of your application. This is an old version that came with CodeWarrior; you need to use the latest one that is shipped with the NDK. Add the latest one to your project.

    • If your NLM must work on NetWare 5.1SP4, you need libcaux.lib from the NDK as well.

  6. Select the Target Settings and check the following, as shown in Figure 4.

    Figure 4: Making the Target Settings.

    The linker must be NLM Linker. The name of your target can be anything you want.

  7. In the next screen (see Figure 5), check your system paths. You only need access paths to the NDKs include and imports directories.

    Figure 5: Paths to the include and imports directories.

  8. If you specify the system access paths as shown in Figure 5, you can remove this environment variable. If your code needs to be compilable on different systems, it may be better to use the environment variable NDK in the system access paths., as shown in Figure 6.

    Figure 6: The Source Trees settings.

  9. In the NLM Target screen (see Figure 7), specify the name of your NLM and the stack size. The stack size is very important. If your stack size is too small your server may crash. Good values are 32767 or 65535. The default is set to only 16384 bytes.

    Figure 7: Setting the NLM Target.

  10. LibC can build "multibyte aware" applications. If you want to make sure that CodeWarrior builds MultiByte-aware code, you must mark the checkbox as shown in Figure 8.

    Figure 8: The Language settings.

    Sometimes it is necessary to check "Relaxed Pointer Type Rules". In this case the compiler will take care of many typecasts.

    Some other commonly-used options are "Use Unsigned Chars" and "Enums Always Int". Whether or not you use these depends on the project you are working on.

  11. In the next screen (see Figure 9), choose "Pentium" as your target processor. LibC NLMs will run only on NetWare 5.1 and NetWare 6 or above, and these operating systems only run on Pentium-type machines or better.

    Figure 9: The Code Generation settings.

    Make sure Byte Alignemnt is set to 4 or 1. That means that every number stored in memory is stored in a boundary of 4 or 1 bytes.

    Don't use MMX! There is a bug in CodeWarrior that could result in curious compiler failures.

    If you need debug information on NetWare, only use CodeView Format. SYM doesn't make any sense on NetWare.

    Don't use "Inline Intrinsic Functions". These functions may abend your application because they avoid the use of LibC functions. Some MetroWerksMetroWerks functions will be used instead.

  12. In the next screen (see Figure 10), you make the NLM Linker settings.

    Figure 10: The NLM Linker settings.

    LibC NLMs must be marked preemptive with the "PseudoPreemption" flag. They will not load without this flag set.

    Always use "Allocate uninitialized Data in File". This is different on other platforms.

    If you need a version you can debug, check "Generate Internal Debugger Records" and "Generate CV SYM File". In addition, you can select "Generate Link Map" to generate a text file that helps to find a function if you have a lot of source files.

Common Porting Problems and Solutions

Alignment and the Type "bool"

Alignment is the way a data type is stored in memory and which size the different data types have. NetWare stores its numbers with a boundary of 4 bytes. This is the reason why we set the byte alignment in the target to 4. This is similar to other POSIX systems.

There is only one difference. On most systems the type "bool" is defined as a char; on NetWare it is an int. To solve this problem, you must define the type "bool" yourself before any header file is included. Here's an example:

#define bool char       /* needs to be defined before any header will be included */
 
#include <unistd.h>
#include <errno.h>

In stdbool.h there is a check to see if bool is already defined. If so, it will not be redefined.

All other types have the following alignments on NetWare:

#define ALIGNOF_SHORT 2
#define ALIGNOF_INT 4
#define ALIGNOF_LONG 4
#define ALIGNOF_LONG_LONG_INT 4
#define ALIGNOF_DOUBLE 4
#define MAXIMUM_ALIGNOF 4

This is one of the most hidden problems if you port open-source software to NetWare because you don't see any error messages. Your application just crashes if it depends on alignments.

POSIX Style Path Names

UNIX pathnames use forward slashes, whereas NetWare normally uses backslashes. If you use getcwd, argv[0], or other functions, you will always see results like this: SYS:\SYSTEM\MYAPP.NLM . POSIX systems expect it in this way: sys:/system/myapp.nlm .

Be sure to check every place in your code where strings with path names are compared or otherwise computed. LibC has no problem if you use POSIX style paths in file or directory operations like open() or rename() .

Auto Close a User Defined Screen

If you use your own screen (that means that you have a separate screen that you can access with <Ctrl>-<Esc> similar to Monitor or Apache), you will see that this screen will be closed as soon as your application exits. Sometimes you don't want this behavior, like if you want to show a help screen with a command such as "MYAPP -help", for example. To avoid this problem use:

setscreenmode(0);

This will ask you to close the screen after your application has finished.

Here is a small code snippet that will solve this problem. This code doesn't auto- close the screen if you use "-help", "-?", "-version", or "-V".

if (argc > 2)
{
if (strcmp(argv[2], "--help") == 0 || strcmp(argv[2], "-?") == 0 ||
   strcmp(argv[2], "--version") == 0 || strcmp(argv[2], "-V") == 0)
       {
        setscreenmode (0);
       }
}

Config.h File for NKS/LibC

Open source software normally uses autoconf to check what is available on the target platform. NetWare doesn't have a POSIX shell to run autoconf. To solve this problem, here is a config.h file for NKS/LibC:

/*************************************************************************
$name: config.h
$version: 1.02 
$date_modified: 090402
$description: config.h for NetWare LIBC.
$owner: Ulrich Neumann, Germany BSD License
**************************************************************************
This config.h file was developed during porting of several open source
projects to NetWare.
Based on LIBC NDK May 2002
**************************************************************************/
#ifndef CONFIG_H
#define CONFIG_H
/* Definition of available LibC headers */
#define HAVE_ENDIAN_H 1       /* Set to 1 if you have <endian.h> */
#define HAVE_SYS_SELECT_H 1       /* Set to 1 if you have <sys/select.h> */
#define HAVE_DIRENT_H 1       /* Set to 1 if you have <sys/dirent.h> */
#define HAVE_UNISTD_H 1       /* Set to 1 if you have <unistd.h> */
#define HAVE_STRING_H 1       /* Set to 1 if you have <string.h> */
#define HAVE_LIMITS_H 1       /* Set to 1 if you have <limits.h> */
#define HAVE_STDARG_H 1       /* Set to 1 if you have <stdarg.h>  */
#define HAVE_SYS_UTSNAME_H 1     /* Set to 1 if you have <sys/utsname.h>  */
#define HAVE_SYS_IOCTL_H 1      /* Set to 1 if you have <sys/ioctl.h>  */
#define HAVE_GETOPT_H 1       /* Set to 1 if you have <getopt.h>  */
 
/* Definition of available LibC standards */
#define _POSIX_VERSION 1      /* Set to 1 if you have POSIX */
#define STDC_HEADERS 1       /* Set to 1 if you have standard C headers */
    
/* Definition of available LibC functions and macros */
#define HAVE_SNPRINTF 1        /* Set to 1 if you have snprintf() */
#define HAVE_SNPRINTF_DECL 1        /* Set to 1 if you have snprintf() */
#define HAVE_VSNPRINTF 1        /* Set to 1 if you have vsnprintf() */
#define HAVE_VSNPRINTF_DECL 1      /* Set to 1 if you have vsnprintf() */
#define HAVE_STRERROR 1        /* Set to 1 if you have strerror() */
#define HAVE_ISINF 1        /* Set to 1 if you have isinf() */
#define HAVE_GETHOSTNAME 1     /* Set to 1 if you have gethostname() */
#define HAVE_INET_ATON 1     /* Set to 1 if you have inet_aton() */
#define HAVE_FCVT 1        /* Set to 1 if you have fcvt() */
#define HAVE_RINT 1     /* Set to 1 if you have rint() */
#define HAVE_MEMMOVE 1      /* Set to 1 if you have memmove() */
#define HAVE_STRCASECMP 1        /* Set to 1 if you have sigprocmask() */
#define HAVE_MEMCHR 1        /* Set to 1 if you have memchr() */
#define HAVE_STRTOLL 1        /* Set to 1 if you have strtoll() */
#define HAVE_STRTOULL 1      /* Set to 1 if you have strtoull() */
#define HAVE_STRTOL 1       /* Set to 1 if you have strtol() */
#define HAVE_STRTOUL 1        /* Set to 1 if you have strtoul() */
#define HAVE_STRDUP 1        /* Set to 1 if you have strdup() */
#define HAVE_RANDOM 1     /* Set to 1 if you have random() */
#define HAVE_SRANDOM 1        /* Set to 1 if you have srandom() */
#define HAVE_DLOPEN 1        /* Set to 1 if you have dlopen() */
#define HAVE_STRSTR 1     /* Set to 1 if you have strstr() */
#define HAVE_STRNCASECMP 1        /* Set to 1 if you have strncasecmp() */
#define HAVE_STRPBRK 1       /* Set to 1 if you have strpbrk() */
#define HAVE_MKTIME 1        /* Set to 1 if you have mktime() */
#define HAVE_ISATTY 1     /* Set to 1 if you have isatty() */
#define HAVE_ATEXIT 1     /* Set to 1 if you have atexit() */
#define HAVE_SELECT 1     /* Set to 1 if you have select() */
#define HAVE_FDATASYNC 1      /* Set to 1 if you have fdatasync() */
#define HAVE_FDATASYNC_DECL 1      /* Set to 1 if you have fdatasync() */
#define HAVE_GETOPT_LONG 1     /* Set to 1 if you have getopt_long() */
#define HAVE_INT_OPTRESET 1      /* Set to 1 if you have the optreset */
  
/* Definition of available LibC data types and alignments */
/* Define if C++ compiler accepts "using namespace std" */
#define HAVE_NAMESPACE_STD 1
  
/* Set to 1 if type "long int" works and is 64 bits */
#define HAVE_LONG_LONG_INT_64 1
/* Set to 1 if you have the unsigned long long type. */
#define HAVE_UNSIGNED_LONG_LONG 1
/* Set to 1 if type "long long int" constants should be suffixed by LL */
#define HAVE_LL_CONSTANTS 1
/* Define this as the appropriate snprintf format for 64-bit ints, if any */
#define INT64_FORMAT "%lld"
 
/* Define Alignments */
#define ALIGNOF_SHORT 2
#define ALIGNOF_INT 4
#define ALIGNOF_LONG 4
#define ALIGNOF_LONG_LONG_INT 4
#define ALIGNOF_DOUBLE 4
#define MAXIMUM_ALIGNOF 4
   
#define bool char;
#include <unistd.h>
    
#endif /* CONFIG_H */

This file can be used as a prefix file. You can specify a prefix file in the target on the C/C++ Language settings. A prefix file will be included before a source file will be compiled.

fork() Replacement in NKS/LIBC

fork() is the biggest problem on NetWare. You can't clone a process with all the same file descriptors, the same stack, the same IO wiring, the same variables and others.If your application can be ported to a threading model, you're in good shape. NKS/LibC has NKS threads, POSIX threads, and UI threads. You have barriers, semaphores, rw locks, mutexes in all three threading models. If you go from a forked model to a threaded model, you must solve the problem of "global variables". Global variables are unique in every cloned process on POSIX systems, but if you use threads a global variable is the same for every thread. A solution to this can be very difficult. I've added a sample to the LibC NDK that gives a solution how you can switch variables between threads.

If you must find a way around fork, you can use NXVmSpawn. Here is an example that shows one way to avoid fork():

/***************************************************************************
$name: lcfork1.c 
$version: 1.1 
$date_modified: 080402 
$description: Demonstrates the use of NXVmSpawn to emulate simple fork/exec.
$owner: Ulrich Neumann, Germany, BSD License
*****************************************************************************
This code demonstrates the use of NXVmSpawn and how to use this function
to "emulate" fork known from POSIX systems.
   
The following POSIX code will be emulated (simple fork, no wiring):
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
 
#include <sys/exec.h>
int main
    (
    int argc,
    char **argv
  )
    {
    int noofchilds = 0;
  pid_t pid;
   
 pid = fork();
    switch (pid)
     {
        case -1:    // error
         printf ("Error forking child");
          exit (0);
    
     case 0: // child process starts here
         printf ("We are in the child\n");
            sleep (2);
           printf ("Child is shutting down\n");
         exit (0);
    
     default:    // parent process starts here
            noofchilds++;
            printf ("We are in the parent. New child has PID: %d\n", pid);
       }
    
 // parent code continues:
    sleep (3);
   printf ("Paent is shutting down\n");
 }
****************************************************************************/
    
/* ANSI C Standard header */
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
    
/* Novell NKS/LibC header */
#include <nksapi.h>
  
int main
(
  int argc,
    char *argv[]
)
 {
    int noofchilds = 0;
  pid_t pid;
   
 /* we need these additional variables for NXVmSpawn */
   int flag;
    NXExecEnvSpec_t forkEnvSpec;
 NXNameSpec_t forkNameSpec;
   
/* Begin code that is similar to switch... case 0 (child) */
  optind = 1;
  while ((flag = getopt(argc, argv, "p")) != -1)
       {
        switch (flag)
            {
            case 'p':
                /*
                * p - special flag passed if forked
              */
              printf ("We are in the child.\n");
               sleep (2);
               printf ("Child is shutting down.\n");
                exit (0);
                break;
           }
        }
/* End code that is similar to switch... case 0 (child) */
   
/* Begin code that is similar to fork() */
    /* 
   * Prepare everything for NXVmSpawn 
  * I am adding one argument that indicates that we are forked from the Parent 
    */
  argv[argc++] = (char *) "-p";
    
 forkEnvSpec.esFlags         = 0;
 forkEnvSpec.esArgc          = argc;
  forkEnvSpec.esArgv          = argv;
  forkEnvSpec.esEnv           = 0;
 forkEnvSpec.esStdin.ssType      = NX_OBJ_DEFAULT;
    forkEnvSpec.esStdin.ssHandle    = -1;
    forkEnvSpec.esStdin.ssPathCtx   = NULL;
  forkEnvSpec.esStdin.ssPath      = NULL;
  forkEnvSpec.esStdout.ssType = NX_OBJ_DEFAULT;
    forkEnvSpec.esStdout.ssHandle   = -1;
    forkEnvSpec.esStdout.ssPathCtx  = NULL;
  forkEnvSpec.esStdout.ssPath = NULL;
  forkEnvSpec.esStderr.ssType = NX_OBJ_DEFAULT;
    forkEnvSpec.esStderr.ssHandle   = -1;
    forkEnvSpec.esStderr.ssPathCtx  = NULL;
  forkEnvSpec.esStderr.ssPath = NULL;
  
 forkNameSpec.ssType         = NX_OBJ_FILE;
   forkNameSpec.ssHandle       = 0;
 forkNameSpec.ssPathCtx      = NULL;
  forkNameSpec.ssPath         = argv[0]; 
/* argv[0] is the name of the NLM itself */
    
 flag = NXVmSpawn(&forkNameSpec, &forkEnvSpec, NX_VM_SAME_ADDRSPACE, &pid);
/* End code that is similar to fork() */
    
/* Begin code that is similar to switch... case -1 */
 if (flag)
        exit(0);        // error
/* End code that is similar to switch... case -1 */
   
/* Begin code that is similar to switch... case default (parent) */
   noofchilds++;
    printf ("We are in the parent.\nThe new child has PID: %d\n", pid);
/* End code that is similar to switch... case default (parent) */
  
 // parent code continues:
    sleep (3);
   printf ("Parent is shutting down.\n");
   }

This is a very limited implementation, but it demonstrates the way you have to go to use NXVmSpawn instead of fork().

The output of this sample will show the following:

We are in the parent.
The new child has PID: -791664832
We are in the child.
Child is shutting down.
Parent is shutting down.

The NXVmSpawn call itself is a very complex one. If you look in the header file nks/vm.h, you will find this:

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

The parameters that can be passed into NXVmSpawn are the following:


Parameter
Definition

name

The name of the application binary that will be run in the new VM.

envSpec

Specifies the environment of the new Virtual Machine. This is mainly stdin, stdout, stderr, argc and argv.

flags

Flags that control the type of the new VM. There are three different flags possible: NX_VM_CREATE_DETACHED, NX_VM_INHERIT_ENV, and NX_VM_SAME_ADDRSPACE.

newVm

Returns the ID of the new VM. This is similar to PID on POSIX systems.

There is another way, processve() and processle() on the way with support for unnamed pipes and sharing of file descriptors.

Replacing fork/exec with processle/processve and pipes

Another often used mechanism to create processes on UNIX is the combination of fork() and exec() or CreateProcess() on Windows. As mentioned before fork() is the biggest problem on Netware, but with LibC there are two new functions to create a new process and to wire stdin, stdout, stderr and in the future every other file descriptor between processes. LibC has processle() and processve() defined in unistd.h. One of the nice things is that you can use pipe() and you'll get back two descriptors, exactly in the same way as defined in POSIX. With these pipes you can communicate between processes created with processle/processve very easy. The following sample demonstrates how you can do this:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <nksapi.h>
 
void main(int argc, char *argv[])
{
 int         in[2], out[2], err[2];
   int         len;

  wiring_t                wire;
    const char              *args[3];
    char        *cstring, *pstring, buf[200];
    
 cstring="If this can be read the child is talking to the parent\n";
  pstring="If this can be read the parent is talking to the child\n";
  
 setscreenmode(0);                       /* Don't auto close screen */
  
 args[0] = cstring;
   args[1] = cstring;
   args[2] = NULL;
  
 len = strlen(cstring) + 1;
   
if (argv[1] && !strcmp(argv[1], cstring))
 {
        /* here we are in the child part */
  
     printf("%s", cstring);
       fwrite("", 1, 1, stdout);                                   /* Send stuff via stdout to the parent */
        
     fgets(buf, len, stdin);
      fread(&buf[len-1], 1, 1, stdin);                                            /* Read stuff from the parent */
     cprintf("%s", buf);
  
 }
    else
 {
        /* here we are in the parent */
      pipe(in);
        pipe(out);
       pipe(err);
   
     /* wire stdin, stdout and stdin between parent and child */
      wire.infd  = in [0];
     wire.outfd = out[1];
     wire.errfd = err[1];
 
     processve("proctest.nlm", PROC_CURRENT_SPACE, NULL, &wire, NULL, NULL, args);
    
     close(in[0]);
        close(out[1]);
       close(err[1]);
   
     if (read(out[0], buf, len))                                     /* read from the child */
            printf(buf);
 
     // talk to the child
     write(in[1], pstring, len);                                     /* write to the child */
 
     close(in[1]);
        close(out[0]);
       close(err[0]);
   }
}

This sample requires that your NLM is called proctest.nlm and that it is marked multiple . If you don't mark this NLM multiple you'll see an error message that the process couldn't be created because of the NLM couldn't be loaded more than once. You can mark your NLM multiple in the target settings/NLM Linker of your project. Additional you should specify a user defined screen in the NLM Target settings.

A run of this sample will show the following on your screen:

 If this can be read the child is talking to the parent
   If this can be read the parent is talking to the child

processve() and processle() are defined in unistd.h . If you look in this header file you'll find the following:

pid_t        processve( const char *path, unsigned long flags,
                         const char *env[], wiring_t *wiring,
                         struct fd_set *fds, void *reserved,
                          const char *argv[] );
pid_t        processle( const char *path, unsigned long flags,
                           const char *env[], wiring_t *wiring,
                         struct fd_set *fds, void *reserved,
                          const char *arg0, ... );

processve() and processle() have only one difference in the parameter passing. processve() uses const char *argv[], processle() uses const char *arg0, ( this is similar to execve and execle on UNIX).

The following table describes the parameters in detail:


Parameter
Description

path

The name of the application binary that will be run in the new process.

flags

Flags that control the type of the process.

fds

File descriptor table. All file descriptors in this table are open in the new process and can be used in the parent and in the child process.

reserved

Currently unused.

argv[]/argc0

Arguments that will be passed to the new process.

The system call pipe() opens stdin, stdout and stderr with two file descriptors. One for reading and one for writing. That's why one file descriptor is closed immediately in the parent. pipe() works exactly the same as defined in POSIX.

Preemption

NetWare has a functionality called Pseudo Preemption. If you have developed on CLib you know that it was the job of the programmer to give control back to the CPU via ThreadSwitchWithDelay() . This has changed. LibC applications are preemptive by default.

If, for some circumstances, your application seems to hang the server, you still need to add a similar functionality. In LibC (technically, this is in NKS, not LibC), this is called NXThreadYield() . Add this command to your code to solve such issues. If you are familiar with SUN Solaris and UI threads, you'll know that you have to use thr_yield() sometimes. The same is possible in LibC instead of NXThreadYield() .

One Source for CLib and LibC

If you develop an application for different platforms or you want to use the same source within a CLib and a LibC environment, you can check in your source code if you are in a LibC environment. LibC defines __NOVELL_LIBC__ and you can use the following lines in your code:

#ifdef __NOVELL_LIBC__ 
/* Your LibC specific code comes here */

#else
/* other code comes here */

#endif

Other Porting Problems

There are several other problems you will see if you port an application to LibC. There is some development on the way to solve some of them, but it is not possible to solve everything. The following table shows some critical sections:


POSIX/System-V Functionality
LibC Functionary
Comments

Signals

LibC currently supports only a basic set of signals. For more details look in signal.h

NXThreadInterrupts is another way to send a "signal" to another thread, but it is not possible to use NXThreadInterrupt between VMs or processes

tcsetattr and tcgetattr

tcsetattr and tcgetattr to block ctrl-c, ctrl-d or ctrl-z

tcsetattr and tcgetattr is only usable to block ctrl-c, ctrl-d or ctrl-z from stdin. There is no other functionality in termios.h

getopt

getopt

LibC supports optreset in the same way as BSD systems do. GNU systems doesn't have optreset.

struct utsname

struct utsname

struct utsname has some NetWare specific extensions to report NLM Version information, copyright, description NLM name, and others.

System V semaphores

System V Semaphores

System V semaphores are fully functional on NetWare

A Porting Strategy

During my years of writing applications for the NetWare platform, I've found a porting strategy that is very effective. I hope this strategy will help you port without all the headaches I had as I started my first ports to NetWare.

  1. Read the readme of a project and figure out how the application is working on another platform.

  2. Check if the application needs other libraries that needs to be ported first.

  3. Check if there is a config.h file or if it will be generated on other platforms. If so copy this config.h file from a UNIX platform and modify it for the NetWare platform.

  4. Check the makefile of the project to find out which sourcefiles are really necessary for the project and create a MetroWerks project with these files.

  5. Search if there is "fork" somewhere in the sources.

  6. If there is no fork, go to step 8.

  7. Look at the source where you found fork. See whether it is used like fork/exec or as a pure fork. Now you have to find out if it is possible to avoid fork() with threads or with processve() or processle().

  8. Check whether there is shared memory or mutexes in the code. Especially look for shared memory. Try to find a way to solve the process synchronisation issues.

  9. Try to compile the project and make a list of all unknown functions. Try to find a way around these functions.

Conclusion

NKS/LibC has many features that are defined in standards such as ANSI C99, POSIX, BSD 4.3, UNIX98, SVID, and UI, as well as features that are commonly used in the "open source world." Sometimes you will be surprised what LibC has to offer. It is really amazing if you look inside the header files.

If you have any questions, you can use the NKS Newsgroup or you can send me an e-mail. The Newsgroup is monitored by the LibC engineers and the DeveloperNet SysOps. This is the place where you find hot news about LibC first.

If you are searching for ANSI C, POSIX or other standards, check out the links below. Some of these standard documents cost money, but if you are a professional software developer they are a must-have. You can buy a CD that contains all major standard documents, called "The Single UNIX Specification Version 3," for a low price at http://www.unix-systems.org.

* 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