aio_task_ptr copy_remote_files( const end_point& remote, std::string& source_dir, std::vector<std::string>& files, // empty for all std::string& dest_dir, bool overwrite, task_code callback_code, servicelet* owner, aio_handler callback, int hash /*= 0*/ ) { aio_task_ptr tsk(callback != nullptr ? static_cast<aio_task*>(new internal_use_only::service_aio_task(callback_code, owner, callback, hash)) : static_cast<aio_task*>(new aio_task_empty(callback_code, hash)) ); copy_remote_files(remote, source_dir, files, dest_dir, overwrite, tsk); return tsk; }
static void do_standby_clone(void) { PGconn *conn; PGresult *res; char sqlquery[QUERY_STR_LEN]; int r = 0; int i; bool pg_dir = false; char master_data_directory[MAXLEN]; char master_config_file[MAXLEN]; char master_hba_file[MAXLEN]; char master_ident_file[MAXLEN]; char master_control_file[MAXLEN]; char local_control_file[MAXLEN]; const char *first_wal_segment = NULL; const char *last_wal_segment = NULL; char master_version[MAXVERSIONSTR]; /* if dest_dir hasn't been provided, initialize to current directory */ if (dest_dir == NULL) { dest_dir = malloc(5); strcpy(dest_dir, "."); } /* Check this directory could be used as a PGDATA dir */ switch (check_dir(dest_dir)) { case 0: /* dest_dir not there, must create it */ if (verbose) printf(_("creating directory %s ... "), dest_dir); fflush(stdout); if (!create_directory(dest_dir)) { fprintf(stderr, _("%s: couldn't create directory %s ... "), progname, dest_dir); return; } break; case 1: /* Present but empty, fix permissions and use it */ if (verbose) printf(_("fixing permissions on existing directory %s ... "), dest_dir); fflush(stdout); if (!set_directory_permissions(dest_dir)) { fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"), progname, dest_dir, strerror(errno)); return; } break; case 2: /* Present and not empty */ fprintf(stderr, _("%s: directory \"%s\" exists but is not empty\n"), progname, dest_dir); pg_dir = is_pg_dir(dest_dir); if (pg_dir && !force) { fprintf(stderr, _("\nThis looks like a PostgreSQL directroy.\n" "If you are sure you want to clone here, " "please check there is no PostgreSQL server " "running and use the --force option\n")); return; } else if (pg_dir && force) { /* Let it continue */ break; } else return; default: /* Trouble accessing directory */ fprintf(stderr, _("%s: could not access directory \"%s\": %s\n"), progname, dest_dir, strerror(errno)); } /* Connection parameters for master only */ keywords[0] = "host"; values[0] = host; keywords[1] = "port"; values[1] = masterport; /* We need to connect to check configuration and start a backup */ conn = PQconnectdbParams(keywords, values, true); if (!conn) { fprintf(stderr, _("%s: could not connect to master\n"), progname); return; } /* primary should be v9 or better */ pg_version(conn, master_version); if (strcmp(master_version, "") == 0) { PQfinish(conn); fprintf(stderr, _("%s needs master to be PostgreSQL 9.0 or better\n"), progname); return; } /* Check we are cloning a primary node */ if (is_standby(conn)) { PQfinish(conn); fprintf(stderr, "\nThe command should clone a primary node\n"); return; } /* And check if it is well configured */ if (!guc_setted(conn, "wal_level", "=", "hot_standby")) { PQfinish(conn); fprintf(stderr, _("%s needs parameter 'wal_level' to be set to 'hot_standby'\n"), progname); return; } if (!guc_setted(conn, "wal_keep_segments", ">=", wal_keep_segments)) { PQfinish(conn); fprintf(stderr, _("%s needs parameter 'wal_keep_segments' to be set to %s or greater\n"), wal_keep_segments, progname); return; } if (!guc_setted(conn, "archive_mode", "=", "on")) { PQfinish(conn); fprintf(stderr, _("%s needs parameter 'archive_mode' to be set to 'on'\n"), progname); return; } if (verbose) 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')"); res = PQexec(conn, sqlquery); if (PQresultStatus(res) != PGRES_TUPLES_OK) { fprintf(stderr, "Can't get info about tablespaces: %s\n", PQerrorMessage(conn)); PQclear(res); PQfinish(conn); return; } for (i = 0; i < PQntuples(res); i++) { char *tblspc_dir = NULL; strcpy(tblspc_dir, PQgetvalue(res, i, 0)); /* Check this directory could be used as a PGDATA dir */ switch (check_dir(tblspc_dir)) { case 0: /* tblspc_dir not there, must create it */ if (verbose) printf(_("creating directory \"%s\"... "), tblspc_dir); fflush(stdout); if (!create_directory(tblspc_dir)) { fprintf(stderr, _("%s: couldn't create directory \"%s\"... "), progname, tblspc_dir); PQclear(res); PQfinish(conn); return; } break; case 1: /* Present but empty, fix permissions and use it */ if (verbose) printf(_("fixing permissions on existing directory \"%s\"... "), tblspc_dir); fflush(stdout); if (!set_directory_permissions(tblspc_dir)) { fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"), progname, tblspc_dir, strerror(errno)); PQclear(res); PQfinish(conn); return; } break; case 2: /* Present and not empty */ if (!force) { fprintf(stderr, _("%s: directory \"%s\" exists but is not empty\n"), progname, tblspc_dir); PQclear(res); PQfinish(conn); return; } default: /* Trouble accessing directory */ fprintf(stderr, _("%s: could not access directory \"%s\": %s\n"), progname, tblspc_dir, strerror(errno)); PQclear(res); PQfinish(conn); return; } } fprintf(stderr, "Starting backup...\n"); /* Get the data directory full path and the configuration files location */ sprintf(sqlquery, "SELECT name, setting " " FROM pg_settings " " WHERE name IN ('data_directory', 'config_file', 'hba_file', 'ident_file')"); res = PQexec(conn, sqlquery); if (PQresultStatus(res) != PGRES_TUPLES_OK) { fprintf(stderr, "Can't get info about data directory and configuration files: %s\n", PQerrorMessage(conn)); PQclear(res); PQfinish(conn); return; } for (i = 0; i < PQntuples(res); i++) { if (strcmp(PQgetvalue(res, i, 0), "data_directory") == 0) strcpy(master_data_directory, PQgetvalue(res, i, 1)); else if (strcmp(PQgetvalue(res, i, 0), "config_file") == 0) strcpy(master_config_file, PQgetvalue(res, i, 1)); else if (strcmp(PQgetvalue(res, i, 0), "hba_file") == 0) strcpy(master_hba_file, PQgetvalue(res, i, 1)); else if (strcmp(PQgetvalue(res, i, 0), "ident_file") == 0) strcpy(master_ident_file, PQgetvalue(res, i, 1)); else fprintf(stderr, _("uknown parameter: %s"), PQgetvalue(res, i, 0)); } PQclear(res); /* * 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)); res = PQexec(conn, sqlquery); if (PQresultStatus(res) != PGRES_TUPLES_OK) { fprintf(stderr, "Can't start backup: %s\n", PQerrorMessage(conn)); PQclear(res); PQfinish(conn); return; } first_wal_segment = PQgetvalue(res, 0, 0); PQclear(res); /* * 1) first move global/pg_control * * 2) then move data_directory ommiting the files we have already moved and pg_xlog * content * * 3) finally We need to backup configuration files (that could be on other directories, debian * like systems likes to do that), so look at config_file, hba_file and ident_file but we * can omit external_pid_file ;) * * On error we need to return but before that execute pg_stop_backup() */ /* 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); if (!create_directory(local_control_file)) { fprintf(stderr, _("%s: couldn't create directory %s ... "), progname, dest_dir); goto stop_backup; } r = copy_remote_files(host, remote_user, master_control_file, local_control_file, false); if (r != 0) goto stop_backup; r = copy_remote_files(host, remote_user, master_data_directory, dest_dir, true); if (r != 0) goto stop_backup; /* * Copy tablespace locations, i'm doing this separately because i couldn't 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')"); res = PQexec(conn, sqlquery); if (PQresultStatus(res) != PGRES_TUPLES_OK) { fprintf(stderr, "Can't get info about tablespaces: %s\n", PQerrorMessage(conn)); PQclear(res); goto stop_backup; } for (i = 0; i < PQntuples(res); i++) { r = copy_remote_files(host, remote_user, PQgetvalue(res, i, 0), PQgetvalue(res, i, 0), true); if (r != 0) goto stop_backup; } r = copy_remote_files(host, remote_user, master_config_file, dest_dir, false); if (r != 0) goto stop_backup; r = copy_remote_files(host, remote_user, master_hba_file, dest_dir, false); if (r != 0) goto stop_backup; r = copy_remote_files(host, remote_user, master_ident_file, dest_dir, false); if (r != 0) goto stop_backup; stop_backup: /* inform the master that we have finished the backup */ conn = PQconnectdbParams(keywords, values, true); if (!conn) { fprintf(stderr, _("%s: could not connect to master\n"), progname); return; } fprintf(stderr, "Finishing backup...\n"); sprintf(sqlquery, "SELECT pg_xlogfile_name(pg_stop_backup())"); res = PQexec(conn, sqlquery); if (PQresultStatus(res) != PGRES_TUPLES_OK) { fprintf(stderr, "Can't stop backup: %s\n", PQerrorMessage(conn)); PQclear(res); PQfinish(conn); return; } last_wal_segment = PQgetvalue(res, 0, 0); PQclear(res); PQfinish(conn); /* Now, if the rsync failed then exit */ if (r != 0) return; if (verbose) printf(_("%s requires primary to keep WAL files %s until at least %s\n"), progname, first_wal_segment, last_wal_segment); /* 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); if (!create_directory(local_control_file)) { fprintf(stderr, _("%s: couldn't create directory %s, you will need to do it manually...\n"), progname, dest_dir); } /* Finally, write the recovery.conf file */ create_recovery_file(dest_dir); /* We don't start the service because we still may want to move the directory */ return; }