#! /usr/bin/perl
###############################################################################
#    Copyright (C) 2002-2204 by Eric Gerbier
#    Bug reports to: eric.gerbier@tutanota.com
#    $Id$
#
#    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.
#
###############################################################################
# afickonfig is designed to modify afick's config file in a batch way
# it just add, replace, remove any components (macro, alias, directives, rules)
# of this file
###############################################################################

use strict;
use warnings;
use Pod::Usage;
use English qw(-no_match_vars);
use Getopt::Long;    # option analysis

# debuggging
# use diagnostics;
# use Data::Dumper;

use File::Basename;    # for path
my $dirname = dirname($PROGRAM_NAME);
require $dirname . '/afick-common.pl';

###############################################################################
#                     global variables
###############################################################################

# no critic (ProhibitMixedCaseVars)
my $Version = '0.12';

my $EMPTY = Afick::Constant->EMPTY;

#############################################################
# build config line according type
sub buildligne($$$$;$) {
	my $title     = shift @_;    # type of ligne : rule/macro/directive/alias
	my $r_changes = shift @_;    # ref to hash, containing changes
	my $r_onlydir = shift @_;    # ref to hash, for selection
	my $key       = shift @_;    # parameter value
	my $list      = shift @_;    # true in list mode

	## no critic (RequireInterpolationOfMetachars)
	my %h_format = (
		macro     => '@@define %s %s',
		alias     => '%s = %s',
		directive => '%s := %s',
	);
	## use critic

	if ( $title eq 'rule' ) {

		# return of the quotes
		my $newkey = ( $key =~ m/\s/ ) ? "\"$key\"" : $key;
		my $newligne;
		if ( !$r_changes->{$key} ) {
			if ($list) {
				if ( exists $r_changes->{$key} ) {

					#negative rule
					$newligne = "! $newkey";
				}
				else {

					# nothing
					Afick::Msg->warning("rule $key not found");
					$newligne = $EMPTY;
				}
			}
			elsif ( $r_changes->{$key} eq '0' ) {

				#negative rule
				$newligne = "! $newkey";
			}
			else {

				# we have to supress the rule
				$newligne = $newkey;
			}
		}
		elsif ( exists $r_onlydir->{$key} ) {
			$newligne = "= $newkey $r_changes->{$key}";
		}
		else {
			$newligne = "$newkey $r_changes->{$key}";
		}
		return $newligne;
	}
	else {

		# macros, alias, directive
		return sprintf $h_format{$title}, $key, $r_changes->{$key};
	}
}
#############################################################
# generic sub to apply changes on a parameter type
sub change($$$$$) {
	my $title        = shift @_;    # parameter name
	my $changes      = shift @_;    # ref to hash, containing changes
	my $onlydir      = shift @_;    # ref to hash, for selection
	my $test_pattern = shift @_;    # ref to sub to detect adequate pattern
	my $config       = shift @_;    # array of config file lines

	my $nb_changes = 0;
	foreach my $key ( keys %{$changes} ) {

		Afick::Msg->debug("(change) $title : $key");
		my $found          = 0;
		my $nb_changes_key = 0;
		my $i              = 0;
		my $newligne       = buildligne( $title, $changes, $onlydir, $key, 0 );
		foreach my $ligne ( @{$config} ) {

			my @ret;
			if ( @ret = &{$test_pattern}($ligne) ) {

				# found pattern
				#print Dumper(@ret);
				my $key_lu = shift @ret;
				if ( $key_lu eq $key ) {

					# found same key
					if ( $ligne eq $newligne ) {
						print "no changes for $title $key\n";
					}
					elsif ( ( defined $changes->{$key} )
						and ( $changes->{$key} eq $EMPTY ) )
					{
						print "delete $title $key\n";
						$ligne = '# ' . $ligne;
						@{$config}[$i] = $ligne;
						$nb_changes_key++;
					}
					else {
						print "replace $title $ligne by $newligne\n";
						@{$config}[$i] = $newligne;
						$nb_changes_key++;
					}
					$found = 1;
				}    # found key
			}    # test pattern
			$i++;
		}    # foreach config
		if ( !$found ) {
			if (    ( defined $changes->{$key} )
				and ( $changes->{$key} eq $EMPTY ) )
			{
				Afick::Msg->warning("can not delete $title $key : not found");
			}
			else {
				print "add $title line $newligne\n";
				push @{$config}, $newligne;
				$nb_changes_key++;
			}
		}
		if ( $nb_changes_key > 1 ) {
			Afick::Msg->warning(
				"too many changes for $title $key : $nb_changes_key");
		}
		$nb_changes += $nb_changes_key;
	}    #foreach change

	return $nb_changes;
}
#############################################################
# apply changes
sub change_config($$$$$$) {
	my $configfile = shift @_;    # config file name
	my $directives = shift @_;    # ref to hash containing directives changes
	my $macros     = shift @_;    # ref to hash containing macro changes
	my $alias      = shift @_;    # ref to hash containing alias changes
	my $rule       = shift @_;    # ref to hash containing rules changes
	my $onlydir    = shift @_;    # ref to hash for equal selections

	my $cfg    = Afick::Cfg->new($configfile);
	my @config = $cfg->_read_config();

	# begin directives
	my $nb_changes_dir =
	  change( 'directive', $directives, $onlydir, \&is_directive_line,
		\@config );

	# begin macros
	my $nb_changes_macros =
	  change( 'macro', $macros, $onlydir, \&is_macro_line, \@config );

	# begin alias
	my $nb_changes_alias =
	  change( 'alias', $alias, $onlydir, \&is_alias_line, \@config );

	# begin rules
	my $nb_changes_rules =
	  change( 'rule', $rule, $onlydir, \&is_anysel_line, \@config );

	my $nb_changes =
	  $nb_changes_dir +
	  $nb_changes_macros +
	  $nb_changes_alias +
	  $nb_changes_rules;

	if ($nb_changes) {

		# save modified config file
		print "rewrite changed $configfile ($nb_changes)\n";
		print
"directives ($nb_changes_dir) macros($nb_changes_macros) alias ($nb_changes_alias) rules ($nb_changes_rules)\n";
		$cfg->_write_config( \@config );
	}
	return $nb_changes;
}
#############################################################
# just display program version
sub version($) {
	my $version = shift @_;
	print
"afickonfig : another file integrity checker configurator\nversion $version\n";
	return;
}
#############################################################
# a generic sub to add a new valid directive
sub convert2dir($$$) {
	my $rdir = shift @_;    # hash table of new directives
	my $key  = shift @_;    # directive key
	my $val  = shift @_;    # directive value

	my $directives = Afick::Directives->new();
	if ( defined $val ) {
		if ( !defined $directives->check_directive( $key, $val, 1 ) ) {
			Afick::Msg->warning(
				"skip directive $key : " . Afick::Msg->get_error() );
		}
		else {
			Afick::Msg->debug(
				"(convert2dir) find option directive $key : $val");
			$rdir->{$key} = $val;
		}
	}
	return;
}
#############################################################
sub get_var_list($) {
	my $key = shift @_;

	if ( exists $ENV{$key} ) {
		my $path_sep = is_microsoft() ? q{;} : q{:};
		return split /$path_sep/, $ENV{$key};
	}
	else {
		return ();
	}
}
#############################################################
# search in current PATH
sub search_command($) {
	my $prog = shift @_;

	foreach ( get_var_list('PATH') ) {
		if ( -x "$_/$prog" ) {
			return "$_/$prog";
		}
	}
	return;
}
#############################################################
sub ldd() {

	# check if ldd command exists
	my $ldd = search_command('ldd');
	if ($ldd) {

		# call ldd on perl it-self
		my $perlbin = $EXECUTABLE_NAME;

		## no critic (ProhibitBacktickOperators)
		my @output = `$ldd $perlbin`;
		## use critic

		# extract lib directories
		my $libs = $EMPTY;
		foreach my $line (@output) {
			## no critic (ProhibitEmptyQuotes)
			my @tab = split ' ', $line;
			## use critic
			foreach my $elem (@tab) {
				if ( $elem =~ m/^\// ) {
					my $dir = dirname($elem);
					if ($libs) {
						$libs .= ":$dir";
					}
					else {
						$libs = $dir;
					}
				}
			}
		}
		Afick::Msg->debug("(ldd) : $libs");
		return $libs;
	}
	return;
}
#############################################################
# add content of an environment variable
sub add_env($$$$) {
	my $name       = shift @_;    # env var name
	my $r_scan     = shift @_;    # old rules hash
	my $r_newrules = shift @_;    # new rules hash
	my $alias      = shift @_;    # alias object

	my $val = $ENV{$name};
	if ($val) {
		Afick::Msg->debug("(add_env) env $name -> $val");
		my $microsoft = is_microsoft();
		my @tab       = get_var_list($name);
		foreach my $elem (@tab) {

			# relative or absolute path ?
			if ( $microsoft or ( $elem =~ m/^\// ) ) {

				# first check if file exists
				if ( !-e $elem ) {
					Afick::Msg->debug("(add_env) skip $elem : does not exist");
					next;
				}

				# we have to clean name as for rules
				my $elembis = reg_name($elem);

				# remove trailing slash
				$elembis =~ s{/$}{};

				# absolute path
				my $test = ( rech_parent( $elembis, $r_scan ) )[0] || $EMPTY;

				Afick::Msg->debug("(add_env) $elem ($elembis) parent $test");
				if ($test) {

					# test if rules are enough
					# whe should have at least checksum
					my $old_alias = $alias->check_alias( $test, 0 );
					if ( ( $old_alias =~ m/1/ ) or ( $old_alias =~ m/5/ ) ) {

						# ok
						Afick::Msg->debug(
"(add_env) rule $test ($old_alias) for $elem is enough"
						);
					}
					else {

						# not enough
						Afick::Msg->warning(
"(add_env) rule $test ($old_alias) for $elem is not enough"
						);
						$r_newrules->{$elembis} = 'all';
						$r_scan->{$elembis}     = 'all';
					}
				}
				else {

					# no rules : we have to add it
					Afick::Msg->warning(
						"(add_env) add default rule all for $elem");
					$r_newrules->{$elembis} = 'all';
					$r_scan->{$elembis}     = 'all';
				}
			}
			else {

				# relative path
				Afick::Msg->debug("(add_env) skip $elem : relative path");
			}
		}
	}
	else {
		Afick::Msg->debug("(add_env) nothing to add from env : $name");
	}
	return;
}
#############################################################
# read a file and convert content to csv, delimited by ':'
sub convert_file($) {
	my $name = shift @_;

	my $list = $EMPTY;
	if ( -r $name ) {
		if ( open my $fh, '<', $name ) {
			my @tab = <$fh>;
			close $fh or Afick::Msg->warning("can not close $name : $ERRNO");
			chomp @tab;
			$list = join q{:}, @tab;
		}
		else {
			Afick::Msg->warning("can not open $name : $ERRNO");
		}
	}
	return $list;
}
#############################################################
# used in list mode to display a config parameter
# using same syntaxe as configuration file
sub display_value($$$;$) {
	my $type      = shift @_;    # macro/directives/alias/rule
	my $rh_new    = shift @_;    # which keys to display
	my $rh_config = shift @_;    # config values
	my $rh_only   = shift @_;    # onlydir hash

	my %only = ();
	if ( !defined $rh_only ) {
		$rh_only = \%only;
	}

	my @keys = keys %{$rh_new};
	foreach my $key (@keys) {
		my $ligne = buildligne( $type, $rh_config, $rh_only, $key, 1 );
		Afick::Msg->info($ligne);
	}
	return 1;
}
#############################################################
#                          main
#############################################################

my $config              = Afick::Cfg->new();
my $default_config_file = $config->get_configfile();

$OUTPUT_AUTOFLUSH = 1;

# variables for parameter analysis
my $configfile;    # config file name
my $opt_help;
my $opt_man;
my $opt_version;
my $opt_print_config;
my $opt_check_config;
my $opt_clean_config;
my $opt_move_database;
my $opt_verbose;
my $opt_list;

# parameters for afick
my (
	$archive,            $debug_level,        $ignore_case,
	$report_full_newdel, $history,            $warn_missing_file,
	$running,            $warn_dead_symlinks, $follow_symlinks,
	$allow_overload,     $sufx_list,          $prefx_list,
	$re_list,            $timing,             $database,
	$max_checksum_size,  $addpath,            $addlib,
	$allow_relativepath, $report_syslog,      $report_summary,
	$report_context,
);

Getopt::Long::Configure('no_ignore_case');
if (
	!GetOptions(

		# afickonfig options
		'config_file|c=s' => \$configfile,
		'check_config|C'  => \$opt_check_config,
		'clean_config|G'  => \$opt_clean_config,
		'help|?'          => \$opt_help,
		'list|l'          => \$opt_list,
		'man'             => \$opt_man,
		'move_database'   => \$opt_move_database,
		'print_config'    => \$opt_print_config,
		'version|V'       => \$opt_version,
		'verbose'         => \$opt_verbose,
		'addpath'         => \$addpath,
		'addlib'          => \$addlib,

		# afick options
		'archive=s'                           => \$archive,
		'database|D=s'                        => \$database,
		'ignore_case|a!'                      => \$ignore_case,
		'report_full_newdel|full_newdel|f!'   => \$report_full_newdel,
		'history|y=s'                         => \$history,
		'warn_missing_file|missing_files|m!'  => \$warn_missing_file,
		'running_files|r!'                    => \$running,
		'warn_dead_symlinks|dead_symlinks|s!' => \$warn_dead_symlinks,
		'follow_symlinks|Y!'                  => \$follow_symlinks,
		'allow_overload|o!'                   => \$allow_overload,
		'allow_relativepath!'                 => \$allow_relativepath,
		'exclude_suffix|x=s'                  => \$sufx_list,
		'exclude_prefix|X=s'                  => \$prefx_list,
		'exclude_re|R=s'                      => \$re_list,
		'timing|t!'                           => \$timing,
		'debug|d=i'                           => \$debug_level,
		'max_checksum_size|S=i'               => \$max_checksum_size,
		'report_syslog!'                      => \$report_syslog,
		'report_summary!'                     => \$report_summary,
		'report_context!'                     => \$report_context,
	)
  )
{
	pod2usage('incorrect option');
}

if ( defined $opt_verbose ) {
	Afick::Msg->set_verbose($opt_verbose);
}

if ($opt_help) {

	# -h : help
	pod2usage(1);
}
elsif ($opt_version) {

	# -V : version
	version($Version);
	exit;
}
elsif ($opt_man) {
	pod2usage( -verbose => 2 );
}

if ($configfile) {
}
elsif ( -e $default_config_file ) {
	$configfile = $default_config_file;
}
else {
	pod2usage(
"missing configfile name (-c flag) and default config file $default_config_file"
	);
}

# some more controls
## no critic (ProhibitCascadingIfElse)
if ( !-e $configfile ) {
	die "abort : missing configfile name $configfile\n";
}
elsif ($opt_print_config) {
	$config->set_configfile($configfile);
	$config->read_configuration(0);
	$config->print_config( 1, 1, 1, 1, 1 );
	exit;
}
elsif ($opt_check_config) {
	$config->set_configfile($configfile);
	$config->read_configuration(0);
	exit;
}
elsif ( !-w $configfile ) {
	die "abort : configfile name $configfile is not writable\n";
}

# all others actions need write access to config file
if ($opt_clean_config) {
	$config->set_configfile($configfile);
	$config->read_configuration(1);
	exit;
}

# convert afick like options to %newdirectives
my %newdirectives;
convert2dir( \%newdirectives, 'archive',            $archive );
convert2dir( \%newdirectives, 'database',           $database );
convert2dir( \%newdirectives, 'debug',              $debug_level );
convert2dir( \%newdirectives, 'history',            $history );
convert2dir( \%newdirectives, 'ignore_case',        $ignore_case );
convert2dir( \%newdirectives, 'report_full_newdel', $report_full_newdel );
convert2dir( \%newdirectives, 'running_files',      $running );
convert2dir( \%newdirectives, 'warn_dead_symlinks', $warn_dead_symlinks );
convert2dir( \%newdirectives, 'follow_symlinks',    $follow_symlinks );
convert2dir( \%newdirectives, 'allow_overload',     $allow_overload );
convert2dir( \%newdirectives, 'allow_relativepath', $allow_relativepath );
convert2dir( \%newdirectives, 'warn_missing_file',  $warn_missing_file );
convert2dir( \%newdirectives, 'exclude_suffix',     $sufx_list );
convert2dir( \%newdirectives, 'exclude_prefix',     $prefx_list );
convert2dir( \%newdirectives, 'exclude_re',         $re_list );
convert2dir( \%newdirectives, 'timing',             $timing );
convert2dir( \%newdirectives, 'max_checksum_size',  $max_checksum_size );
convert2dir( \%newdirectives, 'report_syslog',      $report_syslog );
convert2dir( \%newdirectives, 'report_summary',     $report_summary );
convert2dir( \%newdirectives, 'report_context',     $report_context );

# get old config from, to be able to check new aliases/rules
my %macros;
my %alias;
my %directive;
my %rules;
my %onlydir;

$config->get_configuration( $configfile, \%macros, \%alias, \%directive,
	\%rules, \%onlydir );

# objects to use check_* methods
my $aliases = Afick::Aliases->new();
$aliases->populate( \%alias );    # add aliases from config files

# change rules to get attributes, not masq
foreach my $k ( keys %rules ) {
	$rules{$k} = $aliases->decode_alias( $rules{$k} );
}

# config changes
my %newmacros;
my %newalias;
my %newrules;
my %newonlydir;

if ($addpath) {

	add_env( 'PATH', \%rules, \%newrules, $aliases );
	if ( is_microsoft() ) {
		add_env( 'systemroot',   \%rules, \%newrules, $aliases );
		add_env( 'ProgramFiles', \%rules, \%newrules, $aliases );
	}
}
if ($addlib) {
	add_env( 'SHLIB_PATH',      \%rules, \%newrules, $aliases );
	add_env( 'LD_LIBRARY_PATH', \%rules, \%newrules, $aliases );

	# save old value if exists
	my $save = $ENV{'AFICKONFIG_TMP'};

	if ( !is_microsoft() ) {

		# add the defaults lib dir /lib and /usr/lib
		$ENV{'AFICKONFIG_TMP'} = '/lib:/usr/lib:/lib64:/usr/lib64';
		add_env( 'AFICKONFIG_TMP', \%rules, \%newrules, $aliases );

		# libs from perl
		$ENV{'AFICKONFIG_TMP'} = ldd();
		add_env( 'AFICKONFIG_TMP', \%rules, \%newrules, $aliases );

		if ( is_linux() ) {

			# add contents of /etc/ld.so.conf config file
			$ENV{'AFICKONFIG_TMP'} = convert_file('/etc/ld.so.conf');
			add_env( 'AFICKONFIG_TMP', \%rules, \%newrules, $aliases );
		}
	}

	# restore value
	$ENV{'AFICKONFIG_TMP'} = $save if ($save);
}
if ($opt_move_database) {
	my $old_database      = $directive{'database'};
	my $new_database_path = 'database';

	if ( $old_database !~ m/^$new_database_path/ ) {
		$newdirectives{'database'} = "$new_database_path/$old_database";
	}
}

# and config changes from program parameters
foreach my $elem (@ARGV) {

	#remove_trailing_spaces( \$elem );

	Afick::Msg->debug("test $elem for type");
	my @ret = ();
	## no critic (ProhibitCascadingIfElse)
	if ( @ret = is_macro_line( $elem, 1 ) ) {

		# macros
		my $key = shift @ret;
		my $val = shift @ret;

		if ( !defined $config->check_macro( $key, $val, 1 ) ) {
			Afick::Msg->warning(
				"skip macro $elem : " . Afick::Msg->get_error() );
		}
		else {
			Afick::Msg->debug("found macro $key : $val");
			$newmacros{$key} = $val;
		}
	}
	elsif ( @ret = is_directive_line( $elem, 1 ) ) {

		# directives
		# another way to set directives
		my $key = shift @ret;
		my $val = shift @ret;

		if ( !defined $config->check_directive( $key, $val, 1 ) ) {
			Afick::Msg->warning(
				"skip directive $elem : " . Afick::Msg->get_error() );
		}
		else {
			Afick::Msg->debug("found directive $key : $val");
			$newdirectives{$key} = $val;
		}
	}
	elsif ( @ret = is_alias_line( $elem, 1 ) ) {

		# alias
		my $key = shift @ret;
		my $val = shift @ret;

		# we do not try to resolv aliases, because it can depends
		# on config file definitions
		if ( !defined $config->check_alias( $val, 1 ) ) {
			Afick::Msg->warning(
				"skip alias $elem : " . Afick::Msg->get_error() );
		}
		else {
			Afick::Msg->debug("found alias $key : $val");
			$newalias{$key} = $val;

			# add in alias list to allow a rule to use it
			$aliases->set_alias( $key, $val ) if ($val);
		}
	}
	elsif ( @ret = is_negsel_line($elem) ) {

		# negative option
		my $key = shift @ret;
		if ( !is_anyfile($key) ) {
			Afick::Msg->warning(
				"skip negsel rule $elem : " . Afick::Msg->get_error() );
		}
		else {

			# see also test in buildligne for 0 value
			Afick::Msg->debug("found negsel $key");
			$newrules{$key} = 0;
		}
	}
	elsif ( @ret = is_equalsel_line($elem) ) {

		# only dir option
		my $name = shift @ret;
		my $attribute = shift @ret || $EMPTY;

		# do not check resolv globbing
		# just check attribute syntax
		if ( !defined $config->check_alias( $attribute, 1 ) ) {
			Afick::Msg->warning(
				"skip equalsel rule $elem : " . Afick::Msg->get_error() );
		}
		else {
			Afick::Msg->debug("found equal rule $name : $attribute");
			$newrules{$name}   = $attribute;
			$newonlydir{$name} = 1;
		}
	}
	elsif ( @ret = is_sel_line( $elem, 0 ) ) {

		# classic selection
		my $name = shift @ret;
		my $attribute = shift @ret || $EMPTY;

		# do not check resolv globbing
		# just check attribute syntax
		if ( !defined $config->check_alias( $attribute, 1 ) ) {
			Afick::Msg->warning(
				"skip rule $elem : " . Afick::Msg->get_error() );
		}
		else {
			Afick::Msg->debug("found rule $name : $attribute");
			$newrules{$name} = $attribute;
		}
	}
	elsif ( @ret = is_sel_line( $elem, 1 ) ) {

		# delete selection
		my $name      = shift @ret;
		my $attribute = shift @ret;

		# do not check resolv globbing
		# just check attribute syntax
		if ( !defined $config->check_alias( $attribute, 1 ) ) {
			Afick::Msg->warning(
				"skip delete rule $elem : " . Afick::Msg->get_error() );
		}
		else {
			Afick::Msg->debug("found delete rule $name");
			$newrules{$name} = $EMPTY;
		}
	}
	else {
		Afick::Msg->warning("unknown element $elem (ignored)");
	}
	## use critic
}

my $return_value;
if ($opt_list) {

	# only display
	display_value( 'directive', \%newdirectives, \%directive );
	display_value( 'macro',     \%newmacros,     \%macros );
	my %aliases = $aliases->aliases();
	display_value( 'alias', \%newalias, \%aliases );
	display_value( 'rule', \%newrules, \%rules, \%onlydir );
	$return_value = 0;
}
else {

	# apply changes
	$return_value = change_config(
		$configfile, \%newdirectives, \%newmacros,
		\%newalias,  \%newrules,      \%newonlydir
	);

	# control after the changes
	my $config2 = Afick::Cfg->new($configfile);
	$config2->read_configuration(0);
}

exit $return_value;

__END__

=head1 NAME

afickonfig - a tool to manage Afick's config files

=head1 DESCRIPTION

C<afickonfig> is to change parameters in afick's config file, in a batch way.
It can add, replace, remove any components (macro, alias, directives, rules)
It was designed to work with same options names as afick (directives).

Note : in the current version, it can checks some arguments syntax before applying,

The idea came from the "postconf" utility from postfix.

=head1 SYNOPSIS

afickonfig.pl  [L<options|/OPTIONS>] [L<action|/ACTIONS>] [L<macros|/MACROS>] [L<alias|/ALIAS>] [L<directives|/DIRECTIVES>] [L<rules|/RULES>]

afick use posix syntax, which allow many possibilities :

=over 4

=item *
long (--) options

=item *
short (-) options

=item *
negative (--no) options

=back

=head1 REQUIRED ARGUMENTS

if run without arguments, afickonfig will just check the
default config file

=head1 OPTIONS

options are used to control afickconfig

=over 4

=item B<--config_file|-c configfile>

read the configuration in config file named "configfile".

=item B<--check_config|-C>

only check config file syntax and exit with the number of errors

=item B<--clean_config|-G>

check config file syntax, clean bad line, and exit with the number of errors

=item B<--help|-?>

Output summary help information and exit.


=item B<--list|-l>

display parameter value, do not change it

=item B<--man>

Output full help information and exit.

=item B<--print_config>

display internals variables after arguments and config file parsing (for debugging purposes)

=item B<--version|-V>

Output version information and exit.

=item B<--verbose|-v>

add debugging messages

=item B<--addpath>

add PATH directories to rules

=item B<--addlib>

add libraries directories to rules (linux)

=item B<--move_database>

change database path in config file to new "standard" (windows)

=back

=head1 ACTIONS

actions are used to change afick's configuration

=over 4

=item B<--allow_overload,(--noallow_overload)>

if set, allow rule overload (the last rule wins), else put a warning and keep the first rule.

=item B<--allow_relativepath,(--noallow_relativepath)>

if set, auto-control files (afick scripts, config and database) are stored as relative path.

=item B<--archive|-A directory>

write reports to "directory".

=item B<--database|-D name>

select the database named "name".

=item B<--debug|-d level>

set a level of debugging messages, from 0 (none) to 3 (full)

=item B<--report_full_newdel|-f,(--noreport_full_newdel)>

(do not) report full information on new and deleted directories.

=item B<--history|-y historyfile>

write session status to history file

=item B<--ignore_case|-a>

ignore case for file names. Can be helpful on Windows platforms, but is dangerous on Unix ones.

=item B<--warn_missing_file|-m,(--nowarn_missing_file)>

(do not) warn about files declared in config files which does not exist.

=item B<--max_checksum_size|-S size>

fix a maximum size (bytes) for checksum. on bigger files, compute checksum only on first 'size' bytes.
(0 means no limit)

=item B<--warn_dead_symlinks|-s,(--nowarn_dead_symlinks)>

(do not) warn about dead symlinks.

=item B<--follow_symlinks,(--nofollow_symlinks)>

if set, do checksum on target file, else do checksum on target file name.

=item B<--running_files|-r,(--norunning_files)>

(do not) warn about "running" files : modified since program begin.

=item B<--timing|-t,(--notiming)>

(do not) Print timing statistics.

=item B<--report_syslog,(--noreport_syslog)>

If true, send also the report to syslog

=item B<--report_summary,(--noreport_summary)>

If true, report in the summary section, one ligne by file change

=item B<--report_context,(--noreport_context)>

If true, display all attributes changes, not only those selected by rule.
To make a difference, attributes from rules will have a "w_" prefix (warning),
and other attributes will have a 'i_' prefix (info).

=item B<--exclude_suffix|-x "ext1 ext2 ... extn">

list of suffixes (files/directories ending in .ext1 or .ext2 ...) to ignore.

=item B<--exclude_prefix|-X "pre1 pre2 ... pren">

list of prefix (files/directories beginning with pre1 or pre2 ...) to ignore.

=item B<--exclude_re|-R "pat1 pat2 ... patn">

list of patterns (regular expressions) to ignore files or directories

=back

=head1 MACROS

macros are to be set in afick configuration format (see afick.conf(5)) : C< '@@define macro value'>

=head1 ALIAS

aliases are to be set in afick configuration format (see afick.conf(5)) : C<'newrule = attributes'>

=head1 DIRECTIVES

directives are to be set in afick configuration format (see afick.conf(5)) : C<'directive := value'>

=head1 RULES

rules are to be set in afick configuration format (see afick.conf(5)) : C<'file alias'>

=head1 FILES

if no config file on command line, afick try to open F</etc/afick.conf> (Unix) or F<windows.conf> (Windows) as
default config

for config file syntax see afick.conf(5)

=head1 USE

afickonfig may

=over 4

=item change a config

if it can find an old config

=item add a config

if it does not find a previous value

=item remove a config

you just have to specify a parameter without any value. the old line is commented

=back

=head1 USAGE

To use this program, you can run it with same afick command line options :

C<afickonfig.pl -c afick.conf --timing --norunnig_files --debug=1 --archive=afick_archive>

or the same command in configuration file syntax :

C<afickonfig.pl -c afick.conf 'timing := 1' 'running_files := no' 'debug:=1' 'archive:=afick_archive'>

or a mix of all syntaxes

C<afickonfig.pl -c afick.conf --timing 'debug:=1' '@@define BATCH 0' 'newrule = p+u+g' '/tmp newrule'>

to remove lines, give an empty value

C<afickonfig -c afick.conf 'debug:=' '@@define BATCH' 'newrule=' '/tmp'>

to display a value, use --list

C<afickonfig.pl -c afick.conf --list 'timing:=' 'database:='>

=head1 NOTES

this program only use perl and its standard modules.

=head1 SEE ALSO

=for html
<a href="afick.conf.5.html">afick.conf(5)</a> for the configuration file syntax
<br>
<a href="afick-tk.1.html">afick-tk(1)</a> for the graphical interface
<br>
<a href="afick.1.html">afick(1)</a> for the command-line interface
<br>
<a href="afickonfig.1.html">afickonfig(1)</a> for a tool to change afick's configuration file
<br>
<a href="afick_archive.1.html">afick_archive(1)</a> for a tool to manage archive's reports
<br>
<a href="afick_learn.1.html">afick_learn(1)</a> for a learning tool

=for man
\fIafick.conf\fR\|(5) for the configuration file syntaxe
.PP
\fIafick\-tk\fR\|(1) for the graphical interface
.PP
\fIafick\fR\|(1) for the command-line interface
.PP
\fIafickonfig\fR\|(1) for a tool to change afick's configuration file
.PP
\fIafick_archive\fR\|(1) for a tool to manage archive's reports
.PP
\fIafick_learn\fR\|(1) for a learning tool

=head1 DIAGNOSTICS

all warnings go to stderr

=head1 EXIT STATUS

The exit status is the number of real changes

=head1 CONFIGURATION

no specific configuration file

=head1 DEPENDENCIES

perl

=head1 INCOMPATIBILITIES

(none)

=head1 BUGS AND LIMITATIONS

(none known)

=head1 LICENSE AND COPYRIGHT

Copyright (c) 2002 Eric Gerbier
All rights reserved.

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.

=head1 AUTHOR

Eric Gerbier

you can report any bug or suggest to eric.gerbier@tutanota.com
