1.5. Command Line Arguments

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 };
	  


// 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 }
	  


// 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 }
	  

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");
}
		

1.5.1. Running Process As UNIX daemon

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:

Note

Consult data member GenServer::m_daemon to find out it the flag was set or not.

1.5.2. Redirecting debug messages to stdout

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!
		  

Note

Consult data member GenServer::m_log_stdout_flag to find out it the flag was set or not.

1.5.3. Log File Name

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 }

		

1.5.4. Message Logging Levels

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.

Note

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  ---^---
		  

Note

See also Section 4.18 for more information on debug masks.

1.5.5. Log File Size

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).

1.5.6. Version And Author Information

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 }
		  

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
		  

1.5.7. Service Name (Port Number)

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.

1.5.8. Configuration File Name

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 ();

    // ...
};
		

1.5.9. Process Instance Number

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.

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
		

Note

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.

1.5.10. PID Lock File

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}

1.5.11. Embedded man Page

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:

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
		

1.5.12. Command Line Options Summary

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

VariableAccessDefaultCommand-line Argument
m_cmdline_namegetCmdLineName() process command line name  
m_instance -1 {-n, --instance=NUM}
m_proc_namegetProcName(), setProcName()= m_cmdline_name + m_instance 
m_portgetPortName(), setPortName()= m_proc_name {-p, --port=NAME}
m_std_config_namegetStdConfigName() "$HOME/." + m_cmdline_name + ".cfg"  
m_alt_config_namegetAltConfigName()  {-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}