#!/usr/bin/perl
# $Id: cce_construct
# Copyright 2001 Sun Microsystems, Inc.  All rights reserved.
#
# Copyright(c) 2000 Cobalt Networks, Inc.
# Author: Adrian Sun (asun@cobalt.com)
#
# the much simpler sysreset
# it's like the old one, but it makes cce do all of the hard work.
#
# syntax: $0 <type>
# where type can be one of
#   sysinit
#   sysreset
#   [nothing]
#
# 2026-11-01: Per-constructor wall-clock timeout + always clear lockfile.
# Without this, a dead cced leaves a constructor blocked on the Unix socket
# forever, cce_construct.lock held, swatch skips forever, AM never updates.
# Reported and diagnosed by Greg Kuhnert.

use strict;
use warnings;

my $consdir = '/usr/sausalito/constructor';
my $destroydir = '/usr/sausalito/destructor';
my $preferdir = 'base';
my $lockfile = '/var/lock/subsys/cce_construct.lock';

# Seconds per constructor script (override with CCE_CONSTRUCT_TIMEOUT).
# Long scripts (e.g. email sync) need headroom; dead-socket hangs were hours.
my $CONSTRUCT_TIMEOUT = $ENV{CCE_CONSTRUCT_TIMEOUT} || 300;
my $TIMEOUT_BIN = '/usr/bin/timeout';

# Do not put SO_RCVTIMEO on constructor CCE clients by default (Greg Kuhnert):
# readline+EAGAIN looks like EOF. The wall clock below bounds wedged scripts.
$ENV{CCE_IO_TIMEOUT} = '0' unless exists $ENV{CCE_IO_TIMEOUT};


system('/usr/bin/logger', 'cce_construct started');

# Always drop the lock on exit (normal, die, signal) so swatch cannot wedge.
sub clear_lock {
    unlink $lockfile if defined $lockfile && -e $lockfile;
}
END { clear_lock(); }
$SIG{TERM} = $SIG{INT} = $SIG{HUP} = sub {
    system('/usr/bin/logger', 'cce_construct: caught signal, clearing lock');
    clear_lock();
    exit 143;
};

sub get_files
{
    my ($dir, $allowpat, $excludepat) = @_;
    my $name;
    my (%hash, %dirhash);
    local *DIR;

    if (opendir(DIR, $dir)) {
        while ($_ = readdir(DIR)) {
            next if /^\./; # always ignore stuff starting with .
            $name = "$dir/$_";
            if (-d $name) {
                %dirhash = get_files($name, $allowpat, $excludepat);
                foreach $name (keys %dirhash) {
                    $hash{$name} = $dirhash{$name};
                }
            } else {
                next if $allowpat and not /^$allowpat/;
                next if $excludepat and /^$excludepat/;
                $hash{"$_$dir"} = $name;
            }
        }
        closedir(DIR);
    }
    return %hash;
}

# Run one constructor with a hard wall-clock limit so a dead cced cannot
# block forever (unix_stream_data_wait on dead socket).
sub run_constructor {
    my ($script) = @_;
    return 1 unless defined $script && length $script && -f $script;

    my $timeout = $CONSTRUCT_TIMEOUT;
    # Email sync and similar can be slow on first boot
    if ($script =~ m{/email/syncEmailService\.pl$}) {
        $timeout = $ENV{CCE_CONSTRUCT_TIMEOUT_LONG} || ($CONSTRUCT_TIMEOUT * 2);
    }

    my $rc;
    if (-x $TIMEOUT_BIN) {
        # TERM first, then KILL if still stuck (dead-socket read)
        $rc = system($TIMEOUT_BIN, '--signal=TERM', '--kill-after=15',
            "$timeout", $script);
    } else {
        $rc = system($script);
    }

    my $exit = $? >> 8;
    my $sig  = $? & 127;
    if ($exit == 124) {
        system('/usr/bin/logger',
            "***** cce_construct: TIMEOUT (${timeout}s): $script (Greg Kuhnert: dead cced must not wedge forever)");
        print STDERR "   TIMEOUT after ${timeout}s: $script\n";
        return 124;
    }
    if ($sig) {
        system('/usr/bin/logger',
            "***** cce_construct: killed by signal $sig: $script");
        return 128 + $sig;
    }
    if ($exit != 0) {
        system('/usr/bin/logger',
            "***** cce_construct: exit $exit: $script");
    }
    return $exit;
}

# Create lockfile:
system('mkdir', '-p', '/var/lock/subsys');
system('touch', $lockfile);

my %hash;
my ($key, $allowpat, $excludepat);

while (@ARGV && $ARGV[0] =~ /^\-/) {
    if ($ARGV[0] eq '-f') {
        print "i'm pretending to reset the machine to factory defaults.\n";
    }
    shift @ARGV;
}

if ($ARGV[0]) {
    $allowpat = ':' . $ARGV[0] . ':';
} else {
    $excludepat = ':';
}

# Really, CentOS? I think you forgot something!
if ( ! -d '/run/php-fpm') {
    system('mkdir', '-p', '/run/php-fpm');
}

# We should run the Constructor that creates the 'System' Object first of all.
# Too much stuff depends on the 'System' Object being there.
if (-f '/usr/sausalito/constructor/base/system/10_addSystem.pl') {
    print("   /usr/sausalito/constructor/base/system/10_addSystem.pl\n");
    system('/usr/bin/logger', '***** cce_construct: /usr/sausalito/constructor/base/system/10_addSystem.pl');
    run_constructor('/usr/sausalito/constructor/base/system/10_addSystem.pl');
}

# running constructors
print "----- constructing -----\n";
# run the base one first
%hash = get_files("$consdir/$preferdir", $allowpat, $excludepat);
if (%hash) {
    foreach $key (sort keys %hash) {
        # already ran 10_addSystem.pl first
        next if $hash{$key} eq '/usr/sausalito/constructor/base/system/10_addSystem.pl';
        print("   $hash{$key}\n");
        system('/usr/bin/logger', "***** cce_construct: $hash{$key}");
        run_constructor($hash{$key});
    }
}

# now, collate the rest of the directories and run
# them in alphabetical order.

my (@dirs, $dir);
if (opendir(my $PDIR, $consdir)) {
    while ($_ = readdir($PDIR)) {
        next if /^\./;
        next if /^$preferdir$/;
        push @dirs, $_;
    }
    closedir($PDIR);
}

foreach $dir (sort @dirs) {
    %hash = get_files("$consdir/$dir", $allowpat, $excludepat);
    if (%hash) {
        foreach $key (sort keys %hash) {
            print("   $hash{$key}\n");
            system('/usr/bin/logger', "*** cce_construct: $hash{$key}");
            run_constructor($hash{$key});
        }
    }
}
system('/usr/bin/logger', '***** cce_construct finished');
clear_lock();
print "\n----- done -----\n";

exit 0;
