#!/usr/bin/perl
#####################################################################
# Function: pg-rex_slave_start
#
#
# 概要:
# PG-REX での Slave 起動実行ツール。
# 手順の簡易化を目的として作成している。
# 
# 特記事項:
# なし
#
# Copyright (c) 2012-2017, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
#
#####################################################################
package PGRex;

use warnings;
use strict;
use sigtrap qw(die normal-signals error-signals);
use File::Compare;
use Getopt::Long;
use Class::Struct;
use IO::Socket::INET;
use PGRex::command;
use PGRex::common qw(pacemaker_online pgrex_failed_action network_running master_running
                     slave_running vip_running stonith_running ping_running diskd_running
                     read_config read_cib read_hacf get_ssh_passwd exec_command 
                     ssh_exec_command scp_exec_command get_pg_command_path check_user
                     printlog get_node get_another_iclan_netaddr get_iclan_ipaddr
                     rsync_exec_command check_support_version ifcheckd_running
                     get_pg_version get_controldata_value get_xlog_filename compare_lsn
                     get_pg_dir_state get_restore_archivewal_num get_start_wal_filename
                     check_dbcluster_access create_pid_file unlink_pid_file);

BEGIN {
    if ($ENV{'LANG'} =~ m/ja/i){
        eval qq{
            use PGRex::Po::ja;
        };
    }
    else{
        eval qq{
            use PGRex::Po::en;
        }
    }

	create_pid_file();
};

END {
	unlink_pid_file();
};

$SIG{INT} = sub {
    printlog("LOG", SLAVESTART_MS0001);
};

struct Check_result => {
	version_is_match => '$', # B)
	recovery_conf_exists => '$', # C)
	my_xlog_is_over => '$', # D)
	full_page_writes => '$', # F)
	same_branch_point => '$', # H)
	pass_my_tli => '$', # I)
	promote_before_my_current_lsn => '$', # J)
	match_database_id => '$', # K)
	gt_another_tli => '$', # L)
	wal_log_hints => '$', # M)
	status_is_shutdown => '$', # N)
	checkpoint_before_branch => '$', # O) 'none' or 'latest' or 'prior'
	is_conf_add_comment => '$',
	my_archive_is_over => '$'
};

struct My_data => {
	pg_version => '$',
	cluster_version => '$',
	pg_xlog_is_empty => '$',
	current_xlog_location => '$',
	controldata => '%',
	latest_branch_tli => '$',
	latest_branch_lsn => '$',
	copy_base_lsn => '$'
};

struct Another_data => {
	pg_xlog_is_empty => '$',
	current_xlog_location => '$',
	controldata => '%',
	new_tli => '$', # P) 'same' or 'history' or 'pg_control'
	history_file => '$',
  	history_tli => '$',
	history_value => '%',
	branch_lsn => '$',
	pgport => '$'
};

use constant {
	NORMAL                    => 1,
	PG_REWIND                 => 2,
	PG_BASEBACKUP             => 4,
	DB_SHUTDOWNED             => "shut down",
	DB_SHUTDOWNED_IN_RECOVERY => "shut down in recovery",
	ADD_ARCHIVE_MODE          => "archive_mode = off ",
	ADD_KEEP_SEGMENTS         => "wal_keep_segments = 2147483647 ",
	ADD_COMMENT               => "# added by pg-rex_slave_start"
};

main();

1;

sub main{
    my $config_path = CONFIG_PATH.CONFIG_FILENAME;
    my %config_value;
    my $myself;

    my $operation_num = 1;
    my $operation_sub_num = 1;
    my $exit_code;
    my $result;
    my @results;
    my $command;
	my $line;

    my $help_mode = 0;
    my $normal_mode = 0;
	my $rewind_mode = 0;
    my $force_mode = 0;
    my $check_only_mode = 0;
	my $interactive_mode = 0;
	my $dry_run_mode = 0;
    my $shared_archive_directory_mode = 0;
    my $basebackup_options = "";
    my $version_mode = 0;
	my $ready_list = 0;
	my $is_ready;

    my $ssh_pass;
    my %node_value;
    my $copy_dir = "/tmp/";
    my $copy_authkey_path = $copy_dir.AUTHKEY_FILENAME;
    my $authkey_path = HACF_PATH.AUTHKEY_FILENAME;
    my $hacf_path = HACF_PATH.HACF_FILENAME;
    my %hacf_value;
    my @another_iclan_netaddr = ();
    my %iclan_ipaddr;
    my $cib_path = CIB_PATH.CIB_FILENAME;
    my $copy_cib_path = $copy_dir."cib.xml";
    my %master_cib_value;
    my $kill_when_no_data = 1;
    my $lock_file = "";

    my %command_path;
    my $pg_command_user = "postgres";
    my $pg_xlog_dir;
    my $symbolic_link = 0;

    my $timeout = 300;
    my $monitor_delay = 10;
    my $monitor_interval = 2;
    my $wait_time = 0;
    my $starting_resource = "";

	my $historyfile_path;
	my @history_files;
	my $tli;
	my @controldata_strings;

	my $check_result = new Check_result();
	my $pg_dir_state = new Pg_dir_state();
	my $my_data = new My_data();
	my $another_data = new Another_data();

    # 標準出力が途中で停止するのを防ぐ為に
    # 標準出力のオートフラッシュを有効化
    $| = 1;

    # 実行ユーザの確認
    check_user();

    # 環境設定ファイルの読み込み
    %config_value = read_config($config_path);

    # PostgreSQL のコマンドパスを取得
    %command_path = get_pg_command_path($config_value{'PGPATH'});

    # Pacemaker と PostgreSQL がサポート対象バージョンであるかを確認
    check_support_version($command_path{'postgres'});

    ### スクリプト実行準備 ###

    # オプション解析
    foreach ( @ARGV ){
        if ( "$_" eq "-" || "$_" eq "--" ){
            $help_mode = 1;
        }
    }
    {
        no warnings 'once';
    $exit_code = GetOptions('help'                  => \$help_mode,
                         'normal'                   => \$normal_mode,
                         'rewind'                   => \$rewind_mode,
                         'force'                    => \$force_mode,
                         'basebackup'               => \$force_mode,
                         'check-only'               => \$check_only_mode,
                         'dry-run'                  => \$dry_run_mode,
                         'shared-archive-directory' => \$shared_archive_directory_mode,
                         'options=s'                => \$basebackup_options,
                         'additional_information'   => \$PGRex::common::additional_information_mode,
                         'version'                  => \$version_mode);
    }
    $myself = $0;
    $myself =~ s/.*\///g;
    if ($help_mode || !$exit_code){
        printlog("VERSION", VERSIONINFO, $myself, VERSIONNUM);
        print "\n";
        printlog("USAGE", SLAVESTART_USAGE);
        exit(0);
    }
    if ($version_mode){
        printlog("VERSION", VERSIONINFO, $myself, VERSIONNUM);
        exit(0);
    }

    # pg_basebackupのオプションの確定
    if ($basebackup_options ne "") {
        $config_value{'PG_BASEBACKUP_opt'} = $basebackup_options;
    }

	# 対話形式かどうかの確認
	if (!$normal_mode && !$rewind_mode && !$force_mode) {
		$interactive_mode = 1;
	}

    # ssh 接続の為の情報の取得
    $ssh_pass = get_ssh_passwd($config_value{'Another_D-LAN_IPAddress'}, $config_value{'PEER_NODE_SSH_PASS_MODE'}, $config_value{'PEER_NODE_SSH_PASS_FILE'});
    my $ssh_info = new Ssh_info();
    $ssh_info->address("$config_value{'Another_D-LAN_IPAddress'}");
    $ssh_info->user("root");
    $ssh_info->pass("$ssh_pass");

    # 自身のノードと相手のノードで ifcheckd が起動していることを確認
    ifcheckd_running($ssh_info);

    # コマンドを実行しているマシンのノード名ともう一台のノード名を取得
    %node_value = get_node($ssh_info);

    # 相手のノードと同じクラスタ認証ファイルが配置されていること
    scp_exec_command($ssh_info, $authkey_path, $copy_dir);
    if (compare($authkey_path, $copy_authkey_path)){
        printlog("ERROR", SLAVESTART_MS0002, $authkey_path);
    }
    exec_command("$RM -f $copy_authkey_path");

    # corosync.conf の読み込み
    %hacf_value = read_hacf($hacf_path, "start");

    # 相手先のノードの corosync.conf に設定されている bindnetaddr を取得する 
    @another_iclan_netaddr = get_another_iclan_netaddr($hacf_path, $ssh_info);

    ### Pacemaker 起動の為の準備 ###
    # 自身のノードの Pacemaker 及び corosync の稼働確認
    printlog("LOG", SLAVESTART_MS0004, $operation_num++);
    if (pacemaker_running()){
        printlog("LOG", SLAVESTART_MS0005);
        printlog("ERROR", SLAVESTART_MS0006);
    }
    printlog("LOG", SLAVESTART_MS0007);

    ### 稼働中の Master の存在確認 ###
    printlog("LOG", SLAVESTART_MS0008, $operation_num++);
    if (!master_running($node_value{'another_node'}, $config_value{'PG-REX_Master_ResourceID'}, $config_value{'PG-REX_Primitive_ResourceID'}, $ssh_info)){
        printlog("LOG", SLAVESTART_MS0005);
        printlog("ERROR", SLAVESTART_MS0010);
    }
    printlog("LOG", SLAVESTART_MS0007);

    # Master マシンにある cib.xml をコピーし読み込む
    scp_exec_command($ssh_info, $cib_path, $copy_dir);
    %master_cib_value = read_cib($copy_cib_path, $config_value{'PG-REX_Primitive_ResourceID'}, $kill_when_no_data, $config_path, $config_value{'ARCHIVE_DELETE_DBCLUSTER_DIR'});
    exec_command("$RM -f $copy_cib_path");

	# 自身のノードで他にDBクラスタにアクセスしているプロセスがないか確認
	check_dbcluster_access($master_cib_value{'pgdata'});

    ### 起動禁止フラグの存在確認 ###
    printlog("LOG", SLAVESTART_MS0013, $operation_num++);

    # 起動禁止フラグが存在しないことを確認
    $lock_file = $master_cib_value{'tmpdir'}."/".LOCK_FILENAME;
    if (-e $lock_file){
        printlog("LOG", SLAVESTART_MS0005);
        printlog("ERROR", SLAVESTART_MS0014, $lock_file);
    }

    printlog("LOG", SLAVESTART_MS0007);


	### DBクラスタの状態確認 ###

	printlog("LOG", SLAVESTART_MS0034, $operation_num);

    ## DB クラスタの状態を収集する
    # DB クラスタの及びアーカイブディレクトリの状態を収集する A)、E)
    $pg_dir_state = get_pg_dir_state($master_cib_value{'pgdata'}, $config_value{'Archive_dir'});

	# 相手ノードのfull_page_writesを取得 F)
	$command = "$SU - $pg_command_user -c \"$command_path{'psql'} -tAc \\\"SHOW full_page_writes\\\" 2> /dev/null \"";
	@results = ssh_exec_command($ssh_info, $command);
	if ($results[0] eq "on") {
		$check_result->full_page_writes(1);
	} else {
		$check_result->full_page_writes(0);
	}
	$command = "$SU - $pg_command_user -c \"$command_path{'psql'} -tAc \\\"SHOW port\\\" 2> /dev/null \"";
	@results = ssh_exec_command($ssh_info, $command);
	$another_data->pgport($results[0]);

	# 相手のノードの pg_control と TLI hisotry ファイルの情報を収集する
	# TLI hisotry ファイルの情報を収集
	$historyfile_path = $config_value{'Archive_dir'}."/*.history";
	@results = ssh_exec_command($ssh_info, "$LS -1r $historyfile_path* 2> /dev/null");
	if ($results[0] eq "") {
		printlog("ERROR", SLAVESTART_MS0042);
	}
	@history_files = split(/\n/, $results[0]);
	$another_data->history_file($history_files[0]);
	($tli=$history_files[0]) =~ s/^.*\/([0-9A-F]+)\..*$/$1/;
	$another_data->history_tli(hex($tli));
	@results = get_history_value($history_files[0], $ssh_info);
	my %val_list ;
	foreach $line (@results) {
		if( $line =~ /^(\d+)\s+([0-9A-F\/]+)/ ) {
			$val_list{$1} = $2;
		}
	}
	$another_data->history_value(\%val_list);

	# pg_controlの情報を収集
	@results = ssh_exec_command($ssh_info, "$SU - $pg_command_user -c \"export LANG=C; $command_path{'pg_controldata'} $master_cib_value{'pgdata'}\"");
	@controldata_strings = split(/\n/, $results[0]);
	$another_data->controldata(get_controldata_value(@controldata_strings));

	# 相手ノードのhistoryファイルが最新であることを確認 G)
	# -> historyのTLIが、pg_controlのTLIより小さい場合NG
	# 処理を継続する意味がないため、ここで異常終了する
	if ($another_data->history_tli < $another_data->controldata("Latest checkpoint's TimeLineID")) {
		my $tli_string = sprintf("%08X", $another_data->controldata("Latest checkpoint's TimeLineID"));
		printlog("LOG", SLAVESTART_MS0087,
				 $another_data->controldata("Latest checkpoint's TimeLineID"),
				 File::Spec->catfile($config_value{'Archive_dir'}, $tli_string.".history"));
		printlog("ERROR", SLAVESTART_MS0065);
	}

	# wal_log_hints settingがonまたは、Data page checksum versionが0以外を確認 M)
	if ($another_data->controldata("wal_log_hints setting") eq "on"
		|| $another_data->controldata("Data page checksum version") > 0) {
		$check_result->wal_log_hints(1);
	} else {
		$check_result->wal_log_hints(0);
	}

	# 相手のノードのTLIを比較 P)
	# -> pg_rewind実行前にチェックポイントが必要かの判定および
	#    TLIが同じで、自身のノードのDatabase cluster stateが、
	#    shut down以外の場合は、pg_rewind実行NGとする判定に必要なため
	if ($another_data->history_tli == $another_data->controldata("Latest checkpoint's TimeLineID")) {
		$another_data->new_tli('same');
	} elsif($another_data->history_tli > $another_data->controldata("Latest checkpoint's TimeLineID")) {
		$another_data->new_tli('history');
	} else {
		$another_data->new_tli('pg_control');
	}

	# 自身のノードのアーカイブにファイルがある場合、相手のノードより後の
	# WALを含むセグメントファイルがないか確認
	$check_result->my_archive_is_over(0);
	if (!$pg_dir_state->pgarch_empty) {
		# 相手のノードの最新のTLIを設定
		my $s_tli = $another_data->history_tli;
		# 相手のノードの最新のLSNを取得
		$command = "$SU - $pg_command_user -c \"$command_path{'psql'} -tAc \\\"SELECT pg_current_xlog_location()\\\" 2> /dev/null \"";
		@results = ssh_exec_command($ssh_info, $command);
		$results[0] =~ /^([0-9A-F]+)\/([0-9A-F]+)$/;
		my ($s_logid, $s_segno) = (hex($1), hex($2));
		my $s_lsn = ($s_logid << 32) + ($s_segno << 24);

		# 相手のノードより後のセグメントファイル名を確認
		opendir(my $dir, $config_value{'Archive_dir'}) || die "Failed to open archive directory: $config_value{'Archive_dir'}";
		foreach my $file (sort readdir($dir)) {
			next if ($file !~ /^(([0-9A-F]{8})([0-9A-F]{8})([0-9A-F]{8}))(\.partial)?(?:\.(.+))?$/);
			my ($f_basename, $f_tli, $f_logid, $f_segno, $f_partial, $f_suffix) =
				($1, hex($2), hex($3), hex($4), defined $5, $6);
			my $f_lsn = ($f_logid << 32) + ($f_segno << 24);
			if ($f_tli > $s_tli || $f_lsn >= $s_lsn) {
				$check_result->my_archive_is_over(1);
				last;
			}
		}
		closedir($dir);
	}

	$check_result->is_conf_add_comment(0);
	# 以降は自身のノードが存在する場合のみチェック
	if ($pg_dir_state->pgdata_exist) {
		# pg-rex_slave_startが追記した内容がpostgresql.confに存在するかチェック
		$command = "$GREP \"".ADD_COMMENT."\" $master_cib_value{'pgdata'}/postgresql.conf";
		$result = `$command`;
		if ($result) {
			$check_result->is_conf_add_comment(1);
		}

		# 自身のノードの pg_control と TLI hisotry ファイルの情報を収集する
		# TLI hisotry ファイルの情報を収集
		$historyfile_path = $config_value{"Archive_dir"}."/*.history";
		$result = `$LS -1r $historyfile_path* 2> /dev/null`;
		my $exit_code = $? >> 8;
		if ($exit_code != 0) {
			# ダミーデータを設定
			$my_data->latest_branch_tli(1);
			$my_data->latest_branch_lsn("0/00000000");
		} else {
			@results = split(/\n/, $result);
			my @history_files = split(/\n/, $results[0]);
			@results = get_history_value($history_files[0]);
			foreach $line (@results) {
				# 最後のTLIの情報のみ収集
				if( $line =~ /^(\d+)\s+([0-9A-F\/]+)/) {
					$my_data->latest_branch_tli($1);
					$my_data->latest_branch_lsn($2);
				}
			}
		}

		# pg_controlの情報を収集
        $result = exec_command("$SU - $pg_command_user -c \"export LANG=C; $command_path{'pg_controldata'} $master_cib_value{'pgdata'}\"");
        @controldata_strings = split(/\n/, $result);
        $my_data->controldata(get_controldata_value(@controldata_strings));

		## 自身のノード単独の確認
		# クラスタのバージョンチェック B)
		my $pg_version_file = $master_cib_value{'pgdata'}."/PG_VERSION";
        my $dbcluster_version = `$SU $pg_command_user -c "$CAT $pg_version_file"`;
        chomp $dbcluster_version;
		if ($dbcluster_version) {
			$my_data->cluster_version($dbcluster_version);
			my @version_factor = split(/\./, get_pg_version($command_path{'postgres'}));
			$my_data->pg_version($version_factor[0].".".$version_factor[1]);
			if ($my_data->cluster_version ne $my_data->pg_version) {
				$check_result->version_is_match(0);
			} else {
				$check_result->version_is_match(1);
			}
		} else {
			$check_result->version_is_match(0);
		}

		# recovery.confの有無のチェック C)
		my $recovery_conf = $master_cib_value{'pgdata'}."/recovery.conf";
		if (-f $recovery_conf) {
			$check_result->recovery_conf_exists(1);
		} else {
			$check_result->recovery_conf_exists(0);
		}

		# 自身のノードのDatabase cluster stateが、shut downまたは、
		# shut down in recoveryのいずれかであることを確認 N)
		if ($my_data->controldata("Database cluster state") eq "shut down"
			|| $my_data->controldata("Database cluster state") eq "shut down in recovery") {
			$check_result->status_is_shutdown(1);
		} else {
			$check_result->status_is_shutdown(0);
		}

		# (STEP7)自身ノードの TLI が過去に通っていることを確認 I)
		# 自身のノードのLatest checkpoint locationまたは
		# Prior checkpoint locationのいずれかが分岐点前であることの確認 O)
		my $target_tli;
		if ($another_data->history_tli == $my_data->controldata("Latest checkpoint's TimeLineID")) {
			$target_tli = $my_data->controldata("Latest checkpoint's TimeLineID") -1;
		} elsif ($another_data->history_tli < $my_data->controldata("Latest checkpoint's TimeLineID")) {
			$target_tli = $another_data->history_tli -1 ;
		}else {
			$target_tli = $my_data->controldata("Latest checkpoint's TimeLineID");
		}

		$another_data->branch_lsn($another_data->history_value($target_tli));
		if ($another_data->branch_lsn) {
			$check_result->pass_my_tli(1);

			if (compare_lsn($another_data->branch_lsn, $my_data->controldata("Latest checkpoint location")) >= 0) {
				$check_result->checkpoint_before_branch("latest");
				$my_data->copy_base_lsn($my_data->controldata("Latest checkpoint location"));
			} elsif (compare_lsn($another_data->branch_lsn, $my_data->controldata("Prior checkpoint location")) >= 0) {
				$check_result->checkpoint_before_branch("prior");
				$my_data->copy_base_lsn($my_data->controldata("Prior checkpoint location"));
			} else {
				$check_result->checkpoint_before_branch("none");
			}
		} else {
			$check_result->pass_my_tli(0);
			$check_result->checkpoint_before_branch("none");
		}

		## 自身のノードと相手のノードの比較
		# (STEP 3)自身のノードと相手のノードのデータベース識別子が一致していることを確認 K)
		if ($my_data->controldata("Database system identifier")
			eq $another_data->controldata("Database system identifier")) {
			$check_result->match_database_id(1);
		} else {
			$check_result->match_database_id(0);
		}

		# (STEP 4)自身のノードの TLI が相手のノードを超過していないことを確認 L)
		if ($my_data->controldata("Latest checkpoint's TimeLineID")
			> $another_data->history_tli) {
			$check_result->gt_another_tli(1);
		} else {
			$check_result->gt_another_tli(0);
		}

		# 両系のpg_xlogdumpに指定する読取り終了セグメントファイル名を特定する
		my $my_latest_wal_filename;
		my $another_latest_walfile_name;
		my $another_latest_checkpoint_walfile_path;
		my $another_latest_checkpoint_walfile_name;
		$command = "$LS -l $master_cib_value{'pgdata'}/pg_xlog/ | $GREP -P \"[0-9A-F]{24}\$\" | $TAIL -1";
		$result = exec_command($command);
		if ($result =~ /.*([0-9A-F]{24})$/){
			$my_latest_wal_filename = $1;
			$my_data->pg_xlog_is_empty(0);
		} else {
			$my_data->pg_xlog_is_empty(1);
		}

		$command = "$LS -l $master_cib_value{'pgdata'}/pg_xlog/ | $GREP -P \"[0-9A-F]{24}\$\" | $TAIL -1";
		@results = ssh_exec_command($ssh_info, $command);
		if ($results[0] =~ /.*([0-9A-F]{24})$/){
			$another_latest_walfile_name = $1;
			$another_data->pg_xlog_is_empty(0);

			# 読取り開始セグメントファイル名を特定する
			my $filename = $another_data->controldata("Latest checkpoint's REDO WAL file");
			$filename =~ s/^[0-9A-F]{8}([0-9A-F]{16})$/$1/;
			$command = "$LS -1 $master_cib_value{'pgdata'}/pg_xlog/*$filename* | $GREP -P '[0-9A-F]{24}(\.partial)?'";
			@results = ssh_exec_command($ssh_info, $command);
			my @filenames = split(/\n/, $results[0]);
			# 最大2件しか返ってこない
			if (($filename = $filenames[0]) =~ /.*\.partial/) {
				$another_latest_checkpoint_walfile_path = $filenames[1];
			} else {
				$another_latest_checkpoint_walfile_path = $filenames[0];
			}
		} else {
			$another_data->pg_xlog_is_empty(1);
		}

		# 自身のノードのTLIが相手のノードを超過している場合、
		# 以降のLSN関連のチェックは行わない
		if ($check_result->gt_another_tli) {
			# 省略したチェック項目のエラーが出ないように値を設定
			$check_result->my_xlog_is_over(0);
			$check_result->same_branch_point(1);
			$check_result->promote_before_my_current_lsn(0);
			$check_result->pass_my_tli(1);

		} else {
			# (STEP 5)自身のノードの XLOG 位置が相手のノードを超過していないことを確認 D)
			$check_result->my_xlog_is_over(0);
			if ($my_data->pg_xlog_is_empty || $another_data->pg_xlog_is_empty) {
				$check_result->my_xlog_is_over(1);
			} else {

				# 自信のノードの最新のLSNを取得する
				$my_data->current_xlog_location(getLatestLsn($my_data->controldata("Latest checkpoint's REDO WAL file"), 
						$my_latest_wal_filename, 
						$master_cib_value{'pgdata'}."/pg_xlog",
						$command_path{'pg_xlogdump'}, 
						$pg_command_user));

				# 相手のノードの最新のLSNを取得する
				# WALファイル名からTLIを取得
				$another_latest_checkpoint_walfile_path =~ /^.*([0-9A-F]{24}(\.partial)?)/;
				$another_latest_checkpoint_walfile_name = $1;
				my $another_latest_checkpoint_tli = substr($another_latest_checkpoint_walfile_name,0,8);
				my $another_latest_tli = substr($another_latest_walfile_name,0,8);
				
				# pg_controlのTLIが最新のTLIより小さい場合、
				# pg_xlogdumpの読み取り開始ファイルを最新のTLIのpg_walに存在する最古のWALセグメントファイルにする
				if ($another_latest_checkpoint_tli lt $another_latest_tli){
					$command = "$LS -1Hr $master_cib_value{'pgdata'}/pg_xlog/ | $GREP -P \"^${another_latest_tli}[0-9A-F]{16}\$\" | $TAIL -1";
					@results = ssh_exec_command($ssh_info, $command);
					chomp $results[0];
					if ($results[0] eq ""){
						# pg_walにWALセグメントファイルが存在しない
						printlog("ERROR", SLAVESTART_MS0099);
					}
					$another_latest_walfile_name = $results[0];
				}
				my $xlogdump_walfilepath_start = "$master_cib_value{'pgdata'}/pg_xlog/$another_latest_checkpoint_walfile_name";
				my $xlogdump_walfilepath_end = "$master_cib_value{'pgdata'}/pg_xlog/$another_latest_walfile_name";
				$command = "$SU - $pg_command_user -c \"$command_path{'pg_xlogdump'} $xlogdump_walfilepath_start $xlogdump_walfilepath_end 2> /dev/null | $TAIL -1\"";
				@results = ssh_exec_command($ssh_info, $command);
				if ($results[0] =~ /.*lsn:\s+([0-9A-F\/]+).*/){
					$another_data->current_xlog_location($1);
				}

				# 取得した XLOG 位置を比較(自身のノードの XLOG 位置の方が進んでいないか確認)
				$result = compare_lsn($my_data->current_xlog_location,
									  $another_data->current_xlog_location);
				if ($result > 0){
					$check_result->my_xlog_is_over(1);
				}
			}

			# TLIのhistoryファイルを確認し、分岐点が一致していることを確認 H)
			if ($my_data->latest_branch_tli >= $another_data->history_tli) {
				$check_result->same_branch_point(0);
			} elsif ($check_result->pass_my_tli
				&& compare_lsn($my_data->latest_branch_lsn,
							   $another_data->history_value($my_data->latest_branch_tli))) {
				$check_result->same_branch_point(0);
			} else {
				$check_result->same_branch_point(1);
			}

			# 両系で、TLI、分岐点が同じ場合、STEP8はチェック不要
			if ($another_data->history_tli == $my_data->controldata("Latest checkpoint's TimeLineID")) {
				$check_result->promote_before_my_current_lsn(0);
			} else {
				# (STEP8) 自身ノードの現在の LSN よりも先に promote されていないことを確認 J)

				# 自身ノードの TLI が過去に通っていなければチェックしない
				if (!$check_result->pass_my_tli) {
					$check_result->promote_before_my_current_lsn(0);
				} else {
					$result = compare_lsn($my_data->current_xlog_location,
										  $another_data->history_value($my_data->controldata("Latest checkpoint's TimeLineID")));
					if ($result < 0){
						$check_result->promote_before_my_current_lsn(0);
					} else {
						$check_result->promote_before_my_current_lsn(1);
					}
				}
			}
		}
	}


	### 既存クラスタのチェック ###
	if ($normal_mode || $check_only_mode || $interactive_mode) {
		printlog("LOG", SLAVESTART_MS0050, $operation_num, $operation_sub_num++);

		if ($pg_dir_state->pgdata_exist) {
			# 共通の確認項目
			$is_ready = common_cluster_check($check_result, $pg_dir_state,
											 $my_data, $another_data);

			# (STEP 5)自身のノードの XLOG 位置が相手のノードを超過していないことを確認 D)
			if ($check_result->my_xlog_is_over) {
				if ($my_data->pg_xlog_is_empty) {
					printlog("ERROR", SLAVESTART_MS0039, $master_cib_value{'pgdata'}."/pg_xlog");
				}
				if ($another_data->pg_xlog_is_empty) {
					printlog("ERROR", SLAVESTART_MS0040, $master_cib_value{'pgdata'}."/pg_xlog");
				}
				printlog("LOG", SLAVESTART_MS0041, 
						 $my_data->current_xlog_location,
						 $another_data->current_xlog_location);
				$is_ready = 0;
			}

			# (STEP8) 自身ノードの現在の LSN よりも先に promote されていないことを確認 J)
			if ($check_result->promote_before_my_current_lsn) {
				printlog("LOG", SLAVESTART_MS0044);
				$is_ready = 0;
			}
		} else {
			# (STEP 1)DB クラスタが存在していることを確認 A)
			printlog("LOG", SLAVESTART_MS0033);
			$is_ready = 0;
		}

		if ($is_ready) {
			$ready_list += NORMAL;
		}
	}

	### pg_rewind実行のためのチェック ###
	if ($rewind_mode || $check_only_mode || $interactive_mode) {
		printlog("LOG", SLAVESTART_MS0051, $operation_num, $operation_sub_num++);

		if ($pg_dir_state->pgdata_exist) {
			# 共通の確認項目
			$is_ready = common_cluster_check($check_result, $pg_dir_state,
											 $my_data, $another_data);

			# 自身のノードにrecovery.confが存在しないことの確認 C)
			if ($check_result->recovery_conf_exists) {
				printlog("LOG", SLAVESTART_MS0054,
						 $master_cib_value{'pgdata'});
				$is_ready = 0;
			}

			# 相手ノード側のfull_page_writesがonであることを確認 F)
			if (!$check_result->full_page_writes) {
				printlog("LOG", SLAVESTART_MS0055);
				$is_ready = 0;
			}

			# pg_controlのwal_log_hints settingがonまたは、
			# Data page checksum versionが0以外となっていることの確認 M)
			if (!$check_result->wal_log_hints) {
				printlog("LOG", SLAVESTART_MS0056);
				$is_ready = 0;
			}

			# 自身のノードのLatest checkpoint locationまたは
			# Prior checkpoint locationのいずれかが分岐点前であることの確認 O)
			if ($another_data->branch_lsn) {
				# デバッグ情報は、branch LSNがある場合のみ表示
				printlog("DEBUG", "branch LSN                 : [0]\n",
						 $another_data->branch_lsn);
				printlog("DEBUG", "Prior checkpoint location  : [0]\n",
						 $my_data->controldata("Prior checkpoint location"));
				printlog("DEBUG", "Latest checkpoint location : [0]\n",
						 $my_data->controldata("Latest checkpoint location"));
			}
			if ($check_result->checkpoint_before_branch eq "none") {
				printlog("LOG", SLAVESTART_MS0072);
				$is_ready = 0;
			}

			# 自身のノードと相手のノードのTLIが同じで、かつ自身のノードの
			# Database cluster stateが"shut down","shut down in recovery"
			# 以外となっていないか確認
			if ($another_data->new_tli eq "same" 
				&& $my_data->controldata("Latest checkpoint's TimeLineID") == $another_data->history_tli
				&& $my_data->controldata("Database cluster state") ne DB_SHUTDOWNED
				&& $my_data->controldata("Database cluster state") ne DB_SHUTDOWNED_IN_RECOVERY) {
				printlog("LOG", SLAVESTART_MS0057);
				$is_ready = 0;
			}

		} else {
			printlog("LOG", SLAVESTART_MS0033);
			$is_ready = 0;
		}

		if ($is_ready) {
			$ready_list += PG_REWIND;
		}
	}

	### pg_basebackup実行のためのチェック ###
	if ($force_mode || $check_only_mode || $interactive_mode) {
		printlog("LOG", SLAVESTART_MS0052, $operation_num,
				 $operation_sub_num++);

		$is_ready = 1;

        # アーカイブディレクトリ共有モードの場合はアーカイブ WAL の存在確認は行わない
        if (!$shared_archive_directory_mode){
            if ($pg_dir_state->pgdata_exist){
                # Master 側と DB 識別子が一致していない、かつアーカイブ
				# WAL が存在する場合は異常終了する
                # NOTE: 適用不可のアーカイブ WAL が残存している可能性があるため
                if ($my_data->controldata("Database system identifier")
					ne $another_data->controldata("Database system identifier")
					&& !$pg_dir_state->pgarch_empty){
                    printlog("LOG", SLAVESTART_MS0032,
							 $config_value{'Archive_dir'});
					$is_ready = 0;
                }
            }
            else {
                # DB クラスタが存在せず、かつアーカイブ WAL が存在する場合は
				# 異常終了する
                # NOTE: Slave初回起動時に不要のアーカイブWALが残存しているため
                if (!$pg_dir_state->pgarch_empty){
                    printlog("LOG", SLAVESTART_MS0032,
							 $config_value{'Archive_dir'});
					$is_ready = 0;
                }
            }
        }

		# 相手のノードより後のセグメントファイルがアーカイブに存在する場合は
		# 異常終了する
		if (!$pg_dir_state->pgarch_empty && $is_ready 
			&& $check_result->my_archive_is_over) {
			printlog("LOG", SLAVESTART_MS0076);
			$is_ready = 0;
		}

		if ($is_ready) {
			$ready_list += PG_BASEBACKUP;
		}
	}
	$operation_num++;

	### チェックモードおよび起動するための選択肢が存在しない場合の処理 ###
	if ($check_only_mode || !$ready_list) {
		## 選択肢が存在しない場合の処理
		if (!$ready_list) {
			printlog("LOG", SLAVESTART_MS0058);
			exit(0);
		}

		## チェックモードの確認結果表示
		printlog("LOG", SLAVESTART_MS0059);
		if ($ready_list & NORMAL) {
			printlog("LOG", SLAVESTART_MS0060);
		} elsif ($ready_list & PG_REWIND) {
			printlog("LOG", SLAVESTART_MS0061);
		} else {
			printlog("LOG", SLAVESTART_MS0062);
		}

		# check-only オプションが指定されている場合はここで終了する
		if ($check_only_mode) {
			exit(0);
		}
	}

	### 対話形式の処理 ###
	if ($interactive_mode) {
		# メッセージはあとでなおす
		my $select_items = "";

		# 起動方法の確認
		printlog("LOG", SLAVESTART_MS0059);

		# 選択肢表示
		if ($ready_list & NORMAL) {
			printlog("LOG", SLAVESTART_MS0060);
			$select_items .= "n/";
		} elsif ($ready_list & PG_REWIND) {
			printlog("LOG", SLAVESTART_MS0061);
			$select_items .= "r/";
		}
		if ($ready_list & PG_BASEBACKUP) {
			printlog("LOG", SLAVESTART_MS0062);
			$select_items .= "b/";
		}

		printlog("LOG", SLAVESTART_MS0063);
		$select_items .= "q";

		while(1) {
			printlog("LOG", SLAVESTART_MS0064, $select_items);

			# 入力処理
			my $input = <STDIN>;
			chomp $input;
			if ($input =~ m/^n$/i && $ready_list & NORMAL){
				$normal_mode = 1;
				last;
			} elsif ($input =~ m/^r$/i && $ready_list & PG_REWIND){
				$rewind_mode = 1;
				last;
			} elsif ($input =~ m/^b$/i && $ready_list & PG_BASEBACKUP){
				$force_mode = 1;
				last;
			} elsif ($input =~ m/^q$/i){
				printlog("LOG", SLAVESTART_MS0065);
				exit(0);
			} else {
				printlog("LOG", SLAVESTART_MS0066);
			}
		}
	}

    ### IC-LAN が接続されていることを確認 ###
    # 自身のノードと相手先のノードの IC-LAN IP アドレスを取得して接続確認を行う
	# dry runの時は実際に確認を行わない
    printlog("LOG", SLAVESTART_MS0011, $operation_num++);

	if (!$dry_run_mode) {
		%iclan_ipaddr = get_iclan_ipaddr(\@{$hacf_value{iclan_netaddr}}, \@another_iclan_netaddr, \%node_value, $ssh_info);

		foreach my $ipaddr (@{$iclan_ipaddr{'another_node'}}){
			`$PING -c 5 $ipaddr`;
			$exit_code = $? >> 8;
			if ($exit_code){
				printlog("LOG", SLAVESTART_MS0005);
				printlog("ERROR", SLAVESTART_MS0012);
			}
		}
	}
    printlog("LOG", SLAVESTART_MS0007);

	### 起動モードごとの処理
	if ($normal_mode && $ready_list & NORMAL) {
		## 既存のDBクラスタを用いたSlaveの起動の場合の処理 ###
		if (!$interactive_mode) {
			printlog("LOG", SLAVESTART_MS0067);
		}

	} elsif ($rewind_mode && $ready_list & PG_REWIND) {
		## pg_rewind実行後Slave起動の場合の処理 ###
		if (!$interactive_mode) {
			printlog("LOG", SLAVESTART_MS0068);
		}

		### 巻き戻し ###
		printlog("LOG", SLAVESTART_MS0070, $operation_num++);
		printlog("LOG", SLAVESTART_MS0041, $my_data->current_xlog_location,
				 $another_data->branch_lsn);
		printlog("LOG", SLAVESTART_MS0071);

		### PostgreSQL起動,停止 ###
		if ($my_data->controldata("Database cluster state") ne DB_SHUTDOWNED
			&& $my_data->controldata("Database cluster state") ne DB_SHUTDOWNED_IN_RECOVERY
			&& !$dry_run_mode) {

			# archive_modeとwal_keep_segmentsをpostgresql.confに追加
			# ファイルへの追加だけなので、rootで行ってしまう
			printlog("DEBUG", "append postgresql.conf\n");
			open(OUT, ">> $master_cib_value{'pgdata'}/postgresql.conf") || die('Failed to open postgresql.conf.');
			print OUT ADD_ARCHIVE_MODE;
			print OUT ADD_COMMENT."\n";
			print OUT ADD_KEEP_SEGMENTS;
			print OUT ADD_COMMENT."\n";
			close(OUT);

			# 自身のノードのPostgreSQLを起動
			printlog("DEBUG", "PostgreSQL start\n");
			$result = exec_command("$SU - $pg_command_user -c \"$command_path{'pg_ctl'} -w -D $master_cib_value{'pgdata'} start > /dev/null\"");

			# 自身のノードのPostgreSQLをfastモードで停止
			printlog("DEBUG", "PostgreSQL stop\n");
			$result = exec_command("$SU - $pg_command_user -c \"$command_path{'pg_ctl'} -D $master_cib_value{'pgdata'} -m fast stop > /dev/null\"");
		}

		### 相手のノードにcheckpointを実行 ###
		if ($another_data->history_tli != $another_data->controldata("Latest checkpoint's TimeLineID")
			&& !$dry_run_mode) {
			printlog("DEBUG", "execute \"checkpoint\" to peer node\n");
			$command = "$SU - $pg_command_user -c \"$command_path{'psql'} -tAc \\\"CHECKPOINT\\\" 2> /dev/null \"";
			@results = ssh_exec_command($ssh_info, $command);
		}

		### pg_rewindが相手のノードに接続するための設定 ###
		if (!$dry_run_mode) {
			# 使用するポートの検索
			my $port;
			foreach ($another_data->pgport .. 60000) {
				if ( my $socket = IO::Socket::INET->new(
						 Proto => 'tcp',
						 LocalHost => '127.0.0.1',
						 LocalPort => $_,
						 Timeout => '1')) {
					$port = $_;
					close($socket);
					last;
				}
			}

			# ポートフォワードの準備
			my $ssh;
			if ($ssh_info->pass eq "") {
				$ssh = Net::OpenSSH->new(
					$config_value{'Another_D-LAN_IPAddress'},
					user => $ssh_info->user,
					master_stderr_discard => 1);
			} else {
				$ssh = Net::OpenSSH->new(
					$config_value{'Another_D-LAN_IPAddress'},
					user => $ssh_info->user, password => $ssh_info->pass,
					master_stderr_discard => 1);
			}
			$ssh->error and printlog("ERROR", SLAVESTART_MS0073, $ssh->error);
			my $peer_port = $another_data->pgport;
			my @out = $ssh->open_ex({stdin_pipe => 1, stdout_pipe => 1, stderr_pipe => 1, ssh_opts => "-L127.0.0.1:$port:127.0.0.1:$peer_port"});
			$ssh->error and printlog("ERROR", SLAVESTART_MS0073, $ssh->error);

			### pg_rewind実行 ###
			$command = "$SU - $pg_command_user -c \"export LANG=C;$command_path{'pg_rewind'} -D $master_cib_value{'pgdata'} --source-server='host=127.0.0.1 port=$port user=$pg_command_user' -P\"";
			# pg_rewindはエラーも標準出力に表示するため、エラー時の情報を
			# 取得するため、exec_commandは使用しない
			$result = `$command`;
			$exit_code = $? >> 8;
			if ($exit_code != 0) {
				if ($result =~ /server closed the connection unexpectedly/){
					printlog("DEBUG", $result);
					printlog("LOG", SLAVESTART_MS0074);
				} else {
					printlog("LOG", $result);
				}
				printlog("ERROR", SLAVESTART_MS0005);
			}
			printlog("LOG", $result);

			### pg_rewindが相手のノードに接続するための設定の削除 ###
			undef $ssh;
		}

		printlog("LOG", SLAVESTART_MS0007);

	} elsif ($force_mode && $ready_list & PG_BASEBACKUP) {
		## pg_basebackup実行後Slave起動の場合の処理 ###
		if (!$interactive_mode) {
			printlog("LOG", SLAVESTART_MS0069);
		}

		printlog("LOG", SLAVESTART_MS0015, $operation_num++);

        # Slave 側の DB クラスタを削除
		if (!$dry_run_mode) {
			exec_command("$SU - $pg_command_user -c \"$RM -rf $master_cib_value{'pgdata'} \"");

			# Master の pg_xlog の場所を取得し、Slave の pg_xlog を削除
			# ※readlink コマンドは、引数に指定されたファイルにシンボリックリンクが存在する場合、コマンドの戻り値に"0"を返す
			# ※戻り値が"0"の場合のみ、pg_xlog を削除する
			@results = ssh_exec_command($ssh_info, "$READLINK $master_cib_value{'pgdata'}/pg_xlog ");
			$exit_code = $results[1];
			if ($exit_code != 0 && $exit_code != 1){
				printlog("LOG", SLAVESTART_MS0005);
				printlog("ERROR", SLAVESTART_MS0018);
			}
			elsif (!$exit_code){
				chomp $results[0];
				# pg_xlog format : ~/pg_xlog
				$results[0] =~ s/\/pg_xlog$//;
				$pg_xlog_dir = $results[0];
				exec_command("$SU - $pg_command_user -c \"$RM -rf $pg_xlog_dir/pg_xlog\"");
				$symbolic_link = 1;
			}

			exec_command("$SU - $pg_command_user -c \" $command_path{'pg_basebackup'} -h $config_value{'Another_D-LAN_IPAddress'} -U $master_cib_value{'repuser'} -D $master_cib_value{'pgdata'} $config_value{'PG_BASEBACKUP_opt'} -P \"");

        # pg_xlog のシンボリックリンクを作成
			if ($symbolic_link){
				exec_command("$SU - $pg_command_user -c \"$MV $master_cib_value{'pgdata'}/pg_xlog $pg_xlog_dir \"");
				exec_command("$SU - $pg_command_user -c \"$LN -s $pg_xlog_dir/pg_xlog $master_cib_value{'pgdata'}/pg_xlog \"");
			}
		}
        printlog("LOG", SLAVESTART_MS0007);

	} else {
		## BUG ##
		# 起動できる選択肢がないとして処理が終わっているはずなので、
		# ここに来るのはバグ以外ない
		printlog("ERROR", SLAVESTART_MS0099);
	}


	### Master のアーカイブディレクトリと同期 ###
	# su コマンドとリモート先を指定する rsync コマンドを併用すると
	# マシンによっては実行できないため、ここでは root ユーザで実行する
	# shared-archive-directory オプションが指定されている場合は実行しない
	if (!$shared_archive_directory_mode) {
		printlog("LOG", SLAVESTART_MS0019, $operation_num++);
		if (!$dry_run_mode) {
			rsync_exec_command($ssh_info, $config_value{'Archive_dir'}."/",
							   $config_value{'Archive_dir'}."/");
		}
		printlog("LOG", SLAVESTART_MS0007);
	}

    ### /var/lib/pacemaker/cib 配下のファイルの削除 ###
    my $cib_dir = CIB_PATH;
    $cib_dir =~ s/\/$//g;
    printlog("LOG", SLAVESTART_MS0020, $operation_num++, $cib_dir);
    $cib_dir = CIB_PATH;
	if (!$dry_run_mode) {
		exec_command("$RM -f $cib_dir"."*");
	}
    printlog("LOG", SLAVESTART_MS0007);

    ### アーカイブリカバリ対象の WAL セグメント数を算出
    # backup_label が存在する場合は backup_label からリカバリが開始される WAL ファイル名を取得する
    my $start_wal_filename;
    my $backup_label_path = $master_cib_value{'pgdata'}."/backup_label";
    if (-f $backup_label_path){
        $result = `$CAT $backup_label_path`;
        my @backup_label_string = split(/\n/, $result);
        $start_wal_filename = get_start_wal_filename($backup_label_path, @backup_label_string);
    }
    # backup_label が存在しない場合は pg_control から取得する
    else {
        $start_wal_filename = $my_data->controldata("Latest checkpoint's REDO WAL file");
    }

    my $wal_segment_num = get_restore_archivewal_num($master_cib_value{'pgdata'}, $config_value{'Archive_dir'}, $start_wal_filename);

    ### Pacemaker 起動 ###
    printlog("LOG", SLAVESTART_MS0021, $operation_num++, $wal_segment_num);
	if (!$dry_run_mode) {
		exec_command("$PMSTART");
	}
    printlog("LOG", SLAVESTART_MS0007);

    ### Slave の起動確認 ###
    printlog("LOG", SLAVESTART_MS0022, $operation_num++);

    # Pacemaker 及び リソースの起動確認
    # 前回起動中に自ノードで PostgreSQL の制御エラーが発生していた場合、Pacemaker 起動から暫くの間は
    # crm_monの結果に PostgreSQL の制御エラー情報(Failed actions)が残っている可能性がある。そのため、
    # 起動確認の前にRA(pgsql) のモニタ間隔以上のディレイをおき、誤検知しないようにする。
    my $error_code = 0;
	if (!$dry_run_mode) {
		sleep $monitor_delay;
		while (1){
			if ($wait_time >= $timeout){
				printlog("LOG", SLAVESTART_MS0005);
				printlog("LOG", SLAVESTART_MS0046, $timeout);
				if ($starting_resource eq "PostgreSQL" && $error_code == 2){
					printlog("ERROR", SLAVESTART_MS0047, $starting_resource);
				}
				else {
					printlog("ERROR", SLAVESTART_MS0048, $starting_resource);
				}
			}
			sleep $monitor_interval;
			$wait_time += $monitor_interval;

			if (!pacemaker_online($node_value{'my_node'})){
				$starting_resource = "Pacemaker";
				next;
			}

			if (pgrex_failed_action($node_value{'my_node'}, $config_value{'PG-REX_Primitive_ResourceID'})){
				printlog("LOG", SLAVESTART_MS0005);
				printlog("ERROR", SLAVESTART_MS0024);
			}

			if (!network_running($node_value{'my_node'}, $node_value{'another_node'}, \%iclan_ipaddr)){
				$starting_resource = "IC-LAN monitor";
				next;
			}

			# PING のリソース ID 指定有りの場合、起動確認を行なう
			if ($config_value{'PING_ResourceID'} && !ping_running($node_value{'my_node'}, $config_value{'PING_ResourceID'})){
				$starting_resource = "PING";
				next;
			}

			# DISKD のリソース ID 指定有りの場合、起動確認を行なう
			if ($config_value{'DISKD_ResourceID'} && !diskd_running($node_value{'my_node'}, $config_value{'DISKD_ResourceID'})){
				$starting_resource = "DISKD";
				next;
			}

			# STONITH 環境有りかつリソース ID 指定有りの場合、起動確認を行なう
			if ($config_value{'STONITH_ResourceID'} && !stonith_running($node_value{'my_node'}, $config_value{'STONITH_ResourceID'})){
				$starting_resource = "STONITH";
				next;
			}

			if (!slave_running($node_value{'my_node'}, $config_value{'PG-REX_Master_ResourceID'}, $config_value{'PG-REX_Primitive_ResourceID'}, \$error_code)){
				$starting_resource = "PostgreSQL";
				next;
			}

			# VIP-SLAVE 環境有りかつリソース ID 指定有りの場合、起動確認を行なう
			if ($config_value{'VIP-SLAVE_ResourceID'} && !vip_running($node_value{'my_node'}, $config_value{'VIP-SLAVE_ResourceID'})){
				$starting_resource = "VIP-SLAVE";
				next;
			}

			# crm_mon の結果が全て揃ったら無限ループを抜ける
			last;
		}
	}

    printlog("LOG", SLAVESTART_MS0007);
    printlog("LOG", SLAVESTART_MS0029, $node_value{'my_node'});
    exit(0);
}

sub ng_result {

    printlog("LOG", SLAVESTART_MS0005);
    printlog("ERROR", SLAVESTART_MS0045);

}

sub get_history_value {
	my ($history_file, $ssh_info) = @_;
	my $ext;
	my @results;
	my $result;
	my @ret;

	($ext=$history_file) =~ s/^.*\.(.*)$/$1/;
	if ($ext eq "history"){ # 圧縮なし
		if ($ssh_info) {
			@results = ssh_exec_command($ssh_info, "$CAT $history_file");
			@ret = split(/\n/, $results[0]);
		} else {
			$result = exec_command("$CAT $history_file");
			@ret = split(/\n/, $result);
		}
	}
	elsif ($ext eq "gz"){ # GZIP圧縮
		if ($ssh_info) {
			@results = ssh_exec_command($ssh_info, "$GZIP -dc $history_file");
			@ret = split(/\n/, $results[0]);
		} else {
			$result = exec_command("$GZIP -dc $history_file");
			@ret = split(/\n/, $result);
		}
	}
	elsif ($ext eq "bz2"){ # BZIP2圧縮
		if ($ssh_info) {
			@results = ssh_exec_command($ssh_info, "$BZIP2 -dc $history_file");
			@ret = split(/\n/, $results[0]);
		} else {
			$result = exec_command("$BZIP2 -dc $history_file");
			@ret = split(/\n/, $result);
		}
	}
	else{  # 未サポートの拡張子
		printlog("ERROR", SLAVESTART_MS0049, $ext);
	}

	return @ret;
}

sub common_cluster_check {
	my ($check_result, $pg_dir_state, $my_data, $another_data) = @_;
	my $is_ready = 1;

	# (STEP 2)DB クラスタのバージョンと PostgreSQL サーバのバージョンが一致していることを確認 B)
	if (!$check_result->version_is_match) {
		if (!$my_data->cluster_version) {
			printlog("LOG", SLAVESTART_MS0035);
		} else {
			printlog("LOG", SLAVESTART_MS0036, $my_data->cluster_version,
					 $my_data->pg_version);
		}
		$is_ready = 0;
	}

	# (STEP 3)自身のノードと相手のノードのデータベース識別子が一致していることを確認 K)
	if (!$check_result->match_database_id) {
		printlog("LOG", SLAVESTART_MS0037,
				 $my_data->controldata("Database system identifier"),
				 $another_data->controldata("Database system identifier"));
		$is_ready = 0;
	}

	# (STEP 4)自身のノードの TLI が相手のノードを超過していないことを確認 L)
	if ($check_result->gt_another_tli) {
		printlog("LOG", SLAVESTART_MS0038,
				 $my_data->controldata("Latest checkpoint's TimeLineID"),
				 $another_data->controldata("Latest checkpoint's TimeLineID"));
		$is_ready = 0;
	}

	# (STEP7)自身ノードの TLI が過去に通っていることを確認 I)
	if (!$check_result->pass_my_tli) {
		printlog("LOG", SLAVESTART_MS0043, $another_data->history_file);
		$is_ready = 0;
	}

	# TLIのhistoryファイルを確認し、分岐点が一致していることを確認 H)
	if (!$check_result->same_branch_point && !$pg_dir_state->pgarch_empty) {
		printlog("LOG", SLAVESTART_MS0053, $my_data->latest_branch_tli);
		$is_ready = 0;
	}

	# postgresql.confに"# added by pg-rex_slave_start"がないことを確認
	if ($check_result->is_conf_add_comment) {
		printlog("LOG", SLAVESTART_MS0075);
		$is_ready = 0;
	}

	# 相手のノードより後のセグメントファイルがアーカイブに存在するか確認
	if ($check_result->my_archive_is_over) {
		printlog("LOG", SLAVESTART_MS0076);
		$is_ready = 0;
	}

	return $is_ready;
}

sub copy_wal_from_archive {
	my ($start_lsn, $archive_dir, $xlog_dir, $pg_command_user) = @_;

    if ($start_lsn !~ /^([0-9A-F]+)\/([0-9A-F]+)$/)
    {
		printlog("ERROR", "Failure: [0]\n", $start_lsn);
    }

    my ($s_tli, $s_logid, $s_segno) = ($1, hex($1), hex($2) >> 24);
    my $s_lsn = ($s_logid << 32) + ($s_segno << 24);

    my $lastxlogname = "";
    my $firstxlogname;
    my $basename;

    opendir(my $dir, $archive_dir) || die "Failed to open archive directory: $archive_dir";
    foreach my $file (sort readdir($dir)) {
		next if ($file !~ /^(([0-9A-F]{8})([0-9A-F]{8})([0-9A-F]{8}))(\.partial)?(?:\.(.+))?$/);
		my ($f_basename, $f_tli, $f_logid, $f_segno, $f_partial, $f_suffix) =
			($1, hex($2), hex($3), hex($4), defined $5, $6);
		my $f_lsn = ($f_logid << 32) + ($f_segno << 24);

		next if ($f_lsn < $s_lsn);
		my $xlogfilename = "$xlog_dir/$f_basename";
		my $archivefilename = "$archive_dir/$file";
		my $command;
		my $result;

		if (!defined $f_suffix) {
			$command = "$CP $archivefilename $xlogfilename";
		} elsif ($f_suffix eq 'gz') { # GZIP圧縮
			$command = "$GZIP -dc $archivefilename > $xlogfilename";
		} elsif ($f_suffix eq 'bz2') { # BZIP2圧縮
			$command = "$BZIP2 -dc $archivefilename > $xlogfilename";
		} else {
			printlog("ERROR", SLAVESTART_MS0049, $f_suffix);
		}

		$result = exec_command($command);
		$command = "chown $pg_command_user $xlogfilename";
		$result = exec_command($command);

		$firstxlogname = $f_basename if (!defined $firstxlogname);
		$lastxlogname = $f_basename;

		printlog("DEBUG", "COPY: [0]\n", $f_basename);
    }
    closedir($dir);
	printlog("LOG", "Restored archives: [0] - [1]\n",
			 $firstxlogname, $lastxlogname);

}

# 最新のLSNを取得する
sub getLatestLsn {
	my ($latest_checkpoint_walfile_name, $latest_walfile_name, $pg_xlog_dir, $pg_xlogdump_path, $pg_command_user) = @_;

	# WALファイル名からTLIを取得
	my $latest_checkpoint_tli = substr($latest_checkpoint_walfile_name,0,8);
	my $latest_tli = substr($latest_walfile_name,0,8);

	# pg_controlのTLIが最新のTLIより小さい場合、
	# pg_xlogdumpの読み取り開始ファイルを最新のTLIのpg_xlogに存在する最古のWALセグメントファイルにする
	if ($latest_checkpoint_tli lt $latest_tli){
		my @walfile_list = ();

		# 最新のTLIのpg_xlogに存在する最古のWALセグメントファイルの取得
		opendir(my $dir, $pg_xlog_dir) || die "Failed to open pg_xlog directory: $pg_xlog_dir";
		foreach my $walfile (grep { $_ =~ /^$latest_tli[0-9A-F]{16}$/ } readdir($dir)) {
			push(@walfile_list, $walfile);
			next;
		}
		closedir($dir);

		if(@walfile_list){
			@walfile_list = sort @walfile_list;
			$latest_checkpoint_walfile_name = $walfile_list[0];
		} else {
			# pg_xlogにWALセグメントファイルが存在しない
			printlog("ERROR", SLAVESTART_MS0099);
		}
	}

	my $xlogdump_walfilepath_start = "$pg_xlog_dir/$latest_checkpoint_walfile_name";
	my $xlogdump_walfilepath_end   = "$pg_xlog_dir/$latest_walfile_name";

	my $command = "$SU - $pg_command_user -c \"$pg_xlogdump_path $xlogdump_walfilepath_start $xlogdump_walfilepath_end 2> /dev/null | $TAIL -1\"";

	my $result = exec_command($command);
	if ($result =~ /.*lsn:\s+([0-9A-F\/]+).*/){
		return $1;
	} else {
		# pg_xlogdumpで最新のLSNの取得に失敗
		printlog("ERROR", SLAVESTART_MS0099);
	}
}
