Novell is now a part of Micro Focus

Adding Console Commands to NetWare

Articles and Tips: article

MORGAN B. ADAIR
Senior Research Engineer
Novell Systems Research Department

01 Apr 1996


NetWare provides a mechanism to allow you to add console commands to the NetWare file server. This Devnote tells how to write a console command interperter, and provides an example program that implements a HELP command (like the one on NetWare 4 file servers) for NetWare 3.

NetWare Console Commands

Like most operating systems, NetWare file servers have a console command interpreter. NetWare allows you to extend the functionality of the file server console by adding new console commands. Custom console commands are useful when (for example) your NLM doesn't require its own screen, and only requires a simple command-line interface.

To add a console command, write an NLM (NetWare Loadable Module) to install a function as a command interpreter. Whenever a user enters a command line on the file server console, NetWare's internal command interpreter parses the command line and handles all commands in NetWare's native command set. NetWare then passes any unrecognized command lines to your command interpreter, which then parses the command line to see if it contains a command you want to handle.

Upon return, your command interpreter must tell NetWare whether it has accepted the command for processing, or if it should be passed to any other command interpreters that might be registered on the file server.

It is not difficult to write an NLM that implements a console command handler, but there are a few things you need to know first. This DevNote tells about the structure of console command NLMs, and gives you a few tips that will make the process of writing one easier. I've also provided source code for an example NLM. This is no brain-dead "Hello World" program; rather it implements the HELP command that is included in NetWare 4, but was left out of NetWare 3.

Registering a Console Command Interpreter

To register a console command, your NLM must call RegisterConsoleCommand. The function takes one parameter, a commandParserStructure, which is defined as follows:

struct commandParserStructure

{

struct commandParserStructure *Link; /* set by RegisterConsoleCommand */

LONG (*parseRoutine) ( LONG screenID, BYTE *commandLine);

struct ResourceTagStructure *RTag;

}

RegisterConsoleCommand sets the Link field. You supply the parseRoutine function. You get the resource tag by calling AllocateResourceTag, specifying that you want a console command resource tag for the calling NLM.

The parseRoutine function (or command line interpreter) must take two parameters, a screen ID and the command line. The screen ID tells the command line interpreter which screen (if any) to direct its output to; the command line is the full command line as the user typed it on the file server console.

The command line interpreter should do as little processing as possible. Ideally, it should just identify whether the command line passed to it contains the appropriate command, schedule another thread to process the command, then return. The command line interpreter returns a one to tell NetWare that it processed the command, zero to indicate that the command line was unrecognized.

After registering the command line interpreter, your NLM should call atexit to schedule the DeregisterConsoleCommand to be executed when the NLM exits.

Handling Console Commands

You can use a single thread to process all commands your NLM recognizes, or you can use a separate thread for each command. Either way, the command line interpreter, once it has recognized the command, schedules the console command handler thread to process the command.

Threads are typically implemented as endless loops. At some point in the loop, the thread must yield control of the processor by calling SuspendThread. The BeginThread function initiates a new thread. If you need to pass data to the thread at initialization, the new thread can take a single 32-bit parameter (which can be a pointer). Once the thread is executing, it can communicate with other NLM threads only through shared data.

If the command handler thread calls any CLIB function that uses any thread group global data items (like the current connection, current screen, or current working directory) you must first set the thread context. For more information, see the documentation for the RegisterConsoleCommand function in the NLM Library Reference, and the "Thread Services" chapter in Using NetWare Services for NLMs.

Example Program: HELP.NLM

HELP.NLM implements the HELP console command for NetWare 3. If you want to test HELP on a NetWare 4 file server, you'll have to change the name of the command it recognizes to something other than HELP (or another native NetWare server command).

HELP.NLM was developed with Base Technology's NLM development kit and Borland C++. The source code is also compatible with Watcom C.

Source code and executable for HELP.NLM is in a file called NW3HELP.EXE, which you can download from CompuServe's NOVUSER forum.

HELP.H

//function prototype for command handler

void HandleHelpRequest(void *dummy);

//function prototype for function to wake up

//command handler

void ScheduleHelpCommand(void);

HELP.C

HELP.C contains the NLM's main function, which registers the command line interpreter, initializes the command handler thread, schedules the command line interpreter to be deregistered when the NLM terminates, then exits.

#include <stdio.h<<
#include <string.h<<
#include <conio.h<<
#include <errno.h<<
#include <nwdir.h<<
#include <process.h<<
#include <advanced.h<<
#include "help.h""
//return codes for console command interpreter

#define HANDLEDCOMMAND  0

#define NOTMYCOMMAND    1

//Global data. Used for communication between the command interpreter and the

//command handler thread

char helpCommand[20] = "\0";"
/*

The console command interpreter function.

The program registers this function with the NetWare OS. When the user types

a command that the server's internal command interpreter does not recognize,

it will pass the command line to this function, which can then decide whether

the command line contains a command it wants to handle. Multiple console

command interpreters can therefore be registered with the OS. A command

interpreter must return 0 if it handled the command passed to it, or 1 if not.

*/

static LONG CommandLineInterpreter(LONG screenID, BYTE *commandLine)

{

/*

Figure out whether this is a command we're interested in, then schedule

the thread to handle the request. You may have one thread to process all

commands recognized by the program, or one thread for each command. If

you use multiple threads, then this function would schedule the thread

that processes the appropriate command.

This function should execute quickly and return control to the server's

internal console command interpreter. So it's a good idea to have separate

thread(s) to process console commands, and just have this function identify

the commands we're interested, wake the appropriate thread to process the

command, then return. If you want to do all of the work here, you must

change the CLIB context to that of a thread group in your NLM. You can use

SetThreadGroupID to do this. Remember to switch it back before you return.

*/

char *pCommand;

//if you want to run this NLM on a NetWare 4 server, change the HELP command

//in the following line to something not in NetWare's native command set

if( !strnicmp("HELP", (char *)commandLine, 4) )"
{

  if (strlen((char *)commandLine) > 4)>
  {

     pCommand = (char *)(commandLine + 5);

     memcpy(helpCommand, pCommand, 20);

  }

  else

     memset(helpCommand, 0, 20);

  ScheduleHelpCommand();

  return HANDLEDCOMMAND;

}

return NOTMYCOMMAND;

}

/*  Structure used to register/deregister a console handler with the OS */

static  struct  commandParserStructure commandParser = 

{

  0,

  CommandLineInterpreter,

  0

};

/*  The following function is called during NLM shutdown */

static void RemoveConsoleHandler()

{

  UnRegisterConsoleCommand(&commandParser);&
}

void main(void)

{

unsigned int nlmHandle;

ConsolePrintf("\n\r Example program for Novell Developer Notes\n\r");"
ConsolePrintf("  Produced by Novell Systems Research Department\n\r");"
ConsolePrintf("  Distribute freely.\n\n\r");"
nlmHandle = GetNLMHandle();

if (!(commandParser.RTag =(struct ResourceTagStructure*)AllocateResourceTag(nlmHandle, 

    (BYTE *)"Help Console Command",ConsoleCommandSignature))) {"
    ConsolePrintf("HELP: Unable to allocate resource tag.\n\r");"
    exit(1);

}

   if (BeginThread(HandleHelpRequest, NULL, 8192, NULL) == EFAILURE)

   {

      ConsolePrintf("HELP: Couldn't start HandleHelpRequest thread.\n\r");"
      exit(1);

   }

   ThreadSwitch();

   RegisterConsoleCommand(&commandParser);&
   atexit(RemoveConsoleHandler);

   ExitThread(EXIT_THREAD,0);

}

HELPCOM.C

HELPCOM.C contains code for the command handler thread and the function that is used to reschedule it. It is implemented as a large if-else if statement. If none of NetWare's native command set is on the command line, the command handler displays a warning in the console screen, saying that the command is unrecognized.

#include <conio.h<<
#include <process.h<<
#include <string.h<<
#include "help.h""
static LONG HelpCommandThread;

extern char helpCommand[20];

void    ScheduleHelpCommand()

{

 ResumeThread(HelpCommandThread);

}

void    HandleHelpRequest(void *dummy)

{

//Thread initialization code here

//Just go to sleep for now

SuspendThread(HelpCommandThread = GetThreadID());

  for(;;){

  //Code for console command handler goes here

     strlwr(helpCommand);

     if (strlen(helpCommand) == 0) //user typed "help" with no parameters"
     {

        ConsolePrintf("\nABORT REMIRROR    ADD NAME SPACE      BIND");"
        ConsolePrintf("\n\rBROADCAST       CLEAR STATION       CLS");"
        ConsolePrintf("\n\rCONFIG          DISABLE LOGIN       DISABLE TTS");"
        //lines deleted here

     }

     else if (!strcmp(helpCommand, "abort remirror"))"
     {

        ConsolePrintf("\rABORT REMIRROR partition_number");"
        ConsolePrintf("\n\r Stops the remirroring of the specified partition.");"
        ConsolePrintf("\n\r Example:  abort remirror 3\n\n\r");"
     }

     else if (!strcmp(helpCommand, "add name space"))"
     {

        //rest of help commands deleted

     }

     else

     {

        ConsolePrintf("\r'%s' is not a valid command.", helpCommand);"
        ConsolePrintf("\n\rType 'HELP' for a list of valid commands.\n\r");"
     }

     SuspendThread(HelpCommandThread = GetThreadID());

  }

  }

* 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