diff --git a/delete.c b/delete.c index d271bf58..93aebbb4 100644 --- a/delete.c +++ b/delete.c @@ -9,15 +9,20 @@ #include "pg_arman.h" +#include +#include + static int pgBackupDeleteFiles(pgBackup *backup); int do_delete(pgBackupRange *range) { - int i; - int ret; - parray *backup_list; - bool do_delete = false; + int i; + int ret; + parray *backup_list; + bool do_delete = false; + XLogRecPtr oldest_lsn = InvalidXLogRecPtr; + TimeLineID oldest_tli; /* DATE are always required */ if (!pgBackupRangeIsValid(range)) @@ -56,7 +61,11 @@ do_delete(pgBackupRange *range) if (backup->backup_mode >= BACKUP_MODE_FULL && backup->status == BACKUP_STATUS_OK && backup->start_time <= range->begin) + { do_delete = true; + oldest_lsn = backup->start_lsn; + oldest_tli = backup->tli; + } } /* release catalog lock */ @@ -65,6 +74,80 @@ do_delete(pgBackupRange *range) /* cleanup */ parray_walk(backup_list, pgBackupFree); parray_free(backup_list); + + /* + * Delete in archive WAL segments that are not needed anymore. The oldest + * segment to be kept is the first segment that the oldest full backup + * found around needs to keep. + */ + if (!XLogRecPtrIsInvalid(oldest_lsn)) + { + XLogSegNo targetSegNo; + char oldestSegmentNeeded[MAXFNAMELEN]; + DIR *arcdir; + struct dirent *arcde; + char wal_file[MAXPGPATH]; + int rc; + + XLByteToSeg(oldest_lsn, targetSegNo); + XLogFileName(oldestSegmentNeeded, oldest_tli, targetSegNo); + elog(LOG, "Removing segments older than %s", oldestSegmentNeeded); + + /* + * Now is time to do the actual work and to remove all the segments + * not needed anymore. + */ + if ((arcdir = opendir(arclog_path)) != NULL) + { + while (errno = 0, (arcde = readdir(arcdir)) != NULL) + { + /* + * We ignore the timeline part of the XLOG segment identifiers in + * deciding whether a segment is still needed. This ensures that + * we won't prematurely remove a segment from a parent timeline. + * We could probably be a little more proactive about removing + * segments of non-parent timelines, but that would be a whole lot + * more complicated. + * + * We use the alphanumeric sorting property of the filenames to + * decide which ones are earlier than the exclusiveCleanupFileName + * file. Note that this means files are not removed in the order + * they were originally written, in case this worries you. + */ + if ((IsXLogFileName(arcde->d_name) || + IsPartialXLogFileName(arcde->d_name)) && + strcmp(arcde->d_name + 8, oldestSegmentNeeded + 8) < 0) + { + /* + * Use the original file name again now, including any + * extension that might have been chopped off before testing + * the sequence. + */ + snprintf(wal_file, MAXPGPATH, "%s/%s", + arclog_path, arcde->d_name); + + rc = unlink(wal_file); + if (rc != 0) + { + elog(WARNING, "could not remove file \"%s\": %s", + wal_file, strerror(errno)); + break; + } + elog(LOG, "removed WAL segment \"%s\"", wal_file); + } + } + if (errno) + elog(WARNING, "could not read archive location \"%s\": %s", + arclog_path, strerror(errno)); + if (closedir(arcdir)) + elog(WARNING, "could not close archive location \"%s\": %s", + arclog_path, strerror(errno)); + } + else + elog(WARNING, "could not open archive location \"%s\": %s", + arclog_path, strerror(errno)); + } + return 0; } diff --git a/doc/pg_arman.txt b/doc/pg_arman.txt index 6deb0310..5c1cf795 100644 --- a/doc/pg_arman.txt +++ b/doc/pg_arman.txt @@ -178,7 +178,9 @@ You can check the "RECOVERY_XID" and "RECOVERY_TIME" which are used for restore option "--recovery-target-xid", "--recovery-target-time". The delete command deletes backup files not required by recovery after -the specified date. +the specified date. This command also cleans up in the WAL archive the +WAL segments that are no longer needed to restore from the remaining +backups. == OPTIONS == diff --git a/expected/option.out b/expected/option.out index 3d0d3088..fea0e84a 100644 --- a/expected/option.out +++ b/expected/option.out @@ -72,37 +72,42 @@ ERROR: invalid backup-mode "bad" 12 ###### COMMAND OPTION TEST-0006 ###### +###### delete failure without archive path ###### +ERROR: delete command needs ARCLOG_PATH (-A, --arclog-path) to be set +12 + +###### COMMAND OPTION TEST-0007 ###### ###### delete failure without DATE ###### ERROR: required delete range option not specified: delete DATE 12 -###### COMMAND OPTION TEST-0007 ###### +###### COMMAND OPTION TEST-0008 ###### ###### syntax error in pg_arman.ini ###### WARNING: syntax error in " = INFINITE" ERROR: Required parameter not specified: BACKUP_MODE (-b, --backup-mode) 12 -###### COMMAND OPTION TEST-0008 ###### +###### COMMAND OPTION TEST-0009 ###### ###### invalid value in pg_arman.ini ###### ERROR: invalid backup-mode "" 12 -###### COMMAND OPTION TEST-0009 ###### +###### COMMAND OPTION TEST-0010 ###### ###### invalid value in pg_arman.ini ###### ERROR: option --keep-data-generations should be a 32bit signed integer: 'TRUE' 12 -###### COMMAND OPTION TEST-0010 ###### +###### COMMAND OPTION TEST-0011 ###### ###### invalid value in pg_arman.ini ###### ERROR: option -C, --smooth-checkpoint should be a boolean: 'FOO' 12 -###### COMMAND OPTION TEST-0011 ###### +###### COMMAND OPTION TEST-0012 ###### ###### invalid option in pg_arman.ini ###### ERROR: invalid option "TIMELINEID" 12 -###### COMMAND OPTION TEST-0012 ###### +###### COMMAND OPTION TEST-0013 ###### ###### check priority of several pg_arman.ini files ###### ERROR: invalid backup-mode "ENV_PATH" 12 diff --git a/pg_arman.c b/pg_arman.c index d722011d..60391c53 100644 --- a/pg_arman.c +++ b/pg_arman.c @@ -148,6 +148,10 @@ main(int argc, char *argv[]) if (arclog_path != NULL && !is_absolute_path(arclog_path)) elog(ERROR_ARGS, "-A, --arclog-path must be an absolute path"); + /* Sanity checks with commands */ + if (pg_strcasecmp(cmd, "delete") == 0 && arclog_path == NULL) + elog(ERROR_ARGS, "delete command needs ARCLOG_PATH (-A, --arclog-path) to be set"); + /* setup exclusion list for file search */ for (i = 0; pgdata_exclude[i]; i++) /* find first empty slot */ ; diff --git a/sql/option.sh b/sql/option.sh index 6d0e7432..d3a35c1b 100644 --- a/sql/option.sh +++ b/sql/option.sh @@ -35,47 +35,52 @@ pg_arman backup -B ${BACKUP_PATH} -A ${ARCLOG_PATH} -b bad -p ${TEST_PGPORT};ech echo '' echo '###### COMMAND OPTION TEST-0006 ######' -echo '###### delete failure without DATE ######' +echo '###### delete failure without archive path ######' pg_arman delete -B ${BACKUP_PATH};echo $? echo '' +echo '###### COMMAND OPTION TEST-0007 ######' +echo '###### delete failure without DATE ######' +pg_arman delete -B ${BACKUP_PATH} -A ${ARCLOG_PATH};echo $? +echo '' + init_backup -echo '###### COMMAND OPTION TEST-0007 ######' +echo '###### COMMAND OPTION TEST-0008 ######' echo '###### syntax error in pg_arman.ini ######' echo " = INFINITE" >> ${BACKUP_PATH}/pg_arman.ini pg_arman backup -B ${BACKUP_PATH} -A ${ARCLOG_PATH} -p ${TEST_PGPORT};echo $? echo '' -echo '###### COMMAND OPTION TEST-0008 ######' -echo '###### invalid value in pg_arman.ini ######' -init_catalog -echo "BACKUP_MODE=" >> ${BACKUP_PATH}/pg_arman.ini -pg_arman backup -B ${BACKUP_PATH} -A ${ARCLOG_PATH} -p ${TEST_PGPORT};echo $? -echo '' - echo '###### COMMAND OPTION TEST-0009 ######' echo '###### invalid value in pg_arman.ini ######' init_catalog +echo "BACKUP_MODE=" >> ${BACKUP_PATH}/pg_arman.ini +pg_arman backup -B ${BACKUP_PATH} -A ${ARCLOG_PATH} -p ${TEST_PGPORT};echo $? +echo '' + +echo '###### COMMAND OPTION TEST-0010 ######' +echo '###### invalid value in pg_arman.ini ######' +init_catalog echo "KEEP_DATA_GENERATIONS=TRUE" >> ${BACKUP_PATH}/pg_arman.ini pg_arman backup -B ${BACKUP_PATH} -A ${ARCLOG_PATH} -b full -p ${TEST_PGPORT};echo $? echo '' -echo '###### COMMAND OPTION TEST-0010 ######' +echo '###### COMMAND OPTION TEST-0011 ######' echo '###### invalid value in pg_arman.ini ######' init_catalog echo "SMOOTH_CHECKPOINT=FOO" >> ${BACKUP_PATH}/pg_arman.ini pg_arman backup -B ${BACKUP_PATH} -A ${ARCLOG_PATH} -b full -p ${TEST_PGPORT};echo $? echo '' -echo '###### COMMAND OPTION TEST-0011 ######' +echo '###### COMMAND OPTION TEST-0012 ######' echo '###### invalid option in pg_arman.ini ######' init_catalog echo "TIMELINEID=1" >> ${BACKUP_PATH}/pg_arman.ini pg_arman backup -B ${BACKUP_PATH} -A ${ARCLOG_PATH} -b full -p ${TEST_PGPORT};echo $? echo '' -echo '###### COMMAND OPTION TEST-0012 ######' +echo '###### COMMAND OPTION TEST-0013 ######' echo '###### check priority of several pg_arman.ini files ######' init_catalog mkdir -p ${BACKUP_PATH}/conf_path_a