You've already forked pgbackrest
mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2025-06-18 23:57:33 +02:00
Prior to this the Perl remote was used to satisfy C requests. This worked fine but since the remote needed to be migrated to C anyway there was no reason to wait. Add the ProtocolServer object and tweak ProtocolClient to work with it. It was also necessary to add a mechanism to get option values from the remote so that encryption settings could be read and used in the storage object. Update the remote storage objects to comply with the protocol changes and add the storage protocol handler. Ideally this commit would have been broken up into smaller chunks but there are cross-dependencies in the protocol layer and it didn't seem worth the extra effort.
322 lines
12 KiB
C
322 lines
12 KiB
C
/***********************************************************************************************************************************
|
|
Protocol Client
|
|
***********************************************************************************************************************************/
|
|
#include "common/debug.h"
|
|
#include "common/log.h"
|
|
#include "common/memContext.h"
|
|
#include "common/time.h"
|
|
#include "common/type/json.h"
|
|
#include "common/type/keyValue.h"
|
|
#include "protocol/client.h"
|
|
#include "version.h"
|
|
|
|
/***********************************************************************************************************************************
|
|
Constants
|
|
***********************************************************************************************************************************/
|
|
STRING_EXTERN(PROTOCOL_GREETING_NAME_STR, PROTOCOL_GREETING_NAME);
|
|
STRING_EXTERN(PROTOCOL_GREETING_SERVICE_STR, PROTOCOL_GREETING_SERVICE);
|
|
STRING_EXTERN(PROTOCOL_GREETING_VERSION_STR, PROTOCOL_GREETING_VERSION);
|
|
|
|
STRING_EXTERN(PROTOCOL_COMMAND_STR, PROTOCOL_COMMAND);
|
|
STRING_EXTERN(PROTOCOL_COMMAND_NOOP_STR, PROTOCOL_COMMAND_NOOP);
|
|
STRING_EXTERN(PROTOCOL_COMMAND_EXIT_STR, PROTOCOL_COMMAND_EXIT);
|
|
|
|
STRING_EXTERN(PROTOCOL_ERROR_STR, PROTOCOL_ERROR);
|
|
|
|
STRING_EXTERN(PROTOCOL_OUTPUT_STR, PROTOCOL_OUTPUT);
|
|
|
|
STRING_EXTERN(PROTOCOL_PARAMETER_STR, PROTOCOL_PARAMETER);
|
|
|
|
/***********************************************************************************************************************************
|
|
Object type
|
|
***********************************************************************************************************************************/
|
|
struct ProtocolClient
|
|
{
|
|
MemContext *memContext;
|
|
const String *name;
|
|
const String *errorPrefix;
|
|
IoRead *read;
|
|
IoWrite *write;
|
|
TimeMSec keepAliveTime;
|
|
};
|
|
|
|
/***********************************************************************************************************************************
|
|
Create object
|
|
***********************************************************************************************************************************/
|
|
ProtocolClient *
|
|
protocolClientNew(const String *name, const String *service, IoRead *read, IoWrite *write)
|
|
{
|
|
FUNCTION_LOG_BEGIN(logLevelTrace);
|
|
FUNCTION_LOG_PARAM(STRING, name);
|
|
FUNCTION_LOG_PARAM(STRING, service);
|
|
FUNCTION_LOG_PARAM(IO_READ, read);
|
|
FUNCTION_LOG_PARAM(IO_WRITE, write);
|
|
FUNCTION_LOG_END();
|
|
|
|
ASSERT(name != NULL);
|
|
ASSERT(read != NULL);
|
|
ASSERT(write != NULL);
|
|
|
|
ProtocolClient *this = NULL;
|
|
|
|
MEM_CONTEXT_NEW_BEGIN("ProtocolClient")
|
|
{
|
|
this = memNew(sizeof(ProtocolClient));
|
|
this->memContext = memContextCurrent();
|
|
|
|
this->name = strDup(name);
|
|
this->errorPrefix = strNewFmt("raised from %s", strPtr(this->name));
|
|
this->read = read;
|
|
this->write = write;
|
|
this->keepAliveTime = timeMSec();
|
|
|
|
// Read, parse, and check the protocol greeting
|
|
MEM_CONTEXT_TEMP_BEGIN()
|
|
{
|
|
String *greeting = ioReadLine(this->read);
|
|
KeyValue *greetingKv = varKv(jsonToVar(greeting));
|
|
|
|
const String *expected[] =
|
|
{
|
|
PROTOCOL_GREETING_NAME_STR, strNew(PROJECT_NAME),
|
|
PROTOCOL_GREETING_SERVICE_STR, service,
|
|
PROTOCOL_GREETING_VERSION_STR, strNew(PROJECT_VERSION),
|
|
};
|
|
|
|
for (unsigned int expectedIdx = 0; expectedIdx < sizeof(expected) / sizeof(char *) / 2; expectedIdx++)
|
|
{
|
|
const String *expectedKey = expected[expectedIdx * 2];
|
|
const String *expectedValue = expected[expectedIdx * 2 + 1];
|
|
|
|
const Variant *actualValue = kvGet(greetingKv, varNewStr(expectedKey));
|
|
|
|
if (actualValue == NULL)
|
|
THROW_FMT(ProtocolError, "unable to find greeting key '%s'", strPtr(expectedKey));
|
|
|
|
if (varType(actualValue) != varTypeString)
|
|
THROW_FMT(ProtocolError, "greeting key '%s' must be string type", strPtr(expectedKey));
|
|
|
|
if (!strEq(varStr(actualValue), expectedValue))
|
|
{
|
|
THROW_FMT(
|
|
ProtocolError, "expected value '%s' for greeting key '%s' but got '%s'", strPtr(expectedValue),
|
|
strPtr(expectedKey), strPtr(varStr(actualValue)));
|
|
}
|
|
}
|
|
}
|
|
MEM_CONTEXT_TEMP_END();
|
|
|
|
// Send one noop to catch any errors that might happen after the greeting
|
|
protocolClientNoOp(this);
|
|
|
|
// Set a callback to shutdown the protocol
|
|
memContextCallback(this->memContext, (MemContextCallback)protocolClientFree, this);
|
|
}
|
|
MEM_CONTEXT_NEW_END();
|
|
|
|
FUNCTION_LOG_RETURN(PROTOCOL_CLIENT, this);
|
|
}
|
|
|
|
/***********************************************************************************************************************************
|
|
Read the command output
|
|
***********************************************************************************************************************************/
|
|
const Variant *
|
|
protocolClientReadOutput(ProtocolClient *this, bool outputRequired)
|
|
{
|
|
FUNCTION_LOG_BEGIN(logLevelTrace);
|
|
FUNCTION_LOG_PARAM(PROTOCOL_CLIENT, this);
|
|
FUNCTION_LOG_PARAM(BOOL, outputRequired);
|
|
FUNCTION_LOG_END();
|
|
|
|
ASSERT(this != NULL);
|
|
|
|
const Variant *result = NULL;
|
|
|
|
MEM_CONTEXT_TEMP_BEGIN()
|
|
{
|
|
// Read the response
|
|
String *response = ioReadLine(this->read);
|
|
KeyValue *responseKv = varKv(jsonToVar(response));
|
|
|
|
// Process error if any
|
|
const Variant *error = kvGet(responseKv, varNewStr(PROTOCOL_ERROR_STR));
|
|
|
|
if (error != NULL)
|
|
{
|
|
const String *message = varStr(kvGet(responseKv, varNewStr(PROTOCOL_OUTPUT_STR)));
|
|
|
|
THROWP_FMT(
|
|
errorTypeFromCode(varIntForce(error)), "%s: %s", strPtr(this->errorPrefix),
|
|
message == NULL ? "no details available" : strPtr(message));
|
|
}
|
|
|
|
// Get output
|
|
result = kvGet(responseKv, varNewStr(PROTOCOL_OUTPUT_STR));
|
|
|
|
if (outputRequired)
|
|
{
|
|
// Just move the entire response kv since the output is the largest part if it
|
|
kvMove(responseKv, MEM_CONTEXT_OLD());
|
|
}
|
|
// Else if no output is required then there should not be any
|
|
else if (result != NULL)
|
|
THROW(AssertError, "no output required by command");
|
|
|
|
// Reset the keep alive time
|
|
this->keepAliveTime = timeMSec();
|
|
}
|
|
MEM_CONTEXT_TEMP_END();
|
|
|
|
FUNCTION_LOG_RETURN_CONST(VARIANT, result);
|
|
}
|
|
|
|
/***********************************************************************************************************************************
|
|
Write the protocol command
|
|
***********************************************************************************************************************************/
|
|
void
|
|
protocolClientWriteCommand(ProtocolClient *this, const KeyValue *command)
|
|
{
|
|
FUNCTION_LOG_BEGIN(logLevelTrace);
|
|
FUNCTION_LOG_PARAM(PROTOCOL_CLIENT, this);
|
|
FUNCTION_LOG_PARAM(KEY_VALUE, command);
|
|
FUNCTION_LOG_END();
|
|
|
|
ASSERT(this != NULL);
|
|
ASSERT(command != NULL);
|
|
|
|
// Write out the command
|
|
ioWriteLine(this->write, kvToJson(command, 0));
|
|
ioWriteFlush(this->write);
|
|
|
|
// Reset the keep alive time
|
|
this->keepAliveTime = timeMSec();
|
|
|
|
FUNCTION_LOG_RETURN_VOID();
|
|
}
|
|
|
|
/***********************************************************************************************************************************
|
|
Execute a protocol command and get the output
|
|
***********************************************************************************************************************************/
|
|
const Variant *
|
|
protocolClientExecute(ProtocolClient *this, const KeyValue *command, bool outputRequired)
|
|
{
|
|
FUNCTION_LOG_BEGIN(logLevelTrace);
|
|
FUNCTION_LOG_PARAM(PROTOCOL_CLIENT, this);
|
|
FUNCTION_LOG_PARAM(KEY_VALUE, command);
|
|
FUNCTION_LOG_PARAM(BOOL, outputRequired);
|
|
FUNCTION_LOG_END();
|
|
|
|
ASSERT(this != NULL);
|
|
ASSERT(command != NULL);
|
|
|
|
protocolClientWriteCommand(this, command);
|
|
|
|
FUNCTION_LOG_RETURN_CONST(VARIANT, protocolClientReadOutput(this, outputRequired));
|
|
}
|
|
|
|
/***********************************************************************************************************************************
|
|
Move the file object to a new context
|
|
***********************************************************************************************************************************/
|
|
ProtocolClient *
|
|
protocolClientMove(ProtocolClient *this, MemContext *parentNew)
|
|
{
|
|
FUNCTION_TEST_BEGIN();
|
|
FUNCTION_TEST_PARAM(PROTOCOL_CLIENT, this);
|
|
FUNCTION_TEST_PARAM(MEM_CONTEXT, parentNew);
|
|
FUNCTION_TEST_END();
|
|
|
|
ASSERT(parentNew != NULL);
|
|
|
|
if (this != NULL)
|
|
memContextMove(this->memContext, parentNew);
|
|
|
|
FUNCTION_TEST_RETURN(this);
|
|
}
|
|
|
|
/***********************************************************************************************************************************
|
|
Send noop to test connection or keep it alive
|
|
***********************************************************************************************************************************/
|
|
void
|
|
protocolClientNoOp(ProtocolClient *this)
|
|
{
|
|
FUNCTION_LOG_BEGIN(logLevelTrace);
|
|
FUNCTION_LOG_PARAM(PROTOCOL_CLIENT, this);
|
|
FUNCTION_LOG_END();
|
|
|
|
ASSERT(this != NULL);
|
|
|
|
MEM_CONTEXT_TEMP_BEGIN()
|
|
{
|
|
protocolClientExecute(this, kvPut(kvNew(), varNewStr(PROTOCOL_COMMAND_STR), varNewStr(PROTOCOL_COMMAND_NOOP_STR)), false);
|
|
}
|
|
MEM_CONTEXT_TEMP_END();
|
|
|
|
FUNCTION_LOG_RETURN_VOID();
|
|
}
|
|
|
|
/***********************************************************************************************************************************
|
|
Get read interface
|
|
***********************************************************************************************************************************/
|
|
IoRead *
|
|
protocolClientIoRead(const ProtocolClient *this)
|
|
{
|
|
FUNCTION_TEST_BEGIN();
|
|
FUNCTION_TEST_PARAM(PROTOCOL_CLIENT, this);
|
|
FUNCTION_TEST_END();
|
|
|
|
ASSERT(this != NULL);
|
|
|
|
FUNCTION_TEST_RETURN(this->read);
|
|
}
|
|
|
|
/***********************************************************************************************************************************
|
|
Get write interface
|
|
***********************************************************************************************************************************/
|
|
IoWrite *
|
|
protocolClientIoWrite(const ProtocolClient *this)
|
|
{
|
|
FUNCTION_TEST_BEGIN();
|
|
FUNCTION_TEST_PARAM(PROTOCOL_CLIENT, this);
|
|
FUNCTION_TEST_END();
|
|
|
|
ASSERT(this != NULL);
|
|
|
|
FUNCTION_TEST_RETURN(this->write);
|
|
}
|
|
|
|
/***********************************************************************************************************************************
|
|
Render as string for logging
|
|
***********************************************************************************************************************************/
|
|
String *
|
|
protocolClientToLog(const ProtocolClient *this)
|
|
{
|
|
return strNewFmt("{name: %s}", strPtr(this->name));
|
|
}
|
|
|
|
/***********************************************************************************************************************************
|
|
Free the file
|
|
***********************************************************************************************************************************/
|
|
void
|
|
protocolClientFree(ProtocolClient *this)
|
|
{
|
|
FUNCTION_LOG_BEGIN(logLevelTrace);
|
|
FUNCTION_LOG_PARAM(PROTOCOL_CLIENT, this);
|
|
FUNCTION_LOG_END();
|
|
|
|
if (this != NULL)
|
|
{
|
|
memContextCallbackClear(this->memContext);
|
|
|
|
// Send an exit command but don't wait to see if it succeeds
|
|
MEM_CONTEXT_TEMP_BEGIN()
|
|
{
|
|
protocolClientWriteCommand(this, kvPut(kvNew(), varNewStr(PROTOCOL_COMMAND_STR), varNewStr(PROTOCOL_COMMAND_EXIT_STR)));
|
|
}
|
|
MEM_CONTEXT_TEMP_END();
|
|
|
|
memContextFree(this->memContext);
|
|
}
|
|
|
|
FUNCTION_LOG_RETURN_VOID();
|
|
}
|