@@ -962,23 +962,88 @@ restore_data_file(const char *to_path, pgFile *file, bool allow_truncate,
962
962
}
963
963
964
964
/*
965
- * Restore files in the from_root directory to the to_root directory with
966
- * same relative path.
967
- *
968
- * If write_header is true then we add header to each restored block, currently
969
- * it is used for MERGE command.
970
- *
971
- * to_fullpath and from_fullpath are provided strictly for ERROR reporting
965
+ * Iterate over parent backup chain and lookup given destination file in
966
+ * filelist of every chain member starting with FULL backup.
967
+ * Apply changed blocks to destination file from every backup in parent chain.
972
968
*/
973
969
void
974
- restore_data_file_new (FILE * in , FILE * out , pgFile * file , uint32 backup_version ,
970
+ restore_data_file_new (parray * parent_chain , pgFile * dest_file , FILE * out , const char * to_fullpath )
971
+ {
972
+ int i ;
973
+
974
+ for (i = parray_num (parent_chain ) - 1 ; i >= 0 ; i -- )
975
+ {
976
+ char from_root [MAXPGPATH ];
977
+ char from_fullpath [MAXPGPATH ];
978
+ FILE * in = NULL ;
979
+
980
+ pgFile * * res_file = NULL ;
981
+ pgFile * tmp_file = NULL ;
982
+
983
+ pgBackup * backup = (pgBackup * ) parray_get (parent_chain , i );
984
+
985
+ /* check for interrupt */
986
+ if (interrupted || thread_interrupted )
987
+ elog (ERROR , "Interrupted during restore" );
988
+
989
+ /* lookup file in intermediate backup */
990
+ res_file = parray_bsearch (backup -> files , dest_file , pgFileCompareRelPathWithExternal );
991
+ tmp_file = (res_file ) ? * res_file : NULL ;
992
+
993
+ /* Destination file is not exists yet at this moment */
994
+ if (tmp_file == NULL )
995
+ continue ;
996
+
997
+ /*
998
+ * Skip file if it haven't changed since previous backup
999
+ * and thus was not backed up.
1000
+ */
1001
+ if (tmp_file -> write_size == BYTES_INVALID )
1002
+ {
1003
+ // elog(VERBOSE, "The file didn`t change. Skip restore: \"%s\"", tmp_file->rel_path);
1004
+ continue ;
1005
+ }
1006
+
1007
+ /*
1008
+ * At this point we are sure, that something is going to be copied
1009
+ * Open source file.
1010
+ */
1011
+ join_path_components (from_root , backup -> root_dir , DATABASE_DIR );
1012
+ join_path_components (from_fullpath , from_root , tmp_file -> rel_path );
1013
+
1014
+ in = fopen (from_fullpath , PG_BINARY_R );
1015
+ if (in == NULL )
1016
+ {
1017
+ elog (INFO , "Cannot open backup file \"%s\": %s" , from_fullpath ,
1018
+ strerror (errno ));
1019
+ Assert (0 );
1020
+ }
1021
+
1022
+ /*
1023
+ * restore the file.
1024
+ * Datafiles are backed up block by block and every block
1025
+ * have BackupPageHeader with meta information, so we cannot just
1026
+ * copy the file from backup.
1027
+ */
1028
+ restore_data_file_internal (in , out , tmp_file ,
1029
+ parse_program_version (backup -> program_version ),
1030
+ from_fullpath , to_fullpath , dest_file -> n_blocks );
1031
+
1032
+ if (fio_fclose (in ) != 0 )
1033
+ elog (ERROR , "Cannot close file \"%s\": %s" , from_fullpath ,
1034
+ strerror (errno ));
1035
+ }
1036
+ }
1037
+
1038
+ void
1039
+ restore_data_file_internal (FILE * in , FILE * out , pgFile * file , uint32 backup_version ,
975
1040
const char * from_fullpath , const char * to_fullpath , int nblocks )
976
1041
{
977
1042
BackupPageHeader header ;
978
1043
BlockNumber blknum = 0 ;
979
1044
size_t write_len = 0 ;
980
1045
981
- while (true )
1046
+ for (;; )
982
1047
{
983
1048
off_t write_pos ;
984
1049
size_t read_len ;
@@ -1016,9 +1081,44 @@ restore_data_file_new(FILE *in, FILE *out, pgFile *file, uint32 backup_version,
1016
1081
1017
1082
blknum = header .block ;
1018
1083
1084
+ /*
1085
+ * Backupward compatibility kludge: in the good old days
1086
+ * n_blocks attribute was available only in DELTA backups.
1087
+ * File truncate in PAGE and PTRACK happened on the fly when
1088
+ * special value PageIsTruncated is encountered.
1089
+ * It is inefficient.
1090
+ *
1091
+ * Nowadays every backup type has n_blocks, so instead
1092
+ * writing and then truncating redundant data, writing
1093
+ * is not happening in the first place.
1094
+ * TODO: remove in 3.0.0
1095
+ */
1096
+ if (header .compressed_size == PageIsTruncated )
1097
+ {
1098
+ /*
1099
+ * Block header contains information that this block was truncated.
1100
+ * We need to truncate file to this length.
1101
+ */
1102
+
1103
+ elog (VERBOSE , "Truncate file \"%s\" to block %u" , to_fullpath , header .block );
1104
+
1105
+ /* To correctly truncate file, we must first flush STDIO buffers */
1106
+ if (fio_fflush (out ) != 0 )
1107
+ elog (ERROR , "Cannot flush file \"%s\": %s" , to_fullpath , strerror (errno ));
1108
+
1109
+ /* Set position to the start of file */
1110
+ if (fio_fseek (out , 0 ) < 0 )
1111
+ elog (ERROR , "Cannot seek to the start of file \"%s\": %s" , to_fullpath , strerror (errno ));
1112
+
1113
+ if (fio_ftruncate (out , header .block * BLCKSZ ) != 0 )
1114
+ elog (ERROR , "Cannot truncate file \"%s\": %s" , to_fullpath , strerror (errno ));
1115
+
1116
+ break ;
1117
+ }
1118
+
1019
1119
/* no point in writing redundant data */
1020
1120
if (nblocks > 0 && blknum >= nblocks )
1021
- return ;
1121
+ break ;
1022
1122
1023
1123
if (header .compressed_size > BLCKSZ )
1024
1124
elog (ERROR , "Size of a blknum %i exceed BLCKSZ" , blknum );
@@ -1061,7 +1161,6 @@ restore_data_file_new(FILE *in, FILE *out, pgFile *file, uint32 backup_version,
1061
1161
1062
1162
/*
1063
1163
* Seek and write the restored page.
1064
- * TODO: invent fio_pwrite().
1065
1164
*/
1066
1165
if (fio_fseek (out , write_pos ) < 0 )
1067
1166
elog (ERROR , "Cannot seek block %u of \"%s\": %s" ,
@@ -1096,7 +1195,7 @@ restore_data_file_new(FILE *in, FILE *out, pgFile *file, uint32 backup_version,
1096
1195
* it is either small control file or already compressed cfs file.
1097
1196
*/
1098
1197
void
1099
- restore_non_data_file (FILE * in , FILE * out , pgFile * file ,
1198
+ restore_non_data_file_internal (FILE * in , FILE * out , pgFile * file ,
1100
1199
const char * from_fullpath , const char * to_fullpath )
1101
1200
{
1102
1201
size_t read_len = 0 ;
@@ -1148,6 +1247,100 @@ restore_non_data_file(FILE *in, FILE *out, pgFile *file,
1148
1247
elog (VERBOSE , "Copied file \"%s\": %lu bytes" , from_fullpath , file -> write_size );
1149
1248
}
1150
1249
1250
+ void
1251
+ restore_non_data_file (parray * parent_chain , pgBackup * dest_backup ,
1252
+ pgFile * dest_file , FILE * out , const char * to_fullpath )
1253
+ {
1254
+ int i ;
1255
+ char from_root [MAXPGPATH ];
1256
+ char from_fullpath [MAXPGPATH ];
1257
+ FILE * in = NULL ;
1258
+
1259
+ pgFile * tmp_file = NULL ;
1260
+ pgBackup * tmp_backup = NULL ;
1261
+
1262
+ /* Check if full copy of destination file is available in destination backup */
1263
+ if (dest_file -> write_size > 0 )
1264
+ {
1265
+ tmp_file = dest_file ;
1266
+ tmp_backup = dest_backup ;
1267
+ }
1268
+ else
1269
+ {
1270
+ /*
1271
+ * Iterate over parent chain starting from direct parent of destination
1272
+ * backup to oldest backup in chain, and look for the first
1273
+ * full copy of destination file.
1274
+ * Full copy is latest possible destination file with size equal or
1275
+ * greater than zero.
1276
+ */
1277
+ for (i = 1 ; i < parray_num (parent_chain ); i ++ )
1278
+ {
1279
+ pgFile * * res_file = NULL ;
1280
+
1281
+ tmp_backup = (pgBackup * ) parray_get (parent_chain , i );
1282
+
1283
+ /* lookup file in intermediate backup */
1284
+ res_file = parray_bsearch (tmp_backup -> files , dest_file , pgFileCompareRelPathWithExternal );
1285
+ tmp_file = (res_file ) ? * res_file : NULL ;
1286
+
1287
+ /*
1288
+ * It should not be possible not to find destination file in intermediate
1289
+ * backup, without encountering full copy first.
1290
+ */
1291
+ if (!tmp_file )
1292
+ {
1293
+ elog (ERROR , "Failed to locate non-data file \"%s\" in backup %s" ,
1294
+ dest_file -> rel_path , base36enc (tmp_backup -> start_time ));
1295
+ continue ;
1296
+ }
1297
+
1298
+ /* Full copy is found and it is null sized, nothing to do here */
1299
+ if (tmp_file -> write_size == 0 )
1300
+ return ;
1301
+
1302
+ /* Full copy is found */
1303
+ if (tmp_file -> write_size > 0 )
1304
+ break ;
1305
+ }
1306
+ }
1307
+
1308
+ /* sanity */
1309
+ if (!tmp_backup )
1310
+ elog (ERROR , "Failed to found a backup containing full copy of non-data file \"%s\"" ,
1311
+ to_fullpath );
1312
+
1313
+ if (!tmp_file )
1314
+ elog (ERROR , "Failed to locate a full copy of non-data file \"%s\"" , to_fullpath );
1315
+
1316
+ if (tmp_file -> external_dir_num == 0 )
1317
+ // pgBackupGetPath(tmp_backup, from_root, lengthof(from_root), DATABASE_DIR);
1318
+ join_path_components (from_root , tmp_backup -> root_dir , DATABASE_DIR );
1319
+ else
1320
+ {
1321
+ // get external prefix for tmp_backup
1322
+ char external_prefix [MAXPGPATH ];
1323
+
1324
+ join_path_components (external_prefix , tmp_backup -> root_dir , EXTERNAL_DIR );
1325
+ makeExternalDirPathByNum (from_root , external_prefix , tmp_file -> external_dir_num );
1326
+ }
1327
+
1328
+ join_path_components (from_fullpath , from_root , dest_file -> rel_path );
1329
+
1330
+ in = fopen (from_fullpath , PG_BINARY_R );
1331
+ if (in == NULL )
1332
+ {
1333
+ elog (ERROR , "Cannot open backup file \"%s\": %s" , from_fullpath ,
1334
+ strerror (errno ));
1335
+ }
1336
+
1337
+ restore_non_data_file_internal (in , out , tmp_file , from_fullpath , to_fullpath );
1338
+
1339
+ if (fio_fclose (in ) != 0 )
1340
+ elog (ERROR , "Cannot close file \"%s\": %s" , from_fullpath ,
1341
+ strerror (errno ));
1342
+ }
1343
+
1151
1344
/*
1152
1345
* Copy file to backup.
1153
1346
* We do not apply compression to these files, because
0 commit comments