#!/usr/bin/perl

=encoding utf8

=head1 NAME
    
    push-client-linker - создание hard-link-ов в стиле logrotate на подневные логи

=head1 USAGE

    push-client-linker /var/log/yandex/push-client-links/ /var/log/yandex/PPCLOG/ppclog_cmd.log ...

=head1 DESCRIPTION

    Исторически, Директ добавляет в конец имени лога дату
    Исторически, push-client работает только с логам, ротируемыми аналогично logrotate (log, log.1, ...)
    Утилита push-client-linker делает hard-линки на подневные логи, так что push-client не подозревает об обмане

    Имена линков получаются из имен файлов, с такими изменениями:
      - символы [._] заменяются на -
      - в конец добавляется суффикс -log, если его нет

    Первый аргумент - директория, в которой будут создаваться линки
    Последующие аргументы - префиксы лог-файлов, нуждающихся в перелинковке

    Дополнительные опции:
    --help - помощь
    --verbose - выводить все действия в STDERR
    --links-num - сколько линков делать (не меньше 2, не больше 10)
    --create-dir - создать директорию для линков, если её ешё нет
    --lock-name - имя файловой блокировки (нужно, есть на машине запускается несколько push-client-linker)

=cut

use strict;
use warnings;
use utf8;

use Fatal qw/link rename unlink/;
use Carp qw/croak/;
use Getopt::Long;
use Path::Tiny;
use Pid::File::Flock;

use Yandex::TimeCommon qw/today yesterday/;

my $VERBOSE = 0;
my $CREATE_DIR = 0;
my $MAX_LINKS_NUM = 3;
my $MAX_DAYS_AGO = 366;
my $LOCK_NAME;
GetOptions(
    "help" => sub { system("pod2text $0"); exit $?; },
    "verbose+" => \$VERBOSE,
    "links-num=i" => \$MAX_LINKS_NUM,
    "lock-name=s" => \$LOCK_NAME,
    "create-dir" => \$CREATE_DIR,
    ) || croak "Incorrect options";

my ($LINKS_DIR, @LOGS) = @ARGV;
if (!defined $LINKS_DIR || !@LOGS || (!-d $LINKS_DIR && !$CREATE_DIR)) {
    croak "Usage: $0 links_dst_dir log_prefix1 log_prefix2 ...";
}
if (!-d $LINKS_DIR && $CREATE_DIR) {
    _debug("Create link directory $LINKS_DIR");
    path($LINKS_DIR)->mkpath;
}
if ($MAX_LINKS_NUM < 2 || $MAX_LINKS_NUM > 10) {
    croak "Incorrect links-num: '$MAX_LINKS_NUM'";
}

my $lock = Pid::File::Flock->new(name => $LOCK_NAME);

for my $log (map {path($_)} @LOGS) {
    process_log($log);
}


sub process_log {
    my ($log) = @_;

    # определяем имена линков - имя должно заканчиваться на -log,
    # нежелательно иметь точки и подчёркивания (https://wiki.yandex-team.ru/nadezhdamarkitantova/supply-quickstart#logtype)
    my $log_link_name = $log->basename;
    $log_link_name =~ s/[\._]/-/g;
    $log_link_name .= '-log' if $log_link_name !~ /-log$/;

    my @log_files = get_log_files($log);

    # link
    for(my $i = $MAX_LINKS_NUM; $i >= 0; $i--) {
        my $log_file = $log_files[$i];
        my $link_file = "$LINKS_DIR/$log_link_name".($i ? ".$i" : '');
        if ($#log_files < $i) {
            if (-e $link_file) {
                _debug("Unlink $link_file");
                unlink($link_file);
            }
        } else {
            if (-e $link_file && path($link_file)->stat->ino == path($log_file)->stat->ino) {
                next;
            } else {
                if (-e "$link_file.tmp") {
                    _debug("Unlink $link_file.tmp");
                    unlink("$link_file.tmp");
                }
                _debug("Link $link_file.tmp -> $log_file");
                link($log_file, "$link_file.tmp");
                _debug("Rename $link_file.tmp -> $link_file");
                rename("$link_file.tmp", $link_file);
            }
        }
    }

    # cleanup
    for my $file (path($LINKS_DIR)->children) {
        if ($file->basename =~ /^\Q$log_link_name\E\.(\d)$/ && $1 >= $MAX_LINKS_NUM
            || $file->basename eq $log_link_name && !@log_files
        ) {
            _debug("Rename $file");
            unlink($file);
        }
    }
}


sub _debug {
    my ($text) = @_;
    print STDERR "$text\n" if $VERBOSE;
}


sub get_log_files {
    my ($log) = @_;

    my $date = today();
    my @ret;
    for my $days_ago (0..$MAX_DAYS_AGO) {
        my $date_log = path("$log.$date");
        if (-f $date_log) {
            push @ret, $date_log;
            last if @ret >= $MAX_LINKS_NUM;
        }
        $date = yesterday($date);
    }

    return @ret;
}


