2013-01-24 09:35:48 +03:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* validate.c: validate backup files.
|
|
|
|
*
|
2017-03-01 15:50:07 +02:00
|
|
|
* Portions Copyright (c) 2009-2011, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
|
|
|
|
* Portions Copyright (c) 2015-2017, Postgres Professional
|
2013-01-24 09:35:48 +03:00
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
|
2016-11-16 19:34:21 +02:00
|
|
|
#include "pg_probackup.h"
|
2013-01-24 09:35:48 +03:00
|
|
|
|
|
|
|
#include <sys/stat.h>
|
2016-11-25 13:26:58 +02:00
|
|
|
#include <pthread.h>
|
2013-01-24 09:35:48 +03:00
|
|
|
|
2016-11-25 13:26:58 +02:00
|
|
|
static void pgBackupValidateFiles(void *arg);
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
parray *files;
|
2017-04-14 11:51:05 +02:00
|
|
|
bool validate_crc;
|
2016-11-25 13:26:58 +02:00
|
|
|
bool corrupted;
|
|
|
|
} validate_files_args;
|
2013-01-24 09:35:48 +03:00
|
|
|
|
|
|
|
/*
|
2017-04-14 11:51:05 +02:00
|
|
|
* Validate backup files.
|
2017-04-18 10:41:02 +02:00
|
|
|
* TODO review
|
2013-01-24 09:35:48 +03:00
|
|
|
*/
|
2017-04-14 11:51:05 +02:00
|
|
|
void
|
|
|
|
pgBackupValidate(pgBackup *backup)
|
2013-01-24 09:35:48 +03:00
|
|
|
{
|
2016-12-06 15:44:18 +02:00
|
|
|
char *backup_id_string;
|
2013-01-24 09:35:48 +03:00
|
|
|
char base_path[MAXPGPATH];
|
|
|
|
char path[MAXPGPATH];
|
|
|
|
parray *files;
|
|
|
|
bool corrupted = false;
|
2016-11-25 13:26:58 +02:00
|
|
|
pthread_t validate_threads[num_threads];
|
|
|
|
validate_files_args *validate_threads_args[num_threads];
|
2017-04-14 11:51:05 +02:00
|
|
|
int i;
|
2013-01-24 09:35:48 +03:00
|
|
|
|
2016-12-06 15:44:18 +02:00
|
|
|
backup_id_string = base36enc(backup->start_time);
|
2017-04-14 11:51:05 +02:00
|
|
|
|
|
|
|
elog(LOG, "Validate backup %s", backup_id_string);
|
|
|
|
|
|
|
|
if (backup->backup_mode != BACKUP_MODE_FULL &&
|
|
|
|
backup->backup_mode != BACKUP_MODE_DIFF_PAGE &&
|
|
|
|
backup->backup_mode != BACKUP_MODE_DIFF_PTRACK)
|
|
|
|
elog(LOG, "Invalid backup_mode of backup %s", backup_id_string);
|
|
|
|
|
|
|
|
pgBackupGetPath(backup, base_path, lengthof(base_path), DATABASE_DIR);
|
|
|
|
pgBackupGetPath(backup, path, lengthof(path), DATABASE_FILE_LIST);
|
|
|
|
files = dir_read_file_list(base_path, path);
|
|
|
|
|
|
|
|
/* setup threads */
|
|
|
|
for (i = 0; i < parray_num(files); i++)
|
2013-12-15 18:30:49 +03:00
|
|
|
{
|
2017-04-14 11:51:05 +02:00
|
|
|
pgFile *file = (pgFile *) parray_get(files, i);
|
|
|
|
__sync_lock_release(&file->lock);
|
2013-01-24 09:35:48 +03:00
|
|
|
}
|
|
|
|
|
2017-04-14 11:51:05 +02:00
|
|
|
/* Validate files */
|
|
|
|
for (i = 0; i < num_threads; i++)
|
2013-12-15 18:30:49 +03:00
|
|
|
{
|
2017-04-14 11:51:05 +02:00
|
|
|
validate_files_args *arg = pg_malloc(sizeof(validate_files_args));
|
|
|
|
arg->files = files;
|
|
|
|
arg->validate_crc = true;
|
2013-12-15 18:30:49 +03:00
|
|
|
|
2017-04-14 11:51:05 +02:00
|
|
|
/* TODO Why didn't we validate checksums on restore before? */
|
|
|
|
// if (backup_subcmd == RESTORE)
|
|
|
|
// arg->validate_crc = false;
|
2013-01-24 09:35:48 +03:00
|
|
|
|
2017-04-14 11:51:05 +02:00
|
|
|
arg->corrupted = false;
|
|
|
|
validate_threads_args[i] = arg;
|
|
|
|
pthread_create(&validate_threads[i], NULL, (void *(*)(void *)) pgBackupValidateFiles, arg);
|
2013-01-24 09:35:48 +03:00
|
|
|
}
|
2017-02-27 14:00:44 +02:00
|
|
|
|
2017-04-14 11:51:05 +02:00
|
|
|
/* Wait theads */
|
|
|
|
for (i = 0; i < num_threads; i++)
|
|
|
|
{
|
|
|
|
pthread_join(validate_threads[i], NULL);
|
|
|
|
if (validate_threads_args[i]->corrupted)
|
|
|
|
corrupted = true;
|
|
|
|
pg_free(validate_threads_args[i]);
|
|
|
|
}
|
2017-04-12 16:39:20 +02:00
|
|
|
|
2017-04-14 11:51:05 +02:00
|
|
|
/* cleanup */
|
|
|
|
parray_walk(files, pgFileFree);
|
|
|
|
parray_free(files);
|
2017-04-12 16:39:20 +02:00
|
|
|
|
2017-04-14 11:51:05 +02:00
|
|
|
/* Update backup status */
|
|
|
|
backup->status = corrupted ? BACKUP_STATUS_CORRUPT : BACKUP_STATUS_OK;
|
|
|
|
pgBackupWriteConf(backup);
|
2013-01-24 09:35:48 +03:00
|
|
|
|
2017-04-14 11:51:05 +02:00
|
|
|
if (corrupted)
|
|
|
|
elog(WARNING, "Backup %s is corrupted", backup_id_string);
|
2013-01-24 09:35:48 +03:00
|
|
|
else
|
2017-04-14 11:51:05 +02:00
|
|
|
elog(LOG, "Backup %s is valid", backup_id_string);
|
2013-01-24 09:35:48 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Validate files in the backup with size or CRC.
|
2017-04-14 11:51:05 +02:00
|
|
|
* NOTE: If file is not valid, do not use ERROR log message,
|
|
|
|
* rather throw a WARNING and set arguments->corrupted = true.
|
|
|
|
* This is necessary to update backup status.
|
2013-01-24 09:35:48 +03:00
|
|
|
*/
|
2016-11-25 13:26:58 +02:00
|
|
|
static void
|
|
|
|
pgBackupValidateFiles(void *arg)
|
2013-01-24 09:35:48 +03:00
|
|
|
{
|
|
|
|
int i;
|
2016-11-25 13:26:58 +02:00
|
|
|
validate_files_args *arguments = (validate_files_args *)arg;
|
|
|
|
|
|
|
|
for (i = 0; i < parray_num(arguments->files); i++)
|
2013-01-24 09:35:48 +03:00
|
|
|
{
|
|
|
|
struct stat st;
|
|
|
|
|
2016-11-25 13:26:58 +02:00
|
|
|
pgFile *file = (pgFile *) parray_get(arguments->files, i);
|
|
|
|
if (__sync_lock_test_and_set(&file->lock, 1) != 0)
|
|
|
|
continue;
|
2013-01-24 09:35:48 +03:00
|
|
|
|
|
|
|
if (interrupted)
|
2017-04-14 11:51:05 +02:00
|
|
|
elog(ERROR, "Interrupted during validate");
|
|
|
|
|
|
|
|
/* Validate only regular files */
|
|
|
|
if (!S_ISREG(file->mode))
|
|
|
|
continue;
|
|
|
|
/*
|
|
|
|
* Skip files which has no data, because they
|
|
|
|
* haven't changed between backups.
|
|
|
|
*/
|
|
|
|
if (file->write_size == BYTES_INVALID)
|
|
|
|
continue;
|
2017-04-18 10:41:02 +02:00
|
|
|
/* We don't compute checksums for compressed data, so skip them */
|
2017-04-14 11:51:05 +02:00
|
|
|
if (file->generation != -1)
|
2013-01-24 09:35:48 +03:00
|
|
|
continue;
|
|
|
|
|
|
|
|
/* print progress */
|
2017-04-14 11:51:05 +02:00
|
|
|
elog(LOG, "Validate files: (%d/%lu) %s",
|
|
|
|
i + 1, (unsigned long) parray_num(arguments->files), file->path);
|
2013-01-24 09:35:48 +03:00
|
|
|
|
|
|
|
if (stat(file->path, &st) == -1)
|
|
|
|
{
|
|
|
|
if (errno == ENOENT)
|
2017-04-14 11:51:05 +02:00
|
|
|
elog(WARNING, "Backup file \"%s\" is not found", file->path);
|
2013-01-24 09:35:48 +03:00
|
|
|
else
|
2017-04-14 11:51:05 +02:00
|
|
|
elog(WARNING, "Cannot stat backup file \"%s\": %s",
|
|
|
|
file->path, strerror(errno));
|
2016-11-25 13:26:58 +02:00
|
|
|
arguments->corrupted = true;
|
|
|
|
return;
|
2013-01-24 09:35:48 +03:00
|
|
|
}
|
2017-04-14 11:51:05 +02:00
|
|
|
|
2013-01-24 09:35:48 +03:00
|
|
|
if (file->write_size != st.st_size)
|
|
|
|
{
|
2017-04-14 11:51:05 +02:00
|
|
|
elog(WARNING, "Invalid size of backup file \"%s\" : %lu. Expected %lu",
|
|
|
|
file->path, (unsigned long) file->write_size,
|
|
|
|
(unsigned long) st.st_size);
|
2016-11-25 13:26:58 +02:00
|
|
|
arguments->corrupted = true;
|
|
|
|
return;
|
2013-01-24 09:35:48 +03:00
|
|
|
}
|
|
|
|
|
2017-04-14 11:51:05 +02:00
|
|
|
if (arguments->validate_crc)
|
2013-01-24 09:35:48 +03:00
|
|
|
{
|
|
|
|
pg_crc32 crc;
|
|
|
|
|
|
|
|
crc = pgFileGetCRC(file);
|
|
|
|
if (crc != file->crc)
|
|
|
|
{
|
2017-04-14 11:51:05 +02:00
|
|
|
elog(WARNING, "Invalid CRC of backup file \"%s\" : %X. Expected %X",
|
|
|
|
file->path, file->crc, crc);
|
2016-11-25 13:26:58 +02:00
|
|
|
arguments->corrupted = true;
|
|
|
|
return;
|
2013-01-24 09:35:48 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|