mirror of
https://github.com/postgrespro/pg_probackup.git
synced 2025-01-09 14:45:47 +02:00
f94c5ab447
--debug and --verbose had actually the same meaning as they were aimed at giving to the user information regarding how the process is running, hence both options are merged into --verbose and use elog(LOG) to decide if a given message should be printed out depending on the verbosity of the call. This makes a couple of routines more readable as they do not depend on any boolean checks. The "_()" have been removed from the code, those are aimed at being used for translation but having them mandatorily in each log message is just useless noise. If needed, pgut.c should be updated in consequence to have a more portable facility. At the same time this commit takes care of putting into correct shape some code paths more in-line with PostgreSQL policy. There are surely more of this kind of ugly stuff but at this stage things are more simple and more manageable.
522 lines
12 KiB
C
522 lines
12 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* data.c: data parsing pages
|
|
*
|
|
* Copyright (c) 2009-2013, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "pg_arman.h"
|
|
|
|
#include <unistd.h>
|
|
#include <time.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include "libpq/pqsignal.h"
|
|
#include "storage/block.h"
|
|
#include "storage/bufpage.h"
|
|
|
|
typedef union DataPage
|
|
{
|
|
PageHeaderData page_data;
|
|
char data[BLCKSZ];
|
|
} DataPage;
|
|
|
|
typedef struct BackupPageHeader
|
|
{
|
|
BlockNumber block; /* block number */
|
|
uint16 hole_offset; /* number of bytes before "hole" */
|
|
uint16 hole_length; /* number of bytes in "hole" */
|
|
} BackupPageHeader;
|
|
|
|
static bool
|
|
parse_page(const DataPage *page,
|
|
XLogRecPtr *lsn, uint16 *offset, uint16 *length)
|
|
{
|
|
const PageHeaderData *page_data = &page->page_data;
|
|
|
|
/* Get lsn from page header */
|
|
*lsn = PageXLogRecPtrGet(page_data->pd_lsn);
|
|
|
|
if (PageGetPageSize(page_data) == BLCKSZ &&
|
|
PageGetPageLayoutVersion(page_data) == PG_PAGE_LAYOUT_VERSION &&
|
|
(page_data->pd_flags & ~PD_VALID_FLAG_BITS) == 0 &&
|
|
page_data->pd_lower >= SizeOfPageHeaderData &&
|
|
page_data->pd_lower <= page_data->pd_upper &&
|
|
page_data->pd_upper <= page_data->pd_special &&
|
|
page_data->pd_special <= BLCKSZ &&
|
|
page_data->pd_special == MAXALIGN(page_data->pd_special) &&
|
|
!XLogRecPtrIsInvalid(*lsn))
|
|
{
|
|
*offset = page_data->pd_lower;
|
|
*length = page_data->pd_upper - page_data->pd_lower;
|
|
return true;
|
|
}
|
|
|
|
*offset = *length = 0;
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Backup data file in the from_root directory to the to_root directory with
|
|
* same relative path.
|
|
* If lsn is not NULL, pages only which are modified after the lsn will be
|
|
* copied.
|
|
*/
|
|
bool
|
|
backup_data_file(const char *from_root, const char *to_root,
|
|
pgFile *file, const XLogRecPtr *lsn)
|
|
{
|
|
char to_path[MAXPGPATH];
|
|
FILE *in;
|
|
FILE *out;
|
|
BackupPageHeader header;
|
|
DataPage page; /* used as read buffer */
|
|
BlockNumber blknum;
|
|
size_t read_len;
|
|
int errno_tmp;
|
|
pg_crc32 crc;
|
|
|
|
INIT_CRC32C(crc);
|
|
|
|
/* reset size summary */
|
|
file->read_size = 0;
|
|
file->write_size = 0;
|
|
|
|
/* open backup mode file for read */
|
|
in = fopen(file->path, "r");
|
|
if (in == NULL)
|
|
{
|
|
FIN_CRC32C(crc);
|
|
file->crc = crc;
|
|
|
|
/* maybe vanished, it's not error */
|
|
if (errno == ENOENT)
|
|
return false;
|
|
|
|
elog(ERROR_SYSTEM, "cannot open backup mode file \"%s\": %s",
|
|
file->path, strerror(errno));
|
|
}
|
|
|
|
/* open backup file for write */
|
|
if (check)
|
|
snprintf(to_path, lengthof(to_path), "%s/tmp", backup_path);
|
|
else
|
|
join_path_components(to_path, to_root, file->path + strlen(from_root) + 1);
|
|
out = fopen(to_path, "w");
|
|
if (out == NULL)
|
|
{
|
|
int errno_tmp = errno;
|
|
fclose(in);
|
|
elog(ERROR_SYSTEM, "cannot open backup file \"%s\": %s",
|
|
to_path, strerror(errno_tmp));
|
|
}
|
|
|
|
/* confirm server version */
|
|
check_server_version();
|
|
|
|
/* read each page and write the page excluding hole */
|
|
for (blknum = 0;
|
|
(read_len = fread(&page, 1, sizeof(page), in)) == sizeof(page);
|
|
++blknum)
|
|
{
|
|
XLogRecPtr page_lsn;
|
|
int upper_offset;
|
|
int upper_length;
|
|
|
|
header.block = blknum;
|
|
|
|
/*
|
|
* If a invalid data page was found, fallback to simple copy to ensure
|
|
* all pages in the file don't have BackupPageHeader.
|
|
*/
|
|
if (!parse_page(&page, &page_lsn,
|
|
&header.hole_offset, &header.hole_length))
|
|
{
|
|
elog(LOG, "%s fall back to simple copy", file->path);
|
|
fclose(in);
|
|
fclose(out);
|
|
file->is_datafile = false;
|
|
return copy_file(from_root, to_root, file);
|
|
}
|
|
|
|
file->read_size += read_len;
|
|
|
|
/* if the page has not been modified since last backup, skip it */
|
|
if (lsn && !XLogRecPtrIsInvalid(page_lsn) && page_lsn < *lsn)
|
|
continue;
|
|
|
|
upper_offset = header.hole_offset + header.hole_length;
|
|
upper_length = BLCKSZ - upper_offset;
|
|
|
|
/* write data page excluding hole */
|
|
if (fwrite(&header, 1, sizeof(header), out) != sizeof(header) ||
|
|
fwrite(page.data, 1, header.hole_offset, out) != header.hole_offset ||
|
|
fwrite(page.data + upper_offset, 1, upper_length, out) != upper_length)
|
|
{
|
|
int errno_tmp = errno;
|
|
/* oops */
|
|
fclose(in);
|
|
fclose(out);
|
|
elog(ERROR_SYSTEM, "cannot write at block %u of \"%s\": %s",
|
|
blknum, to_path, strerror(errno_tmp));
|
|
}
|
|
|
|
/* update CRC */
|
|
COMP_CRC32C(crc, &header, sizeof(header));
|
|
COMP_CRC32C(crc, page.data, header.hole_offset);
|
|
COMP_CRC32C(crc, page.data + upper_offset, upper_length);
|
|
|
|
file->write_size += sizeof(header) + read_len - header.hole_length;
|
|
}
|
|
errno_tmp = errno;
|
|
if (!feof(in))
|
|
{
|
|
fclose(in);
|
|
fclose(out);
|
|
elog(ERROR_SYSTEM, "cannot read backup mode file \"%s\": %s",
|
|
file->path, strerror(errno_tmp));
|
|
}
|
|
|
|
/*
|
|
* The odd size page at the tail is probably a page exactly written now, so
|
|
* write whole of it.
|
|
*/
|
|
if (read_len > 0)
|
|
{
|
|
/*
|
|
* If the odd size page is the 1st page, fallback to simple copy because
|
|
* the file is not a datafile.
|
|
* Otherwise treat the page as a datapage with no hole.
|
|
*/
|
|
if (blknum == 0)
|
|
file->is_datafile = false;
|
|
else
|
|
{
|
|
header.block = blknum;
|
|
header.hole_offset = 0;
|
|
header.hole_length = 0;
|
|
|
|
if (fwrite(&header, 1, sizeof(header), out) != sizeof(header))
|
|
{
|
|
int errno_tmp = errno;
|
|
/* oops */
|
|
fclose(in);
|
|
fclose(out);
|
|
elog(ERROR_SYSTEM,
|
|
"cannot write at block %u of \"%s\": %s",
|
|
blknum, to_path, strerror(errno_tmp));
|
|
}
|
|
COMP_CRC32C(crc, &header, sizeof(header));
|
|
file->write_size += sizeof(header);
|
|
}
|
|
|
|
/* write odd size page image */
|
|
if (fwrite(page.data, 1, read_len, out) != read_len)
|
|
{
|
|
int errno_tmp = errno;
|
|
/* oops */
|
|
fclose(in);
|
|
fclose(out);
|
|
elog(ERROR_SYSTEM, "cannot write at block %u of \"%s\": %s",
|
|
blknum, to_path, strerror(errno_tmp));
|
|
}
|
|
|
|
COMP_CRC32C(crc, page.data, read_len);
|
|
|
|
file->write_size += read_len;
|
|
file->read_size += read_len;
|
|
}
|
|
|
|
/*
|
|
* update file permission
|
|
* FIXME: Should set permission on open?
|
|
*/
|
|
if (!check && chmod(to_path, FILE_PERMISSION) == -1)
|
|
{
|
|
int errno_tmp = errno;
|
|
fclose(in);
|
|
fclose(out);
|
|
elog(ERROR_SYSTEM, "cannot change mode of \"%s\": %s", file->path,
|
|
strerror(errno_tmp));
|
|
}
|
|
|
|
fclose(in);
|
|
fclose(out);
|
|
|
|
/* finish CRC calculation and store into pgFile */
|
|
FIN_CRC32C(crc);
|
|
file->crc = crc;
|
|
|
|
/* Treat empty file as not-datafile */
|
|
if (file->read_size == 0)
|
|
file->is_datafile = false;
|
|
|
|
/* We do not backup if all pages skipped. */
|
|
if (file->write_size == 0 && file->read_size > 0)
|
|
{
|
|
if (remove(to_path) == -1)
|
|
elog(ERROR_SYSTEM, "cannot remove file \"%s\": %s", to_path,
|
|
strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
/* remove $BACKUP_PATH/tmp created during check */
|
|
if (check)
|
|
remove(to_path);
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Restore files in the from_root directory to the to_root directory with
|
|
* same relative path.
|
|
*/
|
|
void
|
|
restore_data_file(const char *from_root,
|
|
const char *to_root,
|
|
pgFile *file)
|
|
{
|
|
char to_path[MAXPGPATH];
|
|
FILE *in;
|
|
FILE *out;
|
|
BackupPageHeader header;
|
|
BlockNumber blknum;
|
|
|
|
/* If the file is not a datafile, just copy it. */
|
|
if (!file->is_datafile)
|
|
{
|
|
copy_file(from_root, to_root, file);
|
|
return;
|
|
}
|
|
|
|
/* open backup mode file for read */
|
|
in = fopen(file->path, "r");
|
|
if (in == NULL)
|
|
{
|
|
elog(ERROR_SYSTEM, "cannot open backup file \"%s\": %s", file->path,
|
|
strerror(errno));
|
|
}
|
|
|
|
/*
|
|
* Open backup file for write. We use "r+" at first to overwrite only
|
|
* modified pages for differential restore. If the file is not exists,
|
|
* re-open it with "w" to create an empty file.
|
|
*/
|
|
join_path_components(to_path, to_root, file->path + strlen(from_root) + 1);
|
|
out = fopen(to_path, "r+");
|
|
if (out == NULL && errno == ENOENT)
|
|
out = fopen(to_path, "w");
|
|
if (out == NULL)
|
|
{
|
|
int errno_tmp = errno;
|
|
fclose(in);
|
|
elog(ERROR_SYSTEM, "cannot open restore target file \"%s\": %s",
|
|
to_path, strerror(errno_tmp));
|
|
}
|
|
|
|
for (blknum = 0; ; blknum++)
|
|
{
|
|
size_t read_len;
|
|
DataPage page; /* used as read buffer */
|
|
int upper_offset;
|
|
int upper_length;
|
|
|
|
/* read BackupPageHeader */
|
|
read_len = fread(&header, 1, sizeof(header), in);
|
|
if (read_len != sizeof(header))
|
|
{
|
|
int errno_tmp = errno;
|
|
if (read_len == 0 && feof(in))
|
|
break; /* EOF found */
|
|
else if (read_len != 0 && feof(in))
|
|
{
|
|
elog(ERROR_CORRUPTED,
|
|
"odd size page found at block %u of \"%s\"",
|
|
blknum, file->path);
|
|
}
|
|
else
|
|
{
|
|
elog(ERROR_SYSTEM, "cannot read block %u of \"%s\": %s",
|
|
blknum, file->path, strerror(errno_tmp));
|
|
}
|
|
}
|
|
|
|
if (header.block < blknum || header.hole_offset > BLCKSZ ||
|
|
(int) header.hole_offset + (int) header.hole_length > BLCKSZ)
|
|
{
|
|
elog(ERROR_CORRUPTED, "backup is broken at block %u",
|
|
blknum);
|
|
}
|
|
|
|
upper_offset = header.hole_offset + header.hole_length;
|
|
upper_length = BLCKSZ - upper_offset;
|
|
|
|
/* read lower/upper into page.data and restore hole */
|
|
memset(page.data + header.hole_offset, 0, header.hole_length);
|
|
|
|
if (fread(page.data, 1, header.hole_offset, in) != header.hole_offset ||
|
|
fread(page.data + upper_offset, 1, upper_length, in) != upper_length)
|
|
{
|
|
elog(ERROR_SYSTEM, "cannot read block %u of \"%s\": %s",
|
|
blknum, file->path, strerror(errno));
|
|
}
|
|
|
|
/*
|
|
* Seek and write the restored page. Backup might have holes in
|
|
* differential backups.
|
|
*/
|
|
blknum = header.block;
|
|
if (fseek(out, blknum * BLCKSZ, SEEK_SET) < 0)
|
|
elog(ERROR_SYSTEM, "cannot seek block %u of \"%s\": %s",
|
|
blknum, to_path, strerror(errno));
|
|
if (fwrite(page.data, 1, sizeof(page), out) != sizeof(page))
|
|
elog(ERROR_SYSTEM, "cannot write block %u of \"%s\": %s",
|
|
blknum, file->path, strerror(errno));
|
|
}
|
|
|
|
/* update file permission */
|
|
if (chmod(to_path, file->mode) == -1)
|
|
{
|
|
int errno_tmp = errno;
|
|
fclose(in);
|
|
fclose(out);
|
|
elog(ERROR_SYSTEM, "cannot change mode of \"%s\": %s", to_path,
|
|
strerror(errno_tmp));
|
|
}
|
|
|
|
fclose(in);
|
|
fclose(out);
|
|
}
|
|
|
|
bool
|
|
copy_file(const char *from_root, const char *to_root, pgFile *file)
|
|
{
|
|
char to_path[MAXPGPATH];
|
|
FILE *in;
|
|
FILE *out;
|
|
size_t read_len = 0;
|
|
int errno_tmp;
|
|
char buf[8192];
|
|
struct stat st;
|
|
pg_crc32 crc;
|
|
|
|
INIT_CRC32C(crc);
|
|
|
|
/* reset size summary */
|
|
file->read_size = 0;
|
|
file->write_size = 0;
|
|
|
|
/* open backup mode file for read */
|
|
in = fopen(file->path, "r");
|
|
if (in == NULL)
|
|
{
|
|
FIN_CRC32C(crc);
|
|
file->crc = crc;
|
|
|
|
/* maybe deleted, it's not error */
|
|
if (errno == ENOENT)
|
|
return false;
|
|
|
|
elog(ERROR_SYSTEM, "cannot open source file \"%s\": %s", file->path,
|
|
strerror(errno));
|
|
}
|
|
|
|
/* open backup file for write */
|
|
if (check)
|
|
snprintf(to_path, lengthof(to_path), "%s/tmp", backup_path);
|
|
else
|
|
join_path_components(to_path, to_root, file->path + strlen(from_root) + 1);
|
|
out = fopen(to_path, "w");
|
|
if (out == NULL)
|
|
{
|
|
int errno_tmp = errno;
|
|
fclose(in);
|
|
elog(ERROR_SYSTEM, "cannot open destination file \"%s\": %s",
|
|
to_path, strerror(errno_tmp));
|
|
}
|
|
|
|
/* stat source file to change mode of destination file */
|
|
if (fstat(fileno(in), &st) == -1)
|
|
{
|
|
fclose(in);
|
|
fclose(out);
|
|
elog(ERROR_SYSTEM, "cannot stat \"%s\": %s", file->path,
|
|
strerror(errno));
|
|
}
|
|
|
|
/* copy content and calc CRC */
|
|
for (;;)
|
|
{
|
|
if ((read_len = fread(buf, 1, sizeof(buf), in)) != sizeof(buf))
|
|
break;
|
|
|
|
if (fwrite(buf, 1, read_len, out) != read_len)
|
|
{
|
|
errno_tmp = errno;
|
|
/* oops */
|
|
fclose(in);
|
|
fclose(out);
|
|
elog(ERROR_SYSTEM, "cannot write to \"%s\": %s", to_path,
|
|
strerror(errno_tmp));
|
|
}
|
|
/* update CRC */
|
|
COMP_CRC32C(crc, buf, read_len);
|
|
|
|
file->write_size += sizeof(buf);
|
|
file->read_size += sizeof(buf);
|
|
}
|
|
|
|
errno_tmp = errno;
|
|
if (!feof(in))
|
|
{
|
|
fclose(in);
|
|
fclose(out);
|
|
elog(ERROR_SYSTEM, "cannot read backup mode file \"%s\": %s",
|
|
file->path, strerror(errno_tmp));
|
|
}
|
|
|
|
/* copy odd part. */
|
|
if (read_len > 0)
|
|
{
|
|
if (fwrite(buf, 1, read_len, out) != read_len)
|
|
{
|
|
errno_tmp = errno;
|
|
/* oops */
|
|
fclose(in);
|
|
fclose(out);
|
|
elog(ERROR_SYSTEM, "cannot write to \"%s\": %s", to_path,
|
|
strerror(errno_tmp));
|
|
}
|
|
/* update CRC */
|
|
COMP_CRC32C(crc, buf, read_len);
|
|
|
|
file->write_size += read_len;
|
|
file->read_size += read_len;
|
|
}
|
|
|
|
/* finish CRC calculation and store into pgFile */
|
|
FIN_CRC32C(crc);
|
|
file->crc = crc;
|
|
|
|
/* update file permission */
|
|
if (chmod(to_path, st.st_mode) == -1)
|
|
{
|
|
errno_tmp = errno;
|
|
fclose(in);
|
|
fclose(out);
|
|
elog(ERROR_SYSTEM, "cannot change mode of \"%s\": %s", to_path,
|
|
strerror(errno_tmp));
|
|
}
|
|
|
|
fclose(in);
|
|
fclose(out);
|
|
|
|
if (check)
|
|
remove(to_path);
|
|
|
|
return true;
|
|
}
|