#!perl -w
#
# Sys::Manage::Schedule - Scriptic schedule trunk
#
# makarow, 2005-09-15
#
# !!! see in source code
# ??? qclad(@_) inside 'cmdfck'?
# !!! 'soon' multiplatform
# !!! 'chpswd' may fail?
# Platform SDK: Directory Services: Changing the Password on a Service's User Account
# ??? '-surun' may be interrupted by system down or another -surun
#
package Sys::Manage::Schedule;
require 5.000;
use strict;
use Carp;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
$VERSION = '0.53';
1;
#######################
sub new { # Create new object
my $c=shift; # ( ? [command line], ? -option=>value,...) -> self
my $s ={};
bless $s,$c;
$s =$s->initialize(@_);
$s
}
sub initialize { # Initialize newly created object
my $s =shift; # ( ? [command line], ? -option=>value,...) -> self
%$s =( '' =>undef
,-dirm =>do{$0 =~/([\\\/])/ # directory marker
? $1
: $^O eq 'MSWin32'
? '\\'
: '/'}
,-dirb =>do { # directory base
my $v =$^O eq 'MSWin32' ? scalar(Win32::GetFullPathName($0)) ||$0 : $0;
$v !~/[\\\/]/
? (-d './var' ? '.' : -d '../var' ? '..' : '.')
: $v =~/(?:[\\\/]bin){0,1}[\\\/][^\\\/]+$/i
? $`
: '.'}
,-dirv =>'' # directory for vars & logs, below
,-logmax =>1024*1024*2 # log file size
,-prgfn =>do{$^O eq 'MSWin32' # program full name
? scalar(Win32::GetFullPathName($0)) ||$0
: $0}
,-prgsn =>do{$0 =~/([^\\\/]+)$/ # program short name
? $1
: $0}
,-prgcn =>do{ # program common name
$0 =~/([^\\\/]+?)(?:\.\w+){0,1}$/
? $1
: $0}
,-w32at =>$^O eq 'MSWin32' # is win32 (internally)
#-w32svco =>undef # win32 service obj (internally)
#-susr =>'' # su user
#-spsw =>'' # su password
,-n0 =>22 # night begin hour
,-n1 =>0 # night middle hour
,-n2 =>4 # night end hour
,-d0 =>8 # day begin hour
,-d1 =>12 # day middle hour
,-d2 =>19 # day end hour
,-time =>time() # start time
,-timel =>undef # start localtime
,-runmod =>'' # ... # runnimg mode: -run, -runsu, -set,...
,-runarg =>'' # ... # schedule args escaped
,-atopt =>'' # at options saved (internally)
#-atarg =>undef # at first cmd (internally)
#-crontab =>undef # crontab entries (internally)
#-wrlck =>undef # lock file
#-wrlcs =>'' # lock file additional
#-wrlog =>undef # writing logfile redirected
#-wrout =>undef # old stdout
#-wrerr =>undef # old stderr
#-cmdfile =>undef # 'cmdfile' object
, %$s
);
$s->{-timel} =localtime($s->{-time});
$s->set(@_);
$s
}
sub class {
substr($_[0], 0, index($_[0],'='))
}
sub daemonize { # Daemonize process
my $s =$_[0];
my $null =$^O eq 'MSWin32' ? 'nul' : '/dev/null';
open(STDIN, "$null") || return($s->echologdie("daemonize(STDIN) -> $!"));
open(STDOUT,">$null") || return($s->echologdie("daemonize(STDOUT) -> $!"));
eval("use POSIX 'setsid'; setsid()");
open(STDERR,'>&STDOUT')|| return($s->echologdie("daemonize(STDERR) -> $!"));
$s
}
sub DESTROY {
my $s =$_[0];
if($s->{-crontab}) {
$s->vfwrite('>crontab.txt',@{$s->{-crontab}});
$s->run('crontab',$s->vfname('crontab.txt'));
}
}
sub set { # Get/set slots of object
# () -> options
# (-option) -> value
# ( ? [command line], ? -option=>value,...) -> self
return(keys(%{$_[0]})) if (@_ <2);
return($_[0]->{$_[1]}) if (@_ <3) && !ref($_[1]);
my($s, $arg, %opt) =ref($_[1]) ? @_ : ($_[0],undef,@_[1..$#_]);
if ($arg) {
$opt{-runmod} =$arg->[0] || '-set';
$opt{-runarg} =$arg->[1] || '0';
}
foreach my $k (keys(%opt)) {
$s->{$k} =$opt{$k};
}
$s
}
sub max { # Max number
(($_[1]||0) >($_[2]||0) ? $_[1] : $_[2])||0
}
sub strtime { # Log time formatter
my @t =localtime();
join('-', $t[5]+1900, map {length($_)<2 ? "0$_" : $_} $t[4]+1,$t[3])
.' '
.join(':', map {length($_)<2 ? "0$_" : $_} $t[2],$t[1],$t[0])
}
sub qclad { # Quote command line arg(s) on demand
map { !defined($_) || ($_ eq '')
? '""'
: /[&<>\[\]{}^=;!'+,`~\s%"?*|()]/ # ??? see shell
? do { my $v =$_; $v =~s/"/\\"/g; '"' .$v .'"' }
: $_ } @_[1..$#_]
}
sub printflush { # Print STDOUT with autoflush
my $s =$_[0]; # (print args) -> print result
my @f =(select(), do{select(STDERR); $|}, do{select(STDOUT); $|});
select(STDERR); $|=1;
select(STDOUT); $|=1;
my $r =print(@_[1..$#_]);
select(STDERR); $|=$f[1];
select(STDOUT); $|=$f[2];
select($f[0]);
$r
}
sub echolog { # Echo + log
my $s=shift; # (message)
$s->printflush(@_,"\n");
$s->{-wrlog} && $s->{-wrlog}->print($s->strtime(),' ',@_,"\n")
&& $s->{-wrlog}->flush();
}
sub echologdie { # Echo + log + die
my $s=shift; # (message)
$s->{-wrlog} && $s->{-wrlog}->print($s->strtime(),' ',@_,"\n")
&& $s->{-wrlog}->flush();
croak(join('',@_) ."\n")
}
sub echologwarn { # Echo + log + die
my $s=shift; # (message)
$s->{-wrlog} && $s->{-wrlog}->print($s->strtime(),' ',@_,"\n")
&& $s->{-wrlog}->flush();
carp(join('',@_) ."\n")
}
sub flush { # Flush STDOUT/STDERR
eval('use IO::File; STDOUT->flush(); STDERR->flush()')
}
sub fopen { # File open
my ($s,$f) =@_; # (file name) -> file handle
eval('use IO::File');
IO::File->new($f) || $s->echologdie("Error '$!' (fopen,'$f')");
}
sub copen { # Command output open
my $s =$_[0]; # (handle var, command,...) -> pid
my $p;
if (0) {
$_[1] =eval('use IO::File; IO::File->new()');
$p =open($_[1], '-|', join(' ', @_[2..$#_]) .' 2>>&1');
}
else {
eval('use IPC::Open3');
my $x;
$p =eval{IPC::Open3::open3($x, $_[1], $_[1], @_[2..$#_])};
eval{fileno($x) && close($x)};
}
$p
}
sub fwrite { # File write
# (file name, strings)
my $fn =$_[1] =~/^[+<>]/ ? $_[1] : ('>' .$_[1]);
my $fh =$_[0]->fopen($fn);
my $r =$fh->print(map {/[\r\n]$/ ? $_ : "$_\n";
} @_[2..$#_]);
$fh->close();
$r;
}
sub ftruncate { # File truncate
my ($s, $f) =@_; # (filename)
my $max =$s->{-logmax}; # ~1024*1024
return(1) if !$max;
my $frm =1024*4;
my $fsz =(-s $f)||0;
return(1) if $fsz <=$max;
my $ftw =$f =~/\.([^.\\\/]+)/ ? ($` .'.tmp') : ($f .'.tmp');
if (1) {
my $f0 =eval{$s->fopen("<$f")} ||return(undef);
my $f1 =eval{$s->fopen(">$ftw")} ||return(undef);
my $cnt=0;
my $row='';
while ( ( ($cnt <$frm)
||($fsz -$cnt >$max))
&& defined($row=readline($f0))) {
$cnt +=length($row);
}
while (defined($row=readline($f0))) {
$f1->print($row);
}
$f0->close(); $f1->close();
rename($ftw, $f) || return(undef);
}
2;
}
sub vfname { # Var file name (in variables directory)
!$_[1] # (abbreviate) -> full file name
? $_[0]->{-dirv} .$_[0]->{-dirm}
: $_[1] =~/^([+<>]+)(.+)/
? $1 .$_[0]->{-dirv} .$_[0]->{-dirm} .$_[0]->{-prgcn} .'-' .$2
: $_[0]->{-dirv} .$_[0]->{-dirm} .$_[0]->{-prgcn} .'-' .$_[1]
}
sub vfopen { # Var file open
$_[0]->fopen( # (abbreviate) -> file handle
$_[0]->vfname($_[1]||'>>log.txt'))
}
sub vfwrite { # Var file write
# (abbreviate, strings)
$_[0]->fwrite($_[0]->vfname($_[1]), @_[2..$#_])
}
sub vftruncate { # Var file truncate
# (abbreviate)
$_[0]->ftruncate($_[0]->vfname($_[1]))
}
sub logrdr { # Log output redirector
my $s =$_[0]; # (on) -> -wrout (output saved)
# () -> -wrout
# (off) -> -wrlog (log opened)
# (undef) -> undef
if (!$#_) {
return($s->{-wrout})
}
elsif ($_[1] && !$s->{-wrout}) {
$s->flush();
$s->{-wrlog} =$s->vfopen('>>log.txt') if !$s->{-wrlog};
$s->{-wrout} =eval('use IO::File; IO::File->new(">&STDOUT")');
$s->{-wrerr} =eval('use IO::File; IO::File->new(">&STDERR")');
open(STDERR, '>&STDOUT');
open(STDOUT, '>&' .$s->{-wrlog}->fileno());
return($s->{-wrout})
}
elsif (!$_[1] && $s->{-wrout}) {
$s->flush();
open(STDOUT, '>&' .$s->{-wrout}->fileno());
open(STDERR, '>&' .$s->{-wrerr}->fileno());
$s->{-wrout}->close(); $s->{-wrerr}->close();
delete $s->{-wrout}; delete $s->{-wrerr};
}
elsif (!$_[1] && defined($_[1]) && !$s->{-wrlog}) {
$s->{-wrlog} =$s->vfopen('>>log.txt')
}
if (!defined($_[1]) && $s->{-wrlog}) {
$s->{-wrlog}->close(); delete $s->{-wrlog}
}
$s->{-wrlog}
}
sub loglck { # Lock operation
my ($s,$l,$t) =@_; # (true|false|undef, ?nonblock) -> success
$t =($t ? '| LOCK_NB' : '');
$s->{-wrlck} =$s->vfopen('>lck.txt') if !$s->{-wrlck};
my $r =flock($s->{-wrlck}
, !$l
? eval("use Fcntl qw(:flock); LOCK_UN $t")
: $l >1
? eval("use Fcntl qw(:flock); LOCK_EX $t")
: eval("use Fcntl qw(:flock); LOCK_SH $t"));
if ($#_ && !defined($l)) {
$s->{-wrlck}->close() if $s->{-wrlck};
delete $s->{-wrlck};
}
$r;
}
sub loglcs { # Lock operation for su / soon
my ($s,$l,$t) =@_; # (true|false|undef, ?nonblock) -> success
$t =($t ? '| LOCK_NB' : '');
$s->{-wrlcs} =$s->vfopen('>lcs.txt') if !$s->{-wrlcs};
my $r =flock($s->{-wrlcs}
, !$l
? eval("use Fcntl qw(:flock); LOCK_UN $t")
: $l >1
? eval("use Fcntl qw(:flock); LOCK_EX $t")
: eval("use Fcntl qw(:flock); LOCK_SH $t"));
if ($#_ && !defined($l)) {
$s->{-wrlcs}->close() if $s->{-wrlcs};
delete $s->{-wrlcs};
}
$r;
}
sub atesc { # Escape 'at' options
my $v =ref($_[1]) ? join(' ', @{$_[1]}) : join(' ',@_[1..$#_]);
$v =~s/([^-\d\w:,\/\s])/sprintf("\\%02x",ord($1))/eg;
$v =~s/([\s])/_/g;
$v
}
sub atarg { # Repeat 'at' options in subsequent entry using first
my $s =$_[0]; # (at entry) -> (at entry filled)
$s->{-atarg}
? (map { !defined($_[$_+1]) ||($_[$_+1] eq '')
? $s->{-atarg}->[$_]
: $_[$_+1]
} (0..max($s,$#{$s->{-atarg}},$#_-1)))
: (@_[1..$#_])
}
sub run { # Start OS command
my $s =$_[0]; # (command) -> !exit code
$!=$^E=0;
$s->printflush(join(' ',@_[1..$#_]),"\n");
my $r =system(@_[1..$#_]);
$s->printflush("Error '$!'\n") if $r <0;
($r >=0) && !($?>>8)
}
sub runopen { # Open OS command as filehandle
my $s =$_[0]; # (command) -> file handle
$s->printflush(join(' ',@_[1..$#_]),"\n");
my $h;
$!=$^E=0;
$s->{-runopen} =$s->copen($h, @_[1..$#_]);
$s->{-runopen} && $h;
}
sub runlist { # List OS command as an array
my $s =$_[0]; # (command) -> strings list
my $h =runopen(@_);
my @r =map {(/[\r\n]*$/ ? $` : $_)} <$h>;
waitpid($s->{-runopen},0);
#$s->flush();
@r
}
sub runlcl { # Log os command line
my $s =shift; # (command) -> success
$s->loglck(1);
$s->logrdr(0) if !$s->{-wrlog};
$s->echolog(join(' ',@_));
my $r =system(@_);
if ($r <0) {
$s->{-wrlog}->print($s->strtime(), " Error '$!' (", join(' ',@_) ,")\n");
}
else {
$s->{-wrlog}->print($s->strtime(), ' Exit ',($?>>8),' (', join(' ',@_), ")\n");
}
$s->{-wrlog}->flush();
$s->loglck(0);
($r >=0) && !($?>>8)
}
sub runlog { # Log os command execution
my $s =shift; # (command) -> success
$s->loglck(1);
$s->logrdr(0) if !$s->{-wrlog};
$s->echolog(join(' ',@_));
$!=$^E=0;
my $hi;
my $pid =$s->copen($hi, @_);
if ($pid) {
$s->{-wrlog}->print($s->strtime(), ' $$', $pid, ' (', join(' ',@_), ")\n");
$s->{-wrlog}->flush();
my $r =undef;
while(defined($r=readline($hi))) {
$r = $` if $r =~/[\r\n]*$/;
print($r, "\n");
$s->{-wrlog}->print($r, "\n");
}
$hi && close($hi);
waitpid($pid,0);
$s->{-wrlog}->print($s->strtime(), ' Exit ', ($?>>8),' (', join(' ',@_), ")\n");
$s->{-wrlog}->flush();
$s->loglck(0);
return !($?>>8)
}
else {
my @e =($!, $^E);
$hi && close($hi);
($!, $^E) =@e;
$s->{-wrlog}->print($s->strtime(), " Error '$!' (", join(' ',@_), ")\n");
$s->{-wrlog}->flush();
$s->loglck(0);
return(undef)
}
}
sub cmdfile { # Shift command file
$_[0]->{-cmdfile} =eval('use Sys::Manage::CmdFile; Sys::Manage::CmdFile->new()')
if !$_[0]->{-cmdfile};
$_[0]->{-cmdfile}->dofile(@_[1..$#_])
}
sub cmdfck { # Check command file
$_[0]->{-cmdfile} =eval('use Sys::Manage::CmdFile; Sys::Manage::CmdFile->new()')
if !$_[0]->{-cmdfile};
$_[0]->{-cmdfile}->dofck(@_[1..$#_])
}
sub w32oleerr { # Win32 OLE error message
(Win32::OLE->LastError()||'undef')
.' '
.(Win32::OLE->LastError() && Win32::FormatMessage(Win32::OLE->LastError()) ||'undef');
}
sub w32svcr { # Win32 services registry
# (... ) -> registry
no warnings;
eval('use Win32::TieRegistry');
$Win32::TieRegistry::Registry->{"LMachine\\System\\CurrentControlSet\\Services" .($_[1] ? '\\' .$_[1] : '')}
}
sub w32svco { # Win32 service object
my $s=$_[0]; # () -> object | (stop|start) -> success
my $n=$s->{-prgsn};
my $m=1; # 0 net use, 1 wmi, 2 adsi
local $^W=undef;
my $o =$s->{-w32svco} =$s->{-w32svco} ||
(!$m
? undef
: $m ==1
? (eval('use Win32::OLE; 1') && Win32::OLE->GetObject("winmgmts:Win32_Service.Name='$n'"))
: (eval('use Win32::OLE; 1') && Win32::OLE->GetObject('WinNT://' .Win32::NodeName() .'/' .$n .',Service')));
return($o) if !$#_;
my $r;
$@ =undef;
if (!$m) {
$r =$s->run('net', ($_[1] ? 'start' : 'stop'), $n);
$@ ='exit ' .($?>>8) if !$r;
}
elsif ($m ==1) {
$r =$_[1] ? $o->StartService() : $o->StopService();
$@ ='wmi ' .$r if $r;
$r =$r ? 0 : 1;
sleep(1) if $_[1] && $r; # $o->{Started} -> false always!
}
elsif ($m ==2) {
$r =$_[1] ? $o->Start() : $o->Stop(); # fails sometimes
$@ ='adsi ' .$r if $r;
$r =$r ? 0 : 1;
sleep(1) if $_[1] && $r; # $o->{Status} -> 8(ADS_SERVICE_ERROR)?
}
$r
}
sub startup { # Start schedule execution (internal)
my $s=$_[0]; # () -> self
$s->{''} =1;
$s->{-dirv} = -d ($s->{-dirb} .$s->{-dirm} .'var')
? ($s->{-dirb} .$s->{-dirm} .'var')
: ($s->{-dirb} .$s->{-dirm} .$s->{-prgcn})
if !$s->{-dirv};
if ($s->{-runmod} =~/^-set/) { # clean schedule existed
$s->printflush($s->class(), " Scheduling '", $s->{-prgsn}, "'\n");
mkdir($s->{-dirv},0777) if !-e $s->{-dirv};
if ($s->{-w32at}) {
my $qs =$s->{-prgfn};
my @at =split /\s*\r*\n\r*/, `at`;
foreach my $r (@at) {
next if $r !~/\b\Q$qs\E\b/;
my $i =($r =~/^[^\d]*(\d+)/ ? $1 : undef);
next if !$i;
$s->run('at', $i, '/d', '/y');
}
}
else {
my $qs =$s->{-prgfn};
$s->{-crontab} =
[map { /\b\Q$qs\E\b/ ? () : ($_)
} split /\s*\r*\n\r*/, `crontab -l`];
$s->vfwrite('>crontab.txt',@{$s->{-crontab}});
$s->run('crontab',$s->vfname('crontab.txt'));
}
exit(0) if $s->{-runmod} =~/^-setdel/; # delete settings
}
elsif ($s->{-runmod} eq '-svcinst') { # install service
croak("Error: Win32 only function") if $^O ne 'MSWin32';
my $n =$s->{-prgsn};
$s->printflush($s->class(), " Installing Windows Service '$n'...\n");
my $p ='';
foreach my $d (split /\s*\;\s*/, $ENV{PATH}) {
next if !-e "$d\\srvany.exe";
$p =$d; last;
}
if (!$p) {
$s->printflush("Not found ResKip 'srvany.exe' in path, exit.\n");
exit(1);
}
$s->run('instsrv',$n,"$p\\srvany.exe");
$s->w32svcr()->{$n}
={'Parameters' =>{
'Application'=>$^X
,'AppParameters'=>'-e"$SIG{CHLD}=\'IGNORE\';system(1,$^X,@ARGV)" '
.$s->{-prgfn} .' -runsu 0'
,'AppDirectory'=>$s->{-dirb}
}};
$s->run('sc','config',$n
,'type=', 'own'
,'start=', 'auto'
,($ARGV[1] ? ('obj=', $ARGV[1]) : ())
,($ARGV[2] ? ('password=', $ARGV[2]) : ())
);
$s->printflush("\nCheck '$n' service 'Startup type' and 'Log on'!\n");
exit(0);
}
elsif ($s->{-runmod} eq '-svcdel') { # remove service
croak("Error: Win32 only function") if $^O ne 'MSWin32';
my $n =$s->{-prgsn};
$s->printflush($s->class(), " Removing Windows Service '$n'...\n");
$s->run('instsrv',$n,"Remove");
exit(0);
}
elsif ($s->{-runmod} eq '-surun') { # start switched user and exit
if ($^O eq 'MSWin32') {
if ($s->{-susr} && $s->{-spsw}) {
$s->printflush('[',$s->{-prgfn},'] -surun:wmi, ',($s->{-runarg}||0), ', $$', $$,"...\n");
local $^W=undef;
eval('use Win32::OLE');
my $wmi =Win32::OLE->new('WbemScripting.SWbemLocator');
$wmi =$wmi->ConnectServer(Win32::NodeName(),'root\\cimv2',$s->{-susr},$s->{-spsw})
|| croak 'Error(WMI->ConnectServer): ' .$s->w32oleerr();
# !!! OLE exception from "SWbemLocator": User credentials cannot be used for local connections
$wmi->{Security_}->{ImpersonationLevel}=3; # 4-delegate, 3-impersonate;
my $mih =$wmi->Get('Win32_Process')
|| croak 'Error(WMI->Win32_Process): ' .$s->w32oleerr();
$mih->Create(join(' '
,$^X,$s->{-prgfn},'-runsu'
,$s->{-runarg}||0
), undef,undef,$wmi)
&& croak 'Error(WMI->Create): ' .$s->w32oleerr();
}
else {
my $n =$s->{-prgsn};
$s->printflush('[',$s->{-prgfn},'] -surun, ',($s->{-runarg}||0), ', $$', $$,"...\n");
$s->logrdr(0) if !$s->{-wrlog};
$s->{-wrlog}->print("\n", $s->strtime(), ' [', $s->{-prgsn},'] -surun, ',($s->{-runarg}||0), ', $$', $$,"\n");
$s->{-wrlog}->flush();
$s->loglcs(2);
$s->w32svco(0);
$s->w32svcr("$n\\Parameters")->{AppParameters}
='-e"$SIG{CHLD}=\'IGNORE\';system(1,$^X,@ARGV)" '
.$s->{-prgfn} .' -runsu ' .($s->{-runarg}||0);
$s->w32svco(1);
$s->{-wrlog}->print("Error '$@' (w32svco,1)\n")
if $@;
# !!! service startup may be continues, interrupted
# $s->w32svcr("$n\\Parameters")->{AppParameters}
# ='-e"$SIG{CHLD}=\'IGNORE\';system(1,$^X,@ARGV)" '
# .$s->{-prgfn} .' -runsu 0';
$s->loglcs(0);
}
exit(0);
}
else {
if ($s->{-susr} && !$s->{-spsw}) {
$SIG{CHLD}='IGNORE';
($>||0) ==0
? $s->run(1
,(-e '/bin/su' ? '/bin/su' : 'su')
,$s->{-susr}
,'-c','"' .join(' '
,$^X
,($s->{-prgfn} =~/\.(?:bat|cmd)$/i ? ('-x') : ())
,$s->{-prgfn}, '-runsu', $s->{-runarg})
.'"')
: $s->run(1
,(-e '/bin/sudo' ? '/bin/sudo' : 'sudo')
,'-u', $s->{-susr}
,$^X, $s->{-prgfn}, '-runsu', $s->{-runarg});
exit(0)
}
$s->printflush('[',$s->{-prgfn},'] -runsu, ',($s->{-runarg}||0), ', $$', $$,"...\n");
$s->{-runmod} ='-runsu';
}
}
elsif ($s->{-runmod} eq '-run') { # run task
$s->printflush('[',$s->{-prgfn},'] -run, ',($s->{-runarg}||0), ', $$', $$,"...\n");
}
elsif ($s->{-runmod} eq '-runsu') { # run task switched user
if ($^O eq 'MSWin32') {
my $n =$s->{-prgsn};
$s->printflush('[',$s->{-prgfn},'] -runsu, ',($s->{-runarg}||0), ', $$', $$,"...\n");
if (!$s->{-susr}
&& (($s->w32svcr("$n\\Parameters\\AppParameters")||'')
!~/\s0\s*$/)) { # from -surun
$s->loglcs(2);
$s->w32svcr("$n\\Parameters")->{AppParameters}
='-e"$SIG{CHLD}=\'IGNORE\';system(1,$^X,@ARGV)" '
.$s->{-prgfn} .' -runsu 0';
$s->loglcs(0);
}
$s->w32svco(0) if !$s->{-susr};
}
else {
$s->printflush('[',$s->{-prgfn},'] -runsu, ',($s->{-runarg}||0), ', $$', $$,"...\n");
}
}
else {
croak("Error('-runmod'): "
.(!defined($s->{-runmod}) ? 'undef' : $s->{-runmod} eq '' ? "''" : $s->{-runmod})
);
}
if ($s->{-runmod} =~/^-run/) { # log file rotator
if ($s->{-logmax}
&& ($s->{-logmax} <((-s $s->vfname('log.txt'))||0))) {
$s->logrdr(undef) if $s->{-wrlog};
if ($s->loglck(2,1)) {
$s->vftruncate('log.txt');
$s->loglck(0);
}
}
$s->vfwrite('>>log.txt', "\n". $s->strtime() .' ['.$s->{-prgsn}
."] " .$s->{-runmod} .', ' .$s->{-runarg} .', $$' .$$);
}
if ($s->{-runmod} =~/^-run/) { # work dir
chdir $s->{-dirb};
}
$s;
}
sub at { # At... condition (interface)
# (?-options, ?at args, ?sub{}) -> true | false
my ($s,$o) =($_[0], $_[1] =~/^-/ ? $_[1] : '');
my $r;
$s->{''} =$s->startup() && 1 # startup, if not yet
if !$s->{''};
$s->{-atarg} =undef; # check condition / set 'at'
if (ref($o ? $_[2] : $_[1])) {
foreach my $c (@_[($o ? 2 : 1) .. $#_]) {
next if ref($c) ne 'ARRAY';
$r =at_($s, $o, @$c);
$s->{-atarg} =$c if !$s->{-atarg};
last if $r && ($s->{-runmod} =~/^-run/);
}
}
else {
$r =$o
? (ref($_[$#_]) ? at_(@_[1..$#_-1]) : at_(@_))
: (ref($_[$#_]) ? at_($s, $o, @_[1..$#_-1]) : at_($s, $o, @_[1..$#_]))
}
return(undef)
if ($s->{-runmod} !~/^-run/) || !$r;
$o =~/w/ # write logfile
? $s->loglck(1) && $s->logrdr(1)
: $s->{-wrout}
? $s->logrdr(undef)
: undef;
$r # execute sub{} given
&& (ref($_[$#_]) eq 'CODE')
&& &{$_[$#_]}($s);
$s->loglck(0) if $o =~/w/;
$r
}
sub at_ { # At... condition (implementation)
my ($s,$o) =@_[0..1];
if ($s->{-runmod} =~/^-run/) { # Run condition
if (($o =~/s/) && ($s->{-runmod} !~/^-runsu/)) {
return(undef)
}
elsif (($o !~/s/) && ($s->{-runmod} =~/^-runsu/)) {
return(undef)
}
elsif ($o =~/a/) { # run anytime
return(1)
}
elsif ($o =~/([dn]\d)/) { # run daily/nightly begin/middle/end
my $on =$1;
return(1) if $s->{-atopt} =~/\Q$on\E/;
$s->loglck(2);
$s->logrdr(0) if !$s->{-wrlog};
my $r =undef;
if ($s->atopt("-$on")) {
$s->{-atopt} .=$on;
$s->vfwrite($on .'.txt', $s->strtime() ." " .$s->class() .'::at $$' .$$);
$s->{-wrlog}->print($s->strtime(), " [", $s->{-prgsn}, "] $o\n");
$s->{-wrlog}->flush();
$r =1
}
$s->loglck(0);
return($r)
}
return( ($s->{-atarg} # comparing sys scheduler options
? $s->{-runarg} ne atesc($s, atarg($s, @_[2..$#_]))
: $s->{-runarg} ne atesc($s, @_[2..$#_]))
? undef
: ($o =~/s/) && ($s->{-runmod} !~/^-runsu/)
? undef
: 1
)
}
elsif ($s->{-runmod} =~/^-set/) { # Setup system scheduler
if (($o =~/([dn]\d)/) # daily/nightly autofill
&& !$s->{-atarg}
&& (!defined($_[2]) ||($_[2] eq ''))
) {
$s->{-atarg} =$s->{-w32at}
? [$s->{"-$1"} .':00', '/every:M,T,W,Th,F,S,Su']
: [0, $s->{"-$1"}, '*', '*', '*']
}
return(undef) # without system scheduler
if ($#_<3)
&& !$s->{-atarg}
&& (!defined($_[2]) ||($_[2] =~/^(?:|0|\w[\w\d_]*)$/));
if ($s->{-w32at}) { # win32at system scheduler
$s->echologdie("Error(-set): Win32 service '" .$s->{-prgsn} ."' not installed")
if ($o =~/s/)
&& !$s->{-susr} && !$s->{-spsw}
&& !$s->w32svcr($s->{-prgsn});
$s->run('at', atarg($s, @_[2..$#_]), $^X
, ($s->{-prgfn} =~/\.(?:bat|cmd)$/i ? ('-x') : ())
, $s->{-prgfn}
, $o =~/s/ ? '-surun' : '-run'
, atesc($s, atarg($s, @_[2..$#_])))
}
else { # crontab system scheduler
push @{$s->{-crontab}}
, join(' '
, atarg($s, @_[2..$#_]), $^X, $s->{-prgfn}
, $o =~/s/ ? '-surun' : '-run'
, atesc($s, atarg($s, @_[2..$#_])));
}
return(undef);
}
}
sub atopt { # At options check
my ($s,$o) =@_[0..1]; # (-options) -> success
if ($o =~/a/) { # run anytime
return(1)
}
elsif ($o =~/([dn]\d)/) { # run daily/nightly begin/middle/end
my $on =$1;
return(1) if $s->{-atopt} =~/\Q$on\E/;
my $os =(stat $s->vfname($on .'.txt'))[9];
my $ot =$os ? [localtime $os] : undef;
my $cs =$s->{-time};
my $ct =[localtime($cs)];
foreach my $l ($ot,$ct) {
$l->[7] =$l->[5] # year .yday
. (length($l->[7]) <3
? ('0' x (3 -length($l->[7]))) .$l->[7]
: $l->[7]) if $l
}
if ($on =~/n1|n2|d0/) { # night middle, night end, day begin
return(1)
if ($ct->[2] >=$s->{"-$on"})
&& ($ct->[7] ne ($ot && $ot->[7] || 0))
}
elsif ($on =~/d1|d2|n0/) { # day middle, day end, night begin
return(1)
if !$ot
? 1
: ($ct->[2] >=$s->{"-$on"})
? ($ot->[2] < $s->{"-$on"}) || ($ot->[7] <$ct->[7])
: ($ot->[7] < $ct->[7]-1)
? 1
: ($ot->[7] <$ct->[7]) && ($ot->[2] <$s->{"-$on"})
}
}
undef
}
sub soon { # Cyclical starting sub{}
# (?-options, period, command)
# (?-options, 'self', -runmod, -runarg)
# (?-options, period, name, code{})
my ($s, $o, $p, $n, $c) =(shift, ($_[0]=~/^-/ ? shift : '-'), @_);
$s->echologdie("Error(soon): not Win32") if $^O ne 'MSWin32';
return(undef) if $s->{-runmod} !~/^(?:-run|-runsu|-set)/;
if (!$c || ($n eq 'self')) {
if ($s->{-runmod} =~/^(?:-run|-runsu)/) {
my @t1 =localtime(time +(!$p ? 60 *15 : $p <60 ? 60 : $p));
my @c =('at',$t1[2] .':' .$t1[1]
,($o =~/i/ ? ('/interactive') : ())
,($n eq 'self'
? join(' ', $^X, $s->{-prgfn}, @_[2..$#_])
: $n));
return($s->run(@c));
}
else {
return(undef);
}
}
$s->{-wrout} && $s->logrdr(0);
my $q =$s->{-prgfn} .' ' .($o =~/s/ ? '-surun' : '-run') .' ' .$n;
my $t =defined($s->{-runarg}) && ($s->{-runarg} eq $n)
&& defined($s->{-runmod})
&& ($s->{-runmod} eq ($o =~/s/ ? '-runsu' : '-run'));
return(undef) if !$t && (`at` =~/\b\Q$q\E\b/);
my $r =undef;
if ($s->loglcs(2, $t ? 0 : 1)) {
$s->logrdr(0) if !$s->{-wrlog};
if ($t) { # execute
$s->logrdr(1) if $o =~/w/;
$r =eval{&$c($s,$o,@_)};
$s->{-wrlog}->flush();
$s->logrdr(0) if $o =~/w/;
}
if (1) { # reschedule
foreach my $r (split /\s*\r*\n\r*/, `at`) {
next if $r !~/\b\Q$q\E\b/;
my $i =($r =~/^[^\d]*(\d+)/ ? $1 : undef);
next if !$i;
my @c =('at', $i, '/d', '/y');
$s->echolog('[soon] ', join(' ',$n,@c));
system(@c);
}
$p =$p # !!! long period may be shorten somehow
if !$t && ($p/2 >60*15);
my @t1 =localtime(time +(!$p ? 60 *15 : $p <60 ? 60 : $p));
my @c =('at',$t1[2] .':' .$t1[1]
,($o =~/i/ ? ('/interactive') : ())
,$^X
,($s->{-prgfn} =~/\.(?:bat|cmd)$/i ? ('-x') : ())
,$s->{-prgfn}
,$o =~/s/ ? '-surun' : '-run'
,$n);
$s->echolog('[soon] ', join(' ',$n,@c));
map {$s->echolog('[soon] ',$n,' ',$_)
} $s->runlist(@c);
$s->{-wrlog}->flush();
}
$s->loglcs(0,1);
}
else {
$s->loglcs(0,1);
}
$r
}
sub chpswd { # Change service password
my ($s,$su,$sp,@sh) =@_; # (user, password, additional hosts) -> success
# (..., [host, service],...)
$s->echologdie("Error(chpswd): not Win32") if $^O ne 'MSWin32';
$s->loglck(2);
$s->logrdr(0) if !$s->{-wrlog};
my $sn =$s->{-prgsn};
$su =$su || $s->{-prgcn};
$sp =$sp || do { my $p='';
while(length($p) <20) {
my $c =rand(ord('z'));
next if chr($c) !~/[\w\d.!#\@\$\%&+-]/;
$p .=chr($c);
} $p};
print("[chpswd] $sn,$su,'*','',", join(',',@sh), "\n");
$s->{-wrlog}->print($s->strtime(), " [chpswd] net user $su\n");
my $e ='';
my @rc =$s->runlist('net','user'
,($su =~/\\/ ? $' : $su =~/\@/ ? $` : $su)
,'"' .$sp .'"'
,($su =~/(?:\\|\@)/ ? '/domain' : ())
);
if (scalar(@rc) ? !grep /\ssuccess/i, @rc : $?>>8) {
$e =$@ ="Error " .($?>>8) ." (net user $su)\n" .join("\n", @rc);
$s->echologwarn($@);
$s->loglck(0);
return(undef);
}
else {
map { $s->{-wrlog}->print($s->strtime()," [chpswd] $_\n") if $_
} @rc;
foreach my $h ('',@sh) {
$s->{-wrlog}->print($s->strtime(), ' [chpswd] sc '
, (ref($h) ? $h->[0] ||'' : $h ||'')
,' config \'', (ref($h) ? $h->[1] ||$sn ||'' : $sn ||'')
,"'\n");
my $ei ='';
my $i =0;
while(1) {
@rc =$s->runlist('sc', (ref($h) && $h->[0]
? '\\\\' .$h->[0]
: !ref($h) && $h
? "\\\\$h"
: ())
,'config', (ref($h) && $h->[1]
? $h->[1]
: $sn)
,'type=', 'own'
,'obj=', ($su =~/(?:\\|\@)/ ? $su : ".\\$su")
,'password=', '"' .$sp .'"');
if (scalar(@rc) ? !grep /\ssuccess/i, @rc : $?>>8) {
$@ ='Error '
.($?>>8)
.' (sc '
.(ref($h) ? $h->[0] ||'' : $h ||'')
.' config \''
.(ref($h) ? $h->[1] ||$sn ||'' : $sn ||'')
.'\')' ."\n" .join("\n", @rc);
$ei =$@;
$s->echologwarn($@);
last if !$h || (ref($h) && !$h->[0]);
last if ++$i >2;
sleep(10)
}
else {
$ei ='';
map { $s->{-wrlog}->print($s->strtime()
," [chpswd] $_\n") if $_
} @rc;
last;
}
}
$e .=($e ? "\n" : '') .$ei if $ei;
}
}
$s->loglck(0);
$@=$e;
!$e
}