デーモンプログラムを書いてみる

    • whileで無限ループなものを書くのならいっそデーモンにしてしまえということで
    • Proc::Daemonというのがいいらしい perldoc perlfaq8 にものってるし。
    • という安易な考えで調べて見てたら結構深い。
    • 素直にモジュール使うのがいいかもしれない。
    • App::Daemonは実行ユーザーの変更、ログ出力もサポートしてるがLog::Log4perl,Proc::ProcessTableと依存モジュールがある。
    • Proc::DaemonはPP
  • 手順
    • forkで子プロセスを作成する
    • 親プロセスを終了する
    • umask(0)でマスク値を設定する
      • umaskが設定されていると意図しない権限でファイルを作成してしまうかもしれない
    • setsid
      • setsidするにはセッションリーダーでない必要があるので上記forkが必要
    • さらにforkする
      • セッションリーダーのままだと端末に関連付けられてしまうことがあるのを回避
    • カレントディレクトリを "/" に移動する
      • プロセスが居座っているディレクトリは消せないしアンマウントもできない
    • ファイルディスクリプタを閉じる
      • 閉じないと起動した端末に出力が出てきたりする。
  • ここまでのまとめ。そこそこのとこまで書けた?
#!/usr/bin/perl

use strict;
use warnings;
use POSIX;
use FindBin qw/$Bin/;

my $user = "ymko";


### main ###

daemonize();
set_stdio(
	stdin  => "/dev/null",
	stdout => "$Bin/daemon.log",
	stderr => "$Bin/daemon_error.log",
);
set_process_user($user);
set_trap_signal();

set_pidfile(
	pidfile => "$Bin/pidfile.txt",
);

`touch "$Bin/hold.txt"`;
my $cnt = 0;
while (1) {
	$cnt++;
	print "$cnt\n";
	sleep 1;
	
	exit 0 unless -f "$Bin/hold.txt";
}

sub set_pidfile
{
	my (%ref) = @_;
	my $pidfile = (exists $ref{'pidfile'}) ? $ref{'pidfile'} : undef;
	die "pidfile not defined\n" unless $pidfile;
	
	open my $fh, ">", $pidfile or die "$!";
	print $fh "$$\n";
	close $fh;
	return;
}

sub set_process_user
{
	if ($> == 0) {
		my ($user) = @_;
		my ($uid, $gid) = (getpwnam $user)[2, 3];
		POSIX::setgid($gid);
		POSIX::setuid($uid);
	}
	return ;
}

sub set_trap_signal
{
	# reload config file.
	$SIG{'HUP'} = sub {
		print "get signal hup\n";	
	};
	$SIG{'QUIT'} = sub {
		print "get signal quit\n";
	};
	$SIG{'USR1'} = sub {
		print "get signal user1\n";
	};
	$SIG{'USR2'} = sub {
		print "get signal user2\n";
	};
	$SIG{'TERM'} = sub {
		print "get signal term\n";
		exit 0;
	};
	return ;
}

sub set_stdio
{
	my (%ref) = @_;

	my $stdin  = (exists $ref{'stdin' }) ? $ref{'stdin' } : "/dev/null";
	my $stdout = (exists $ref{'stdout'}) ? $ref{'stdout'} : "/dev/null";
	my $stderr = (exists $ref{'stderr'}) ? $ref{'stderr'} : "/dev/null";

	open (STDIN,  "<",  $stdin);
	open (STDOUT, ">>", $stdout);
	open (STDERR, ">>", $stderr);

	return;
}


sub daemonize
{

	my $pid = fork;
	if ($pid > 0) {
		# parent
		exit 0;
	}
	elsif ($pid < 0) {
		# faild
		exit -1;
	}
	
	if (POSIX::setsid() < 0) {
		die "setsid failed\n";
	}
	
	my $sub_pid = fork;
	if ($sub_pid) {
		exit 0;
	}
	
	chdir "/";
	umask 0;
	
	my $open_max = POSIX::sysconf( POSIX::_SC_OPEN_MAX );
	#print "open_max=$open_max\n";
	foreach (0 .. $open_max) { 
		#print "close $_\n";
		#next if $_ eq 1; stdout
		POSIX::close( $_ );
	}
	return ;
}