[glom/feature_backup2] PostgreSQL backups: Use .pgpass.
- From: Murray Cumming <murrayc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glom/feature_backup2] PostgreSQL backups: Use .pgpass.
- Date: Wed, 7 Jul 2010 07:24:22 +0000 (UTC)
commit f99ace4afa91b2938392f0974dddc24562026e6a
Author: Murray Cumming <murrayc murrayc com>
Date: Wed Jul 7 09:15:21 2010 +0200
PostgreSQL backups: Use .pgpass.
* glom/libglom/connectionpool_backends/postgres.[h|cc]:
* glom/libglom/connectionpool_backends/postgres_self.[h|cc]:
Move create_text_file() from PostgresSelf to Postgres and add an
only_for_current_user bool parameter, so we can use it in a
new save_password_to_pgpass() method.
save_backup(), convert_backup(): Temporarily store the password in
~/.pgpass, so pg_dump and pg_restore actually work.
ChangeLog | 12 ++
glom/libglom/connectionpool_backends/postgres.cc | 210 ++++++++++++++++++--
glom/libglom/connectionpool_backends/postgres.h | 28 ++-
.../connectionpool_backends/postgres_self.cc | 81 +--------
.../connectionpool_backends/postgres_self.h | 2 -
5 files changed, 231 insertions(+), 102 deletions(-)
---
diff --git a/ChangeLog b/ChangeLog
index e2a1ad5..686721d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,15 @@
+2010-07-07 Murray Cumming <murrayc murrayc com>
+
+ PostgreSQL backups: Use .pgpass.
+
+ * glom/libglom/connectionpool_backends/postgres.[h|cc]:
+ * glom/libglom/connectionpool_backends/postgres_self.[h|cc]:
+ Move create_text_file() from PostgresSelf to Postgres and add an
+ only_for_current_user bool parameter, so we can use it in a
+ new save_password_to_pgpass() method.
+ save_backup(), convert_backup(): Temporarily store the password in
+ ~/.pgpass, so pg_dump and pg_restore actually work.
+
2010-07-05 Murray Cumming <murrayc murrayc com>
Show progress in the UI during backup saving and restoring.
diff --git a/glom/libglom/connectionpool_backends/postgres.cc b/glom/libglom/connectionpool_backends/postgres.cc
index 5065d0b..20e0ec0 100644
--- a/glom/libglom/connectionpool_backends/postgres.cc
+++ b/glom/libglom/connectionpool_backends/postgres.cc
@@ -25,6 +25,7 @@
#include <libglom/spawn_with_feedback.h>
#include <libglom/utils.h>
#include <giomm.h>
+#include <glib/gstdio.h> /* For g_rename(). TODO: Wrap this in glibmm? */
#include <glibmm/i18n.h>
// Uncomment to see debug messages
@@ -478,7 +479,7 @@ std::string Postgres::get_path_to_postgres_executable(const std::string& program
// building postgres with another prefix than /local/pgsql.
gchar* installation_directory = g_win32_get_package_installation_directory_of_module(0);
std::string test;
-
+
try
{
test = Glib::build_filename(installation_directory, Glib::build_filename("bin", real_program));
@@ -522,6 +523,62 @@ Glib::ustring Postgres::port_as_string(int port_num)
return result;
}
+//Because ~/.pgpass is not an absolute path.
+static std::string get_absolute_pgpass_filepath()
+{
+ return Glib::build_filename(
+ Glib::get_home_dir(), ".pgpass");
+}
+
+bool Postgres::save_password_to_pgpass(const Glib::ustring username, const Glib::ustring& password, std::string& filepath_previous, std::string& filepath_original)
+{
+ //Initialize output variables:
+ filepath_previous.clear();
+ filepath_original.clear();
+
+ const std::string filepath_pgpass = get_absolute_pgpass_filepath();
+ filepath_original = filepath_pgpass;
+
+ //Move any existing file out of the way:
+ if(file_exists_filepath(filepath_pgpass))
+ {
+ std::cout << "DEBUG: File exists: " << filepath_pgpass << std::endl;
+ filepath_previous = filepath_pgpass + ".glombackup";
+ if(g_rename(filepath_pgpass.c_str(), filepath_previous.c_str()) != 0)
+ {
+ std::cerr << G_STRFUNC << "Could not rename file: from=" << filepath_pgpass << ", to=" << filepath_previous << std::endl;
+ return false;
+ }
+ }
+
+ //See http://www.postgresql.org/docs/8.4/static/libpq-pgpass.html
+ //TODO: Escape \ and : characters.
+ const Glib::ustring contents =
+ m_host + ":" + port_as_string(m_port) + ":*:" + username + ":" + password;
+
+ std::string uri;
+ try
+ {
+ uri = Glib::filename_to_uri(filepath_pgpass);
+ }
+ catch(const Glib::Error& ex)
+ {
+ std::cerr << G_STRFUNC << ": exception from Glib::filename_from_uri(): " << ex.what() << std::endl;
+ g_rename(filepath_previous.c_str(), filepath_pgpass.c_str());
+ return false;
+ }
+
+ const bool result = create_text_file(uri, contents, true /* current user only */);
+ if(!result)
+ {
+ std::cerr << G_STRFUNC << ": create_text_file() failed." << std::endl;
+ g_rename(filepath_previous.c_str(), filepath_pgpass.c_str());
+ return false;
+ }
+
+ return result;
+}
+
bool Postgres::save_backup(const SlotProgress& slot_progress, const Glib::ustring& username, const Glib::ustring& password, const Glib::ustring& database_name)
{
/* TODO:
@@ -557,12 +614,20 @@ bool Postgres::save_backup(const SlotProgress& slot_progress, const Glib::ustrin
return false;
}
- //TODO: Save the password to .pgpass
+ // Save the password to ~/.pgpass, because this is the only way to use
+ // pg_dump without it asking for the password:
+ std::string pgpass_backup, pgpass_original;
+ const bool pgpass_created = save_password_to_pgpass(username, password, pgpass_backup, pgpass_original);
+ if(!pgpass_created)
+ {
+ std::cerr << G_STRFUNC << ": save_password_to_pgpass() failed." << std::endl;
+ return false;
+ }
const std::string path_backup = get_self_hosting_backup_path(std::string(), true /* create parent directory if necessary */);
if(path_backup.empty())
return false;
-
+
// Make sure to use double quotes for the executable path, because the
// CreateProcess() API used on Windows does not support single quotes.
const std::string command_dump = "\"" + get_path_to_postgres_executable("pg_dump") + "\"" +
@@ -575,10 +640,17 @@ bool Postgres::save_backup(const SlotProgress& slot_progress, const Glib::ustrin
//std::cout << "DEBUG: command_dump=" << command_dump << std::endl;
-
- //TODO: Put the password in .pgpass
const bool result = Glom::Spawn::execute_command_line_and_wait(command_dump, slot_progress);
+
+ //Move the previously-existing .pgpass file back:
+ //TODO: Really, we should just edit the file instead of completely replacing it,
+ // because another application might try to edit it in the meantime.
+ if(!pgpass_backup.empty())
+ {
+ g_rename(pgpass_backup.c_str(), pgpass_original.c_str());
+ }
+
if(!result)
{
std::cerr << "Error while attempting to call pg_dump." << std::endl;
@@ -631,7 +703,17 @@ bool Postgres::convert_backup(const SlotProgress& slot_progress, const std::stri
std::cerr << G_STRFUNC << ": Backup file not found: " << path_backup << std::endl;
return false;
}
-
+
+ // Save the password to ~/.pgpass, because this is the only wayt to use
+ // pg_dump without it asking for the password:
+ std::string pgpass_backup, pgpass_original;
+ const bool pgpass_created = save_password_to_pgpass(username, password, pgpass_backup, pgpass_original);
+ if(!pgpass_created)
+ {
+ std::cerr << G_STRFUNC << ": save_password_to_pgpass() failed." << std::endl;
+ return false;
+ }
+
// Make sure to use double quotes for the executable path, because the
// CreateProcess() API used on Windows does not support single quotes.
const std::string command_restore = "\"" + get_path_to_postgres_executable("pg_restore") + "\"" +
@@ -642,10 +724,19 @@ bool Postgres::convert_backup(const SlotProgress& slot_progress, const std::stri
" " + path_backup;
std::cout << "DEBUG: command_restore=" << command_restore << std::endl;
-
+
//TODO: Put the password in .pgpass
const bool result = Glom::Spawn::execute_command_line_and_wait(command_restore, slot_progress);
+
+ //Move the previously-existing .pgpass file back:
+ //TODO: Really, we should just edit the file instead of completely replacing it,
+ // because another application might try to edit it in the meantime.
+ if(!pgpass_backup.empty())
+ {
+ g_rename(pgpass_backup.c_str(), pgpass_original.c_str());
+ }
+
if(!result)
{
std::cerr << "Error while attempting to call pg_restore." << std::endl;
@@ -668,7 +759,7 @@ std::string Postgres::get_self_hosting_path(bool create, const std::string& chil
}
catch(const Glib::Error& ex)
{
- std::cerr << G_STRFUNC << ": exception from Glib::build_filename(): " << ex.what() << std::endl;
+ std::cerr << G_STRFUNC << ": exception from Glib::build_filename(): " << ex.what() << std::endl;
}
if(file_exists_filepath(dbdir))
@@ -677,7 +768,7 @@ std::string Postgres::get_self_hosting_path(bool create, const std::string& chil
return std::string();
//Create the directory:
-
+
//std::cout << "debug: dbdir=" << dbdir << std::endl;
g_assert(!dbdir.empty());
@@ -698,7 +789,7 @@ std::string Postgres::get_self_hosting_data_path(bool create)
}
std::string Postgres::get_self_hosting_backup_path(const std::string& base_directory, bool create_parent_dir)
-{
+{
//This is a file, not a directory, so we don't use get_self_hosting_path("backup");
std::string dbdir;
if(base_directory.empty())
@@ -707,7 +798,7 @@ std::string Postgres::get_self_hosting_backup_path(const std::string& base_direc
{
dbdir = base_directory;
}
-
+
if(dbdir.empty())
return std::string();
@@ -717,7 +808,7 @@ std::string Postgres::get_self_hosting_backup_path(const std::string& base_direc
}
catch(const Glib::Error& ex)
{
- std::cerr << G_STRFUNC << ": exception from Glib::build_filename(): " << ex.what() << std::endl;
+ std::cerr << G_STRFUNC << ": exception from Glib::build_filename(): " << ex.what() << std::endl;
return std::string();
}
}
@@ -735,7 +826,7 @@ bool Postgres::create_directory_filepath(const std::string& filepath)
return false;
}
-
+
return true;
}
@@ -757,6 +848,99 @@ bool Postgres::file_exists_uri(const std::string& uri) const
return file && file->query_exists();
}
+
+
+bool Postgres::create_text_file(const std::string& file_uri, const std::string& contents, bool current_user_only)
+{
+ if(file_uri.empty())
+ return false;
+
+ Glib::RefPtr<Gio::File> file = Gio::File::create_for_uri(file_uri);
+ Glib::RefPtr<Gio::FileOutputStream> stream;
+
+ //Create the file if it does not already exist:
+#ifdef GLIBMM_EXCEPTIONS_ENABLED
+ try
+ {
+ if(file->query_exists())
+ {
+ if(current_user_only)
+ {
+ stream = file->replace(std::string() /* etag */, false /* make_backup */, Gio::FILE_CREATE_PRIVATE); //Instead of append_to().
+ }
+ else
+ {
+ stream = file->replace(); //Instead of append_to().
+ }
+ }
+ else
+ {
+ //By default files created are generally readable by everyone, but if we pass FILE_CREATE_PRIVATE in flags the file will be made readable only to the current user, to the level that is supported on the target filesystem.
+ if(current_user_only)
+ {
+ //TODO: Do we want to specify 0660 exactly? (means "this user and his group can read and write this non-executable file".)
+ stream = file->create_file(Gio::FILE_CREATE_PRIVATE);
+ }
+ else
+ {
+ stream = file->create_file();
+ }
+ }
+ }
+ catch(const Gio::Error& ex)
+ {
+#else
+ std::auto_ptr<Gio::Error> error;
+ stream.create(error);
+ if(error.get())
+ {
+ const Gio::Error& ex = *error.get();
+#endif
+ // If the operation was not successful, print the error and abort
+ std::cerr << "ConnectionPool::create_text_file(): exception while creating file." << std::endl
+ << " file uri:" << file_uri << std::endl
+ << " error:" << ex.what() << std::endl;
+ return false; // print_error(ex, output_uri_string);
+ }
+
+
+ if(!stream)
+ return false;
+
+
+ gsize bytes_written = 0;
+ const std::string::size_type contents_size = contents.size();
+#ifdef GLIBMM_EXCEPTIONS_ENABLED
+ try
+ {
+ //Write the data to the output uri
+ bytes_written = stream->write(contents.data(), contents_size);
+ }
+ catch(const Gio::Error& ex)
+ {
+#else
+ bytes_written = stream->write(contents.data(), contents_size, error);
+ if(error.get())
+ {
+ Gio::Error& ex = *error.get();
+#endif
+ // If the operation was not successful, print the error and abort
+ std::cerr << "ConnectionPool::create_text_file(): exception while writing to file." << std::endl
+ << " file uri:" << file_uri << std::endl
+ << " error:" << ex.what() << std::endl;
+ return false; //print_error(ex, output_uri_string);
+ }
+
+ if(bytes_written != contents_size)
+ {
+ std::cerr << "ConnectionPool::create_text_file(): not all bytes written when writing to file." << std::endl
+ << " file uri:" << file_uri << std::endl;
+ return false;
+ }
+
+ return true; //Success.
+}
+
} //namespace ConnectionPoolBackends
} //namespace Glom
diff --git a/glom/libglom/connectionpool_backends/postgres.h b/glom/libglom/connectionpool_backends/postgres.h
index 6878f70..37829af 100644
--- a/glom/libglom/connectionpool_backends/postgres.h
+++ b/glom/libglom/connectionpool_backends/postgres.h
@@ -55,12 +55,12 @@ public:
/** Save a backup file, using the same directory layout as used by self-hosting.
*/
virtual bool save_backup(const SlotProgress& slot_progress, const Glib::ustring& username, const Glib::ustring& password, const Glib::ustring& database_name);
-
+
virtual bool convert_backup(const SlotProgress& slot_progress, const std::string& base_directory, const Glib::ustring& username, const Glib::ustring& password);
static std::string get_path_to_postgres_executable(const std::string& program);
-
-
+
+
private:
virtual Field::sql_format get_sql_format() const { return Field::SQL_FORMAT_POSTGRES; }
@@ -69,7 +69,7 @@ private:
virtual const char* get_public_schema_name() const { return "public"; }
virtual bool change_columns(const Glib::RefPtr<Gnome::Gda::Connection>& connection, const Glib::ustring& table_name, const type_vec_const_fields& old_fields, const type_vec_const_fields& new_fields, std::auto_ptr<Glib::Error>& error);
-
+
protected:
bool attempt_create_database(const Glib::ustring& database_name, const Glib::ustring& host, const Glib::ustring& port, const Glib::ustring& username, const Glib::ustring& password, std::auto_ptr<Glib::Error>& error);
@@ -79,15 +79,15 @@ protected:
Glib::RefPtr<Gnome::Gda::Connection> attempt_connect(const Glib::ustring& port, const Glib::ustring& database, const Glib::ustring& username, const Glib::ustring& password, std::auto_ptr<ExceptionConnection>& error) throw();
std::string get_self_hosting_path(bool create = false, const std::string& child_directory = std::string());
-
+
/** Get the path to the config sub-directory, optionally creating it.
*/
std::string get_self_hosting_config_path(bool create = false);
-
+
/** Get the path to the data sub-directory, optionally creating it.
*/
std::string get_self_hosting_data_path(bool create = false);
-
+
/** Get the path to the backup file, regardless of whether it exists.
* @param base_directory Where to find the backup file, under a normal Glom directory structure.
* If @a base_directory is empty then it uses get_database_directory_uri().
@@ -97,7 +97,19 @@ protected:
bool create_directory_filepath(const std::string& filepath);
bool file_exists_filepath(const std::string& filepath);
bool file_exists_uri(const std::string& uri) const;
-
+
+ /**
+ * @param current_user_only If true then only the current user will be able to read or write the file.
+ */
+ static bool create_text_file(const std::string& file_uri, const std::string& contents, bool current_user_only = false);
+
+ /**
+ * @param filepath_previous The path to which the previous .pgpass, if any was moved.
+ * @param filepath_original The path to which filepath_previous should be moved back after the caller has finished.
+ * @param result whether it succeeded.
+ */
+ bool save_password_to_pgpass(const Glib::ustring username, const Glib::ustring& password, std::string& filepath_previous, std::string& filepath_original);
+
protected:
static Glib::ustring port_as_string(int port_num);
diff --git a/glom/libglom/connectionpool_backends/postgres_self.cc b/glom/libglom/connectionpool_backends/postgres_self.cc
index e8c8771..35674ed 100644
--- a/glom/libglom/connectionpool_backends/postgres_self.cc
+++ b/glom/libglom/connectionpool_backends/postgres_self.cc
@@ -388,7 +388,7 @@ Backend::StartupErrors PostgresSelfHosted::startup(const SlotProgress& slot_prog
std::cerr << G_STRFUNC << ": Already started." << std::endl;
return STARTUPERROR_NONE; //Just do it once.
}
-
+
const std::string dbdir_uri = m_database_directory_uri;
if(!(file_exists_uri(dbdir_uri)))
@@ -418,7 +418,7 @@ Backend::StartupErrors PostgresSelfHosted::startup(const SlotProgress& slot_prog
return STARTUPERROR_FAILED_NO_DATA;
}
}
-
+
//Attempt to ensure that the config files are correct:
set_network_shared(slot_progress, m_network_shared); //Creates pg_hba.conf and pg_ident.conf
@@ -701,83 +701,6 @@ int PostgresSelfHosted::discover_first_free_port(int start_port, int end_port)
return 0;
}
-bool PostgresSelfHosted::create_text_file(const std::string& file_uri, const std::string& contents)
-{
- if(file_uri.empty())
- return false;
-
- Glib::RefPtr<Gio::File> file = Gio::File::create_for_uri(file_uri);
- Glib::RefPtr<Gio::FileOutputStream> stream;
-
- //Create the file if it does not already exist:
-#ifdef GLIBMM_EXCEPTIONS_ENABLED
- try
- {
- if(file->query_exists())
- {
- stream = file->replace(); //Instead of append_to().
- }
- else
- {
- //By default files created are generally readable by everyone, but if we pass FILE_CREATE_PRIVATE in flags the file will be made readable only to the current user, to the level that is supported on the target filesystem.
- //TODO: Do we want to specify 0660 exactly? (means "this user and his group can read and write this non-executable file".)
- stream = file->create_file();
- }
- }
- catch(const Gio::Error& ex)
- {
-#else
- std::auto_ptr<Gio::Error> error;
- stream.create(error);
- if(error.get())
- {
- const Gio::Error& ex = *error.get();
-#endif
- // If the operation was not successful, print the error and abort
- std::cerr << "ConnectionPool::create_text_file(): exception while creating file." << std::endl
- << " file uri:" << file_uri << std::endl
- << " error:" << ex.what() << std::endl;
- return false; // print_error(ex, output_uri_string);
- }
-
-
- if(!stream)
- return false;
-
-
- gsize bytes_written = 0;
- const std::string::size_type contents_size = contents.size();
-#ifdef GLIBMM_EXCEPTIONS_ENABLED
- try
- {
- //Write the data to the output uri
- bytes_written = stream->write(contents.data(), contents_size);
- }
- catch(const Gio::Error& ex)
- {
-#else
- bytes_written = stream->write(contents.data(), contents_size, error);
- if(error.get())
- {
- Gio::Error& ex = *error.get();
-#endif
- // If the operation was not successful, print the error and abort
- std::cerr << "ConnectionPool::create_text_file(): exception while writing to file." << std::endl
- << " file uri:" << file_uri << std::endl
- << " error:" << ex.what() << std::endl;
- return false; //print_error(ex, output_uri_string);
- }
-
- if(bytes_written != contents_size)
- {
- std::cerr << "ConnectionPool::create_text_file(): not all bytes written when writing to file." << std::endl
- << " file uri:" << file_uri << std::endl;
- return false;
- }
-
- return true; //Success.
-}
-
} // namespace ConnectionPoolBackends
} // namespcae Glom
diff --git a/glom/libglom/connectionpool_backends/postgres_self.h b/glom/libglom/connectionpool_backends/postgres_self.h
index 0c1b809..74aebf4 100644
--- a/glom/libglom/connectionpool_backends/postgres_self.h
+++ b/glom/libglom/connectionpool_backends/postgres_self.h
@@ -71,8 +71,6 @@ private:
*/
static int discover_first_free_port(int start_port, int end_port);
- static bool create_text_file(const std::string& file_uri, const std::string& contents);
-
/** Run the command-line with the --version option to discover what version
* of PostgreSQL is installed, so we can use the appropriate configuration
* options when self-hosting.
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]