#!/usr/bin/perl

###############################################################################
## Copyright (c) 2007-2012 SUSE LINUX Products GmbH, Nuernberg, Germany.
###############################################################################

use strict;
use warnings;
use FindBin;
BEGIN { unshift @INC, "$FindBin::Bin/../www/perl-lib" }

use SMT::Mirror::RpmMd;
use SMT::Mirror::Yum;
use SMT::Mirror::Utils;
use SMT::Parser::RpmMdPatches;
use SMT::Patch;
use SMT::Utils;
use SMT::CLI;
use SMT::Parser::RegData;
use SMT::Repositories;
use SMT::Filter;
use Config::IniFiles;
use File::Path;
use URI;
use URI::file;
use Getopt::Long;
use File::Basename;
use File::Temp;
use Time::HiRes qw(gettimeofday tv_interval);

#use Data::Dumper;

use Locale::gettext ();
use POSIX ();     # Needed for setlocale()

POSIX::setlocale(&POSIX::LC_MESSAGES, "");

# Autoflush the STDOUT - Needed for YaST .process agent to read
# the output ASAP and to display that to a user
$| = 1;

#
# FIXME: what about /root/.curlrc for proxy settings?
#
if(!SMT::Utils::dropPrivileges())
{
    print STDERR __("Unable to drop privileges. Abort!\n");
    exit 1;
}

$SIG{INT}  = \&signal_handler;
$SIG{TERM} = \&signal_handler;

my $stagingDir = "full";

my $debug    = 0;
my $vblevel  = LOG_ERROR|LOG_WARN|LOG_INFO1|LOG_INFO2;

my $clean    = 0;
my $hardlink = undef;
my $deepverify = 0;
my $dryrun     = 0;

my $dbreplfile = undef;
my $dbreplacement = [];

my $LocalBasePath = "";
my $FromLocalBasePath = "";

my $help     = 0;
my $logfile = "/dev/null";
my $mail     = 0;
my $mailtempfile = undef;

my $download = 0;
my $link = 0;
my $copy = 0;
my $errors = 0;
my $size = 0;
my $secpatches = 0;
my $recpatches = 0;
my %secpatchtitles = ();
my %recpatchtitles = ();
my $fromlocalsmt = 0;

my $nohardlink = 0;

my $repository;

Getopt::Long::Configure( 'no_auto_abbrev');
my $optres = GetOptions ("debug|d"      => \$debug,
                         "verboselevel|v=i" => \$vblevel,
                         "clean|c"      => \$clean,
                         "hardlink=i"   => \$hardlink,
                         "directory=s"  => \$LocalBasePath,
                         "fromdir=s"    => \$FromLocalBasePath,
                         "fromlocalsmt" => \$fromlocalsmt,
                         "deepverify"   => \$deepverify,
                         "dryrun|n"     => \$dryrun,
                         "testrun"      => \$dryrun,
                         "nohardlink"   => \$nohardlink,
                         "dbreplfile=s" => \$dbreplfile,
                         "logfile|L=s"  => \$logfile,
                         "mail|m"       => \$mail,
                         "help|h"       => \$help,
                         "repository|r=s" => \$repository
                        );


if($help || !$optres)
{
    print basename($0) . " [--directory path] [--deepverify] [--testrun] [--dbreplfile file] [-L file] [--debug] [--verboselevel level] [--nohardlink] [--mail] [--repository repositoryid]\n";
    print basename($0) . " --clean [--directory path] [--dbreplfile file] [-L file] [--debug]\n";
    print basename($0) . " --hardlink size [--directory path] [-L file] [--debug]\n";
    print "\n";
    print __("Options:\n");
    print "--debug -d              " . __("enable debug mode\n");
    print "--verboselevel -v level " . __("set the verbose level\n");
    print "--clean -c              " . __("cleanup all mirrored repositories.\n");
    print "                        " . __("Remove all files no longer mentioned in the metadata.\n");
    print "                        " . __("It does not mirror new files.\n");
    print "--hardlink size         " . __("Search for duplicate files with size > 'size' (in Kilobytes) and create hardlinks\n");
    print "                        " . __("for them\n");
    print "--directory arg         " . __("The directory to work on. Using this option ignores the configured\n");
    print "                        " . __("default value in smt.conf\n");
    print "--fromdir arg           " . __("Mirror from the directory give here, instead of mirroring from the remote sources.\n");
    print "                        " . __("The directory can e.g. be created by calling smt-mirror --directory\n");
    print "--fromlocalsmt          " . __("Mirror from the local running SMT server. This options requires the --directory\n");
    print "                        " . __("option being set. The resulting directory can  e.g later be imported to another server\n");
    print "                        " . __("using smt-mirror --fromdir\n");
    print "--deepverify            " . __("Verify all checksums \n");
    print "--testrun               " . __("Run mirror algorithm without downloading the rpms \n");
    print "                        " . __("(but it needs to download the metadata into a temporary directory).\n");
    print "                        " . __("It shows only the names which would be downloaded \n");
    print "--nohardlink            " . __("If a file already exists on the local harddisk do not link it into the\n");
    print "                        " . __("mirrored repository, but copy it.\n");
    print "--dbreplfile arg        " . __("Path to XML file to use as database replacement. Such a file can\n");
    print "                        " . __("be created with the sync-scc command. This option is only\n");
    print "                        " . __("useful if the smt database does not exist on the host from\n");
    print "                        " . __("which the smt-mirror script is being executed.\n");
    print "--mail -m               " . __("Send output as e-mail to the administrators defined in reportEmail in smt.conf.\n");
    print "                        " . __("The output on stdout and stderr will be suppressed.\n");
    print "--logfile -L file       " . __("Path to logfile\n");
    print "--repository ID         " . __("Repository ID to mirror. By default, all repositories are selected. Not allowed with --dbreplfile.\n");
    exit 0;
}

my $mirrorStartTime = [gettimeofday] ;

$vblevel = LOG_ERROR|LOG_WARN|LOG_INFO1|LOG_INFO2|LOG_DEBUG|LOG_DEBUG2 if($debug);


if($mail)
{
    my $dir = File::Temp::tempdir("smt-XXXXXXXX", CLEANUP => 1, TMPDIR => 1);
    $mailtempfile = "$dir/mail";
    open(MAILTEMPFILE, "> $mailtempfile") or die "Cannot open file:$!";
    open(STDOUT, ">& MAILTEMPFILE") or die "Cannot dup:$!";
    open(STDERR, ">& MAILTEMPFILE") or die "Cannot dup:$!";
    select STDERR; $| = 1;      # make unbuffered
    select STDOUT; $| = 1;      # make unbuffered
    select MAILTEMPFILE; $| = 1;        # make unbuffered
}


# get a lock

if(!SMT::Utils::openLock("smt-mirror"))
{
    print __("Mirror process is still running.\n");
    exit 0;
}

# open the logfile

my $LOG = SMT::Utils::openLog($logfile);

my $cfg = undef;

eval
{
    $cfg = SMT::Utils::getSMTConfig();
};
if($@ || !defined $cfg)
{
    SMT::Utils::printLog($LOG, $vblevel, LOG_ERROR, sprintf(__("Cannot read the SMT configuration file: %s"), $@));
    SMT::Utils::unLockAndExit( "smt-mirror", 1, $LOG, $vblevel );
}

if(!defined $LocalBasePath || $LocalBasePath eq "" )
{
    $LocalBasePath = $cfg->val("LOCAL", "MirrorTo");
    if(!defined $LocalBasePath || $LocalBasePath eq "" || !-d $LocalBasePath)
    {
        SMT::Utils::printLog($LOG, $vblevel, LOG_ERROR, __("Cannot read the local base path"));
        SMT::Utils::unLockAndExit( "smt-mirror", 1, $LOG, $vblevel );
    }
}
else
{
    if(!-d $LocalBasePath)
    {
        # directory does not exists, try to create it.
        eval {
            &File::Path::mkpath($LocalBasePath);
        };
        if ($@)
        {
            SMT::Utils::printLog($LOG, $vblevel, LOG_ERROR, sprintf(__("Cannot create %s: %s"), $LocalBasePath, $@));
            SMT::Utils::unLockAndExit( "smt-mirror", 1, $LOG, $vblevel );
        }
    }
}

if ( defined $FromLocalBasePath && $FromLocalBasePath ne "" )
{
    if ( -d $FromLocalBasePath."/repo" )
    {
        $FromLocalBasePath =  $FromLocalBasePath."/repo/";
    }
    else
    {
        if (! -d $FromLocalBasePath )
        {
            SMT::Utils::printLog($LOG, $vblevel, LOG_ERROR, sprintf(__("The directory %s does not exist"), $FromLocalBasePath));
            SMT::Utils::unLockAndExit( "smt-mirror", 1, $LOG, $vblevel );
        }
    }
}
if ( $fromlocalsmt )
{
    if ( $LocalBasePath eq $cfg->val("LOCAL", "MirrorTo") )
    {
        SMT::Utils::printLog($LOG, $vblevel, LOG_ERROR,
                sprintf( __("Using '--fromlocalsmt' requires the '--directory' argument.")));
        SMT::Utils::unLockAndExit( "smt-mirror", 1, $LOG, $vblevel );
    }
    if ( defined $FromLocalBasePath && $FromLocalBasePath ne "" )
    {
        SMT::Utils::printLog($LOG, $vblevel, LOG_ERROR,
                sprintf( __("The options '--fromlocalsmt' and '--fromdir' cannot be used at the same time")));
        SMT::Utils::unLockAndExit( "smt-mirror", 1, $LOG, $vblevel );
    }
}

if(defined $hardlink)
{
    SMT::CLI::hardlink(log => $LOG, size => $hardlink, vblevel => $vblevel, basepath => $LocalBasePath);
    SMT::Utils::unLockAndExit( "smt-mirror", 0, $LOG, $vblevel );
}


my $dbh = undef;

if(!defined $dbreplfile)
{
    $dbh = SMT::Utils::db_connect($cfg);

    if(!$dbh)
    {
        SMT::Utils::printLog($LOG, $vblevel, LOG_ERROR,  __("Cannot connect to database"));
        SMT::Utils::unLockAndExit( "smt-mirror", 1, $LOG, $vblevel );
    }
}
else
{
    if(defined $repository)
    {
        SMT::Utils::printLog($LOG, $vblevel, LOG_WARN, __("Option --repository not allowed together with --dbreplfile. Ignoring"));
        $repository = undef;
    }

    # add a parser
    $dbreplacement = [];

    my $parser = SMT::Parser::RegData->new();
    $parser->parse( $dbreplfile, sub { catalog_handler($dbreplacement, @_); });
}

my $useragent = SMT::Utils::createUserAgent(log => $LOG, vblevel => $vblevel);

#print Data::Dumper->Dump([$dbreplacement]);

my $nuUser = $cfg->val("NU", "NUUser");
my $nuPass = $cfg->val("NU", "NUPass");
chomp($nuUser);
chomp($nuPass);
if(!defined $nuUser || $nuUser eq "" ||
   !defined $nuPass || $nuPass eq "")
{
    SMT::Utils::printLog($LOG, $vblevel, LOG_ERROR, __("Cannot read the Mirror Credentials"));
    SMT::Utils::unLockAndExit( "smt-mirror", 1, $LOG, $vblevel );
}


my $mirrorsrc = $cfg->val("LOCAL", "MirrorSRC");
chomp($mirrorsrc);
if(defined $mirrorsrc && lc($mirrorsrc) eq "false")
{
    $mirrorsrc = 0;
}
else
{
    $mirrorsrc = 1;
}

# Filter used to mirror just a given repository
my $additional_filters = '';
if (defined $repository) {
    $additional_filters .= ' AND '.SMT::Repositories::REPOSITORYID.'='.$dbh->quote($repository).' ';
}

my $array = [];

if(!defined $dbreplfile)
{
    #
    # search for all zypp repositories we need to mirror and start the mirror process
    #
    $array = $dbh->selectall_arrayref( "select ID, CATALOGID, LOCALPATH, EXTURL, EXTHOST, CATALOGTYPE, STAGING, AUTHTOKEN ".
                                       "from Catalogs ".
                                       "where MIRRORABLE='Y' and DOMIRROR='Y' ".$additional_filters.
                                       "order by CATALOGTYPE, NAME",
                                       { Slice => {} } );
}
else
{
    $array = $dbreplacement;
}

foreach my $entry (@{$array})
{
    next if(!exists $entry->{CATALOGTYPE} || ! defined $entry->{CATALOGTYPE});

    if( $entry->{EXTURL} ne "" && $entry->{LOCALPATH} ne "" )
    {
        my $zuri;
        if ( defined $FromLocalBasePath && $FromLocalBasePath ne "" )
        {
            $zuri = URI::file->new($FromLocalBasePath.$entry->{LOCALPATH});
        }
        elsif ( $fromlocalsmt )
        {
            $zuri = URI::file->new($cfg->val("LOCAL", "MirrorTo")."/repo/".$entry->{LOCALPATH});
        }
        else
        {
            # in case of SCC with updates.suse.com we use tokenauth
            # no user/password needed
            $zuri = URI->new($entry->{EXTURL});
            if($zuri->host eq "nu.novell.com")
            {
                $zuri->userinfo("$nuUser:$nuPass");
            }
            if ($entry->{AUTHTOKEN})
            {
                # if we have an authtoken, we overwrite everything.
                # a repo URL should not have extra query params
                $zuri->query($entry->{AUTHTOKEN});
            }
        }

        # if STAGING is Y, put full/ infront of LOCALPATH
        if( exists $entry->{STAGING} && defined $entry->{STAGING} && uc($entry->{STAGING}) eq "Y")
        {
            if ( $fromlocalsmt )
            {
                $zuri->path($cfg->val("LOCAL", "MirrorTo")."/repo/$stagingDir/".$entry->{LOCALPATH});
            }
            else
            {
                $entry->{LOCALPATH} = "$stagingDir/".$entry->{LOCALPATH};
            }
        }

        &File::Path::mkpath( SMT::Utils::cleanPath( $LocalBasePath, "repo", $entry->{LOCALPATH} ) );

        my $zyppMirror = undef;
        if( SMT::Utils::doesFileExist($useragent, SMT::Utils::appendPathToURI($zuri, "headers/header.info")))
        {
            $zyppMirror = SMT::Mirror::Yum->new(vblevel => $vblevel, log => $LOG, dbh => $dbh,
                                                mirrorsrc => $mirrorsrc, nohardlink => $nohardlink,
                                                cfg => $cfg, repoid => $entry->{ID},
                                                useragent => $useragent);
        }
        else
        {
            $zyppMirror = SMT::Mirror::RpmMd->new(vblevel => $vblevel, log => $LOG, dbh => $dbh,
                                                  mirrorsrc => $mirrorsrc, nohardlink => $nohardlink,
                                                  cfg => $cfg, repoid => $entry->{ID},
                                                  useragent => $useragent);
        }
        $zyppMirror->localBasePath( SMT::Utils::cleanPath( $LocalBasePath, "repo" ) );
        $zyppMirror->localRepoPath( $entry->{LOCALPATH} );

        if($clean)
        {
            $zyppMirror->clean();
        }
        else
        {
            $zyppMirror->uri( $zuri->as_string );
            $zyppMirror->deepverify($deepverify);
            $zyppMirror->mirror( dryrun => $dryrun );

            my $s = $zyppMirror->statistic();
            $download   += $s->{DOWNLOAD};
            $link       += $s->{LINK};
            $copy       += $s->{COPY};
            $errors     += $s->{ERROR};
            $size       += $s->{DOWNLOAD_SIZE};
            $secpatches += $s->{NEWSECPATCHES};
            $recpatches += $s->{NEWRECPATCHES};
            foreach my $title (@{$s->{NEWSECTITLES}})
            {
              if( exists $secpatchtitles{$title} )
              {
                $secpatchtitles{$title} += 1;
              }
              else
              {
                $secpatchtitles{$title} = 1;
              }
            }
            foreach my $title (@{$s->{NEWRECTITLES}})
            {
              if( exists $recpatchtitles{$title} )
              {
                $recpatchtitles{$title} += 1;
              }
              else
              {
                $recpatchtitles{$title} = 1;
              }
            }

            # Update last mirror timestamp and filtering only if not mirroring
            # to a custom directory and no error occured (bnc #614997)
            if ( ! $errors && $LocalBasePath eq $cfg->val("LOCAL", "MirrorTo") )
            {
                my $rh = SMT::Repositories::new($dbh, $LOG);
                if ( !$dryrun )
                {
                    # This date reflects the last successfull sync.
                    # Updated even if nothing changed.
                    $rh->updateLastMirror ($entry->{ID});
                    # Something new has been downloaded, update the mirroring timestamp
                    # BNC #510320: Downloaded per repository, not a total download size
                    if ($s->{DOWNLOAD})
                    {
                        # This status is the date of the last change in the repo
                        # This is used to compare staged repos full => testing => production
                        if (!SMT::Mirror::Utils::saveStatus($zyppMirror->fullLocalRepoPath()))
                        {
                            SMT::Utils::printLog($LOG, $vblevel, LOG_WARN,
                                sprintf(__("Failed to save the mirror status at '%s'"), $zyppMirror->fullLocalRepoPath()));
                        }
                    }
                }
                # filter new patches -> admin needs to manually allow which patches
                # s/he wants (maybe this should be configurable)
                # Only add new filter for a new patch, if the current one does
                # not match it, to avoid filling the DB unnecesarily.
                if ($rh->filteringAllowed($entry->{ID}, $cfg->val("LOCAL", "MirrorTo")))
                {
                    my $newpatches = $zyppMirror->newpatches();
                    foreach my $group (@{SMT::Utils::getStagingGroupsForCatalogID($dbh, $entry->{ID})})
                    {
                        my $filter = SMT::Filter->new();
                        $filter->load($dbh, $entry->{ID}, $group);
                        for (keys %$newpatches)
                        {
                            $filter->add(SMT::Filter->TYPE_NAME_VERSION, $_)
                                if (!$filter->matches($newpatches->{$_}));
                        }
                        $filter->save($dbh, $entry->{ID}, $group);
                    }
                }
            }
        }
    }
}

# search for Catalogs which has the DOMIRROR flag equals Y but the MIRRORABLE flag to N
if(!defined $dbreplfile)
{
    my $hash = $dbh->selectall_hashref( "select ID, NAME, TARGET, CATALOGTYPE ".
                                        "from Catalogs ".
                                        "where MIRRORABLE='N' and DOMIRROR='Y'".$additional_filters,
                                        "ID" );

    if(keys %{$hash} > 0)
    {

        my $warning = __("WARNING: The following repositories cannot be mirrored.\n");
        $warning   .= __("         Maybe you have not enough permissions to download these repositories?\n\n");

        foreach my $id (keys %{$hash})
        {
            $hash->{$id}->{TARGET} = '' if not $hash->{$id}->{TARGET};
            $warning .= "* ".$hash->{$id}->{NAME}." ".$hash->{$id}->{TARGET}."\n";
        }

        SMT::Utils::printLog($LOG, $vblevel, LOG_WARN, $warning);
    }
}

# bsc#949361
if( $LocalBasePath && ! -d SMT::Utils::cleanPath($LocalBasePath, "SUSE") &&
    -d SMT::Utils::cleanPath($LocalBasePath, "repo", "SUSE"))
{
    symlink("repo/SUSE", SMT::Utils::cleanPath($LocalBasePath, "SUSE"));
}

SMT::Utils::runHook("mirror_preunlock_hook");

if(!SMT::Utils::unLock("smt-mirror"))
{
    SMT::Utils::printLog($LOG, $vblevel, LOG_ERROR, __("Cannot remove lockfile."));
    exit 1;
}

SMT::Utils::runHook("mirror_postunlock_hook");

if(!$clean && !$hardlink)
{
    # print summary only when mirroring
    my @summary = ();

    push @summary, __("Summary");
    push @summary, sprintf(__("Total transfered files    : %d "), $download );
    push @summary, sprintf(__("Total transfered file size: %d (%s)"), $size, SMT::Utils::byteFormat($size));
    push @summary, sprintf(__("Total linked files        : %s"), $link);
    push @summary, sprintf(__("Total copied files        : %s"), $copy);
    push @summary, sprintf(__("New security updates      : %s"), $secpatches);
    foreach my $title (keys %secpatchtitles)
    {
      push @summary, sprintf(__("   * %s (%d)"), $title, $secpatchtitles{$title} );
    }
    push @summary, sprintf(__("New recommended updates   : %s"), $recpatches);
    foreach my $title (keys %recpatchtitles)
    {
      push @summary, sprintf(__("   * %s (%d)"), $title, $recpatchtitles{$title} );
    }
    push @summary, sprintf(__("Errors:                   : %d"), $errors);
    push @summary, sprintf(__("Total Mirror Time         : %s"), SMT::Utils::timeFormat(tv_interval($mirrorStartTime)));

    if(!$mail)
    {
        foreach my $str (@summary)
        {
            SMT::Utils::printLog($LOG, $vblevel, (LOG_INFO1|LOG_INFO2), $str);
        }
    }
    else
    {
        my $body = "";
        foreach my $str (@summary)
        {
            SMT::Utils::printLog($LOG, $vblevel, (LOG_INFO1|LOG_INFO2), $str, 0, 1);
            $body .= $str."\n";
        }
        $body .= "\n";

        close (STDOUT);
        close (STDERR);
        close (MAILTEMPFILE);

        open(MAIL, "< $mailtempfile") and do
        {
            while(<MAIL>)
            {
                $body .= $_;
            }
            close MAIL;
        };
        my $datestring = POSIX::strftime("%Y-%m-%d %H:%M", localtime);
        my $subject = sprintf("SMT Mirror Report $datestring (%s) -- $errors Errors", SMT::Utils::getFQDN());
        SMT::Utils::sendMailToAdmins($subject, $body);
    }

    #
    # does not make sense here. If a complete repo is up-to-date we do not have the number of files
    #
    #SMT::Utils::printLog($LOG, $vblevel, LOG_INFO1, sprintf(__("Files up to date          : %s"), $uptodate));

}


exit 0;

sub catalog_handler
{
    my $data = shift;
    my $node = shift;

    #print "called ".Data::Dumper->Dump([$node])."\n";

    if(defined $node && ref($node) eq "HASH" &&
       exists $node->{MAINELEMENT} && defined $node->{MAINELEMENT} &&
       lc($node->{MAINELEMENT}) eq "catalogs")
    {
        push @{$data}, $node;
    }
}

sub signal_handler
{
  SMT::Utils::printLog($LOG, $vblevel, LOG_INFO1, "Interrupted by signal. Exitting.");
  SMT::Utils::unLockAndExit("smt-mirror", 1, $LOG, $vblevel);
}

#
# Manpage
#

=head1 NAME

smt mirror

=head1 SYNOPSIS

smt [help|--help|-h] mirror

smt mirror [--directory path] [--deepverify] [--testrun] [--dbreplfile file] [-L file] [--debug] [--verboselevel level] [--nohardlink] [--mail] [--repository repositoryid]

smt mirror --clean [--directory path] [--dbreplfile file] [-L file] [--debug] [--verboselevel level]

smt mirror --hardlink size [--directory path] [-L file] [--debug] [--verboselevel level]

=head1 DESCRIPTION

C<smt mirror> performs the mirroring procedure and downloads repositories which are set to be mirrored.

If it is called with the B<--clean> parameter it searches inside of the mirrored repositories for
obsolete files and remove them.

The B<--hardlink> paramter cause C<smt mirror> to search for files greater then I<size>. If it finds
duplicate files it creates hardlinks instead of holding two copies of the same file.

Depending on the verbose level you will see the files which are checked in the output.
Such a line starts with a flag to show the status of this file.

=over

=item D download

=item L hardlink

=item C copy

=item E error

=item N new file

=item U up-to-date

=back

=head1 OPTIONS

=head2 MIRROR

=over

=item --directory <path>

Defined the directory (B<path>) to work on. Using this option ignores the configured
default value in smt.conf

=item --deepverify

Before starting the mirror procedure verify checksums of B<all> files in the repositories
and remove broken files. without this option only the metadata are verified.

=item --testrun

Run mirror procedure without downloading the rpms (but it needs to download the metadata into a temporary directory).
It shows only the names which would be downloaded.

=item --dbreplfile <file>

Path to XML file to use as database replacement. Such a file can
be created with the sync-scc command. This option is only
useful if the smt database does not exist on the host from
which the smt-mirror script is being executed.

=item --fromdir <path>

Use the directory (B<path>) as the source for mirroring instead of contacting the
remote mirrors. It expects a directory structure created by:

smt-mirror --directory <path>

=item --fromlocalsmt

Mirror from the local running SMT server. This options requires the --directory
option being set. The resulting directory can  e.g later be imported to another server
using:

smt-mirror --fromdir <path>

=item --logfile -L <file>

Write log messages to B<file>.

=item --debug -d

Enable debug mode.

=item --verboselevel -v <level>

Set the output verbose level. The following categories exists.
These categories can be bitwise-or'd to use as verbose level.

=over 4

=item error messages

Value: 0x0001 == 1

=item warning messages

Value: 0x0002 == 2

=item info messages 1

Value: 0x0004 == 4

=item info messages 2

Value: 0x0008 == 8

=item debug messages 1

Value: 0x0010 == 16

=item debug messages 2

Value: 0x0020 == 32

=item debug messages 3

Value: 0x0040 == 64

=back

The default verbose level is 15 (error, warning and all info messages).
B<--debug> set the verbose level to 63.

=item --repository -r <repository ID>

Mirrors only a repository of a given ID.

=item --nohardlink

Instead of using hardlinks, copy the files.

=item --mail -m

Send output as e-mail to the administrators defined in reportEmail in smt.conf .
The output on stdout and stderr will be suppressed in this mode.

=back

=head2 CLEAN

=over

=item --clean

Enable clean mode. With this parameter C<smt mirror> searches inside of
the mirrored repositories for
obsolete files and remove them. It does not mirror new files.

=item --directory <path>

Defined the directory (B<path>) to work on. Using this option ignores the configured
default value in smt.conf

=item --dbreplfile <file>

Path to XML file to use as database replacement. Such a file can be created with
the sync-scc command. This option is only usefull if the smt database is not on the
same host as this script should run.

=item --logfile -L <file>

Write log messages to B<file>.

=item --debug -d

Enable debug mode.

=item --verboselevel -v <level>

Set the output verbose level.

=back

=head2 HARDLINK

=over

=item --hardlink <size>

The B<--hardlink> paramter cause C<smt mirror> to search for files greater then I<size> (in kb).
If it finds duplicate files (equal in name, size and checksum), it creates hardlinks instead
of holding two copies of the same file.

=item --directory <path>

Defined the directory (B<path>) to work on. Using this option ignores the configured
default value in smt.conf

=item --logfile -L <file>

Write log messages to B<file>.

=item --debug -d

Enable debug mode.

=item --verboselevel -v <level>

Set the output verbose level.

=back

=head1 EXAMPLES

Start the mirror procedure with logging

 smt mirror --logfile /var/log/smt-mirror.log

start the mirror procedure with logging and a different verbose level

 smt mirror -v 11 --logfile /var/log/smt-mirror.log

run mirror with verifing all checksums and debug mode

 smt mirror -d --deepverify

clean the repositories from files, which are no longer mentioned in the metadata

 smt mirror --clean

create hardlinks for all duplicate files greater then 20000 kb

 smt mirror --hardlink 20000

=head1 AUTHORS and CONTRIBUTORS

Duncan Mac-Vicar Prett, Lukas Ocilka, Jens Daniel Schmidt, Michael Calmer,
Ralf Haferkamp

=head1 LICENSE

Copyright (c) 2007-2012 SUSE LINUX Products GmbH, Nuernberg, Germany.

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 675 Mass
Ave, Cambridge, MA 02139, USA.

=cut
