#!/usr/bin/perl

use File::Find;

###########################################################################

# rsnapgraph
# version 0.6
# 2005-03-31
#
# Copyright (c) 2005, Denis McLaughlin
# Released under the GPL license, version 2
# http://www.gnu.org/copyleft/gpl.html
#
# http://denism.homeip.net/software/rsnapgraph
#
# This script processes an rsnapshot root directory (containing the daily.0,
# daily.1, etc directories), and produces graphs showing the rate at which
# files are being added and removed, in terms of the number of files, and
# their size.

###########################################################################

# a quick title
print "\n";
print "rsnapgraph running...\n";
print "\n";

# initialize from commandline and config file
init();

# a summary of what's going to happen
summary();

# This is the beginning of the main processing:
#  - get a list of directories (daily.*, weekly.*, etc) to be processed
#  - for each directory (from least to most recently modified), compare
#    the number and size of files between them, either by traversing
#    the directory or by reading a previously cached data file for it
#  - between each directory, generate the relative and absolute file
#    and disk space changes
#  - plot the data

# get the list of directories to process
opendir ROOT,"$rootdir" or die "error opening $rootdir: $!\n";
@tmpdirs = grep !/^\./,readdir ROOT;
closedir ROOT;

# remove any that aren't directories
foreach $dir (sort @tmpdirs)
{
    if ( -d "$rootdir/$dir" )
    { push @dirs,$dir; }
}

# figure out the time stamp of each of the directories
foreach $dir (@dirs)
{
    # we only care about the mod time
    ($junk,$inode,$junk,$junk,$junk,$junk,$junk,$junk,$junk,$mod,@junk)
        = stat("$rootdir/$dir");
    $topdir{$inode}{name} = $dir;
    $topdir{$inode}{mod} = $mod;
}

# if we're supposed to clean the working directory, we do it now
# if we see files in $workdir that don't correspond to inodes for
# directories in $rootdir, then we delete
if ( $clean )
{
    print "cleaning $workdir\n";

    # open the workdir and get the list of files
    opendir WORK,"$workdir" or die "error opening $workdir: $!\n";
    @workfiles = grep !/^\./,readdir WORK;
    closedir WORK;

    # foreach file, if it's not an inode file, delete it
    foreach $workfile (@workfiles)
    {
        if ( ! defined($topdir{$workfile}) && -f "$workdir/$workfile")
        {
            print "  removing $workdir/$workfile\n";
            unlink "$workdir/$workfile";
        }
    }
    print "\n";
}

# this puts the inodes into @inodes from least to most recently modified
@inodes = sort { $topdir{$a}{mod} <=> $topdir{$b}{mod} } keys %topdir;

# if @inodes only has a single entry, we can't graph differences, so abort
if ( $#inodes < 1 )
{
    die "must have at least two directories to graph\n";
}

# this prepares for the first directory, which has nothing to change from
$file{$inodes[0]}{inc} = 0;
$file{$inodes[0]}{dec} = 0;
$file{$inodes[0]}{net} = 0;
$file{$inodes[0]}{mod} = $topdir{$inodes[0]}{mod};
$file{$inodes[0]}{name} = $topdir{$inodes[0]}{name};
$file{max} = 0;
$file{min} = 999999999999;

$size{$inodes[0]}{inc} = 0;
$size{$inodes[0]}{dec} = 0;
$size{$inodes[0]}{net} = 0;
$size{$inodes[0]}{mod} = $topdir{$inodes[0]}{mod};
$size{$inodes[0]}{name} = $topdir{$inodes[0]}{name};
$size{max} = 0;
$size{min} = 999999999999;


# for each of the directories, we need to get the inodes and sizes of all
# their subtended files: if we've run before, we can read it from a cached
# copy, otherwise we have to traverse the directory
foreach $inode (@inodes)
{
    $dir = "$topdir{$inode}{name}";

    # check if we already have a list of inodes and sizes for this directory
    if ( -f "$workdir/$inode" )
    {
        print "reading cache for $dir\n";
        open DATA,"<$workdir/$inode"
            or die "error opening $workdir/$inode: $!\n";

        # if so, suck em up
        while (<DATA>)
        {
            # look for lines of the form:
            # 553190 11919
            if ( $_ =~ /^[0-9]+\s+[0-9]+/ )
            {
                ($finode,$size,$junk) = split ' ',$_;
                $current{$finode}{size} = $size;
                $currentCount++;
                $currentSize += $size;
            }
        }
    }
    else
    {
        # if we haven't already got the data, we traverse the directory
        print "traversing $dir\n";

        # this will traverse the directory, and put the inode/size pairs
        # into the global %topdir hash
        find(\&wanted, ("$rootdir/$dir"));

        # and write the data out to be reused next time
        open DATA,">$workdir/$inode"
            or die "error opening $workdir/$inode: $!\n";
        foreach $finode ( keys %current )
        {
            print DATA
                "$finode $current{$finode}{size} $current{$finode}{name}\n";
        }
        close DATA;
    }

    # store the aggregate count and size
    $file{$inode}{size} = $currentSize;
    $file{$inode}{count} = $currentCount;
    $y2maxSize = $currentSize if ( $currentSize > $y2maxSize );
    $y2maxCount = $currentCount if ( $currentCount > $y2maxCount );
    $currentSize = 0;
    $currentCount = 0;

    # if this is the second or subsequent time through, generate the data 
    if ( defined(%previous) )
    {
        # compute the file and size differences
        ($file{$inode}{inc},$file{$inode}{dec}) =
            filediff(\%previous,\%current);
        ($size{$inode}{inc},$size{$inode}{dec}) =
            sizediff(\%previous,\%current);

        # compute some stats
        $file{$inode}{net} = $file{$inode}{inc} + $file{$inode}{dec};
        $size{$inode}{net} = $size{$inode}{inc} + $size{$inode}{dec};
        $file{max} = $file{$inode}{inc}
            if ( $file{$inode}{inc} > $file{max} );
        $file{max} = abs($file{$inode}{dec})
            if ( abs($file{$inode}{dec}) > $file{max} );
        $size{max} = $size{$inode}{inc}
            if ( $size{$inode}{inc} > $size{max} );
        $size{max} = abs($size{$inode}{dec})
            if ( abs($size{$inode}{dec}) > $size{max} );
    }

    # and set current to be the previous
    %previous = %current;  undef(%current);
}
print "\n";

# tidy up some memory
undef(%previous);

#debug();

# now graph stuff
fileGraph();
sizeGraph();


###########################################################################
# this uses the %topdir hash to graph the change in the number of files
# over time, producing the rsnapgraph-files.png graph

sub fileGraph
{
    # start gnuplot
    open GNUPLOT, "|gnuplot" or die "error running gnuplot: $!\n";

    # set the graph file name, depending on whether we're doing symlinks    
    $graphfilebase = makeGraphName("rsnapgraph-files");
    $graphfile = "$graphdir/$graphfilebase";

    # set some variables for convenience
    $ymax = $file{max} * 1.1;
    $y2max = $y2maxCount * 1.1;
    $xmax = $#inodes+2;
    $datestring = getDate(time());
    
    # prepare the plot
    print GNUPLOT "set terminal png\n";
    print GNUPLOT "set grid\n";
    print GNUPLOT "set key left bottom\n";
    print GNUPLOT "set style fill solid 1.0\n";
    print GNUPLOT "set xlabel 'time of snapshot (month/day)'\n";
    print GNUPLOT "set xlabel ,-1\n";
    print GNUPLOT "set xzeroaxis -1\n";
    print GNUPLOT "set y2tics\n";
    print GNUPLOT "set ytics nomirror\n";
    print GNUPLOT "set ylabel 'change in number of files (rel #)'\n";
    print GNUPLOT "set y2label 'number of files (abs #)'\n";
    print GNUPLOT "set output '$graphfile'\n";
    print GNUPLOT "set yrange [-$ymax:$ymax]\n";
    print GNUPLOT "set y2range [-$y2max:$y2max]\n";
    print GNUPLOT "set xrange [0:$xmax]\n";
    print GNUPLOT "set boxwidth 0.25\n";
    print GNUPLOT "set key left\n";
    print GNUPLOT "set size 1.5,1\n";
    print GNUPLOT "set title ",
        "\"Snapshot File Change\\n",
        "Generated on $datestring\"\n";
    
    # this sets up the column names
    $count = 1; $sep = "";
    print GNUPLOT "set xtics (";
    foreach $inode (@inodes)
    {
        ($n = $topdir{$inode}{name}) =~ s/\.[0-9]+//;
        $date = getShortDate($topdir{$inode}{mod});
        print GNUPLOT "$sep \"$n\\n$date\" $count ";
        $sep = ",";
        $count++;
    }
    print GNUPLOT ")\n";
        
    
    print GNUPLOT "plot ",
        " '-' title 'number of new files (rel #)' with boxes fill 2 ",
        ", '-' title 'number of deleted files (rel #)' with boxes fill 1 ",
        ", '-' axis x1y2 title 'number of files (abs #)' with lines lw 50",
        "\n";
    
    # print the file increase data
    $count=1;
    foreach $inode (@inodes)
    {
        print GNUPLOT "$count $file{$inode}{inc}\n";
        $count++;
    }
    print GNUPLOT "e\n";
    
    # print the file decrease data
    $count=1;
    foreach $inode (@inodes)
    {
        print GNUPLOT "$count $file{$inode}{dec}\n";
        $count++;
    }
    print GNUPLOT "e\n";
    
    # print the number of files
    $count=1;
    foreach $inode (@inodes)
    {
        print GNUPLOT "$count $file{$inode}{count}\n";
        $count++;
    }
    print GNUPLOT "e\n";
    
    print "file graph is in $graphfile\n";
    close GNUPLOT;

    # if we're symlinking, then do it
    if ( $symlink )
    {
        unlink("$graphdir/rsnapgraph-files.png")
            if ( -f "$graphdir/rsnapgraph-files.png");
        symlink($graphfilebase,"$graphdir/rsnapgraph-files.png");
    }
}


###########################################################################
# this uses the %topdir hash to graph the change in the size
# over time, producing the rsnapgraph-files.png graph

sub sizeGraph
{
    # start gnuplot
    open GNUPLOT, "|gnuplot" or die "error running gnuplot: $!\n";

    # set the graph file name, depending on whether we're doing symlinks    
    $graphfilebase = makeGraphName("rsnapgraph-size");
    $graphfile = "$graphdir/$graphfilebase";
    
    # set some variables for convenience
    $ymax = $size{max} * 1.1;
    $y2max = $y2maxSize * 1.1;
    $xmax = $#inodes+2;
    $datestring = getDate(time());

    # depending on ymax, figure out a good scale
    $yunits = "bytes" , $yfactor = 1;
    $yunits = "Kbytes" , $yfactor = 1024 if ( $ymax > 1024 );
    $yunits = "Mbytes" , $yfactor = 1048576 if ( $ymax > 1048576 );
    $yunits = "Gbytes" , $yfactor = 1073741824 if ( $ymax > 1073741824 );
    $ymax /= $yfactor;

    # depending on y2max, figure out a good scale
    $y2units = "bytes" , $y2factor = 1;
    $y2units = "Kbytes" , $y2factor = 1024 if ( $y2max > 1024 );
    $y2units = "Mbytes" , $y2factor = 1048576 if ( $y2max > 1048576 );
    $y2units = "Gbytes" , $y2factor = 1073741824 if ( $y2max > 1073741824 );
    $y2max /= $y2factor;
    
    # prepare the plot
    print GNUPLOT "set terminal png\n";
    print GNUPLOT "set grid\n";
    print GNUPLOT "set key left bottom\n";
    print GNUPLOT "set style fill solid 1.0\n";
    print GNUPLOT "set xlabel ' '\n";
    print GNUPLOT "set xlabel ,-1\n";
    print GNUPLOT "set xzeroaxis -1\n";
    print GNUPLOT "set y2tics\n";
    print GNUPLOT "set ytics nomirror\n";
    print GNUPLOT "set ylabel 'change in disk space (rel $yunits)'\n";
    print GNUPLOT "set y2label 'disk space (abs $y2units)'\n";
    print GNUPLOT "set output '$graphfile'\n";
    print GNUPLOT "set yrange [-$ymax:$ymax]\n";
    print GNUPLOT "set y2range [-$y2max:$y2max]\n";
    print GNUPLOT "set xrange [0:$xmax]\n";
    print GNUPLOT "set boxwidth 0.25\n";
    print GNUPLOT "set key left\n";
    print GNUPLOT "set size 1.5,1\n";
    print GNUPLOT "set title ",
        "\"Snapshot Disk Space Change\\n",
        "Generated on $datestring\"\n";
    
    # this sets up the column names
    $count = 1; $sep = "";
    print GNUPLOT "set xtics (";
    foreach $inode (@inodes)
    {
        ($n = $topdir{$inode}{name}) =~ s/\.[0-9]+//;
        $date = getShortDate($topdir{$inode}{mod});
        print GNUPLOT "$sep \"$n\\n$date\" $count ";
        $sep = ",";
        $count++;
    }
    print GNUPLOT ")\n";
        
    
    print GNUPLOT "plot ",
        " '-' title 'size of new files (rel $yunits)' with boxes fill 2 ",
        ", '-' title 'size of deleted files (rel $yunits)' with boxes fill 1 ",
        ", '-' axis x1y2 title 'net change (abs $y2units)' with lines lw 50",
        "\n";
    
    # print the size increase data
    $count=1;
    foreach $inode (@inodes)
    {
        print GNUPLOT "$count ",$size{$inode}{inc}/$yfactor,"\n";
        $count++;
    }
    print GNUPLOT "e\n";
    
    # print the size decrease data
    $count=1;
    foreach $inode (@inodes)
    {
        print GNUPLOT "$count ",$size{$inode}{dec}/$yfactor,"\n";
        $count++;
    }
    print GNUPLOT "e\n";
    
    # print the size data
    $count=1;
    foreach $inode (@inodes)
    {
        #print GNUPLOT "$count ",$size{$inode}{net}/$yfactor,"\n";
        print GNUPLOT "$count ",$file{$inode}{size}/$y2factor,"\n";
        $count++;
    }
    print GNUPLOT "e\n";
    
    print "size graph is in $graphfile\n";
    close GNUPLOT;

    # if we're symlinking, then do it
    if ( $symlink )
    {
        unlink("$graphdir/rsnapgraph-size.png")
            if ( -f "$graphdir/rsnapgraph-size.png");
        symlink($graphfilebase,"$graphdir/rsnapgraph-size.png");
    }
}


###########################################################################
# this processes each file, adding {<inode>} = <size> to %data for each file

sub wanted
{
    # this is the basename of $_
    my $base = $File::Find::dir;

    # if it's a directory, we don't care: directories can't be hard linked
    # (leading to spurious new file results) and their size isn't really
    # relevant
    return if ( -d "$base/$_" );

    # fields from stat() are:
    # dev, ino, mode, nlink, uid, gid, rdev, size, atime, mtime,
    # ctime, blksize, blocks: we care about field 1 (inode) and field
    # 7 (size in bytes)
    @fields = stat("$_");

    # and now we stuff it into the topdir hash: $inode was set when we
    # called find()
    $current{$fields[1]}{size} = $fields[7];
    $current{$fields[1]}{name} = "$base/$_";
    $currentCount++;
    $currentSize += $fields[7];
}


###########################################################################
# this takes two hashes (the {data} key from %topinode) and returns the
# number of new ($inc) and deleted ($dec) files from the first to the second

sub filediff
{
    my(%a) = %{$_[0]};
    my(%b) = %{$_[1]};
    my($inc,$dec,$inode);
    $inc = 0; $dec = 0;

    # step through the a hash: if there's an entry that's not in b, then
    # it's been deleted, so $dec--
    foreach $inode (keys %a)
    { $dec-- if ( !defined($b{$inode}) ); }

    # step through the b hash: if there's an entry that's not in a, then
    # it's been added, so $inc++
    foreach $inode (keys %b)
    { $inc++ if ( !defined($a{$inode}) ); }

    return($inc,$dec);
}


###########################################################################
# this takes two hashes (the {data} key from %topinode) and returns the
# size of the new ($inc) and deleted ($dec) files from the first to the second

sub sizediff
{
    my(%a) = %{$_[0]};
    my(%b) = %{$_[1]};
    my($inc,$dec,$inode);
    $inc = 0; $dec = 0;

    # step through the a hash: if there's an entry that's not in b, then
    # it's been deleted, so $dec-=<size>
    foreach $inode (keys %a)
    { $dec-=$a{$inode}{size} if ( !defined($b{$inode}) ); }

    # step through the b hash: if there's an entry that's not in a, then
    # it's been added, so $inc+=<size>
    foreach $inode (keys %b)
    { $inc+=$b{$inode}{size} if ( !defined($a{$inode}) ); }

    return($inc,$dec);
}


###########################################################################
# this returns a nicely formatted date string, corresponding to the epoch
# seconds value passed into the function

sub getDate
{
    my($sec,$min,$hour,$day,$month,$year) = localtime($_[0]);
    $year += 1900 if ( $year < 1900 );
    $month++;
    $sec = "0$sec" if ( $sec < 10 );
    $min = "0$min" if ( $min < 10 );
    $hour = "0$hour" if ( $hour < 10 );
    $day = "0$day" if ( $day < 10 );
    $month = "0$month" if ( $month < 10 );

    return "$year/$month/$day $hour:$min:$sec";
}


###########################################################################
# this returns a nicely formatted date string, corresponding to the epoch
# seconds value passed into the function

sub getShortDate
{
    my($second,$minute,$hour,$day,$month,$year,@junk) = localtime($_[0]);
    $year += 1900;
    $month ++;
    $month = "0$month" if ( $month < 10 );
    $day = "0$day" if ( $day < 10 );

    return "$month/$day";
}


###########################################################################
# a support routine to print stuff out

sub debug
{
    foreach $inode (@inodes)
    {
        print "$topdir{$inode}{name}:\n";
        print "  file inc: $file{$inode}{inc}\n";
        print "  file dec: $file{$inode}{dec}\n";
        print "  file net: $file{$inode}{net}\n";
        print "  size inc: $size{$inode}{inc}\n";
        print "  size dec: $size{$inode}{dec}\n";
        print "  size net: $size{$inode}{net}\n";
        print "\n";
    }
}


###########################################################################
# This routine sets several variables which determine where we do things.
# These are set to an initial default value, which can then be overridden
# by config files (which take precedence over the defaults), or via
# commandline switches (which take precedence over the config file).

sub init
{
    # this is the script version
    $version = "0.6";

    # these are base values for these variables: they can be overridden
    # by a config file, or by the command line switches
    $baseconf = "/etc/rsnapgraph.conf";
    $baseclean = 0;
    $basesymlink = 0;

    # process our command line switches
    while ($arg = shift @ARGV )
    {
        # set the config file
        if ( $arg eq "-f" )
        {
            $argconf = shift @ARGV;
            die "no -f argument specified, aborting"
               
                if ( ! length $argconf );
            next;
        }

        # set the root dir
        if ( $arg eq "-d" )
        {
            $argrootdir = shift @ARGV;
            die "no -d argument specified, aborting"
                if ( ! length $argrootdir );
            next;
        }

        # set the work dir
        if ( $arg eq "-w" )
        {
            $argworkdir = shift @ARGV;
            die "no -w argument specified, aborting"
                if ( ! length $argworkdir );
            next;
        }

        # set the graph dir
        if ( $arg eq "-g" )
        {
            $arggraphdir = shift @ARGV;
            die "no -g argument specified, aborting"
                if ( ! length $arggraphdir );
            next;
        }

        # turn cleaning on
        if ( $arg eq "-c" ) { $argclean = 1; next; }

        # turn symlinking on
        if ( $arg eq "-s" ) { $argsymlink = 1; next; }

        # print help
        if ( $arg eq "-h" ) { usage(); exit; }

        # if we get here, we don't recognize the option
        die "unknown option: $arg, aborting\n"
    }

    # we determine the correct config file to read: $argconf takes
    # precedence over $baseconf
    $conf = $baseconf;
    $conf = $argconf if ( $argconf );

    # process the specified config file
    readConf();

    # now resolve the arg versus conf variables: arg values take precedence
    # over conf values which take precedence over base values (if there is any)
    $rootdir = $confrootdir;
    $rootdir = $argrootdir if ( $argrootdir );

    $workdir = $confworkdir;
    $workdir = $argworkdir if ( $argworkdir );

    $graphdir = $confgraphdir;
    $graphdir = $arggraphdir if ( $arggraphdir );

    $clean = $baseclean;
    $clean = $confclean if ( $confclean );;
    $clean = $argclean if ( $argclean );

    $symlink = $basesymlink;
    $symlink = $confsymlink if ( $confsymlink );;
    $symlink = $argsymlink if ( $argsymlink );

    # check that rootdir was set
    if ( length($rootdir) == 0 )
    { die "no rootdir set: use -d or set the rootdir parameter in $conf\n"; }

    # check that workdir was set
    if ( length($workdir) == 0 )
    { die "no workdir set: use -w or set the workdir parameter in $conf\n"; }

    # check that graphdir was set
    if ( length($graphdir) == 0 )
    { die "no graphdir set: use -g or set the graphdir parameter in $conf\n"; }

    # (all other values have defaults)

    # check that the variables are valid
    if ( ! -d $rootdir )
    { die "rootdir $rootdir doesn't exist: cannot continue\n"; }

    if ( ! -d $workdir )
    { die "workdir $workdir doesn't exist: cannot continue\n"; }

    if ( ! -d $graphdir )
    { die "graphdir $graphdir doesn't exist: cannot continue\n"; }
}


###########################################################################
# This routine prints a summary of the scripts configuration information.

sub summary
{
    print "\n";
    print "config file: $conf\n";
    print "root directory: $rootdir\n";
    print "work directory: $workdir\n";
    print "graph directory: $graphdir\n";

    print "clean work directory: ";
    print "true\n" if ( $clean );
    print "false\n" if ( ! $clean );

    print "symlink graphs: ";
    print "true\n" if ( $symlink );
    print "false\n" if ( ! $symlink );

    print "\n";
}


###########################################################################
# This routine takes a directory as an argument: if the directory really
# exists, it returns true, otherwise it prints an error message and returns
# false.

sub checkDir
{
    if ( ! -d $_[0] )
    {
        print STDERR "cannot find directory $$_[0]: ignoring\n";
        return 0;
    }

    return 1;
}


###########################################################################
# This routine reads $conf as a config file, setting $confrootdir,
# $confworkdir, and $confgraphdir as read from the file.  If $conf doesn't
# exist, an error is printed.

sub readConf
{
    # print an error and return if $conf does not exist
    if ( ! -f $conf )
    {
        print "config file $conf does not exist: skipping\n";
        return;
    }

    # if it does exist, we read it line by line, setting $confrootdir,
    # $confworkdir, and $confgraphdir as (and if) we read them
    open CONF,"<$conf" or die print "error reading config file $conf: $!\n";

    print "reading config file: $conf\n";
    while (<CONF>)
    {
        # set the rootdir
        if ( /^\s*rootdir\s*=\s*([^\s]+)\s*$/ )
        { $confrootdir = $1; next; }

        # set the workdir
        if ( /^\s*workdir\s*=\s*([^\s]+)\s*$/ )
        { $confworkdir = $1; next; }

        # set the graphdir
        if ( /^\s*graphdir\s*=\s*([^\s]+)\s*$/ )
        { $confgraphdir = $1; next; }

        # set clean
        if ( /^\s*clean\s*=\s*([^\s]+)\s*$/ )
        {
            $confclean = $1;
            $confclean =~ s/true/1/i;
            $confclean =~ s/false/0/i;
            $confclean =~ s/yes/1/i;
            $confclean =~ s/no/0/i;
            die "bad clean value in $conf: $1"
                if ( $confclean != "0" && $confclean != "1" );
            next;
        }

        # set symlink
        if ( /^\s*symlink\s*=\s*([^\s]+)\s*$/ )
        {
            $confsymlink = $1;
            $confsymlink =~ s/true/1/i;
            $confsymlink =~ s/false/0/i;
            $confsymlink =~ s/yes/1/i;
            $confsymlink =~ s/no/0/i;
            die "bad symlink value in $conf: $1"
                if ( $confsymlink != "0" && $confsymlink != "1" );
            next;
        }

        # skip blank and commented lines
        next if ( /^\s*$/ );
        next if ( /^\s*#.*$/ );

        # everything else is an error
        die "\ninvalid symbol at line $. in $conf:\n$_";
    }
}


###########################################################################
# This prints a usage text for this program

sub usage
{
    print STDERR "
rsnapgraph version $version

rsnapgraph [-h] [-c] [-s]
           [-f <conf>] [-d <rootdir>] [-w <workdir>] [-g <graphdir>]

Generates a set of graphs showing file and disk space changes for the
rsnapshot maintained directories located in <rootdir>.

 -h            : prints this help text

 -c            : will clean files out of <workdir> for which no corresponding
                 directory in <rootdir> can be found (default: false)

 -s            : will create dated graphs, symlinked to a generic name

 -f <conf>     : will read configuration settings from the file <conf>
                 (default: /etc/rsnapgraph.conf)

 -d <rootdir>  : will process the rsnapshot-maintained directories located
                 in <rootdir>, ie. <rootdir> contains the daily.*, weekly.*,
                 etc. directories (default: none, must be specified)

 -w <workdir>  : will store working data in <workdir>; this data will be
                 reused on subsequent runs, to speed up processing
                 (default: ./)

 -g <graphdir> : will store the generated graphs in <graphdir> (default: ./)


Config file has the following format:
 - blank lines and lines beginning with # are ignored
 - valid lines are in name value pairs:
    <name>=<value>
   with white space permitted anywhere in the line
 - valid names are: rootdir, workdir, graphdir, clean, symlink
 - clean and symlink can be specified with 1/0, true/false, yes/no, case
   insensitively

Example config file:

    rootdir = /bak
    workdir = /data/rsnapgraph
    graphdir = /data/html/graphs/rsnapgraph
    clean = false
    symlink = true
";
}


###########################################################################
# This routine takes a single string argument <root>, and generates a
# date-based file name, using that <root> value, specifically it will
# return <root>-YYYY.MM.DD-hh.mm.ss.png, where YYYY, MM, DD are the year,
# month, and day, and hh, mm, ss are the hour, minute and second.

sub makeGraphName
{
    my ($sec,$min,$hour,$day,$month,$year);

    if ( $symlink )
    {
        ($sec,$min,$hour,$day,$month,$year) = localtime();
        $year += 1900 if ( $year < 1900 );
        $month++;
        $sec = "0$sec" if ( $sec < 10 );
        $min = "0$min" if ( $min < 10 );
        $hour = "0$hour" if ( $hour < 10 );
        $day = "0$day" if ( $day < 10 );
        $month = "0$month" if ( $month < 10 );

        return "$_[0]-$year.$month.$day-$hour.$min.$sec.png";
    }
    else
    {
        return "$_[0].png";
    }
}
