diff --git a/Makefile b/Makefile index 9c5c9758..dccbf2cf 100644 --- a/Makefile +++ b/Makefile @@ -4,8 +4,10 @@ # Copyright (c) 2ndQuadrant, 2010 # Copyright (c) Heroku, 2010 -repmgrd_OBJS = dbutils.o config.o repmgrd.o -repmgr_OBJS = dbutils.o check_dir.o config.o repmgr.o +repmgrd_OBJS = dbutils.o config.o repmgrd.o strutil.o +repmgr_OBJS = dbutils.o check_dir.o config.o repmgr.o strutil.o + +DATA = repmgr.sql uninstall_repmgr.sql PG_CPPFLAGS = -I$(libpq_srcdir) PG_LIBS = $(libpq_pgport) diff --git a/check_dir.c b/check_dir.c index 7296261d..e0220b7b 100644 --- a/check_dir.c +++ b/check_dir.c @@ -1,6 +1,8 @@ /* * check_dir.c + * * Copyright (c) 2ndQuadrant, 2010 + * Copyright (c) Heroku, 2010 * * Directories management functions */ @@ -12,9 +14,12 @@ #include #include +/* NB: postgres_fe must be included BEFORE check_dir */ #include "postgres_fe.h" #include "check_dir.h" +#include "strutil.h" + static int mkdir_p(char *path, mode_t omode); @@ -207,10 +212,11 @@ mkdir_p(char *path, mode_t omode) bool is_pg_dir(char *dir) { - char path[8192]; - struct stat sb; + const size_t buf_sz = 8192; + char path[buf_sz]; + struct stat sb; - sprintf(path, "%s/PG_VERSION", dir); + xsnprintf(path, buf_sz, "%s/PG_VERSION", dir); return (stat(path, &sb) == 0) ? true : false; } diff --git a/config.c b/config.c index 4c4ecdb6..6ae49e5b 100644 --- a/config.c +++ b/config.c @@ -1,12 +1,16 @@ /* * config.c + * * Copyright (c) 2ndQuadrant, 2010 + * Copyright (c) Heroku, 2010 * * Functions to parse the config file */ #include "repmgr.h" +#include "strutil.h" + void parse_config(const char *config_file, char *cluster_name, int *node, char *conninfo) diff --git a/dbutils.c b/dbutils.c index bec62d66..e3f5577f 100644 --- a/dbutils.c +++ b/dbutils.c @@ -7,6 +7,7 @@ */ #include "repmgr.h" +#include "strutil.h" PGconn * establishDBConnection(const char *conninfo, const bool exit_on_error) @@ -66,11 +67,13 @@ is_standby(PGconn *conn) char * pg_version(PGconn *conn) { - PGresult *res; - char *major_version; + PGresult *res; - int major_version1; - char *major_version2; + const size_t major_version_sz = 10; + char *major_version; + + int major_version1; + char *major_version2; res = PQexec(conn, "WITH pg_version(ver) AS " @@ -90,12 +93,13 @@ pg_version(PGconn *conn) major_version2 = PQgetvalue(res, 0, 1); PQclear(res); - major_version = malloc(10); + major_version = malloc(major_version_sz); if (major_version1 >= 9) { /* form a major version string */ - sprintf(major_version, "%d.%s", major_version1, major_version2); + xsnprintf(major_version, major_version_sz, "%d.%s", + major_version1, major_version2); } else strcpy(major_version, ""); @@ -109,9 +113,9 @@ guc_setted(PGconn *conn, const char *parameter, const char *op, const char *value) { PGresult *res; - char sqlquery[8192]; + char sqlquery[QUERY_STR_LEN]; - sprintf(sqlquery, "SELECT true FROM pg_settings " + sqlquery_snprintf(sqlquery, "SELECT true FROM pg_settings " " WHERE name = '%s' AND setting %s '%s'", parameter, op, value); @@ -139,9 +143,9 @@ get_cluster_size(PGconn *conn) { PGresult *res; const char *size; - char sqlquery[8192]; + char sqlquery[QUERY_STR_LEN]; - sprintf(sqlquery, + sqlquery_snprintf(sqlquery, "SELECT pg_size_pretty(SUM(pg_database_size(oid))::bigint) " " FROM pg_database "); @@ -169,12 +173,12 @@ getMasterConnection(PGconn *standby_conn, int id, char *cluster, PGconn *master_conn = NULL; PGresult *res1; PGresult *res2; - char sqlquery[8192]; + char sqlquery[QUERY_STR_LEN]; char master_conninfo[8192]; int i; /* find all nodes belonging to this cluster */ - sprintf(sqlquery, "SELECT * FROM repmgr_%s.repl_nodes " + sqlquery_snprintf(sqlquery, "SELECT * FROM repmgr_%s.repl_nodes " " WHERE cluster = '%s' and id <> %d", cluster, cluster, id); diff --git a/repmgr.c b/repmgr.c index 950c6761..61d1e85f 100644 --- a/repmgr.c +++ b/repmgr.c @@ -1,6 +1,8 @@ /* * repmgr.c + * * Copyright (c) 2ndQuadrant, 2010 + * Copyright (c) Heroku, 2010 * * Command interpreter for the repmgr * This module is a command-line utility to easily setup a cluster of @@ -19,6 +21,7 @@ #include #include "check_dir.h" +#include "strutil.h" #define RECOVERY_FILE "recovery.conf" #define RECOVERY_DONE_FILE "recovery.done" @@ -30,7 +33,6 @@ #define STANDBY_PROMOTE 4 #define STANDBY_FOLLOW 5 -#define QUERY_STR_LEN 8192 static void help(const char *progname); static bool create_recovery_file(const char *data_dir); @@ -227,8 +229,10 @@ main(int argc, char **argv) if (config_file == NULL) { - config_file = malloc(5 + sizeof(CONFIG_FILE)); - sprintf(config_file, "./%s", CONFIG_FILE); + const int buf_sz = 3 + sizeof(CONFIG_FILE); + + config_file = malloc(buf_sz); + xsnprintf(config_file, buf_sz, "./%s", CONFIG_FILE); } if (wal_keep_segments == NULL) @@ -329,7 +333,8 @@ do_master_register(void) } /* Check if there is a schema for this cluster */ - sprintf(sqlquery, "SELECT 1 FROM pg_namespace WHERE nspname = 'repmgr_%s'", + sqlquery_snprintf(sqlquery, + "SELECT 1 FROM pg_namespace WHERE nspname = 'repmgr_%s'", myClusterName); res = PQexec(conn, sqlquery); if (PQresultStatus(res) != PGRES_TUPLES_OK) @@ -357,7 +362,7 @@ do_master_register(void) if (!schema_exists) { /* ok, create the schema */ - sprintf(sqlquery, "CREATE SCHEMA repmgr_%s", myClusterName); + sqlquery_snprintf(sqlquery, "CREATE SCHEMA repmgr_%s", myClusterName); if (!PQexec(conn, sqlquery)) { fprintf(stderr, "Cannot create the schema repmgr_%s: %s\n", @@ -367,7 +372,7 @@ do_master_register(void) } /* ... the tables */ - sprintf(sqlquery, "CREATE TABLE repmgr_%s.repl_nodes ( " + sqlquery_snprintf(sqlquery, "CREATE TABLE repmgr_%s.repl_nodes ( " " id integer primary key, " " cluster text not null, " " conninfo text not null)", myClusterName); @@ -380,7 +385,7 @@ do_master_register(void) return; } - sprintf(sqlquery, "CREATE TABLE repmgr_%s.repl_monitor ( " + sqlquery_snprintf(sqlquery, "CREATE TABLE repmgr_%s.repl_monitor ( " " primary_node INTEGER NOT NULL, " " standby_node INTEGER NOT NULL, " " last_monitor_time TIMESTAMP WITH TIME ZONE NOT NULL, " @@ -399,7 +404,7 @@ do_master_register(void) } /* and the view */ - sprintf(sqlquery, "CREATE VIEW repmgr_%s.repl_status AS " + sqlquery_snprintf(sqlquery, "CREATE VIEW repmgr_%s.repl_status AS " " WITH monitor_info AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY primary_node, standby_node " " ORDER BY last_monitor_time desc) " " FROM repmgr_%s.repl_monitor) " @@ -435,7 +440,7 @@ do_master_register(void) /* Now register the master */ if (force) { - sprintf(sqlquery, "DELETE FROM repmgr_%s.repl_nodes " + sqlquery_snprintf(sqlquery, "DELETE FROM repmgr_%s.repl_nodes " " WHERE id = %d", myClusterName, myLocalId); @@ -448,7 +453,7 @@ do_master_register(void) } } - sprintf(sqlquery, "INSERT INTO repmgr_%s.repl_nodes " + sqlquery_snprintf(sqlquery, "INSERT INTO repmgr_%s.repl_nodes " "VALUES (%d, '%s', '%s')", myClusterName, myLocalId, myClusterName, conninfo); @@ -514,7 +519,7 @@ do_standby_register(void) } /* Check if there is a schema for this cluster */ - sprintf(sqlquery, "SELECT 1 FROM pg_namespace WHERE nspname = 'repmgr_%s'", + sqlquery_snprintf(sqlquery, "SELECT 1 FROM pg_namespace WHERE nspname = 'repmgr_%s'", myClusterName); res = PQexec(conn, sqlquery); if (PQresultStatus(res) != PGRES_TUPLES_OK) @@ -566,7 +571,7 @@ do_standby_register(void) /* Now register the standby */ if (force) { - sprintf(sqlquery, "DELETE FROM repmgr_%s.repl_nodes " + sqlquery_snprintf(sqlquery, "DELETE FROM repmgr_%s.repl_nodes " " WHERE id = %d", myClusterName, myLocalId); @@ -580,7 +585,7 @@ do_standby_register(void) } } - sprintf(sqlquery, "INSERT INTO repmgr_%s.repl_nodes " + sqlquery_snprintf(sqlquery, "INSERT INTO repmgr_%s.repl_nodes " "VALUES (%d, '%s', '%s')", myClusterName, myLocalId, myClusterName, conninfo); @@ -744,7 +749,7 @@ do_standby_clone(void) printf(_("Succesfully connected to primary. Current installation size is %s\n"), get_cluster_size(conn)); /* Check if the tablespace locations exists and that we can write to them */ - sprintf(sqlquery, "select spclocation from pg_tablespace where spcname not in ('pg_default', 'pg_global')"); + sqlquery_snprintf(sqlquery, "select spclocation from pg_tablespace where spcname not in ('pg_default', 'pg_global')"); res = PQexec(conn, sqlquery); if (PQresultStatus(res) != PGRES_TUPLES_OK) { @@ -819,7 +824,7 @@ do_standby_clone(void) fprintf(stderr, "Starting backup...\n"); /* Get the data directory full path and the configuration files location */ - sprintf(sqlquery, "SELECT name, setting " + sqlquery_snprintf(sqlquery, "SELECT name, setting " " FROM pg_settings " " WHERE name IN ('data_directory', 'config_file', 'hba_file', 'ident_file')"); res = PQexec(conn, sqlquery); @@ -849,7 +854,7 @@ do_standby_clone(void) * inform the master we will start a backup and get the first XLog filename * so we can say to the user we need those files */ - sprintf(sqlquery, "SELECT pg_xlogfile_name(pg_start_backup('repmgr_standby_clone_%ld'))", time(NULL)); + sqlquery_snprintf(sqlquery, "SELECT pg_xlogfile_name(pg_start_backup('repmgr_standby_clone_%ld'))", time(NULL)); res = PQexec(conn, sqlquery); if (PQresultStatus(res) != PGRES_TUPLES_OK) { @@ -876,9 +881,9 @@ do_standby_clone(void) */ /* need to create the global sub directory */ - sprintf(master_control_file, "%s/global/pg_control", - master_data_directory); - sprintf(local_control_file, "%s/global", dest_dir); + maxlen_snprintf(master_control_file, "%s/global/pg_control", + master_data_directory); + maxlen_snprintf(local_control_file, "%s/global", dest_dir); if (!create_directory(local_control_file)) { fprintf(stderr, _("%s: couldn't create directory %s ... "), @@ -901,7 +906,7 @@ do_standby_clone(void) * find and appropiate rsync option but besides we could someday make all * these rsync happen concurrently */ - sprintf(sqlquery, "select spclocation from pg_tablespace where spcname not in ('pg_default', 'pg_global')"); + sqlquery_snprintf(sqlquery, "select spclocation from pg_tablespace where spcname not in ('pg_default', 'pg_global')"); res = PQexec(conn, sqlquery); if (PQresultStatus(res) != PGRES_TUPLES_OK) { @@ -944,7 +949,7 @@ stop_backup: fprintf(stderr, "Finishing backup...\n"); - sprintf(sqlquery, "SELECT pg_xlogfile_name(pg_stop_backup())"); + sqlquery_snprintf(sqlquery, "SELECT pg_xlogfile_name(pg_stop_backup())"); res = PQexec(conn, sqlquery); if (PQresultStatus(res) != PGRES_TUPLES_OK) { @@ -970,7 +975,7 @@ stop_backup: * We need to create the pg_xlog sub directory too, I'm reusing a variable * here. */ - sprintf(local_control_file, "%s/pg_xlog", dest_dir); + maxlen_snprintf(local_control_file, "%s/pg_xlog", dest_dir); if (!create_directory(local_control_file)) { fprintf(stderr, _("%s: couldn't create directory %s, you will need to do it manually...\n"), @@ -994,7 +999,7 @@ do_standby_promote(void) PGconn *conn; PGresult *res; char sqlquery[QUERY_STR_LEN]; - char script[QUERY_STR_LEN]; + char script[MAXLEN]; char myClusterName[MAXLEN]; int myLocalId = -1; @@ -1056,7 +1061,7 @@ do_standby_promote(void) printf(_("\n%s: Promoting standby...\n"), progname); /* Get the data directory full path and the last subdirectory */ - sprintf(sqlquery, "SELECT setting " + sqlquery_snprintf(sqlquery, "SELECT setting " " FROM pg_settings WHERE name = 'data_directory'"); res = PQexec(conn, sqlquery); if (PQresultStatus(res) != PGRES_TUPLES_OK) @@ -1071,12 +1076,12 @@ do_standby_promote(void) PQclear(res); PQfinish(conn); - sprintf(recovery_file_path, "%s/%s", data_dir, RECOVERY_FILE); - sprintf(recovery_done_path, "%s/%s", data_dir, RECOVERY_DONE_FILE); + maxlen_snprintf(recovery_file_path, "%s/%s", data_dir, RECOVERY_FILE); + maxlen_snprintf(recovery_done_path, "%s/%s", data_dir, RECOVERY_DONE_FILE); rename(recovery_file_path, recovery_done_path); /* We assume the pg_ctl script is in the PATH */ - sprintf(script, "pg_ctl -D %s -m fast restart", data_dir); + maxlen_snprintf(script, "pg_ctl -D %s -m fast restart", data_dir); r = system(script); if (r != 0) { @@ -1108,7 +1113,7 @@ do_standby_follow(void) PGconn *conn; PGresult *res; char sqlquery[QUERY_STR_LEN]; - char script[QUERY_STR_LEN]; + char script[MAXLEN]; char myClusterName[MAXLEN]; int myLocalId = -1; @@ -1206,7 +1211,7 @@ do_standby_follow(void) printf(_("\n%s: Changing standby's master...\n"), progname); /* Get the data directory full path */ - sprintf(sqlquery, "SELECT setting " + sqlquery_snprintf(sqlquery, "SELECT setting " " FROM pg_settings WHERE name = 'data_directory'"); res = PQexec(conn, sqlquery); if (PQresultStatus(res) != PGRES_TUPLES_OK) @@ -1227,7 +1232,7 @@ do_standby_follow(void) /* Finally, restart the service */ /* We assume the pg_ctl script is in the PATH */ - sprintf(script, "pg_ctl -D %s -m fast restart", data_dir); + maxlen_snprintf(script, "pg_ctl -D %s -m fast restart", data_dir); r = system(script); if (r != 0) { @@ -1282,7 +1287,7 @@ create_recovery_file(const char *data_dir) char recovery_file_path[MAXLEN]; char line[MAXLEN]; - sprintf(recovery_file_path, "%s/%s", data_dir, RECOVERY_FILE); + maxlen_snprintf(recovery_file_path, "%s/%s", data_dir, RECOVERY_FILE); recovery_file = fopen(recovery_file_path, "w"); if (recovery_file == NULL) @@ -1291,7 +1296,7 @@ create_recovery_file(const char *data_dir) return false; } - sprintf(line, "standby_mode = 'on'\n"); + maxlen_snprintf(line, "standby_mode = 'on'\n"); if (fputs(line, recovery_file) == EOF) { fprintf(stderr, "recovery file could not be written, it could be necesary to create it manually\n"); @@ -1299,7 +1304,7 @@ create_recovery_file(const char *data_dir) return false; } - sprintf(line, "primary_conninfo = 'host=%s port=%s'\n", host, + maxlen_snprintf(line, "primary_conninfo = 'host=%s port=%s'\n", host, ((masterport==NULL) ? "5432" : masterport)); if (fputs(line, recovery_file) == EOF) { @@ -1319,35 +1324,36 @@ static int copy_remote_files(char *host, char *remote_user, char *remote_path, char *local_path, bool is_directory) { - char script[QUERY_STR_LEN]; - char options[QUERY_STR_LEN]; - char host_string[QUERY_STR_LEN]; + char script[MAXLEN]; + char options[MAXLEN]; + char host_string[MAXLEN]; int r; - sprintf(options, "--archive --checksum --compress --progress --rsh=ssh"); + maxlen_snprintf(options, + "--archive --checksum --compress --progress --rsh=ssh"); if (force) strcat(options, " --delete"); if (remote_user == NULL) { - sprintf(host_string,"%s",host); + maxlen_snprintf(host_string, "%s", host); } else { - sprintf(host_string,"%s@%s",remote_user,host); + maxlen_snprintf(host_string,"%s@%s",remote_user,host); } if (is_directory) { strcat(options, " --exclude=pg_xlog* --exclude=pg_control --exclude=*.pid"); - sprintf(script, "rsync %s %s:%s/* %s", + maxlen_snprintf(script, "rsync %s %s:%s/* %s", options, host_string, remote_path, local_path); } else { - sprintf(script, "rsync %s %s:%s %s/.", - options, host_string, remote_path, local_path); + maxlen_snprintf(script, "rsync %s %s:%s %s/.", + options, host_string, remote_path, local_path); } if (verbose) diff --git a/repmgr.h b/repmgr.h index 4768f966..93e7ed49 100644 --- a/repmgr.h +++ b/repmgr.h @@ -1,6 +1,8 @@ /* - * dbutils.h + * repmgr.h + * * Copyright (c) 2ndQuadrant, 2010 + * Copyright (c) Heroku, 2010 * */ @@ -18,7 +20,6 @@ #define PRIMARY_MODE 0 #define STANDBY_MODE 1 -#define MAXLEN 80 #define CONFIG_FILE "repmgr.conf" #endif diff --git a/repmgrd.c b/repmgrd.c index a5e00ce8..d4f41131 100644 --- a/repmgrd.c +++ b/repmgrd.c @@ -14,6 +14,7 @@ #include #include "repmgr.h" +#include "strutil.h" #include "libpq/pqsignal.h" @@ -29,7 +30,7 @@ int primaryId; char primaryConninfo[MAXLEN]; PGconn *primaryConn; -char sqlquery[8192]; +char sqlquery[QUERY_STR_LEN]; const char *progname; @@ -121,8 +122,10 @@ main(int argc, char **argv) if (config_file == NULL) { - config_file = malloc(5 + sizeof(CONFIG_FILE)); - sprintf(config_file, "./%s", CONFIG_FILE); + const size_t buf_sz = 3 + sizeof(CONFIG_FILE); + + config_file = malloc(buf_sz); + xsnprintf(config_file, buf_sz, "./%s", CONFIG_FILE); } /* @@ -270,7 +273,7 @@ MonitorExecute(void) CancelQuery(); /* Get local xlog info */ - sprintf(sqlquery, + sqlquery_snprintf(sqlquery, "SELECT CURRENT_TIMESTAMP, pg_last_xlog_receive_location(), " "pg_last_xlog_replay_location()"); @@ -289,7 +292,7 @@ MonitorExecute(void) PQclear(res); /* Get primary xlog info */ - sprintf(sqlquery, "SELECT pg_current_xlog_location() "); + sqlquery_snprintf(sqlquery, "SELECT pg_current_xlog_location() "); res = PQexec(primaryConn, sqlquery); if (PQresultStatus(res) != PGRES_TUPLES_OK) @@ -310,7 +313,7 @@ MonitorExecute(void) /* * Build the SQL to execute on primary */ - sprintf(sqlquery, + sqlquery_snprintf(sqlquery, "INSERT INTO repmgr_%s.repl_monitor " "VALUES(%d, %d, '%s'::timestamp with time zone, " " '%s', '%s', " @@ -336,7 +339,7 @@ checkClusterConfiguration(void) { PGresult *res; - sprintf(sqlquery, "SELECT oid FROM pg_class " + sqlquery_snprintf(sqlquery, "SELECT oid FROM pg_class " " WHERE oid = 'repmgr_%s.repl_nodes'::regclass", myClusterName); res = PQexec(myLocalConn, sqlquery); @@ -374,7 +377,7 @@ checkNodeConfiguration(char *conninfo) PGresult *res; /* Check if we have my node information in repl_nodes */ - sprintf(sqlquery, "SELECT * FROM repmgr_%s.repl_nodes " + sqlquery_snprintf(sqlquery, "SELECT * FROM repmgr_%s.repl_nodes " " WHERE id = %d AND cluster = '%s' ", myClusterName, myLocalId, myClusterName); @@ -397,7 +400,7 @@ checkNodeConfiguration(char *conninfo) PQclear(res); /* Adding the node */ - sprintf(sqlquery, "INSERT INTO repmgr_%s.repl_nodes " + sqlquery_snprintf(sqlquery, "INSERT INTO repmgr_%s.repl_nodes " "VALUES (%d, '%s', '%s')", myClusterName, myLocalId, myClusterName, conninfo); diff --git a/strutil.c b/strutil.c new file mode 100644 index 00000000..6286c31e --- /dev/null +++ b/strutil.c @@ -0,0 +1,72 @@ +/* + * strutil.c + * + * Copyright (c) Heroku, 2010 + * + */ + +#include +#include +#include + +#include "strutil.h" + +static int xvsnprintf(char *str, size_t size, const char *format, va_list ap); + + +static int +xvsnprintf(char *str, size_t size, const char *format, va_list ap) +{ + int retval; + + retval = vsnprintf(str, size, format, ap); + + if (retval >= size) + { + fprintf(stderr, "Buffer not large enough to format entire string\n"); + exit(255); + } + + return retval; +} + + +int +xsnprintf(char *str, size_t size, const char *format, ...) +{ + va_list arglist; + int retval; + + va_start(arglist, format); + retval = xvsnprintf(str, size, format, arglist); + va_end(arglist); + + return retval; +} + + +int +sqlquery_snprintf(char *str, const char *format, ...) +{ + va_list arglist; + int retval; + + va_start(arglist, format); + retval = xvsnprintf(str, QUERY_STR_LEN, format, arglist); + va_end(arglist); + + return retval; +} + + +int maxlen_snprintf(char *str, const char *format, ...) +{ + va_list arglist; + int retval; + + va_start(arglist, format); + retval = xvsnprintf(str, MAXLEN, format, arglist); + va_end(arglist); + + return retval; +} diff --git a/strutil.h b/strutil.h new file mode 100644 index 00000000..c2f5d55c --- /dev/null +++ b/strutil.h @@ -0,0 +1,19 @@ +/* + * strutil.h + * + * Copyright (c) Heroku, 2010 + * + */ + +#ifndef _STRUTIL_H_ +#define _STRUTIL_H_ + +#define QUERY_STR_LEN 8192 +#define MAXLEN 80 + + +extern int xsnprintf(char *str, size_t size, const char *format, ...); +extern int sqlquery_snprintf(char *str, const char *format, ...); +extern int maxlen_snprintf(char *str, const char *format, ...); + +#endif /* _STRUTIL_H_ */