683 lines
20 KiB
Perl
Executable File
683 lines
20 KiB
Perl
Executable File
# logrotate-lib.pl
|
|
# Common functions for parsing the logrotate configuration file.
|
|
|
|
BEGIN { push(@INC, ".."); };
|
|
use WebminCore;
|
|
&init_config();
|
|
|
|
if (open(VERSION, "<$module_config_directory/version")) {
|
|
chop($logrotate_version = <VERSION>);
|
|
close(VERSION);
|
|
}
|
|
|
|
# Use sample config if it exists but real config doesn't yet
|
|
if (!-r $config{'logrotate_conf'} && -r $config{'sample_conf'}) {
|
|
©_source_dest($config{'sample_conf'}, $config{'logrotate_conf'});
|
|
}
|
|
|
|
# get_config_parent()
|
|
# Returns the parsed global config while keeping the writable local file as
|
|
# its save target. Callers must materialize that file before global writes.
|
|
sub get_config_parent
|
|
{
|
|
if (!$get_config_parent_cache) {
|
|
local ($conf, $lines) = &get_config();
|
|
# Even when members came from the vendor config, never make /usr the
|
|
# destination for newly-added global directives.
|
|
$get_config_parent_cache = { 'members' => $conf,
|
|
'file' => $config{'logrotate_conf'},
|
|
'line' => 0,
|
|
'eline' => $lines,
|
|
'global' => 1 };
|
|
}
|
|
return $get_config_parent_cache;
|
|
}
|
|
|
|
# get_main_config_file()
|
|
# Returns the local main config, or the vendor default if no local one exists
|
|
sub get_main_config_file
|
|
{
|
|
return $config{'logrotate_conf'} if (-e $config{'logrotate_conf'});
|
|
return $config{'vendor_logrotate_conf'}
|
|
if ($config{'vendor_logrotate_conf'});
|
|
return $config{'logrotate_conf'};
|
|
}
|
|
|
|
# is_vendor_main_config(file)
|
|
# Returns 1 if a file is the vendor-provided main config
|
|
sub is_vendor_main_config
|
|
{
|
|
my ($file) = @_;
|
|
return $config{'vendor_logrotate_conf'} &&
|
|
&same_file($file, $config{'vendor_logrotate_conf'});
|
|
}
|
|
|
|
# relative_config_path(file, directory)
|
|
# Returns a file's path relative to a config directory
|
|
sub relative_config_path
|
|
{
|
|
my ($file, $dir) = @_;
|
|
return undef if (!$file || !$dir);
|
|
$dir =~ s/\/+$//;
|
|
$dir .= '/';
|
|
return $file =~ /^\Q$dir\E(.+)$/ ? $1 : undef;
|
|
}
|
|
|
|
# is_vendor_config_file(file)
|
|
# Returns 1 if a drop-in comes from the vendor directory
|
|
sub is_vendor_config_file
|
|
{
|
|
my ($file) = @_;
|
|
return defined(&relative_config_path(
|
|
$file, $config{'vendor_add_file'}));
|
|
}
|
|
|
|
# get_local_override_file(vendor-file)
|
|
# Returns the local path that overrides a vendor drop-in
|
|
sub get_local_override_file
|
|
{
|
|
my ($file) = @_;
|
|
my $rel = &relative_config_path($file, $config{'vendor_add_file'});
|
|
return undef if (!defined($rel) || !$config{'add_file'});
|
|
return $config{'add_file'}.'/'.$rel;
|
|
}
|
|
|
|
# get_vendor_config_file(local-file)
|
|
# Returns the vendor file shadowed by a local drop-in, if any
|
|
sub get_vendor_config_file
|
|
{
|
|
my ($file) = @_;
|
|
my $rel = &relative_config_path($file, $config{'add_file'});
|
|
return undef if (!defined($rel) || !$config{'vendor_add_file'});
|
|
my $vendor = $config{'vendor_add_file'}.'/'.$rel;
|
|
return -f $vendor ? $vendor : undef;
|
|
}
|
|
|
|
# flush_logrotate_config_cache()
|
|
# Clears parsed config state after creating a local override
|
|
sub flush_logrotate_config_cache
|
|
{
|
|
%get_config_cache = ( );
|
|
%get_config_lnum_cache = ( );
|
|
%get_config_files_cache = ( );
|
|
$get_config_parent_cache = undef;
|
|
}
|
|
|
|
# copy_vendor_config(source, destination)
|
|
# Copies a vendor config to the writable local tree
|
|
sub copy_vendor_config
|
|
{
|
|
my ($source, $dest) = @_;
|
|
|
|
# An existing independent regular destination is already a usable override.
|
|
# Refuse links to the vendor file, symlinks, and other non-regular file types
|
|
# so the local path cannot redirect writes back into the read-only tree.
|
|
if (-e $dest || -l $dest) {
|
|
if (-f $dest && !-l $dest) {
|
|
if (&same_file($source, $dest)) {
|
|
&error(&text('save_evendorwrite', "<tt>".
|
|
&html_escape($source)."</tt>"));
|
|
}
|
|
else {
|
|
&flush_logrotate_config_cache();
|
|
return $dest;
|
|
}
|
|
}
|
|
&error(&text('save_eoverride', "<tt>".
|
|
&html_escape($dest)."</tt>"));
|
|
}
|
|
|
|
# Create missing subdirectories before copying the complete vendor file.
|
|
# Following a source symlink produces an editable snapshot, not another link.
|
|
my $dir = $dest;
|
|
$dir =~ s/\/[^\/]+$//;
|
|
&make_dir_recursive($dir, 0755) if (!-d $dir);
|
|
my ($ok, $err) = ©_source_dest($source, $dest, 1);
|
|
|
|
# Do not leave a partial override behind after a copy or chmod failure, since
|
|
# even an incomplete local file would hide the valid vendor configuration.
|
|
if (!$ok || !&set_ownership_permissions(undef, undef, 0644, $dest)) {
|
|
$err ||= $!;
|
|
&unlink_file($dest) if (-e $dest || -l $dest);
|
|
&error(&text('save_ecopy', "<tt>".&html_escape($dest)."</tt>",
|
|
&html_escape($err)));
|
|
}
|
|
|
|
# Force the next read to select and parse the newly-created local file.
|
|
&flush_logrotate_config_cache();
|
|
return $dest;
|
|
}
|
|
|
|
# ensure_local_main_config()
|
|
# Creates a writable local main config when only the vendor default exists
|
|
sub ensure_local_main_config
|
|
{
|
|
my $main = &get_main_config_file();
|
|
return $config{'logrotate_conf'}
|
|
if (!&is_vendor_main_config($main));
|
|
return ©_vendor_config($main, $config{'logrotate_conf'});
|
|
}
|
|
|
|
# ensure_local_config_override(vendor-file)
|
|
# Creates a writable local copy that shadows a vendor drop-in
|
|
sub ensure_local_config_override
|
|
{
|
|
my ($file) = @_;
|
|
my $local = &get_local_override_file($file);
|
|
return $file if (!$local);
|
|
return ©_vendor_config($file, $local);
|
|
}
|
|
|
|
# ensure_writable_config_file(file)
|
|
# Makes a config file safe to edit, by creating the local override that
|
|
# shadows it when it belongs to the vendor tree. Callers must use this before
|
|
# saving directives, as save_directive refuses to write to a vendor file.
|
|
sub ensure_writable_config_file
|
|
{
|
|
my ($file) = @_;
|
|
if (&is_vendor_main_config($file)) {
|
|
return &ensure_local_main_config();
|
|
}
|
|
elsif (&is_vendor_config_file($file)) {
|
|
return &ensure_local_config_override($file);
|
|
}
|
|
elsif (my $vendor = &get_vendor_config_file($file)) {
|
|
# A local path that shadows a vendor file, but does not exist yet
|
|
return &ensure_local_config_override($vendor);
|
|
}
|
|
elsif ($file eq &get_config_parent()->{'file'} &&
|
|
&is_vendor_main_config(&get_main_config_file())) {
|
|
# New directives go to the main config, which is still the vendor one
|
|
return &ensure_local_main_config();
|
|
}
|
|
return $file;
|
|
}
|
|
|
|
# list_config_dir_files(directory, [relative-subdirectory])
|
|
# Returns relative and absolute paths for regular files below a directory
|
|
sub list_config_dir_files
|
|
{
|
|
my ($dir, $subdir) = @_;
|
|
my $path = $subdir ? $dir.'/'.$subdir : $dir;
|
|
opendir(my $dh, $path) || return ( );
|
|
my @names = sort { $a cmp $b } readdir($dh);
|
|
closedir($dh);
|
|
my @rv;
|
|
foreach my $name (@names) {
|
|
next if ($name eq '.' || $name eq '..');
|
|
my $rel = $subdir ? $subdir.'/'.$name : $name;
|
|
my $file = $dir.'/'.$rel;
|
|
|
|
# Match find without -L: ignore symlinks, recurse into real directories,
|
|
# and return only regular files with paths relative to the scanned root.
|
|
next if (-l $file);
|
|
if (-d $file) {
|
|
push(@rv, &list_config_dir_files($dir, $rel));
|
|
}
|
|
elsif (-f $file) {
|
|
push(@rv, [ $rel, $file ]);
|
|
}
|
|
}
|
|
return @rv;
|
|
}
|
|
|
|
# get_add_file_configs([&already-loaded-files])
|
|
# Returns the effective vendor and local configs loaded by logrotate-all
|
|
sub get_add_file_configs
|
|
{
|
|
my ($files) = @_;
|
|
return ( ) if (!$config{'scan_add_file'});
|
|
|
|
# Collect the same relative names produced by the wrapper's recursive find.
|
|
# Processing the local tree last records its regular files directly.
|
|
my %effective;
|
|
foreach my $dir ($config{'vendor_add_file'}, $config{'add_file'}) {
|
|
next if (!$dir || !-d $dir);
|
|
foreach my $entry (&list_config_dir_files($dir)) {
|
|
$effective{$entry->[0]} = $entry->[1];
|
|
}
|
|
}
|
|
|
|
# Match the wrapper's stable lexical order and omit files already reached by
|
|
# an explicit include in the main configuration. The existence check also
|
|
# honors a local non-regular counterpart exactly as the wrapper does.
|
|
my @rv;
|
|
foreach my $name (sort { $a cmp $b } keys %effective) {
|
|
my $local = $config{'add_file'} ?
|
|
$config{'add_file'}.'/'.$name : undef;
|
|
my $f = $local && -e $local ? $local : $effective{$name};
|
|
next if ($files &&
|
|
grep { &same_file($_, $f) } @$files);
|
|
push(@rv, $f);
|
|
}
|
|
return @rv;
|
|
}
|
|
|
|
# get_scheduled_logrotate_command()
|
|
# Returns the distro wrapper, or a command for the effective config files
|
|
sub get_scheduled_logrotate_command
|
|
{
|
|
# The distro wrapper discovers the effective drop-in set on every run, so it
|
|
# remains correct when packages or administrators add files later.
|
|
if ($config{'logrotate_all'} && -x $config{'logrotate_all'}) {
|
|
return "e_path($config{'logrotate_all'});
|
|
}
|
|
|
|
# Preserve the historical command exactly on systems that do not opt into
|
|
# external or vendor configuration discovery.
|
|
if (!$config{'vendor_logrotate_conf'} && !$config{'vendor_add_file'} &&
|
|
!$config{'scan_add_file'}) {
|
|
return &has_command($config{'logrotate'})." ".
|
|
$config{'logrotate_conf'};
|
|
}
|
|
|
|
# If the configured wrapper is unavailable, build a usable command from the
|
|
# effective main config and the drop-ins visible at schedule creation time.
|
|
my $main = &get_main_config_file();
|
|
my (undef, undef, $files) = &get_config($main);
|
|
my @configs = ($main, &get_add_file_configs($files));
|
|
my $program = &has_command($config{'logrotate'}) || $config{'logrotate'};
|
|
return "e_path($program).' '.
|
|
join(' ', map { "e_path($_) } @configs);
|
|
}
|
|
|
|
# get_config([file])
|
|
# Returns a list of logrotate config file entries
|
|
sub get_config
|
|
{
|
|
my ($argfile) = @_;
|
|
my $file = $argfile || &get_main_config_file();
|
|
if (!$argfile && $get_config_cache{$file}) {
|
|
return wantarray ? ( $get_config_cache{$file},
|
|
$get_config_lnum_cache{$file},
|
|
$get_config_files_cache{$file} )
|
|
: $get_config_cache{$file};
|
|
}
|
|
my @files = ( $file );
|
|
my @rv;
|
|
my $addto = \@rv;
|
|
my $section;
|
|
my $lnum = 0;
|
|
my $fh = "FILE".$file_count++;
|
|
open($fh, "<".$file);
|
|
while(<$fh>) {
|
|
s/\r|\n//g;
|
|
s/#.*$//;
|
|
if (/^\s*(.*)\{\s*$/) {
|
|
# Start of a section
|
|
push(@name, &split_quoted_string($1));
|
|
$section = { 'name' => [ @name ],
|
|
'members' => [ ],
|
|
'index' => scalar(@$addto),
|
|
'line' => defined($namestart) ? $namestart : $lnum,
|
|
'eline' => $lnum,
|
|
'file' => $file };
|
|
push(@$addto, $section);
|
|
$addto = $section->{'members'};
|
|
@name = ( );
|
|
$namestart = undef;
|
|
}
|
|
elsif ((/^\s*\// || /^\s*"\//) && !$section) {
|
|
# A path before a section
|
|
$namestart = $lnum if (!@name);
|
|
push(@name, &split_quoted_string($_));
|
|
}
|
|
elsif (/^\s*}\s*$/) {
|
|
# End of a section
|
|
$addto = \@rv;
|
|
$section->{'eline'} = $lnum;
|
|
$section = undef;
|
|
}
|
|
elsif (/^\s*include\s+(.*)$/i) {
|
|
# Including other directives files
|
|
my $incfile = $1;
|
|
if (-d $incfile) {
|
|
# Multiple files!
|
|
my $f;
|
|
opendir(DIR, $incfile);
|
|
my @dirs = sort { $a cmp $b } readdir(DIR);
|
|
closedir(DIR);
|
|
foreach $f (@dirs) {
|
|
next if ($f =~ /^\./ ||
|
|
$f =~ /\.rpm(save|orig|new)$/ ||
|
|
$f =~ /\~$/ ||
|
|
$f =~ /,v$/ ||
|
|
$f =~ /\.swp$/ ||
|
|
$f =~ /\.lock$/);
|
|
my ($inc, $ilnum, $ifiles) =
|
|
&get_config("$incfile/$f");
|
|
push(@files, @$ifiles);
|
|
map { $_->{'index'} += @$addto } @$inc;
|
|
push(@$addto, @$inc);
|
|
}
|
|
}
|
|
else {
|
|
# A single file
|
|
my ($inc, $ilnum, $ifiles) = &get_config($incfile);
|
|
push(@files, @$ifiles);
|
|
map { $_->{'index'} += @$addto } @$inc;
|
|
push(@$addto, @$inc);
|
|
}
|
|
}
|
|
elsif (/^\s*(\S+)\s*(.*)$/) {
|
|
# Single directive
|
|
my $dir = { 'name' => $1,
|
|
'value' => $2,
|
|
'index' => scalar(@$addto),
|
|
'line' => $lnum,
|
|
'eline' => $lnum,
|
|
'file' => $file };
|
|
push(@$addto, $dir);
|
|
if ($1 eq 'postrotate' || $1 eq 'prerotate') {
|
|
# Followed by a multi-line script!
|
|
while(<$fh>) {
|
|
$lnum++;
|
|
s/\r|\n//g;
|
|
last if (/^\s*(endscript|endrotate)\s*$/);
|
|
s/^\s+//;
|
|
$dir->{'script'} .= $_."\n";
|
|
}
|
|
$dir->{'eline'} = $lnum;
|
|
}
|
|
}
|
|
$lnum++;
|
|
}
|
|
close($fh);
|
|
if (!$argfile) {
|
|
foreach my $f (&get_add_file_configs(\@files)) {
|
|
my ($inc, undef, $ifiles) = &get_config($f);
|
|
map { $_->{'index'} += @rv } @$inc;
|
|
push(@rv, @$inc);
|
|
push(@files, @$ifiles);
|
|
}
|
|
$get_config_cache{$file} = \@rv;
|
|
$get_config_lnum_cache{$file} = $lnum;
|
|
$get_config_files_cache{$file} = \@files;
|
|
}
|
|
return wantarray ? (\@rv, $lnum, \@files) : \@rv;
|
|
}
|
|
|
|
# join_words(word, ...)
|
|
# Joins an array of words into a string, with quotes if needed
|
|
sub join_words
|
|
{
|
|
return join(" ", map { /\s/ ? "\"$_\"" : $_ } @_);
|
|
}
|
|
|
|
# find(name, &config)
|
|
# Returns an object or objects from the config with some name
|
|
sub find
|
|
{
|
|
my ($name, $conf) = @_;
|
|
my @rv = grep { lc($_->{'name'}) eq lc($name) } @$conf;
|
|
return wantarray ? @rv : $rv[0];
|
|
}
|
|
|
|
# find_value(name, &config)
|
|
# Returns a value or values from the config with some name
|
|
sub find_value
|
|
{
|
|
my ($name, $conf) = @_;
|
|
my @rv = map { defined($_->{'script'}) ? $_->{'script'} : $_->{'value'} }
|
|
grep { lc($_->{'name'}) eq lc($name) } @$conf;
|
|
return wantarray ? @rv : $rv[0];
|
|
}
|
|
|
|
# get_logrotate_version([&out])
|
|
# Returns the version number, and saves the full -v output to the out param
|
|
sub get_logrotate_version
|
|
{
|
|
my ($rv) = @_;
|
|
my $out = &backquote_command("$config{'logrotate'} -v 2>&1", 1);
|
|
$$rv = $out if ($rv);
|
|
return $out =~ /logrotate\s+([0-9\.]+)\s/ ||
|
|
$out =~ /logrotate\-([0-9\.]+)\s/ ? $1 : undef;
|
|
}
|
|
|
|
# get_period(&conf)
|
|
# Returns the rotation time period set in the config
|
|
sub get_period
|
|
{
|
|
my ($conf) = @_;
|
|
foreach my $p ("daily", "weekly", "monthly") {
|
|
my $ex = &find($p, $conf);
|
|
return $p if ($ex);
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
# save_directive(&parent, &old|name, &new, [indent])
|
|
# Updates one entry identified by either its name or parsed object
|
|
sub save_directive
|
|
{
|
|
my ($parent, $oldv, $newv, $indent) = @_;
|
|
my $conf = $parent->{'members'};
|
|
my $old = !defined($oldv) ? undef : ref($oldv) ? $oldv : &find($oldv, $conf);
|
|
my $new = !defined($newv) ? undef : ref($newv) ? $newv :
|
|
{ 'name' => $old ? $old->{'name'} : $oldv,
|
|
'value' => $newv };
|
|
|
|
# Deleting an entry that is already absent is a true no-op. In particular,
|
|
# do not put a missing local main config into the writable line cache.
|
|
return if (!$old && !$new);
|
|
|
|
# Find the file behind this write. Existing directives use their own file,
|
|
# new sections may name a separate file, and other additions use the parent
|
|
# section or effective main config.
|
|
my $vendor_file;
|
|
my $write_file = $parent->{'file'};
|
|
if ($old) {
|
|
$write_file = $old->{'file'};
|
|
}
|
|
elsif ($new && $new->{'file'} &&
|
|
!($parent->{'global'} && !$new->{'members'})) {
|
|
$write_file = $new->{'file'};
|
|
}
|
|
if ($write_file) {
|
|
my $shadowed_vendor = &get_vendor_config_file($write_file);
|
|
if (&is_vendor_main_config($write_file) ||
|
|
&is_vendor_config_file($write_file)) {
|
|
$vendor_file = $write_file;
|
|
}
|
|
elsif ($shadowed_vendor &&
|
|
(!-f $write_file || -l $write_file ||
|
|
&same_file($write_file, $shadowed_vendor))) {
|
|
$vendor_file = $shadowed_vendor;
|
|
}
|
|
}
|
|
if (!$vendor_file && !$old && $parent->{'global'} &&
|
|
&same_file($write_file, $parent->{'file'}) &&
|
|
&is_vendor_main_config(&get_main_config_file())) {
|
|
$vendor_file = &get_main_config_file();
|
|
}
|
|
|
|
# Copying changes which file owns the parsed objects, so callers must create
|
|
# and reload a local override before editing. Never write through a stale
|
|
# object that still points at the vendor tree.
|
|
&error(&text('save_evendorwrite',
|
|
"<tt>".&html_escape($vendor_file)."</tt>")) if ($vendor_file);
|
|
|
|
my $lref = &read_file_lines($write_file);
|
|
my @lines = &directive_lines($new, $indent) if ($new);
|
|
my $gparent = &get_config_parent();
|
|
if ($old && $new) {
|
|
# Update
|
|
my $oldlines = $old->{'eline'} - $old->{'line'} + 1;
|
|
splice(@$lref, $old->{'line'}, $oldlines, @lines);
|
|
$new->{'line'} = $old->{'line'};
|
|
$new->{'index'} = $old->{'index'};
|
|
$new->{'file'} = $old->{'file'};
|
|
$new->{'eline'} = $new->{'line'} + scalar(@lines) - 1;
|
|
%$old = %{$new};
|
|
&renumber($gparent, $old->{'file'}, $old->{'eline'},
|
|
scalar(@lines) - $oldlines, $old);
|
|
}
|
|
elsif ($old && !$new) {
|
|
# Delete
|
|
my $oldlines = $old->{'eline'} - $old->{'line'} + 1;
|
|
splice(@$lref, $old->{'line'}, $old->{'eline'} - $old->{'line'} + 1);
|
|
splice(@$conf, $old->{'index'}, 1);
|
|
&renumber($gparent, $old->{'file'}, $old->{'line'}, -$oldlines);
|
|
}
|
|
elsif (!$old && $new && $parent->{'global'} && !$new->{'members'}) {
|
|
# Add at the start of the file
|
|
if (defined($parent->{'line'})) {
|
|
splice(@$lref, 0, 0, @lines);
|
|
$new->{'line'} = 0;
|
|
$new->{'eline'} = $new->{'line'} + scalar(@lines) - 1;
|
|
$new->{'file'} = $parent->{'file'};
|
|
&renumber($gparent, $new->{'file'}, $new->{'line'}-1, scalar(@lines));
|
|
}
|
|
$new->{'index'} = 0;
|
|
splice(@$conf, 0, 0, $new);
|
|
}
|
|
elsif (!$old && $new) {
|
|
# Add (to end of section)
|
|
if (defined($parent->{'line'})) {
|
|
if (!$new->{'file'} || $parent->{'file'} eq $new->{'file'}) {
|
|
# Adding to parent file
|
|
splice(@$lref, $parent->{'eline'}, 0, @lines);
|
|
$new->{'line'} = $parent->{'eline'};
|
|
$new->{'eline'} = $new->{'line'} + scalar(@lines) - 1;
|
|
$new->{'file'} = $parent->{'file'};
|
|
&renumber($gparent, $new->{'file'}, $new->{'line'}-1, scalar(@lines));
|
|
}
|
|
else {
|
|
# Adding to another file
|
|
my $lref2 = &read_file_lines($new->{'file'});
|
|
$new->{'line'} = scalar(@$lref2);
|
|
$new->{'eline'} = $new->{'line'} + scalar(@lines) - 1;
|
|
push(@$lref2, @lines);
|
|
}
|
|
}
|
|
$new->{'index'} = scalar(@$conf);
|
|
push(@$conf, $new);
|
|
}
|
|
}
|
|
|
|
# renumber(&object, file, startline, count, [&skip])
|
|
# Update line numbers in the config that are in some file and after
|
|
# some line
|
|
sub renumber
|
|
{
|
|
my ($conf, $file, $start, $count, $skip) = @_;
|
|
return if (!$count);
|
|
if ($conf->{'file'} eq $file && $conf ne $skip) {
|
|
$conf->{'line'} += $count if ($conf->{'line'} > $start);
|
|
$conf->{'eline'} += $count if ($conf->{'eline'} > $start);
|
|
}
|
|
if ($conf->{'members'}) {
|
|
foreach my $c (@{$conf->{'members'}}) {
|
|
&renumber($c, $file, $start, $count, $skip);
|
|
}
|
|
}
|
|
}
|
|
|
|
# directive_lines(&dir, indent)
|
|
# Returns an array of lines to add to the config file for some directive
|
|
sub directive_lines
|
|
{
|
|
my ($dir, $indent) = @_;
|
|
my @rv;
|
|
if ($dir->{'members'}) {
|
|
push(@rv, $indent.&join_words(@{$dir->{'name'}})." {");
|
|
foreach my $m (@{$dir->{'members'}}) {
|
|
push(@rv, &directive_lines($m, $indent."\t"));
|
|
}
|
|
push(@rv, $indent."}");
|
|
}
|
|
elsif ($dir->{'script'}) {
|
|
push(@rv, $indent.$dir->{'name'});
|
|
foreach my $s (split(/\n/, $dir->{'script'})) {
|
|
push(@rv, $indent.$s);
|
|
}
|
|
push(@rv, $indent."endscript");
|
|
}
|
|
else {
|
|
push(@rv, $indent.$dir->{'name'}.
|
|
($dir->{'value'} eq "" ? "" : " ".$dir->{'value'}));
|
|
}
|
|
return @rv;
|
|
}
|
|
|
|
# delete_if_empty(file)
|
|
# Removes a file if it has no more parsed entries, unless it is a local
|
|
# override whose continued existence is needed to hide a vendor file
|
|
sub delete_if_empty
|
|
{
|
|
my ($file) = @_;
|
|
return if (&get_vendor_config_file($file));
|
|
my $conf = &get_config();
|
|
my %files = map { $_, 1 } &unique(map { $_->{'file'} } @$conf);
|
|
&unlink_file($file) if (!$files{$file});
|
|
}
|
|
|
|
%global_default = ( "nocompress" => "",
|
|
"compress" => undef,
|
|
"nodelaycompress" => "",
|
|
"delaycompress" => undef,
|
|
"ifempty" => "",
|
|
"notifempty" => undef,
|
|
"nocopytruncate" => "",
|
|
"copytruncate" => undef,
|
|
"nomissingok" => "",
|
|
"missingok" => undef,
|
|
"rotate" => 0,
|
|
"create" => "",
|
|
"nocreate" => undef,
|
|
"noolddir" => "",
|
|
"olddir" => undef,
|
|
"ext" => undef,
|
|
"mail" => undef,
|
|
"nomail" => "",
|
|
"maillast" => "",
|
|
"mailfirst" => undef,
|
|
"errors" => undef,
|
|
"postrotate" => undef,
|
|
"prerotate" => undef,
|
|
"errors" => undef,
|
|
);
|
|
|
|
# rotate_log_now(&log)
|
|
# Call logrotate on a config fragment file to rotate just one set of logs
|
|
# immediately.
|
|
sub rotate_log_now
|
|
{
|
|
my ($dir) = @_;
|
|
my $conf = &get_config();
|
|
my $temp = &transname();
|
|
open(TEMP, ">$temp");
|
|
foreach my $c (@$conf) {
|
|
if (!$c->{'members'}) {
|
|
print TEMP map { "$_\n" } &directive_lines($c);
|
|
}
|
|
}
|
|
print TEMP map { "$_\n" } &directive_lines($dir);
|
|
close(TEMP);
|
|
&set_ownership_permissions(undef, undef, 0644, $temp);
|
|
my $out = &backquote_logged("$config{'logrotate'} -f $temp 2>&1");
|
|
return ($?, $out);
|
|
}
|
|
|
|
# get_add_file([filename])
|
|
# Returns the file to which new logrotate sections should be added
|
|
sub get_add_file
|
|
{
|
|
my ($filename) = @_;
|
|
$filename =~ s/\*/ALL/g;
|
|
if ($config{'add_file'} && -d $config{'add_file'} && $filename) {
|
|
# Adding to a new file in a directory
|
|
return "$config{'add_file'}/$filename.conf";
|
|
}
|
|
elsif ($config{'add_file'} && !-d $config{'add_file'}) {
|
|
# Make sure file is valid
|
|
my ($conf, $lnum, $files) = &get_config();
|
|
if (&indexof($config{'add_file'}, @$files) >= 0) {
|
|
return $config{'add_file'};
|
|
}
|
|
}
|
|
return $config{'logrotate_conf'};
|
|
}
|
|
|
|
1;
|