| 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 | |