You've already forked pg_probackup
							
							
				mirror of
				https://github.com/postgrespro/pg_probackup.git
				synced 2025-10-31 00:17:52 +02:00 
			
		
		
		
	First import.
git-svn-id: http://pg-rman.googlecode.com/svn/trunk@2 182aca00-e38e-11de-a668-6fd11605f5ce
This commit is contained in:
		
							
								
								
									
										26
									
								
								COPYRIGHT
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								COPYRIGHT
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| Copyright (c) 2008-2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
| All rights reserved. | ||||
|  | ||||
| Redistribution and use in source and binary forms, with or without | ||||
| modification, are permitted provided that the following conditions are met: | ||||
|  | ||||
|     * Redistributions of source code must retain the above copyright notice, | ||||
|       this list of conditions and the following disclaimer. | ||||
|     * Redistributions in binary form must reproduce the above copyright | ||||
|       notice, this list of conditions and the following disclaimer in the | ||||
|       documentation and/or other materials provided with the distribution. | ||||
|     * Neither the name of the NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|       (NTT) nor the names of its contributors may be used to endorse or | ||||
|       promote products derived from this software without specific prior | ||||
|       written permission. | ||||
|  | ||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||||
| AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||||
| IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||
| DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||||
| FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||||
| DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||||
| SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||||
| CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||||
| OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
							
								
								
									
										40
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| PROGRAM = pg_rman | ||||
| SRCS = \ | ||||
| 	backup.c \ | ||||
| 	catalog.c \ | ||||
| 	data.c \ | ||||
| 	delete.c \ | ||||
| 	dir.c \ | ||||
| 	init.c \ | ||||
| 	parray.c \ | ||||
| 	pg_rman.c \ | ||||
| 	restore.c \ | ||||
| 	show.c \ | ||||
| 	util.c \ | ||||
| 	validate.c \ | ||||
| 	xlog.c \ | ||||
| 	pgsql_src/pg_ctl.c \ | ||||
| 	pgsql_src/pg_crc.c \ | ||||
| 	pgut/pgut.c \ | ||||
| 	pgut/pgut-port.c | ||||
| OBJS = $(SRCS:.c=.o) | ||||
| # pg_crc.c and are copied from PostgreSQL source tree. | ||||
|  | ||||
| # XXX for debug, add -g and disable optimization | ||||
| PG_CPPFLAGS = -I$(libpq_srcdir) | ||||
| PG_LIBS = $(libpq_pgport) | ||||
|  | ||||
| REGRESS = option init show_validate backup_restore | ||||
|  | ||||
| ifdef USE_PGXS | ||||
| PG_CONFIG = pg_config | ||||
| PGXS := $(shell $(PG_CONFIG) --pgxs) | ||||
| include $(PGXS) | ||||
| else | ||||
| subdir = contrib/pg_rman | ||||
| top_builddir = ../.. | ||||
| include $(top_builddir)/src/Makefile.global | ||||
| include $(top_srcdir)/contrib/contrib-global.mk | ||||
| endif | ||||
|  | ||||
| $(OBJS): pg_rman.h | ||||
							
								
								
									
										593
									
								
								catalog.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										593
									
								
								catalog.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,593 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * catalog.c: backup catalog opration | ||||
|  * | ||||
|  * Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #include "pg_rman.h" | ||||
|  | ||||
| #include <stdlib.h> | ||||
| #include <libgen.h> | ||||
| #include <unistd.h> | ||||
| #include <dirent.h> | ||||
| #include <time.h> | ||||
| #include <sys/stat.h> | ||||
| #include <sys/types.h> | ||||
| #include <sys/file.h> | ||||
|  | ||||
| #include "pgut/pgut-port.h" | ||||
|  | ||||
| static pgBackup *catalog_read_ini(const char *path); | ||||
|  | ||||
| #define BOOL_TO_STR(val)	((val) ? "true" : "false") | ||||
|  | ||||
| static int lock_fd = -1; | ||||
|  | ||||
| /* | ||||
|  * Lock of the catalog with pg_rman.ini file and return 0. | ||||
|  * If the lock is held by another one, return 1 immediately. | ||||
|  */ | ||||
| int | ||||
| catalog_lock(void) | ||||
| { | ||||
| 	int		ret; | ||||
| 	char	id_path[MAXPGPATH]; | ||||
|  | ||||
| 	snprintf(id_path, lengthof(id_path), "%s/%s", backup_path, | ||||
| 		PG_RMAN_INI_FILE); | ||||
| 	lock_fd = open(id_path, O_RDWR); | ||||
| 	if (lock_fd == -1) | ||||
| 		elog(errno == ENOENT ? ERROR_CORRUPTED : ERROR_SYSTEM, | ||||
| 			_("can't open file \"%s\": %s"), id_path, strerror(errno)); | ||||
|  | ||||
| 	ret = flock(lock_fd, LOCK_EX | LOCK_NB);	/* non-blocking */ | ||||
| 	if (ret == -1) | ||||
| 	{ | ||||
| 		if (errno == EWOULDBLOCK) | ||||
| 		{ | ||||
| 			close(lock_fd); | ||||
| 			return 1; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			int errno_tmp = errno; | ||||
| 			close(lock_fd); | ||||
| 			elog(ERROR_SYSTEM, _("can't lock file \"%s\": %s"), id_path, | ||||
| 				strerror(errno_tmp)); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Release catalog lock. | ||||
|  */ | ||||
| void | ||||
| catalog_unlock(void) | ||||
| { | ||||
| 	close(lock_fd); | ||||
| 	lock_fd = -1; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Create a pgBackup which taken at timestamp. | ||||
|  * If no backup matches, return NULL. | ||||
|  */ | ||||
| pgBackup * | ||||
| catalog_get_backup(time_t timestamp) | ||||
| { | ||||
| 	pgBackup tmp; | ||||
| 	char ini_path[MAXPGPATH]; | ||||
|  | ||||
| 	tmp.start_time = timestamp; | ||||
| 	pgBackupGetPath(&tmp, ini_path, lengthof(ini_path), BACKUP_INI_FILE); | ||||
|  | ||||
| 	return catalog_read_ini(ini_path); | ||||
| } | ||||
|  | ||||
| static bool | ||||
| IsDir(const char *dirpath, const DIR *dir, const struct dirent *ent) | ||||
| { | ||||
| #if defined(DT_DIR) | ||||
| 	return ent->d_type == DT_DIR; | ||||
| #elif defined(_finddata_t) | ||||
| 	return (dir->dd_dta.attrib & FILE_ATTRIBUTE_DIRECTORY) != 0; | ||||
| #else  | ||||
| 	char	path[MAXPGPATH]; | ||||
| 	DWORD	attr; | ||||
|  | ||||
| 	/* dirent.d_type does not exists */ | ||||
| 	strlcpy(path, dirpath, MAXPGPATH); | ||||
| 	strlcat(path, "/", MAXPGPATH); | ||||
| 	strlcat(path, ent->d_name, MAXPGPATH); | ||||
| 	attr = GetFileAttributes(path); | ||||
| 	return attr != (DWORD) -1 && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0; | ||||
| #endif | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Create list fo backups started between begin and end from backup catalog. | ||||
|  * If range was NULL, all of backup are listed. | ||||
|  * The list is sorted in order of descending start time. | ||||
|  */ | ||||
| parray * | ||||
| catalog_get_backup_list(const pgBackupRange *range) | ||||
| { | ||||
| 	const pgBackupRange range_all = { 0, 0 }; | ||||
| 	DIR *date_dir = NULL; | ||||
| 	struct dirent *date_ent = NULL; | ||||
| 	DIR *time_dir = NULL; | ||||
| 	struct dirent *time_ent = NULL; | ||||
| 	char date_path[MAXPGPATH]; | ||||
| 	parray *backups = NULL; | ||||
| 	pgBackup *backup = NULL; | ||||
| 	struct tm *tm; | ||||
| 	char begin_date[100]; | ||||
| 	char begin_time[100]; | ||||
| 	char end_date[100]; | ||||
| 	char end_time[100]; | ||||
|  | ||||
| 	if (range == NULL) | ||||
| 		range = &range_all; | ||||
|  | ||||
| 	/* make date/time string */ | ||||
| 	tm = localtime(&range->begin); | ||||
| 	strftime(begin_date, lengthof(begin_date), "%Y%m%d", tm); | ||||
| 	strftime(begin_time, lengthof(begin_time), "%H%M%S", tm); | ||||
| 	tm = localtime(&range->end); | ||||
| 	strftime(end_date, lengthof(end_date), "%Y%m%d", tm); | ||||
| 	strftime(end_time, lengthof(end_time), "%H%M%S", tm); | ||||
|  | ||||
| 	/* open backup root directory */ | ||||
| 	date_dir = opendir(backup_path); | ||||
| 	if (date_dir == NULL) | ||||
| 	{ | ||||
| 		elog(WARNING, _("can't open directory \"%s\": %s"), backup_path, | ||||
| 			strerror(errno)); | ||||
| 		goto err_proc; | ||||
| 	} | ||||
|  | ||||
| 	/* scan date/time directories and list backups in the range */ | ||||
| 	backups = parray_new(); | ||||
| 	for (; (date_ent = readdir(date_dir)) != NULL; errno = 0) | ||||
| 	{ | ||||
| 		/* skip not-directory entries and hidden entries */ | ||||
| 		if (!IsDir(backup_path, date_dir, date_ent) || date_ent->d_name[0] == '.') | ||||
| 			continue; | ||||
|  | ||||
| 		/* skip online WAL & serverlog backup directory */ | ||||
| 		if (strcmp(date_ent->d_name, RESTORE_WORK_DIR) == 0) | ||||
| 			continue; | ||||
|  | ||||
| 		/* If the date is out of range, skip it. */ | ||||
| 		if (pgBackupRangeIsValid(range) && | ||||
| 				(strcmp(begin_date, date_ent->d_name) > 0 || | ||||
| 								strcmp(end_date, date_ent->d_name) < 0)) | ||||
| 			continue; | ||||
|  | ||||
| 		/* open subdirectory (date directory) and search time directory */ | ||||
| 		snprintf(date_path, MAXPGPATH, "%s/%s", backup_path, date_ent->d_name); | ||||
| 		time_dir = opendir(date_path); | ||||
| 		if (time_dir == NULL) | ||||
| 		{ | ||||
| 			elog(WARNING, _("can't open directory \"%s\": %s"), | ||||
| 				date_ent->d_name, strerror(errno)); | ||||
| 			goto err_proc; | ||||
| 		} | ||||
| 		for (; (time_ent = readdir(time_dir)) != NULL; errno = 0) | ||||
| 		{ | ||||
| 			char ini_path[MAXPGPATH]; | ||||
|  | ||||
| 			/* skip not-directry and hidden directories */ | ||||
| 			if (!IsDir(date_path, date_dir, time_ent) || time_ent->d_name[0] == '.') | ||||
| 				continue; | ||||
|  | ||||
| 			/* If the time is out of range, skip it. */ | ||||
| 			if (pgBackupRangeIsValid(range) && | ||||
| 					(strcmp(begin_time, time_ent->d_name) > 0 || | ||||
| 									strcmp(end_time, time_ent->d_name) < 0)) | ||||
| 				continue; | ||||
|  | ||||
| 			/* read backup information from backup.ini */ | ||||
| 			snprintf(ini_path, MAXPGPATH, "%s/%s/%s", date_path, | ||||
| 				time_ent->d_name, BACKUP_INI_FILE); | ||||
| 			backup = catalog_read_ini(ini_path); | ||||
| 			/* ignore corrupted backup */ | ||||
| 			if (backup) | ||||
| 			{ | ||||
| 				parray_append(backups, backup); | ||||
| 				backup = NULL; | ||||
| 			} | ||||
| 		} | ||||
| 		if (errno && errno != ENOENT) | ||||
| 		{ | ||||
| 			elog(WARNING, _("can't read date directory \"%s\": %s"), | ||||
| 				date_ent->d_name, strerror(errno)); | ||||
| 			goto err_proc; | ||||
| 		} | ||||
| 		closedir(time_dir); | ||||
| 		time_dir = NULL; | ||||
| 	} | ||||
| 	if (errno) | ||||
| 	{ | ||||
| 		elog(WARNING, _("can't read backup root directory \"%s\": %s"), | ||||
| 			backup_path, strerror(errno)); | ||||
| 		goto err_proc; | ||||
| 	} | ||||
|  | ||||
| 	closedir(date_dir); | ||||
| 	date_dir = NULL; | ||||
|  | ||||
| 	parray_qsort(backups, pgBackupCompareIdDesc); | ||||
|  | ||||
| 	return backups; | ||||
|  | ||||
| err_proc: | ||||
| 	if (time_dir) | ||||
| 		closedir(time_dir); | ||||
| 	if (date_dir) | ||||
| 		closedir(date_dir); | ||||
| 	if (backup) | ||||
| 		pgBackupFree(backup); | ||||
| 	if (backups) | ||||
| 		parray_walk(backups, pgBackupFree); | ||||
| 	parray_free(backups); | ||||
| 	return NULL; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Find the last completed database backup from the backup list. | ||||
|  */ | ||||
| pgBackup * | ||||
| catalog_get_last_data_backup(parray *backup_list) | ||||
| { | ||||
| 	int i; | ||||
| 	pgBackup *backup = NULL; | ||||
|  | ||||
| 	/* backup_list is sorted in order of descending ID */ | ||||
| 	for (i = 0; i < parray_num(backup_list); i++) | ||||
| 	{ | ||||
| 		backup = (pgBackup *) parray_get(backup_list, i); | ||||
|  | ||||
| 		/* we need completed database backup */ | ||||
| 		if (backup->status == BACKUP_STATUS_OK && HAVE_DATABASE(backup)) | ||||
| 			return backup; | ||||
| 	} | ||||
|  | ||||
| 	return NULL; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Find the last completed archived WAL backup from the backup list. | ||||
|  */ | ||||
| pgBackup * | ||||
| catalog_get_last_arclog_backup(parray *backup_list) | ||||
| { | ||||
| 	int i; | ||||
| 	pgBackup *backup = NULL; | ||||
|  | ||||
| 	/* backup_list is sorted in order of descending ID */ | ||||
| 	for (i = 0; i < parray_num(backup_list); i++) | ||||
| 	{ | ||||
| 		backup = (pgBackup *) parray_get(backup_list, i); | ||||
|  | ||||
| 		/* we need completed archived WAL backup */ | ||||
| 		if (backup->status == BACKUP_STATUS_OK && HAVE_ARCLOG(backup)) | ||||
| 			return backup; | ||||
| 	} | ||||
|  | ||||
| 	return NULL; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Find the last completed serverlog backup from the backup list. | ||||
|  */ | ||||
| pgBackup * | ||||
| catalog_get_last_srvlog_backup(parray *backup_list) | ||||
| { | ||||
| 	int i; | ||||
| 	pgBackup *backup = NULL; | ||||
|  | ||||
| 	/* backup_list is sorted in order of descending ID */ | ||||
| 	for (i = 0; i < parray_num(backup_list); i++) | ||||
| 	{ | ||||
| 		backup = (pgBackup *) parray_get(backup_list, i); | ||||
|  | ||||
| 		/* we need completed serverlog backup */ | ||||
| 		if (backup->status == BACKUP_STATUS_OK && backup->with_serverlog) | ||||
| 			return backup; | ||||
| 	} | ||||
|  | ||||
| 	return NULL; | ||||
| } | ||||
|  | ||||
| /* create backup directory in $BACKUP_PATH */ | ||||
| int | ||||
| pgBackupCreateDir(pgBackup *backup) | ||||
| {  | ||||
| 	int i; | ||||
| 	char path[MAXPGPATH]; | ||||
| 	char *subdirs[] = { DATABASE_DIR, ARCLOG_DIR, SRVLOG_DIR, NULL }; | ||||
|  | ||||
| 	pgBackupGetPath(backup, path, lengthof(path), NULL); | ||||
| 	dir_create_dir(path, DIR_PERMISSION); | ||||
|  | ||||
| 	/* create directories for actual backup files */ | ||||
| 	for (i = 0; subdirs[i]; i++) | ||||
| 	{ | ||||
| 		pgBackupGetPath(backup, path, lengthof(path), subdirs[i]); | ||||
| 		dir_create_dir(path, DIR_PERMISSION); | ||||
| 	} | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Write configuration section of backup.in to stream "out". | ||||
|  */ | ||||
| void | ||||
| pgBackupWriteConfigSection(FILE *out, pgBackup *backup) | ||||
| { | ||||
| 	static const char *modes[] = { "", "ARCHIVE", "INCREMENTAL", "FULL"}; | ||||
|  | ||||
| 	fprintf(out, "# configuration\n"); | ||||
|  | ||||
| 	fprintf(out, "BACKUP_MODE=%s\n", modes[backup->backup_mode]); | ||||
| 	fprintf(out, "WITH_SERVERLOG=%s\n", BOOL_TO_STR(backup->with_serverlog)); | ||||
| 	fprintf(out, "COMPRESS_DATA=%s\n", BOOL_TO_STR(backup->compress_data)); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Write result section of backup.in to stream "out". | ||||
|  */ | ||||
| void | ||||
| pgBackupWriteResultSection(FILE *out, pgBackup *backup) | ||||
| { | ||||
| 	char timestamp[20]; | ||||
|  | ||||
| 	fprintf(out, "# result\n"); | ||||
| 	fprintf(out, "TIMELINEID=%d\n", backup->tli); | ||||
| 	fprintf(out, "START_LSN=%x/%08x\n", backup->start_lsn.xlogid, | ||||
| 		backup->start_lsn.xrecoff); | ||||
| 	fprintf(out, "STOP_LSN=%x/%08x\n", backup->stop_lsn.xlogid, | ||||
| 		backup->stop_lsn.xrecoff); | ||||
| 	time2iso(timestamp, lengthof(timestamp), backup->start_time); | ||||
| 	fprintf(out, "START_TIME='%s'\n", timestamp); | ||||
| 	time2iso(timestamp, lengthof(timestamp), backup->end_time); | ||||
| 	fprintf(out, "END_TIME='%s'\n", timestamp); | ||||
| 	fprintf(out, "TOTAL_DATA_BYTES=" INT64_FORMAT "\n", backup->total_data_bytes); | ||||
| 	fprintf(out, "READ_DATA_BYTES=" INT64_FORMAT "\n", backup->read_data_bytes); | ||||
| 	fprintf(out, "READ_ARCLOG_BYTES=" INT64_FORMAT "\n", backup->read_arclog_bytes); | ||||
| 	fprintf(out, "READ_SRVLOG_BYTES=" INT64_FORMAT "\n", backup->read_srvlog_bytes); | ||||
| 	fprintf(out, "WRITE_BYTES=" INT64_FORMAT "\n", backup->write_bytes); | ||||
|  | ||||
| 	fprintf(out, "BLOCK_SIZE=%u\n", backup->block_size); | ||||
| 	fprintf(out, "XLOG_BLOCK_SIZE=%u\n", backup->wal_block_size); | ||||
|  | ||||
| 	fprintf(out, "STATUS=%s\n", status2str(backup->status)); | ||||
| } | ||||
|  | ||||
| /* create backup.ini */ | ||||
| void | ||||
| pgBackupWriteIni(pgBackup *backup) | ||||
| { | ||||
| 	FILE *fp = NULL; | ||||
| 	char ini_path[MAXPGPATH]; | ||||
|  | ||||
| 	pgBackupGetPath(backup, ini_path, lengthof(ini_path), BACKUP_INI_FILE); | ||||
| 	fp = fopen(ini_path, "wt"); | ||||
| 	if (fp == NULL) | ||||
| 		elog(ERROR_SYSTEM, _("can't open INI file \"%s\": %s"), ini_path, | ||||
| 			strerror(errno)); | ||||
|  | ||||
| 	/* configuration section */ | ||||
| 	pgBackupWriteConfigSection(fp, backup); | ||||
|  | ||||
| 	/* result section */ | ||||
| 	pgBackupWriteResultSection(fp, backup); | ||||
|  | ||||
| 	fclose(fp); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Read backup.ini and create pgBackup. | ||||
|  *  - Comment starts with ';'. | ||||
|  *  - Do not care section. | ||||
|  */ | ||||
| static pgBackup * | ||||
| catalog_read_ini(const char *path) | ||||
| { | ||||
| 	pgBackup   *backup; | ||||
| 	char	   *backup_mode = NULL; | ||||
| 	char	   *start_lsn = NULL; | ||||
| 	char	   *stop_lsn = NULL; | ||||
| 	char	   *status = NULL; | ||||
| 	int			i; | ||||
|  | ||||
| 	pgut_option options[] = | ||||
| 	{ | ||||
| 		{ 's', 0, "backup-mode"			, NULL, SOURCE_ENV }, | ||||
| 		{ 'b', 0, "with-serverlog"		, NULL, SOURCE_ENV }, | ||||
| 		{ 'b', 0, "compress-data"		, NULL, SOURCE_ENV }, | ||||
| 		{ 'u', 0, "timelineid"			, NULL, SOURCE_ENV }, | ||||
| 		{ 's', 0, "start-lsn"			, NULL, SOURCE_ENV }, | ||||
| 		{ 's', 0, "stop-lsn"			, NULL, SOURCE_ENV }, | ||||
| 		{ 't', 0, "start-time"			, NULL, SOURCE_ENV }, | ||||
| 		{ 't', 0, "end-time"			, NULL, SOURCE_ENV }, | ||||
| 		{ 'I', 0, "total-data-bytes"	, NULL, SOURCE_ENV }, | ||||
| 		{ 'I', 0, "read-data-bytes"		, NULL, SOURCE_ENV }, | ||||
| 		{ 'I', 0, "read-arclog-bytes"	, NULL, SOURCE_ENV }, | ||||
| 		{ 'I', 0, "read-srvlog-bytes"	, NULL, SOURCE_ENV }, | ||||
| 		{ 'I', 0, "write-bytes"			, NULL, SOURCE_ENV }, | ||||
| 		{ 'u', 0, "block-size"			, NULL, SOURCE_ENV }, | ||||
| 		{ 'u', 0, "xlog-block-size"		, NULL, SOURCE_ENV }, | ||||
| 		{ 's', 0, "status"				, NULL, SOURCE_ENV }, | ||||
| 		{ 0 } | ||||
| 	}; | ||||
|  | ||||
| 	backup = (pgBackup *) pgut_malloc(sizeof(*backup)); | ||||
| 	catalog_init_config(backup); | ||||
|  | ||||
| 	i = 0; | ||||
| 	options[i++].var = &backup_mode; | ||||
| 	options[i++].var = &backup->with_serverlog; | ||||
| 	options[i++].var = &backup->compress_data; | ||||
| 	options[i++].var = &backup->tli; | ||||
| 	options[i++].var = &start_lsn; | ||||
| 	options[i++].var = &stop_lsn; | ||||
| 	options[i++].var = &backup->start_time; | ||||
| 	options[i++].var = &backup->end_time; | ||||
| 	options[i++].var = &backup->total_data_bytes; | ||||
| 	options[i++].var = &backup->read_data_bytes; | ||||
| 	options[i++].var = &backup->read_arclog_bytes; | ||||
| 	options[i++].var = &backup->read_srvlog_bytes; | ||||
| 	options[i++].var = &backup->write_bytes; | ||||
| 	options[i++].var = &backup->block_size; | ||||
| 	options[i++].var = &backup->wal_block_size; | ||||
| 	options[i++].var = &status; | ||||
| 	Assert(i == lengthof(options) - 1); | ||||
|  | ||||
| 	pgut_readopt(path, options, ERROR_CORRUPTED); | ||||
|  | ||||
| 	if (backup_mode) | ||||
| 	{ | ||||
| 		backup->backup_mode = parse_backup_mode(backup_mode, WARNING); | ||||
| 		free(backup_mode); | ||||
| 	} | ||||
|  | ||||
| 	if (start_lsn) | ||||
| 	{ | ||||
| 		XLogRecPtr lsn; | ||||
| 		if (sscanf(start_lsn, "%X/%X", &lsn.xlogid, &lsn.xrecoff) == 2) | ||||
| 			backup->start_lsn = lsn; | ||||
| 		else | ||||
| 			elog(WARNING, _("invalid START_LSN \"%s\""), start_lsn); | ||||
| 		free(start_lsn); | ||||
| 	} | ||||
|  | ||||
| 	if (stop_lsn) | ||||
| 	{ | ||||
| 		XLogRecPtr lsn; | ||||
| 		if (sscanf(stop_lsn, "%X/%X", &lsn.xlogid, &lsn.xrecoff) == 2) | ||||
| 			backup->stop_lsn = lsn; | ||||
| 		else | ||||
| 			elog(WARNING, _("invalid STOP_LSN \"%s\""), stop_lsn); | ||||
| 		free(stop_lsn); | ||||
| 	} | ||||
|  | ||||
| 	if (status) | ||||
| 	{ | ||||
| 		if (strcmp(status, "OK") == 0) | ||||
| 			backup->status = BACKUP_STATUS_OK; | ||||
| 		else if (strcmp(status, "RUNNING") == 0) | ||||
| 			backup->status = BACKUP_STATUS_RUNNING; | ||||
| 		else if (strcmp(status, "ERROR") == 0) | ||||
| 			backup->status = BACKUP_STATUS_ERROR; | ||||
| 		else if (strcmp(status, "DELETING") == 0) | ||||
| 			backup->status = BACKUP_STATUS_DELETING; | ||||
| 		else if (strcmp(status, "DELETED") == 0) | ||||
| 			backup->status = BACKUP_STATUS_DELETED; | ||||
| 		else if (strcmp(status, "DONE") == 0) | ||||
| 			backup->status = BACKUP_STATUS_DONE; | ||||
| 		else if (strcmp(status, "CORRUPT") == 0) | ||||
| 			backup->status = BACKUP_STATUS_CORRUPT; | ||||
| 		else | ||||
| 			elog(WARNING, _("invalid STATUS \"%s\""), status); | ||||
| 		free(status); | ||||
| 	} | ||||
|  | ||||
| 	return backup; | ||||
| } | ||||
|  | ||||
| BackupMode | ||||
| parse_backup_mode(const char *value, int elevel) | ||||
| { | ||||
| 	const char *v = value; | ||||
| 	size_t		len; | ||||
|  | ||||
| 	while (IsSpace(*v)) { v++; } | ||||
| 	len = strlen(v); | ||||
|  | ||||
| 	if (len > 0 && pg_strncasecmp("full", v, len) == 0) | ||||
| 		return BACKUP_MODE_FULL; | ||||
| 	else if (len > 0 && pg_strncasecmp("incremental", v, len) == 0) | ||||
| 		return BACKUP_MODE_INCREMENTAL; | ||||
| 	else if (len > 0 && pg_strncasecmp("archive", v, len) == 0) | ||||
| 		return BACKUP_MODE_ARCHIVE; | ||||
|  | ||||
| 	elog(elevel, _("invalid backup-mode \"%s\""), value); | ||||
| 	return BACKUP_MODE_INVALID; | ||||
| } | ||||
|  | ||||
| /* free pgBackup object */ | ||||
| void | ||||
| pgBackupFree(void *backup) | ||||
| { | ||||
| 	free(backup); | ||||
| } | ||||
|  | ||||
| /* Compare two pgBackup with their ID (start time) in ascending order */ | ||||
| int | ||||
| pgBackupCompareId(const void *l, const void *r) | ||||
| { | ||||
| 	pgBackup *lp = *(pgBackup **)l; | ||||
| 	pgBackup *rp = *(pgBackup **)r; | ||||
|  | ||||
| 	if (lp->start_time > rp->start_time) | ||||
| 		return 1; | ||||
| 	else if (lp->start_time < rp->start_time) | ||||
| 		return -1; | ||||
| 	else | ||||
| 		return 0; | ||||
| } | ||||
|  | ||||
| /* Compare two pgBackup with their ID in descending order */ | ||||
| int | ||||
| pgBackupCompareIdDesc(const void *l, const void *r) | ||||
| { | ||||
| 	return -pgBackupCompareId(l, r); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Construct absolute path of the backup directory. | ||||
|  * If subdir is not NULL, it will be appended after the path. | ||||
|  */ | ||||
| void | ||||
| pgBackupGetPath(const pgBackup *backup, char *path, size_t len, const char *subdir) | ||||
| { | ||||
| 	char datetime[20]; | ||||
| 	struct tm *tm; | ||||
|  | ||||
| 	/* generate $BACKUP_PATH/date/time path */ | ||||
| 	tm = localtime(&backup->start_time); | ||||
| 	strftime(datetime, lengthof(datetime), "%Y%m%d/%H%M%S", tm); | ||||
| 	if (subdir) | ||||
| 		snprintf(path, len, "%s/%s/%s", backup_path, datetime, subdir); | ||||
| 	else | ||||
| 		snprintf(path, len, "%s/%s", backup_path, datetime); | ||||
| } | ||||
|  | ||||
| void | ||||
| catalog_init_config(pgBackup *backup) | ||||
| { | ||||
| 	backup->backup_mode = BACKUP_MODE_INVALID; | ||||
| 	backup->with_serverlog = false; | ||||
| 	backup->compress_data = false; | ||||
| 	backup->status = BACKUP_STATUS_INVALID; | ||||
| 	backup->tli = 0; | ||||
| 	backup->start_lsn.xlogid = 0; | ||||
| 	backup->start_lsn.xrecoff = 0; | ||||
| 	backup->stop_lsn.xlogid = 0; | ||||
| 	backup->stop_lsn.xrecoff = 0; | ||||
| 	backup->start_time = (time_t) 0; | ||||
| 	backup->end_time = (time_t) 0; | ||||
| 	backup->total_data_bytes = BYTES_INVALID; | ||||
| 	backup->read_data_bytes = BYTES_INVALID; | ||||
| 	backup->read_arclog_bytes = BYTES_INVALID; | ||||
| 	backup->read_srvlog_bytes = BYTES_INVALID; | ||||
| 	backup->write_bytes = BYTES_INVALID; | ||||
| } | ||||
							
								
								
									
										886
									
								
								data.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										886
									
								
								data.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,886 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * data.c: compress / uncompress data pages | ||||
|  * | ||||
|  * Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #include "pg_rman.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" | ||||
|  | ||||
| #if PG_VERSION_NUM < 80300 | ||||
| #define XLogRecPtrIsInvalid(r)	((r).xrecoff == 0) | ||||
| #endif | ||||
|  | ||||
| #ifdef HAVE_LIBZ | ||||
| #include <zlib.h> | ||||
|  | ||||
| #define zlibOutSize 4096 | ||||
| #define zlibInSize  4096 | ||||
|  | ||||
| static int doDeflate(z_stream *zp, size_t in_size, size_t out_size, void *inbuf, | ||||
| 	void *outbuf, FILE *in, FILE *out, pg_crc32 *crc, size_t *write_size, | ||||
| 	int flash); | ||||
| static int doInflate(z_stream *zp, size_t in_size, size_t out_size,void *inbuf, | ||||
| 	void *outbuf, FILE *in, FILE *out, pg_crc32 *crc, size_t *read_size); | ||||
|  | ||||
| static int | ||||
| doDeflate(z_stream *zp, size_t in_size, size_t out_size, void *inbuf, | ||||
| 	void *outbuf, FILE *in, FILE *out, pg_crc32 *crc, size_t *write_size, | ||||
| 	int flash) | ||||
| { | ||||
| 	int	status; | ||||
|  | ||||
| 	zp->next_in = inbuf; | ||||
| 	zp->avail_in = in_size; | ||||
|  | ||||
| 	/* compresses until an input buffer becomes empty. */ | ||||
| 	do | ||||
| 	{ | ||||
| 		if (interrupted) | ||||
| 			elog(ERROR_INTERRUPTED, _("interrupted during deflate")); | ||||
|  | ||||
| 		status = deflate(zp, flash); | ||||
|  | ||||
| 		if (status == Z_STREAM_ERROR) | ||||
| 		{ | ||||
| 			fclose(in); | ||||
| 			fclose(out); | ||||
| 			elog(ERROR_SYSTEM, _("can't compress data: %s"), zp->msg); | ||||
| 		} | ||||
|  | ||||
| 		if (fwrite(outbuf, 1, out_size - zp->avail_out, out) != | ||||
| 				out_size - zp->avail_out) | ||||
| 		{ | ||||
| 			fclose(in); | ||||
| 			fclose(out); | ||||
| 			elog(ERROR_SYSTEM, _("can't write file: %s"), strerror(errno)); | ||||
| 		} | ||||
|  | ||||
| 		/* update CRC */ | ||||
| 		COMP_CRC32(*crc, outbuf, out_size - zp->avail_out); | ||||
|  | ||||
| 		*write_size += out_size - zp->avail_out; | ||||
|  | ||||
| 		zp->next_out = outbuf; | ||||
| 		zp->avail_out = out_size; | ||||
| 	} while (zp->avail_in != 0); | ||||
|  | ||||
| 	return status; | ||||
| } | ||||
|  | ||||
| static int | ||||
| doInflate(z_stream *zp, size_t in_size, size_t out_size,void *inbuf, | ||||
| 	void *outbuf, FILE *in, FILE *out, pg_crc32 *crc, size_t *read_size) | ||||
| { | ||||
| 	int	status = Z_OK; | ||||
|  | ||||
| 	zp->next_out = outbuf; | ||||
| 	zp->avail_out = out_size; | ||||
|  | ||||
| 	/* decompresses until an output buffer becomes full. */ | ||||
| 	for (;;) | ||||
| 	{ | ||||
| 		if (interrupted) | ||||
| 			elog(ERROR_INTERRUPTED, _("interrupted during inflate")); | ||||
|  | ||||
| 		/* input buffer becomes empty, read it from a file. */ | ||||
| 		if (zp->avail_in == 0) | ||||
| 		{ | ||||
| 			size_t	read_len; | ||||
|  | ||||
| 			read_len = fread(inbuf, 1, in_size, in); | ||||
|  | ||||
| 			if (read_len != in_size) | ||||
| 			{ | ||||
| 				int errno_tmp = errno; | ||||
|  | ||||
| 				if (!feof(in)) | ||||
| 				{ | ||||
| 					fclose(in); | ||||
| 					fclose(out); | ||||
| 					elog(ERROR_CORRUPTED, | ||||
| 						_("can't read compress file: %s"), strerror(errno_tmp)); | ||||
| 				} | ||||
|  | ||||
| 				if (read_len == 0 && *read_size == 0) | ||||
| 					return Z_STREAM_END; | ||||
| 			} | ||||
|  | ||||
| 			zp->next_in = inbuf; | ||||
| 			zp->avail_in = read_len; | ||||
| 			*read_size += read_len; | ||||
| 		} | ||||
|  | ||||
| 		/* decompresses input file data */ | ||||
| 		status = inflate(zp, Z_NO_FLUSH); | ||||
|  | ||||
| 		if (status == Z_STREAM_END) | ||||
| 		{ | ||||
| 			if (feof(in)) | ||||
| 				break; | ||||
| 			/* not reached to EOF, read again */ | ||||
| 		} | ||||
| 		else if (status == Z_OK) | ||||
| 		{ | ||||
| 			if (zp->avail_out == 0) | ||||
| 				break; | ||||
| 			/* more input needed to fill out_buf */ | ||||
| 		} | ||||
| 		else if (status != Z_OK) | ||||
| 		{ | ||||
| 			fclose(in); | ||||
| 			fclose(out); | ||||
| 			elog(ERROR_SYSTEM, _("can't uncompress data: %s"), strerror(errno)); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/* update CRC */ | ||||
| 	COMP_CRC32(*crc, outbuf, out_size - zp->avail_out); | ||||
|  | ||||
| 	return status; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| typedef union DataPage | ||||
| { | ||||
| 	PageHeaderData	header; | ||||
| 	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 | ||||
| is_valid_header(const PageHeader page) | ||||
| { | ||||
| 	const char *pagebytes; | ||||
| 	int			i; | ||||
|  | ||||
| 	/* Check normal case */ | ||||
| 	if (PageGetPageSize(page) == BLCKSZ && | ||||
| 		PageGetPageLayoutVersion(page) == PG_PAGE_LAYOUT_VERSION && | ||||
| #if PG_VERSION_NUM >= 80300 | ||||
| 		(page->pd_flags & ~PD_VALID_FLAG_BITS) == 0 && | ||||
| #endif | ||||
| 		page->pd_lower >= SizeOfPageHeaderData && | ||||
| 		page->pd_lower <= page->pd_upper && | ||||
| 		page->pd_upper <= page->pd_special && | ||||
| 		page->pd_special <= BLCKSZ && | ||||
| 		page->pd_special == MAXALIGN(page->pd_special)) | ||||
| 		return true; | ||||
|  | ||||
| 	/* Check all-zeroes case */ | ||||
| 	pagebytes = (char *) page; | ||||
| 	for (i = 0; i < BLCKSZ; i++) | ||||
| 	{ | ||||
| 		if (pagebytes[i] != 0) | ||||
| 			return false; | ||||
| 	} | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * 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. | ||||
|  */ | ||||
| void | ||||
| backup_data_file(const char *from_root, const char *to_root, | ||||
| 				 pgFile *file, const XLogRecPtr *lsn, bool compress_data) | ||||
| { | ||||
| 	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; | ||||
| #ifdef HAVE_LIBZ | ||||
| 	z_stream			z; | ||||
| 	char				outbuf[zlibOutSize]; | ||||
| #endif | ||||
|  | ||||
| 	/* open backup mode file for read */ | ||||
| 	in = fopen(file->path, "r"); | ||||
| 	if (in == NULL) | ||||
| 	{ | ||||
| 		/* meybe vanished, it's not error */ | ||||
| 		if (errno == ENOENT) | ||||
| 			return; | ||||
|  | ||||
| 		elog(ERROR_SYSTEM, _("can't 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 | ||||
| 		snprintf(to_path, lengthof(to_path), "%s/%s", | ||||
| 			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, _("can't open backup file \"%s\": %s"), | ||||
| 			to_path, strerror(errno_tmp)); | ||||
| 	} | ||||
|  | ||||
| 	INIT_CRC32(crc); | ||||
|  | ||||
| 	/* reset size summary */ | ||||
| 	file->read_size = 0; | ||||
| 	file->write_size = 0; | ||||
|  | ||||
| #ifdef HAVE_LIBZ | ||||
| 	if (compress_data) | ||||
| 	{ | ||||
| 		z.zalloc = Z_NULL; | ||||
| 		z.zfree = Z_NULL; | ||||
| 		z.opaque = Z_NULL; | ||||
|  | ||||
| 		if (deflateInit(&z, Z_DEFAULT_COMPRESSION) != Z_OK) | ||||
| 			elog(ERROR_SYSTEM, _("can't initialize compression library: %s"), | ||||
| 				z.msg); | ||||
|  | ||||
| 		z.avail_in = 0; | ||||
| 		z.next_out = (void *) outbuf; | ||||
| 		z.avail_out = zlibOutSize; | ||||
| 	} | ||||
| #endif | ||||
|  | ||||
| 	/* read each page and write the page excluding hole */ | ||||
| 	for (blknum = 0; | ||||
| 		 (read_len = fread(&page, 1, sizeof(page), in)) == sizeof(page); | ||||
| 		 ++blknum) | ||||
| 	{ | ||||
| 		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 (!is_valid_header(&page.header) || | ||||
| 				!XLogRecPtrIsInvalid(PageGetLSN(&page.header))) | ||||
| 		{ | ||||
| 			elog(LOG, "%s fall back to simple copy", file->path); | ||||
| 			fclose(in); | ||||
| 			fclose(out); | ||||
| 			file->is_datafile = false; | ||||
| 			copy_file(from_root, to_root, file, | ||||
| 				compress_data ? COMPRESSION : NO_COMPRESSION); | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		file->read_size += read_len; | ||||
|  | ||||
| 		/* if the page has not been modified since last backup, skip it */ | ||||
| 		if (lsn && !XLogRecPtrIsInvalid(PageGetLSN(&page.header)) && | ||||
| 				XLByteLT(PageGetLSN(&page.header), *lsn)) | ||||
| 			continue; | ||||
|  | ||||
| 		header.hole_offset = page.header.pd_lower; | ||||
| 		header.hole_length = page.header.pd_upper - page.header.pd_lower; | ||||
|  | ||||
| 		upper_offset = header.hole_offset + header.hole_length; | ||||
| 		upper_length = BLCKSZ - upper_offset; | ||||
|  | ||||
| #ifdef HAVE_LIBZ | ||||
| 		if (compress_data) | ||||
| 		{ | ||||
| 			doDeflate(&z, sizeof(header), sizeof(outbuf), &header, outbuf, in, | ||||
| 					  out, &crc, &file->write_size, Z_NO_FLUSH); | ||||
| 			doDeflate(&z, header.hole_offset, sizeof(outbuf), page.data, outbuf, | ||||
| 					  in, out, &crc, &file->write_size, Z_NO_FLUSH); | ||||
| 			doDeflate(&z, upper_length, sizeof(outbuf), | ||||
| 					  page.data + upper_offset, outbuf, in, out, &crc, | ||||
| 					  &file->write_size, Z_NO_FLUSH); | ||||
| 		} | ||||
| 		else | ||||
| #endif | ||||
| 		{ | ||||
| 			/* 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, _("can't write at block %u of \"%s\": %s"), | ||||
| 					blknum, to_path, strerror(errno_tmp)); | ||||
| 			} | ||||
|  | ||||
| 			/* update CRC */ | ||||
| 			COMP_CRC32(crc, &header, sizeof(header)); | ||||
| 			COMP_CRC32(crc, page.data, header.hole_offset); | ||||
| 			COMP_CRC32(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, _("can't 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; | ||||
|  | ||||
| #ifdef HAVE_LIBZ | ||||
| 			if (compress_data) | ||||
| 			{ | ||||
| 				doDeflate(&z, sizeof(header), sizeof(outbuf), &header, outbuf, | ||||
| 					in, out, &crc, &file->write_size, Z_NO_FLUSH); | ||||
| 			} | ||||
| 			else | ||||
| #endif | ||||
| 			{ | ||||
| 				if (fwrite(&header, 1, sizeof(header), out) != sizeof(header)) | ||||
| 				{ | ||||
| 					int errno_tmp = errno; | ||||
| 					/* oops */ | ||||
| 					fclose(in); | ||||
| 					fclose(out); | ||||
| 					elog(ERROR_SYSTEM, | ||||
| 						 _("can't write at block %u of \"%s\": %s"), | ||||
| 						 blknum, to_path, strerror(errno_tmp)); | ||||
| 				} | ||||
| 				COMP_CRC32(crc, &header, sizeof(header)); | ||||
| 				file->write_size += sizeof(header); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		/* write odd size page image */ | ||||
| #ifdef HAVE_LIBZ | ||||
| 		if (compress_data) | ||||
| 		{ | ||||
| 			doDeflate(&z, read_len, sizeof(outbuf), page.data, outbuf, in, out, | ||||
| 				&crc, &file->write_size, Z_NO_FLUSH); | ||||
| 		} | ||||
| 		else | ||||
| #endif | ||||
| 		{ | ||||
| 			if (fwrite(page.data, 1, read_len, out) != read_len) | ||||
| 			{ | ||||
| 				int errno_tmp = errno; | ||||
| 				/* oops */ | ||||
| 				fclose(in); | ||||
| 				fclose(out); | ||||
| 				elog(ERROR_SYSTEM, _("can't write at block %u of \"%s\": %s"), | ||||
| 					blknum, to_path, strerror(errno_tmp)); | ||||
| 			} | ||||
|  | ||||
| 			COMP_CRC32(crc, page.data, read_len); | ||||
| 			file->write_size += read_len; | ||||
| 		} | ||||
|  | ||||
| 		file->read_size += read_len; | ||||
| 	} | ||||
|  | ||||
| #ifdef HAVE_LIBZ | ||||
| 	if (compress_data) | ||||
| 	{ | ||||
| 		if (file->read_size > 0) | ||||
| 		{ | ||||
| 			while (doDeflate(&z, 0, sizeof(outbuf), NULL, outbuf, in, out, &crc, | ||||
| 							 &file->write_size, Z_FINISH) != Z_STREAM_END) | ||||
| 			{ | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if (deflateEnd(&z) != Z_OK) | ||||
| 		{ | ||||
| 			fclose(in); | ||||
| 			fclose(out); | ||||
| 			elog(ERROR_SYSTEM, _("can't close compression stream: %s"), z.msg); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| #endif | ||||
| 	/* update file permission */ | ||||
| 	if (!check && chmod(to_path, FILE_PERMISSION) == -1) | ||||
| 	{ | ||||
| 		int errno_tmp = errno; | ||||
| 		fclose(in); | ||||
| 		fclose(out); | ||||
| 		elog(ERROR_SYSTEM, _("can't change mode of \"%s\": %s"), file->path, | ||||
| 			strerror(errno_tmp)); | ||||
| 	} | ||||
|  | ||||
| 	fclose(in); | ||||
| 	fclose(out); | ||||
|  | ||||
| 	/* finish CRC calculation and store into pgFile */ | ||||
| 	FIN_CRC32(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, _("can't remove file \"%s\": %s"), to_path, | ||||
| 				strerror(errno)); | ||||
|  | ||||
| 	/* remove $BACKUP_PATH/tmp created during check */ | ||||
| 	if (check) | ||||
| 		remove(to_path); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * 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, bool compress_data) | ||||
| { | ||||
| 	char to_path[MAXPGPATH]; | ||||
| 	FILE *in; | ||||
| 	FILE *out; | ||||
| 	BackupPageHeader header; | ||||
| 	BlockNumber blknum; | ||||
| #ifdef HAVE_LIBZ | ||||
| 	z_stream	z; | ||||
| 	int			status; | ||||
| 	char		inbuf[zlibInSize]; | ||||
| 	pg_crc32	crc; | ||||
| 	size_t		read_size; | ||||
| #endif | ||||
|  | ||||
| 	/* If the file is not a datafile, copy it. */ | ||||
| 	if (!file->is_datafile) | ||||
| 	{ | ||||
| 		copy_file(from_root, to_root, file, | ||||
| 			compress_data ? DECOMPRESSION : NO_COMPRESSION); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	/* open backup mode file for read */ | ||||
| 	in = fopen(file->path, "r"); | ||||
| 	if (in == NULL) | ||||
| 	{ | ||||
| 		elog(ERROR_SYSTEM, _("can't open backup file \"%s\": %s"), file->path, | ||||
| 			strerror(errno)); | ||||
| 	} | ||||
|  | ||||
| 	/* open backup file for write  */ | ||||
| 	snprintf(to_path, lengthof(to_path), "%s/%s", 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, _("can't open restore target file \"%s\": %s"), | ||||
| 			to_path, strerror(errno_tmp)); | ||||
| 	} | ||||
|  | ||||
| #ifdef HAVE_LIBZ | ||||
| 	if (compress_data) | ||||
| 	{ | ||||
| 		z.zalloc = Z_NULL; | ||||
| 		z.zfree = Z_NULL; | ||||
| 		z.opaque = Z_NULL; | ||||
| 		z.next_in = Z_NULL; | ||||
| 		z.avail_in = 0; | ||||
|  | ||||
| 		if (inflateInit(&z) != Z_OK) | ||||
| 			elog(ERROR_SYSTEM, _("can't initialize compression library: %s"), | ||||
| 				z.msg); | ||||
| 		INIT_CRC32(crc); | ||||
| 		read_size = 0; | ||||
| 	} | ||||
| #endif | ||||
|  | ||||
| 	for (blknum = 0; ;blknum++) | ||||
| 	{ | ||||
| 		size_t read_len; | ||||
| 		DataPage page;		/* used as read buffer */ | ||||
| 		int upper_offset; | ||||
| 		int upper_length; | ||||
|  | ||||
| 		/* read BackupPageHeader */ | ||||
| #ifdef HAVE_LIBZ | ||||
| 		if (compress_data) | ||||
| 		{ | ||||
| 			status = doInflate(&z, sizeof(inbuf), sizeof(header), inbuf, | ||||
| 						&header, in, out, &crc, &read_size); | ||||
| 			if (status == Z_STREAM_END) | ||||
| 			{ | ||||
| 				if (z.avail_out != sizeof(header)) | ||||
| 					elog(ERROR_CORRUPTED, _("backup is broken header")); | ||||
| 				break; | ||||
| 			} | ||||
| 			if (z.avail_out != 0) | ||||
| 				elog(ERROR_SYSTEM, _("can't read block %u of \"%s\""), | ||||
| 					blknum, file->path); | ||||
| 		} | ||||
| 		else | ||||
| #endif | ||||
| 		{ | ||||
| 			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, _("can't 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); | ||||
| #ifdef HAVE_LIBZ | ||||
| 		if (compress_data) | ||||
| 		{ | ||||
| 			elog(LOG, "\n%s() %s %d %d", __FUNCTION__, file->path, header.hole_offset, upper_length); | ||||
| 			if (header.hole_offset > 0) | ||||
| 			{ | ||||
| 				doInflate(&z, sizeof(inbuf), header.hole_offset, inbuf, page.data, | ||||
| 					in, out, &crc, &read_size); | ||||
| 				if (z.avail_out != 0) | ||||
| 					elog(ERROR_SYSTEM, _("can't read block %u of \"%s\""), | ||||
| 						blknum, file->path); | ||||
| 			} | ||||
|  | ||||
| 			if (upper_length > 0) | ||||
| 			{ | ||||
| 				doInflate(&z, sizeof(inbuf), upper_length, inbuf, | ||||
| 					page.data + upper_offset, in, out, &crc, &read_size); | ||||
| 				if (z.avail_out != 0) | ||||
| 					elog(ERROR_SYSTEM, _("can't read block %u of \"%s\""), | ||||
| 						blknum, file->path); | ||||
| 			} | ||||
| 		} | ||||
| 		else | ||||
| #endif | ||||
| 		{ | ||||
| 			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, _("can't read block %u of \"%s\": %s"), | ||||
| 					blknum, file->path, strerror(errno)); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		/* by the incremental backup, we skip in a page without the update. */ | ||||
| 		if (blknum != header.block) | ||||
| 		{ | ||||
| 			if (fseek(out, (header.block - blknum) * BLCKSZ, SEEK_CUR) < 0) | ||||
| 				elog(ERROR_SYSTEM, _("can't seek restore target file \"%s\": %s"), | ||||
| 					to_path, strerror(errno)); | ||||
| 			blknum = header.block; | ||||
| 		} | ||||
|  | ||||
| 		if (fwrite(page.data, 1, sizeof(page), out) != sizeof(page)) | ||||
| 		{ | ||||
| 			elog(ERROR_SYSTEM, _("can't write block %u of \"%s\": %s"), | ||||
| 				blknum, file->path, strerror(errno)); | ||||
| 		} | ||||
|  | ||||
| 	} | ||||
|  | ||||
| #ifdef HAVE_LIBZ | ||||
| 	if (compress_data && inflateEnd(&z) != Z_OK) | ||||
| 		elog(ERROR_SYSTEM, _("can't close compression stream: %s"), z.msg); | ||||
| #endif | ||||
|  | ||||
| 	/* update file permission */ | ||||
| 	if (chmod(to_path, file->mode) == -1) | ||||
| 	{ | ||||
| 		int errno_tmp = errno; | ||||
| 		fclose(in); | ||||
| 		fclose(out); | ||||
| 		elog(ERROR_SYSTEM, _("can't change mode of \"%s\": %s"), to_path, | ||||
| 			strerror(errno_tmp)); | ||||
| 	} | ||||
|  | ||||
| 	fclose(in); | ||||
| 	fclose(out); | ||||
| } | ||||
|  | ||||
| void | ||||
| copy_file(const char *from_root, const char *to_root, pgFile *file, | ||||
| 	CompressionMode mode) | ||||
| { | ||||
| 	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; | ||||
| #ifdef HAVE_LIBZ | ||||
| 	z_stream	z; | ||||
| 	int			status; | ||||
| 	char		outbuf[zlibOutSize]; | ||||
| 	char		inbuf[zlibInSize]; | ||||
| #endif | ||||
|  | ||||
| 	/* open backup mode file for read */ | ||||
| 	in = fopen(file->path, "r"); | ||||
| 	if (in == NULL) | ||||
| 	{ | ||||
| 		/* meybe deleted, it's not error */ | ||||
| 		if (errno == ENOENT) | ||||
| 			return; | ||||
|  | ||||
| 		elog(ERROR_SYSTEM, _("can't 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 | ||||
| 		snprintf(to_path, lengthof(to_path), "%s/%s", 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, _("can't 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) | ||||
| 	{ | ||||
| 		elog(ERROR_SYSTEM, _("can't stat \"%s\": %s"), file->path, | ||||
| 			strerror(errno)); | ||||
| 	} | ||||
|  | ||||
| 	INIT_CRC32(crc); | ||||
|  | ||||
| 	/* reset size summary */ | ||||
| 	file->read_size = 0; | ||||
| 	file->write_size = 0; | ||||
|  | ||||
| #ifdef HAVE_LIBZ | ||||
| 	z.zalloc = Z_NULL; | ||||
| 	z.zfree = Z_NULL; | ||||
| 	z.opaque = Z_NULL; | ||||
|  | ||||
| 	if (mode == COMPRESSION) | ||||
| 	{ | ||||
| 		if (deflateInit(&z, Z_DEFAULT_COMPRESSION) != Z_OK) | ||||
| 			elog(ERROR_SYSTEM, _("can't initialize compression library: %s"), | ||||
| 				z.msg); | ||||
|  | ||||
| 		z.avail_in = 0; | ||||
| 		z.next_out = (void *) outbuf; | ||||
| 		z.avail_out = zlibOutSize; | ||||
| 	} | ||||
| 	else if (mode == DECOMPRESSION) | ||||
| 	{ | ||||
| 		z.next_in = Z_NULL; | ||||
| 		z.avail_in = 0; | ||||
| 		if (inflateInit(&z) != Z_OK) | ||||
| 			elog(ERROR_SYSTEM, _("can't initialize compression library: %s"), | ||||
| 				z.msg); | ||||
| 	} | ||||
| #endif | ||||
|  | ||||
| 	/* copy content and calc CRC */ | ||||
| 	for (;;) | ||||
| 	{ | ||||
| #ifdef HAVE_LIBZ | ||||
| 		if (mode == COMPRESSION) | ||||
| 		{ | ||||
| 			if ((read_len = fread(buf, 1, sizeof(buf), in)) != sizeof(buf)) | ||||
| 				break; | ||||
|  | ||||
| 			doDeflate(&z, read_len, sizeof(outbuf), buf, outbuf, in, out, &crc, | ||||
| 					  &file->write_size, Z_NO_FLUSH); | ||||
| 			file->read_size += sizeof(buf); | ||||
| 		} | ||||
| 		else if (mode == DECOMPRESSION) | ||||
| 		{ | ||||
| 			status = doInflate(&z, sizeof(inbuf), sizeof(outbuf), inbuf, outbuf, | ||||
| 						in, out, &crc, &file->read_size); | ||||
| 			if (fwrite(outbuf, 1, sizeof(outbuf) - z.avail_out, out) != | ||||
| 					sizeof(outbuf) - z.avail_out) | ||||
| 			{ | ||||
| 				errno_tmp = errno; | ||||
| 				/* oops */ | ||||
| 				fclose(in); | ||||
| 				fclose(out); | ||||
| 				elog(ERROR_SYSTEM, _("can't write to \"%s\": %s"), to_path, | ||||
| 					strerror(errno_tmp)); | ||||
| 			} | ||||
|  | ||||
| 			file->write_size += sizeof(outbuf) - z.avail_out; | ||||
| 			if (status == Z_STREAM_END) | ||||
| 				break; | ||||
| 		} | ||||
| 		else | ||||
| #endif | ||||
| 		{ | ||||
| 			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, _("can't write to \"%s\": %s"), to_path, | ||||
| 					strerror(errno_tmp)); | ||||
| 			} | ||||
| 			/* update CRC */ | ||||
| 			COMP_CRC32(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, _("can't read backup mode file \"%s\": %s"), | ||||
| 			file->path, strerror(errno_tmp)); | ||||
| 	} | ||||
|  | ||||
| 	/* copy odd part. */ | ||||
| 	if (read_len > 0) | ||||
| 	{ | ||||
| #ifdef HAVE_LIBZ | ||||
| 		if (mode == COMPRESSION) | ||||
| 		{ | ||||
| 			doDeflate(&z, read_len, sizeof(outbuf), buf, outbuf, in, out, &crc, | ||||
| 					  &file->write_size, Z_NO_FLUSH); | ||||
| 		} | ||||
| 		else | ||||
| #endif | ||||
| 		{ | ||||
| 			if (fwrite(buf, 1, read_len, out) != read_len) | ||||
| 			{ | ||||
| 				errno_tmp = errno; | ||||
| 				/* oops */ | ||||
| 				fclose(in); | ||||
| 				fclose(out); | ||||
| 				elog(ERROR_SYSTEM, _("can't write to \"%s\": %s"), to_path, | ||||
| 					strerror(errno_tmp)); | ||||
| 			} | ||||
| 			/* update CRC */ | ||||
| 			COMP_CRC32(crc, buf, read_len); | ||||
|  | ||||
| 			file->write_size += read_len; | ||||
| 		} | ||||
|  | ||||
| 		file->read_size += read_len; | ||||
| 	} | ||||
|  | ||||
| #ifdef HAVE_LIBZ | ||||
| 	if (mode == COMPRESSION) | ||||
| 	{ | ||||
| 		if (file->read_size > 0) | ||||
| 		{ | ||||
| 			while (doDeflate(&z, 0, sizeof(outbuf), NULL, outbuf, in, out, &crc, | ||||
| 							 &file->write_size, Z_FINISH) != Z_STREAM_END) | ||||
| 			{ | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if (deflateEnd(&z) != Z_OK) | ||||
| 			elog(ERROR_SYSTEM, _("can't close compression stream: %s"), z.msg); | ||||
| 	} | ||||
| 	else if (mode == DECOMPRESSION) | ||||
| 	{ | ||||
| 		if (inflateEnd(&z) != Z_OK) | ||||
| 			elog(ERROR_SYSTEM, _("can't close compression stream: %s"), z.msg); | ||||
| 	} | ||||
|  | ||||
| #endif | ||||
| 	/* finish CRC calculation and store into pgFile */ | ||||
| 	FIN_CRC32(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, _("can't change mode of \"%s\": %s"), to_path, | ||||
| 			strerror(errno_tmp)); | ||||
| 	} | ||||
|  | ||||
| 	fclose(in); | ||||
| 	fclose(out); | ||||
|  | ||||
| 	if (check) | ||||
| 		remove(to_path); | ||||
| } | ||||
							
								
								
									
										18
									
								
								data/sample_backup/20090531/170553/backup.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								data/sample_backup/20090531/170553/backup.ini
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| # configuration | ||||
| BACKUP_MODE=FULL | ||||
| WITH_SERVERLOG=NO | ||||
| COMPRESS_DATA=NO | ||||
| # result | ||||
| TIMELINEID=1 | ||||
| START_LSN=0/0b40c800 | ||||
| STOP_LSN=0/0b4c8020 | ||||
| START_TIME='2009-05-31 17:05:53' | ||||
| END_TIME='2009-05-31 17:09:13' | ||||
| TOTAL_DATA_BYTES=1242102558 | ||||
| READ_DATA_BYTES=1024 | ||||
| READ_ARCLOG_BYTES=9223372036854775807 | ||||
| READ_SRVLOG_BYTES=-1 | ||||
| WRITE_BYTES=242102558 | ||||
| BLOCK_SIZE=8192 | ||||
| XLOG_BLOCK_SIZE=8192 | ||||
| STATUS=DONE | ||||
							
								
								
									
										1
									
								
								data/sample_backup/20090531/170553/database/PG_VERSION
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								data/sample_backup/20090531/170553/database/PG_VERSION
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| 8.4 | ||||
							
								
								
									
										0
									
								
								data/sample_backup/20090531/170553/file_arclog.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								data/sample_backup/20090531/170553/file_arclog.txt
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										1
									
								
								data/sample_backup/20090531/170553/file_database.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								data/sample_backup/20090531/170553/file_database.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| PG_VERSION f 4 4277607361 0600 2009-08-06 18:40:18 | ||||
							
								
								
									
										18
									
								
								data/sample_backup/20090601/170553/backup.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								data/sample_backup/20090601/170553/backup.ini
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| # configuration | ||||
| BACKUP_MODE=INCREMENTAL | ||||
| WITH_SERVERLOG=NO | ||||
| COMPRESS_DATA=NO | ||||
| # result | ||||
| TIMELINEID=1 | ||||
| START_LSN=0/0b40c800 | ||||
| STOP_LSN=0/0b4c8020 | ||||
| START_TIME='2009-06-01 17:05:53' | ||||
| END_TIME='2009-06-01 17:09:13' | ||||
| TOTAL_DATA_BYTES=1242102558 | ||||
| READ_DATA_BYTES=9223372036854775807 | ||||
| READ_ARCLOG_BYTES=16777216 | ||||
| READ_SRVLOG_BYTES=-1 | ||||
| WRITE_BYTES=162372983 | ||||
| BLOCK_SIZE=8192 | ||||
| XLOG_BLOCK_SIZE=8192 | ||||
| STATUS=DONE | ||||
							
								
								
									
										1
									
								
								data/sample_backup/20090601/170553/database/PG_VERSION
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								data/sample_backup/20090601/170553/database/PG_VERSION
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| 8.4 | ||||
							
								
								
									
										0
									
								
								data/sample_backup/20090601/170553/file_arclog.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								data/sample_backup/20090601/170553/file_arclog.txt
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										1
									
								
								data/sample_backup/20090601/170553/file_database.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								data/sample_backup/20090601/170553/file_database.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| PG_VERSION f 4 0 0600 2009-08-06 18:40:18 | ||||
							
								
								
									
										18
									
								
								data/sample_backup/20090602/170553/backup.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								data/sample_backup/20090602/170553/backup.ini
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| # configuration | ||||
| BACKUP_MODE=ARCHIVE | ||||
| WITH_SERVERLOG=YES | ||||
| COMPRESS_DATA=NO | ||||
| # result | ||||
| TIMELINEID=1 | ||||
| START_LSN=0/0b40c800 | ||||
| STOP_LSN=0/0b4c8020 | ||||
| START_TIME='2009-06-02 17:05:03' | ||||
| END_TIME='2009-06-02 17:05:03' | ||||
| TOTAL_DATA_BYTES=-1 | ||||
| READ_DATA_BYTES=-1 | ||||
| READ_ARCLOG_BYTES=-1 | ||||
| READ_SRVLOG_BYTES=4335423 | ||||
| WRITE_BYTES=162372983 | ||||
| BLOCK_SIZE=8192 | ||||
| XLOG_BLOCK_SIZE=8192 | ||||
| STATUS=DELETED | ||||
							
								
								
									
										18
									
								
								data/sample_backup/20090603/170553/backup.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								data/sample_backup/20090603/170553/backup.ini
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| # configuration | ||||
| BACKUP_MODE=FULL | ||||
| WITH_SERVERLOG=YES | ||||
| COMPRESS_DATA=NO | ||||
| # result | ||||
| TIMELINEID=1 | ||||
| START_LSN=0/0b40c800 | ||||
| STOP_LSN=0/0b4c8020 | ||||
| START_TIME='2009-06-03 17:05:53' | ||||
| END_TIME='****-**-** **:**:**' | ||||
| TOTAL_DATA_BYTES=-1 | ||||
| READ_DATA_BYTES=-1 | ||||
| READ_ARCLOG_BYTES=-1 | ||||
| READ_SRVLOG_BYTES=-1 | ||||
| WRITE_BYTES=-1 | ||||
| BLOCK_SIZE=8192 | ||||
| XLOG_BLOCK_SIZE=8192 | ||||
| STATUS=RUNNING | ||||
							
								
								
									
										0
									
								
								data/sample_backup/20090603/170553/file_arclog.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								data/sample_backup/20090603/170553/file_arclog.txt
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										0
									
								
								data/sample_backup/20090603/170553/file_srvlog.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								data/sample_backup/20090603/170553/file_srvlog.txt
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										0
									
								
								data/sample_backup/pg_rman.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								data/sample_backup/pg_rman.ini
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										235
									
								
								delete.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										235
									
								
								delete.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,235 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * delete.c: delete backup files. | ||||
|  * | ||||
|  * Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #include "pg_rman.h" | ||||
|  | ||||
| static int pgBackupDeleteFiles(pgBackup *backup); | ||||
|  | ||||
| int | ||||
| do_delete(pgBackupRange *range) | ||||
| { | ||||
| 	int		i; | ||||
| 	int		ret; | ||||
| 	parray *backup_list; | ||||
| 	bool	do_delete; | ||||
|  | ||||
| 	/* DATE are always required */ | ||||
| 	if (!pgBackupRangeIsValid(range)) | ||||
| 		elog(ERROR_ARGS, _("required delete range option not specified: delete DATE")); | ||||
|  | ||||
| 	/* get exclusive lock of backup catalog */ | ||||
| 	ret = catalog_lock(); | ||||
| 	if (ret == -1) | ||||
| 		elog(ERROR_SYSTEM, _("can't lock backup catalog.")); | ||||
| 	else if (ret == 1) | ||||
| 		elog(ERROR_ALREADY_RUNNING, | ||||
| 			_("another pg_rman is running, stop restore.")); | ||||
|  | ||||
| 	/* get list of backups. */ | ||||
| 	backup_list = catalog_get_backup_list(NULL); | ||||
|  | ||||
| 	do_delete = false; | ||||
| 	/* find delete target backup. */ | ||||
| 	for (i = 0; i < parray_num(backup_list); i++) | ||||
| 	{ | ||||
| 		pgBackup *backup = (pgBackup *)parray_get(backup_list, i); | ||||
|  | ||||
| 		/* delete backup and update status to DELETED */ | ||||
| 		if (do_delete) | ||||
| 		{ | ||||
| 			pgBackupDeleteFiles(backup); | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		/* find latest full backup. */ | ||||
| 		if (backup->backup_mode >= BACKUP_MODE_FULL && | ||||
| 			backup->status == BACKUP_STATUS_OK && | ||||
| 			backup->start_time <= range->begin) | ||||
| 			do_delete = true; | ||||
| 	} | ||||
|  | ||||
| 	/* release catalog lock */ | ||||
| 	catalog_unlock(); | ||||
|  | ||||
| 	/* cleanup */ | ||||
| 	parray_walk(backup_list, pgBackupFree); | ||||
| 	parray_free(backup_list); | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Delete backups that are older than KEEP_xxx_DAYS and have more generations | ||||
|  * than KEEP_xxx_FILES. | ||||
|  */ | ||||
| void | ||||
| pgBackupDelete(int keep_generations, int keep_days) | ||||
| { | ||||
| 	int		i; | ||||
| 	parray *backup_list; | ||||
| 	int		backup_num; | ||||
| 	time_t	days_threashold = current.start_time - (keep_days * 60 * 60 * 24); | ||||
|  | ||||
| 	if (verbose) | ||||
| 	{ | ||||
| 		char generations_str[100]; | ||||
| 		char days_str[100]; | ||||
|  | ||||
| 		if (keep_generations == KEEP_INFINITE) | ||||
| 			strncpy(generations_str, "INFINITE", | ||||
| 					lengthof(generations_str)); | ||||
| 		else | ||||
| 			snprintf(generations_str, lengthof(generations_str), | ||||
| 					"%d", keep_generations); | ||||
|  | ||||
| 		if (keep_days == KEEP_INFINITE) | ||||
| 			strncpy(days_str, "INFINITE", lengthof(days_str)); | ||||
| 		else | ||||
| 			snprintf(days_str, lengthof(days_str), "%d", keep_days); | ||||
|  | ||||
| 		printf(_("delete old backups (generations=%s, days=%s)\n"), | ||||
| 			generations_str, days_str); | ||||
| 	} | ||||
|  | ||||
| 	/* delete files which satisfy both condition */ | ||||
| 	if (keep_generations == KEEP_INFINITE || keep_days == KEEP_INFINITE) | ||||
| 	{ | ||||
| 		elog(LOG, "%s() infinite", __FUNCTION__); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	/* get list of backups. */ | ||||
| 	backup_list = catalog_get_backup_list(NULL); | ||||
|  | ||||
| 	backup_num = 0; | ||||
| 	/* find delete target backup. */ | ||||
| 	for (i = 0; i < parray_num(backup_list); i++) | ||||
| 	{ | ||||
| 		pgBackup *backup = (pgBackup *)parray_get(backup_list, i); | ||||
|  | ||||
| 		elog(LOG, "%s() %lu", __FUNCTION__, backup->start_time); | ||||
| 		/* | ||||
| 		 * when validate full backup was found, we can delete the backup | ||||
| 		 * that is older than it | ||||
| 		 */ | ||||
| 		if (backup->backup_mode >= BACKUP_MODE_FULL && | ||||
| 			backup->status == BACKUP_STATUS_OK) | ||||
| 			backup_num++; | ||||
|  | ||||
| 		/* do not include the latest full backup in a count. */ | ||||
| 		if (backup_num - 1 <= keep_generations) | ||||
| 		{ | ||||
| 			elog(LOG, "%s() backup are only %d", __FUNCTION__, backup_num); | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		/* | ||||
| 		 * If the start time of the backup is older than the threashold and | ||||
| 		 * there are enough generations of full backups, delete the backup. | ||||
| 		 */ | ||||
| 		if (backup->start_time >= days_threashold) | ||||
| 		{ | ||||
| 			elog(LOG, "%s() %lu is not older than %lu", __FUNCTION__, | ||||
| 				backup->start_time, days_threashold); | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		elog(LOG, "%s() %lu is older than %lu", __FUNCTION__, | ||||
| 			backup->start_time, days_threashold); | ||||
|  | ||||
| 		/* delete backup and update status to DELETED */ | ||||
| 		pgBackupDeleteFiles(backup); | ||||
| 	} | ||||
|  | ||||
| 	/* cleanup */ | ||||
| 	parray_walk(backup_list, pgBackupFree); | ||||
| 	parray_free(backup_list); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Delete backup files of the backup and update the status of the backup to | ||||
|  * BACKUP_STATUS_DELETED. | ||||
|  */ | ||||
| static int | ||||
| pgBackupDeleteFiles(pgBackup *backup) | ||||
| { | ||||
| 	int		i; | ||||
| 	char	path[MAXPGPATH]; | ||||
| 	char	timestamp[20]; | ||||
| 	parray *files; | ||||
|  | ||||
| 	/* | ||||
| 	 * If the backup was deleted already, nothing to do and such situation | ||||
| 	 * is not error. | ||||
| 	 */ | ||||
| 	if (backup->status == BACKUP_STATUS_DELETED) | ||||
| 		return 0; | ||||
|  | ||||
| 	time2iso(timestamp, lengthof(timestamp), backup->start_time); | ||||
|  | ||||
| 	elog(INFO, _("delete: %s"), timestamp); | ||||
|  | ||||
| 	/* | ||||
| 	 * update STATUS to BACKUP_STATUS_DELETING in preparation for the case which | ||||
| 	 * the error occurs before deleting all backup files.  | ||||
| 	 */ | ||||
| 	if (!check) | ||||
| 	{ | ||||
| 		backup->status = BACKUP_STATUS_DELETING; | ||||
| 		pgBackupWriteIni(backup); | ||||
| 	} | ||||
|  | ||||
| 	/* list files to be deleted */ | ||||
| 	files = parray_new(); | ||||
| 	pgBackupGetPath(backup, path, lengthof(path), DATABASE_DIR); | ||||
| 	dir_list_file(files, path, NULL, true, true); | ||||
| 	pgBackupGetPath(backup, path, lengthof(path), ARCLOG_DIR); | ||||
| 	dir_list_file(files, path, NULL, true, true); | ||||
| 	pgBackupGetPath(backup, path, lengthof(path), SRVLOG_DIR); | ||||
| 	dir_list_file(files, path, NULL, true, true); | ||||
|  | ||||
| 	/* delete leaf node first */ | ||||
| 	parray_qsort(files, pgFileComparePathDesc); | ||||
| 	for (i = 0; i < parray_num(files); i++) | ||||
| 	{ | ||||
| 		pgFile *file = (pgFile *) parray_get(files, i); | ||||
|  | ||||
| 		/* print progress */ | ||||
| 		elog(LOG, _("delete file(%d/%lu) \"%s\"\n"), i + 1, | ||||
| 				(unsigned long) parray_num(files), file->path); | ||||
|  | ||||
| 		/* skip actual deletion in check mode */ | ||||
| 		if (!check) | ||||
| 		{ | ||||
| 			if (remove(file->path)) | ||||
| 			{ | ||||
| 				elog(WARNING, _("can't remove \"%s\": %s"), file->path, | ||||
| 					strerror(errno)); | ||||
| 				parray_walk(files, pgFileFree); | ||||
| 				parray_free(files); | ||||
| 				return 1; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/* | ||||
| 	 * After deleting all of the backup files, update STATUS to | ||||
| 	 * BACKUP_STATUS_DELETED. | ||||
| 	 */ | ||||
| 	if (!check) | ||||
| 	{ | ||||
| 		backup->status = BACKUP_STATUS_DELETED; | ||||
| 		pgBackupWriteIni(backup); | ||||
| 	} | ||||
|  | ||||
| 	parray_walk(files, pgFileFree); | ||||
| 	parray_free(files); | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
							
								
								
									
										567
									
								
								dir.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										567
									
								
								dir.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,567 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * dir.c: directory operation utility. | ||||
|  * | ||||
|  * Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #include "pg_rman.h" | ||||
|  | ||||
| #include <libgen.h> | ||||
| #include <unistd.h> | ||||
| #include <sys/stat.h> | ||||
| #include <sys/types.h> | ||||
| #include <dirent.h> | ||||
| #include <time.h> | ||||
|  | ||||
| #include "pgut/pgut-port.h" | ||||
|  | ||||
| /* directory exclusion list for backup mode listing */ | ||||
| const char *pgdata_exclude[] = | ||||
| { | ||||
| 	"pg_xlog", | ||||
| 	"pg_stat_tmp", | ||||
| 	"pgsql_tmp", | ||||
| 	NULL,			/* arclog_path will be set later */ | ||||
| 	NULL,			/* srvlog_path will be set later */ | ||||
| 	NULL,			/* centinel */ | ||||
| }; | ||||
|  | ||||
| static pgFile *pgFileNew(const char *path, bool omit_symlink); | ||||
|  | ||||
| /* create directory, also create parent directories if necessary */ | ||||
| int | ||||
| dir_create_dir(const char *dir, mode_t mode) | ||||
| { | ||||
| 	char copy[MAXPGPATH]; | ||||
| 	char *parent; | ||||
|  | ||||
| 	strncpy(copy, dir, MAXPGPATH); | ||||
| 	parent= dirname(copy); | ||||
| 	if (access(parent, F_OK) == -1) | ||||
| 		dir_create_dir(parent, mode); | ||||
| 	if (mkdir(dir, mode) == -1) | ||||
| 	{ | ||||
| 		if (errno == EEXIST)	/* already exist */ | ||||
| 			return 0; | ||||
| 		elog(ERROR_SYSTEM, _("can't create directory \"%s\": %s"), dir, | ||||
| 			strerror(errno)); | ||||
| 	} | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| static pgFile * | ||||
| pgFileNew(const char *path, bool omit_symlink) | ||||
| { | ||||
| 	struct stat st; | ||||
| 	pgFile *file; | ||||
|  | ||||
| 	/* stat the file */ | ||||
| 	if ((omit_symlink ? stat(path, &st) : lstat(path, &st)) == -1) | ||||
| 	{ | ||||
| 		/* file not found is not an error case */ | ||||
| 		if (errno == ENOENT) | ||||
| 			return NULL; | ||||
| 		elog(ERROR_SYSTEM, _("can't stat file \"%s\": %s"), path, | ||||
| 			strerror(errno)); | ||||
| 	} | ||||
|  | ||||
| 	file = (pgFile *) pgut_malloc(offsetof(pgFile, path) + strlen(path) + 1); | ||||
|  | ||||
| 	file->mtime = st.st_mtime; | ||||
| 	file->size = st.st_size; | ||||
| 	file->read_size = 0; | ||||
| 	file->write_size = 0; | ||||
| 	file->mode = st.st_mode; | ||||
| 	file->crc = 0; | ||||
| 	file->is_datafile = false; | ||||
| 	file->linked = NULL; | ||||
| 	strcpy(file->path, path);		/* enough buffer size guaranteed */ | ||||
|  | ||||
| 	return file; | ||||
| } | ||||
|  | ||||
| void | ||||
| pgFileDump(pgFile *file, FILE *out) | ||||
| { | ||||
| 	char mtime_str[100]; | ||||
|  | ||||
| 	fprintf(out, "=================\n"); | ||||
| 	if (file) | ||||
| 	{ | ||||
| 		time2iso(mtime_str, 100, file->mtime); | ||||
| 		fprintf(out, "mtime=%lu(%s)\n", file->mtime, mtime_str); | ||||
| 		fprintf(out, "size=" UINT64_FORMAT "\n", (uint64)file->size); | ||||
| 		fprintf(out, "read_size=" UINT64_FORMAT "\n", (uint64)file->read_size); | ||||
| 		fprintf(out, "write_size=" UINT64_FORMAT "\n", (uint64)file->write_size); | ||||
| 		fprintf(out, "mode=0%o\n", file->mode); | ||||
| 		fprintf(out, "crc=%u\n", file->crc); | ||||
| 		fprintf(out, "is_datafile=%s\n", file->is_datafile ? "true" : "false"); | ||||
| 		fprintf(out, "linked=\"%s\"\n", file->linked ? file->linked : "nil"); | ||||
| 		fprintf(out, "path=\"%s\"\n", file->path); | ||||
| 	} | ||||
| 	fprintf(out, "=================\n"); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Delete file pointed by the pgFile. | ||||
|  * If the pgFile points directory, the directory must be empty. | ||||
|  */ | ||||
| void | ||||
| pgFileDelete(pgFile *file) | ||||
| { | ||||
| 	if (S_ISDIR(file->mode)) | ||||
| 	{ | ||||
| 		if (rmdir(file->path) == -1) | ||||
| 		{ | ||||
| 			if (errno == ENOENT) | ||||
| 				return; | ||||
| 			else if (errno == ENOTDIR)	/* could be symbolic link */ | ||||
| 				goto delete_file; | ||||
|  | ||||
| 			elog(ERROR_SYSTEM, _("can't remove directory \"%s\": %s"), | ||||
| 				file->path, strerror(errno)); | ||||
| 		} | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| delete_file: | ||||
| 	if (remove(file->path) == -1) | ||||
| 	{ | ||||
| 		if (errno == ENOENT) | ||||
| 			return; | ||||
| 		elog(ERROR_SYSTEM, _("can't remove file \"%s\": %s"), file->path, | ||||
| 			strerror(errno)); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| pg_crc32 | ||||
| pgFileGetCRC(pgFile *file) | ||||
| { | ||||
| 	FILE *fp; | ||||
| 	pg_crc32 crc = 0; | ||||
| 	char buf[1024]; | ||||
| 	size_t len; | ||||
| 	int errno_tmp; | ||||
|  | ||||
| 	/* open file in binary read mode */ | ||||
| 	fp = fopen(file->path, "r"); | ||||
| 	if (fp == NULL) | ||||
| 		elog(ERROR_SYSTEM, _("can't open file \"%s\": %s"), | ||||
| 			file->path, strerror(errno)); | ||||
|  | ||||
| 	/* calc CRC of backup file */ | ||||
| 	INIT_CRC32(crc); | ||||
| 	while ((len = fread(buf, 1, sizeof(buf), fp)) == sizeof(buf)) | ||||
| 	{ | ||||
| 		if (interrupted) | ||||
| 			elog(ERROR_INTERRUPTED, _("interrupted during CRC calculation")); | ||||
| 		COMP_CRC32(crc, buf, len); | ||||
| 	} | ||||
| 	errno_tmp = errno; | ||||
| 	if (!feof(fp)) | ||||
| 		elog(WARNING, _("can't read \"%s\": %s"), file->path, | ||||
| 			strerror(errno_tmp)); | ||||
| 	if (len > 0) | ||||
| 		COMP_CRC32(crc, buf, len); | ||||
| 	FIN_CRC32(crc); | ||||
|  | ||||
| 	fclose(fp); | ||||
|  | ||||
| 	return crc; | ||||
| } | ||||
|  | ||||
| void | ||||
| pgFileFree(void *file) | ||||
| { | ||||
| 	if (file == NULL) | ||||
| 		return; | ||||
| 	free(((pgFile *)file)->linked); | ||||
| 	free(file); | ||||
| } | ||||
|  | ||||
| /* Compare two pgFile with their path in ascending order of ASCII code. */ | ||||
| int | ||||
| pgFileComparePath(const void *f1, const void *f2) | ||||
| { | ||||
| 	pgFile *f1p = *(pgFile **)f1; | ||||
| 	pgFile *f2p = *(pgFile **)f2; | ||||
|  | ||||
| 	return strcmp(f1p->path, f2p->path); | ||||
| } | ||||
|  | ||||
| /* Compare two pgFile with their path in descending order of ASCII code. */ | ||||
| int | ||||
| pgFileComparePathDesc(const void *f1, const void *f2) | ||||
| { | ||||
| 	return -pgFileComparePath(f1, f2); | ||||
| } | ||||
|  | ||||
| /* Compare two pgFile with their modify timestamp. */ | ||||
| int | ||||
| pgFileCompareMtime(const void *f1, const void *f2) | ||||
| { | ||||
| 	pgFile *f1p = *(pgFile **)f1; | ||||
| 	pgFile *f2p = *(pgFile **)f2; | ||||
|  | ||||
| 	if (f1p->mtime > f2p->mtime) | ||||
| 		return 1; | ||||
| 	else if (f1p->mtime < f2p->mtime) | ||||
| 		return -1; | ||||
| 	else return 0; | ||||
| } | ||||
|  | ||||
| /* Compare two pgFile with their modify timestamp in descending order. */ | ||||
| int | ||||
| pgFileCompareMtimeDesc(const void *f1, const void *f2) | ||||
| { | ||||
| 	return -pgFileCompareMtime(f1, f2); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * List files, symbolic links and directories in the directory "root" and add | ||||
|  * pgFile objects to "files".  We add "root" to "files" if add_root is true. | ||||
|  * | ||||
|  * If the sub-directory name is in "exclude" list, the sub-directory itself is | ||||
|  * listed but the contents of the sub-directory is ignored. | ||||
|  * | ||||
|  * When omit_symlink is true, symbolic link is ignored and only file or | ||||
|  * directory llnked to will be listed. | ||||
|  */ | ||||
| void | ||||
| dir_list_file(parray *files, const char *root, const char *exclude[], bool omit_symlink, bool add_root) | ||||
| { | ||||
| 	pgFile *file; | ||||
|  | ||||
| 	file = pgFileNew(root, omit_symlink); | ||||
| 	if (file == NULL) | ||||
| 		return; | ||||
|  | ||||
| 	if (add_root) | ||||
| 		parray_append(files, file); | ||||
|  | ||||
| 	/* chase symbolic link chain and find regular file or directory */ | ||||
| 	while (S_ISLNK(file->mode)) | ||||
| 	{ | ||||
| 		ssize_t len; | ||||
| 		char linked[MAXPGPATH]; | ||||
|  | ||||
| 		len = readlink(file->path, linked, sizeof(linked)); | ||||
| 		if (len == -1) | ||||
| 		{ | ||||
| 			elog(ERROR_SYSTEM, _("can't read link \"%s\": %s"), file->path, | ||||
| 				strerror(errno)); | ||||
| 		} | ||||
| 		linked[len] = '\0'; | ||||
| 		file->linked = pgut_strdup(linked); | ||||
|  | ||||
| 		/* make absolute path to read linked file */ | ||||
| 		if (linked[0] != '/') | ||||
| 		{ | ||||
| 			char dname[MAXPGPATH]; | ||||
| 			char *dnamep; | ||||
| 			char absolute[MAXPGPATH]; | ||||
|  | ||||
| 			strncpy(dname, file->path, lengthof(dname)); | ||||
| 			dnamep = dirname(dname); | ||||
| 			snprintf(absolute, lengthof(absolute), "%s/%s", dname, linked); | ||||
| 			file = pgFileNew(absolute, omit_symlink); | ||||
| 		} | ||||
| 		else | ||||
| 			file = pgFileNew(file->linked, omit_symlink); | ||||
|  | ||||
| 		/* linked file is not found, stop chasing link chain */ | ||||
| 		if (file == NULL) | ||||
| 			return; | ||||
|  | ||||
| 		parray_append(files, file); | ||||
| 	} | ||||
|  | ||||
| 	/* | ||||
| 	 * If the entry was a directory, add it to the list and add call this | ||||
| 	 * function recursivelly. | ||||
| 	 * If the directory name is in the exclude list, do not list the contents. | ||||
| 	 */ | ||||
| 	while (S_ISDIR(file->mode)) | ||||
| 	{ | ||||
| 		int i; | ||||
| 		bool skip = false; | ||||
| 		DIR *dir; | ||||
| 		struct dirent *dent; | ||||
| 		char *dirname; | ||||
|  | ||||
| 		/* skip entry which matches exclude list */ | ||||
| 	   	dirname = strrchr(file->path, '/'); | ||||
| 		if (dirname == NULL) | ||||
| 			dirname = file->path; | ||||
| 		else | ||||
| 			dirname++; | ||||
|  | ||||
| 		/* | ||||
| 		 * If the item in the exclude list starts with '/', compare to th | ||||
| 		 * absolute path of the directory. Otherwise compare to the directory | ||||
| 		 * name portion. | ||||
| 		 */ | ||||
| 		for (i = 0; exclude && exclude[i]; i++) | ||||
| 		{ | ||||
| 			if (exclude[i][0] == '/') | ||||
| 			{ | ||||
| 				if (strcmp(file->path, exclude[i]) == 0) | ||||
| 				{ | ||||
| 					skip = true; | ||||
| 					break; | ||||
| 				} | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				if (strcmp(dirname, exclude[i]) == 0) | ||||
| 				{ | ||||
| 					skip = true; | ||||
| 					break; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		if (skip) | ||||
| 			break; | ||||
|  | ||||
| 		/* open directory and list contents */ | ||||
| 		dir = opendir(file->path); | ||||
| 		if (dir == NULL) | ||||
| 		{ | ||||
| 			if (errno == ENOENT) | ||||
| 			{ | ||||
| 				/* maybe the direcotry was removed */ | ||||
| 				return; | ||||
| 			} | ||||
| 			elog(ERROR_SYSTEM, _("can't open directory \"%s\": %s"), | ||||
| 				file->path, strerror(errno)); | ||||
| 		} | ||||
|  | ||||
| 		errno = 0; | ||||
| 		while ((dent = readdir(dir))) | ||||
| 		{ | ||||
| 			char child[MAXPGPATH]; | ||||
|  | ||||
| 			/* skip entries point current dir or parent dir */ | ||||
| 			if (strcmp(dent->d_name, ".") == 0 || | ||||
| 				strcmp(dent->d_name, "..") == 0) | ||||
| 				continue; | ||||
|  | ||||
| 			snprintf(child, lengthof(child), "%s/%s", file->path, dent->d_name); | ||||
| 			dir_list_file(files, child, exclude, omit_symlink, true); | ||||
| 		} | ||||
| 		if (errno && errno != ENOENT) | ||||
| 		{ | ||||
| 			int errno_tmp = errno; | ||||
| 			closedir(dir); | ||||
| 			elog(ERROR_SYSTEM, _("can't read directory \"%s\": %s"), | ||||
| 				file->path, strerror(errno_tmp)); | ||||
| 		} | ||||
| 		closedir(dir); | ||||
|  | ||||
| 		break;	/* psuedo loop */ | ||||
| 	} | ||||
|  | ||||
| 	parray_qsort(files, pgFileComparePath); | ||||
| } | ||||
|  | ||||
| /* print mkdirs.sh */ | ||||
| void | ||||
| dir_print_mkdirs_sh(FILE *out, const parray *files, const char *root) | ||||
| { | ||||
| 	int i; | ||||
|  | ||||
| 	for (i = 0; i < parray_num(files); i++) | ||||
| 	{ | ||||
| 		pgFile *file = (pgFile *) parray_get(files, i); | ||||
| 		if (S_ISDIR(file->mode)) | ||||
| 		{ | ||||
| 			if (strstr(file->path, root) == file->path) | ||||
| 				fprintf(out, "mkdir -m 700 -p %s\n", file->path + strlen(root) | ||||
| 					+ 1); | ||||
| 			else | ||||
| 				fprintf(out, "mkdir -m 700 -p %s\n", file->path); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	fprintf(out, "\n"); | ||||
|  | ||||
| 	for (i = 0; i < parray_num(files); i++) | ||||
| 	{ | ||||
| 		pgFile *file = (pgFile *) parray_get(files, i); | ||||
| 		if (S_ISLNK(file->mode)) | ||||
| 		{ | ||||
| 			fprintf(out, "rm -f %s\n", file->path + strlen(root) + 1); | ||||
| 			fprintf(out, "ln -s %s %s\n", file->linked, file->path + strlen(root) + 1); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| /* print file list */ | ||||
| void | ||||
| dir_print_file_list(FILE *out, const parray *files, const char *root) | ||||
| { | ||||
| 	int i; | ||||
| 	int root_len = 0; | ||||
|  | ||||
| 	/* calculate length of root directory portion */ | ||||
| 	if (root) | ||||
| 	{ | ||||
| 		root_len = strlen(root); | ||||
| 		if (root[root_len - 1] != '/') | ||||
| 			root_len++; | ||||
| 	} | ||||
|  | ||||
| 	/* print each file in the list */ | ||||
| 	for (i = 0; i < parray_num(files); i++) { | ||||
| 		pgFile *file = (pgFile *)parray_get(files, i); | ||||
| 		char *path = file->path; | ||||
| 		char type; | ||||
|  | ||||
| 		/* omit root directory portion */ | ||||
| 		if (root && strstr(path, root) == path) | ||||
| 			path = path + root_len; | ||||
|  | ||||
| 		if (S_ISREG(file->mode) && file->is_datafile) | ||||
| 			type = 'F'; | ||||
| 		else if (S_ISREG(file->mode) && !file->is_datafile) | ||||
| 			type = 'f'; | ||||
| 		else if (S_ISDIR(file->mode)) | ||||
| 			type = 'd'; | ||||
| 		else if (S_ISLNK(file->mode)) | ||||
| 			type = 'l'; | ||||
| 		else | ||||
| 			type = '?'; | ||||
|  | ||||
| 		fprintf(out, "%s %c %lu %u 0%o", path, type, | ||||
| 			(unsigned long) file->write_size, | ||||
| 			file->crc, file->mode & (S_IRWXU | S_IRWXG | S_IRWXO)); | ||||
|  | ||||
| 		if (S_ISLNK(file->mode)) | ||||
| 			fprintf(out, " %s\n", file->linked); | ||||
| 		else | ||||
| 		{ | ||||
| 			char timestamp[20]; | ||||
| 			time2iso(timestamp, 20, file->mtime); | ||||
| 			fprintf(out, " %s\n", timestamp); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Construct parray of pgFile from the file list. | ||||
|  * If root is not NULL, path will be absolute path. | ||||
|  */ | ||||
| parray * | ||||
| dir_read_file_list(const char *root, const char *file_txt) | ||||
| { | ||||
| 	FILE *fp; | ||||
| 	parray *files; | ||||
| 	char buf[MAXPGPATH * 2]; | ||||
|  | ||||
| 	fp = fopen(file_txt, "rt"); | ||||
| 	if (fp == NULL) | ||||
| 		elog(errno == ENOENT ? ERROR_CORRUPTED : ERROR_SYSTEM, | ||||
| 			_("can't open \"%s\": %s"), file_txt, strerror(errno)); | ||||
|  | ||||
| 	files = parray_new(); | ||||
|  | ||||
| 	while (fgets(buf, lengthof(buf), fp)) | ||||
| 	{ | ||||
| 		char			path[MAXPGPATH]; | ||||
| 		char			type; | ||||
| 		unsigned long	write_size; | ||||
| 		pg_crc32		crc; | ||||
| 		unsigned int	mode;	/* bit length of mode_t depends on platforms */ | ||||
| 		struct tm		tm; | ||||
| 		pgFile		   *file; | ||||
|  | ||||
| 		if (sscanf(buf, "%s %c %lu %u %o %d-%d-%d %d:%d:%d", | ||||
| 			path, &type, &write_size, &crc, &mode, | ||||
| 			&tm.tm_year, &tm.tm_mon, &tm.tm_mday, | ||||
| 			&tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 11) | ||||
| 		{ | ||||
| 			elog(ERROR_CORRUPTED, _("invalid format found in \"%s\""), | ||||
| 				file_txt); | ||||
| 		} | ||||
| 		if (type != 'f' && type != 'F' && type != 'd' && type != 'l') | ||||
| 		{ | ||||
| 			elog(ERROR_CORRUPTED, _("invalid type '%c' found in \"%s\""), | ||||
| 				type, file_txt); | ||||
| 		} | ||||
|  | ||||
| 		file = (pgFile *) pgut_malloc(offsetof(pgFile, path) + | ||||
| 					(root ? strlen(root) + 1 : 0) + strlen(path) + 1); | ||||
|  | ||||
| 		tm.tm_year -= 1900; | ||||
| 		tm.tm_mon -= 1; | ||||
| 		file->mtime = mktime(&tm); | ||||
| 		file->mode = mode | | ||||
| 			((type == 'f' || type == 'F') ? S_IFREG : | ||||
| 			 type == 'd' ? S_IFDIR : type == 'l' ? S_IFLNK : 0); | ||||
| 		file->size = 0; | ||||
| 		file->read_size = 0; | ||||
| 		file->write_size = write_size; | ||||
| 		file->crc = crc; | ||||
| 		file->is_datafile = (type == 'F' ? true : false); | ||||
| 		file->linked = NULL; | ||||
| 		if (root) | ||||
| 			sprintf(file->path, "%s/%s", root, path); | ||||
| 		else | ||||
| 			strcpy(file->path, path); | ||||
|  | ||||
| 		parray_append(files, file); | ||||
| 	} | ||||
|  | ||||
| 	fclose(fp); | ||||
|  | ||||
| 	/* file.txt is sorted, so this qsort is redundant */ | ||||
| 	parray_qsort(files, pgFileComparePath); | ||||
|  | ||||
| 	return files; | ||||
| } | ||||
|  | ||||
| /* copy contents of directory from_root into to_root */ | ||||
| void | ||||
| dir_copy_files(const char *from_root, const char *to_root) | ||||
| { | ||||
| 	int i; | ||||
| 	parray *files; | ||||
| 	files = parray_new(); | ||||
|  | ||||
| 	/* don't copy root directory */ | ||||
| 	dir_list_file(files, from_root, NULL, true, false); | ||||
|  | ||||
| 	for (i = 0; i < parray_num(files); i++) | ||||
| 	{ | ||||
| 		pgFile *file = (pgFile *) parray_get(files, i); | ||||
|  | ||||
| 		if (S_ISDIR(file->mode)) | ||||
| 		{ | ||||
| 			char to_path[MAXPGPATH]; | ||||
| 			snprintf(to_path, lengthof(to_path), "%s/%s", to_root, | ||||
| 				file->path + strlen(from_root) + 1); | ||||
| 			if (verbose && !check) | ||||
| 				printf(_("create directory \"%s\"\n"), | ||||
| 					file->path + strlen(from_root) + 1); | ||||
| 			if (!check) | ||||
| 				dir_create_dir(to_path, DIR_PERMISSION); | ||||
| 			continue; | ||||
| 		} | ||||
| 		else if(S_ISREG(file->mode)) | ||||
| 		{ | ||||
| 			if (verbose && !check) | ||||
| 				printf(_("copy \"%s\"\n"), | ||||
| 					file->path + strlen(from_root) + 1); | ||||
| 			if (!check) | ||||
| 				copy_file(from_root, to_root, file, NO_COMPRESSION); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/* cleanup */ | ||||
| 	parray_walk(files, pgFileFree); | ||||
| 	parray_free(files); | ||||
| } | ||||
							
								
								
									
										40
									
								
								expected/backup_restore.out
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								expected/backup_restore.out
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| \! sh sql/backup_restore.sh | ||||
| Line style is ascii. | ||||
| CREATE TABLESPACE | ||||
| CREATE DATABASE | ||||
| # of deleted backups | ||||
| 0 | ||||
| full database backup | ||||
| CHECKPOINT | ||||
| incremental database backup | ||||
| CHECKPOINT | ||||
| CHECKPOINT | ||||
| archived WAL and serverlog backup | ||||
| stop DB during running pgbench | ||||
| diff files in BACKUP_PATH/backup/pg_xlog | ||||
| # of files in BACKUP_PATH/backup/srvlog | ||||
| 1 | ||||
| diff files in BACKUP_PATH/backup/pg_xlog | ||||
| # of files in BACKUP_PATH/backup/srvlog | ||||
| 2 | ||||
| full database backup after recovery | ||||
| CHECKPOINT | ||||
| # of files in BACKUP_PATH/backup/pg_xlog | ||||
| 0 | ||||
| # of files in BACKUP_PATH/backup/srvlog | ||||
| 2 | ||||
| # of symbolic links in ARCLOG_PATH | ||||
| 0 | ||||
| # of files in BACKUP_PATH/timeline_history | ||||
| 2 | ||||
| # of recovery target option in recovery.conf | ||||
| 3 | ||||
| # of deleted backups (show all) | ||||
| 4 | ||||
| # of deleted backups | ||||
| 0 | ||||
| delete backup | ||||
| # of deleted backups | ||||
| 4 | ||||
| # of deleted backups | ||||
| 9 | ||||
							
								
								
									
										14
									
								
								expected/init.out
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								expected/init.out
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| \! rm -rf results/init_test | ||||
| \! pg_rman init -B results/init_test --quiet;echo $? | ||||
| WARNING: ARCLOG_PATH is not set because archive_command is empty | ||||
| 0 | ||||
| \! find results/init_test | xargs ls -Fd | sort | ||||
| results/init_test/ | ||||
| results/init_test/backup/ | ||||
| results/init_test/backup/pg_xlog/ | ||||
| results/init_test/backup/srvlog/ | ||||
| results/init_test/pg_rman.ini | ||||
| results/init_test/timeline_history/ | ||||
| \! pg_rman init -B results/init_test --quiet;echo $? | ||||
| ERROR: backup catalog already exist. | ||||
| 2 | ||||
							
								
								
									
										80
									
								
								expected/option.out
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								expected/option.out
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | ||||
| \! sh sql/option.sh | ||||
| pg_rman manage backup/recovery of PostgreSQL database. | ||||
|  | ||||
| Usage: | ||||
|   pg_rman OPTION init | ||||
|   pg_rman OPTION backup | ||||
|   pg_rman OPTION restore | ||||
|   pg_rman OPTION show [DATE] | ||||
|   pg_rman OPTION show timeline [DATE] | ||||
|   pg_rman OPTION validate [DATE] | ||||
|   pg_rman OPTION delete DATE | ||||
|  | ||||
| Common Options: | ||||
|   -D, --pgdata=PATH         location of the database storage area | ||||
|   -A, --arclog-path=PATH    location of archive WAL storage area | ||||
|   -S, --srvlog-path=PATH    location of server log storage area | ||||
|   -B, --backup-path=PATH    location of the backup storage area | ||||
|   -c, --check               show what would have been done | ||||
|  | ||||
| Backup options: | ||||
|   -b, --backup-mode=MODE    full, incremental, or archive | ||||
|   -s, --with-serverlog      also backup server log files | ||||
|   -Z, --compress-data       compress data backup with zlib | ||||
|   -C, --smooth-checkpoint   do smooth checkpoint before backup | ||||
|   --keep-data-generations=N keep GENERATION of full data backup | ||||
|   --keep-data-days=DAY      keep enough data backup to recover to DAY days age | ||||
|   --keep-arclog-files=NUM   keep NUM of archived WAL | ||||
|   --keep-arclog-days=DAY    keep archived WAL modified in DAY days | ||||
|   --keep-srvlog-files=NUM   keep NUM of serverlogs | ||||
|   --keep-srvlog-days=DAY    keep serverlog modified in DAY days | ||||
|  | ||||
| Restore options: | ||||
|   --recovery-target-time    time stamp up to which recovery will proceed | ||||
|   --recovery-target-xid     transaction ID up to which recovery will proceed | ||||
|   --recovery-target-inclusive whether we stop just after the recovery target | ||||
|   --recovery-target-timeline  recovering into a particular timeline | ||||
|  | ||||
| Catalog options: | ||||
|   -a, --show-all            show deleted backup too | ||||
|  | ||||
| Connection options: | ||||
|   -d, --dbname=DBNAME       database to connect | ||||
|   -h, --host=HOSTNAME       database server host or socket directory | ||||
|   -p, --port=PORT           database server port | ||||
|   -U, --username=USERNAME   user name to connect as | ||||
|   -w, --no-password         never prompt for password | ||||
|   -W, --password            force password prompt | ||||
|  | ||||
| Generic options: | ||||
|   -q, --quiet               don't write any messages | ||||
|   --debug                   debug mode | ||||
|   --help                    show this help, then exit | ||||
|   --version                 output version information, then exit | ||||
|  | ||||
| Read the website for details. <http://rman.projects.postgresql.org/> | ||||
| Report bugs to <rman-general@lists.pgfoundry.org>. | ||||
| pg_rman 1.1.0 | ||||
| ERROR: required parameter not specified: BACKUP_PATH (-B, --backup-path) | ||||
| ERROR: required parameter not specified: BACKUP_MODE (-b, --backup-mode) | ||||
| ERROR: required parameter not specified: ARCLOG_PATH (-A, --arclog-path) | ||||
| ERROR: required parameter not specified: ARCLOG_PATH (-A, --arclog-path) | ||||
| ERROR: required parameter not specified: ARCLOG_PATH (-A, --arclog-path) | ||||
| ERROR: invalid backup-mode "bad" | ||||
| ERROR: required delete range option not specified: delete DATE | ||||
| INFO: validate: 2009-05-31 17:05:53 | ||||
| INFO: validate: 2009-06-01 17:05:53 | ||||
| WARNING: CRC of backup file "PG_VERSION" must be 0 but FEF71BC1 | ||||
| WARNING: backup 2009-06-01 17:05:53 is corrupted | ||||
| WARNING: syntax error in " = INFINITE". | ||||
| ERROR: required parameter not specified: BACKUP_MODE (-b, --backup-mode) | ||||
| ERROR: invalid backup-mode "" | ||||
| ERROR: required parameter not specified: ARCLOG_PATH (-A, --arclog-path) | ||||
| ERROR: required parameter not specified: ARCLOG_PATH (-A, --arclog-path) | ||||
| ERROR: invalid backup-mode "B" | ||||
| ERROR: option -Z, --compress-data should be a boolean: 'FOO' | ||||
| ERROR: option --keep-arclog-files should be a 32bit signed integer: 'YES' | ||||
| ERROR: invalid option "TIMELINEID" | ||||
| ERROR: invalid option "BACKUP_TARGETS" | ||||
| ERROR: invalid backup-mode "F''\F" | ||||
| ERROR: invalid backup-mode "ENV_PATH" | ||||
							
								
								
									
										50
									
								
								expected/show_validate.out
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								expected/show_validate.out
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| -- test show command | ||||
| \! rm -rf results/sample_backup | ||||
| \! cp -rp data/sample_backup results/sample_backup | ||||
| \! pg_rman show -B results/sample_backup | ||||
| ============================================================================ | ||||
| Start                Time   Total    Data     WAL     Log  Backup   Status   | ||||
| ============================================================================ | ||||
| 2009-06-03 17:05:53  ----    ----    ----    ----    ----    ----   RUNNING | ||||
| 2009-06-01 17:05:53    3m    ----  9223PB    16MB    ----   162MB   DONE | ||||
| 2009-05-31 17:05:53    3m  1242MB    ----  9223PB    ----   242MB   DONE | ||||
| \! pg_rman validate -B results/sample_backup 2009-05-31 17:05:53 --debug | ||||
| INFO: validate: 2009-05-31 17:05:53 | ||||
| LOG: database files... | ||||
| LOG: (1/1) PG_VERSION | ||||
| LOG: archive WAL files... | ||||
| LOG: backup 2009-05-31 17:05:53 is valid | ||||
| \! pg_rman validate -B results/sample_backup 2009-06-01 17:05:53 --debug | ||||
| INFO: validate: 2009-06-01 17:05:53 | ||||
| LOG: database files... | ||||
| LOG: (1/1) PG_VERSION | ||||
| WARNING: CRC of backup file "PG_VERSION" must be 0 but FEF71BC1 | ||||
| LOG: archive WAL files... | ||||
| WARNING: backup 2009-06-01 17:05:53 is corrupted | ||||
| \! pg_rman show -a -B results/sample_backup | ||||
| ============================================================================ | ||||
| Start                Time   Total    Data     WAL     Log  Backup   Status   | ||||
| ============================================================================ | ||||
| 2009-06-03 17:05:53  ----    ----    ----    ----    ----    ----   RUNNING | ||||
| 2009-06-02 17:05:03    0m    ----    ----    ----  4335kB   162MB   DELETED | ||||
| 2009-06-01 17:05:53    3m    ----  9223PB    16MB    ----   162MB   CORRUPT | ||||
| 2009-05-31 17:05:53    3m  1242MB    ----  9223PB    ----   242MB   OK | ||||
| \! pg_rman show 2009-06-01 17:05:53 -B results/sample_backup | ||||
| # configuration | ||||
| BACKUP_MODE=INCREMENTAL | ||||
| WITH_SERVERLOG=false | ||||
| COMPRESS_DATA=false | ||||
| # result | ||||
| TIMELINEID=1 | ||||
| START_LSN=0/0b40c800 | ||||
| STOP_LSN=0/0b4c8020 | ||||
| START_TIME='2009-06-01 17:05:53' | ||||
| END_TIME='2009-06-01 17:09:13' | ||||
| TOTAL_DATA_BYTES=1242102558 | ||||
| READ_DATA_BYTES=9223372036854775807 | ||||
| READ_ARCLOG_BYTES=16777216 | ||||
| READ_SRVLOG_BYTES=-1 | ||||
| WRITE_BYTES=162372983 | ||||
| BLOCK_SIZE=8192 | ||||
| XLOG_BLOCK_SIZE=8192 | ||||
| STATUS=CORRUPT | ||||
							
								
								
									
										156
									
								
								init.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								init.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,156 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * init.c: manage backup catalog. | ||||
|  * | ||||
|  * Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #include "pg_rman.h" | ||||
|  | ||||
| #include <unistd.h> | ||||
|  | ||||
| static void parse_postgresql_conf(const char *path, char **log_directory, | ||||
| 								  char **archive_command); | ||||
|  | ||||
| /* | ||||
|  * Initialize backup catalog. | ||||
|  */ | ||||
| int | ||||
| do_init(void) | ||||
| { | ||||
| 	char	path[MAXPGPATH]; | ||||
| 	char   *log_directory = NULL; | ||||
| 	char   *archive_command = NULL; | ||||
| 	FILE   *fp; | ||||
|  | ||||
| 	if (access(backup_path, F_OK) == 0) | ||||
| 		elog(ERROR, _("backup catalog already exist.")); | ||||
|  | ||||
| 	/* create backup catalog root directory */ | ||||
| 	dir_create_dir(backup_path, DIR_PERMISSION); | ||||
|  | ||||
| 	/* create directories for backup of online files */ | ||||
| 	snprintf(path, lengthof(path), "%s/%s", backup_path, RESTORE_WORK_DIR); | ||||
| 	dir_create_dir(path, DIR_PERMISSION); | ||||
| 	snprintf(path, lengthof(path), "%s/%s/%s", backup_path, RESTORE_WORK_DIR, | ||||
| 		PG_XLOG_DIR); | ||||
| 	dir_create_dir(path, DIR_PERMISSION); | ||||
| 	snprintf(path, lengthof(path), "%s/%s/%s", backup_path, RESTORE_WORK_DIR, | ||||
| 		SRVLOG_DIR); | ||||
| 	dir_create_dir(path, DIR_PERMISSION); | ||||
|  | ||||
| 	/* create directory for timeline history files */ | ||||
| 	snprintf(path, lengthof(path), "%s/%s", backup_path, TIMELINE_HISTORY_DIR); | ||||
| 	dir_create_dir(path, DIR_PERMISSION); | ||||
|  | ||||
| 	/* read postgresql.conf */ | ||||
| 	if (pgdata) | ||||
| 	{ | ||||
| 		snprintf(path, lengthof(path), "%s/%s", pgdata, "postgresql.conf"); | ||||
| 		parse_postgresql_conf(path, &log_directory, &archive_command); | ||||
| 	} | ||||
|  | ||||
| 	/* create pg_rman.ini */ | ||||
| 	snprintf(path, lengthof(path), "%s/%s", backup_path, PG_RMAN_INI_FILE); | ||||
| 	fp = fopen(path, "wt"); | ||||
| 	if (fp == NULL) | ||||
| 		elog(ERROR_SYSTEM, _("can't create pg_rman.ini: %s"), strerror(errno)); | ||||
|  | ||||
| 	/* set ARCLOG_PATH refered with log_directory */ | ||||
| 	if (arclog_path == NULL && archive_command && archive_command[0]) | ||||
| 	{ | ||||
| 		char *command = pgut_strdup(archive_command); | ||||
| 		char *begin; | ||||
| 		char *end; | ||||
| 		char *fname; | ||||
|  | ||||
| 		/* example: 'cp "%p" /path/to/arclog/"%f"' */ | ||||
| 		for (begin = command; *begin;) | ||||
| 		{ | ||||
| 			begin = begin + strspn(begin, " \n\r\t\v"); | ||||
| 			end = begin + strcspn(begin, " \n\r\t\v"); | ||||
| 			*end = '\0'; | ||||
|  | ||||
| 			if ((fname = strstr(begin, "%f")) != NULL) | ||||
| 			{ | ||||
| 				while (strchr(" \n\r\t\v""'", *begin)) | ||||
| 					begin++; | ||||
| 				fname--; | ||||
| 				while (fname > begin && strchr(" \n\r\t\v\"'/", fname[-1])) | ||||
| 					fname--; | ||||
| 				*fname = '\0'; | ||||
|  | ||||
| 				if (is_absolute_path(begin)) | ||||
| 					arclog_path = pgut_strdup(begin); | ||||
| 				break; | ||||
| 			} | ||||
|  | ||||
| 			begin = end + 1; | ||||
| 		} | ||||
|  | ||||
| 		free(command); | ||||
| 	} | ||||
| 	if (arclog_path) | ||||
| 	{ | ||||
| 		fprintf(fp, "ARCLOG_PATH='%s'\n", arclog_path); | ||||
| 		elog(INFO, "ARCLOG_PATH is set to '%s'", arclog_path); | ||||
| 	} | ||||
| 	else if (archive_command && archive_command[0]) | ||||
| 		elog(WARNING, "ARCLOG_PATH is not set because failed to parse archive_command '%s'", archive_command); | ||||
| 	else | ||||
| 		elog(WARNING, "ARCLOG_PATH is not set because archive_command is empty"); | ||||
|  | ||||
| 	/* set SRVLOG_PATH refered with log_directory */ | ||||
| 	if (srvlog_path == NULL) | ||||
| 	{ | ||||
| 		if (log_directory) | ||||
| 		{ | ||||
| 			if (is_absolute_path(log_directory)) | ||||
| 				srvlog_path = pgut_strdup(log_directory); | ||||
| 			else | ||||
| 			{ | ||||
| 				srvlog_path = pgut_malloc(MAXPGPATH); | ||||
| 				snprintf(srvlog_path, MAXPGPATH, "%s/%s", pgdata, log_directory); | ||||
| 			} | ||||
| 		} | ||||
| 		else if (pgdata) | ||||
| 		{ | ||||
| 			/* default: log_directory = 'pg_log' */ | ||||
| 			srvlog_path = pgut_malloc(MAXPGPATH); | ||||
| 			snprintf(srvlog_path, MAXPGPATH, "%s/%s", pgdata, "pg_log"); | ||||
| 		} | ||||
| 	} | ||||
| 	if (srvlog_path) | ||||
| 	{ | ||||
| 		fprintf(fp, "SRVLOG_PATH='%s'\n", srvlog_path); | ||||
| 		elog(INFO, "SRVLOG_PATH is set to '%s'", srvlog_path); | ||||
| 	} | ||||
|  | ||||
| 	fprintf(fp, "\n"); | ||||
| 	fclose(fp); | ||||
|  | ||||
| 	free(archive_command); | ||||
| 	free(log_directory); | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| static void | ||||
| parse_postgresql_conf(const char *path, | ||||
| 					  char **log_directory, | ||||
| 					  char **archive_command) | ||||
| { | ||||
| 	pgut_option options[] = | ||||
| 	{ | ||||
| 		{ 's', 0, "log_directory"		, NULL, SOURCE_ENV }, | ||||
| 		{ 's', 0, "archive_command"		, NULL, SOURCE_ENV }, | ||||
| 		{ 0 } | ||||
| 	}; | ||||
|  | ||||
| 	options[0].var = log_directory; | ||||
| 	options[1].var = archive_command; | ||||
|  | ||||
| 	pgut_readopt(path, options, LOG);	/* ignore unknown options */ | ||||
| } | ||||
							
								
								
									
										184
									
								
								parray.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								parray.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,184 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * parray.c: pointer array collection. | ||||
|  * | ||||
|  * Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #include "pg_rman.h" | ||||
|  | ||||
| /* members of struct parray are hidden from client. */ | ||||
| struct parray | ||||
| { | ||||
| 	void **data;		/* poiter array, expanded if necessary */ | ||||
| 	size_t alloced;		/* number of elements allocated */ | ||||
| 	size_t used;		/* number of elements in use */ | ||||
| }; | ||||
|  | ||||
| /* | ||||
|  * Create new parray object. | ||||
|  * Never returns NULL. | ||||
|  */ | ||||
| parray * | ||||
| parray_new(void) | ||||
| { | ||||
| 	parray *a = pgut_malloc(sizeof(parray)); | ||||
|  | ||||
| 	a->data = NULL; | ||||
| 	a->used = 0; | ||||
| 	a->alloced = 0; | ||||
|  | ||||
| 	parray_expand(a, 1024); | ||||
|  | ||||
| 	return a; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Expand array pointed by data to newsize. | ||||
|  * Elements in expanded area are initialized to NULL. | ||||
|  * Note: never returns NULL. | ||||
|  */ | ||||
| void | ||||
| parray_expand(parray *array, size_t newsize) | ||||
| { | ||||
| 	void **p; | ||||
|  | ||||
| 	/* already allocated */ | ||||
| 	if (newsize <= array->alloced) | ||||
| 		return; | ||||
|  | ||||
| 	p = pgut_realloc(array->data, sizeof(void *) * newsize); | ||||
|  | ||||
| 	/* initialize expanded area to NULL */ | ||||
| 	memset(p + array->alloced, 0, (newsize - array->alloced) * sizeof(void *)); | ||||
|  | ||||
| 	array->alloced = newsize; | ||||
| 	array->data = p; | ||||
|  | ||||
| 	return;  | ||||
| } | ||||
|  | ||||
| void | ||||
| parray_free(parray *array) | ||||
| { | ||||
| 	if (array == NULL) | ||||
| 		return; | ||||
| 	free(array->data); | ||||
| 	free(array); | ||||
| } | ||||
|  | ||||
| void | ||||
| parray_append(parray *array, void *elem) | ||||
| { | ||||
| 	if (array->used + 1 > array->alloced) | ||||
| 		parray_expand(array, array->alloced * 2); | ||||
|  | ||||
| 	array->data[array->used++] = elem; | ||||
| } | ||||
|  | ||||
| void | ||||
| parray_insert(parray *array, size_t index, void *elem) | ||||
| { | ||||
| 	if (array->used + 1 > array->alloced) | ||||
| 		parray_expand(array, array->alloced * 2); | ||||
|  | ||||
| 	memmove(array->data + index + 1, array->data + index, | ||||
| 		(array->alloced - index - 1) * sizeof(void *)); | ||||
| 	array->data[index] = elem; | ||||
|  | ||||
| 	/* adjust used count */ | ||||
| 	if (array->used < index + 1) | ||||
| 		array->used = index + 1; | ||||
| 	else | ||||
| 		array->used++; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Concatinate two parray. | ||||
|  * parray_concat() appends the copy of the content of src to the end of dest. | ||||
|  */ | ||||
| parray * | ||||
| parray_concat(parray *dest, const parray *src) | ||||
| { | ||||
| 	/* expand head array */ | ||||
| 	parray_expand(dest, dest->used + src->used); | ||||
|  | ||||
| 	/* copy content of src after content of dest */ | ||||
| 	memcpy(dest->data + dest->used * sizeof(void *), src->data, | ||||
| 		src->used * sizeof(void *)); | ||||
| 	dest->used += parray_num(src); | ||||
|  | ||||
| 	return dest; | ||||
| } | ||||
|  | ||||
| void | ||||
| parray_set(parray *array, size_t index, void *elem) | ||||
| { | ||||
| 	if (index > array->alloced - 1) | ||||
| 		parray_expand(array, index + 1); | ||||
|  | ||||
| 	array->data[index] = elem; | ||||
|  | ||||
| 	/* adjust used count */ | ||||
| 	if (array->used < index + 1) | ||||
| 		array->used = index + 1; | ||||
| } | ||||
|  | ||||
| void * | ||||
| parray_get(const parray *array, size_t index) | ||||
| { | ||||
| 	if (index > array->alloced - 1) | ||||
| 		return NULL; | ||||
| 	return array->data[index]; | ||||
| } | ||||
|  | ||||
| void * | ||||
| parray_remove(parray *array, size_t index) | ||||
| { | ||||
| 	void *val; | ||||
|  | ||||
| 	/* removing unused element */ | ||||
| 	if (index > array->used) | ||||
| 		return NULL; | ||||
|  | ||||
| 	val = array->data[index]; | ||||
|  | ||||
| 	/* Do not move if the last element was removed. */ | ||||
| 	if (index < array->alloced - 1) | ||||
| 		memmove(array->data + index, array->data + index + 1, | ||||
| 			(array->alloced - index - 1) * sizeof(void *)); | ||||
|  | ||||
| 	/* adjust used count */ | ||||
| 	array->used--; | ||||
|  | ||||
| 	return val; | ||||
| } | ||||
|  | ||||
| size_t | ||||
| parray_num(const parray *array) | ||||
| { | ||||
| 	return array->used; | ||||
| } | ||||
|  | ||||
| void | ||||
| parray_qsort(parray *array, int(*compare)(const void *, const void *)) | ||||
| { | ||||
| 	qsort(array->data, array->used, sizeof(void *), compare); | ||||
| } | ||||
|  | ||||
| void | ||||
| parray_walk(parray *array, void (*action)(void *)) | ||||
| { | ||||
| 	int i; | ||||
| 	for (i = 0; i < array->used; i++) | ||||
| 		action(array->data[i]); | ||||
| } | ||||
|  | ||||
| void * | ||||
| parray_bsearch(parray *array, const void *key, int(*compare)(const void *, const void *)) | ||||
| { | ||||
| 	return bsearch(&key, array->data, array->used, sizeof(void *), compare); | ||||
| } | ||||
|  | ||||
							
								
								
									
										34
									
								
								parray.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								parray.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * parray.h: pointer array collection. | ||||
|  * | ||||
|  * Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #ifndef PARRAY_H | ||||
| #define PARRAY_H | ||||
|  | ||||
| /* | ||||
|  * "parray" hold pointers to objects in a linear memory area. | ||||
|  * Client use "parray *" to access parray object. | ||||
|  */ | ||||
| typedef struct parray parray; | ||||
|  | ||||
| extern parray *parray_new(void); | ||||
| extern void parray_expand(parray *array, size_t newnum); | ||||
| extern void parray_free(parray *array); | ||||
| extern void parray_append(parray *array, void *val); | ||||
| extern void parray_insert(parray *array, size_t index, void *val); | ||||
| extern parray *parray_concat(parray *head, const parray *tail); | ||||
| extern void parray_set(parray *array, size_t index, void *val); | ||||
| extern void *parray_get(const parray *array, size_t index); | ||||
| extern void *parray_remove(parray *array, size_t index); | ||||
| extern size_t parray_num(const parray *array); | ||||
| extern void parray_qsort(parray *array, int(*compare)(const void *, const void *)); | ||||
| extern void *parray_bsearch(parray *array, const void *key, int(*compare)(const void *, const void *)); | ||||
| extern void parray_walk(parray *array, void (*action)(void *)); | ||||
|  | ||||
| #endif /* PARRAY_H */ | ||||
|  | ||||
							
								
								
									
										301
									
								
								pg_rman.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										301
									
								
								pg_rman.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,301 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * pg_rman.c: Backup/Recovery manager for PostgreSQL. | ||||
|  * | ||||
|  * Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #include "pg_rman.h" | ||||
|  | ||||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
| #include <time.h> | ||||
|  | ||||
| const char *PROGRAM_VERSION	= "1.1.1"; | ||||
| const char *PROGRAM_URL		= "https://code.google.com/p/pg-rman/"; | ||||
| const char *PROGRAM_EMAIL	= "https://code.google.com/p/pg-rman/issues/list"; | ||||
|  | ||||
| /* path configuration */ | ||||
| char *backup_path; | ||||
| char *pgdata; | ||||
| char *arclog_path; | ||||
| char *srvlog_path; | ||||
|  | ||||
| /* common configuration */ | ||||
| bool verbose = false; | ||||
| bool check = false; | ||||
|  | ||||
| /* directory configuration */ | ||||
| pgBackup	current; | ||||
|  | ||||
| /* backup configuration */ | ||||
| static bool		smooth_checkpoint; | ||||
| static int		keep_arclog_files = KEEP_INFINITE; | ||||
| static int		keep_arclog_days = KEEP_INFINITE; | ||||
| static int		keep_srvlog_files = KEEP_INFINITE; | ||||
| static int		keep_srvlog_days = KEEP_INFINITE; | ||||
| static int		keep_data_generations = KEEP_INFINITE; | ||||
| static int		keep_data_days = KEEP_INFINITE; | ||||
|  | ||||
| /* restore configuration */ | ||||
| static char		   *target_time; | ||||
| static char		   *target_xid; | ||||
| static char		   *target_inclusive; | ||||
| static TimeLineID	target_tli; | ||||
|  | ||||
| /* show configuration */ | ||||
| static bool			show_all = false; | ||||
|  | ||||
| static void opt_backup_mode(pgut_option *opt, const char *arg); | ||||
| static void parse_range(pgBackupRange *range, const char *arg1, const char *arg2); | ||||
|  | ||||
| static pgut_option options[] = | ||||
| { | ||||
| 	/* directory options */ | ||||
| 	{ 's', 'D', "pgdata"		, &pgdata		, SOURCE_ENV }, | ||||
| 	{ 's', 'A', "arclog-path"	, &arclog_path	, SOURCE_ENV }, | ||||
| 	{ 's', 'B', "backup-path"	, &backup_path	, SOURCE_ENV }, | ||||
| 	{ 's', 'S', "srvlog-path"	, &srvlog_path	, SOURCE_ENV }, | ||||
| 	/* common options */ | ||||
| 	{ 'b', 'v', "verbose"		, &verbose }, | ||||
| 	{ 'b', 'c', "check"			, &check }, | ||||
| 	/* backup options */ | ||||
| 	{ 'f', 'b', "backup-mode"		, opt_backup_mode			, SOURCE_ENV }, | ||||
| 	{ 'b', 's', "with-serverlog"	, ¤t.with_serverlog	, SOURCE_ENV }, | ||||
| 	{ 'b', 'Z', "compress-data"		, ¤t.compress_data	, SOURCE_ENV }, | ||||
| 	{ 'b', 'C', "smooth-checkpoint"	, &smooth_checkpoint		, SOURCE_ENV }, | ||||
| 	/* options with only long name (keep-xxx) */ | ||||
| 	{ 'i',  1, "keep-data-generations"	, &keep_data_generations, SOURCE_ENV }, | ||||
| 	{ 'i',  2, "keep-data-days"			, &keep_data_days		, SOURCE_ENV }, | ||||
| 	{ 'i',  3, "keep-arclog-files"		, &keep_arclog_files	, SOURCE_ENV }, | ||||
| 	{ 'i',  4, "keep-arclog-days"		, &keep_arclog_days		, SOURCE_ENV }, | ||||
| 	{ 'i',  5, "keep-srvlog-files"		, &keep_srvlog_files	, SOURCE_ENV }, | ||||
| 	{ 'i',  6, "keep-srvlog-days"		, &keep_srvlog_days		, SOURCE_ENV }, | ||||
| 	/* restore options */ | ||||
| 	{ 's',  7, "recovery-target-time"		, &target_time		, SOURCE_ENV }, | ||||
| 	{ 's',  8, "recovery-target-xid"		, &target_xid		, SOURCE_ENV }, | ||||
| 	{ 's',  9, "recovery-target-inclusive"	, &target_inclusive	, SOURCE_ENV }, | ||||
| 	{ 'u', 10, "recovery-target-timeline"	, &target_tli		, SOURCE_ENV }, | ||||
| 	/* catalog options */ | ||||
| 	{ 'b', 'a', "show-all"		, &show_all }, | ||||
| 	{ 0 } | ||||
| }; | ||||
|  | ||||
| /* | ||||
|  * Entry point of pg_rman command. | ||||
|  */ | ||||
| int | ||||
| main(int argc, char *argv[]) | ||||
| { | ||||
| 	const char	   *cmd = NULL; | ||||
| 	const char	   *range1 = NULL; | ||||
| 	const char	   *range2 = NULL; | ||||
| 	bool			show_timeline = false; | ||||
| 	pgBackupRange	range; | ||||
| 	int				i; | ||||
|  | ||||
| 	/* do not buffer progress messages */ | ||||
| 	setvbuf(stdout, 0, _IONBF, 0);	/* TODO: remove this */ | ||||
|  | ||||
| 	/* initialize configuration */ | ||||
| 	catalog_init_config(¤t); | ||||
|  | ||||
| 	/* overwrite configuration with command line arguments */ | ||||
| 	i = pgut_getopt(argc, argv, options); | ||||
|  | ||||
| 	for (; i < argc; i++) | ||||
| 	{ | ||||
| 		if (cmd == NULL) | ||||
| 			cmd = argv[i]; | ||||
| 		else if (pg_strcasecmp(argv[i], "timeline") == 0 && | ||||
| 				 pg_strcasecmp(cmd, "show") == 0) | ||||
| 			show_timeline = true; | ||||
| 		else if (range1 == NULL) | ||||
| 			range1 = argv[i]; | ||||
| 		else if (range2 == NULL) | ||||
| 			range2 = argv[i]; | ||||
| 		else | ||||
| 			elog(ERROR_ARGS, "too many arguments"); | ||||
| 	} | ||||
|  | ||||
| 	/* command argument (backup/restore/show/...) is required. */ | ||||
| 	if (cmd == NULL) | ||||
| 	{ | ||||
| 		help(false); | ||||
| 		return HELP; | ||||
| 	} | ||||
|  | ||||
| 	/* get object range argument if any */ | ||||
| 	if (range1 && range2) | ||||
| 		parse_range(&range, range1, range2); | ||||
| 	else if (range1) | ||||
| 		parse_range(&range, range1, ""); | ||||
| 	else | ||||
| 		range.begin = range.end = 0; | ||||
|  | ||||
| 	/* Read default configuration from file. */ | ||||
| 	if (backup_path) | ||||
| 	{ | ||||
| 		char	path[MAXPGPATH]; | ||||
|  | ||||
| 		snprintf(path, lengthof(path), "%s/%s", backup_path, PG_RMAN_INI_FILE); | ||||
| 		pgut_readopt(path, options, ERROR_ARGS); | ||||
| 	} | ||||
|  | ||||
| 	/* BACKUP_PATH is always required */ | ||||
| 	if (backup_path == NULL) | ||||
| 		elog(ERROR_ARGS, "required parameter not specified: BACKUP_PATH (-B, --backup-path)"); | ||||
|  | ||||
| 	/* path must be absolute */ | ||||
| 	if (pgdata != NULL && !is_absolute_path(pgdata)) | ||||
| 		elog(ERROR_ARGS, "-D, --pgdata must be an absolute path"); | ||||
| 	if (arclog_path != NULL && !is_absolute_path(arclog_path)) | ||||
| 		elog(ERROR_ARGS, "-A, --arclog-path must be an absolute path"); | ||||
| 	if (srvlog_path != NULL && !is_absolute_path(srvlog_path)) | ||||
| 		elog(ERROR_ARGS, "-S, --srvlog-path must be an absolute path"); | ||||
|  | ||||
| 	/* setup exclusion list for file search */ | ||||
| 	for (i = 0; pgdata_exclude[i]; i++)		/* find first empty slot */ | ||||
| 		; | ||||
| 	if (arclog_path) | ||||
| 		pgdata_exclude[i++] = arclog_path; | ||||
| 	if (srvlog_path) | ||||
| 		pgdata_exclude[i++] = srvlog_path; | ||||
|  | ||||
| 	/* do actual operation */ | ||||
| 	if (pg_strcasecmp(cmd, "init") == 0) | ||||
| 		return do_init(); | ||||
| 	else if (pg_strcasecmp(cmd, "backup") == 0) | ||||
| 		return do_backup(smooth_checkpoint, | ||||
| 						 keep_arclog_files, keep_arclog_days, | ||||
| 						 keep_srvlog_files, keep_srvlog_days, | ||||
| 						 keep_data_generations, keep_data_days); | ||||
| 	else if (pg_strcasecmp(cmd, "restore") == 0) | ||||
| 		return do_restore(target_time, target_xid, target_inclusive, target_tli); | ||||
| 	else if (pg_strcasecmp(cmd, "show") == 0) | ||||
| 		return do_show(&range, show_timeline, show_all); | ||||
| 	else if (pg_strcasecmp(cmd, "validate") == 0) | ||||
| 		return do_validate(&range); | ||||
| 	else if (pg_strcasecmp(cmd, "delete") == 0) | ||||
| 		return do_delete(&range); | ||||
| 	else | ||||
| 		elog(ERROR_ARGS, "invalid command \"%s\"", cmd); | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| void | ||||
| pgut_help(bool details) | ||||
| { | ||||
| 	printf(_("%s manage backup/recovery of PostgreSQL database.\n\n"), PROGRAM_NAME); | ||||
| 	printf(_("Usage:\n")); | ||||
| 	printf(_("  %s OPTION init\n"), PROGRAM_NAME); | ||||
| 	printf(_("  %s OPTION backup\n"), PROGRAM_NAME); | ||||
| 	printf(_("  %s OPTION restore\n"), PROGRAM_NAME); | ||||
| 	printf(_("  %s OPTION show [DATE]\n"), PROGRAM_NAME); | ||||
| 	printf(_("  %s OPTION show timeline [DATE]\n"), PROGRAM_NAME); | ||||
| 	printf(_("  %s OPTION validate [DATE]\n"), PROGRAM_NAME); | ||||
| 	printf(_("  %s OPTION delete DATE\n"), PROGRAM_NAME); | ||||
|  | ||||
| 	if (!details) | ||||
| 		return; | ||||
|  | ||||
| 	printf(_("\nCommon Options:\n")); | ||||
| 	printf(_("  -D, --pgdata=PATH         location of the database storage area\n")); | ||||
| 	printf(_("  -A, --arclog-path=PATH    location of archive WAL storage area\n")); | ||||
| 	printf(_("  -S, --srvlog-path=PATH    location of server log storage area\n")); | ||||
| 	printf(_("  -B, --backup-path=PATH    location of the backup storage area\n")); | ||||
| 	printf(_("  -c, --check               show what would have been done\n")); | ||||
| 	printf(_("\nBackup options:\n")); | ||||
| 	printf(_("  -b, --backup-mode=MODE    full, incremental, or archive\n")); | ||||
| 	printf(_("  -s, --with-serverlog      also backup server log files\n")); | ||||
| 	printf(_("  -Z, --compress-data       compress data backup with zlib\n")); | ||||
| 	printf(_("  -C, --smooth-checkpoint   do smooth checkpoint before backup\n")); | ||||
| 	printf(_("  --keep-data-generations=N keep GENERATION of full data backup\n")); | ||||
| 	printf(_("  --keep-data-days=DAY      keep enough data backup to recover to DAY days age\n")); | ||||
| 	printf(_("  --keep-arclog-files=NUM   keep NUM of archived WAL\n")); | ||||
| 	printf(_("  --keep-arclog-days=DAY    keep archived WAL modified in DAY days\n")); | ||||
| 	printf(_("  --keep-srvlog-files=NUM   keep NUM of serverlogs\n")); | ||||
| 	printf(_("  --keep-srvlog-days=DAY    keep serverlog modified in DAY days\n")); | ||||
| 	printf(_("\nRestore options:\n")); | ||||
| 	printf(_("  --recovery-target-time    time stamp up to which recovery will proceed\n")); | ||||
| 	printf(_("  --recovery-target-xid     transaction ID up to which recovery will proceed\n")); | ||||
| 	printf(_("  --recovery-target-inclusive whether we stop just after the recovery target\n")); | ||||
| 	printf(_("  --recovery-target-timeline  recovering into a particular timeline\n")); | ||||
| 	printf(_("\nCatalog options:\n")); | ||||
| 	printf(_("  -a, --show-all            show deleted backup too\n")); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Create range object from one or two arguments. | ||||
|  * All not-digit characters in the argument(s) are igonred. | ||||
|  * Both arg1 and arg2 must be valid pointer. | ||||
|  */ | ||||
| static void | ||||
| parse_range(pgBackupRange *range, const char *arg1, const char *arg2) | ||||
| { | ||||
| 	size_t		len = strlen(arg1) + strlen(arg2) + 1; | ||||
| 	char	   *tmp; | ||||
| 	int			num; | ||||
| 	struct tm	tm; | ||||
|  | ||||
| 	tmp = pgut_malloc(len); | ||||
| 	tmp[0] = '\0'; | ||||
| 	if (arg1 != NULL) | ||||
| 		remove_not_digit(tmp, len, arg1); | ||||
| 	if (arg2 != NULL) | ||||
| 		remove_not_digit(tmp + strlen(tmp), len - strlen(tmp), arg2); | ||||
|  | ||||
| 	tm.tm_year = 0;		/* tm_year is year - 1900 */ | ||||
| 	tm.tm_mon = 0;		/* tm_mon is 0 - 11 */ | ||||
| 	tm.tm_mday = 1;		/* tm_mday is 1 - 31 */ | ||||
| 	tm.tm_hour = 0; | ||||
| 	tm.tm_min = 0; | ||||
| 	tm.tm_sec = 0; | ||||
| 	num = sscanf(tmp, "%04d %02d %02d %02d %02d %02d", | ||||
| 		&tm.tm_year, &tm.tm_mon, &tm.tm_mday, | ||||
| 		&tm.tm_hour, &tm.tm_min, &tm.tm_sec); | ||||
|  | ||||
| 	if (num < 1) | ||||
| 		elog(ERROR_ARGS, _("supplied id(%s) is invalid."), tmp); | ||||
|  | ||||
| 	free(tmp); | ||||
|  | ||||
| 	/* adjust year and month to convert to time_t */ | ||||
| 	tm.tm_year -= 1900; | ||||
| 	if (num > 1) | ||||
| 		tm.tm_mon -= 1; | ||||
| 	range->begin = mktime(&tm); | ||||
|  | ||||
| 	switch (num) | ||||
| 	{ | ||||
| 		case 1: | ||||
| 			tm.tm_year++; | ||||
| 			break; | ||||
| 		case 2: | ||||
| 			tm.tm_mon++; | ||||
| 			break; | ||||
| 		case 3: | ||||
| 			tm.tm_mday++; | ||||
| 			break; | ||||
| 		case 4: | ||||
| 			tm.tm_hour++; | ||||
| 			break; | ||||
| 		case 5: | ||||
| 			tm.tm_min++; | ||||
| 			break; | ||||
| 		case 6: | ||||
| 			tm.tm_sec++; | ||||
| 			break; | ||||
| 	} | ||||
| 	range->end = mktime(&tm); | ||||
| 	range->end--; | ||||
| } | ||||
|  | ||||
| static void | ||||
| opt_backup_mode(pgut_option *opt, const char *arg) | ||||
| { | ||||
| 	current.backup_mode = parse_backup_mode(arg, ERROR_ARGS); | ||||
| } | ||||
							
								
								
									
										283
									
								
								pg_rman.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										283
									
								
								pg_rman.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,283 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * pg_rman.h: Backup/Recovery manager for PostgreSQL. | ||||
|  * | ||||
|  * Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
| #ifndef PG_RMAN_H | ||||
| #define PG_RMAN_H | ||||
|  | ||||
| #include "postgres_fe.h" | ||||
|  | ||||
| #include <limits.h> | ||||
| #include "libpq-fe.h" | ||||
|  | ||||
| #include "pgut/pgut.h" | ||||
| #include "access/xlogdefs.h" | ||||
| #include "utils/pg_crc.h" | ||||
| #include "parray.h" | ||||
|  | ||||
| /* Directory/File names */ | ||||
| #define DATABASE_DIR			"database" | ||||
| #define ARCLOG_DIR				"arclog" | ||||
| #define SRVLOG_DIR				"srvlog" | ||||
| #define RESTORE_WORK_DIR		"backup" | ||||
| #define PG_XLOG_DIR				"pg_xlog" | ||||
| #define TIMELINE_HISTORY_DIR	"timeline_history" | ||||
| #define BACKUP_INI_FILE			"backup.ini" | ||||
| #define PG_RMAN_INI_FILE		"pg_rman.ini" | ||||
| #define MKDIRS_SH_FILE			"mkdirs.sh" | ||||
| #define DATABASE_FILE_LIST		"file_database.txt" | ||||
| #define ARCLOG_FILE_LIST		"file_arclog.txt" | ||||
| #define SRVLOG_FILE_LIST		"file_srvlog.txt" | ||||
|  | ||||
| /* Direcotry/File permission */ | ||||
| #define DIR_PERMISSION		(0700) | ||||
| #define FILE_PERMISSION		(0600) | ||||
|  | ||||
| /* Exit code */ | ||||
| #define ERROR_ARCHIVE_FAILED	20	/* cannot archive xlog file */ | ||||
| #define ERROR_NO_BACKUP			21	/* backup was not found in the catalog */ | ||||
| #define ERROR_CORRUPTED			22	/* backup catalog is corrupted */ | ||||
| #define ERROR_ALREADY_RUNNING	23	/* another pg_rman is running */ | ||||
| #define ERROR_PG_INCOMPATIBLE	24	/* block size is not compatible */ | ||||
| #define ERROR_PG_RUNNING		25	/* PostgreSQL server is running */ | ||||
| #define ERROR_PID_BROKEN		26	/* postmaster.pid file is broken */ | ||||
|  | ||||
| /* backup mode file */ | ||||
| typedef struct pgFile | ||||
| { | ||||
| 	time_t	mtime;			/* time of last modification */ | ||||
| 	mode_t	mode;			/* protection (file type and permission) */ | ||||
| 	size_t	size;			/* size of the file */ | ||||
| 	size_t	read_size;		/* size of the portion read (if only some pages are  | ||||
| 							   backed up partially, it's different from size) */ | ||||
| 	size_t	write_size;		/* size of the backed-up file. BYTES_INVALID means | ||||
| 							   that the file existed but was not backed up | ||||
| 							   because not modified since last backup. */ | ||||
| 	pg_crc32 crc;			/* CRC value of the file, regular file only */ | ||||
| 	char   *linked;			/* path of the linked file */ | ||||
| 	bool	is_datafile;	/* true if the file is PostgreSQL data file */ | ||||
| 	char	path[1]; 		/* path of the file */ | ||||
| } pgFile; | ||||
|  | ||||
| typedef struct pgBackupRange | ||||
| { | ||||
| 	time_t	begin; | ||||
| 	time_t	end;			/* begin +1 when one backup is target */ | ||||
| } pgBackupRange; | ||||
|  | ||||
| #define pgBackupRangeIsValid(range)	\ | ||||
| 	(((range)->begin != (time_t) 0) || ((range)->end != (time_t) 0)) | ||||
| #define pgBackupRangeIsSingle(range) \ | ||||
| 	(pgBackupRangeIsValid(range) && (range)->begin == ((range)->end)) | ||||
|  | ||||
| /* Backup status */ | ||||
| /* XXX re-order ? */ | ||||
| typedef enum BackupStatus | ||||
| { | ||||
| 	BACKUP_STATUS_INVALID,		/* the pgBackup is invalid */ | ||||
| 	BACKUP_STATUS_OK,			/* completed backup */ | ||||
| 	BACKUP_STATUS_RUNNING,		/* running backup */ | ||||
| 	BACKUP_STATUS_ERROR,		/* aborted because of unexpected error */ | ||||
| 	BACKUP_STATUS_DELETING,		/* data files are being deleted */ | ||||
| 	BACKUP_STATUS_DELETED,		/* data files have been deleted */ | ||||
| 	BACKUP_STATUS_DONE,			/* completed but not validated yet */ | ||||
| 	BACKUP_STATUS_CORRUPT		/* files are corrupted, not available */ | ||||
| } BackupStatus; | ||||
|  | ||||
| typedef enum BackupMode | ||||
| { | ||||
| 	BACKUP_MODE_INVALID, | ||||
| 	BACKUP_MODE_ARCHIVE,		/* arhicve only */ | ||||
| 	BACKUP_MODE_INCREMENTAL,	/* incremental backup */ | ||||
| 	BACKUP_MODE_FULL			/* full backup */ | ||||
| } BackupMode; | ||||
|  | ||||
| /* | ||||
|  * pg_rman takes backup into the directroy $BACKUP_PATH/<date>/<time>. | ||||
|  * | ||||
|  * status == -1 indicates the pgBackup is invalid. | ||||
|  */ | ||||
| typedef struct pgBackup | ||||
| { | ||||
| 	/* Backup Level */ | ||||
| 	BackupMode	backup_mode; | ||||
| 	bool		with_serverlog; | ||||
| 	bool		compress_data; | ||||
|  | ||||
| 	/* Status - one of BACKUP_STATUS_xxx */ | ||||
| 	BackupStatus	status; | ||||
|  | ||||
| 	/* Timestamp, etc. */ | ||||
| 	TimeLineID	tli; | ||||
| 	XLogRecPtr	start_lsn; | ||||
| 	XLogRecPtr	stop_lsn; | ||||
| 	time_t		start_time; | ||||
| 	time_t		end_time; | ||||
|  | ||||
| 	/* Size (-1 means not-backup'ed) */ | ||||
| 	int64		total_data_bytes; | ||||
| 	int64		read_data_bytes; | ||||
| 	int64		read_arclog_bytes; | ||||
| 	int64		read_srvlog_bytes; | ||||
| 	int64		write_bytes; | ||||
|  | ||||
| 	/* data/wal block size for compatibility check */ | ||||
| 	uint32		block_size; | ||||
| 	uint32		wal_block_size; | ||||
| } pgBackup; | ||||
|  | ||||
| /* special values of pgBackup */ | ||||
| #define KEEP_INFINITE			(INT_MAX) | ||||
| #define BYTES_INVALID			(-1) | ||||
|  | ||||
| #define HAVE_DATABASE(backup)	((backup)->backup_mode >= BACKUP_MODE_INCREMENTAL) | ||||
| #define HAVE_ARCLOG(backup)		((backup)->backup_mode >= BACKUP_MODE_ARCHIVE) | ||||
| #define TOTAL_READ_SIZE(backup)	\ | ||||
| 	((HAVE_DATABASE((backup)) ? (backup)->read_data_bytes : 0) + \ | ||||
| 	 (HAVE_ARCLOG((backup)) ? (backup)->read_arclog_bytes : 0) + \ | ||||
| 	 ((backup)->with_serverlog ? (backup)->read_srvlog_bytes : 0)) | ||||
|  | ||||
| typedef struct pgTimeLine | ||||
| { | ||||
| 	TimeLineID	tli; | ||||
| 	XLogRecPtr	end; | ||||
| } pgTimeLine; | ||||
|  | ||||
| typedef enum CompressionMode | ||||
| { | ||||
| 	NO_COMPRESSION, | ||||
| 	COMPRESSION, | ||||
| 	DECOMPRESSION, | ||||
| } CompressionMode; | ||||
|  | ||||
| /* path configuration */ | ||||
| extern char *backup_path; | ||||
| extern char *pgdata; | ||||
| extern char *arclog_path; | ||||
| extern char *srvlog_path; | ||||
|  | ||||
| /* common configuration */ | ||||
| extern bool verbose; | ||||
| extern bool check; | ||||
|  | ||||
| /* current settings */ | ||||
| extern pgBackup current; | ||||
|  | ||||
| /* exclude directory list for $PGDATA file listing */ | ||||
| extern const char *pgdata_exclude[]; | ||||
|  | ||||
| /* in backup.c */ | ||||
| extern int do_backup(bool smooth_checkpoint, | ||||
| 					 int keep_arclog_files, | ||||
| 					 int keep_arclog_days, | ||||
| 					 int keep_srvlog_files, | ||||
| 					 int keep_srvlog_days, | ||||
| 					 int keep_data_generations, | ||||
| 					 int keep_data_days); | ||||
| extern BackupMode parse_backup_mode(const char *value, int elevel); | ||||
|  | ||||
| /* in restore.c */ | ||||
| extern int do_restore(const char *target_time, | ||||
| 					  const char *target_xid, | ||||
| 					  const char *target_inclusive, | ||||
| 					  TimeLineID target_tli); | ||||
|  | ||||
| /* in init.c */ | ||||
| extern int do_init(void); | ||||
|  | ||||
| /* in show.c */ | ||||
| extern int do_show(pgBackupRange *range, bool show_timeline, bool show_all); | ||||
|  | ||||
| /* in delete.c */ | ||||
| extern int do_delete(pgBackupRange *range); | ||||
| extern void pgBackupDelete(int keep_generations, int keep_days); | ||||
|  | ||||
| /* in validate.c */ | ||||
| extern int do_validate(pgBackupRange *range); | ||||
| extern void pgBackupValidate(pgBackup *backup, bool size_only); | ||||
|  | ||||
| /* in catalog.c */ | ||||
| extern pgBackup *catalog_get_backup(time_t timestamp); | ||||
| extern parray *catalog_get_backup_list(const pgBackupRange *range); | ||||
| extern pgBackup *catalog_get_last_data_backup(parray *backup_list); | ||||
| extern pgBackup *catalog_get_last_arclog_backup(parray *backup_list); | ||||
| extern pgBackup *catalog_get_last_srvlog_backup(parray *backup_list); | ||||
|  | ||||
| extern int catalog_lock(void); | ||||
| extern void catalog_unlock(void); | ||||
|  | ||||
| extern void catalog_init_config(pgBackup *backup); | ||||
|  | ||||
| extern void pgBackupWriteConfigSection(FILE *out, pgBackup *backup); | ||||
| extern void pgBackupWriteResultSection(FILE *out, pgBackup *backup); | ||||
| extern void pgBackupWriteIni(pgBackup *backup); | ||||
| extern void pgBackupGetPath(const pgBackup *backup, char *path, size_t len, const char *subdir); | ||||
| extern int pgBackupCreateDir(pgBackup *backup); | ||||
| extern void pgBackupFree(void *backup); | ||||
| extern int pgBackupCompareId(const void *f1, const void *f2); | ||||
| extern int pgBackupCompareIdDesc(const void *f1, const void *f2); | ||||
|  | ||||
| /* in dir.c */ | ||||
| extern void dir_list_file(parray *files, const char *root, const char *exclude[], bool omit_symlink, bool add_root); | ||||
| extern void dir_print_mkdirs_sh(FILE *out, const parray *files, const char *root); | ||||
| extern void dir_print_file_list(FILE *out, const parray *files, const char *root); | ||||
| extern parray *dir_read_file_list(const char *root, const char *file_txt); | ||||
|  | ||||
| extern int dir_create_dir(const char *path, mode_t mode); | ||||
| extern void dir_copy_files(const char *from_root, const char *to_root); | ||||
|  | ||||
| extern void pgFileDump(pgFile *file, FILE *out); | ||||
| extern void pgFileDelete(pgFile *file); | ||||
| extern void pgFileFree(void *file); | ||||
| extern pg_crc32 pgFileGetCRC(pgFile *file); | ||||
| extern int pgFileComparePath(const void *f1, const void *f2); | ||||
| extern int pgFileComparePathDesc(const void *f1, const void *f2); | ||||
| extern int pgFileCompareMtime(const void *f1, const void *f2); | ||||
| extern int pgFileCompareMtimeDesc(const void *f1, const void *f2); | ||||
|  | ||||
| /* in xlog.c */ | ||||
| extern bool xlog_is_complete_wal(const pgFile *file); | ||||
| extern bool xlog_logfname2lsn(const char *logfname, XLogRecPtr *lsn); | ||||
| extern void xlog_fname(char *fname, size_t len, TimeLineID tli, XLogRecPtr *lsn); | ||||
|  | ||||
| /* in data.c */ | ||||
| extern void backup_data_file(const char *from_root, const char *to_root, | ||||
| 							 pgFile *file, const XLogRecPtr *lsn, bool compress); | ||||
| extern void restore_data_file(const char *from_root, const char *to_root, | ||||
| 							  pgFile *file, bool compress); | ||||
| extern void copy_file(const char *from_root, const char *to_root, | ||||
| 					  pgFile *file, CompressionMode compress); | ||||
|  | ||||
| /* in util.c */ | ||||
| extern void time2iso(char *buf, size_t len, time_t time); | ||||
| extern const char *status2str(BackupStatus status); | ||||
| extern void remove_trailing_space(char *buf, int comment_mark); | ||||
| extern void remove_not_digit(char *buf, size_t len, const char *str); | ||||
|  | ||||
| /* in pgsql_src/pg_ctl.c */ | ||||
| extern bool is_pg_running(void); | ||||
|  | ||||
| /* access/xlog_internal.h */ | ||||
| #define XLogSegSize		((uint32) XLOG_SEG_SIZE) | ||||
| #define XLogSegsPerFile (((uint32) 0xffffffff) / XLogSegSize) | ||||
| #define XLogFileSize	(XLogSegsPerFile * XLogSegSize) | ||||
|  | ||||
| #define NextLogSeg(logId, logSeg)	\ | ||||
| 	do { \ | ||||
| 		if ((logSeg) >= XLogSegsPerFile-1) \ | ||||
| 		{ \ | ||||
| 			(logId)++; \ | ||||
| 			(logSeg) = 0; \ | ||||
| 		} \ | ||||
| 		else \ | ||||
| 			(logSeg)++; \ | ||||
| 	} while (0) | ||||
| #define MAXFNAMELEN		64 | ||||
| #define XLogFileName(fname, tli, log, seg)	\ | ||||
| 	snprintf(fname, MAXFNAMELEN, "%08X%08X%08X", tli, log, seg) | ||||
|  | ||||
| #endif /* PG_RMAN_H */ | ||||
							
								
								
									
										24
									
								
								pgsql_src/COPYRIGHT.pgsql_src
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								pgsql_src/COPYRIGHT.pgsql_src
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| Files in this directory are parts of PostgreSQL Database Management System. | ||||
| Copyright holders of those files are following organizations: | ||||
|  | ||||
|  | ||||
| Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group | ||||
|  | ||||
| Portions Copyright (c) 1994, The Regents of the University of California | ||||
|  | ||||
| Permission to use, copy, modify, and distribute this software and its | ||||
| documentation for any purpose, without fee, and without a written agreement | ||||
| is hereby granted, provided that the above copyright notice and this | ||||
| paragraph and the following two paragraphs appear in all copies. | ||||
|  | ||||
| IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR | ||||
| DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING | ||||
| LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS | ||||
| DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE | ||||
| POSSIBILITY OF SUCH DAMAGE. | ||||
|  | ||||
| THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, | ||||
| INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY | ||||
| AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS | ||||
| ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO | ||||
| PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. | ||||
							
								
								
									
										515
									
								
								pgsql_src/pg_crc.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										515
									
								
								pgsql_src/pg_crc.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,515 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * pg_crc.c | ||||
|  *	  PostgreSQL CRC support | ||||
|  * | ||||
|  * See Ross Williams' excellent introduction | ||||
|  * A PAINLESS GUIDE TO CRC ERROR DETECTION ALGORITHMS, available from | ||||
|  * http://www.ross.net/crc/download/crc_v3.txt or several other net sites. | ||||
|  * | ||||
|  * We use a normal (not "reflected", in Williams' terms) CRC, using initial | ||||
|  * all-ones register contents and a final bit inversion. | ||||
|  * | ||||
|  * The 64-bit variant is not used as of PostgreSQL 8.1, but we retain the | ||||
|  * code for possible future use. | ||||
|  * | ||||
|  * | ||||
|  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group | ||||
|  * Portions Copyright (c) 1994, Regents of the University of California | ||||
|  * | ||||
|  * | ||||
|  * IDENTIFICATION | ||||
|  *	  $PostgreSQL: pgsql/src/backend/utils/hash/pg_crc.c,v 1.21 2009/01/01 17:23:51 momjian Exp $ | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| /* Use c.h so that this file can be built in either frontend or backend */ | ||||
| #include "c.h" | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * This table is based on the polynomial | ||||
|  *	x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1. | ||||
|  * (This is the same polynomial used in Ethernet checksums, for instance.) | ||||
|  */ | ||||
| const uint32 pg_crc32_table[256] = { | ||||
| 	0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, | ||||
| 	0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, | ||||
| 	0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, | ||||
| 	0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, | ||||
| 	0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, | ||||
| 	0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, | ||||
| 	0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, | ||||
| 	0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, | ||||
| 	0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, | ||||
| 	0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, | ||||
| 	0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, | ||||
| 	0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, | ||||
| 	0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, | ||||
| 	0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, | ||||
| 	0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, | ||||
| 	0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, | ||||
| 	0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, | ||||
| 	0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, | ||||
| 	0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, | ||||
| 	0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, | ||||
| 	0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, | ||||
| 	0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, | ||||
| 	0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, | ||||
| 	0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, | ||||
| 	0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, | ||||
| 	0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, | ||||
| 	0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, | ||||
| 	0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, | ||||
| 	0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, | ||||
| 	0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, | ||||
| 	0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, | ||||
| 	0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, | ||||
| 	0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, | ||||
| 	0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, | ||||
| 	0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, | ||||
| 	0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, | ||||
| 	0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, | ||||
| 	0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, | ||||
| 	0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, | ||||
| 	0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, | ||||
| 	0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, | ||||
| 	0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, | ||||
| 	0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, | ||||
| 	0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, | ||||
| 	0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, | ||||
| 	0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, | ||||
| 	0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, | ||||
| 	0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, | ||||
| 	0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, | ||||
| 	0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, | ||||
| 	0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, | ||||
| 	0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, | ||||
| 	0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, | ||||
| 	0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, | ||||
| 	0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, | ||||
| 	0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, | ||||
| 	0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, | ||||
| 	0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, | ||||
| 	0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, | ||||
| 	0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, | ||||
| 	0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, | ||||
| 	0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, | ||||
| 	0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, | ||||
| 	0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D | ||||
| }; | ||||
|  | ||||
|  | ||||
| #ifdef PROVIDE_64BIT_CRC | ||||
|  | ||||
| /* | ||||
|  * This table is based on the polynomial | ||||
|  * | ||||
|  * x^64 + x^62 + x^57 + x^55 + x^54 + x^53 + x^52 + x^47 + x^46 + x^45 + | ||||
|  * x^40 + x^39 + x^38 + x^37 + x^35 + x^33 + x^32 + x^31 + x^29 + x^27 + | ||||
|  * x^24 + x^23 + x^22 + x^21 + x^19 + x^17 + x^13 + x^12 + x^10 + x^9 + | ||||
|  * x^7 + x^4 + x + 1 | ||||
|  * | ||||
|  * which is borrowed from the DLT1 spec | ||||
|  * (ECMA-182, available from http://www.ecma.ch/ecma1/STAND/ECMA-182.HTM) | ||||
|  */ | ||||
|  | ||||
| #ifdef INT64_IS_BUSTED | ||||
|  | ||||
| const uint32 pg_crc64_table0[256] = { | ||||
| 	0x00000000, 0xA9EA3693, | ||||
| 	0x53D46D26, 0xFA3E5BB5, | ||||
| 	0x0E42ECDF, 0xA7A8DA4C, | ||||
| 	0x5D9681F9, 0xF47CB76A, | ||||
| 	0x1C85D9BE, 0xB56FEF2D, | ||||
| 	0x4F51B498, 0xE6BB820B, | ||||
| 	0x12C73561, 0xBB2D03F2, | ||||
| 	0x41135847, 0xE8F96ED4, | ||||
| 	0x90E185EF, 0x390BB37C, | ||||
| 	0xC335E8C9, 0x6ADFDE5A, | ||||
| 	0x9EA36930, 0x37495FA3, | ||||
| 	0xCD770416, 0x649D3285, | ||||
| 	0x8C645C51, 0x258E6AC2, | ||||
| 	0xDFB03177, 0x765A07E4, | ||||
| 	0x8226B08E, 0x2BCC861D, | ||||
| 	0xD1F2DDA8, 0x7818EB3B, | ||||
| 	0x21C30BDE, 0x88293D4D, | ||||
| 	0x721766F8, 0xDBFD506B, | ||||
| 	0x2F81E701, 0x866BD192, | ||||
| 	0x7C558A27, 0xD5BFBCB4, | ||||
| 	0x3D46D260, 0x94ACE4F3, | ||||
| 	0x6E92BF46, 0xC77889D5, | ||||
| 	0x33043EBF, 0x9AEE082C, | ||||
| 	0x60D05399, 0xC93A650A, | ||||
| 	0xB1228E31, 0x18C8B8A2, | ||||
| 	0xE2F6E317, 0x4B1CD584, | ||||
| 	0xBF6062EE, 0x168A547D, | ||||
| 	0xECB40FC8, 0x455E395B, | ||||
| 	0xADA7578F, 0x044D611C, | ||||
| 	0xFE733AA9, 0x57990C3A, | ||||
| 	0xA3E5BB50, 0x0A0F8DC3, | ||||
| 	0xF031D676, 0x59DBE0E5, | ||||
| 	0xEA6C212F, 0x438617BC, | ||||
| 	0xB9B84C09, 0x10527A9A, | ||||
| 	0xE42ECDF0, 0x4DC4FB63, | ||||
| 	0xB7FAA0D6, 0x1E109645, | ||||
| 	0xF6E9F891, 0x5F03CE02, | ||||
| 	0xA53D95B7, 0x0CD7A324, | ||||
| 	0xF8AB144E, 0x514122DD, | ||||
| 	0xAB7F7968, 0x02954FFB, | ||||
| 	0x7A8DA4C0, 0xD3679253, | ||||
| 	0x2959C9E6, 0x80B3FF75, | ||||
| 	0x74CF481F, 0xDD257E8C, | ||||
| 	0x271B2539, 0x8EF113AA, | ||||
| 	0x66087D7E, 0xCFE24BED, | ||||
| 	0x35DC1058, 0x9C3626CB, | ||||
| 	0x684A91A1, 0xC1A0A732, | ||||
| 	0x3B9EFC87, 0x9274CA14, | ||||
| 	0xCBAF2AF1, 0x62451C62, | ||||
| 	0x987B47D7, 0x31917144, | ||||
| 	0xC5EDC62E, 0x6C07F0BD, | ||||
| 	0x9639AB08, 0x3FD39D9B, | ||||
| 	0xD72AF34F, 0x7EC0C5DC, | ||||
| 	0x84FE9E69, 0x2D14A8FA, | ||||
| 	0xD9681F90, 0x70822903, | ||||
| 	0x8ABC72B6, 0x23564425, | ||||
| 	0x5B4EAF1E, 0xF2A4998D, | ||||
| 	0x089AC238, 0xA170F4AB, | ||||
| 	0x550C43C1, 0xFCE67552, | ||||
| 	0x06D82EE7, 0xAF321874, | ||||
| 	0x47CB76A0, 0xEE214033, | ||||
| 	0x141F1B86, 0xBDF52D15, | ||||
| 	0x49899A7F, 0xE063ACEC, | ||||
| 	0x1A5DF759, 0xB3B7C1CA, | ||||
| 	0x7D3274CD, 0xD4D8425E, | ||||
| 	0x2EE619EB, 0x870C2F78, | ||||
| 	0x73709812, 0xDA9AAE81, | ||||
| 	0x20A4F534, 0x894EC3A7, | ||||
| 	0x61B7AD73, 0xC85D9BE0, | ||||
| 	0x3263C055, 0x9B89F6C6, | ||||
| 	0x6FF541AC, 0xC61F773F, | ||||
| 	0x3C212C8A, 0x95CB1A19, | ||||
| 	0xEDD3F122, 0x4439C7B1, | ||||
| 	0xBE079C04, 0x17EDAA97, | ||||
| 	0xE3911DFD, 0x4A7B2B6E, | ||||
| 	0xB04570DB, 0x19AF4648, | ||||
| 	0xF156289C, 0x58BC1E0F, | ||||
| 	0xA28245BA, 0x0B687329, | ||||
| 	0xFF14C443, 0x56FEF2D0, | ||||
| 	0xACC0A965, 0x052A9FF6, | ||||
| 	0x5CF17F13, 0xF51B4980, | ||||
| 	0x0F251235, 0xA6CF24A6, | ||||
| 	0x52B393CC, 0xFB59A55F, | ||||
| 	0x0167FEEA, 0xA88DC879, | ||||
| 	0x4074A6AD, 0xE99E903E, | ||||
| 	0x13A0CB8B, 0xBA4AFD18, | ||||
| 	0x4E364A72, 0xE7DC7CE1, | ||||
| 	0x1DE22754, 0xB40811C7, | ||||
| 	0xCC10FAFC, 0x65FACC6F, | ||||
| 	0x9FC497DA, 0x362EA149, | ||||
| 	0xC2521623, 0x6BB820B0, | ||||
| 	0x91867B05, 0x386C4D96, | ||||
| 	0xD0952342, 0x797F15D1, | ||||
| 	0x83414E64, 0x2AAB78F7, | ||||
| 	0xDED7CF9D, 0x773DF90E, | ||||
| 	0x8D03A2BB, 0x24E99428, | ||||
| 	0x975E55E2, 0x3EB46371, | ||||
| 	0xC48A38C4, 0x6D600E57, | ||||
| 	0x991CB93D, 0x30F68FAE, | ||||
| 	0xCAC8D41B, 0x6322E288, | ||||
| 	0x8BDB8C5C, 0x2231BACF, | ||||
| 	0xD80FE17A, 0x71E5D7E9, | ||||
| 	0x85996083, 0x2C735610, | ||||
| 	0xD64D0DA5, 0x7FA73B36, | ||||
| 	0x07BFD00D, 0xAE55E69E, | ||||
| 	0x546BBD2B, 0xFD818BB8, | ||||
| 	0x09FD3CD2, 0xA0170A41, | ||||
| 	0x5A2951F4, 0xF3C36767, | ||||
| 	0x1B3A09B3, 0xB2D03F20, | ||||
| 	0x48EE6495, 0xE1045206, | ||||
| 	0x1578E56C, 0xBC92D3FF, | ||||
| 	0x46AC884A, 0xEF46BED9, | ||||
| 	0xB69D5E3C, 0x1F7768AF, | ||||
| 	0xE549331A, 0x4CA30589, | ||||
| 	0xB8DFB2E3, 0x11358470, | ||||
| 	0xEB0BDFC5, 0x42E1E956, | ||||
| 	0xAA188782, 0x03F2B111, | ||||
| 	0xF9CCEAA4, 0x5026DC37, | ||||
| 	0xA45A6B5D, 0x0DB05DCE, | ||||
| 	0xF78E067B, 0x5E6430E8, | ||||
| 	0x267CDBD3, 0x8F96ED40, | ||||
| 	0x75A8B6F5, 0xDC428066, | ||||
| 	0x283E370C, 0x81D4019F, | ||||
| 	0x7BEA5A2A, 0xD2006CB9, | ||||
| 	0x3AF9026D, 0x931334FE, | ||||
| 	0x692D6F4B, 0xC0C759D8, | ||||
| 	0x34BBEEB2, 0x9D51D821, | ||||
| 	0x676F8394, 0xCE85B507 | ||||
| }; | ||||
|  | ||||
| const uint32 pg_crc64_table1[256] = { | ||||
| 	0x00000000, 0x42F0E1EB, | ||||
| 	0x85E1C3D7, 0xC711223C, | ||||
| 	0x49336645, 0x0BC387AE, | ||||
| 	0xCCD2A592, 0x8E224479, | ||||
| 	0x9266CC8A, 0xD0962D61, | ||||
| 	0x17870F5D, 0x5577EEB6, | ||||
| 	0xDB55AACF, 0x99A54B24, | ||||
| 	0x5EB46918, 0x1C4488F3, | ||||
| 	0x663D78FF, 0x24CD9914, | ||||
| 	0xE3DCBB28, 0xA12C5AC3, | ||||
| 	0x2F0E1EBA, 0x6DFEFF51, | ||||
| 	0xAAEFDD6D, 0xE81F3C86, | ||||
| 	0xF45BB475, 0xB6AB559E, | ||||
| 	0x71BA77A2, 0x334A9649, | ||||
| 	0xBD68D230, 0xFF9833DB, | ||||
| 	0x388911E7, 0x7A79F00C, | ||||
| 	0xCC7AF1FF, 0x8E8A1014, | ||||
| 	0x499B3228, 0x0B6BD3C3, | ||||
| 	0x854997BA, 0xC7B97651, | ||||
| 	0x00A8546D, 0x4258B586, | ||||
| 	0x5E1C3D75, 0x1CECDC9E, | ||||
| 	0xDBFDFEA2, 0x990D1F49, | ||||
| 	0x172F5B30, 0x55DFBADB, | ||||
| 	0x92CE98E7, 0xD03E790C, | ||||
| 	0xAA478900, 0xE8B768EB, | ||||
| 	0x2FA64AD7, 0x6D56AB3C, | ||||
| 	0xE374EF45, 0xA1840EAE, | ||||
| 	0x66952C92, 0x2465CD79, | ||||
| 	0x3821458A, 0x7AD1A461, | ||||
| 	0xBDC0865D, 0xFF3067B6, | ||||
| 	0x711223CF, 0x33E2C224, | ||||
| 	0xF4F3E018, 0xB60301F3, | ||||
| 	0xDA050215, 0x98F5E3FE, | ||||
| 	0x5FE4C1C2, 0x1D142029, | ||||
| 	0x93366450, 0xD1C685BB, | ||||
| 	0x16D7A787, 0x5427466C, | ||||
| 	0x4863CE9F, 0x0A932F74, | ||||
| 	0xCD820D48, 0x8F72ECA3, | ||||
| 	0x0150A8DA, 0x43A04931, | ||||
| 	0x84B16B0D, 0xC6418AE6, | ||||
| 	0xBC387AEA, 0xFEC89B01, | ||||
| 	0x39D9B93D, 0x7B2958D6, | ||||
| 	0xF50B1CAF, 0xB7FBFD44, | ||||
| 	0x70EADF78, 0x321A3E93, | ||||
| 	0x2E5EB660, 0x6CAE578B, | ||||
| 	0xABBF75B7, 0xE94F945C, | ||||
| 	0x676DD025, 0x259D31CE, | ||||
| 	0xE28C13F2, 0xA07CF219, | ||||
| 	0x167FF3EA, 0x548F1201, | ||||
| 	0x939E303D, 0xD16ED1D6, | ||||
| 	0x5F4C95AF, 0x1DBC7444, | ||||
| 	0xDAAD5678, 0x985DB793, | ||||
| 	0x84193F60, 0xC6E9DE8B, | ||||
| 	0x01F8FCB7, 0x43081D5C, | ||||
| 	0xCD2A5925, 0x8FDAB8CE, | ||||
| 	0x48CB9AF2, 0x0A3B7B19, | ||||
| 	0x70428B15, 0x32B26AFE, | ||||
| 	0xF5A348C2, 0xB753A929, | ||||
| 	0x3971ED50, 0x7B810CBB, | ||||
| 	0xBC902E87, 0xFE60CF6C, | ||||
| 	0xE224479F, 0xA0D4A674, | ||||
| 	0x67C58448, 0x253565A3, | ||||
| 	0xAB1721DA, 0xE9E7C031, | ||||
| 	0x2EF6E20D, 0x6C0603E6, | ||||
| 	0xF6FAE5C0, 0xB40A042B, | ||||
| 	0x731B2617, 0x31EBC7FC, | ||||
| 	0xBFC98385, 0xFD39626E, | ||||
| 	0x3A284052, 0x78D8A1B9, | ||||
| 	0x649C294A, 0x266CC8A1, | ||||
| 	0xE17DEA9D, 0xA38D0B76, | ||||
| 	0x2DAF4F0F, 0x6F5FAEE4, | ||||
| 	0xA84E8CD8, 0xEABE6D33, | ||||
| 	0x90C79D3F, 0xD2377CD4, | ||||
| 	0x15265EE8, 0x57D6BF03, | ||||
| 	0xD9F4FB7A, 0x9B041A91, | ||||
| 	0x5C1538AD, 0x1EE5D946, | ||||
| 	0x02A151B5, 0x4051B05E, | ||||
| 	0x87409262, 0xC5B07389, | ||||
| 	0x4B9237F0, 0x0962D61B, | ||||
| 	0xCE73F427, 0x8C8315CC, | ||||
| 	0x3A80143F, 0x7870F5D4, | ||||
| 	0xBF61D7E8, 0xFD913603, | ||||
| 	0x73B3727A, 0x31439391, | ||||
| 	0xF652B1AD, 0xB4A25046, | ||||
| 	0xA8E6D8B5, 0xEA16395E, | ||||
| 	0x2D071B62, 0x6FF7FA89, | ||||
| 	0xE1D5BEF0, 0xA3255F1B, | ||||
| 	0x64347D27, 0x26C49CCC, | ||||
| 	0x5CBD6CC0, 0x1E4D8D2B, | ||||
| 	0xD95CAF17, 0x9BAC4EFC, | ||||
| 	0x158E0A85, 0x577EEB6E, | ||||
| 	0x906FC952, 0xD29F28B9, | ||||
| 	0xCEDBA04A, 0x8C2B41A1, | ||||
| 	0x4B3A639D, 0x09CA8276, | ||||
| 	0x87E8C60F, 0xC51827E4, | ||||
| 	0x020905D8, 0x40F9E433, | ||||
| 	0x2CFFE7D5, 0x6E0F063E, | ||||
| 	0xA91E2402, 0xEBEEC5E9, | ||||
| 	0x65CC8190, 0x273C607B, | ||||
| 	0xE02D4247, 0xA2DDA3AC, | ||||
| 	0xBE992B5F, 0xFC69CAB4, | ||||
| 	0x3B78E888, 0x79880963, | ||||
| 	0xF7AA4D1A, 0xB55AACF1, | ||||
| 	0x724B8ECD, 0x30BB6F26, | ||||
| 	0x4AC29F2A, 0x08327EC1, | ||||
| 	0xCF235CFD, 0x8DD3BD16, | ||||
| 	0x03F1F96F, 0x41011884, | ||||
| 	0x86103AB8, 0xC4E0DB53, | ||||
| 	0xD8A453A0, 0x9A54B24B, | ||||
| 	0x5D459077, 0x1FB5719C, | ||||
| 	0x919735E5, 0xD367D40E, | ||||
| 	0x1476F632, 0x568617D9, | ||||
| 	0xE085162A, 0xA275F7C1, | ||||
| 	0x6564D5FD, 0x27943416, | ||||
| 	0xA9B6706F, 0xEB469184, | ||||
| 	0x2C57B3B8, 0x6EA75253, | ||||
| 	0x72E3DAA0, 0x30133B4B, | ||||
| 	0xF7021977, 0xB5F2F89C, | ||||
| 	0x3BD0BCE5, 0x79205D0E, | ||||
| 	0xBE317F32, 0xFCC19ED9, | ||||
| 	0x86B86ED5, 0xC4488F3E, | ||||
| 	0x0359AD02, 0x41A94CE9, | ||||
| 	0xCF8B0890, 0x8D7BE97B, | ||||
| 	0x4A6ACB47, 0x089A2AAC, | ||||
| 	0x14DEA25F, 0x562E43B4, | ||||
| 	0x913F6188, 0xD3CF8063, | ||||
| 	0x5DEDC41A, 0x1F1D25F1, | ||||
| 	0xD80C07CD, 0x9AFCE626 | ||||
| }; | ||||
| #else							/* int64 works */ | ||||
|  | ||||
| const uint64 pg_crc64_table[256] = { | ||||
| 	UINT64CONST(0x0000000000000000), UINT64CONST(0x42F0E1EBA9EA3693), | ||||
| 	UINT64CONST(0x85E1C3D753D46D26), UINT64CONST(0xC711223CFA3E5BB5), | ||||
| 	UINT64CONST(0x493366450E42ECDF), UINT64CONST(0x0BC387AEA7A8DA4C), | ||||
| 	UINT64CONST(0xCCD2A5925D9681F9), UINT64CONST(0x8E224479F47CB76A), | ||||
| 	UINT64CONST(0x9266CC8A1C85D9BE), UINT64CONST(0xD0962D61B56FEF2D), | ||||
| 	UINT64CONST(0x17870F5D4F51B498), UINT64CONST(0x5577EEB6E6BB820B), | ||||
| 	UINT64CONST(0xDB55AACF12C73561), UINT64CONST(0x99A54B24BB2D03F2), | ||||
| 	UINT64CONST(0x5EB4691841135847), UINT64CONST(0x1C4488F3E8F96ED4), | ||||
| 	UINT64CONST(0x663D78FF90E185EF), UINT64CONST(0x24CD9914390BB37C), | ||||
| 	UINT64CONST(0xE3DCBB28C335E8C9), UINT64CONST(0xA12C5AC36ADFDE5A), | ||||
| 	UINT64CONST(0x2F0E1EBA9EA36930), UINT64CONST(0x6DFEFF5137495FA3), | ||||
| 	UINT64CONST(0xAAEFDD6DCD770416), UINT64CONST(0xE81F3C86649D3285), | ||||
| 	UINT64CONST(0xF45BB4758C645C51), UINT64CONST(0xB6AB559E258E6AC2), | ||||
| 	UINT64CONST(0x71BA77A2DFB03177), UINT64CONST(0x334A9649765A07E4), | ||||
| 	UINT64CONST(0xBD68D2308226B08E), UINT64CONST(0xFF9833DB2BCC861D), | ||||
| 	UINT64CONST(0x388911E7D1F2DDA8), UINT64CONST(0x7A79F00C7818EB3B), | ||||
| 	UINT64CONST(0xCC7AF1FF21C30BDE), UINT64CONST(0x8E8A101488293D4D), | ||||
| 	UINT64CONST(0x499B3228721766F8), UINT64CONST(0x0B6BD3C3DBFD506B), | ||||
| 	UINT64CONST(0x854997BA2F81E701), UINT64CONST(0xC7B97651866BD192), | ||||
| 	UINT64CONST(0x00A8546D7C558A27), UINT64CONST(0x4258B586D5BFBCB4), | ||||
| 	UINT64CONST(0x5E1C3D753D46D260), UINT64CONST(0x1CECDC9E94ACE4F3), | ||||
| 	UINT64CONST(0xDBFDFEA26E92BF46), UINT64CONST(0x990D1F49C77889D5), | ||||
| 	UINT64CONST(0x172F5B3033043EBF), UINT64CONST(0x55DFBADB9AEE082C), | ||||
| 	UINT64CONST(0x92CE98E760D05399), UINT64CONST(0xD03E790CC93A650A), | ||||
| 	UINT64CONST(0xAA478900B1228E31), UINT64CONST(0xE8B768EB18C8B8A2), | ||||
| 	UINT64CONST(0x2FA64AD7E2F6E317), UINT64CONST(0x6D56AB3C4B1CD584), | ||||
| 	UINT64CONST(0xE374EF45BF6062EE), UINT64CONST(0xA1840EAE168A547D), | ||||
| 	UINT64CONST(0x66952C92ECB40FC8), UINT64CONST(0x2465CD79455E395B), | ||||
| 	UINT64CONST(0x3821458AADA7578F), UINT64CONST(0x7AD1A461044D611C), | ||||
| 	UINT64CONST(0xBDC0865DFE733AA9), UINT64CONST(0xFF3067B657990C3A), | ||||
| 	UINT64CONST(0x711223CFA3E5BB50), UINT64CONST(0x33E2C2240A0F8DC3), | ||||
| 	UINT64CONST(0xF4F3E018F031D676), UINT64CONST(0xB60301F359DBE0E5), | ||||
| 	UINT64CONST(0xDA050215EA6C212F), UINT64CONST(0x98F5E3FE438617BC), | ||||
| 	UINT64CONST(0x5FE4C1C2B9B84C09), UINT64CONST(0x1D14202910527A9A), | ||||
| 	UINT64CONST(0x93366450E42ECDF0), UINT64CONST(0xD1C685BB4DC4FB63), | ||||
| 	UINT64CONST(0x16D7A787B7FAA0D6), UINT64CONST(0x5427466C1E109645), | ||||
| 	UINT64CONST(0x4863CE9FF6E9F891), UINT64CONST(0x0A932F745F03CE02), | ||||
| 	UINT64CONST(0xCD820D48A53D95B7), UINT64CONST(0x8F72ECA30CD7A324), | ||||
| 	UINT64CONST(0x0150A8DAF8AB144E), UINT64CONST(0x43A04931514122DD), | ||||
| 	UINT64CONST(0x84B16B0DAB7F7968), UINT64CONST(0xC6418AE602954FFB), | ||||
| 	UINT64CONST(0xBC387AEA7A8DA4C0), UINT64CONST(0xFEC89B01D3679253), | ||||
| 	UINT64CONST(0x39D9B93D2959C9E6), UINT64CONST(0x7B2958D680B3FF75), | ||||
| 	UINT64CONST(0xF50B1CAF74CF481F), UINT64CONST(0xB7FBFD44DD257E8C), | ||||
| 	UINT64CONST(0x70EADF78271B2539), UINT64CONST(0x321A3E938EF113AA), | ||||
| 	UINT64CONST(0x2E5EB66066087D7E), UINT64CONST(0x6CAE578BCFE24BED), | ||||
| 	UINT64CONST(0xABBF75B735DC1058), UINT64CONST(0xE94F945C9C3626CB), | ||||
| 	UINT64CONST(0x676DD025684A91A1), UINT64CONST(0x259D31CEC1A0A732), | ||||
| 	UINT64CONST(0xE28C13F23B9EFC87), UINT64CONST(0xA07CF2199274CA14), | ||||
| 	UINT64CONST(0x167FF3EACBAF2AF1), UINT64CONST(0x548F120162451C62), | ||||
| 	UINT64CONST(0x939E303D987B47D7), UINT64CONST(0xD16ED1D631917144), | ||||
| 	UINT64CONST(0x5F4C95AFC5EDC62E), UINT64CONST(0x1DBC74446C07F0BD), | ||||
| 	UINT64CONST(0xDAAD56789639AB08), UINT64CONST(0x985DB7933FD39D9B), | ||||
| 	UINT64CONST(0x84193F60D72AF34F), UINT64CONST(0xC6E9DE8B7EC0C5DC), | ||||
| 	UINT64CONST(0x01F8FCB784FE9E69), UINT64CONST(0x43081D5C2D14A8FA), | ||||
| 	UINT64CONST(0xCD2A5925D9681F90), UINT64CONST(0x8FDAB8CE70822903), | ||||
| 	UINT64CONST(0x48CB9AF28ABC72B6), UINT64CONST(0x0A3B7B1923564425), | ||||
| 	UINT64CONST(0x70428B155B4EAF1E), UINT64CONST(0x32B26AFEF2A4998D), | ||||
| 	UINT64CONST(0xF5A348C2089AC238), UINT64CONST(0xB753A929A170F4AB), | ||||
| 	UINT64CONST(0x3971ED50550C43C1), UINT64CONST(0x7B810CBBFCE67552), | ||||
| 	UINT64CONST(0xBC902E8706D82EE7), UINT64CONST(0xFE60CF6CAF321874), | ||||
| 	UINT64CONST(0xE224479F47CB76A0), UINT64CONST(0xA0D4A674EE214033), | ||||
| 	UINT64CONST(0x67C58448141F1B86), UINT64CONST(0x253565A3BDF52D15), | ||||
| 	UINT64CONST(0xAB1721DA49899A7F), UINT64CONST(0xE9E7C031E063ACEC), | ||||
| 	UINT64CONST(0x2EF6E20D1A5DF759), UINT64CONST(0x6C0603E6B3B7C1CA), | ||||
| 	UINT64CONST(0xF6FAE5C07D3274CD), UINT64CONST(0xB40A042BD4D8425E), | ||||
| 	UINT64CONST(0x731B26172EE619EB), UINT64CONST(0x31EBC7FC870C2F78), | ||||
| 	UINT64CONST(0xBFC9838573709812), UINT64CONST(0xFD39626EDA9AAE81), | ||||
| 	UINT64CONST(0x3A28405220A4F534), UINT64CONST(0x78D8A1B9894EC3A7), | ||||
| 	UINT64CONST(0x649C294A61B7AD73), UINT64CONST(0x266CC8A1C85D9BE0), | ||||
| 	UINT64CONST(0xE17DEA9D3263C055), UINT64CONST(0xA38D0B769B89F6C6), | ||||
| 	UINT64CONST(0x2DAF4F0F6FF541AC), UINT64CONST(0x6F5FAEE4C61F773F), | ||||
| 	UINT64CONST(0xA84E8CD83C212C8A), UINT64CONST(0xEABE6D3395CB1A19), | ||||
| 	UINT64CONST(0x90C79D3FEDD3F122), UINT64CONST(0xD2377CD44439C7B1), | ||||
| 	UINT64CONST(0x15265EE8BE079C04), UINT64CONST(0x57D6BF0317EDAA97), | ||||
| 	UINT64CONST(0xD9F4FB7AE3911DFD), UINT64CONST(0x9B041A914A7B2B6E), | ||||
| 	UINT64CONST(0x5C1538ADB04570DB), UINT64CONST(0x1EE5D94619AF4648), | ||||
| 	UINT64CONST(0x02A151B5F156289C), UINT64CONST(0x4051B05E58BC1E0F), | ||||
| 	UINT64CONST(0x87409262A28245BA), UINT64CONST(0xC5B073890B687329), | ||||
| 	UINT64CONST(0x4B9237F0FF14C443), UINT64CONST(0x0962D61B56FEF2D0), | ||||
| 	UINT64CONST(0xCE73F427ACC0A965), UINT64CONST(0x8C8315CC052A9FF6), | ||||
| 	UINT64CONST(0x3A80143F5CF17F13), UINT64CONST(0x7870F5D4F51B4980), | ||||
| 	UINT64CONST(0xBF61D7E80F251235), UINT64CONST(0xFD913603A6CF24A6), | ||||
| 	UINT64CONST(0x73B3727A52B393CC), UINT64CONST(0x31439391FB59A55F), | ||||
| 	UINT64CONST(0xF652B1AD0167FEEA), UINT64CONST(0xB4A25046A88DC879), | ||||
| 	UINT64CONST(0xA8E6D8B54074A6AD), UINT64CONST(0xEA16395EE99E903E), | ||||
| 	UINT64CONST(0x2D071B6213A0CB8B), UINT64CONST(0x6FF7FA89BA4AFD18), | ||||
| 	UINT64CONST(0xE1D5BEF04E364A72), UINT64CONST(0xA3255F1BE7DC7CE1), | ||||
| 	UINT64CONST(0x64347D271DE22754), UINT64CONST(0x26C49CCCB40811C7), | ||||
| 	UINT64CONST(0x5CBD6CC0CC10FAFC), UINT64CONST(0x1E4D8D2B65FACC6F), | ||||
| 	UINT64CONST(0xD95CAF179FC497DA), UINT64CONST(0x9BAC4EFC362EA149), | ||||
| 	UINT64CONST(0x158E0A85C2521623), UINT64CONST(0x577EEB6E6BB820B0), | ||||
| 	UINT64CONST(0x906FC95291867B05), UINT64CONST(0xD29F28B9386C4D96), | ||||
| 	UINT64CONST(0xCEDBA04AD0952342), UINT64CONST(0x8C2B41A1797F15D1), | ||||
| 	UINT64CONST(0x4B3A639D83414E64), UINT64CONST(0x09CA82762AAB78F7), | ||||
| 	UINT64CONST(0x87E8C60FDED7CF9D), UINT64CONST(0xC51827E4773DF90E), | ||||
| 	UINT64CONST(0x020905D88D03A2BB), UINT64CONST(0x40F9E43324E99428), | ||||
| 	UINT64CONST(0x2CFFE7D5975E55E2), UINT64CONST(0x6E0F063E3EB46371), | ||||
| 	UINT64CONST(0xA91E2402C48A38C4), UINT64CONST(0xEBEEC5E96D600E57), | ||||
| 	UINT64CONST(0x65CC8190991CB93D), UINT64CONST(0x273C607B30F68FAE), | ||||
| 	UINT64CONST(0xE02D4247CAC8D41B), UINT64CONST(0xA2DDA3AC6322E288), | ||||
| 	UINT64CONST(0xBE992B5F8BDB8C5C), UINT64CONST(0xFC69CAB42231BACF), | ||||
| 	UINT64CONST(0x3B78E888D80FE17A), UINT64CONST(0x7988096371E5D7E9), | ||||
| 	UINT64CONST(0xF7AA4D1A85996083), UINT64CONST(0xB55AACF12C735610), | ||||
| 	UINT64CONST(0x724B8ECDD64D0DA5), UINT64CONST(0x30BB6F267FA73B36), | ||||
| 	UINT64CONST(0x4AC29F2A07BFD00D), UINT64CONST(0x08327EC1AE55E69E), | ||||
| 	UINT64CONST(0xCF235CFD546BBD2B), UINT64CONST(0x8DD3BD16FD818BB8), | ||||
| 	UINT64CONST(0x03F1F96F09FD3CD2), UINT64CONST(0x41011884A0170A41), | ||||
| 	UINT64CONST(0x86103AB85A2951F4), UINT64CONST(0xC4E0DB53F3C36767), | ||||
| 	UINT64CONST(0xD8A453A01B3A09B3), UINT64CONST(0x9A54B24BB2D03F20), | ||||
| 	UINT64CONST(0x5D45907748EE6495), UINT64CONST(0x1FB5719CE1045206), | ||||
| 	UINT64CONST(0x919735E51578E56C), UINT64CONST(0xD367D40EBC92D3FF), | ||||
| 	UINT64CONST(0x1476F63246AC884A), UINT64CONST(0x568617D9EF46BED9), | ||||
| 	UINT64CONST(0xE085162AB69D5E3C), UINT64CONST(0xA275F7C11F7768AF), | ||||
| 	UINT64CONST(0x6564D5FDE549331A), UINT64CONST(0x279434164CA30589), | ||||
| 	UINT64CONST(0xA9B6706FB8DFB2E3), UINT64CONST(0xEB46918411358470), | ||||
| 	UINT64CONST(0x2C57B3B8EB0BDFC5), UINT64CONST(0x6EA7525342E1E956), | ||||
| 	UINT64CONST(0x72E3DAA0AA188782), UINT64CONST(0x30133B4B03F2B111), | ||||
| 	UINT64CONST(0xF7021977F9CCEAA4), UINT64CONST(0xB5F2F89C5026DC37), | ||||
| 	UINT64CONST(0x3BD0BCE5A45A6B5D), UINT64CONST(0x79205D0E0DB05DCE), | ||||
| 	UINT64CONST(0xBE317F32F78E067B), UINT64CONST(0xFCC19ED95E6430E8), | ||||
| 	UINT64CONST(0x86B86ED5267CDBD3), UINT64CONST(0xC4488F3E8F96ED40), | ||||
| 	UINT64CONST(0x0359AD0275A8B6F5), UINT64CONST(0x41A94CE9DC428066), | ||||
| 	UINT64CONST(0xCF8B0890283E370C), UINT64CONST(0x8D7BE97B81D4019F), | ||||
| 	UINT64CONST(0x4A6ACB477BEA5A2A), UINT64CONST(0x089A2AACD2006CB9), | ||||
| 	UINT64CONST(0x14DEA25F3AF9026D), UINT64CONST(0x562E43B4931334FE), | ||||
| 	UINT64CONST(0x913F6188692D6F4B), UINT64CONST(0xD3CF8063C0C759D8), | ||||
| 	UINT64CONST(0x5DEDC41A34BBEEB2), UINT64CONST(0x1F1D25F19D51D821), | ||||
| 	UINT64CONST(0xD80C07CD676F8394), UINT64CONST(0x9AFCE626CE85B507) | ||||
| }; | ||||
| #endif   /* INT64_IS_BUSTED */ | ||||
|  | ||||
| #endif   /* PROVIDE_64BIT_CRC */ | ||||
							
								
								
									
										105
									
								
								pgsql_src/pg_ctl.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								pgsql_src/pg_ctl.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * pg_ctl --- start/stops/restarts the PostgreSQL server | ||||
|  * | ||||
|  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group | ||||
|  * | ||||
|  * $PostgreSQL: pgsql/src/bin/pg_ctl/pg_ctl.c,v 1.111 2009/06/11 14:49:07 momjian Exp $ | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
|  | ||||
| #include "postgres_fe.h" | ||||
|  | ||||
| #include <signal.h> | ||||
| #include <sys/types.h> | ||||
| #include <unistd.h> | ||||
|  | ||||
| #include "pg_rman.h" | ||||
|  | ||||
| /* PID can be negative for standalone backend */ | ||||
| typedef long pgpid_t; | ||||
|  | ||||
| static pgpid_t get_pgpid(void); | ||||
| static bool postmaster_is_alive(pid_t pid); | ||||
|  | ||||
| static char pid_file[MAXPGPATH]; | ||||
|  | ||||
|  | ||||
| static pgpid_t | ||||
| get_pgpid(void) | ||||
| { | ||||
| 	FILE	   *pidf; | ||||
| 	long		pid; | ||||
|  | ||||
| 	snprintf(pid_file, lengthof(pid_file), "%s/postmaster.pid", pgdata); | ||||
| 	pidf = fopen(pid_file, "r"); | ||||
| 	if (pidf == NULL) | ||||
| 	{ | ||||
| 		/* No pid file, not an error on startup */ | ||||
| 		if (errno == ENOENT) | ||||
| 			return 0; | ||||
| 		else | ||||
| 			elog(ERROR_SYSTEM, _("could not open PID file \"%s\": %s\n"), | ||||
| 						 pid_file, strerror(errno)); | ||||
| 	} | ||||
| 	if (fscanf(pidf, "%ld", &pid) != 1) | ||||
| 		elog(ERROR_PID_BROKEN, _("invalid data in PID file \"%s\"\n"), pid_file); | ||||
| 	fclose(pidf); | ||||
| 	return (pgpid_t) pid; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  *	utility routines | ||||
|  */ | ||||
|  | ||||
| static bool | ||||
| postmaster_is_alive(pid_t pid) | ||||
| { | ||||
| 	/* | ||||
| 	 * Test to see if the process is still there.  Note that we do not | ||||
| 	 * consider an EPERM failure to mean that the process is still there; | ||||
| 	 * EPERM must mean that the given PID belongs to some other userid, and | ||||
| 	 * considering the permissions on $PGDATA, that means it's not the | ||||
| 	 * postmaster we are after. | ||||
| 	 * | ||||
| 	 * Don't believe that our own PID or parent shell's PID is the postmaster, | ||||
| 	 * either.	(Windows hasn't got getppid(), though.) | ||||
| 	 */ | ||||
| 	if (pid == getpid()) | ||||
| 		return false; | ||||
| #ifndef WIN32 | ||||
| 	if (pid == getppid()) | ||||
| 		return false; | ||||
| #endif | ||||
| 	if (kill(pid, 0) == 0) | ||||
| 		return true; | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * original is do_status() in src/bin/pg_ctl/pg_ctl.c | ||||
|  * changes are: | ||||
|  *   renamed from do_status() from do_status(). | ||||
|  *   return true if PG server is running. | ||||
|  *   don't print any message. | ||||
|  *   don't print postopts file. | ||||
|  *   log with elog() in pgut library. | ||||
|  */ | ||||
| bool | ||||
| is_pg_running(void) | ||||
| { | ||||
| 	pgpid_t		pid; | ||||
|  | ||||
| 	pid = get_pgpid(); | ||||
| 	if (pid == 0)				/* 0 means no pid file */ | ||||
| 		return false; | ||||
|  | ||||
| 	if (pid < 0)			/* standalone backend */ | ||||
| 		pid = -pid; | ||||
|  | ||||
|  | ||||
| 	return postmaster_is_alive((pid_t) pid); | ||||
| } | ||||
|  | ||||
							
								
								
									
										240
									
								
								pgut/pgut-port.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								pgut/pgut-port.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,240 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * pgut-port.c | ||||
|  * | ||||
|  * Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #include "c.h" | ||||
| #include "pgut-port.h" | ||||
|  | ||||
| #ifdef WIN32 | ||||
|  | ||||
| #include <winioctl.h> | ||||
|  | ||||
| int | ||||
| uname(struct utsname *buf) | ||||
| { | ||||
| 	OSVERSIONINFO	os = { sizeof(OSVERSIONINFO) }; | ||||
| 	SYSTEM_INFO		sys = { { sizeof(SYSTEM_INFO) } }; | ||||
| 	DWORD			bufsize; | ||||
|  | ||||
| 	GetVersionEx(&os); | ||||
| 	GetSystemInfo(&sys); | ||||
|  | ||||
| 	/* sysname */ | ||||
| 	strcpy(buf->sysname, "Windows"); | ||||
|  | ||||
| 	/* nodename */ | ||||
| 	bufsize = lengthof(buf->nodename); | ||||
| 	GetComputerName(buf->nodename, &bufsize); | ||||
|  | ||||
| 	/* release: major.minor */ | ||||
| 	snprintf(buf->release, lengthof(buf->release), "%ld.%ld", | ||||
| 		os.dwMajorVersion, os.dwMinorVersion); | ||||
|  | ||||
| 	/* version */ | ||||
| 	strcpy(buf->sysname, os.szCSDVersion); | ||||
|  | ||||
| 	/* machine */ | ||||
| 	switch (sys.wProcessorArchitecture) | ||||
| 	{ | ||||
| 	case PROCESSOR_ARCHITECTURE_INTEL: | ||||
| 		strcpy(buf->machine, "x86"); | ||||
| 		break; | ||||
| 	case PROCESSOR_ARCHITECTURE_IA64: | ||||
| 		strcpy(buf->machine, "IA64"); | ||||
| 		break; | ||||
| 	case PROCESSOR_ARCHITECTURE_AMD64: | ||||
| 		strcpy(buf->machine, "x86_64"); | ||||
| 		break; | ||||
| 	case PROCESSOR_ARCHITECTURE_IA32_ON_WIN64: | ||||
| 		strcpy(buf->machine, "x86_on_win64"); | ||||
| 		break; | ||||
| 	default: | ||||
| 		strcpy(buf->machine, "unknown"); | ||||
| 		break; | ||||
| 	} | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| #define NTFS_BLOCK_SIZE		512 | ||||
|  | ||||
| int | ||||
| statfs(const char *path, struct statfs *buf) | ||||
| { | ||||
| 	ULARGE_INTEGER	availBytes; | ||||
| 	ULARGE_INTEGER	totalBytes; | ||||
| 	ULARGE_INTEGER	freeBytes; | ||||
|  | ||||
| 	if (!GetDiskFreeSpaceEx(path, &availBytes, &totalBytes, &freeBytes)) | ||||
| 	{ | ||||
| 		_dosmaperr(GetLastError()); | ||||
| 		return -1; | ||||
| 	} | ||||
|  | ||||
| 	memset(buf, 0, sizeof(struct statfs)); | ||||
| 	buf->f_type = NTFS_SB_MAGIC; | ||||
| 	buf->f_bsize = NTFS_BLOCK_SIZE; | ||||
| 	buf->f_blocks = (long) (totalBytes.QuadPart / NTFS_BLOCK_SIZE); | ||||
| 	buf->f_bfree = (long) (freeBytes.QuadPart / NTFS_BLOCK_SIZE); | ||||
| 	buf->f_bavail = (long) (availBytes.QuadPart / NTFS_BLOCK_SIZE); | ||||
| 	buf->f_namelen = MAX_PATH; | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| #define REPARSE_DATA_SIZE		1024 | ||||
|  | ||||
| /* same layout as REPARSE_DATA_BUFFER, which is defined only in old winnt.h */ | ||||
| typedef struct REPARSE_DATA | ||||
| { | ||||
| 	ULONG	ReparseTag; | ||||
| 	WORD	ReparseDataLength; | ||||
| 	WORD	Reserved; | ||||
| 	union | ||||
| 	{ | ||||
| 		struct | ||||
| 		{ | ||||
| 			WORD	SubstituteNameOffset; | ||||
| 			WORD	SubstituteNameLength; | ||||
| 			WORD	PrintNameOffset; | ||||
| 			WORD	PrintNameLength; | ||||
| 			ULONG	Flags; | ||||
| 			WCHAR	PathBuffer[1]; | ||||
| 		} Symlink; | ||||
| 		struct | ||||
| 		{ | ||||
| 			WORD	SubstituteNameOffset; | ||||
| 			WORD	SubstituteNameLength; | ||||
| 			WORD	PrintNameOffset; | ||||
| 			WORD	PrintNameLength; | ||||
| 			WCHAR	PathBuffer[1]; | ||||
| 		} Mount; | ||||
| 		struct | ||||
| 		{ | ||||
| 			BYTE  DataBuffer[REPARSE_DATA_SIZE]; | ||||
| 		} Generic; | ||||
| 	}; | ||||
| } REPARSE_DATA; | ||||
|  | ||||
| ssize_t | ||||
| readlink(const char *path, char *target, size_t size) | ||||
| { | ||||
|     HANDLE			handle;  | ||||
|  	DWORD			attr; | ||||
| 	REPARSE_DATA	data; | ||||
|  	DWORD			datasize; | ||||
| 	PCWSTR			wpath; | ||||
| 	int				wlen; | ||||
| 	int				r; | ||||
|  | ||||
| 	attr = GetFileAttributes(path); | ||||
| 	if (attr == INVALID_FILE_ATTRIBUTES) | ||||
| 	{ | ||||
| 		_dosmaperr(GetLastError()); | ||||
|         return -1;  | ||||
|     }  | ||||
| 	if ((attr & FILE_ATTRIBUTE_REPARSE_POINT) == 0) | ||||
| 	{ | ||||
| 		errno = EINVAL;	/* not a symlink */ | ||||
|         return -1;  | ||||
| 	} | ||||
|  | ||||
|     handle = CreateFileA(path, 0, | ||||
| 		FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, NULL, | ||||
| 		OPEN_EXISTING, | ||||
|         FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL); | ||||
| 	if (handle == INVALID_HANDLE_VALUE) | ||||
| 	{ | ||||
| 		_dosmaperr(GetLastError()); | ||||
|         return -1;  | ||||
|     } | ||||
|   | ||||
| 	wpath = NULL; | ||||
| 	if (DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0, | ||||
|         &data, sizeof(data), &datasize, NULL)) | ||||
| 	{ | ||||
| 		switch (data.ReparseTag) | ||||
| 		{ | ||||
| 			case IO_REPARSE_TAG_MOUNT_POINT: | ||||
| 			{ | ||||
| 				wpath = data.Mount.PathBuffer + data.Mount.SubstituteNameOffset; | ||||
| 				wlen = data.Mount.SubstituteNameLength; | ||||
| 				break; | ||||
| 			} | ||||
| 			case IO_REPARSE_TAG_SYMLINK: | ||||
| 			{ | ||||
| 				wpath = data.Symlink.PathBuffer + data.Symlink.SubstituteNameOffset; | ||||
| 				wlen = data.Symlink.SubstituteNameLength; | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (wpath == NULL) | ||||
| 		r = -1; | ||||
| 	else | ||||
| 	{ | ||||
| 		if (wcsncmp(wpath, L"\\??\\", 4) == 0 || | ||||
| 			wcsncmp(wpath, L"\\\\?\\", 4) == 0) | ||||
| 		{ | ||||
| 			wpath += 4; | ||||
| 			wlen -= 4; | ||||
| 		} | ||||
| 		r = WideCharToMultiByte(CP_ACP, 0, wpath, wlen, target, size, NULL, NULL); | ||||
| 	} | ||||
|  | ||||
| 	CloseHandle(handle); | ||||
| 	return r; | ||||
| } | ||||
|  | ||||
| char * | ||||
| blkid_devno_to_devname(dev_t devno) | ||||
| { | ||||
| 	static char	devname[4]; | ||||
| 	char		letter = 'A' + devno; | ||||
| 	if ('A' <= letter && letter <= 'Z') | ||||
| 	{ | ||||
| 		snprintf(devname, lengthof(devname), "%c:\\", 'A' + devno); | ||||
| 		return devname; | ||||
| 	} | ||||
| 	else | ||||
| 		return NULL; | ||||
| } | ||||
|  | ||||
| int | ||||
| flock(int fd, int operation) | ||||
| { | ||||
| 	BOOL	ret; | ||||
| 	HANDLE	handle = (HANDLE) _get_osfhandle(fd); | ||||
| 	DWORD	lo = 0; | ||||
| 	DWORD	hi = 1; | ||||
|  | ||||
| 	if (operation & LOCK_UN) | ||||
| 	{ | ||||
| 		ret = UnlockFileEx(handle, 0, lo, hi, NULL); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		DWORD	flags = 0; | ||||
| 		if (operation & LOCK_EX) | ||||
| 			flags |= LOCKFILE_EXCLUSIVE_LOCK; | ||||
| 		if (operation & LOCK_NB) | ||||
| 			flags |= LOCKFILE_FAIL_IMMEDIATELY; | ||||
| 		ret = LockFileEx(handle, flags, 0, lo, hi, NULL); | ||||
| 	} | ||||
|  | ||||
| 	if (!ret) | ||||
| 	{ | ||||
| 		_dosmaperr(GetLastError()); | ||||
| 		return -1; | ||||
| 	} | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										71
									
								
								pgut/pgut-port.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								pgut/pgut-port.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * pgut-port.h | ||||
|  * | ||||
|  * Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #ifndef PGUT_PORT_H | ||||
| #define PGUT_PORT_H | ||||
|  | ||||
| #include <sys/stat.h> | ||||
|  | ||||
| #ifndef WIN32 | ||||
|  | ||||
| #include <blkid/blkid.h> | ||||
| #include <sys/utsname.h> | ||||
| #include <sys/statfs.h> | ||||
| #include <unistd.h> | ||||
|  | ||||
| #else | ||||
|  | ||||
| #include <time.h> | ||||
|  | ||||
| struct utsname | ||||
| { | ||||
| 	char	sysname[32]; | ||||
| 	char	nodename[256]; | ||||
| 	char	release[128]; | ||||
| 	char	version[128]; | ||||
| 	char	machine[32]; | ||||
| }; | ||||
|  | ||||
| #define NTFS_SB_MAGIC	0x5346544e | ||||
|  | ||||
| typedef struct { int val[2]; } fsid_t; | ||||
|  | ||||
| struct statfs | ||||
| { | ||||
| 	long	f_type; | ||||
| 	long	f_bsize; | ||||
| 	long	f_blocks; | ||||
| 	long	f_bfree; | ||||
| 	long	f_bavail; | ||||
| 	long	f_files; | ||||
| 	long	f_ffree; | ||||
| 	fsid_t	f_fsid; | ||||
| 	long	f_namelen; | ||||
| }; | ||||
|  | ||||
| extern int uname(struct utsname *buf); | ||||
| extern int statfs(const char *path, struct statfs *buf); | ||||
| extern ssize_t readlink(const char *path, char *target, size_t size); | ||||
| extern char *blkid_devno_to_devname(dev_t devno); | ||||
|  | ||||
| #define LOCK_SH		1	/* Shared lock.  */ | ||||
| #define LOCK_EX		2	/* Exclusive lock.  */ | ||||
| #define LOCK_UN		8	/* Unlock.  */ | ||||
| #define LOCK_NB		4	/* Don't block when locking.  */ | ||||
|  | ||||
| extern int flock(int fd, int operation); | ||||
|  | ||||
| #define S_IFLNK			(0) | ||||
| #define S_IRWXG			(0) | ||||
| #define S_IRWXO			(0) | ||||
| #define S_ISLNK(mode)	(0) | ||||
|  | ||||
| #endif | ||||
|  | ||||
| #endif   /* PGUT_PORT_H */ | ||||
							
								
								
									
										1686
									
								
								pgut/pgut.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1686
									
								
								pgut/pgut.c
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										245
									
								
								pgut/pgut.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										245
									
								
								pgut/pgut.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,245 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * pgut.h | ||||
|  * | ||||
|  * Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #ifndef PGUT_H | ||||
| #define PGUT_H | ||||
|  | ||||
| #include "libpq-fe.h" | ||||
| #include "pqexpbuffer.h" | ||||
|  | ||||
| #include <assert.h> | ||||
| #include <sys/time.h> | ||||
|  | ||||
| #if !defined(C_H) && !defined(__cplusplus) | ||||
| #ifndef bool | ||||
| typedef char bool; | ||||
| #endif | ||||
| #ifndef true | ||||
| #define true	((bool) 1) | ||||
| #endif | ||||
| #ifndef false | ||||
| #define false	((bool) 0) | ||||
| #endif | ||||
| #endif | ||||
|  | ||||
| #define INFINITE_STR		"INFINITE" | ||||
|  | ||||
| typedef enum YesNo | ||||
| { | ||||
| 	DEFAULT, | ||||
| 	NO, | ||||
| 	YES | ||||
| } YesNo; | ||||
|  | ||||
| typedef enum pgut_optsrc | ||||
| { | ||||
| 	SOURCE_DEFAULT, | ||||
| 	SOURCE_ENV, | ||||
| 	SOURCE_FILE, | ||||
| 	SOURCE_CMDLINE, | ||||
| 	SOURCE_CONST | ||||
| } pgut_optsrc; | ||||
|  | ||||
| /* | ||||
|  * type: | ||||
|  *	b: bool (true) | ||||
|  *	B: bool (false) | ||||
|  *  f: pgut_optfn | ||||
|  *	i: 32bit singed integer | ||||
|  *	u: 32bit unsigned integer | ||||
|  *	I: 64bit singed integer | ||||
|  *	U: 64bit unsigned integer | ||||
|  *	s: string | ||||
|  *  t: time_t | ||||
|  *	y: YesNo (YES) | ||||
|  *	Y: YesNo (NO) | ||||
|  */ | ||||
| typedef struct pgut_option | ||||
| { | ||||
| 	char		type; | ||||
| 	char		sname;		/* short name */ | ||||
| 	const char *lname;		/* long name */ | ||||
| 	void	   *var;		/* pointer to variable */ | ||||
| 	pgut_optsrc	allowed;	/* allowed source */ | ||||
| 	pgut_optsrc	source;		/* actual source */ | ||||
| } pgut_option; | ||||
|  | ||||
| typedef void (*pgut_optfn) (pgut_option *opt, const char *arg); | ||||
| typedef void (*pgut_atexit_callback)(bool fatal, void *userdata); | ||||
|  | ||||
| /* | ||||
|  * pgut client variables and functions | ||||
|  */ | ||||
| extern const char  *PROGRAM_NAME; | ||||
| extern const char  *PROGRAM_VERSION; | ||||
| extern const char  *PROGRAM_URL; | ||||
| extern const char  *PROGRAM_EMAIL; | ||||
|  | ||||
| extern void	pgut_help(bool details); | ||||
|  | ||||
| /* | ||||
|  * pgut framework variables and functions | ||||
|  */ | ||||
| extern const char  *dbname; | ||||
| extern const char  *host; | ||||
| extern const char  *port; | ||||
| extern const char  *username; | ||||
| extern char		   *password; | ||||
| extern bool			debug; | ||||
| extern bool			quiet; | ||||
|  | ||||
| #ifndef PGUT_NO_PROMPT | ||||
| extern YesNo	prompt_password; | ||||
| #endif | ||||
|  | ||||
| extern PGconn	   *connection; | ||||
| extern bool			interrupted; | ||||
|  | ||||
| extern void help(bool details); | ||||
| extern int pgut_getopt(int argc, char **argv, pgut_option options[]); | ||||
| extern void pgut_readopt(const char *path, pgut_option options[], int elevel); | ||||
| extern void pgut_atexit_push(pgut_atexit_callback callback, void *userdata); | ||||
| extern void pgut_atexit_pop(pgut_atexit_callback callback, void *userdata); | ||||
|  | ||||
| /* | ||||
|  * Database connections | ||||
|  */ | ||||
| extern PGconn *pgut_connect(int elevel); | ||||
| extern void pgut_disconnect(PGconn *conn); | ||||
| extern PGresult *pgut_execute(PGconn* conn, const char *query, int nParams, const char **params, int elevel); | ||||
| extern void pgut_command(PGconn* conn, const char *query, int nParams, const char **params, int elevel); | ||||
| extern bool pgut_send(PGconn* conn, const char *query, int nParams, const char **params, int elevel); | ||||
| extern int pgut_wait(int num, PGconn *connections[], struct timeval *timeout); | ||||
|  | ||||
| extern PGconn *reconnect_elevel(int elevel); | ||||
| extern void reconnect(void); | ||||
| extern void disconnect(void); | ||||
| extern PGresult *execute_elevel(const char *query, int nParams, const char **params, int elevel); | ||||
| extern PGresult *execute(const char *query, int nParams, const char **params); | ||||
| extern void command(const char *query, int nParams, const char **params); | ||||
|  | ||||
| /* | ||||
|  * memory allocators | ||||
|  */ | ||||
| extern void *pgut_malloc(size_t size); | ||||
| extern void *pgut_realloc(void *p, size_t size); | ||||
| extern char *pgut_strdup(const char *str); | ||||
| extern char *strdup_with_len(const char *str, size_t len); | ||||
| extern char *strdup_trim(const char *str); | ||||
|  | ||||
| /* | ||||
|  * elog | ||||
|  */ | ||||
| #define LOG			(-4) | ||||
| #define INFO		(-3) | ||||
| #define NOTICE		(-2) | ||||
| #define WARNING		(-1) | ||||
| #define HELP		1 | ||||
| #define ERROR		2 | ||||
| #define FATAL		3 | ||||
| #define PANIC		4 | ||||
|  | ||||
| #define ERROR_SYSTEM			10	/* I/O or system error */ | ||||
| #define ERROR_NOMEM				11	/* memory exhausted */ | ||||
| #define ERROR_ARGS				12	/* some configurations are invalid */ | ||||
| #define ERROR_INTERRUPTED		13	/* interrupted by signal */ | ||||
| #define ERROR_PG_COMMAND		14	/* PostgreSQL query or command error */ | ||||
| #define ERROR_PG_CONNECT		15	/* PostgreSQL connection error */ | ||||
|  | ||||
| #undef elog | ||||
| extern void | ||||
| elog(int elevel, const char *fmt, ...) | ||||
| __attribute__((format(printf, 2, 3))); | ||||
|  | ||||
| /* | ||||
|  * Assert | ||||
|  */ | ||||
| #undef Assert | ||||
| #undef AssertArg | ||||
| #undef AssertMacro | ||||
|  | ||||
| #ifdef USE_ASSERT_CHECKING | ||||
| #define Assert(x)		assert(x) | ||||
| #define AssertArg(x)	assert(x) | ||||
| #define AssertMacro(x)	assert(x) | ||||
| #else | ||||
| #define Assert(x)		((void) 0) | ||||
| #define AssertArg(x)	((void) 0) | ||||
| #define AssertMacro(x)	((void) 0) | ||||
| #endif | ||||
|  | ||||
| /* | ||||
|  * StringInfo and string operations | ||||
|  */ | ||||
| #define STRINGINFO_H | ||||
|  | ||||
| #define StringInfoData			PQExpBufferData | ||||
| #define StringInfo				PQExpBuffer | ||||
| #define makeStringInfo			createPQExpBuffer | ||||
| #define initStringInfo			initPQExpBuffer | ||||
| #define freeStringInfo			destroyPQExpBuffer | ||||
| #define termStringInfo			termPQExpBuffer | ||||
| #define resetStringInfo			resetPQExpBuffer | ||||
| #define enlargeStringInfo		enlargePQExpBuffer | ||||
| #define printfStringInfo		printfPQExpBuffer	/* reset + append */ | ||||
| #define appendStringInfo		appendPQExpBuffer | ||||
| #define appendStringInfoString	appendPQExpBufferStr | ||||
| #define appendStringInfoChar	appendPQExpBufferChar | ||||
| #define appendBinaryStringInfo	appendBinaryPQExpBuffer | ||||
|  | ||||
| extern int appendStringInfoFile(StringInfo str, FILE *fp); | ||||
| extern int appendStringInfoFd(StringInfo str, int fd); | ||||
|  | ||||
| extern bool parse_bool(const char *value, bool *result); | ||||
| extern bool parse_bool_with_len(const char *value, size_t len, bool *result); | ||||
| extern bool parse_int32(const char *value, int32 *result); | ||||
| extern bool parse_uint32(const char *value, uint32 *result); | ||||
| extern bool parse_int64(const char *value, int64 *result); | ||||
| extern bool parse_uint64(const char *value, uint64 *result); | ||||
| extern bool parse_time(const char *value, time_t *time); | ||||
|  | ||||
| #define IsSpace(c)		(isspace((unsigned char)(c))) | ||||
| #define IsAlpha(c)		(isalpha((unsigned char)(c))) | ||||
| #define IsAlnum(c)		(isalnum((unsigned char)(c))) | ||||
| #define IsIdentHead(c)	(IsAlpha(c) || (c) == '_') | ||||
| #define IsIdentBody(c)	(IsAlnum(c) || (c) == '_') | ||||
| #define ToLower(c)		(tolower((unsigned char)(c))) | ||||
| #define ToUpper(c)		(toupper((unsigned char)(c))) | ||||
|  | ||||
| /* | ||||
|  * socket operations | ||||
|  */ | ||||
| extern int wait_for_socket(int sock, struct timeval *timeout); | ||||
| extern int wait_for_sockets(int nfds, fd_set *fds, struct timeval *timeout); | ||||
|  | ||||
| /* | ||||
|  * import from postgres.h and catalog/genbki.h in 8.4 | ||||
|  */ | ||||
| #if PG_VERSION_NUM < 80400 | ||||
|  | ||||
| typedef unsigned long Datum; | ||||
| typedef struct MemoryContextData *MemoryContext; | ||||
|  | ||||
| #define CATALOG(name,oid)	typedef struct CppConcat(FormData_,name) | ||||
| #define BKI_BOOTSTRAP | ||||
| #define BKI_SHARED_RELATION | ||||
| #define BKI_WITHOUT_OIDS | ||||
| #define DATA(x)   extern int no_such_variable | ||||
| #define DESCR(x)  extern int no_such_variable | ||||
| #define SHDESCR(x) extern int no_such_variable | ||||
| typedef int aclitem; | ||||
|  | ||||
| #endif | ||||
|  | ||||
| #ifdef WIN32 | ||||
| extern int sleep(unsigned int seconds); | ||||
| extern int usleep(unsigned int usec); | ||||
| #endif | ||||
|  | ||||
| #endif   /* PGUT_H */ | ||||
							
								
								
									
										978
									
								
								restore.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										978
									
								
								restore.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,978 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * restore.c: restore DB cluster and archived WAL. | ||||
|  * | ||||
|  * Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #include "pg_rman.h" | ||||
|  | ||||
| #include <fcntl.h> | ||||
| #include <sys/stat.h> | ||||
| #include <unistd.h> | ||||
|  | ||||
| #include "catalog/pg_control.h" | ||||
|  | ||||
| #if PG_VERSION_NUM < 80200 | ||||
| #define XLOG_BLCKSZ		BLCKSZ | ||||
| #endif | ||||
|  | ||||
| static void backup_online_files(bool re_recovery); | ||||
| static void restore_online_files(void); | ||||
| static void restore_database(pgBackup *backup); | ||||
| static void restore_archive_logs(pgBackup *backup); | ||||
| static void create_recovery_conf(const char *target_time, | ||||
| 								 const char *target_xid, | ||||
| 								 const char *target_inclusive, | ||||
| 								 TimeLineID target_tli); | ||||
| static parray * readTimeLineHistory(TimeLineID targetTLI); | ||||
| static bool satisfy_timeline(const parray *timelines, const pgBackup *backup); | ||||
| static TimeLineID get_current_timeline(void); | ||||
| static TimeLineID get_fullbackup_timeline(parray *backups); | ||||
| static void print_backup_id(const pgBackup *backup); | ||||
| static void search_next_wal(const char *path, uint32 *needId, uint32 *needSeg, parray *timelines); | ||||
|  | ||||
| int | ||||
| do_restore(const char *target_time, | ||||
| 		   const char *target_xid, | ||||
| 		   const char *target_inclusive, | ||||
| 		   TimeLineID target_tli) | ||||
| { | ||||
| 	int i; | ||||
| 	int base_index;				/* index of base (full) backup */ | ||||
| 	int last_restored_index;	/* index of last restored database backup */ | ||||
| 	int ret; | ||||
| 	TimeLineID	cur_tli; | ||||
| 	TimeLineID	backup_tli; | ||||
| 	parray *backups; | ||||
| 	pgBackup *base_backup = NULL; | ||||
| 	parray *files; | ||||
| 	parray *timelines; | ||||
| 	char timeline_dir[MAXPGPATH]; | ||||
| 	uint32 needId = 0; | ||||
| 	uint32 needSeg = 0; | ||||
|  | ||||
| 	/* PGDATA and ARCLOG_PATH are always required */ | ||||
| 	if (pgdata == NULL) | ||||
| 		elog(ERROR_ARGS, | ||||
| 			_("required parameter not specified: PGDATA (-D, --pgdata)")); | ||||
| 	if (arclog_path == NULL) | ||||
| 		elog(ERROR_ARGS, | ||||
| 			_("required parameter not specified: ARCLOG_PATH (-A, --arclog-path)")); | ||||
| 	if (srvlog_path == NULL) | ||||
| 		elog(ERROR_ARGS, | ||||
| 			_("required parameter not specified: SRVLOG_PATH (-S, --srvlog-path)")); | ||||
|  | ||||
| 	if (verbose) | ||||
| 	{ | ||||
| 		printf(_("========================================\n")); | ||||
| 		printf(_("restore start\n")); | ||||
| 	} | ||||
|  | ||||
| 	/* get exclusive lock of backup catalog */ | ||||
| 	ret = catalog_lock(); | ||||
| 	if (ret == -1) | ||||
| 		elog(ERROR_SYSTEM, _("can't lock backup catalog.")); | ||||
| 	else if (ret == 1) | ||||
| 		elog(ERROR_ALREADY_RUNNING, | ||||
| 			_("another pg_rman is running, stop restore.")); | ||||
|  | ||||
| 	/* confirm the PostgreSQL server is not running */ | ||||
| 	if (is_pg_running()) | ||||
| 		elog(ERROR_PG_RUNNING, _("PostgreSQL server is running")); | ||||
|  | ||||
| 	/* get list of backups. (index == 0) is the last backup */ | ||||
| 	backups = catalog_get_backup_list(NULL); | ||||
|  | ||||
| 	cur_tli = get_current_timeline(); | ||||
| 	backup_tli = get_fullbackup_timeline(backups); | ||||
|  | ||||
| 	/* determine target timeline */ | ||||
| 	if (target_tli == 0) | ||||
| 		target_tli = cur_tli != 0 ? cur_tli : backup_tli; | ||||
|  | ||||
| 	if (verbose) | ||||
| 	{ | ||||
| 		printf(_("current timeline ID = %u\n"), cur_tli); | ||||
| 		printf(_("latest full backup timeline ID = %u\n"), backup_tli); | ||||
| 		printf(_("target timeline ID = %u\n"), target_tli); | ||||
| 	} | ||||
|  | ||||
| 	/* backup online WAL and serverlog */ | ||||
| 	backup_online_files(cur_tli != 0 && cur_tli != backup_tli); | ||||
|  | ||||
| 	/* | ||||
| 	 * Clear restore destination, but don't remove $PGDATA. | ||||
| 	 * To remove symbolic link, get file list with "omit_symlink = false". | ||||
| 	 */ | ||||
| 	if (!check) | ||||
| 	{ | ||||
| 		if (verbose) | ||||
| 		{ | ||||
| 			printf(_("----------------------------------------\n")); | ||||
| 			printf(_("clearing restore destination\n")); | ||||
| 		} | ||||
| 		files = parray_new(); | ||||
| 		dir_list_file(files, pgdata, NULL, false, false); | ||||
| 		parray_qsort(files, pgFileComparePathDesc);	/* delete from leaf */ | ||||
|  | ||||
| 		for (i = 0; i < parray_num(files); i++) | ||||
| 		{ | ||||
| 			pgFile *file = (pgFile *) parray_get(files, i); | ||||
| 			pgFileDelete(file); | ||||
| 		} | ||||
| 		parray_walk(files, pgFileFree); | ||||
| 		parray_free(files); | ||||
| 	} | ||||
|  | ||||
| 	/* | ||||
| 	 * restore timeline history files and get timeline branches can reach | ||||
| 	 * recovery target point. | ||||
| 	 */ | ||||
| 	snprintf(timeline_dir, lengthof(timeline_dir), "%s/%s", backup_path, | ||||
| 		TIMELINE_HISTORY_DIR); | ||||
| 	if (verbose && !check) | ||||
| 		printf(_("restoring timeline history files\n")); | ||||
| 	dir_copy_files(timeline_dir, arclog_path); | ||||
| 	timelines = readTimeLineHistory(target_tli); | ||||
|  | ||||
| 	/* find last full backup which can be used as base backup. */ | ||||
| 	if (verbose) | ||||
| 		printf(_("searching recent full backup\n")); | ||||
| 	for (i = 0; i < parray_num(backups); i++) | ||||
| 	{ | ||||
| 		base_backup = (pgBackup *) parray_get(backups, i); | ||||
|  | ||||
| 		if (base_backup->backup_mode < BACKUP_MODE_FULL || | ||||
| 			base_backup->status != BACKUP_STATUS_OK) | ||||
| 			continue; | ||||
|  | ||||
| #ifndef HAVE_LIBZ | ||||
| 		/* Make sure we won't need decompression we haven't got */ | ||||
| 		if (base_backup->compress_data && | ||||
| 			(HAVE_DATABASE(base_backup) || HAVE_ARCLOG(base_backup))) | ||||
| 		{ | ||||
| 			elog(EXIT_NOT_SUPPORTED, | ||||
| 				_("can't restore from compressed backup (compression not supported in this installation)")); | ||||
| 		} | ||||
| #endif | ||||
| 		if (satisfy_timeline(timelines, base_backup)) | ||||
| 		{ | ||||
| 			goto base_backup_found; | ||||
| 		} | ||||
| 	} | ||||
| 	/* no full backup found, can't restore */ | ||||
| 	elog(ERROR_NO_BACKUP, _("no full backup found, can't restore.")); | ||||
|  | ||||
| base_backup_found: | ||||
| 	base_index = i; | ||||
|  | ||||
| 	if (verbose) | ||||
| 		print_backup_id(base_backup); | ||||
|  | ||||
| 	/* restore base backup */ | ||||
| 	restore_database(base_backup); | ||||
| 	last_restored_index = base_index; | ||||
|  | ||||
| 	/* restore following incremental backup */ | ||||
| 	if (verbose) | ||||
| 		printf(_("searching incremental backup...\n")); | ||||
| 	for (i = base_index - 1; i >= 0; i--) | ||||
| 	{ | ||||
| 		pgBackup *backup = (pgBackup *) parray_get(backups, i); | ||||
|  | ||||
| 		/* don't use incomplete nor different timeline backup */ | ||||
| 		if (backup->status != BACKUP_STATUS_OK || | ||||
| 					backup->tli != base_backup->tli) | ||||
| 			continue; | ||||
|  | ||||
| 		/* use database backup only */ | ||||
| 		if (backup->backup_mode < BACKUP_MODE_INCREMENTAL) | ||||
| 			continue; | ||||
|  | ||||
| 		/* is the backup is necessary for restore to target timeline ? */ | ||||
| 		if (!satisfy_timeline(timelines, backup)) | ||||
| 			continue; | ||||
|  | ||||
| 		if (verbose) | ||||
| 			print_backup_id(backup); | ||||
|  | ||||
| 		restore_database(backup); | ||||
| 		last_restored_index = i; | ||||
| 	} | ||||
|  | ||||
| 	/* | ||||
| 	 * Restore archived WAL which backed up with or after last restored backup. | ||||
| 	 * We don't check the backup->tli because a backup of arhived WAL | ||||
| 	 * can contain WALs which were archived in multiple timeline. | ||||
| 	 */ | ||||
| 	if (verbose) | ||||
| 		printf(_("searching backed-up WAL...\n")); | ||||
|  | ||||
| 	if (check) | ||||
| 	{ | ||||
| 		pgBackup *backup = (pgBackup *) parray_get(backups, last_restored_index); | ||||
| 		/* XLByteToSeg(xlrp, logId, logSeg) */ | ||||
| 		needId = backup->start_lsn.xlogid; | ||||
| 		needSeg = backup->start_lsn.xrecoff / XLogSegSize; | ||||
| 	} | ||||
|  | ||||
| 	for (i = last_restored_index; i >= 0; i--) | ||||
| 	{ | ||||
| 		pgBackup *backup = (pgBackup *) parray_get(backups, i); | ||||
|  | ||||
| 		/* don't use incomplete backup */ | ||||
| 		if (backup->status != BACKUP_STATUS_OK) | ||||
| 			continue; | ||||
|  | ||||
| 		if (!HAVE_ARCLOG(backup)) | ||||
| 			continue; | ||||
|  | ||||
| 		/* care timeline junction */ | ||||
| 		if (!satisfy_timeline(timelines, backup)) | ||||
| 			continue; | ||||
|  | ||||
| 		restore_archive_logs(backup); | ||||
|  | ||||
| 		if (check) | ||||
| 		{ | ||||
| 			char	xlogpath[MAXPGPATH]; | ||||
|  | ||||
| 			pgBackupGetPath(backup, xlogpath, lengthof(xlogpath), ARCLOG_DIR); | ||||
| 			search_next_wal(xlogpath, &needId, &needSeg, timelines); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/* copy online WAL backup to $PGDATA/pg_xlog */ | ||||
| 	restore_online_files(); | ||||
|  | ||||
| 	if (check) | ||||
| 	{ | ||||
| 		char	xlogpath[MAXPGPATH]; | ||||
| 		if (verbose) | ||||
| 			printf(_("searching archived WAL...\n")); | ||||
|  | ||||
| 		search_next_wal(arclog_path, &needId, &needSeg, timelines); | ||||
|  | ||||
| 		if (verbose) | ||||
| 			printf(_("searching online WAL...\n")); | ||||
|  | ||||
| 		snprintf(xlogpath, lengthof(xlogpath), "%s/%s", pgdata, PG_XLOG_DIR); | ||||
| 		search_next_wal(xlogpath, &needId, &needSeg, timelines); | ||||
|  | ||||
| 		if (verbose) | ||||
| 			printf(_("all necessary files are found.\n")); | ||||
| 	} | ||||
|  | ||||
| 	/* create recovery.conf */ | ||||
| 	create_recovery_conf(target_time, target_xid, target_inclusive, target_tli); | ||||
|  | ||||
| 	/* release catalog lock */ | ||||
| 	catalog_unlock(); | ||||
|  | ||||
| 	/* cleanup */ | ||||
| 	parray_walk(backups, pgBackupFree); | ||||
| 	parray_free(backups); | ||||
|  | ||||
| 	/* print restore complete message */ | ||||
| 	if (verbose && !check) | ||||
| 	{ | ||||
| 		printf(_("all restore completed\n")); | ||||
| 		printf(_("========================================\n")); | ||||
| 	} | ||||
| 	if (!check) | ||||
| 		elog(INFO, _("restore complete. Recovery starts automatically when the PostgreSQL server is started.")); | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Validate and restore backup. | ||||
|  */ | ||||
| void | ||||
| restore_database(pgBackup *backup) | ||||
| { | ||||
| 	char timestamp[100]; | ||||
| 	char path[MAXPGPATH]; | ||||
| 	char list_path[MAXPGPATH]; | ||||
| 	int ret; | ||||
| 	parray *files; | ||||
| 	int i; | ||||
|  | ||||
| 	/* confirm block size compatibility */ | ||||
| 	if (backup->block_size != BLCKSZ) | ||||
| 		elog(ERROR_PG_INCOMPATIBLE, | ||||
| 			_("BLCKSZ(%d) is not compatible(%d expected)"), | ||||
| 			backup->block_size, BLCKSZ); | ||||
| 	if (backup->wal_block_size != XLOG_BLCKSZ) | ||||
| 		elog(ERROR_PG_INCOMPATIBLE, | ||||
| 			_("XLOG_BLCKSZ(%d) is not compatible(%d expected)"), | ||||
| 			backup->wal_block_size, XLOG_BLCKSZ); | ||||
|  | ||||
| 	time2iso(timestamp, lengthof(timestamp), backup->start_time); | ||||
| 	if (verbose && !check) | ||||
| 	{ | ||||
| 		printf(_("----------------------------------------\n")); | ||||
| 		printf(_("restoring database from backup %s.\n"), timestamp); | ||||
| 	} | ||||
|  | ||||
| 	/* | ||||
| 	 * Validate backup files with its size, because load of CRC calculation is | ||||
| 	 * not light. | ||||
| 	 */ | ||||
| 	pgBackupValidate(backup, true); | ||||
|  | ||||
| 	/* make direcotries and symbolic links */ | ||||
| 	pgBackupGetPath(backup, path, lengthof(path), MKDIRS_SH_FILE); | ||||
| 	if (!check) | ||||
| 	{ | ||||
| 		char pwd[MAXPGPATH]; | ||||
|  | ||||
| 		/* keep orginal directory */ | ||||
| 		if (getcwd(pwd, sizeof(pwd)) == NULL) | ||||
| 			elog(ERROR_SYSTEM, _("can't get current working directoryh: %s"), | ||||
| 				strerror(errno)); | ||||
|  | ||||
| 		/* create pgdata directory */ | ||||
| 		dir_create_dir(pgdata, DIR_PERMISSION); | ||||
|  | ||||
| 		/* change directory to pgdata */ | ||||
| 		if (chdir(pgdata)) | ||||
| 			elog(ERROR_SYSTEM, _("can't change directoryh: %s"), | ||||
| 				strerror(errno)); | ||||
|  | ||||
| 		/* Execute mkdirs.sh */ | ||||
| 		ret = system(path); | ||||
| 		if (ret != 0) | ||||
| 			elog(ERROR_SYSTEM, _("can't execute mkdirs.sh: %s"), | ||||
| 				strerror(errno)); | ||||
|  | ||||
| 		/* go back to original directory */ | ||||
| 		if (chdir(pwd)) | ||||
| 			elog(ERROR_SYSTEM, _("can't change directoryh: %s"), | ||||
| 				strerror(errno)); | ||||
| 	} | ||||
|  | ||||
| 	/* | ||||
| 	 * get list of files which need to be restored. | ||||
| 	 */ | ||||
| 	pgBackupGetPath(backup, path, lengthof(path), DATABASE_DIR); | ||||
| 	pgBackupGetPath(backup, list_path, lengthof(list_path), DATABASE_FILE_LIST); | ||||
| 	files = dir_read_file_list(path, list_path); | ||||
| 	for (i = parray_num(files) - 1; i >= 0; i--) | ||||
| 	{ | ||||
| 		pgFile *file = (pgFile *) parray_get(files, i); | ||||
|  | ||||
| 		/* remove files which are not backed up */ | ||||
| 		if (file->write_size == BYTES_INVALID) | ||||
| 			pgFileFree(parray_remove(files, i)); | ||||
| 	} | ||||
|  | ||||
| 	/* restore files into $PGDATA */ | ||||
| 	for (i = 0; i < parray_num(files); i++) | ||||
| 	{ | ||||
| 		char from_root[MAXPGPATH]; | ||||
| 		pgFile *file = (pgFile *) parray_get(files, i); | ||||
|  | ||||
| 		pgBackupGetPath(backup, from_root, lengthof(from_root), DATABASE_DIR); | ||||
|  | ||||
| 		/* check for interrupt */ | ||||
| 		if (interrupted) | ||||
| 			elog(ERROR_INTERRUPTED, _("interrupted during restore database")); | ||||
|  | ||||
| 		/* print progress */ | ||||
| 		if (verbose && !check) | ||||
| 			printf(_("(%d/%lu) %s "), i + 1, (unsigned long) parray_num(files), | ||||
| 				file->path + strlen(from_root) + 1); | ||||
|  | ||||
| 		/* directories are created with mkdirs.sh */ | ||||
| 		if (S_ISDIR(file->mode)) | ||||
| 		{ | ||||
| 			if (verbose && !check) | ||||
| 				printf(_("directory, skip\n")); | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		/* not backed up */ | ||||
| 		if (file->write_size == BYTES_INVALID) | ||||
| 		{ | ||||
| 			if (verbose && !check) | ||||
| 				printf(_("not backed up, skip\n")); | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		/* restore file */ | ||||
| 		if (!check) | ||||
| 			restore_data_file(from_root, pgdata, file, backup->compress_data); | ||||
|  | ||||
| 		/* print size of restored file */ | ||||
| 		if (verbose && !check) | ||||
| 			printf(_("restored %lu\n"), (unsigned long) file->write_size); | ||||
| 	} | ||||
|  | ||||
| 	/* Delete files which are not in file list. */ | ||||
| 	if (!check) | ||||
| 	{ | ||||
| 		parray *files_now; | ||||
|  | ||||
| 		parray_walk(files, pgFileFree); | ||||
| 		parray_free(files); | ||||
|  | ||||
| 		/* re-read file list to change base path to $PGDATA */ | ||||
| 		files = dir_read_file_list(pgdata, list_path); | ||||
| 		parray_qsort(files, pgFileComparePathDesc); | ||||
|  | ||||
| 		/* get list of files restored to pgdata */ | ||||
| 		files_now = parray_new(); | ||||
| 		dir_list_file(files_now, pgdata, pgdata_exclude, true, false); | ||||
| 		/* to delete from leaf, sort in reversed order */ | ||||
| 		parray_qsort(files_now, pgFileComparePathDesc); | ||||
|  | ||||
| 		for (i = 0; i < parray_num(files_now); i++) | ||||
| 		{ | ||||
| 			pgFile *file = (pgFile *) parray_get(files_now, i); | ||||
|  | ||||
| 			/* If the file is not in the file list, delete it */ | ||||
| 			if (parray_bsearch(files, file, pgFileComparePathDesc) == NULL) | ||||
| 			{ | ||||
| 				if (verbose) | ||||
| 					printf(_("  delete %s\n"), file->path + strlen(pgdata) + 1); | ||||
| 				pgFileDelete(file); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		parray_walk(files_now, pgFileFree); | ||||
| 		parray_free(files_now); | ||||
| 	} | ||||
|  | ||||
| 	/* remove postmaster.pid */ | ||||
| 	snprintf(path, lengthof(path), "%s/postmaster.pid", pgdata); | ||||
| 	if (remove(path) == -1 && errno != ENOENT) | ||||
| 		elog(ERROR_SYSTEM, _("can't remove postmaster.pid: %s"), | ||||
| 			strerror(errno)); | ||||
|  | ||||
| 	/* cleanup */ | ||||
| 	parray_walk(files, pgFileFree); | ||||
| 	parray_free(files); | ||||
|  | ||||
| 	if (verbose && !check) | ||||
| 		printf(_("resotre backup completed\n")); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Restore archived WAL by creating symbolic link which linked to backup WAL in | ||||
|  * archive directory. | ||||
|  */ | ||||
| void | ||||
| restore_archive_logs(pgBackup *backup) | ||||
| { | ||||
| 	int i; | ||||
| 	char timestamp[100]; | ||||
| 	parray *files; | ||||
| 	char path[MAXPGPATH]; | ||||
| 	char list_path[MAXPGPATH]; | ||||
| 	char base_path[MAXPGPATH]; | ||||
|  | ||||
| 	time2iso(timestamp, lengthof(timestamp), backup->start_time); | ||||
| 	if (verbose && !check) | ||||
| 	{ | ||||
| 		printf(_("----------------------------------------\n")); | ||||
| 		printf(_("restoring WAL from backup %s.\n"), timestamp); | ||||
| 	} | ||||
|  | ||||
| 	pgBackupGetPath(backup, list_path, lengthof(list_path), ARCLOG_FILE_LIST); | ||||
| 	pgBackupGetPath(backup, base_path, lengthof(list_path), ARCLOG_DIR); | ||||
| 	files = dir_read_file_list(base_path, list_path); | ||||
| 	for (i = 0; i < parray_num(files); i++) | ||||
| 	{ | ||||
| 		pgFile *file = (pgFile *) parray_get(files, i); | ||||
|  | ||||
| 		/* check for interrupt */ | ||||
| 		if (interrupted) | ||||
| 			elog(ERROR_INTERRUPTED, _("interrupted during restore WAL")); | ||||
|  | ||||
| 		/* print progress */ | ||||
| 		snprintf(path, lengthof(path), "%s/%s", arclog_path, | ||||
| 			file->path + strlen(base_path) + 1); | ||||
| 		if (verbose && !check) | ||||
| 			printf(_("(%d/%lu) %s "), i + 1, (unsigned long) parray_num(files), | ||||
| 				file->path + strlen(base_path) + 1); | ||||
|  | ||||
| 		/* skip files which are not in backup */ | ||||
| 		if (file->write_size == BYTES_INVALID) | ||||
| 		{ | ||||
| 			if (verbose && !check) | ||||
| 				printf(_("skip(not backed up)\n")); | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		/* | ||||
| 		 * skip timeline history files because timeline history files will be | ||||
| 		 * restored from $BACKUP_PATH/timeline_history. | ||||
| 		 */ | ||||
| 		if (strstr(file->path, ".history") == | ||||
| 				file->path + strlen(file->path) - strlen(".history")) | ||||
| 		{ | ||||
| 			if (verbose && !check) | ||||
| 				printf(_("skip(timeline history)\n")); | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		if (!check) | ||||
| 		{ | ||||
| 			if (backup->compress_data) | ||||
| 			{ | ||||
| 				copy_file(base_path, arclog_path, file, DECOMPRESSION); | ||||
| 				if (verbose) | ||||
| 					printf(_("decompressed\n")); | ||||
|  | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
| 			/* even same file exist, use backup file */ | ||||
| 			if ((remove(path) == -1) && errno != ENOENT) | ||||
| 				elog(ERROR_SYSTEM, _("can't remove file \"%s\": %s"), path, | ||||
| 					strerror(errno)); | ||||
|  | ||||
| 			if ((symlink(file->path, path) == -1)) | ||||
| 				elog(ERROR_SYSTEM, _("can't create link to \"%s\": %s"), | ||||
| 					file->path, strerror(errno)); | ||||
|  | ||||
| 			if (verbose) | ||||
| 				printf(_("linked\n")); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	parray_walk(files, pgFileFree); | ||||
| 	parray_free(files); | ||||
| } | ||||
|  | ||||
| static void | ||||
| create_recovery_conf(const char *target_time, | ||||
| 					 const char *target_xid, | ||||
| 					 const char *target_inclusive, | ||||
| 					 TimeLineID target_tli) | ||||
| { | ||||
| 	char path[MAXPGPATH]; | ||||
| 	FILE *fp; | ||||
|  | ||||
| 	if (verbose && !check) | ||||
| 	{ | ||||
| 		printf(_("----------------------------------------\n")); | ||||
| 		printf(_("creating recovery.conf\n")); | ||||
| 	} | ||||
|  | ||||
| 	if (!check) | ||||
| 	{ | ||||
| 		snprintf(path, lengthof(path), "%s/recovery.conf", pgdata); | ||||
| 		fp = fopen(path, "wt"); | ||||
| 		if (fp == NULL) | ||||
| 			elog(ERROR_SYSTEM, _("can't open recovery.conf \"%s\": %s"), path, | ||||
| 				strerror(errno)); | ||||
|  | ||||
| 		fprintf(fp, "# recovery.conf generated by pg_rman %s\n", | ||||
| 			PROGRAM_VERSION); | ||||
| 		fprintf(fp, "restore_command = 'cp %s/%%f %%p'\n", arclog_path); | ||||
| 		if (target_time) | ||||
| 			fprintf(fp, "recovery_target_time = '%s'\n", target_time); | ||||
| 		if (target_xid) | ||||
| 			fprintf(fp, "recovery_target_xid = '%s'\n", target_xid); | ||||
| 		if (target_inclusive) | ||||
| 			fprintf(fp, "recovery_target_inclusive = '%s'\n", target_inclusive); | ||||
| 		fprintf(fp, "recovery_target_timeline = '%u'\n", target_tli); | ||||
|  | ||||
| 		fclose(fp); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void | ||||
| backup_online_files(bool re_recovery) | ||||
| { | ||||
| 	char work_path[MAXPGPATH]; | ||||
| 	char pg_xlog_path[MAXPGPATH]; | ||||
| 	bool files_exist; | ||||
| 	parray *files; | ||||
|  | ||||
| 	if (verbose && !check) | ||||
| 	{ | ||||
| 		printf(_("----------------------------------------\n")); | ||||
| 		printf(_("backup online WAL and serverlog start\n")); | ||||
| 	} | ||||
|  | ||||
| 	/* get list of files in $BACKUP_PATH/backup/pg_xlog */ | ||||
| 	files = parray_new(); | ||||
| 	snprintf(work_path, lengthof(work_path), "%s/%s/%s", backup_path, | ||||
| 		RESTORE_WORK_DIR, PG_XLOG_DIR); | ||||
| 	dir_list_file(files, work_path, NULL, true, false); | ||||
|  | ||||
| 	files_exist = parray_num(files) > 0; | ||||
|  | ||||
| 	parray_walk(files, pgFileFree); | ||||
| 	parray_free(files); | ||||
|  | ||||
| 	/* If files exist in RESTORE_WORK_DIR and not re-recovery, use them. */ | ||||
| 	if (files_exist && !re_recovery) | ||||
| 	{ | ||||
| 		if (verbose) | ||||
| 			printf(_("online WALs have been already backed up, use them.\n")); | ||||
|  | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	/* backup online WAL */ | ||||
| 	snprintf(pg_xlog_path, lengthof(pg_xlog_path), "%s/pg_xlog", pgdata); | ||||
| 	snprintf(work_path, lengthof(work_path), "%s/%s/%s", backup_path, | ||||
| 		RESTORE_WORK_DIR, PG_XLOG_DIR); | ||||
| 	dir_create_dir(work_path, DIR_PERMISSION); | ||||
| 	dir_copy_files(pg_xlog_path, work_path); | ||||
|  | ||||
| 	/* backup serverlog */ | ||||
| 	snprintf(work_path, lengthof(work_path), "%s/%s/%s", backup_path, | ||||
| 		RESTORE_WORK_DIR, SRVLOG_DIR); | ||||
| 	dir_create_dir(work_path, DIR_PERMISSION); | ||||
| 	dir_copy_files(srvlog_path, work_path); | ||||
| } | ||||
|  | ||||
| static void | ||||
| restore_online_files(void) | ||||
| { | ||||
| 	int		i; | ||||
| 	char	root_backup[MAXPGPATH]; | ||||
| 	parray *files_backup; | ||||
|  | ||||
| 	/* get list of files in $BACKUP_PATH/backup/pg_xlog */ | ||||
| 	files_backup = parray_new(); | ||||
| 	snprintf(root_backup, lengthof(root_backup), "%s/%s/%s", backup_path, | ||||
| 		RESTORE_WORK_DIR, PG_XLOG_DIR); | ||||
| 	dir_list_file(files_backup, root_backup, NULL, true, false); | ||||
|  | ||||
| 	if (verbose && !check) | ||||
| 	{ | ||||
| 		printf(_("----------------------------------------\n")); | ||||
| 		printf(_("restoring online WAL\n")); | ||||
| 	} | ||||
|  | ||||
| 	/* restore online WAL */ | ||||
| 	for (i = 0; i < parray_num(files_backup); i++) | ||||
| 	{ | ||||
| 		pgFile *file = (pgFile *) parray_get(files_backup, i); | ||||
|  | ||||
| 		if (S_ISDIR(file->mode)) | ||||
| 		{ | ||||
| 			char to_path[MAXPGPATH]; | ||||
| 			snprintf(to_path, lengthof(to_path), "%s/%s/%s", pgdata, | ||||
| 				PG_XLOG_DIR, file->path + strlen(root_backup) + 1); | ||||
| 			if (verbose && !check) | ||||
| 				printf(_("create directory \"%s\"\n"), | ||||
| 					file->path + strlen(root_backup) + 1); | ||||
| 			if (!check) | ||||
| 				dir_create_dir(to_path, DIR_PERMISSION); | ||||
| 			continue; | ||||
| 		} | ||||
| 		else if(S_ISREG(file->mode)) | ||||
| 		{ | ||||
| 			char to_root[MAXPGPATH]; | ||||
| 			snprintf(to_root, lengthof(to_root), "%s/%s", pgdata, PG_XLOG_DIR); | ||||
| 			if (verbose && !check) | ||||
| 				printf(_("restore \"%s\"\n"), | ||||
| 					file->path + strlen(root_backup) + 1); | ||||
| 			if (!check) | ||||
| 				copy_file(root_backup, to_root, file, NO_COMPRESSION); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/* cleanup */ | ||||
| 	parray_walk(files_backup, pgFileFree); | ||||
| 	parray_free(files_backup); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Try to read a timeline's history file. | ||||
|  * | ||||
|  * If successful, return the list of component pgTimeLine (the ancestor | ||||
|  * timelines followed by target timeline).	If we can't find the history file, | ||||
|  * assume that the timeline has no parents, and return a list of just the | ||||
|  * specified timeline ID. | ||||
|  * based on readTimeLineHistory() in xlog.c | ||||
|  */ | ||||
| static parray * | ||||
| readTimeLineHistory(TimeLineID targetTLI) | ||||
| { | ||||
| 	parray	   *result; | ||||
| 	char		path[MAXPGPATH]; | ||||
| 	char		fline[MAXPGPATH]; | ||||
| 	FILE	   *fd; | ||||
| 	pgTimeLine *timeline; | ||||
| 	pgTimeLine *last_timeline = NULL; | ||||
|  | ||||
| 	result = parray_new(); | ||||
|  | ||||
| 	/* search from arclog_path first */ | ||||
| 	snprintf(path, lengthof(path), "%s/%08X.history", arclog_path, | ||||
| 		targetTLI); | ||||
| 	fd = fopen(path, "rt"); | ||||
| 	if (fd == NULL) | ||||
| 	{ | ||||
| 		if (errno != ENOENT) | ||||
| 			elog(ERROR_SYSTEM, _("could not open file \"%s\": %s"), path, | ||||
| 				strerror(errno)); | ||||
|  | ||||
| 		/* search from restore work directory next */ | ||||
| 		snprintf(path, lengthof(path), "%s/%s/%s/%08X.history", backup_path, | ||||
| 			RESTORE_WORK_DIR, PG_XLOG_DIR, targetTLI); | ||||
| 		fd = fopen(path, "rt"); | ||||
| 		if (fd == NULL) | ||||
| 		{ | ||||
| 			if (errno != ENOENT) | ||||
| 				elog(ERROR_SYSTEM, _("could not open file \"%s\": %s"), path, | ||||
| 						strerror(errno)); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/* | ||||
| 	 * Parse the file... | ||||
| 	 */ | ||||
| 	while (fd && fgets(fline, sizeof(fline), fd) != NULL) | ||||
| 	{ | ||||
| 		/* skip leading whitespace and check for # comment */ | ||||
| 		char	   *ptr; | ||||
| 		char	   *endptr; | ||||
|  | ||||
| 		for (ptr = fline; *ptr; ptr++) | ||||
| 		{ | ||||
| 			if (!IsSpace(*ptr)) | ||||
| 				break; | ||||
| 		} | ||||
| 		if (*ptr == '\0' || *ptr == '#') | ||||
| 			continue; | ||||
|  | ||||
| 		timeline = pgut_malloc(sizeof(*timeline)); | ||||
| 		timeline->tli = 0; | ||||
| 		timeline->end.xlogid = 0; | ||||
| 		timeline->end.xrecoff = 0; | ||||
|  | ||||
| 		/* expect a numeric timeline ID as first field of line */ | ||||
| 		timeline->tli = (TimeLineID) strtoul(ptr, &endptr, 0); | ||||
| 		if (endptr == ptr) | ||||
| 			elog(ERROR_CORRUPTED, | ||||
| 					_("syntax error(timeline ID) in history file: %s"), | ||||
| 					fline); | ||||
|  | ||||
| 		if (last_timeline && timeline->tli <= last_timeline->tli) | ||||
| 			elog(ERROR_CORRUPTED, | ||||
| 				   _("Timeline IDs must be in increasing sequence.")); | ||||
|  | ||||
| 		/* Build list with newest item first */ | ||||
| 		parray_insert(result, 0, timeline); | ||||
| 		last_timeline = timeline; | ||||
|  | ||||
| 		/* parse end point(logfname, xid) in the timeline */ | ||||
| 		for (ptr = endptr; *ptr; ptr++) | ||||
| 		{ | ||||
| 			if (!IsSpace(*ptr)) | ||||
| 				break; | ||||
| 		} | ||||
| 		if (*ptr == '\0' || *ptr == '#') | ||||
| 			elog(ERROR_CORRUPTED, | ||||
| 			   _("End logfile must follow Timeline ID.")); | ||||
|  | ||||
| 		if (!xlog_logfname2lsn(ptr, &timeline->end)) | ||||
| 			elog(ERROR_CORRUPTED, | ||||
| 					_("syntax error(endfname) in history file: %s"), fline); | ||||
| 		/* we ignore the remainder of each line */ | ||||
| 	} | ||||
|  | ||||
| 	if (fd) | ||||
| 		fclose(fd); | ||||
|  | ||||
| 	if (last_timeline && targetTLI <= last_timeline->tli) | ||||
| 		elog(ERROR_CORRUPTED, | ||||
| 			_("Timeline IDs must be less than child timeline's ID.")); | ||||
|  | ||||
| 	/* append target timeline */ | ||||
| 	timeline = pgut_malloc(sizeof(*timeline)); | ||||
| 	timeline->tli = targetTLI; | ||||
| 	timeline->end.xlogid = (uint32) -1; /* lsn in target timelie is valid */ | ||||
| 	timeline->end.xrecoff = (uint32) -1; /* lsn target timelie is valid */ | ||||
| 	parray_insert(result, 0, timeline); | ||||
|  | ||||
| 	/* dump timeline branches for debug */ | ||||
| 	if (debug) | ||||
| 	{ | ||||
| 		int i; | ||||
| 		for (i = 0; i < parray_num(result); i++) | ||||
| 		{ | ||||
| 			pgTimeLine *timeline = parray_get(result, i); | ||||
| 			elog(LOG, "%s() result[%d]: %08X/%08X/%08X", __FUNCTION__, i, | ||||
| 				timeline->tli, timeline->end.xlogid, timeline->end.xrecoff); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| static bool | ||||
| satisfy_timeline(const parray *timelines, const pgBackup *backup) | ||||
| { | ||||
| 	int i; | ||||
| 	for (i = 0; i < parray_num(timelines); i++) | ||||
| 	{ | ||||
| 		pgTimeLine *timeline = (pgTimeLine *) parray_get(timelines, i); | ||||
| 		if (backup->tli == timeline->tli && | ||||
| 				XLByteLT(backup->stop_lsn, timeline->end)) | ||||
| 			return true; | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| /* get TLI of the current database */ | ||||
| static TimeLineID | ||||
| get_current_timeline(void) | ||||
| { | ||||
| 	ControlFileData ControlFile; | ||||
| 	int			fd; | ||||
| 	char		ControlFilePath[MAXPGPATH]; | ||||
| 	pg_crc32	crc; | ||||
| 	TimeLineID	ret; | ||||
|  | ||||
| 	snprintf(ControlFilePath, MAXPGPATH, "%s/global/pg_control", pgdata); | ||||
|  | ||||
| 	if ((fd = open(ControlFilePath, O_RDONLY | PG_BINARY, 0)) == -1) | ||||
| 	{ | ||||
| 		elog(WARNING, _("can't open pg_controldata file \"%s\": %s"), | ||||
| 			ControlFilePath, strerror(errno)); | ||||
| 		return 0; | ||||
| 	} | ||||
|  | ||||
| 	if (read(fd, &ControlFile, sizeof(ControlFileData)) != sizeof(ControlFileData)) | ||||
| 	{ | ||||
| 		elog(WARNING, _("can't read pg_controldata file \"%s\": %s"), | ||||
| 			ControlFilePath, strerror(errno)); | ||||
| 		return 0; | ||||
| 	} | ||||
| 	close(fd); | ||||
|  | ||||
| 	/* Check the CRC. */ | ||||
| 	INIT_CRC32(crc); | ||||
| 	COMP_CRC32(crc, | ||||
| 		   	(char *) &ControlFile, | ||||
| 		   	offsetof(ControlFileData, crc)); | ||||
| 	FIN_CRC32(crc); | ||||
|  | ||||
| 	if (!EQ_CRC32(crc, ControlFile.crc)) | ||||
| 	{ | ||||
| 		elog(WARNING, _("Calculated CRC checksum does not match value stored in file.\n" | ||||
| 			"Either the file is corrupt, or it has a different layout than this program\n" | ||||
| 			"is expecting.  The results below are untrustworthy.\n")); | ||||
| 		return 0; | ||||
| 	} | ||||
|  | ||||
| 	if (ControlFile.pg_control_version % 65536 == 0 && ControlFile.pg_control_version / 65536 != 0) | ||||
| 	{ | ||||
| 		elog(WARNING, _("possible byte ordering mismatch\n" | ||||
| 			"The byte ordering used to store the pg_control file might not match the one\n" | ||||
| 			"used by this program.  In that case the results below would be incorrect, and\n" | ||||
| 			"the PostgreSQL installation would be incompatible with this data directory.\n")); | ||||
| 		return 0; | ||||
| 	} | ||||
|  | ||||
| 	ret = ControlFile.checkPointCopy.ThisTimeLineID; | ||||
|  | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
| /* get TLI of the latest full backup */ | ||||
| static TimeLineID | ||||
| get_fullbackup_timeline(parray *backups) | ||||
| { | ||||
| 	int			i; | ||||
| 	pgBackup   *base_backup = NULL; | ||||
| 	TimeLineID	ret; | ||||
|  | ||||
| 	for (i = 0; i < parray_num(backups); i++) | ||||
| 	{ | ||||
| 		base_backup = (pgBackup *) parray_get(backups, i); | ||||
|  | ||||
| 		if (base_backup->backup_mode >= BACKUP_MODE_FULL) | ||||
| 		{ | ||||
| 			/* | ||||
| 			 * Validate backup files with its size, because load of CRC | ||||
| 			 * calculation is not light. | ||||
| 			 */ | ||||
| 			if (base_backup->status == BACKUP_STATUS_DONE) | ||||
| 				pgBackupValidate(base_backup, true); | ||||
|  | ||||
| 			if (base_backup->status == BACKUP_STATUS_OK) | ||||
| 				break; | ||||
| 		} | ||||
| 	} | ||||
| 	/* no full backup found, can't restore */ | ||||
| 	if (i == parray_num(backups)) | ||||
| 		elog(ERROR_NO_BACKUP, _("no full backup found, can't restore.")); | ||||
|  | ||||
| 	ret = base_backup->tli; | ||||
|  | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
| static void | ||||
| print_backup_id(const pgBackup *backup) | ||||
| { | ||||
| 	char timestamp[100]; | ||||
| 	time2iso(timestamp, lengthof(timestamp), backup->start_time); | ||||
| 	printf(_("  %s (%X/%08X)\n"), timestamp, backup->stop_lsn.xlogid, | ||||
| 		backup->stop_lsn.xrecoff); | ||||
| } | ||||
|  | ||||
| static void | ||||
| search_next_wal(const char *path, uint32 *needId, uint32 *needSeg, parray *timelines) | ||||
| { | ||||
| 	int		i; | ||||
| 	int		j; | ||||
| 	int		count; | ||||
| 	char	xlogfname[MAXFNAMELEN]; | ||||
| 	char	pre_xlogfname[MAXFNAMELEN]; | ||||
| 	char	xlogpath[MAXPGPATH]; | ||||
| 	struct stat	st; | ||||
|  | ||||
| 	count = 0; | ||||
| 	for (;;) | ||||
| 	{ | ||||
| 		for (i = 0; i < parray_num(timelines); i++) | ||||
| 		{ | ||||
| 			pgTimeLine *timeline = (pgTimeLine *) parray_get(timelines, i); | ||||
|  | ||||
| 			XLogFileName(xlogfname, timeline->tli, *needId, *needSeg); | ||||
| 			snprintf(xlogpath, lengthof(xlogpath), "%s/%s", path, xlogfname); | ||||
|  | ||||
| 			if (stat(xlogpath, &st) == 0) | ||||
| 				break; | ||||
| 		} | ||||
|  | ||||
| 		/* not found */ | ||||
| 		if (i == parray_num(timelines)) | ||||
| 		{ | ||||
| 			if (count == 1) | ||||
| 				printf(_("\n")); | ||||
| 			else if (count > 1) | ||||
| 				printf(_(" - %s\n"), pre_xlogfname); | ||||
|  | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		count++; | ||||
| 		if (count == 1) | ||||
| 			printf(_("%s"), xlogfname); | ||||
|  | ||||
| 		strcpy(pre_xlogfname, xlogfname); | ||||
|  | ||||
| 		/* delete old TLI */ | ||||
| 		for (j = i + 1; j < parray_num(timelines); j++) | ||||
| 			parray_remove(timelines, i + 1); | ||||
| 		/* XXX: should we add a linebreak when we find a timeline? */ | ||||
|  | ||||
| 		NextLogSeg(*needId, *needSeg); | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										255
									
								
								show.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										255
									
								
								show.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,255 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * show.c: show backup catalog. | ||||
|  * | ||||
|  * Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #include "pg_rman.h" | ||||
|  | ||||
| static void show_backup_list(FILE *out, parray *backup_list, bool show_all); | ||||
| static void show_timeline_backup_list(FILE *out, parray *backup_list, bool show_all); | ||||
| static void show_backup_detail(FILE *out, pgBackup *backup); | ||||
|  | ||||
| /* | ||||
|  * Show backup catalog information. | ||||
|  * If range is { 0, 0 }, show list of all backup, otherwise show detail of the | ||||
|  * backup indicated by id. | ||||
|  */ | ||||
| int | ||||
| do_show(pgBackupRange *range, bool show_timeline, bool show_all) | ||||
| { | ||||
| 	if (pgBackupRangeIsSingle(range)) | ||||
| 	{ | ||||
| 		pgBackup *backup; | ||||
|  | ||||
| 		backup = catalog_get_backup(range->begin); | ||||
| 		if (backup == NULL) | ||||
| 		{ | ||||
| 			char timestamp[100]; | ||||
| 			time2iso(timestamp, lengthof(timestamp), range->begin); | ||||
| 			elog(INFO, _("backup taken at \"%s\" doesn not exist."), | ||||
| 				timestamp); | ||||
| 			/* This is not error case */ | ||||
| 			return 0; | ||||
| 		} | ||||
| 		show_backup_detail(stdout, backup); | ||||
|  | ||||
| 		/* cleanup */ | ||||
| 		pgBackupFree(backup); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		parray *backup_list; | ||||
|  | ||||
| 		backup_list = catalog_get_backup_list(range); | ||||
| 		if (backup_list == NULL) | ||||
| 			return 1; | ||||
|  | ||||
| 		if (!show_timeline) | ||||
| 			show_backup_list(stdout, backup_list, show_all); | ||||
| 		else | ||||
| 			show_timeline_backup_list(stdout, backup_list, show_all); | ||||
|  | ||||
| 		/* cleanup */ | ||||
| 		parray_walk(backup_list, pgBackupFree); | ||||
| 		parray_free(backup_list); | ||||
| 	} | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| static void | ||||
| pretty_size(int64 size, char *buf, size_t len) | ||||
| { | ||||
| 	int exp = 0; | ||||
|  | ||||
| 	/* minus means the size is invalid */ | ||||
| 	if (size < 0) | ||||
| 	{ | ||||
| 		strncpy(buf, "----", len); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	/* determine postfix */ | ||||
| 	while (size > 9999) | ||||
| 	{ | ||||
| 		++exp; | ||||
| 		size /= 1000; | ||||
| 	} | ||||
|  | ||||
| 	switch (exp) | ||||
| 	{ | ||||
| 		case 0: | ||||
| 			snprintf(buf, len, INT64_FORMAT "B", size); | ||||
| 			break; | ||||
| 		case 1: | ||||
| 			snprintf(buf, len, INT64_FORMAT "kB", size); | ||||
| 			break; | ||||
| 		case 2: | ||||
| 			snprintf(buf, len, INT64_FORMAT "MB", size); | ||||
| 			break; | ||||
| 		case 3: | ||||
| 			snprintf(buf, len, INT64_FORMAT "GB", size); | ||||
| 			break; | ||||
| 		case 4: | ||||
| 			snprintf(buf, len, INT64_FORMAT "TB", size); | ||||
| 			break; | ||||
| 		case 5: | ||||
| 			snprintf(buf, len, INT64_FORMAT "PB", size); | ||||
| 			break; | ||||
| 		default: | ||||
| 			strncpy(buf, "***", len); | ||||
| 			break; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static TimeLineID | ||||
| get_parent_tli(TimeLineID child_tli) | ||||
| { | ||||
| 	TimeLineID	result = 0; | ||||
| 	char		path[MAXPGPATH]; | ||||
| 	char		fline[MAXPGPATH]; | ||||
| 	FILE	   *fd; | ||||
|  | ||||
| 	/* search from timeline history dir */ | ||||
| 	snprintf(path, lengthof(path), "%s/%s/%08X.history", backup_path, | ||||
| 		TIMELINE_HISTORY_DIR, child_tli); | ||||
| 	fd = fopen(path, "rt"); | ||||
| 	if (fd == NULL) | ||||
| 	{ | ||||
| 		if (errno != ENOENT) | ||||
| 			elog(ERROR_SYSTEM, _("could not open file \"%s\": %s"), path, | ||||
| 				strerror(errno)); | ||||
|  | ||||
| 		return 0; | ||||
| 	} | ||||
|  | ||||
| 	/* | ||||
| 	 * Parse the file... | ||||
| 	 */ | ||||
| 	while (fgets(fline, sizeof(fline), fd) != NULL) | ||||
| 	{ | ||||
| 		/* skip leading whitespace and check for # comment */ | ||||
| 		char	   *ptr; | ||||
| 		char	   *endptr; | ||||
|  | ||||
| 		for (ptr = fline; *ptr; ptr++) | ||||
| 		{ | ||||
| 			if (!IsSpace(*ptr)) | ||||
| 				break; | ||||
| 		} | ||||
| 		if (*ptr == '\0' || *ptr == '#') | ||||
| 			continue; | ||||
|  | ||||
| 		/* expect a numeric timeline ID as first field of line */ | ||||
| 		result = (TimeLineID) strtoul(ptr, &endptr, 0); | ||||
| 		if (endptr == ptr) | ||||
| 			elog(ERROR_CORRUPTED, | ||||
| 					_("syntax error(timeline ID) in history file: %s"), | ||||
| 					fline); | ||||
| 	} | ||||
|  | ||||
| 	fclose(fd); | ||||
|  | ||||
| 	/* TLI of the last line is parent TLI */ | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| static void | ||||
| show_backup_list(FILE *out, parray *backup_list, bool show_all) | ||||
| { | ||||
| 	int i; | ||||
|  | ||||
| 	/* show header */ | ||||
| 	fputs("============================================================================\n", out); | ||||
| 	fputs("Start                Time   Total    Data     WAL     Log  Backup   Status  \n", out); | ||||
| 	fputs("============================================================================\n", out); | ||||
|  | ||||
| 	for (i = 0; i < parray_num(backup_list); i++) | ||||
| 	{ | ||||
| 		pgBackup *backup; | ||||
| 		char timestamp[20]; | ||||
| 		char duration[20] = "----"; | ||||
| 		char total_data_bytes_str[10] = "----"; | ||||
| 		char read_data_bytes_str[10] = "----"; | ||||
| 		char read_arclog_bytes_str[10] = "----"; | ||||
| 		char read_srvlog_bytes_str[10] = "----"; | ||||
| 		char write_bytes_str[10]; | ||||
|  | ||||
| 		backup = parray_get(backup_list, i); | ||||
|  | ||||
| 		/* skip deleted backup */ | ||||
| 		if (backup->status == BACKUP_STATUS_DELETED && !show_all) | ||||
| 			continue; | ||||
|  | ||||
| 		time2iso(timestamp, lengthof(timestamp), backup->start_time); | ||||
| 		if (backup->end_time != (time_t) 0) | ||||
| 			snprintf(duration, lengthof(duration), "%lum", | ||||
| 				(backup->end_time - backup->start_time) / 60); | ||||
| 		/* "Full" is only for full backup */ | ||||
| 		if (backup->backup_mode >= BACKUP_MODE_FULL) | ||||
| 			pretty_size(backup->total_data_bytes, total_data_bytes_str, | ||||
| 					lengthof(total_data_bytes_str)); | ||||
| 		else if (backup->backup_mode >= BACKUP_MODE_INCREMENTAL) | ||||
| 			pretty_size(backup->read_data_bytes, read_data_bytes_str, | ||||
| 					lengthof(read_data_bytes_str)); | ||||
| 		if (HAVE_ARCLOG(backup)) | ||||
| 			pretty_size(backup->read_arclog_bytes, read_arclog_bytes_str, | ||||
| 					lengthof(read_arclog_bytes_str)); | ||||
| 		if (backup->with_serverlog) | ||||
| 			pretty_size(backup->read_srvlog_bytes, read_srvlog_bytes_str, | ||||
| 					lengthof(read_srvlog_bytes_str)); | ||||
| 		pretty_size(backup->write_bytes, write_bytes_str, | ||||
| 				lengthof(write_bytes_str)); | ||||
|  | ||||
| 		fprintf(out, "%-19s %5s  %6s  %6s  %6s  %6s  %6s   %s\n", | ||||
| 			timestamp, duration, | ||||
| 			total_data_bytes_str, read_data_bytes_str, read_arclog_bytes_str, | ||||
| 			read_srvlog_bytes_str, write_bytes_str, status2str(backup->status)); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void | ||||
| show_timeline_backup_list(FILE *out, parray *backup_list, bool show_all) | ||||
| { | ||||
| 	int		i; | ||||
|  | ||||
| 	/* show header */ | ||||
| 	fputs("============================================================\n", out); | ||||
| 	fputs("Start                Mode  Current TLI  Parent TLI  Status  \n", out); | ||||
| 	fputs("============================================================\n", out); | ||||
|  | ||||
| 	for (i = 0; i < parray_num(backup_list); i++) | ||||
| 	{ | ||||
| 		static const char *modes[] = { "", "ARCH", "INCR", "FULL"}; | ||||
|  | ||||
| 		pgBackup *backup; | ||||
| 		char timestamp[20]; | ||||
| 		TimeLineID	parent_tli; | ||||
|  | ||||
| 		backup = parray_get(backup_list, i); | ||||
|  | ||||
| 		/* skip deleted backup and serverlog backup */ | ||||
| 		if ((backup->status == BACKUP_STATUS_DELETED || !HAVE_ARCLOG(backup)) && | ||||
| 			!show_all) | ||||
| 			continue; | ||||
|  | ||||
| 		time2iso(timestamp, lengthof(timestamp), backup->start_time); | ||||
|  | ||||
| 		parent_tli = get_parent_tli(backup->tli); | ||||
|  | ||||
| 		fprintf(out, "%-19s  %-4s   %10d  %10d  %s\n", | ||||
| 			timestamp, modes[backup->backup_mode], backup->tli, parent_tli, | ||||
| 			status2str(backup->status)); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void | ||||
| show_backup_detail(FILE *out, pgBackup *backup) | ||||
| { | ||||
| 	pgBackupWriteConfigSection(out, backup); | ||||
| 	pgBackupWriteResultSection(out, backup); | ||||
| } | ||||
							
								
								
									
										3
									
								
								sql/backup.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								sql/backup.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| \! pg_rman backup --verbose | ||||
| \! pg_rman backup -B data/sample_backup --verbose | ||||
|  | ||||
							
								
								
									
										274
									
								
								sql/backup_restore.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										274
									
								
								sql/backup_restore.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,274 @@ | ||||
| #!/bin/sh | ||||
|  | ||||
| BASE_PATH=`pwd` | ||||
| export PGDATA=$BASE_PATH/results/sample_database | ||||
|  | ||||
| export BACKUP_PATH=$BASE_PATH/results/sample_backup2 | ||||
| export ARCLOG_PATH=$BASE_PATH/results/arclog | ||||
| export SRVLOG_PATH=$PGDATA/pg_log | ||||
| export COMPRESS_DATA=YES | ||||
| XLOG_PATH=$PGDATA/pg_xlog | ||||
| TBLSPC_PATH=$BASE_PATH/results/tblspc | ||||
|  | ||||
| # Port used for test database cluster | ||||
| TEST_PGPORT=54321 | ||||
|  | ||||
| # configuration | ||||
| SCALE=1 | ||||
| DURATION=10 | ||||
| ISOLATE_SRVLOG=0 | ||||
| ISOLATE_WAL=0 | ||||
|  | ||||
| while [ $# -gt 0 ]; do | ||||
| 	case $1 in | ||||
| 		"-s") | ||||
| 			ISOLATE_SRVLOG=1 | ||||
| 			shift | ||||
| 			;; | ||||
| 		"-w") | ||||
| 			ISOLATE_WAL=1 | ||||
| 			shift | ||||
| 			;; | ||||
| 		"-d") | ||||
| 			DURATION=`expr $2 + 0` | ||||
| 			if [ $? -ne 0 ]; then | ||||
| 				echo "invalid duration" | ||||
| 				exit 1 | ||||
| 			fi | ||||
| 			shift 2 | ||||
| 			;; | ||||
| 		"-s") | ||||
| 			SCALE=`expr $2 + 0` | ||||
| 			if [ $? -ne 0 ]; then | ||||
| 				echo "invalid scale" | ||||
| 				exit 1 | ||||
| 			fi | ||||
| 			shift 2 | ||||
| 			;; | ||||
| 		*) | ||||
| 			shift | ||||
| 			;; | ||||
| 	esac | ||||
| done | ||||
|  | ||||
| # delete old database cluster | ||||
| pg_ctl stop -m immediate > /dev/null 2>&1 | ||||
| rm -rf $PGDATA | ||||
| rm -rf $BASE_PATH/results/pg_xlog | ||||
| rm -rf $BASE_PATH/results/srvlog | ||||
| rm -rf $ARCLOG_PATH | ||||
| rm -rf $SRVLOG_PATH | ||||
| rm -rf $TBLSPC_PATH | ||||
|  | ||||
| # create new backup catalog | ||||
| rm -rf $BACKUP_PATH | ||||
| pg_rman init -B $BACKUP_PATH --quiet | ||||
|  | ||||
| # create default configuration file | ||||
| cat << EOF > $BACKUP_PATH/pg_rman.ini | ||||
|   # comment | ||||
|   BACKUP_MODE = F # comment | ||||
| EOF | ||||
|  | ||||
| # create new database cluster | ||||
| initdb --no-locale > $BASE_PATH/results/initdb.log 2>&1 | ||||
| cat << EOF >> $PGDATA/postgresql.conf | ||||
| port = $TEST_PGPORT | ||||
| logging_collector = on | ||||
| archive_mode = on | ||||
| archive_command = 'cp "%p" "$ARCLOG_PATH/%f"' | ||||
| EOF | ||||
|  | ||||
| mkdir -p $ARCLOG_PATH | ||||
| mkdir -p $TBLSPC_PATH | ||||
|  | ||||
| # determine serverlog directory | ||||
| if [ "$ISOLATE_SRVLOG" -ne 0 ]; then | ||||
| 	export SRVLOG_PATH=$BASE_PATH/results/srvlog | ||||
| 	echo "log_directory = '$SRVLOG_PATH'" >> $PGDATA/postgresql.conf | ||||
| 	mkdir -p $SRVLOG_PATH | ||||
| else | ||||
| 	export SRVLOG_PATH=$PGDATA/pg_log | ||||
| 	echo "log_directory = 'pg_log'" >> $PGDATA/postgresql.conf | ||||
| fi | ||||
|  | ||||
| # isolate online WAL | ||||
| if [ "$ISOLATE_WAL" -ne 0 ]; then | ||||
| 	XLOG_PATH=$BASE_PATH/results/pg_xlog | ||||
| 	mv $PGDATA/pg_xlog $XLOG_PATH | ||||
| 	ln -s $XLOG_PATH $PGDATA/pg_xlog | ||||
| fi | ||||
|  | ||||
| # start PostgreSQL | ||||
| pg_ctl start -w -t 3600 > /dev/null 2>&1 | ||||
|  | ||||
| # create tablespace and database for pgbench | ||||
| mkdir -p $TBLSPC_PATH/pgbench | ||||
| psql -p $TEST_PGPORT postgres <<EOF | ||||
| CREATE TABLESPACE pgbench LOCATION '$TBLSPC_PATH/pgbench'; | ||||
| CREATE DATABASE pgbench TABLESPACE = pgbench; | ||||
| EOF | ||||
|  | ||||
| # data_delete | ||||
| export KEEP_DATA_GENERATIONS=2 | ||||
| export KEEP_DATA_DAYS=0 | ||||
| for i in `seq 1 5`; do | ||||
| 	pg_rman -p $TEST_PGPORT backup --verbose -d postgres > $BASE_PATH/results/log_full_0_$i 2>&1 | ||||
| done | ||||
| pg_rman -p $TEST_PGPORT show `date +%Y` -a --verbose -d postgres > $BASE_PATH/results/log_show_d_1 2>&1 | ||||
| echo "# of deleted backups" | ||||
| grep -c DELETED $BASE_PATH/results/log_show_d_1 | ||||
|  | ||||
| pgbench -p $TEST_PGPORT -i -s $SCALE pgbench > $BASE_PATH/results/pgbench.log 2>&1 | ||||
|  | ||||
| echo "full database backup" | ||||
| psql -p $TEST_PGPORT postgres -c "checkpoint" | ||||
| pg_rman -p $TEST_PGPORT backup --verbose -d postgres > $BASE_PATH/results/log_full_1 2>&1 | ||||
|  | ||||
| pgbench -p $TEST_PGPORT -T $DURATION -c 10 pgbench >> $BASE_PATH/results/pgbench.log 2>&1 | ||||
| echo "incremental database backup" | ||||
| psql -p $TEST_PGPORT postgres -c "checkpoint" | ||||
| pg_rman -p $TEST_PGPORT backup -b i --verbose -d postgres > $BASE_PATH/results/log_incr1 2>&1 | ||||
|  | ||||
| # validate all backup | ||||
| pg_rman validate `date +%Y` --verbose > $BASE_PATH/results/log_validate1 2>&1 | ||||
| pg_rman -p $TEST_PGPORT show `date +%Y` -a --verbose -d postgres > $BASE_PATH/results/log_show0 2>&1 | ||||
| pg_dumpall > $BASE_PATH/results/dump_before_rtx.sql | ||||
| target_xid=`psql -p $TEST_PGPORT pgbench -tAq -c "INSERT INTO pgbench_history VALUES (1) RETURNING(xmin);"` | ||||
| psql -p $TEST_PGPORT postgres -c "checkpoint" | ||||
| pg_rman -p $TEST_PGPORT backup -b i --verbose -d postgres > $BASE_PATH/results/log_incr2 2>&1 | ||||
|  | ||||
| pgbench -p $TEST_PGPORT -T $DURATION -c 10 pgbench >> $BASE_PATH/results/pgbench.log 2>&1 | ||||
| echo "archived WAL and serverlog backup" | ||||
| pg_rman -p $TEST_PGPORT backup -b a --verbose -d postgres > $BASE_PATH/results/log_arclog 2>&1 | ||||
|  | ||||
| # stop PG during transaction and get commited info for verifing | ||||
| echo "stop DB during running pgbench" | ||||
| pgbench -p $TEST_PGPORT -T $DURATION -c 10 pgbench >> $BASE_PATH/results/pgbench.log 2>&1 & | ||||
| sleep `expr $DURATION / 2` | ||||
| pg_ctl stop -m immediate > /dev/null 2>&1 | ||||
| cp -rp $PGDATA $PGDATA.bak | ||||
| pg_ctl start -w -t 3600 > /dev/null 2>&1 | ||||
| pg_dumpall > $BASE_PATH/results/dump_before.sql | ||||
|  | ||||
| # revert to crushed cluster | ||||
| pg_ctl stop > /dev/null 2>&1 | ||||
| rm -rf $PGDATA | ||||
| mv $PGDATA.bak $PGDATA | ||||
|  | ||||
| # validate all backup | ||||
| pg_rman validate `date +%Y` --verbose > $BASE_PATH/results/log_validate2 2>&1 | ||||
|  | ||||
| # restore check with pg_rman | ||||
| pg_rman restore -! --verbose --check > $BASE_PATH/results/log_restore_check_1 2>&1 | ||||
|  | ||||
| # restore with pg_rman | ||||
| CUR_TLI=`pg_controldata | grep TimeLineID | awk '{print $4}'` | ||||
| pg_rman restore -! --verbose > $BASE_PATH/results/log_restore1_1 2>&1 | ||||
| CUR_TLI_R=`grep "current timeline ID = " $BASE_PATH/results/log_restore1_1 | awk '{print $5}'` | ||||
| TARGET_TLI=`grep "target timeline ID = " $BASE_PATH/results/log_restore1_1 | awk '{print $5}'` | ||||
| if [ "$CUR_TLI" != "$CUR_TLI_R" -o "$CUR_TLI" != "$CUR_TLI_R" ]; then | ||||
| 	echo "failed: bad timeline ID" CUR_TLI=$CUR_TLI CUR_TLI_R=$CUR_TLI_R | ||||
| fi | ||||
|  | ||||
| # Backup of online-WAL and serverlog. | ||||
| echo "diff files in BACKUP_PATH/backup/pg_xlog" | ||||
| diff -r $PGDATA/pg_xlog $BACKUP_PATH/backup/pg_xlog | ||||
| echo "# of files in BACKUP_PATH/backup/srvlog" | ||||
| find $BACKUP_PATH/backup/srvlog -type f | wc -l | ||||
|  | ||||
| # recovery database | ||||
| pg_ctl start -w -t 3600 > /dev/null 2>&1 | ||||
|  | ||||
| # re-restore with pg_rman | ||||
| pg_ctl stop -m immediate > /dev/null 2>&1 | ||||
|  | ||||
| # restore check with pg_rman | ||||
| pg_rman restore -! --verbose --check > $BASE_PATH/results/log_restore_check_2 2>&1 | ||||
|  | ||||
| CUR_TLI=`pg_controldata | grep TimeLineID | awk '{print $4}'` | ||||
| pg_rman restore -! --verbose > $BASE_PATH/results/log_restore1_2 2>&1 | ||||
| CUR_TLI_R=`grep "current timeline ID = " $BASE_PATH/results/log_restore1_2 | awk '{print $5}'` | ||||
| TARGET_TLI=`grep "target timeline ID = " $BASE_PATH/results/log_restore1_2 | awk '{print $5}'` | ||||
| if [ "$CUR_TLI" != "$CUR_TLI_R" -o "$CUR_TLI" != "$CUR_TLI_R" ]; then | ||||
| 	echo "failed: bad timeline ID" CUR_TLI=$CUR_TLI CUR_TLI_R=$CUR_TLI_R | ||||
| fi | ||||
|  | ||||
| # Backup of online-WAL and serverlog. | ||||
| echo "diff files in BACKUP_PATH/backup/pg_xlog" | ||||
| diff -r $PGDATA/pg_xlog $BACKUP_PATH/backup/pg_xlog | ||||
| echo "# of files in BACKUP_PATH/backup/srvlog" | ||||
| find $BACKUP_PATH/backup/srvlog -type f | wc -l | ||||
|  | ||||
| # re-recovery database | ||||
| pg_ctl start -w -t 3600 > /dev/null 2>&1 | ||||
|  | ||||
| # compare recovery results | ||||
| pg_dumpall > $BASE_PATH/results/dump_after.sql | ||||
| diff $BASE_PATH/results/dump_before.sql $BASE_PATH/results/dump_after.sql | ||||
|  | ||||
| # take a backup and delete backed up online files | ||||
| # incrementa backup can't find last full backup because new timeline started. | ||||
| echo "full database backup after recovery" | ||||
| psql -p $TEST_PGPORT postgres -c "checkpoint" | ||||
| pg_rman -p $TEST_PGPORT backup -b f --verbose -d postgres > $BASE_PATH/results/log_full2 2>&1 | ||||
|  | ||||
| # Backup of online-WAL should been deleted, but serverlog remain. | ||||
| echo "# of files in BACKUP_PATH/backup/pg_xlog" | ||||
| find $BACKUP_PATH/backup/pg_xlog -type f | wc -l | ||||
| echo "# of files in BACKUP_PATH/backup/srvlog" | ||||
| find $BACKUP_PATH/backup/srvlog -type f | wc -l | ||||
|  | ||||
| # Symbolic links in $ARCLOG_PATH should be deleted. | ||||
| echo "# of symbolic links in ARCLOG_PATH" | ||||
| find $ARCLOG_PATH -type l | wc -l | ||||
|  | ||||
| # timeline history files are backed up. | ||||
| echo "# of files in BACKUP_PATH/timeline_history" | ||||
| find $BACKUP_PATH/timeline_history -type f | wc -l | ||||
|  | ||||
| # restore with pg_rman | ||||
| pg_ctl stop -m immediate > /dev/null 2>&1 | ||||
|  | ||||
| # restore check with pg_rman | ||||
| pg_rman restore -! --verbose --check > $BASE_PATH/results/log_restore_check_3 2>&1 | ||||
|  | ||||
| CUR_TLI=`pg_controldata | grep TimeLineID | awk '{print $4}'` | ||||
| pg_rman restore -! --recovery-target-xid $target_xid --recovery-target-inclusive false --verbose > $BASE_PATH/results/log_restore2 2>&1 | ||||
| CUR_TLI_R=`grep "current timeline ID = " $BASE_PATH/results/log_restore2 | awk '{print $5}'` | ||||
| TARGET_TLI=`grep "target timeline ID = " $BASE_PATH/results/log_restore2 | awk '{print $5}'` | ||||
| if [ "$CUR_TLI" != "$CUR_TLI_R" -o "$CUR_TLI" != "$CUR_TLI_R" ]; then | ||||
| 	echo "failed: bad timeline ID" CUR_TLI=$CUR_TLI CUR_TLI_R=$CUR_TLI_R | ||||
| fi | ||||
| echo "# of recovery target option in recovery.conf" | ||||
| grep -c "recovery_target_" $PGDATA/recovery.conf | ||||
|  | ||||
| # recovery database | ||||
| pg_ctl start -w -t 3600 > /dev/null 2>&1 | ||||
|  | ||||
| pg_dumpall > $BASE_PATH/results/dump_after_rtx.sql | ||||
| diff $BASE_PATH/results/dump_before_rtx.sql $BASE_PATH/results/dump_after_rtx.sql | ||||
|  | ||||
| # show timeline | ||||
| pg_rman -p $TEST_PGPORT show timeline --verbose -a -d postgres > $BASE_PATH/results/log_show_timeline_1 2>&1 | ||||
| pg_rman -p $TEST_PGPORT show timeline `date +%Y` -a --verbose -d postgres > $BASE_PATH/results/log_show_timeline_2 2>&1 | ||||
| pg_rman -p $TEST_PGPORT show timeline `date +%Y` --verbose -d postgres > $BASE_PATH/results/log_show_timeline_3 2>&1 | ||||
| echo "# of deleted backups (show all)" | ||||
| grep -c DELETED $BASE_PATH/results/log_show_timeline_2 | ||||
| echo "# of deleted backups" | ||||
| grep -c DELETED $BASE_PATH/results/log_show_timeline_3 | ||||
|  | ||||
| echo "delete backup" | ||||
| pg_rman -p $TEST_PGPORT delete --debug -d postgres > $BASE_PATH/results/log_delete1 2>&1 | ||||
| pg_rman -p $TEST_PGPORT show `date +%Y` -a --verbose -d postgres > $BASE_PATH/results/log_show1 2>&1 | ||||
| echo "# of deleted backups" | ||||
| grep -c DELETED $BASE_PATH/results/log_show1 | ||||
| pg_rman -p $TEST_PGPORT delete `date "+%Y-%m-%d %T"` --debug -d postgres > $BASE_PATH/results/log_delete2 2>&1 | ||||
| pg_rman -p $TEST_PGPORT show `date +%Y` -a --verbose -d postgres > $BASE_PATH/results/log_show2 2>&1 | ||||
| echo "# of deleted backups" | ||||
| grep -c DELETED $BASE_PATH/results/log_show2 | ||||
| pg_rman -p $TEST_PGPORT show timeline `date +%Y` -a --verbose -d postgres > $BASE_PATH/results/log_show_timeline_4 2>&1 | ||||
|  | ||||
| # cleanup | ||||
| pg_ctl stop -m immediate > /dev/null 2>&1 | ||||
|  | ||||
							
								
								
									
										2
									
								
								sql/backup_restore.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								sql/backup_restore.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| \! sh sql/backup_restore.sh | ||||
|  | ||||
							
								
								
									
										4
									
								
								sql/init.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								sql/init.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| \! rm -rf results/init_test | ||||
| \! pg_rman init -B results/init_test --quiet;echo $? | ||||
| \! find results/init_test | xargs ls -Fd | sort | ||||
| \! pg_rman init -B results/init_test --quiet;echo $? | ||||
							
								
								
									
										80
									
								
								sql/option.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								sql/option.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | ||||
| #!/bin/sh | ||||
|  | ||||
| #============================================================================ | ||||
| # This is a test script for option test of pg_rman. | ||||
| #============================================================================ | ||||
|  | ||||
| BASE_PATH=`pwd` | ||||
|  | ||||
| # Clear environment variables used by pg_rman except $PGDATA. | ||||
| # List of environment variables is defined in catalog.c. | ||||
| unset BACKUP_PATH | ||||
| unset ARCLOG_PATH | ||||
| unset SRVLOG_PATH | ||||
| unset BACKUP_MODE | ||||
| unset COMPRESS_DATA | ||||
| unset KEEP_ARCLOG_DAYS | ||||
| unset KEEP_DATA_GENERATIONS | ||||
| unset KEEP_DATA_DAYS | ||||
| unset KEEP_SRVLOG_FILES | ||||
| unset KEEP_SRVLOG_DAYS | ||||
|  | ||||
| export PGDATA=$BASE_PATH/results/sample_database | ||||
|  | ||||
| # Note: not exported | ||||
| BACKUP_PATH=$BASE_PATH/results/sample_backup2 | ||||
|  | ||||
| # Setup backup catalog for backup test. | ||||
| rm -rf $BACKUP_PATH | ||||
| cp -rp data/sample_backup $BACKUP_PATH | ||||
|  | ||||
| # general option | ||||
| pg_rman --help | ||||
| pg_rman --version | ||||
|  | ||||
| # backup option | ||||
| # required arguments check | ||||
| pg_rman backup --verbose | ||||
| pg_rman backup --verbose -B $BACKUP_PATH | ||||
| pg_rman backup --verbose -B $BACKUP_PATH -b f | ||||
| pg_rman backup --verbose -B $BACKUP_PATH -b i | ||||
| pg_rman backup --verbose -B $BACKUP_PATH -b a | ||||
|  | ||||
| # bad arguments check | ||||
| pg_rman backup --verbose -B $BACKUP_PATH -b bad | ||||
|  | ||||
| # delete or validate requires DATE | ||||
| pg_rman delete -B $BACKUP_PATH | ||||
| pg_rman validate -B $BACKUP_PATH | ||||
|  | ||||
| # invalid configuration file check | ||||
| echo " = INFINITE" > $BACKUP_PATH/pg_rman.ini | ||||
| pg_rman backup --verbose -B $BACKUP_PATH | ||||
| echo "BACKUP_MODE= " > $BACKUP_PATH/pg_rman.ini | ||||
| pg_rman backup --verbose -B $BACKUP_PATH | ||||
| echo "BACKUP_MODE = F#S" > $BACKUP_PATH/pg_rman.ini | ||||
| pg_rman backup --verbose -B $BACKUP_PATH | ||||
| echo "BACKUP_MODE = F #comment A" > $BACKUP_PATH/pg_rman.ini | ||||
| pg_rman backup --verbose -B $BACKUP_PATH | ||||
| echo "BACKUP_MODE=B" > $BACKUP_PATH/pg_rman.ini | ||||
| pg_rman backup --verbose -B $BACKUP_PATH | ||||
| echo "COMPRESS_DATA=FOO" > $BACKUP_PATH/pg_rman.ini | ||||
| pg_rman backup --verbose -B $BACKUP_PATH | ||||
| echo "KEEP_ARCLOG_FILES=YES" > $BACKUP_PATH/pg_rman.ini | ||||
| pg_rman backup --verbose -B $BACKUP_PATH | ||||
| echo "TIMELINEID=-1" > $BACKUP_PATH/pg_rman.ini | ||||
| pg_rman backup --verbose -B $BACKUP_PATH | ||||
| echo "BACKUP_TARGETS=F" > $BACKUP_PATH/pg_rman.ini | ||||
| pg_rman backup --verbose -B $BACKUP_PATH | ||||
| echo "BACKUP_MODE='F''\'\\\F'" > $BACKUP_PATH/pg_rman.ini | ||||
| pg_rman backup --verbose -B $BACKUP_PATH | ||||
|  | ||||
| # configuration priorityfile check | ||||
| echo "BACKUP_MODE=ENV_PATH" > $BACKUP_PATH/pg_rman.ini | ||||
| mkdir $BACKUP_PATH/conf_path | ||||
| echo "BACKUP_PATH=$BACKUP_PATH/conf_path" > $BACKUP_PATH/pg_rman.conf | ||||
| echo "BACKUP_MODE=CONF_PATH" > $BACKUP_PATH/conf_path/pg_rman.ini | ||||
| mkdir $BACKUP_PATH/comm_path | ||||
| echo "BACKUP_MODE=COMM_PATH" > $BACKUP_PATH/comm_path/pg_rman.ini | ||||
| export BACKUP_PATH=$BACKUP_PATH | ||||
| pg_rman backup --verbose | ||||
							
								
								
									
										1
									
								
								sql/option.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								sql/option.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| \! sh sql/option.sh | ||||
							
								
								
									
										8
									
								
								sql/show_validate.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								sql/show_validate.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| -- test show command | ||||
| \! rm -rf results/sample_backup | ||||
| \! cp -rp data/sample_backup results/sample_backup | ||||
| \! pg_rman show -B results/sample_backup | ||||
| \! pg_rman validate -B results/sample_backup 2009-05-31 17:05:53 --debug | ||||
| \! pg_rman validate -B results/sample_backup 2009-06-01 17:05:53 --debug | ||||
| \! pg_rman show -a -B results/sample_backup | ||||
| \! pg_rman show 2009-06-01 17:05:53 -B results/sample_backup | ||||
							
								
								
									
										85
									
								
								util.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								util.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * util.c: log messages to log file or stderr, and misc code. | ||||
|  * | ||||
|  * Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #include "pg_rman.h" | ||||
|  | ||||
| #include <time.h> | ||||
|  | ||||
| /* | ||||
|  * Convert time_t value to ISO-8601 format string | ||||
|  */ | ||||
| void | ||||
| time2iso(char *buf, size_t len, time_t time) | ||||
| { | ||||
| 	struct tm *tm = localtime(&time); | ||||
|  | ||||
| 	if (time == (time_t) 0) | ||||
| 		strncpy(buf,"****-**-** **:**:**", len); | ||||
| 	else | ||||
| 		strftime(buf, len, "%Y-%m-%d %H:%M:%S", tm); | ||||
| } | ||||
|  | ||||
| const char * | ||||
| status2str(BackupStatus status) | ||||
| { | ||||
| 	static const char *statusName[] = | ||||
| 	{ | ||||
| 		"UNKNOWN", | ||||
| 		"OK", | ||||
| 		"RUNNING", | ||||
| 		"ERROR", | ||||
| 		"DELETING", | ||||
| 		"DELETED", | ||||
| 		"DONE", | ||||
| 		"CORRUPT" | ||||
| 	}; | ||||
|  | ||||
| 	if (status < BACKUP_STATUS_INVALID || BACKUP_STATUS_CORRUPT < status) | ||||
| 		return "UNKNOWN"; | ||||
|  | ||||
| 	return statusName[status]; | ||||
| } | ||||
|  | ||||
| void | ||||
| remove_trailing_space(char *buf, int comment_mark) | ||||
| { | ||||
| 	int		i; | ||||
| 	char   *last_char = NULL; | ||||
|  | ||||
| 	for (i = 0; buf[i]; i++) | ||||
| 	{ | ||||
| 		if (buf[i] == comment_mark || buf[i] == '\n' || buf[i] == '\r') | ||||
| 		{ | ||||
| 			buf[i] = '\0'; | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| 	for (i = 0; buf[i]; i++) | ||||
| 	{ | ||||
| 		if (!isspace(buf[i])) | ||||
| 			last_char = buf + i; | ||||
| 	} | ||||
| 	if (last_char != NULL) | ||||
| 		*(last_char + 1) = '\0'; | ||||
|  | ||||
| } | ||||
|  | ||||
| void | ||||
| remove_not_digit(char *buf, size_t len, const char *str) | ||||
| { | ||||
| 	int i, j; | ||||
|  | ||||
| 	for (i = 0, j = 0; str[i] && j < len; i++) | ||||
| 	{ | ||||
| 		if (!isdigit(str[i])) | ||||
| 			continue; | ||||
| 		buf[j++] = str[i]; | ||||
| 	} | ||||
| 	buf[j] = '\0'; | ||||
| } | ||||
							
								
								
									
										183
									
								
								validate.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								validate.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,183 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * validate.c: validate backup files. | ||||
|  * | ||||
|  * Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #include "pg_rman.h" | ||||
|  | ||||
| #include <sys/stat.h> | ||||
|  | ||||
| static bool pgBackupValidateFiles(parray *files, const char *root, bool size_only); | ||||
|  | ||||
| /* | ||||
|  * Validate files in the backup and update its status to OK. | ||||
|  * If any of files are corrupted, update its stutus to CORRUPT. | ||||
|  */ | ||||
| int | ||||
| do_validate(pgBackupRange *range) | ||||
| { | ||||
| 	int i; | ||||
| 	parray *backup_list; | ||||
|  | ||||
| 	catalog_lock(); | ||||
|  | ||||
| 	/* get backup list matches given range */ | ||||
| 	backup_list = catalog_get_backup_list(range); | ||||
| 	parray_qsort(backup_list, pgBackupCompareId); | ||||
| 	for (i = 0; i < parray_num(backup_list); i++) | ||||
| 	{ | ||||
| 		pgBackup *backup = (pgBackup *)parray_get(backup_list, i); | ||||
|  | ||||
| 		/* Validate completed backups only. */ | ||||
| 		if (backup->status != BACKUP_STATUS_DONE) | ||||
| 			continue; | ||||
|  | ||||
| 		/* validate with CRC value and update status to OK */ | ||||
| 		pgBackupValidate(backup, false); | ||||
| 	} | ||||
|  | ||||
| 	/* cleanup */ | ||||
| 	parray_walk(backup_list, pgBackupFree); | ||||
| 	parray_free(backup_list); | ||||
|  | ||||
| 	catalog_unlock(); | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Validate each files in the backup with its size. | ||||
|  */ | ||||
| void | ||||
| pgBackupValidate(pgBackup *backup, bool size_only) | ||||
| { | ||||
| 	char	timestamp[100]; | ||||
| 	char	base_path[MAXPGPATH]; | ||||
| 	char	path[MAXPGPATH]; | ||||
| 	parray *files; | ||||
| 	bool	corrupted = false; | ||||
|  | ||||
| 	time2iso(timestamp, lengthof(timestamp), backup->start_time); | ||||
| 	elog(INFO, "validate: %s", timestamp); | ||||
|  | ||||
| 	if (HAVE_DATABASE(backup)) | ||||
| 	{ | ||||
| 		elog(LOG, "database files..."); | ||||
| 		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); | ||||
| 		if (!pgBackupValidateFiles(files, base_path, size_only)) | ||||
| 			corrupted = true; | ||||
| 		parray_walk(files, pgFileFree); | ||||
| 		parray_free(files); | ||||
| 	} | ||||
| 	if (HAVE_ARCLOG(backup)) | ||||
| 	{ | ||||
| 		elog(LOG, "archive WAL files..."); | ||||
| 		pgBackupGetPath(backup, base_path, lengthof(base_path), ARCLOG_DIR); | ||||
| 		pgBackupGetPath(backup, path, lengthof(path), ARCLOG_FILE_LIST); | ||||
| 		files = dir_read_file_list(base_path, path); | ||||
| 		if (!pgBackupValidateFiles(files, base_path, size_only)) | ||||
| 			corrupted = true; | ||||
| 		parray_walk(files, pgFileFree); | ||||
| 		parray_free(files); | ||||
| 	} | ||||
| 	if (backup->with_serverlog) | ||||
| 	{ | ||||
| 		elog(LOG, "server log files..."); | ||||
| 		pgBackupGetPath(backup, base_path, lengthof(base_path), SRVLOG_DIR); | ||||
| 		pgBackupGetPath(backup, path, lengthof(path), SRVLOG_FILE_LIST); | ||||
| 		files = dir_read_file_list(base_path, path); | ||||
| 		if (!pgBackupValidateFiles(files, base_path, size_only)) | ||||
| 			corrupted = true; | ||||
| 		parray_walk(files, pgFileFree); | ||||
| 		parray_free(files); | ||||
| 	} | ||||
|  | ||||
| 	/* update status to OK */ | ||||
| 	if (corrupted) | ||||
| 		backup->status = BACKUP_STATUS_CORRUPT; | ||||
| 	else | ||||
| 		backup->status = BACKUP_STATUS_OK; | ||||
| 	pgBackupWriteIni(backup); | ||||
|  | ||||
| 	if (corrupted) | ||||
| 		elog(WARNING, "backup %s is corrupted", timestamp); | ||||
| 	else | ||||
| 		elog(LOG, "backup %s is valid", timestamp); | ||||
| } | ||||
|  | ||||
| static const char * | ||||
| get_relative_path(const char *path, const char *root) | ||||
| { | ||||
| 	size_t	rootlen = strlen(root); | ||||
| 	if (strncmp(path, root, rootlen) == 0 && path[rootlen] == '/') | ||||
| 		return path + rootlen + 1; | ||||
| 	else | ||||
| 		return path; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Validate files in the backup with size or CRC. | ||||
|  */ | ||||
| static bool | ||||
| pgBackupValidateFiles(parray *files, const char *root, bool size_only) | ||||
| { | ||||
| 	int		i; | ||||
|  | ||||
| 	for (i = 0; i < parray_num(files); i++) | ||||
| 	{ | ||||
| 		struct stat st; | ||||
| 		pgFile *file = (pgFile *) parray_get(files, i); | ||||
|  | ||||
| 		if (interrupted) | ||||
| 			elog(ERROR_INTERRUPTED, _("interrupted during validate")); | ||||
|  | ||||
| 		/* skipped backup while incremental backup */ | ||||
| 		if (file->write_size == BYTES_INVALID || !S_ISREG(file->mode)) | ||||
| 			continue; | ||||
|  | ||||
| 		/* print progress */ | ||||
| 		elog(LOG, _("(%d/%lu) %s"), i + 1, (unsigned long) parray_num(files), | ||||
| 			get_relative_path(file->path, root)); | ||||
|  | ||||
| 		/* always validate file size */ | ||||
| 		if (stat(file->path, &st) == -1) | ||||
| 		{ | ||||
| 			if (errno == ENOENT) | ||||
| 				elog(WARNING, _("backup file \"%s\" vanished"), file->path); | ||||
| 			else | ||||
| 				elog(ERROR_SYSTEM, _("can't stat backup file \"%s\": %s"), | ||||
| 					get_relative_path(file->path, root), strerror(errno)); | ||||
| 			return false; | ||||
| 		} | ||||
| 		if (file->write_size != st.st_size) | ||||
| 		{ | ||||
| 			elog(WARNING, _("size of backup file \"%s\" must be %lu but %lu"), | ||||
| 				get_relative_path(file->path, root), | ||||
| 				(unsigned long) file->write_size, | ||||
| 				(unsigned long) st.st_size); | ||||
| 			return false; | ||||
| 		} | ||||
|  | ||||
| 		/* validate CRC too */ | ||||
| 		if (!size_only) | ||||
| 		{ | ||||
| 			pg_crc32 crc; | ||||
| 			crc = pgFileGetCRC(file); | ||||
| 			if (crc != file->crc) | ||||
| 			{ | ||||
| 				elog(WARNING, _("CRC of backup file \"%s\" must be %X but %X"), | ||||
| 					get_relative_path(file->path, root), file->crc, crc); | ||||
| 				return false; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
							
								
								
									
										91
									
								
								xlog.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								xlog.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * xlog.c: Parse WAL files. | ||||
|  * | ||||
|  * Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #include "pg_rman.h" | ||||
|  | ||||
| #include <stdio.h> | ||||
| #include <sys/types.h> | ||||
| #include <sys/stat.h> | ||||
| #include <unistd.h> | ||||
|  | ||||
| #if PG_VERSION_NUM >= 80400 | ||||
| typedef unsigned long Datum; | ||||
| typedef struct MemoryContextData *MemoryContext; | ||||
| #endif | ||||
|  | ||||
| #include "access/xlog_internal.h" | ||||
|  | ||||
| /* | ||||
|  * Return whether the file is a WAL segment or not. | ||||
|  * based on ValidXLOGHeader() in src/backend/access/transam/xlog.c. | ||||
|  */ | ||||
| bool | ||||
| xlog_is_complete_wal(const pgFile *file) | ||||
| { | ||||
| 	FILE *fp; | ||||
| 	char page[XLOG_BLCKSZ]; | ||||
| 	XLogPageHeader header = (XLogPageHeader) page; | ||||
| 	XLogLongPageHeader lheader = (XLogLongPageHeader) page; | ||||
|  | ||||
| 	fp = fopen(file->path, "r"); | ||||
| 	if (!fp) | ||||
| 		return false; | ||||
| 	if (fread(page, 1, sizeof(page), fp) != XLOG_BLCKSZ) | ||||
| 	{ | ||||
| 		fclose(fp); | ||||
| 		return false; | ||||
| 	} | ||||
| 	fclose(fp); | ||||
|  | ||||
| 	/* check header */ | ||||
| 	if (header->xlp_magic != XLOG_PAGE_MAGIC) | ||||
| 		return false; | ||||
| 	if ((header->xlp_info & ~XLP_ALL_FLAGS) != 0) | ||||
| 		return false; | ||||
| 		 | ||||
| 	if (header->xlp_info & XLP_LONG_HEADER) | ||||
| 	{ | ||||
| 		if (lheader->xlp_seg_size != XLogSegSize) | ||||
| 			return false; | ||||
|  | ||||
| 		/* compressed WAL (with lesslog) has 0 in lheader->xlp_xlog_blcksz. */ | ||||
| 		if (lheader->xlp_xlog_blcksz != XLOG_BLCKSZ && | ||||
| 			lheader->xlp_xlog_blcksz != 0) | ||||
| 			return false; | ||||
| 	} | ||||
|  | ||||
| 	/* check size (actual file size, not backup file size) */ | ||||
| 	if (lheader->xlp_xlog_blcksz == XLOG_BLCKSZ && file->size != XLogSegSize) | ||||
| 		return false; | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool | ||||
| xlog_logfname2lsn(const char *logfname, XLogRecPtr *lsn) | ||||
| { | ||||
| 	uint32 tli; | ||||
| 	if (sscanf(logfname, "%08X%08X%08X", | ||||
| 			&tli, &lsn->xlogid, &lsn->xrecoff) != 3) | ||||
| 		return false; | ||||
|  | ||||
| 	lsn->xrecoff *= XLogSegSize; | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * based on XLogFileName() in xlog_internal.h | ||||
|  */ | ||||
| void | ||||
| xlog_fname(char *fname, size_t len, TimeLineID tli, XLogRecPtr *lsn) | ||||
| { | ||||
| 	snprintf(fname, len, "%08X%08X%08X", tli, | ||||
| 		lsn->xlogid, lsn->xrecoff / XLogSegSize); | ||||
| } | ||||
		Reference in New Issue
	
	Block a user