#!/usr/bin/perl -Tw
# #
# Software subject to following license(s):
#   Apache 2 License (http://www.opensource.org/licenses/apache2.0)
#   Copyright (c) Responsible Organization
#

# #
# Current developer(s):
#   Luis Fernando Muñoz Mejías <Luis.Munoz@UGent.be>
#   Michel Jouvin <jouvin@lal.in2p3.fr>
#   Nick Williams <nick.williams@morganstanley.com>
#

# #
# Author(s): Germán Cancio Meliá, Marco Emilio Poleggi
#

# #
# ncm-cdispd, 26.2.0, 1, Tue Apr 07 2026
#

=pod

=head1 NAME

cdispd - Configuration Dispatch Daemon

=head1 SYNOPSIS

cdispd [--interval secs] [--ncd-retries n] [--ncd-timeout n]
[--ncd-useprofile profile_id] [--noaction] [--cfgfile <file>]
[--logfile <file>] [--help] [--version] [--verbose]
[--debug <level>] [--quiet | -D] [--pidfile <file>]

=head1 DESCRIPTION

The Configuration Dispatch Daemon waits for new incoming configuration
profiles by monitoring the CCM cache. In case of changes with respect to
the previous profile, cdispd will invoke the affected components via
the 'ncd' program.

A component's configuration declares which subtrees of the node
configuration profile it is interested in (see below). If the
configuration information in or below anyone of these subtrees
changes/appears/disappears, then cdispd will add the component to the
list of components to be invoked (except when the component itself is
removed). The check for changed configurations is done using the
element checksum provided by the CCM.

There is a /software/components/<componentX>/register_change subtree,
where one can set a list of all configuration paths on which the
ncm-cdispd should trigger the execution of <componentX>.

By default a component gets registered in its configuration, and in
its software package.

The dispatch of a component can be skipped by setting the 'dispatch'
flag to false

 "/software/components/spma/dispatch" = false;

If /software/components is not defined, cdispd logs a error message
and continues to run.

In order to ensure consistency, the following rules apply:

=over 4

=item *

If NCM execution (ncm-ncd) fails, cdispd will not update the reference
configuration. This means that subsequent profile changes will
be compared to the profile as it was when NCM failed. In such a way,
failed components are not "forgotten" about (unless they are
deactivated in the new profile).

=item *

In the particular case that the last NCM run reported errors
AND no NCM relevant changes between the reference configuration
and a new profile are detected, ncm-ncd is invoked for all active
components.

=back

On startup, ncm-cdispd will make an initial run of all
components where the 'dispatch' flag is set to 'true'.

=head1 EXAMPLE

Take as example the NCM configuration component for the SPMA:

 #
 # run SPMA comp if anything below /software/packages has changed
 # (eg. new packages)
 #
 "/software/components/spma/register_change/1" = "/software/packages";


=head1 OPTIONS

=over 4

=item --state path

Path for a directory in which to record state.  If no state path is
specified (the default), then no state is maintained.

If a state directory is provided, then ncm-cdispd will touch
(i.e. create and/or update modified time) files corresponding to
component names within that directory. These files will be updated
whenever it is determined that a component needs configuration. Note
that the 'dispatch' flag is not referred to when creating these state
files, only the 'active' flag is observed. If a component becomes
inactive, then any state file for that component will be removed. See
the corresponding "state" option within ncm-ncd.

=item --interval secs

Check for new profiles every 'secs' seconds.

=item --cache_root path

Path for the cache root directory. If no path is specified, the
default cache root directory (provided by CCM) will be used.

=item --ncd-retries n

This option will be passed to B<ncd>: try 'n' times if locked
(a value of 0 means infinite).

=item --ncd-timeout n

This option will be passed to B<ncd>: wait 'n' seconds between
retries.

=item --ncd-useprofile profile_id

This option will be passed to B<ncd>: use 'profile_id' as
configuration profile ID (default: latest)

=item --noautoregcomp

Do not automatically register the path of the component.

=item --noautoregpkg

Do not automatically register the path of component package.

=item --noaction

Compute and show the operations, but do not execute them.

=item --cfgfile <file>

Use <file> for storing default options.

=item --logfile <file>

Store and append B<cdispd> logs in <file>. The default is
/var/log/ncm-cdispd.log

=item --D

Becomes a daemon and suppress application output to standard output.

=back

=head2 Other Options

=over

=item --help

Displays a help message with all options and default settings.

=item --version

Displays cdispd version information.

=item --verbose

Print verbose details on operations.

=item --debug <1..5>

Set the debugging level to <1..5>.

=item --facility <f>

Set the syslog facility to <f> (Eg. local1).

=item --quiet

Becomes a daemon, and suppress application output to standard output
(this option is equivalent to --D option).

=back

=head1 CONFIGURATION FILE

A configuration file can keep site-wide configuration settings. The
location of the configuration file is defined in the --cfgfile
option. A default configuration file is found in /etc/ncm-cdispd.conf.

=head1 SIGNAL HANDLING

ncm-cdispd handles four signals: B<INT>, B<TERM>, B<QUIT> and B<HUP>.
All signals except B<HUP> cause ncm-cdispd to exit. B<HUP> causes
ncm-cdispd to reinitialize (configuration reset to values in the configuration
file and command line options ignored, reference profile set to last
profile available).

B<HUP> and B<TERM> signals are not processed immediately if they occur when `ncm-ncd`
is run but they are remembered and the relevant action is done at the end of
ncm-ncd run. At any other moment, they are processed immediately.

The delayed processing of `TERM` may lead to a suprising situation if you do a
restart (start + stop) when ncm-ncd is running: the new ncm-cdispd process is started
before the current one is really stopped and you end up with 2 (or even more
depending on the number of restart you did!) ncm-cdispd process. In fact this is harmless because
ncm-ncd creates a lock that forbid new processes to actually do anything until the original
one has exited, at the end of `ncm-ncd`.

=cut


#############################################################
# cdispd program and its functions
#
# Note about debug level used:
#    - level 1: main loop and initialization
#    - level 2: compare_profiles() and add_component()
#    - level 3: utility functions used to compare profiles
#               (very verbose!)
#############################################################

use strict;
use warnings;

BEGIN {
    # use perl libs in /usr/lib/perl
    unshift(@INC, '/usr/lib/perl' );
}

our $this_app;

use CDISPD::Main qw(main);

main();

exit(0);
