/*------------------------------------------------------------------------- * * configure.c: - manage backup catalog. * * Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd. * Copyright (c) 2017-2019, Postgres Professional * *------------------------------------------------------------------------- */ #include "pg_probackup.h" #include #include "configuration.h" #include "json.h" static void assign_log_level_console(ConfigOption *opt, const char *arg); static void assign_log_level_file(ConfigOption *opt, const char *arg); static void assign_compress_alg(ConfigOption *opt, const char *arg); static char *get_log_level_console(ConfigOption *opt); static char *get_log_level_file(ConfigOption *opt); static char *get_compress_alg(ConfigOption *opt); static void show_configure_start(void); static void show_configure_end(void); static void show_configure_plain(ConfigOption *opt); static void show_configure_json(ConfigOption *opt); #define RETENTION_REDUNDANCY_DEFAULT 0 #define RETENTION_WINDOW_DEFAULT 0 #define OPTION_INSTANCE_GROUP "Backup instance information" #define OPTION_CONN_GROUP "Connection parameters" #define OPTION_ARCHIVE_GROUP "Archive parameters" #define OPTION_LOG_GROUP "Logging parameters" #define OPTION_RETENTION_GROUP "Retention parameters" #define OPTION_COMPRESS_GROUP "Compression parameters" #define OPTION_REMOTE_GROUP "Remote access parameters" /* * Short name should be non-printable ASCII character. */ ConfigOption instance_options[] = { /* Instance options */ { 's', 'D', "pgdata", &instance_config.pgdata, SOURCE_CMD, (OptionSource)0, OPTION_INSTANCE_GROUP, 0, option_get_value }, { 'U', 200, "system-identifier", &instance_config.system_identifier, SOURCE_FILE_STRICT, (OptionSource)0, OPTION_INSTANCE_GROUP, 0, option_get_value }, #if PG_VERSION_NUM >= 110000 { 'u', 201, "xlog-seg-size", &instance_config.xlog_seg_size, SOURCE_FILE_STRICT, 0, OPTION_INSTANCE_GROUP, 0, option_get_value }, #endif { 's', 'E', "external-dirs", &instance_config.external_dir_str, SOURCE_CMD, (OptionSource)0, OPTION_INSTANCE_GROUP, 0, option_get_value }, /* Connection options */ { 's', 'd', "pgdatabase", &instance_config.conn_opt.pgdatabase, SOURCE_CMD, (OptionSource)0, OPTION_CONN_GROUP, 0, option_get_value }, { 's', 'h', "pghost", &instance_config.conn_opt.pghost, SOURCE_CMD, (OptionSource)0, OPTION_CONN_GROUP, 0, option_get_value }, { 's', 'p', "pgport", &instance_config.conn_opt.pgport, SOURCE_CMD, (OptionSource)0, OPTION_CONN_GROUP, 0, option_get_value }, { 's', 'U', "pguser", &instance_config.conn_opt.pguser, SOURCE_CMD, (OptionSource)0, OPTION_CONN_GROUP, 0, option_get_value }, /* Archive options */ { 'u', 207, "archive-timeout", &instance_config.archive_timeout, SOURCE_CMD, SOURCE_DEFAULT, OPTION_ARCHIVE_GROUP, OPTION_UNIT_S, option_get_value }, { 's', 208, "archive-host", &instance_config.archive.host, SOURCE_CMD, (OptionSource)0, OPTION_ARCHIVE_GROUP, 0, option_get_value }, { 's', 209, "archive-port", &instance_config.archive.port, SOURCE_CMD, (OptionSource)0, OPTION_ARCHIVE_GROUP, 0, option_get_value }, { 's', 210, "archive-user", &instance_config.archive.user, SOURCE_CMD, (OptionSource)0, OPTION_ARCHIVE_GROUP, 0, option_get_value }, { 's', 211, "restore-command", &instance_config.restore_command, SOURCE_CMD, SOURCE_DEFAULT, OPTION_ARCHIVE_GROUP, 0, option_get_value }, /* Logging options */ { 'f', 212, "log-level-console", (void *)assign_log_level_console, SOURCE_CMD, (OptionSource)0, OPTION_LOG_GROUP, 0, get_log_level_console }, { 'f', 213, "log-level-file", (void *)assign_log_level_file, SOURCE_CMD, (OptionSource)0, OPTION_LOG_GROUP, 0, get_log_level_file }, { 's', 214, "log-filename", &instance_config.logger.log_filename, SOURCE_CMD, (OptionSource)0, OPTION_LOG_GROUP, 0, option_get_value }, { 's', 215, "error-log-filename", &instance_config.logger.error_log_filename, SOURCE_CMD, (OptionSource)0, OPTION_LOG_GROUP, 0, option_get_value }, { 's', 216, "log-directory", &instance_config.logger.log_directory, SOURCE_CMD, (OptionSource)0, OPTION_LOG_GROUP, 0, option_get_value }, { 'U', 217, "log-rotation-size", &instance_config.logger.log_rotation_size, SOURCE_CMD, SOURCE_DEFAULT, OPTION_LOG_GROUP, OPTION_UNIT_KB, option_get_value }, { 'U', 218, "log-rotation-age", &instance_config.logger.log_rotation_age, SOURCE_CMD, SOURCE_DEFAULT, OPTION_LOG_GROUP, OPTION_UNIT_MS, option_get_value }, /* Retention options */ { 'u', 219, "retention-redundancy", &instance_config.retention_redundancy, SOURCE_CMD, (OptionSource)0, OPTION_RETENTION_GROUP, 0, option_get_value }, { 'u', 220, "retention-window", &instance_config.retention_window, SOURCE_CMD, (OptionSource)0, OPTION_RETENTION_GROUP, 0, option_get_value }, { 'u', 221, "wal-depth", &instance_config.wal_depth, SOURCE_CMD, (OptionSource)0, OPTION_RETENTION_GROUP, 0, option_get_value }, /* Compression options */ { 'f', 222, "compress-algorithm", (void *)assign_compress_alg, SOURCE_CMD, (OptionSource)0, OPTION_COMPRESS_GROUP, 0, get_compress_alg }, { 'u', 223, "compress-level", &instance_config.compress_level, SOURCE_CMD, (OptionSource)0, OPTION_COMPRESS_GROUP, 0, option_get_value }, /* Remote backup options */ { 's', 224, "remote-proto", &instance_config.remote.proto, SOURCE_CMD, (OptionSource)0, OPTION_REMOTE_GROUP, 0, option_get_value }, { 's', 225, "remote-host", &instance_config.remote.host, SOURCE_CMD, (OptionSource)0, OPTION_REMOTE_GROUP, 0, option_get_value }, { 's', 226, "remote-port", &instance_config.remote.port, SOURCE_CMD, (OptionSource)0, OPTION_REMOTE_GROUP, 0, option_get_value }, { 's', 227, "remote-path", &instance_config.remote.path, SOURCE_CMD, (OptionSource)0, OPTION_REMOTE_GROUP, 0, option_get_value }, { 's', 228, "remote-user", &instance_config.remote.user, SOURCE_CMD, (OptionSource)0, OPTION_REMOTE_GROUP, 0, option_get_value }, { 's', 229, "ssh-options", &instance_config.remote.ssh_options, SOURCE_CMD, (OptionSource)0, OPTION_REMOTE_GROUP, 0, option_get_value }, { 's', 230, "ssh-config", &instance_config.remote.ssh_config, SOURCE_CMD, (OptionSource)0, OPTION_REMOTE_GROUP, 0, option_get_value }, { 's', 231, "remote-libpath", &instance_config.remote.libpath, SOURCE_CMD, (OptionSource)0, OPTION_REMOTE_GROUP, 0, option_get_value }, { 0 } }; /* An instance configuration with default options */ InstanceConfig instance_config; static PQExpBufferData show_buf; static int32 json_level = 0; static const char *current_group = NULL; /* * Show configure options including default values. */ void do_show_config(void) { int i; show_configure_start(); for (i = 0; instance_options[i].type; i++) { if (show_format == SHOW_PLAIN) show_configure_plain(&instance_options[i]); else show_configure_json(&instance_options[i]); } show_configure_end(); } /* * Save configure options into BACKUP_CATALOG_CONF_FILE. Do not save default * values into the file. */ void do_set_config(bool missing_ok) { char path[MAXPGPATH]; char path_temp[MAXPGPATH]; FILE *fp; int i; int nRet = 0; join_path_components(path, backup_instance_path, BACKUP_CATALOG_CONF_FILE); nRet = snprintf_s(path_temp, sizeof(path_temp), sizeof(path_temp) - 1, "%s.tmp", path); securec_check_ss_c(nRet, "\0", "\0"); if (!missing_ok && !fileExists(path, FIO_LOCAL_HOST)) elog(ERROR, "Configuration file \"%s\" doesn't exist", path); fp = fopen(path_temp, "wt"); if (fp == NULL) elog(ERROR, "Cannot create configuration file \"%s\": %s", BACKUP_CATALOG_CONF_FILE, strerror(errno)); current_group = NULL; for (i = 0; instance_options[i].type; i++) { ConfigOption *opt = &instance_options[i]; char *value; /* Save only options from command line */ if (opt->source != SOURCE_CMD && /* ...or options from the previous configure file */ opt->source != SOURCE_FILE && opt->source != SOURCE_FILE_STRICT) continue; value = opt->get_value(opt); if (value == NULL) continue; if (current_group == NULL || strcmp(opt->group, current_group) != 0) { current_group = opt->group; fprintf(fp, "# %s\n", current_group); } if (strchr(value, ' ')) fprintf(fp, "%s = '%s'\n", opt->lname, value); else fprintf(fp, "%s = %s\n", opt->lname, value); pfree(value); } fclose(fp); if (rename(path_temp, path) < 0) { int errno_temp = errno; unlink(path_temp); elog(ERROR, "Cannot rename configuration file \"%s\" to \"%s\": %s", path_temp, path, strerror(errno_temp)); } } void init_config(InstanceConfig *config, const char *instance_name) { errno_t rc = 0; rc = memset_s(config, sizeof(InstanceConfig),0, sizeof(InstanceConfig)); securec_check(rc, "\0", "\0"); config->name = pgut_strdup(instance_name); /* * Starting from PostgreSQL 11 WAL segment size may vary. Prior to * PostgreSQL 10 xlog_seg_size is equal to XLOG_SEG_SIZE. */ #if PG_VERSION_NUM >= 110000 config->xlog_seg_size = 0; #else config->xlog_seg_size = XLOG_SEG_SIZE; #endif config->archive_timeout = ARCHIVE_TIMEOUT_DEFAULT; /* Copy logger defaults */ config->logger = logger_config; config->retention_redundancy = RETENTION_REDUNDANCY_DEFAULT; config->retention_window = RETENTION_WINDOW_DEFAULT; config->wal_depth = 0; config->compress_alg = COMPRESS_ALG_DEFAULT; config->compress_level = COMPRESS_LEVEL_DEFAULT; config->remote.proto = (const char*)"ssh"; } /* * read instance config from file */ InstanceConfig * readInstanceConfigFile(const char *instance_name) { char path[MAXPGPATH]; InstanceConfig *instance = pgut_new(InstanceConfig); char *log_level_console = NULL; char *log_level_file = NULL; char *compress_alg = NULL; int parsed_options; int nRet = 0; ConfigOption instance_options[] = { /* Instance options */ { 's', 'D', "pgdata", &instance->pgdata, SOURCE_CMD, (OptionSource)0, OPTION_INSTANCE_GROUP, 0, option_get_value }, { 'U', 200, "system-identifier", &instance->system_identifier, SOURCE_FILE_STRICT, (OptionSource)0, OPTION_INSTANCE_GROUP, 0, option_get_value }, #if PG_VERSION_NUM >= 110000 { 'u', 201, "xlog-seg-size", &instance->xlog_seg_size, SOURCE_FILE_STRICT, 0, OPTION_INSTANCE_GROUP, 0, option_get_value }, #endif { 's', 'E', "external-dirs", &instance->external_dir_str, SOURCE_CMD, (OptionSource)0, OPTION_INSTANCE_GROUP, 0, option_get_value }, /* Connection options */ { 's', 'd', "pgdatabase", &instance->conn_opt.pgdatabase, SOURCE_CMD, (OptionSource)0, OPTION_CONN_GROUP, 0, option_get_value }, { 's', 'h', "pghost", &instance->conn_opt.pghost, SOURCE_CMD, (OptionSource)0, OPTION_CONN_GROUP, 0, option_get_value }, { 's', 'p', "pgport", &instance->conn_opt.pgport, SOURCE_CMD, (OptionSource)0, OPTION_CONN_GROUP, 0, option_get_value }, { 's', 'U', "pguser", &instance->conn_opt.pguser, SOURCE_CMD, (OptionSource)0, OPTION_CONN_GROUP, 0, option_get_value }, /* Archive options */ { 'u', 207, "archive-timeout", &instance->archive_timeout, SOURCE_CMD, SOURCE_DEFAULT, OPTION_ARCHIVE_GROUP, OPTION_UNIT_S, option_get_value }, { 's', 208, "archive-host", &instance_config.archive.host, SOURCE_CMD, (OptionSource)0, OPTION_ARCHIVE_GROUP, 0, option_get_value }, { 's', 209, "archive-port", &instance_config.archive.port, SOURCE_CMD, (OptionSource)0, OPTION_ARCHIVE_GROUP, 0, option_get_value }, { 's', 210, "archive-user", &instance_config.archive.user, SOURCE_CMD, (OptionSource)0, OPTION_ARCHIVE_GROUP, 0, option_get_value }, { 's', 211, "restore-command", &instance->restore_command, SOURCE_CMD, (OptionSource)0, OPTION_ARCHIVE_GROUP, 0, option_get_value }, /* Instance options */ { 's', 'D', "pgdata", &instance->pgdata, SOURCE_CMD, (OptionSource)0, OPTION_INSTANCE_GROUP, 0, option_get_value }, /* Logging options */ { 's', 212, "log-level-console", &log_level_console, SOURCE_CMD, (OptionSource)0, OPTION_LOG_GROUP, 0, option_get_value }, { 's', 213, "log-level-file", &log_level_file, SOURCE_CMD, (OptionSource)0, OPTION_LOG_GROUP, 0, option_get_value }, { 's', 214, "log-filename", &instance->logger.log_filename, SOURCE_CMD, (OptionSource)0, OPTION_LOG_GROUP, 0, option_get_value }, { 's', 215, "error-log-filename", &instance->logger.error_log_filename, SOURCE_CMD, (OptionSource)0, OPTION_LOG_GROUP, 0, option_get_value }, { 's', 216, "log-directory", &instance->logger.log_directory, SOURCE_CMD, (OptionSource)0, OPTION_LOG_GROUP, 0, option_get_value }, { 'U', 217, "log-rotation-size", &instance->logger.log_rotation_size, SOURCE_CMD, SOURCE_DEFAULT, OPTION_LOG_GROUP, OPTION_UNIT_KB, option_get_value }, { 'U', 218, "log-rotation-age", &instance->logger.log_rotation_age, SOURCE_CMD, SOURCE_DEFAULT, OPTION_LOG_GROUP, OPTION_UNIT_MS, option_get_value }, /* Retention options */ { 'u', 219, "retention-redundancy", &instance->retention_redundancy, SOURCE_CMD, (OptionSource)0, OPTION_RETENTION_GROUP, 0, option_get_value }, { 'u', 220, "retention-window", &instance->retention_window, SOURCE_CMD, (OptionSource)0, OPTION_RETENTION_GROUP, 0, option_get_value }, { 'u', 221, "wal-depth", &instance->wal_depth, SOURCE_CMD, (OptionSource)0, OPTION_RETENTION_GROUP, 0, option_get_value }, /* Compression options */ { 's', 222, "compress-algorithm", &compress_alg, SOURCE_CMD, (OptionSource)0, OPTION_LOG_GROUP, 0, option_get_value }, { 'u', 223, "compress-level", &instance->compress_level, SOURCE_CMD, (OptionSource)0, OPTION_COMPRESS_GROUP, 0, option_get_value }, /* Remote backup options */ { 's', 224, "remote-proto", &instance->remote.proto, SOURCE_CMD, (OptionSource)0, OPTION_REMOTE_GROUP, 0, option_get_value }, { 's', 225, "remote-host", &instance->remote.host, SOURCE_CMD, (OptionSource)0, OPTION_REMOTE_GROUP, 0, option_get_value }, { 's', 226, "remote-port", &instance->remote.port, SOURCE_CMD, (OptionSource)0, OPTION_REMOTE_GROUP, 0, option_get_value }, { 's', 227, "remote-path", &instance->remote.path, SOURCE_CMD, (OptionSource)0, OPTION_REMOTE_GROUP, 0, option_get_value }, { 's', 228, "remote-user", &instance->remote.user, SOURCE_CMD, (OptionSource)0, OPTION_REMOTE_GROUP, 0, option_get_value }, { 's', 229, "ssh-options", &instance->remote.ssh_options, SOURCE_CMD, (OptionSource)0, OPTION_REMOTE_GROUP, 0, option_get_value }, { 's', 230, "ssh-config", &instance->remote.ssh_config, SOURCE_CMD, (OptionSource)0, OPTION_REMOTE_GROUP, 0, option_get_value }, { 's', 231, "remote-libpath", &instance->remote.libpath, SOURCE_CMD, (OptionSource)0, OPTION_REMOTE_GROUP, 0, option_get_value }, { 0 } }; init_config(instance, instance_name); nRet = snprintf_s(instance->backup_instance_path,MAXPGPATH,MAXPGPATH - 1, "%s/%s/%s", backup_path, BACKUPS_DIR, instance_name); securec_check_ss_c(nRet, "\0", "\0"); canonicalize_path(instance->backup_instance_path); nRet = snprintf_s(instance->arclog_path,MAXPGPATH,MAXPGPATH - 1, "%s/%s/%s", backup_path, "wal", instance_name); securec_check_ss_c(nRet, "\0", "\0"); canonicalize_path(instance->arclog_path); join_path_components(path, instance->backup_instance_path, BACKUP_CATALOG_CONF_FILE); if (fio_access(path, F_OK, FIO_BACKUP_HOST) != 0) { elog(WARNING, "Control file \"%s\" doesn't exist", path); pfree(instance); return NULL; } parsed_options = config_read_opt(path, instance_options, WARNING, true, true); if (parsed_options == 0) { elog(WARNING, "Control file \"%s\" is empty", path); pfree(instance); return NULL; } if (log_level_console) instance->logger.log_level_console = parse_log_level(log_level_console); if (log_level_file) instance->logger.log_level_file = parse_log_level(log_level_file); if (compress_alg) instance->compress_alg = parse_compress_alg(compress_alg); #if PG_VERSION_NUM >= 110000 /* If for some reason xlog-seg-size is missing, then set it to 16MB */ if (!instance->xlog_seg_size) instance->xlog_seg_size = DEFAULT_XLOG_SEG_SIZE; #endif return instance; } static void assign_log_level_console(ConfigOption *opt, const char *arg) { instance_config.logger.log_level_console = parse_log_level(arg); } static void assign_log_level_file(ConfigOption *opt, const char *arg) { instance_config.logger.log_level_file = parse_log_level(arg); } static void assign_compress_alg(ConfigOption *opt, const char *arg) { instance_config.compress_alg = parse_compress_alg(arg); } static char * get_log_level_console(ConfigOption *opt) { return gs_pstrdup(deparse_log_level(instance_config.logger.log_level_console)); } static char * get_log_level_file(ConfigOption *opt) { return gs_pstrdup(deparse_log_level(instance_config.logger.log_level_file)); } static char * get_compress_alg(ConfigOption *opt) { return gs_pstrdup(deparse_compress_alg(instance_config.compress_alg)); } /* * Initialize configure visualization. */ static void show_configure_start(void) { initPQExpBuffer(&show_buf); if (show_format == SHOW_PLAIN) current_group = NULL; else { json_level = 0; json_add(&show_buf, JT_BEGIN_OBJECT, &json_level); } } /* * Finalize configure visualization. */ static void show_configure_end(void) { if (show_format == SHOW_PLAIN) current_group = NULL; else { json_add(&show_buf, JT_END_OBJECT, &json_level); appendPQExpBufferChar(&show_buf, '\n'); } fputs(show_buf.data, stdout); termPQExpBuffer(&show_buf); } /* * Plain output. */ static void show_configure_plain(ConfigOption *opt) { char *value; value = opt->get_value(opt); if (value == NULL) return; if (current_group == NULL || strcmp(opt->group, current_group) != 0) { current_group = opt->group; appendPQExpBuffer(&show_buf, "# %s\n", current_group); } appendPQExpBuffer(&show_buf, "%s = %s\n", opt->lname, value); pfree(value); } /* * Json output. */ static void show_configure_json(ConfigOption *opt) { char *value; value = opt->get_value(opt); if (value == NULL) return; json_add_value(&show_buf, opt->lname, value, json_level, true); pfree(value); }