mirror of https://github.com/postgres/postgres
Add four new SQL accessible functions: pg_control_system(), pg_control_checkpoint(), pg_control_recovery(), and pg_control_init() which expose a subset of the control file data. Along the way move the code to read and validate the control file to src/common, where it can be shared by the new backend functions and the original pg_controldata frontend program. Patch by me, significant input, testing, and review by Michael Paquier.pull/11/head
parent
d34794f7d5
commit
dc7d70ea05
@ -0,0 +1,341 @@ |
|||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
* |
||||||
|
* pg_controldata.c |
||||||
|
* |
||||||
|
* Routines to expose the contents of the control data file via |
||||||
|
* a set of SQL functions. |
||||||
|
* |
||||||
|
* Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group |
||||||
|
* Portions Copyright (c) 1994, Regents of the University of California |
||||||
|
* |
||||||
|
* IDENTIFICATION |
||||||
|
* src/backend/utils/misc/pg_controldata.c |
||||||
|
*------------------------------------------------------------------------- |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "postgres.h" |
||||||
|
|
||||||
|
#include "funcapi.h" |
||||||
|
#include "miscadmin.h" |
||||||
|
#include "access/htup_details.h" |
||||||
|
#include "access/xlog_internal.h" |
||||||
|
#include "catalog/pg_control.h" |
||||||
|
#include "catalog/pg_type.h" |
||||||
|
#include "common/controldata_utils.h" |
||||||
|
#include "utils/builtins.h" |
||||||
|
#include "utils/pg_lsn.h" |
||||||
|
#include "utils/timestamp.h" |
||||||
|
|
||||||
|
Datum |
||||||
|
pg_control_system(PG_FUNCTION_ARGS) |
||||||
|
{ |
||||||
|
Datum values[4]; |
||||||
|
bool nulls[4]; |
||||||
|
TupleDesc tupdesc; |
||||||
|
HeapTuple htup; |
||||||
|
ControlFileData *ControlFile; |
||||||
|
|
||||||
|
/*
|
||||||
|
* Construct a tuple descriptor for the result row. This must match this |
||||||
|
* function's pg_proc entry! |
||||||
|
*/ |
||||||
|
tupdesc = CreateTemplateTupleDesc(4, false); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_control_version", |
||||||
|
INT4OID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "catalog_version_no", |
||||||
|
INT4OID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 3, "system_identifier", |
||||||
|
INT8OID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 4, "pg_control_last_modified", |
||||||
|
TIMESTAMPTZOID, -1, 0); |
||||||
|
tupdesc = BlessTupleDesc(tupdesc); |
||||||
|
|
||||||
|
/* read the control file */ |
||||||
|
ControlFile = get_controlfile(DataDir, NULL); |
||||||
|
|
||||||
|
values[0] = Int32GetDatum(ControlFile->pg_control_version); |
||||||
|
nulls[0] = false; |
||||||
|
|
||||||
|
values[1] = Int32GetDatum(ControlFile->catalog_version_no); |
||||||
|
nulls[1] = false; |
||||||
|
|
||||||
|
values[2] = Int64GetDatum(ControlFile->system_identifier); |
||||||
|
nulls[2] = false; |
||||||
|
|
||||||
|
values[3] = TimestampTzGetDatum(time_t_to_timestamptz(ControlFile->time)); |
||||||
|
nulls[3] = false; |
||||||
|
|
||||||
|
htup = heap_form_tuple(tupdesc, values, nulls); |
||||||
|
|
||||||
|
PG_RETURN_DATUM(HeapTupleGetDatum(htup)); |
||||||
|
} |
||||||
|
|
||||||
|
Datum |
||||||
|
pg_control_checkpoint(PG_FUNCTION_ARGS) |
||||||
|
{ |
||||||
|
Datum values[19]; |
||||||
|
bool nulls[19]; |
||||||
|
TupleDesc tupdesc; |
||||||
|
HeapTuple htup; |
||||||
|
ControlFileData *ControlFile; |
||||||
|
XLogSegNo segno; |
||||||
|
char xlogfilename[MAXFNAMELEN]; |
||||||
|
|
||||||
|
/*
|
||||||
|
* Construct a tuple descriptor for the result row. This must match this |
||||||
|
* function's pg_proc entry! |
||||||
|
*/ |
||||||
|
tupdesc = CreateTemplateTupleDesc(19, false); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "checkpoint_location", |
||||||
|
LSNOID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "prior_location", |
||||||
|
LSNOID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 3, "redo_location", |
||||||
|
LSNOID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 4, "redo_wal_file", |
||||||
|
TEXTOID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 5, "timeline_id", |
||||||
|
INT4OID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 6, "prev_timeline_id", |
||||||
|
INT4OID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 7, "full_page_writes", |
||||||
|
BOOLOID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 8, "next_xid", |
||||||
|
TEXTOID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 9, "next_oid", |
||||||
|
OIDOID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 10, "next_multixact_id", |
||||||
|
XIDOID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 11, "next_multi_offset", |
||||||
|
XIDOID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 12, "oldest_xid", |
||||||
|
XIDOID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 13, "oldest_xid_dbid", |
||||||
|
OIDOID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 14, "oldest_active_xid", |
||||||
|
XIDOID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 15, "oldest_multi_xid", |
||||||
|
XIDOID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 16, "oldest_multi_dbid", |
||||||
|
OIDOID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 17, "oldest_commit_ts_xid", |
||||||
|
XIDOID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 18, "newest_commit_ts_xid", |
||||||
|
XIDOID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 19, "checkpoint_time", |
||||||
|
TIMESTAMPTZOID, -1, 0); |
||||||
|
tupdesc = BlessTupleDesc(tupdesc); |
||||||
|
|
||||||
|
/* Read the control file. */ |
||||||
|
ControlFile = get_controlfile(DataDir, NULL); |
||||||
|
|
||||||
|
/*
|
||||||
|
* Calculate name of the WAL file containing the latest checkpoint's REDO |
||||||
|
* start point. |
||||||
|
*/ |
||||||
|
XLByteToSeg(ControlFile->checkPointCopy.redo, segno); |
||||||
|
XLogFileName(xlogfilename, ControlFile->checkPointCopy.ThisTimeLineID, segno); |
||||||
|
|
||||||
|
/* Populate the values and null arrays */ |
||||||
|
values[0] = LSNGetDatum(ControlFile->checkPoint); |
||||||
|
nulls[0] = false; |
||||||
|
|
||||||
|
values[1] = LSNGetDatum(ControlFile->prevCheckPoint); |
||||||
|
nulls[1] = false; |
||||||
|
|
||||||
|
values[2] = LSNGetDatum(ControlFile->checkPointCopy.redo); |
||||||
|
nulls[2] = false; |
||||||
|
|
||||||
|
values[3] = CStringGetTextDatum(xlogfilename); |
||||||
|
nulls[3] = false; |
||||||
|
|
||||||
|
values[4] = Int32GetDatum(ControlFile->checkPointCopy.ThisTimeLineID); |
||||||
|
nulls[4] = false; |
||||||
|
|
||||||
|
values[5] = Int32GetDatum(ControlFile->checkPointCopy.PrevTimeLineID); |
||||||
|
nulls[5] = false; |
||||||
|
|
||||||
|
values[6] = BoolGetDatum(ControlFile->checkPointCopy.fullPageWrites); |
||||||
|
nulls[6] = false; |
||||||
|
|
||||||
|
values[7] = CStringGetTextDatum(psprintf("%u:%u", |
||||||
|
ControlFile->checkPointCopy.nextXidEpoch, |
||||||
|
ControlFile->checkPointCopy.nextXid)); |
||||||
|
nulls[7] = false; |
||||||
|
|
||||||
|
values[8] = ObjectIdGetDatum(ControlFile->checkPointCopy.nextOid); |
||||||
|
nulls[8] = false; |
||||||
|
|
||||||
|
values[9] = TransactionIdGetDatum(ControlFile->checkPointCopy.nextMulti); |
||||||
|
nulls[9] = false; |
||||||
|
|
||||||
|
values[10] = TransactionIdGetDatum(ControlFile->checkPointCopy.nextMultiOffset); |
||||||
|
nulls[10] = false; |
||||||
|
|
||||||
|
values[11] = TransactionIdGetDatum(ControlFile->checkPointCopy.oldestXid); |
||||||
|
nulls[11] = false; |
||||||
|
|
||||||
|
values[12] = ObjectIdGetDatum(ControlFile->checkPointCopy.oldestXidDB); |
||||||
|
nulls[12] = false; |
||||||
|
|
||||||
|
values[13] = TransactionIdGetDatum(ControlFile->checkPointCopy.oldestActiveXid); |
||||||
|
nulls[13] = false; |
||||||
|
|
||||||
|
values[14] = TransactionIdGetDatum(ControlFile->checkPointCopy.oldestMulti); |
||||||
|
nulls[14] = false; |
||||||
|
|
||||||
|
values[15] = ObjectIdGetDatum(ControlFile->checkPointCopy.oldestMultiDB); |
||||||
|
nulls[15] = false; |
||||||
|
|
||||||
|
values[16] = TransactionIdGetDatum(ControlFile->checkPointCopy.oldestCommitTsXid); |
||||||
|
nulls[16] = false; |
||||||
|
|
||||||
|
values[17] = TransactionIdGetDatum(ControlFile->checkPointCopy.newestCommitTsXid); |
||||||
|
nulls[17] = false; |
||||||
|
|
||||||
|
values[18] = TimestampTzGetDatum( |
||||||
|
time_t_to_timestamptz(ControlFile->checkPointCopy.time)); |
||||||
|
nulls[18] = false; |
||||||
|
|
||||||
|
htup = heap_form_tuple(tupdesc, values, nulls); |
||||||
|
|
||||||
|
PG_RETURN_DATUM(HeapTupleGetDatum(htup)); |
||||||
|
} |
||||||
|
|
||||||
|
Datum |
||||||
|
pg_control_recovery(PG_FUNCTION_ARGS) |
||||||
|
{ |
||||||
|
Datum values[5]; |
||||||
|
bool nulls[5]; |
||||||
|
TupleDesc tupdesc; |
||||||
|
HeapTuple htup; |
||||||
|
ControlFileData *ControlFile; |
||||||
|
|
||||||
|
/*
|
||||||
|
* Construct a tuple descriptor for the result row. This must match this |
||||||
|
* function's pg_proc entry! |
||||||
|
*/ |
||||||
|
tupdesc = CreateTemplateTupleDesc(5, false); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "min_recovery_end_location", |
||||||
|
LSNOID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "min_recovery_end_timeline", |
||||||
|
INT4OID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 3, "backup_start_location", |
||||||
|
LSNOID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 4, "backup_end_location", |
||||||
|
LSNOID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 5, "end_of_backup_record_required", |
||||||
|
BOOLOID, -1, 0); |
||||||
|
tupdesc = BlessTupleDesc(tupdesc); |
||||||
|
|
||||||
|
/* read the control file */ |
||||||
|
ControlFile = get_controlfile(DataDir, NULL); |
||||||
|
|
||||||
|
values[0] = LSNGetDatum(ControlFile->minRecoveryPoint); |
||||||
|
nulls[0] = false; |
||||||
|
|
||||||
|
values[1] = Int32GetDatum(ControlFile->minRecoveryPointTLI); |
||||||
|
nulls[1] = false; |
||||||
|
|
||||||
|
values[2] = LSNGetDatum(ControlFile->backupStartPoint); |
||||||
|
nulls[2] = false; |
||||||
|
|
||||||
|
values[3] = LSNGetDatum(ControlFile->backupEndPoint); |
||||||
|
nulls[3] = false; |
||||||
|
|
||||||
|
values[4] = BoolGetDatum(ControlFile->backupEndRequired); |
||||||
|
nulls[4] = false; |
||||||
|
|
||||||
|
htup = heap_form_tuple(tupdesc, values, nulls); |
||||||
|
|
||||||
|
PG_RETURN_DATUM(HeapTupleGetDatum(htup)); |
||||||
|
} |
||||||
|
|
||||||
|
Datum |
||||||
|
pg_control_init(PG_FUNCTION_ARGS) |
||||||
|
{ |
||||||
|
Datum values[13]; |
||||||
|
bool nulls[13]; |
||||||
|
TupleDesc tupdesc; |
||||||
|
HeapTuple htup; |
||||||
|
ControlFileData *ControlFile; |
||||||
|
|
||||||
|
/*
|
||||||
|
* Construct a tuple descriptor for the result row. This must match this |
||||||
|
* function's pg_proc entry! |
||||||
|
*/ |
||||||
|
tupdesc = CreateTemplateTupleDesc(13, false); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "max_data_alignment", |
||||||
|
INT4OID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "database_block_size", |
||||||
|
INT4OID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 3, "blocks_per_segment", |
||||||
|
INT4OID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 4, "wal_block_size", |
||||||
|
INT4OID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 5, "bytes_per_wal_segment", |
||||||
|
INT4OID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 6, "max_identifier_length", |
||||||
|
INT4OID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 7, "max_index_columns", |
||||||
|
INT4OID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 8, "max_toast_chunk_size", |
||||||
|
INT4OID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 9, "large_object_chunk_size", |
||||||
|
INT4OID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 10, "bigint_timestamps", |
||||||
|
BOOLOID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 11, "float4_pass_by_value", |
||||||
|
BOOLOID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 12, "float8_pass_by_value", |
||||||
|
BOOLOID, -1, 0); |
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 13, "data_page_checksum_version", |
||||||
|
INT4OID, -1, 0); |
||||||
|
tupdesc = BlessTupleDesc(tupdesc); |
||||||
|
|
||||||
|
/* read the control file */ |
||||||
|
ControlFile = get_controlfile(DataDir, NULL); |
||||||
|
|
||||||
|
values[0] = Int32GetDatum(ControlFile->maxAlign); |
||||||
|
nulls[0] = false; |
||||||
|
|
||||||
|
values[1] = Int32GetDatum(ControlFile->blcksz); |
||||||
|
nulls[1] = false; |
||||||
|
|
||||||
|
values[2] = Int32GetDatum(ControlFile->relseg_size); |
||||||
|
nulls[2] = false; |
||||||
|
|
||||||
|
values[3] = Int32GetDatum(ControlFile->xlog_blcksz); |
||||||
|
nulls[3] = false; |
||||||
|
|
||||||
|
values[4] = Int32GetDatum(ControlFile->xlog_seg_size); |
||||||
|
nulls[4] = false; |
||||||
|
|
||||||
|
values[5] = Int32GetDatum(ControlFile->nameDataLen); |
||||||
|
nulls[5] = false; |
||||||
|
|
||||||
|
values[6] = Int32GetDatum(ControlFile->indexMaxKeys); |
||||||
|
nulls[6] = false; |
||||||
|
|
||||||
|
values[7] = Int32GetDatum(ControlFile->toast_max_chunk_size); |
||||||
|
nulls[7] = false; |
||||||
|
|
||||||
|
values[8] = Int32GetDatum(ControlFile->loblksize); |
||||||
|
nulls[8] = false; |
||||||
|
|
||||||
|
values[9] = BoolGetDatum(ControlFile->enableIntTimes); |
||||||
|
nulls[9] = false; |
||||||
|
|
||||||
|
values[10] = BoolGetDatum(ControlFile->float4ByVal); |
||||||
|
nulls[10] = false; |
||||||
|
|
||||||
|
values[11] = BoolGetDatum(ControlFile->float8ByVal); |
||||||
|
nulls[11] = false; |
||||||
|
|
||||||
|
values[12] = Int32GetDatum(ControlFile->data_checksum_version); |
||||||
|
nulls[12] = false; |
||||||
|
|
||||||
|
htup = heap_form_tuple(tupdesc, values, nulls); |
||||||
|
|
||||||
|
PG_RETURN_DATUM(HeapTupleGetDatum(htup)); |
||||||
|
} |
||||||
@ -0,0 +1,100 @@ |
|||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
* |
||||||
|
* controldata_utils.c |
||||||
|
* Common code for control data file output. |
||||||
|
* |
||||||
|
* |
||||||
|
* Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group |
||||||
|
* Portions Copyright (c) 1994, Regents of the University of California |
||||||
|
* |
||||||
|
* |
||||||
|
* IDENTIFICATION |
||||||
|
* src/common/controldata_utils.c |
||||||
|
* |
||||||
|
*------------------------------------------------------------------------- |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef FRONTEND |
||||||
|
#include "postgres.h" |
||||||
|
#else |
||||||
|
#include "postgres_fe.h" |
||||||
|
#endif |
||||||
|
|
||||||
|
#include <unistd.h> |
||||||
|
#include <sys/stat.h> |
||||||
|
#include <fcntl.h> |
||||||
|
|
||||||
|
#include "catalog/pg_control.h" |
||||||
|
#include "common/controldata_utils.h" |
||||||
|
#include "port/pg_crc32c.h" |
||||||
|
|
||||||
|
#ifndef FRONTEND |
||||||
|
/* NOTE: caller must provide gettext call around the format string */ |
||||||
|
#define log_error(...) \ |
||||||
|
elog(ERROR, __VA_ARGS__) |
||||||
|
#else |
||||||
|
#define log_error(...) \ |
||||||
|
do { \
|
||||||
|
char *buf = psprintf(__VA_ARGS__); \
|
||||||
|
fprintf(stderr, "%s: %s\n", progname, buf); \
|
||||||
|
exit(2); \
|
||||||
|
} while (0) |
||||||
|
#endif |
||||||
|
|
||||||
|
/*
|
||||||
|
* get_controlfile(char *DataDir, const char *progname) |
||||||
|
* |
||||||
|
* Get controlfile values. The caller is responsible |
||||||
|
* for pfreeing the result. |
||||||
|
*/ |
||||||
|
ControlFileData * |
||||||
|
get_controlfile(char *DataDir, const char *progname) |
||||||
|
{ |
||||||
|
ControlFileData *ControlFile; |
||||||
|
int fd; |
||||||
|
char ControlFilePath[MAXPGPATH]; |
||||||
|
pg_crc32c crc; |
||||||
|
|
||||||
|
ControlFile = palloc(sizeof(ControlFileData)); |
||||||
|
snprintf(ControlFilePath, MAXPGPATH, "%s/global/pg_control", DataDir); |
||||||
|
|
||||||
|
if ((fd = open(ControlFilePath, O_RDONLY | PG_BINARY, 0)) == -1) |
||||||
|
log_error(_("could not open file \"%s\" for reading: %s"), |
||||||
|
ControlFilePath, strerror(errno)); |
||||||
|
|
||||||
|
if (read(fd, ControlFile, sizeof(ControlFileData)) != sizeof(ControlFileData)) |
||||||
|
log_error(_("could not read file \"%s\": %s"), |
||||||
|
ControlFilePath, strerror(errno)); |
||||||
|
|
||||||
|
close(fd); |
||||||
|
|
||||||
|
/* Check the CRC. */ |
||||||
|
INIT_CRC32C(crc); |
||||||
|
COMP_CRC32C(crc, |
||||||
|
(char *) ControlFile, |
||||||
|
offsetof(ControlFileData, crc)); |
||||||
|
FIN_CRC32C(crc); |
||||||
|
|
||||||
|
if (!EQ_CRC32C(crc, ControlFile->crc)) |
||||||
|
#ifndef FRONTEND |
||||||
|
elog(ERROR, _("calculated CRC checksum does not match value stored in file")); |
||||||
|
#else |
||||||
|
printf(_("WARNING: Calculated CRC checksum does not match value stored in file.\n" |
||||||
|
"Either the file is corrupt, or it has a different layout than this program\n" |
||||||
|
"is expecting. The results below are untrustworthy.\n\n")); |
||||||
|
#endif |
||||||
|
|
||||||
|
/* Make sure the control file is valid byte order. */ |
||||||
|
if (ControlFile->pg_control_version % 65536 == 0 && |
||||||
|
ControlFile->pg_control_version / 65536 != 0) |
||||||
|
#ifndef FRONTEND |
||||||
|
elog(ERROR, _("byte ordering mismatch")); |
||||||
|
#else |
||||||
|
printf(_("WARNING: possible byte ordering mismatch\n" |
||||||
|
"The byte ordering used to store the pg_control file might not match the one\n" |
||||||
|
"used by this program. In that case the results below would be incorrect, and\n" |
||||||
|
"the PostgreSQL installation would be incompatible with this data directory.\n")); |
||||||
|
#endif |
||||||
|
|
||||||
|
return ControlFile; |
||||||
|
} |
||||||
@ -0,0 +1,15 @@ |
|||||||
|
/*
|
||||||
|
* controldata_utils.h |
||||||
|
* Common code for pg_controldata output |
||||||
|
* |
||||||
|
* Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group |
||||||
|
* Portions Copyright (c) 1994, Regents of the University of California |
||||||
|
* |
||||||
|
* src/include/common/controldata_utils.h |
||||||
|
*/ |
||||||
|
#ifndef COMMON_CONTROLDATA_UTILS_H |
||||||
|
#define COMMON_CONTROLDATA_UTILS_H |
||||||
|
|
||||||
|
extern ControlFileData *get_controlfile(char *DataDir, const char *progname); |
||||||
|
|
||||||
|
#endif /* COMMON_CONTROLDATA_UTILS_H */ |
||||||
Loading…
Reference in new issue