| /*------------------------------------------------------------------------- |
| * |
| * basebackup_server.c |
| * store basebackup archives on the server |
| * |
| * IDENTIFICATION |
| * src/backend/backup/basebackup_server.c |
| * |
| *------------------------------------------------------------------------- |
| */ |
| #include "postgres.h" |
| |
| #include "access/xact.h" |
| #include "backup/basebackup.h" |
| #include "backup/basebackup_sink.h" |
| #include "catalog/pg_authid.h" |
| #include "miscadmin.h" |
| #include "storage/fd.h" |
| #include "utils/acl.h" |
| #include "utils/timestamp.h" |
| #include "utils/wait_event.h" |
| |
| typedef struct bbsink_server |
| { |
| /* Common information for all types of sink. */ |
| bbsink base; |
| |
| /* Directory in which backup is to be stored. */ |
| char *pathname; |
| |
| /* Currently open file (or 0 if nothing open). */ |
| File file; |
| |
| /* Current file position. */ |
| off_t filepos; |
| } bbsink_server; |
| |
| static void bbsink_server_begin_archive(bbsink *sink, |
| const char *archive_name); |
| static void bbsink_server_archive_contents(bbsink *sink, size_t len); |
| static void bbsink_server_end_archive(bbsink *sink); |
| static void bbsink_server_begin_manifest(bbsink *sink); |
| static void bbsink_server_manifest_contents(bbsink *sink, size_t len); |
| static void bbsink_server_end_manifest(bbsink *sink); |
| |
| static const bbsink_ops bbsink_server_ops = { |
| .begin_backup = bbsink_forward_begin_backup, |
| .begin_archive = bbsink_server_begin_archive, |
| .archive_contents = bbsink_server_archive_contents, |
| .end_archive = bbsink_server_end_archive, |
| .begin_manifest = bbsink_server_begin_manifest, |
| .manifest_contents = bbsink_server_manifest_contents, |
| .end_manifest = bbsink_server_end_manifest, |
| .end_backup = bbsink_forward_end_backup, |
| .cleanup = bbsink_forward_cleanup |
| }; |
| |
| /* |
| * Create a new 'server' bbsink. |
| */ |
| bbsink * |
| bbsink_server_new(bbsink *next, char *pathname) |
| { |
| bbsink_server *sink = palloc0(sizeof(bbsink_server)); |
| |
| *((const bbsink_ops **) &sink->base.bbs_ops) = &bbsink_server_ops; |
| sink->pathname = pathname; |
| sink->base.bbs_next = next; |
| |
| /* Replication permission is not sufficient in this case. */ |
| StartTransactionCommand(); |
| if (!has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES)) |
| ereport(ERROR, |
| (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
| errmsg("permission denied to create backup stored on server"), |
| errdetail("Only roles with privileges of the \"%s\" role may create a backup stored on the server.", |
| "pg_write_server_files"))); |
| CommitTransactionCommand(); |
| |
| /* |
| * It's not a good idea to store your backups in the same directory that |
| * you're backing up. If we allowed a relative path here, that could |
| * easily happen accidentally, so we don't. The user could still |
| * accomplish the same thing by including the absolute path to $PGDATA in |
| * the pathname, but that's likely an intentional bad decision rather than |
| * an accident. |
| */ |
| if (!is_absolute_path(pathname)) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_NAME), |
| errmsg("relative path not allowed for backup stored on server"))); |
| |
| switch (pg_check_dir(pathname)) |
| { |
| case 0: |
| |
| /* |
| * Does not exist, so create it using the same permissions we'd |
| * use for a new subdirectory of the data directory itself. |
| */ |
| if (MakePGDirectory(pathname) < 0) |
| ereport(ERROR, |
| (errcode_for_file_access(), |
| errmsg("could not create directory \"%s\": %m", pathname))); |
| break; |
| |
| case 1: |
| /* Exists, empty. */ |
| break; |
| |
| case 2: |
| case 3: |
| case 4: |
| /* Exists, not empty. */ |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_FILE), |
| errmsg("directory \"%s\" exists but is not empty", |
| pathname))); |
| break; |
| |
| default: |
| /* Access problem. */ |
| ereport(ERROR, |
| (errcode_for_file_access(), |
| errmsg("could not access directory \"%s\": %m", |
| pathname))); |
| } |
| |
| return &sink->base; |
| } |
| |
| /* |
| * Open the correct output file for this archive. |
| */ |
| static void |
| bbsink_server_begin_archive(bbsink *sink, const char *archive_name) |
| { |
| bbsink_server *mysink = (bbsink_server *) sink; |
| char *filename; |
| |
| Assert(mysink->file == 0); |
| Assert(mysink->filepos == 0); |
| |
| filename = psprintf("%s/%s", mysink->pathname, archive_name); |
| |
| mysink->file = PathNameOpenFile(filename, |
| O_CREAT | O_EXCL | O_WRONLY | PG_BINARY); |
| if (mysink->file <= 0) |
| ereport(ERROR, |
| (errcode_for_file_access(), |
| errmsg("could not create file \"%s\": %m", filename))); |
| |
| pfree(filename); |
| |
| bbsink_forward_begin_archive(sink, archive_name); |
| } |
| |
| /* |
| * Write the data to the output file. |
| */ |
| static void |
| bbsink_server_archive_contents(bbsink *sink, size_t len) |
| { |
| bbsink_server *mysink = (bbsink_server *) sink; |
| int nbytes; |
| |
| nbytes = FileWrite(mysink->file, mysink->base.bbs_buffer, len, |
| mysink->filepos, WAIT_EVENT_BASEBACKUP_WRITE); |
| |
| if (nbytes != len) |
| { |
| if (nbytes < 0) |
| ereport(ERROR, |
| (errcode_for_file_access(), |
| errmsg("could not write file \"%s\": %m", |
| FilePathName(mysink->file)), |
| errhint("Check free disk space."))); |
| /* short write: complain appropriately */ |
| ereport(ERROR, |
| (errcode(ERRCODE_DISK_FULL), |
| errmsg("could not write file \"%s\": wrote only %d of %d bytes at offset %u", |
| FilePathName(mysink->file), |
| nbytes, (int) len, (unsigned) mysink->filepos), |
| errhint("Check free disk space."))); |
| } |
| |
| mysink->filepos += nbytes; |
| |
| bbsink_forward_archive_contents(sink, len); |
| } |
| |
| /* |
| * fsync and close the current output file. |
| */ |
| static void |
| bbsink_server_end_archive(bbsink *sink) |
| { |
| bbsink_server *mysink = (bbsink_server *) sink; |
| |
| /* |
| * We intentionally don't use data_sync_elevel here, because the server |
| * shouldn't PANIC just because we can't guarantee that the backup has |
| * been written down to disk. Running recovery won't fix anything in this |
| * case anyway. |
| */ |
| if (FileSync(mysink->file, WAIT_EVENT_BASEBACKUP_SYNC) < 0) |
| ereport(ERROR, |
| (errcode_for_file_access(), |
| errmsg("could not fsync file \"%s\": %m", |
| FilePathName(mysink->file)))); |
| |
| |
| /* We're done with this file now. */ |
| FileClose(mysink->file); |
| mysink->file = 0; |
| mysink->filepos = 0; |
| |
| bbsink_forward_end_archive(sink); |
| } |
| |
| /* |
| * Open the output file to which we will write the manifest. |
| * |
| * Just like pg_basebackup, we write the manifest first under a temporary |
| * name and then rename it into place after fsync. That way, if the manifest |
| * is there and under the correct name, the user can be sure that the backup |
| * completed. |
| */ |
| static void |
| bbsink_server_begin_manifest(bbsink *sink) |
| { |
| bbsink_server *mysink = (bbsink_server *) sink; |
| char *tmp_filename; |
| |
| Assert(mysink->file == 0); |
| |
| tmp_filename = psprintf("%s/backup_manifest.tmp", mysink->pathname); |
| |
| mysink->file = PathNameOpenFile(tmp_filename, |
| O_CREAT | O_EXCL | O_WRONLY | PG_BINARY); |
| if (mysink->file <= 0) |
| ereport(ERROR, |
| (errcode_for_file_access(), |
| errmsg("could not create file \"%s\": %m", tmp_filename))); |
| |
| pfree(tmp_filename); |
| |
| bbsink_forward_begin_manifest(sink); |
| } |
| |
| /* |
| * Each chunk of manifest data is sent using a CopyData message. |
| */ |
| static void |
| bbsink_server_manifest_contents(bbsink *sink, size_t len) |
| { |
| bbsink_server *mysink = (bbsink_server *) sink; |
| int nbytes; |
| |
| nbytes = FileWrite(mysink->file, mysink->base.bbs_buffer, len, |
| mysink->filepos, WAIT_EVENT_BASEBACKUP_WRITE); |
| |
| if (nbytes != len) |
| { |
| if (nbytes < 0) |
| ereport(ERROR, |
| (errcode_for_file_access(), |
| errmsg("could not write file \"%s\": %m", |
| FilePathName(mysink->file)), |
| errhint("Check free disk space."))); |
| /* short write: complain appropriately */ |
| ereport(ERROR, |
| (errcode(ERRCODE_DISK_FULL), |
| errmsg("could not write file \"%s\": wrote only %d of %d bytes at offset %u", |
| FilePathName(mysink->file), |
| nbytes, (int) len, (unsigned) mysink->filepos), |
| errhint("Check free disk space."))); |
| } |
| |
| mysink->filepos += nbytes; |
| |
| bbsink_forward_manifest_contents(sink, len); |
| } |
| |
| /* |
| * fsync the backup manifest, close the file, and then rename it into place. |
| */ |
| static void |
| bbsink_server_end_manifest(bbsink *sink) |
| { |
| bbsink_server *mysink = (bbsink_server *) sink; |
| char *tmp_filename; |
| char *filename; |
| |
| /* We're done with this file now. */ |
| FileClose(mysink->file); |
| mysink->file = 0; |
| |
| /* |
| * Rename it into place. This also fsyncs the temporary file, so we don't |
| * need to do that here. We don't use data_sync_elevel here for the same |
| * reasons as in bbsink_server_end_archive. |
| */ |
| tmp_filename = psprintf("%s/backup_manifest.tmp", mysink->pathname); |
| filename = psprintf("%s/backup_manifest", mysink->pathname); |
| durable_rename(tmp_filename, filename, ERROR); |
| pfree(filename); |
| pfree(tmp_filename); |
| |
| bbsink_forward_end_manifest(sink); |
| } |