@@ -17,6 +17,7 @@ Expire Command
17
17
#include "storage/helper.h"
18
18
19
19
#include <stdlib.h>
20
+ #include <stdio.h>
20
21
21
22
/***********************************************************************************************************************************
22
23
Helper functions and structures
@@ -97,6 +98,93 @@ expireBackup(InfoBackup *infoBackup, const String *backupLabel)
97
98
FUNCTION_LOG_RETURN (STRING_LIST , result );
98
99
}
99
100
101
+ /***********************************************************************************************************************************
102
+ Function to expire a selected backup (and all its dependents) regardless of retention rules.
103
+ ***********************************************************************************************************************************/
104
+ static unsigned int
105
+ expireAdhocBackup (InfoBackup * infoBackup , const String * backupLabel )
106
+ {
107
+ FUNCTION_LOG_BEGIN (logLevelDebug );
108
+ FUNCTION_LOG_PARAM (INFO_BACKUP , infoBackup );
109
+ FUNCTION_LOG_PARAM (STRING , backupLabel );
110
+ FUNCTION_LOG_END ();
111
+
112
+ ASSERT (infoBackup != NULL );
113
+ ASSERT (backupLabel != NULL );
114
+
115
+ unsigned int result = 0 ;
116
+
117
+ MEM_CONTEXT_TEMP_BEGIN ()
118
+ {
119
+ // If the label format is invalid, then error
120
+ if (!regExpMatchOne (backupRegExpP (.full = true, .differential = true, .incremental = true), backupLabel ))
121
+ {
122
+ THROW_FMT (OptionInvalidValueError , "'%s' is not a valid backup label format" , strPtr (backupLabel ));
123
+ }
124
+
125
+ // If the label is not a current backup then notify user and exit
126
+ if (infoBackupDataByLabel (infoBackup , backupLabel ) == NULL )
127
+ {
128
+ LOG_WARN_FMT (
129
+ "backup %s does not exist\nHINT: run the info command and confirm the backup is listed" , strPtr (backupLabel ));
130
+ }
131
+ else
132
+ {
133
+ // Get a list of all full backups with most recent in position 0
134
+ StringList * fullList = strLstSort (infoBackupDataLabelList (infoBackup , backupRegExpP (.full = true)), sortOrderDesc );
135
+
136
+ // If the requested backup to expire is the latest full backup
137
+ if (strCmp (strLstGet (fullList , 0 ), backupLabel ) == 0 )
138
+ {
139
+ // If the latest full backup requested is the only backup or the prior full backup is not for the same db-id
140
+ // then the backup requested cannot be expired
141
+ if (strLstSize (fullList ) == 1 || infoBackupDataByLabel (infoBackup , backupLabel )-> backupPgId !=
142
+ infoBackupDataByLabel (infoBackup , strLstGet (fullList , 1 ))-> backupPgId )
143
+ {
144
+ THROW_FMT (
145
+ BackupSetInvalidError , "full backup %s cannot be expired until another full backup has been created" ,
146
+ strPtr (backupLabel ));
147
+ }
148
+ }
149
+
150
+ // Save off what is currently the latest backup (it may be removed if it is the adhoc backup or is a dependent of the
151
+ // adhoc backup
152
+ const String * latestBackup = infoBackupData (infoBackup , infoBackupDataTotal (infoBackup ) - 1 ).backupLabel ;
153
+
154
+ // Expire the requested backup and any dependents
155
+ StringList * backupExpired = expireBackup (infoBackup , backupLabel );
156
+
157
+ // If the latest backup was removed, then update the latest link if not a dry-run
158
+ if (infoBackupDataByLabel (infoBackup , latestBackup ) == NULL )
159
+ {
160
+ // If retention settings have been configured, then there may be holes in the archives. For example, if the archive
161
+ // for db-id=1 has 01,02,03,04,05 and F1 backup has archive start-stop 02-03 and rentention-full=1
162
+ // (hence retention-archive=1 and retention-archive-type=full), then when F2 backup is created and assuming its
163
+ // archive start-stop=05-06 then archives 01 and 04 will be removed resulting in F1 not being able to play through
164
+ // PITR, which is expected. Now adhoc expire is attempted on F2 - it will be allowed but now there will be no
165
+ // backups that can be recovered through PITR until the next full backup is created. Same problem for differential
166
+ // backups with retention-diff.
167
+ LOG_WARN_FMT (
168
+ "expiring latest backup %s - the ability to perform point-in-time-recovery (PITR) may be affected\n"
169
+ "HINT: non-default settings for '%s'/'%s' (even in prior expires) can cause gaps in the WAL." ,
170
+ strPtr (latestBackup ), cfgOptionName (cfgOptRepoRetentionArchive ), cfgOptionName (cfgOptRepoRetentionArchiveType ));
171
+
172
+ // Adhoc expire is never performed through backup command so only check to determine if dry-run has been set or not
173
+ if (!cfgOptionBool (cfgOptDryRun ))
174
+ backupLinkLatest (infoBackupData (infoBackup , infoBackupDataTotal (infoBackup ) - 1 ).backupLabel );
175
+ }
176
+
177
+ result = strLstSize (backupExpired );
178
+
179
+ // Log the expired backup list (prepend "set:" if there were any dependents that were also expired)
180
+ LOG_INFO_FMT ("expire adhoc backup %s%s" , (result > 1 ? "set: " : "" ), strPtr (strLstJoin (backupExpired , ", " )));
181
+ }
182
+ }
183
+ MEM_CONTEXT_TEMP_END ();
184
+
185
+ FUNCTION_LOG_RETURN (UINT , result );
186
+ }
187
+
100
188
/***********************************************************************************************************************************
101
189
Expire differential backups
102
190
***********************************************************************************************************************************/
@@ -600,24 +688,59 @@ removeExpiredArchive(InfoBackup *infoBackup)
600
688
Remove expired backups from repo
601
689
***********************************************************************************************************************************/
602
690
static void
603
- removeExpiredBackup (InfoBackup * infoBackup )
691
+ removeExpiredBackup (InfoBackup * infoBackup , const String * adhocBackupLabel )
604
692
{
605
693
FUNCTION_LOG_BEGIN (logLevelDebug );
606
694
FUNCTION_LOG_PARAM (INFO_BACKUP , infoBackup );
695
+ FUNCTION_LOG_PARAM (STRING , adhocBackupLabel );
607
696
FUNCTION_LOG_END ();
608
697
609
698
ASSERT (infoBackup != NULL );
610
699
611
- // Get all the current backups in backup.info
700
+ // Get all the current backups in backup.info - these will not be expired
612
701
StringList * currentBackupList = strLstSort (infoBackupDataLabelList (infoBackup , NULL ), sortOrderDesc );
702
+
703
+ // Get all the backups on disk
613
704
StringList * backupList = strLstSort (
614
705
storageListP (
615
706
storageRepo (), STORAGE_REPO_BACKUP_STR ,
616
707
.expression = backupRegExpP (.full = true, .differential = true, .incremental = true)),
617
708
sortOrderDesc );
618
709
710
+ // Initialize the index to the lastest backup on disk
711
+ unsigned int backupIdx = 0 ;
712
+
713
+ // Only remove the resumable backup if there is a possibility it is a dependent of the adhoc label being expired
714
+ if (adhocBackupLabel != NULL )
715
+ {
716
+ String * manifestFileName = strNewFmt (
717
+ STORAGE_REPO_BACKUP "/%s/" BACKUP_MANIFEST_FILE , strPtr (strLstGet (backupList , backupIdx )));
718
+ String * manifestCopyFileName = strNewFmt ("%s" INFO_COPY_EXT , strPtr (manifestFileName ));
719
+
720
+ // If the latest backup is resumable (has a backup.manifest.copy but no backup.manifest)
721
+ if (!storageExistsP (storageRepo (), manifestFileName ) && storageExistsP (storageRepo (), manifestCopyFileName ))
722
+ {
723
+ // If the resumable backup is not related to the expired adhoc backup then don't remove it
724
+ if (!strBeginsWith (strLstGet (backupList , backupIdx ), strSubN (adhocBackupLabel , 0 , 16 )))
725
+ {
726
+ backupIdx = 1 ;
727
+ }
728
+ // Else it may be related to the adhoc backup so check if its ancestor still exists
729
+ else
730
+ {
731
+ Manifest * manifestResume = manifestLoadFile (
732
+ storageRepo (), manifestFileName , cipherType (cfgOptionStr (cfgOptRepoCipherType )),
733
+ infoPgCipherPass (infoBackupPg (infoBackup )));
734
+
735
+ // If the ancestor of the resumable backup still exists in backup.info then do not remove the resumable backup
736
+ if (infoBackupDataByLabel (infoBackup , manifestData (manifestResume )-> backupLabelPrior ) != NULL )
737
+ backupIdx = 1 ;
738
+ }
739
+ }
740
+ }
741
+
619
742
// Remove non-current backups from disk
620
- for (unsigned int backupIdx = 0 ; backupIdx < strLstSize (backupList ); backupIdx ++ )
743
+ for (; backupIdx < strLstSize (backupList ); backupIdx ++ )
621
744
{
622
745
if (!strLstExists (currentBackupList , strLstGet (backupList , backupIdx )))
623
746
{
@@ -655,8 +778,19 @@ cmdExpire(void)
655
778
storageRepo (), INFO_BACKUP_PATH_FILE_STR , cipherType (cfgOptionStr (cfgOptRepoCipherType )),
656
779
cfgOptionStr (cfgOptRepoCipherPass ));
657
780
658
- expireFullBackup (infoBackup );
659
- expireDiffBackup (infoBackup );
781
+ const String * adhocBackupLabel = NULL ;
782
+
783
+ // If the --set option is valid (i.e. expire is called on its own) and is set then attempt to expire the requested backup
784
+ if (cfgOptionValid (cfgOptSet ) && cfgOptionTest (cfgOptSet ))
785
+ {
786
+ adhocBackupLabel = cfgOptionStr (cfgOptSet );
787
+ expireAdhocBackup (infoBackup , adhocBackupLabel );
788
+ }
789
+ else
790
+ {
791
+ expireFullBackup (infoBackup );
792
+ expireDiffBackup (infoBackup );
793
+ }
660
794
661
795
// Store the new backup info only if the dry-run mode is disabled
662
796
if (!cfgOptionValid (cfgOptDryRun ) || !cfgOptionBool (cfgOptDryRun ))
@@ -666,7 +800,8 @@ cmdExpire(void)
666
800
cfgOptionStr (cfgOptRepoCipherPass ));
667
801
}
668
802
669
- removeExpiredBackup (infoBackup );
803
+ // Remove all files on disk that are now expired
804
+ removeExpiredBackup (infoBackup , adhocBackupLabel );
670
805
removeExpiredArchive (infoBackup );
671
806
}
672
807
MEM_CONTEXT_TEMP_END ();
0 commit comments