*
* util.c: log messages to log file or stderr, and misc code.
*
* Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
* Portions Copyright (c) 2009-2011, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
* Portions Copyright (c) 2015-2019, Postgres Professional
*
*-------------------------------------------------------------------------
*/
#include "pg_probackup.h"
#include "catalog/pg_control.h"
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <sys/stat.h>
#include "tool_common.h"
#include "common/fe_memutils.h"
#include "storage/file/fio_device.h"
#include "oss/include/restore.h"
static const char *statusName[] =
{
"UNKNOWN",
"OK",
"ERROR",
"RUNNING",
"MERGING",
"MERGED",
"DELETING",
"DELETED",
"DONE",
"ORPHAN",
"CORRUPT"
};
static const char *devTypeName[] =
{
"FILE",
"DSS",
"UNKNOWN",
"UNKNOWN"
};
static const char *ossStatusName[] =
{
"LOCAL",
"S3",
"UNKNOWN",
"UNKNOWN"
};
uint32 NUM_65536 = 65536;
uint32 NUM_10000 = 10000;
const char *
base36enc(long unsigned int value)
{
const char base36[36 + 1] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
static char buffer[14];
unsigned int offset = sizeof(buffer);
buffer[--offset] = '\0';
do {
buffer[--offset] = base36[value % 36];
} while (value /= 36);
return &buffer[offset];
}
* Same as base36enc(), but the results must be released by the user.
*/
char *
base36enc_dup(long unsigned int value)
{
const char base36[36 + 1] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
char buffer[14];
unsigned int offset = sizeof(buffer);
buffer[--offset] = '\0';
do {
buffer[--offset] = base36[value % 36];
} while (value /= 36);
return strdup(&buffer[offset]);
}
long unsigned int
base36dec(const char *text)
{
return strtoul(text, NULL, 36);
}
static void
checkControlFile(ControlFileData *ControlFile)
{
pg_crc32c crc;
INIT_CRC32C(crc);
COMP_CRC32C(crc, (char *) ControlFile, offsetof(ControlFileData, crc));
FIN_CRC32C(crc);
if (!EQ_CRC32C(crc, ControlFile->crc))
elog(ERROR, "Calculated CRC checksum does not match value stored in file.\n"
"Either the file is corrupt, or it has a different layout than this program\n"
"is expecting. The results below are untrustworthy.");
if ((ControlFile->pg_control_version % NUM_65536 == 0 || ControlFile->pg_control_version % NUM_65536 > NUM_10000) &&
ControlFile->pg_control_version / NUM_65536 != 0)
elog(ERROR, "possible byte ordering mismatch\n"
"The byte ordering used to store the pg_control file might not match the one\n"
"used by this program. In that case the results below would be incorrect, and\n"
"the PostgreSQL installation would be incompatible with this data directory.");
}
static void checkSSControlFile(ControlFileData* ControlFile, char* last, size_t size)
{
pg_crc32c crc;
INIT_CRC32C(crc);
COMP_CRC32C(crc, (char *) ControlFile, offsetof(ControlFileData, crc));
COMP_CRC32C(crc, last, size);
FIN_CRC32C(crc);
ControlFile->crc = crc;
if ((ControlFile->pg_control_version % NUM_65536 == 0 || ControlFile->pg_control_version % NUM_65536 > NUM_10000) &&
ControlFile->pg_control_version / NUM_65536 != 0)
elog(ERROR, "possible byte ordering mismatch\n"
"The byte ordering used to store the pg_control file might not match the one\n"
"used by this program. In that case the results below would be incorrect, and\n"
"the PostgreSQL installation would be incompatible with this data directory.");
}
* Verify control file contents in the buffer src, and copy it to *ControlFile.
*/
static void
digestControlFile(ControlFileData *ControlFile, char *src, size_t size)
{
errno_t rc;
char* oldSrc = src;
char* tmpDssSrc;
bool dssMode = IsDssMode();
size_t compareSize = PG_CONTROL_SIZE;
if (dssMode) {
compareSize = (REFORMER_CTL_INSTANCEID + 1) * PG_CONTROL_SIZE;
}
if (size != compareSize)
elog(ERROR, "unexpected control file size %d, expected %d",
(int) size, compareSize);
rc = memcpy_s(ControlFile, sizeof(ControlFileData), src, sizeof(ControlFileData));
securec_check_c(rc, "\0", "\0");
if (dssMode) {
tmpDssSrc = oldSrc;
tmpDssSrc += (REFORMER_CTL_INSTANCEID) * PG_CONTROL_SIZE;
checkSSControlFile(ControlFile, tmpDssSrc, PG_CONTROL_SIZE);
pg_crc32c crc;
INIT_CRC32C(crc);
COMP_CRC32C(crc, src, offsetof(ControlFileData, crc));
FIN_CRC32C(crc);
((ControlFileData*)src)->crc = crc;
} else {
checkControlFile(ControlFile);
}
}
* Write ControlFile to pg_control
*/
static void
writeControlFile(ControlFileData *ControlFile, const char *path, fio_location location)
{
int fd;
char *buffer = NULL;
buffer = (char *)pg_malloc(PG_CONTROL_SIZE);
errno_t rc = memcpy_s(buffer, sizeof(ControlFileData), ControlFile, sizeof(ControlFileData));
securec_check_c(rc, "\0", "\0");
fd = fio_open(path,
O_RDWR | O_CREAT | O_TRUNC | PG_BINARY, location);
if (fd < 0)
elog(ERROR, "Failed to open file: %s", path);
if (fio_write(fd, buffer, PG_CONTROL_SIZE) != PG_CONTROL_SIZE)
elog(ERROR, "Failed to overwrite file: %s", path);
if (fio_flush(fd) != 0)
elog(ERROR, "Failed to sync file: %s", path);
fio_close(fd);
pg_free(buffer);
}
* Write Dss buffer to pg_control
*/
static void
writeDssControlFile(char* src, size_t srcLen, const char *path, fio_location location)
{
int fd;
fd = fio_open(path,
O_RDWR | O_CREAT | O_TRUNC | PG_BINARY, location);
if (fd < 0)
elog(ERROR, "Failed to open file: %s", path);
if (fio_write(fd, src, srcLen) != (ssize_t)srcLen)
elog(ERROR, "Failed to overwrite file: %s", path);
if (fio_flush(fd) != 0)
elog(ERROR, "Failed to sync file: %s", path);
if (fio_close(fd) != 0) {
elog(ERROR, "Failed to close file: %s", path);
}
}
* Utility shared by backup and restore to fetch the current timeline
* used by a node.
*/
TimeLineID
get_current_timeline(PGconn *conn)
{
PGresult *res;
TimeLineID tli = 0;
char *val;
res = pgut_execute_extended(conn,
"SELECT timeline_id FROM pg_control_checkpoint()", 0, NULL, true, true);
if (PQresultStatus(res) == PGRES_TUPLES_OK)
val = PQgetvalue(res, 0, 0);
else
return get_current_timeline_from_control(false);
if (!parse_uint32(val, &tli, 0))
{
PQclear(res);
elog(WARNING, "Invalid value of timeline_id %s", val);
return get_current_timeline_from_control(false);
}
return tli;
}
TimeLineID
get_current_timeline_from_control(bool safe)
{
ControlFileData ControlFile;
char *buffer;
size_t size;
fio_location location;
location = is_dss_file(T_XLOG_CONTROL_FILE) ? FIO_DSS_HOST : FIO_DB_HOST;
if (IsDssMode()) {
buffer = slurpFile(T_XLOG_CONTROL_FILE, &size, false, location);
} else {
char xlog_ctl_file_path[MAXPGPATH] = {'\0'};
join_path_components(xlog_ctl_file_path, instance_config.pgdata, T_XLOG_CONTROL_FILE);
buffer = slurpFile(xlog_ctl_file_path, &size, false, location);
}
if (safe && buffer == NULL)
return 0;
digestControlFile(&ControlFile, buffer, size);
pg_free(buffer);
return ControlFile.checkPointCopy.ThisTimeLineID;
}
* Get last check point record ptr from pg_tonrol.
*/
XLogRecPtr
get_checkpoint_location(PGconn *conn)
{
#if PG_VERSION_NUM >= 90600
PGresult *res;
uint32 lsn_hi;
uint32 lsn_lo;
XLogRecPtr lsn;
#if PG_VERSION_NUM >= 100000
res = pgut_execute(conn,
"SELECT checkpoint_lsn FROM pg_catalog.pg_control_checkpoint()",
0, NULL);
#else
res = pgut_execute(conn,
"SELECT checkpoint_location FROM pg_catalog.pg_control_checkpoint()",
0, NULL);
#endif
XLogDataFromLSN(PQgetvalue(res, 0, 0), &lsn_hi, &lsn_lo);
PQclear(res);
lsn = ((uint64) lsn_hi) << 32 | lsn_lo;
return lsn;
#else
char *buffer;
size_t size;
ControlFileData ControlFile;
fio_location location;
location = is_dss_file(T_XLOG_CONTROL_FILE) ? FIO_DSS_HOST : FIO_DB_HOST;
if (IsDssMode()) {
buffer = slurpFile(T_XLOG_CONTROL_FILE, &size, false, location);
} else {
char xlog_ctl_file_path[MAXPGPATH] = {'\0'};
join_path_components(xlog_ctl_file_path, instance_config.pgdata, T_XLOG_CONTROL_FILE);
buffer = slurpFile(xlog_ctl_file_path, &size, false, location);
}
digestControlFile(&ControlFile, buffer, size);
pg_free(buffer);
return ControlFile.checkPoint;
#endif
}
uint64
get_system_identifier(const char *pgdata_path)
{
ControlFileData ControlFile;
char *buffer;
size_t size;
fio_location location;
location = is_dss_file(T_XLOG_CONTROL_FILE) ? FIO_DSS_HOST : FIO_DB_HOST;
if (IsDssMode()) {
buffer = slurpFile(T_XLOG_CONTROL_FILE, &size, false, location);
} else {
char xlog_ctl_file_path[MAXPGPATH] = {'\0'};
join_path_components(xlog_ctl_file_path, pgdata_path, T_XLOG_CONTROL_FILE);
buffer = slurpFile(xlog_ctl_file_path, &size, false, location);
}
if (buffer == NULL)
return 0;
digestControlFile(&ControlFile, buffer, size);
pg_free(buffer);
return ControlFile.system_identifier;
}
uint64
get_remote_system_identifier(PGconn *conn)
{
#if PG_VERSION_NUM >= 90600
PGresult *res;
uint64 system_id_conn;
char *val;
res = pgut_execute(conn,
"SELECT system_identifier FROM pg_catalog.pg_control_system()",
0, NULL);
val = PQgetvalue(res, 0, 0);
if (!parse_uint64(val, &system_id_conn, 0))
{
PQclear(res);
elog(ERROR, "%s is not system_identifier", val);
}
PQclear(res);
return system_id_conn;
#else
char *buffer;
size_t size;
ControlFileData ControlFile;
fio_location location;
location = is_dss_file(T_XLOG_CONTROL_FILE) ? FIO_DSS_HOST : FIO_DB_HOST;
location = is_dss_file(T_XLOG_CONTROL_FILE) ? FIO_DSS_HOST : FIO_DB_HOST;
if (IsDssMode()) {
buffer = slurpFile(T_XLOG_CONTROL_FILE, &size, false, location);
} else {
char xlog_ctl_file_path[MAXPGPATH] = {'\0'};
join_path_components(xlog_ctl_file_path, instance_config.pgdata, T_XLOG_CONTROL_FILE);
buffer = slurpFile(xlog_ctl_file_path, &size, false, location);
}
digestControlFile(&ControlFile, buffer, size);
pg_free(buffer);
return ControlFile.system_identifier;
#endif
}
uint32
get_xlog_seg_size(char *pgdata_path)
{
#if PG_VERSION_NUM >= 110000
ControlFileData ControlFile;
char *buffer;
size_t size;
fio_location location;
location = is_dss_file(T_XLOG_CONTROL_FILE) ? FIO_DSS_HOST : FIO_DB_HOST;
location = is_dss_file(T_XLOG_CONTROL_FILE) ? FIO_DSS_HOST : FIO_DB_HOST;
if (IsDssMode()) {
buffer = slurpFile(T_XLOG_CONTROL_FILE, &size, false, location);
} else {
char xlog_ctl_file_path[MAXPGPATH] = {'\0'};
join_path_components(xlog_ctl_file_path, pgdata_path, T_XLOG_CONTROL_FILE);
buffer = slurpFile(xlog_ctl_file_path, &size, false, location);
}
digestControlFile(&ControlFile, buffer, size);
pg_free(buffer);
return ControlFile.xlog_seg_size;
#else
return (uint32) XLogSegSize;
#endif
}
uint32
get_data_checksum_version(bool safe)
{
ControlFileData ControlFile;
char *buffer;
size_t size;
fio_location location;
location = is_dss_file(T_XLOG_CONTROL_FILE) ? FIO_DSS_HOST : FIO_DB_HOST;
if (IsDssMode()) {
buffer = slurpFile(T_XLOG_CONTROL_FILE, &size, false, location);
} else {
char xlog_ctl_file_path[MAXPGPATH] = {'\0'};
join_path_components(xlog_ctl_file_path, instance_config.pgdata, T_XLOG_CONTROL_FILE);
buffer = slurpFile(xlog_ctl_file_path, &size, false, location);
}
if (buffer == NULL)
return 0;
digestControlFile(&ControlFile, buffer, size);
pg_free(buffer);
return 0;
}
pg_crc32c
get_pgcontrol_checksum(const char *fullpath)
{
ControlFileData ControlFile;
char *buffer;
size_t size;
buffer = slurpFile(fullpath, &size, false, FIO_BACKUP_HOST);
digestControlFile(&ControlFile, buffer, size);
pg_free(buffer);
return ControlFile.crc;
}
void
get_redo(const char *pgdata_path, RedoParams *redo)
{
ControlFileData ControlFile;
char *buffer;
size_t size;
fio_location location;
location = is_dss_file(T_XLOG_CONTROL_FILE) ? FIO_DSS_HOST : FIO_DB_HOST;
if (IsDssMode()) {
buffer = slurpFile(T_XLOG_CONTROL_FILE, &size, false, location);
} else {
char xlog_ctl_file_path[MAXPGPATH] = {'\0'};
join_path_components(xlog_ctl_file_path, pgdata_path, T_XLOG_CONTROL_FILE);
buffer = slurpFile(xlog_ctl_file_path, &size, false, location);
}
digestControlFile(&ControlFile, buffer, size);
pg_free(buffer);
redo->lsn = ControlFile.checkPointCopy.redo;
redo->tli = ControlFile.checkPointCopy.ThisTimeLineID;
if (ControlFile.minRecoveryPoint > 0 &&
ControlFile.minRecoveryPoint < redo->lsn)
{
redo->lsn = ControlFile.minRecoveryPoint;
}
if (ControlFile.backupStartPoint > 0 &&
ControlFile.backupStartPoint < redo->lsn)
{
redo->lsn = ControlFile.backupStartPoint;
redo->tli = ControlFile.checkPointCopy.ThisTimeLineID;
}
}
void parse_vgname_args(const char* args)
{
char *vgname = xstrdup(args);
if (strstr(vgname, "/") != NULL)
elog(ERROR, "invalid token \"/\" in vgname");
char *comma = strstr(vgname, ",");
if (comma == NULL) {
elog(ERROR, "invalid vgname args, there is at least one \",\" in vgname\n"
"insufficient vgname args, check the number of args, example: --vgname=\"+data,+log\"");
}
if (*(comma + 1) == '\0' ){
elog(ERROR, "insufficient vgname args, check the number of args, example: --vgname=\"+data,+log\"");
}
instance_config.dss.vgdata = xstrdup(vgname);
comma = strstr(instance_config.dss.vgdata, ",");
comma[0] = '\0';
instance_config.dss.vglog = comma + 1;
if (strstr(instance_config.dss.vgdata, ",") != NULL)
elog(ERROR, "invalid vgname args, should be two volume group names, example: \"+data,+log\"");
if (strstr(instance_config.dss.vglog, ",") != NULL)
elog(ERROR, "invalid vgname args, should be two volume group names, example: \"+data,+log\"");
}
bool is_dss_xlog_dir(const char *ss_dir)
{
if (IsDssMode() && (pg_strcasecmp(ss_dir, "pg_xlog") == 0 || pg_strcasecmp(ss_dir, "pg_replication") == 0)) {
return true;
}
return false;
}
* Rewrite minRecoveryPoint of pg_control in backup directory. minRecoveryPoint
* 'as-is' is not to be trusted.
*/
void
set_min_recovery_point(pgFile *file, const char *fullpath,
XLogRecPtr stop_backup_lsn)
{
ControlFileData ControlFile;
char *buffer;
size_t size;
fio_location location;
location = is_dss_file(T_XLOG_CONTROL_FILE) ? FIO_DSS_HOST : FIO_DB_HOST;
if (IsDssMode()) {
buffer = slurpFile(T_XLOG_CONTROL_FILE, &size, false, location);
} else {
char xlog_ctl_file_path[MAXPGPATH] = {'\0'};
join_path_components(xlog_ctl_file_path, instance_config.pgdata, T_XLOG_CONTROL_FILE);
buffer = slurpFile(xlog_ctl_file_path, &size, false, location);
}
digestControlFile(&ControlFile, buffer, size);
elog(LOG, "Current minRecPoint %X/%X",
(uint32) (ControlFile.minRecoveryPoint >> 32),
(uint32) ControlFile.minRecoveryPoint);
elog(LOG, "Setting minRecPoint to %X/%X",
(uint32) (stop_backup_lsn >> 32),
(uint32) stop_backup_lsn);
ControlFile.minRecoveryPoint = stop_backup_lsn;
INIT_CRC32C(ControlFile.crc);
COMP_CRC32C(ControlFile.crc, (char *) &ControlFile,
offsetof(ControlFileData, crc));
FIN_CRC32C(ControlFile.crc);
writeControlFile(&ControlFile, fullpath, FIO_LOCAL_HOST);
file->crc = ControlFile.crc;
if (current.media_type == MEDIA_TYPE_OSS) {
uploadConfigFile(fullpath, fullpath);
}
pg_free(buffer);
}
* Copy pg_control file to backup. We do not apply compression to this file.
*/
void
copy_pgcontrol_file(const char *from_fullpath, fio_location from_location,
const char *to_fullpath, fio_location to_location, pgFile *file)
{
ControlFileData ControlFile;
char *buffer;
size_t size;
buffer = slurpFile(from_fullpath, &size, false, from_location);
* In dss mode, we need to set the parameter list_stable
* on the pg_control file's last page to 0.
*/
if (is_dss_type(file->type)) {
ss_reformer_ctrl_t *reformerCtrl;
reformerCtrl = (ss_reformer_ctrl_t *)(buffer + (MAX_INSTANCEID + 1) * PG_CONTROL_SIZE);
reformerCtrl->list_stable = 0;
pg_crc32c crc;
INIT_CRC32C(crc);
COMP_CRC32C(crc, (char *) reformerCtrl, offsetof(ss_reformer_ctrl_t, crc));
FIN_CRC32C(crc);
reformerCtrl->crc = crc;
}
digestControlFile(&ControlFile, buffer, size);
file->crc = ControlFile.crc;
file->read_size = size;
file->write_size = size;
file->uncompressed_size = size;
if (is_dss_type(file->type)) {
writeDssControlFile(buffer, size, to_fullpath, to_location);
} else {
writeControlFile(&ControlFile, to_fullpath, to_location);
}
if (current.media_type == MEDIA_TYPE_OSS) {
uploadConfigFile(to_fullpath, to_fullpath);
}
pg_free(buffer);
}
* Parse string representation of the server version.
*/
uint32
parse_server_version(const char *server_version_str)
{
int nfields;
uint32 result = 0;
int major_version = 0;
int minor_version = 0;
nfields = sscanf_s(server_version_str, "%d.%d", &major_version, &minor_version);
if (nfields == 2)
{
if (major_version > 10)
elog(ERROR, "Server version format doesn't match major version %d", major_version);
result = major_version * 10000 + minor_version * 100;
}
else if (nfields == 1)
{
if (major_version < 10)
elog(ERROR, "Server version format doesn't match major version %d", major_version);
result = major_version * 10000;
}
else
elog(ERROR, "Unknown server version format %s", server_version_str);
return result;
}
* Parse string representation of the program version.
*/
uint32
parse_program_version(const char *program_version)
{
int nfields;
int major = 0,
minor = 0,
micro = 0;
uint32 result = 0;
if (program_version == NULL || program_version[0] == '\0')
return 0;
nfields = sscanf_s(program_version, "%d.%d.%d", &major, &minor, µ);
if (nfields == 3)
result = major * 10000 + minor * 100 + micro;
else
elog(ERROR, "Unknown program version format %s", program_version);
return result;
}
const char *
status2str(BackupStatus status)
{
if (status < BACKUP_STATUS_INVALID || BACKUP_STATUS_CORRUPT < status)
return "UNKNOWN";
return statusName[status];
}
BackupStatus
str2status(const char *status)
{
for (int i = 0; i <= BACKUP_STATUS_CORRUPT; i++)
{
if (pg_strcasecmp(status, statusName[i]) == 0) {
return (BackupStatus)i;
}
}
return BACKUP_STATUS_INVALID;
}
const char *dev2str(device_type_t type)
{
return devTypeName[type];
}
const char *ossStatus2str(oss_status_t type)
{
return ossStatusName[type];
}
device_type_t str2dev(const char *dev)
{
for (int i = 0; i < (int)DEV_TYPE_NUM; i++) {
if (pg_strcasecmp(dev, devTypeName[i]) == 0)
return (device_type_t)i;
}
return DEV_TYPE_INVALID;
}
oss_status_t str2ossStatus(const char *status)
{
for (int i = 0; i < (int)OSS_STATUS_NUM; i++) {
if (pg_strcasecmp(status, ossStatusName[i]) == 0)
return (oss_status_t)i;
}
return OSS_STATUS_INVALID;
}
bool
datapagemap_is_set(datapagemap_t *map, BlockNumber blkno)
{
int offset;
int bitno;
offset = blkno / 8;
bitno = blkno % 8;
if (map->bitmapsize <= offset)
{
int oldsize = map->bitmapsize;
int newsize;
* The minimum to hold the new bit is offset + 1. But add some
* headroom, so that we don't need to repeatedly enlarge the bitmap in
* the common case that blocks are modified in order, from beginning
* of a relation to the end.
*/
newsize = offset + 1;
newsize += 10;
map->bitmap = (unsigned char *)pg_realloc(map->bitmap, newsize);
errno_t rc = memset_s(&map->bitmap[oldsize], newsize - oldsize, 0, newsize - oldsize);
securec_check(rc, "\0", "\0");
map->bitmapsize = newsize;
}
return map->bitmap[offset] & (1 << bitno);
}
* A debugging aid. Prints out the contents of the page map.
*/
void
datapagemap_print_debug(datapagemap_t *map)
{
datapagemap_iterator_t *iter;
BlockNumber blocknum;
iter = datapagemap_iterate(map);
while (datapagemap_next(iter, &blocknum))
elog(INFO, " block %u", blocknum);
pg_free(iter);
}
* Return pid of postmaster process running in given pgdata.
* Return 0 if there is none.
* Return 1 if postmaster.pid is mangled.
*/
pid_t
check_postmaster(const char *pgdata)
{
FILE *fp;
pid_t pid;
char pid_file[MAXPGPATH];
errno_t rc = snprintf_s(pid_file, MAXPGPATH, MAXPGPATH - 1,
"%s/postmaster.pid", pgdata);
securec_check_ss_c(rc, "\0", "\0");
canonicalize_path(pid_file);
fp = fopen(pid_file, "r");
if (fp == NULL)
{
if (errno == ENOENT)
return 0;
else
elog(ERROR, "Cannot open file \"%s\": %s",
pid_file, strerror(errno));
}
if (fscanf_s(fp, "%i", &pid) != 1)
{
pid = 1;
}
if (pid > 1)
{
if (kill(pid, 0) != 0)
{
if (errno == ESRCH)
pid = 0;
else
elog(ERROR, "Failed to send signal 0 to a process %d: %s",
pid, strerror(errno));
}
}
fclose(fp);
return pid;
}
* Replace the actual password with *'s.
*/
void replace_password(int argc, char** argv, const char* optionName)
{
int count = 0;
char* pchPass = NULL;
char* pchTemp = NULL;
for (count = 0; count < argc; count++) {
if (strncmp(optionName, argv[count], strlen(optionName)) == 0) {
pchTemp = strchr(argv[count], '=');
if (pchTemp != NULL) {
pchPass = pchTemp + 1;
} else if ((NULL != strstr(argv[count], optionName)) && (strlen(argv[count]) > strlen(optionName))) {
pchPass = argv[count] + strlen(optionName);
} else {
pchPass = argv[(int)(count + 1)];
}
if (strlen(pchPass) > 0) {
*pchPass = '*';
pchPass = pchPass + 1;
while ('\0' != *pchPass) {
*pchPass = '\0';
pchPass++;
}
}
break;
}
}
}
void pg_free_callback(bool fatal, void* userdata)
{
if (userdata) {
pg_free(userdata);
}
}