#!/usr/bin/perl
use strict;
use warnings;
use v5.10;
use utf8;										# le source est encodé en UTF-8 (littéraux décodés en interne)
use open qw(:encoding(UTF-8));					# tous les open/pipes/backticks en UTF-8 : la lecture des
												# sorties de commandes et l'écriture des fichiers/logs sont
												# cohérentes (un octet non-UTF-8 en entrée est échappé en \xNN
												# avec warning, sans corruption)
use Encode ();
use Text::ParseWords ();	# découpage shell-like des chaînes de commande (module core)

# Détection (Linux, via /proc/self/fd) d'une redirection de STDOUT vers un
# fichier réel, par opposition à un terminal ou un pipe. Sert ci-dessous à
# choisir un charset de sortie par défaut cohérent avec les consommateurs
# historiques qui capturent STDOUT dans un fichier (ex: Server.pm ->
# $UPDATE_CACHE.tmp -> autoupdate.error), qui attendent du Latin-1.
sub _stdout_is_real_file {
	return 0 if (-t STDOUT);
	my $target = readlink("/proc/self/fd/1");
	return 0 unless (defined $target);
	return 0 if ($target =~ m{^(?:pipe|socket):});
	return 1;
}

# Charset des sorties STDOUT/STDERR sélectionnable par l'appelant via KWARTZ_CHARSET,
# pour les consommateurs restés en Latin-1 (ex : la console web / autoupdate.cgi).
# Si KWARTZ_CHARSET n'est pas positionné : UTF-8 par défaut, sauf si STDOUT est
# redirigé vers un fichier réel (cf _stdout_is_real_file), auquel cas on bascule
# en Latin-1 pour rester compatible avec ces consommateurs historiques.
# Dans tous les cas, un caractère non représentable est échappé (pas de troncature).
my $gOutCharset = $ENV{'KWARTZ_CHARSET'};
if (!defined $gOutCharset) {
	$gOutCharset = _stdout_is_real_file() ? 'ISO-8859-1' : 'UTF-8';
}
$gOutCharset = 'UTF-8' unless (Encode::resolve_alias($gOutCharset));
binmode(STDIN,  ':encoding(UTF-8)');
binmode(STDOUT, ":encoding($gOutCharset)");
binmode(STDERR, ":encoding($gOutCharset)");

use LWP::Simple qw(!head);
use File::Basename;
use IO::Uncompress::Gunzip qw(gunzip $GunzipError);	# repli Packages.gz du renew de secours (module core)
use File::Copy;
use File::stat;
use Fcntl qw(:flock :mode O_CREAT O_RDWR O_EXCL SEEK_SET);
use Getopt::Std;
use Encode qw/from_to/;
use POSIX qw(setsid WNOHANG sysconf _SC_CLK_TCK);
use File::Pid;
use Data::Dumper;
use Cwd qw(getcwd);

use vars qw($opt_r $opt_i $opt_u $opt_k $opt_S $opt_d $opt_v $opt_p $opt_a $opt_s $opt_g $opt_G $opt_I $opt_P $opt_q $opt_U $opt_D $opt_C $opt_o $opt_F);

{
	# Bug perl #11541 (corrigé dans les perls récents) : sur les vieux perls (ex. Kwartz5),
	# require lit le fichier à travers les couches par défaut de « use open ». sharelib.pl
	# étant en Latin-1, sa lecture en UTF-8 génère des « does not map to Unicode » et
	# corrompt ses littéraux. On surcharge lexicalement la pragma en :raw pour ce require.
	use open IO => ':raw';
	require '/usr/lib/kwartz/control/sharelib.pl';		#load common fonctions and defintion
}

# libenum-perl n'est pas présent sur Kwart, il faut faire sans
##
use constant {
	RET_OK 				=>  0, # OK
	RET_ERROR			=>  1, # Erreur diverse
	RET_VARSIZE			=>  2, # /var trop petit
	RET_HOMESIZE		=>  3, # /home trop petit
	RET_OLDINSTALL		=>  4, # Erreur ancienne installation
	RET_INSTALLPENDING	=>  5, # Installation en cours
	RET_NOKEY			=>  6, # Pas de clé
	RET_CLUSTER			=>  7, # Mode cluster (obsolète)
	RET_VALIDATE		=>  8, # Echec validate()
	RET_APTUPDATE		=>  9, # Echec fsm AptUpdate
	RET_RENEWOK			=> 10, # Renew OK
	RET_RENEWKO			=> 11, # Echec renew
	RET_DAEMON			=> 12, # Exit from daemonize
	RET_DAEMONEND		=> 13, # daemon ended
	RET_ROOTSIZE		=> 14, # / trop petit
	RET_PRE_UPDATEVERSION	=> 20,  # Echec de préparation mise à jour iso
	RET_UPDATEVERSION	=> 30  # Echec de mise à jour iso
};

my $VARLOGFILE	= "/var/log/kwartz-update.log";
my $LOGFILE		= "/root/kwartz-configuration.log";
my $STATEFILE	= "/var/cache/kwartz/kwartz-autoupdate.state";	# Conservation de l'état de la FSM pour interrogation
my $RUNDIR		= "/run/kwartz-autoupdate";						# Répertoire de travail temporaire
my $SOURCE		= "/etc/kwartz/sources.list.d/kwartz.list";		# sources.list dépôt kwartz
my $SOURCEREDIS	= "/etc/kwartz/sources.list.d/redis.list";		# sources.list dépôt redis
my $SOURCESAMBA416 = "/etc/kwartz/sources.list.d/samba416corpit.list";	# sources.list dépôt corpit samba 4.16
my $SOURCESAMBA4 = "/etc/kwartz/sources.list.d/samba4corpit.list";	# sources.list dépôt corpit samba4
my $SOURCECORRIUM  = "/etc/kwartz/sources.list.d/corrium.list";			# sources.list dépôt corrium
my $SOURCE_S	= "/etc/kwartz/sources_s.list";					# sources.list dépôts ubuntu principal et sécurité
my $SOURCE_RENEW = "/etc/kwartz/kwartz_renew.list";				# sources dédié renew, SANS auth, HORS sources.list.d/ (non ramassé par Dir::Etc::SourceParts)
my $UPDATE_CACHE ="/var/cache/kwartz/autoupdate";				# Cache de résultat
my $SEM 		= "/tmp/".basename($0).".lck";					# Sémaphore de protection logfile en réentrance

my $ver;						# Version du Kwartz X.YrZ
my $type;						# Produit : Server, KmcBox...
my $autoupdate;					# répertoire: autoupdatek, autoupdate5...
my ($licence, $key);			# Num licence et clé produit
my ($upd_user, $upd_pass);		# Renvoyé par validate, par défaut (licence,clé)
my ($krel, $kver);				# kernel release (5.8.0-53-generic) / kernel ver (#60~20.04.1-Ubuntu SMP Thu May 6 09:52:46 UTC 2021)
my $debname;					# Non distrib : lenny, precise, xenial, focal...
my @updateduri=();				# URI utilisées dans les sources.list
my $restricted = "";			# deprecated ?
my $gres = RET_OK;				# Retour du script	
my $gIso = "";					# Image iso pour mise à jour distrib
my $gStateHandler = undef;		# Registre d'état de la FSM
my @callerchain = ();			# Chemin d'appel à partir d'init
my %aptsyntax;					# Handle apt deprecations
my $reentrancy = 0;				# We are called again
###

# acquire_lock
# Paramètres :
#  semaphore
#  timeoutms
#  intervalms
# Valeur de retour :
#  0 $fh: Lock OK
#  1 : timeout
#  2 : Erreur
# Usage :
#  my ($res, $handle) = acquire_lock ($SEM, 600000, 100);
sub acquire_lock {
	my ($semaphore, $timeoutms, $intervalms) = @_;
	open (my $fh, ">", $semaphore) or return 2;
	my $count = $timeoutms/$intervalms + 1;
	while ($count--) {
		return (0, $fh) if (flock($fh, LOCK_EX|LOCK_NB));
		select(undef, undef, undef, $intervalms/1000.0);
	}
    return 1;
}

# release_lock
# Paramètres :
#  $fh
# Usage :
#  release_lock ($handle);
sub release_lock {
	my ($fh) = @_;
	flock($fh, LOCK_UN) if (defined ($fh));
}


# myexitlog("comment stdout", "comment logfile", value);
# exit avec log dans le logfile et sortie sur STDOUT
# Impose un affichage sur STDOUT si valeur de sortie != 0 et $opt_v, sauf si on passe chaine vide
# (Impose de réécrire les myexitlog avec les strings de sortie en premier paramètre)
##
sub myexitlog
{
	my ($output, $comment, $exitval) = @_;
	
	if (defined($output)) {
		print "$output\n" if ($output ne "");
	} elsif (defined($opt_v) && ($exitval != 0)) {	# Force un affichage générique si sortie en erreur
		print "erreur\n";
	}
	
	unless (($reentrancy gt 0) ||
			defined($opt_C) ||
			($exitval == RET_INSTALLPENDING)
		) {
		# copy log data into rotating log
		my ($res1, $handle1) = acquire_lock ($SEM, 1000, 10); # 1 seconde
		if ( -f $VARLOGFILE ) {
			my $size = -s $VARLOGFILE;
			if ($size > 0) {
				mklogfile() unless (-l $LOGFILE);
				mysystem("cat $VARLOGFILE >> $LOGFILE");
				unlink "$VARLOGFILE";
			}
		}
		release_lock($handle1);
	}
	
	# Print comment into rotating log (link)
	if (defined($comment)) {
		if ( open (my $fh, ">>", $LOGFILE) ) {
			my $dateexit = `env LANG=fr_FR.UTF-8 date`; chomp ($dateexit);
			print $fh "$dateexit : $comment (caller: $callerchain[0]->{'cmndline'}) (sortie: $exitval)\n";
			close ($fh);
		}
	
		#TODO : On ne ferait pas systématiquement une sortie sur stderr ???
		print STDERR "$comment\n" if ($exitval != 0 and ($opt_v or $opt_U));
		open (my $resfh, ">", "$RUNDIR/$$.res");
		print $resfh "$comment";
		close $resfh;
	}
	
	# Cleanup progress file
	if (defined ($gStateHandler)) {
		close $gStateHandler;
		unlink("$RUNDIR/$$.progress");
	}
	my $pidfile = File::Pid->new({file => "$RUNDIR/".basename($0).".pid"});
	$pidfile->remove if ($$ == $pidfile->pid);
	exit $exitval;
}



sub mklogfile
{
    my $log_rep = "/root/kwartz";
    my $date = `env LANG=C date '+%Y%m%d'`; chomp ($date);

    mkdir ($log_rep) if ( ! -d "$log_rep" );
    if (( -f $LOGFILE ) && (! -l $LOGFILE)) { # Uniquement à l'installation
		system ("mv $LOGFILE $log_rep/.");
		symlink ("$log_rep/kwartz-configuration.log", $LOGFILE);
    }

	my $name = "$log_rep/kwartz-configuration.log.$date";
	for (my $loop=0; ( -f "$name" ); $loop++ ) {
		$name = "$log_rep/kwartz-configuration.log.$date-$loop";
    }
    if ( ! open (my $fh, ">", $name)) {
	    warn "cannot touch $name: $!";
		return 1;
	} else {
		print $fh "Kwartz $type $ver\n";
		print $fh "kwartz-autoupdate ";
		print $fh `dpkg-query --showformat=\'\${Version}\' --show kwartz-autoupdate`;
		print $fh "\n";
		print $fh `env LANG=fr_FR.UTF-8 date`;
		close ($fh);
		unlink ($LOGFILE) if ( -l $LOGFILE );
		symlink ($name, $LOGFILE);
	}
	return 0;
}


##  Various exec commands
# mysystem ($command)
#	input : command
#	return value : $?
#
# myback ($command)
#	input : command
#	return value : @result
#
# mypopenprint ($command, \@answers)
#	input : command
#	output (if defined) : @result
#	return value : $?
#
#	$? :	($? & 127) : value of signal when command died
#			($? & 128) : coredumped 
#			command_result<<8
##

#use enum qw(BITMASK:LOG_ STDOUT STDERR LOGFILE);
use constant {
    LOG_STDOUT          =>  1,
    LOG_STDERR          =>  2,
    LOG_LOGFILE         =>  4,
};

# _ExecLog
# Exécute une commande shell et diffuse sa sortie ligne à ligne, en direct,
# vers une ou plusieurs destinations (console, fichier de log, tableau).
# C'est la primitive d'exécution derrière mypopenprint() et UpdateExecLog().
# Paramètres :
#  $command : commande à exécuter, sous deux formes :
#             - chaîne : passée AU SHELL (comportement historique) ;
#             - référence de tableau : exécution directe SANS shell (forme liste,
#               aucune injection possible).
#             Seul stdout est capturé par le pipe : en forme chaîne, ajouter
#             " 2>&1" à la commande pour capturer stderr.
#  %opts    :
#   flags     : masque de bits LOG_STDOUT|LOG_STDERR = destinations console où
#               recopier chaque ligne (défaut LOG_STDOUT ; 0 = silencieux).
#               Conditionne aussi l'écho de $command en mode debug ($opt_d).
#   logfile   : chemin d'un fichier de log ouvert en AJOUT (>>). Utilisé par le
#               mode CGI : DoWaitPid suit ce fichier pour la progression.
#               Si l'ouverture échoue, le logfile est ignoré silencieusement
#               (la commande s'exécute et les autres destinations fonctionnent).
#   autoflush : 1 (défaut) = débufferise $fh et les sorties sélectionnées par
#               flags, pour un suivi en temps réel ; 0 = buffering normal.
#   trail     : 1 = chomp chaque ligne AVANT son ajout dans resmsgref (l'écho
#               console/fichier garde, lui, la fin de ligne) ; 0 (défaut) = brute.
#   resmsgref : référence de tableau où accumuler chaque ligne lue.
#   echo_cmd  : 1 = en mode debug, écho de la commande sur STDOUT même si
#               flags ne comporte pas LOG_STDOUT (utilisé par myback).
#   merge_stderr : 1 = (forme liste uniquement) fusionne stderr du fils dans
#               stdout avant capture, comme « 2>&1 » mais sans shell. Ignoré
#               en forme chaîne (y ajouter « 2>&1 » directement si besoin).
# Valeur de retour :
#  $? brut de la commande (statut 16 bits, cf. perlvar) : 0 = succès,
#  sinon code_de_sortie << 8 | signal. Les appelants font ($ret >> 8) au besoin.
# Notes :
#  - le pipe de lecture est décodé en UTF-8 (pragma « use open » du fichier) ;
#    les sorties encodent selon le charset actif (cf. KWARTZ_CHARSET).
#  - handle de pipe lexical : la fonction est réentrante (un myback() peut par
#    exemple être appelé pendant qu'un autre _ExecLog est en cours).
#  - le die sur l'open du pipe ne se déclenche qu'en cas d'échec du fork ;
#    une commande introuvable se signale via $? à la fermeture, pas ici.
#  - l'écho debug de $command masque les identifiants d'URL (//user:pass@),
#    comme mysystem(). myback() est bâtie sur cette fonction.
# Usage :
#  my $ret = _ExecLog("apt-get update 2>&1", flags=>LOG_STDERR, resmsgref=>\@out);
sub _ExecLog
{
	my $command = shift;
	my %opts = (
		flags => LOG_STDOUT,
		logfile => '',
		autoflush => 1,
		trail => 0,
		resmsgref => undef,
		echo_cmd => 0,
		merge_stderr => 0,
		@_);
	my $resmsgref = $opts{'resmsgref'};
	my $fh = undef;
	# mode appel cgi pour waitpid
	if (length($opts{'logfile'} // '') > 0) {
		open ($fh, ">>", $opts{'logfile'}) or do {
			# sur échec, $fh resterait défini (glob autovivifié) et le garde defined($fh) ne protégerait pas
			print STDERR "logfile inaccessible: ".$opts{'logfile'}." ($!)\n" if ($opt_d);
			undef $fh;
		};
	}
	# Turn on autoflush
	if ($opts{'autoflush'} == 1) {
		select((select($fh), $|=1)[0]) if (defined($fh));
		select((select(STDOUT), $|=1)[0]) if ($opts{'flags'} & LOG_STDOUT);
		select((select(STDERR), $|=1)[0]) if ($opts{'flags'} & LOG_STDERR);
	}

	# Print command if debug (identifiants d'URL masqués, comme mysystem)
	if ($opt_d) {
		(my $disp = ref($command) eq 'ARRAY' ? join(" ", @$command) : $command) =~ s{//[^/@\s]+@}{//***@}g;
		print STDOUT "$disp\n" if (($opts{'flags'} & LOG_STDOUT) || $opts{'echo_cmd'});
		print STDERR "$disp\n" if ($opts{'flags'} & LOG_STDERR);
	}

	my $cmd_fh;
	if (ref($command) eq 'ARRAY') {
		if ($opts{'merge_stderr'}) {
			# Équivalent de « 2>&1 » SANS shell : on fork, on redirige stderr
			# vers stdout dans le fils, puis exec en liste. Le pipe reçoit les
			# deux flux fusionnés, dans l'ordre d'émission.
			my $pid = open ($cmd_fh, "-|");
			die "fork impossible" unless (defined $pid);
			if ($pid == 0) {
				open (STDERR, ">&", \*STDOUT) or die;
				exec (@$command) or die;	# n'échoue que si exec lui-même échoue
			}
		} else {
			# forme liste : exécution directe sans shell (aucune injection possible)
			open ($cmd_fh, "-|", @$command) or die;
		}
	} else {
		open ($cmd_fh, "$command |") or die;
	}
	# Le pipe peut émettre du Latin-1 (commandes en locale fr : \xA0, \xE9...) ou de
	# l'UTF-8. On lit en octets bruts (:raw) pour ne jamais planter sur un décodage
	# UTF-8 strict, puis on décode chaque ligne de façon tolérante : UTF-8 d'abord,
	# repli Latin-1 si des octets ne mappent pas. Les consommateurs reçoivent ainsi
	# des chaînes de caractères propres, réémises correctement selon le charset actif.
	binmode ($cmd_fh, ':raw');
	while (my $line = <$cmd_fh>) {
		my $dec = Encode::decode('UTF-8', $line, Encode::FB_DEFAULT);
		$dec = Encode::decode('ISO-8859-1', $line) if ($dec =~ /\x{FFFD}/);
		$_ = $dec;
		print STDERR $_ if ($opts{'flags'} & LOG_STDERR);
		print STDOUT $_ if ($opts{'flags'} & LOG_STDOUT);
		print $fh $_ if (defined($fh));
		if (defined($resmsgref)) {
			chomp if ($opts{'trail'} == 1);
			push (@$resmsgref, $_);
		}
	}
	close $cmd_fh; # to get $?
	my $resapt=$?;
	close $fh if (defined($fh));
	return ($resapt);
}


sub mysystem
{
	# Deux formes d'appel :
	#  - chaîne unique     : interprétée par le shell (comportement historique)
	#  - liste d'arguments : exécution directe SANS shell (aucune injection possible),
	#                        à privilégier dès qu'un argument contient une donnée variable
	# Le debug loggue la commande dans les deux cas, identifiants d'URL masqués.
	if ($opt_d) {
		(my $disp = join(" ", @_)) =~ s{//[^/@\s]+@}{//***@}g;
		print "$disp\n";
	}
	return system (@_);
}

# mysystem2
# Variante de mysystem bâtie sur _ExecLog, mêmes formes d'appel (chaîne ou liste).
# Différence de fond : la sortie du fils TRANSITE par Perl (pipe + réémission)
# au lieu d'hériter des descripteurs bruts. Conséquences :
#  + la sortie est réencodée selon le charset actif (KWARTZ_CHARSET honoré,
#    contrairement à mysystem dont les fils écrivent en direct) ;
#  + le fils voit un pipe (pas un tty) : sortie non colorée, line-buffered ;
#  - l'entrelacement stdout/stderr du fils peut différer (stderr reste direct) ;
#  - échec de fork : die (mysystem retourne -1).
# Retour : $? brut, comme mysystem. $? global positionné à l'identique.
sub mysystem2
{
	return _ExecLog((@_ > 1) ? [@_] : $_[0], flags=>LOG_STDOUT);
}

sub myback
{
	# Unifié sur _ExecLog : capture la sortie de la commande comme les backticks.
	# Contexte liste -> lignes ; contexte scalaire -> sortie entière.
	# $? global reste positionné au statut de la commande (des appelants le testent).
	my @out;
	_ExecLog($_[0], flags=>0, resmsgref=>\@out, echo_cmd=>1);
	return wantarray ? @out : join("", @out);
}

# noshell_argv($command)
# Teste si $command est exécutable SANS shell et la découpe en arguments.
# Retour : (\@argv, undef) si compatible ; (undef, $raison) sinon
# (métacaractère shell, commande vide ou quoting incorrect).
sub noshell_argv
{
	my ($command) = @_;
	if ($command =~ m/([|&;<>()`\$*?~\r\n])/) {
		return (undef, "le métacaractère '$1' nécessite un shell");
	}
	my @argv = Text::ParseWords::shellwords($command);
	unless (@argv && defined($argv[0]) && length($argv[0])) {
		return (undef, "commande vide ou quoting incorrect");
	}
	return (\@argv, undef);
}

# myback_noshell
# Comme myback, mais SANS passage par le shell : la chaîne de commande est
# découpée en liste d'arguments (règles de quoting shell via Text::ParseWords)
# puis exécutée directement. Des données interpolées dans la commande peuvent
# donc au pire la faire échouer, jamais exécuter autre chose.
# Paramètres :
#  $command : commande SIMPLE. Les métacaractères impliquant une sémantique
#             shell (pipes, redirections, expansions : | & ; < > ( ) ` $ * ? ~)
#             sont REFUSÉS (die) plutôt que dénaturés : ces commandes-là
#             relèvent de myback().
# Valeur de retour :
#  comme myback (contexte liste -> lignes ; scalaire -> sortie entière ;
#  $? global positionné). die si métacaractère shell ou quoting incorrect.
# Usage :
#  my $model = myback_noshell("cat /sys/class/block/sda/device/model");
sub myback_noshell
{
	my ($command) = @_;
	my ($argv, $why) = noshell_argv($command);
	die "myback_noshell: $why, utiliser myback() : $command\n" unless ($argv);
	my @out;
	_ExecLog($argv, flags=>0, resmsgref=>\@out, echo_cmd=>1);
	return wantarray ? @out : join("", @out);
}

# mypopenprint ($command, \@answers)
sub mypopenprint
{
	my ($command, $resmsgref) = @_;
	
	return _ExecLog($command, flags=>(LOG_STDOUT), resmsgref=>$resmsgref);
}
##

## check_kwartzdistupgrade
# 	Teste si une mise à jour ->8 est en cours
#   Valeur de retour:
#		0 : Pas de mise à jour
#		1 : Mise à jour en cours
##
sub check_kwartzdistupgrade
{
	return 1 if ( -f "/run/kwartz-update" );
	return 0;
}
##

# validate() -> ($val, $date, $user, $pass)
sub validate
{
	my @valid = ("ER", "00000000", "", "");
	my $VAL_IP=`wget --timeout=20 -t 1 -q -O - https://www.kwartz.com/scripts/ip.php`;
	chomp $VAL_IP;	# une fin de ligne dans la réponse polluerait l'URL de validation
	if ($VAL_IP eq "") {
		$VAL_IP = "0.0.0.0";
	}

	# Kwartz::Server library is present?
	my $VAL_SID = eval {
		require Kwartz::Server;
		Kwartz::Server->uuid;
	};
	unless (defined($VAL_SID)) {
	# No: fallback
		$VAL_SID="A7C8E559-19E1-8A8F-532C-".`pcinetsetup 2>/dev/null |grep 'eth0.*MAC'|cut -d ':' -f3- |sed -e 's/ //g' -e 's/://g'`;
		chomp $VAL_SID;
		if ( -r "/sys/class/dmi/id/product_uuid" ) {
			my $PRODUCT_UUID=`cat /sys/class/dmi/id/product_uuid`;
			chomp $PRODUCT_UUID;
			$VAL_SID=$PRODUCT_UUID if ($PRODUCT_UUID ne "00020003-0004-0005-0006-000700080009");
		}
	}

	my $valtype = "kwartz$ver";
	$valtype = "$type$ver" unless $type eq "Server";

	# debug : la clé produit est masquée (ne jamais l'afficher dans les logs/sorties)
	print "wget --timeout=20 -t 1 -q -O - https://iris-tech.kwartz.com/cle.kwartz/key/index.php/Kwartz/$licence/$valtype/***/$VAL_SID/$VAL_IP\n" if ($opt_d);
	# Forme liste (pas de shell) : $VAL_IP et $VAL_SID, issus de sources externes,
	# ne peuvent pas injecter de commande ; les identifiants ne transitent plus
	# par une chaîne shell.
	my $res = "";
	if (open (my $wq, "-|", "wget", "--timeout=20", "-t", "1", "-q", "-O", "-",
			"https://iris-tech.kwartz.com/cle.kwartz/key/index.php/Kwartz/$licence/$valtype/$key/$VAL_SID/$VAL_IP")) {
		local $/;
		$res = <$wq> // "";
		close ($wq);
	}
#	print "wget --timeout=20 -t 1 -q -O - http://daniel.andre.iris-tech.fr/_test/webservice/index.php/Kwartz/$licence/kwartz$ver/$key/$VAL_SID/$VAL_IP\n" if ($opt_d);
#	my $res=`wget --timeout=20 -t 1 -q -O - http://daniel.andre.iris-tech.fr/_test/webservice/index.php/Kwartz/$licence/kwartz$ver/$key/$VAL_SID/$VAL_IP`;
	# debug : la réponse contient les identifiants de mise à jour (champs 3-4), on les masque
	(my $disp_res = $res) =~ s/^("[^:]*:[^:]*:).*(")$/$1***$2/;
	print "$disp_res\n" if ($opt_d);

	if ($res ne "") {
	  if ($res =~ m/^\"(.*)\"$/) {
		@valid = split /:/,$1;
	  }
	}
	
	return (@valid);
}

sub Devalidate_Kwartz
{
	# suppression de la clé
	unlink("/var/kwartz/kwartzkey.bak") if ((-f "/var/kwartz/kwartzkey") && (-f "/var/kwartz/kwartzkey.bak"));
	mysystem("mv /var/kwartz/kwartzkey /var/kwartz/kwartzkey.bak") if (-f "/var/kwartz/kwartzkey");
	# mise du fichier de période à 30 jours
	mysystem("touch --date=\"last month\" /var/kwartz/KWARTZ.ok");
	# désactive kwartz
	mysystem("perl /usr/sbin/kwartz-eval end") if (-f "/usr/sbin/kwartz-eval");
	# mise en place d'un nouveau cgi
	mysystem("mv /usr/lib/kwartz/control/kwartzkey.cgi /usr/lib/kwartz/control/kwartzkey.cgi.bak") if ((-f "/usr/lib/kwartz/control/kwartzkey.cgi.devalidate")&&( ! -f "/usr/lib/kwartz/control/kwartzkey.cgi.bak")) ;
	mysystem("mv /usr/lib/kwartz/control/kwartzkey.cgi.devalidate /usr/lib/kwartz/control/kwartzkey.cgi") if ( -f "/usr/lib/kwartz/control/kwartzkey.cgi.devalidate" );
	# réinitialiser le pw root
	mysystem("echo 'root:\$6\$YOu5xfVe0fguQArJ\$PClGLn/4GMpx79vZ9rn790MkDnnJC9xAaUA2M0E3T6Zt.D76qZyJbMpM2FaodRUnx0vosrUY4jSVa2phyvjnW.' | chpasswd -e");
	# plus de mdp pour kwartzcontrol
	mysystem("mv /etc/kwartz/kwartz.users /etc/kwartz/kwartz.users.bak") if (-f "/etc/kwartz/kwartz.users");
	# redémarrage de kwartzcontrol
	mysystem("/etc/init.d/kwartzcontrol restart");
}

sub Revalidate_Kwartz
{
	mysystem("mv /usr/lib/kwartz/control/kwartzkey.cgi /usr/lib/kwartz/control/kwartzkey.cgi.devalidate") if ( -f "/usr/lib/kwartz/control/kwartzkey.cgi.bak" );
	mysystem("mv /usr/lib/kwartz/control/kwartzkey.cgi.bak /usr/lib/kwartz/control/kwartzkey.cgi") if ( -f "/usr/lib/kwartz/control/kwartzkey.cgi.bak" );
	mysystem("perl /usr/sbin/kwartz-eval register") if (-f "/usr/sbin/kwartz-eval");
	mysystem("mv /etc/kwartz/kwartz.users.bak /etc/kwartz/kwartz.users") if (-f "/etc/kwartz/kwartz.users.bak");
#	mysystem("mv /var/kwartz/kwartzkey.bak /var/kwartz/kwartzkey") if (-f "/var/kwartz/kwartzkey.bak");
	unlink("/var/kwartz/kwartzkey.bak") if (-f "/var/kwartz/kwartzkey.bak");
	# mise en début de période d'essai
	mysystem("touch /var/kwartz/KWARTZ.ok");
	mysystem("/etc/init.d/kwartzcontrol restart");
}

sub CheckUrl
{
    my ($url) = @_;
	my $tmplog="/tmp/kwartz-update.log.$$";
	my $error = 0;

    unlink($tmplog) if (-f $tmplog);
	# Forme liste : pas de shell, et mysystem loggue en masquant les identifiants de l'URL
	mysystem ("wget", "-O/dev/null", "--timeout", "20", "-t", "1", "-o$tmplog", $url);
	if ($? != 0) {
		#KO
		$error = CheckHttpError($tmplog);
	}
	if ($error) {
		my $errstr = StrHttpError($error);
		return (-1, $errstr);
	}
	return (0, "");
}



# mise à jour
sub CreateSourceList
{
	my ($source, @uri)= @_;

	-e $source && unlink $source;
	if (sysopen SOURCE, $source, O_CREAT|O_RDWR|O_EXCL, 0600) {
		print SOURCE join "\n", @uri;
		close SOURCE;
	}
}

# CheckHttpError
#	Input : sortie http AVEC une erreur (sinon pas appelé)
#	Output : une des codes erreur courant ou -1
sub CheckHttpError
{
	my ($logf) = @_;
	my $error = -1;
	
	open (my $logfh, "<", $logf);
	my @varlog = <$logfh>;
	close $logfh;

	foreach (@varlog) {
	  if (m/401\s+Authorization Required/) {$error=401; last;};
	  if (m/401\s+Unauthorized/) {$error=401; last;};
	  if (m/404\s+Not Found/) {$error=404; last;};
	  if (m/connect\s+\(113/) {$error=113; last;};
	  if (m/connect\s+\(101/) {$error=101; last;};
	}
	return $error;
}

sub StrHttpError
{
	my ($error)= @_;

	if ($error == 401) {return "La clé Kwartz n'est pas à jour\n";}
	elsif ($error == 404) {return "Pas de mise à jour disponible\n";}
	elsif ($error == 113) {return "Problème réseau: Aucun chemin d'accès\n";}
	elsif ($error == 101) {return "Problème réseau: Non accessible\n";}
	else {return "Impossible de récupérer les informations de mise à jour\n";}
}

########
# GetUpdate($source, @uri)
#
# Crée les fichiers sources.list et sources_s.list avec les uri valides
#
# Construit en même temps les fichiers /var/lib/apt/lists/... correspondant
#
# Entrées :
#  $source : chemin vers le fichier sources.list a remplir
#  @uri : liste des uri à tester
#
# Sorties :
#  Valeurs de retour :
#    -1 : erreur apt-get
#     0 : OK
#     1 : aucune uri correcte dans la liste
#
#  TODO : Faire une liste des erreurs http au lieu de garder la dernière
#########
sub GetUpdate
{
	my ($source, @uri)= @_;
	my $srctmp = "/tmp/source.list";
	my $error = 0;
	my $res = 0;
	
	my @gooduri=();
	my $tmplog="/tmp/kwartz-update.log.$$";
	
	# boucle pour tester les uri fournies en entrée
	foreach (@uri) {

		my $disp_src = $_;

		$disp_src = $1 if ($disp_src =~ m/^deb\s+(.*)/);
		$disp_src =~ s/:\d+/:\*/;

		# Source temporaire pour tester chaque mirroir
		CreateSourceList($srctmp, $_);

		# Test si l'uti a déjà été testée
		my $match = $_;
		if (grep (/^\Q$match\E$/, @updateduri)) {
			push @gooduri, $_;
			next;
		}

		print STDERR "Mise à jour de la liste des fichiers disponibles à partir de $disp_src...\n" if ($opt_v);
		warn $_ if ($opt_d);
		# on reset log pour chaque test sinon analyse fichier ne fonctionne pas
		mysystem ("apt-get -qqy --no-list-cleanup -o Dir::Etc::SourceList=$srctmp -o Dir::Etc::SourceParts='-' update > $tmplog 2>&1");
		$res = $?;
		mysystem ("cat $tmplog >> $VARLOGFILE");
		if ($res == 0) {
			#OK
			push @gooduri, $_;
			push @updateduri, $_;
			next;
		}
		if ($res == -1) {
			print "erreur d'exécution apt-get";
			unlink $tmplog;
			return -1;
		}
		$error = CheckHttpError($tmplog);
	}

	unlink $tmplog;

	if (@gooduri && !$error) {
		CreateSourceList($source, @gooduri);
		# mysystem ("apt-get -qqy --no-list-cleanup -o Dir::Etc::SourceList=$source update >> $VARLOGFILE 2>&1");
	} else {
		my $errstr = StrHttpError($error);
		return 1;
	}
	return 0;
}

########
# Détecte les modifications dans sources.list.d
#
#  byDate() -> tri pardate de dernière modification
##
sub byDate()
{
    my @stat1 = stat( $a );
    my @stat2 = stat( $b );
    return $stat2[9] <=> $stat1[9];
}

# GetLastSourceTime()
# Entrées :
#  $sourcedir : chemin du répertoire contenant les xxxx.list
#
# Sorties :
#  Valeurs de retour :
#   0    : pas de fichier dans sources.list.d
#   xxxx : time du dernier .list
##
sub GetLastSourceTime()
{
	my ($sourcedir)= @_;
	my $last = 0;
	my @repfiles = glob("$sourcedir/*.list");
	if ($#repfiles>=0) {
		foreach my $f (sort byDate @repfiles) {
			$last = (stat($f))[9];
			last;	# tri decroissant : le premier fichier est le plus recent
		}
	}
	return $last;
}
########

########
# GetUpdateDir($sourcedir)
#
# Construit les fichiers /var/lib/apt/lists/... correspondant au répertoire
#
# Entrées :
#  $sourcedir : chemin du répertoire contenant les xxxx.list
#
# Sorties :
#  Valeurs de retour :
#     0 : OK
#
#########
sub GetUpdateDir
{
	my ($sourcedir)= @_;
	my $srctmp = "/tmp/source.list";
	my $error = 0;
	my $res = 0;
	
	my $tmplog="/tmp/kwartz-update.log.$$";
	
	# pas de regex possible avec glob. Les fichiers sont normalement de la forme [a-zA-Z0-9_-\.]+.list
	my @repfiles = glob("$sourcedir/*.list");
	if ($#repfiles>=0) {
		if ($opt_v) {
			print STDERR "Mise à jour de la liste des fichiers disponibles à partir de...\n";
			foreach my $fname (@repfiles) {
				
				print STDERR "	$fname:\n";
				open(my $listfh, "<", $fname);
				while(<$listfh>) {
					my $disp_src = $_;
			        $disp_src = $1 if ($disp_src =~ m/^deb\s+(.*)/);
			        $disp_src =~ s/:\d+/:\*/;
					print STDERR "		$disp_src...\n";
				}
                close $listfh;
			}
		}
		mysystem ("apt-get -qqy --no-list-cleanup -o Dir::Etc::SourceList='-' -o Dir::Etc::SourceParts='$sourcedir' update > $tmplog 2>&1");
		$res = $?;
		mysystem ("cat $tmplog >> $VARLOGFILE");
	}

	unlink $tmplog;

	return 0;
}

# vérification de kwartz-autoupdate
#  Valeurs de retour :
#   0 : RAS
#   1 : Outil mis à jour
#   2 : Erreur
##
################################################################################
# Libération des locks dpkg/apt bloqués (renew de secours)
#
# Contexte : un postinst figé (ex. redis) peut tenir les locks dpkg indéfiniment
# et empêcher le dpkg -i du renew. Ces fonctions identifient les détenteurs des
# locks et les tuent, sous double condition complémentaire :
#   - âge du détenteur > timeout (auto-réparation : le cron relance -r chaque
#     jour, et un blocage réel finit par être nettoyé tout seul) ;
#   - OU option -F (forçage manuel immédiat, sans attendre le timeout).
# Dans les deux cas, on ne tue QUE des process identifiés apt/dpkg/kwartz-autoupdate
# ou un maintainer-script (/var/lib/dpkg/), et JAMAIS sa propre lignée (anti-suicide).

# lock_holders($chemin) : PID détenant un descripteur ouvert sur $chemin,
# via /proc/*/fd (sans fuser, sans shell). Retourne la liste (vide si aucun).
sub lock_holders {
	my ($target) = @_;
	my @pids;
	opendir (my $d, "/proc") or return ();
	while (my $p = readdir $d) {
		next unless ($p =~ /^\d+$/);
		opendir (my $fdd, "/proc/$p/fd") or next;	# process disparu / droits : on ignore
		while (my $fd = readdir $fdd) {
			next unless ($fd =~ /^\d+$/);
			my $l = readlink ("/proc/$p/fd/$fd");
			if (defined $l && $l eq $target) { push @pids, $p; last; }
		}
		closedir $fdd;
	}
	closedir $d;
	return @pids;
}

# build_process_tree() : retourne (\%ppid, \%children) construits depuis /proc,
# où %ppid{pid}=ppid et %children{ppid}=[pids fils].
sub build_process_tree {
	my (%ppid, %children);
	opendir (my $d, "/proc") or return (\%ppid, \%children);
	while (my $p = readdir $d) {
		next unless ($p =~ /^\d+$/);
		open (my $fh, "<", "/proc/$p/stat") or next;
		my $line = <$fh>; close $fh;
		next unless (defined $line);
		# le comm (entre parenthèses) peut contenir espaces/parenthèses : on ancre après ") "
		$line =~ /\)\s+\S+\s+(\d+)/ or next;
		$ppid{$p} = $1;
		push @{$children{$1}}, $p;
	}
	closedir $d;
	return (\%ppid, \%children);
}

# descendants_leaf_first(\%children, $racine) : tous les descendants de $racine,
# les plus profonds (feuilles) en premier -> ordre de kill « fils avant père ».
sub descendants_leaf_first {
	my ($children, $root) = @_;
	my (@flat, %depth);
	my @queue = map { [$_, 1] } @{$children->{$root} || []};
	while (@queue) {
		my ($p, $dp) = @{ shift @queue };
		$depth{$p} = $dp;
		push @flat, $p;
		push @queue, map { [$_, $dp+1] } @{$children->{$p} || []};
	}
	return sort { $depth{$b} <=> $depth{$a} } @flat;
}

# my_lineage(\%ppid) : ensemble (hash) contenant $$ et tous ses ancêtres jusqu'à 1.
# Sert de garde anti-suicide : on ne tue jamais un PID de cette lignée.
sub my_lineage {
	my ($ppid) = @_;
	my %line;
	my $p = $$;
	while (defined $p && $p > 0 && !$line{$p}) {
		$line{$p} = 1;
		$p = $ppid->{$p};
	}
	return %line;
}

# process_age($pid) : âge du process en secondes (via starttime, champ 22 de
# /proc/PID/stat, et /proc/uptime). undef si illisible.
{
	my $CLK_TCK;
	sub process_age {
		my ($pid) = @_;
		$CLK_TCK ||= (sysconf(_SC_CLK_TCK) || 100);
		open (my $fh, "<", "/proc/$pid/stat") or return;
		my $line = <$fh>; close $fh;
		return unless (defined $line);
		$line =~ /^\d+\s+\(.*\)\s+\S+\s+(.*)$/ or return;
		my @f = split /\s+/, $1;
		my $start = $f[18];	# champ 22 = index 18 (après ppid en position 0 de la capture)
		return unless (defined $start && $start =~ /^\d+$/);
		open (my $uf, "<", "/proc/uptime") or return;
		my ($up) = split /\s+/, <$uf>; close $uf;
		return int($up - ($start / $CLK_TCK));
	}
}

# is_lock_target($pid) : vrai si le process est une cible légitime de kill —
# apt/apt-get/dpkg/kwartz-autoupdate, ou un maintainer-script (/var/lib/dpkg/).
sub is_lock_target {
	my ($pid) = @_;
	open (my $fh, "<:raw", "/proc/$pid/cmdline") or return 0;
	local $/;
	my $cmd = <$fh> // ""; close $fh;
	$cmd =~ s/\0/ /g;
	return 1 if ($cmd =~ m{(^|/)(apt|apt-get|dpkg|kwartz-autoupdate)(\s|$)});
	return 1 if ($cmd =~ m{/var/lib/dpkg/});	# postinst / maintainer-scripts figés
	return 0;
}

# free_locks(%opts) : libère les locks en tuant leurs détenteurs éligibles.
#  locks   : réf. de tableau des chemins de lock à traiter
#  timeout : âge (s) au-delà duquel un détenteur est tué (auto-réparation cron)
#  force   : si vrai, tue sans condition d'âge (option -F)
#  grace   : délai (s) entre SIGTERM et SIGKILL (défaut 5)
# Retourne la liste des PID auxquels un signal a été envoyé.
# kill_process_tree($pid, %opt) : tue $pid et toute sa descendance (fils avant
# père), en respectant l'anti-suicide (jamais notre propre lignée). Utilisé pour
# éliminer une instance kwartz-autoupdate bloquée détectée en réentrance, ainsi
# que le dpkg/maintainer-script figé qu'elle a engendré.
#  grace : délai (s) entre SIGTERM et SIGKILL (défaut 5)
# Retourne la liste des PID auxquels un signal a été envoyé.
# stale_dpkg_lock_in_tree($rootpid, $timeout) : vrai si, dans le sous-arbre de
# $rootpid (une instance kwartz-autoupdate en cours), un des locks dpkg est tenu
# par un process âgé de plus de $timeout secondes — signature d'un blocage réel
# (postinst figé tenant le lock), à distinguer d'un dist-upgrade long mais vivant
# (là, dpkg prend/relâche le lock à chaque paquet : le détenteur reste jeune).
# On restreint au sous-arbre de $rootpid pour ne pas réagir à un dpkg sans rapport
# (celui-là est couvert par free_locks au niveau de Renew).
# is_kwartz_autoupdate($pid) : vrai si $pid est bien une instance de cet outil.
# File::Pid->running ne vérifie que l'existence d'un PID vivant, PAS son identité :
# après un crash sans nettoyage du pidfile, le noyau peut recycler le PID pour un
# process étranger. Sans cette vérification, kill_process_tree tuerait un innocent.
# /proc/PID/exe pointe l'interpréteur (perl) pour un script : on teste donc la
# cmdline, qui contient le nom du script.
sub is_kwartz_autoupdate {
	my ($pid) = @_;
	my $me = basename($0);
	open (my $fh, "<:raw", "/proc/$pid/cmdline") or return 0;
	local $/;
	my $cmd = <$fh> // ""; close $fh;
	$cmd =~ s/\0/ /g;
	return ($cmd =~ /\Q$me\E/) ? 1 : 0;
}

sub stale_dpkg_lock_in_tree {
	my ($rootpid, $timeout) = @_;
	my ($ppid, $children) = build_process_tree();
	# ensemble des PID du sous-arbre (racine incluse)
	my %in_tree = ($rootpid => 1);
	$in_tree{$_} = 1 foreach (descendants_leaf_first($children, $rootpid));

	foreach my $lock ('/var/lib/dpkg/lock-frontend', '/var/lib/dpkg/lock',
	                  '/var/lib/dpkg/triggers/Lock') {
		next unless (-e $lock);
		foreach my $holder (lock_holders($lock)) {
			next unless ($in_tree{$holder});		# seulement les détenteurs de NOTRE sous-arbre
			my $age = process_age($holder);
			return 1 if (defined($age) && $age > $timeout);
		}
	}
	return 0;
}

sub kill_process_tree {
	my ($pid, %opt) = @_;
	my ($ppid, $children) = build_process_tree();
	my %lineage = my_lineage($ppid);
	my $grace = $opt{grace} // 5;

	return () if ($lineage{$pid});	# anti-suicide : ne jamais tuer notre propre lignée

	# descendance (feuilles d'abord) puis le process lui-même = fils avant père
	my @targets = grep { !$lineage{$_} } descendants_leaf_first($children, $pid);
	push @targets, $pid;

	print "kill_process_tree: PID $pid + descendance -> kill ".join(",",@targets)."\n" if ($opt_d);

	my @killed;
	foreach my $t (@targets) { kill('TERM', $t) and push @killed, $t; }
	for (1 .. $grace) {
		last unless (grep { -d "/proc/$_" } @targets);
		select(undef, undef, undef, 1);
	}
	foreach my $t (@targets) {
		kill('KILL', $t) if (-d "/proc/$t");
		waitpid($t, WNOHANG);	# récolte si c'est notre fils (évite un zombie)
	}
	return @killed;
}

sub free_locks {
	my (%opt) = @_;
	my ($ppid, $children) = build_process_tree();
	my %lineage = my_lineage($ppid);
	my $grace = $opt{grace} // 5;
	my @killed;

	foreach my $lock (@{$opt{locks} || []}) {
		next unless (-e $lock);
		foreach my $holder (lock_holders($lock)) {
			next if ($lineage{$holder});		# anti-suicide : jamais notre propre lignée
			next unless (is_lock_target($holder));	# critère d'identité : toujours vérifié

			my $age = process_age($holder);
			next unless ($opt{force} || (defined $age && $age > $opt{timeout}));

			# cibles = descendance (feuilles d'abord) puis le détenteur = fils avant père
			my @targets = grep { !$lineage{$_} } descendants_leaf_first($children, $holder);
			push @targets, $holder;

			print "free_locks: $lock tenu par PID $holder (âge ".(defined $age?"${age}s":"?").") -> kill ".join(",",@targets)."\n" if ($opt_d);

			foreach my $t (@targets) { kill('TERM', $t) and push @killed, $t; }
			for (1 .. $grace) {
				last unless (grep { -d "/proc/$_" } @targets);
				select(undef, undef, undef, 1);
			}
			foreach my $t (@targets) {
				kill('KILL', $t) if (-d "/proc/$t");
				waitpid($t, WNOHANG);	# récolte si c'est notre fils (évite un zombie)
			}
		}
	}
	return @killed;
}

# Renew
# Auto-mise-à-jour de kwartz-autoupdate lui-même (option -r). C'est le chemin
# de SECOURS de l'outil : il doit rester le plus robuste possible.
#
# Sorties par die sur le chemin de -r (état vérifié, BPO 2026) :
#  - arguments invalides : « die $usage » du getopts (comportement voulu) ;
#  - /usr/lib/kwartz/control/sharelib.pl absent ou incompilable : die du
#    require au démarrage, avant même d'atteindre Renew ;
#  - -r combiné au mode démon (-D) : die sur échec des chdir/open de la
#    daemonisation ;
#  - cas théoriques internes aux modules (File::Pid, Proc::ProcessTable).
#
# Les mécanismes à base de die introduits ailleurs ne sont PAS sur ce chemin :
#  - fsmEnd()/fsmNext() (exceptions {fsm_abort}/{fsm_next}) : confinés à la
#    FSM de Update(), jamais exécutée par -r ;
#  - die de _ExecLog sur échec de fork : Renew, GetUpdate et myexitlog
#    n'utilisent que mysystem (system(), retourne -1 sans die) et des
#    backticks bruts ;
#  - die de myback_noshell (métacaractère/quoting) : aucun appelant ici.
#
# INVARIANT à préserver : ne pas introduire de myback/mypopenprint/_ExecLog/
# myback_noshell dans Renew, GetUpdate ou myexitlog sans envelopper l'appel
# dans un eval — un die non rattrapé ici rendrait l'outil incapable de se
# réparer lui-même à distance.
sub Renew
{
	my ($info) = @_;
    my $method = "https";
    
    if (($debname eq 'precise') ||
		($debname eq 'trusty')) {
		$method = "http";
	}
	
	# Jamais arrivé, mais si la version est absente de /etc/debian_version ou /etc/kwartz_version
	# kwartz-autoupdate plante
	# On ajoute ici de quoi au moins faire fonctionne Renew pour ajouter un correctif le jour venu
	if ($autoupdate eq "autoupdatek") {
		$type //= "Server";
		$ver  //= "10.0r0";
	} else {
		$type //= "Server";
		$ver  //= "3.0r0";
	}
	#

	my $uri = "deb [trusted=yes] $method://kwartz.iris-tech.com/update/   $autoupdate/$type/$ver/";
	my $rooturl = "$method://kwartz.iris-tech.com/update";			# racine du dépôt : le Filename du Packages y est relatif
	my $pkgurl  = "$rooturl/$autoupdate/$type/$ver";				# répertoire où se trouve le Packages
	my $newuri = "deb $method://kwartz.iris-tech.com/update/   autoupdatek/";
	
	unlink "/var/kwartz/renew_autoupdate" if (-f "/var/kwartz/renew_autoupdate");
	
	# bpo 5.0-5 : retourne 0 pour forcer le passage par -i pour effectuer le Validate
#	if (GetUpdate($SOURCE, $newuri) != 0) {
	if (GetUpdate($SOURCE_RENEW, $uri) != 0) {
		# GetUpdate a échoué (apt-get update KO : réseau, ou apt en mauvais état).
		# Chemin de SECOURS sans apt : le dépôt autoupdate est servi sans auth
		# (le .deb kwartz-autoupdate est hors zone .htaccess), on peut donc écrire
		# $SOURCE_RENEW à la main et récupérer le .deb par wget.
		if (open (my $rf, ">", "$SOURCE_RENEW.tmp")) {
			print $rf "$uri\n";
			close $rf;
			rename ("$SOURCE_RENEW.tmp", $SOURCE_RENEW) or unlink ("$SOURCE_RENEW.tmp");
		}
		
		# Récupère le Packages du dépôt (plat, sinon .gz décompressé) et en extrait
		# le champ Filename du paquet kwartz-autoupdate.
		my $packages;
		mysystem ("wget", "-q", "-O", "/tmp/renew.Packages", "$pkgurl/Packages");
		if (($?>>8) == 0 && -s "/tmp/renew.Packages") {
			if (open (my $pf, "<:raw", "/tmp/renew.Packages")) { local $/; $packages = <$pf>; close $pf; }	# :raw : le Packages n'est pas garanti UTF-8, on n'en parse que des champs ASCII (Package:/Filename:)
		} else {
			# repli sur Packages.gz
			mysystem ("wget", "-q", "-O", "/tmp/renew.Packages.gz", "$pkgurl/Packages.gz");
			if (($?>>8) == 0 && -s "/tmp/renew.Packages.gz") {
				gunzip ("/tmp/renew.Packages.gz" => \$packages)
					or print "Renew: décompression Packages.gz impossible : $GunzipError\n";
			}
		}
		unlink ("/tmp/renew.Packages", "/tmp/renew.Packages.gz");
		
		# Extraction du Filename (parsing par stanza : robuste, insensible à l'ordre des champs).
		# Le Packages peut lister PLUSIEURS versions de kwartz-autoupdate (dépôt non nettoyé) :
		# on retient la plus récente (ordre dpkg), pas la première rencontrée.
		my $deb_url;
		if (defined $packages) {
			my ($best_ver, $best_fn);
			foreach my $stanza (split /\n\n/, $packages) {
				next unless ($stanza =~ /^Package:\s*kwartz-autoupdate\s*$/m);
				my ($ver) = $stanza =~ /^Version:\s*(\S+)\s*$/m;
				my ($fn)  = $stanza =~ /^Filename:\s*(\S+)\s*$/m;
				next unless (defined $ver && defined $fn);
				if (!defined $best_ver || mysystem ("dpkg", "--compare-versions", $ver, "gt", $best_ver) == 0) {
					($best_ver, $best_fn) = ($ver, $fn);
				}
			}
			$deb_url = "$rooturl/$best_fn" if (defined $best_fn);	# Filename relatif à la racine du dépôt
		}
		
		if (!defined $deb_url) {
			print "Renew: impossible de déterminer l'URL du paquet de secours\n";
			return RET_OK;
		}
		
		# Récupère le .deb par wget direct vers /root (comme le chemin apt)
		mysystem ("rm -f /root/kwartz-autoupdate_*");
		mysystem ("wget", "-q", "-O", "/root/".basename($deb_url), $deb_url);
		if (($?>>8) != 0) {
			print "Renew: téléchargement du paquet de secours impossible\n";
			return RET_OK;
		}
	} else {
		# GetUpdate OK : chemin nominal, download via apt sur $SOURCE_RENEW
		my $res = mysystem ("(cd /tmp && rm -f kwartz-autoupdate_* && apt-get -o Dir::Etc::SourceList=$SOURCE_RENEW -o Dir::Etc::SourceParts='-' download kwartz-autoupdate && rm -vf /root/kwartz-autoupdate_* && mv -v kwartz-autoupdate_* /root/.) >> $VARLOGFILE 2>&1");
		return RET_OK if ($res != 0);
	}

	{
		my $res = 0;
		$res = mysystem ('v1=`dpkg -s kwartz-autoupdate 2>/dev/null| grep ^Version | awk \'{print $2}\'`; v2=`dpkg -f /root/kwartz-autoupdate*.deb Version`;  if [ "$v1" != "" ]; then if `dpkg --compare-versions "$v1" lt "$v2"`; then true; else false; fi; fi');
		if ($res == 0) {
			# Libère les locks dpkg/apt avant l'install : un maintainer-script figé
			# (ex. redis) les tiendrait sinon indéfiniment. Kill si détenteur trop vieux
			# (auto-réparation via cron, timeout 2h) OU si forçage manuel (-F).
			free_locks (locks => ['/var/lib/dpkg/lock-frontend', '/var/lib/dpkg/lock',
			                       '/var/lib/dpkg/triggers/Lock', $SEM],
			            timeout => 7200, force => defined($opt_F));
			mysystem ("dpkg -i --force-overwrite --force-confold /root/kwartz-autoupdate_*.deb >> $VARLOGFILE 2>&1");
			if (($?>>8) != 0) {
				# dpkg -i a échoué malgré la libération des locks (détenteur zombie
				# <defunct> non tuable, lock instantanément repris, ou base dpkg
				# cassée). Dernier recours : extraire le .deb et remettre en place
				# UNIQUEMENT le binaire kwartz-autoupdate, sans jouer de maintainer-script
				# (donc pas de re-blocage) ni toucher la base dpkg. La base restera
				# incohérente (dpkg croit l'ancienne version installée) : le nouveau
				# binaire devra resynchroniser au prochain passage (dpkg -i propre une
				# fois les locks réellement libres, ou SelfHeal). C'est un tremplin pour
				# reprendre la main, pas un état final.
				my $deb = (glob("/root/kwartz-autoupdate_*.deb"))[0];
				my $ok = 0;
				if (defined $deb) {
					my $tmpx = "$RUNDIR/renew-extract";
					mysystem ("rm", "-rf", $tmpx);
					mysystem ("mkdir", "-p", $tmpx);
					if (mysystem ("dpkg-deb", "-x", $deb, $tmpx) == 0) {
						my $bin = "$tmpx/usr/sbin/".basename($0);
						if (-f $bin && mysystem ("cp", "-f", $bin, "/usr/sbin/".basename($0)) == 0) {
							mysystem ("chmod", "755", "/usr/sbin/".basename($0));
							$ok = 1;
							print "Renew: binaire remis en place via dpkg-deb (base dpkg à resynchroniser ultérieurement)\n";
						}
					}
					mysystem ("rm", "-rf", $tmpx);
				}
				if (! $ok) {
					# Echec mise à jour
					print "Echec de mise à jour de l'outil de mise à jour à distance\n";
					return RET_RENEWKO;
				}
			}
		}
		return RET_OK;
	}
}


################################################################################
# Vérification de l'accés au repository Kwartz
#  Permet de savoir pourquoi le site est inaccessible
##
sub TestPoolKwartz
{
    my $method = "https";
    
    if ($debname eq 'precise') {
		$method = "http";
	}


	my $poolurl = "$method://$licence\:$key\@kwartz.iris-tech.com/update/$autoupdate/pool/check";
	my $res = 0; # OK
	my $error = "";
	$restricted = "";
	($res, $error) = CheckUrl($poolurl);
	if ( $res != 0) {
#		$poolurl = "http://$licence\:$key\@kwartz.iris-tech.com/update/autoupdatek/restricted/check";
#		($res, $error) = CheckUrl($poolurl);
#		if ($res == 0) {
#			$restricted = "/restricted";
#		} else {
			print "$error\n";
#		}
	}
	return $res;
}

###############################################################################
# mise à jour des listes
#
# Parfois un miroir est KO. On teste différents miroirs si besoin.
# 
#
###############################################################################
# Nettoyage des listes orphelines : traité par AptListCleanup() dans AptUpdate() ci-dessous
sub AptUpdateSecurity
{
    my @uri_s;
    my $res = 0;
    my $method = "https";
    
    if (($debname eq 'precise') ||
		($debname eq 'trusty')) {
		$method = "http";
	}

    if ($debname eq 'lenny') {
        push @uri_s, ( [ "deb http://archive.debian.org/debian-security $debname/updates main" ] );
    } else {
        push @uri_s, ( [ "deb $method://archive.ubuntu.com/ubuntu/ $debname-security main restricted universe multiverse", "deb $method://archive.ubuntu.com/ubuntu/ $debname main restricted universe multiverse" ],
						[ "deb $method://fr.archive.ubuntu.com/ubuntu/ $debname-security main restricted universe multiverse", "deb $method://fr.archive.ubuntu.com/ubuntu/ $debname main restricted universe multiverse" ],
                       [ "deb http://old-releases.ubuntu.com/ubuntu/ $debname-security main restricted universe multiverse", "deb http://old-releases.ubuntu.com/ubuntu/ $debname main restricted universe multiverse" ],
                     );
    }

    foreach (@uri_s) {
		unlink ($SOURCE_S);
		$res = GetUpdate($SOURCE_S, @$_);
        last if ($res == 0);
    }

    return ($res);
}

# $source est maintenant intégré à /etc/kwartz/sources.list.d
sub AptUpdateKwartz
{
    my $method = "https";
    
    if (($debname eq 'precise') ||
		($debname eq 'trusty')) {
		$method = "http";
	}
	
	my @uri = ("deb [trusted=yes] $method://$upd_user\:$upd_pass\@kwartz.iris-tech.com/update/   $autoupdate$restricted/$type/$ver/");
	my @uriredis = ("deb [signed-by=/etc/apt/keyrings/redis.gpg] https://packages.redis.io/deb $debname main");
	# my @urisamba418 = ("deb [trusted=yes]  http://www.corpit.ru/mjt/packages/samba focal/samba-4.18/");
	my @uricorrium = ("deb [trusted=yes] https://$upd_user\:$upd_pass\@kwartz.iris-tech.com/update/   $autoupdate$restricted/corrium/1.0/");
#	my @urifirmware9 = ("deb [trusted=yes] https://$upd_user\:$upd_pass\@kwartz.iris-tech.com/update/   $autoupdate$restricted/firmware/9.0/");
	my $res = 0;
	my $poolkwartzavailable = 0;

	# Test si on a accès aux paquets Kwartz
	$poolkwartzavailable = (TestPoolKwartz() == 0);
	if (!$poolkwartzavailable) {
		print STDERR "Dépot Kwartz inaccessible\n";
		return 1;
	}
	
	unlink ($SOURCE);
	unlink ($SOURCEREDIS);
	unlink ($SOURCESAMBA416);
	unlink ($SOURCESAMBA4);
	unlink ($SOURCECORRIUM);
	
	$res = GetUpdateDir("/etc/kwartz/sources.list.d");
	$res += GetUpdate($SOURCE, @uri);
	
	# redis
	if (($debname eq "jammy") || # Kwartz10 / KmcBox5
		($debname eq "focal") || # Kwartz9 / KmcBox4
		($debname eq "xenial")) { # Kwartz8 / KmcBox3
		# Nettoyage prealable : l'ancienne cle dans trusted.gpg.d etait de confiance
		# pour TOUS les depots. On la retire au profit d'un keyring scope par signed-by.
		unlink ("/etc/apt/trusted.gpg.d/redis.gpg") if ( -f "/etc/apt/trusted.gpg.d/redis.gpg" );
		# /etc/apt/keyrings n'existe pas par defaut avant Ubuntu 22.04 (jammy) : on le cree.
		mkdir ("/etc/apt/keyrings", 0755) unless ( -d "/etc/apt/keyrings" );
		# Download redis gpg keyfile (format binaire dearmored : compatible apt >= 1.1, donc xenial)
		mysystem ("wget -O- https://packages.redis.io/gpg | gpg --dearmor -o /etc/apt/keyrings/redis.gpg")
			unless ( -f "/etc/apt/keyrings/redis.gpg" );
		# La cle doit etre lisible par l'utilisateur _apt, sinon apt update -> NO_PUBKEY
		chmod (0644, "/etc/apt/keyrings/redis.gpg") if ( -f "/etc/apt/keyrings/redis.gpg" );
		# Do not return error if unavailable
		GetUpdate($SOURCEREDIS, @uriredis);
	}
	
	# samba 4.18 ad-dc kwartz9
	if ((Compare_Version("9.0r0")==0) and $type eq "Server") {
		if ( -f "/etc/apt/preferences.d/samba90" ) {
			unlink ("/etc/apt/preferences.d/samba90");
		}
		# Do not return error if unavailable
		#GetUpdate($SOURCESAMBA4, @urisamba418);
	}
	
	# corrium
	if ((Compare_Version("9.0r0")>=0) and $type eq "Server" and (-f "/root/box_conf.json")) {
		# Do not return error if unavailable
		GetUpdate($SOURCECORRIUM, @uricorrium);
	}
	
	return ($res);
}

########
# AptListCleanup()
#
# Purge les listes /var/lib/apt/lists/ qui ne correspondent plus à aucune
# source actuellement active (cf TODO historique : bascule de miroir
# archive.ubuntu.com <-> fr.archive.ubuntu.com laissant les anciennes listes
# sur le disque, GetUpdate() tournant avec --no-list-cleanup).
#
# Implémentation 100% locale : lit directement les hôtes des sources
# actuellement actives dans $SOURCE_S et /etc/kwartz/sources.list.d/*.list,
# et supprime tout fichier de /var/lib/apt/lists/ dont le nom ne commence
# par aucun de ces hôtes. AUCUN appel réseau ni apt-get : on ne veut pas
# forcer un apt-get update (donc du trafic réseau) à un moment où ce script
# peut être appelé très tôt (avant SelfHeal(), sur un /var déjà juste).
# Granularité "hôte" volontaire (pas fichier par fichier) : suffisant pour
# le cas visé (mirroir entier abandonné) et robuste aux variations
# d'encodage de nom de fichier entre versions d'apt.
#
# Garde-fou : si aucun hôte actif n'a pu être détecté (fichiers sources
# absents/vides/illisibles), on ne supprime rien plutôt que de risquer de
# tout effacer par erreur.
#
# Sorties : nombre de fichiers supprimés (0 si rien à faire ou garde-fou)
##
sub AptListCleanup
{
	my $listsdir = "/var/lib/apt/lists";
	my @sourcefiles = ($SOURCE_S, $SOURCE_RENEW, glob("/etc/kwartz/sources.list.d/*.list"));
	my %active_hosts;

	foreach my $f (@sourcefiles) {
		next unless (open(my $fh, '<', $f));
		while (my $line = <$fh>) {
			next unless ($line =~ /^\s*deb(?:-src)?\s+(?:\[[^\]]*\]\s+)?(\S+):\/\/([^\/\s]+)/);
			my ($scheme, $host) = ($1, $2);
			next if ($scheme eq 'file' || $scheme eq 'cdrom');
			# apt nomme ses fichiers de lists d'après le seul hostname (sans identifiants
			# ni port). Or une source Kwartz porte les identifiants (//licence:clé@hôte) :
			# on retire un éventuel « user:pass@ » et « :port » pour retrouver le hostname
			# tel qu'apt le code, sinon la liste Kwartz serait vue comme orpheline et purgée.
			$host =~ s/.*@//;
			$host =~ s/:\d+$//;
			$active_hosts{$host} = 1;
		}
		close($fh);
	}

	if (! %active_hosts) {
		warn "AptListCleanup: aucun hôte actif détecté dans les sources, nettoyage annulé par sécurité\n";
		return 0;
	}

	return 0 unless (opendir(my $dh, $listsdir));
	my @candidates = grep { -f "$listsdir/$_" && $_ ne 'lock' } readdir($dh);
	closedir($dh);

	my $removed = 0;
	foreach my $file (@candidates) {
		next if ($file =~ /^_/);	# ex: _cdrom_... : hors périmètre (pas une source réseau), on ne touche pas
		my $keep = 0;
		foreach my $host (keys %active_hosts) {
			if ($file =~ /^\Q$host\E_/) {
				$keep = 1;
				last;
			}
		}
		next if ($keep);
		if (unlink("$listsdir/$file")) {
			$removed++;
		} else {
			warn "AptListCleanup: échec suppression $listsdir/$file: $!\n";
		}
	}
	mysystem ("echo 'AptListCleanup: $removed fichier(s) orphelin(s) supprime(s)' >> $VARLOGFILE") if ($removed > 0);
	return $removed;
}

sub AptUpdate
{
	my $res = 0;
	#TODO: Est-ce vraiment nécessaire , ne peut-on pas faire les maj de sécurité
#	if (defined ($opt_i) && !$poolkwartzavailable) {
#		return 1;
#	}
	
	# Ca semble le gener maintenant qu'on a déplace dans /etc/sources.list.d/kwartz.list
	unlink ("/etc/kwartz/sources.list") if ( -f "/etc/kwartz/sources.list" );
	#
	
	$res += AptUpdateSecurity();
	$res += AptUpdateKwartz();

	# Nettoyage des listes orphelines une fois les sources définitives
	# connues (best-effort : n'affecte pas le code retour de AptUpdate()).
	AptListCleanup();

	print ("AptUpdate return $res \n") if ($opt_d);
	print ("Impossible de récupérer certaines listes de paquets\n") if ($res != 0);
	return ($res);
}


###############################################################################
# compare la version passée en paramètre à la version courante
#
# Compare_Version ("version")
#
# Sortie : 
#	-1: version actuelle inférieure à paramètre
#	 0: versions égales
#	 1: version actuelle supérieure à paramètre
##
sub Compare_Version
{
	my ($testver) = @_;

	if ( eval {
		require Kwartz::Product;
		# Compare est présent depuis version 5 de kwartz-core
		my $code = Kwartz::Product->code;
		($code ne "") && Kwartz::Product->can("compare")
		} ) {
		return Kwartz::Product->default->compare($testver);
	} else {

		# BPO fix : la comparaison de chaines est fausse des qu une version >=10 apparait
		# ("10.0r0" lt "9.0r0" en comparaison de chaines). On delegue a dpkg,
		# dont l ordre est celui reellement utilise par apt.
		return -1 if (!defined($ver) || $ver eq "");	# comportement identique a l ancien code ("" lt tout)
		return -1 if (system("dpkg", "--compare-versions", $ver, "lt", $testver) == 0);
		return  1 if (system("dpkg", "--compare-versions", $ver, "gt", $testver) == 0);
		return 0;
	}
} # Compare_Version

###############################################################################
# mise à jour des paquets
#
# Evolution 20150323 : Utilisation du repository ubuntu pour ne pas dupliquer les paquets sur notre ftp
#
#	Dans le principe :
#		Etape 1 (ne change pas):
#			Hold retiré, dist-upgrade avec comme repository :
#				kwartz
#
#					Mise à jour des paquets kwartz et des paquets recompilés
#					On fait ainsi parce que :
#						- Il nous est arrivé de recompiler un paquet avec un tag de version contenant kwartz qui paraissait pour dpkg --compare-versions plus petit que le tag officiel
#						- Si cette dépendance est nouvelle et qu'on met en même temps les repository kwartz et ubuntu c'est le paquet d'origine ubuntu qui sera installé
#						- Une fois ce paquet installé, son tag de version contenant kwartz, il sera protégé par le hold lorsque il sera exposé au repository ubuntu
#					
#					Ne mettra pas à jour si :
#						- Un paquet Kwartz présente une nouvelle dépendance ubuntu
#
#					Problème potentiel :
#						- Si un paquet Kwartz dépend de façon non explicite d'un autre paquet Kwartz qui a des dépendances ubuntu non satisfaites,
#							il y aura une fenêtre plus ou moin longue (lic 1019 pendant 5 heures) où ce paquet ne sera pas fonctionnel.
#						- Si un paquet Kwartz pre-dépend de façon explicite d'un autre paquet Kwartz d'une version particulière,
#						    dans notre exemple kwartz-mdm pre-dépend de kwartz-mdm-core même version,
#							si une nouvelle dépendance empêche la mise à jour de kwartz-mdm mais permet celle de kwartz-mdm-core,
#							kwartz-mdm-core sera mis à jour et kwartz-mdm supprimé
#
#					Attention :
#						- Si on ajoute en dépendance un paquet recompilé, il est nécessaire que toutes les dépendances de ce paquet soient disponibles dans le repository kwartz...
#						    à moins d'être sûr qu'il ne puisse pas y avoir de conflit avec les paquets ubuntu
#
#		Etape 2 :
#			Hold positionné, dist-upgrade avec comme repository :
#				ubuntu
#				security
#				kwartz
#
#					Mise à jour kwartz et sécurité de ce qui est installé.
#					Met à jour les paquets empêchés à l'étape 1.
#					Pour cela, il ne faut pas mettre de hold sur les paquets kwartz-xxx : pas de "kwartz" dans leur tag de version
#
#					Ne mettra pas à jour si : aucun
#					Problèmes potentiels :
#						- sur une nouvelle dépendance, conflit entre un paquet recompilé et un paquet ubuntu
#						si le paquet ubuntu est plus récent il prendra la place du paquet recompilé
#					
#
###############################################################################

sub GetHold
{
	my ($hr) = @_;
	my @pkts;
	my %pkts = ();
	my $rec;


	my @dpkglist;
	eval {
		@dpkglist = `dpkg-query -W`;
	};
	return 0 if ($@);

	foreach(@dpkglist) {
		if (m/(\S+)\s+(\S+)/) {
			$pkts{$1} = "$2";
		}
	}

	foreach(sort keys(%pkts)) {
		next if ($_ =~ m/kwartz/);
		
		if ($pkts{$_} =~ m/kwartz/) {
			push (@$hr, $_);
		}
	
		# pour test
		# if ($_ =~ m/clamav/) {
		# 	push (@$hr, $_);
		# }
	}

	# BPO 20260126
	# hold kwartz-mdm et kwartz-mdm-core à cause de dépendances de kwartz-mdm sur jre8/aapt
	# Ces paquets n'étant pas dans nos dépôts, kwartz-mdm-core peut être mis à jour mais pas kwartz-mdm
	# Vu qu'il y a une dépendance sur la version dans kwartz-mdm, sans hold apt considère qu'il est préférable
	# de supprimer kwartz-mdm plutôt que de garder les deux dans l'ancienne version
	# Modif qui va de pair avec l'inversion du sens du hold pour les paquets kwartz-xxx dans DoHoldApt et dans
	# l'état DoApt de la machine d'états.
	if  (defined($pkts{"kwartz-mdm"}) &&
		(system("dpkg", "--compare-versions", $pkts{"kwartz-mdm"}, 'lt', "2.0.49") == 0) &&
		defined($pkts{"kwartz-mdm-core"}) &&
		(system("dpkg", "--compare-versions", $pkts{"kwartz-mdm-core"}, 'lt', "2.0.49") == 0)
		) {
		push (@$hr, "kwartz-mdm");
		push (@$hr, "kwartz-mdm-core");
	}

	return 1;
}


##########
# DoHoldApt($refapt)
# Entrée :
#	$refapt : Référence vers un couple de paramètres:
#		Paramètre 1: 1 = hold, 0 = install
#		Paramètre 2: Commande apt à exécuter
# Sortie :
#	Sortie de la commande
##
sub DoHoldApt
{
	my ($ref) = @_;
	
	my $dohold = $$ref[0];
	my $aptcmd = $$ref[1];
	
	my @hold;
	my $res=0;
	if (GetHold(\@hold)) {
		my $holdstr;
		# BPO 20260126 : Inversion du sens du Hold pour les paquets kwartz-xxx
		# Quand les paquets qui ont kwartz dans la version mais pas dans le nom passent en hold,
		# ceux qui ont kwartz dans le nom passent en install, et inversement
		# Ceci de manière à forcer un hold sur les paquets kwartz-xxx qui risquent un remove à cause de soucis d'interdépendance
		# 
		foreach(@hold) {
			if ($_ =~ m/kwartz/) {
				$holdstr = $dohold?"install":"hold";
			} else {
				$holdstr = $dohold?"hold":"install";
			}
			
			mysystem ("echo $_ $holdstr | dpkg --set-selections 2>&1");
			my $check = `dpkg --get-selections $_ | awk '{print \$2}'`; chomp($check);
			if ($holdstr ne $check) {
				$res++;
				warn "echec set-selections $_ $holdstr" if ($opt_d);
			}
		}
	
		if ($res == 0) {
			return "" if ($aptcmd eq "");
			# Sans shell si le format de la commande le permet, sinon shell
			# (pipes/redirections). Le pré-test garantit que myback_noshell ne die pas.
			my ($argv) = noshell_argv($aptcmd);
			return $argv ? myback_noshell($aptcmd) : myback($aptcmd);
		} else {
			return "";
		}
	} else {
		return "";
	}
}

##########
# Update()
# Entrée :
#	$info    : 0 = Do Operation, 1 = dry-run
#	$reflist : référence vers un tableau rempli avec la liste des paquets à mettre à jour.
#	@list    : liste éventuelle des paquets à installer
#
# Sortie :
#	0 : OK
#   ?
##
my @statelist = ( 	"Start",
					"AptUpdateAll",
					"CleanKernelPre",
					"TestSize",
					"SpecialHold",
					"DoApt",
					"AptUpdate",
					"CheckLoop",
					"CleanKernel",
					"SpecialCorrections",
					"CheckLoop2",
					"PrepUpdateVersion",
					"UpdateVersion",
					"End"
				);
my %statelist_index;
@statelist_index{ @statelist } = (0 .. $#statelist);

sub Update
{
	my ($info, $reflist, @list) = @_;
	 
	my $res = 0;
	my $badkey = 0;
	my $ninst = 0;
	my $debug = defined($opt_d)?"-o Debug::pkgProblemResolver=yes":"";
	my @updpkts; # Liste pour dédoublonner l'affichage (overlap entre les 2 listes)
	my @rmvpkts1; # Liste des paquets supprimés en liste1
	my @rmvpkts2; # Liste des paquets supprimés en liste2
	
	my $updscript = ""; # Script pour UpdateVersion
	
	
	state $fsmExit;
	state $fsmExitStr;
	state $next_state_num;
	
	$fsmExit = RET_OK;
	$fsmExitStr = "";
	$next_state_num = $statelist_index{ "Start" };

	sub UpdateExecLog
	{
		my ($command, $resmsgref) = @_;
		
		if (defined($opt_v) || defined($resmsgref)) {
			# mode appel cgi pour waitpid
			# Si la commande est compatible, on l'exécute sans shell (forme liste)
			# en fusionnant stdout+stderr côté fils (merge_stderr) ; le « 2>&1 »
			# n'est alors plus nécessaire. Sinon (pipes, redirections), on garde
			# la forme chaîne historique avec « 2>&1 ».
			my ($argv) = noshell_argv($command);
			my $resapt = $argv
				? _ExecLog($argv, flags=>($opt_v?LOG_STDERR:0), trail => 1, resmsgref=>$resmsgref, merge_stderr=>1)
				: _ExecLog("$command 2>&1", flags=>($opt_v?LOG_STDERR:0), trail => 1, resmsgref=>$resmsgref);
			return ($resapt>>8);
		} else { # $opt_v is false
			mysystem ("$command >> $VARLOGFILE 2>&1");
			return ($?>>8);
		} # $opt_v		
	}

	# FSM
	FSM: while ((my $state_num=$next_state_num++) <= $statelist_index{"End"}) {
		my $state=$statelist[$state_num];
		print "\nState: $state\n" if ($opt_d);
		if (defined($gStateHandler)) {
			seek($gStateHandler, 0, SEEK_SET);
			print $gStateHandler "$state_num";
			if (defined $opt_U){
				print STDOUT "State: $state\n";
			}
		}
		
		sub fsmEnd {
			($fsmExitStr, $fsmExit) = @_;
			$next_state_num = $statelist_index{"End"};
			die { fsm_abort => 1 };	# on ne pilote plus le flux via next FSM depuis ici (cf. eval autour du corps de boucle)
		}
		sub fsmNext {
			die { fsm_next => 1 };	# equivalent d un next nu dans la boucle FSM : saute a l etat suivant (cf. eval)
		}
	
		eval {
		if ($state eq "Start") {
			# Initialisations
			my $nelements = $#list+1;
			print ("Update\($info, ..., $nelements elements\)\n") if ($opt_d);
			@updpkts=();
			# pour le pbm de kwartz-mdm-core supprimé par le CD 6.0r2
			mysystem("echo kwartz-mdm-core install | dpkg --set-selections 2>&1");
			#
			# Cleanup samba90 preferences if info launched several times
			my $samba_ver = `dpkg -s samba 2>/dev/null| grep ^Version | awk '{print \$2}'`; chomp $samba_ver;
			#mysystem("dpkg --compare-versions \"$samba_ver\" ge \"2:4.13.17~dfsg-0ubuntu1.20.04.4\"");
			#unlink ("/etc/apt/preferences.d/samba90") if (($?>>8) == 0);
			unlink ("/etc/apt/preferences.d/samba90");
			
			unlink ("/etc/apt/preferences.d/squid410") if (-f "/etc/apt/preferences.d/squid410");
			unlink ("/etc/apt/preferences.d/php74") if (-f "/etc/apt/preferences.d/php74");

			#
			# Kwartz8 / KmcBox3 et pin 1.3 (Noprmalement n'arrive plus)
			if (($ver =~ /^8\..+/ and $type eq "Server") ||
				($ver =~ /^3\..+/ and $type eq "kmcbox") ) {
				eval {
					require Kwartz::Key;

					my $kwartzkey = Kwartz::Key->default();

					my $dohold = 0;
					if ($kwartzkey->licence and $kwartzkey->is_valid) {
						# read pinning info from kwartzkey 
						# protect with if ($kwartzkey->can('tag'))?
						my $pin_kmc_version = eval {$kwartzkey->tag('pin_kmc_version')};
						if ($pin_kmc_version) {
							my ($maj, $min, $rev) = ("*","*","*");
							if ($pin_kmc_version =~ m/^(?:(\d+)\.)?(?:(\d+)\.)?(\*|\d+)$/) {
								$maj = $1 if ($1);
								$min = $2 if ($2);
								$rev = $3 if ($3);
							}
							if (($maj == 1) &&
								(($min ne "*") && ($min < 4)) ) {
									$dohold++;
							}
						}
					}
					my $pkg_status =`dpkg -s libmojolicious-perl | grep '^Status' | awk '{print \$2}'`;  chomp $pkg_status;
					if ( $dohold ) {
						if ($pkg_status eq 'install') {
							mysystem("echo hold mojolicious >> $VARLOGFILE 2>&1");
							mysystem("echo \"libmojolicious-perl  hold\" | dpkg --set-selections >> $VARLOGFILE 2>&1");
						}
					} elsif ($pkg_status eq 'hold') {
						mysystem("echo unhold mojolicious >> $VARLOGFILE 2>&1");
						mysystem("echo \"libmojolicious-perl  install\" | dpkg --set-selections >> $VARLOGFILE 2>&1");
					}
				}
			}
			
			# Cleanup /var
			mysystem ("rm -rf /var/lib/clamav/tmp.*");
			mysystem ("rm -rf /var/lib/clamav/*.tmp");
			
			#  Found 780 Mo /var/cache/bind/core on lic 6842
			mysystem ("rm -f /var/cache/bind/core");
			
			# Interim /tmp liberate when ios_renew_enroll goes mad
			# TODO : fix not sending ios_renew_enroll when certificate is expired. Check on lic 6964.
			mysystem ("ps ax |grep -e \"ios_renew_enroll\" -e \"ios_refresh\" |grep -v \"grep\" | awk \'{print \$1}\' | xargs --no-run-if-empty kill");
			mysystem ("find /tmp -name \"sharefile*\" -delete");
			
			# Kwartz9 : cleanup kwartz-owncloud.list
			if ($ver =~ /^9\..+/ and $type eq "Server") {
				mysystem("rm -f /etc/kwartz/sources.list.d/kwartz-owncloud.list");
			}
			
			# Create temporary filesystem on /home
			my $homefreesize=Get_Free_Size("/home");
			if (( -d "/home/install" ) 						&& 	# Sometimes /home/install is nuked
				( ! -f "/home/install/tmpaptcachefs.img" ) 	&&
				( $homefreesize > 5*1000*1000 )) {
				mysystem ("dd if=/dev/zero of=/home/install/tmpaptcachefs.img bs=1k count=1500000 >/dev/null 2>&1");
				my $loopdev=`/sbin/losetup --show -f /home/install/tmpaptcachefs.img`; chomp $loopdev;
				if ($loopdev =~ m/\/dev\/loop\d+/) {
					mysystem("mkfs.ext4 $loopdev >/dev/null 2>&1");
				}
			} elsif ($homefreesize < 3*1000*1000 ) {	# 3 Go left, time to give up
				my $loopassociated=`losetup -j /home/install/tmpaptcachefs.img`; chomp $loopassociated;
				if ($loopassociated =~ m/^(\/dev\/loop\d+)/) {
					mysystem ("umount $1") if (is_mounted($1));
					mysystem ("/sbin/losetup -d /home/install/tmpaptcachefs.img");
				}
				mysystem ("rm -f /home/install/tmpaptcachefs.img");
			}
			
			if ( -f "/home/install/tmpaptcachefs.img" ) {
				my $loopdev=`/sbin/losetup -j /home/install/tmpaptcachefs.img`; chomp $loopdev;
				if ($loopdev eq "") {
					$loopdev=`/sbin/losetup --show -f /home/install/tmpaptcachefs.img`; chomp $loopdev;
				}
				if ($loopdev =~ m/^(\/dev\/loop\d+)/) {
					mysystem ("mount $1 /var/cache/apt/archives") unless (is_mounted("/var/cache/apt/archives"));
				}
			}
			
			# Kwartz10/KmcBox5 : Pin linux-firmware for TestSize
			# TODO : next split TestSize to use Holds
			if (($ver =~ /^10\..+/ and $type eq "Server") ||
				($ver =~ /^5\..+/ and $type eq "kmcbox") ) {
				my $installed = installed_version("linux-firmware");
				if (($installed =~ /kwartz/) &&
				    !installed_preference("firmware")) {
				    my $pref = <<_MARKER_;
Package: linux-firmware
Pin: version $installed
Pin-Priority: 1000
_MARKER_
					
					do_install_preference("firmware", $pref);
				}
			}
			
			# Kwartz10/KmcBox5 : Pin kernel if / is 2 Go
			if (($ver =~ /^10\..+/ and $type eq "Server") ||
				($ver =~ /^5\..+/ and $type eq "kmcbox")) {
				my $kver = `uname -r`; chomp $kver;
				if (! -f "/lib/modules/$kver/kwartz_compress_done") {
					my $rootsize = Get_Part_Size("/");
					my $installed = installed_version("linux-image-generic-hwe-22.04");
					
					if (($rootsize < 2100000) &&
						!installed_preference("kernel")) {
						
						my $pref = <<_MARKER_;
Package: linux-image-generic-hwe-22.04
Pin: version $installed
Pin-Priority: 1000
_MARKER_
						do_install_preference("kernel", $pref);
					}
				} else {
					do_remove_preference("kernel");
				}
			}
			#
		} elsif ($state eq "AptUpdateAll") {
			if (($info && !$opt_q) ||
				( ! -f $SOURCE ) ||
				( ! system("grep -q @ $SOURCE"))) {
				# Mise à jour de l'ensemble des repositories
				fsmEnd("", RET_APTUPDATE) if (AptUpdate() != 0);
			}
			#
		} elsif ($info && !$opt_q && ($state eq "TestSize")) {
			my $aptcmd = [ 0, "env LANG=C apt-get --assume-no $debug -o Dir::Etc::SourceList=$SOURCE_S -o Dir::Etc::SourceParts='/etc/kwartz/sources.list.d/' dist-upgrade 2>/dev/null" ];

			# Calcul de la taille requise, hold retirés
			my $maxsize = 0;
			# #
			# BPO 20150327
			#  sources_s contient maintenant l'uri kwartz
			# BPO 20150929
			#  Le calcul de taille ne fonctionne pas pour install
			# BPO 20180905
			#  sources_s ne contient maintenant plus l'uri kwartz
			#  l'uri Kwartz déplacée dans sources.list.d/kwartz.list
			# #
			if ($#list==-1) {  # Pas de liste de paquets à installer
				my @size = DoHoldApt($aptcmd);
				my $size = 0; my $mult = "";
				foreach (@size) {
					if (m/Need to get ([0-9]*\.?[0-9]+) (\w?)B of archives/) {
						$size = $1; $mult = $2; 
						if ($mult eq "k") {
							$size *= 1000;
						} elsif ($mult eq "M") {
							$size *= 1000*1000;
						} elsif ($mult eq "G") {
							$size *= 1000*1000*1000;
						}
						$maxsize = $size if ($size > $maxsize);
					}
				}
				
				# Dev: Display free size
				#my $dsize = Get_Free_Size("/");
				#print ("free /     : $dsize\n");
				#$dsize = Get_Free_Size("/var");
				#print ("free /var  : $dsize\n");
				#$dsize = Get_Free_Size("/home");
				#print ("free /home : $dsize\n");

				
				my $cachetarget = "/var";
				$cachetarget = "/var/cache/apt/archives" if is_mounted("/var/cache/apt/archives");
				if (($maxsize + 10*1000*1000) > (Get_Free_Root_Size($cachetarget)*1000)) {
					my $freesize = Get_Free_Root_Size($cachetarget)*1000;
					my $needsize = $maxsize + 10*1000*1000;
					print ("L'espace système /var est trop petit pour faire une mise à jour à distance (besoin: $needsize/$freesize)\n");
					fsmEnd("espace système /var trop petit", RET_VARSIZE);
				}
			
				if (Get_Free_Size("/home") < 500*1000) {
					print ("L'espace utilisateur est trop petit pour faire une mise à jour à distance\n");
					fsmEnd("espace utilisateur est trop petit", RET_HOMESIZE);
				}
				
				# BPO 20200526
				#  Teste 320 Mo de libre sur /
				if (Get_Free_Size("/") < 320*1000) {
					print ("L'espace système / est trop petit pour faire une mise à jour à distance\n");
					fsmEnd("espace système / trop petit", RET_ROOTSIZE);
				}
			}
			# End TestSize -> Mais jamais exécuté
		} elsif ($state eq "SpecialHold") {
			# BPO 04/01/2023 kwartz9
		    # freeradius 3.0.20+dfsg-3ubuntu0.2 ubuntu update breaks 
		    # freeradius-ldap postinst if radius disabled

			if ($ver =~ /^9\..+/ and $type eq "Server") {
				# Status: install ok installed
				# Status: hold ok installed
				my $pkg_status =`dpkg -s freeradius | grep '^Status' | awk '{print \$2}'`;  chomp $pkg_status;
				my $pkg_version=`dpkg -s freeradius | grep '^Version' | awk '{print \$2}'`; chomp $pkg_version;
				
				# clients.conf empty = freeradius disabled
				if (system("dpkg", "--compare-versions", $pkg_version, 'gt', "3.0.20") == 0) {
					if ((! -s '/etc/freeradius/3.0/clients.conf') &&
							($pkg_status eq 'install')) {
						mysystem("echo hold freeradius >> $VARLOGFILE 2>&1");
						mysystem("echo \"freeradius-ldap   hold\" | dpkg --set-selections >> $VARLOGFILE 2>&1");
						mysystem("echo \"freeradius-common hold\" | dpkg --set-selections >> $VARLOGFILE 2>&1");
						mysystem("echo \"freeradius-config hold\" | dpkg --set-selections >> $VARLOGFILE 2>&1");
						mysystem("echo \"freeradius        hold\" | dpkg --set-selections >> $VARLOGFILE 2>&1");
					} elsif ((-s '/etc/freeradius/3.0/clients.conf') &&
							($pkg_status eq 'hold')) {
						mysystem("echo unhold freeradius >> $VARLOGFILE 2>&1");
						mysystem("echo \"freeradius-ldap   install\" | dpkg --set-selections >> $VARLOGFILE 2>&1");
						mysystem("echo \"freeradius-common install\" | dpkg --set-selections >> $VARLOGFILE 2>&1");
						mysystem("echo \"freeradius-config install\" | dpkg --set-selections >> $VARLOGFILE 2>&1");
						mysystem("echo \"freeradius        install\" | dpkg --set-selections >> $VARLOGFILE 2>&1");
					}
				}
			}
			# End SpecialHold
		} elsif ($state eq "DoApt") {
			my @aptcmd = ();
			
			my @prefs = glob("/etc/apt/preferences.d/*{kwartz,kmc}*");
			my $allowdowngrade = "";
			if ($#prefs>=0) {
				$allowdowngrade = $aptsyntax{'allowdowngrade'};
			}
			if ($info) {
				if ($#list==-1) {
					push @aptcmd, ([ 0, "apt-get -qsuy $debug $allowdowngrade -o Dir::Etc::SourceList='-' -o Dir::Etc::SourceParts='/etc/kwartz/sources.list.d/' -o DPKG::Options::=--force-overwrite -o DPKG::Options::=--force-confold dist-upgrade 2>&1" ]) if (-f $SOURCE);
					push @aptcmd, ([ 1, "apt-get -qsuy $debug $allowdowngrade -o Dir::Etc::SourceList=$SOURCE_S -o Dir::Etc::SourceParts='/etc/kwartz/sources.list.d/' -o DPKG::Options::=--force-overwrite -o DPKG::Options::=--force-confold dist-upgrade 2>&1" ]);
				} elsif ($#list>=0) {
					my $installlist = join(" ", @list);
					push @aptcmd, ([1, "env LANG=C apt-get -qsuy $debug $allowdowngrade -o Dir::Etc::SourceList=$SOURCE_S -o Dir::Etc::SourceParts='/etc/kwartz/sources.list.d/' -o DPKG::Options::=--force-overwrite install $installlist 2>&1"]);
				}

				foreach (@aptcmd) {
					# DoHoldApt effectue le Hold et lance la commande Apt
					my @updates = DoHoldApt($_);
					##    Format de sortie
					#  Mise  a jour
					# Inst kwartz-ldap [5.2-5] (5.2-6 kwartz8 [all])
					# Inst kwartz-samba [5.6-5] (5.6-7 kwartz8 [all])
					# Inst kwartz-cdtower [4.2-0] (4.2-1 kwartz8 [all])
					# Inst kwartz-cups [1.1-1] (1.1-2 kwartz8 [all])
					# Inst kwartz-dhcp [4.4.3] (4.4.5 kwartz8 [all])
					# Inst libnss3-nssdb [2:3.28.4-0ubuntu0.16.04.6] (2:3.28.4-0ubuntu0.16.04.10 Ubuntu:16.04/xenial-security [all]) []
					# Inst libnss3 [2:3.28.4-0ubuntu0.16.04.6] (2:3.28.4-0ubuntu0.16.04.10 Ubuntu:16.04/xenial-security [amd64])
					# Inst libssh-gcrypt-4 [0.6.3-4.3ubuntu0.2] (0.6.3-4.3ubuntu0.5 Ubuntu:16.04/xenial-security [amd64])
					# Inst libwireshark-data [2.6.8-1~ubuntu16.04.0] (2.6.10-1~ubuntu16.04.0 Ubuntu:16.04/xenial-security [all])
					# Inst libwsutil9 [2.6.8-1~ubuntu16.04.0] (2.6.10-1~ubuntu16.04.0 Ubuntu:16.04/xenial-security [amd64])
					# Remv php7.4-soap [7.4.3-4ubuntu2.29] [libapache2-mod-php:amd64 php7.4-xml:amd64 ]
					# Remv php7.4-xml [7.4.3-4ubuntu2.29] [libapache2-mod-php:amd64 ]
					#
					#  Installation
					# Inst libipc-run-perl (0.94-1 Ubuntu:16.04/xenial [all])
					
					
					# Dist: en kwartz7 $rfrom="kwartz.iris-tech.com"
					#		en kwartz8 $rfrom="kwartz8"
					#		en kwartz9 $rfrom="autoupdatek/Server/9.0a0/"
					
					# 20210602 bpo kwartz7 suite r8188
					#	Inst squid [3.3.8-1ubuntu6.6~kwartz1] (3.5.12-1ubuntu7.3~kwartz70+2 kwartz.iris-tech.com [amd64]) [squid:amd64 on squid3:amd64] [squid3:amd64 ]
					#	Conf squid-common (3.5.12-1ubuntu7.3~kwartz70+2 kwartz.iris-tech.com [all]) [squid3:amd64 ]
					#	Conf squid:amd64 broken
					#	 Breaks:squid3:amd64
					#	 [squid3:amd64 ]
					#	Inst squid3 [3.3.8-1ubuntu6.6~kwartz1] (3.5.12-1ubuntu7.3~kwartz70+1 kwartz.iris-tech.com [all])
					#   ...
					#	E: Conf Broken squid:amd64
					my $skip_20210602 = 0;
					##

					foreach (@updates) {
						print if ($opt_d);
						my ($pkt, $verf, $vert, $rfrom, $pktarch);
						if ((($pkt, $verf, $vert, $rfrom, $pktarch) = ($_ =~ m/^Inst (\S+) \[(\S+)\] \((\S+) (\S+) \[(\S+)\]\)/)) ||
							(($pkt, $vert, $rfrom, $pktarch) = ($_ =~ m/^Inst (\S+) \((\S+) (\S+) \[(\S+)\]\)/))) {
							$verf = "0" unless defined ($verf);
							$ninst++;
							# On transforme Suite de la forme autoupdatek/Server/9.0a0/ en kwartz9
							if ($rfrom =~ m/autoupdatek\/Server\/(\d+)/) {
								$rfrom = "kwartz$1";
							}
							# On ne garde que xxx-security si Suite Ubuntu:vv.04/xxx-security
							if ($rfrom =~ m/(\S+)\/(\S+)/) {
								$rfrom = (split /,/, $2)[0];
							}
							my $field = "$pkt [$verf] ($vert $rfrom [$pktarch])";
							# Dedoublonnage
							push @updpkts, $field if (! grep (m/^\Q$field\E$/, @updpkts));

							if (($pkt eq "squid") && ($vert eq "3.5.12-1ubuntu7.3~kwartz70+2")) {
								$skip_20210602++;
							}
						}
						next if ((m/^E: Conf Broken squid:amd64/) && $skip_20210602);
						next if  (m/^E: Des paquets ont/);
						if (m/^E:/) {
							print ("Impossible de récupérer l'ensemble des dépendances\n");
							fsmEnd("pbm dependances apt", 1);
						}
					}
				}
				# Renvoie la liste des paquets en attente pour affichage
				#  $reflist est un paramètre de la fonction Update()
				if (defined $reflist) {
					@$reflist = @updpkts;
				}
			} else { # $info is false
				if ($#list==-1) {
					push @aptcmd, ([ 0, "dpkg-query -W --showformat='\${Package}\\t\${Priority}\\n' | grep \"required\$\" |awk '{print \$1}' | env LANG=C xargs apt-get -qy $debug -o Dir::Etc::SourceList='-' -o Dir::Etc::SourceParts='/etc/kwartz/sources.list.d/' -o DPKG::Options::=--force-overwrite -o DPKG::Options::=--force-confold install |grep -v newest 2>/dev/null" ]) if (-f $SOURCE);
					push @aptcmd, ([ 0, "apt-get -qy $debug $allowdowngrade -o Dir::Etc::SourceList='-' -o Dir::Etc::SourceParts='/etc/kwartz/sources.list.d/' -o DPKG::Options::=--force-overwrite -o DPKG::Options::=--force-confold dist-upgrade" ]) if (-f $SOURCE);
					push @aptcmd, ([ 1, "apt-get -qy $debug $allowdowngrade -o Dir::Etc::SourceList=$SOURCE_S -o Dir::Etc::SourceParts='/etc/kwartz/sources.list.d/' -o DPKG::Options::=--force-overwrite -o DPKG::Options::=--force-confold dist-upgrade" ]);
				} elsif ($#list>=0) {
					my $installlist = join(" ", @list);
					push @aptcmd, ([1, "env LANG=C apt-get -qy $debug $allowdowngrade -o Dir::Etc::SourceList=$SOURCE_S -o Dir::Etc::SourceParts='/etc/kwartz/sources.list.d/' -o DPKG::Options::=--force-overwrite install $installlist 2>/dev/null"]);
				}

				$res = 0;
				foreach (@aptcmd) {
					# DoHoldApt() inutilisable ici, pour trois raisons (BPO 2026) :
					#  1. type de retour : DoHoldApt retourne la SORTIE TEXTE de la
					#     commande (ce que veut la branche simulation $info qui la
					#     parse) ; ici il faut le CODE DE SORTIE numérique, accumulé
					#     dans $res pour décider de la branche de récupération.
					#  2. sémantique d'exécution : ici il faut UpdateExecLog (diffusion
					#     temps réel sur STDERR en -v pour la progression CGI, log dans
					#     $VARLOGFILE sinon, resmsgref, UCF_FORCE_CONFOLD) ; DoHoldApt
					#     capture silencieusement.
					#  3. politique d'échec : DoHoldApt ABANDONNE si un set-selections
					#     ne se vérifie pas (juste pour une simulation) ; ici on warn et
					#     on CONTINUE (on n'avorte pas une mise à jour réelle pour ça).
					# Factorisation possible : extraire la boucle commune de pose des
					# holds (SetHolds), chaque site gardant sa politique et son exécuteur.
					my $dohold = $$_[0];
					my $aptcmd = $$_[1];
					
					my @hold;
					if (GetHold(\@hold)) {
						my $holdstr;
						foreach(@hold) {
							# BPO 20260126 : Voir commentaire dans DoHoldApt()
							if ($_ =~ m/kwartz/) {
								$holdstr = $dohold?"install":"hold";
							} else {
								$holdstr = $dohold?"hold":"install";
							}
							mysystem ("echo $_ $holdstr | dpkg --set-selections 2>&1");
							my $check = `dpkg --get-selections $_ | awk '{print \$2}'`; chomp($check);
							if ($holdstr ne $check) {
								warn "echec set-selections $_ $holdstr" if ($opt_d);
							}
						}
					
						print "$aptcmd\n" if ($opt_d);

						$ENV{UCF_FORCE_CONFOLD}="1";
						$res += UpdateExecLog($aptcmd);
					}
				}
				
				# Autocorrectif
				SelfHeal();

				# Check
				if ($res != 0) {
					
					# On peut continuer ?
					my @dpkgc = myback_noshell("env LANG=C dpkg -C");
					my @dpkgko;
					my @dpkgtrigger;

					if ( $#dpkgc >= 0) {
						my $trigger = 0;
	
						foreach (@dpkgc) {
							if (m/The following packages have been triggered/) {
								$trigger = 1;
							}
							if ($trigger == 0) {
								push (@dpkgko, $1) if (m/^ (\S+)/);
							} else {
								push (@dpkgtrigger, $1) if (m/^ (\S+)/);
							}
						}
					}
					
					foreach (@dpkgtrigger) {
						mysystem("dpkg --triggers-only $_ >> $VARLOGFILE 2>&1");
					}

					if ( $#dpkgko >= 0) {
						print "Une erreur est survenue lors de la mise à jour d'un composant\n";
						# TODO: return ? exit ? voir ce qui est le mieux
						return 1;
					}
				}
			} # $info
			
			# On sort si option -g / -G
			$next_state_num = $statelist_index{"End"} if ($#list != -1);
			
			# End DoApt
		} elsif (!$info && ($state eq "AptUpdate")) {
			AptUpdateKwartz();
			#
		} elsif (!$info && (($state eq "CheckLoop") || ($state eq "CheckLoop2"))) {
			state @lastupgradeable;
			state @lastdpkglist;
			eval "use Data::Compare; 1;" or fsmNext();

			my @aptcmd = ();
			push @aptcmd, ([ 0, "apt-get -qqsuy $debug -o Dir::Etc::SourceList='-' -o Dir::Etc::SourceParts='/etc/kwartz/sources.list.d/' -o DPKG::Options::=--force-overwrite -o DPKG::Options::=--force-confold dist-upgrade 2>/dev/null" ]) if (-f $SOURCE);
			push @aptcmd, ([ 1, "apt-get -qqsuy $debug -o Dir::Etc::SourceList=$SOURCE_S -o Dir::Etc::SourceParts='/etc/kwartz/sources.list.d/' -o DPKG::Options::=--force-overwrite -o DPKG::Options::=--force-confold dist-upgrade 2>/dev/null" ]);

			my @upgradeableunsorted = ();
			my @upgradeable = ();
			my @cachelist = ();

			#TODO: factoriser
			foreach (@aptcmd) {
				# DoHoldApt effectue le Hold et lance la commande Apt
				my @updates = DoHoldApt($_);
				## Format de sortie
				# Inst kwartz-ldap [5.2-5] (5.2-6 kwartz8 [all])
				# Inst kwartz-samba [5.6-5] (5.6-7 kwartz8 [all])
				# Inst kwartz-cdtower [4.2-0] (4.2-1 kwartz8 [all])
				# Inst kwartz-cups [1.1-1] (1.1-2 kwartz8 [all])
				# Inst kwartz-dhcp [4.4.3] (4.4.5 kwartz8 [all])
				# Inst libnss3-nssdb [2:3.28.4-0ubuntu0.16.04.6] (2:3.28.4-0ubuntu0.16.04.10 Ubuntu:16.04/xenial-security [all]) []
				# Inst libnss3 [2:3.28.4-0ubuntu0.16.04.6] (2:3.28.4-0ubuntu0.16.04.10 Ubuntu:16.04/xenial-security [amd64])
				# Inst libssh-gcrypt-4 [0.6.3-4.3ubuntu0.2] (0.6.3-4.3ubuntu0.5 Ubuntu:16.04/xenial-security [amd64])
				# Inst libwireshark-data [2.6.8-1~ubuntu16.04.0] (2.6.10-1~ubuntu16.04.0 Ubuntu:16.04/xenial-security [all])
				# Inst libwsutil9 [2.6.8-1~ubuntu16.04.0] (2.6.10-1~ubuntu16.04.0 Ubuntu:16.04/xenial-security [amd64])
				foreach (@updates) {
					if (my ($pkt, $verf, $vert, $rfrom, $pktarch) = ($_ =~ m/^Inst (\S+) \[(\S+)\] \((\S+) (\S+) \[(\S+)\]\)/)) {
						$ninst++;
						# On transforme Suite de la forme autoupdatek/Server/9.0a0/ en kwartz9
						if ($rfrom =~ m/autoupdatek\/Server\/(\d+)/) {
							$rfrom = "kwartz$1";
						}
						# On ne garde que xxx-security si Ubuntu:vv.04/xxx-security
						if ($rfrom =~ m/(\S+)\/(\S+)/) {
							$rfrom = (split /,/, $2)[0];
						}
						my $field = "$pkt [$verf] ($vert $rfrom [$pktarch])";
						# Dedoublonnage
						push @upgradeableunsorted, $field if (! grep (m/^\Q$field\E$/, @upgradeableunsorted));
					}
				}
			}
			@upgradeable = sort (@upgradeableunsorted);

				
			my @dpkglist = myback("dpkg -l | grep -a '^i' | awk '{print \$2\"_\"\$4\"_\"\$3}'");
			
			#foreach(@upgradeable) {
			#	print;
			#}
			
			if (($#upgradeable >= 0) && 
				(($#lastupgradeable == -1) ||
					(! Compare(\@lastupgradeable, \@upgradeable) ||
						! Compare(\@lastdpkglist, \@dpkglist))
				)) {

				print Compare(\@lastupgradeable, \@upgradeable)?"":"Compare_upgradeable\n" if ($opt_d);
				print Compare(\@lastdpkglist, \@dpkglist)?"":"Compare_dpkglist\n" if ($opt_d);
				
				###
				# Boucle -> Etat suivant.
				$next_state_num = $statelist_index{ "DoApt" };
				###
			}  else {
				# Recreate cache
				my $tmp_file = "$UPDATE_CACHE.tmp";
				if (open (my $fh, ">", "$tmp_file")) {
					foreach (@cachelist) {
						print $fh "$_\n";
					}
					if (close $fh) {
						move $tmp_file, $UPDATE_CACHE;
					}
				}
			}
			@lastupgradeable = @upgradeable;
			@lastdpkglist = @dpkglist;
			# End CheckLoop / CheckLoop2
		} elsif (( $info && ($state eq "CleanKernelPre")) ||
				 (!$info && ($state eq "CleanKernel"))) {
			# /sbin/update-kwartz-kernel no more called via postinst_hook value in /etc/kernel-img.conf
			# since precise linux-image-4.4.0-143-generic
			# causes bug #1899
			if (! -f "/var/kwartz/schedule_remove_kernel") {
				mysystem("/sbin/update-kwartz-kernel 0 / >> $VARLOGFILE 2>&1");
			}
			if (-f "/var/kwartz/schedule_remove_kernel") {
				mysystem("dpkg --purge `cat /var/kwartz/schedule_remove_kernel` >> $VARLOGFILE 2>&1");
				unlink("/var/kwartz/schedule_remove_kernel");
			}
			# Kwartz10/Kmcbox5 : Keep only 1 kernel
			if (($ver =~ /^10\..+/ and $type eq "Server") ||
				($ver =~ /^5\..+/ and $type eq "kmcbox") ) {
				if (($krel =~ m/6.8/) &&
					installed_version("linux-image-5.4*")) {
					mysystem("/sbin/update-kwartz-kernel -1 / >> $VARLOGFILE 2>&1");
				}
			}
			# Compress unused modules into current kernel
			if (($ver =~ /^10\..+/ and $type eq "Server") ||
				($ver =~ /^5\..+/ and $type eq "kmcbox") ) {
				my $kver = `uname -r`; chomp $kver;
				if (! -f "/lib/modules/$kver/kwartz_compress_done") {
					mysystem ("find /lib/modules/$kver/ -name \"*.ko\" \$(printf \"! -name %s \" \$(cat /proc/modules |awk '{print \$1}' | sed s/\$/.ko/))  -exec zstd -q --rm {} \\;");
					mysystem ("depmod -a");
					mysystem ("update-initramfs -u -k $kver");
					mysystem("touch \"/lib/modules/$kver/kwartz_compress_done\"");
					do_remove_preference("kernel");
				}
			}
			
			#
		} elsif ($state eq "SpecialCorrections") {
			Special_Corrections($info, $reflist);
			#
		} elsif ($state eq "PrepUpdateVersion") {
			fsmNext() if (!defined($opt_U));
			
			(-f $gIso) || fsmEnd("L'image iso $gIso est indisponible", RET_PRE_UPDATEVERSION);
			
			my $isoinfo = myback("/sbin/blkid -o value -s LABEL $gIso");
			($?>>8 == 0) || fsmEnd("L'image iso $gIso est incorrecte", RET_PRE_UPDATEVERSION);
			my ($isoprod,$isover) = split (/ /, $isoinfo);
			chomp $isoprod;
			chomp $isover;
			
			# Versions gérées :
			#	KWARTZ
			#		8.0r0
			#		8.0r0+1
			#		9.0r0
			#		9.0r0+1
			#	KmcBox
			#		3.0r0
			##
			if ($type eq "Server") {
				if ($ver =~ /^7\..+/) {
					$updscript = "/etc/yasdi/update_kwartz7.sh";
					if (($isoprod ne "KWARTZ") || ($isover !~ /^8\..+/)) {
						fsmEnd("L'image iso ne correspond pas : $type $ver -> $isoprod $isover", RET_PRE_UPDATEVERSION);
					}
				}
				if ($ver =~ /^8\..+/) {
					$updscript = "/etc/yasdi/update_kwartz8.sh";
					if (($isoprod ne "KWARTZ") || ($isover !~ /^9\..+/)) {
						fsmEnd("L'image iso ne correspond pas : $type $ver -> $isoprod $isover", RET_PRE_UPDATEVERSION);
					}
				}
				if ($ver =~ /^9\..+/) {
					$updscript = "/etc/yasdi/update_kwartz9.sh";
					if (($isoprod ne "KWARTZ") || ($isover !~ /^10\..+/)) {
						fsmEnd("L'image iso ne correspond pas : $type $ver -> $isoprod $isover", RET_PRE_UPDATEVERSION);
					}
				}
				if ($ver =~ /^10\..+/) {
					$updscript = "/etc/yasdi/update_kwartz9.sh";
					if (($isoprod ne "KWARTZ") || ($isover !~ /^10\..+/)) {
						fsmEnd("L'image iso ne correspond pas : $type $ver -> $isoprod $isover", RET_PRE_UPDATEVERSION);
					}
				}
			} elsif ($type eq "kmcbox") {
				if ($ver =~ /^2\..+/) {
					$updscript = "/etc/yasdi/update_kmcbox2.sh";
					if (($isoprod ne "KmcBox") || ($isover !~ /^3\..+/)) {
						fsmEnd("L'image iso ne correspond pas : $type $ver -> $isoprod $isover", RET_PRE_UPDATEVERSION);
					}
				}
				if ($ver =~ /^3\..+/) {
					$updscript = "/etc/yasdi/update_kmcbox3.sh";
					if (($isoprod ne "KmcBox") || ($isover!~ /^4\..+/)) {
						fsmEnd("L'image iso ne correspond pas : $type $ver -> $isoprod $isover", RET_PRE_UPDATEVERSION);
					}
				}
				if ($ver =~ /^4\..+/) {
					$updscript = "/etc/yasdi/update_kmcbox4.sh";
					if (($isoprod ne "KmcBox") || ($isover!~ /^5\..+/)) {
						fsmEnd("L'image iso ne correspond pas : $type $ver -> $isoprod $isover", RET_PRE_UPDATEVERSION);
					}
				}
				if ($ver =~ /^5\..+/) {
					$updscript = "/etc/yasdi/update_kmcbox4.sh";
					if (($isoprod ne "KmcBox") || ($isover!~ /^5\..+/)) {
						fsmEnd("L'image iso ne correspond pas : $type $ver -> $isoprod $isover", RET_PRE_UPDATEVERSION);
					}
				}
			
			}
			
			if ($updscript eq "") {
				fsmEnd("Le script de mise à jour est indéfini", RET_PRE_UPDATEVERSION);
			}
			
			if ($info) {
				
				print ("Mise_à_jour_de_version [$ver] ($isover download.kwartz.com)");
			} else {
				if (open (my $logfh, ">>", $VARLOGFILE)) {
					print $logfh "Mise à jour de version de $ver vers $isover\n";
					close $logfh;
				}
			
				$res = 0;
				# Do checks
				# enum retcode { UPD_K7_OK, UPD_K7_SERVICES, UPD_K7_VERSION, UPD_K7_LOCK, UPD_K7_INSSERV, UPD_K7_APTUPDATE, UPD_K7_SIMULATE, UPD_K7_APT, UPD_K7_KMC, UPD_K7_NOLICENCE, UPD_K7_LICENCEKO, UPD_K7_MEM }
				use constant {
					UPD_K7_OK 			=>  0, # OK
					UPD_K7_SERVICES		=>  1, # Certains services sont en alerte
					UPD_K7_VERSION		=>  2, # autoupdate à faire (A priori on ne tombe pas dedans)
					UPD_K7_LOCK			=>  3, # une autre installation/vérification est en cours
					UPD_K7_INSSERV		=>  4, # Soucis avec headers de services
					UPD_K7_APTUPDATE	=>  5, # Impossible de mettre à jor la liste des paquets
					UPD_K7_SIMULATE		=>  6, # Soucis avec la simulation de mise à jour
					UPD_K7_APT			=>  7, # La mise à jour a échoué
					UPD_K7_KMC			=>  8, # La licence kmc ne permet pas la mise à jour
					UPD_K7_NOLICENCE	=>  9, # Pas de licence
					UPD_K7_LICENCEKO	=> 10, # licence incorrecte
					UPD_K7_MEM			=> 11, # Moins de 2 Go de mémoire
					UPD_K7_ROOTSIZE		=> 12, # / inférieure à 2 Go
					UPD_K7_FREESPACE2	=> 13, # alerte FREESPACE2 (système)
					UPD_K7_FREESPACE1	=> 14, # alerte FREESPACE1 (utilisateur)
					UPD_K7_VERMODIFIED	=> 15, # modification /etc/debian_version
					UPD_K7_CONFIGFAIL	=> 16, # erreur test_config
				};
				my @errmsg = ();
				if ($res = UpdateExecLog("$updscript test_services", \@errmsg)) {
					if ($#errmsg > 1) { # Must be 3 lines min
						# Last line of $updscript is date when called in pipe context
						# Previous one is generic message
						# Previous one is last service tested
						if ($errmsg[$#errmsg-2] eq "Redemarrage en attente") {
							fsmEnd("Le serveur doit être redémarré", RET_PRE_UPDATEVERSION);
						}
						if ($errmsg[$#errmsg-2] eq "Bases MySQL a corriger") {
							fsmEnd("Les bases MySQL doivent être corrigées", RET_PRE_UPDATEVERSION);
						}
						if ($errmsg[$#errmsg-2] eq "Certificat MDM a renouveler") {
							fsmEnd("Le certificate MDM ne s'est pas renouvelé", RET_PRE_UPDATEVERSION);
						}
                    } 
					fsmEnd("Des services sont en alerte. La mise à jour ne peut débuter", RET_PRE_UPDATEVERSION);
				}
				if ($res = UpdateExecLog("$updscript test_config", \@errmsg)) {
					if ($res == UPD_K7_MEM) {
						fsmEnd("Vous devez disposer au minimum de 2 Go de RAM pour mettre à jour", RET_PRE_UPDATEVERSION);
					} elsif ($res == UPD_K7_ROOTSIZE) {
						fsmEnd("Vous devez disposer d'une partition racine de 2 Go pour mettre à jour.", RET_PRE_UPDATEVERSION);
					} elsif ($res == UPD_K7_FREESPACE2) {
						if ($#errmsg > 1) { # Must be 2 lines min
							# Last line of $updscript is date when called in pipe context
							# Previous one is message
							fsmEnd($errmsg[$#errmsg-1], RET_PRE_UPDATEVERSION);
						} else { 
							fsmEnd("L'espace système disponible est trop petit pour mettre à jour", RET_PRE_UPDATEVERSION);
						}
					} elsif ($res == UPD_K7_FREESPACE1) {
						if ($#errmsg > 1) { # Must be 2 lines min
							# Last line of $updscript is date when called in pipe context
							# Previous one is message
							fsmEnd($errmsg[$#errmsg-1], RET_PRE_UPDATEVERSION);
						} else { 
							fsmEnd("L'espace utilisateur disponible est trop petit pour mettre à jour", RET_PRE_UPDATEVERSION);
						}
					} elsif ($res == UPD_K7_VERMODIFIED) {
						fsmEnd("Le fichier de version a été modifié manuellement", RET_PRE_UPDATEVERSION);
					} else {
						fsmEnd("Un soucis de configuration du serveur interdit la mise à jour", RET_PRE_UPDATEVERSION);
					}
				}
				
				# Select iso
    			if ($res = UpdateExecLog("$updscript select_iso $gIso")) {
					fsmEnd("Impossible d'accéder à l'iso de Kwartz téléchargée", RET_PRE_UPDATEVERSION);
    			}
				
				@errmsg = ();
				if ($res = UpdateExecLog("$updscript test_versions", \@errmsg)) {
					if ($res == UPD_K7_VERSION) {
						if ($#errmsg > 0) { # Must be 2 lines min
							# Last line of $updscript is date when called in pipe context
							fsmEnd($errmsg[$#errmsg-1], RET_PRE_UPDATEVERSION);
						} else {
							fsmEnd("Vous devez faire les mises à jour à distance avant d'actualiser la version de Kwartz", RET_PRE_UPDATEVERSION);
						}
					} elsif ($res == UPD_K7_KMC) {
						fsmEnd("Votre licence KMC ne vous permet pas de faire cette mise à jour de kwartz", RET_PRE_UPDATEVERSION);
					} elsif ($res == UPD_K7_NOLICENCE) {
						fsmEnd("L'absence de licence ne vous permet pas de faire cette mise à jour de kwartz", RET_PRE_UPDATEVERSION);
					} elsif ($res == UPD_K7_LICENCEKO) {
						fsmEnd("Votre licence kwartz est invalide", RET_PRE_UPDATEVERSION);
					} else {
						fsmEnd("Un problème de vérification a été rencontré", RET_PRE_UPDATEVERSION);
					}
				}

				# Remove Holds
				DoHoldApt([0, ""]);
				
				# Simulate
    			if ($res = UpdateExecLog("$updscript update_simulate")) {
					fsmEnd("La simulation de mise à jour a échoué", RET_PRE_UPDATEVERSION);
    			}
    		}	
		} elsif ($state eq "UpdateVersion") {
			if (! $info) {
				# Really update
				fsmNext() if (!defined($opt_U));
				
				#  Set /run flag
				mysystem("touch /run/kwartz-update");
				#  Stop mon
				mysystem("invoke-rc.d mon stop >> $VARLOGFILE 2>&1");
				myback("sed -e 's/ENABLED=.*/ENABLED=\"no\"/' -i /etc/default/mon");
				#  Launch
				$res = UpdateExecLog("$updscript update");
				#  Start mon
				myback("sed -e 's/ENABLED=.*/ENABLED=\"yes\"/' -i /etc/default/mon");
				mysystem("invoke-rc.d mon start >> $VARLOGFILE 2>&1");
				#  Unset /run flag
				unlink("/run/kwartz-update");
				
				# $res est le résultat de update
				# Veiller à ce qu'il ne soit pas modifié entre xxx update et ici
				if ($res) {
					fsmEnd("La mise à jour a échoué", RET_UPDATEVERSION);
				} 

				# Suggests reboot
				mysystem("touch /var/run/reboot-required");
				mysystem("echo kwartz-dist-upgrade >> /var/run/reboot-required.pkgs");
			}
			#
		}
		# state CheckLoop2 is here (See CheckLoop)
		elsif ($state eq "End") {
			if ((Compare_Version("6.0r2")<=0) and $type eq "Server") {
				mysystem ("echo kwartz-mdm-core hold | dpkg --set-selections 2>&1");
			}
			
			# Retire les Hold
			DoHoldApt([0, ""]);
			
			mysystem ("apt-get autoclean >/dev/null 2>&1");
		}
		};
		if (my $fsm_err = $@) {
			die $fsm_err unless (ref($fsm_err) eq "HASH" && ($fsm_err->{fsm_abort} || $fsm_err->{fsm_next}));
			# Sortie normale via fsmEnd() : $next_state_num pointe deja sur "End", la boucle FSM continue normalement.
		}
	}
	#TODO: Ce n'est pas propre de mettre un exit ici
	myexitlog($fsmExitStr, $fsmExitStr, $fsmExit) if ($fsmExit != 0);
	return 0;
}

sub url_to_list
{
    my ($url) = @_;
    $url =~ s/^(http|ftp):\/\///;
    $url =~ s/\//_/g;
	$url =~ s/\.gz$//;
    $url =~ s/\.bz2$//;
	return $url;
}

sub url_to_uri
{
	my ($url) = @_;
	my $uri = "";

	$uri = "deb $1 $2 $3" if ($url =~ m/^((?:http|ftp):\/\/[\w.]+)\/dists\/([\w\/]+)\/(main|contrib|non-free)\/binary-i386/);
	return $uri;
}

# Mise a jour du kernel
# Le dernier truc a changer, apres on redemarre
sub Kernel
{
	my ($info) = @_;
	my $uri = "deb [trusted=yes] https://$upd_user\:$upd_pass\@kwartz.iris-tech.com/update/   $autoupdate$restricted/$type/$ver/";
	my $res = 0;
	my $badkey = 0;
	my $kernel= "kernel-image-2.6";
	my $kerneldeb = "";
	my @kernels;
	my $issmp = 0;
	my $kernelinstdebver = 0;

	$kernel = "kernel-image-2.4" if ($krel =~ m/2.4/);

	
	my @updates = `apt-cache -o Dir::Etc::SourceList=$SOURCE --names-only search $kernel 2>/dev/null`;
	# boucle sur tous les noyaus dispos
	# même ceux installés
	# quelle que soit leur version
   	foreach (@updates) {
		chomp;
		s/ .*//;
		# on saute les noyaux installés pour l'instant indépendamment de leur version debian
		# sur kwartz on incrémente les version du noyau, pas du paquet
#		(my $k1 = $_) =~ s/$/smp/;
#		(my $k2 = $_) =~ s/smp//;
#		next if (`dpkg -s $_ 2>/dev/null | grep " installed"`);
#		next if (`dpkg -s $k1 2>/dev/null | grep " installed"`);
#		next if (`dpkg -s $k2 2>/dev/null | grep " installed"`);
	# teste la version du kernel	
	if (m/image-(\S+)/) {
			next if ($1 lt $krel)
		}
   		push @kernels,$_;
	}
	$issmp = 1 if ($kver =~ m/SMP/);
	my @k;
	if ($issmp) {
		@k = grep (/smp/,@kernels);
	}
	if (($#k < 0) || ($issmp == 0)) {
		@k = grep (!/smp/,@kernels);
	}
	return 0 if ($#k < 0);
	$kerneldeb = $k[0];
	my @fullinfo = `apt-cache -o Dir::Etc::SourceList=$SOURCE --names-only --full search $kerneldeb 2>/dev/null`;
	my $kerneldebver;
	foreach (@fullinfo) {
		if (m/^Version: (\S+)$/) {
			$kerneldebver = $1;
			last;
		}
	}
	# test de la version debian
	if (mysystem ("dpkg -s $kerneldeb 2>/dev/null | grep -q ' installed'") == 0) {
		my @fulldeb = `dpkg -s $kerneldeb`;
		foreach (@fulldeb) {
			if (m/^Version: (\S+)$/) {
				$kernelinstdebver = $1;
				last;
			}
		}
		return 0 if ($kernelinstdebver eq "");
		return 0 if (mysystem("dpkg --compare-versions $kerneldebver le $kernelinstdebver") == 0);
	}
	if ($info) {
		print "$kerneldeb ($kerneldebver kwartz.iris-tech.com)\n";
	} else {
		mysystem ("apt-get -qmy -o Dir::Etc::SourceList=$SOURCE -o DPKG::Options::=--force-overwrite -o DPKG::Options::=--force-confold install $kerneldeb >> $VARLOGFILE 2>&1");
		my $res = ($?>>8);
		mysystem ("apt-get autoclean >/dev/null 2>&1");
		if ($res != 0) {
			print "Impossible de mettre à jour le noyau linux : $kerneldeb\n";
			return 1;
		}
	}
	return 0;
}

# Get_Part_Size
#  Renvoie l'espace total en Mo fourni par df -m 
#  df -m calcule ainsi :
#   total [1]
#   used  [2]
#   free  [3]
##
sub Get_Part_Size
{
	my $part = shift;

	my @diskused = `env LANG=C df -m $part`;
	my $fs = 0;
	foreach (@diskused) {
    	next if (! m/^\//);
	    my @fields = split;
    	$fs += $fields[1]*1000;
	}
	return $fs;
}

# Get_Free_Size
#  Renvoie l'espace libre user en Mo fourni par df -m 
#  df -m calcule ainsi :
#   total
#	used = total - available_to_root
#	free = available
##
sub Get_Free_Size
{
	my $part = shift;

	my @diskused = `env LANG=C df -m $part`;
	my $fs = 0;
	foreach (@diskused) {
    	next if (! m/^\//);
	    my @fields = split;
    	$fs += $fields[3]*1000;
	}
	return $fs;
}

# Get_Free_Root_Size
#  Renvoie l'espace libre pour root en Mo fourni par df -m 
sub Get_Free_Root_Size
{
	my $part = shift;

	my @diskused = `env LANG=C df -m $part`;
	my $fs = 0;
	foreach (@diskused) {
    	next if (! m/^\//);
	    my @fields = split;
    	$fs += ($fields[1]-$fields[2])*1000;
	}
	return $fs;
}

# Get_Used_Dir_Size
#  Renvoie l'espace utilisé en Mo par du -sm
##
sub Get_Used_Dir_Size
{
	my $dir = shift;

	my $diskused = `du -smx $dir`; chomp $diskused;
	return (split(' ', $diskused))[0];
}


# is_mounted
#  Renvoie vrai si partition montée
sub is_mounted
{
	my $part = shift;
	my $partmounted = 0;
	my $procfh;
	eval {
		open ($procfh, "<", "/proc/mounts");
	};
	if (!$@) {
		foreach (<$procfh>) {
			$partmounted++ if (m/$part/);
		}
		close $procfh;
	}
	return ($partmounted);
}


sub Special_Corrections
{
    my ($info, $reflist) = @_;
	my $cor = 0;

	# correction nut
	if ((Compare_Version("7.0")<0) and (Compare_Version("6.0r1")>=0) and $type eq "Server") {
	    my $v1=`dpkg -s nut 2>/dev/null| grep ^Version | awk '{print \$2}'`; chomp $v1;
    	mysystem("dpkg --compare-versions \"$v1\" gt \"2.6.3-1ubuntu1.1kwartz1\"");
	    if (($?>>8) == 0) {
			if ($info) {
				$cor++;
				print "kwartz-correctif-nut [0.0] (20141210 correctif_intégré)\n";
			} else {
				system ("echo Correctif intégré nut >> $VARLOGFILE");
	    	    mysystem ("apt-get -qy --force-yes --ignore-hold -o Dir::Etc::SourceList=$SOURCE -o DPKG::Options::=--force-overwrite -o DPKG::Options::=--force-confold install nut=2.6.3-1ubuntu1.1kwartz1 nut-cgi=2.6.3-1ubuntu1.1kwartz1 nut-server=2.6.3-1ubuntu1.1kwartz1 nut-client=2.6.3-1ubuntu1.1kwartz1 >> $VARLOGFILE 2>&1");
				mysystem("/etc/init.d/nut-server restart");
			}
    	}
	} # nut

	# correction maj secu sqlite
	if ((Compare_Version("5.0r0")>=0) and $type eq "Server") {
		my $v1=`dpkg -s libsqlite3-0:amd64 2>/dev/null| grep ^Version | awk '{print \$2}'`; chomp $v1;
        if ("$v1" eq "" or "$v1" eq "3.7.9-2ubuntu1.1") {
			my $v2=`dpkg -s libsqlite3-0 2>/dev/null| grep ^Version | awk '{print \$2}'`; chomp $v2;
			if ("$v2" gt "3.7.9-2ubuntu1.1") {
				if ($info) {
					$cor++;
					print "kwartz-correctif-sqlite [0.0] (20150805 correctif_intégré)\n";
				} else {
					system ("echo Correctif intégré sqlite >> $VARLOGFILE");
					DoOptaCommand("update");
					DoOptaCommand("install", "", "--ignore-hold", ("sqlite3=3.7.9-2ubuntu1.1", "libsqlite3-0=3.7.9-2ubuntu1.1"));
				}
			}
		}
		if ((Compare_Version("6.0r3")<0) and "$v1" gt "3.7.9-2ubuntu1.1") {
			if ($info) {
            	$cor++;
                print "kwartz-correctif-sqlite [0.0] (20150805 correctif_intégré)\n";
			} else {
				system ("echo Correctif intégré sqlite >> $VARLOGFILE");
				DoOptaCommand("update");
				DoOptaCommand("install", "", "--ignore-hold", ("sqlite3=3.7.9-2ubuntu1.1", "libsqlite3-0=3.7.9-2ubuntu1.1", "libsqlite3-0:amd64=3.7.9-2ubuntu1.1"));
			}
		}
		if ((Compare_Version("6.0r3")>=0)) {
			mysystem ("echo sqlite3 install | dpkg --set-selections 2>&1");
			mysystem ("echo libsqlite3-0 install | dpkg --set-selections 2>&1");
			mysystem ("echo libsqlite3-0:amd64 install | dpkg --set-selections 2>&1");
		} else {
			mysystem ("echo sqlite3 hold | dpkg --set-selections 2>&1");
			mysystem ("echo libsqlite3-0 hold | dpkg --set-selections 2>&1");
			mysystem ("echo libsqlite3-0:amd64 hold | dpkg --set-selections 2>&1");
		}		
	}

	# correction ulogd
	# Nécessaire pour les noyaux 64 bits (à partir de 5.2r0)
	if ((Compare_Version("5.2r0")>=0) and (Compare_Version("7.0a0")<0) and $type eq "Server") {
		my $v1=`dpkg -s ulogd:amd64 2>/dev/null| grep ^Version | awk '{print \$2}'`; chomp $v1;
		if ("$v1" eq "") {
			if ($info) {
				$cor++;
				print "kwartz-correctif-ulogd [0.0] (20141210 correctif_intégré)\n";
			} else {
				system ("echo Correctif intégré ulogd >> $VARLOGFILE");
				DoOptaCommand("update");
				DoOptaCommand("install", "", "--ignore-hold", ("libsqlite3-0:amd64"));
				DoOptaCommand("install", "", "--ignore-hold", ("ulogd:amd64"));
				DoOptaCommand("install", "", "--ignore-hold", ("ulogd-sqlite3:amd64"));
				mysystem ("dpkg --force-confold --configure -a >> $VARLOGFILE 2>&1");
			}
		}
	} # ulogd

	# Corrections perms /home
	if ((Compare_Version("5.2r0")>=0) and $type eq "Server") {
		my $sb = stat("/home");
		if ($sb->mode & S_IWGRP) {
			if ($info) {
				$cor++;
				print "kwartz-correctif-perms [0.0] (20141210 correctif_intégré)\n";
			} else {
				system ("echo Correctif intégré perms /home >> $VARLOGFILE");
				mysystem("chmod 0755 /home >> $VARLOGFILE 2>&1");
			}
		}
	}	# perms /home

	# Correction UUID swap
	if ((Compare_Version("5.0r0")>=0) and $type eq "Server") {
		# Test UUID /dev/xxx2
		my $swap=`/sbin/blkid -t TYPE="swap"`;
		my $swapuuid="";
		my $swapname="";
		my $fstabko = 0;
		my $uuid;

		$swapuuid=$1 if ($swap =~ m/UUID=(\S+)/);
		$swapname=$1 if ($swap =~ m/(^\S+):/);
	
		if ($swap ne "" && $swapuuid eq "") {
			if ($info) {
				$cor++;
				print "kwartz-correctif-swap [0.0] (20141216 correctif_intégré)\n";
			} else {
				system ("echo Correctif intégré uuid swap >> $VARLOGFILE");
				$uuid=`uuidgen`; chomp $uuid;
				mysystem("swaplabel -U $uuid $swapname  >> $VARLOGFILE 2>&1") if ($swapname ne "");
			}
		}
			
		# Test fstab
		my $fstabfh;
		eval {
			open ($fstabfh, "<", "/etc/fstab");
		};
		if (!$@) {
			foreach (<$fstabfh>) {
				$fstabko++ if (m/UUID=\s+none\s+swap/);
			}
			close $fstabfh;
		}

		if ($fstabko) {
			if ($info && defined($swapuuid) ) {
				$cor++;
				print "kwartz-correctif-swapfstab [0.0] (20140209 correctif_intégré)\n";
			}
			if (!$info && (defined($swapuuid) || $uuid)) {
				system ("echo Correctif intégré uuid fstab swap >> $VARLOGFILE");
				$uuid = $swapuuid if (!$uuid);
				mysystem("sed -e '/^UUID= none/s/UUID=/UUID=${uuid}/' -i.bak /etc/fstab >> $VARLOGFILE 2>&1");
				if ($? == 0) {
					unlink("/etc/fstab.bak");
					mysystem("swapon -a");
				} else {
					mysystem("mv -vf /etc/fstab.bak /etc/fstab >> $VARLOGFILE 2>&1");
				}
			}
		}
	}
	##


	if ((Compare_Version("7.0r0")>=0) and $type eq "Server") {
		# MATCH k7
		my $required=0;
		# check kwartz-ldap version
		my $ldap_ver = `dpkg -s kwartz-ldap 2>/dev/null| grep ^Version | awk '{print \$2}'`; chomp $ldap_ver;
		mysystem("dpkg --compare-versions \"$ldap_ver\" lt \"5.2-1\"");
		$required=1 if (($?>>8) == 0);
		if ($required) {
			# candidate_version
			my $candidate=candidate_version('kwartz-ldap');
			print STDERR "kwartz-ldap candidate:$candidate\n"  if ($opt_v);
			if ($candidate) {
				if ( system("dpkg", "--compare-versions", $candidate, 'ge', '5.2-0') == 0 ) {					
					if ($info) {
						print "nscld-migration [0.0] (20180517 kwartz.iris-tech.com)\n";
						# print("apt-get -qy --force-yes --ignore-hold -o Dir::Etc::SourceList=$SOURCE_S -o Dir::Etc::SourceParts='/etc/kwartz/sources.list.d/' -o DPKG::Options::=--force-overwrite -o DPKG::Options::=--force-confold install nslcd libnss-ldapd libnss-ldapd:i386 libpam-ldapd libpam-ldapd:i386 kwartz-ldap >> $VARLOGFILE 2>&1");
					} else {
						# warn should remove /etc/kwartz/sources.list.d/
						my $apt_cmd="apt-get -qy --force-yes --ignore-hold -o Dir::Etc::SourceList=$SOURCE_S -o Dir::Etc::SourceParts='/etc/kwartz/sources.list.d/' -o DPKG::Options::=--force-overwrite -o DPKG::Options::=--force-confold install nslcd libnss-ldapd libnss-ldapd:i386 libpam-ldapd libpam-ldapd:i386 kwartz-ldap";
						my $redir=($opt_v)?"2>&1 | tee -a $VARLOGFILE 1>&2":">>$VARLOGFILE 2>&1";
						mysystem("$apt_cmd $redir");
						#run apt-get autoremove
						mysystem("apt-get -qy autoremove $redir");
					}
				}
			}
		}
	}
	##

    # Protection disque pulse débranché
	if ((Compare_Version("7.0r0")>=0) and $type eq "Server") {
		my $fstabko = 0;
		my $ispulsedisk = mysystem("grep -q deploy /etc/fstab");

		if ($ispulsedisk == 0) {

			# Test fstab
			my $fstabfh;
			eval {
				open ($fstabfh, "<", "/etc/fstab");
			};
			if (!$@) {
				foreach (<$fstabfh>) {
					$fstabko++ if (m|^UUID=.*/home/kwartz/deploy.*defaults,acl,user_xattr\s+|);
				}
				close $fstabfh;
			}

			if ($fstabko) {
				if ($info && $fstabko) {
					$cor++;
					print "kwartz-correctif-pulsefstab [0.0] (20201118 correctif_intégré)\n";
				}
				if (!$info && $fstabko) {
					system ("echo Correctif intégré pulse fstab nofail >> $VARLOGFILE");
					mysystem("sed -e '\\;/home/kwartz/deploy;s;defaults,acl,user_xattr;defaults,acl,user_xattr,nofail;' -i.bak /etc/fstab >> $VARLOGFILE 2>&1");
					if ($? == 0) {
						unlink("/etc/fstab.bak");
					} else {
						mysystem("mv -vf /etc/fstab.bak /etc/fstab >> $VARLOGFILE 2>&1");
					}
				}
			}
		}
	}
    ##
    
	# BPO 20230125 Correction K9 PDC hs suite maj dépot security focal samba 2:4.13.17~dfsg-0ubuntu1.20.04.4
	# modifie BPO 20200429 Correction K8 ldap AD hs suite maj dépot security xenial samba : inutile corrigé par ubuntu
	# A supprimer si samba publie un correctif
	# modifie BPO 20230209 : corrige soucis si la version 2:4.13.17~dfsg-0ubuntu1.20.04.5 a été installée juste après passage upgrade vers k9.
	# supprimé BPO 20230308 : diffusion 2:4.15.13+dfsg-0ubuntu0.20.04.1 qui vient avec libldb2 2:2.4.4-0ubuntu0.20.04.1 incompatible avec samba 2:4.13.17~dfsg-0ubuntu1.20.04.1
	if ((Compare_Version("9.0r0")==0) and $type eq "Server" and (! -f "/var/lib/samba/private/sam.ldb")) {
		# MATCH k9
		my $required=0;
		if ( ! -f "/etc/apt/preferences.d/samba90" ) {
		    my $CFG;
		    if (open($CFG, ">", "/etc/apt/preferences.d/samba90")) {

			    print $CFG <<_MARKER_;
Package: samba-vfs-modules
Pin: version 2:4.13.17~dfsg-0ubuntu1.20.04.1
Pin-Priority: 1000

Package: samba-dsdb-modules
Pin: version  2:4.13.17~dfsg-0ubuntu1.20.04.1
Pin-Priority: 1000

Package: samba:amd64
Pin: version  2:4.13.17~dfsg-0ubuntu1.20.04.1
Pin-Priority: 1000

Package: samba-common-bin
Pin: version  2:4.13.17~dfsg-0ubuntu1.20.04.1
Pin-Priority: 1000

Package: smbclient
Pin: version  2:4.13.17~dfsg-0ubuntu1.20.04.1
Pin-Priority: 1000

Package: libsmbclient
Pin: version  2:4.13.17~dfsg-0ubuntu1.20.04.1
Pin-Priority: 1000

Package: python3-samba
Pin: version  2:4.13.17~dfsg-0ubuntu1.20.04.1
Pin-Priority: 1000

Package: samba-libs
Pin: version  2:4.13.17~dfsg-0ubuntu1.20.04.1
Pin-Priority: 1000

Package: libwbclient0
Pin: version  2:4.13.17~dfsg-0ubuntu1.20.04.1
Pin-Priority: 1000

Package: winbind
Pin: version  2:4.13.17~dfsg-0ubuntu1.20.04.1
Pin-Priority: 1000

Package: samba-common
Pin: version  2:4.13.17~dfsg-0ubuntu1.20.04.1
Pin-Priority: 1000
_MARKER_

	    		close($CFG);
			}
		}	
	
		my $samba_ver = `dpkg -s samba 2>/dev/null| grep ^Version | awk '{print \$2}'`; chomp $samba_ver;
   	    mysystem("dpkg --compare-versions \"$samba_ver\" eq \"2:4.13.17~dfsg-0ubuntu1.20.04.4\"");
   	    $required=1 if (($?>>8) == 0);
    	if ($required) {
		# candidate_version
   	        my $candidate=candidate_version('samba');
       	    print STDERR "samba candidate:$candidate\n"  if ($opt_v);
           	if ($candidate) {
    	        if ( system("dpkg", "--compare-versions", $candidate, 'eq', '2:4.13.17~dfsg-0ubuntu1.20.04.1') == 0 ) {
   	            	if ($info) {
       		           	if (defined($reflist)) {
							push @$reflist, "kwartz-correctif-samba [0.1] (20230125 kwartz.iris-tech.com)";
						}
                   		#print("apt-get -qsuy --force-yes --ignore-hold -o Dir::Etc::SourceList=$source_s -o Dir::Etc::SourceParts='/etc/kwartz/sources.list.d/' -o DPKG::Options::=--force-overwrite -o DPKG::Options::=--force-confold dist-upgrade --allow-downgrades >> $varlogfile 2>&1");
					} else {
						# --allow-downgrades remplace --force-yes pour K8> : beaucoup plus sur
#		         		my $apt_cmd="apt-get -quy --force-yes --ignore-hold -o Dir::Etc::SourceList=$source_s -o Dir::Etc::SourceParts='/etc/kwartz/sources.list.d/' -o DPKG::Options::=--force-overwrite -o DPKG::Options::=--force-confold dist-upgrade --allow-downgrades";
		         		my $apt_cmd="apt-get -quy -o Dir::Etc::SourceList=/etc/kwartz/sources_s.list -o Dir::Etc::SourceParts='/etc/kwartz/sources.list.d/' -o DPKG::Options::=--force-overwrite -o DPKG::Options::=--force-confold install samba-vfs-modules=2:4.13.17~dfsg-0ubuntu1.20.04.1 samba-dsdb-modules=2:4.13.17~dfsg-0ubuntu1.20.04.1 samba=2:4.13.17~dfsg-0ubuntu1.20.04.1 samba-common-bin=2:4.13.17~dfsg-0ubuntu1.20.04.1 smbclient=2:4.13.17~dfsg-0ubuntu1.20.04.1 libsmbclient=2:4.13.17~dfsg-0ubuntu1.20.04.1 python3-samba=2:4.13.17~dfsg-0ubuntu1.20.04.1 samba-libs=2:4.13.17~dfsg-0ubuntu1.20.04.1 libwbclient0=2:4.13.17~dfsg-0ubuntu1.20.04.1 winbind=2:4.13.17~dfsg-0ubuntu1.20.04.1 samba-common=2:4.13.17~dfsg-0ubuntu1.20.04.1 --allow-downgrades";
   	    		 		my $redir=($opt_v)?"2>&1 | tee -a $VARLOGFILE 1>&2":">>$VARLOGFILE 2>&1";
						mysystem("$apt_cmd $redir");
		            }
				}
			}
#		} elsif (system("dpkg --compare-versions \"$samba_ver\" ne \"2:4.13.17~dfsg-0ubuntu1.20.04.1\"") == 0) {
		} else {
			unlink ("/etc/apt/preferences.d/samba90");
		}
	} ##
	
	# BPO 20240411 Correction squid hs suite maj dépot security focal squid squid_4.10-1ubuntu1.10_amd64.deb
	# correctif ubuntu diffusé 4.10-1ubuntu1.11
	
#	if (($ver ge "9.0r0" and $type eq "Server") ||
#		($ver ge "4.0r0" and $type eq "kmcbox")) {
#		# MATCH k9 / kmcbox4
#		my $required=0;
#		if ( ! -f "/etc/apt/preferences.d/squid410" ) {
#		    my $CFG;
#		    if (open($CFG, "> /etc/apt/preferences.d/squid410")) {
#
#			    print $CFG <<_MARKER_;
#Package: squid
#Pin: version 4.10-1ubuntu1.9
#Pin-Priority: 1000
#
#Package: squid-common
#Pin: version 4.10-1ubuntu1.9
#Pin-Priority: 1000
#
#Package: squidclient
#Pin: version 4.10-1ubuntu1.9
#Pin-Priority: 1000
#_MARKER_
#
#	    		close($CFG);
#			}
#		}	
#	
#		my $squid_ver = `dpkg -s squid 2>/dev/null| grep ^Version | awk '{print \$2}'`; chomp $squid_ver;
#		mysystem("dpkg --compare-versions \"$squid_ver\" eq \"4.10-1ubuntu1.10\"");
# 	    $required=1 if (($?>>8) == 0);
# 	if ($required) {
#		# candidate_version
#   	        my $candidate=candidate_version('squid');
#       	    print STDERR "squid candidate:$candidate\n"  if ($opt_v);
#           	if ($candidate) {
#    	        if ( system("dpkg", "--compare-versions", $candidate, 'eq', '4.10-1ubuntu1.9') == 0 ) {
#   	            	if ($info) {
#       		           	if (defined($reflist)) {
#							push @$reflist, "kwartz-correctif-squid [0.1] (20240411 kwartz.iris-tech.com)";
#						}
#                   		#print("apt-get -qsuy --force-yes --ignore-hold -o Dir::Etc::SourceList=$source_s -o Dir::Etc::SourceParts='/etc/kwartz/sources.list.d/' -o DPKG::Options::=--force-overwrite -o DPKG::Options::=--force-confold dist-upgrade --allow-downgrades >> $varlogfile 2>&1");
#					} else {
#						# --allow-downgrades remplace --force-yes pour K8> : beaucoup plus sur
#		         		my $apt_cmd="apt-get -quy -o Dir::Etc::SourceList=/etc/kwartz/sources_s.list -o Dir::Etc::SourceParts='/etc/kwartz/sources.list.d/' -o DPKG::Options::=--force-overwrite -o DPKG::Options::=--force-confold install squid-common=4.10-1ubuntu1.9 squid=4.10-1ubuntu1.9 squidclient=4.10-1ubuntu1.9 --allow-downgrades";
#   	    		 		my $redir=($opt_v)?"2>&1 | tee -a $VARLOGFILE 1>&2":">>$VARLOGFILE 2>&1";
#						mysystem("$apt_cmd $redir");
#		            }
#				}
#			}
#		} elsif (system("dpkg --compare-versions \"$squid_ver\" ne \"4.10-1ubuntu1.9\"") == 0) {
#			unlink ("/etc/apt/preferences.d/squid410");
#		}
#	} ##	

#	# BPO 20241213 Correction nextcloud hs suite maj dépot security focal php7.4
#	# correctif ubuntu diffusé 7.4.3-4ubuntu2.26
#	
#	if (($ver ge "9.0r0" and $type eq "Server") ||
#		($ver ge "4.0r0" and $type eq "kmcbox")) {
#		# MATCH k9 / kmcbox4
#		my $required=0;
#		if ( ! -f "/etc/apt/preferences.d/php74" ) {
#		    my $CFG;
#		    if (open($CFG, "> /etc/apt/preferences.d/php74")) {
#
#			    print $CFG <<_MARKER_;
#Package: php7.4-common
#Pin: version 7.4.3-4ubuntu2.24
#Pin-Priority: 1000
#
#Package: php7.4
#Pin: version 7.4.3-4ubuntu2.24
#Pin-Priority: 1000
#_MARKER_
#
#	    		close($CFG);
#			}
#		}	
#	
#		my $php_ver = `dpkg -s php7.4 2>/dev/null| grep ^Version | awk '{print \$2}'`; chomp $php_ver;
#		mysystem("dpkg --compare-versions \"$php_ver\" eq \"7.4.3-4ubuntu2.26\"");
# 	    $required=1 if (($?>>8) == 0);
# 	if ($required) {
#		# candidate_version
#   	        my $candidate=candidate_version('php7.4');
#       	    print STDERR "php7.4 candidate:$candidate\n"  if ($opt_v);
#           	if ($candidate) {
#    	        if ( system("dpkg", "--compare-versions", $candidate, 'eq', '7.4.3-4ubuntu2.24') == 0 ) {
#   	            	if ($info) {
#       		           	if (defined($reflist)) {
#							push @$reflist, "kwartz-correctif-php74 [0.1] (20241213 kwartz.iris-tech.com)";
#						}
#						unlink("/etc/apt/preferences.d/php74");
#                   		#print("apt-get -qsuy --force-yes --ignore-hold -o Dir::Etc::SourceList=$source_s -o Dir::Etc::SourceParts='/etc/kwartz/#sources.list.d/' -o DPKG::Options::=--force-overwrite -o DPKG::Options::=--force-confold dist-upgrade --allow-downgrades >> $varlogfile 2>&1");
#					} else {
#						# --allow-downgrades remplace --force-yes pour K8> : beaucoup plus sur
#		         		my $apt_cmd="apt-get -quy -o Dir::Etc::SourceList=/etc/kwartz/sources_s.list -o Dir::Etc::SourceParts='/etc/kwartz/#sources.list.d/' -o DPKG::Options::=--force-overwrite -o DPKG::Options::=--force-confold install php7.4=7.4.3-4ubuntu2.24 php7.4-#common=7.4.3-4ubuntu2.24 libapache2-mod-php7.4=7.4.3-4ubuntu2.24 php7.4-cli=7.4.3-4ubuntu2.24 php7.4-json=7.4.3-4ubuntu2.24 php7.4-#opcache=7.4.3-4ubuntu2.24 php7.4-readline=7.4.3-4ubuntu2.24 php7.4-bcmath=7.4.3-4ubuntu2.24 php7.4-cgi=7.4.3-4ubuntu2.24 php7.4-curl=7.4.3-4ubuntu2.24 #php7.4-fpm=7.4.3-4ubuntu2.24 php7.4-gd=7.4.3-4ubuntu2.24 php7.4-gmp=7.4.3-4ubuntu2.24 php7.4-imap=7.4.3-4ubuntu2.24 php7.4-intl=7.4.3-4ubuntu2.24 #php7.4-ldap=7.4.3-4ubuntu2.24 php7.4-mbstring=7.4.3-4ubuntu2.24 php7.4-mysql=7.4.3-4ubuntu2.24 php7.4-pspell=7.4.3-4ubuntu2.24 php7.4-#soap=7.4.3-4ubuntu2.24 php7.4-xml=7.4.3-4ubuntu2.24 php7.4-zip=7.4.3-4ubuntu2.24 --allow-downgrades --no-remove";
#   	    		 		my $redir=($opt_v)?"2>&1 | tee -a $VARLOGFILE 1>&2":">>$VARLOGFILE 2>&1";
#						mysystem("$apt_cmd $redir");
#		            }
#				}
#			}
#		} elsif (system("dpkg --compare-versions \"$php_ver\" ne \"7.4.3-4ubuntu2.24\"") == 0) {
#			unlink ("/etc/apt/preferences.d/php74");
#		}
#	} ##

# calls hook
	if ($info) {
		# this file is a HASH type config file that can be used
                # by hooks handler store data used during update process
                # it is used by kwartz-mongo not to apply updates that
                # haven't been previously shown in kwartzcontrol
		unlink "/var/run/kwartz-autoupdate/status.conf";
		my @all_special = qx(kwartz-hook emit core/autoupdate-special info);
		foreach my $desc (@all_special) {
			$cor++;
			from_to($desc,"UTF-8","iso-8859-15");
			print $desc;
		}
	} else {
		mysystem("kwartz-hook emit core/autoupdate-special apply >> $VARLOGFILE 2>&1");
	}


} # Special_Corrections()

# installed_preference(name)
# return presence of /etc/apt/preferences.d/name
sub installed_preference {
	my $name = shift;
	return (-f "/etc/apt/preferences.d/$name");
}

# do_install_preference(name, content)
# create preference file
# name : /etc/apt/preferences.d/name
# content : strings filled into file
sub do_install_preference {
	my $name = shift;
	my $content = shift;
	
	my $file = "/etc/apt/preferences.d/$name";

	my $CFG;
	if (! open($CFG, ">", $file)) {
		return ("ERROR: Failure opening '$file' - $!");
	}

	print $CFG $content;
	close($CFG);
	return (undef);   # Success
}

# do_remove_preference(name)
# remove preference file
# name : /etc/apt/preferences.d/name
sub do_remove_preference {
	my $name = shift;
	
	unlink ("/etc/apt/preferences.d/$name");
}
	
# installed_version(package)
# return package installed version
# return empty string on error or if package not installed
sub installed_version {
	my $package=shift;
	return '' unless $package;
	
	my $res=qx/dpkg-query -W $package 2>\/dev\/null/;
	# die "failed to get $package installed version: $!"
	return '' if ($?);
	
	my $status=qx/dpkg-query --showformat=\'\${Status}\' --show $package/;
	return '' if ($status =~ m/deinstall/);
	
	chomp $res;
	$res=~s/^$package\s+//s;
	return  $res;
}

# candidate_version(package)
# return candidate version  for package according to apt preferences (pinning)
# return empty string on error or if no candidate
sub candidate_version {
	my $package=shift;
	my $res='';
	return $res  unless $package;
	my @policy=qx|env LANG=C apt-cache policy -o Dir::Etc::SourceList=/etc/kwartz/sources.list -o Dir::Etc::SourceParts=/etc/kwartz/sources.list.d/ $package|;
	foreach (@policy) {
		chomp;		
		if (  s/^\s+Candidate:\s+// )  {
			$res=$_; 
			last;
		}
	}
	return $res
}

# candidate_allversions(package)
# return more recent distant candidate version for package according independant of apt preferences (pinning)
# return empty string on error or if no candidate
sub candidate_more_recent_500_version {
	my $package=shift;
	my $res='';
	return $res  unless $package;
	my @policy=qx|env LANG=C apt-cache policy -o Dir::Etc::SourceList=/etc/kwartz/sources.list -o Dir::Etc::SourceParts=/etc/kwartz/sources.list.d/ $package|;
	foreach (@policy) {
		chomp;
		if (  m/^\s+(\S+) 500$/ )  {
			$res=$1;
			last;
		}
	}
	return $res
}


# is_pkg_downgrade(package)
# return 1 if package downgrade required ie candidate version lt installed
# else return 0 
sub is_pkg_downgrade {
	my $package=shift;
	return 0 unless $package;
	my $installed=installed_version($package);
	return 0 unless $installed;
	my $candidate=candidate_version($package);
	if ($candidate) {
		if ( system("dpkg", "--compare-versions", $candidate, 'lt', $installed) == 0 ) {
			warn "DOWNGRADE $package from $installed to $candidate...\n";			
			return 1;
		}	
	}
	return 0;
}


sub handle_kmc_downgrade {
	if ( is_pkg_downgrade('kwartz-mdm') ) {		
		print STDERR "KMC mis en VERSION INFERIEURE\n" if ($opt_v);
		# same as kwartz-autoupdate -a install kwartz-mdm kwartz-mdm-core
		# add -o DPKG::Options::=--force-overwrite -o DPKG::Options::=--force-confold
		my $cmd="apt-get -quy -o Dir::Etc::SourceList=$SOURCE_S -o Dir::Etc::SourceParts='/etc/kwartz/sources.list.d/' -o DPKG::Options::=--force-overwrite -o DPKG::Options::=--force-confold install kwartz-mdm kwartz-mdm-core $aptsyntax{'allowdowngrade'}";
		if ($opt_v) {
			mysystem("$cmd 2>&1 | tee -a $VARLOGFILE 1>&2");
		} else {
			mysystem("$cmd >>$VARLOGFILE 2>&1");
		}
	}
}

##
# Add Persistent config capacity to kwartz-autoupdate
# See:
#   https://www.perlmonks.org/?node_id=464358
##

# Read a configuration file
#   The arg can be a relative or full path, or
#   it can be a file located somewhere in @INC.
sub ReadCfg
{   
	my $file = $_[0];
	
	our $err;
	
	{   # Put config data into a separate namespace
		package CFG;
		
		# Process the contents of the config file
		if (! -e $file) {
			our %CFG = ();
		} else {
			my $rc = do($file);
			
			# Check for errors
			if ($@) {
				$::err = "ERROR: Failure compiling '$file' - $@";
			} elsif (! defined($rc)) {
				$::err = "ERROR: Failure reading '$file' - $!";
			} elsif (! $rc) {
				$::err = "ERROR: Failure processing '$file'";
			}
		}
	
	}
	
	return ($err);
}

# Save configuration data
#   Use the same arg as used with ReadCfg()
#   so that file can be found in the %INC.
sub SaveCfg
{
	my $file = $_[0];

	my $CFG;
	if (! open($CFG, ">", $file)) {
		return ("ERROR: Failure opening '$file' - $!");
	}

	print $CFG <<_MARKER_;
#####
#
# My configuration file
#
#####

use strict;
use warnings;


# The configuration data
our @{[Data::Dumper->Dump([\%CFG::CFG], ['*CFG'])]}
1;
# EOF
_MARKER_

	close($CFG);
	return (undef);   # Success
}
#


 
#################################
# #
# Valeurs de sortie
#	RET_OK 				=>  0, # OK
#	RET_ERROR			=>  1, # Erreur diverse
#	RET_VARSIZE			=>  2, # /var trop petit
#	RET_HOMESIZE		=>  3, # /home trop petit
#	RET_OLDINSTALL		=>  4, # Une installation précédente à échoué
#	RET_INSTALLPENDING	=>  5, # L'installation est toujours en cours
#	RET_NOKEY			=>  6, # Pas de clé
#	RET_CLUSTER			=>  7, # Mode cluster (obsolète)
#	RET_VALIDATE		=>  8, # Echec validate()
#	RET_APTUPDATE		=>  9, # Echec fsm AptUpdate
#	RET_RENEWOK			=> 10, # Renew OK
#	RET_RENEWKO			=> 11, # Echec renew
#	RET_DAEMON			=> 12,
#	RET_DAEMONEND		=> 13,
#	RET_PRE_UPDATEVERSION	=> 20,  # Echec de préparation mise à jour iso
#	RET_UPDATEVERSION	=> 30  # Echec de mise à jour iso
##

my $usage=
" 
      Mise à jour à distance de Kwartz.

Utilisation: $0 (-d -v -q -D)	{-r|-k|-S|-p}
								{-i|-I|-u} (-U image_iso)
								{-a update}
								{-a install xxx yyy ...} (-s) (-o \"opt1 opt2...\")
								{-a download xxx yyy ...}
								{-g|-G xxx.deb...}
								{-P nom_paquet}
								{-C pid|-1}

   -a (all)     : Utilise le sources.list complet kwartz+ubuntu+security
   -s (simulate): mode dry-run pour l'option -a
   -o (options) : options pour apt-get install pour l'option -a
   -g (dpkg)    : installe toutes les dépendances d'une liste de .deb
   -G (dpkG)    : installe un ou plusieurs .deb et leurs dépendances
   -i (info)    : Affiche les modules disponibles
   -I (Info)    : -i sans test de sauvegarde
   -k (kill)    : désactive le serveur
   -p (recup)   : fait une tentative de recup apres detection echec installation
   -P (Policy)  : fait un apt-cache policy sur un paquet
   -r (renew)   : Met à jour kwartz-autoupdate
   -F (Force)   : force la levée des verrous dpkg/apt bloqués (tue les processus
                 détenteurs). À combiner avec -r pour débloquer une mise à jour
                 figée sans attendre le délai automatique.
   -S (Swipe)   : remet le serveur dans un état fonctionnel après dévalidation
   -u (update)  : Met à jour Kwartz
   -U (dist_Upgrade) : dist_upgrade sur kwartz. A combiner avec -i ou -u
   -C (Check)   : Renvoie l'état du daemon
      
   -q (quick)   : A combiner avec -i, saute l'étape mise à jour des listes
   -d (debug)   : affiche les commandes lancées
   -v (verbose) : affiche les msg apt-get lors de la mise à jour des paquets
   -D (Daemonize)    : Lance en tache de fond. Renvoie le pid sur stdout
";


($#ARGV>=0 ) or die $usage;
getopts('adC:DFgGiIkpP:qrsSuU:vo:') or die $usage;

# Fichier d'état
# Get our configuration information
if (my $errstate = ReadCfg($STATEFILE)) {
}

###################################
# Protection contre la réentrance
#
# Si une commande est en cours :
#	On accepte opt_C pour renvoyer l'état en cours
#	On accepte opt_a si on est en condition de réentrance
##

# Creation ou test fichier pid
if (! -d $RUNDIR) {
	mkdir $RUNDIR,0755
}
my $pidfile = File::Pid->new({file => "$RUNDIR/".basename($0).".pid"});

my $curpid = $pidfile->running;

# Fiabilise $curpid : File::Pid->running ne garantit qu'un PID vivant, pas que ce
# soit une instance de cet outil (pidfile obsolète + PID recyclé après un crash).
# Si le PID pointé n'est pas un kwartz-autoupdate, le pidfile est périmé : on
# l'ignore (pas de réentrance réelle) plutôt que de sortir à tort ou de tuer un
# process étranger.
if (defined($curpid) && !is_kwartz_autoupdate($curpid)) {
	print ("pidfile obsolète : PID $curpid n'est pas un ".basename($0)." -> ignoré\n") if (defined $opt_d);
	$curpid = undef;
}

# Qui m'appelle ?
use Proc::ProcessTable;

my $procs = Proc::ProcessTable->new;


my $ppid = getppid();
do {
	my %caller;
	foreach my $p ( @{$procs->table} ) {
		if ($p->pid == $ppid) {
			$caller{'cmndline'} = $p->cmndline;
			$caller{'ppid'} = $p->ppid;
			if (defined($curpid)) {
				if ($p->pid == $curpid) {
					$reentrancy++;
					print ("Réentrance $reentrancy\n") if (defined $opt_d);
				}
			}
			
			$ppid = $p->ppid;
			push @callerchain, \%caller;
			last;
		}
	}
} while ($ppid != 1);

# kwartz-agent capture la sortie de kwartz-autoupdate et l'attend en Latin-1
# (consommateur historique, non modifiable). Quand il figure dans la chaîne d'appel,
# on émet en Latin-1. Un KWARTZ_CHARSET explicite reste prioritaire ; l'usage manuel
# en terminal garde l'UTF-8 pour le débogage. Placé après la construction de
# @callerchain et avant les premières émissions accentuées (myexitlog ci-dessous).
if ((grep { defined($_->{'cmndline'}) && $_->{'cmndline'} =~ /kwartz-agent/ } @callerchain)
    && !defined($ENV{'KWARTZ_CHARSET'})
    && !(-t STDOUT)) {
	$gOutCharset = 'ISO-8859-1';
	# Un binmode :encoding EMPILE une couche : appliqué sur STDOUT/STDERR qui ont déjà
	# la couche UTF-8 initiale, il donne « UTF-8 puis ISO-8859-1 » et corrompt la sortie
	# (le caractère ressort en \x{fffd}). On repasse d'abord en :raw pour retirer la
	# couche existante, puis on pose la seule couche voulue.
	binmode(STDOUT, ":raw");
	binmode(STDOUT, ":encoding($gOutCharset)");
	binmode(STDERR, ":raw");
	binmode(STDERR, ":encoding($gOutCharset)");
}

if (defined($curpid)) {
	# Une autre instance tourne déjà ($curpid). On la tue (elle + tout son sous-arbre :
	# l'instance ET le dpkg/postinst figé qu'elle a engendré — tuer seulement le dpkg
	# la laisserait relancer un « dpkg --configure -a » dans SelfHeal et se re-bloquer).
	# Le kill n'a lieu QUE sur forçage -F, ou sur renew par cron (-r) quand un lock dpkg
	# du sous-arbre est tenu depuis plus de 2h (blocage réel, distinct d'un dist-upgrade
	# long mais vivant). Les autres modes (-i fréquent, etc.) ne tuent JAMAIS et sortent
	# comme avant : ils ne peuvent pas savoir si l'instance est bloquée ou occupée.
	if (defined($opt_F) ||
		(defined($opt_r) && stale_dpkg_lock_in_tree($curpid, 7200))) {
		print ("Réentrance : instance $curpid bloquée (lock dpkg figé) -> kill du sous-arbre\n") if (defined $opt_d);
		kill_process_tree($curpid);
		# On prend possession du pidfile pour que le suivi d'état reste cohérent.
		$pidfile->write;
	} elsif (! ( defined ($opt_C) ||
	        (defined ($opt_a) && ($reentrancy<=1))   # Pas plus d'un niveau de réentrance
	      )
	   ) {
		myexitlog(	"Une opération est en cours",
					"Operation en cours",
					RET_INSTALLPENDING);
	}
} else {
	$pidfile->write;
}

#
## Réentrance OK
################

##
# Get proxy info
# récupération de l'url
# proxy sur la connexion isp?
our %workfile;
my $ispfile=$workfile{"isp"};
my %ispconf;
#initialize data values...
my $http_proxy='';
if (-f $ispfile) {
  #... from work file
  &read_file($ispfile,\%ispconf);
  if ($ispconf{"ProxyAddress"}) {
    $http_proxy="http://$ispconf{'ProxyAddress'}";
    $http_proxy.=":$ispconf{'ProxyPort'}"
      if ( $ispconf{'ProxyPort'} );
  }
}
$ENV{'http_proxy'}="$http_proxy";
$ENV{'DEBIAN_FRONTEND'}='noninteractive';

##
# Gestion des évolutions / déprécations de apt-get
# récupère la version de apt-get
#	Kwartz5 : apt 0.8.16~exp12ubuntu10.21 pour i386 compilé sur Oct  8 2014 12:37:48
#	Kwartz6 : apt 0.8.16~exp12ubuntu10.29 pour i386 compilé sur May 28 2020 17:30:39
#	Kwartz7 : apt 1.0.1ubuntu2 pour amd64 compilé sur Jan 18 2019 20:24:31
#	Kwartz8 : apt 1.2.32ubuntu0.2 (amd64)
#	Kwartz9 : apt 2.0.2ubuntu0.2 (amd64)
##

my $aptver=`apt-get --version | head -1 | awk '{print \$2}'`; chomp $aptver;

# --force-yes déprécié à partir de 1.1
$aptsyntax{'allowdowngrade'} = "--force-yes";
if (mysystem("dpkg --compare-versions \"$aptver\" ge \"1.1\"") == 0) {
	$aptsyntax{'allowdowngrade'} = "--allow-downgrades";
}


# Special check command for status
if (defined($opt_C)) {
	my $statenum = 0;
	my $result;
	my $resultcode = 0;
	my $resultstr = "";
	
	my $lastpid = $opt_C;
	if ($lastpid == -1) {
		if (defined ($CFG::CFG{'lastrun'}{'pid'}) and $CFG::CFG{'lastrun'}{'pid'} =~ m/^\d+$/) {
			$lastpid = $CFG::CFG{'lastrun'}{'pid'};
		}
	}
	# Test if files exists
	unless ($lastpid == -1) {
		if (! -e "$RUNDIR/$lastpid.progress" and ! -e "$RUNDIR/$lastpid.res") {
			$lastpid = -1;
		}
	}
	# Couldn't find pid or pid files
	if ($lastpid == -1) {
		print STDERR "installation introuvable\n";
		myexitlog(undef, undef, RET_ERROR);
	}
	
	if (open (my $fh, "<", "$RUNDIR/$lastpid.progress")) {
		$statenum = <$fh>;
		close ($fh);
		chomp($statenum);
	}
	if (open (my $fh, "<", "$RUNDIR/$lastpid.res")) {
		$result = <$fh>;
		close ($fh);
		chomp($result);
		if ($result =~ m/^(.*?) (\d+)$/) {
			$resultstr = $1;
			$resultcode = $2;
		}
	}
	
	# Send back check result
	# Input:
	#	$lastpid :
	#			-1	: pas de running trouvé
		#	$opt_C  : pid passé en paramètre
		#	lastrun : pid enregistré si $opt_C vaut -1
	#
	# 	$pidfile->pid :
	#		Current running process
	#
	# Output:
	#	STDOUT : état / résultat
	#	STDERR : message
	#	logfile: néant
	#
	# Code de retour :
	#	running : RET_INSTALLPENDING
	#	ended   : RET_DAEMONEND
	##
	#  if still running
	if ($pidfile->pid == $lastpid) {
		print STDERR "installation en cours...\n";
		# no log
		myexitlog("State: $statelist[$statenum]", undef, RET_INSTALLPENDING);
	} else {
	#  if ended
		print STDERR ($resultstr eq "")?"Installation terminée\n":"$resultstr\n";
		# no log
		myexitlog("Resultat: $resultcode", undef, RET_DAEMONEND);
	}
} # $opt_C

# Ici tout sauf $opt_C

##
# Tests mise à jour en cours (sautés en forçage -F : on sait ce qu'on fait)
##
if (! defined($opt_F)) {
	if ((check_kwartzdistupgrade() == 1) or #  via /run/kwartz-update
		(system("/usr/bin/pgrep apt-get >/dev/null 2>&1")==0) or  #  Générique
		(system("echo | dpkg --set-selections >/dev/null 2>&1")>>8 == 2) ) { # Test dpkg lock

		myexitlog(	"L'installation est toujours en cours",
					"installation en cours",
					RET_INSTALLPENDING);
	}
}
##

# Daemonize
sub term_handler {
	# On ne fait rien... pas de TERM autorisé
}

##
# Daemonize kwartz-autoupdate
#
# See :
#  http://www.farhadsaberi.com/perl/2013/02/perl-detach-process-daemon.html
#  http://world.std.com/~swmcd/steven/tech/daemon.html
##
if (defined($opt_D)) {
	
	pipe(READER, WRITER) || myexitlog ("Erreur interne", "pipe failed: $!", RET_ERROR);
	defined (my $son = fork) or myexitlog ("Erreur interne", "Cannot fork: $!", RET_ERROR);
	if ($son) {
		# I am parent
		close(WRITER);

		my $line = <READER>;
		chomp $line;
		my $daemon_pid = $line;
		close(READER) || warn "kid existed $?";
		# waitpid($son,0);
		
		# Transfert du PID sur le daemon
		$pidfile->remove;
		$pidfile = File::Pid->new({file => "$RUNDIR/".basename($0).".pid",
									pid => "$daemon_pid"});
		if ($pidfile->running) {
			exit(RET_INSTALLPENDING);
		}
		$pidfile->write;
		
		print ("$daemon_pid");
		exit(RET_DAEMON);
	} else {
		# I am son
		close(READER);
		
		# Prepare for detach
		close STDIN;
		close STDOUT;
		close STDERR;

		# Detach myself
		setsid or myexitlog(defined($opt_v)?"Erreur interne":"", "Can't start a new session: $!", RET_ERROR);
		umask(0027); # create files with perms -rw-r----- 
		chdir '/' or die "Can't chdir to /: $!";

		# acquire process leader stdio
		open STDIN,  "<", "/dev/null" or die $!;
		open STDOUT, ">", "/dev/null" or die $!;
		open STDERR, ">", "/dev/null";

		defined (my $grandson = fork) or myexitlog (defined($opt_v)?"Erreur interne":"", "Cannot fork: $!", RET_ERROR);

		if ($grandson) {
			# I continue to be the son of principal
			print WRITER "$grandson\n";
			close(WRITER);

			# intercepte le signal TERM pour ne pas stopper
			$SIG{'TERM'} = "term_handler";
			
			waitpid($grandson, 0);
			my $ress = $?>>8;
			open (my $resfh, ">>", "$RUNDIR/$grandson.res");
			print $resfh " $ress\n";
			close $resfh;
			exit (0);
		}
		# my daemon is here.
		
		close STDIN;
		close STDOUT;
		close STDERR;

		open STDIN,  "<", "/dev/null" or die $!;
		open STDOUT, ">", "$RUNDIR/$$.out" or die $!;
		open STDERR, ">", "$RUNDIR/$$.err";
		
		# intercepte le signal TERM pour ne pas stopper
		$SIG{'TERM'} = "term_handler";
	}
} # $opt_D

# Infos pour conserver la dernière commande active
# Ici on ne doit pas être appelé plusieurs fois.
#  Protection plus haut par gpidfile
my $lastcommand = "unknown";
$lastcommand = "info" if (defined ($opt_i) or defined ($opt_I));
$lastcommand = "update" if (defined ($opt_u) and ! defined ($opt_U));
$lastcommand = "dist-upgrade" if (defined ($opt_u) and defined ($opt_U));
$lastcommand = "install" if (defined ($opt_g) or defined ($opt_G) or defined ($opt_a));
$lastcommand = "renew" if (defined ($opt_r));
$CFG::CFG{'lastrun'}{'pid'} = $$;
$CFG::CFG{'lastrun'}{'command'} = $lastcommand;
SaveCfg($STATEFILE);

open $gStateHandler, '>', "$RUNDIR/$$.progress" or $gStateHandler = undef;
print $gStateHandler "0" if defined ($gStateHandler);

$opt_i = 1 if defined($opt_I);

# Kwartz::Product library is present?
unless (eval {
	require Kwartz::Product;
	Kwartz::Product->import;
	($type,$ver) = Kwartz::Product->code;
	$type=~ s/\d+$//;
	
	#new update credentials are common for all kwartz products
	$autoupdate = "autoupdatek";

	if ( $type eq 'kwartz' ){
		$type = 'Server';
#		# for everybody now
#		$autoupdate = "autoupdatek";
        #$autoupdate = "autoupdate".substr($ver,0,1);
		$autoupdate = "autoupdate".substr($ver,0,1) unless (Compare_Version("7.0")>=0);
	}
	return 1;
}) {
	# No: fallback
	# récupération de la version
	open (my $verfh, "<", "/etc/debian_version");
	my @version = <$verfh>;
	close $verfh;
	$version[0] =~ m/(.+) (?:kwartz|KWARTZ)~*(\D*)(\d+.\w+)/;
	my $debver = $1;
	$type = $2;
	$type = "Server" if ( $type eq "" );

	$ver = $3;
	$autoupdate = "autoupdate";
	#$autoupdate = "autoupdate3" if ($ver ge "3.0r0" and $type eq "Server");
	#$autoupdate = "autoupdate4" if ($ver ge "4.0" and $type eq "Server");
	#$autoupdate = "autoupdate5" if ($ver ge "5.0" and $type eq "Server");
	if ((Compare_Version("3.0r0")>=0) and $type eq "Server"){
		$autoupdate = "autoupdate".substr($ver,0,1);
	}
	# kwartz 5.0 isn't already filled with Kwartz::Product
#	if ($ver ge "5.0r0" and $type eq "Server"){
#		$autoupdate = "autoupdatek";
#	}
	$autoupdate = "autoupdatephone" if ($type eq "Phone");
}


$debname = "lenny";
# plus nécessaire car fork 2.0 pour kwartz 4
# $debname = "woody" if ( $debver eq "3.0" );
# $debname = "sarge" if ( $debver eq "3.1" );
# $debname = "etch"  if ( $debver ge "4.0" );

if (((Compare_Version("5.0")>=0) and $type eq "Server") ||
	($type eq "kmcbox" )) {
	$debname=`lsb_release -sc`;
  	chomp $debname;
}

# récupération de la version du noyau
$krel = `uname -r`; chomp $krel;
$kver = `uname -v`; chomp $kver;

if (defined($opt_k)) {
	Devalidate_Kwartz();
	myexitlog(	defined($opt_v)?"Devalidate_Kwartz":"",
				"Devalidate_Kwartz",
				RET_OK);
}

if (defined($opt_S)) {
	Revalidate_Kwartz();
	myexitlog(	defined($opt_v)?"Revalidate_Kwartz":"",
				"Revalidate_Kwartz",
				RET_OK);
}

# nettoyage
mysystem ("apt-get autoclean >/dev/null 2>&1");

# verif espace libre pour /var/cache/apt/pkgcache.bin ...
if (Get_Free_Size("/var") < 80*1000) {
	my $freesize = Get_Free_Size("/var");
	myexitlog(	"L'espace système est trop petit pour faire une mise à jour à distance (besoin: 80/$freesize Mo )",
				"espace système /var trop petit",
				RET_VARSIZE);
}


#
# Mise à jour de kwartz-autoupdate (option -r)
# Sortie après la mise à jour
# Indépendant de la validation
#
if (defined($opt_r)) {
	my $res = Renew();
	if ($res ne 0) {
		my $kav = `dpkg-query --showformat=\'\${Version}\' --show kwartz-autoupdate`;
		myexitlog(	"kwartz-autoupdate mis a jour",
					"kwartz-autoupdate mis a jour : $kav",
					$res);
	} else {
		myexitlog(undef, "fin renew", RET_OK);
	}
} # $opt_r
##

##
# vérification de la licence
#
my %kwartzkey;
my $keyfile=$workfile{'kwartzkey'};
$keyfile.=".bak" if ( ! -f $keyfile);
if ( ! -f $keyfile) {
	myexitlog(	"Vous devez au préalable inscrire la clé Kwartz pour bénéficier de cette fonctionnalité",
				"inscrire clé Kwartz",
				RET_NOKEY);
}
&read_file($keyfile,\%kwartzkey);
$licence = $kwartzkey{'licence'};

open (my $keyfh, "<:raw", $keyfile);
my @keyfile = <$keyfh>;
close $keyfh;

foreach (@keyfile) {
	$key = $1 if (m/^key:(\d+)/) ;
}
# TODO transformer d'éventuels @ en %40

$upd_user=$licence;
$upd_pass=$key;
## 

# vérification sauvegarde en cours
if (! $opt_I && -f "/var/run/kwartz-backup.pid") {
	myexitlog(	"Il n'est pas possible de mettre à jour les composants pendant une sauvegarde",
				"Sauvegarde en cours",
				RET_INSTALLPENDING);
}

# vérification du mode cluster
my $cluster;
{
	use open IO => ':raw';	# même protection que pour sharelib.pl (bug perl #11541 sur vieux perls)
	$cluster = eval "require 'clusterlib.pl'";
}
if ($cluster && GetNodeType() ne 'single') {
	myexitlog("Il n'est pas possible de mettre à jour les composants en mode cluster", "maj cluster impossible", RET_CLUSTER);
}

# validation de la clé en info
# validate() -> ($val, $date, $user, $pass)
my @valres = validate();
my $v_val = shift (@valres);
if ($v_val eq "OK") {
	# validation OK
	my ($v_date, $v_user, $v_pass) = @valres;
	$upd_user=$v_user if ($v_user ne "");
	$upd_pass=$v_pass if ($v_pass ne "");
	Revalidate_Kwartz() if (( -f "/usr/lib/kwartz/control/kwartzkey.cgi.bak" ) && ( -f "/var/kwartz/kwartzkey.bak" ));
} elsif ($v_val eq "KO") {
	# echec validation
    # on désactive kwartz
  	Devalidate_Kwartz();
	# on affiche le message renvoyé
	my ($v_date, $v_code, $v_msg) = @valres;
	#   $v_msg =~ s/\\u([a-fA-F0-9][a-fA-F0-9][a-fA-F0-9][a-fA-F0-9])/encode('utf-8', chr(hex($1)))/eg;
	$v_msg =~ s/\\u([a-fA-F0-9][a-fA-F0-9][a-fA-F0-9][a-fA-F0-9])/chr(hex($1))/eg;
	myexitlog(	"$v_msg",
				"Dévalidation Kwartz par serveur",
				RET_VALIDATE);
} else {
	  # echec recup info
}

# BPO 20260803 : blocage redis
if ((! -f "/etc/systemd/system/redis.service.d/timeout.conf") &&
		(($type eq "Server" and (Compare_Version("10.0r0")>=0)) ||
		($type eq "kmcbox" and (Compare_Version("5.0r0")>=0))) ) {
	mysystem("mkdir -p /etc/systemd/system/redis.service.d >> $VARLOGFILE 2>&1");
    my $CFG;
    if (open($CFG, ">:raw", "/etc/systemd/system/redis.service.d/timeout.conf")) {	# :raw : contenu unit systemd en pur ASCII, insensible à la pragma use open UTF-8

	    print $CFG <<_MARKER_;
[Service]
TimeoutStopSec=10s
_MARKER_

		close($CFG);
	}
	mysystem("systemctl daemon-reload >> $VARLOGFILE 2>&1");
}

# pbm ou verif dpkg
my @dpkgerr = glob("/var/lib/dpkg/updates/[0-9]*");
my $checkstatus = mysystem("grep -q 'Status:.*unpacked' /var/lib/dpkg/status");
my $checkapt = mysystem("apt-get -qq check >/dev/null 2>&1");

# tentative de recup
if (defined($opt_p) or
	($#dpkgerr>=0) or
	($checkstatus == 0) or
	((($checkapt&127) == 0) && (($checkapt>>8) > 0))
	) {
	# print "tentative de récupération\n" if (defined($opt_v));
	$ENV{UCF_FORCE_CONFOLD}="1";
	mysystem("echo tentative de récupération >> $VARLOGFILE 2>&1");
	
	mysystem("dpkg --force-confold --configure -a >> $VARLOGFILE 2>&1");
	if ((-f "/etc/kwartz/sources_s.list") &&
		(Get_Free_Size("/") > 200*1000) ) {
		mysystem("apt-get -y -o Dir::Etc::SourceList=/etc/kwartz/sources_s.list -o Dir::Etc::SourceParts='/etc/kwartz/sources.list.d' --no-remove -f install >> $VARLOGFILE 2>&1");
	}
}

sub FixH240Iommu
{
	# Correctif + préventif : contrôleur HPE Smart Array/HBA Gen9 (dont H240)
	# sujet à un bug connu du driver IOMMU Intel VT-d (DMAR: DMA PTE for
	# vPFN ... already set), pouvant corrompre les données en transit sur
	# certains ProLiant Gen8/Gen9. Ajoute intel_iommu=off à
	# GRUB_CMDLINE_LINUX si un tel contrôleur est détecté et que le
	# paramètre n'y est pas déjà. Idempotent : sans effet si déjà appliqué,
	# si le matériel ne correspond pas, ou si la version Kwartz est trop
	# ancienne pour ce correctif.

	my $GRUB_FILE = "/etc/default/grub";
	my $PARAM     = "intel_iommu=off";

	# Détection via ID PCI vendor:device (103c:3239 = HPE Smart Array
	# Gen9, famille incluant le H240) ; fallback textuel en complément
	# car le libellé lisible ne mentionne pas toujours "H240".
	my @lspci = myback("lspci -nn");
	return 0 unless (grep { /103c:3239/i || /H240/i } @lspci);

	return 0 unless (-f $GRUB_FILE);

	open(my $fh, '<', $GRUB_FILE) or do {
		warn "FixH240Iommu: impossible d'ouvrir $GRUB_FILE: $!\n";
		return 0;
	};
	my @lines = <$fh>;
	close($fh);

	my $modified = 0;
	foreach (@lines) {
		next unless (/^GRUB_CMDLINE_LINUX="([^"]*)"/);
		my $current = $1;
		last if ($current =~ /\Q$PARAM\E/);	# déjà présent, rien à faire
		$_ = qq(GRUB_CMDLINE_LINUX="$current $PARAM"\n);
		$modified = 1;
		last;
	}
	return 0 unless ($modified);

	my $backup = "$GRUB_FILE.bak." . time();
	# Forme liste de mysystem (sans shell) : mysystem() renvoie le statut
	# brut de system(), contrairement à myback_noshell() qui ne renvoie que
	# la sortie capturée (jamais utilisé ailleurs dans ce script pour
	# détecter un succès).
	if (mysystem("cp", $GRUB_FILE, $backup) != 0) {
		warn "FixH240Iommu: échec de la sauvegarde de $GRUB_FILE, abandon\n";
		return 0;
	}

	open(my $out, '>', $GRUB_FILE) or do {
		warn "FixH240Iommu: impossible d'écrire $GRUB_FILE: $!\n";
		return 0;
	};
	print $out @lines;
	close($out);

	mysystem("update-grub >> $VARLOGFILE 2>&1");
	mysystem("echo 'FixH240Iommu: contrôleur H240/Gen9 détecté, $PARAM ajouté à $GRUB_FILE (sauvegarde: $backup). Redémarrage requis.' >> $VARLOGFILE");
	return 1;
} # FixH240Iommu

sub SelfHeal
{
	print "\nSelfHeal()\n" if ($opt_d);

	# Nettoyage des listes apt orphelines en tout premier, inconditionnel :
	# implémentation 100% locale (pas de réseau), donc assez légère pour
	# tourner systématiquement ici plutôt que sous condition d'espace libre.
	AptListCleanup();
	
	# afl: correction kwartz-mdm-core halconfigured sur kmcbox (pb % env HOME anacron)
	if ($type eq "kmcbox") {
		my $kwartz_mdm_core_version=`dpkg -s kwartz-mdm-core | grep '^Version' | awk '{print \$2}'`;
		chomp $kwartz_mdm_core_version;
		my $kwartz_mdm_core_status=`dpkg -s kwartz-mdm-core | grep '^Status' | awk '{print \$4}'`;
		chomp $kwartz_mdm_core_status;
		if ($kwartz_mdm_core_status eq "half-configured") {
			mysystem("echo tentative de récupération kmcbox / kwartz-mdm-core >> $VARLOGFILE 2>&1");
			if (mysystem("dpkg --compare-versions $kwartz_mdm_core_version le 1.1.9") == 0) {
				system("HOME=/root dpkg --configure kwartz-mdm-core kwartz-mdm >> $VARLOGFILE 2>&1");
			}
		}

	}

	# AFL 28/07/2017 kwartz7
	# BPO 01/02/2023 kwartz9
	# freeradius 2.1.12+dfsg-1.2ubuntu8.2 ubuntu update breaks 
	# freeradius-ldap postinst if radius disabled
	# reload: Unknown instance:
	# invoke-rc.d: initscript freeradius, action "force-reload" failed.
	# dpkg: error processing package freeradius-ldap (--configure):
	# le sous-processus script post-installation installé a retourné une erreur de sortie d'état 1

	if (($ver =~ /^7\..+/ or $ver =~ /^9\..+/) and $type eq "Server") {	
		my $pkg_status=`dpkg -s freeradius-ldap | grep '^Status' | awk '{print \$4}'`;
		chomp $pkg_status;
		if ($pkg_status eq 'half-configured') {
			# clients.conf empty = freeradius disabled
			unless (-s '/etc/freeradius/clients.conf') {
				mysystem("echo tentative de récupération $ver / freeradius-ldap >> $VARLOGFILE 2>&1");
				# del postinst to fix config 
				unlink '/var/lib/dpkg/info/freeradius-ldap.postinst';
				mysystem("dpkg --configure freeradius-ldap >> $VARLOGFILE 2>&1");
			}
		}
	}

	## BPO 12/01/2018 toutes architectures
	# GRUB_RECORDFAIL_TIMEOUT a été incorrectement mis à la valeur 0
	# ce qui pose problème si le kernel ne démarre pas
	# On le force à la valeur 60 (ubuntu conseille -1, mais ça pose des soucis pour les kmcbox par exemple)
	#
	if (-f "/etc/default/grub") {
		if (system('grep -q "GRUB_RECORDFAIL_TIMEOUT=0" /etc/default/grub') == 0) {
			mysystem("sed -e 's/GRUB_RECORDFAIL_TIMEOUT=0/GRUB_RECORDFAIL_TIMEOUT=60/' -i.bak /etc/default/grub >> $VARLOGFILE 2>&1");
			if ($? == 0) {
				unlink("/etc/default/grub.bak");
				mysystem("update-grub >> $VARLOGFILE 2>&1");
			} else {
				mysystem("mv -vf /etc/default/grub.bak /etc/default/grub >> $VARLOGFILE 2>&1");
			}
		}
	}
	##
	
	# Pbm ulogd
	if ( -f "/etc/init.d/ulogd.dpkg-dist" || -f "/etc/init.d/ulogd.diverted.dpkg-dist") {
		mysystem("mv -vf /etc/init.d/ulogd.dpkg-dist /etc/init.d/ulogd >> $VARLOGFILE 2>&1");
		mysystem("mv -vf /etc/init.d/ulogd.diverted.dpkg-dist /etc/init.d/ulogd.diverted >> $VARLOGFILE 2>&1");
	}
	
	# Correction TRIM mSata
	if ((Compare_Version("2.0r0")>=0) and $type eq "kmcbox") {
		my $fstabdiscard = 0;
		my $procdiscard = 0;
		
		my $model = myback("cat /sys/class/block/sda/device/model");
		my @hdparm = ();
		my @trim = ();
			
		if ($model =~ m/InnoDisk|KingFast|SATA SSD|TS512GMSA370|TS64GMTS552T2/) {
			@hdparm = myback("hdparm -I /dev/sda");
			@trim = grep(/TRIM/, @hdparm);
		}

		# Test fstab
		my $fstabfh;
		eval {
			open ($fstabfh, "<", "/etc/fstab");
		};
		if (!$@) {
			foreach (<$fstabfh>) {
				$fstabdiscard++ if (m/^UUID=.*discard/);
			}
			close $fstabfh;
		}
		
		# Test proc
		my $procfh;
		eval {
			open ($procfh, "<", "/proc/mounts");
		};
		if (!$@) {
			foreach (<$procfh>) {
				$procdiscard++ if (m/discard/);
			}
			close $procfh;
		}

		# Set automatic Trim
		if ($fstabdiscard == 0) {
			if ($#trim >=0) {
				system ("echo Correctif intégré trim >> $VARLOGFILE");

				unlink("/etc/fstab.bak");
				mysystem("sed -e \'/^UUID=/s/defaults/defaults,discard/g\' -e \'/^UUID=/s/sw\t/sw,discard\t/g\' -i.bak /etc/fstab >> $VARLOGFILE 2>&1");
				if ($? == 0) {
					unlink("/etc/fstab.bak");
				} else {
					mysystem("mv -vf /etc/fstab.bak /etc/fstab >> $VARLOGFILE 2>&1") if (-f "/etc/fstab.bak");
				}
				mysystem("touch /var/run/reboot-required");
				mysystem("echo trim >> /var/run/reboot-required.pkgs");
				mysystem("echo vm.swappiness=40 > /etc/sysctl.d/kwartz-swappiness.conf");
			}
		}
		
		# Force manual Trim
		if ($procdiscard == 0) {
			if ($#trim >=0) {
				if ( -x "/sbin/fstrim-all" ) {
					mysystem("fstrim-all --no-model-check >/dev/null 2>&1");
				} else {
					mysystem("fstrim -va >/dev/null 2>&1");
				}
			}
		}
	}
	## TRIM
	
	## BPO 06/10/2020 kwartz8
	# Correction kwartz-wapt et certificat Let's Encrypt
	#
	if ($ver =~ /^8\..+/ and $type eq "Server") {
		my $pkg_status=`dpkg -s kwartz-wapt | grep '^Status' | awk '{print \$4}'`; chomp $pkg_status;
		my $pkg_version=`dpkg -s kwartz-wapt | grep '^Version' | awk '{print \$2}'`; chomp $pkg_version;
		if ($pkg_status eq 'half-configured' and $pkg_version eq "2.0-3") {
			my $sslfile="/etc/ssl/private/kwartz.key";
			my $grp=`stat --printf=\"%G\" $sslfile`;
			my $perm=`stat --printf=\"%a\" $sslfile`;
			if ($grp ne "ssl-cert") {
				mysystem("chgrp -c ssl-cert $sslfile");
			}
			if ($perm ne "640") {
				mysystem("chmod -c 0640 $sslfile");
			}
			mysystem("invoke-rc.d postgresql restart");
			
			my $count = 10;
			while ($count--) {
				if (system("pg_isready") > 0) {
					sleep(1);
					next;
				}
				if (mysystem("su - postgres -c \"psql -lqt | cut -d \\| -f 1 | grep -qw wapt\"") != 0) {
					mysystem("mv /etc/kwartz/.waptpgpw /etc/kwartz/.waptpgpw.nodb");
				}
				mysystem("dpkg --configure kwartz-wapt >> $VARLOGFILE 2>&1");
				last;
			}
		}
	}
	
	######################################################
	# BPO 20210524 / 20230109
	# Correction préventive mise à jour K7->K8 kwartz-ldap
	# /home/install/-|
	#                |-updates-|
	#                          |-downgrades8-|
	#                                        |-pool-|
	#                                        |      |-local-|
	#                                        |              |- kwartz-ldap_5.2-6_all.deb
	#                                        |-Packages
	#                                        |-Packages.gz
	#                                        |-Release
	##

	# do_ftparchive ($label)
	# executes apt-ftparchive on cwd
	##
	sub DoFtparchive
	{
		my ($label) = @_;
		
		unlink "Packages", "Pacgages.gz", "Release";
		mypopenprint("apt-ftparchive packages pool | tee Packages | gzip -9 > Packages.gz 2>&1");
		mypopenprint("apt-ftparchive \\
					-o APT::FTPArchive::Release::Origin=\"Kwartz\" \\
					-o APT::FTPArchive::Release::Label=\"$label\" \\
					-o APT::FTPArchive::Release::Architectures=\"i386 amd64 noarch\" \\
					-o APT::FTPArchive::Release::Components=\"local\" \\
					-o APT::FTPArchive::Release::Description=\"Kwartz\" \\
					-o APT::FTPArchive::Release::Codename=\"Kwartz\" \\
					-o APT::FTPArchive::Release::Suite=\"Kwartz\" \\
					release . > ./Release.tmp; \\
			mv ./Release.tmp ./Release 2>&1");
	}

	if ( ! -d "/home/install" ) {
		mysystem("mkdir /home/install >> $VARLOGFILE 2>&1");
		mysystem("chown install: /home/install >> $VARLOGFILE 2>&1");
	}
	if ( ! -d "/home/install/updates" ) {
		mysystem("mkdir /home/install/updates >> $VARLOGFILE 2>&1");
	}
	if ($ver =~ /^7\..+/ and $type eq "Server") {
		if ( ! -d "/home/install/updates/downgrades8/pool/local" ) {
			mysystem("mkdir -p /home/install/updates/downgrades8/pool/local >> $VARLOGFILE 2>&1");
		}
		if (! -f "/home/install/updates/downgrades8/pool/local/kwartz-ldap_5.2-6_all.deb") {
			my $output = "";
			my $oldFH;
			my $outputFH;
			if ( open $outputFH, '>', \$output ) {
				$oldFH = select $outputFH;
				$| = 1;
			}
			
			my $curdir = getcwd();
			chdir ("/home/install/updates/downgrades8/pool/local");
			DoOptaCommand("update");
			DoOptaCommand("download", "", "", ("kwartz-ldap=5.2-6"));
			chdir("../..");
			DoFtparchive("Kwartz8Downgrade");
			chdir($curdir);
			
			select $oldFH if (defined($oldFH));
			close $outputFH if (defined($outputFH));

			print "Prepare downgrade kwartz-ldap 5.2-6 : $output\n";
			if ( open (my $fh, ">>", $VARLOGFILE) ) {
				print $fh "Prepare downgrade kwartz-ldap 5.2-6 :\n";
				print $fh $output;
				close ($fh);
			}
		}
	}
	# End kwartz-ldap_5.2-6_all.deb
	##
	
	# BPO 20210604 Kwartz8 kwartz-server
	# Starting 2 pkgProblemResolver with broken count: 1
	# Investigating (0) kwartz-server [ amd64 ] < 8.0-0 -> 8.0-4 > ( unknown )
	# Broken kwartz-server:amd64 Dépend on kwartz-hostsusage [ amd64 ] < none -> 4.5-7 > ( unknown )
	#   Considering kwartz-hostsusage:amd64 1 as a solution to kwartz-server:amd64 2
	#   Holding Back kwartz-server:amd64 rather than change kwartz-hostsusage:amd64
	#  Try to Re-Instate (1) kwartz-server:amd64
	# Done	
	if ($ver =~ /^8\..+/ and $type eq "Server") {
		my $installed=installed_version("kwartz-server");
		my $candidate=candidate_version("kwartz-server");
		if (($installed ne "") && ($candidate ne "")) {
			if ( system("dpkg", "--compare-versions", $candidate, 'gt', $installed) == 0 ) {
				system ("echo \"kwartz-server didn't install\" >> $VARLOGFILE");
				if ( system("dpkg", "--compare-versions", $candidate, 'ge', '8.0-4') == 0 ) {
					mysystem("apt-get -qy purge libaccountsservice0 >> $VARLOGFILE 2>&1");
				}
			}
		}	
	}
	##
	
	## BPO 08/07/2021 kwartz7+
	# Correction éventuelle suppression de kwartz-agent
	#
	if ((Compare_Version("7.0r0")>=0) and $type eq "Server") {
		my $installed=installed_version("kwartz-agent");
		if ($installed eq "") {
			DoOptaCommand("update");
			DoOptaCommand("install", "", "", ("kwartz-agent"));
		}
	}
	##
	
	## BPO 09/07/2021 kwartz8
	# Ajout de fsck.repair=yes pour systemd
	if ((-f "/etc/default/grub") &&
		(((Compare_Version("8.0r0")>=0) and $type eq "Server") ||
		 ((Compare_Version("3.0r0")>=0) and $type eq "kmcbox"))) {
		if (system('grep -q "fsck.repair=yes" /etc/default/grub') != 0) {
			mysystem("sed -E 's/GRUB_CMDLINE_LINUX=\"(.*)\"/GRUB_CMDLINE_LINUX=\"\\1 fsck.repair=yes\"/' -i.bak /etc/default/grub >> $VARLOGFILE 2>&1");
			if ($? == 0) {
				unlink("/etc/default/grub.bak");
				mysystem("update-grub >> $VARLOGFILE 2>&1");
			} else {
				mysystem("mv -vf /etc/default/grub.bak /etc/default/grub >> $VARLOGFILE 2>&1");
			}
		}
	}

	## BPO 23/08/2021 kwartz8
	# Ajout d'un .ackrc (présent en install kwartz9)
	if ((! -f "/root/.ackrc" ) &&
		(($type eq "Server" and (Compare_Version("8.0r0")>=0)) ||
		 ($type eq "kmcbox" and (Compare_Version("3.0r0")>=0)))) {
		if ( open (my $fh, ">>", "/tmp/.ackrc") ) {
			print $fh <<EOF;
--pager=less -r
--type-add=perl=.ep
EOF
			close $fh;
	        system("chmod 0644 /tmp/.ackrc");
	        system("cp -af /tmp/.ackrc /root/. || true");
	        if ( -d "/home/winadmin" ) {
				system( "cp -af /tmp/.ackrc /home/winadmin/. || true" );
				system( "chown 999.999 /home/winadmin/.ackrc || true" );
    	    }
	        unlink ("/tmp/.ackrc");
		}
	}
		
	##
	# Catch all après correctifs ciblés
	##
	#  Ajout BPO 08/04/2022 kwartz9
	#	rencontré si	: Install k9, restauration sauvegarde, maj distance
	#	non rencontré si: Install k9, maj distance, restauration sauvegarde 
	#	gestion :
	#		root@kwartz-server(9.0r0):~# dpkg -C
	#		Les paquets suivants ont activé le traitement d'actions différées,
	#		mais ce traitement n'est pas terminé. Le traitement d'actions différées
	#		peut être demandé via « dselect » ou « dpkg --configure --pending »
	#		( ou encore « dpkg --triggers-only » ) : 
	#		 systemd              system and service manager
	#
	#		root@kwartz-server(9.0r0):~# LANG=C dpkg -C
	#		The following packages have been triggered, but the trigger processing
	#		has not yet been done.  Trigger processing can be requested using
	#		dselect or dpkg --configure --pending (or dpkg --triggers-only):
	#		 systemd              system and service manager
	##

	my @dpkgc = myback_noshell("env LANG=C dpkg -C");
	if ( $#dpkgc >= 0) {
		$ENV{UCF_FORCE_CONFOLD}="1";
		mysystem("dpkg --force-confold --configure -a >> $VARLOGFILE 2>&1");
		mysystem("dpkg --triggers-only -a >> $VARLOGFILE 2>&1");
	}
	
	##
	# Nettoyage /etc/init/kwartz-configure.conf
	##
	#  BPO 19/05/2022
	#   Efface /etc/init/kwartz-configure.conf si :
	#		* On est en systemd
	#		* La mise à jour est OK : kwartz-configure.service n'est pas configuré pour démarrer
	##
	
	if ( -e "/etc/init/kwartz-configure.conf" ) {
		my $initname=`ps --no-headers -o comm 1`; chomp ($initname);
		if ( $initname eq "systemd" ) {
			if ( ! -l "/lib/systemd/system/kwartz.target.wants/kwartz-configure.service" ) {
				system ("echo Effacement /etc/init/kwartz-configure.conf >> $VARLOGFILE");
				unlink ("/etc/init/kwartz-configure.conf");
			}
		}
	}
	
	##
	# Nettoyage des téléchargements clamav dans les logs de mise à jour
	# gzip des fichiers plus anciens que 30 jours
	##
	#  BPO 24/05/2022
	##
	my $exclude = "";
	if ( -l "/root/kwartz-configuration.log" ) {
		my $curtarget = readlink "/root/kwartz-configuration.log";
		$curtarget  =~ s/.*\///s;
		$exclude = "! -name \"$curtarget\"";
	}
		
	mysystem("find /root/kwartz -type f -size +2M  $exclude ! -name \"*.gz\" -exec sed -i \'s/^\\x1b\\[?7l.*//g\' {} \\; -exec gzip -qf {} \\;");
	mysystem("find /root/kwartz -type f -mtime +30 ! -name \"*.gz\" -exec gzip -qf {} \\;");
	##
	
	##
	# Installation de db-util sur Kwartz9
	##
	# BPO 06/07/2022
	##
	
	if (Compare_Version("9.0r0")>=0 and $type eq "Server") {
		my $installed=installed_version("db-util");
		if ($installed eq "") {
			DoOptaCommand("update");
			DoOptaCommand("install", "", "", ("db-util"));
		}
	}
	##
	
	##
	# fail2ban sur mise à jour kmcbox3/kwartz8
	# on a [ssh] dans jail.local, mais le filter est sshd
	#
	##
	#  BPO 31/01/2023
	##
	if ((-f "/etc/fail2ban/jail.local") &&
		(($type eq "Server" and (Compare_Version("8.0r0")>=0)) ||
		 ($type eq "kmcbox" and (Compare_Version("3.0r0")>=0)))) {
		 
		if (system('grep -q "[ssh]" /etc/fail2ban/jail.local') == 0) {
			mysystem(q{sed -e 's/\[ssh\]/\[sshd\]/' -i.bak /etc/fail2ban/jail.local}." >> $VARLOGFILE 2>&1");
			if ($? == 0) {
				unlink("/etc/fail2ban/jail.local.bak");
				mysystem("invoke-rc.d fail2ban restart >> $VARLOGFILE 2>&1");
			} else {
				mysystem("mv -vf /etc/fail2ban/jail.local.bak /etc/fail2ban/jail.local >> $VARLOGFILE 2>&1");
			}
		}
	}
	##
	
	# BPO 28/03/2023 kwartz9
	# kwartz-common 4.1-29 update breaks 
	# postinst if no /var/log/journal/*/user-*.journal
	# rm: impossible de supprimer '/var/log/journal/*/user-*.journal': Aucun fichier ou dossier de ce type
	# dpkg: erreur de traitement du paquet kwartz-common (--configure) :
	# installed kwartz-common package post-installation script subprocess returned error exit status 1

	if (($ver =~ /^9\..+/) and $type eq "Server") {	
		my $pkg_version=`dpkg -s kwartz-common | grep '^Version' | awk '{print \$2}'`;
		chomp $pkg_version;
		my $pkg_status=`dpkg -s kwartz-common | grep '^Status' | awk '{print \$4}'`;
		chomp $pkg_status;
		if (($pkg_status eq 'half-configured') and
			($pkg_version eq '4.1-29')) {
			# /var/log/journal/*/user-*.journal
			my @journalfiles = glob("/var/log/journal/*/user-*.journal");
			if ($#journalfiles == -1) {
				my @journaldir = glob("/var/log/journal/*");
				if ($#journaldir >= 0) {
					mysystem("touch $journaldir[0]/user-000.journal");
					mysystem("dpkg --configure -a >> $VARLOGFILE 2>&1");
				}
			}
		}
	}
	
	# BPO 28/08/2023
	# packages.gz missed winbind
	# removed kwartz-backup kwartz-cdtower kwartz-cups kwartz-hostsusage kwartz-samba kwartz-server winbind
	if (($ver =~ /^8\..+/) and $type eq "Server") {
		my $installed=installed_version("winbind");
		if ($installed eq "") {
			DoOptaCommand("update");
			DoOptaCommand("install", "", "", ("kwartz-backup", "kwartz-cdtower", "kwartz-cups", "kwartz-hostsusage", "kwartz-samba", "kwartz-server", "winbind"), ">> $VARLOGFILE");
		}
	}
	
	# BPO 01/09/2023
	# Correction mise a jour kmcbox4 casse sshd : serveur inaccessible pour corriger les autres paquets cassés
	if (($ver =~ /^3\..+/) and $type eq "kmcbox") {
		my $installed=installed_version("openssh-server");
		if ($installed eq "1:8.2p1-4ubuntu0.2") {
			my $pkg_status=`dpkg -s openssh-server | grep '^Status' | awk '{print \$4}'`;
			chomp $pkg_status;
			if ($pkg_status ne 'installed') {
				if ( ! -d "/home/install/updates" ) {
					mysystem("mkdir -p /home/install/updates >> $VARLOGFILE 2>&1");
				}
				if (! -f "/home/install/updates/openssh-server_1%3a7.2p2-4ubuntu2.8_amd64.deb") {
					my $output = "";
					my $oldFH;
					my $outputFH;
					if ( open $outputFH, '>', \$output ) {
						$oldFH = select $outputFH;
						$| = 1;
					}
					
					my $curdir = getcwd();
					chdir ("/home/install/updates");
					DoOptaCommand("update");
					DoOptaCommand("download", "", "", ("openssh-server=1:7.2p2-4ubuntu2.8"));
					system("mkdir -p /var/run/sshd");
					system("dpkg -i openssh-server_1%3a7.2p2-4ubuntu2.8_amd64.deb");
					system("invoke-rc.d ssh restart");
					chdir($curdir);
					
					select $oldFH if (defined($oldFH));
					close $outputFH if (defined($outputFH));
					
					print "Prepare fix openssh-server : $output\n";
					if ( open (my $fh, ">>", $VARLOGFILE) ) {
						print $fh "Prepare fix openssh-server :\n";
						print $fh $output;
						close ($fh);
					}
				}
			}	
		}
	}
	
	# BPO 05/09/2023
	# Fix si rejouer update_kmcbox3.sh n'a pas reconstruit le lien iptables
	if (((Compare_Version("9.0r0")>=0) and $type eq "Server") ||
		((Compare_Version("4.0r0")>=0) and $type eq "kmcbox")) {
		if (( ! -l "/sbin/iptables" ) &&
			( -e "/usr/sbin/iptables" )) {
			system("ln -fs /usr/sbin/iptables /sbin/iptables");
		}
	}
	
	# BPO 08/09/2023
	# Fix erroneous grub.cfg
	if ( -f "/boot/grub/grub.cfg" ) {
		if (system("grep -q root=PARTUUID /boot/grub/grub.cfg") == 0) {
			system("echo renew grub.cfg... >>$VARLOGFILE 2>&1");
			mysystem("update-grub >>$VARLOGFILE 2>&1");
		}
	}
	
	# BPO XX/XX/2025
	# Kwartz9 : Mise à jour du linux-firmware pour les proliant ML30 Gen10
#	if (($ver =~ /^9\..+/) and $type eq "Server") {
#		my $installed=installed_version("linux-firmware");
#		if ($installed eq "1.187") {
#			if (( -f   "/sys/class/dmi/id/product_name" ) &&
#			    ( ! -z "/sys/class/dmi/id/product_name" )) {
#			    if (open(FH, '<', "/sys/class/dmi/id/product_name")) {
#					my $product = <FH>; chomp $product;
#					close FH;
#					if ($product =~ m/ML30 Gen10/) {
#					}
#				}
#			}
#		}
#	}

	# BPO 10/01/2024
	# Fix borg cache wrongly fills all / free space
	if (($type eq "Server" and (Compare_Version("8.0r0")>=0)) ||
		($type eq "kmcbox" and (Compare_Version("3.0r0")>=0))) {
		
		goto FIXBORGEND if (Get_Free_Size("/") > 1000); # Teste > 1 Mo

		my $rootpart=`/sbin/blkid --label KWARTZ_ROOT`; chomp $rootpart;
		goto FIXBORGEND if ($rootpart !~ m/^\/dev.*/);
		
		goto FIXBORGEND if (is_mounted("/media"));
		
		eval {
			mysystem("mount $rootpart /media");
		};
		goto FIXBORGEND if ($@);
		
		mysystem("rm -rf /media/home/kwartz");
		mysystem("umount /media");
FIXBORGEND:
	}
	##
	
	# BPO 11/01/2024
	# Fix borg wrongly fills mountpoint when not mounted
	# 23/01/2024 : Add /var/kwartz/backup (seen lic 6172)
	my @borgmntpoints = ("/mnt/backup.borg",
						 "/mnt/backup.usb",
						 "/var/kwartz/backup");
	foreach (@borgmntpoints) {
		if (( -d $_ ) &&
			(! is_mounted($_)) &&
			(Get_Used_Dir_Size($_) > 1)) {
			mysystem("rm -rf $_/*") if ($_ =~ m/^\/mnt\/backup/);
			mysystem("rm -rf $_/*") if ($_ =~ m/^\/var\/kwartz\/backup/);
		}
	}
	##
	
	# BPO 02/10/2025 kmcbox4
	# Correction kwartz-nextcloud éjecté dû au dépôt local de php7.4 incomplet
	if ($type eq "kmcbox" and (Compare_Version("4.0r0")>=0)) {
		# kmcbox4
		my $installed=installed_version("kwartz-nextcloud");
		if ($installed eq "") {
			DoOptaCommand("update");
			DoOptaCommand("install", "", "", ("kwartz-nextcloud", "kwartz-nextcloud-sqlite"));
		}
	}
	##
	
	# BPO 28/01/2026 kwartz9/10 kmcbox4/5
	# Récupération de kwartz-mdm si il est supprimé
	if (($type eq "Server" and (Compare_Version("9.0r0")>=0)) ||
		($type eq "kmcbox" and (Compare_Version("4.0r0")>=0))) {
		my $installed=installed_version("kwartz-mdm");
		if ($installed eq "") {
			DoOptaCommand("update");
			DoOptaCommand("install", "", "", ("kwartz-mdm"));
		}
	}
	
	# BPO 06/01/2026 kwartz9+
	# Nettoyage du sun jre 6
	if ($type eq "Server" and (Compare_Version("9.0r0")>=0)) {
		my $installed=installed_version("openjdk-8-jre-headless");
		if ($installed ne "") {
			$installed=installed_version("sun-java6-jre");
			if ($installed ne "") {
				mysystem("apt-get -qy purge sun-java6-jre sun-java6-bin");
			}
		}
	}
	
	# BPO 04/05/2026 kwartz10/kmcbox5
	if (($type eq "Server" and (Compare_Version("10.0r0")>=0)) ||
		($type eq "kmcbox" and (Compare_Version("5.0r0")>=0))) {
		my $installed=installed_version("unattended-upgrades");
		if ($installed eq "") {
			DoOptaCommand("update");
			DoOptaCommand("install", "", "", ("unattended-upgrades"));
		}
	}
	##
	
	# BPO 20260814 : nommage interfaces sur lilo (kwartz10 / kmcbox5)
	# Si /etc/lilo.conf force net.ifnames=0 (noms non predictibles), on bascule vers
	# le prefixe enpkwartz (net.ifnames.prefix=enpkwartz) puis on reexecute lilo.
	# La condition grep sur net.ifnames=0 rend le bloc idempotent.
	if ((-f "/etc/lilo.conf") &&
		(($type eq "Server" and (Compare_Version("10.0r0")>=0)) ||
		($type eq "kmcbox" and (Compare_Version("5.0r0")>=0))) &&
		(mysystem("grep -q 'net\\.ifnames=0' /etc/lilo.conf") == 0) ) {
		mysystem("echo 'SelfHeal: bascule net.ifnames=0 vers net.ifnames.prefix=enpkwartz dans lilo.conf' >> $VARLOGFILE 2>&1");
		mysystem("sed -i 's/net\\.ifnames=0/net.ifnames.prefix=enpkwartz/' /etc/lilo.conf >> $VARLOGFILE 2>&1");
		mysystem("lilo >> $VARLOGFILE 2>&1");
		# Le renommage d'interface ne sera effectif qu'au prochain boot : on pose un flag
		# persistant (/var/run est volatil, donc on écrit dans /etc/kwartz) pour que le
		# bloc post-reboot reconfigure le DHCP, et on signale le reboot nécessaire à l'admin
		# (reboot non automatique).
		mysystem("touch /etc/kwartz/dhcp/reconf-after-reboot >> $VARLOGFILE 2>&1");
		mysystem("touch /var/run/reboot-required");
		mysystem("echo lilo-net-rename >> /var/run/reboot-required.pkgs");
	}
	##
	
	# BPO 20260814 : reconfiguration DHCP après le reboot ayant appliqué le renommage
	# d'interface (flag posé par le bloc lilo ci-dessus). Une fois les interfaces
	# renommées (enpkwartz*), on régénère /etc/default/isc-dhcp-server via dhcpconf,
	# puis on (ré)active le service. Le flag est effacé systématiquement (pas de
	# réessai en boucle), le résultat étant journalisé dans $VARLOGFILE.
	# Garde : on n'agit que si /proc/cmdline contient déjà net.ifnames.prefix=enpkwartz,
	# preuve que le reboot appliquant le renommage a bien eu lieu. Sinon on ne touche à
	# rien et on garde le flag pour réessayer après le vrai reboot (sinon dhcpconf
	# régénérerait la conf avec l'ancien préfixe d'interface).
	if ((-f "/etc/kwartz/dhcp/reconf-after-reboot") &&
			(mysystem("grep -q 'net\\.ifnames\\.prefix=enpkwartz' /proc/cmdline") == 0)) {
		mysystem("echo 'SelfHeal: reconfiguration DHCP post-renommage interface (flag lilo-net-rename)' >> $VARLOGFILE 2>&1");
		my $dhcpconf_ret = mysystem("/usr/lib/kwartz/install/dhcpconf >> $VARLOGFILE 2>&1");
		if ($dhcpconf_ret == 0) {
			mysystem("echo 'SelfHeal: dhcpconf OK, activation isc-dhcp-server' >> $VARLOGFILE 2>&1");
			mysystem("systemctl enable --now isc-dhcp-server >> $VARLOGFILE 2>&1");
		} else {
			my $code = $dhcpconf_ret >> 8;
			mysystem("echo 'SelfHeal: dhcpconf a echoue (code $code), isc-dhcp-server non active' >> $VARLOGFILE 2>&1");
		}
		unlink("/etc/kwartz/dhcp/reconf-after-reboot");
		mysystem("echo 'SelfHeal: flag reconf-after-reboot efface' >> $VARLOGFILE 2>&1");
	}
	##
	
	# BPO 27/08/2026 kwartz9/kwartz10 : contournement bug IOMMU H240/Gen9 (DMAR)
	if ((Compare_Version("9.0r0")>=0) and $type eq "Server") {
		FixH240Iommu();
	}
	##
	
	# BPO 20260313
	# Préparation downgrade kwartz-deploy en 0.96
#	if ($type eq "Server" and (Compare_Version("10.0r0")>=0)) {
#		my $installed=installed_version("kwartz-deploy");
#		my $last=candidate_more_recent_500_version("kwartz-deploy");
#		if ($installed ne "") {
#			if ( system("dpkg", "--compare-versions", $installed, 'eq', '1.04') == 0 ) {
#				my $pref = <<_MARKER_;
#Package: kwartz-deploy
#Pin: version 0.96
#Pin-Priority: 1000
#_MARKER_
#
#				do_install_preference("kwartz-deploy", $pref);
#			} elsif (system("dpkg", "--compare-versions", $last, 'gt', '0.96') == 0 ) {
#				do_remove_preference("kwartz-deploy");
#			}
#		}
#	}
	
} # SelfHeal()

unless (defined($opt_a)) {
	SelfHeal();
}

# DoOptaCommand
#
# Execute command for -a ... call
# DoOptaCommand($command, $simulate, $options, @listpks, $redir)
##
sub DoOptaCommand
{
	my ($command, $simulate, $options, @listpks, $redir) = @_;
	my $redirection = $redir || '';
	
	if ($command eq "update") {
		AptUpdate();
	} elsif ($command eq "install") {
		my $pkglist = join(" ", @listpks);
		# bpo 20210521 : add --force-yes to handle downgrades
		# bpo 20210526 : handles --force-yes deprecation
		mypopenprint("apt-get $aptsyntax{'allowdowngrade'} -quy$simulate $options -o DPKG::Options::=\"--force-overwrite\" -o Dir::Etc::SourceList=$SOURCE_S -o Dir::Etc::SourceParts='/etc/kwartz/sources.list.d/' install $pkglist $redirection 2>&1");
	} elsif ($command eq "dist-upgrade") {
		mypopenprint("apt-get $aptsyntax{'allowdowngrade'} -quy$simulate $options -o DPKG::Options::=\"--force-overwrite\" -o Dir::Etc::SourceList=$SOURCE_S -o Dir::Etc::SourceParts='/etc/kwartz/sources.list.d/' dist-upgrade $redirection 2>&1");
	} elsif ($command eq "download") {
		my $pkglist = join(" ", @listpks);
		mypopenprint("apt-get -o Dir::Etc::SourceList=$SOURCE_S -o Dir::Etc::SourceParts='/etc/kwartz/sources.list.d/' download $pkglist $redirection 2>&1");
	}
}

# Check current dpkg status after SelfHeal()
my @dpkgc = myback_noshell("env LANG=C dpkg -C");
my @dpkgko;
my $pklist = "";

if ( $#dpkgc >= 0) {
	my $trigger = 0;
	
	foreach (@dpkgc) {
		if (m/The following packages have been triggered/) {
			$trigger = 1;
		}
		if ($trigger == 0) {
			push (@dpkgko, $1) if (m/^ (\S+)/);
		}
	}
}

if (( $#dpkgko >= 0) && ( !defined($opt_a))) {
	print "Une installation précédente a échoué.\n";
	foreach (@dpkgko) {
		$pklist.=" $1" if (m/^ (\S+)/);
	}
	print "$pklist\n";
	myexitlog(	"",	# Force no more print on STDOUT
				"Une installation précédente a échoué",
				RET_OLDINSTALL);
}

# verif repository apt
unless (defined($opt_a)) {
	my $aptres=mysystem("apt-get -qq check  >> $VARLOGFILE 2>&1");
	if ($aptres != 0) {
		mysystem("apt-get -qq update  >> $VARLOGFILE 2>&1");
	}
}

# Gestion dist_upgrade
if (defined($opt_U)) {
	$gIso = $opt_U;
}

if (defined($opt_i)) {
	my @list;
	
	mysystem("/usr/sbin/kwartz-pin-kmc  >> $VARLOGFILE 2>&1"); 

	$gres = Update (1, \@list);
	# Affichage en sortie de la liste des paquets en attente de mise à jour
	# Gestion du cache. J'utilise le même fichier temporaire que kwartz-agent
	my $tmp_file = "$UPDATE_CACHE.tmp";
	my $fileno = fileno(STDOUT);
	my $qfn = "";
	my $fh = undef;
	
	##
	# Pas de gestion du cache si le flux de sortie (STDOUT) est déjà redirigé vers le fichier temporaire
	##
	if (!defined($fileno) ||
		($fileno < 0) ||
		!($qfn = readlink("/proc/$$/fd/$fileno")) ||
		($qfn ne $tmp_file)) {
		open($fh, ">", "$tmp_file");
	}
	
	# Affichage des paquets sur STDOUT et dans l'éventuel fichier temporaire
	foreach(sort @list) {
		print "$_\n";
		print $fh "$_\n" if (defined ($fh));
	}
	
	if (defined($fh) &&
		close($fh)) {
		unlink "$UPDATE_CACHE.bak";
		unlink "$UPDATE_CACHE.error";
		move $UPDATE_CACHE,"$UPDATE_CACHE.bak";
		move $tmp_file, $UPDATE_CACHE;
	}
} elsif (defined($opt_u)) {
	my @list;
		
	# erase cache
	unlink "$UPDATE_CACHE";
	if (-f "/var/kwartz/renew_autoupdate") {
		unlink "/var/kwartz/renew_autoupdate";
	}
	my ($res2, $handle2) = acquire_lock ($SEM, 1000, 10); # 1 seconde
	mklogfile();
	release_lock($handle2);
	
	system ("echo -n Update: >> $VARLOGFILE");
	
	# must handle kmc downgrade first else apt-get dist-upgrade fail with
	#  There are problems and -y was used without --force-yes
	handle_kmc_downgrade();

	$gres = Update (0, \@list);
	system ("echo $? >> $VARLOGFILE");


	# tmp aFL
	# quick fix for trac #2241
	# check dhcpd failed to purge apparmor cache, reconfigure 
	if (-x '/sbin/apparmor_parser') {
		if ( system("systemctl is-failed isc-dhcp-server") == 0 ) {
			system ("echo 'apparmor_parser --purge-cache' >> $VARLOGFILE");
			mysystem("/sbin/apparmor_parser --purge-cache");
			mysystem("dpkg-reconfigure kwartz-dhcp");
		}
	}


	# mysystem("cat $VARLOGFILE >> $LOGFILE");
	
	# Appel du hook
    mysystem("/usr/sbin/kwartz-hook emit core/autoupdate") if -x "/usr/sbin/kwartz-hook";

} elsif (defined($opt_a)) {
	($#ARGV >= 0 ) or die $usage;
	my $command = shift @ARGV;
	if (($command eq "install") ||
		($command eq "download") ||
		($command eq "dist-upgrade") ||
		($command eq "update")) {
		
		my $options = "";
		if (defined($opt_o)) {
			foreach (split(/ /, $opt_o)) {
				$options .= "-o Dpkg::Options::=\"$_\" ";
			}
		}
		$options .= defined($opt_d)?"-o Debug::pkgProblemResolver=yes":"";

		DoOptaCommand(  $command,
						defined($opt_s)?"s":"",
						$options,
						@ARGV);
	} else {
		die $usage;
	}
} elsif ((defined $opt_g) || (defined $opt_G)) {
	($#ARGV >= 0 ) or die $usage;
	
	my $curarch = `dpkg --print-architecture`;
	my @pending; # liste des noms de paquets
	my @missing;
	my @glpredeps;
	my @installlist;

	foreach my $pkt (@ARGV) {
		push @installlist, $pkt;
		push (@pending, $1) if ($pkt =~ (m/(.*?)_.*/));
	}
	
	foreach my $pkt (@ARGV) {
		my $arch = `dpkg -I $pkt | grep Architecture | sed -s 's/ Architecture: //'`; chomp $arch;
		my @deps = split(',', myback("dpkg -I $pkt | grep \" Depends\" | sed -s 's/ Depends: //'"));
		my @predeps = split(',', myback("dpkg -I $pkt | grep \" Pre-Depends\" | sed -s 's/ Pre-Depends: //'"));
		push @deps, @predeps;
		
		foreach (@predeps) {
			my $pn = (split(' ', $_))[0];  # ignore la version;
			push @glpredeps, $pn if (!grep(/^$pn$/, @glpredeps)); #dedup
		}
		
		foreach (@deps) {
			my @choices;
			my $found;
			my $dep;

			s/^\s+//;
			if (m/\|/) {
				# Entrées multiple
				my @multiple = split('\|', $_);
DEPS:			foreach (@multiple) {
					$dep = $_;
					$dep =~ (s/^\s+//);
					# dep
					# dep (= ver)
					# dev (>> ver)
					# dep (>= ver)
					my ($pkt, $op, $ver) = ($1, $2, $3) if ($dep =~ (m/([a-z0-9-+:\.]+)(?= \(([=><]+) (.*)\))?/));
					my @versions= myback("apt-cache -o Dir::Etc::SourceList=$SOURCE_S -o Dir::Etc::SourceParts='/etc/kwartz/sources.list.d/' show $pkt | grep 'Version:' | sed 's/Version: //'");
					if ($#versions>=0) {
						if (defined($ver)) {
							foreach my $tver (@versions) {
								chomp  $tver;
								if (mysystem("dpkg --compare-versions $tver \'$op\' $ver") == 0) {
									last DEPS;
								}
							}
						} else {
							last DEPS;
						}
					}
				}
			} else {
				$dep = $_;
			}
			$dep =~ s/\((=.*)\)/=$1/;
			$dep =~ s/\([><].*\)//;
			$dep =~ s/\s+//g;
			push (@choices, $dep);
			
			# Spécifie l'architecture si différente de celle par défaut
			if (("$arch" ne "all") &&
				("$curarch" ne "$arch")) {
				foreach(@choices) {
					s/([a-z0-9-+:\.]+)(\=?.*)/$1:${arch}$2/;
				}
			}
			$found=0;
			foreach my $p (@choices) {
				#TODO: prendre en compte la version
				my $k=(split('=', $p))[0];
				my $v1=installed_version($k);
				# Si le paquet est présent, sortie et flag positionné
				# Si le paquet est déjà dans la liste des manquants, idem
				# Si le paquet est dans la liste des dpkg, idem
				if (("$v1" ne "") ||
					(grep (/^\Q$k\E$/, @missing)) ||
					(grep (/^\Q$k\E$/, @pending))) {
					$found++;
					last;
				}
				
			}
			# Si plusieurs choix possibles dans les dépendances, on sélectionne d'autorité la première
			if ($found==0) {
				push (@missing, $choices[0]);
			}
		}
	}
	my @inst;
	if ($#missing>=0) {
		my @correctedmissing = ();
		my @inst;
		# Recup de l'ensemble des paquets installés
		$gres = Update (1, \@inst, @missing);

		# Verif si des nouvelles dépendances ont été recompilées
		foreach (@inst) {
			next if (!m/(^\S+)\s.*/);
			my $pkt = $1;
			if (my @specific=grep (/^\Q$pkt\E=\S+/, @missing)) {
				push @correctedmissing, $specific[0];
				next;
			}
			
			# Test si dans le lot un paquet a été recompilé pour Kwartz
			my @versions= myback("apt-cache -o Dir::Etc::SourceList=$SOURCE_S -o Dir::Etc::SourceParts='/etc/kwartz/sources.list.d/' show $pkt | grep 'Version:' | sed 's/Version: //'");
			if (my @kwartz=grep (/.*kwartz.*/, @versions)) {
				my $verkwartz = $kwartz[0];
				chomp $verkwartz;
				push @correctedmissing, "$pkt=$verkwartz";
				next;
			}
			push @correctedmissing, $pkt;
		}
		# Verif si ça marche toujours avec des dépendances recompilées
		if ($#correctedmissing>=0) {
			$gres = Update (1, \@inst, @correctedmissing);

			# On installe les dépendances
			if ($gres == 0) {
				$gres = Update (0, \@inst, @correctedmissing);
			}
		}
	}
		
	if ((defined $opt_G) && ($gres == 0)) {
	# TODO: Gérer l'ordre des pré-dépendances
		if ($#glpredeps>=0) {
			my @predep_indexes;
			for (my $id=0; $id <= $#pending; $id++) {
				push @predep_indexes, $id if (grep(/^$pending[$id]$/, @glpredeps));
			}
			foreach(@predep_indexes) {
				myback("dpkg -i $installlist[$_]");
			}
			foreach(@predep_indexes) {
				splice @installlist, $_, 1;
			}
		}
		myback("dpkg -i @installlist");
	}
} elsif (defined($opt_P)) {
	my $pkg = $opt_P;
	my @updates_s = myback("apt-cache -o Dir::Etc::SourceList=$SOURCE_S -o Dir::Etc::SourceParts='/etc/kwartz/sources.list.d/' policy $pkg 2>&1");
	foreach (@updates_s) {
		print;
	}
}

# Sortie
myexitlog (	"",	# TODO: Mettre quelque chose sur STDOUT ?
			"fin autoupdate",
			$gres);

END {
	# $opt_C : no res backuped for check
	# $opt_D : if kept, res for parent and grandson. So son is in charge to backup res
	unless (defined($opt_C) or defined($opt_D)) {
		open (my $resfh, ">>", "$RUNDIR/$$.res");
		print $resfh " $?";
		close $resfh;
	}
}
