#!/usr/bin/perl -w

# Copyright 2008-2014 Cumulus Systems Incorporated.
# All Rights Reserved.

# This file contains the code to send Raw data to Probe appliance.
# Logic: Watch a directory in real time, detect files as created, and send them to remote server.
#
# Following are the command line option.
#
# -q dir     : Sent all files from specified directory. Here we will not monitor for new files in default location.
# -N         : Do not lock directory (individual files are always locked).
# -x         : Enable debugging. For program maintainers.
# -h         : Help.
# -d dir     : Installation directory.
# -B seconds : Overrides configuration setting for transferring data.
#
# @return : In case of success, returns 0. In case of failure, returns 1.
#
# Sample command: perl sendRawData.pl [-x] [-N] [-B batchtime] [-q queuedir] [-d installation directory] (or -h for help)

# Switch on warnings. It is used so that we can switch-off warning in Getopts() call.
$^W = 1;

# Flush output after every write. Unbuffered output is usually desired.
$| = 1;

use strict;

# Use select for non-blocking I/O from inotifywatch process.
use IO::Select;

# To set auto flush on file handles.
use IO::Handle;

# This is used to create folder recursively.
use File::Path qw(mkpath);

# Used to read script arguments.
use Getopt::Std;

# It is required for logging information.
use commonLogModule;

# This variable stores the probe type.
my $probeType = "Linux";

# This variable stores commonLogModule instance.
my $logObj = "";

# This variable stores IP of this machine.
my $ipOfThisMachine = "";

# Maximum wait-time for can_read() call before returning an empty handle list.
my $SELECT_TIMEOUT = 5;

# Delay after can_read() call so that sysread() can return all names.
my $READ_DELAY = 5;

# Time in seconds to wait before start sending files.
my $ACCUMULATE_DELAY = 5;

# Buffer size in bytes (4 KB) for reading event string list from inotify process.
my $FNAMEBUF = 8192;

# Buffer size in bytes (1 MB) for transmitting data to server.
my $IOBUFSIZE = 1024 * 1024;

# Time out in seconds for a file send operation.
my $TIMEOUT = 30;

# This variable is used to store the return status of a function call.
my $status = 0;

# Our install location.
my $INSTALL_DIR = "";

# Configuration file.
my $RUNTIME_CF = "";

# Data directory where we will look for new data files.
my $DATA_DIR = "";

# Files which got sent get moved into a this directory.
my $SENT_DIR = "";

# Location of "inotifywatch" program which watches directories.
my $INOTIFY_WATCH = "";

# Our standard lock file name
my $LOCKNAME = "lck.rt_send";

# Command to run the data directory watching.
my $PROCESS = "";

# Destination host name.
my $DESTHOST = "";

# Destination port number.
my $DESTPORT = "";

# Data collection interval in seconds.
my $INTERVAL = "";

# Number of samples in a file.
my $SAMPLES = "";

# Transfer interval.
my $XFER = "";

# Never send files whose names are shorter than 46 words.
# [Hostname] + [IP Address] + [Time stamp] + [OS type] + [Data Type] + [Interval] + [Samples] + [Site name] + [Probe name] + [_]
# 1 + 7 + 14 + 5 + 6 + 1 + 1 + 1 + 1 + 9 = 46
my $MIN_FNAME_SIZE = 46;

# If defined then run script in debug mode.
$::debug = undef;

#--------Command line options.--------#
# Enable debugging.
$::opt_x = undef;

# Gives help menu.
$::opt_h = undef;

# If this option provided then send files only from this directory.
$::opt_q = undef;

# If this option provided then data directory will not be locked.
$::opt_N = undef;

# This will over write transfer interval.
$::opt_B = undef;

# Installation directory.
$::opt_d = undef;

#--------Command line options.--------#

# Script usage message.
$::usage = "Usage: sendRawData.pl [-vtx] -y [-N] [-B batch time] [-q queue dir] [-d installation directory](or -h for help)";

# Following (umask 022) ensure that files or directories created will have permission 0755.
umask(022);

# ----------------------------------------------------------------------------------------------------------------------------------------------------
# Main function
# ----------------------------------------------------------------------------------------------------------------------------------------------------

# Initialize the environment.
$status = init();
if (0 != $status)
{
    $logObj->error("Call to init() failed.");
    goto EXIT;
}

# Send files from specified directory.
if (defined($::opt_q))
{
    $logObj->info("Sending files from specified directory.");

    $status = sendFilesFromSpecifiedDir();
    if (0 != $status)
    {
        $logObj->error("Call to sendFilesFromSpecifiedDir() failed.");
        goto EXIT;
    }
}
# Send files from default directory.
else
{
    $logObj->info("Sending files from default directory.");

    $status = sendFilesFromDefaultDir();
    if (0 != $status)
    {
        $logObj->error("Call to sendFilesFromDefaultDir() failed.");
        goto EXIT;
    }
}

EXIT:

$logObj->info("Return status: [$status].");

exit $status;

# ----------------------------------------------------------------------------------------------------------------------------------------------------
# Sub-routines
# ----------------------------------------------------------------------------------------------------------------------------------------------------

# This function is used to initialize the environment to run the Perl script correctly.
#
# @affected global variables :
#   $baseFolder
#   $propFilePath
#
# @return :
#   0 if Success
#   1 if Error
sub init
{
    # This variable is used to store the return value of function calls.
    my $retVal = 0;

    # This variable stores Log folder path.
    my $logFolder = "";

    # If first argument is not an option then it is an error.
    if (@ARGV && $ARGV[0] !~ "^-.+" )
    {
        $logObj->error("First argument is not an option.");
        $retVal = 1;

        goto EXIT;
    }
    else
    {
        # Suppress annoying undef warnings.
        local($^W) = 0;

        # Read option arguments.
        &getopts("xq:NB:hd:");
    }

    # Give help menu if user passed -h.
    if (defined($::opt_h))
    {
        giveHelp();
        exit(0);
    }

    # Get IP address of this machine.
    $ipOfThisMachine = `/sbin/ifconfig eth0 | sed -n '/.*inet .*dr:/{;s/.*dr://;s/ .*//;p;}'`;

    chomp($ipOfThisMachine);

    if ($ipOfThisMachine !~ /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/)
    {
        $ipOfThisMachine = getNonLoopBackIp();
        chomp($ipOfThisMachine);
    }
    
    # Our install location.
    $INSTALL_DIR = $::opt_d;

    $logFolder = $INSTALL_DIR."/logs";

    # If log folder does not exist then create it.
    if (! (-e $logFolder))
    {
        # Create the log folder.
        unless (defined eval {mkpath($logFolder)})
        {
            $retVal = 1;

            goto EXIT;
        }
    }

    # Get commonLogModule instance.
    $logObj = commonLogModule->getInstance($logFolder, $probeType, $ipOfThisMachine);

    # Settings file.
    $RUNTIME_CF = "$INSTALL_DIR/conf/runtime.properties";

    # Location of "inotifywatch" program which watches directories.
    $INOTIFY_WATCH = "$INSTALL_DIR/bin/inotifywatch";

    # Data directory where we will look for new data files.
    $DATA_DIR = "$INSTALL_DIR/data";

    # Enabled debugging.
    if (defined($::opt_x))
    {
        $logObj->debug("Enabled debugging.");
        $::debug = $::opt_x;
    }

    # Set different data directory path.
    if (defined($::opt_q))
    {
        $logObj->info("Setting data directory as specified by option [-q].");
        $DATA_DIR = $::opt_q;
    }

    # If data folder does not exist then exit.
    if (!(-e $DATA_DIR))
    {
        $logObj->error("Directory not exist: [$DATA_DIR].");
        $retVal = 1;

        goto EXIT;
    }

    # Files which got sent moved into a 'sent' sub directory.
    $SENT_DIR = "$DATA_DIR/sent";

    # If data folder does not exist then create it.
    if (!(-e $SENT_DIR))
    {
        # Create the data folder.
        unless (defined eval {mkpath($SENT_DIR)})
        {
            $logObj->error("Not able to create folder: [$SENT_DIR].");
            $retVal = 1;

            goto EXIT;
        }
    }

    # Set command to watch for new files.
    $PROCESS = "$INOTIFY_WATCH $DATA_DIR";

    # Read configuration file.
    $retVal = readConfigurationFile();
    if (0 != $retVal)
    {
        $logObj->error("Call to readConfigurationFile() failed.");
        $retVal = 1;

        goto EXIT;
    }

    # Check here whether data directory require lock by rt_send script.
    if (defined($::opt_N))
    {
        $logObj->debug("Directory lock not needed as -N option is provided.");
    }
    else
    {
        # Suppress 'used only once' warning. "our" makes it a global variable.
        our $DATA;

        $logObj->debug("Acquiring directory lock as -N option is provided.");

        my $lockFile = "$DATA_DIR/$LOCKNAME";
        my $msg = getLock($lockFile, \*DATA);

        if (defined($msg))
        {
            $logObj->error("Lock failed. File name: [$lockFile]. Error: [$msg].");
            $logObj->error("Exiting as directory lock failed.");

            exit(0);
        }
    }

EXIT:

    return $retVal;
}

# This function called when [-q <directory>] option is not provided.
#
# @param:
#    None.
#
# @return:
#    Nothing.
sub sendFilesFromDefaultDir
{
    # Send all existing files as we will miss sending these files in real time mode.
    sendExistingFiles($DATA_DIR);

    # Again call sendExistingFiles() to catch any new files created while sending the existing files.
    sendExistingFiles($DATA_DIR);

    # Check to detect data transfer mode. If Sample Interval * Collection Interval is greater than or equal to Transfer Interval then run in real
    # time mode else run in batch mode. Reason is that core scripts will create a new file in Sample Interval * Collection Interval time. So it
    # transfer interval is less than this no batch will have more than one file and even few times, no file will found for transfer.
    if ($XFER <= $INTERVAL * $SAMPLES)
    {
        $logObj->debug("In real time mode. Transfer Interval:[$XFER], Sample Interval: [$INTERVAL], Number of Samples: [$SAMPLES].");
        # Real time mode.
        doRealTimeMode();
    }
    else
    {
        $logObj->debug("In batch mode. Transfer Interval:[$XFER], Sample Interval: [$INTERVAL], Number of Samples: [$SAMPLES].");
        # Batched mode.
        doBatchMode();
    }
}

# This function gets called when user provide [-q <directory>] option. This function send files only from this directory. So we don't have to use
# inotify process to monitor any new file creation.
#
# @param:
#     None.
#
# @return :
#   0 if Success
#   1 if Error
sub sendFilesFromSpecifiedDir
{
    # This variable is used to store the return value of function calls.
    my $retVal = 0;

    if (! -d $DATA_DIR)
    {
        $logObj->error("No such directory: [$DATA_DIR], error: [$!].");
        $retVal = 1;

        goto EXIT;
    }

    sendExistingFiles($DATA_DIR);

EXIT:

    return $retVal;
}

# In this mode, multiple files will be sent to the server at a time.
#
# @param:
#    None.
#
# @return:
#    Nothing.
sub doBatchMode
{
    $logObj->info("Running in Batch mode with transfer time as [$XFER] seconds.");

    for ( ; ; )
    {
        $logObj->info("Sleep for $XFER seconds...");

        sleep($XFER);

        $logObj->debug("Done with sleep. Starting file send operation.");

        # Send all existing files.
        sendExistingFiles($DATA_DIR);

        # Again call sendExistingFiles() to catch any new files created while sending the existing files.
        sendExistingFiles($DATA_DIR);
    }
}

# In this mode files will be sent to the probe appliance as they get created.
#
# @param:
#    None.
#
# @return:
#    Nothing.
sub doRealTimeMode
{
    # Invoke watch process; re-invoke if it exits.
    $logObj->info("Running in Real time mode and will send files as they get created.");

    for ( ; ; )
    {
        # Create the inotify watch process for data directory. "-|" means that interpret file name as command and pipes output of it to us.
        if (open(WATCH, "-|", "$PROCESS"))
        {
            $logObj->debug("Subprocess active: [$PROCESS].");
            doWatchAndSend();
        }
        else
        {
            $logObj->error("Can't invoke '$PROCESS' Error: [$!].");
            sleep(15);
        }
    }
}

# Collect names of new file from our watch process. Accumulate few names so we can do a single send operation for all.
# But wait no more than 5 seconds.
#
# @param:
#    None.
#
# @return:
#    Nothing.
sub doWatchAndSend
{
    my $lastSentTime = time;
    my @FILES = ();

    my $s = IO::Select->new();

    $s->add(\*WATCH);

    # Don't collect more than this many file names at a time to avoid running out of memory.
    my $MAXFILES = 200;

SL: for ( ; ; )
    {
        # If there are new files available and we haven't reached at max file read limit, then continue collecting file name.
        if ($#FILES < $MAXFILES && $s->can_read($SELECT_TIMEOUT))
        {
            # At least one file name is available.
            $logObj->debug("Can read from [$INOTIFY_WATCH].");

            # Wait a little for multiple event string to accumulate.
            sleep($READ_DELAY);

            my $string = "";

            # Get all available event strings.
            my $count = sysread(WATCH, $string, $FNAMEBUF);

            # Check for error.
            if (!defined($count))
            {
                $logObj->error("Not able to read events from inotify process.");
                last SL;
            }

            # No more event is available.
            if ($count eq 0)
            {
                last SL;
            }

            if ($::debug)
            {
                # For debugging, replace newline character with space.
                my $s = $string;
                $s =~ s/\n/ /g;

                $logObj->debug(" Read line from watch process: [$s].");
            }

            # Split into lines
            my @lines = split(/\n/, $string);

            # Loop for all event string which we got.
LN:         for my $line (@lines)
            {
                # Got the event for new file.
                if ($line =~ /^event newfile (.+)$/)
                {
                    my $file = $1;

                    # Skip short file names.
                    # TODO: Why we are doing this?
                    if (length($file) < $MIN_FNAME_SIZE)
                    {
                        $logObj->debug("Skip file with short name: [$file].");
                        next LN;
                    }

                    # Skip new.* files as they are not completely written.
                    if ($file =~ /^new\./)
                    {
                        $logObj->debug("Skip new.* file: [$file].");
                        next LN;
                    }

                    # Add file name to send queue.
                    push(@FILES, $file);

                    $logObj->debug("Queued [$file]. All queued files: [@FILES].");
                }
            }
        }

        # If this many seconds have elapsed, and we have any file names, send them all.
        my $diff = time - $lastSentTime;

        if (@FILES)
        {
            # Don't wait more than accumulation time for start sending files.
            if ($diff > $ACCUMULATE_DELAY)
            {
                # Return value is list of files not yet sent so we can retry.
                $logObj->debug("Accumulation time: [$diff]. Send files: [@FILES].");

                @FILES = sendNewFiles($DATA_DIR, @FILES);

                # Reset start time for next send trigger.
                $lastSentTime = time;
            }
            else
            {
                #$logObj->debug("Accumulation time: [$diff]. Will not send files: [@FILES].");
            }
        }
        else
        {
            $logObj->debug("Accumulation time: [$diff]. No files to send.");
        }
    }
}

# Send files (@files) present in directory ($dir) to remote server. Return a list of files failed to send. However, there is no guarantee
# that this list is accurate so we still need a separate mechanism for periodically sending stray files. If any files in the list do not
# exist, just ignore them.
#
# @param:
#     $_[0] - [In] Directory from which files should be send.
#     $_[1] - [In] List of files.
#
# @return:
#     List of files which are failed to send on remote machine.
sub sendNewFiles
{
    my ($dir, @fileList) = @_;

    # List for files which we are not able to send.
    my @redo = ();

    my $retVal = 0;

    for my $fileName (@fileList)
    {
        # If file exist then send it.
        if (-e "$dir/$fileName")
        {
            $retVal = sendSingleFileWrapper($dir, $fileName);

            # If there is some problem in sending the file then put it in redo list.
            if (0 != $retVal)
            {
                $logObj->error("Call to function sendSingleFileWrapper() failed.");
                push(@redo, $fileName);
            }
        }
        else
        {
            $logObj->error("Ignoring non existent file: [$fileName].");
        }
    }

    # return list of files to be resent
    return (@redo);
}

# This function calls sendSingleFile() function after setting system alarm for time out.
#
# @param:
#     $_[0] - [In] Directory in which file exist.
#     $_[1] - [In] File to send.
#
# @return:
#     0 if Success
#     1 if Error
sub sendSingleFileWrapper
{
    my ($dir, $file) = @_;

    my $retval = 0;

    eval
    {
        # If there is time out in file send operation then eval will set $@ with "alarm".
        local $SIG{ALRM} = sub { die "alarm\n" };

        # Set the time out for file send operation.
        alarm($TIMEOUT);

        $retval = sendSingleFile($dir, $file);

        # Cancel the alarm.
        alarm(0);
    };

    # $@ gives error message from the last eval command.
    if ($@)
    {
        if ($@ ne "alarm\n")
        {
            $logObj->error("Error during file send operation for file: [$file]. Error: [$@].");
            $retval = 1;
        }
        else
        {
            $logObj->info("Time out in file send operation. File: [$file].");
            $retval = 1;
        }
    }

    return $retval;
}

# This function send a file ($file) present in directory ($dir) to probe appliance machine. Caller of this function will handle time out.
# On success, move file into sent sub directory. A file is always locked with flock before it is sent.
#
# @note: The following special situations cause a message to be logged even though the file was not transmitted:
#
# 1). Not a regular file (not sent, left untouched).
# 2). File has zero length (not sent, moved into sent sub directory).
#
# @param:
#     $_[0] - [In] Directory of file to be send.
#     $_[1] - [In] File name to send.
#
#  @return:
#     0 on Success. 1 on Failure.
sub sendSingleFile
{
    my ($dir, $file) = @_;

    my $retval = 0;

    # Path of file to send.
    my $path = "$dir/$file";

    # After sending file to remote machine, file will be moved to sent folder.
    my $sentPath = "$dir/sent/$file";

    # Open file for read-write, no truncate. Write is for locking, read is for transmitting.
    if (! open(INF, '+<', $path))
    {
        $logObj->error("Failed to open file [$file].");
        $retval = 1;

        return $retval;
    }

    # Skip if it is not regular file.
    if (! -f INF)
    {
        $logObj->debug("Skipping non-regular file [$file].");
        $retval = 1;

        goto EXIT;
    }

    # Take lock on file. flock function returns 0 on failure to set/unset lock and 1 on success to set/unset lock.
    # 2 => Exclusive lock and 4 => Non blocking lock.
    if (0 == flock(INF, (2 | 4)))
    {
        $logObj->error("Can't lock $file: [$!].");
        $retval = 1;

        goto EXIT;
    }

    # Get size of file. "_" tells Perl to use last open file handle. No need to open file handle again.
    my $fileSize = -s _;
    if (! $fileSize)
    {
        $logObj->error("Found file with size 0. File: [$file].");
        $retval = 1;

        goto EXIT;
    }

    # Open socket to Probe appliance. Creating object interface of IO::Socket::INET modules which internally creates socket, binds and
    # connects to the TCP server running on the specific port.
    use IO::Socket;
    my $socket = new IO::Socket::INET(
        Proto => 'tcp',
        PeerAddr => $DESTHOST,
        PeerPort => $DESTPORT,
    );

    if (! defined($socket))
    {
        $logObj->error("Socket creation failed.");
        $retval = 1;

        goto EXIT;
    }

    # Following is format of data being transferred:
    #   file name length: 10 decimal digits
    #   blank
    #   checksum: 10 decimal digits
    #   blank
    #   file size: 10 decimal digits
    #   blank
    #   file name
    #   newline

    # Currently we are not implementing checksum logic.
    my $checksum = '9999999999';
    my $fileNameLength = length($file);

    $logObj->debug("Sending file with File name length=[$fileNameLength]. checksum=[$checksum]. File size=[$fileSize] File name=[$file].");

    my $output = sprintf("%010u %010u %010u %s\n", $fileNameLength, $checksum, $fileSize, $file);

    my $msg = transmitData($socket, $output, length($output));

    if (defined($msg))
    {
        $logObj->error("Error sending file to remote host. File: [$path]. Error: [$msg].");
        $retval = 1;

        goto EXIT;
    }

    # If 1 then it means that not able to read any file data.
    my $noFileRead = 1;

    # Buffer to read data.
    my $buf = "";

    # Send actual file data.
L1: for ( ; ; )
    {
        # Read file data.
        my $length = sysread(INF, $buf, $IOBUFSIZE);

        # If get some error, stop sending data.
        if (! defined($length))
        {
            last L1;
        }

        # Read complete file.
        if ($length eq 0)
        {
            last L1;
        }

        $noFileRead = 0;
        $logObj->debug("Sending [$length] bytes.");

        my $msg = transmitData($socket, $buf, length($buf));
        if (defined($msg))
        {
            $logObj->error("Error sending data to remote host. File: [$path]. Error: [$msg].");
            $retval = 1;

            goto EXIT;
        }

        $logObj->debug("Sent [$length] bytes.");
    }

    # If not able to read any data from file then move the file in sent folder.
    if ($noFileRead)
    {
        $logObj->warn("Not able to read any data from file. Moving file into 'sent' folder.");

        rename($path, $sentPath);
        $retval = 1;

        goto EXIT;
    }

    # Read reply from remote machine which will send 'ok' message.
    $logObj->debug("Reading reply.");

    # Buffer to read reply.
    my $reply = "";

    # \$reply means sending the reference.
    $msg = receiveData($socket, \$reply, 3);

    if (defined($msg))
    {
        $logObj->error("Not able to receive data from remote machine. Error: [$!]");
        $retval = 1;

        goto EXIT;
    }

    $logObj->debug("Reply was: [$reply].");

    # If got "ok" message then move file in "sent" folder.
    if ($reply =~ /^ok/)
    {
        $logObj->debug("Got ok reply. Moving [$file] in sent folder.");

        # Ignore rename error.
        rename($path, $sentPath);
    }

EXIT:

    if (defined($socket))
    {
        close($socket);
    }

    close(INF);

    return $retval;
}

# Read file data in a given buffer using sysread() API.
#
# @param:
#    $_[0]: [In] File handle.
#    $_[1]: [Out] Buffer.
#    $_[2]: [In] Data size.
#
# @return:
#    undef on success. Otherwise error code.
sub receiveData
{
    my ($handle, $buf, $count) = @_;

    my $offset = 0;

    for ( ; ; )
    {
        # Read data from remote machine. $$ means dereferencing the value.
        my $got = $handle->sysread($$buf, $count, $offset);

        $logObj->debug("Received buffer: [$buf]. Data size: [$count]. Offset: [$offset]. Read size: [$got].");

        # If not able to read data then return.
        if (! defined($got))
        {
            return $!;
        }

        $count -= $got;

        if ($count <= 0)
        {
            return undef;
        }

        $offset += $got;
    }
}

# Send file data to remote machine using syswrite() API.
#
# @param:
#    $_[0]: [In] File handle.
#    $_[1]: [In] Buffer.
#    $_[2]: [In] Data size.
#
# @return:
#    undef on success. Otherwise error code.
sub transmitData
{
    my ($handle, $buf, $count) = @_;

    my $offset = 0;

    for ( ; ; )
    {
        my $written = $handle->syswrite($buf, $count, $offset);

        # If error in sending data then return with error code.
        if (! defined($written))
        {
            return $!;
        }

        $count -= $written;

        if ($count <= 0)
        {
            last;
        }

        $offset += $written;
    }

    return undef;
}

# This function iterates on files in directory [$dir] and calls sendSingleFileWrapper() function for each file.
#
# @param:
#    $_[0] - [In] Directory to iterate.
#
# @return :
#    Nothing.
sub sendExistingFiles
{
    my ($dir) = @_;

    my @LIST;

    # If not able to open the folder then return.
    if (! opendir(DIR, $dir))
    {
        $logObj->error("Can't open directory [$dir]. $!");
        return;
    }

    $logObj->debug("Opened directory [$dir].");

    my $fileName = "";

    # Read all objects inside the folder.
T1: while ($fileName = readdir(DIR))
    {
        # Ignore objects.
        if ($fileName eq '.' || $fileName eq '..')
        {
            next;
        }

        # If file name starts with "new" then it means that this file has not completely been written.
        if ($fileName !~ /^new\./)
        {
            # Skip short file names.
            if (length($fileName) < $MIN_FNAME_SIZE)
            {
                $logObj->debug("Skip short filename ['$fileName'].");
                next T1;
            }

            $logObj->info("Sending file [$fileName].");

            my $retval = sendSingleFileWrapper($dir, $fileName);

            # We are not able to send the file.
            if (0 != $retval)
            {
                $logObj->error("File send fail for: [$fileName].");
            }
        }
    }
}

# This function is used to read the configuration file.
#
# @affected global variables :
#   $DESTHOST
#   $DESTPORT
#   $INTERVAL
#   $SAMPLES
#   $XFER
#
# @return :
#   0 if Success
#   1 if Error
sub readConfigurationFile
{
    # This variable is used to store the return code for this function.
    my $retVal = 0;

    $logObj->info("Reading file: $RUNTIME_CF");

    open(RUNTIME, $RUNTIME_CF) or die $logObj->error("Not able to open the file: [$RUNTIME_CF].");

    # Loop through the content of .properties file.
    while (<RUNTIME>)
    {
        if (/^DESTHOST=['"]?([a-zA-Z0-9-._]+)/)
        {
            $DESTHOST = $1;
        }
        elsif (/^DESTPORT=(\d+)/)
        {
            $DESTPORT = $1;
        }
        elsif (/^INTERVAL=(\d+)/)
        {
            $INTERVAL = $1;
        }
        elsif (/^SAMPLES=(\d+)/)
        {
            $SAMPLES = $1;
        }
        elsif (/^XFER=(\d+)/)
        {
            $XFER = $1;
        }
    }

    if ('%DESTHOST%' eq $DESTHOST)
    {
        $logObj->error("DESTHOST is not initialized in [$RUNTIME_CF].");
        $retVal = 1;

        goto EXIT;
    }

    # Over-write default transfer interval.
    if (defined($::opt_B))
    {
        $XFER = $::opt_B;
    }


EXIT:

    close(RUNTIME);

    return $retVal;
}

# Get a write lock on file $lockFile using file handle $fh. Create file if it does not exist and put our PID into the file.
#
# @param:
#    $_[0]: [In] Lock file
#    $_[1]: [In] File Handle.
#
# @return:
#    On error, return error message. On failure, return undef.
sub getLock
{
    my ($lockFile, $fh) = @_;

    # Create lock file if not exist.
    if (open($fh, '>>', $lockFile))
    {
        close($fh);
    }

    # Open the lock file for write purpose.
    if (!open($fh, '+<', $lockFile))
    {
        return "Can not open '$lockFile'. Error: [$!].";
    }

    # Move the position to the start of the file.
    seek($fh, 0, 0);

    # Get a file lock. 2 => Exclusive lock and 4 => Non blocking lock.
    if (0 == flock($fh, (2 | 4)))
    {
        return "Can't lock '$lockFile'. Error: [$!].";
    }

    # Truncate the file.
    truncate($fh, 0);

    # Set auto-flush after any write operation on file.
    $fh->autoflush(1);

    # Write PID in file.
    print($fh, "$$\n");

    # Flush. Ignore write errors.
    print($fh, "");

    # Don't close lock file. We need to keep it open to keep it locked.

    return undef;
}

# This function is used to get the Loopback IP address
#
# @param:
#     None
#
# @return:
#     Loopback IP address list.
sub getLoopBackIps
{
    # Get ifconfig output.
    my $ifconfig = `/sbin/ifconfig -a`;

    # Used to decide the line which contain IP address.
    my $readNextLine = 0;

    # This list is used to store complete line having IP address.
    my @outArray = ();

    # Output list which contain Loopback IP address.
    my @outIpList = ();

    # Split output for each line.
    for (split(/\n/, $ifconfig))
    {
        # If current line is empty then go to next line.
        if (/^$/)
        {
            next;
        }

        # If current line doesn't have "Loopback" string or exact "inet" string (no "inet6") then go to next line.
        if ($_ !~ /Loopback|\binet\b/)
        {
            next;
        }

        # This line has IP address.
        if ($readNextLine == 1)
        {
            push(@outArray, $_);
        }

        # If current line has "Loopback" string then means that next line will have IP address.
        if (~/Loopback$/)
        {
            $readNextLine = 1;
        }
        # If current line has no "Loopback" string then means that we already found IP address.
        else
        {
            $readNextLine = 0;
        }
    }

    # Use only those lines where "inet" is at the start of line.
    my @ipArray = grep {/^\s*inet/} @outArray;

    foreach (@ipArray)
    {
        # Find IP address and push in output list.
        if (m/addr:(.*)\s+M/)
        {
            push(@outIpList, $1);
        }
    }

    return @outIpList;
}

# This function is used to return an IP address which is not a loopback IP.
#
# @param
#    None
#
# @return
#    IP address which is not loopback IP.
sub getNonLoopBackIp
{
    # Get IP address list as a single string.
    my $ifconf = `/sbin/ifconfig | sed -n '/.*inet .*dr:/{;s/.*dr://;s/ .*//;p;}'`;

    # Get IP addresses in a list.
    my @ips = split(/\n/, $ifconf);

    # Get the list of loopback IPs.
    my @loopBackIps = getLoopBackIps();

    # Loop for all IPs.
    for (my $i = 0; $i <= $#ips; $i++)
    {
        # Loop for all loopback IPs.
        foreach my $loopBackaddr (@loopBackIps)
        {
            # If we found any IP which is not a loopback IP then return that IP.
            if ($ips[$i] ne $loopBackaddr)
            {
                return $ips[$i];
            }
        }
    }

    return undef;
}

# Print help message on console.
#
# @param:
#    None.
#
# @return:
#    Nothing.
sub giveHelp
{
   print <<EOF;
   $::usage
Invoke a subprocess to watch our data directory. As files
gets created, send each file to MARS Probe appliance.

 -q dir     : Sent all files from specified directory.
 -N         : Do not lock directory (individual files are always locked).
 -x         : Enable debugging. For program maintainers.
 -h         : Help.
 -d dir     : Installation directory.
 -B seconds : Overrides configuration setting for transferring data.
EOF
}