@@ -962,23 +962,88 @@ restore_data_file(const char *to_path, pgFile *file, bool allow_truncate,
962962}
963963
964964/*
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.
972968 */
973969void
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 ,
9751040 const char * from_fullpath , const char * to_fullpath , int nblocks )
9761041{
9771042 BackupPageHeader header ;
9781043 BlockNumber blknum = 0 ;
9791044 size_t write_len = 0 ;
9801045
981- while (true )
1046+ for (;; )
9821047 {
9831048 off_t write_pos ;
9841049 size_t read_len ;
@@ -1016,9 +1081,44 @@ restore_data_file_new(FILE *in, FILE *out, pgFile *file, uint32 backup_version,
10161081
10171082 blknum = header .block ;
10181083
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+
10191119 /* no point in writing redundant data */
10201120 if (nblocks > 0 && blknum >= nblocks )
1021- return ;
1121+ break ;
10221122
10231123 if (header .compressed_size > BLCKSZ )
10241124 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,
10611161
10621162 /*
10631163 * Seek and write the restored page.
1064- * TODO: invent fio_pwrite().
10651164 */
10661165 if (fio_fseek (out , write_pos ) < 0 )
10671166 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,
10961195 * it is either small control file or already compressed cfs file.
10971196 */
10981197void
1099- restore_non_data_file (FILE * in , FILE * out , pgFile * file ,
1198+ restore_non_data_file_internal (FILE * in , FILE * out , pgFile * file ,
11001199 const char * from_fullpath , const char * to_fullpath )
11011200{
11021201 size_t read_len = 0 ;
@@ -1148,6 +1247,100 @@ restore_non_data_file(FILE *in, FILE *out, pgFile *file,
11481247 elog (VERBOSE , "Copied file \"%s\": %lu bytes" , from_fullpath , file -> write_size );
11491248}
11501249
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+
11511344/*
11521345 * Copy file to backup.
11531346 * We do not apply compression to these files, because
0 commit comments