-
Notifications
You must be signed in to change notification settings - Fork 86
/
Copy pathbackup.c
2377 lines (2020 loc) · 71.3 KB
/
backup.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*-------------------------------------------------------------------------
*
* backup.c: backup DB cluster, archived WAL
*
* Portions Copyright (c) 2009-2013, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
* Portions Copyright (c) 2015-2019, Postgres Professional
*
*-------------------------------------------------------------------------
*/
#include "pg_probackup.h"
#if PG_VERSION_NUM < 110000
#include "catalog/catalog.h"
#endif
#include "catalog/pg_tablespace.h"
#include "pgtar.h"
#include "streamutil.h"
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include "utils/thread.h"
#include "utils/file.h"
//const char *progname = "pg_probackup";
/* list of files contained in backup */
parray *backup_files_list = NULL;
/* We need critical section for datapagemap_add() in case of using threads */
static pthread_mutex_t backup_pagemap_mutex = PTHREAD_MUTEX_INITIALIZER;
// TODO: move to PGnodeInfo
bool exclusive_backup = false;
/* Is pg_start_backup() was executed */
bool backup_in_progress = false;
/*
* Backup routines
*/
static void backup_cleanup(bool fatal, void *userdata);
static void *backup_files(void *arg);
static void do_backup_pg(InstanceState *instanceState, PGconn *backup_conn,
PGNodeInfo *nodeInfo, bool no_sync, bool backup_logs);
static void pg_switch_wal(PGconn *conn);
static void pg_stop_backup(InstanceState *instanceState, pgBackup *backup, PGconn *pg_startbackup_conn, PGNodeInfo *nodeInfo);
static void check_external_for_tablespaces(parray *external_list,
PGconn *backup_conn);
static parray *get_database_map(PGconn *pg_startbackup_conn);
/* pgpro specific functions */
static bool pgpro_support(PGconn *conn);
/* Check functions */
static bool pg_is_checksum_enabled(PGconn *conn);
static bool pg_is_in_recovery(PGconn *conn);
static bool pg_is_superuser(PGconn *conn);
static void check_server_version(PGconn *conn, PGNodeInfo *nodeInfo);
static void confirm_block_size(PGconn *conn, const char *name, int blcksz);
static void set_cfs_datafiles(parray *files, const char *root, char *relative, size_t i);
static StopBackupCallbackParams stop_callback_params;
static void
backup_stopbackup_callback(bool fatal, void *userdata)
{
StopBackupCallbackParams *st = (StopBackupCallbackParams *) userdata;
/*
* If backup is in progress, notify stop of backup to PostgreSQL
*/
if (backup_in_progress)
{
elog(WARNING, "backup in progress, stop backup");
/* don't care about stop_lsn in case of error */
pg_stop_backup_send(st->conn, st->server_version, current.from_replica, exclusive_backup, NULL);
}
}
/*
* Take a backup of a single postgresql instance.
* Move files from 'pgdata' to a subdirectory in backup catalog.
*/
static void
do_backup_pg(InstanceState *instanceState, PGconn *backup_conn,
PGNodeInfo *nodeInfo, bool no_sync, bool backup_logs)
{
int i;
char external_prefix[MAXPGPATH]; /* Temp value. Used as template */
char dst_backup_path[MAXPGPATH];
char label[1024];
XLogRecPtr prev_backup_start_lsn = InvalidXLogRecPtr;
/* arrays with meta info for multi threaded backup */
pthread_t *threads;
backup_files_arg *threads_args;
bool backup_isok = true;
pgBackup *prev_backup = NULL;
parray *prev_backup_filelist = NULL;
parray *backup_list = NULL;
parray *external_dirs = NULL;
parray *database_map = NULL;
/* used for multitimeline incremental backup */
parray *tli_list = NULL;
/* for fancy reporting */
time_t start_time, end_time;
char pretty_time[20];
char pretty_bytes[20];
elog(LOG, "Database backup start");
if(current.external_dir_str)
{
external_dirs = make_external_directory_list(current.external_dir_str,
false);
check_external_for_tablespaces(external_dirs, backup_conn);
}
/* Clear ptrack files for not PTRACK backups */
if (current.backup_mode != BACKUP_MODE_DIFF_PTRACK && nodeInfo->is_ptrack_enable)
pg_ptrack_clear(backup_conn, nodeInfo->ptrack_version_num);
/* notify start of backup to PostgreSQL server */
time2iso(label, lengthof(label), current.start_time, false);
strncat(label, " with pg_probackup", lengthof(label) -
strlen(" with pg_probackup"));
/* Call pg_start_backup function in PostgreSQL connect */
pg_start_backup(label, smooth_checkpoint, ¤t, nodeInfo, backup_conn);
/* Obtain current timeline */
#if PG_VERSION_NUM >= 90600
current.tli = get_current_timeline(backup_conn);
#else
current.tli = get_current_timeline_from_control(false);
#endif
/*
* In incremental backup mode ensure that already-validated
* backup on current timeline exists and get its filelist.
*/
if (current.backup_mode == BACKUP_MODE_DIFF_PAGE ||
current.backup_mode == BACKUP_MODE_DIFF_PTRACK ||
current.backup_mode == BACKUP_MODE_DIFF_DELTA)
{
/* get list of backups already taken */
backup_list = catalog_get_backup_list(instanceState, INVALID_BACKUP_ID);
prev_backup = catalog_get_last_data_backup(backup_list, current.tli, current.start_time);
if (prev_backup == NULL)
{
/* try to setup multi-timeline backup chain */
elog(WARNING, "Valid full backup on current timeline %u is not found, "
"trying to look up on previous timelines",
current.tli);
tli_list = get_history_streaming(&instance_config.conn_opt, current.tli, backup_list);
if (!tli_list)
{
elog(WARNING, "Failed to obtain current timeline history file via replication protocol");
/* fallback to using archive */
tli_list = catalog_get_timelines(instanceState, &instance_config);
}
if (parray_num(tli_list) == 0)
elog(WARNING, "Cannot find valid backup on previous timelines, "
"WAL archive is not available");
else
{
prev_backup = get_multi_timeline_parent(backup_list, tli_list, current.tli,
current.start_time, &instance_config);
if (prev_backup == NULL)
elog(WARNING, "Cannot find valid backup on previous timelines");
}
/* failed to find suitable parent, error out */
if (!prev_backup)
elog(ERROR, "Create new full backup before an incremental one");
}
}
if (prev_backup)
{
if (parse_program_version(prev_backup->program_version) > parse_program_version(PROGRAM_VERSION))
elog(ERROR, "pg_probackup binary version is %s, but backup %s version is %s. "
"pg_probackup do not guarantee to be forward compatible. "
"Please upgrade pg_probackup binary.",
PROGRAM_VERSION, base36enc(prev_backup->start_time), prev_backup->program_version);
elog(INFO, "Parent backup: %s", base36enc(prev_backup->start_time));
/* Files of previous backup needed by DELTA backup */
prev_backup_filelist = get_backup_filelist(prev_backup, true);
/* If lsn is not NULL, only pages with higher lsn will be copied. */
prev_backup_start_lsn = prev_backup->start_lsn;
current.parent_backup = prev_backup->start_time;
write_backup(¤t, true);
}
/*
* It`s illegal to take PTRACK backup if LSN from ptrack_control() is not
* equal to start_lsn of previous backup.
*/
if (current.backup_mode == BACKUP_MODE_DIFF_PTRACK)
{
XLogRecPtr ptrack_lsn = get_last_ptrack_lsn(backup_conn, nodeInfo);
if (nodeInfo->ptrack_version_num < 200)
{
// backward compatibility kludge: use Stop LSN for ptrack 1.x,
if (ptrack_lsn > prev_backup->stop_lsn || ptrack_lsn == InvalidXLogRecPtr)
{
elog(ERROR, "LSN from ptrack_control %X/%X differs from Stop LSN of previous backup %X/%X.\n"
"Create new full backup before an incremental one.",
(uint32) (ptrack_lsn >> 32), (uint32) (ptrack_lsn),
(uint32) (prev_backup->stop_lsn >> 32),
(uint32) (prev_backup->stop_lsn));
}
}
else
{
// new ptrack is more robust and checks Start LSN
if (ptrack_lsn > prev_backup->start_lsn || ptrack_lsn == InvalidXLogRecPtr)
{
elog(ERROR, "LSN from ptrack_control %X/%X is greater than Start LSN of previous backup %X/%X.\n"
"Create new full backup before an incremental one.",
(uint32) (ptrack_lsn >> 32), (uint32) (ptrack_lsn),
(uint32) (prev_backup->start_lsn >> 32),
(uint32) (prev_backup->start_lsn));
}
}
}
/* For incremental backup check that start_lsn is not from the past
* Though it will not save us if PostgreSQL instance is actually
* restored STREAM backup.
*/
if (current.backup_mode != BACKUP_MODE_FULL &&
prev_backup->start_lsn > current.start_lsn)
elog(ERROR, "Current START LSN %X/%X is lower than START LSN %X/%X of previous backup %s. "
"It may indicate that we are trying to backup PostgreSQL instance from the past.",
(uint32) (current.start_lsn >> 32), (uint32) (current.start_lsn),
(uint32) (prev_backup->start_lsn >> 32), (uint32) (prev_backup->start_lsn),
base36enc(prev_backup->start_time));
/* Update running backup meta with START LSN */
write_backup(¤t, true);
/* In PAGE mode or in ARCHIVE wal-mode wait for current segment */
if (current.backup_mode == BACKUP_MODE_DIFF_PAGE || !current.stream)
{
/* Check that archive_dir can be reached */
if (fio_access(instanceState->instance_wal_subdir_path, F_OK, FIO_BACKUP_HOST) != 0)
elog(ERROR, "WAL archive directory is not accessible \"%s\": %s",
instanceState->instance_wal_subdir_path, strerror(errno));
/*
* Do not wait start_lsn for stream backup.
* Because WAL streaming will start after pg_start_backup() in stream
* mode.
*/
wait_wal_lsn(instanceState->instance_wal_subdir_path, current.start_lsn, true, current.tli, false, true, ERROR, false);
}
/* start stream replication */
if (current.stream)
{
join_path_components(dst_backup_path, current.database_dir, PG_XLOG_DIR);
fio_mkdir(dst_backup_path, DIR_PERMISSION, FIO_BACKUP_HOST);
start_WAL_streaming(backup_conn, dst_backup_path, &instance_config.conn_opt,
current.start_lsn, current.tli);
/* Make sure that WAL streaming is working
* PAGE backup in stream mode is waited twice, first for
* segment in WAL archive and then for streamed segment
*/
wait_wal_lsn(dst_backup_path, current.start_lsn, true, current.tli, false, true, ERROR, true);
}
/* initialize backup's file list */
backup_files_list = parray_new();
join_path_components(external_prefix, current.root_dir, EXTERNAL_DIR);
/* list files with the logical path. omit $PGDATA */
fio_list_dir(backup_files_list, instance_config.pgdata,
true, true, false, backup_logs, true, 0);
/*
* Get database_map (name to oid) for use in partial restore feature.
* It's possible that we fail and database_map will be NULL.
*/
database_map = get_database_map(backup_conn);
/*
* Append to backup list all files and directories
* from external directory option
*/
if (external_dirs)
{
for (i = 0; i < parray_num(external_dirs); i++)
{
/* External dirs numeration starts with 1.
* 0 value is not external dir */
if (fio_is_remote(FIO_DB_HOST))
fio_list_dir(backup_files_list, parray_get(external_dirs, i),
false, true, false, false, true, i+1);
else
dir_list_file(backup_files_list, parray_get(external_dirs, i),
false, true, false, false, true, i+1, FIO_LOCAL_HOST);
}
}
/* close ssh session in main thread */
fio_disconnect();
/* Sanity check for backup_files_list, thank you, Windows:
* https://github.com/postgrespro/pg_probackup/issues/48
*/
if (parray_num(backup_files_list) < 100)
elog(ERROR, "PGDATA is almost empty. Either it was concurrently deleted or "
"pg_probackup do not possess sufficient permissions to list PGDATA content");
/* Calculate pgdata_bytes */
for (i = 0; i < parray_num(backup_files_list); i++)
{
pgFile *file = (pgFile *) parray_get(backup_files_list, i);
if (file->external_dir_num != 0)
continue;
if (S_ISDIR(file->mode))
{
current.pgdata_bytes += 4096;
continue;
}
current.pgdata_bytes += file->size;
}
pretty_size(current.pgdata_bytes, pretty_bytes, lengthof(pretty_bytes));
elog(INFO, "PGDATA size: %s", pretty_bytes);
/*
* Sort pathname ascending. It is necessary to create intermediate
* directories sequentially.
*
* For example:
* 1 - create 'base'
* 2 - create 'base/1'
*
* Sorted array is used at least in parse_filelist_filenames(),
* extractPageMap(), make_pagemap_from_ptrack().
*/
parray_qsort(backup_files_list, pgFileCompareRelPathWithExternal);
/* Extract information about files in backup_list parsing their names:*/
parse_filelist_filenames(backup_files_list, instance_config.pgdata);
elog(LOG, "Current Start LSN: %X/%X, TLI: %X",
(uint32) (current.start_lsn >> 32), (uint32) (current.start_lsn),
current.tli);
if (current.backup_mode != BACKUP_MODE_FULL)
elog(LOG, "Parent Start LSN: %X/%X, TLI: %X",
(uint32) (prev_backup->start_lsn >> 32), (uint32) (prev_backup->start_lsn),
prev_backup->tli);
/*
* Build page mapping in incremental mode.
*/
if (current.backup_mode == BACKUP_MODE_DIFF_PAGE ||
current.backup_mode == BACKUP_MODE_DIFF_PTRACK)
{
bool pagemap_isok = true;
time(&start_time);
elog(INFO, "Extracting pagemap of changed blocks");
if (current.backup_mode == BACKUP_MODE_DIFF_PAGE)
{
/*
* Build the page map. Obtain information about changed pages
* reading WAL segments present in archives up to the point
* where this backup has started.
*/
pagemap_isok = extractPageMap(instanceState->instance_wal_subdir_path,
instance_config.xlog_seg_size,
prev_backup->start_lsn, prev_backup->tli,
current.start_lsn, current.tli, tli_list);
}
else if (current.backup_mode == BACKUP_MODE_DIFF_PTRACK)
{
/*
* Build the page map from ptrack information.
*/
if (nodeInfo->ptrack_version_num >= 200)
make_pagemap_from_ptrack_2(backup_files_list, backup_conn,
nodeInfo->ptrack_schema,
nodeInfo->ptrack_version_num,
prev_backup_start_lsn);
else if (nodeInfo->ptrack_version_num == 105 ||
nodeInfo->ptrack_version_num == 106 ||
nodeInfo->ptrack_version_num == 107)
make_pagemap_from_ptrack_1(backup_files_list, backup_conn);
}
time(&end_time);
/* TODO: add ms precision */
if (pagemap_isok)
elog(INFO, "Pagemap successfully extracted, time elapsed: %.0f sec",
difftime(end_time, start_time));
else
elog(ERROR, "Pagemap extraction failed, time elasped: %.0f sec",
difftime(end_time, start_time));
}
/*
* Make directories before backup
*/
for (i = 0; i < parray_num(backup_files_list); i++)
{
pgFile *file = (pgFile *) parray_get(backup_files_list, i);
/* if the entry was a directory, create it in the backup */
if (S_ISDIR(file->mode))
{
char dirpath[MAXPGPATH];
if (file->external_dir_num)
{
char temp[MAXPGPATH];
snprintf(temp, MAXPGPATH, "%s%d", external_prefix,
file->external_dir_num);
join_path_components(dirpath, temp, file->rel_path);
}
else
join_path_components(dirpath, current.database_dir, file->rel_path);
elog(VERBOSE, "Create directory '%s'", dirpath);
fio_mkdir(dirpath, DIR_PERMISSION, FIO_BACKUP_HOST);
}
}
/* setup thread locks */
pfilearray_clear_locks(backup_files_list);
/* Sort by size for load balancing */
parray_qsort(backup_files_list, pgFileCompareSize);
/* Sort the array for binary search */
if (prev_backup_filelist)
parray_qsort(prev_backup_filelist, pgFileCompareRelPathWithExternal);
/* write initial backup_content.control file and update backup.control */
write_backup_filelist(¤t, backup_files_list,
instance_config.pgdata, external_dirs, true);
write_backup(¤t, true);
/* Init backup page header map */
init_header_map(¤t);
/* init thread args with own file lists */
threads = (pthread_t *) palloc(sizeof(pthread_t) * num_threads);
threads_args = (backup_files_arg *) palloc(sizeof(backup_files_arg)*num_threads);
for (i = 0; i < num_threads; i++)
{
backup_files_arg *arg = &(threads_args[i]);
arg->nodeInfo = nodeInfo;
arg->from_root = instance_config.pgdata;
arg->to_root = current.database_dir;
arg->external_prefix = external_prefix;
arg->external_dirs = external_dirs;
arg->files_list = backup_files_list;
arg->prev_filelist = prev_backup_filelist;
arg->prev_start_lsn = prev_backup_start_lsn;
arg->conn_arg.conn = NULL;
arg->conn_arg.cancel_conn = NULL;
arg->hdr_map = &(current.hdr_map);
arg->thread_num = i+1;
/* By default there are some error */
arg->ret = 1;
}
/* Run threads */
thread_interrupted = false;
elog(INFO, "Start transferring data files");
time(&start_time);
for (i = 0; i < num_threads; i++)
{
backup_files_arg *arg = &(threads_args[i]);
elog(VERBOSE, "Start thread num: %i", i);
pthread_create(&threads[i], NULL, backup_files, arg);
}
/* Wait threads */
for (i = 0; i < num_threads; i++)
{
pthread_join(threads[i], NULL);
if (threads_args[i].ret == 1)
backup_isok = false;
}
time(&end_time);
pretty_time_interval(difftime(end_time, start_time),
pretty_time, lengthof(pretty_time));
if (backup_isok)
elog(INFO, "Data files are transferred, time elapsed: %s",
pretty_time);
else
elog(ERROR, "Data files transferring failed, time elapsed: %s",
pretty_time);
/* clean previous backup file list */
if (prev_backup_filelist)
{
parray_walk(prev_backup_filelist, pgFileFree);
parray_free(prev_backup_filelist);
}
/* Notify end of backup */
pg_stop_backup(instanceState, ¤t, backup_conn, nodeInfo);
/* In case of backup from replica >= 9.6 we must fix minRecPoint,
* First we must find pg_control in backup_files_list.
*/
if (current.from_replica && !exclusive_backup)
{
pgFile *pg_control = NULL;
for (i = 0; i < parray_num(backup_files_list); i++)
{
pgFile *tmp_file = (pgFile *) parray_get(backup_files_list, i);
if (tmp_file->external_dir_num == 0 &&
(strcmp(tmp_file->rel_path, XLOG_CONTROL_FILE) == 0))
{
pg_control = tmp_file;
break;
}
}
if (!pg_control)
elog(ERROR, "Failed to find file \"%s\" in backup filelist.",
XLOG_CONTROL_FILE);
set_min_recovery_point(pg_control, current.database_dir, current.stop_lsn);
}
/* close and sync page header map */
if (current.hdr_map.fp)
{
cleanup_header_map(&(current.hdr_map));
if (fio_sync(current.hdr_map.path, FIO_BACKUP_HOST) != 0)
elog(ERROR, "Cannot sync file \"%s\": %s", current.hdr_map.path, strerror(errno));
}
/* close ssh session in main thread */
fio_disconnect();
/*
* Add archived xlog files into the list of files of this backup
* NOTHING TO DO HERE
*/
/* write database map to file and add it to control file */
if (database_map)
{
write_database_map(¤t, database_map, backup_files_list);
/* cleanup */
parray_walk(database_map, db_map_entry_free);
parray_free(database_map);
}
/* Print the list of files to backup catalog */
write_backup_filelist(¤t, backup_files_list, instance_config.pgdata,
external_dirs, true);
/* update backup control file to update size info */
write_backup(¤t, true);
/* Sync all copied files unless '--no-sync' flag is used */
if (no_sync)
elog(WARNING, "Backup files are not synced to disk");
else
{
elog(INFO, "Syncing backup files to disk");
time(&start_time);
for (i = 0; i < parray_num(backup_files_list); i++)
{
char to_fullpath[MAXPGPATH];
pgFile *file = (pgFile *) parray_get(backup_files_list, i);
/* TODO: sync directory ? */
if (S_ISDIR(file->mode))
continue;
if (file->write_size <= 0)
continue;
/* construct fullpath */
if (file->external_dir_num == 0)
join_path_components(to_fullpath, current.database_dir, file->rel_path);
else
{
char external_dst[MAXPGPATH];
makeExternalDirPathByNum(external_dst, external_prefix,
file->external_dir_num);
join_path_components(to_fullpath, external_dst, file->rel_path);
}
if (fio_sync(to_fullpath, FIO_BACKUP_HOST) != 0)
elog(ERROR, "Cannot sync file \"%s\": %s", to_fullpath, strerror(errno));
}
time(&end_time);
pretty_time_interval(difftime(end_time, start_time),
pretty_time, lengthof(pretty_time));
elog(INFO, "Backup files are synced, time elapsed: %s", pretty_time);
}
/* be paranoid about instance been from the past */
if (current.backup_mode != BACKUP_MODE_FULL &&
current.stop_lsn < prev_backup->stop_lsn)
elog(ERROR, "Current backup STOP LSN %X/%X is lower than STOP LSN %X/%X of previous backup %s. "
"It may indicate that we are trying to backup PostgreSQL instance from the past.",
(uint32) (current.stop_lsn >> 32), (uint32) (current.stop_lsn),
(uint32) (prev_backup->stop_lsn >> 32), (uint32) (prev_backup->stop_lsn),
base36enc(prev_backup->stop_lsn));
/* clean external directories list */
if (external_dirs)
free_dir_list(external_dirs);
/* Cleanup */
if (backup_list)
{
parray_walk(backup_list, pgBackupFree);
parray_free(backup_list);
}
if (tli_list)
{
parray_walk(tli_list, timelineInfoFree);
parray_free(tli_list);
}
parray_walk(backup_files_list, pgFileFree);
parray_free(backup_files_list);
backup_files_list = NULL;
}
/*
* Common code for CHECKDB and BACKUP commands.
* Ensure that we're able to connect to the instance
* check compatibility and fill basic info.
* For checkdb launched in amcheck mode with pgdata validation
* do not check system ID, it gives user an opportunity to
* check remote PostgreSQL instance.
* Also checking system ID in this case serves no purpose, because
* all work is done by server.
*
* Returns established connection
*/
PGconn *
pgdata_basic_setup(ConnectionOptions conn_opt, PGNodeInfo *nodeInfo)
{
PGconn *cur_conn;
/* Create connection for PostgreSQL */
cur_conn = pgut_connect(conn_opt.pghost, conn_opt.pgport,
conn_opt.pgdatabase,
conn_opt.pguser);
current.primary_conninfo = pgut_get_conninfo_string(cur_conn);
/* Confirm data block size and xlog block size are compatible */
confirm_block_size(cur_conn, "block_size", BLCKSZ);
confirm_block_size(cur_conn, "wal_block_size", XLOG_BLCKSZ);
nodeInfo->block_size = BLCKSZ;
nodeInfo->wal_block_size = XLOG_BLCKSZ;
nodeInfo->is_superuser = pg_is_superuser(cur_conn);
nodeInfo->pgpro_support = pgpro_support(cur_conn);
current.from_replica = pg_is_in_recovery(cur_conn);
/* Confirm that this server version is supported */
check_server_version(cur_conn, nodeInfo);
if (pg_is_checksum_enabled(cur_conn))
current.checksum_version = 1;
else
current.checksum_version = 0;
nodeInfo->checksum_version = current.checksum_version;
if (current.checksum_version)
elog(LOG, "This PostgreSQL instance was initialized with data block checksums. "
"Data block corruption will be detected");
else
elog(WARNING, "This PostgreSQL instance was initialized without data block checksums. "
"pg_probackup have no way to detect data block corruption without them. "
"Reinitialize PGDATA with option '--data-checksums'.");
if (nodeInfo->is_superuser)
elog(WARNING, "Current PostgreSQL role is superuser. "
"It is not recommended to run backup or checkdb as superuser.");
StrNCpy(current.server_version, nodeInfo->server_version_str,
sizeof(current.server_version));
return cur_conn;
}
/*
* Entry point of pg_probackup BACKUP subcommand.
*/
int
do_backup(InstanceState *instanceState, pgSetBackupParams *set_backup_params,
bool no_validate, bool no_sync, bool backup_logs)
{
PGconn *backup_conn = NULL;
PGNodeInfo nodeInfo;
char pretty_bytes[20];
/* Initialize PGInfonode */
pgNodeInit(&nodeInfo);
/* Save list of external directories */
if (instance_config.external_dir_str &&
(pg_strcasecmp(instance_config.external_dir_str, "none") != 0))
current.external_dir_str = instance_config.external_dir_str;
/* Create backup directory and BACKUP_CONTROL_FILE */
pgBackupCreateDir(¤t, instanceState->instance_backup_subdir_path);
if (!instance_config.pgdata)
elog(ERROR, "required parameter not specified: PGDATA "
"(-D, --pgdata)");
/* Update backup status and other metainfo. */
current.status = BACKUP_STATUS_RUNNING;
current.start_time = current.backup_id;
StrNCpy(current.program_version, PROGRAM_VERSION,
sizeof(current.program_version));
current.compress_alg = instance_config.compress_alg;
current.compress_level = instance_config.compress_level;
elog(INFO, "Backup start, pg_probackup version: %s, instance: %s, backup ID: %s, backup mode: %s, "
"wal mode: %s, remote: %s, compress-algorithm: %s, compress-level: %i",
PROGRAM_VERSION, instanceState->instance_name, base36enc(current.backup_id), pgBackupGetBackupMode(¤t, false),
current.stream ? "STREAM" : "ARCHIVE", IsSshProtocol() ? "true" : "false",
deparse_compress_alg(current.compress_alg), current.compress_level);
if (!lock_backup(¤t, true, true))
elog(ERROR, "Cannot lock backup %s directory",
base36enc(current.backup_id));
write_backup(¤t, true);
/* set the error processing function for the backup process */
pgut_atexit_push(backup_cleanup, NULL);
elog(LOG, "Backup destination is initialized");
/*
* setup backup_conn, do some compatibility checks and
* fill basic info about instance
*/
backup_conn = pgdata_basic_setup(instance_config.conn_opt, &nodeInfo);
if (current.from_replica)
elog(INFO, "Backup %s is going to be taken from standby", base36enc(current.backup_id));
/* TODO, print PostgreSQL full version */
//elog(INFO, "PostgreSQL version: %s", nodeInfo.server_version_str);
/*
* Ensure that backup directory was initialized for the same PostgreSQL
* instance we opened connection to. And that target backup database PGDATA
* belogns to the same instance.
*/
check_system_identifiers(backup_conn, instance_config.pgdata);
/* below perform checks specific for backup command */
#if PG_VERSION_NUM >= 110000
if (!RetrieveWalSegSize(backup_conn))
elog(ERROR, "Failed to retrieve wal_segment_size");
#endif
get_ptrack_version(backup_conn, &nodeInfo);
// elog(WARNING, "ptrack_version_num %d", ptrack_version_num);
if (nodeInfo.ptrack_version_num > 0)
nodeInfo.is_ptrack_enable = pg_ptrack_enable(backup_conn, nodeInfo.ptrack_version_num);
if (current.backup_mode == BACKUP_MODE_DIFF_PTRACK)
{
if (nodeInfo.ptrack_version_num == 0)
elog(ERROR, "This PostgreSQL instance does not support ptrack");
else
{
if (!nodeInfo.is_ptrack_enable)
elog(ERROR, "Ptrack is disabled");
}
}
if (current.from_replica && exclusive_backup)
/* Check master connection options */
if (instance_config.master_conn_opt.pghost == NULL)
elog(ERROR, "Options for connection to master must be provided to perform backup from replica");
/* add note to backup if requested */
if (set_backup_params && set_backup_params->note)
add_note(¤t, set_backup_params->note);
/* backup data */
do_backup_pg(instanceState, backup_conn, &nodeInfo, no_sync, backup_logs);
pgut_atexit_pop(backup_cleanup, NULL);
/* compute size of wal files of this backup stored in the archive */
if (!current.stream)
{
XLogSegNo start_segno;
XLogSegNo stop_segno;
GetXLogSegNo(current.start_lsn, start_segno, instance_config.xlog_seg_size);
GetXLogSegNo(current.stop_lsn, stop_segno, instance_config.xlog_seg_size);
current.wal_bytes = (stop_segno - start_segno) * instance_config.xlog_seg_size;
/*
* If start_lsn and stop_lsn are located in the same segment, then
* set wal_bytes to the size of 1 segment.
*/
if (current.wal_bytes <= 0)
current.wal_bytes = instance_config.xlog_seg_size;
}
/* Backup is done. Update backup status */
current.end_time = time(NULL);
current.status = BACKUP_STATUS_DONE;
write_backup(¤t, true);
/* Pin backup if requested */
if (set_backup_params &&
(set_backup_params->ttl > 0 ||
set_backup_params->expire_time > 0))
{
pin_backup(¤t, set_backup_params);
}
if (!no_validate)
pgBackupValidate(¤t, NULL);
/* Notify user about backup size */
if (current.stream)
pretty_size(current.data_bytes + current.wal_bytes, pretty_bytes, lengthof(pretty_bytes));
else
pretty_size(current.data_bytes, pretty_bytes, lengthof(pretty_bytes));
elog(INFO, "Backup %s resident size: %s", base36enc(current.start_time), pretty_bytes);
if (current.status == BACKUP_STATUS_OK ||
current.status == BACKUP_STATUS_DONE)
elog(INFO, "Backup %s completed", base36enc(current.start_time));
else
elog(ERROR, "Backup %s failed", base36enc(current.start_time));
/*
* After successful backup completion remove backups
* which are expired according to retention policies
*/
if (delete_expired || merge_expired || delete_wal)
do_retention(instanceState, no_validate, no_sync);
return 0;
}
/*
* Confirm that this server version is supported
*/
static void
check_server_version(PGconn *conn, PGNodeInfo *nodeInfo)
{
PGresult *res = NULL;
/* confirm server version */
nodeInfo->server_version = PQserverVersion(conn);
if (nodeInfo->server_version == 0)
elog(ERROR, "Unknown server version %d", nodeInfo->server_version);
if (nodeInfo->server_version < 100000)
sprintf(nodeInfo->server_version_str, "%d.%d",
nodeInfo->server_version / 10000,
(nodeInfo->server_version / 100) % 100);
else
sprintf(nodeInfo->server_version_str, "%d",
nodeInfo->server_version / 10000);
if (nodeInfo->server_version < 90500)
elog(ERROR,
"server version is %s, must be %s or higher",
nodeInfo->server_version_str, "9.5");
if (current.from_replica && nodeInfo->server_version < 90600)
elog(ERROR,
"server version is %s, must be %s or higher for backup from replica",
nodeInfo->server_version_str, "9.6");
if (nodeInfo->pgpro_support)
res = pgut_execute(conn, "SELECT pgpro_edition()", 0, NULL);
/*
* Check major version of connected PostgreSQL and major version of
* compiled PostgreSQL.
*/
#ifdef PGPRO_VERSION
if (!res)
/* It seems we connected to PostgreSQL (not Postgres Pro) */
elog(ERROR, "%s was built with Postgres Pro %s %s, "
"but connection is made with PostgreSQL %s",
PROGRAM_NAME, PG_MAJORVERSION, PGPRO_EDITION, nodeInfo->server_version_str);
else
{
if (strcmp(nodeInfo->server_version_str, PG_MAJORVERSION) != 0 &&
strcmp(PQgetvalue(res, 0, 0), PGPRO_EDITION) != 0)
elog(ERROR, "%s was built with Postgres Pro %s %s, "
"but connection is made with Postgres Pro %s %s",
PROGRAM_NAME, PG_MAJORVERSION, PGPRO_EDITION,
nodeInfo->server_version_str, PQgetvalue(res, 0, 0));
}
#else
if (res)
/* It seems we connected to Postgres Pro (not PostgreSQL) */
elog(ERROR, "%s was built with PostgreSQL %s, "
"but connection is made with Postgres Pro %s %s",
PROGRAM_NAME, PG_MAJORVERSION,
nodeInfo->server_version_str, PQgetvalue(res, 0, 0));
else
{
if (strcmp(nodeInfo->server_version_str, PG_MAJORVERSION) != 0)
elog(ERROR, "%s was built with PostgreSQL %s, but connection is made with %s",
PROGRAM_NAME, PG_MAJORVERSION, nodeInfo->server_version_str);
}
#endif
if (res)
PQclear(res);
/* Do exclusive backup only for PostgreSQL 9.5 */
exclusive_backup = nodeInfo->server_version < 90600;
}
/*
* Ensure that backup directory was initialized for the same PostgreSQL
* instance we opened connection to. And that target backup database PGDATA
* belogns to the same instance.
* All system identifiers must be equal.
*/
void
check_system_identifiers(PGconn *conn, char *pgdata)
{
uint64 system_id_conn;
uint64 system_id_pgdata;
system_id_pgdata = get_system_identifier(pgdata, XLOG_CONTROL_FILE, FIO_DB_HOST);
system_id_conn = get_remote_system_identifier(conn);
/* for checkdb check only system_id_pgdata and system_id_conn */
if (current.backup_mode == BACKUP_MODE_INVALID)
{
if (system_id_conn != system_id_pgdata)
{
elog(ERROR, "Data directory initialized with system id " UINT64_FORMAT ", "
"but connected instance system id is " UINT64_FORMAT,
system_id_pgdata, system_id_conn);
}
return;
}