You've already forked pg_probackup
							
							
				mirror of
				https://github.com/postgrespro/pg_probackup.git
				synced 2025-10-31 00:17:52 +02:00 
			
		
		
		
	Convert newline characters from CRLF to LF.
git-svn-id: http://pg-rman.googlecode.com/svn/trunk@70 182aca00-e38e-11de-a668-6fd11605f5ce
This commit is contained in:
		
							
								
								
									
										624
									
								
								clean.c
									
									
									
									
									
								
							
							
						
						
									
										624
									
								
								clean.c
									
									
									
									
									
								
							| @@ -1,312 +1,312 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * clean.c: cleanup backup files. | ||||
|  * | ||||
|  * Copyright (c) 2009-2011, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #include "pg_rman.h" | ||||
|  | ||||
| #include <dirent.h> | ||||
| #include <unistd.h> | ||||
|  | ||||
| static void clean_backup(Database db, pgBackup *backup); | ||||
|  | ||||
| #define CLEAN_MASK		(BACKUP_MASK(BACKUP_ERROR) | BACKUP_MASK(BACKUP_BAD)) | ||||
|  | ||||
| void | ||||
| do_clean(int keep_data_generations, | ||||
| 		 int keep_data_days, | ||||
| 		 int keep_srvlog_files, | ||||
| 		 int keep_srvlog_days) | ||||
| { | ||||
| 	Database	db; | ||||
| 	List	   *backups; | ||||
| 	ListCell   *cell; | ||||
|  | ||||
| 	db = db_open(); | ||||
| 	backups = db_list_backups(db, make_range(0, NULL), CLEAN_MASK); | ||||
|  | ||||
| 	foreach (cell, backups) | ||||
| 		clean_backup(db, lfirst(cell)); | ||||
|  | ||||
| 	db_close(db); | ||||
| 	list_free_deep(backups); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Delete files of the backup and update the status to DELETED. | ||||
|  */ | ||||
| static void | ||||
| clean_backup(Database db, pgBackup *backup) | ||||
| { | ||||
| 	char		datetime[DATESTRLEN]; | ||||
| 	char		path[MAXPGPATH]; | ||||
|  | ||||
| 	elog(INFO, "clean: %s", date2str(datetime, backup->start_time)); | ||||
|  | ||||
| 	/* | ||||
| 	 * update the status to BAD before the actual deletion because abort | ||||
| 	 * during deletion could leave corrupted backup files. | ||||
| 	 */ | ||||
| 	if (backup->status != BACKUP_BAD) | ||||
| 	{ | ||||
| 		backup->status = BACKUP_BAD; | ||||
| 		db_update_status(db, backup, NIL); | ||||
| 	} | ||||
|  | ||||
| 	/* remove data files. */ | ||||
| 	make_backup_path(path, backup->start_time); | ||||
| 	remove_file(path); | ||||
|  | ||||
| 	/* update the status to DELETED */ | ||||
| 	backup->status = BACKUP_DELETED; | ||||
| 	db_update_status(db, backup, NIL); | ||||
| } | ||||
|  | ||||
| #if 0 | ||||
| /* that are older than KEEP_xxx_DAYS and have more generations | ||||
|  * than KEEP_xxx_FILES. | ||||
|  */ | ||||
| 	int		i; | ||||
| 	parray *backup_list; | ||||
| 	int		backup_num; | ||||
| 	time_t	days_threshold = current.start_time - (keep_days * 60 * 60 * 24); | ||||
|  | ||||
| 	/* cleanup 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); | ||||
|  | ||||
| /* | ||||
|  * Remove backup files  | ||||
|  */ | ||||
| void | ||||
| clean_backup(int keep_generations, int keep_days) | ||||
| { | ||||
| 	backup_num = 0; | ||||
| 	/* find cleanup 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 verify full backup was found, we can cleanup the backup | ||||
| 		 * that is older than it | ||||
| 		 */ | ||||
| 		if (backup->backup_mode >= BACKUP_MODE_FULL && | ||||
| 			backup->status == BACKUP_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 threshold and | ||||
| 		 * there are enough generations of full backups, cleanup the backup. | ||||
| 		 */ | ||||
| 		if (backup->start_time >= days_threshold) | ||||
| 		{ | ||||
| 			elog(LOG, "%s() %lu is not older than %lu", __FUNCTION__, | ||||
| 				backup->start_time, days_threshold); | ||||
| 			continue; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| static void delete_old_files(const char *root, parray *files, int keep_files, | ||||
| 							 int keep_days, int server_version, bool is_arclog); | ||||
| static void delete_arclog_link(void); | ||||
| static void delete_online_wal_backup(void); | ||||
|  | ||||
| /* | ||||
|  * Delete files modified before than KEEP_xxx_DAYS or more than KEEP_xxx_FILES | ||||
|  * of newer files exist. | ||||
|  */ | ||||
| static void | ||||
| delete_old_files(const char *root, | ||||
| 				 parray *files, | ||||
| 				 int keep_files, | ||||
| 				 int keep_days, | ||||
| 				 int server_version, | ||||
| 				 bool is_arclog) | ||||
| { | ||||
| 	int		i; | ||||
| 	int		j; | ||||
| 	int		file_num = 0; | ||||
| 	time_t	days_threshold = start_time - (keep_days * 60 * 60 * 24); | ||||
|  | ||||
| 	if (verbose) | ||||
| 	{ | ||||
| 		char files_str[100]; | ||||
| 		char days_str[100]; | ||||
|  | ||||
| 		if (keep_files == KEEP_INFINITE) | ||||
| 			strncpy(files_str, "INFINITE", lengthof(files_str)); | ||||
| 		else | ||||
| 			snprintf(files_str, lengthof(files_str), "%d", keep_files); | ||||
|  | ||||
| 		if (keep_days == KEEP_INFINITE) | ||||
| 			strncpy(days_str, "INFINITE", lengthof(days_str)); | ||||
| 		else | ||||
| 			snprintf(days_str, lengthof(days_str), "%d", keep_days); | ||||
|  | ||||
| 		printf("cleanup old files from \"%s\" (files=%s, days=%s)\n", | ||||
| 			root, files_str, days_str); | ||||
| 	} | ||||
|  | ||||
| 	/* cleanup files which satisfy both conditions */ | ||||
| 	if (keep_files == KEEP_INFINITE || keep_days == KEEP_INFINITE) | ||||
| 	{ | ||||
| 		elog(LOG, "%s() infinite", __FUNCTION__); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	parray_qsort(files, pgFileCompareMtime); | ||||
| 	for (i = parray_num(files) - 1; i >= 0; i--) | ||||
| 	{ | ||||
| 		pgFile *file = (pgFile *) parray_get(files, i); | ||||
|  | ||||
| 		elog(LOG, "%s() %s", __FUNCTION__, file->path); | ||||
| 		/* Delete completed WALs only. */ | ||||
| 		if (is_arclog && !xlog_completed(file, server_version)) | ||||
| 		{ | ||||
| 			elog(LOG, "%s() not complete WAL", __FUNCTION__); | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		file_num++; | ||||
|  | ||||
| 		/* | ||||
| 		 * If the mtime of the file is older than the threshold and there are | ||||
| 		 * enough number of files newer than the files, cleanup the file. | ||||
| 		 */ | ||||
| 		if (file->mtime >= days_threshold) | ||||
| 		{ | ||||
| 			elog(LOG, "%s() %lu is not older than %lu", __FUNCTION__, | ||||
| 				file->mtime, days_threshold); | ||||
| 			continue; | ||||
| 		} | ||||
| 		elog(LOG, "%s() %lu is older than %lu", __FUNCTION__, | ||||
| 			file->mtime, days_threshold); | ||||
|  | ||||
| 		if (file_num <= keep_files) | ||||
| 		{ | ||||
| 			elog(LOG, "%s() newer files are only %d", __FUNCTION__, file_num); | ||||
| 			continue; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void | ||||
| delete_online_wal_backup(void) | ||||
| { | ||||
| 	int i; | ||||
| 	parray *files = parray_new(); | ||||
| 	char work_path[MAXPGPATH]; | ||||
|  | ||||
| 	if (verbose) | ||||
| 	{ | ||||
| 		printf("========================================\n")); | ||||
| 		printf("cleanup online WAL backup\n")); | ||||
| 	} | ||||
|  | ||||
| 	snprintf(work_path, lengthof(work_path), "%s/%s/%s", backup_path, | ||||
| 		RESTORE_WORK_DIR, PG_XLOG_DIR); | ||||
| 	/* don't cleanup root dir */ | ||||
| 	files = pgFileEnum(work_path, NULL, true, false); | ||||
| 	if (parray_num(files) == 0) | ||||
| 	{ | ||||
| 		parray_free(files); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	parray_qsort(files, pgFileComparePathDesc);	/* cleanup from leaf */ | ||||
| 	for (i = 0; i < parray_num(files); i++) | ||||
| 	{ | ||||
| 		pgFile *file = (pgFile *) parray_get(files, i); | ||||
| 		if (verbose) | ||||
| 			printf("cleanup \"%s\"\n", file->path); | ||||
| 		if (!check) | ||||
| 			pgFileDelete(file); | ||||
| 	} | ||||
|  | ||||
| 	parray_walk(files, pgFile_free); | ||||
| 	parray_free(files); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Remove symbolic links point archived WAL in backup catalog. | ||||
|  */ | ||||
| static void | ||||
| delete_arclog_link(void) | ||||
| { | ||||
| 	int i; | ||||
| 	parray *files = parray_new(); | ||||
|  | ||||
| 	if (verbose) | ||||
| 	{ | ||||
| 		printf("========================================\n")); | ||||
| 		printf("cleanup symbolic link in archive directory\n")); | ||||
| 	} | ||||
|  | ||||
| 	files = pgFileEnum(arclog_path, NULL, false, false); | ||||
| 	for (i = 0; i < parray_num(files); i++) | ||||
| 	{ | ||||
| 		pgFile *file = (pgFile *) parray_get(files, i); | ||||
|  | ||||
| 		if (!S_ISLNK(file->mode)) | ||||
| 			continue; | ||||
|  | ||||
| 		if (verbose) | ||||
| 			printf("cleanup \"%s\"\n", file->path); | ||||
|  | ||||
| 		if (!check && remove(file->path) == -1) | ||||
| 			elog(ERROR_SYSTEM, "could not remove link \"%s\": %s", file->path, | ||||
| 				strerror(errno)); | ||||
| 	} | ||||
|  | ||||
| 	parray_walk(files, pgFile_free); | ||||
| 	parray_free(files); | ||||
| } | ||||
|  | ||||
|  | ||||
| 	/* | ||||
| 	 * Delete old files (archived WAL and serverlog) after update of status. | ||||
| 	 */ | ||||
| 	if (HAVE_ARCLOG(¤t)) | ||||
| 		delete_old_files(arclog_path, files_arclog, keep_arclog_files, | ||||
| 			keep_arclog_days, server_version, true); | ||||
|  | ||||
| 	/* Delete old backup files after all backup operation. */ | ||||
| 	clean_backup(keep_data_generations, keep_data_days); | ||||
|  | ||||
| 	/* | ||||
| 	 * If this backup is full backup, cleanup backup of online WAL. | ||||
| 	 * Note that sereverlog files which were backed up during first restoration | ||||
| 	 * don't be cleanup. | ||||
| 	 * Also cleanup symbolic link in the archive directory. | ||||
| 	 */ | ||||
| 	if (backup_mode == BACKUP_MODE_FULL) | ||||
| 	{ | ||||
| 		delete_online_wal_backup(); | ||||
| 		delete_arclog_link(); | ||||
| 	} | ||||
|  | ||||
| #endif | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * clean.c: cleanup backup files. | ||||
|  * | ||||
|  * Copyright (c) 2009-2011, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #include "pg_rman.h" | ||||
|  | ||||
| #include <dirent.h> | ||||
| #include <unistd.h> | ||||
|  | ||||
| static void clean_backup(Database db, pgBackup *backup); | ||||
|  | ||||
| #define CLEAN_MASK		(BACKUP_MASK(BACKUP_ERROR) | BACKUP_MASK(BACKUP_BAD)) | ||||
|  | ||||
| void | ||||
| do_clean(int keep_data_generations, | ||||
| 		 int keep_data_days, | ||||
| 		 int keep_srvlog_files, | ||||
| 		 int keep_srvlog_days) | ||||
| { | ||||
| 	Database	db; | ||||
| 	List	   *backups; | ||||
| 	ListCell   *cell; | ||||
|  | ||||
| 	db = db_open(); | ||||
| 	backups = db_list_backups(db, make_range(0, NULL), CLEAN_MASK); | ||||
|  | ||||
| 	foreach (cell, backups) | ||||
| 		clean_backup(db, lfirst(cell)); | ||||
|  | ||||
| 	db_close(db); | ||||
| 	list_free_deep(backups); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Delete files of the backup and update the status to DELETED. | ||||
|  */ | ||||
| static void | ||||
| clean_backup(Database db, pgBackup *backup) | ||||
| { | ||||
| 	char		datetime[DATESTRLEN]; | ||||
| 	char		path[MAXPGPATH]; | ||||
|  | ||||
| 	elog(INFO, "clean: %s", date2str(datetime, backup->start_time)); | ||||
|  | ||||
| 	/* | ||||
| 	 * update the status to BAD before the actual deletion because abort | ||||
| 	 * during deletion could leave corrupted backup files. | ||||
| 	 */ | ||||
| 	if (backup->status != BACKUP_BAD) | ||||
| 	{ | ||||
| 		backup->status = BACKUP_BAD; | ||||
| 		db_update_status(db, backup, NIL); | ||||
| 	} | ||||
|  | ||||
| 	/* remove data files. */ | ||||
| 	make_backup_path(path, backup->start_time); | ||||
| 	remove_file(path); | ||||
|  | ||||
| 	/* update the status to DELETED */ | ||||
| 	backup->status = BACKUP_DELETED; | ||||
| 	db_update_status(db, backup, NIL); | ||||
| } | ||||
|  | ||||
| #if 0 | ||||
| /* that are older than KEEP_xxx_DAYS and have more generations | ||||
|  * than KEEP_xxx_FILES. | ||||
|  */ | ||||
| 	int		i; | ||||
| 	parray *backup_list; | ||||
| 	int		backup_num; | ||||
| 	time_t	days_threshold = current.start_time - (keep_days * 60 * 60 * 24); | ||||
|  | ||||
| 	/* cleanup 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); | ||||
|  | ||||
| /* | ||||
|  * Remove backup files  | ||||
|  */ | ||||
| void | ||||
| clean_backup(int keep_generations, int keep_days) | ||||
| { | ||||
| 	backup_num = 0; | ||||
| 	/* find cleanup 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 verify full backup was found, we can cleanup the backup | ||||
| 		 * that is older than it | ||||
| 		 */ | ||||
| 		if (backup->backup_mode >= BACKUP_MODE_FULL && | ||||
| 			backup->status == BACKUP_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 threshold and | ||||
| 		 * there are enough generations of full backups, cleanup the backup. | ||||
| 		 */ | ||||
| 		if (backup->start_time >= days_threshold) | ||||
| 		{ | ||||
| 			elog(LOG, "%s() %lu is not older than %lu", __FUNCTION__, | ||||
| 				backup->start_time, days_threshold); | ||||
| 			continue; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| static void delete_old_files(const char *root, parray *files, int keep_files, | ||||
| 							 int keep_days, int server_version, bool is_arclog); | ||||
| static void delete_arclog_link(void); | ||||
| static void delete_online_wal_backup(void); | ||||
|  | ||||
| /* | ||||
|  * Delete files modified before than KEEP_xxx_DAYS or more than KEEP_xxx_FILES | ||||
|  * of newer files exist. | ||||
|  */ | ||||
| static void | ||||
| delete_old_files(const char *root, | ||||
| 				 parray *files, | ||||
| 				 int keep_files, | ||||
| 				 int keep_days, | ||||
| 				 int server_version, | ||||
| 				 bool is_arclog) | ||||
| { | ||||
| 	int		i; | ||||
| 	int		j; | ||||
| 	int		file_num = 0; | ||||
| 	time_t	days_threshold = start_time - (keep_days * 60 * 60 * 24); | ||||
|  | ||||
| 	if (verbose) | ||||
| 	{ | ||||
| 		char files_str[100]; | ||||
| 		char days_str[100]; | ||||
|  | ||||
| 		if (keep_files == KEEP_INFINITE) | ||||
| 			strncpy(files_str, "INFINITE", lengthof(files_str)); | ||||
| 		else | ||||
| 			snprintf(files_str, lengthof(files_str), "%d", keep_files); | ||||
|  | ||||
| 		if (keep_days == KEEP_INFINITE) | ||||
| 			strncpy(days_str, "INFINITE", lengthof(days_str)); | ||||
| 		else | ||||
| 			snprintf(days_str, lengthof(days_str), "%d", keep_days); | ||||
|  | ||||
| 		printf("cleanup old files from \"%s\" (files=%s, days=%s)\n", | ||||
| 			root, files_str, days_str); | ||||
| 	} | ||||
|  | ||||
| 	/* cleanup files which satisfy both conditions */ | ||||
| 	if (keep_files == KEEP_INFINITE || keep_days == KEEP_INFINITE) | ||||
| 	{ | ||||
| 		elog(LOG, "%s() infinite", __FUNCTION__); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	parray_qsort(files, pgFileCompareMtime); | ||||
| 	for (i = parray_num(files) - 1; i >= 0; i--) | ||||
| 	{ | ||||
| 		pgFile *file = (pgFile *) parray_get(files, i); | ||||
|  | ||||
| 		elog(LOG, "%s() %s", __FUNCTION__, file->path); | ||||
| 		/* Delete completed WALs only. */ | ||||
| 		if (is_arclog && !xlog_completed(file, server_version)) | ||||
| 		{ | ||||
| 			elog(LOG, "%s() not complete WAL", __FUNCTION__); | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		file_num++; | ||||
|  | ||||
| 		/* | ||||
| 		 * If the mtime of the file is older than the threshold and there are | ||||
| 		 * enough number of files newer than the files, cleanup the file. | ||||
| 		 */ | ||||
| 		if (file->mtime >= days_threshold) | ||||
| 		{ | ||||
| 			elog(LOG, "%s() %lu is not older than %lu", __FUNCTION__, | ||||
| 				file->mtime, days_threshold); | ||||
| 			continue; | ||||
| 		} | ||||
| 		elog(LOG, "%s() %lu is older than %lu", __FUNCTION__, | ||||
| 			file->mtime, days_threshold); | ||||
|  | ||||
| 		if (file_num <= keep_files) | ||||
| 		{ | ||||
| 			elog(LOG, "%s() newer files are only %d", __FUNCTION__, file_num); | ||||
| 			continue; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void | ||||
| delete_online_wal_backup(void) | ||||
| { | ||||
| 	int i; | ||||
| 	parray *files = parray_new(); | ||||
| 	char work_path[MAXPGPATH]; | ||||
|  | ||||
| 	if (verbose) | ||||
| 	{ | ||||
| 		printf("========================================\n")); | ||||
| 		printf("cleanup online WAL backup\n")); | ||||
| 	} | ||||
|  | ||||
| 	snprintf(work_path, lengthof(work_path), "%s/%s/%s", backup_path, | ||||
| 		RESTORE_WORK_DIR, PG_XLOG_DIR); | ||||
| 	/* don't cleanup root dir */ | ||||
| 	files = pgFileEnum(work_path, NULL, true, false); | ||||
| 	if (parray_num(files) == 0) | ||||
| 	{ | ||||
| 		parray_free(files); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	parray_qsort(files, pgFileComparePathDesc);	/* cleanup from leaf */ | ||||
| 	for (i = 0; i < parray_num(files); i++) | ||||
| 	{ | ||||
| 		pgFile *file = (pgFile *) parray_get(files, i); | ||||
| 		if (verbose) | ||||
| 			printf("cleanup \"%s\"\n", file->path); | ||||
| 		if (!check) | ||||
| 			pgFileDelete(file); | ||||
| 	} | ||||
|  | ||||
| 	parray_walk(files, pgFile_free); | ||||
| 	parray_free(files); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Remove symbolic links point archived WAL in backup catalog. | ||||
|  */ | ||||
| static void | ||||
| delete_arclog_link(void) | ||||
| { | ||||
| 	int i; | ||||
| 	parray *files = parray_new(); | ||||
|  | ||||
| 	if (verbose) | ||||
| 	{ | ||||
| 		printf("========================================\n")); | ||||
| 		printf("cleanup symbolic link in archive directory\n")); | ||||
| 	} | ||||
|  | ||||
| 	files = pgFileEnum(arclog_path, NULL, false, false); | ||||
| 	for (i = 0; i < parray_num(files); i++) | ||||
| 	{ | ||||
| 		pgFile *file = (pgFile *) parray_get(files, i); | ||||
|  | ||||
| 		if (!S_ISLNK(file->mode)) | ||||
| 			continue; | ||||
|  | ||||
| 		if (verbose) | ||||
| 			printf("cleanup \"%s\"\n", file->path); | ||||
|  | ||||
| 		if (!check && remove(file->path) == -1) | ||||
| 			elog(ERROR_SYSTEM, "could not remove link \"%s\": %s", file->path, | ||||
| 				strerror(errno)); | ||||
| 	} | ||||
|  | ||||
| 	parray_walk(files, pgFile_free); | ||||
| 	parray_free(files); | ||||
| } | ||||
|  | ||||
|  | ||||
| 	/* | ||||
| 	 * Delete old files (archived WAL and serverlog) after update of status. | ||||
| 	 */ | ||||
| 	if (HAVE_ARCLOG(¤t)) | ||||
| 		delete_old_files(arclog_path, files_arclog, keep_arclog_files, | ||||
| 			keep_arclog_days, server_version, true); | ||||
|  | ||||
| 	/* Delete old backup files after all backup operation. */ | ||||
| 	clean_backup(keep_data_generations, keep_data_days); | ||||
|  | ||||
| 	/* | ||||
| 	 * If this backup is full backup, cleanup backup of online WAL. | ||||
| 	 * Note that sereverlog files which were backed up during first restoration | ||||
| 	 * don't be cleanup. | ||||
| 	 * Also cleanup symbolic link in the archive directory. | ||||
| 	 */ | ||||
| 	if (backup_mode == BACKUP_MODE_FULL) | ||||
| 	{ | ||||
| 		delete_online_wal_backup(); | ||||
| 		delete_arclog_link(); | ||||
| 	} | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -1,18 +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 | ||||
| # 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 +1 @@ | ||||
| PG_VERSION f 4 4277607361 0600 2009-08-06 18:40:18 | ||||
| PG_VERSION f 4 4277607361 0600 2009-08-06 18:40:18 | ||||
|   | ||||
| @@ -1,18 +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 | ||||
| # 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 +1 @@ | ||||
| PG_VERSION f 4 0 0600 2009-08-06 18:40:18 | ||||
| PG_VERSION f 4 0 0600 2009-08-06 18:40:18 | ||||
|   | ||||
| @@ -1,18 +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 | ||||
| # 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 | ||||
|   | ||||
| @@ -1,18 +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='2009-06-03 17:05:53' | ||||
| 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 | ||||
| # 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='2009-06-03 17:05:53' | ||||
| 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 | ||||
|   | ||||
							
								
								
									
										526
									
								
								delete.c
									
									
									
									
									
								
							
							
						
						
									
										526
									
								
								delete.c
									
									
									
									
									
								
							| @@ -1,263 +1,263 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * delete.c: delete backup files. | ||||
|  * | ||||
|  * Copyright (c) 2009-2011, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #include "pg_rman.h" | ||||
|  | ||||
| static int pgBackupDeleteFiles(pgBackup *backup); | ||||
| static bool checkIfDeletable(pgBackup *backup); | ||||
|  | ||||
| int | ||||
| //do_delete(pgBackupRange *range) | ||||
| do_delete(pgBackupRange *range, bool force) | ||||
| { | ||||
| 	int		i; | ||||
| 	int		ret; | ||||
| 	parray *backup_list; | ||||
| 	bool	do_delete; | ||||
| 	bool	force_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 delete.")); | ||||
|  | ||||
| 	/* get list of backups. */ | ||||
| 	backup_list = catalog_get_backup_list(NULL); | ||||
| 	if(!backup_list){ | ||||
| 		elog(ERROR_SYSTEM, _("can't process any more.")); | ||||
| 	} | ||||
|  | ||||
| 	do_delete = false; | ||||
| 	force_delete = false; | ||||
| 	/* find delete target backup. */ | ||||
| 	for (i = 0; i < parray_num(backup_list); i++) | ||||
| 	{ | ||||
| 		pgBackup *backup = (pgBackup *)parray_get(backup_list, i); | ||||
|  | ||||
| 		if(force) | ||||
| 			force_delete = checkIfDeletable(backup); | ||||
|  | ||||
| 		/* delete backup and update status to DELETED */ | ||||
| 		if (do_delete || force_delete) | ||||
| 		{ | ||||
| 			/* check for interrupt */ | ||||
| 			if (interrupted) | ||||
| 				elog(ERROR_INTERRUPTED, _("interrupted during delete backup")); | ||||
|  | ||||
| 			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_threshold = 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) | ||||
| 		if (backup_num <= keep_generations) | ||||
| 		{ | ||||
| 			elog(LOG, "%s() backup are only %d", __FUNCTION__, backup_num); | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		/* | ||||
| 		 * If the start time of the backup is older than the threshold and | ||||
| 		 * there are enough generations of full backups, delete the backup. | ||||
| 		 */ | ||||
| 		if (backup->start_time >= days_threshold) | ||||
| 		{ | ||||
| 			elog(LOG, "%s() %lu is not older than %lu", __FUNCTION__, | ||||
| 				backup->start_time, days_threshold); | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		elog(LOG, "%s() %lu is older than %lu", __FUNCTION__, | ||||
| 			backup->start_time, days_threshold); | ||||
|  | ||||
| 		/* 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; | ||||
| } | ||||
|  | ||||
| bool | ||||
| checkIfDeletable(pgBackup *backup) | ||||
| { | ||||
| 	/* find latest full backup. */ | ||||
| 	if (backup->status != BACKUP_STATUS_OK && | ||||
| 		backup->status != BACKUP_STATUS_DELETED && | ||||
| 		backup->status != BACKUP_STATUS_DONE) | ||||
| 		return true; | ||||
|  | ||||
| 	return false; | ||||
| } | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * delete.c: delete backup files. | ||||
|  * | ||||
|  * Copyright (c) 2009-2011, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #include "pg_rman.h" | ||||
|  | ||||
| static int pgBackupDeleteFiles(pgBackup *backup); | ||||
| static bool checkIfDeletable(pgBackup *backup); | ||||
|  | ||||
| int | ||||
| //do_delete(pgBackupRange *range) | ||||
| do_delete(pgBackupRange *range, bool force) | ||||
| { | ||||
| 	int		i; | ||||
| 	int		ret; | ||||
| 	parray *backup_list; | ||||
| 	bool	do_delete; | ||||
| 	bool	force_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 delete.")); | ||||
|  | ||||
| 	/* get list of backups. */ | ||||
| 	backup_list = catalog_get_backup_list(NULL); | ||||
| 	if(!backup_list){ | ||||
| 		elog(ERROR_SYSTEM, _("can't process any more.")); | ||||
| 	} | ||||
|  | ||||
| 	do_delete = false; | ||||
| 	force_delete = false; | ||||
| 	/* find delete target backup. */ | ||||
| 	for (i = 0; i < parray_num(backup_list); i++) | ||||
| 	{ | ||||
| 		pgBackup *backup = (pgBackup *)parray_get(backup_list, i); | ||||
|  | ||||
| 		if(force) | ||||
| 			force_delete = checkIfDeletable(backup); | ||||
|  | ||||
| 		/* delete backup and update status to DELETED */ | ||||
| 		if (do_delete || force_delete) | ||||
| 		{ | ||||
| 			/* check for interrupt */ | ||||
| 			if (interrupted) | ||||
| 				elog(ERROR_INTERRUPTED, _("interrupted during delete backup")); | ||||
|  | ||||
| 			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_threshold = 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) | ||||
| 		if (backup_num <= keep_generations) | ||||
| 		{ | ||||
| 			elog(LOG, "%s() backup are only %d", __FUNCTION__, backup_num); | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		/* | ||||
| 		 * If the start time of the backup is older than the threshold and | ||||
| 		 * there are enough generations of full backups, delete the backup. | ||||
| 		 */ | ||||
| 		if (backup->start_time >= days_threshold) | ||||
| 		{ | ||||
| 			elog(LOG, "%s() %lu is not older than %lu", __FUNCTION__, | ||||
| 				backup->start_time, days_threshold); | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		elog(LOG, "%s() %lu is older than %lu", __FUNCTION__, | ||||
| 			backup->start_time, days_threshold); | ||||
|  | ||||
| 		/* 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; | ||||
| } | ||||
|  | ||||
| bool | ||||
| checkIfDeletable(pgBackup *backup) | ||||
| { | ||||
| 	/* find latest full backup. */ | ||||
| 	if (backup->status != BACKUP_STATUS_OK && | ||||
| 		backup->status != BACKUP_STATUS_DELETED && | ||||
| 		backup->status != BACKUP_STATUS_DONE) | ||||
| 		return true; | ||||
|  | ||||
| 	return false; | ||||
| } | ||||
|   | ||||
							
								
								
									
										346
									
								
								init.c
									
									
									
									
									
								
							
							
						
						
									
										346
									
								
								init.c
									
									
									
									
									
								
							| @@ -1,173 +1,173 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * init.c: manage backup catalog. | ||||
|  * | ||||
|  * Copyright (c) 2009-2011, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #include "pg_rman.h" | ||||
|  | ||||
| #include <unistd.h> | ||||
| #include <dirent.h> | ||||
|  | ||||
| static void parse_postgresql_conf(const char *path, char **log_directory, | ||||
| 								  char **archive_command); | ||||
|  | ||||
| /* | ||||
|  * selects function for scandir. | ||||
|  */ | ||||
| static int selects(const struct dirent *dir) | ||||
| { | ||||
|   return dir->d_name[0] != '.'; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Initialize backup catalog. | ||||
|  */ | ||||
| int | ||||
| do_init(void) | ||||
| { | ||||
| 	char	path[MAXPGPATH]; | ||||
| 	char   *log_directory = NULL; | ||||
| 	char   *archive_command = NULL; | ||||
| 	FILE   *fp; | ||||
|  | ||||
| 	struct dirent **dp; | ||||
| 	int results; | ||||
| 	if (access(backup_path, F_OK) == 0){ | ||||
| 		results = scandir(backup_path, &dp, selects, NULL); | ||||
| 		if(results != 0){ | ||||
| 			elog(ERROR, _("backup catalog already exist. and it's not empty.")); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/* create backup catalog root directory */ | ||||
| 	dir_create_dir(backup_path, DIR_PERMISSION); | ||||
|  | ||||
| 	/* create directories for backup of online files */ | ||||
| 	join_path_components(path, 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 */ | ||||
| 	join_path_components(path, backup_path, TIMELINE_HISTORY_DIR); | ||||
| 	dir_create_dir(path, DIR_PERMISSION); | ||||
|  | ||||
| 	/* read postgresql.conf */ | ||||
| 	if (pgdata) | ||||
| 	{ | ||||
| 		join_path_components(path, pgdata, "postgresql.conf"); | ||||
| 		parse_postgresql_conf(path, &log_directory, &archive_command); | ||||
| 	} | ||||
|  | ||||
| 	/* create pg_rman.ini */ | ||||
| 	join_path_components(path, 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'." | ||||
| 				"Please set ARCLOG_PATH in pg_rman.ini or environmental variable", archive_command); | ||||
| 	else | ||||
| 		elog(WARNING, "ARCLOG_PATH is not set because archive_command is empty." | ||||
| 				"Please set ARCLOG_PATH in pg_rman.ini or environmental variable"); | ||||
|  | ||||
| 	/* 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); | ||||
| 				join_path_components(srvlog_path, pgdata, log_directory); | ||||
| 			} | ||||
| 		} | ||||
| 		else if (pgdata) | ||||
| 		{ | ||||
| 			/* default: log_directory = 'pg_log' */ | ||||
| 			srvlog_path = pgut_malloc(MAXPGPATH); | ||||
| 			join_path_components(srvlog_path, 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 */ | ||||
| } | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * init.c: manage backup catalog. | ||||
|  * | ||||
|  * Copyright (c) 2009-2011, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #include "pg_rman.h" | ||||
|  | ||||
| #include <unistd.h> | ||||
| #include <dirent.h> | ||||
|  | ||||
| static void parse_postgresql_conf(const char *path, char **log_directory, | ||||
| 								  char **archive_command); | ||||
|  | ||||
| /* | ||||
|  * selects function for scandir. | ||||
|  */ | ||||
| static int selects(const struct dirent *dir) | ||||
| { | ||||
|   return dir->d_name[0] != '.'; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Initialize backup catalog. | ||||
|  */ | ||||
| int | ||||
| do_init(void) | ||||
| { | ||||
| 	char	path[MAXPGPATH]; | ||||
| 	char   *log_directory = NULL; | ||||
| 	char   *archive_command = NULL; | ||||
| 	FILE   *fp; | ||||
|  | ||||
| 	struct dirent **dp; | ||||
| 	int results; | ||||
| 	if (access(backup_path, F_OK) == 0){ | ||||
| 		results = scandir(backup_path, &dp, selects, NULL); | ||||
| 		if(results != 0){ | ||||
| 			elog(ERROR, _("backup catalog already exist. and it's not empty.")); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/* create backup catalog root directory */ | ||||
| 	dir_create_dir(backup_path, DIR_PERMISSION); | ||||
|  | ||||
| 	/* create directories for backup of online files */ | ||||
| 	join_path_components(path, 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 */ | ||||
| 	join_path_components(path, backup_path, TIMELINE_HISTORY_DIR); | ||||
| 	dir_create_dir(path, DIR_PERMISSION); | ||||
|  | ||||
| 	/* read postgresql.conf */ | ||||
| 	if (pgdata) | ||||
| 	{ | ||||
| 		join_path_components(path, pgdata, "postgresql.conf"); | ||||
| 		parse_postgresql_conf(path, &log_directory, &archive_command); | ||||
| 	} | ||||
|  | ||||
| 	/* create pg_rman.ini */ | ||||
| 	join_path_components(path, 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'." | ||||
| 				"Please set ARCLOG_PATH in pg_rman.ini or environmental variable", archive_command); | ||||
| 	else | ||||
| 		elog(WARNING, "ARCLOG_PATH is not set because archive_command is empty." | ||||
| 				"Please set ARCLOG_PATH in pg_rman.ini or environmental variable"); | ||||
|  | ||||
| 	/* 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); | ||||
| 				join_path_components(srvlog_path, pgdata, log_directory); | ||||
| 			} | ||||
| 		} | ||||
| 		else if (pgdata) | ||||
| 		{ | ||||
| 			/* default: log_directory = 'pg_log' */ | ||||
| 			srvlog_path = pgut_malloc(MAXPGPATH); | ||||
| 			join_path_components(srvlog_path, 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 */ | ||||
| } | ||||
|   | ||||
							
								
								
									
										394
									
								
								parray.c
									
									
									
									
									
								
							
							
						
						
									
										394
									
								
								parray.c
									
									
									
									
									
								
							| @@ -1,197 +1,197 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * parray.c: pointer array collection. | ||||
|  * | ||||
|  * Copyright (c) 2009-2011, 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_new(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; | ||||
| } | ||||
|  | ||||
| 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, 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; | ||||
| } | ||||
|  | ||||
| bool | ||||
| parray_rm(parray *array, const void *key, int(*compare)(const void *, const void *)) | ||||
| { | ||||
| 	int i; | ||||
|  | ||||
| 	for (i = 0; i < array->used; i++) | ||||
| 	{ | ||||
| 		if (compare(&key, &array->data[i]) == 0) | ||||
| 		{ | ||||
| 			parray_remove(array, i); | ||||
| 			return true; | ||||
| 		} | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| 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); | ||||
| } | ||||
|  | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * parray.c: pointer array collection. | ||||
|  * | ||||
|  * Copyright (c) 2009-2011, 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_new(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; | ||||
| } | ||||
|  | ||||
| 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, 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; | ||||
| } | ||||
|  | ||||
| bool | ||||
| parray_rm(parray *array, const void *key, int(*compare)(const void *, const void *)) | ||||
| { | ||||
| 	int i; | ||||
|  | ||||
| 	for (i = 0; i < array->used; i++) | ||||
| 	{ | ||||
| 		if (compare(&key, &array->data[i]) == 0) | ||||
| 		{ | ||||
| 			parray_remove(array, i); | ||||
| 			return true; | ||||
| 		} | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| 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); | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										70
									
								
								parray.h
									
									
									
									
									
								
							
							
						
						
									
										70
									
								
								parray.h
									
									
									
									
									
								
							| @@ -1,35 +1,35 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * parray.h: pointer array collection. | ||||
|  * | ||||
|  * Copyright (c) 2009-2011, 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 bool parray_rm(parray *array, const void *key, int(*compare)(const void *, const void *)); | ||||
| 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 */ | ||||
|  | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * parray.h: pointer array collection. | ||||
|  * | ||||
|  * Copyright (c) 2009-2011, 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 bool parray_rm(parray *array, const void *key, int(*compare)(const void *, const void *)); | ||||
| 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 */ | ||||
|  | ||||
|   | ||||
							
								
								
									
										186
									
								
								pg_crc.c
									
									
									
									
									
								
							
							
						
						
									
										186
									
								
								pg_crc.c
									
									
									
									
									
								
							| @@ -1,93 +1,93 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * 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. | ||||
|  * | ||||
|  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group | ||||
|  * Portions Copyright (c) 1994, Regents of the University of California | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| /* 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 | ||||
| }; | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * 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. | ||||
|  * | ||||
|  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group | ||||
|  * Portions Copyright (c) 1994, Regents of the University of California | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| /* 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 | ||||
| }; | ||||
|   | ||||
							
								
								
									
										484
									
								
								pg_ctl.c
									
									
									
									
									
								
							
							
						
						
									
										484
									
								
								pg_ctl.c
									
									
									
									
									
								
							| @@ -1,242 +1,242 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * pg_ctl.c: operations for control file | ||||
|  * | ||||
|  * Copyright (c) 2009-2011, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #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 bool read_control_file(const char *path, ControlFileData *ctrl); | ||||
|  | ||||
| static pgpid_t | ||||
| get_pgpid(void) | ||||
| { | ||||
| 	FILE	   *fp; | ||||
| 	pgpid_t		pid; | ||||
| 	char		path[MAXPGPATH]; | ||||
|  | ||||
| 	snprintf(path, lengthof(path), "%s/postmaster.pid", pgdata); | ||||
| 	if ((fp = pgut_fopen(path, "r")) == NULL) | ||||
| 		return 0;	/* No pid file, not an error on startup */ | ||||
| 	if (fscanf(fp, "%ld", &pid) != 1) | ||||
| 		elog(ERROR_INCOMPATIBLE, "invalid data in PID file \"%s\"", path); | ||||
| 	fclose(fp); | ||||
|  | ||||
| 	return 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); | ||||
| } | ||||
|  | ||||
| static void | ||||
| compare_uint32(const char *name, uint32 server, uint32 backup) | ||||
| { | ||||
| 	if (server != backup) | ||||
| 		elog(ERROR_INCOMPATIBLE, | ||||
| 			"incompatible %s: server=%u / backup=%u", | ||||
| 			name, server, backup); | ||||
| } | ||||
|  | ||||
| static void | ||||
| compare_uint64(const char *name, uint64 server, uint64 backup) | ||||
| { | ||||
| 	if (server != backup) | ||||
| 		elog(ERROR_INCOMPATIBLE, | ||||
| 			"incompatible %s: server=" UINT64_FORMAT " / backup=" UINT64_FORMAT, | ||||
| 			name, server, backup); | ||||
| } | ||||
|  | ||||
| static void | ||||
| compare_double(const char *name, double server, double backup) | ||||
| { | ||||
| 	if (server != backup) | ||||
| 		elog(ERROR_INCOMPATIBLE, | ||||
| 			"incompatible %s: server=%f / backup=%f", | ||||
| 			name, server, backup); | ||||
| } | ||||
|  | ||||
| static void | ||||
| compare_bool(const char *name, bool server, bool backup) | ||||
| { | ||||
| 	if ((server && !backup) || (!server && backup)) | ||||
| 		elog(ERROR_INCOMPATIBLE, | ||||
| 			"incompatible %s: server=%s / backup=%s", | ||||
| 			name, (server ? "true" : "false"), (backup ? "true" : "false")); | ||||
| } | ||||
|  | ||||
| /* verify control file */ | ||||
| bool | ||||
| verify_control_file(const char *pgdata, const char *catalog) | ||||
| { | ||||
| 	char			path[MAXPGPATH]; | ||||
| 	ControlFileData	ctrl; | ||||
| 	bool			in_pgdata; | ||||
| 	bool			in_backup; | ||||
|  | ||||
| 	snprintf(path, MAXPGPATH, "%s/global/pg_control", pgdata); | ||||
| 	in_pgdata = read_control_file(path, &pgControlFile); | ||||
|  | ||||
| 	snprintf(path, MAXPGPATH, "%s/pg_control", catalog); | ||||
| 	in_backup = read_control_file(path, &ctrl); | ||||
|  | ||||
| 	if (in_pgdata) | ||||
| 	{ | ||||
| 		if (in_backup) | ||||
| 		{ | ||||
| 			/* compare control files */ | ||||
| 			compare_uint32("pg_control version number", | ||||
| 				pgControlFile.pg_control_version, ctrl.pg_control_version); | ||||
| 			compare_uint32("catalog version number", | ||||
| 				pgControlFile.catalog_version_no, ctrl.catalog_version_no); | ||||
| 			compare_uint64("database system identifier", | ||||
| 				pgControlFile.system_identifier, ctrl.system_identifier); | ||||
| 			compare_uint32("maximum data alignment", | ||||
| 				pgControlFile.maxAlign, ctrl.maxAlign); | ||||
| 			compare_uint32("float format", | ||||
| 				pgControlFile.floatFormat, ctrl.floatFormat); | ||||
| 			compare_double("database block size", | ||||
| 				pgControlFile.blcksz, ctrl.blcksz); | ||||
| 			compare_uint32("blocks per segment of large relation", | ||||
| 				pgControlFile.relseg_size, ctrl.relseg_size); | ||||
| 			compare_uint32("wal block size", | ||||
| 				pgControlFile.xlog_blcksz, ctrl.xlog_blcksz); | ||||
| 			compare_uint32("bytes per wal segment", | ||||
| 				pgControlFile.xlog_seg_size, ctrl.xlog_seg_size); | ||||
| 			compare_uint32("maximum length of identifiers", | ||||
| 				pgControlFile.nameDataLen, ctrl.nameDataLen); | ||||
| 			compare_uint32("maximum columns in an index", | ||||
| 				pgControlFile.indexMaxKeys, ctrl.indexMaxKeys); | ||||
| 			compare_uint32("maximum size of a toast chunk", | ||||
| 				pgControlFile.toast_max_chunk_size, ctrl.toast_max_chunk_size); | ||||
| 			compare_bool("date/time type storage", | ||||
| 				pgControlFile.enableIntTimes, ctrl.enableIntTimes); | ||||
| 			compare_bool("float4 argument passing", | ||||
| 				pgControlFile.float4ByVal, ctrl.float4ByVal); | ||||
| 			compare_bool("float8 argument passing", | ||||
| 				pgControlFile.float8ByVal, ctrl.float8ByVal); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			/* write in_backup pg_control */ | ||||
| 			FILE *fp = pgut_fopen(path, "w"); | ||||
| 			if (fwrite(&pgControlFile, 1, sizeof(ControlFileData), fp) != sizeof(ControlFileData)) | ||||
| 			{ | ||||
| 				fclose(fp); | ||||
| 				unlink(path); | ||||
| 				ereport(ERROR, | ||||
| 					(errcode_errno(), | ||||
| 					 errmsg("could not write control file \"%s\": ", path))); | ||||
| 			} | ||||
| 			fclose(fp); | ||||
| 		} | ||||
| 		return true; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		if (in_backup) | ||||
| 		{ | ||||
| 			/* volatile parts are unavialable */ | ||||
| 			memset(((char *) &ctrl) + offsetof(ControlFileData, state), 0, | ||||
| 					offsetof(ControlFileData, maxAlign) - offsetof(ControlFileData, state)); | ||||
| 			pgControlFile = ctrl; | ||||
| 		} | ||||
| 		else | ||||
| 			ereport(ERROR, | ||||
| 				(errcode(ENOENT), | ||||
| 				 errmsg("control files not found"))); | ||||
| 		return false; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * FIXME: ControlFileData might be changed in versions. | ||||
|  */ | ||||
| static bool | ||||
| read_control_file(const char *path, ControlFileData *ctrl) | ||||
| { | ||||
| 	FILE	   *fp; | ||||
| 	pg_crc32	crc; | ||||
|  | ||||
| 	if ((fp = pgut_fopen(path, "r")) == NULL) | ||||
| 	{ | ||||
| 		memset(ctrl, 0, sizeof(ControlFileData)); | ||||
| 		return false; | ||||
| 	} | ||||
| 	if (fread(ctrl, 1, sizeof(ControlFileData), fp) != sizeof(ControlFileData)) | ||||
| 		elog(ERROR_INCOMPATIBLE, | ||||
| 			"could not read control file \"%s\": %s", path, strerror(errno)); | ||||
| 	fclose(fp); | ||||
|  | ||||
| 	/* Check the CRC. */ | ||||
| 	INIT_CRC32(crc); | ||||
| 	COMP_CRC32(crc, ctrl, offsetof(ControlFileData, crc)); | ||||
| 	FIN_CRC32(crc); | ||||
| 	if (!EQ_CRC32(crc, ctrl->crc)) | ||||
| 		elog(ERROR_INCOMPATIBLE, | ||||
| 			"bad CRC checksum for control file \"%s\"", path); | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * pg_ctl.c: operations for control file | ||||
|  * | ||||
|  * Copyright (c) 2009-2011, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #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 bool read_control_file(const char *path, ControlFileData *ctrl); | ||||
|  | ||||
| static pgpid_t | ||||
| get_pgpid(void) | ||||
| { | ||||
| 	FILE	   *fp; | ||||
| 	pgpid_t		pid; | ||||
| 	char		path[MAXPGPATH]; | ||||
|  | ||||
| 	snprintf(path, lengthof(path), "%s/postmaster.pid", pgdata); | ||||
| 	if ((fp = pgut_fopen(path, "r")) == NULL) | ||||
| 		return 0;	/* No pid file, not an error on startup */ | ||||
| 	if (fscanf(fp, "%ld", &pid) != 1) | ||||
| 		elog(ERROR_INCOMPATIBLE, "invalid data in PID file \"%s\"", path); | ||||
| 	fclose(fp); | ||||
|  | ||||
| 	return 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); | ||||
| } | ||||
|  | ||||
| static void | ||||
| compare_uint32(const char *name, uint32 server, uint32 backup) | ||||
| { | ||||
| 	if (server != backup) | ||||
| 		elog(ERROR_INCOMPATIBLE, | ||||
| 			"incompatible %s: server=%u / backup=%u", | ||||
| 			name, server, backup); | ||||
| } | ||||
|  | ||||
| static void | ||||
| compare_uint64(const char *name, uint64 server, uint64 backup) | ||||
| { | ||||
| 	if (server != backup) | ||||
| 		elog(ERROR_INCOMPATIBLE, | ||||
| 			"incompatible %s: server=" UINT64_FORMAT " / backup=" UINT64_FORMAT, | ||||
| 			name, server, backup); | ||||
| } | ||||
|  | ||||
| static void | ||||
| compare_double(const char *name, double server, double backup) | ||||
| { | ||||
| 	if (server != backup) | ||||
| 		elog(ERROR_INCOMPATIBLE, | ||||
| 			"incompatible %s: server=%f / backup=%f", | ||||
| 			name, server, backup); | ||||
| } | ||||
|  | ||||
| static void | ||||
| compare_bool(const char *name, bool server, bool backup) | ||||
| { | ||||
| 	if ((server && !backup) || (!server && backup)) | ||||
| 		elog(ERROR_INCOMPATIBLE, | ||||
| 			"incompatible %s: server=%s / backup=%s", | ||||
| 			name, (server ? "true" : "false"), (backup ? "true" : "false")); | ||||
| } | ||||
|  | ||||
| /* verify control file */ | ||||
| bool | ||||
| verify_control_file(const char *pgdata, const char *catalog) | ||||
| { | ||||
| 	char			path[MAXPGPATH]; | ||||
| 	ControlFileData	ctrl; | ||||
| 	bool			in_pgdata; | ||||
| 	bool			in_backup; | ||||
|  | ||||
| 	snprintf(path, MAXPGPATH, "%s/global/pg_control", pgdata); | ||||
| 	in_pgdata = read_control_file(path, &pgControlFile); | ||||
|  | ||||
| 	snprintf(path, MAXPGPATH, "%s/pg_control", catalog); | ||||
| 	in_backup = read_control_file(path, &ctrl); | ||||
|  | ||||
| 	if (in_pgdata) | ||||
| 	{ | ||||
| 		if (in_backup) | ||||
| 		{ | ||||
| 			/* compare control files */ | ||||
| 			compare_uint32("pg_control version number", | ||||
| 				pgControlFile.pg_control_version, ctrl.pg_control_version); | ||||
| 			compare_uint32("catalog version number", | ||||
| 				pgControlFile.catalog_version_no, ctrl.catalog_version_no); | ||||
| 			compare_uint64("database system identifier", | ||||
| 				pgControlFile.system_identifier, ctrl.system_identifier); | ||||
| 			compare_uint32("maximum data alignment", | ||||
| 				pgControlFile.maxAlign, ctrl.maxAlign); | ||||
| 			compare_uint32("float format", | ||||
| 				pgControlFile.floatFormat, ctrl.floatFormat); | ||||
| 			compare_double("database block size", | ||||
| 				pgControlFile.blcksz, ctrl.blcksz); | ||||
| 			compare_uint32("blocks per segment of large relation", | ||||
| 				pgControlFile.relseg_size, ctrl.relseg_size); | ||||
| 			compare_uint32("wal block size", | ||||
| 				pgControlFile.xlog_blcksz, ctrl.xlog_blcksz); | ||||
| 			compare_uint32("bytes per wal segment", | ||||
| 				pgControlFile.xlog_seg_size, ctrl.xlog_seg_size); | ||||
| 			compare_uint32("maximum length of identifiers", | ||||
| 				pgControlFile.nameDataLen, ctrl.nameDataLen); | ||||
| 			compare_uint32("maximum columns in an index", | ||||
| 				pgControlFile.indexMaxKeys, ctrl.indexMaxKeys); | ||||
| 			compare_uint32("maximum size of a toast chunk", | ||||
| 				pgControlFile.toast_max_chunk_size, ctrl.toast_max_chunk_size); | ||||
| 			compare_bool("date/time type storage", | ||||
| 				pgControlFile.enableIntTimes, ctrl.enableIntTimes); | ||||
| 			compare_bool("float4 argument passing", | ||||
| 				pgControlFile.float4ByVal, ctrl.float4ByVal); | ||||
| 			compare_bool("float8 argument passing", | ||||
| 				pgControlFile.float8ByVal, ctrl.float8ByVal); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			/* write in_backup pg_control */ | ||||
| 			FILE *fp = pgut_fopen(path, "w"); | ||||
| 			if (fwrite(&pgControlFile, 1, sizeof(ControlFileData), fp) != sizeof(ControlFileData)) | ||||
| 			{ | ||||
| 				fclose(fp); | ||||
| 				unlink(path); | ||||
| 				ereport(ERROR, | ||||
| 					(errcode_errno(), | ||||
| 					 errmsg("could not write control file \"%s\": ", path))); | ||||
| 			} | ||||
| 			fclose(fp); | ||||
| 		} | ||||
| 		return true; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		if (in_backup) | ||||
| 		{ | ||||
| 			/* volatile parts are unavialable */ | ||||
| 			memset(((char *) &ctrl) + offsetof(ControlFileData, state), 0, | ||||
| 					offsetof(ControlFileData, maxAlign) - offsetof(ControlFileData, state)); | ||||
| 			pgControlFile = ctrl; | ||||
| 		} | ||||
| 		else | ||||
| 			ereport(ERROR, | ||||
| 				(errcode(ENOENT), | ||||
| 				 errmsg("control files not found"))); | ||||
| 		return false; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * FIXME: ControlFileData might be changed in versions. | ||||
|  */ | ||||
| static bool | ||||
| read_control_file(const char *path, ControlFileData *ctrl) | ||||
| { | ||||
| 	FILE	   *fp; | ||||
| 	pg_crc32	crc; | ||||
|  | ||||
| 	if ((fp = pgut_fopen(path, "r")) == NULL) | ||||
| 	{ | ||||
| 		memset(ctrl, 0, sizeof(ControlFileData)); | ||||
| 		return false; | ||||
| 	} | ||||
| 	if (fread(ctrl, 1, sizeof(ControlFileData), fp) != sizeof(ControlFileData)) | ||||
| 		elog(ERROR_INCOMPATIBLE, | ||||
| 			"could not read control file \"%s\": %s", path, strerror(errno)); | ||||
| 	fclose(fp); | ||||
|  | ||||
| 	/* Check the CRC. */ | ||||
| 	INIT_CRC32(crc); | ||||
| 	COMP_CRC32(crc, ctrl, offsetof(ControlFileData, crc)); | ||||
| 	FIN_CRC32(crc); | ||||
| 	if (!EQ_CRC32(crc, ctrl->crc)) | ||||
| 		elog(ERROR_INCOMPATIBLE, | ||||
| 			"bad CRC checksum for control file \"%s\"", path); | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|   | ||||
							
								
								
									
										666
									
								
								pg_rman.h
									
									
									
									
									
								
							
							
						
						
									
										666
									
								
								pg_rman.h
									
									
									
									
									
								
							| @@ -1,333 +1,333 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * pg_rman.h: Backup/Recovery manager for PostgreSQL. | ||||
|  * | ||||
|  * Copyright (c) 2009-2011, 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" | ||||
|  | ||||
| #if PG_VERSION_NUM < 80200 | ||||
| #define XLOG_BLCKSZ		BLCKSZ | ||||
| #endif | ||||
|  | ||||
| #if PG_VERSION_NUM < 80300 | ||||
| #define TXID_CURRENT_SQL	"SELECT transactionid FROM pg_locks WHERE locktype = 'transactionid' AND pid = pg_backend_pid();" | ||||
| #include <sys/stat.h> | ||||
| #else | ||||
| #define TXID_CURRENT_SQL	"SELECT txid_current();" | ||||
| #endif | ||||
|  | ||||
| /* 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 PG_TBLSPC_DIR			"pg_tblspc" | ||||
| #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" | ||||
| #define SNAPSHOT_SCRIPT_FILE	"snapshot_script" | ||||
|  | ||||
| /* Snapshot script command */ | ||||
| #define SNAPSHOT_FREEZE			"freeze" | ||||
| #define SNAPSHOT_UNFREEZE		"unfreeze" | ||||
| #define SNAPSHOT_SPLIT			"split" | ||||
| #define SNAPSHOT_RESYNC			"resync" | ||||
| #define SNAPSHOT_MOUNT			"mount" | ||||
| #define SNAPSHOT_UMOUNT			"umount" | ||||
|  | ||||
| /* 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)) | ||||
|  | ||||
| #define IsValidTime(tm)	\ | ||||
| 	((tm.tm_sec >= 0 && tm.tm_sec <= 60) && 	/* range check for tm_sec (0-60)  */ \ | ||||
| 	 (tm.tm_min >= 0 && tm.tm_min <= 59) && 	/* range check for tm_min (0-59)  */ \ | ||||
| 	 (tm.tm_hour >= 0 && tm.tm_hour <= 23) && 	/* range check for tm_hour(0-23)  */ \ | ||||
| 	 (tm.tm_mday >= 1 && tm.tm_mday <= 31) && 	/* range check for tm_mday(1-31)  */ \ | ||||
| 	 (tm.tm_mon >= 0 && tm.tm_mon <= 11) && 	/* range check for tm_mon (0-23)  */ \ | ||||
| 	 (tm.tm_year + 1900 >= 1900)) 			/* range check for tm_year(70-)    */ | ||||
|  | ||||
| /* 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,		/* archive 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; | ||||
| 	time_t		recovery_time; | ||||
| 	uint32		recovery_xid; | ||||
|  | ||||
| 	/* 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 struct pgRecoveryTarget | ||||
| { | ||||
| 	bool		time_specified; | ||||
| 	time_t		recovery_target_time; | ||||
| 	bool		xid_specified; | ||||
| 	unsigned int	recovery_target_xid; | ||||
| 	bool		recovery_target_inclusive; | ||||
| } pgRecoveryTarget; | ||||
|  | ||||
| typedef enum CompressionMode | ||||
| { | ||||
| 	NO_COMPRESSION, | ||||
| 	COMPRESSION, | ||||
| 	DECOMPRESSION, | ||||
| } CompressionMode; | ||||
|  | ||||
| /* | ||||
|  * return pointer that exceeds the length of prefix from character string. | ||||
|  * ex. str="/xxx/yyy/zzz", prefix="/xxx/yyy", return="zzz". | ||||
|  */ | ||||
| #define JoinPathEnd(str, prefix) \ | ||||
| 	((strlen(str) <= strlen(prefix)) ? "" : str + strlen(prefix) + 1) | ||||
|  | ||||
| /* 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); | ||||
| extern int get_server_version(void); | ||||
|  | ||||
| /* 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 int do_delete(pgBackupRange *range, bool force); | ||||
| 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, bool for_get_timeline, bool with_database); | ||||
|  | ||||
| /* 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, const char *prefix); | ||||
| 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 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, int server_version); | ||||
| 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 bool 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 bool 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 */ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * pg_rman.h: Backup/Recovery manager for PostgreSQL. | ||||
|  * | ||||
|  * Copyright (c) 2009-2011, 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" | ||||
|  | ||||
| #if PG_VERSION_NUM < 80200 | ||||
| #define XLOG_BLCKSZ		BLCKSZ | ||||
| #endif | ||||
|  | ||||
| #if PG_VERSION_NUM < 80300 | ||||
| #define TXID_CURRENT_SQL	"SELECT transactionid FROM pg_locks WHERE locktype = 'transactionid' AND pid = pg_backend_pid();" | ||||
| #include <sys/stat.h> | ||||
| #else | ||||
| #define TXID_CURRENT_SQL	"SELECT txid_current();" | ||||
| #endif | ||||
|  | ||||
| /* 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 PG_TBLSPC_DIR			"pg_tblspc" | ||||
| #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" | ||||
| #define SNAPSHOT_SCRIPT_FILE	"snapshot_script" | ||||
|  | ||||
| /* Snapshot script command */ | ||||
| #define SNAPSHOT_FREEZE			"freeze" | ||||
| #define SNAPSHOT_UNFREEZE		"unfreeze" | ||||
| #define SNAPSHOT_SPLIT			"split" | ||||
| #define SNAPSHOT_RESYNC			"resync" | ||||
| #define SNAPSHOT_MOUNT			"mount" | ||||
| #define SNAPSHOT_UMOUNT			"umount" | ||||
|  | ||||
| /* 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)) | ||||
|  | ||||
| #define IsValidTime(tm)	\ | ||||
| 	((tm.tm_sec >= 0 && tm.tm_sec <= 60) && 	/* range check for tm_sec (0-60)  */ \ | ||||
| 	 (tm.tm_min >= 0 && tm.tm_min <= 59) && 	/* range check for tm_min (0-59)  */ \ | ||||
| 	 (tm.tm_hour >= 0 && tm.tm_hour <= 23) && 	/* range check for tm_hour(0-23)  */ \ | ||||
| 	 (tm.tm_mday >= 1 && tm.tm_mday <= 31) && 	/* range check for tm_mday(1-31)  */ \ | ||||
| 	 (tm.tm_mon >= 0 && tm.tm_mon <= 11) && 	/* range check for tm_mon (0-23)  */ \ | ||||
| 	 (tm.tm_year + 1900 >= 1900)) 			/* range check for tm_year(70-)    */ | ||||
|  | ||||
| /* 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,		/* archive 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; | ||||
| 	time_t		recovery_time; | ||||
| 	uint32		recovery_xid; | ||||
|  | ||||
| 	/* 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 struct pgRecoveryTarget | ||||
| { | ||||
| 	bool		time_specified; | ||||
| 	time_t		recovery_target_time; | ||||
| 	bool		xid_specified; | ||||
| 	unsigned int	recovery_target_xid; | ||||
| 	bool		recovery_target_inclusive; | ||||
| } pgRecoveryTarget; | ||||
|  | ||||
| typedef enum CompressionMode | ||||
| { | ||||
| 	NO_COMPRESSION, | ||||
| 	COMPRESSION, | ||||
| 	DECOMPRESSION, | ||||
| } CompressionMode; | ||||
|  | ||||
| /* | ||||
|  * return pointer that exceeds the length of prefix from character string. | ||||
|  * ex. str="/xxx/yyy/zzz", prefix="/xxx/yyy", return="zzz". | ||||
|  */ | ||||
| #define JoinPathEnd(str, prefix) \ | ||||
| 	((strlen(str) <= strlen(prefix)) ? "" : str + strlen(prefix) + 1) | ||||
|  | ||||
| /* 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); | ||||
| extern int get_server_version(void); | ||||
|  | ||||
| /* 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 int do_delete(pgBackupRange *range, bool force); | ||||
| 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, bool for_get_timeline, bool with_database); | ||||
|  | ||||
| /* 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, const char *prefix); | ||||
| 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 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, int server_version); | ||||
| 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 bool 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 bool 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 */ | ||||
|   | ||||
							
								
								
									
										1030
									
								
								pgsql_src/pg_crc.c
									
									
									
									
									
								
							
							
						
						
									
										1030
									
								
								pgsql_src/pg_crc.c
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,105 +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); | ||||
| } | ||||
|  | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * 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); | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										386
									
								
								pgut/pgut-port.c
									
									
									
									
									
								
							
							
						
						
									
										386
									
								
								pgut/pgut-port.c
									
									
									
									
									
								
							| @@ -1,193 +1,193 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * pgut-port.c | ||||
|  * | ||||
|  * Copyright (c) 2009-2010, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #include "c.h" | ||||
| #include "pgut-port.h" | ||||
|  | ||||
| #undef flock | ||||
|  | ||||
| #include <unistd.h> | ||||
| #include <fcntl.h> | ||||
|  | ||||
| #ifdef WIN32 | ||||
|  | ||||
| #include <winioctl.h> | ||||
|  | ||||
| #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; | ||||
| } | ||||
|  | ||||
| #endif | ||||
|  | ||||
| #ifdef PGUT_FLOCK | ||||
|  | ||||
| #ifdef WIN32 | ||||
| int | ||||
| pgut_flock(int fd, int operation) | ||||
| { | ||||
| 	BOOL	ret; | ||||
| 	HANDLE	handle = (HANDLE) _get_osfhandle(fd); | ||||
| 	DWORD	lo = 0; | ||||
| 	DWORD	hi = 0; | ||||
|  | ||||
| 	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; | ||||
| } | ||||
|  | ||||
| #else | ||||
|  | ||||
| int | ||||
| pgut_flock(int fd, int operation) | ||||
| { | ||||
| 	struct flock	lck; | ||||
| 	int				cmd; | ||||
|  | ||||
| 	memset(&lck, 0, sizeof(lck)); | ||||
| 	lck.l_whence = SEEK_SET; | ||||
| 	lck.l_start = 0; | ||||
| 	lck.l_len = 0; | ||||
| 	lck.l_pid = getpid(); | ||||
|  | ||||
| 	if (operation & LOCK_UN) | ||||
| 		lck.l_type = F_UNLCK; | ||||
| 	else if (operation & LOCK_EX) | ||||
| 		lck.l_type = F_WRLCK; | ||||
| 	else | ||||
| 		lck.l_type = F_RDLCK; | ||||
|  | ||||
| 	if (operation & LOCK_NB) | ||||
| 		cmd = F_SETLK; | ||||
| 	else | ||||
| 		cmd = F_SETLKW; | ||||
|  | ||||
| 	return fcntl(fd, cmd, &lck); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #endif | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * pgut-port.c | ||||
|  * | ||||
|  * Copyright (c) 2009-2010, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #include "c.h" | ||||
| #include "pgut-port.h" | ||||
|  | ||||
| #undef flock | ||||
|  | ||||
| #include <unistd.h> | ||||
| #include <fcntl.h> | ||||
|  | ||||
| #ifdef WIN32 | ||||
|  | ||||
| #include <winioctl.h> | ||||
|  | ||||
| #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; | ||||
| } | ||||
|  | ||||
| #endif | ||||
|  | ||||
| #ifdef PGUT_FLOCK | ||||
|  | ||||
| #ifdef WIN32 | ||||
| int | ||||
| pgut_flock(int fd, int operation) | ||||
| { | ||||
| 	BOOL	ret; | ||||
| 	HANDLE	handle = (HANDLE) _get_osfhandle(fd); | ||||
| 	DWORD	lo = 0; | ||||
| 	DWORD	hi = 0; | ||||
|  | ||||
| 	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; | ||||
| } | ||||
|  | ||||
| #else | ||||
|  | ||||
| int | ||||
| pgut_flock(int fd, int operation) | ||||
| { | ||||
| 	struct flock	lck; | ||||
| 	int				cmd; | ||||
|  | ||||
| 	memset(&lck, 0, sizeof(lck)); | ||||
| 	lck.l_whence = SEEK_SET; | ||||
| 	lck.l_start = 0; | ||||
| 	lck.l_len = 0; | ||||
| 	lck.l_pid = getpid(); | ||||
|  | ||||
| 	if (operation & LOCK_UN) | ||||
| 		lck.l_type = F_UNLCK; | ||||
| 	else if (operation & LOCK_EX) | ||||
| 		lck.l_type = F_WRLCK; | ||||
| 	else | ||||
| 		lck.l_type = F_RDLCK; | ||||
|  | ||||
| 	if (operation & LOCK_NB) | ||||
| 		cmd = F_SETLK; | ||||
| 	else | ||||
| 		cmd = F_SETLKW; | ||||
|  | ||||
| 	return fcntl(fd, cmd, &lck); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #endif | ||||
|   | ||||
							
								
								
									
										100
									
								
								pgut/pgut-port.h
									
									
									
									
									
								
							
							
						
						
									
										100
									
								
								pgut/pgut-port.h
									
									
									
									
									
								
							| @@ -1,50 +1,50 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * pgut-port.h | ||||
|  * | ||||
|  * Copyright (c) 2009-2010, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #ifndef PGUT_PORT_H | ||||
| #define PGUT_PORT_H | ||||
|  | ||||
| /* | ||||
|  * readlink ports | ||||
|  */ | ||||
| #ifdef WIN32 | ||||
|  | ||||
| #define S_IFLNK			(0) | ||||
| #define S_IRWXG			(0) | ||||
| #define S_IRWXO			(0) | ||||
| #define S_ISLNK(mode)	(0) | ||||
|  | ||||
| extern ssize_t readlink(const char *path, char *target, size_t size); | ||||
|  | ||||
| #endif | ||||
|  | ||||
| /* | ||||
|  * flock ports | ||||
|  */ | ||||
| #ifndef LOCK_EX | ||||
|  | ||||
| #define PGUT_FLOCK | ||||
|  | ||||
| #undef LOCK_SH | ||||
| #undef LOCK_EX | ||||
| #undef LOCK_UN | ||||
| #undef LOCK_NB | ||||
|  | ||||
| #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 pgut_flock(int fd, int operation); | ||||
|  | ||||
| #define flock	pgut_flock | ||||
|  | ||||
| #endif | ||||
|  | ||||
| #endif   /* PGUT_PORT_H */ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * pgut-port.h | ||||
|  * | ||||
|  * Copyright (c) 2009-2010, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #ifndef PGUT_PORT_H | ||||
| #define PGUT_PORT_H | ||||
|  | ||||
| /* | ||||
|  * readlink ports | ||||
|  */ | ||||
| #ifdef WIN32 | ||||
|  | ||||
| #define S_IFLNK			(0) | ||||
| #define S_IRWXG			(0) | ||||
| #define S_IRWXO			(0) | ||||
| #define S_ISLNK(mode)	(0) | ||||
|  | ||||
| extern ssize_t readlink(const char *path, char *target, size_t size); | ||||
|  | ||||
| #endif | ||||
|  | ||||
| /* | ||||
|  * flock ports | ||||
|  */ | ||||
| #ifndef LOCK_EX | ||||
|  | ||||
| #define PGUT_FLOCK | ||||
|  | ||||
| #undef LOCK_SH | ||||
| #undef LOCK_EX | ||||
| #undef LOCK_UN | ||||
| #undef LOCK_NB | ||||
|  | ||||
| #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 pgut_flock(int fd, int operation); | ||||
|  | ||||
| #define flock	pgut_flock | ||||
|  | ||||
| #endif | ||||
|  | ||||
| #endif   /* PGUT_PORT_H */ | ||||
|   | ||||
							
								
								
									
										3408
									
								
								pgut/pgut.c
									
									
									
									
									
								
							
							
						
						
									
										3408
									
								
								pgut/pgut.c
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										506
									
								
								pgut/pgut.h
									
									
									
									
									
								
							
							
						
						
									
										506
									
								
								pgut/pgut.h
									
									
									
									
									
								
							| @@ -1,253 +1,253 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * pgut.h | ||||
|  * | ||||
|  * Copyright (c) 2009-2010, 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 signed integer | ||||
|  *	u: 32bit unsigned integer | ||||
|  *	I: 64bit signed 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); | ||||
|  | ||||
| #define pgut_new(type)			((type *) pgut_malloc(sizeof(type))) | ||||
| #define pgut_newarray(type, n)	((type *) pgut_malloc(sizeof(type) * (n))) | ||||
|  | ||||
| /* | ||||
|  * file operations | ||||
|  */ | ||||
| extern FILE *pgut_fopen(const char *path, const char *mode, bool missing_ok); | ||||
|  | ||||
| /* | ||||
|  * 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 */ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * pgut.h | ||||
|  * | ||||
|  * Copyright (c) 2009-2010, 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 signed integer | ||||
|  *	u: 32bit unsigned integer | ||||
|  *	I: 64bit signed 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); | ||||
|  | ||||
| #define pgut_new(type)			((type *) pgut_malloc(sizeof(type))) | ||||
| #define pgut_newarray(type, n)	((type *) pgut_malloc(sizeof(type) * (n))) | ||||
|  | ||||
| /* | ||||
|  * file operations | ||||
|  */ | ||||
| extern FILE *pgut_fopen(const char *path, const char *mode, bool missing_ok); | ||||
|  | ||||
| /* | ||||
|  * 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 */ | ||||
|   | ||||
							
								
								
									
										340
									
								
								queue.c
									
									
									
									
									
								
							
							
						
						
									
										340
									
								
								queue.c
									
									
									
									
									
								
							| @@ -1,170 +1,170 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * queue.c: Job queue with thread pooling. | ||||
|  * | ||||
|  * Copyright (c) 2009-2011, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #include "pg_rman.h" | ||||
| #include "pgut/pgut-pthread.h" | ||||
|  | ||||
| struct JobQueue | ||||
| { | ||||
| 	pthread_mutex_t		mutex;		/* protects the queue data */ | ||||
| 	pthread_cond_t		anyjobs;	/* fired if any jobs */ | ||||
| 	pthread_cond_t		nojobs;		/* fired if no jobs */ | ||||
| 	List			   *threads;	/* list of worker thread handles */ | ||||
| 	List			   *jobs;		/* pending jobs */ | ||||
| 	volatile int		maximum;	/* maximum allowed threads */ | ||||
| 	volatile int		idle;		/* number of idle threads */ | ||||
| 	volatile bool		terminated;	/* in termination? */ | ||||
| }; | ||||
|  | ||||
| static void *worker_thread(void *arg); | ||||
|  | ||||
| JobQueue * | ||||
| JobQueue_new(int nthreads) | ||||
| { | ||||
| 	JobQueue	*queue; | ||||
|  | ||||
| 	Assert(nthreads >= 1); | ||||
|  | ||||
| 	queue = pgut_new(JobQueue); | ||||
| 	pthread_mutex_init(&queue->mutex, NULL); | ||||
| 	pthread_cond_init(&queue->anyjobs, NULL); | ||||
| 	pthread_cond_init(&queue->nojobs, NULL); | ||||
| 	queue->threads = NIL; | ||||
| 	queue->jobs = NIL; | ||||
| 	queue->maximum = nthreads; | ||||
| 	queue->idle = 0; | ||||
| 	queue->terminated = false; | ||||
|  | ||||
| 	return queue; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Job must be allocated with malloc. The ownership will be granted to | ||||
|  * the queue. | ||||
|  */ | ||||
| void | ||||
| JobQueue_push(JobQueue *queue, Job *job) | ||||
| { | ||||
| 	Assert(queue); | ||||
| 	Assert(!queue->terminated); | ||||
| 	Assert(job); | ||||
| 	Assert(job->routine); | ||||
|  | ||||
| 	pgut_mutex_lock(&queue->mutex); | ||||
| 	queue->jobs = lappend(queue->jobs, job); | ||||
|  | ||||
| 	if (queue->idle > 0) | ||||
| 		pthread_cond_signal(&queue->anyjobs); | ||||
| 	else if (list_length(queue->threads) < queue->maximum) | ||||
| 	{ | ||||
| 		pthread_t	th; | ||||
|  | ||||
| 		if (pthread_create(&th, NULL, worker_thread, queue)) | ||||
| 			ereport(ERROR, | ||||
| 				(errcode_errno(), | ||||
| 				 errmsg("could not create thread: "))); | ||||
|  | ||||
| 		queue->threads = lappend(queue->threads, (void *) th); | ||||
| 		Assert(list_length(queue->threads) <= queue->maximum); | ||||
| 	} | ||||
|  | ||||
| 	pthread_mutex_unlock(&queue->mutex); | ||||
| } | ||||
|  | ||||
| /* wait for all job finished */ | ||||
| void | ||||
| JobQueue_wait(JobQueue *queue) | ||||
| { | ||||
| 	Assert(queue); | ||||
| 	Assert(!queue->terminated); | ||||
|  | ||||
| 	pgut_mutex_lock(&queue->mutex); | ||||
| 	while (queue->jobs || queue->idle < list_length(queue->threads)) | ||||
| 		pgut_cond_wait(&queue->nojobs, &queue->mutex); | ||||
| 	pthread_mutex_unlock(&queue->mutex); | ||||
| } | ||||
|  | ||||
| /* Free job queue. All pending jobs are also discarded. */ | ||||
| void | ||||
| JobQueue_free(JobQueue *queue) | ||||
| { | ||||
| 	ListCell *cell; | ||||
|  | ||||
| 	if (queue == NULL) | ||||
| 		return; | ||||
|  | ||||
| 	Assert(!queue->terminated); | ||||
|  | ||||
| 	/* Terminate all threads. */ | ||||
| 	pgut_mutex_lock(&queue->mutex); | ||||
| 	queue->terminated = true; | ||||
| 	pthread_cond_broadcast(&queue->anyjobs); | ||||
| 	pthread_mutex_unlock(&queue->mutex); | ||||
|  | ||||
| 	/* | ||||
| 	 * Wait for all threads. | ||||
| 	 * XXX: cancel thread for long running jobs? | ||||
| 	 */ | ||||
| 	foreach(cell, queue->threads) | ||||
| 	{ | ||||
| 		pthread_t	th = (pthread_t) lfirst(cell); | ||||
|  | ||||
| 		pthread_join(th, NULL); | ||||
| 	} | ||||
| 	list_free(queue->threads); | ||||
|  | ||||
| 	/* Free all pending jobs, though it must be avoided. */ | ||||
| 	list_free_deep(queue->jobs); | ||||
|  | ||||
| 	pthread_cond_destroy(&queue->nojobs); | ||||
| 	pthread_cond_destroy(&queue->anyjobs); | ||||
| 	pthread_mutex_destroy(&queue->mutex); | ||||
| 	free(queue); | ||||
| } | ||||
|  | ||||
| static void * | ||||
| worker_thread(void *arg) | ||||
| { | ||||
| 	JobQueue *queue = (JobQueue *) arg; | ||||
|  | ||||
| 	pgut_mutex_lock(&queue->mutex); | ||||
| 	while (!queue->terminated) | ||||
| 	{ | ||||
| 		Job *job; | ||||
|  | ||||
| 		if (queue->jobs == NIL) | ||||
| 		{ | ||||
| 			queue->idle++; | ||||
|  | ||||
| 			/* notify if done all jobs */ | ||||
| 			if (queue->idle >= list_length(queue->threads)) | ||||
| 				pthread_cond_broadcast(&queue->nojobs); | ||||
|  | ||||
| 			pgut_cond_wait(&queue->anyjobs, &queue->mutex); | ||||
|  | ||||
| 			queue->idle--; | ||||
| 			if (queue->terminated) | ||||
| 				break; | ||||
| 		} | ||||
|  | ||||
| 		if (queue->jobs == NIL) | ||||
| 			continue;	/* job might have done by another worker */ | ||||
|  | ||||
| 		job = linitial(queue->jobs); | ||||
| 		queue->jobs = list_delete_first(queue->jobs); | ||||
|  | ||||
| 		pthread_mutex_unlock(&queue->mutex); | ||||
| 		job->routine(job); | ||||
| 		free(job); | ||||
| 		pgut_mutex_lock(&queue->mutex); | ||||
| 	} | ||||
| 	pthread_mutex_unlock(&queue->mutex); | ||||
|  | ||||
| 	return NULL; | ||||
| } | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * queue.c: Job queue with thread pooling. | ||||
|  * | ||||
|  * Copyright (c) 2009-2011, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #include "pg_rman.h" | ||||
| #include "pgut/pgut-pthread.h" | ||||
|  | ||||
| struct JobQueue | ||||
| { | ||||
| 	pthread_mutex_t		mutex;		/* protects the queue data */ | ||||
| 	pthread_cond_t		anyjobs;	/* fired if any jobs */ | ||||
| 	pthread_cond_t		nojobs;		/* fired if no jobs */ | ||||
| 	List			   *threads;	/* list of worker thread handles */ | ||||
| 	List			   *jobs;		/* pending jobs */ | ||||
| 	volatile int		maximum;	/* maximum allowed threads */ | ||||
| 	volatile int		idle;		/* number of idle threads */ | ||||
| 	volatile bool		terminated;	/* in termination? */ | ||||
| }; | ||||
|  | ||||
| static void *worker_thread(void *arg); | ||||
|  | ||||
| JobQueue * | ||||
| JobQueue_new(int nthreads) | ||||
| { | ||||
| 	JobQueue	*queue; | ||||
|  | ||||
| 	Assert(nthreads >= 1); | ||||
|  | ||||
| 	queue = pgut_new(JobQueue); | ||||
| 	pthread_mutex_init(&queue->mutex, NULL); | ||||
| 	pthread_cond_init(&queue->anyjobs, NULL); | ||||
| 	pthread_cond_init(&queue->nojobs, NULL); | ||||
| 	queue->threads = NIL; | ||||
| 	queue->jobs = NIL; | ||||
| 	queue->maximum = nthreads; | ||||
| 	queue->idle = 0; | ||||
| 	queue->terminated = false; | ||||
|  | ||||
| 	return queue; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Job must be allocated with malloc. The ownership will be granted to | ||||
|  * the queue. | ||||
|  */ | ||||
| void | ||||
| JobQueue_push(JobQueue *queue, Job *job) | ||||
| { | ||||
| 	Assert(queue); | ||||
| 	Assert(!queue->terminated); | ||||
| 	Assert(job); | ||||
| 	Assert(job->routine); | ||||
|  | ||||
| 	pgut_mutex_lock(&queue->mutex); | ||||
| 	queue->jobs = lappend(queue->jobs, job); | ||||
|  | ||||
| 	if (queue->idle > 0) | ||||
| 		pthread_cond_signal(&queue->anyjobs); | ||||
| 	else if (list_length(queue->threads) < queue->maximum) | ||||
| 	{ | ||||
| 		pthread_t	th; | ||||
|  | ||||
| 		if (pthread_create(&th, NULL, worker_thread, queue)) | ||||
| 			ereport(ERROR, | ||||
| 				(errcode_errno(), | ||||
| 				 errmsg("could not create thread: "))); | ||||
|  | ||||
| 		queue->threads = lappend(queue->threads, (void *) th); | ||||
| 		Assert(list_length(queue->threads) <= queue->maximum); | ||||
| 	} | ||||
|  | ||||
| 	pthread_mutex_unlock(&queue->mutex); | ||||
| } | ||||
|  | ||||
| /* wait for all job finished */ | ||||
| void | ||||
| JobQueue_wait(JobQueue *queue) | ||||
| { | ||||
| 	Assert(queue); | ||||
| 	Assert(!queue->terminated); | ||||
|  | ||||
| 	pgut_mutex_lock(&queue->mutex); | ||||
| 	while (queue->jobs || queue->idle < list_length(queue->threads)) | ||||
| 		pgut_cond_wait(&queue->nojobs, &queue->mutex); | ||||
| 	pthread_mutex_unlock(&queue->mutex); | ||||
| } | ||||
|  | ||||
| /* Free job queue. All pending jobs are also discarded. */ | ||||
| void | ||||
| JobQueue_free(JobQueue *queue) | ||||
| { | ||||
| 	ListCell *cell; | ||||
|  | ||||
| 	if (queue == NULL) | ||||
| 		return; | ||||
|  | ||||
| 	Assert(!queue->terminated); | ||||
|  | ||||
| 	/* Terminate all threads. */ | ||||
| 	pgut_mutex_lock(&queue->mutex); | ||||
| 	queue->terminated = true; | ||||
| 	pthread_cond_broadcast(&queue->anyjobs); | ||||
| 	pthread_mutex_unlock(&queue->mutex); | ||||
|  | ||||
| 	/* | ||||
| 	 * Wait for all threads. | ||||
| 	 * XXX: cancel thread for long running jobs? | ||||
| 	 */ | ||||
| 	foreach(cell, queue->threads) | ||||
| 	{ | ||||
| 		pthread_t	th = (pthread_t) lfirst(cell); | ||||
|  | ||||
| 		pthread_join(th, NULL); | ||||
| 	} | ||||
| 	list_free(queue->threads); | ||||
|  | ||||
| 	/* Free all pending jobs, though it must be avoided. */ | ||||
| 	list_free_deep(queue->jobs); | ||||
|  | ||||
| 	pthread_cond_destroy(&queue->nojobs); | ||||
| 	pthread_cond_destroy(&queue->anyjobs); | ||||
| 	pthread_mutex_destroy(&queue->mutex); | ||||
| 	free(queue); | ||||
| } | ||||
|  | ||||
| static void * | ||||
| worker_thread(void *arg) | ||||
| { | ||||
| 	JobQueue *queue = (JobQueue *) arg; | ||||
|  | ||||
| 	pgut_mutex_lock(&queue->mutex); | ||||
| 	while (!queue->terminated) | ||||
| 	{ | ||||
| 		Job *job; | ||||
|  | ||||
| 		if (queue->jobs == NIL) | ||||
| 		{ | ||||
| 			queue->idle++; | ||||
|  | ||||
| 			/* notify if done all jobs */ | ||||
| 			if (queue->idle >= list_length(queue->threads)) | ||||
| 				pthread_cond_broadcast(&queue->nojobs); | ||||
|  | ||||
| 			pgut_cond_wait(&queue->anyjobs, &queue->mutex); | ||||
|  | ||||
| 			queue->idle--; | ||||
| 			if (queue->terminated) | ||||
| 				break; | ||||
| 		} | ||||
|  | ||||
| 		if (queue->jobs == NIL) | ||||
| 			continue;	/* job might have done by another worker */ | ||||
|  | ||||
| 		job = linitial(queue->jobs); | ||||
| 		queue->jobs = list_delete_first(queue->jobs); | ||||
|  | ||||
| 		pthread_mutex_unlock(&queue->mutex); | ||||
| 		job->routine(job); | ||||
| 		free(job); | ||||
| 		pgut_mutex_lock(&queue->mutex); | ||||
| 	} | ||||
| 	pthread_mutex_unlock(&queue->mutex); | ||||
|  | ||||
| 	return NULL; | ||||
| } | ||||
|   | ||||
| @@ -1,306 +1,306 @@ | ||||
| #!/bin/sh | ||||
| ############################################################################# | ||||
| #  Copyright (c) 2009-2010, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
| ############################################################################# | ||||
|  | ||||
| CMD_SUDO="/usr/bin/sudo" | ||||
| CMD_LVCREATE="${CMD_SUDO} /usr/sbin/lvcreate" | ||||
| CMD_LVREMOVE="${CMD_SUDO} /usr/sbin/lvremove" | ||||
| CMD_MOUNT="${CMD_SUDO} /bin/mount" | ||||
| CMD_UMOUNT="${CMD_SUDO} /bin/umount" | ||||
|  | ||||
| INFO=-2 | ||||
| WARNING=-1 | ||||
| ERROR=0 | ||||
|  | ||||
| # | ||||
| # Please set the information necessary for the snapshot acquisition | ||||
| # to each array. | ||||
| #  | ||||
| # The following arrays are one sets. | ||||
| #  | ||||
| # SNAPSHOT_NAME .... name for snapshot volume | ||||
| # SNAPSHOT_SIZE .... size to allocate for snapshot volume | ||||
| # SNAPSHOT_VG ...... volume group to which logical volume to create snapshot belongs | ||||
| # SNAPSHOT_LV ...... logical volume to create snapshot | ||||
| # SNAPSHOT_MOUNT ... mount point to file-system of snapshot volume | ||||
| #  | ||||
| # increase and decrease the slot in proportion to the number of acquisition | ||||
| # of snapshots. | ||||
| #  | ||||
| # example: | ||||
| # | ||||
| # SNAPSHOT_NAME[0]="snap00" | ||||
| # SNAPSHOT_SIZE[0]="2G" | ||||
| # SNAPSHOT_VG[0]="/dev/VolumeGroup00"  | ||||
| # SNAPSHOT_LV[0]="/dev/VolumeGroup00/LogicalVolume00" | ||||
| # SNAPSHOT_MOUNT[0]="/mnt/pgdata" | ||||
| # | ||||
| # SNAPSHOT_NAME[1]="snap01" | ||||
| # SNAPSHOT_SIZE[1]="2G" | ||||
| # SNAPSHOT_VG[1]="/dev/VolumeGroup00"  | ||||
| # SNAPSHOT_LV[1]="/dev/VolumeGroup00/LogicalVolume01" | ||||
| # SNAPSHOT_MOUNT[1]="/mnt/tblspc/account" | ||||
| # | ||||
| #SNAPSHOT_NAME[0]="" | ||||
| #SNAPSHOT_SIZE[0]="" | ||||
| #SNAPSHOT_VG[0]=""  | ||||
| #SNAPSHOT_LV[0]="" | ||||
| #SNAPSHOT_MOUNT[0]="" | ||||
|  | ||||
| # | ||||
| # Please set the tablespace name and tablespace storage path in snapshot  | ||||
| # to each array. | ||||
| #  | ||||
| # The following arrays are one sets. | ||||
| #  | ||||
| # SNAPSHOT_TBLSPC ........ name of tablespace in snapshot volume | ||||
| # SNAPSHOT_TBLSPC_DIR .... stored directory of tablespace in snapshot volume | ||||
| # | ||||
| # Note: set 'PG-DATA' to SNAPSHOT_TBLSPC when PGDATA in snapshot volume. | ||||
| # | ||||
| # increase and decrease the slot in proportion to the number of acquisition | ||||
| # of tablespace. | ||||
| #  | ||||
| # example: | ||||
| # | ||||
| # SNAPSHOT_TBLSPC[0]="PG-DATA" | ||||
| # SNAPSHOT_TBLSPC_DIR[0]="/mnt/pgdata/pgdata" | ||||
| # SNAPSHOT_TBLSPC[1]="custom" | ||||
| # SNAPSHOT_TBLSPC_DIR[1]="/mnt/tblspc_custom/tblspc/custom" | ||||
| # SNAPSHOT_TBLSPC[2]="account" | ||||
| # SNAPSHOT_TBLSPC_DIR[2]="/mnt/tblspc_account/tblspc/account" | ||||
| # | ||||
| #SNAPSHOT_TBLSPC[0]="" | ||||
| #SNAPSHOT_TBLSPC_DIR[0]="" | ||||
|  | ||||
| # | ||||
| # argument of the command. | ||||
| # this variables are set by set_args(). | ||||
| # | ||||
| ARGS_SS_NAME=""  # SNAPSHOT_NAME[N] | ||||
| ARGS_SS_SIZE=""  # SNAPSHOT_SIZE[N] | ||||
| ARGS_SS_VG=""    # SNAPSHOT_VG[N] | ||||
| ARGS_SS_LV=""    # SNAPSHOT_LV[N] | ||||
| ARGS_SS_MOUNT="" # SNAPSHOT_MOUNT[N] | ||||
|  | ||||
|  | ||||
| # | ||||
| # implement of interface 'freeze'. | ||||
| # don't remove this function even if there is no necessity. | ||||
| # | ||||
| function freeze() | ||||
| { | ||||
| 	# nothing to do | ||||
| 	return | ||||
| } | ||||
|  | ||||
| # | ||||
| # implement of interface 'unfreeze'. | ||||
| # don't remove this function even if there is no necessity. | ||||
| # | ||||
| function unfreeze() | ||||
| { | ||||
| 	# nothing to do | ||||
| 	return | ||||
| } | ||||
|  | ||||
| # | ||||
| # implement of interface 'split'. | ||||
| # create a snapshot volume from the setting of the specified slot. | ||||
| # don't remove this function even if there is no necessity. | ||||
| #  | ||||
| function split() | ||||
| { | ||||
| 	local i=0 | ||||
| 	 | ||||
| 	for ss_name in "${SNAPSHOT_NAME[@]}" | ||||
| 	do | ||||
| 		set_args "${ss_name}" "${SNAPSHOT_SIZE[${i}]}" "" "${SNAPSHOT_LV[${i}]}" "" | ||||
| 		execute_split | ||||
| 		i=$(expr ${i} + 1) | ||||
| 	done | ||||
| 	 | ||||
| 	# print tablespace name | ||||
| 	i=0 | ||||
| 	for tblspc in "${SNAPSHOT_TBLSPC[@]}" | ||||
| 	do | ||||
| 		local tblspc="${SNAPSHOT_TBLSPC[${i}]}" | ||||
| 		 | ||||
| 		echo "${tblspc}" | ||||
| 		i=$(expr ${i} + 1) | ||||
| 	done | ||||
| 	return | ||||
| } | ||||
|  | ||||
| # | ||||
| # implement of interface 'resync'. | ||||
| # remove a snapshot volume from the setting of the specified slot. | ||||
| # don't remove this function even if there is no necessity. | ||||
| # | ||||
| function resync() | ||||
| { | ||||
| 	local i=0 | ||||
| 	 | ||||
| 	for ss_name in "${SNAPSHOT_NAME[@]}" | ||||
| 	do | ||||
| 		set_args "${ss_name}" "" "${SNAPSHOT_VG[${i}]}" "" "" | ||||
| 		execute_resync | ||||
| 		i=$(expr ${i} + 1) | ||||
| 	done | ||||
| 	return | ||||
| } | ||||
|  | ||||
| # | ||||
| # implement of interface 'mount'. | ||||
| # create mount point of the snapshot volume to the file-system. | ||||
| # don't remove this function even if there is no necessity. | ||||
| # | ||||
| function mount() | ||||
| { | ||||
| 	local i=0 | ||||
| 	 | ||||
| 	for ss_name in "${SNAPSHOT_NAME[@]}" | ||||
| 	do | ||||
| 		set_args "${ss_name}" "" "${SNAPSHOT_VG[${i}]}" "" "${SNAPSHOT_MOUNT[${i}]}" | ||||
| 		execute_mount | ||||
| 		i=$(expr ${i} + 1) | ||||
| 	done | ||||
| 	 | ||||
| 	# print tablespace name and stored directory | ||||
| 	i=0 | ||||
| 	for tblspc in "${SNAPSHOT_TBLSPC[@]}" | ||||
| 	do | ||||
| 		local tblspc_mp="${SNAPSHOT_TBLSPC_DIR[${i}]}" | ||||
| 		 | ||||
| 		echo "${tblspc}=${tblspc_mp}" | ||||
| 		i=$(expr ${i} + 1) | ||||
| 	done | ||||
| 	return | ||||
| } | ||||
|  | ||||
| # | ||||
| # implement of interface 'umount'. | ||||
| # remove mount point of the snapshot volume from the file-system. | ||||
| # don't remove this function even if there is no necessity. | ||||
| # | ||||
| function umount() | ||||
| { | ||||
| 	for ss_mp in "${SNAPSHOT_MOUNT[@]}" | ||||
| 	do | ||||
| 		set_args "" "" "" "" "${ss_mp}" | ||||
| 		execute_umount | ||||
| 	done | ||||
| 	return | ||||
| } | ||||
|  | ||||
| # | ||||
| # create the snapshot volume. | ||||
| # | ||||
| function execute_split() | ||||
| { | ||||
| 	${CMD_LVCREATE} --snapshot --size=${ARGS_SS_SIZE} --name="${ARGS_SS_NAME}" "${ARGS_SS_LV}" > /dev/null | ||||
| 	[ ${?} -ne 0 ] && \ | ||||
| 		print_log ${ERROR} "${CMD_LVCREATE} command failed: ${ARGS_SS_LV}" | ||||
| } | ||||
|  | ||||
| # | ||||
| # remove the snapshot volume. | ||||
| # | ||||
| function execute_resync() | ||||
| { | ||||
| 	${CMD_LVREMOVE} -f "${ARGS_SS_VG}/${ARGS_SS_NAME}" > /dev/null | ||||
| 	[ ${?} -ne 0 ] && \ | ||||
| 		print_log ${ERROR} "${CMD_LVREMOVE} command failed: ${ARGS_SS_VG}/${ARGS_SS_NAME}" | ||||
| } | ||||
|  | ||||
| # | ||||
| # mount the snapshot volume to file-system. | ||||
| # | ||||
| function execute_mount() | ||||
| { | ||||
| 	${CMD_MOUNT} "${ARGS_SS_VG}/${ARGS_SS_NAME}" "${ARGS_SS_MOUNT}" > /dev/null | ||||
| 	[ ${?} -ne 0 ] && \ | ||||
| 		print_log ${ERROR} "${CMD_MOUNT} command failed: ${ARGS_SS_MOUNT}" | ||||
| } | ||||
|  | ||||
| # | ||||
| # unmount the directory from file-system in snapshot volume. | ||||
| # | ||||
| function execute_umount() | ||||
| { | ||||
| 	${CMD_UMOUNT} "${ARGS_SS_MOUNT}" > /dev/null | ||||
| 	[ ${?} -ne 0 ] && \ | ||||
| 		print_log ${ERROR} "${CMD_UMOUNT} command failed: ${ARGS_SS_MOUNT}" | ||||
| } | ||||
|  | ||||
| # | ||||
| # set argument of command to execute. | ||||
| # | ||||
| set_args() | ||||
| { | ||||
| 	ARGS_SS_NAME="${1}" | ||||
| 	ARGS_SS_SIZE="${2}" | ||||
| 	ARGS_SS_VG="${3}" | ||||
| 	ARGS_SS_LV="${4}" | ||||
| 	ARGS_SS_MOUNT="${5}" | ||||
| } | ||||
|  | ||||
| # | ||||
| # output the log message and abort the script when level <= ERROR | ||||
| # | ||||
| function print_log() | ||||
| { | ||||
| 	local level="${1}" | ||||
| 	local message="${2}" | ||||
| 	 | ||||
| 	# if cleanup enable change ERROR to WARNING | ||||
| 	[ -n "${cleanup}" -a ${level} -ge 0 ] && \ | ||||
| 		level=${WARNING} | ||||
| 	 | ||||
| 	case "${level}" in | ||||
| 		${INFO} ) # INFO | ||||
| 			echo "INFO: ${message}" 1>&2 | ||||
| 		;; | ||||
| 		${WARNING} ) # WARNING | ||||
| 			echo "WARNING: ${message}" 1>&2 | ||||
| 		;; | ||||
| 		${ERROR} ) # ERROR | ||||
| 			echo "ERROR: ${message}" 1>&2 | ||||
| 		;; | ||||
| 	esac | ||||
| 	[ ${level} -ge 0 ] && exit | ||||
| } | ||||
|  | ||||
| # | ||||
| # main | ||||
| # | ||||
| command="${1}" | ||||
| cleanup="${2}" | ||||
|  | ||||
| case "${command}" in | ||||
| 	"freeze" ) | ||||
| 		freeze | ||||
| 	;; | ||||
| 	"unfreeze" ) | ||||
| 		unfreeze | ||||
| 	;; | ||||
| 	"split" ) | ||||
| 		split | ||||
| 	;; | ||||
| 	"resync" ) | ||||
| 		resync | ||||
| 	;; | ||||
| 	"mount" ) | ||||
| 		mount | ||||
| 	;; | ||||
| 	"umount" ) | ||||
| 		umount | ||||
| 	;; | ||||
| 	* ) | ||||
| 		print_log ${ERROR} "specified invalid command: ${command} (internal error)" | ||||
| 	;; | ||||
| esac | ||||
|  | ||||
| echo "SUCCESS" | ||||
| exit | ||||
| #!/bin/sh | ||||
| ############################################################################# | ||||
| #  Copyright (c) 2009-2010, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
| ############################################################################# | ||||
|  | ||||
| CMD_SUDO="/usr/bin/sudo" | ||||
| CMD_LVCREATE="${CMD_SUDO} /usr/sbin/lvcreate" | ||||
| CMD_LVREMOVE="${CMD_SUDO} /usr/sbin/lvremove" | ||||
| CMD_MOUNT="${CMD_SUDO} /bin/mount" | ||||
| CMD_UMOUNT="${CMD_SUDO} /bin/umount" | ||||
|  | ||||
| INFO=-2 | ||||
| WARNING=-1 | ||||
| ERROR=0 | ||||
|  | ||||
| # | ||||
| # Please set the information necessary for the snapshot acquisition | ||||
| # to each array. | ||||
| #  | ||||
| # The following arrays are one sets. | ||||
| #  | ||||
| # SNAPSHOT_NAME .... name for snapshot volume | ||||
| # SNAPSHOT_SIZE .... size to allocate for snapshot volume | ||||
| # SNAPSHOT_VG ...... volume group to which logical volume to create snapshot belongs | ||||
| # SNAPSHOT_LV ...... logical volume to create snapshot | ||||
| # SNAPSHOT_MOUNT ... mount point to file-system of snapshot volume | ||||
| #  | ||||
| # increase and decrease the slot in proportion to the number of acquisition | ||||
| # of snapshots. | ||||
| #  | ||||
| # example: | ||||
| # | ||||
| # SNAPSHOT_NAME[0]="snap00" | ||||
| # SNAPSHOT_SIZE[0]="2G" | ||||
| # SNAPSHOT_VG[0]="/dev/VolumeGroup00"  | ||||
| # SNAPSHOT_LV[0]="/dev/VolumeGroup00/LogicalVolume00" | ||||
| # SNAPSHOT_MOUNT[0]="/mnt/pgdata" | ||||
| # | ||||
| # SNAPSHOT_NAME[1]="snap01" | ||||
| # SNAPSHOT_SIZE[1]="2G" | ||||
| # SNAPSHOT_VG[1]="/dev/VolumeGroup00"  | ||||
| # SNAPSHOT_LV[1]="/dev/VolumeGroup00/LogicalVolume01" | ||||
| # SNAPSHOT_MOUNT[1]="/mnt/tblspc/account" | ||||
| # | ||||
| #SNAPSHOT_NAME[0]="" | ||||
| #SNAPSHOT_SIZE[0]="" | ||||
| #SNAPSHOT_VG[0]=""  | ||||
| #SNAPSHOT_LV[0]="" | ||||
| #SNAPSHOT_MOUNT[0]="" | ||||
|  | ||||
| # | ||||
| # Please set the tablespace name and tablespace storage path in snapshot  | ||||
| # to each array. | ||||
| #  | ||||
| # The following arrays are one sets. | ||||
| #  | ||||
| # SNAPSHOT_TBLSPC ........ name of tablespace in snapshot volume | ||||
| # SNAPSHOT_TBLSPC_DIR .... stored directory of tablespace in snapshot volume | ||||
| # | ||||
| # Note: set 'PG-DATA' to SNAPSHOT_TBLSPC when PGDATA in snapshot volume. | ||||
| # | ||||
| # increase and decrease the slot in proportion to the number of acquisition | ||||
| # of tablespace. | ||||
| #  | ||||
| # example: | ||||
| # | ||||
| # SNAPSHOT_TBLSPC[0]="PG-DATA" | ||||
| # SNAPSHOT_TBLSPC_DIR[0]="/mnt/pgdata/pgdata" | ||||
| # SNAPSHOT_TBLSPC[1]="custom" | ||||
| # SNAPSHOT_TBLSPC_DIR[1]="/mnt/tblspc_custom/tblspc/custom" | ||||
| # SNAPSHOT_TBLSPC[2]="account" | ||||
| # SNAPSHOT_TBLSPC_DIR[2]="/mnt/tblspc_account/tblspc/account" | ||||
| # | ||||
| #SNAPSHOT_TBLSPC[0]="" | ||||
| #SNAPSHOT_TBLSPC_DIR[0]="" | ||||
|  | ||||
| # | ||||
| # argument of the command. | ||||
| # this variables are set by set_args(). | ||||
| # | ||||
| ARGS_SS_NAME=""  # SNAPSHOT_NAME[N] | ||||
| ARGS_SS_SIZE=""  # SNAPSHOT_SIZE[N] | ||||
| ARGS_SS_VG=""    # SNAPSHOT_VG[N] | ||||
| ARGS_SS_LV=""    # SNAPSHOT_LV[N] | ||||
| ARGS_SS_MOUNT="" # SNAPSHOT_MOUNT[N] | ||||
|  | ||||
|  | ||||
| # | ||||
| # implement of interface 'freeze'. | ||||
| # don't remove this function even if there is no necessity. | ||||
| # | ||||
| function freeze() | ||||
| { | ||||
| 	# nothing to do | ||||
| 	return | ||||
| } | ||||
|  | ||||
| # | ||||
| # implement of interface 'unfreeze'. | ||||
| # don't remove this function even if there is no necessity. | ||||
| # | ||||
| function unfreeze() | ||||
| { | ||||
| 	# nothing to do | ||||
| 	return | ||||
| } | ||||
|  | ||||
| # | ||||
| # implement of interface 'split'. | ||||
| # create a snapshot volume from the setting of the specified slot. | ||||
| # don't remove this function even if there is no necessity. | ||||
| #  | ||||
| function split() | ||||
| { | ||||
| 	local i=0 | ||||
| 	 | ||||
| 	for ss_name in "${SNAPSHOT_NAME[@]}" | ||||
| 	do | ||||
| 		set_args "${ss_name}" "${SNAPSHOT_SIZE[${i}]}" "" "${SNAPSHOT_LV[${i}]}" "" | ||||
| 		execute_split | ||||
| 		i=$(expr ${i} + 1) | ||||
| 	done | ||||
| 	 | ||||
| 	# print tablespace name | ||||
| 	i=0 | ||||
| 	for tblspc in "${SNAPSHOT_TBLSPC[@]}" | ||||
| 	do | ||||
| 		local tblspc="${SNAPSHOT_TBLSPC[${i}]}" | ||||
| 		 | ||||
| 		echo "${tblspc}" | ||||
| 		i=$(expr ${i} + 1) | ||||
| 	done | ||||
| 	return | ||||
| } | ||||
|  | ||||
| # | ||||
| # implement of interface 'resync'. | ||||
| # remove a snapshot volume from the setting of the specified slot. | ||||
| # don't remove this function even if there is no necessity. | ||||
| # | ||||
| function resync() | ||||
| { | ||||
| 	local i=0 | ||||
| 	 | ||||
| 	for ss_name in "${SNAPSHOT_NAME[@]}" | ||||
| 	do | ||||
| 		set_args "${ss_name}" "" "${SNAPSHOT_VG[${i}]}" "" "" | ||||
| 		execute_resync | ||||
| 		i=$(expr ${i} + 1) | ||||
| 	done | ||||
| 	return | ||||
| } | ||||
|  | ||||
| # | ||||
| # implement of interface 'mount'. | ||||
| # create mount point of the snapshot volume to the file-system. | ||||
| # don't remove this function even if there is no necessity. | ||||
| # | ||||
| function mount() | ||||
| { | ||||
| 	local i=0 | ||||
| 	 | ||||
| 	for ss_name in "${SNAPSHOT_NAME[@]}" | ||||
| 	do | ||||
| 		set_args "${ss_name}" "" "${SNAPSHOT_VG[${i}]}" "" "${SNAPSHOT_MOUNT[${i}]}" | ||||
| 		execute_mount | ||||
| 		i=$(expr ${i} + 1) | ||||
| 	done | ||||
| 	 | ||||
| 	# print tablespace name and stored directory | ||||
| 	i=0 | ||||
| 	for tblspc in "${SNAPSHOT_TBLSPC[@]}" | ||||
| 	do | ||||
| 		local tblspc_mp="${SNAPSHOT_TBLSPC_DIR[${i}]}" | ||||
| 		 | ||||
| 		echo "${tblspc}=${tblspc_mp}" | ||||
| 		i=$(expr ${i} + 1) | ||||
| 	done | ||||
| 	return | ||||
| } | ||||
|  | ||||
| # | ||||
| # implement of interface 'umount'. | ||||
| # remove mount point of the snapshot volume from the file-system. | ||||
| # don't remove this function even if there is no necessity. | ||||
| # | ||||
| function umount() | ||||
| { | ||||
| 	for ss_mp in "${SNAPSHOT_MOUNT[@]}" | ||||
| 	do | ||||
| 		set_args "" "" "" "" "${ss_mp}" | ||||
| 		execute_umount | ||||
| 	done | ||||
| 	return | ||||
| } | ||||
|  | ||||
| # | ||||
| # create the snapshot volume. | ||||
| # | ||||
| function execute_split() | ||||
| { | ||||
| 	${CMD_LVCREATE} --snapshot --size=${ARGS_SS_SIZE} --name="${ARGS_SS_NAME}" "${ARGS_SS_LV}" > /dev/null | ||||
| 	[ ${?} -ne 0 ] && \ | ||||
| 		print_log ${ERROR} "${CMD_LVCREATE} command failed: ${ARGS_SS_LV}" | ||||
| } | ||||
|  | ||||
| # | ||||
| # remove the snapshot volume. | ||||
| # | ||||
| function execute_resync() | ||||
| { | ||||
| 	${CMD_LVREMOVE} -f "${ARGS_SS_VG}/${ARGS_SS_NAME}" > /dev/null | ||||
| 	[ ${?} -ne 0 ] && \ | ||||
| 		print_log ${ERROR} "${CMD_LVREMOVE} command failed: ${ARGS_SS_VG}/${ARGS_SS_NAME}" | ||||
| } | ||||
|  | ||||
| # | ||||
| # mount the snapshot volume to file-system. | ||||
| # | ||||
| function execute_mount() | ||||
| { | ||||
| 	${CMD_MOUNT} "${ARGS_SS_VG}/${ARGS_SS_NAME}" "${ARGS_SS_MOUNT}" > /dev/null | ||||
| 	[ ${?} -ne 0 ] && \ | ||||
| 		print_log ${ERROR} "${CMD_MOUNT} command failed: ${ARGS_SS_MOUNT}" | ||||
| } | ||||
|  | ||||
| # | ||||
| # unmount the directory from file-system in snapshot volume. | ||||
| # | ||||
| function execute_umount() | ||||
| { | ||||
| 	${CMD_UMOUNT} "${ARGS_SS_MOUNT}" > /dev/null | ||||
| 	[ ${?} -ne 0 ] && \ | ||||
| 		print_log ${ERROR} "${CMD_UMOUNT} command failed: ${ARGS_SS_MOUNT}" | ||||
| } | ||||
|  | ||||
| # | ||||
| # set argument of command to execute. | ||||
| # | ||||
| set_args() | ||||
| { | ||||
| 	ARGS_SS_NAME="${1}" | ||||
| 	ARGS_SS_SIZE="${2}" | ||||
| 	ARGS_SS_VG="${3}" | ||||
| 	ARGS_SS_LV="${4}" | ||||
| 	ARGS_SS_MOUNT="${5}" | ||||
| } | ||||
|  | ||||
| # | ||||
| # output the log message and abort the script when level <= ERROR | ||||
| # | ||||
| function print_log() | ||||
| { | ||||
| 	local level="${1}" | ||||
| 	local message="${2}" | ||||
| 	 | ||||
| 	# if cleanup enable change ERROR to WARNING | ||||
| 	[ -n "${cleanup}" -a ${level} -ge 0 ] && \ | ||||
| 		level=${WARNING} | ||||
| 	 | ||||
| 	case "${level}" in | ||||
| 		${INFO} ) # INFO | ||||
| 			echo "INFO: ${message}" 1>&2 | ||||
| 		;; | ||||
| 		${WARNING} ) # WARNING | ||||
| 			echo "WARNING: ${message}" 1>&2 | ||||
| 		;; | ||||
| 		${ERROR} ) # ERROR | ||||
| 			echo "ERROR: ${message}" 1>&2 | ||||
| 		;; | ||||
| 	esac | ||||
| 	[ ${level} -ge 0 ] && exit | ||||
| } | ||||
|  | ||||
| # | ||||
| # main | ||||
| # | ||||
| command="${1}" | ||||
| cleanup="${2}" | ||||
|  | ||||
| case "${command}" in | ||||
| 	"freeze" ) | ||||
| 		freeze | ||||
| 	;; | ||||
| 	"unfreeze" ) | ||||
| 		unfreeze | ||||
| 	;; | ||||
| 	"split" ) | ||||
| 		split | ||||
| 	;; | ||||
| 	"resync" ) | ||||
| 		resync | ||||
| 	;; | ||||
| 	"mount" ) | ||||
| 		mount | ||||
| 	;; | ||||
| 	"umount" ) | ||||
| 		umount | ||||
| 	;; | ||||
| 	* ) | ||||
| 		print_log ${ERROR} "specified invalid command: ${command} (internal error)" | ||||
| 	;; | ||||
| esac | ||||
|  | ||||
| echo "SUCCESS" | ||||
| exit | ||||
|   | ||||
							
								
								
									
										512
									
								
								show.c
									
									
									
									
									
								
							
							
						
						
									
										512
									
								
								show.c
									
									
									
									
									
								
							| @@ -1,256 +1,256 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * show.c: show backup catalog. | ||||
|  * | ||||
|  * Copyright (c) 2009-2011, 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){ | ||||
| 			elog(ERROR_SYSTEM, _("can't process any more.")); | ||||
| 		} | ||||
|  | ||||
| 		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); | ||||
| } | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * show.c: show backup catalog. | ||||
|  * | ||||
|  * Copyright (c) 2009-2011, 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){ | ||||
| 			elog(ERROR_SYSTEM, _("can't process any more.")); | ||||
| 		} | ||||
|  | ||||
| 		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); | ||||
| } | ||||
|   | ||||
| @@ -1,281 +1,281 @@ | ||||
| #!/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 | ||||
| wal_level = archive | ||||
| 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 | ||||
| 	pg_rman -w -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 | ||||
| pg_rman -w -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 | ||||
| pg_rman -w -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 | ||||
| pg_rman -w -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 | ||||
| pg_rman -w -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 | ||||
| pg_rman -w -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 | ||||
|  | ||||
| #!/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 | ||||
| wal_level = archive | ||||
| 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 | ||||
| 	pg_rman -w -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 | ||||
| pg_rman -w -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 | ||||
| pg_rman -w -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 | ||||
| pg_rman -w -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 | ||||
| pg_rman -w -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 | ||||
| pg_rman -w -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 | ||||
|  | ||||
|   | ||||
							
								
								
									
										160
									
								
								sql/option.sh
									
									
									
									
									
								
							
							
						
						
									
										160
									
								
								sql/option.sh
									
									
									
									
									
								
							| @@ -1,80 +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 | ||||
| #!/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 | ||||
|   | ||||
							
								
								
									
										164
									
								
								util.c
									
									
									
									
									
								
							
							
						
						
									
										164
									
								
								util.c
									
									
									
									
									
								
							| @@ -1,82 +1,82 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * util.c: log messages to log file or stderr, and misc code. | ||||
|  * | ||||
|  * Copyright (c) 2009-2011, 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); | ||||
|  | ||||
| 	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'; | ||||
| } | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * util.c: log messages to log file or stderr, and misc code. | ||||
|  * | ||||
|  * Copyright (c) 2009-2011, 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); | ||||
|  | ||||
| 	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'; | ||||
| } | ||||
|   | ||||
							
								
								
									
										972
									
								
								utils.c
									
									
									
									
									
								
							
							
						
						
									
										972
									
								
								utils.c
									
									
									
									
									
								
							| @@ -1,486 +1,486 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * utils.c: | ||||
|  * | ||||
|  * Copyright (c) 2009-2011, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #include "pg_rman.h" | ||||
|  | ||||
| #include <dirent.h> | ||||
| #include <time.h> | ||||
| #include <limits.h> | ||||
|  | ||||
| #ifdef WIN32 | ||||
| #include <winioctl.h> | ||||
| #endif | ||||
|  | ||||
| /* | ||||
|  * Convert time_t value to ISO-8601 format string. | ||||
|  * The size of buffer must be larger than DATESTRLEN. | ||||
|  */ | ||||
| char * | ||||
| date2str(char *buf, time_t date) | ||||
| { | ||||
| 	struct tm *tm = localtime(&date); | ||||
| 	strftime(buf, DATESTRLEN, "%Y-%m-%d %H:%M:%S", tm); | ||||
| 	return buf; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * The size of buffer must be larger than TIMESTRLEN. | ||||
|  */ | ||||
| char * | ||||
| time2str(char *buf, time_t time) | ||||
| { | ||||
| 	/* set empty if nagative duration */ | ||||
| 	if (time < 0) | ||||
| 		buf[0] = '\0'; | ||||
| 	else if (time >= 100 * 24 * 60 * 60) | ||||
| 		snprintf(buf, TIMESTRLEN, "%.1fd", time / 86400.0); | ||||
| 	else if (time >= 60 * 60) | ||||
| 		snprintf(buf, TIMESTRLEN, "%.1fh", time / 3600.0); | ||||
| 	else if (time >= 60) | ||||
| 		snprintf(buf, TIMESTRLEN, "%.1fm", time / 60.0); | ||||
| 	else | ||||
| 		snprintf(buf, TIMESTRLEN, "%lds", (long) time); | ||||
| 	return buf; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * The size of buffer must be larger than SIZESTRLEN. | ||||
|  */ | ||||
| char * | ||||
| size2str(char *buf, int64 size) | ||||
| { | ||||
| 	int		exp; | ||||
| 	int64	base; | ||||
| 	double	n; | ||||
| 	static const char *units[] = { "B ", "KB", "MB", "GB", "TB", "PB" }; | ||||
|  | ||||
| 	/* set empty if nagative size */ | ||||
| 	if (size < 0) | ||||
| 	{ | ||||
| 		buf[0] = '\0'; | ||||
| 		return buf; | ||||
| 	} | ||||
|  | ||||
| 	/* determine the unit */ | ||||
| 	for (exp = 0, base = 1; | ||||
| 		 exp < lengthof(units) && base * 1024 < size; | ||||
| 		 ++exp, base *= 1024) | ||||
| 		 ; | ||||
|  | ||||
| 	n = size / (double) base; | ||||
| 	if (n >= 100.0) | ||||
| 		snprintf(buf, SIZESTRLEN, "%4.0f%s", n, units[exp]); | ||||
| 	else if (n >= 10.0) | ||||
| 		snprintf(buf, SIZESTRLEN, "%3.1f%s", n, units[exp]); | ||||
| 	else | ||||
| 		snprintf(buf, SIZESTRLEN, "%3.2f%s", n, units[exp]); | ||||
|  | ||||
| 	return buf; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Parse for backup mode. empty input is treated as full. | ||||
|  */ | ||||
| BackupMode | ||||
| parse_backup_mode(const char *value) | ||||
| { | ||||
| 	const char *v = value; | ||||
| 	size_t		len; | ||||
|  | ||||
| 	if (v == NULL) | ||||
| 		return MODE_FULL;	/* null input is full. */ | ||||
|  | ||||
| 	while (IsSpace(*v)) { v++; } | ||||
| 	if ((len = strlen(v)) == 0) | ||||
| 		return MODE_FULL;	/* empty input is full. */ | ||||
|  | ||||
| 	/* Do a prefix match. For example, "incr" means incremental.  */ | ||||
| 	if (pg_strncasecmp("full", v, len) == 0) | ||||
| 		return MODE_FULL; | ||||
| 	else if (pg_strncasecmp("incremental", v, len) == 0) | ||||
| 		return MODE_INCREMENTAL; | ||||
| 	else if (pg_strncasecmp("archive", v, len) == 0) | ||||
| 		return MODE_ARCHIVE; | ||||
|  | ||||
| 	ereport(ERROR, | ||||
| 		(errcode(EINVAL), | ||||
| 		 errmsg("invalid backup mode: '%s'", value))); | ||||
| 	return (BackupMode) -1; | ||||
| } | ||||
|  | ||||
| XLogName | ||||
| parse_xlogname(const char *value) | ||||
| { | ||||
| 	XLogName	xlog; | ||||
| 	char		junk[2]; | ||||
|  | ||||
| 	if (sscanf(value, "%08X%08X%08X%1s", | ||||
| 		&xlog.tli, &xlog.log, &xlog.seg, junk) != 3) | ||||
| 		ereport(ERROR, | ||||
| 			(errcode(EINVAL), | ||||
| 			 errmsg("invalid xlog name: '%s'", value))); | ||||
|  | ||||
| 	return xlog; | ||||
| } | ||||
|  | ||||
| /* return max value of time_t */ | ||||
| time_t | ||||
| time_max(void) | ||||
| { | ||||
| 	static time_t	value = 0; | ||||
|  | ||||
| 	if (value == 0) | ||||
| 	{ | ||||
| 		if (sizeof(time_t) > sizeof(int32)) | ||||
| 		{ | ||||
| 			struct tm	tm = { 0 }; | ||||
|  | ||||
| 			/* '9999-12-31 23:59:59' for 64bit time_t */ | ||||
| 			tm.tm_year = 9999 - 1900; | ||||
| 			tm.tm_mon = 12 - 1; | ||||
| 			tm.tm_mday = 31; | ||||
| 			tm.tm_hour = 23; | ||||
| 			tm.tm_min = 59; | ||||
| 			tm.tm_sec = 59; | ||||
|  | ||||
| 			value = mktime(&tm); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			/* '2038-01-19 03:14:07' for 32bit time_t */ | ||||
| 			value = INT_MAX; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return value; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Create range object from one or two arguments. | ||||
|  * All not-digit characters in the argument(s) are igonred. | ||||
|  */ | ||||
| pgRange | ||||
| make_range(int argc, char * const *argv) | ||||
| { | ||||
| 	pgRange		range; | ||||
| 	const char *arg1; | ||||
| 	const char *arg2; | ||||
| 	size_t		len; | ||||
| 	char	   *tmp; | ||||
| 	int			i; | ||||
| 	struct tm	tm; | ||||
| 	char		junk[2]; | ||||
|  | ||||
| 	/* takes 0, 1, or 2 arguments */ | ||||
| 	if (argc > 2) | ||||
| 		ereport(ERROR, | ||||
| 			(errcode(EINVAL), | ||||
| 			 errmsg("too many arguments"))); | ||||
|  | ||||
| 	/* no input means unlimited range */ | ||||
| 	if (argc < 1) | ||||
| 	{ | ||||
| 		range.begin = 0; | ||||
| 		range.end = time_max(); | ||||
| 		return range; | ||||
| 	} | ||||
|  | ||||
| 	arg1 = argv[0]; | ||||
| 	arg2 = (argc > 1 ? argv[1] : ""); | ||||
|  | ||||
| 	/* tmp = replace( concat(arg1, arg2), !isalnum, ' ' ) */ | ||||
| 	tmp = pgut_malloc(strlen(arg1) + strlen(arg2) + 1); | ||||
| 	len = 0; | ||||
| 	for (i = 0; arg1[i]; i++) | ||||
| 		tmp[len++] = (IsAlnum(arg1[i]) ? arg1[i] : ' '); | ||||
| 	for (i = 0; arg2[i]; i++) | ||||
| 		tmp[len++] = (IsAlnum(arg2[i]) ? arg2[i] : ' '); | ||||
| 	tmp[len] = '\0'; | ||||
|  | ||||
| 	/* parse for "YYYY-MM-DD HH:MI:SS" */ | ||||
| 	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; | ||||
| 	i = sscanf(tmp, "%04d %02d %02d %02d %02d %02d%1s", | ||||
| 		&tm.tm_year, &tm.tm_mon, &tm.tm_mday, | ||||
| 		&tm.tm_hour, &tm.tm_min, &tm.tm_sec, junk); | ||||
| 	if (i < 1 || 6 < i) | ||||
| 		ereport(ERROR, | ||||
| 			(errcode(EINVAL), | ||||
| 			 errmsg("invalid range syntax: '%s'", tmp))); | ||||
|  | ||||
| 	free(tmp); | ||||
|  | ||||
| 	/* adjust year */ | ||||
| 	if (tm.tm_year < 100) | ||||
| 		tm.tm_year += 2000 - 1900; | ||||
| 	else if (tm.tm_year >= 1900) | ||||
| 		tm.tm_year -= 1900; | ||||
|  | ||||
| 	/* adjust month */ | ||||
| 	if (i > 1) | ||||
| 		tm.tm_mon -= 1; | ||||
|  | ||||
| 	range.begin = mktime(&tm); | ||||
| 	switch (i) | ||||
| 	{ | ||||
| 		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); | ||||
|  | ||||
| 	return range; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * check path is a directory and returns errno of opendir. | ||||
|  * ENOENT is treated as succeeded if missing_ok. | ||||
|  */ | ||||
| int | ||||
| check_dir(const char *path, bool missing_ok) | ||||
| { | ||||
| 	DIR	   *dir; | ||||
|  | ||||
| 	if ((dir = opendir(path)) == NULL) | ||||
| 	{ | ||||
| 		if (missing_ok && errno == ENOENT) | ||||
| 			return 0; | ||||
| 		else | ||||
| 			return errno; | ||||
| 	} | ||||
|  | ||||
| 	closedir(dir); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * make sure the directory either doesn't exist or is empty | ||||
|  * | ||||
|  * Returns 0 if nonexistent, 1 if exists and empty, 2 if not empty, | ||||
|  * or -1 if trouble accessing directory | ||||
|  */ | ||||
| void | ||||
| make_empty_dir(const char *path) | ||||
| { | ||||
| 	DIR *dir; | ||||
|  | ||||
| 	errno = 0; | ||||
| 	if ((dir = opendir(path)) == NULL) | ||||
| 	{ | ||||
| 		/* Directory does not exist. */ | ||||
| 		if (errno != ENOENT) | ||||
| 			ereport(ERROR, | ||||
| 				(errcode_errno(), | ||||
| 				 errmsg("could not access directory \"%s\": ", path))); | ||||
|  | ||||
| 		pgut_mkdir(path); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		/* Directory exists. */ | ||||
| 		struct dirent *file; | ||||
|  | ||||
| 		while ((file = readdir(dir)) != NULL) | ||||
| 		{ | ||||
| 			if (strcmp(".", file->d_name) == 0 || | ||||
| 				strcmp("..", file->d_name) == 0) | ||||
| 			{ | ||||
| 				/* skip this and parent directory */ | ||||
| 				continue; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				/* Present and not empty */ | ||||
| 				closedir(dir); | ||||
|  | ||||
| 				ereport(ERROR, | ||||
| 					(errcode(EEXIST), | ||||
| 					 errmsg("directory \"%s\" exists but is not empty", path))); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| #ifdef WIN32 | ||||
| 		/* | ||||
| 		 * This fix is in mingw cvs (runtime/mingwex/dirent.c rev 1.4), | ||||
| 		 * but not in released version | ||||
| 		 */ | ||||
| 		if (GetLastError() == ERROR_NO_MORE_FILES) | ||||
| 			errno = 0; | ||||
| #endif | ||||
| 		closedir(dir); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Remove files recursively, but follow symbolic link to directories. | ||||
|  * We remove the symbolic link files, but delete the linked directories. | ||||
|  */ | ||||
| void | ||||
| remove_file(const char *path) | ||||
| { | ||||
| 	remove_children(path); | ||||
|  | ||||
| 	if (remove(path) != 0 && errno != ENOENT) | ||||
| 		elog(ERROR, "could not remove file \"%s\": %s", path, strerror(errno)); | ||||
| } | ||||
|  | ||||
| void | ||||
| remove_children(const char *path) | ||||
| { | ||||
| 	DIR	   *dir; | ||||
|  | ||||
| 	/* try to open as directory and remove children. */ | ||||
| 	if ((dir = opendir(path)) != NULL) | ||||
| 	{ | ||||
| 		struct dirent  *dent; | ||||
|  | ||||
| 		while ((dent = readdir(dir)) != NULL) | ||||
| 		{ | ||||
| 			char	child[MAXPGPATH]; | ||||
|  | ||||
| 			/* skip entries point current dir or parent dir */ | ||||
| 			if (strcmp(dent->d_name, ".") == 0 || | ||||
| 				strcmp(dent->d_name, "..") == 0) | ||||
| 				continue; | ||||
|  | ||||
| 			join_path_components(child, path, dent->d_name); | ||||
| 			remove_file(child); | ||||
| 		} | ||||
|  | ||||
| 		closedir(dir); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| #ifdef WIN32 | ||||
|  | ||||
| #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; | ||||
| } | ||||
|  | ||||
| #endif | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * utils.c: | ||||
|  * | ||||
|  * Copyright (c) 2009-2011, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #include "pg_rman.h" | ||||
|  | ||||
| #include <dirent.h> | ||||
| #include <time.h> | ||||
| #include <limits.h> | ||||
|  | ||||
| #ifdef WIN32 | ||||
| #include <winioctl.h> | ||||
| #endif | ||||
|  | ||||
| /* | ||||
|  * Convert time_t value to ISO-8601 format string. | ||||
|  * The size of buffer must be larger than DATESTRLEN. | ||||
|  */ | ||||
| char * | ||||
| date2str(char *buf, time_t date) | ||||
| { | ||||
| 	struct tm *tm = localtime(&date); | ||||
| 	strftime(buf, DATESTRLEN, "%Y-%m-%d %H:%M:%S", tm); | ||||
| 	return buf; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * The size of buffer must be larger than TIMESTRLEN. | ||||
|  */ | ||||
| char * | ||||
| time2str(char *buf, time_t time) | ||||
| { | ||||
| 	/* set empty if nagative duration */ | ||||
| 	if (time < 0) | ||||
| 		buf[0] = '\0'; | ||||
| 	else if (time >= 100 * 24 * 60 * 60) | ||||
| 		snprintf(buf, TIMESTRLEN, "%.1fd", time / 86400.0); | ||||
| 	else if (time >= 60 * 60) | ||||
| 		snprintf(buf, TIMESTRLEN, "%.1fh", time / 3600.0); | ||||
| 	else if (time >= 60) | ||||
| 		snprintf(buf, TIMESTRLEN, "%.1fm", time / 60.0); | ||||
| 	else | ||||
| 		snprintf(buf, TIMESTRLEN, "%lds", (long) time); | ||||
| 	return buf; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * The size of buffer must be larger than SIZESTRLEN. | ||||
|  */ | ||||
| char * | ||||
| size2str(char *buf, int64 size) | ||||
| { | ||||
| 	int		exp; | ||||
| 	int64	base; | ||||
| 	double	n; | ||||
| 	static const char *units[] = { "B ", "KB", "MB", "GB", "TB", "PB" }; | ||||
|  | ||||
| 	/* set empty if nagative size */ | ||||
| 	if (size < 0) | ||||
| 	{ | ||||
| 		buf[0] = '\0'; | ||||
| 		return buf; | ||||
| 	} | ||||
|  | ||||
| 	/* determine the unit */ | ||||
| 	for (exp = 0, base = 1; | ||||
| 		 exp < lengthof(units) && base * 1024 < size; | ||||
| 		 ++exp, base *= 1024) | ||||
| 		 ; | ||||
|  | ||||
| 	n = size / (double) base; | ||||
| 	if (n >= 100.0) | ||||
| 		snprintf(buf, SIZESTRLEN, "%4.0f%s", n, units[exp]); | ||||
| 	else if (n >= 10.0) | ||||
| 		snprintf(buf, SIZESTRLEN, "%3.1f%s", n, units[exp]); | ||||
| 	else | ||||
| 		snprintf(buf, SIZESTRLEN, "%3.2f%s", n, units[exp]); | ||||
|  | ||||
| 	return buf; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Parse for backup mode. empty input is treated as full. | ||||
|  */ | ||||
| BackupMode | ||||
| parse_backup_mode(const char *value) | ||||
| { | ||||
| 	const char *v = value; | ||||
| 	size_t		len; | ||||
|  | ||||
| 	if (v == NULL) | ||||
| 		return MODE_FULL;	/* null input is full. */ | ||||
|  | ||||
| 	while (IsSpace(*v)) { v++; } | ||||
| 	if ((len = strlen(v)) == 0) | ||||
| 		return MODE_FULL;	/* empty input is full. */ | ||||
|  | ||||
| 	/* Do a prefix match. For example, "incr" means incremental.  */ | ||||
| 	if (pg_strncasecmp("full", v, len) == 0) | ||||
| 		return MODE_FULL; | ||||
| 	else if (pg_strncasecmp("incremental", v, len) == 0) | ||||
| 		return MODE_INCREMENTAL; | ||||
| 	else if (pg_strncasecmp("archive", v, len) == 0) | ||||
| 		return MODE_ARCHIVE; | ||||
|  | ||||
| 	ereport(ERROR, | ||||
| 		(errcode(EINVAL), | ||||
| 		 errmsg("invalid backup mode: '%s'", value))); | ||||
| 	return (BackupMode) -1; | ||||
| } | ||||
|  | ||||
| XLogName | ||||
| parse_xlogname(const char *value) | ||||
| { | ||||
| 	XLogName	xlog; | ||||
| 	char		junk[2]; | ||||
|  | ||||
| 	if (sscanf(value, "%08X%08X%08X%1s", | ||||
| 		&xlog.tli, &xlog.log, &xlog.seg, junk) != 3) | ||||
| 		ereport(ERROR, | ||||
| 			(errcode(EINVAL), | ||||
| 			 errmsg("invalid xlog name: '%s'", value))); | ||||
|  | ||||
| 	return xlog; | ||||
| } | ||||
|  | ||||
| /* return max value of time_t */ | ||||
| time_t | ||||
| time_max(void) | ||||
| { | ||||
| 	static time_t	value = 0; | ||||
|  | ||||
| 	if (value == 0) | ||||
| 	{ | ||||
| 		if (sizeof(time_t) > sizeof(int32)) | ||||
| 		{ | ||||
| 			struct tm	tm = { 0 }; | ||||
|  | ||||
| 			/* '9999-12-31 23:59:59' for 64bit time_t */ | ||||
| 			tm.tm_year = 9999 - 1900; | ||||
| 			tm.tm_mon = 12 - 1; | ||||
| 			tm.tm_mday = 31; | ||||
| 			tm.tm_hour = 23; | ||||
| 			tm.tm_min = 59; | ||||
| 			tm.tm_sec = 59; | ||||
|  | ||||
| 			value = mktime(&tm); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			/* '2038-01-19 03:14:07' for 32bit time_t */ | ||||
| 			value = INT_MAX; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return value; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Create range object from one or two arguments. | ||||
|  * All not-digit characters in the argument(s) are igonred. | ||||
|  */ | ||||
| pgRange | ||||
| make_range(int argc, char * const *argv) | ||||
| { | ||||
| 	pgRange		range; | ||||
| 	const char *arg1; | ||||
| 	const char *arg2; | ||||
| 	size_t		len; | ||||
| 	char	   *tmp; | ||||
| 	int			i; | ||||
| 	struct tm	tm; | ||||
| 	char		junk[2]; | ||||
|  | ||||
| 	/* takes 0, 1, or 2 arguments */ | ||||
| 	if (argc > 2) | ||||
| 		ereport(ERROR, | ||||
| 			(errcode(EINVAL), | ||||
| 			 errmsg("too many arguments"))); | ||||
|  | ||||
| 	/* no input means unlimited range */ | ||||
| 	if (argc < 1) | ||||
| 	{ | ||||
| 		range.begin = 0; | ||||
| 		range.end = time_max(); | ||||
| 		return range; | ||||
| 	} | ||||
|  | ||||
| 	arg1 = argv[0]; | ||||
| 	arg2 = (argc > 1 ? argv[1] : ""); | ||||
|  | ||||
| 	/* tmp = replace( concat(arg1, arg2), !isalnum, ' ' ) */ | ||||
| 	tmp = pgut_malloc(strlen(arg1) + strlen(arg2) + 1); | ||||
| 	len = 0; | ||||
| 	for (i = 0; arg1[i]; i++) | ||||
| 		tmp[len++] = (IsAlnum(arg1[i]) ? arg1[i] : ' '); | ||||
| 	for (i = 0; arg2[i]; i++) | ||||
| 		tmp[len++] = (IsAlnum(arg2[i]) ? arg2[i] : ' '); | ||||
| 	tmp[len] = '\0'; | ||||
|  | ||||
| 	/* parse for "YYYY-MM-DD HH:MI:SS" */ | ||||
| 	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; | ||||
| 	i = sscanf(tmp, "%04d %02d %02d %02d %02d %02d%1s", | ||||
| 		&tm.tm_year, &tm.tm_mon, &tm.tm_mday, | ||||
| 		&tm.tm_hour, &tm.tm_min, &tm.tm_sec, junk); | ||||
| 	if (i < 1 || 6 < i) | ||||
| 		ereport(ERROR, | ||||
| 			(errcode(EINVAL), | ||||
| 			 errmsg("invalid range syntax: '%s'", tmp))); | ||||
|  | ||||
| 	free(tmp); | ||||
|  | ||||
| 	/* adjust year */ | ||||
| 	if (tm.tm_year < 100) | ||||
| 		tm.tm_year += 2000 - 1900; | ||||
| 	else if (tm.tm_year >= 1900) | ||||
| 		tm.tm_year -= 1900; | ||||
|  | ||||
| 	/* adjust month */ | ||||
| 	if (i > 1) | ||||
| 		tm.tm_mon -= 1; | ||||
|  | ||||
| 	range.begin = mktime(&tm); | ||||
| 	switch (i) | ||||
| 	{ | ||||
| 		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); | ||||
|  | ||||
| 	return range; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * check path is a directory and returns errno of opendir. | ||||
|  * ENOENT is treated as succeeded if missing_ok. | ||||
|  */ | ||||
| int | ||||
| check_dir(const char *path, bool missing_ok) | ||||
| { | ||||
| 	DIR	   *dir; | ||||
|  | ||||
| 	if ((dir = opendir(path)) == NULL) | ||||
| 	{ | ||||
| 		if (missing_ok && errno == ENOENT) | ||||
| 			return 0; | ||||
| 		else | ||||
| 			return errno; | ||||
| 	} | ||||
|  | ||||
| 	closedir(dir); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * make sure the directory either doesn't exist or is empty | ||||
|  * | ||||
|  * Returns 0 if nonexistent, 1 if exists and empty, 2 if not empty, | ||||
|  * or -1 if trouble accessing directory | ||||
|  */ | ||||
| void | ||||
| make_empty_dir(const char *path) | ||||
| { | ||||
| 	DIR *dir; | ||||
|  | ||||
| 	errno = 0; | ||||
| 	if ((dir = opendir(path)) == NULL) | ||||
| 	{ | ||||
| 		/* Directory does not exist. */ | ||||
| 		if (errno != ENOENT) | ||||
| 			ereport(ERROR, | ||||
| 				(errcode_errno(), | ||||
| 				 errmsg("could not access directory \"%s\": ", path))); | ||||
|  | ||||
| 		pgut_mkdir(path); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		/* Directory exists. */ | ||||
| 		struct dirent *file; | ||||
|  | ||||
| 		while ((file = readdir(dir)) != NULL) | ||||
| 		{ | ||||
| 			if (strcmp(".", file->d_name) == 0 || | ||||
| 				strcmp("..", file->d_name) == 0) | ||||
| 			{ | ||||
| 				/* skip this and parent directory */ | ||||
| 				continue; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				/* Present and not empty */ | ||||
| 				closedir(dir); | ||||
|  | ||||
| 				ereport(ERROR, | ||||
| 					(errcode(EEXIST), | ||||
| 					 errmsg("directory \"%s\" exists but is not empty", path))); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| #ifdef WIN32 | ||||
| 		/* | ||||
| 		 * This fix is in mingw cvs (runtime/mingwex/dirent.c rev 1.4), | ||||
| 		 * but not in released version | ||||
| 		 */ | ||||
| 		if (GetLastError() == ERROR_NO_MORE_FILES) | ||||
| 			errno = 0; | ||||
| #endif | ||||
| 		closedir(dir); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Remove files recursively, but follow symbolic link to directories. | ||||
|  * We remove the symbolic link files, but delete the linked directories. | ||||
|  */ | ||||
| void | ||||
| remove_file(const char *path) | ||||
| { | ||||
| 	remove_children(path); | ||||
|  | ||||
| 	if (remove(path) != 0 && errno != ENOENT) | ||||
| 		elog(ERROR, "could not remove file \"%s\": %s", path, strerror(errno)); | ||||
| } | ||||
|  | ||||
| void | ||||
| remove_children(const char *path) | ||||
| { | ||||
| 	DIR	   *dir; | ||||
|  | ||||
| 	/* try to open as directory and remove children. */ | ||||
| 	if ((dir = opendir(path)) != NULL) | ||||
| 	{ | ||||
| 		struct dirent  *dent; | ||||
|  | ||||
| 		while ((dent = readdir(dir)) != NULL) | ||||
| 		{ | ||||
| 			char	child[MAXPGPATH]; | ||||
|  | ||||
| 			/* skip entries point current dir or parent dir */ | ||||
| 			if (strcmp(dent->d_name, ".") == 0 || | ||||
| 				strcmp(dent->d_name, "..") == 0) | ||||
| 				continue; | ||||
|  | ||||
| 			join_path_components(child, path, dent->d_name); | ||||
| 			remove_file(child); | ||||
| 		} | ||||
|  | ||||
| 		closedir(dir); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| #ifdef WIN32 | ||||
|  | ||||
| #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; | ||||
| } | ||||
|  | ||||
| #endif | ||||
|   | ||||
							
								
								
									
										420
									
								
								validate.c
									
									
									
									
									
								
							
							
						
						
									
										420
									
								
								validate.c
									
									
									
									
									
								
							| @@ -1,210 +1,210 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * validate.c: validate backup files. | ||||
|  * | ||||
|  * Copyright (c) 2009-2011, 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; | ||||
| 	int ret; | ||||
| 	bool another_pg_rman = false; | ||||
|  | ||||
| 	ret = catalog_lock(); | ||||
| 	if (ret == 1) | ||||
| 		another_pg_rman = true; | ||||
|  | ||||
| 	/* get backup list matches given range */ | ||||
| 	backup_list = catalog_get_backup_list(range); | ||||
| 	if(!backup_list){ | ||||
| 		elog(ERROR_SYSTEM, _("can't process any more.")); | ||||
| 	} | ||||
| 	parray_qsort(backup_list, pgBackupCompareId); | ||||
| 	for (i = 0; i < parray_num(backup_list); i++) | ||||
| 	{ | ||||
| 		pgBackup *backup = (pgBackup *)parray_get(backup_list, i); | ||||
|  | ||||
| 		/* clean extra backups (switch STATUS to ERROR) */ | ||||
| 		if(!another_pg_rman && | ||||
| 		   (backup->status == BACKUP_STATUS_RUNNING || backup->status == BACKUP_STATUS_DELETING)){ | ||||
| 			backup->status = BACKUP_STATUS_ERROR; | ||||
| 			pgBackupWriteIni(backup); | ||||
| 		} | ||||
|  | ||||
| 		/* Validate completed backups only. */ | ||||
| 		if (backup->status != BACKUP_STATUS_DONE) | ||||
| 			continue; | ||||
|  | ||||
| 		/* validate with CRC value and update status to OK */ | ||||
| 		pgBackupValidate(backup, false, false, (HAVE_DATABASE(backup))); | ||||
| 	} | ||||
|  | ||||
| 	/* 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, bool for_get_timeline, bool with_database) | ||||
| { | ||||
| 	char	timestamp[100]; | ||||
| 	char	base_path[MAXPGPATH]; | ||||
| 	char	path[MAXPGPATH]; | ||||
| 	parray *files; | ||||
| 	bool	corrupted = false; | ||||
|  | ||||
| 	time2iso(timestamp, lengthof(timestamp), backup->start_time); | ||||
| 	if(!for_get_timeline){ | ||||
| 		if (with_database) | ||||
| 			elog(INFO, "validate: %s backup and archive log files by %s", timestamp, (size_only ? "SIZE" : "CRC")); | ||||
| 		else{ | ||||
| 			if (backup->backup_mode == BACKUP_MODE_ARCHIVE) | ||||
| 				elog(INFO, "validate: %s archive log files by %s", timestamp, (size_only ? "SIZE" : "CRC")); | ||||
| 			else if (backup->with_serverlog) | ||||
| 				elog(INFO, "validate: %s server log files by %s", timestamp, (size_only ? "SIZE" : "CRC")); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if(!check){ | ||||
| 		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; | ||||
| } | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * validate.c: validate backup files. | ||||
|  * | ||||
|  * Copyright (c) 2009-2011, 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; | ||||
| 	int ret; | ||||
| 	bool another_pg_rman = false; | ||||
|  | ||||
| 	ret = catalog_lock(); | ||||
| 	if (ret == 1) | ||||
| 		another_pg_rman = true; | ||||
|  | ||||
| 	/* get backup list matches given range */ | ||||
| 	backup_list = catalog_get_backup_list(range); | ||||
| 	if(!backup_list){ | ||||
| 		elog(ERROR_SYSTEM, _("can't process any more.")); | ||||
| 	} | ||||
| 	parray_qsort(backup_list, pgBackupCompareId); | ||||
| 	for (i = 0; i < parray_num(backup_list); i++) | ||||
| 	{ | ||||
| 		pgBackup *backup = (pgBackup *)parray_get(backup_list, i); | ||||
|  | ||||
| 		/* clean extra backups (switch STATUS to ERROR) */ | ||||
| 		if(!another_pg_rman && | ||||
| 		   (backup->status == BACKUP_STATUS_RUNNING || backup->status == BACKUP_STATUS_DELETING)){ | ||||
| 			backup->status = BACKUP_STATUS_ERROR; | ||||
| 			pgBackupWriteIni(backup); | ||||
| 		} | ||||
|  | ||||
| 		/* Validate completed backups only. */ | ||||
| 		if (backup->status != BACKUP_STATUS_DONE) | ||||
| 			continue; | ||||
|  | ||||
| 		/* validate with CRC value and update status to OK */ | ||||
| 		pgBackupValidate(backup, false, false, (HAVE_DATABASE(backup))); | ||||
| 	} | ||||
|  | ||||
| 	/* 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, bool for_get_timeline, bool with_database) | ||||
| { | ||||
| 	char	timestamp[100]; | ||||
| 	char	base_path[MAXPGPATH]; | ||||
| 	char	path[MAXPGPATH]; | ||||
| 	parray *files; | ||||
| 	bool	corrupted = false; | ||||
|  | ||||
| 	time2iso(timestamp, lengthof(timestamp), backup->start_time); | ||||
| 	if(!for_get_timeline){ | ||||
| 		if (with_database) | ||||
| 			elog(INFO, "validate: %s backup and archive log files by %s", timestamp, (size_only ? "SIZE" : "CRC")); | ||||
| 		else{ | ||||
| 			if (backup->backup_mode == BACKUP_MODE_ARCHIVE) | ||||
| 				elog(INFO, "validate: %s archive log files by %s", timestamp, (size_only ? "SIZE" : "CRC")); | ||||
| 			else if (backup->with_serverlog) | ||||
| 				elog(INFO, "validate: %s server log files by %s", timestamp, (size_only ? "SIZE" : "CRC")); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if(!check){ | ||||
| 		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; | ||||
| } | ||||
|   | ||||
							
								
								
									
										368
									
								
								verify.c
									
									
									
									
									
								
							
							
						
						
									
										368
									
								
								verify.c
									
									
									
									
									
								
							| @@ -1,184 +1,184 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * verify.c: verify backup files. | ||||
|  * | ||||
|  * Copyright (c) 2009-2011, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #include "pg_rman.h" | ||||
|  | ||||
| #include <unistd.h> | ||||
|  | ||||
| static bool verify_files(List *files, const char *root); | ||||
| static bool verify_file(pgFile *file, const char *root); | ||||
|  | ||||
| typedef struct VerifyJob | ||||
| { | ||||
| 	void		  (*routine)(struct VerifyJob *); | ||||
| 	pgFile		   *file; | ||||
| 	const char	   *root; | ||||
| 	volatile bool  *ok; | ||||
| } VerifyJob; | ||||
|  | ||||
| /* copy the file into backup */ | ||||
| static void | ||||
| verify_routine(VerifyJob *job) | ||||
| { | ||||
| 	if (*job->ok && !verify_file(job->file, job->root)) | ||||
| 		*job->ok = false; | ||||
| } | ||||
|  | ||||
| #define VERIFY_MASK		(BACKUP_MASK(BACKUP_DONE)) | ||||
|  | ||||
| void | ||||
| do_verify(pgRange range) | ||||
| { | ||||
| 	Database	db; | ||||
| 	List	   *backups; | ||||
| 	ListCell   *cell; | ||||
|  | ||||
| 	db = db_open(); | ||||
| 	backups = db_list_backups(db, range, VERIFY_MASK); | ||||
|  | ||||
| 	foreach (cell, backups) | ||||
| 		verify_backup(db, lfirst(cell)); | ||||
|  | ||||
| 	db_close(db); | ||||
| 	list_free_deep(backups); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Verify files in the backup and update the status to OK or BAD. | ||||
|  */ | ||||
| void | ||||
| verify_backup(Database db, pgBackup *backup) | ||||
| { | ||||
| 	char		root[MAXPGPATH]; | ||||
| 	char		datetime[DATESTRLEN]; | ||||
| 	List	   *dbfiles; | ||||
| 	List	   *arclogs = NIL; | ||||
| 	bool		ok; | ||||
|  | ||||
| 	if (backup->status == BACKUP_OK) | ||||
| 		return;	/* already verified */ | ||||
|  | ||||
| 	elog(INFO, "verify: %s", date2str(datetime, backup->start_time)); | ||||
| 	make_backup_path(root, backup->start_time); | ||||
|  | ||||
| 	/* Verify data files. */ | ||||
| 	dbfiles = db_list_dbfiles(db, backup); | ||||
| 	ok = verify_files(dbfiles, root); | ||||
| 	list_destroy(dbfiles, pgFile_free); | ||||
|  | ||||
| 	/* Verify archive log files. */ | ||||
| 	arclogs = db_list_arclogs(db, backup); | ||||
| 	ok = ok && verify_files(arclogs, root); | ||||
|  | ||||
| 	/* update the status to OK or BAD */ | ||||
| 	backup->status = (ok ? BACKUP_OK : BACKUP_BAD); | ||||
| 	db_update_status(db, backup, arclogs); | ||||
| 	list_destroy(arclogs, pgFile_free); | ||||
|  | ||||
| 	if (!ok) | ||||
| 		elog(WARNING, "corrupted backup: %s", datetime); | ||||
| } | ||||
|  | ||||
| static bool | ||||
| verify_files(List *files, const char *root) | ||||
| { | ||||
| 	volatile bool	ok = true; | ||||
| 	ListCell   *cell; | ||||
|  | ||||
| 	foreach (cell, files) | ||||
| 	{ | ||||
| 		pgFile *file = lfirst(cell); | ||||
| 		VerifyJob *job; | ||||
|  | ||||
| 		CHECK_FOR_INTERRUPTS(); | ||||
|  | ||||
| 		if (!ok) | ||||
| 			break; | ||||
|  | ||||
| 		job = pgut_new(VerifyJob); | ||||
| 		job->routine = verify_routine; | ||||
| 		job->file = file; | ||||
| 		job->root = root; | ||||
| 		job->ok = &ok; | ||||
| 		job_push((Job *) job); | ||||
| 	} | ||||
|  | ||||
| 	job_wait(); | ||||
|  | ||||
| 	return ok; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Verify files in the backup with size or CRC. | ||||
|  */ | ||||
| static bool | ||||
| verify_file(pgFile *file, const char *root) | ||||
| { | ||||
| 	FILE	   *fp; | ||||
| 	pg_crc32	crc; | ||||
| 	size_t		len; | ||||
| 	char		path[MAXPGPATH]; | ||||
| 	char		buf[8291]; | ||||
|  | ||||
| 	/* skipped or already verified file */ | ||||
| 	if (file->flags & (PGFILE_UNMODIFIED | PGFILE_VERIFIED)) | ||||
| 		return true; | ||||
|  | ||||
| 	/* not a file */ | ||||
| 	if (!S_ISREG(file->mode)) | ||||
| 	{ | ||||
| 		/* XXX: check if exists? */ | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	elog(LOG, "verify file: %s", file->name); | ||||
|  | ||||
| 	/* | ||||
| 	 * Exit on error and don't mark the backup in corrupted status to | ||||
| 	 * let users retry verification. | ||||
| 	 */ | ||||
| 	join_path_components(path, root, file->name); | ||||
| 	if ((fp = pgut_fopen(path, "r+")) == NULL) | ||||
| 	{ | ||||
| 		elog(WARNING, "missing file \"%s\"", path); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	/* Does file have correct crc? */ | ||||
| 	INIT_CRC32(crc); | ||||
| 	while ((len = fread(buf, 1, sizeof(buf), fp)) > 0) | ||||
| 	{ | ||||
| 		CHECK_FOR_INTERRUPTS(); | ||||
| 		COMP_CRC32(crc, buf, len); | ||||
| 	} | ||||
| 	FIN_CRC32(crc); | ||||
|  | ||||
| 	/* Update crc if not calclated yet. */ | ||||
| 	if ((file->flags & PGFILE_CRC) == 0) | ||||
| 	{ | ||||
| 		file->crc = crc; | ||||
| 		file->flags |= PGFILE_CRC; | ||||
| 	} | ||||
| 	else if (file->crc != crc) | ||||
| 	{ | ||||
| 		fclose(fp); | ||||
| 		elog(WARNING, "corrupted file \"%s\"", path); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	/* Flush backup files into disk */ | ||||
| 	if (fflush(fp) != 0 || fsync(fileno(fp)) != 0) | ||||
| 		ereport(ERROR, | ||||
| 			(errcode_errno(), | ||||
| 			 errmsg("could not flush file \"%s\": ", path))); | ||||
|  | ||||
| 	fclose(fp); | ||||
| 	file->flags |= PGFILE_VERIFIED; | ||||
| 	return true; | ||||
| } | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * verify.c: verify backup files. | ||||
|  * | ||||
|  * Copyright (c) 2009-2011, NIPPON TELEGRAPH AND TELEPHONE CORPORATION | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #include "pg_rman.h" | ||||
|  | ||||
| #include <unistd.h> | ||||
|  | ||||
| static bool verify_files(List *files, const char *root); | ||||
| static bool verify_file(pgFile *file, const char *root); | ||||
|  | ||||
| typedef struct VerifyJob | ||||
| { | ||||
| 	void		  (*routine)(struct VerifyJob *); | ||||
| 	pgFile		   *file; | ||||
| 	const char	   *root; | ||||
| 	volatile bool  *ok; | ||||
| } VerifyJob; | ||||
|  | ||||
| /* copy the file into backup */ | ||||
| static void | ||||
| verify_routine(VerifyJob *job) | ||||
| { | ||||
| 	if (*job->ok && !verify_file(job->file, job->root)) | ||||
| 		*job->ok = false; | ||||
| } | ||||
|  | ||||
| #define VERIFY_MASK		(BACKUP_MASK(BACKUP_DONE)) | ||||
|  | ||||
| void | ||||
| do_verify(pgRange range) | ||||
| { | ||||
| 	Database	db; | ||||
| 	List	   *backups; | ||||
| 	ListCell   *cell; | ||||
|  | ||||
| 	db = db_open(); | ||||
| 	backups = db_list_backups(db, range, VERIFY_MASK); | ||||
|  | ||||
| 	foreach (cell, backups) | ||||
| 		verify_backup(db, lfirst(cell)); | ||||
|  | ||||
| 	db_close(db); | ||||
| 	list_free_deep(backups); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Verify files in the backup and update the status to OK or BAD. | ||||
|  */ | ||||
| void | ||||
| verify_backup(Database db, pgBackup *backup) | ||||
| { | ||||
| 	char		root[MAXPGPATH]; | ||||
| 	char		datetime[DATESTRLEN]; | ||||
| 	List	   *dbfiles; | ||||
| 	List	   *arclogs = NIL; | ||||
| 	bool		ok; | ||||
|  | ||||
| 	if (backup->status == BACKUP_OK) | ||||
| 		return;	/* already verified */ | ||||
|  | ||||
| 	elog(INFO, "verify: %s", date2str(datetime, backup->start_time)); | ||||
| 	make_backup_path(root, backup->start_time); | ||||
|  | ||||
| 	/* Verify data files. */ | ||||
| 	dbfiles = db_list_dbfiles(db, backup); | ||||
| 	ok = verify_files(dbfiles, root); | ||||
| 	list_destroy(dbfiles, pgFile_free); | ||||
|  | ||||
| 	/* Verify archive log files. */ | ||||
| 	arclogs = db_list_arclogs(db, backup); | ||||
| 	ok = ok && verify_files(arclogs, root); | ||||
|  | ||||
| 	/* update the status to OK or BAD */ | ||||
| 	backup->status = (ok ? BACKUP_OK : BACKUP_BAD); | ||||
| 	db_update_status(db, backup, arclogs); | ||||
| 	list_destroy(arclogs, pgFile_free); | ||||
|  | ||||
| 	if (!ok) | ||||
| 		elog(WARNING, "corrupted backup: %s", datetime); | ||||
| } | ||||
|  | ||||
| static bool | ||||
| verify_files(List *files, const char *root) | ||||
| { | ||||
| 	volatile bool	ok = true; | ||||
| 	ListCell   *cell; | ||||
|  | ||||
| 	foreach (cell, files) | ||||
| 	{ | ||||
| 		pgFile *file = lfirst(cell); | ||||
| 		VerifyJob *job; | ||||
|  | ||||
| 		CHECK_FOR_INTERRUPTS(); | ||||
|  | ||||
| 		if (!ok) | ||||
| 			break; | ||||
|  | ||||
| 		job = pgut_new(VerifyJob); | ||||
| 		job->routine = verify_routine; | ||||
| 		job->file = file; | ||||
| 		job->root = root; | ||||
| 		job->ok = &ok; | ||||
| 		job_push((Job *) job); | ||||
| 	} | ||||
|  | ||||
| 	job_wait(); | ||||
|  | ||||
| 	return ok; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Verify files in the backup with size or CRC. | ||||
|  */ | ||||
| static bool | ||||
| verify_file(pgFile *file, const char *root) | ||||
| { | ||||
| 	FILE	   *fp; | ||||
| 	pg_crc32	crc; | ||||
| 	size_t		len; | ||||
| 	char		path[MAXPGPATH]; | ||||
| 	char		buf[8291]; | ||||
|  | ||||
| 	/* skipped or already verified file */ | ||||
| 	if (file->flags & (PGFILE_UNMODIFIED | PGFILE_VERIFIED)) | ||||
| 		return true; | ||||
|  | ||||
| 	/* not a file */ | ||||
| 	if (!S_ISREG(file->mode)) | ||||
| 	{ | ||||
| 		/* XXX: check if exists? */ | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	elog(LOG, "verify file: %s", file->name); | ||||
|  | ||||
| 	/* | ||||
| 	 * Exit on error and don't mark the backup in corrupted status to | ||||
| 	 * let users retry verification. | ||||
| 	 */ | ||||
| 	join_path_components(path, root, file->name); | ||||
| 	if ((fp = pgut_fopen(path, "r+")) == NULL) | ||||
| 	{ | ||||
| 		elog(WARNING, "missing file \"%s\"", path); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	/* Does file have correct crc? */ | ||||
| 	INIT_CRC32(crc); | ||||
| 	while ((len = fread(buf, 1, sizeof(buf), fp)) > 0) | ||||
| 	{ | ||||
| 		CHECK_FOR_INTERRUPTS(); | ||||
| 		COMP_CRC32(crc, buf, len); | ||||
| 	} | ||||
| 	FIN_CRC32(crc); | ||||
|  | ||||
| 	/* Update crc if not calclated yet. */ | ||||
| 	if ((file->flags & PGFILE_CRC) == 0) | ||||
| 	{ | ||||
| 		file->crc = crc; | ||||
| 		file->flags |= PGFILE_CRC; | ||||
| 	} | ||||
| 	else if (file->crc != crc) | ||||
| 	{ | ||||
| 		fclose(fp); | ||||
| 		elog(WARNING, "corrupted file \"%s\"", path); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	/* Flush backup files into disk */ | ||||
| 	if (fflush(fp) != 0 || fsync(fileno(fp)) != 0) | ||||
| 		ereport(ERROR, | ||||
| 			(errcode_errno(), | ||||
| 			 errmsg("could not flush file \"%s\": ", path))); | ||||
|  | ||||
| 	fclose(fp); | ||||
| 	file->flags |= PGFILE_VERIFIED; | ||||
| 	return true; | ||||
| } | ||||
|   | ||||
							
								
								
									
										254
									
								
								xlog.c
									
									
									
									
									
								
							
							
						
						
									
										254
									
								
								xlog.c
									
									
									
									
									
								
							| @@ -1,127 +1,127 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * xlog.c: Parse WAL files. | ||||
|  * | ||||
|  * Copyright (c) 2009-2011, 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" | ||||
|  | ||||
| #define XLOG_PAGE_MAGIC_v80		0xD05C	/* 8.0 */ | ||||
| #define XLOG_PAGE_MAGIC_v81		0xD05D	/* 8.1 */ | ||||
| #define XLOG_PAGE_MAGIC_v82		0xD05E	/* 8.2 */ | ||||
| #define XLOG_PAGE_MAGIC_v83		0xD062	/* 8.3 */ | ||||
| #define XLOG_PAGE_MAGIC_v84		0xD063	/* 8.4 */ | ||||
| #define XLOG_PAGE_MAGIC_v90		0xD064	/* 9.0 */ | ||||
| #define XLOG_PAGE_MAGIC_v91		0xD066	/* 9.1 */ | ||||
|  | ||||
| /* | ||||
|  * XLogLongPageHeaderData is modified in 8.3, but the layout is compatible | ||||
|  * except xlp_xlog_blcksz. | ||||
|  */ | ||||
| typedef union XLogPage | ||||
| { | ||||
| 	XLogPageHeaderData		header; | ||||
| 	XLogLongPageHeaderData	lheader; | ||||
| 	char					data[XLOG_BLCKSZ]; | ||||
| } XLogPage; | ||||
|  | ||||
| /* | ||||
|  * 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, int server_version) | ||||
| { | ||||
| 	FILE		   *fp; | ||||
| 	XLogPage		page; | ||||
| 	uint16			xlog_page_magic; | ||||
|  | ||||
| 	fp = fopen(file->path, "r"); | ||||
| 	if (!fp) | ||||
| 		return false; | ||||
| 	if (fread(&page, 1, sizeof(page), fp) != XLOG_BLCKSZ) | ||||
| 	{ | ||||
| 		fclose(fp); | ||||
| 		return false; | ||||
| 	} | ||||
| 	fclose(fp); | ||||
|  | ||||
| 	/* xlog_page_magic from server version */ | ||||
| 	if (server_version < 80000) | ||||
| 		return false;	/* never happen */ | ||||
| 	else if (server_version < 80100) | ||||
| 		xlog_page_magic = XLOG_PAGE_MAGIC_v80; | ||||
| 	else if (server_version < 80200) | ||||
| 		xlog_page_magic = XLOG_PAGE_MAGIC_v81; | ||||
| 	else if (server_version < 80300) | ||||
| 		xlog_page_magic = XLOG_PAGE_MAGIC_v82; | ||||
| 	else if (server_version < 80400) | ||||
| 		xlog_page_magic = XLOG_PAGE_MAGIC_v83; | ||||
| 	else if (server_version < 90000) | ||||
| 		xlog_page_magic = XLOG_PAGE_MAGIC_v84; | ||||
| 	else if (server_version < 90100) | ||||
| 		xlog_page_magic = XLOG_PAGE_MAGIC_v90; | ||||
| 	else if (server_version < 90200) | ||||
| 		xlog_page_magic = XLOG_PAGE_MAGIC_v91; | ||||
| 	else | ||||
| 		return false;	/* not supported */ | ||||
|  | ||||
| 	/* check header */ | ||||
| 	if (page.header.xlp_magic != xlog_page_magic) | ||||
| 		return false; | ||||
| 	if ((page.header.xlp_info & ~XLP_ALL_FLAGS) != 0) | ||||
| 		return false; | ||||
| 	if ((page.header.xlp_info & XLP_LONG_HEADER) == 0) | ||||
| 		return false; | ||||
| 	if (page.lheader.xlp_seg_size != XLogSegSize) | ||||
| 		return false; | ||||
| 	if (server_version >= 80300 && page.lheader.xlp_xlog_blcksz != XLOG_BLCKSZ) | ||||
| 		return false; | ||||
|  | ||||
| 	/* | ||||
| 	 * check size (actual file size, not backup file size) | ||||
| 	 * TODO: Support pre-compressed xlog. They might have different file sizes. | ||||
| 	 */ | ||||
| 	if (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); | ||||
| } | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * xlog.c: Parse WAL files. | ||||
|  * | ||||
|  * Copyright (c) 2009-2011, 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" | ||||
|  | ||||
| #define XLOG_PAGE_MAGIC_v80		0xD05C	/* 8.0 */ | ||||
| #define XLOG_PAGE_MAGIC_v81		0xD05D	/* 8.1 */ | ||||
| #define XLOG_PAGE_MAGIC_v82		0xD05E	/* 8.2 */ | ||||
| #define XLOG_PAGE_MAGIC_v83		0xD062	/* 8.3 */ | ||||
| #define XLOG_PAGE_MAGIC_v84		0xD063	/* 8.4 */ | ||||
| #define XLOG_PAGE_MAGIC_v90		0xD064	/* 9.0 */ | ||||
| #define XLOG_PAGE_MAGIC_v91		0xD066	/* 9.1 */ | ||||
|  | ||||
| /* | ||||
|  * XLogLongPageHeaderData is modified in 8.3, but the layout is compatible | ||||
|  * except xlp_xlog_blcksz. | ||||
|  */ | ||||
| typedef union XLogPage | ||||
| { | ||||
| 	XLogPageHeaderData		header; | ||||
| 	XLogLongPageHeaderData	lheader; | ||||
| 	char					data[XLOG_BLCKSZ]; | ||||
| } XLogPage; | ||||
|  | ||||
| /* | ||||
|  * 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, int server_version) | ||||
| { | ||||
| 	FILE		   *fp; | ||||
| 	XLogPage		page; | ||||
| 	uint16			xlog_page_magic; | ||||
|  | ||||
| 	fp = fopen(file->path, "r"); | ||||
| 	if (!fp) | ||||
| 		return false; | ||||
| 	if (fread(&page, 1, sizeof(page), fp) != XLOG_BLCKSZ) | ||||
| 	{ | ||||
| 		fclose(fp); | ||||
| 		return false; | ||||
| 	} | ||||
| 	fclose(fp); | ||||
|  | ||||
| 	/* xlog_page_magic from server version */ | ||||
| 	if (server_version < 80000) | ||||
| 		return false;	/* never happen */ | ||||
| 	else if (server_version < 80100) | ||||
| 		xlog_page_magic = XLOG_PAGE_MAGIC_v80; | ||||
| 	else if (server_version < 80200) | ||||
| 		xlog_page_magic = XLOG_PAGE_MAGIC_v81; | ||||
| 	else if (server_version < 80300) | ||||
| 		xlog_page_magic = XLOG_PAGE_MAGIC_v82; | ||||
| 	else if (server_version < 80400) | ||||
| 		xlog_page_magic = XLOG_PAGE_MAGIC_v83; | ||||
| 	else if (server_version < 90000) | ||||
| 		xlog_page_magic = XLOG_PAGE_MAGIC_v84; | ||||
| 	else if (server_version < 90100) | ||||
| 		xlog_page_magic = XLOG_PAGE_MAGIC_v90; | ||||
| 	else if (server_version < 90200) | ||||
| 		xlog_page_magic = XLOG_PAGE_MAGIC_v91; | ||||
| 	else | ||||
| 		return false;	/* not supported */ | ||||
|  | ||||
| 	/* check header */ | ||||
| 	if (page.header.xlp_magic != xlog_page_magic) | ||||
| 		return false; | ||||
| 	if ((page.header.xlp_info & ~XLP_ALL_FLAGS) != 0) | ||||
| 		return false; | ||||
| 	if ((page.header.xlp_info & XLP_LONG_HEADER) == 0) | ||||
| 		return false; | ||||
| 	if (page.lheader.xlp_seg_size != XLogSegSize) | ||||
| 		return false; | ||||
| 	if (server_version >= 80300 && page.lheader.xlp_xlog_blcksz != XLOG_BLCKSZ) | ||||
| 		return false; | ||||
|  | ||||
| 	/* | ||||
| 	 * check size (actual file size, not backup file size) | ||||
| 	 * TODO: Support pre-compressed xlog. They might have different file sizes. | ||||
| 	 */ | ||||
| 	if (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