1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-07-07 00:35:37 +02:00
Files
pgbackrest/src/protocol/client.c
David Steele d30ec9c9ae Replace OBJECT_DEFINE_MOVE() and OBJECT_DEFINE_FREE() with inlines.
Inline functions are more efficient and if they are not used are automatically omitted from the binary.

This also makes the implementation of these functions easier to find and removes the need for a declaration. That is, the complete implementation is located in the header rather than being spread between the header and C file.
2021-04-08 10:04:57 -04:00

367 lines
12 KiB
C

/***********************************************************************************************************************************
Protocol Client
***********************************************************************************************************************************/
#include "build.auto.h"
#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_NOOP_STR, PROTOCOL_COMMAND_NOOP);
STRING_EXTERN(PROTOCOL_COMMAND_EXIT_STR, PROTOCOL_COMMAND_EXIT);
STRING_EXTERN(PROTOCOL_ERROR_STR, PROTOCOL_ERROR);
STRING_EXTERN(PROTOCOL_ERROR_STACK_STR, PROTOCOL_ERROR_STACK);
STRING_EXTERN(PROTOCOL_OUTPUT_STR, PROTOCOL_OUTPUT);
/***********************************************************************************************************************************
Object type
***********************************************************************************************************************************/
struct ProtocolClient
{
MemContext *memContext;
const String *name;
const String *errorPrefix;
IoRead *read;
IoWrite *write;
TimeMSec keepAliveTime;
};
/***********************************************************************************************************************************
Close protocol connection
***********************************************************************************************************************************/
static void
protocolClientFreeResource(THIS_VOID)
{
THIS(ProtocolClient);
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(PROTOCOL_CLIENT, this);
FUNCTION_LOG_END();
ASSERT(this != NULL);
// Send an exit command but don't wait to see if it succeeds
MEM_CONTEXT_TEMP_BEGIN()
{
protocolClientWriteCommand(this, protocolCommandNew(PROTOCOL_COMMAND_EXIT_STR));
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN_VOID();
}
/**********************************************************************************************************************************/
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 = (ProtocolClient)
{
.memContext = memContextCurrent(),
.name = strDup(name),
.errorPrefix = strNewFmt("raised from %s", strZ(name)),
.read = read,
.write = write,
.keepAliveTime = timeMSec(),
};
// Read, parse, and check the protocol greeting
MEM_CONTEXT_TEMP_BEGIN()
{
String *greeting = ioReadLine(this->read);
KeyValue *greetingKv = jsonToKv(greeting);
const String *expected[] =
{
PROTOCOL_GREETING_NAME_STR, STRDEF(PROJECT_NAME),
PROTOCOL_GREETING_SERVICE_STR, service,
PROTOCOL_GREETING_VERSION_STR, STRDEF(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, VARSTR(expectedKey));
if (actualValue == NULL)
THROW_FMT(ProtocolError, "unable to find greeting key '%s'", strZ(expectedKey));
if (varType(actualValue) != varTypeString)
THROW_FMT(ProtocolError, "greeting key '%s' must be string type", strZ(expectedKey));
if (!strEq(varStr(actualValue), expectedValue))
{
THROW_FMT(
ProtocolError,
"expected value '%s' for greeting key '%s' but got '%s'\n"
"HINT: is the same version of " PROJECT_NAME " installed on the local and remote host?",
strZ(expectedValue), strZ(expectedKey), strZ(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
memContextCallbackSet(this->memContext, protocolClientFreeResource, this);
}
MEM_CONTEXT_NEW_END();
FUNCTION_LOG_RETURN(PROTOCOL_CLIENT, this);
}
/**********************************************************************************************************************************/
// Helper to process errors
static void
protocolClientProcessError(ProtocolClient *this, KeyValue *errorKv)
{
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(PROTOCOL_CLIENT, this);
FUNCTION_LOG_PARAM(KEY_VALUE, errorKv);
FUNCTION_LOG_END();
ASSERT(this != NULL);
ASSERT(errorKv != NULL);
MEM_CONTEXT_TEMP_BEGIN()
{
// Process error if any
const Variant *error = kvGet(errorKv, VARSTR(PROTOCOL_ERROR_STR));
if (error != NULL)
{
const ErrorType *type = errorTypeFromCode(varIntForce(error));
const String *message = varStr(kvGet(errorKv, VARSTR(PROTOCOL_OUTPUT_STR)));
// Required part of the message
String *throwMessage = strNewFmt(
"%s: %s", strZ(this->errorPrefix), message == NULL ? "no details available" : strZ(message));
// Add stack trace if the error is an assertion or debug-level logging is enabled
if (type == &AssertError || logAny(logLevelDebug))
{
const String *stack = varStr(kvGet(errorKv, VARSTR(PROTOCOL_ERROR_STACK_STR)));
strCat(throwMessage, LF_STR);
strCat(throwMessage, stack == NULL ? STRDEF("no stack trace available") : stack);
}
THROWP(type, strZ(throwMessage));
}
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN_VOID();
}
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
protocolClientProcessError(this, responseKv);
// Get output
result = kvGet(responseKv, VARSTR(PROTOCOL_OUTPUT_STR));
if (outputRequired)
{
// Just move the entire response kv since the output is the largest part if it
kvMove(responseKv, memContextPrior());
}
// 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);
}
/**********************************************************************************************************************************/
void
protocolClientWriteCommand(ProtocolClient *this, const ProtocolCommand *command)
{
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(PROTOCOL_CLIENT, this);
FUNCTION_LOG_PARAM(PROTOCOL_COMMAND, command);
FUNCTION_LOG_END();
ASSERT(this != NULL);
ASSERT(command != NULL);
// Write out the command
ioWriteStrLine(this->write, protocolCommandJson(command));
ioWriteFlush(this->write);
// Reset the keep alive time
this->keepAliveTime = timeMSec();
FUNCTION_LOG_RETURN_VOID();
}
/**********************************************************************************************************************************/
const Variant *
protocolClientExecute(ProtocolClient *this, const ProtocolCommand *command, bool outputRequired)
{
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(PROTOCOL_CLIENT, this);
FUNCTION_LOG_PARAM(PROTOCOL_COMMAND, 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));
}
/**********************************************************************************************************************************/
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, protocolCommandNew(PROTOCOL_COMMAND_NOOP_STR), false);
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN_VOID();
}
/**********************************************************************************************************************************/
String *
protocolClientReadLine(ProtocolClient *this)
{
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(PROTOCOL_CLIENT, this);
FUNCTION_LOG_END();
ASSERT(this != NULL);
String *result = NULL;
MEM_CONTEXT_TEMP_BEGIN()
{
result = ioReadLine(this->read);
if (strSize(result) == 0)
{
THROW(FormatError, "unexpected empty line");
}
else if (strZ(result)[0] == '{')
{
KeyValue *responseKv = varKv(jsonToVar(result));
// Process expected error
protocolClientProcessError(this, responseKv);
// If not an error then there is probably a protocol bug
THROW(FormatError, "expected error but got output");
}
else if (strZ(result)[0] != '.')
THROW_FMT(FormatError, "invalid prefix in '%s'", strZ(result));
MEM_CONTEXT_PRIOR_BEGIN()
{
result = strSub(result, 1);
}
MEM_CONTEXT_PRIOR_END();
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN(STRING, result);
}
/**********************************************************************************************************************************/
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);
}
/**********************************************************************************************************************************/
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);
}
/**********************************************************************************************************************************/
String *
protocolClientToLog(const ProtocolClient *this)
{
return strNewFmt("{name: %s}", strZ(this->name));
}