1 /***********************************************************************************************************************************
2 Protocol Helper
3 ***********************************************************************************************************************************/
4 #include "build.auto.h"
5 
6 #include <string.h>
7 
8 #include "common/crypto/common.h"
9 #include "common/debug.h"
10 #include "common/exec.h"
11 #include "common/memContext.h"
12 #include "config/config.intern.h"
13 #include "config/exec.h"
14 #include "config/parse.h"
15 #include "config/protocol.h"
16 #include "postgres/version.h"
17 #include "protocol/helper.h"
18 #include "version.h"
19 
20 /***********************************************************************************************************************************
21 Constants
22 ***********************************************************************************************************************************/
23 STRING_EXTERN(PROTOCOL_SERVICE_LOCAL_STR,                           PROTOCOL_SERVICE_LOCAL);
24 STRING_EXTERN(PROTOCOL_SERVICE_REMOTE_STR,                          PROTOCOL_SERVICE_REMOTE);
25 
26 /***********************************************************************************************************************************
27 Local variables
28 ***********************************************************************************************************************************/
29 typedef struct ProtocolHelperClient
30 {
31     Exec *exec;                                                     // Executed client
32     ProtocolClient *client;                                         // Protocol client
33 } ProtocolHelperClient;
34 
35 static struct
36 {
37     MemContext *memContext;                                         // Mem context for protocol helper
38 
39     unsigned int clientRemoteSize;                                  // Remote clients
40     ProtocolHelperClient *clientRemote;
41 
42     unsigned int clientLocalSize;                                   // Local clients
43     ProtocolHelperClient *clientLocal;
44 } protocolHelper;
45 
46 /***********************************************************************************************************************************
47 Init local mem context and data structure
48 ***********************************************************************************************************************************/
49 static void
protocolHelperInit(void)50 protocolHelperInit(void)
51 {
52     // In the protocol helper has not been initialized
53     if (protocolHelper.memContext == NULL)
54     {
55         // Create a mem context to store protocol objects
56         MEM_CONTEXT_BEGIN(memContextTop())
57         {
58             MEM_CONTEXT_NEW_BEGIN("ProtocolHelper")
59             {
60                 protocolHelper.memContext = MEM_CONTEXT_NEW();
61             }
62             MEM_CONTEXT_NEW_END();
63         }
64         MEM_CONTEXT_END();
65     }
66 }
67 
68 /**********************************************************************************************************************************/
69 bool
repoIsLocal(unsigned int repoIdx)70 repoIsLocal(unsigned int repoIdx)
71 {
72     FUNCTION_LOG_BEGIN(logLevelDebug);
73         FUNCTION_LOG_PARAM(UINT, repoIdx);
74     FUNCTION_LOG_END();
75 
76     FUNCTION_LOG_RETURN(BOOL, !cfgOptionIdxTest(cfgOptRepoHost, repoIdx));
77 }
78 
79 /**********************************************************************************************************************************/
80 void
repoIsLocalVerify(void)81 repoIsLocalVerify(void)
82 {
83     FUNCTION_TEST_VOID();
84 
85     repoIsLocalVerifyIdx(cfgOptionGroupIdxDefault(cfgOptGrpRepo));
86 
87     FUNCTION_TEST_RETURN_VOID();
88 }
89 
90 /**********************************************************************************************************************************/
91 void
repoIsLocalVerifyIdx(unsigned int repoIdx)92 repoIsLocalVerifyIdx(unsigned int repoIdx)
93 {
94     FUNCTION_TEST_VOID();
95 
96     if (!repoIsLocal(repoIdx))
97         THROW_FMT(HostInvalidError, "%s command must be run on the repository host", cfgCommandName());
98 
99     FUNCTION_TEST_RETURN_VOID();
100 }
101 
102 /**********************************************************************************************************************************/
103 bool
pgIsLocal(unsigned int pgIdx)104 pgIsLocal(unsigned int pgIdx)
105 {
106     FUNCTION_LOG_BEGIN(logLevelDebug);
107         FUNCTION_LOG_PARAM(UINT, pgIdx);
108     FUNCTION_LOG_END();
109 
110     FUNCTION_LOG_RETURN(BOOL, !cfgOptionIdxTest(cfgOptPgHost, pgIdx));
111 }
112 
113 /**********************************************************************************************************************************/
114 void
pgIsLocalVerify(void)115 pgIsLocalVerify(void)
116 {
117     FUNCTION_TEST_VOID();
118 
119     if (!pgIsLocal(cfgOptionGroupIdxDefault(cfgOptGrpPg)))
120         THROW_FMT(HostInvalidError, "%s command must be run on the " PG_NAME " host", cfgCommandName());
121 
122     FUNCTION_TEST_RETURN_VOID();
123 }
124 
125 /***********************************************************************************************************************************
126 Get the command line required for local protocol execution
127 ***********************************************************************************************************************************/
128 static StringList *
protocolLocalParam(ProtocolStorageType protocolStorageType,unsigned int hostIdx,unsigned int processId)129 protocolLocalParam(ProtocolStorageType protocolStorageType, unsigned int hostIdx, unsigned int processId)
130 {
131     FUNCTION_LOG_BEGIN(logLevelDebug);
132         FUNCTION_LOG_PARAM(STRING_ID, protocolStorageType);
133         FUNCTION_LOG_PARAM(UINT, hostIdx);
134         FUNCTION_LOG_PARAM(UINT, processId);
135     FUNCTION_LOG_END();
136 
137     StringList *result = NULL;
138 
139     MEM_CONTEXT_TEMP_BEGIN()
140     {
141         // Option replacements
142         KeyValue *optionReplace = kvNew();
143 
144         // Add the process id -- used when more than one process will be called
145         kvPut(optionReplace, VARSTRDEF(CFGOPT_PROCESS), VARUINT(processId));
146 
147         // Add the pg default. Don't do this for repos because the repo default should come from the user or the local should
148         // handle all the repos equally. Repos don't get special handling like pg primaries or standbys.
149         if (protocolStorageType == protocolStorageTypePg)
150             kvPut(optionReplace, VARSTRDEF(CFGOPT_PG), VARUINT(cfgOptionGroupIdxToKey(cfgOptGrpPg, hostIdx)));
151 
152         // Add the remote type
153         kvPut(optionReplace, VARSTRDEF(CFGOPT_REMOTE_TYPE), VARSTR(strIdToStr(protocolStorageType)));
154 
155         // Only enable file logging on the local when requested
156         kvPut(
157             optionReplace, VARSTRDEF(CFGOPT_LOG_LEVEL_FILE),
158             cfgOptionBool(cfgOptLogSubprocess) ? cfgOption(cfgOptLogLevelFile) : VARSTRDEF("off"));
159 
160         // Always output errors on stderr for debugging purposes
161         kvPut(optionReplace, VARSTRDEF(CFGOPT_LOG_LEVEL_STDERR), VARSTRDEF("error"));
162 
163         // Disable output to stdout since it is used by the protocol
164         kvPut(optionReplace, VARSTRDEF(CFGOPT_LOG_LEVEL_CONSOLE), VARSTRDEF("off"));
165 
166         result = strLstMove(cfgExecParam(cfgCommand(), cfgCmdRoleLocal, optionReplace, true, false), memContextPrior());
167     }
168     MEM_CONTEXT_TEMP_END();
169 
170     FUNCTION_LOG_RETURN(STRING_LIST, result);
171 }
172 
173 /**********************************************************************************************************************************/
174 // Helper to execute the local process. This is a separate function solely so that it can be shimmed during testing.
175 static void
protocolLocalExec(ProtocolHelperClient * helper,ProtocolStorageType protocolStorageType,unsigned int hostIdx,unsigned int processId)176 protocolLocalExec(
177     ProtocolHelperClient *helper, ProtocolStorageType protocolStorageType, unsigned int hostIdx, unsigned int processId)
178 {
179     FUNCTION_TEST_BEGIN();
180         FUNCTION_TEST_PARAM_P(VOID, helper);
181         FUNCTION_TEST_PARAM(ENUM, protocolStorageType);
182         FUNCTION_TEST_PARAM(UINT, hostIdx);
183         FUNCTION_TEST_PARAM(UINT, processId);
184     FUNCTION_TEST_END();
185 
186     ASSERT(helper != NULL);
187 
188     // Execute the protocol command
189     helper->exec = execNew(
190         cfgExe(), protocolLocalParam(protocolStorageType, hostIdx, processId),
191         strNewFmt(PROTOCOL_SERVICE_LOCAL "-%u process", processId), cfgOptionUInt64(cfgOptProtocolTimeout));
192     execOpen(helper->exec);
193 
194     // Create protocol object
195     helper->client = protocolClientNew(
196         strNewFmt(PROTOCOL_SERVICE_LOCAL "-%u protocol", processId),
197         PROTOCOL_SERVICE_LOCAL_STR, execIoRead(helper->exec), execIoWrite(helper->exec));
198 
199     // Move client to exec context so they are freed together
200     protocolClientMove(helper->client, execMemContext(helper->exec));
201 
202     FUNCTION_TEST_RETURN_VOID();
203 }
204 
205 ProtocolClient *
protocolLocalGet(ProtocolStorageType protocolStorageType,unsigned int hostIdx,unsigned int processId)206 protocolLocalGet(ProtocolStorageType protocolStorageType, unsigned int hostIdx, unsigned int processId)
207 {
208     FUNCTION_LOG_BEGIN(logLevelDebug);
209         FUNCTION_LOG_PARAM(STRING_ID, protocolStorageType);
210         FUNCTION_LOG_PARAM(UINT, hostIdx);
211         FUNCTION_LOG_PARAM(UINT, processId);
212     FUNCTION_LOG_END();
213 
214     protocolHelperInit();
215 
216     // Allocate the client cache
217     if (protocolHelper.clientLocalSize == 0)
218     {
219         MEM_CONTEXT_BEGIN(protocolHelper.memContext)
220         {
221             protocolHelper.clientLocalSize = cfgOptionUInt(cfgOptProcessMax) + 1;
222             protocolHelper.clientLocal = memNew(protocolHelper.clientLocalSize * sizeof(ProtocolHelperClient));
223 
224             for (unsigned int clientIdx = 0; clientIdx < protocolHelper.clientLocalSize; clientIdx++)
225                 protocolHelper.clientLocal[clientIdx] = (ProtocolHelperClient){.exec = NULL};
226         }
227         MEM_CONTEXT_END();
228     }
229 
230     ASSERT(processId <= protocolHelper.clientLocalSize);
231 
232     // Create protocol object
233     ProtocolHelperClient *protocolHelperClient = &protocolHelper.clientLocal[processId - 1];
234 
235     if (protocolHelperClient->client == NULL)
236     {
237         MEM_CONTEXT_BEGIN(protocolHelper.memContext)
238         {
239             protocolLocalExec(protocolHelperClient, protocolStorageType, hostIdx, processId);
240         }
241         MEM_CONTEXT_END();
242 
243         // Send noop to catch initialization errors
244         protocolClientNoOp(protocolHelperClient->client);
245     }
246 
247     FUNCTION_LOG_RETURN(PROTOCOL_CLIENT, protocolHelperClient->client);
248 }
249 
250 /***********************************************************************************************************************************
251 Free the protocol client and underlying exec'd process. Log any errors as warnings since it is not worth terminating the process
252 while closing a local/remote that has already completed its work. The warning will be an indication that something is not right.
253 ***********************************************************************************************************************************/
254 static void
protocolHelperClientFree(ProtocolHelperClient * protocolHelperClient)255 protocolHelperClientFree(ProtocolHelperClient *protocolHelperClient)
256 {
257     FUNCTION_LOG_BEGIN(logLevelTrace);
258         FUNCTION_LOG_PARAM_P(VOID, protocolHelperClient);
259     FUNCTION_LOG_END();
260 
261     if (protocolHelperClient->client != NULL)
262     {
263         // Try to shutdown the protocol but only warn on error
264         TRY_BEGIN()
265         {
266             protocolClientFree(protocolHelperClient->client);
267         }
268         CATCH_ANY()
269         {
270             LOG_WARN(errorMessage());
271         }
272         TRY_END();
273 
274         // Try to end the child process but only warn on error
275         TRY_BEGIN()
276         {
277             execFree(protocolHelperClient->exec);
278         }
279         CATCH_ANY()
280         {
281             LOG_WARN(errorMessage());
282         }
283         TRY_END();
284 
285         protocolHelperClient->client = NULL;
286         protocolHelperClient->exec = NULL;
287     }
288 
289     FUNCTION_LOG_RETURN_VOID();
290 }
291 
292 /**********************************************************************************************************************************/
293 void
protocolLocalFree(unsigned int processId)294 protocolLocalFree(unsigned int processId)
295 {
296     FUNCTION_LOG_BEGIN(logLevelDebug);
297         FUNCTION_LOG_PARAM(UINT, processId);
298     FUNCTION_LOG_END();
299 
300     if (protocolHelper.clientLocal != NULL)
301     {
302         ASSERT(processId <= protocolHelper.clientLocalSize);
303         protocolHelperClientFree(&protocolHelper.clientLocal[processId - 1]);
304     }
305 
306     FUNCTION_LOG_RETURN_VOID();
307 }
308 
309 /***********************************************************************************************************************************
310 Get the command line required for remote protocol execution
311 ***********************************************************************************************************************************/
312 static StringList *
protocolRemoteParam(ProtocolStorageType protocolStorageType,unsigned int hostIdx)313 protocolRemoteParam(ProtocolStorageType protocolStorageType, unsigned int hostIdx)
314 {
315     FUNCTION_LOG_BEGIN(logLevelDebug);
316         FUNCTION_LOG_PARAM(STRING_ID, protocolStorageType);
317         FUNCTION_LOG_PARAM(UINT, hostIdx);
318     FUNCTION_LOG_END();
319 
320     // Is this a repo remote?
321     bool isRepo = protocolStorageType == protocolStorageTypeRepo;
322 
323     // Option replacements
324     KeyValue *optionReplace = kvNew();
325 
326     // Replace config options with the host versions
327     unsigned int optConfig = isRepo ? cfgOptRepoHostConfig : cfgOptPgHostConfig;
328 
329     kvPut(
330         optionReplace, VARSTRDEF(CFGOPT_CONFIG),
331         cfgOptionIdxSource(optConfig, hostIdx) != cfgSourceDefault ? VARSTR(cfgOptionIdxStr(optConfig, hostIdx)) : NULL);
332 
333     unsigned int optConfigIncludePath = isRepo ? cfgOptRepoHostConfigIncludePath : cfgOptPgHostConfigIncludePath;
334 
335     kvPut(
336         optionReplace, VARSTRDEF(CFGOPT_CONFIG_INCLUDE_PATH),
337         cfgOptionIdxSource(optConfigIncludePath, hostIdx) != cfgSourceDefault ?
338             VARSTR(cfgOptionIdxStr(optConfigIncludePath, hostIdx)) : NULL);
339 
340     unsigned int optConfigPath = isRepo ? cfgOptRepoHostConfigPath : cfgOptPgHostConfigPath;
341 
342     kvPut(
343         optionReplace, VARSTRDEF(CFGOPT_CONFIG_PATH),
344         cfgOptionIdxSource(optConfigPath, hostIdx) != cfgSourceDefault ? VARSTR(cfgOptionIdxStr(optConfigPath, hostIdx)) : NULL);
345 
346     // Update/remove repo/pg options that are sent to the remote
347     for (ConfigOption optionId = 0; optionId < CFG_OPTION_TOTAL; optionId++)
348     {
349         // Skip options that are not part of a group
350         if (!cfgOptionGroup(optionId))
351             continue;
352 
353         bool remove = false;
354         bool skipHostZero = false;
355 
356         // Remove repo options that are not needed on the remote
357         if (cfgOptionGroupId(optionId) == cfgOptGrpRepo)
358         {
359             // If remote type is pg then remove all repo options since they will not be used
360             if (protocolStorageType == protocolStorageTypePg)
361             {
362                 remove = true;
363             }
364             // Else remove repo options for indexes that are not being passed to the repo. This prevents the remote from getting
365             // partial info about a repo, which could cause an error during validation.
366             else
367             {
368                 for (unsigned int optionIdx = 0; optionIdx < cfgOptionIdxTotal(optionId); optionIdx++)
369                 {
370                     if (cfgOptionIdxTest(optionId, optionIdx) && optionIdx != hostIdx)
371                         kvPut(optionReplace, VARSTRZ(cfgOptionIdxName(optionId, optionIdx)), NULL);
372                 }
373             }
374         }
375         // Remove pg options that are not needed on the remote
376         else
377         {
378             ASSERT(cfgOptionGroupId(optionId) == cfgOptGrpPg);
379 
380             // Remove unrequired/defaulted pg options when the remote type is repo since they won't be used
381             if (protocolStorageType == protocolStorageTypeRepo)
382             {
383                 remove = !cfgParseOptionRequired(cfgCommand(), optionId) || cfgParseOptionDefault(cfgCommand(), optionId) != NULL;
384             }
385             // Move pg options to host index 0 (key 1) so they will be in the default index on the remote host
386             else
387             {
388                 if (hostIdx != 0)
389                 {
390                     kvPut(
391                         optionReplace, VARSTRZ(cfgOptionIdxName(optionId, 0)),
392                         cfgOptionIdxSource(optionId, hostIdx) != cfgSourceDefault ? cfgOptionIdx(optionId, hostIdx) : NULL);
393                 }
394 
395                 remove = true;
396                 skipHostZero = true;
397             }
398         }
399 
400         // Remove options that have been marked for removal if they are not already null or invalid. This is more efficient because
401         // cfgExecParam() won't have to search through as large a list looking for overrides.
402         if (remove)
403         {
404             // Loop through option indexes
405             for (unsigned int optionIdx = 0; optionIdx < cfgOptionIdxTotal(optionId); optionIdx++)
406             {
407                 if (cfgOptionIdxTest(optionId, optionIdx) && !(skipHostZero && optionIdx == 0))
408                     kvPut(optionReplace, VARSTRZ(cfgOptionIdxName(optionId, optionIdx)), NULL);
409             }
410         }
411     }
412 
413     // Set repo default so the remote only operates on a single repo
414     if (protocolStorageType == protocolStorageTypeRepo)
415         kvPut(optionReplace, VARSTRDEF(CFGOPT_REPO), VARUINT(cfgOptionGroupIdxToKey(cfgOptGrpRepo, hostIdx)));
416 
417     // Add the process id if not set. This means that the remote is being started from the main process and should always get a
418     // process id of 0.
419     if (!cfgOptionTest(cfgOptProcess))
420         kvPut(optionReplace, VARSTRDEF(CFGOPT_PROCESS), VARINT(0));
421 
422     // Don't pass log-path or lock-path since these are host specific
423     kvPut(optionReplace, VARSTRDEF(CFGOPT_LOG_PATH), NULL);
424     kvPut(optionReplace, VARSTRDEF(CFGOPT_LOCK_PATH), NULL);
425 
426     // Only enable file logging on the remote when requested
427     kvPut(
428         optionReplace, VARSTRDEF(CFGOPT_LOG_LEVEL_FILE),
429         cfgOptionBool(cfgOptLogSubprocess) ? cfgOption(cfgOptLogLevelFile) : VARSTRDEF("off"));
430 
431     // Always output errors on stderr for debugging purposes
432     kvPut(optionReplace, VARSTRDEF(CFGOPT_LOG_LEVEL_STDERR), VARSTRDEF("error"));
433 
434     // Disable output to stdout since it is used by the protocol
435     kvPut(optionReplace, VARSTRDEF(CFGOPT_LOG_LEVEL_CONSOLE), VARSTRDEF("off"));
436 
437     // Add the remote type
438     kvPut(optionReplace, VARSTRDEF(CFGOPT_REMOTE_TYPE), VARSTR(strIdToStr(protocolStorageType)));
439 
440     FUNCTION_LOG_RETURN(STRING_LIST, cfgExecParam(cfgCommand(), cfgCmdRoleRemote, optionReplace, false, true));
441 }
442 
443 // Helper to add SSH parameters when executing the remote via SSH
444 static StringList *
protocolRemoteParamSsh(const ProtocolStorageType protocolStorageType,const unsigned int hostIdx)445 protocolRemoteParamSsh(const ProtocolStorageType protocolStorageType, const unsigned int hostIdx)
446 {
447     FUNCTION_LOG_BEGIN(logLevelDebug);
448         FUNCTION_LOG_PARAM(STRING_ID, protocolStorageType);
449         FUNCTION_LOG_PARAM(UINT, hostIdx);
450     FUNCTION_LOG_END();
451 
452     StringList *result = NULL;
453 
454     MEM_CONTEXT_TEMP_BEGIN()
455     {
456         // Is this a repo remote?
457         bool isRepo = protocolStorageType == protocolStorageTypeRepo;
458 
459         // Fixed parameters for ssh command
460         result = strLstNew();
461         strLstAddZ(result, "-o");
462         strLstAddZ(result, "LogLevel=error");
463         strLstAddZ(result, "-o");
464         strLstAddZ(result, "Compression=no");
465         strLstAddZ(result, "-o");
466         strLstAddZ(result, "PasswordAuthentication=no");
467 
468         // Append port if specified
469         ConfigOption optHostPort = isRepo ? cfgOptRepoHostPort : cfgOptPgHostPort;
470 
471         if (cfgOptionIdxTest(optHostPort, hostIdx))
472         {
473             strLstAddZ(result, "-p");
474             strLstAdd(result, strNewFmt("%u", cfgOptionIdxUInt(optHostPort, hostIdx)));
475         }
476 
477         // Append user/host
478         strLstAdd(
479             result,
480             strNewFmt(
481                 "%s@%s", strZ(cfgOptionIdxStr(isRepo ? cfgOptRepoHostUser : cfgOptPgHostUser, hostIdx)),
482                 strZ(cfgOptionIdxStr(isRepo ? cfgOptRepoHost : cfgOptPgHost, hostIdx))));
483 
484         // Add remote command and parameters
485         StringList *paramList = protocolRemoteParam(protocolStorageType, hostIdx);
486 
487         strLstInsert(paramList, 0, cfgOptionIdxStr(isRepo ? cfgOptRepoHostCmd : cfgOptPgHostCmd, hostIdx));
488         strLstAdd(result, strLstJoin(paramList, " "));
489 
490         // Move to prior context
491         strLstMove(result, memContextPrior());
492     }
493     MEM_CONTEXT_TEMP_END();
494 
495     FUNCTION_LOG_RETURN(STRING_LIST, result);
496 }
497 
498 /**********************************************************************************************************************************/
499 // Helper to execute the local process. This is a separate function solely so that it can be shimmed during testing.
500 static void
protocolRemoteExec(ProtocolHelperClient * const helper,const ProtocolStorageType protocolStorageType,const unsigned int hostIdx,const unsigned int processId)501 protocolRemoteExec(
502     ProtocolHelperClient *const helper, const ProtocolStorageType protocolStorageType, const unsigned int hostIdx,
503     const unsigned int processId)
504 {
505     FUNCTION_TEST_BEGIN();
506         FUNCTION_TEST_PARAM_P(VOID, helper);
507         FUNCTION_TEST_PARAM(ENUM, protocolStorageType);
508         FUNCTION_TEST_PARAM(UINT, hostIdx);
509         FUNCTION_TEST_PARAM(UINT, processId);
510     FUNCTION_TEST_END();
511 
512     ASSERT(helper != NULL);
513 
514     // Execute the protocol command
515     const char *const host =
516         strZ(cfgOptionIdxStr(protocolStorageType == protocolStorageTypeRepo ? cfgOptRepoHost : cfgOptPgHost, hostIdx));
517 
518     helper->exec = execNew(
519         cfgOptionStr(cfgOptCmdSsh), protocolRemoteParamSsh(protocolStorageType, hostIdx),
520         strNewFmt(PROTOCOL_SERVICE_REMOTE "-%u process on '%s'", processId, host), cfgOptionUInt64(cfgOptProtocolTimeout));
521     execOpen(helper->exec);
522 
523     // Create protocol object
524     helper->client = protocolClientNew(
525         strNewFmt(PROTOCOL_SERVICE_REMOTE "-%u protocol on '%s'", processId, host), PROTOCOL_SERVICE_REMOTE_STR,
526         execIoRead(helper->exec), execIoWrite(helper->exec));
527 
528     // Move client to exec context so they are freed together
529     protocolClientMove(helper->client, execMemContext(helper->exec));
530 
531     FUNCTION_TEST_RETURN_VOID();
532 }
533 
534 ProtocolClient *
protocolRemoteGet(ProtocolStorageType protocolStorageType,unsigned int hostIdx)535 protocolRemoteGet(ProtocolStorageType protocolStorageType, unsigned int hostIdx)
536 {
537     FUNCTION_LOG_BEGIN(logLevelDebug);
538         FUNCTION_LOG_PARAM(STRING_ID, protocolStorageType);
539         FUNCTION_LOG_PARAM(UINT, hostIdx);
540     FUNCTION_LOG_END();
541 
542     // Is this a repo remote?
543     bool isRepo = protocolStorageType == protocolStorageTypeRepo;
544 
545     protocolHelperInit();
546 
547     // Allocate the client cache
548     if (protocolHelper.clientRemoteSize == 0)
549     {
550         MEM_CONTEXT_BEGIN(protocolHelper.memContext)
551         {
552             protocolHelper.clientRemoteSize = cfgOptionGroupIdxTotal(isRepo ? cfgOptGrpRepo : cfgOptGrpPg) + 1;
553             protocolHelper.clientRemote = memNew(protocolHelper.clientRemoteSize * sizeof(ProtocolHelperClient));
554 
555             for (unsigned int clientIdx = 0; clientIdx < protocolHelper.clientRemoteSize; clientIdx++)
556                 protocolHelper.clientRemote[clientIdx] = (ProtocolHelperClient){.exec = NULL};
557         }
558         MEM_CONTEXT_END();
559     }
560 
561     // Determine protocol id for the remote.  If the process option is set then use that since we want the remote protocol id to
562     // match the local protocol id. Otherwise set to 0 since the remote is being started from a main process and there should only
563     // be one remote per host.
564     unsigned int processId = 0;
565 
566     if (cfgOptionTest(cfgOptProcess))
567         processId = cfgOptionUInt(cfgOptProcess);
568 
569     CHECK(hostIdx < protocolHelper.clientRemoteSize);
570 
571     // Create protocol object
572     ProtocolHelperClient *protocolHelperClient = &protocolHelper.clientRemote[hostIdx];
573 
574     if (protocolHelperClient->client == NULL)
575     {
576         MEM_CONTEXT_BEGIN(protocolHelper.memContext)
577         {
578             protocolRemoteExec(protocolHelperClient, protocolStorageType, hostIdx, processId);
579 
580             // Send noop to catch initialization errors
581             protocolClientNoOp(protocolHelperClient->client);
582 
583             // Get cipher options from the remote if none are locally configured
584             if (isRepo && cfgOptionIdxStrId(cfgOptRepoCipherType, hostIdx) == cipherTypeNone)
585             {
586                 // Options to query
587                 VariantList *param = varLstNew();
588                 varLstAdd(param, varNewStrZ(cfgOptionIdxName(cfgOptRepoCipherType, hostIdx)));
589                 varLstAdd(param, varNewStrZ(cfgOptionIdxName(cfgOptRepoCipherPass, hostIdx)));
590 
591                 VariantList *optionList = configOptionRemote(protocolHelperClient->client, param);
592 
593                 if (!strEq(varStr(varLstGet(optionList, 0)), strIdToStr(cipherTypeNone)))
594                 {
595                     cfgOptionIdxSet(cfgOptRepoCipherType, hostIdx, cfgSourceConfig, varLstGet(optionList, 0));
596                     cfgOptionIdxSet(cfgOptRepoCipherPass, hostIdx, cfgSourceConfig, varLstGet(optionList, 1));
597                 }
598             }
599         }
600         MEM_CONTEXT_END();
601     }
602 
603     FUNCTION_LOG_RETURN(PROTOCOL_CLIENT, protocolHelperClient->client);
604 }
605 
606 /**********************************************************************************************************************************/
607 void
protocolRemoteFree(unsigned int hostIdx)608 protocolRemoteFree(unsigned int hostIdx)
609 {
610     FUNCTION_LOG_BEGIN(logLevelDebug);
611         FUNCTION_LOG_PARAM(UINT, hostIdx);
612     FUNCTION_LOG_END();
613 
614     if (protocolHelper.clientRemote != NULL)
615         protocolHelperClientFree(&protocolHelper.clientRemote[hostIdx]);
616 
617     FUNCTION_LOG_RETURN_VOID();
618 }
619 
620 /**********************************************************************************************************************************/
621 void
protocolKeepAlive(void)622 protocolKeepAlive(void)
623 {
624     FUNCTION_LOG_VOID(logLevelTrace);
625 
626     if (protocolHelper.memContext != NULL)
627     {
628         for (unsigned int clientIdx  = 0; clientIdx < protocolHelper.clientRemoteSize; clientIdx++)
629         {
630             if (protocolHelper.clientRemote[clientIdx].client != NULL)
631                 protocolClientNoOp(protocolHelper.clientRemote[clientIdx].client);
632         }
633     }
634 
635     FUNCTION_LOG_RETURN_VOID();
636 }
637 
638 /**********************************************************************************************************************************/
639 void
protocolFree(void)640 protocolFree(void)
641 {
642     FUNCTION_LOG_VOID(logLevelTrace);
643 
644     if (protocolHelper.memContext != NULL)
645     {
646         // Free remotes
647         for (unsigned int clientIdx = 0; clientIdx < protocolHelper.clientRemoteSize; clientIdx++)
648             protocolRemoteFree(clientIdx);
649 
650         // Free locals
651         for (unsigned int clientIdx = 1; clientIdx <= protocolHelper.clientLocalSize; clientIdx++)
652             protocolLocalFree(clientIdx);
653     }
654 
655     FUNCTION_LOG_RETURN_VOID();
656 }
657