readRecoveryCommandFile(void)
{
	FILE	   *fd;
	TimeLineID	rtli = 0;
	bool		rtliGiven = false;
	ConfigVariable *item,
			   *head = NULL,
			   *tail = NULL;

	fd = AllocateFile(RECOVERY_COMMAND_FILE, "r");
	if (fd == NULL)
	{
		if (errno == ENOENT)
			return;				/* not there, so no archive recovery */
		ereport(FATAL,
				(errcode_for_file_access(),
				 errmsg("could not open recovery command file \"%s\": %m",
						RECOVERY_COMMAND_FILE)));
	}

	/*
	 * Since we're asking ParseConfigFp() to report errors as FATAL, there's
	 * no need to check the return value.
	 */
	(void) ParseConfigFp(fd, RECOVERY_COMMAND_FILE, 0, FATAL, &head, &tail);

	FreeFile(fd);

	for (item = head; item; item = item->next)
	{
		if (strcmp(item->name, "restore_command") == 0)
		{
			recoveryRestoreCommand = pstrdup(item->value);
			ereport(DEBUG2,
					(errmsg_internal("restore_command = '%s'",
									 recoveryRestoreCommand)));
		}
		else if (strcmp(item->name, "recovery_end_command") == 0)
		{
			recoveryEndCommand = pstrdup(item->value);
			ereport(DEBUG2,
					(errmsg_internal("recovery_end_command = '%s'",
									 recoveryEndCommand)));
		}
		else if (strcmp(item->name, "archive_cleanup_command") == 0)
		{
			archiveCleanupCommand = pstrdup(item->value);
			ereport(DEBUG2,
					(errmsg_internal("archive_cleanup_command = '%s'",
									 archiveCleanupCommand)));
		}
		else if (strcmp(item->name, "pause_at_recovery_target") == 0)
		{
			if (!parse_bool(item->value, &recoveryPauseAtTarget))
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("parameter \"%s\" requires a Boolean value", "pause_at_recovery_target")));
			ereport(DEBUG2,
					(errmsg_internal("pause_at_recovery_target = '%s'",
									 item->value)));
		}
		else if (strcmp(item->name, "recovery_target_timeline") == 0)
		{
			rtliGiven = true;
			if (strcmp(item->value, "latest") == 0)
				rtli = 0;
			else
			{
				errno = 0;
				rtli = (TimeLineID) strtoul(item->value, NULL, 0);
				if (errno == EINVAL || errno == ERANGE)
					ereport(FATAL,
							(errmsg("recovery_target_timeline is not a valid number: \"%s\"",
									item->value)));
			}
			if (rtli)
				ereport(DEBUG2,
				   (errmsg_internal("recovery_target_timeline = %u", rtli)));
			else
				ereport(DEBUG2,
					 (errmsg_internal("recovery_target_timeline = latest")));
		}
		else if (strcmp(item->name, "recovery_target_xid") == 0)
		{
			errno = 0;
			recoveryTargetXid = (TransactionId) strtoul(item->value, NULL, 0);
			if (errno == EINVAL || errno == ERANGE)
				ereport(FATAL,
				 (errmsg("recovery_target_xid is not a valid number: \"%s\"",
						 item->value)));
			ereport(DEBUG2,
					(errmsg_internal("recovery_target_xid = %u",
									 recoveryTargetXid)));
			recoveryTarget = RECOVERY_TARGET_XID;
		}
		else if (strcmp(item->name, "recovery_target_time") == 0)
		{
			recoveryTarget = RECOVERY_TARGET_TIME;

			/*
			 * Convert the time string given by the user to TimestampTz form.
			 */
			recoveryTargetTime =
				DatumGetTimestampTz(DirectFunctionCall3(timestamptz_in,
												CStringGetDatum(item->value),
												ObjectIdGetDatum(InvalidOid),
														Int32GetDatum(-1)));
			ereport(DEBUG2,
					(errmsg_internal("recovery_target_time = '%s'",
								   timestamptz_to_str(recoveryTargetTime))));
		}
		else if (strcmp(item->name, "recovery_target_name") == 0)
		{
			recoveryTarget = RECOVERY_TARGET_NAME;

			recoveryTargetName = pstrdup(item->value);
			if (strlen(recoveryTargetName) >= MAXFNAMELEN)
				ereport(FATAL,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("recovery_target_name is too long (maximum %d characters)",
								MAXFNAMELEN - 1)));

			ereport(DEBUG2,
					(errmsg_internal("recovery_target_name = '%s'",
									 recoveryTargetName)));
		}
		else if (strcmp(item->name, "recovery_target") == 0)
		{
			if (strcmp(item->value, "immediate") == 0)
				recoveryTarget = RECOVERY_TARGET_IMMEDIATE;
			else
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("invalid recovery_target parameter"),
						 errhint("The only allowed value is 'immediate'")));
			ereport(DEBUG2,
					(errmsg_internal("recovery_target = '%s'",
									 item->value)));
		}
		else if (strcmp(item->name, "recovery_target_inclusive") == 0)
		{
			/*
			 * does nothing if a recovery_target is not also set
			 */
			if (!parse_bool(item->value, &recoveryTargetInclusive))
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("parameter \"%s\" requires a Boolean value",
								"recovery_target_inclusive")));
			ereport(DEBUG2,
					(errmsg_internal("recovery_target_inclusive = %s",
									 item->value)));
		}
		else if (strcmp(item->name, "standby_mode") == 0)
		{
			if (!parse_bool(item->value, &StandbyModeRequested))
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("parameter \"%s\" requires a Boolean value",
								"standby_mode")));
			ereport(DEBUG2,
					(errmsg_internal("standby_mode = '%s'", item->value)));
		}
		else if (strcmp(item->name, "primary_conninfo") == 0)
		{
			PrimaryConnInfo = pstrdup(item->value);
			ereport(DEBUG2,
					(errmsg_internal("primary_conninfo = '%s'",
									 PrimaryConnInfo)));
		}
		else if (strcmp(item->name, "primary_slotname") == 0)
		{
			ReplicationSlotValidateName(item->value, ERROR);
			PrimarySlotName = pstrdup(item->value);
			ereport(DEBUG2,
					(errmsg_internal("primary_slotname = '%s'",
									 PrimarySlotName)));
		}
		else if (strcmp(item->name, "trigger_file") == 0)
		{
			TriggerFile = pstrdup(item->value);
			ereport(DEBUG2,
					(errmsg_internal("trigger_file = '%s'",
									 TriggerFile)));
		}
		else if (strcmp(item->name, "min_recovery_apply_delay") == 0)
		{
			const char *hintmsg;

			if (!parse_int(item->value, &min_recovery_apply_delay, GUC_UNIT_MS,
					&hintmsg))
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("parameter \"%s\" requires a temporal value", "min_recovery_apply_delay"),
						 hintmsg ? errhint("%s", _(hintmsg)) : 0));
			ereport(DEBUG2,
					(errmsg("min_recovery_apply_delay = '%s'", item->value)));
		}
		else
			ereport(FATAL,
					(errmsg("unrecognized recovery parameter \"%s\"",
							item->name)));
	}

	/*
	 * Check for compulsory parameters
	 */
	if (StandbyModeRequested)
	{
		if (PrimaryConnInfo == NULL && recoveryRestoreCommand == NULL)
			ereport(WARNING,
					(errmsg("recovery command file \"%s\" specified neither primary_conninfo nor restore_command",
							RECOVERY_COMMAND_FILE),
					 errhint("The database server will regularly poll the pg_xlog subdirectory to check for files placed there.")));
	}
	else
	{
		if (recoveryRestoreCommand == NULL)
			ereport(FATAL,
					(errmsg("recovery command file \"%s\" must specify restore_command when standby mode is not enabled",
							RECOVERY_COMMAND_FILE)));
	}

	/* Enable fetching from archive recovery area */
	ArchiveRecoveryRequested = true;

	/*
	 * If user specified recovery_target_timeline, validate it or compute the
	 * "latest" value.	We can't do this until after we've gotten the restore
	 * command and set InArchiveRecovery, because we need to fetch timeline
	 * history files from the archive.
	 */
	if (rtliGiven)
	{
		if (rtli)
		{
			/* Timeline 1 does not have a history file, all else should */
			if (rtli != 1 && !existsTimeLineHistory(rtli))
				ereport(FATAL,
						(errmsg("recovery target timeline %u does not exist",
								rtli)));
			recoveryTargetTLI = rtli;
			recoveryTargetIsLatest = false;
		}
		else
		{
			/* We start the "latest" search from pg_control's timeline */
			recoveryTargetTLI = findNewestTimeLine(recoveryTargetTLI);
			recoveryTargetIsLatest = true;
		}
	}

	FreeConfigVariables(head);
}