/* -------------------------------------------------------------------------
 *
 * pg_controldata.cpp
 *
 * Routines to expose the contents of the control data file via
 * a set of SQL functions.
 *
 * Portions Copyright (c) 1996-2019, 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 "access/transam.h"
#include "access/xlog_internal.h"
#include "access/xlog.h"
#include "catalog/pg_control.h"
#include "catalog/pg_type.h"
#include "utils/elog.h"
#include "catalog/pg_control.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/pg_lsn.h"
#include "utils/timestamp.h"
#include "storage/file/fio_device.h"

ControlFileData* GetControlfile(const char *dataDir, bool *crc_ok_p);

static inline char* GetDataDir()
{
    if (t_thrd.proc_cxt.DataDir != NULL && strlen(t_thrd.proc_cxt.DataDir) > 0) {
        return t_thrd.proc_cxt.DataDir;
    } else {
        char *dataDir = gs_getenv_r("GAUSSHOME");
        if (dataDir == NULL) {
            ereport(ERROR, (errmsg("Get GAUSSHOME failed, please check.")));
            return NULL;
        }
        char realDir[PATH_MAX + 1] = {'\0'};
        if (realpath(dataDir, realDir) == NULL) {
            ereport(ERROR,
                (errmodule(MOD_EXECUTOR), errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION),
                errmsg("Failed to obtain environment value $GAUSSHOME!"),
                errdetail("N/A"),
                errcause("Incorrect environment value."),
                erraction("Please refer to backend log for more details.")));
        }
        dataDir = NULL;
        check_backend_env(realDir);
        return pstrdup(realDir);
    }
}

const int CONTROL_SYSTEM_ATTRNUM = 4;
Datum pg_control_system(PG_FUNCTION_ARGS)
{
    Datum values[CONTROL_SYSTEM_ATTRNUM];
    bool nulls[CONTROL_SYSTEM_ATTRNUM];
    TupleDesc tupdesc;
    HeapTuple htup;
    ControlFileData *controlFile;
    bool crcOk;
    int i = 0;

    /*
     * Construct a tuple descriptor for the result row.  This must match this
     * function's pg_proc entry!
     */
    tupdesc = CreateTemplateTupleDesc(CONTROL_SYSTEM_ATTRNUM, false);
    TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "pg_control_version", INT4OID, -1, 0);
    TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "catalog_version_no", INT4OID, -1, 0);
    TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "system_identifier", XIDOID, -1, 0);
    TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "pg_control_last_modified", TIMESTAMPTZOID, -1, 0);
    tupdesc = BlessTupleDesc(tupdesc);

    /* read the control file */
    controlFile = GetControlfile(GetDataDir(), &crcOk);
    if (!crcOk) {
        ereport(ERROR,
                (errmsg("calculated CRC checksum does not match value stored in file")));
    }

    i = 0;
    values[i] = Int32GetDatum(controlFile->pg_control_version);
    nulls[i] = false;
    i++;

    values[i] = Int32GetDatum(controlFile->catalog_version_no);
    nulls[i] = false;
    i++;

    values[i] = Int64GetDatum(controlFile->system_identifier);
    nulls[i] = false;
    i++;

    values[i] = TimestampTzGetDatum(time_t_to_timestamptz(controlFile->time));
    nulls[i] = false;

    htup = heap_form_tuple(tupdesc, values, nulls);

    PG_RETURN_DATUM(HeapTupleGetDatum(htup));
}

static void OpenControlFileForDSS(char *controlFilePath, ControlFileData *controlFile, size_t bufferCount,
                                  bool *crc_ok_p)
{
    pg_crc32c crc;
    errno_t rc = EOK;
    int fd = -1;
    bool isBakeUpFile = false;
    char *fileName = controlFilePath;
    int read_size = 0;
    int len = 0;
    LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
    fd = BasicOpenFile(fileName, O_RDWR | PG_BINARY, S_IRUSR | S_IWUSR);
    if (fd < 0) {
        LWLockRelease(ControlFileLock);
        ereport(ERROR,
                (errcode_for_file_access(), errmsg("could not open control file %s : %s", fileName, TRANSLATE_ERRNO)));
    }

    len = sizeof(ControlFileData);
    read_size = (int)BUFFERALIGN(len);
    char buffer[read_size] __attribute__((__aligned__(ALIGNOF_BUFFER)));
    while (true) {
        if (read(fd, buffer, read_size) != read_size) {
            LWLockRelease(ControlFileLock);
            ereport(ERROR, (errcode_for_file_access(), errmsg("could not read from control file %s : %s", fileName,
                            TRANSLATE_ERRNO)));
        }

        rc = memcpy_s(controlFile, (size_t)len, buffer, (size_t)len);
        securec_check(rc, "", "");
        if (close(fd) < 0) {
            LWLockRelease(ControlFileLock);
            ereport(ERROR, (errcode_for_file_access(), errmsg("could not close control file %s : %s", fileName,
                            TRANSLATE_ERRNO)));
        }

        /* Now check the CRC. */
        INIT_CRC32C(crc);
        COMP_CRC32C(crc, (char *)controlFile, offsetof(ControlFileData, crc));
        FIN_CRC32C(crc);
        *crc_ok_p = EQ_CRC32C(crc, controlFile->crc);
        if (!*crc_ok_p) {
            LWLockRelease(ControlFileLock);
            if (!isBakeUpFile) {
                ereport(ERROR, (errmsg("control file %s contains incorrect checksum, try backup file", fileName)));
                fileName = XLOG_CONTROL_FILE_BAK;
                isBakeUpFile = true;
            } else {
                ereport(ERROR, (errmsg("incorrect checksum in control file")));
                break;
            }
        } else {
            break;
        }
    }
    LWLockRelease(ControlFileLock);
}

static int OpenControlFile(const char* controlFilePath)
{
    int fd;

    if ((fd = OpenTransientFile((char*)controlFilePath,
        O_RDONLY | PG_BINARY, S_IRUSR | S_IWUSR)) == -1) {
        ereport(ERROR,
                (errcode_for_file_access(),
                 errmsg("could not open file \"%s\" for reading: %m",  controlFilePath)));
    }

    return fd;
}

const int ORDER_BYTE = 65536;
/*
 * get_controlfile()
 *
 * Get controlfile values.  The result is returned as a palloc'd copy of the
 * control file data.
 *
 * crc_ok_p can be used by the caller to see whether the CRC of the control
 * file data is correct.
 */
ControlFileData* GetControlfile(const char *dataDir, bool *crc_ok_p)
{
    ControlFileData *controlFile;
    int fd;
    char controlFilePath[MAXPGPATH];
    pg_crc32c crc;
    int r;

    AssertArg(crc_ok_p);

    errno_t rc = 0;
    controlFile = (ControlFileData *)palloc(sizeof(ControlFileData));
    if (ENABLE_DSS) {
        rc = snprintf_s(controlFilePath, MAXPGPATH, MAXPGPATH - 1, "%s", XLOG_CONTROL_FILE);
        securec_check_ss_c(rc, "\0", "\0");
        OpenControlFileForDSS(controlFilePath, controlFile, sizeof(ControlFileData), crc_ok_p);
    } else {
        rc = snprintf_s(controlFilePath, MAXPGPATH, MAXPGPATH - 1, "%s/%s", dataDir, XLOG_CONTROL_FILE);
        securec_check_ss_c(rc, "\0", "\0");
        fd = OpenControlFile(controlFilePath);
        r = read(fd, controlFile, sizeof(ControlFileData));
        if (r != sizeof(ControlFileData)) {
            if (r < 0) {
                ereport(ERROR, (errcode_for_file_access(), errmsg("could not read file \"%s\": %m", controlFilePath)));
            } else {
                ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("could not read file \"%s\": read %d of %zu",
                                                                        controlFilePath, r, sizeof(ControlFileData))));
            }
        }

        if (CloseTransientFile(fd)) {
            ereport(ERROR, (errcode_for_file_access(), errmsg("could not close file \"%s\": %m", controlFilePath)));
        }

        /* Check the CRC. */
        INIT_CRC32C(crc);
        COMP_CRC32C(crc, (char *)controlFile, offsetof(ControlFileData, crc));
        FIN_CRC32C(crc);

        *crc_ok_p = EQ_CRC32C(crc, controlFile->crc);
    }

    /* Make sure the control file is valid byte order. */
    if (controlFile->pg_control_version % ORDER_BYTE == 0 &&
        controlFile->pg_control_version / ORDER_BYTE != 0) {
        elog(ERROR, _("byte ordering mismatch"));
    }

    return controlFile;
}

static void InitCheckpointTupledesc(TupleDesc tupdesc)
{
    int i = 0;

    TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "checkpoint_lsn", INT8OID, -1, 0);
    TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "redo_lsn", INT8OID, -1, 0);
    TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "redo_wal_file", TEXTOID, -1, 0);
    TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "timeline_id", INT4OID, -1, 0);
    TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "full_page_writes", BOOLOID, -1, 0);
    TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "next_oid", OIDOID, -1, 0);
    TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "next_multixact_id", XIDOID, -1, 0);
    TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "next_multi_offset", XIDOID, -1, 0);
    TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "oldest_xid", XIDOID, -1, 0);
    TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "oldest_xid_dbid", OIDOID, -1, 0);
    TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "oldest_active_xid", XIDOID, -1, 0);
    TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "checkpoint_time", TIMESTAMPTZOID, -1, 0);
}

const int CONTROL_CHECKPOINT_ATTRNUM = 12;
Datum pg_control_checkpoint(PG_FUNCTION_ARGS)
{
    Datum values[CONTROL_CHECKPOINT_ATTRNUM];
    bool nulls[CONTROL_CHECKPOINT_ATTRNUM];
    TupleDesc tupdesc;
    HeapTuple htup;
    ControlFileData *controlFile;
    XLogSegNo segno;
    char xlogfilename[MAXFNAMELEN];
    bool crcOk;
    int i = 0;

    /*
     * Construct a tuple descriptor for the result row.  This must match this
     * function's pg_proc entry!
     */
    tupdesc = CreateTemplateTupleDesc(CONTROL_CHECKPOINT_ATTRNUM, false);
    InitCheckpointTupledesc(tupdesc);
    tupdesc = BlessTupleDesc(tupdesc);

    /* Read the control file. */
    controlFile = GetControlfile(GetDataDir(), &crcOk);
    if (!crcOk) {
        ereport(ERROR,
                (errmsg("calculated CRC checksum does not match value stored in file")));
    }

    /*
     * Calculate name of the WAL file containing the latest checkpoint's REDO
     * start point.
     */
    XLByteToSeg(controlFile->checkPointCopy.redo, segno);

    XLogFileName(xlogfilename, MAXFNAMELEN, controlFile->checkPointCopy.ThisTimeLineID, segno);

    i = 0;
    /* Populate the values and null arrays */
    values[i] = LSNGetDatum(controlFile->checkPoint);
    nulls[i] = false;
    i++;

    values[i] = LSNGetDatum(controlFile->checkPointCopy.redo);
    nulls[i] = false;
    i++;

    values[i] = CStringGetTextDatum(xlogfilename);
    nulls[i] = false;
    i++;

    values[i] = Int32GetDatum(controlFile->checkPointCopy.ThisTimeLineID);
    nulls[i] = false;
    i++;

    values[i] = BoolGetDatum(controlFile->checkPointCopy.fullPageWrites);
    nulls[i] = false;
    i++;

    values[i] = ObjectIdGetDatum(controlFile->checkPointCopy.nextOid);
    nulls[i] = false;
    i++;

    values[i] = TransactionIdGetDatum(controlFile->checkPointCopy.nextMulti);
    nulls[i] = false;
    i++;

    values[i] = TransactionIdGetDatum(controlFile->checkPointCopy.nextMultiOffset);
    nulls[i] = false;
    i++;

    values[i] = TransactionIdGetDatum(controlFile->checkPointCopy.oldestXid);
    nulls[i] = false;
    i++;

    values[i] = ObjectIdGetDatum(controlFile->checkPointCopy.oldestXidDB);
    nulls[i] = false;
    i++;

    values[i] = TransactionIdGetDatum(controlFile->checkPointCopy.oldestActiveXid);
    nulls[i] = false;
    i++;

    values[i] = TimestampTzGetDatum(time_t_to_timestamptz(controlFile->checkPointCopy.time));
    nulls[i] = false;

    htup = heap_form_tuple(tupdesc, values, nulls);

    PG_RETURN_DATUM(HeapTupleGetDatum(htup));
}