1 | /*------------------------------------------------------------------------- |
2 | * |
3 | * xlogfuncs.c |
4 | * |
5 | * PostgreSQL write-ahead log manager user interface functions |
6 | * |
7 | * This file contains WAL control and information functions. |
8 | * |
9 | * |
10 | * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group |
11 | * Portions Copyright (c) 1994, Regents of the University of California |
12 | * |
13 | * src/backend/access/transam/xlogfuncs.c |
14 | * |
15 | *------------------------------------------------------------------------- |
16 | */ |
17 | #include "postgres.h" |
18 | |
19 | #include <unistd.h> |
20 | |
21 | #include "access/htup_details.h" |
22 | #include "access/xlog.h" |
23 | #include "access/xlog_internal.h" |
24 | #include "access/xlogutils.h" |
25 | #include "catalog/pg_type.h" |
26 | #include "funcapi.h" |
27 | #include "miscadmin.h" |
28 | #include "pgstat.h" |
29 | #include "replication/walreceiver.h" |
30 | #include "storage/smgr.h" |
31 | #include "utils/builtins.h" |
32 | #include "utils/memutils.h" |
33 | #include "utils/numeric.h" |
34 | #include "utils/guc.h" |
35 | #include "utils/pg_lsn.h" |
36 | #include "utils/timestamp.h" |
37 | #include "utils/tuplestore.h" |
38 | #include "storage/fd.h" |
39 | #include "storage/ipc.h" |
40 | |
41 | |
42 | /* |
43 | * Store label file and tablespace map during non-exclusive backups. |
44 | */ |
45 | static StringInfo label_file; |
46 | static StringInfo tblspc_map_file; |
47 | |
48 | /* |
49 | * Called when the backend exits with a running non-exclusive base backup, |
50 | * to clean up state. |
51 | */ |
52 | static void |
53 | nonexclusive_base_backup_cleanup(int code, Datum arg) |
54 | { |
55 | do_pg_abort_backup(); |
56 | ereport(WARNING, |
57 | (errmsg("aborting backup due to backend exiting before pg_stop_backup was called" ))); |
58 | } |
59 | |
60 | /* |
61 | * pg_start_backup: set up for taking an on-line backup dump |
62 | * |
63 | * Essentially what this does is to create a backup label file in $PGDATA, |
64 | * where it will be archived as part of the backup dump. The label file |
65 | * contains the user-supplied label string (typically this would be used |
66 | * to tell where the backup dump will be stored) and the starting time and |
67 | * starting WAL location for the dump. |
68 | * |
69 | * Permission checking for this function is managed through the normal |
70 | * GRANT system. |
71 | */ |
72 | Datum |
73 | pg_start_backup(PG_FUNCTION_ARGS) |
74 | { |
75 | text *backupid = PG_GETARG_TEXT_PP(0); |
76 | bool fast = PG_GETARG_BOOL(1); |
77 | bool exclusive = PG_GETARG_BOOL(2); |
78 | char *backupidstr; |
79 | XLogRecPtr startpoint; |
80 | SessionBackupState status = get_backup_status(); |
81 | |
82 | backupidstr = text_to_cstring(backupid); |
83 | |
84 | if (status == SESSION_BACKUP_NON_EXCLUSIVE) |
85 | ereport(ERROR, |
86 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
87 | errmsg("a backup is already in progress in this session" ))); |
88 | |
89 | if (exclusive) |
90 | { |
91 | startpoint = do_pg_start_backup(backupidstr, fast, NULL, NULL, |
92 | NULL, NULL, false, true); |
93 | } |
94 | else |
95 | { |
96 | MemoryContext oldcontext; |
97 | |
98 | /* |
99 | * Label file and tablespace map file need to be long-lived, since |
100 | * they are read in pg_stop_backup. |
101 | */ |
102 | oldcontext = MemoryContextSwitchTo(TopMemoryContext); |
103 | label_file = makeStringInfo(); |
104 | tblspc_map_file = makeStringInfo(); |
105 | MemoryContextSwitchTo(oldcontext); |
106 | |
107 | startpoint = do_pg_start_backup(backupidstr, fast, NULL, label_file, |
108 | NULL, tblspc_map_file, false, true); |
109 | |
110 | before_shmem_exit(nonexclusive_base_backup_cleanup, (Datum) 0); |
111 | } |
112 | |
113 | PG_RETURN_LSN(startpoint); |
114 | } |
115 | |
116 | /* |
117 | * pg_stop_backup: finish taking an on-line backup dump |
118 | * |
119 | * We write an end-of-backup WAL record, and remove the backup label file |
120 | * created by pg_start_backup, creating a backup history file in pg_wal |
121 | * instead (whence it will immediately be archived). The backup history file |
122 | * contains the same info found in the label file, plus the backup-end time |
123 | * and WAL location. Before 9.0, the backup-end time was read from the backup |
124 | * history file at the beginning of archive recovery, but we now use the WAL |
125 | * record for that and the file is for informational and debug purposes only. |
126 | * |
127 | * Note: different from CancelBackup which just cancels online backup mode. |
128 | * |
129 | * Note: this version is only called to stop an exclusive backup. The function |
130 | * pg_stop_backup_v2 (overloaded as pg_stop_backup in SQL) is called to |
131 | * stop non-exclusive backups. |
132 | * |
133 | * Permission checking for this function is managed through the normal |
134 | * GRANT system. |
135 | */ |
136 | Datum |
137 | pg_stop_backup(PG_FUNCTION_ARGS) |
138 | { |
139 | XLogRecPtr stoppoint; |
140 | SessionBackupState status = get_backup_status(); |
141 | |
142 | if (status == SESSION_BACKUP_NON_EXCLUSIVE) |
143 | ereport(ERROR, |
144 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
145 | errmsg("non-exclusive backup in progress" ), |
146 | errhint("Did you mean to use pg_stop_backup('f')?" ))); |
147 | |
148 | /* |
149 | * Exclusive backups were typically started in a different connection, so |
150 | * don't try to verify that status of backup is set to |
151 | * SESSION_BACKUP_EXCLUSIVE in this function. Actual verification that an |
152 | * exclusive backup is in fact running is handled inside |
153 | * do_pg_stop_backup. |
154 | */ |
155 | stoppoint = do_pg_stop_backup(NULL, true, NULL); |
156 | |
157 | PG_RETURN_LSN(stoppoint); |
158 | } |
159 | |
160 | |
161 | /* |
162 | * pg_stop_backup_v2: finish taking exclusive or nonexclusive on-line backup. |
163 | * |
164 | * Works the same as pg_stop_backup, except for non-exclusive backups it returns |
165 | * the backup label and tablespace map files as text fields in as part of the |
166 | * resultset. |
167 | * |
168 | * The first parameter (variable 'exclusive') allows the user to tell us if |
169 | * this is an exclusive or a non-exclusive backup. |
170 | * |
171 | * The second parameter (variable 'waitforarchive'), which is optional, |
172 | * allows the user to choose if they want to wait for the WAL to be archived |
173 | * or if we should just return as soon as the WAL record is written. |
174 | * |
175 | * Permission checking for this function is managed through the normal |
176 | * GRANT system. |
177 | */ |
178 | Datum |
179 | pg_stop_backup_v2(PG_FUNCTION_ARGS) |
180 | { |
181 | ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; |
182 | TupleDesc tupdesc; |
183 | Tuplestorestate *tupstore; |
184 | MemoryContext per_query_ctx; |
185 | MemoryContext oldcontext; |
186 | Datum values[3]; |
187 | bool nulls[3]; |
188 | |
189 | bool exclusive = PG_GETARG_BOOL(0); |
190 | bool waitforarchive = PG_GETARG_BOOL(1); |
191 | XLogRecPtr stoppoint; |
192 | SessionBackupState status = get_backup_status(); |
193 | |
194 | /* check to see if caller supports us returning a tuplestore */ |
195 | if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) |
196 | ereport(ERROR, |
197 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
198 | errmsg("set-valued function called in context that cannot accept a set" ))); |
199 | if (!(rsinfo->allowedModes & SFRM_Materialize)) |
200 | ereport(ERROR, |
201 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
202 | errmsg("materialize mode required, but it is not " \ |
203 | "allowed in this context" ))); |
204 | |
205 | /* Build a tuple descriptor for our result type */ |
206 | if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) |
207 | elog(ERROR, "return type must be a row type" ); |
208 | |
209 | per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; |
210 | oldcontext = MemoryContextSwitchTo(per_query_ctx); |
211 | |
212 | tupstore = tuplestore_begin_heap(true, false, work_mem); |
213 | rsinfo->returnMode = SFRM_Materialize; |
214 | rsinfo->setResult = tupstore; |
215 | rsinfo->setDesc = tupdesc; |
216 | |
217 | MemoryContextSwitchTo(oldcontext); |
218 | |
219 | MemSet(values, 0, sizeof(values)); |
220 | MemSet(nulls, 0, sizeof(nulls)); |
221 | |
222 | if (exclusive) |
223 | { |
224 | if (status == SESSION_BACKUP_NON_EXCLUSIVE) |
225 | ereport(ERROR, |
226 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
227 | errmsg("non-exclusive backup in progress" ), |
228 | errhint("Did you mean to use pg_stop_backup('f')?" ))); |
229 | |
230 | /* |
231 | * Stop the exclusive backup, and since we're in an exclusive backup |
232 | * return NULL for both backup_label and tablespace_map. |
233 | */ |
234 | stoppoint = do_pg_stop_backup(NULL, waitforarchive, NULL); |
235 | |
236 | nulls[1] = true; |
237 | nulls[2] = true; |
238 | } |
239 | else |
240 | { |
241 | if (status != SESSION_BACKUP_NON_EXCLUSIVE) |
242 | ereport(ERROR, |
243 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
244 | errmsg("non-exclusive backup is not in progress" ), |
245 | errhint("Did you mean to use pg_stop_backup('t')?" ))); |
246 | |
247 | /* |
248 | * Stop the non-exclusive backup. Return a copy of the backup label |
249 | * and tablespace map so they can be written to disk by the caller. |
250 | */ |
251 | stoppoint = do_pg_stop_backup(label_file->data, waitforarchive, NULL); |
252 | cancel_before_shmem_exit(nonexclusive_base_backup_cleanup, (Datum) 0); |
253 | |
254 | values[1] = CStringGetTextDatum(label_file->data); |
255 | values[2] = CStringGetTextDatum(tblspc_map_file->data); |
256 | |
257 | /* Free structures allocated in TopMemoryContext */ |
258 | pfree(label_file->data); |
259 | pfree(label_file); |
260 | label_file = NULL; |
261 | pfree(tblspc_map_file->data); |
262 | pfree(tblspc_map_file); |
263 | tblspc_map_file = NULL; |
264 | } |
265 | |
266 | /* Stoppoint is included on both exclusive and nonexclusive backups */ |
267 | values[0] = LSNGetDatum(stoppoint); |
268 | |
269 | tuplestore_putvalues(tupstore, tupdesc, values, nulls); |
270 | tuplestore_donestoring(typstore); |
271 | |
272 | return (Datum) 0; |
273 | } |
274 | |
275 | /* |
276 | * pg_switch_wal: switch to next xlog file |
277 | * |
278 | * Permission checking for this function is managed through the normal |
279 | * GRANT system. |
280 | */ |
281 | Datum |
282 | pg_switch_wal(PG_FUNCTION_ARGS) |
283 | { |
284 | XLogRecPtr switchpoint; |
285 | |
286 | if (RecoveryInProgress()) |
287 | ereport(ERROR, |
288 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
289 | errmsg("recovery is in progress" ), |
290 | errhint("WAL control functions cannot be executed during recovery." ))); |
291 | |
292 | switchpoint = RequestXLogSwitch(false); |
293 | |
294 | /* |
295 | * As a convenience, return the WAL location of the switch record |
296 | */ |
297 | PG_RETURN_LSN(switchpoint); |
298 | } |
299 | |
300 | /* |
301 | * pg_create_restore_point: a named point for restore |
302 | * |
303 | * Permission checking for this function is managed through the normal |
304 | * GRANT system. |
305 | */ |
306 | Datum |
307 | pg_create_restore_point(PG_FUNCTION_ARGS) |
308 | { |
309 | text *restore_name = PG_GETARG_TEXT_PP(0); |
310 | char *restore_name_str; |
311 | XLogRecPtr restorepoint; |
312 | |
313 | if (RecoveryInProgress()) |
314 | ereport(ERROR, |
315 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
316 | (errmsg("recovery is in progress" ), |
317 | errhint("WAL control functions cannot be executed during recovery." )))); |
318 | |
319 | if (!XLogIsNeeded()) |
320 | ereport(ERROR, |
321 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
322 | errmsg("WAL level not sufficient for creating a restore point" ), |
323 | errhint("wal_level must be set to \"replica\" or \"logical\" at server start." ))); |
324 | |
325 | restore_name_str = text_to_cstring(restore_name); |
326 | |
327 | if (strlen(restore_name_str) >= MAXFNAMELEN) |
328 | ereport(ERROR, |
329 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
330 | errmsg("value too long for restore point (maximum %d characters)" , MAXFNAMELEN - 1))); |
331 | |
332 | restorepoint = XLogRestorePoint(restore_name_str); |
333 | |
334 | /* |
335 | * As a convenience, return the WAL location of the restore point record |
336 | */ |
337 | PG_RETURN_LSN(restorepoint); |
338 | } |
339 | |
340 | /* |
341 | * Report the current WAL write location (same format as pg_start_backup etc) |
342 | * |
343 | * This is useful for determining how much of WAL is visible to an external |
344 | * archiving process. Note that the data before this point is written out |
345 | * to the kernel, but is not necessarily synced to disk. |
346 | */ |
347 | Datum |
348 | pg_current_wal_lsn(PG_FUNCTION_ARGS) |
349 | { |
350 | XLogRecPtr current_recptr; |
351 | |
352 | if (RecoveryInProgress()) |
353 | ereport(ERROR, |
354 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
355 | errmsg("recovery is in progress" ), |
356 | errhint("WAL control functions cannot be executed during recovery." ))); |
357 | |
358 | current_recptr = GetXLogWriteRecPtr(); |
359 | |
360 | PG_RETURN_LSN(current_recptr); |
361 | } |
362 | |
363 | /* |
364 | * Report the current WAL insert location (same format as pg_start_backup etc) |
365 | * |
366 | * This function is mostly for debugging purposes. |
367 | */ |
368 | Datum |
369 | pg_current_wal_insert_lsn(PG_FUNCTION_ARGS) |
370 | { |
371 | XLogRecPtr current_recptr; |
372 | |
373 | if (RecoveryInProgress()) |
374 | ereport(ERROR, |
375 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
376 | errmsg("recovery is in progress" ), |
377 | errhint("WAL control functions cannot be executed during recovery." ))); |
378 | |
379 | current_recptr = GetXLogInsertRecPtr(); |
380 | |
381 | PG_RETURN_LSN(current_recptr); |
382 | } |
383 | |
384 | /* |
385 | * Report the current WAL flush location (same format as pg_start_backup etc) |
386 | * |
387 | * This function is mostly for debugging purposes. |
388 | */ |
389 | Datum |
390 | pg_current_wal_flush_lsn(PG_FUNCTION_ARGS) |
391 | { |
392 | XLogRecPtr current_recptr; |
393 | |
394 | if (RecoveryInProgress()) |
395 | ereport(ERROR, |
396 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
397 | errmsg("recovery is in progress" ), |
398 | errhint("WAL control functions cannot be executed during recovery." ))); |
399 | |
400 | current_recptr = GetFlushRecPtr(); |
401 | |
402 | PG_RETURN_LSN(current_recptr); |
403 | } |
404 | |
405 | /* |
406 | * Report the last WAL receive location (same format as pg_start_backup etc) |
407 | * |
408 | * This is useful for determining how much of WAL is guaranteed to be received |
409 | * and synced to disk by walreceiver. |
410 | */ |
411 | Datum |
412 | pg_last_wal_receive_lsn(PG_FUNCTION_ARGS) |
413 | { |
414 | XLogRecPtr recptr; |
415 | |
416 | recptr = GetWalRcvWriteRecPtr(NULL, NULL); |
417 | |
418 | if (recptr == 0) |
419 | PG_RETURN_NULL(); |
420 | |
421 | PG_RETURN_LSN(recptr); |
422 | } |
423 | |
424 | /* |
425 | * Report the last WAL replay location (same format as pg_start_backup etc) |
426 | * |
427 | * This is useful for determining how much of WAL is visible to read-only |
428 | * connections during recovery. |
429 | */ |
430 | Datum |
431 | pg_last_wal_replay_lsn(PG_FUNCTION_ARGS) |
432 | { |
433 | XLogRecPtr recptr; |
434 | |
435 | recptr = GetXLogReplayRecPtr(NULL); |
436 | |
437 | if (recptr == 0) |
438 | PG_RETURN_NULL(); |
439 | |
440 | PG_RETURN_LSN(recptr); |
441 | } |
442 | |
443 | /* |
444 | * Compute an xlog file name and decimal byte offset given a WAL location, |
445 | * such as is returned by pg_stop_backup() or pg_switch_wal(). |
446 | * |
447 | * Note that a location exactly at a segment boundary is taken to be in |
448 | * the previous segment. This is usually the right thing, since the |
449 | * expected usage is to determine which xlog file(s) are ready to archive. |
450 | */ |
451 | Datum |
452 | pg_walfile_name_offset(PG_FUNCTION_ARGS) |
453 | { |
454 | XLogSegNo xlogsegno; |
455 | uint32 xrecoff; |
456 | XLogRecPtr locationpoint = PG_GETARG_LSN(0); |
457 | char xlogfilename[MAXFNAMELEN]; |
458 | Datum values[2]; |
459 | bool isnull[2]; |
460 | TupleDesc resultTupleDesc; |
461 | HeapTuple resultHeapTuple; |
462 | Datum result; |
463 | |
464 | if (RecoveryInProgress()) |
465 | ereport(ERROR, |
466 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
467 | errmsg("recovery is in progress" ), |
468 | errhint("%s cannot be executed during recovery." , |
469 | "pg_walfile_name_offset()" ))); |
470 | |
471 | /* |
472 | * Construct a tuple descriptor for the result row. This must match this |
473 | * function's pg_proc entry! |
474 | */ |
475 | resultTupleDesc = CreateTemplateTupleDesc(2); |
476 | TupleDescInitEntry(resultTupleDesc, (AttrNumber) 1, "file_name" , |
477 | TEXTOID, -1, 0); |
478 | TupleDescInitEntry(resultTupleDesc, (AttrNumber) 2, "file_offset" , |
479 | INT4OID, -1, 0); |
480 | |
481 | resultTupleDesc = BlessTupleDesc(resultTupleDesc); |
482 | |
483 | /* |
484 | * xlogfilename |
485 | */ |
486 | XLByteToPrevSeg(locationpoint, xlogsegno, wal_segment_size); |
487 | XLogFileName(xlogfilename, ThisTimeLineID, xlogsegno, wal_segment_size); |
488 | |
489 | values[0] = CStringGetTextDatum(xlogfilename); |
490 | isnull[0] = false; |
491 | |
492 | /* |
493 | * offset |
494 | */ |
495 | xrecoff = XLogSegmentOffset(locationpoint, wal_segment_size); |
496 | |
497 | values[1] = UInt32GetDatum(xrecoff); |
498 | isnull[1] = false; |
499 | |
500 | /* |
501 | * Tuple jam: Having first prepared your Datums, then squash together |
502 | */ |
503 | resultHeapTuple = heap_form_tuple(resultTupleDesc, values, isnull); |
504 | |
505 | result = HeapTupleGetDatum(resultHeapTuple); |
506 | |
507 | PG_RETURN_DATUM(result); |
508 | } |
509 | |
510 | /* |
511 | * Compute an xlog file name given a WAL location, |
512 | * such as is returned by pg_stop_backup() or pg_switch_wal(). |
513 | */ |
514 | Datum |
515 | pg_walfile_name(PG_FUNCTION_ARGS) |
516 | { |
517 | XLogSegNo xlogsegno; |
518 | XLogRecPtr locationpoint = PG_GETARG_LSN(0); |
519 | char xlogfilename[MAXFNAMELEN]; |
520 | |
521 | if (RecoveryInProgress()) |
522 | ereport(ERROR, |
523 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
524 | errmsg("recovery is in progress" ), |
525 | errhint("%s cannot be executed during recovery." , |
526 | "pg_walfile_name()" ))); |
527 | |
528 | XLByteToPrevSeg(locationpoint, xlogsegno, wal_segment_size); |
529 | XLogFileName(xlogfilename, ThisTimeLineID, xlogsegno, wal_segment_size); |
530 | |
531 | PG_RETURN_TEXT_P(cstring_to_text(xlogfilename)); |
532 | } |
533 | |
534 | /* |
535 | * pg_wal_replay_pause - pause recovery now |
536 | * |
537 | * Permission checking for this function is managed through the normal |
538 | * GRANT system. |
539 | */ |
540 | Datum |
541 | pg_wal_replay_pause(PG_FUNCTION_ARGS) |
542 | { |
543 | if (!RecoveryInProgress()) |
544 | ereport(ERROR, |
545 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
546 | errmsg("recovery is not in progress" ), |
547 | errhint("Recovery control functions can only be executed during recovery." ))); |
548 | |
549 | SetRecoveryPause(true); |
550 | |
551 | PG_RETURN_VOID(); |
552 | } |
553 | |
554 | /* |
555 | * pg_wal_replay_resume - resume recovery now |
556 | * |
557 | * Permission checking for this function is managed through the normal |
558 | * GRANT system. |
559 | */ |
560 | Datum |
561 | pg_wal_replay_resume(PG_FUNCTION_ARGS) |
562 | { |
563 | if (!RecoveryInProgress()) |
564 | ereport(ERROR, |
565 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
566 | errmsg("recovery is not in progress" ), |
567 | errhint("Recovery control functions can only be executed during recovery." ))); |
568 | |
569 | SetRecoveryPause(false); |
570 | |
571 | PG_RETURN_VOID(); |
572 | } |
573 | |
574 | /* |
575 | * pg_is_wal_replay_paused |
576 | */ |
577 | Datum |
578 | pg_is_wal_replay_paused(PG_FUNCTION_ARGS) |
579 | { |
580 | if (!RecoveryInProgress()) |
581 | ereport(ERROR, |
582 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
583 | errmsg("recovery is not in progress" ), |
584 | errhint("Recovery control functions can only be executed during recovery." ))); |
585 | |
586 | PG_RETURN_BOOL(RecoveryIsPaused()); |
587 | } |
588 | |
589 | /* |
590 | * Returns timestamp of latest processed commit/abort record. |
591 | * |
592 | * When the server has been started normally without recovery the function |
593 | * returns NULL. |
594 | */ |
595 | Datum |
596 | pg_last_xact_replay_timestamp(PG_FUNCTION_ARGS) |
597 | { |
598 | TimestampTz xtime; |
599 | |
600 | xtime = GetLatestXTime(); |
601 | if (xtime == 0) |
602 | PG_RETURN_NULL(); |
603 | |
604 | PG_RETURN_TIMESTAMPTZ(xtime); |
605 | } |
606 | |
607 | /* |
608 | * Returns bool with current recovery mode, a global state. |
609 | */ |
610 | Datum |
611 | pg_is_in_recovery(PG_FUNCTION_ARGS) |
612 | { |
613 | PG_RETURN_BOOL(RecoveryInProgress()); |
614 | } |
615 | |
616 | /* |
617 | * Compute the difference in bytes between two WAL locations. |
618 | */ |
619 | Datum |
620 | pg_wal_lsn_diff(PG_FUNCTION_ARGS) |
621 | { |
622 | Datum result; |
623 | |
624 | result = DirectFunctionCall2(pg_lsn_mi, |
625 | PG_GETARG_DATUM(0), |
626 | PG_GETARG_DATUM(1)); |
627 | |
628 | PG_RETURN_NUMERIC(result); |
629 | } |
630 | |
631 | /* |
632 | * Returns bool with current on-line backup mode, a global state. |
633 | */ |
634 | Datum |
635 | pg_is_in_backup(PG_FUNCTION_ARGS) |
636 | { |
637 | PG_RETURN_BOOL(BackupInProgress()); |
638 | } |
639 | |
640 | /* |
641 | * Returns start time of an online exclusive backup. |
642 | * |
643 | * When there's no exclusive backup in progress, the function |
644 | * returns NULL. |
645 | */ |
646 | Datum |
647 | pg_backup_start_time(PG_FUNCTION_ARGS) |
648 | { |
649 | Datum xtime; |
650 | FILE *lfp; |
651 | char fline[MAXPGPATH]; |
652 | char backup_start_time[30]; |
653 | |
654 | /* |
655 | * See if label file is present |
656 | */ |
657 | lfp = AllocateFile(BACKUP_LABEL_FILE, "r" ); |
658 | if (lfp == NULL) |
659 | { |
660 | if (errno != ENOENT) |
661 | ereport(ERROR, |
662 | (errcode_for_file_access(), |
663 | errmsg("could not read file \"%s\": %m" , |
664 | BACKUP_LABEL_FILE))); |
665 | PG_RETURN_NULL(); |
666 | } |
667 | |
668 | /* |
669 | * Parse the file to find the START TIME line. |
670 | */ |
671 | backup_start_time[0] = '\0'; |
672 | while (fgets(fline, sizeof(fline), lfp) != NULL) |
673 | { |
674 | if (sscanf(fline, "START TIME: %25[^\n]\n" , backup_start_time) == 1) |
675 | break; |
676 | } |
677 | |
678 | /* Check for a read error. */ |
679 | if (ferror(lfp)) |
680 | ereport(ERROR, |
681 | (errcode_for_file_access(), |
682 | errmsg("could not read file \"%s\": %m" , BACKUP_LABEL_FILE))); |
683 | |
684 | /* Close the backup label file. */ |
685 | if (FreeFile(lfp)) |
686 | ereport(ERROR, |
687 | (errcode_for_file_access(), |
688 | errmsg("could not close file \"%s\": %m" , BACKUP_LABEL_FILE))); |
689 | |
690 | if (strlen(backup_start_time) == 0) |
691 | ereport(ERROR, |
692 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
693 | errmsg("invalid data in file \"%s\"" , BACKUP_LABEL_FILE))); |
694 | |
695 | /* |
696 | * Convert the time string read from file to TimestampTz form. |
697 | */ |
698 | xtime = DirectFunctionCall3(timestamptz_in, |
699 | CStringGetDatum(backup_start_time), |
700 | ObjectIdGetDatum(InvalidOid), |
701 | Int32GetDatum(-1)); |
702 | |
703 | PG_RETURN_DATUM(xtime); |
704 | } |
705 | |
706 | /* |
707 | * Promotes a standby server. |
708 | * |
709 | * A result of "true" means that promotion has been completed if "wait" is |
710 | * "true", or initiated if "wait" is false. |
711 | */ |
712 | Datum |
713 | pg_promote(PG_FUNCTION_ARGS) |
714 | { |
715 | bool wait = PG_GETARG_BOOL(0); |
716 | int wait_seconds = PG_GETARG_INT32(1); |
717 | FILE *promote_file; |
718 | int i; |
719 | |
720 | if (!RecoveryInProgress()) |
721 | ereport(ERROR, |
722 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
723 | errmsg("recovery is not in progress" ), |
724 | errhint("Recovery control functions can only be executed during recovery." ))); |
725 | |
726 | if (wait_seconds <= 0) |
727 | ereport(ERROR, |
728 | (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), |
729 | errmsg("\"wait_seconds\" must not be negative or zero" ))); |
730 | |
731 | /* create the promote signal file */ |
732 | promote_file = AllocateFile(PROMOTE_SIGNAL_FILE, "w" ); |
733 | if (!promote_file) |
734 | ereport(ERROR, |
735 | (errcode_for_file_access(), |
736 | errmsg("could not create file \"%s\": %m" , |
737 | PROMOTE_SIGNAL_FILE))); |
738 | |
739 | if (FreeFile(promote_file)) |
740 | ereport(ERROR, |
741 | (errcode_for_file_access(), |
742 | errmsg("could not write file \"%s\": %m" , |
743 | PROMOTE_SIGNAL_FILE))); |
744 | |
745 | /* signal the postmaster */ |
746 | if (kill(PostmasterPid, SIGUSR1) != 0) |
747 | { |
748 | ereport(WARNING, |
749 | (errmsg("failed to send signal to postmaster: %m" ))); |
750 | (void) unlink(PROMOTE_SIGNAL_FILE); |
751 | PG_RETURN_BOOL(false); |
752 | } |
753 | |
754 | /* return immediately if waiting was not requested */ |
755 | if (!wait) |
756 | PG_RETURN_BOOL(true); |
757 | |
758 | /* wait for the amount of time wanted until promotion */ |
759 | #define WAITS_PER_SECOND 10 |
760 | for (i = 0; i < WAITS_PER_SECOND * wait_seconds; i++) |
761 | { |
762 | int rc; |
763 | |
764 | ResetLatch(MyLatch); |
765 | |
766 | if (!RecoveryInProgress()) |
767 | PG_RETURN_BOOL(true); |
768 | |
769 | CHECK_FOR_INTERRUPTS(); |
770 | |
771 | rc = WaitLatch(MyLatch, |
772 | WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, |
773 | 1000L / WAITS_PER_SECOND, |
774 | WAIT_EVENT_PROMOTE); |
775 | |
776 | /* |
777 | * Emergency bailout if postmaster has died. This is to avoid the |
778 | * necessity for manual cleanup of all postmaster children. |
779 | */ |
780 | if (rc & WL_POSTMASTER_DEATH) |
781 | PG_RETURN_BOOL(false); |
782 | } |
783 | |
784 | ereport(WARNING, |
785 | (errmsg("server did not promote within %d seconds" , wait_seconds))); |
786 | PG_RETURN_BOOL(false); |
787 | } |
788 | |