Deprecated: The each() function is deprecated. This message will be suppressed on further calls in /home/zhenxiangba/zhenxiangba.com/public_html/phproxy-improved-master/index.php on line 456
#!/usr/bin/perl
use strict;
use English qw/ $PID $PROGRAM_NAME /;
use FileHandle;
use File::Basename qw/ basename /;
use Getopt::Long qw/ :config no_ignore_case /;
use Jcode;
use Net::SMTP;
use POSIX qw/ EAGAIN strftime /;
use Sys::Syslog qw/ openlog syslog closelog /;
# バージョン番号
my $VERSION = sprintf( '0.%d.%02d', q$Revision: 1.15 $ =~ /(\d+)\.(\d+)/ );
my $BASENAME = &basename( $PROGRAM_NAME, ".perl" );
=head1 NAME
polling - ファイルが変化したときに指定されたコマンドを実行する
=head1 SYNOPSIS
polling [OPTIONS] COMMAND [ARGUMENTS...]
=head1 DESCRIPTION
一定時間おきに指定されたファイルの更新時間を調べ,変化があった場合には
指定されたコマンドを実行する.コマンドが標準出力または標準エラー出力に
何らかの出力を行った場合は,メールでその結果を通知する.
=head1 OPTIONS
=over 4
=item -f C
=item --file=C
指定された C の変化を監視する.オプションによる指定がなければ,
F を監視対象とする.
=cut
my $FILE = "/var/log/messages";
=item -i C
=item --interval=C
ファイルの変化を検出する時間間隔を指定する.オプションによる指定がなけ
れば,30 秒おきに検出を行う.
=cut
my $INTERVAL = 30;
=item -c C
=item --command-interval=C
コマンドを実行する最低間隔を指定する.ファイルの変化が検出された場合で
あっても,前回コマンド実行時から指定された秒数が経過していなければ,コ
マンドの実行を行わない.オプションによる指定がなければ,900 秒を最低間
隔とする.
=cut
my $COMMANDINTERVAL = 900; # 900秒 = 15分
=item -r
=item --reentrant
コマンドが reentrant であることを指定する.このオプションが指定されて
いると,コマンドの終了を待たずにファイルの変更日時を検出するループに戻
るため,複数のコマンドが同時に実行されることが有り得る.
=cut
my $REENTRANT = 0;
=item -p C
=item --pidfile=C
background で動作するプロセスの PID を記録するファイル名を指定する.オ
プションによる指定がなければ,F に記録する.
=cut
my $PIDFILE = "/var/run/polling.pid";
=item -P C
=item --priority=C
background で動作するプロセスの process priority を指定する.オプショ
ンによる指定がなければ,12 を用いる.
=cut
my $PRIORITY = 12;
=item -l C
=item --log-facility=C
syslog(3) に記録するときに用いる facility を指定する.オプションによる
指定がなければ,I が用いられる.
=cut
my $FACILITY = "local6";
=item -a C
=item --address=C
コマンドの実行結果を通知するアドレスを指定する.オプションによる指定が
なければ,このスクリプトを実行したユーザーに対して通知を試みる.
=cut
my $FROM = sprintf( "%s\@%s",
( $ENV{'USER'} || "root" ),
( $ENV{'HOST'} || $ENV{'HOSTNAME'} || "localhost" ) );
my $TO = $FROM;
# 子プロセスの起動を再試行する回数
my $RETRY = 5;
# デバッグモード
my $DEBUG;
GetOptions( 'file|f=s' => \$FILE,
'interval|i=i' => \$INTERVAL,
'command-interval|c=i' => \$COMMANDINTERVAL,
'reentrant|r!' => \$REENTRANT,
'pidfile|p=s' => \$PIDFILE,
'priority|P=i' => \$PRIORITY,
'log-facility|l=s' => \$FACILITY,
'address|a=s' => \$TO,
'debug|d+' => \$DEBUG );
die "No command is given\n" unless @ARGV;
die "No target file is specified\n" unless $FILE;
die "Can't find file: $FILE\n" unless -f $FILE;
# strftime() の出力文字列が英語になるように locale を無効化しておく
eval {
use locale;
use POSIX qw/ setlocale LC_TIME /;
setlocale( &LC_TIME, "C" );
};
if( $DEBUG ){
&polling( @ARGV );
} else {
for( my $i; $i < $RETRY; $i++ ){
if ( my $pid = fork ) {
eval { setpriority( 0, $pid, $PRIORITY ); };
exit 0;
} elsif ( defined $pid ) {
# 子プロセス側の処理
STDIN->close;
STDOUT->close;
STDERR->close;
&polling( @ARGV );
exit 0; # Not reach.
} elsif ( $! == &EAGAIN ) {
sleep 5;
next;
} else {
die "Can't fork daemon process: $!\n";
}
}
die "Can't fork daemon process: retry conter exceeded\n";
}
#----------------------------------------------------------------------
# 本体
#----------------------------------------------------------------------
my $PPID;
sub polling (@) {
my( @argv ) = @_;
$PPID = $PID;
$PROGRAM_NAME = sprintf( '%s %s to execute "%s"',
$BASENAME, $FILE, join( " ", @argv ) );
&printinfo( $PROGRAM_NAME );
my $fh = new FileHandle( $PIDFILE, "w" );
if( $fh ){
$fh->print( "$PID\n" );
$fh->close;
$SIG{'INT'} = \&handler;
$SIG{'QUIT'} = \&handler;
$SIG{'TERM'} = \&handler;
$SIG{'HUP'} = 'IGNORE';
} else {
&printerror( "Can't open %s to write: %s", $PIDFILE, $! );
$PIDFILE = undef;
}
my $last;
my $mtime = &mtime( $FILE );
while(1){
my $sleep = $COMMANDINTERVAL + $last - time;
sleep( ( $sleep < $INTERVAL )||( $sleep > $COMMANDINTERVAL )? $INTERVAL : $sleep );
my $x = &mtime( $FILE );
if( $x != $mtime ){
$mtime = $x;
$last = time;
&printinfo( "%s is changed at %s",
$FILE, strftime( "%T", localtime( $mtime ) ) );
if( $REENTRANT ){
&child( $mtime, @argv );
} else {
&start( $mtime, @argv );
}
}
}
}
# ファイルの変更時間を検出する関数
sub mtime ($) {
my( $file ) = @_;
( stat( $file ) )[9];
}
sub printlog ($$@) {
my( $priority, $format, @arg ) = @_;
if( $DEBUG ){
printf "%s %s %s[%d]: ", strftime( "%b %d %T", localtime ), $priority, $BASENAME, $PID;
printf $format, @arg;
print "\n";
STDOUT->flush;
} else {
&openlog( $BASENAME, 'pid', $FACILITY );
&syslog( $priority, $format, @arg );
&closelog();
}
}
sub printinfo ($@) {
&printlog( "info", @_ );
}
sub printerror ($@) {
&printlog( "err", @_ );
}
sub handler ($) {
my( $sig ) = @_;
&printinfo( "Caught a SIG%s -- shutting down", $sig );
unlink $PIDFILE if $PIDFILE;
exit 0;
}
#----------------------------------------------------------------------
# コマンドを実行する関数
#----------------------------------------------------------------------
sub child ($@) {
my( $mtime, @argv ) = @_;
for( my $i; $i < $RETRY; $i++ ){
if( my $pid = fork ){
# 親プロセス側の処理
return wait;
} elsif( defined $pid ){
# 子プロセス側の処理
&grandchild( $mtime, @argv );
exit 0;
} elsif( $! == &EAGAIN ){
sleep 5;
next;
} else {
&printerror( "Can't fork a child process: %s", $! );
}
}
&printerror( "Can't fork a child process: retry conter exceeded" );
}
sub grandchild ($@) {
my( $mtime, @argv ) = @_;
for( my $i; $i < $RETRY; $i++ ){
if( my $pid = fork ){
# 親プロセス側の処理
exit 0;
} elsif( defined $pid ){
# 子プロセス側の処理
&printinfo( "Fork from %s[%s]", $BASENAME, $PPID );
&start( $mtime, @argv );
exit 0;
} elsif( $! == &EAGAIN ){
sleep 5;
next;
} else {
&printerror( "Can't fork a grandchild process: %s", $! );
}
}
&printerror( "Can't fork a grandchild process: retry conter exceeded" );
}
sub start ($@) {
my( $mtime, @argv ) = @_;
local $PROGRAM_NAME =
sprintf( '%s %s and executing "%s"', $BASENAME, $FILE, join( ' ', @argv ) );
&printinfo( 'Execute "%s"', join( ' ', @argv ) );
my $result = &run( @argv );
wait;
if( $result ){
&printinfo( "Send the result mail to %s", $TO );
if( $DEBUG ){
print $result;
print "\n" unless $result =~ m/\n\Z/m;
STDOUT->flush;
} else {
&sendmail( $mtime, join( ' ', @argv ), $result );
}
} else {
&printinfo( "Normal ended" );
}
}
# コマンドの標準出力と標準エラー出力をまとめて,1つの出力文字列として
# 受け取るために,標準の IPC::Open3 ではなく自前の関数を使っている.
sub run (@) {
my( @argv ) = @_;
# 子プロセスと通信するためのパイプを生成する
my( $read, $write ) = FileHandle::pipe;
# 子プロセスを fork する
for( my $i; $i < $RETRY; $i++ ){
if( my $pid = fork ){
# 親プロセス側の処理
close $write;
return join( '', $read->getlines );
} elsif( defined $pid ){
# 子プロセス側の処理
close $read;
STDOUT->fdopen( $write, "w" );
STDERR->fdopen( $write, "w" );
my $null = new FileHandle( "/dev/null", "r" );
if( $null ){
STDIN->fdopen( $null, "r" );
exec @argv;
} else {
&printerror( "Can't open /dev/null: $!" );
}
exit 0; # Not reach.
} elsif( $! == &EAGAIN ){
sleep 5;
next;
} else {
&printerror( "Can't fork the command(%s): %s", join( ' ', @argv ), $! );
return undef;
}
}
&printerror( "Can't fork the command(%s): retry conter exceeded", join( ' ', @argv ) );
undef;
}
#----------------------------------------------------------------------
# メールを送信する関数
#----------------------------------------------------------------------
sub sendmail ($$$) {
my( $time, $command, $str ) = @_;
my $smtp = Net::SMTP->new( "localhost" ) or die;
$smtp->mail( $FROM );
$smtp->to( $TO );
$smtp->data();
$smtp->datasend( &mailheader( $time, $command ) );
$smtp->datasend( Jcode->new( $str )->iso_2022_jp );
$smtp->dataend();
$smtp->quit;
}
sub mailheader ($$) {
my( $time, $command ) = @_;
$time = strftime( "%a, %d %b %Y %T %z", localtime( $time ) );
my $ppid = $REENTRANT ? sprintf( "\nX-Polling-Parent-Pid: %d", $PPID ) : "";
sprintf( <<"__header__", $FROM, $BASENAME, $TO, $FILE, $command, $time, $FILE, $command, $PID, $ppid );
From: %s (%s)
To: %s
Subject: Poll [%s] %s
Date: %s
X-Polling-File: %s
X-Polling-Command: %s
X-Polling-Pid: %d%s
MIME-Version: 1.0
Content-Type: text/plain; charset=iso-2022-jp
__header__
}
=back
=head1 USAGE
このスクリプトは,元々,dhcpd によって管理されているホストが接続された
時点で特定のコマンドを実行することを目的に作成された.
例えば,動的な IP アドレスの割り当てを受けている普通のホストであれば,
アドレスが貸し出された時点で F に記録さ
れるので,このファイルをスクリプトで監視していれば,ホストが接続された
時点でコマンドを実行することができる.
ただし,MAC アドレスに基づいて静的な IP アドレスを付与しているホストの
場合は,F が変化しないため,もう少し工夫
が必要である.
F に記述されている I の設定値を確認し,
dhcpd の動作記録を別にするように F に設定する.
Example:
# For dhcpd
local7.info -/var/log/dhcpd.info
この後,syslogd に対して HUP シグナルを送る.
これで,dhcpd の動作記録が F に保存されるようにな
るので,このファイルを監視していれば,ホストが接続された時点でコマンド
を実行することができる.
作者の環境では,MTA の queue の処理をホストの接続にあわせて行うように
している.
=head1 REQUIREMENT
Perl に標準的に付属しているモジュール以外に,以下の2つのモジュールが必
要である.
=over 4
=item *
L
=item *
L
=back
=head1 AUTHOR
=over 4
=item TSUCHIYA Masatoshi
=back
=head1 COPYRIGHT
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, you can either send email to this
program's maintainer or write to: The Free Software Foundation,
Inc.; 59 Temple Place, Suite 330; Boston, MA 02111-1307, USA.
Last Update: $Date: 2002/12/02 01:35:31 $
=cut