libASSA Programmer's Manual | ||
---|---|---|
<<< Previous | Chapter 1. Application Shell | Next >>> |
Command line arguments are handled by CmdLineOpts class. For detailed description of its usage, see Section 4.4.
A variable is to be defined in your code for each new command line argument. Class CmdLineOpts provides the way of adding, removing and parsing command line arguments. GenServer defines and owns the default set of command line arguments. They are defined as protected data members for derived classes to have an easy access to them along with the appropriate set_()/get_() access functions to change and access their values. When the constructor of GenServer is executed, all of the variables are added to the CmdLineOpts object by associating them with command line argument names and types.
A derived class can reduce and extend the default set of command line arguments in its constructor, if necessary. To illustrate this, we are going to remove all non-applicable arguments, and add a new command-line argument {-a,--greeting-message=STRING}. This argument would allow us to alter the output string ""Hello, World!"" to something different.
// HelloWorld.h 035 class HelloWorld : 036 public ASSA::GenServer, 037 public ASSA::Singleton<HelloWorld> 038 { 039 public: 040 HelloWorld (); ... 048 private: 049 050 std::string m_greeting_message; 051 }; |
In line 50, we added a new data member, m_greeting_message, that will be associated with our new command-line argument.
// HelloWorld.cpp 029 HelloWorld:: 030 HelloWorld () : 031 m_exit_value (0), 032 m_greeting_message ("Hello, World!") 033 { 034 // Add a new command-line argument 035 add_opt ('a', "greeting-message", &m_greeting_message); 036 037 // ---Configuration--- 038 rm_opt ('f', "config-file" ); 039 rm_opt ('n', "instance" ); 040 rm_opt ('p', "port" ); 041 042 // ---Process bookkeeping--- 043 rm_opt ('b', "daemon" ); 044 rm_opt ('l', "pidfile" ); 045 rm_opt ('L', "ommit-pidfile"); 046 047 /*--- 048 * By defauil disable all debugging 049 *---*/ 050 m_debug_mask = ASSA::APP | ASSA::ERROR; 051 m_log_file = "HelloWorld.log"; 052 } |
In line 32, we initialized m_greeting_message with the default value.
In line 35, we added our new argument.
We also shrinked the default arguments set by uncommenting rm_opt() calls in lines 38 through 45.
// HelloWorld.cpp 070 void 071 HelloWorld:: 072 processServer () 073 { 074 trace("HelloWorld::processServer"); 075 076 std::cout << m_greeting_message << std::endl; 077 078 // Shut the service down 079 m_reactor.stopReactor (); 080 DL((ASSA::APP,"Service stopped!\n")); 081 } |
In line 76, we print the message to the standard output. Let's try it out:
Let's rebuild and try it out:
% ./HelloWorld Hello, World! % ./HelloWorld --greeting-message="Hello, Hackers!" Hello, Hackers! |
Below is the list of all default command-line arguments defined by GenServer class.
Example 1-6. List of default command-line arguments defined.
App::App() { rm_opt ('h', "help" ); rm_opt ('v', "version" ); rm_opt ('m', "mask" ); rm_opt ('d', "log-stdout" ); rm_opt ('D', "log-file" ); rm_opt ('z', "log-size" ); rm_opt ('f', "config-file" ); rm_opt ('n', "instance" ); rm_opt ('p', "port" ); rm_opt ('b', "daemon" ); rm_opt ('l', "pidfile" ); rm_opt ('L', "ommit-pidfile"); } |
Command line argument {-b, --daemon} is a binary flag that indicates whether the process is to be run as a UNIX daemon. This is used when we want the process to continue running even after the terminal session it was started from exits.
The member function GenServer::becomeDaemon () does the trick by executing following steps:
Forking the process and exiting the child.
Closing all opened file descriptors.
Redirecting stdout and stderr to /dev/null
Creating a new session and makeing init its session leader.
Consult data member GenServer::m_daemon to find out it the flag was set or not. |
Command line argument {-d, --log-stdout} is a binary flag that makes the program to write all of its logging messages to the standard output.
This is proven to be useful if you want to find out quickly why, for example, your process is exiting immediately upon restart. Instead of looking at its log file, you can redirect all messages to the standard output and observe the error.
For example, running HelloWorld with this option will send all debugging information to the standard output:
% ./HelloWorld --log-stdout [GenServer::initInternals] [GenServer::initInternals] ================================================== [GenServer::initInternals] || Server configuration settings || [GenServer::initInternals] ================================================== [GenServer::initInternals] cmd_line_name = 'HelloWorld' [GenServer::initInternals] name = 'HelloWorld' [GenServer::initInternals] std cfg fname = '/home/vlg/.gnome/HelloWorld' [GenServer::initInternals] alt cfg fname = '' [GenServer::initInternals] debug_mask = 0x22 [GenServer::initInternals] ================================================== [GenServer::initInternals] [HelloWorld::initServer] Service has been initialized Hello, World! [HelloWorld::processServer] Service stopped! |
Consult data member GenServer::m_log_stdout_flag to find out it the flag was set or not. |
You can overwrite the default log file name to something different with {-D, --log-file NAME} option. The NAME must be fully-qualified UNIX path to the file.
For example, we might want to write all debugging messages to the file /tmp/hw.log instead of ./HelloWorld.log:
% ./HelloWorld --log-file=/tmp/hw.log Hello, World! % ls -l /mnt/hw.log -rw-r--r-- 1 vlg 739 Apr 3 18:12 /tmp/hw.log |
By default, when process is restarted, new log messages are appended to the existing log file. This can be changed by instructing GenServer to remove the copy of an old log file if it exists before creating new log file:
// HelloWorld-main.cpp 052 int 053 main (int argc, char* argv[]) 054 { ... 060 HELLOWORLD->set_flags (ASSA::GenServer::RMLOG); ... 068 } |
On line 60, we configure GenServer to remove old log file.
Each library component (a group of tightly-coupled classes) has its own logging level assigned to it. Some components have two logging levels: one is for tracing each function call in the component, and another to log informative messages.
There are several logging levels reserved for the application code: APP, TRACE, and USR1-3. You would use APP to log all of your application messages, and TRACE if you want to trace the sequence of application function calls. The extra three levels gives you some freedom of discretion: USR1, USR2, and USR3.
All logging levels are defined in assa/LogMask.h file:
Example 1-7. Logging Levels
00024 namespace ASSA { 00025 enum Group { 00026 TRACE = 0x00000001, 00027 APP = 0x00000002, 00028 USR1 = 0x00000004, 00029 USR2 = 0x00000008, 00030 USR3 = 0x00000010, 00031 /*-----------------------*/ 00032 ALL_APPS = 0x0000001F, 00033 /*-----------------------*/ 00034 ERROR = 0x00000020, 00035 PIDFLOCK = 0x00000040, 00036 CMDLINEOPTS = 0x00000080, 00037 SEM = 0x00000100, 00038 SIGHAND = 0x00000200, 00039 REACT = 0x00000400, 00040 REACTTRACE = 0x00000800, 00041 SOCK = 0x00001000, 00042 SOCKTRACE = 0x00002000, 00043 XDRBUF = 0x00004000, 00044 XDRBUFTRACE = 0x00008000, 00045 STRMBUF = 0x00010000, 00046 STRMBUFTRACE = 0x00020000, 00047 FORK = 0x00040000, 00048 SIGACT = 0x00080000, 00049 PIPE = 0x00100000, 00050 CHARINBUF = 0x00200000, 00051 ADDRESS = 0x00400000, 00052 RES3 = 0x00800000, 00053 RES4 = 0x01000000, 00054 RES5 = 0x02000000, 00055 RES6 = 0x04000000, 00056 RES7 = 0x08000000, 00057 RES8 = 0x10000000, 00058 RES9 = 0x20000000, 00059 RES10 = 0x40000000, 00060 /*-----------------------*/ 00061 ALL_LIB = 0x7FFFFFE0, 00062 ALL = 0x7FFFFFFF, 00063 NONE = 0x00000000 00064 }; |
Debug message mask argument, {-m, --mask HEX_NUM} allows an application user to filter out the messages by adding masks of only those components that are wanted to be seen.
The mask 0x7fffffff dumps every possible message to the log file. |
In our example, HelloWorld by default logs only ASSA::APP and ASSA::ERROR messages to the log file (0x2 + 0x20 = 0x22). If, for example, we want to see PIDFLOCK related messages, we can achieve it by supplying the mask 0x22 + 0x40 = 0x62:
% HelloWorld --mask=0x62 --log-stdout | /PidFileLock::lock ---v--- | [PidFileLock::lock] PID lock file: "/home/vlg/.lt-HelloWorld.pid" | [PidFileLock::lock] PID lock file opened. | | /PidFileLock::test_region ---v--- | | [PidFileLock::test_region] fcntl(fd=3) returned: 0 | | [PidFileLock::test_region] Region is not locked | | \PidFileLock::test_region ---^--- | [PidFileLock::lock] PID file is not locked | | /PidFileLock::lock_region ---v--- | | [PidFileLock::lock_region] fcntl(fd=3) returned: 0 | | \PidFileLock::lock_region ---^--- | [PidFileLock::lock] PID lock file locked. | [PidFileLock::lock] PID lock file truncated. | [PidFileLock::lock] PID (4432) written to lock file. | [PidFileLock::lock] CLOSE-ON-EXEC is set on FD. | \PidFileLock::lock ---^--- [GenServer::initInternals] [GenServer::initInternals] ================================================== [GenServer::initInternals] || Server configuration settings || [GenServer::initInternals] ================================================== [GenServer::initInternals] cmd_line_name = 'lt-HelloWorld' [GenServer::initInternals] name = 'lt-HelloWorld' [GenServer::initInternals] std cfg fname = '/home/vlg/.gnome/lt-HelloWorld' [GenServer::initInternals] alt cfg fname = '' [GenServer::initInternals] debug_mask = 0x62 [GenServer::initInternals] ================================================== [GenServer::initInternals] [HelloWorld::initServer] Service has been initialized Hello, World! [HelloWorld::processServer] Service stopped! /PidFileLock::~PidFileLock ---v--- [PidFileLock::~PidFileLock] PID lock file removed. [PidFileLock::~PidFileLock] PID lock file closed. \PidFileLock::~PidFileLock ---^--- |
See also Section 4.18 for more information on debug masks. |
Command line argument {-z, --log-size SIZE} specifies the maximum size log file is allowed to reach before being renamed to filename.0. For example, when the size of HelloWorld log file HelloWorld.log reaches 10Mb, it will be renamed to HelloWorld.log.0. The SIZE is specified in bytes. Default is 10Mb (10485760 bytes).
Application's version number, revision number and author's name can be manipulated with these methods:
class GenServer { public: // ... void set_version (const std::string& release_, int revision_); void set_author (const std::string& author_); // ... }; |
The best place to configure the application class derived from GenServer would be before it is initialized in the main() function of the program.
Example 1-8. Adding Version/Author Information
// HelloWorld-main.cpp 001 int 002 main (int argc, char* argv[]) 003 { 004 static const char release[] = "1.0"; 005 int patch_level = 0; 006 007 HELLOWORLD->set_version (release, patch_level); 008 HELLOWORLD->set_author ("Joe the Hacker"); 009 HELLOWORLD->set_flags (ASSA::GenServer::RMLOG); 010 011 HELLOWORLD->init (&argc, argv, help_msg); 012 013 HELLOWORLD->initServer (); 014 HELLOWORLD->processServer (); 015 016 return HELLOWORLD->get_exit_value (); 017 } |
On line 4, we defined our release at 1.0. We also set the patch level on the next line to 0.
On line 8, we set the author's name.
The version and author information can be found with {-v, --version} command-line argument:
% HelloWorld --version HelloWorld Version: 1.0 Revision: 0 Written by Joe the Hacker |
Command line option {-p, --port NAME} is associated with a service name that is bound to the TCP or UDP port number in /etc/services UNIX system configuration file. It has different meaning in different context.
For the server applications, this argument tells the application code the name of its listening socket. For example, if we were to implement an ftp server, the NAME would be ftp which is defined in /etc/services as:
File: /etc/services ftp 21/tcp |
For the client applications, this option may be used to indicate the service name of the server it will be connecting to.
If --port argument is not given on the command line, then it set by defaults to the command line name of the application. Thus, process with command line name HelloWorld will have port name set up by default to HelloWorld. This value can be modified during the initialization with the following methods:
class GenServer { public: // ... void setPortName (string port_); string getPortName (); // ... }; |
If this argument is not applicable at all (such as in our example with HelloWorld), it, as well as any other command-line argument, can be removed alltogether with CmdLineOpts::rm_opt() function.
The default configuration file name is $HOME /.<command_line_name>.cfg; Thus, process with command line name HelloWorld has its default configuration file name set to $HOME/.HelloWorld.cfg.
Sometimes it is desirable to use a configuration file different from the default one. Command line argument {-f, --config-file NAME} allows to specify full UNIX path of the alternative configuration file. Here are applicable methods for accessing the file names:
class GenServer { public: // ... std::string getStdConfigName (); std::string getAltConfigName (); // ... }; |
Sometimes it is desirable to run several instances of the same application program on the same host computer. To achieve this objective, command line argument {-n, --instance NUM} is used.
Setting process instance number modifies several other configuration parameters of GenServer class. The actual values of each of these parameters can be further deviated by using appropriate command line arguments.
m_proc_name
m_cmdline_name
m_port
m_instance
m_pidfile
For example, with our application HelloWorld, if the instance number were to be set to 2 on the command line, and none of the listed parameters were overridden by their corresponding command line arguments, then their values would be:
% HelloWorld --instance=2 m_proc_name : HelloWorld2 m_cmdline_name : HelloWorld m_port : HelloWorld2 m_instance : 2 m_pidfile : $HOME/.HelloWorld2.pid |
If, on the other hand, instance number were not set at all (default), the variables would have these values:
% HelloWorld m_proc_name : HelloWorld m_cmdline_name : HelloWorld m_port : HelloWorld m_instance : -1 m_pidfile : $HOME/.HelloWorld.pid |
Two arguments that are not affected by the --instance argument are configuration file name and log file name. These can be altered by their corresponding command-line arguments instead. |
PID Lock File allows a daemon process to create a file with its PID written in it. The process immediately locks this file to prevent other copies of itself from running on the same host. Every time a new copy of the process starts up, it tries first to open and lock its PID file and, if that fails, the process exits. This exclusion mechanism is much cheaper then trying to open a listening socket and failing. As a side effect, because the PID file of every process can be found and extracted, system administration utilities (see Section 4.15) and cron jobs can effectively monitor the running process and restart it in case of the fatal failure.
There are a couple of options to control PID file creation by the process. Command line option {-L, --ommit-pidfile} tells GenServer not to bother with PID file testing and creation steps at all. It skips PID file processing all together.
You can also delete this option in your application shell class, MyAppShell, by removing both ommit-pidfile and pidfile options and setting m_ommit_pidflock_flag to true:
class MyAppShell : public GenServer { public: // ... MyAppShell () { // ... rm_opt ('L', "ommit-pidfile"); rm_opt ('l', "pidfile"); m_ommit_pidflock_flag = true; // ... } // ... }; |
By default, PID file name is formed from the process name plus process instance number:
m_pidfile = $HOME/.<process_name>[+<instance_number>].pid |
If the process doesn't have its instance_number defined on the command line, then process_name won't have it either. If process_name is HelloWorld, then its PID file will be:
m_pidfile = $HOME/.HelloWorld.pid |
To change PID file name, use command line option {-l, --pidfile NAME}
A libassa-based application optionally can have its man page embedded in the source code.
Command line argument {-h, --help} prints several pieces of information to the standard output:
Name of the application.
Application description.
Application usage.
Description of command-line arguments.
Author information.
assa-genesis utility generates necessary templates to provide all of the above information:
Example 1-9. Help message description template
static const char help_msg[]= " \n" " NAME: \n" " my application program name \n" " \n" " DESCRIPTION: \n" " \n" " short description to give general feeling \n" " of what its main purpose is \n" " \n" " USAGE: \n" " \n" " shell> app_name [OPTIONS] \n" " \n" " OPTIONS: \n" " -b, --daemon - run process as true UNIX daemon \n" " -d, --log-stdout - write debug to standard output \n" " -D, --log-file NAME - write debug to NAME file \n" " -f, --config-file NAME - alternative config file NAME \n" " -h, --help - print this messag \n" " -l, --pidfile PATH - the process ID is written to the lockfile PATH \n" " instead of default ~/.{procname}.pid \n" " -L, --no-pidfile - do not create PID lockfile \n" " -m, --mask MASK - mask (default: DBG_ALL = 0x7fffffff) \n" " -n, --instance NUM - process instance NUM (default - 1) \n" " -p, --port NAME - tcp/ip port NAME (default - procname) \n" " -v, --version - print version number \n" " -z, --log-size NUM - maximum size debug file can reach (dfl: is 10Mb) \n" |
The help_msg[] character array variable that holds the message, is then passed to GenServer::init() function for initialization.
Let's modify our example, HelloWorld, to illustrate the idea.
// HelloWorld-main.cpp static const char help_msg[]= " \n" " NAME: \n" " \n" " HelloWorld \n" " \n" " DESCRIPTION: \n" " \n" " HelloWorld illustrates application shell capabilities \n" " of libassa library. \n" " \n" " USAGE: \n" " \n" " shell> HelloWorld [OPTIONS] \n" " \n" " OPTIONS: \n" " -d, --log-stdout - write debug to standard output \n" " -D, --log-file NAME - write debug to NAME file \n" " -h, --help - print this messag \n" " -m, --mask MASK - mask (default: ASSA::APP | ASSA::ERROR) \n" " -v, --version - print version number \n" " -z, --log-size NUM - maximum size debug file can reach (dfl: is 10Mb) \n" |
The list of command-line arguments available for HelloWorld has been reduced because we have removed the unnecessary arguments in HelloWorld::HelloWorld() constructor definition earlier in this chapter.
% ./HelloWorld --help NAME: HelloWorld DESCRIPTION: HelloWorld illustrates application shell capabilities of libassa library. USAGE: shell> HelloWorld [OPTIONS] OPTIONS: -D, --log-file NAME - Write debug to NAME file -d, --log-stdout - Write debug to standard output -z, --log-size NUM - Maximum size debug file can reach (dfl: is 10Mb) -m, --mask MASK - Mask (default: ALL = 0x7fffffff) -h, --help - Print this messag -v, --version - Print version number Written by Joe the Hacker |
The summary table presents both default values and the way of changing it via command line arguments. It also shows interdependencies between different variables that hold values of arguments.
Table 1-1. Command Line Arguments Summary
Variable | Access | Default | Command-line Argument |
---|---|---|---|
m_cmdline_name | getCmdLineName() | process command line name | |
m_instance | -1 | {-n, --instance=NUM} | |
m_proc_name | getProcName(), setProcName() | = m_cmdline_name + m_instance | |
m_port | getPortName(), setPortName() | = m_proc_name | {-p, --port=NAME} |
m_std_config_name | getStdConfigName() | "$HOME/." + m_cmdline_name + ".cfg" | |
m_alt_config_name | getAltConfigName() | {-f, --config-file=PATH} | |
m_daemon_flag | false | {-b, --daemon} | |
m_log_stdout_flag | false | {-d, --log-stdout} | |
m_log_file | m_cmd_line + ".log" | {-D, --log-file=PATH} | |
m_log_size | 10Mb | {-z, --log-size=SIZE} | |
m_debug_mask | ASSA::ALL | {-m, --mask=MASK} | |
m_version_flag | false | {-v, --version} | |
m_help_flag | false | {-h, --help} |
<<< Previous | Home | Next >>> |
What's Under The Hood? | Up | Handling UNIX Signals |