#!/usr/bin/env perl # Run a single instance of a process @ARGV or die "Usage: single \n"; use strict; use warnings; use Digest::MD5 'md5_hex'; use File::Spec; { local %ENV = %ENV; $ENV{PATH} ||= '/usr/local/bin:/usr/bin:/bin'; $ENV{ALREADY_RUNNING_STATUS} ||= 0; $ENV{SINGLE_HUP_SIGNAL} ||= 'TERM'; $ENV{SINGLE_ID} = md5_hex "@ARGV"; $ENV{SINGLE_INFO_FILE} ||= File::Spec->catfile(File::Spec->tmpdir, "single-$ENV{SINGLE_ID}"); return {%ENV} if defined wantarray; exec perldoc => 'App::single' if @ARGV == 1 and $ARGV[0] =~ /^--?(?:h|help)$/; exit $ENV{ALREADY_RUNNING_STATUS} if App::single::script::running(); exit App::single::script::run(App::single::script::start()); } package App::single::script; sub run { my $pid = shift; my $exit_status = 255; my $restart; local $SIG{HUP} = sub { kill $ENV{SINGLE_HUP_SIGNAL}, $pid; $restart = 1; }; while ($pid) { waitpid $pid, 0; $exit_status = $? >> 8; unlink $ENV{SINGLE_INFO_FILE}; $pid = $restart ? App::single::script::start() : 0; $restart = 0; } return $exit_status; } sub running { my ($info, $pid) = ('', 0); open my $INFO, '<', $ENV{SINGLE_INFO_FILE} or return 0; while (<$INFO>) { $pid = $1 if /pid:\s*(\d+)/; $info .= $_; } if ($pid and kill 0, $pid) { print $info, "status: running\n" unless $ENV{SINGLE_SILENT}; return 1; } return 0; } sub start { my $pid = fork // die "Could not fork @ARGV: $!\n"; if ($pid == 0) { # child exec @ARGV; die "Could not exec @ARGV: $!\n"; } open my $INFO, '>', $ENV{SINGLE_INFO_FILE} or die "Write $ENV{SINGLE_INFO_FILE}: $!"; print $INFO "pid: $pid\n"; print $INFO "command: @ARGV\n"; print $INFO "id: $ENV{SINGLE_ID}\n"; print $INFO "started: @{[~~localtime]}\n"; close $INFO; return $pid; }