#! /usr/local/bin/perl
# -*- indent-tabs-mode: nil; -*-
# vim:ft=perl:et:sw=4
# $Id$

# Sympa - SYsteme de Multi-Postage Automatique
#
# Copyright (c) 1997, 1998, 1999 Institut Pasteur & Christophe Wolfhugel
# Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
# 2006, 2007, 2008, 2009, 2010, 2011 Comite Reseau des Universites
# Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017 GIP RENATER
# Copyright 2017, 2018, 2019 The Sympa Community. See the AUTHORS.md file at
# the top-level directory of this distribution and at
# <https://github.com/sympa-community/sympa.git>.
#
# 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, see <http://www.gnu.org/licenses/>.

use lib split(/:/, $ENV{SYMPALIB} || ''), '/usr/local/libexec/sympa';
use strict;
use warnings;
use English qw(-no_match_vars);
use Getopt::Long;
use Pod::Usage;
use POSIX qw();

use Conf;
use Sympa::Constants;
use Sympa::Crash;    # Show traceback.
use Sympa::Log;
use Sympa::Process;
use Sympa::Spindle::ProcessTask;
use Sympa::Spool::Listmaster;

my $process = Sympa::Process->instance;
$process->init(pidname => 'task_manager');

my %options;
unless (
    GetOptions(
        \%main::options, 'config|f=s',  'debug|d', 'help|h',
        'version|v',     'log_level=s', 'foreground|F'
    )
) {
    pod2usage(-exitval => 1, -output => \*STDERR);
}
if ($main::options{'help'}) {
    pod2usage(0);
} elsif ($main::options{'version'}) {
    printf "Sympa %s\n", Sympa::Constants::VERSION;
    exit 0;
}
$Conf::sympa_config = $main::options{config};

if ($main::options{'debug'}) {
    $main::options{'log_level'} = 2 unless $main::options{'log_level'};
    $main::options{'foreground'} = 1;
}

my $log = Sympa::Log->instance;
$log->{log_to_stderr} = 'all' if $main::options{'foreground'};

my $adrlist = {};

# Load sympa.conf
unless (Conf::load()) {
    die sprintf
        "Unable to load Sympa configuration, file %s or one of the virtual host robot.conf files contain errors. Exiting.\n",
        Conf::get_sympa_conf();
}

$log->openlog($Conf::Conf{'log_facility'}, $Conf::Conf{'log_socket_type'});

# setting log_level using conf unless it is set by calling option
if ($main::options{'log_level'}) {
    $log->{level} = $main::options{'log_level'};
    $log->syslog(
        'info',
        'Configuration file read, log level set using options: %s',
        $main::options{'log_level'}
    );
} else {
    $log->{level} = $Conf::Conf{'log_level'};
    $log->syslog(
        'info',
        'Configuration file read, default log level %s',
        $Conf::Conf{'log_level'}
    );
}

# Put ourselves in background if not in debug mode.
unless ($main::options{'foreground'}) {
    $process->daemonize;
}

# Create and write the PID file.
$process->write_pid(initial => 1);
# If process is running in foreground, don't write STDERR to a dedicated file.
unless ($main::options{foreground}) {
    $process->direct_stderr_to_file;
}

## Set the UserID & GroupID for the process
$GID = $EGID = (getgrnam(Sympa::Constants::GROUP))[2];
$UID = $EUID = (getpwnam(Sympa::Constants::USER))[2];

## Required on FreeBSD to change ALL IDs(effective UID + real UID + saved UID)
POSIX::setuid((getpwnam(Sympa::Constants::USER))[2]);
POSIX::setgid((getgrnam(Sympa::Constants::GROUP))[2]);

# Check if the UID has correctly been set (useful on OS X)
unless (($GID == (getgrnam(Sympa::Constants::GROUP))[2])
    && ($UID == (getpwnam(Sympa::Constants::USER))[2])) {
    die
        "Failed to change process user ID and group ID. Note that on some OS Perl scripts can't change their real UID. In such circumstances Sympa should be run via sudo.\n";
}

## Sets the UMASK
umask(oct($Conf::Conf{'umask'}));

## Change to list root
unless (chdir($Conf::Conf{'home'})) {
    die sprintf 'Unable to change to directory %s', $Conf::Conf{'home'};
}

# Most initializations have now been done.
$log->syslog('notice', 'Task_Manager %s Started',
    Sympa::Constants::VERSION());

my $spindle = Sympa::Spindle::ProcessTask->new;

## Catch signals, in order to exit cleanly, whenever possible.
$SIG{'TERM'} = \&sigterm;
$SIG{'INT'}  = \&sigterm;

# Infinite loop scanning the spool (unless a sig TERM is received).
while (not $spindle->{finish}) {
    $spindle->spin;

    last if $spindle->{finish};

    # If the spool was empty, sleep for a while.
    sleep 60;
}

# Purge grouped notifications
Sympa::Spool::Listmaster->instance->flush(purge => 1);

$log->syslog('notice', 'Task_Manager exited normally due to signal');
$process->remove_pid(final => 1);

exit(0);

## task creations
# Obsoleted. Use Sympa::Task::new() and Sympa::Spool::Task::store().
#sub create;

## check the syntax of a task
# Moved: Use Sympa::Task::_check().
#sub check;

## check a task line
# Moved: Use Sympa::Task::_chk_line().
#sub chk_line;

## check the arguments of a command
# Moved: Use Sympa::Task::_chk_cmd().
#sub chk_cmd;

### TASK EXECUTION SUBROUTINES ###

# Moved to Sympa::Spinlde::ProcessTask module.
#sub execute;
#sub cmd_process;
#sub rm_file;
#sub stop;
#sub send_msg;
#sub next_cmd;
#sub select_subs;
#sub delete_subs_cmd;
#sub create_cmd;
#sub exec_cmd;
#sub purge_logs_table;
#sub _db_log_del;
#sub purge_session_table;
#sub purge_spools;
#sub _clean_spool;
#sub purge_tables;
#sub purge_one_time_ticket_table;
#sub purge_user_table;
#sub purge_orphan_bounces;
#sub expire_bounce;

# Removed because not yet fully implemented.  See r11771.
#sub chk_cert_expiration;

# Removed becuase not yet fully implemented.  See r11771.
#sub update_crl;

# Moved to Sympa::Spindle::ProcessTask module.
#sub eval_bouncers;
#sub none;
#sub process_bouncers;
#sub get_score;

### MISCELLANEOUS SUBROUTINES ###

## when we catch signal, just change the value of the loop variable.
sub sigterm {
    my ($sig) = @_;
    $log->syslog('notice',
        'Signal %s received, still processing current task', $sig);
    $spindle->{finish} = 'term';
}

## sort task name by their epoch date
# No longer used.
#sub epoch_sort;

# Moved to Sympa::Spindle::ProcessTask::_change_label().
#sub change_label;

# Moved to Sympa::Spindle::ProcessTask::error().
#sub error;

# Moved to Sympa::Spindle::ProcessTask module.
#sub sync_include;

__END__

=encoding utf-8

=head1 NAME

task_manager, task_manager.pl - Daemon to process periodical Sympa tasks

=head1 SYNOPSIS

C<task_manager.pl> S<[ C<--foreground> ]> S<[ C<--debug> ]>

=head1 DESCRIPTION

Task_manager is a program which scans permanently the task spool and
processes tasks.
It also checks configuration of site and every list to create necessary tasks.

=head1 OPTIONS

=over 4

=item C<-d>, C<--debug>

Sets the debug mode

=item C<-f>, C<--config=>I<file>

Force task_manager to use an alternative configuration file instead
of F</usr/local/etc/sympa/sympa.conf>.

=item C<-F>, C<--foreground>

Prevents the script from being daemonized

=item C<-h>, C<--help>

Prints this help message.

=item C<--log_level=>I<level>

Set log level.

=back

=head1 FILES

F<$SPOOLDIR/task/> directory for task spool.

F<$PIDDIR/task_manager.pid> this file contains the process ID
of F<task_manager.pl>.

=head1 SEE ALSO

L<sympa_msg(8)>, L<wwsympa(8)>.

L<Sympa::Spindle::ProcessTask>.

=head1 HISTORY

F<task_manager.pl> was contributed by F. Guilleux.
It appeared on Sympa 3.3a-vhost.10.

=cut
