Adding Console Commands to NetWare
Articles and Tips: article
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
- Registering a Console Command Interpreter
- Handling Console Commands
- Example Program: HELP.NLM
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.