# SPDX-License-Identifier: GPL-2.0-or-later

package Amavis::DB;
use strict;
use re 'taint';
use warnings;
use warnings FATAL => qw(utf8 void);
# use warnings 'extra'; no warnings 'experimental::re_strict'; use re 'strict';

BEGIN {
  require Exporter;
  use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
  $VERSION = '2.412';
  @ISA = qw(Exporter);
}

use BerkeleyDB;

use Amavis::Conf qw($db_home $daemon_chroot_dir);
use Amavis::Util qw(untaint ll do_log);
use Amavis::DB::SNMP;

# create new databases, then close them (called by the parent process)
# (called only if $db_home is nonempty)
#
sub init($$) {
  my($predelete_nanny, $predelete_snmp) = @_;
  my $name = $db_home;
  $name = "$daemon_chroot_dir $name"  if $daemon_chroot_dir ne '';
  if ($predelete_nanny || $predelete_snmp) {  # delete existing db files first?
    local(*DIR);
    opendir(DIR,$db_home) or die "db_init: Can't open directory $name: $!";
    # modifying a directory while traversing it can cause surprises, avoid;
    # avoid slurping the whole directory contents into memory
    my($f, @rmfiles);
    while (defined($f = readdir(DIR))) {
      next  if $f eq '.' || $f eq '..';
      if      ($f =~ /^(__db\.)?nanny\.db\z/) {
        push(@rmfiles, $f)  if $predelete_nanny;
      } elsif ($f =~ /^(__db\.)?snmp\.db\z/) {
        push(@rmfiles, $f)  if $predelete_snmp;
      } elsif ($f =~ /^__db\.\d+\z/s) {
        push(@rmfiles, $f)  if $predelete_nanny && $predelete_snmp;
      } elsif ($f =~ /^(?:cache-expiry|cache)\.db\z/s) {
        push(@rmfiles, $f);  # old databases, no longer used since 2.7.0-pre9
      }
    }
    closedir(DIR) or die "db_init: Error closing directory $name: $!";
    do_log(1, 'Deleting db files %s in %s', join(',',@rmfiles), $name);
    for my $f (@rmfiles) {
      my $fname = $db_home . '/' . untaint($f);
      unlink($fname) or die "db_init: Can't delete file $fname: $!";
    }
    undef @rmfiles;  # release storage
  }
  $! = 0; my $env = BerkeleyDB::Env->new(-Home=>$db_home, -Mode=>0640,
    -Flags=> DB_CREATE | DB_INIT_CDB | DB_INIT_MPOOL);
  defined $env
    or die "BDB can't create db env. at $db_home: $BerkeleyDB::Error, $!.";
  do_log(1, 'Creating db in %s/; BerkeleyDB %s, libdb %s',
            $name, BerkeleyDB->VERSION, $BerkeleyDB::db_version);
  $! = 0; my $dbs = BerkeleyDB::Hash->new(
    -Filename=>'snmp.db', -Flags=>DB_CREATE, -Env=>$env );
  defined $dbs or die "db_init: BDB no dbS: $BerkeleyDB::Error, $!.";
  $! = 0; my $dbn = BerkeleyDB::Hash->new(
    -Filename=>'nanny.db', -Flags=>DB_CREATE, -Env=>$env );
  defined $dbn or die "db_init: BDB no dbN: $BerkeleyDB::Error, $!.";

  Amavis::DB::SNMP::put_initial_snmp_data($dbs)  if $predelete_snmp;
  for my $db ($dbs, $dbn) {
    $db->db_close==0 or die "db_init: BDB db_close: $BerkeleyDB::Error, $!.";
  }
}

# open an existing databases environment (called by each child process)
#
sub new {
  my $class = $_[0]; my $env;
  if (defined $db_home) {
    $! = 0; $env = BerkeleyDB::Env->new(
      -Home=>$db_home, -Mode=>0640, -Flags=> DB_INIT_CDB | DB_INIT_MPOOL);
    defined $env
      or die "BDB can't connect db env. at $db_home: $BerkeleyDB::Error, $!.";
  }
  bless \$env, $class;
}

sub get_db_env { my $self = $_[0]; $$self }

1;
