diff --git a/nur_src/cli/Application.php b/nur_src/cli/Application.php index e0906c8..da0db40 100644 --- a/nur_src/cli/Application.php +++ b/nur_src/cli/Application.php @@ -124,27 +124,27 @@ abstract class Application { $unlock = false; $stop = false; register_shutdown_function(function () use (&$unlock, &$stop) { - $self = app::get(); - if ($unlock) $self->getLockfile()->release(); - if ($stop) $self->getRunfile()->stop(); + $runfile = app::get()->getRunfile(); + if ($unlock) $runfile->release(); + if ($stop) $runfile->wfStop(); }); try { static::_app_init(); if (static::USE_RUNFILE) { - $self = app::get(); + $runfile = app::get()->getRunfile(); global $argc, $argv; if ($argc === 2 && $argv[1] === "--Application-Runlock-release") { - $self->getLockfile()->release(); + $runfile->release(); exit(0); } $useRunlock = static::USE_RUNLOCK; - if ($useRunlock && $self->getLockfile()->warnIfLocked()) { + if ($useRunlock && $runfile->warnIfLocked()) { exit(1); } - $self->getRunfile()->start(); + $runfile->wfStart(); $stop = true; if ($useRunlock) { - $self->getLockfile()->lock(); + $runfile->lock(); $unlock = true; } } diff --git a/src/A.php b/src/A.php index 8cfafdb..225f668 100644 --- a/src/A.php +++ b/src/A.php @@ -63,4 +63,36 @@ class A { static final function mpselect(?array &$dest, ?array $merge, ?array $pkeys): void { $dest = cl::mpselect($dest, $merge, $pkeys); } + + static final function append_nn(?array &$dest, $value, $key=null) { + if ($value !== null) { + if ($key === null) $dest[] = $value; + else $dest[$key] = $value; + } + return $value; + } + + static final function append_nz(?array &$dest, $value, $key=null) { + if ($value !== null && $value !== false) { + if ($key === null) $dest[] = $value; + else $dest[$key] = $value; + } + return $value; + } + + static final function prepend_nn(?array &$dest, $value) { + if ($value !== null) { + if ($dest === null) $dest = []; + array_unshift($dest, $value); + } + return $value; + } + + static final function prepend_nz(?array &$dest, $value) { + if ($value !== null && $value !== false) { + if ($dest === null) $dest = []; + array_unshift($dest, $value); + } + return $value; + } } diff --git a/src/app/RunFile.php b/src/app/RunFile.php index b5a5ae4..33b036b 100644 --- a/src/app/RunFile.php +++ b/src/app/RunFile.php @@ -4,6 +4,7 @@ namespace nur\sery\app; use nur\sery\cl; use nur\sery\file\SharedFile; use nur\sery\os\path; +use nur\sery\output\msg; use nur\sery\php\time\DateTime; use nur\sery\str; @@ -43,9 +44,15 @@ class RunFile { "pg_pid" => null, "pid" => posix_getpid(), "serial" => 0, + # lock + "locked" => false, + "date_lock" => null, + "date_release" => null, + # run "date_start" => $dateStart, "date_stop" => null, "exitcode" => null, + # action "action" => null, "action_date_start" => null, "action_current_step" => null, @@ -60,6 +67,93 @@ class RunFile { return $data; } + protected function willWrite(): array { + $file = $this->file; + $file->lockWrite(); + $data = $file->unserialize(null, false, true); + if (!is_array($data)) { + $data = $this->initData(); + $file->ftruncate(); + $file->serialize($data, false, true); + } + $file->ftruncate(); + return [$file, $data]; + } + + protected function serialize(SharedFile $file, array $data, ?array $merge=null): void { + $file->serialize(self::merge($data, $merge), true, true); + } + + protected function update(callable $func): void { + [$file, $data] = $this->willWrite(); + $merge = call_user_func($func, $data); + $this->serialize($file, $data, $merge); + } + + function haveWorked(int $serial, ?int &$currentSerial=null): bool { + $data = $this->read(); + $currentSerial = $data["serial"]; + return $serial !== $currentSerial; + } + + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # verrouillage par défaut + + function isLocked(?array &$data=null): bool { + $data = $this->read(); + return $data["locked"]; + } + + function warnIfLocked(?array $data=null): bool { + if ($data === null) $data = $this->read(); + if ($data["locked"]) { + msg::warning("$data[name]: possède le verrou depuis $data[date_lock]"); + return true; + } + return false; + } + + function lock(): bool { + $this->update(function ($data) use (&$locked) { + if ($data["locked"]) { + $locked = false; + return null; + } else { + $locked = true; + return [ + "locked" => true, + "date_lock" => new DateTime(), + "date_release" => null, + ]; + } + }); + return $locked; + } + + function release(): void { + $this->update(function ($data) { + return [ + "locked" => false, + "date_release" => new DateTime(), + ]; + }); + } + + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # cycle de vie de l'application + + /** + * indiquer que l'application démarre. l'état est entièrement réinitialisé, + * sauf le PID du leader qui est laissé en l'état + */ + function wfStart(): void { + $this->update(function (array $data) { + return cl::merge($this->initData(), [ + "pg_pid" => $data["pg_pid"], + ]); + }); + } + /** tester si l'application a déjà été démarrée */ function wasStarted(): bool { $data = $this->read(); @@ -97,6 +191,13 @@ class RunFile { return true; } + /** indiquer que l'application s'arrête */ + function wfStop(): void { + $this->update(function (array $data) { + return ["date_stop" => new DateTime()]; + }); + } + /** tester si l'application est déjà été stoppée */ function wasStopped(): bool { $data = $this->read(); @@ -109,46 +210,20 @@ class RunFile { return $data["date_start"] !== null && $data["date_stop"] !== null; } - function haveWorked(int $serial, ?int &$currentSerial=null): bool { - $data = $this->read(); - $currentSerial = $data["serial"]; - return $serial !== $currentSerial; - } - - protected function willWrite(): array { - $file = $this->file; - $file->lockWrite(); - $data = $file->unserialize(null, false, true); - if (!is_array($data)) { - $data = $this->initData(); - $file->ftruncate(); - $file->serialize($data, false, true); - } - $file->ftruncate(); - return [$file, $data]; - } - - protected function serialize(SharedFile $file, array $data, ?array $merge=null): void { - $file->serialize(self::merge($data, $merge), true, true); - } - - protected function update(callable $func): void { - [$file, $data] = $this->willWrite(); - $merge = call_user_func($func, $data); - $this->serialize($file, $data, $merge); - } - - /** indiquer que l'application démarre */ - function start(): void { - $this->update(function (array $data) { - # garder l'identifiant de process - $pgPid = $data["pg_pid"] ?? null; - return cl::merge($this->initData(), [ - "pg_pid" => $pgPid, - ]); + /** après l'arrêt de l'application, mettre à jour le code de retour */ + function wfStopped(int $exitcode): void { + $this->update(function (array $data) use ($exitcode) { + return [ + "pg_pid" => null, + "date_stop" => $data["date_stop"] ?? new DateTime(), + "exitcode" => $exitcode, + ]; }); } + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # gestion des actions + /** indiquer le début d'une action */ function action(?string $title, ?int $maxSteps=null): void { $this->update(function (array $data) use ($title, $maxSteps) { @@ -171,23 +246,8 @@ class RunFile { }); } - /** indiquer que l'application s'arrête */ - function stop(): void { - $this->update(function (array $data) { - return ["date_stop" => new DateTime()]; - }); - } - - /** après l'arrêt de l'application, mettre à jour le code de retour */ - function stopped(int $exitcode): void { - $this->update(function (array $data) use ($exitcode) { - return [ - "pg_pid" => null, - "date_stop" => $data["date_stop"] ?? new DateTime(), - "exitcode" => $exitcode, - ]; - }); - } + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Divers function getLockFile(?string $name=null, ?string $title=null): LockFile { $ext = self::LOCK_EXT; @@ -197,7 +257,10 @@ class RunFile { return new LockFile($file, $name, $title); } - /** démarrer un groupe de process */ + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Gestionnaire de tâches (tm_*) + + /** démarrer un groupe de process dont le process courant est le leader */ function tm_startPg(): void { $this->update(function (array $data) { posix_setsid(); diff --git a/src/app/launcher.php b/src/app/launcher.php index fed08a5..5f2c877 100644 --- a/src/app/launcher.php +++ b/src/app/launcher.php @@ -17,13 +17,15 @@ class launcher { return $exitcode; } - static function _start(array $args, Runfile $runfile): void { + static function _start(array $args, Runfile $runfile): bool { + if ($runfile->isLocked()) return false; $pid = pcntl_fork(); if ($pid == -1) { # parent, impossible de forker throw new StateException("unable to fork"); } elseif ($pid) { # parent, fork ok + return true; } else { ## child, fork ok # Créer un groupe de process, pour pouvoir les tuer toutes en même temps @@ -34,10 +36,12 @@ class launcher { $cmd = new Cmd($args); #XXX fichier de log? #XXX charger /g/init.env + $cmd->addSource("/g/init.env"); $cmd->addRedir("null"); $cmd->fork_exec($exitcode); + return true; } finally { - $runfile->stopped($exitcode); + $runfile->wfStopped($exitcode); } } } @@ -72,7 +76,7 @@ class launcher { return; } } - $runfile->stopped(-778); + $runfile->wfStopped(-778); msg::asuccess(); } } diff --git a/src/os/proc/AbstractCmd.php b/src/os/proc/AbstractCmd.php index 0c8ce12..ee3ee17 100644 --- a/src/os/proc/AbstractCmd.php +++ b/src/os/proc/AbstractCmd.php @@ -1,16 +1,17 @@ needsStdin = true; $this->needsTty = true; + $this->sources = null; $this->vars = null; $this->cmds = []; } @@ -74,13 +76,27 @@ abstract class AbstractCmd implements ICmd { $this->needsTty = $needsTty; } + function addSource(?string $source, bool $onlyIfExists=true): void { + if ($source === null) return; + if (!$onlyIfExists || file_exists($source)) { + $source = implode(" ", ["source", sh::quote($source)]); + $this->sources[] = $source; + } + } + + function getSources(?string $sep=null): ?string { + if ($this->sources === null) return null; + if ($sep === null) $sep = "\n"; + return implode($sep, $this->sources); + } + function addLiteralVars($vars, ?string $sep=null): void { - if (base::z($vars)) return; + if (cv::z($vars)) return; if (is_array($vars)) { if ($sep === null) $sep = "\n"; $vars = implode($sep, $vars); } - A::append($this->vars, strval($vars)); + $this->vars[] = strval($vars); } function addVars(?array $vars): void { @@ -88,8 +104,8 @@ abstract class AbstractCmd implements ICmd { foreach ($vars as $name => $value) { $var = []; if (!is_array($value)) $var[] = "export "; - A::merge($var, [$name, "=", shell::quote($value)]); - A::append($this->vars, implode("", $var)); + A::merge($var, [$name, "=", sh::quote($value)]); + $this->vars[] = implode("", $var); } } @@ -106,7 +122,7 @@ abstract class AbstractCmd implements ICmd { if ($cmd instanceof ICmd) { $cmd->addPrefix($prefix); } elseif (is_array($prefix)) { - $prefix = shell::join($prefix); + $prefix = sh::join($prefix); $cmd = "$prefix $cmd"; } else { $cmd = "$prefix $cmd"; @@ -167,8 +183,8 @@ abstract class AbstractCmd implements ICmd { } function system(string &$output=null, int &$retcode=null): bool { - $last_line = system($this->getCmd(), $retcode); - if ($last_line !== false) $output = $last_line; + $lastLine = system($this->getCmd(), $retcode); + if ($lastLine !== false) $output = $lastLine; return $retcode == 0; } diff --git a/src/os/proc/AbstractCmdList.php b/src/os/proc/AbstractCmdList.php index 7d7a7b6..cf5f7f2 100644 --- a/src/os/proc/AbstractCmdList.php +++ b/src/os/proc/AbstractCmdList.php @@ -1,8 +1,8 @@ cmds[] = $cmd; } @@ -35,6 +35,7 @@ abstract class AbstractCmdList extends AbstractCmd { if ($sep === null) $sep = "\n"; $actualCmd = []; + A::append_nn($actualCmd, $this->getSources($sep)); A::append_nn($actualCmd, $this->getVars($sep)); $parts = []; @@ -44,9 +45,8 @@ abstract class AbstractCmdList extends AbstractCmd { } $parts[] = $cmd; } - $psep = $this->sep; - if ($psep === null) $psep = $sep; - A::append($actualCmd, implode($psep, $parts)); + $psep = $this->sep ?? $sep; + $actualCmd[] = implode($psep, $parts); return implode($sep, $actualCmd); } diff --git a/src/os/proc/CmdPipe.php b/src/os/proc/CmdPipe.php index 361eabe..9d911e5 100644 --- a/src/os/proc/CmdPipe.php +++ b/src/os/proc/CmdPipe.php @@ -1,8 +1,8 @@ cmds[] = $cmd; } @@ -53,6 +53,7 @@ class CmdPipe extends AbstractCmd { if ($sep === null) $sep = "\n"; $actualCmd = []; + A::append_nn($actualCmd, $this->getSources($sep)); A::append_nn($actualCmd, $this->getVars($sep)); $parts = []; @@ -73,7 +74,7 @@ class CmdPipe extends AbstractCmd { if ($output !== null) $parts[] = ">".escapeshellarg($output); $cmd = implode(" ", $parts); } - A::append($actualCmd, $cmd); + $actualCmd[] = $cmd; return implode($sep, $actualCmd); } diff --git a/src/wip/app/app.php b/src/wip/app/app.php index c472cf6..f0455a4 100644 --- a/src/wip/app/app.php +++ b/src/wip/app/app.php @@ -7,6 +7,7 @@ use nur\sery\app\LockFile; use nur\sery\app\RunFile; use nur\sery\os\path; use nur\sery\os\sh; +use nur\sery\str; use nur\sery\ValueException; class app { @@ -67,7 +68,11 @@ class app { $this->projdir = $projdir = path::abspath($params["projdir"] ?? "."); $this->appcode = $appcode = $params["appcode"] ?? "app"; $this->apptype = $apptype = $params["apptype"] ?? "cli"; - $this->name = $params["name"] ?? $appcode; + # si $name est une classe, enlever le package et normaliser + $name = $params["name"] ?? $appcode; + $name = preg_replace('/.*\\\\/', "", $name); + $name = str::without_suffix("-app", str::camel2us($name)); + $this->name = $name; $this->title = $params["title"] ?? null; $appcode = strtoupper($appcode); # profile