#!/usr/bin/perl
# RCS: $Id: chklogs,v 2.0 1997/09/28 19:19:38 grimaldo Exp $
#------------------------------------------------------------------------
#	chklogs (c)1995,1996,1997 D. Emilio Grimaldo Tunon
#------------------------------------------------------------------------
# AUTHOR: D. Emilio Grimaldo T.		grimaldo@panama.iaehv.nl
# USAGE: chklogs [[-t | -c] | [-w | -a | -m]
# DESCRIPTION:
#          Checks the sizes of system Log files and reports the results.
#	   The full pathname of the log(s) together with the maximum
#	allowed size and default action are specified in `ConfFile'.
#	   Default actions can be `archive' or `truncate'. When
#	archiving a shuffling mechanism is used to save disk space.
#       
#   Options:
#	none	Process logs according to default action 
#	-a	Archive all overgrown/aged logs regardless of default action
#	-m	Mail summary to administrator
#	-c	Only produce a listing of the (archived) logs
#	-t	Check correctness of Index indicating registered action
#	-w	Warn about overgrown/aged logs without taking action
#	-v	Shows version of Chklogs and its library
#
#   NOTE: mail is sent to admin ONLY if one or more logs are overgrown

# ********** I N C L U D E S    **********
require 5.003;
    use     Getopt::Long;
    use     lib "/usr/local/lib/chklogs";
    use     Chklogs;
    use     Smtp;
    use     DegtUtils;
    use     strict;
# ********* ********************* *********

# ********* CONFIGURATION SECTION *********
#                         Most likely to adapt to your site
my $zipper  ='/bin/gzip';		# Compress program 
my $zipext  ='gz';			# Extension given by zipper command
my $syslogF ='syslog.pid';		# Location of syslog.pid file

#                         Least likely to be modified

my $maxlogs=5;				# Shuffle after maxlogs archives
my $mailer  ='/usr/sbin/sendmail -ep -i '; # only if MiniMail is 'no'
# ********* ********************* *********


# ********** LOCAL DATA SECTION **********
my $cvsId = '$Revision: 2.0 $';

my ($vBanner, $version );
my (%cfg, %cLog, %cGroup);
my ($opWarn, $opArchive, $opMail, $opCheck, $opTest, $opt_default);
my ($global_cnt, $local_cnt);
my ($AgeFlag, $notify, $Today, $exec_pgm, $Repository, $REPORT);
my ($DC, $ArchiveSuffix, $mailout);
my (@Report, @Xheader);
# ********* ********************* *********

#------------------------------------------------
# FUNCTION : initialize
# PROTOTYPE: initialize()
# INPUT    : -
# OUTPUT   : -
# GLOBALS  : %cfg %cLog %cGroup
# DESCRIPTION
#	Global initialization

sub initialize {
    my @tmStamp = ();
		#
		# Global Configuration Settings
		#
    %cfg = (
            'global' => "",			# Alternate Repository Feature
	    'local'  => $RelativePath,
	    'reptype'=> "",			# Repository Type (global|local)
	    'options'=> ""
	   );
		#
		# Current Log
		#
    %cLog = (
            'name'      => "",			# Fully qualified name
	    'size'      => 0,			# Current file size (stat)
	    'mode'      => 0640,		# Current file mode (stat)
	    'uid'	=> 0,			# File Owner user (stat)
	    'gid'	=> 0,			# File Owner group (stat)
	    'threshold' => 0,
	    'action'    => "",
	    'max'       => 0,
	    'params'    => ""
	    );
		#
		# Current Group
		#
    %cGroup = (
              'name'      => 'common',
	      'pre'       => '',
	      'post'      => '',
	      'each'	  => ''
	      );

    $vBanner ="** c h k l o g s  V $version **";
    $mailout="$ENV{'HOME'}/.chklogs.out";	# Temporary file for -m option
    $global_cnt = 0;
    $local_cnt  = 0;

    @tmStamp = localtime(time);
    $ArchiveSuffix = sprintf(".%2d%02d%02d",$tmStamp[5],
                                            $tmStamp[4] + 1,
					    $tmStamp[3]);
    @Report = ();
    @Xheader = ();
    sPrint \@Xheader, "X-Degt-Product: Chklogs $version\n";
    sPrint \@Xheader, "X-Degt-Action: cleanup\n";
}

#------------------------------------------------
# FUNCTION : ExecuteAction
# PROTOTYPE: ExecuteAction($FullLogName, $ActionType)
# INPUT    :
#	     ActionType ::= archive | truncate | execute
# RETURNS  : -
# GLOBALS  : AgeFlag notify Today exec_pgm %cLog Repository admin
# DESCRIPTION
#    Processes the log according to it's associated action. Every
#   log can be processed according to 'ActionType'
#       WARN	 Only warn administrator that this log is too big
#       ARCHIVE	 Archive log, check if overriding truncate from command line
#       TRUNCATE Truncate the log if it is the default action 
#       EXECUTE	 Execute external program, If %L was present it is replaced
#		 by absolute-log-name in the main function.

sub ExecuteAction {
   my ($logname, $p_action) = @_;

    # If the entry is time-oriented we have to adjust the meaning
    # of cLog{'size'}, cLog{'threshold'} was already done in the main body.
    if ($AgeFlag) {				# Age (days) since last action
	$cLog{'size'} = &DayCount($Today) -
	        &DayCount(&TimeLogStamp($logname,$p_action));
    }
    
    if ( $cLog{'size'} >= $cLog{'threshold'} ) {# Has it grown/aged too much?
	$notify = 1;				# Mail ONLY IF at least one is too big
	if (defined($opWarn)) {		# Warn only!
	    $p_action = $p_action . "!";
	    &ConsoleOutput($p_action);
	}
	if (defined($opArchive) ||		# Override default action!
	    $p_action eq 'archive') { 		# Default: archive
	    &ShuffleLogs($logname);
	    &ArchiveLog($logname);
	}
	elsif ($p_action eq 'execute') {	# Execute external program
	    $ENV{'CDKROOT'}   = $Repository;
	    $ENV{'CDKLOG'}    = $logname;
	    $ENV{'CDKMAILTO'} = $admin;
	    system("$exec_pgm $cLog{'param'}");
	    &ConsoleOutput($p_action);
	}
	elsif ($p_action eq 'truncate') {	# Default: truncate
#	    &ShuffleLogs($logname);
	    &TruncateLog($logname);
	}
	&ModifyTimeLog($logname,$Today,$p_action);
    }
    else {					# Size is within limits
	&ConsoleOutput("   ok");		# Size is ok
    }
}

#------------------------------------------------
# FUNCTION : ProcessLog
# PROTOTYPE: ProcessLog($FullLogName)
# INPUT    : 
# RETURNS  : -
# GLOBALS  : %cLog DC exec_pgm 
# DESCRIPTION
# 	Determines what to do with the log based
#	on the options given and then dispatching
#	the right action. Alternatively it processes
#	non-action command line parameters

sub ProcessLog {
    my $LogName = shift;
    my ($dev,$ino,$nlink,$rdev);
    my ($atime,$mtime,$ctime,$blksize,$blocks);

    ($dev,$ino, $cLog{'mode'}, $nlink, $cLog{'uid'}, $cLog{'gid'},
     $rdev, $cLog{'size'}, $atime, $mtime,$ctime,
     $blksize,$blocks) = stat($LogName);

    #
    #   Now do whatever we want with the logs
    #   ARCHIVE option overrides TRUNCATE if given in the command line (-a)
    #
    if ((defined($opMail)   || defined($opt_default) || 
	defined($opArchive) || defined($opWarn)) && ($LogName ne "")) {
	&ExecuteAction($LogName, $cLog{'action'});
    }
    elsif (defined($opCheck)) {     	# Just list the archives and logs
	&ListLogs($LogName,$cLog{'action'});
    }
    elsif (defined($opTest)) {      	# Sanity check of index file
 	if (-r $LogName) {		# Check if it exists/readable
	    if ($cLog{'action'} eq 'execute') {
		printf "%-50s %+7s%s   execute %s %s\n", $LogName, 
							 $cLog{'threshold'}, 
							 $DC, $exec_pgm, 
							 $cLog{'param'};
	    }
	    elsif ($cLog{'action'} eq 'truncate') {
		printf "%-50s %+7s%s   truncate -\n", $LogName,
						      $cLog{'threshold'}, $DC;
	    }
	    elsif ($LogName ne "") {
		printf "%-50s %+7s%s   %-8s %d\n", $LogName,
						   $cLog{'threshold'}, 
						   $DC, $cLog{'action'},
						   $cLog{'max'};
	    }
	}
	else {
	    printf"%50s %7s (does not exist!)\n", $LogName, $cLog{'threshold'};
	}
    }
}

#------------------------------------------------
# FUNCTION : ArchiveLog
# PROTOTYPE: ArchiveLog($FullLogName)
# INPUT    : 
# RETURNS  : -
# GLOBALS  : Repository ArchiveSuffix %cfg %cLog
# DESCRIPTION
# 	Archives the log by compressing it and adding a timestamp in
# 	the filename. No new log is created if it already exists.
#	At this point Repository already contains a valid path for
#	both local and global provided user has followed the rules.

sub ArchiveLog {				# PROTO($)
    my $logname = shift;
    my ($archiveName, $zipArchive);
    my $to;

    $archiveName = $logname . $ArchiveSuffix;
    $zipArchive = $archiveName . ".$zipext";

    if ( ! -e $zipArchive) {
	if (! system("$zipper < $logname > $zipArchive && : > $logname")) {
	    # Set correct ownership
	    chown $cLog{'uid'}, $cLog{'gid'}, $zipArchive;
	    chmod $cLog{'mode'}, $zipArchive;	# Set correct permissions
	    &ConsoleOutput('archived');	
	    
#	    if ( $cfg{'reptype'} eq 'global' ) {
#		$to = "$Repository/" . &basename($zipArchive);
#		rename($zipArchive,$to);
#	    }
#	    elsif ( $cfg{'reptype'} eq 'local' ) {
#		$to = $cfg{'local'} . '/' . &basename($zipArchive);
#		rename($zipArchive,$to);
#	    }

	    # Already set in main taking into consideration
	    # global or local.
	    $to = "$Repository/" . &basename($zipArchive);
	    xdevRename($zipArchive, $to);

	    $global_cnt++ if ($cfg{'reptype'} eq 'global');
	    $local_cnt++ if ($cfg{'reptype'} eq 'local');
	}
	else {
	    warn "Could not create $zipArchive\n";
	    &ConsoleOutput('archive!');		# give a warning
	}    
    }
    else {
	&ConsoleOutput('archive!');		# give a warning
    }
}

#-----------------------------------------
# TruncateLog
#	Truncates the log to zero length using system call 
sub TruncateLog {
    my $logname = shift;
    truncate($logname,0);
    &ConsoleOutput("truncated");
}

#-----------------------------------------
# ConsoleOutput
#	Output to either console or mail file
sub ConsoleOutput {
    my $act = shift;
    my $tmp;

    $tmp = sprintf "%-50s %7d%s  %7d %s\n", $cLog{'name'}, 
    					    $cLog{'size'}, $DC, 
					    $cLog{'threshold'}, $act;
    sPrint $REPORT, $tmp;					    
}

#-----------------------------------------
# ReportHeader 
#	Sets up the header to be displayed or mailed out
sub ReportHeader {
    my ($tDay,$tMon,$tYear,$hostname,$tmp);

    $hostname = `hostname`;

    ($tDay,$tMon,$tYear) = unpack('C A3 I',&GetCurrentDate);
    $Today = "$tDay $tMon $tYear";

    sPrint($REPORT, "\t\t\t$vBanner\n\n");
    if (defined($opMail)) {
#	sPrint \@Report, "\t\t\t$vBanner\n\n";
	$tmp = sprintf "Configuration: %s    Date: %s\nHost:%s%s\n",
	               $ConfFile, $Today, ' 'x8, $hostname;
	sPrint \@Report, $tmp;
	$tmp = sprintf "%-50s Current - Allowed  Action\n", 'Log Name';
	sPrint \@Report, $tmp;
	Print \@Report, "-" x 50," ","-" x 7,"   ","-" x 7,"  ","-" x 6,"\n";
	$notify = 0;
    }
    elsif (!defined($opCheck)) {
	printf "Configuration: %s    Date: %s\nHost:%s%s\n", $ConfFile, 
		$Today, ' 'x8, $hostname;
	if (defined($opTest)) {
	    printf "%-50s Allowed   Action  Arch.\n","Log Name";
	    print "-" x 50," ","-" x 7,"   ","-" x 6,"  ","-----\n";
	}
	else {
	    printf "%-50s Current - Allowed  Action\n","Log Name";
	    print "-" x 50," ","-" x 7,"   ","-" x 7,"  ","-" x 6,"\n";
	}
    }
}

#------------------------------------------------
# FUNCTION : ListLogs
# PROTOTYPE: ListLogs($Directory, $defaultActionType)
# INPUT    : 
# RETURNS  : -
# GLOBALS  : %cGroup %cfg
# DESCRIPTION
# 	Lists all logs matching the name, including a full path 

sub ListLogs {				# PROTO($,$)
    my ($fullpath, $default_action) = @_;
    my ($dname,$bname,$ldir);

    $dname = &dirname($fullpath);
    $bname = &basename($fullpath);
    $ldir  = "$dname/$cfg{'local'}";
    print "\n\n====================================================\n";
    print "Log: $bname(s) in: $dname  [$default_action] Group: $cGroup{'name'}\n";
    if (-d $ldir) {
	chdir($ldir);
	print "\t\t\tLOCAL REPOSITORY\n";
	system("/bin/ls $bname*  2> /dev/null");
    }
    
    if ($cGroup{'name'} eq '' || $cGroup{'name'} eq 'common') {
    	$ldir = "$cfg{'global'}/common";
    }
    else {
    	$ldir = "$cfg{'global'}/$cGroup{'name'}";
    }

    if (-d $ldir) {
	chdir($ldir);
	print "\t\t\tGLOBAL REPOSITORY\n";
	system("/bin/ls $bname*  2> /dev/null");
    }	
}

#-----------------------------------------
# Shuffles logs, when the maximum number of archived logs (per log name)
# is reached we remove the oldest so that we don't waste disk space with
# too many archived logs
#
sub ShuffleLogs {
    my $fullpath = shift;
    my ($logcnt,@LogList);
    my ($dname,$bname);

    $logcnt = 0;
    $dname = $Repository;
    $bname = &basename($fullpath);
    chdir($dname);


    open(ARCHLIST,"ls $bname.*.$zipext 2> /dev/null |") || warn "Could not pipe shuffle";
    while (<ARCHLIST>) {
	chomp($_);
    	@LogList[$logcnt] = $_;
	$logcnt += 1;
    }	
    close(ARCHLIST);

    if ($logcnt >= $cLog{'max'}) {
	unlink(@LogList[0]);		# Remove oldest archived log
    }
}

#-----------------------------------------
# Sanity Check of Configuration Parameters
#
sub ExecCheck {
    my $which_prog = shift;

    if (! -e $which_prog) {
        print "Does not exist!\n";
    }
    elsif (! -x $which_prog) {
        print "Is not executable!\n";
    }
    else { print "ok\n"; }
}

sub SanityCheck {
    my ($mailer_prog, $remainder);

    print "\nChecking configuration parameters...\n";
    print "\tArchiver: $zipper -> ";
    &ExecCheck($zipper);

    ($mailer_prog, $remainder) = split(/\s/,$mailer);
    print "\tMailer: $mailer_prog -> ";
    &ExecCheck($mailer_prog);
}

sub ReportTrailer {
    my $MyOpt;

    sPrint $REPORT, "\n\tArchives as      : <basename>$ArchiveSuffix.$zipext\n";
    sPrint $REPORT, "\tGlobal Repository: $cfg{'global'}  ($global_cnt)\n";
    sPrint $REPORT, "\tLocal  Repository: <logDirname>/$cfg{'local'}" .
		    "  ($local_cnt)\n";
    sPrint $REPORT, "\tGlobal resource  : $globalResource\n";
    sPrint $REPORT, "\tPersonal resource: $personalResource\n";
    sPrint $REPORT, "\tOptions:\n\t\t$cfg{'reptype'}\n";
    sPrint $REPORT, "\t\tuseMiniMail=$useMiniMail\n";
}

sub ProcessDirectory {
    my $Dir = shift;
    my ($one_file);

    $Dir =~ s|/$||;

    if ( opendir(DIRHANDLE,$Dir) ) {
	while ($one_file = readdir(DIRHANDLE)) {
	    if ( $one_file !~ /^\.{1,2}/ ) {
		# Not the dot or dot-dot directory entries.
		if ( $one_file !~ /\.$zipext$/ ) {
		    # Not a zipped file, as archives are.
		    if ( -f "$Dir/$one_file" ) {
			# Is a normal file.
			 &ProcessLog("$Dir/$one_file");
		    }
		}
	    }
	}
	closedir DIRHANDLE;
    }
}

sub ShowVersion {
    print "\tChkLogs version $version\n";
    print "\tCopyright (c)1995-1997 D.Emilio Grimaldo T.\n";
    print "\tFreely distributable\n";
    printf "\t\tChklogs.pm\tv%s\n", &Chklogs::GetLibVersion;
    printf "\t\tSmtp.pm\t\tv%s\n", &Smtp::GetVersion;
    printf "\t\tDegtUtils.pm\tv%s\n", &DegtUtils::GetVersion;
    printf "\t\tInterpret.pm\tv%s\n", &Chklogs::GetInterpretVersion;
    exit 0;
}    

#-----------------------------------------------------
# mailViaUserAgent
#	A last resort for mailing the report. The SMTP
#	socket is the preferred way but if your system
#	doesn't have the latest Socket.pm (>= 1.5) which
#	is platform independent then you will have to
#	resort to dump to a temporary file and use the
#	User Mail Agent such as Sendmail/Mailx/Smail.
sub mailViaUserAgent {
    my $mailcmd="$mailer $admin";	# Command for mail option
    my $i;

    open(MAIL,"> $mailout") || die 'chklogs: Could not create mail file';
    print MAIL "Subject: Chklogs Summary\n";
    foreach $i (0 .. $#Xheader) {
        print MAIL $Xheader[$i];
    }	
    print MAIL "\n\n";
    foreach $i (0 .. $#Report) {
        print MAIL $Report[$i];
    }	
    close(MAIL);
    system("cat $mailout | $mailcmd");
#    unlink($mailout);
}

#=====================================================
# MAIN
#=====================================================
$cvsId   =~ m/Revision:\s+(\d+\.\d+\.*\d*\.*\d*)/;
$version = $1;

if ($#ARGV == -1) {
    $opt_default = 1;
}
else {
    if ($ARGV[0] eq "init" || $ARGV[0] eq "initrepos" || $ARGV[0] eq "newconf" ||
	$ARGV[0] eq "newgroup" || $ARGV[0] eq "rmgroup"  ||
	$ARGV[0] eq "del" || $ARGV[0] eq "gadd" || $ARGV[0] eq "sync" ||
	$ARGV[0] eq "when" || $ARGV[0] eq "syslog") {
	exec 'chklogsadm', @ARGV; 
    }

    my $i;
    foreach $i (0 .. $#ARGV) {
	if ($ARGV[$i] eq "-a") { $opArchive = 1; }
	if ($ARGV[$i] eq "-m") { $opMail = 1; }
	if ($ARGV[$i] eq "-c") { $opCheck = 1; }
	if ($ARGV[$i] eq "-t") { $opTest = 1; }
	if ($ARGV[$i] eq "-w") { $opWarn = 1; }
	if ($ARGV[$i] eq "-v") { &ShowVersion }
    }
}	

&BeginChklogs;
&initialize;			# Must be after interpreter
if (defined($opWarn) || defined($opCheck) || defined($opTest)) {
    &DisableGroups;
    &DisableTimed;
}

my ($BuiltinMethod, $pid, $valid);

open(CONF,$ConfFile) || die "Cannot open $ConfFile\n";

# Feature: TIMED LOGS
#
&ReadTimeLog;				# Read in memory cache

&IdentifySyslog(1);			# Be truly syslog aware...
$BuiltinMethod = 0;

$REPORT = \*STDOUT; 
$REPORT = \@Report if ($opMail); 
&ReportHeader();

while (<CONF>) {
    if (/^#:options\s+/i || /^-options\s+/i) {
	&ReadOptions($', \%cfg);
	next;
    }
    if (/^#:global\s+/i  || /^-global\s+/i) {
	$cfg{'global'} = $';
	chomp($cfg{'global'});
	next;
    }
    if (/^#:group\s+/i   || /^-group\s+/i) {
	$cGroup{'name'} = $';
	chomp $cGroup{'name'};
        &ReadGroupConfiguration(\*CONF, \%cGroup);
	`$cGroup{'pre'}` if $cGroup{'pre'} ne '';
        next;
    }
    if (/^#/ || /^\s*$/) {		# ignore comment line
	`$cGroup{'post'}` if $cGroup{'post'} ne '';
	$cGroup{'name'} = "";		# Reset group handling
	$cGroup{'pre'}  = "";
	$cGroup{'post'} = "";
	next;
    }
    $AgeFlag = 0;			# Default is size-oriented
    $DC = " ";

    #   Logname   MaximumSize  DefaultAction MaxNrLogs [ExecParameters]
    ($cLog{'name'}, $cLog{'threshold'}, $cLog{'action'}, 
                    $cLog{'max'}, $cLog{'param'}) = split(' ',$_,5);

    #   External handler?
    undef($exec_pgm);
    if ($cLog{'action'} eq 'execute') {
	$exec_pgm = $cLog{'max'};
	chomp($cLog{'param'});
	$cLog{'param'} =~ s/%L/$cLog{'name'}/;
    }
    #   Use the default minimum if not specified in index file
    if ($cLog{'max'} == 0) { $cLog{'max'} = $maxlogs; }

    if (&IsSyslogMember($cLog{'name'}) == 1) {
	if ($BuiltinMethod == 0) {
	    $pid = &StopProcess($syslogF);	# send SIGSTOP to syslogd
    	    $BuiltinMethod = 1;
	}
    } else {
	if ($BuiltinMethod == 1) {
	    &ContProcess($pid);			# send SIGCONT to syslogd
	    $BuiltinMethod = 0;			# Let it run free again
	}
    }
    
    # Check if user means (kilo)bytes, days or months
    if ($cLog{'threshold'} =~ /[kK]$/) { 
	$cLog{'threshold'} =~ s/[kK]$//;
	$cLog{'threshold'} *= 1000;
    }
    elsif ($cLog{'threshold'} =~ /[dD]$/) { 
	$cLog{'threshold'} =~ s/[Dd]$//;
	$AgeFlag = 1;
	$DC = "d";
    }
    elsif ($cLog{'threshold'} =~ /[mM]$/) { 
	$cLog{'threshold'} =~ s/[mM]$//;
	$cLog{'threshold'} *= 30;
	$AgeFlag = 1;
	$DC = "d";
    }
    #
    #   Check if the Action field is valid: `archive' or `truncate'
    #
    if ($cLog{'action'} ne 'archive' && $cLog{'action'} ne 'truncate' &&
        $cLog{'action'} ne 'execute') {
	$cLog{'action'} = '????';
    }
    
    # Feature: Alternate Repository
    #
    if ($cLog{'action'} eq 'archive') {	  # Not needed if execute or truncate
	$valid = &ValidateRepository(\$Repository, \%cfg, \%cGroup,
				     &dirname($cLog{'name'}));
 	if ($valid != 0) {
	    # Leave it where it is... this assumption may
	    # change in the future.
	    $Repository = &dirname($cLog{'name'});
	    # Forgive but do not forget
	    print "Configuration line $. : Need to run chklogsadm initrepos\n";
 	}
    }

    if ( -d $cLog{'name'} ) {
	&ProcessDirectory($cLog{'name'});
    }
    else {
	&ProcessLog($cLog{'name'});
    }
}

# Watchcat for syslog handling in case the last log was syslog member.
if ($BuiltinMethod == 1) {
    &ContProcess($pid);			# Meouw !!!
    $BuiltinMethod = 0;
}

&WriteTimeLog;

#
#   We are almost done...
#
close(CONF);

&SanityCheck() if defined($opTest);
&ReportTrailer() unless defined($opTest);
if (defined($opMail) && $notify) {
    my $SMTP_S;					# Handle to SMTP port socket
    my $myself;

    if ($useMiniMail eq 'yes') {
	&OpenSMTP(\*SMTP_S, $mailhost);
	$myself = $ENV{'USER'} || $ENV{'LOGNAME'} || 'nobody';
	$myself .= '@' . &GethostSMTP;
	&Mail(\*SMTP_S,                         # Socket handle
	      $admin,			    # To:
	      $myself,			    # From:
	      'Chklogs Summary',		    # Subject:
	      \@Report,			    # Message content
	      \@Xheader);			    # Custom headers
	&CloseConnection(\*SMTP_S);
    } else {
        &mailViaUserAgent;
    }
}
