forked from huawei/openGauss-server
715 lines
25 KiB
C++
715 lines
25 KiB
C++
/* -------------------------------------------------------------------------
|
|
*
|
|
* genfile.c
|
|
* Functions for direct access to files
|
|
*
|
|
*
|
|
* Copyright (c) 2004-2012, PostgreSQL Global Development Group
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/utils/adt/genfile.c
|
|
*
|
|
* -------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
#include "knl/knl_variable.h"
|
|
|
|
#include <sys/file.h>
|
|
#include <sys/stat.h>
|
|
#include <dirent.h>
|
|
|
|
#include "catalog/pg_authid.h"
|
|
#include "catalog/pg_type.h"
|
|
#include "funcapi.h"
|
|
#include "mb/pg_wchar.h"
|
|
#include "miscadmin.h"
|
|
#include "postmaster/alarmchecker.h"
|
|
#include "postmaster/syslogger.h"
|
|
#include "storage/smgr/fd.h"
|
|
#include "storage/checksum.h"
|
|
#include "replication/basebackup.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/memutils.h"
|
|
#include "utils/timestamp.h"
|
|
|
|
typedef struct {
|
|
char* location;
|
|
DIR* dirdesc;
|
|
} directory_fctx;
|
|
|
|
typedef struct stated_file {
|
|
char* path;
|
|
char* filename;
|
|
stated_file* next;
|
|
} stated_file;
|
|
|
|
typedef struct StatFile_State {
|
|
stated_file* cur_file;
|
|
} StatFile_State;
|
|
|
|
/*
|
|
* Convert a "text" filename argument to C string, and check it's allowable.
|
|
*
|
|
* Filename may be absolute or relative to the t_thrd.proc_cxt.DataDir, but we only allow
|
|
* absolute paths that match t_thrd.proc_cxt.DataDir or Log_directory.
|
|
*/
|
|
static char* convert_and_check_filename(text* arg)
|
|
{
|
|
char* filename = NULL;
|
|
|
|
filename = text_to_cstring(arg);
|
|
canonicalize_path(filename); /* filename can change length here */
|
|
|
|
if (is_absolute_path(filename)) {
|
|
/* Disallow '/a/b/data/..' */
|
|
if (path_contains_parent_reference(filename))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
(errmsg("reference to parent directory (\"..\") not allowed"))));
|
|
|
|
/*
|
|
* Allow absolute paths if within t_thrd.proc_cxt.DataDir or Log_directory, even
|
|
* though Log_directory might be outside t_thrd.proc_cxt.DataDir.
|
|
*/
|
|
if (!path_is_prefix_of_path(t_thrd.proc_cxt.DataDir, filename) &&
|
|
(!is_absolute_path(u_sess->attr.attr_common.Log_directory) ||
|
|
!path_is_prefix_of_path(u_sess->attr.attr_common.Log_directory, filename)))
|
|
ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("absolute path not allowed"))));
|
|
} else if (!path_is_relative_and_below_cwd(filename))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("path must be in or below the current directory"))));
|
|
|
|
return filename;
|
|
}
|
|
|
|
/*
|
|
* Read a section of a file, returning it as bytea
|
|
*
|
|
* Caller is responsible for all permissions checking.
|
|
*
|
|
* We read the whole of the file when bytes_to_read is negative.
|
|
*/
|
|
bytea* read_binary_file(const char* filename, int64 seek_offset, int64 bytes_to_read, bool missing_ok, bool need_check)
|
|
{
|
|
bytea* buf = NULL;
|
|
size_t nbytes;
|
|
FILE* file = NULL;
|
|
int64 offset = 0;
|
|
bool isNeedCheck = false;
|
|
int segNo = 0;
|
|
const int MAX_RETRY_LIMIT = 60;
|
|
int retryCnt = 0;
|
|
errno_t rc = 0;
|
|
UndoFileType undoFileType = UNDO_INVALID;
|
|
|
|
if (bytes_to_read < 0) {
|
|
if (seek_offset < 0)
|
|
bytes_to_read = -seek_offset;
|
|
else {
|
|
struct stat fst;
|
|
|
|
if (stat(filename, &fst) < 0) {
|
|
if (missing_ok && errno == ENOENT)
|
|
return NULL;
|
|
else
|
|
ereport(ERROR, (errcode_for_file_access(), errmsg("could not stat file \"%s\": %m", filename)));
|
|
}
|
|
|
|
bytes_to_read = fst.st_size - seek_offset;
|
|
}
|
|
}
|
|
|
|
/* not sure why anyone thought that int64 length was a good idea */
|
|
if ((unsigned int)(bytes_to_read) > (MaxAllocSize - VARHDRSZ))
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("requested length too large")));
|
|
|
|
if ((file = AllocateFile(filename, PG_BINARY_R)) == NULL) {
|
|
if (missing_ok && errno == ENOENT)
|
|
return NULL;
|
|
else
|
|
ereport(ERROR, (errcode_for_file_access(), errmsg("could not open file \"%s\" for reading: %m", filename)));
|
|
}
|
|
|
|
isNeedCheck = is_row_data_file(filename, &segNo, &undoFileType);
|
|
ereport(DEBUG1, (errmsg("read_binary_file, filename is %s, isNeedCheck is %d", filename, isNeedCheck)));
|
|
|
|
buf = (bytea*)palloc((Size)bytes_to_read + VARHDRSZ);
|
|
|
|
recheck:
|
|
if (fseeko(file, (off_t)seek_offset, (seek_offset >= 0) ? SEEK_SET : SEEK_END) != 0)
|
|
ereport(ERROR, (errcode_for_file_access(), errmsg("could not seek in file \"%s\": %m", filename)));
|
|
|
|
nbytes = fread(VARDATA(buf), 1, (size_t)bytes_to_read, file);
|
|
if (nbytes != (size_t) bytes_to_read) {
|
|
if (ferror(file))
|
|
ereport(ERROR,
|
|
(errcode_for_file_access(),
|
|
errmsg("could not read file \"%s\": %m", filename)));
|
|
}
|
|
|
|
if (ENABLE_INCRE_CKPT && isNeedCheck && need_check) {
|
|
uint32 check_loc = 0;
|
|
BlockNumber blkno = 0;
|
|
uint16 checksum = 0;
|
|
PageHeader phdr = NULL;
|
|
uint32 segSize;
|
|
GET_SEG_SIZE(undoFileType, segSize);
|
|
|
|
if (seek_offset < 0) {
|
|
struct stat fst;
|
|
|
|
if (stat(filename, &fst) < 0) {
|
|
if (missing_ok && errno == ENOENT) {
|
|
return NULL;
|
|
} else {
|
|
ereport(ERROR, (errcode_for_file_access(), errmsg("could not stat file \"%s\": %m", filename)));
|
|
}
|
|
}
|
|
|
|
offset = fst.st_size + seek_offset;
|
|
} else {
|
|
offset = seek_offset;
|
|
}
|
|
|
|
/*offset and nbytes must be integer multiple of BLCKSZ.*/
|
|
if (offset % BLCKSZ != 0 || nbytes % BLCKSZ != 0) {
|
|
ereport(ERROR,
|
|
(errcode_for_file_access(),
|
|
errmsg("file length cannot be divisibed by 8k: file %s, offset %ld, nbytes %lu",
|
|
filename, offset, nbytes)));
|
|
}
|
|
|
|
for (check_loc = 0; check_loc < nbytes; check_loc += BLCKSZ) {
|
|
blkno = offset / BLCKSZ + check_loc / BLCKSZ + (segNo * segSize);
|
|
phdr = PageHeader((char*)VARDATA(buf) + check_loc);
|
|
if (!CheckPageZeroCases(phdr)) {
|
|
continue;
|
|
}
|
|
checksum = pg_checksum_page((char*)VARDATA(buf) + check_loc, blkno);
|
|
|
|
if (phdr->pd_checksum != checksum) {
|
|
rc = memset_s(buf, (Size)bytes_to_read + VARHDRSZ, 0, (Size)bytes_to_read + VARHDRSZ);
|
|
securec_check_c(rc, "", "");
|
|
if (retryCnt < MAX_RETRY_LIMIT) {
|
|
retryCnt++;
|
|
pg_usleep(1000000);
|
|
goto recheck;
|
|
} else {
|
|
ereport(ERROR,
|
|
(errcode_for_file_access(),
|
|
errmsg("cheksum failed in file \"%s\"(computed: %d, recorded: %d)",
|
|
filename, checksum, phdr->pd_checksum)));
|
|
}
|
|
}
|
|
retryCnt = 0;
|
|
}
|
|
}
|
|
|
|
SET_VARSIZE(buf, nbytes + VARHDRSZ);
|
|
|
|
FreeFile(file);
|
|
|
|
return buf;
|
|
}
|
|
|
|
/*
|
|
* Similar to read_binary_file, but we verify that the contents are valid
|
|
* in the database encoding.
|
|
*/
|
|
static text* read_text_file(const char* filename, int64 seek_offset, int64 bytes_to_read)
|
|
{
|
|
bytea* buf = NULL;
|
|
|
|
buf = read_binary_file(filename, seek_offset, bytes_to_read, false);
|
|
|
|
/* Make sure the input is valid */
|
|
pg_verifymbstr(VARDATA(buf), VARSIZE(buf) - VARHDRSZ, false);
|
|
|
|
/* OK, we can cast it to text safely */
|
|
return (text*)buf;
|
|
}
|
|
|
|
/*
|
|
* Read a section of a file, returning it as text
|
|
*/
|
|
Datum pg_read_file(PG_FUNCTION_ARGS)
|
|
{
|
|
text* filename_t = PG_GETARG_TEXT_P(0);
|
|
int64 seek_offset = PG_GETARG_INT64(1);
|
|
int64 bytes_to_read = PG_GETARG_INT64(2);
|
|
char* filename = NULL;
|
|
|
|
if (GetUserId() != BOOTSTRAP_SUPERUSERID)
|
|
ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be initial account to read files"))));
|
|
|
|
filename = convert_and_check_filename(filename_t);
|
|
|
|
if (bytes_to_read < 0)
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("requested length cannot be negative")));
|
|
|
|
PG_RETURN_TEXT_P(read_text_file(filename, seek_offset, bytes_to_read));
|
|
}
|
|
|
|
/*
|
|
* Read the whole of a file, returning it as text
|
|
*/
|
|
Datum pg_read_file_all(PG_FUNCTION_ARGS)
|
|
{
|
|
text* filename_t = PG_GETARG_TEXT_P(0);
|
|
char* filename = NULL;
|
|
|
|
if (GetUserId() != BOOTSTRAP_SUPERUSERID)
|
|
ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be initial account to read files"))));
|
|
|
|
filename = convert_and_check_filename(filename_t);
|
|
|
|
PG_RETURN_TEXT_P(read_text_file(filename, 0, -1));
|
|
}
|
|
|
|
/*
|
|
* Read a section of a file, returning it as bytea
|
|
*/
|
|
Datum pg_read_binary_file(PG_FUNCTION_ARGS)
|
|
{
|
|
text* filename_t = PG_GETARG_TEXT_P(0);
|
|
int64 seek_offset = 0;
|
|
int64 bytes_to_read = -1;
|
|
bool missing_ok = false;
|
|
char* filename = NULL;
|
|
bytea* result = NULL;
|
|
|
|
if (GetUserId() != BOOTSTRAP_SUPERUSERID) {
|
|
ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be initial account to read files"))));
|
|
}
|
|
/* handle optional arguments */
|
|
if (PG_NARGS() >= 3) {
|
|
seek_offset = PG_GETARG_INT64(1);
|
|
bytes_to_read = PG_GETARG_INT64(2);
|
|
if (bytes_to_read < 0)
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("requested length cannot be negative")));
|
|
}
|
|
if (PG_NARGS() >= 4)
|
|
missing_ok = PG_GETARG_BOOL(3);
|
|
|
|
filename = convert_and_check_filename(filename_t);
|
|
result = read_binary_file(filename, seek_offset, bytes_to_read, missing_ok, true);
|
|
|
|
if (NULL != result)
|
|
PG_RETURN_BYTEA_P(result);
|
|
else
|
|
PG_RETURN_NULL();
|
|
}
|
|
|
|
/*
|
|
* Read the whole of a file, returning it as bytea
|
|
*/
|
|
Datum pg_read_binary_file_all(PG_FUNCTION_ARGS)
|
|
{
|
|
text* filename_t = PG_GETARG_TEXT_P(0);
|
|
char* filename = NULL;
|
|
|
|
if (GetUserId() != BOOTSTRAP_SUPERUSERID)
|
|
ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be initial account to read files"))));
|
|
|
|
filename = convert_and_check_filename(filename_t);
|
|
|
|
PG_RETURN_BYTEA_P(read_binary_file(filename, 0, -1, false));
|
|
}
|
|
struct CompressAddressItemState {
|
|
uint32 blkno;
|
|
int segmentNo;
|
|
ReadBlockChunksStruct rbStruct;
|
|
FILE *pcaFile;
|
|
};
|
|
|
|
static void ReadBinaryFileBlocksFirstCall(PG_FUNCTION_ARGS, int32 startBlockNum, int32 blockCount)
|
|
{
|
|
char* path = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
|
|
int segmentNo = 0;
|
|
UndoFileType undoFileType = UNDO_INVALID;
|
|
if (!is_row_data_file(path, &segmentNo, &undoFileType)) {
|
|
ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_RESOURCES), errmsg("%s is not a relation file.", path)));
|
|
}
|
|
/* create a function context for cross-call persistence */
|
|
FuncCallContext* fctx = SRF_FIRSTCALL_INIT();
|
|
|
|
/* switch to memory context appropriate for multiple function calls */
|
|
MemoryContext mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
|
|
|
|
/* initialize file scanning code */
|
|
CompressAddressItemState* itemState = (CompressAddressItemState*)palloc(sizeof(CompressAddressItemState));
|
|
|
|
/* save mmap to inter_call_data->pcMap */
|
|
char pcaFilePath[MAXPGPATH];
|
|
errno_t rc = snprintf_s(pcaFilePath, MAXPGPATH, MAXPGPATH - 1, PCA_SUFFIX, path);
|
|
securec_check_ss(rc, "\0", "\0");
|
|
FILE* pcaFile = AllocateFile((const char*)pcaFilePath, "rb");
|
|
if (pcaFile == NULL) {
|
|
ereport(ERROR, (errcode_for_file_access(), errmsg("could not open file \"%s\": %m", pcaFilePath)));
|
|
}
|
|
PageCompressHeader* map = pc_mmap(fileno(pcaFile), ReadChunkSize(pcaFile, pcaFilePath, MAXPGPATH), true);
|
|
if (map == MAP_FAILED) {
|
|
ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_RESOURCES), errmsg("Failed to mmap %s: %m", pcaFilePath)));
|
|
}
|
|
if ((BlockNumber)startBlockNum + (BlockNumber)blockCount > map->nblocks) {
|
|
auto blockNum = map->nblocks;
|
|
ReleaseMap(map, pcaFilePath);
|
|
ereport(ERROR,
|
|
(ERRCODE_INVALID_PARAMETER_VALUE,
|
|
errmsg("invalid blocknum \"%d\" and block count \"%d\", the max blocknum is \"%u\"",
|
|
startBlockNum,
|
|
blockCount,
|
|
blockNum)));
|
|
}
|
|
/* construct ReadBlockChunksStruct */
|
|
char* pcdFilePath = (char*)palloc0(MAXPGPATH);
|
|
rc = snprintf_s(pcdFilePath, MAXPGPATH, MAXPGPATH - 1, PCD_SUFFIX, path);
|
|
securec_check_ss(rc, "\0", "\0");
|
|
FILE* fp = AllocateFile(pcdFilePath, "rb");
|
|
if (fp == NULL) {
|
|
ereport(ERROR, (errcode_for_file_access(), errmsg("could not open file \"%s\": %m", pcdFilePath)));
|
|
}
|
|
char* pageBuffer = (char*)palloc(BLCKSZ);
|
|
itemState->pcaFile = pcaFile;
|
|
itemState->rbStruct.header = map;
|
|
itemState->rbStruct.pageBuffer = pageBuffer;
|
|
itemState->rbStruct.pageBufferLen = BLCKSZ;
|
|
itemState->rbStruct.fp = fp;
|
|
itemState->rbStruct.segmentNo = segmentNo;
|
|
itemState->rbStruct.fileName = pcdFilePath;
|
|
|
|
/*
|
|
* build tupdesc for result tuples. This must match this function's
|
|
* pg_proc entry!
|
|
*/
|
|
TupleDesc tupdesc = CreateTemplateTupleDesc(4, false, TAM_HEAP);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber)1, "path", TEXTOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber)2, "blocknum", INT4OID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber)3, "len", INT4OID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber)4, "data", BYTEAOID, -1, 0);
|
|
fctx->tuple_desc = BlessTupleDesc(tupdesc);
|
|
|
|
itemState->blkno = startBlockNum;
|
|
fctx->max_calls = blockCount;
|
|
fctx->user_fctx = itemState;
|
|
|
|
MemoryContextSwitchTo(mctx);
|
|
}
|
|
|
|
Datum pg_read_binary_file_blocks(PG_FUNCTION_ARGS)
|
|
{
|
|
int32 startBlockNum = PG_GETARG_INT32(1);
|
|
int32 blockCount = PG_GETARG_INT32(2);
|
|
|
|
if (startBlockNum < 0 || blockCount <= 0 || startBlockNum + blockCount > RELSEG_SIZE) {
|
|
ereport(ERROR, (ERRCODE_INVALID_PARAMETER_VALUE,
|
|
errmsg("invalid blocknum \"%d\" or block count \"%d\"", startBlockNum, blockCount)));
|
|
}
|
|
|
|
/* stuff done only on the first call of the function */
|
|
if (SRF_IS_FIRSTCALL()) {
|
|
ReadBinaryFileBlocksFirstCall(fcinfo, startBlockNum, blockCount);
|
|
}
|
|
|
|
/* stuff done on every call of the function */
|
|
FuncCallContext *fctx = SRF_PERCALL_SETUP();
|
|
CompressAddressItemState *itemState = (CompressAddressItemState *)fctx->user_fctx;
|
|
|
|
if (fctx->call_cntr < fctx->max_calls) {
|
|
bytea *buf = (bytea *)palloc(BLCKSZ + VARHDRSZ);
|
|
size_t len = ReadAllChunkOfBlock(VARDATA(buf), BLCKSZ, itemState->blkno, itemState->rbStruct);
|
|
SET_VARSIZE(buf, len + VARHDRSZ);
|
|
Datum values[4];
|
|
values[0] = PG_GETARG_DATUM(0);
|
|
values[1] = Int32GetDatum(itemState->blkno);
|
|
values[2] = Int32GetDatum(len);
|
|
values[3] = PointerGetDatum(buf);
|
|
|
|
/* Build and return the result tuple. */
|
|
bool nulls[4];
|
|
securec_check(memset_s(nulls, sizeof(nulls), 0, sizeof(nulls)), "\0", "\0");
|
|
HeapTuple tuple = heap_form_tuple(fctx->tuple_desc, (Datum*)values, (bool*)nulls);
|
|
Datum result = HeapTupleGetDatum(tuple);
|
|
itemState->blkno++;
|
|
SRF_RETURN_NEXT(fctx, result);
|
|
} else {
|
|
if (itemState->rbStruct.header != NULL) {
|
|
pc_munmap(itemState->rbStruct.header);
|
|
}
|
|
FreeFile(itemState->pcaFile);
|
|
FreeFile(itemState->rbStruct.fp);
|
|
SRF_RETURN_DONE(fctx);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* stat a file
|
|
*/
|
|
Datum pg_stat_file(PG_FUNCTION_ARGS)
|
|
{
|
|
text* filename_t = PG_GETARG_TEXT_P(0);
|
|
char* filename = NULL;
|
|
struct stat fst;
|
|
Datum values[6];
|
|
bool isnull[6];
|
|
HeapTuple tuple;
|
|
TupleDesc tupdesc;
|
|
errno_t rc;
|
|
|
|
if (GetUserId() != BOOTSTRAP_SUPERUSERID)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be initial account to get file information"))));
|
|
|
|
filename = convert_and_check_filename(filename_t);
|
|
if (stat(filename, &fst) < 0)
|
|
ereport(ERROR, (errcode_for_file_access(), errmsg("could not stat file \"%s\": %m", filename)));
|
|
|
|
/*
|
|
* This record type had better match the output parameters declared for me
|
|
* in pg_proc.h.
|
|
*/
|
|
tupdesc = CreateTemplateTupleDesc(6, false, TAM_HEAP);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber)1, "size", INT8OID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber)2, "access", TIMESTAMPTZOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber)3, "modification", TIMESTAMPTZOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber)4, "change", TIMESTAMPTZOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber)5, "creation", TIMESTAMPTZOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber)6, "isdir", BOOLOID, -1, 0);
|
|
BlessTupleDesc(tupdesc);
|
|
|
|
rc = memset_s(isnull, sizeof(isnull), 0, sizeof(isnull));
|
|
securec_check(rc, "\0", "\0");
|
|
|
|
values[0] = Int64GetDatum((int64)fst.st_size);
|
|
values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_atime));
|
|
values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_mtime));
|
|
/* Unix has file status change time, while Win32 has creation time */
|
|
#if !defined(WIN32) && !defined(__CYGWIN__)
|
|
values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
|
|
isnull[4] = true;
|
|
#else
|
|
isnull[3] = true;
|
|
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
|
|
#endif
|
|
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
|
|
|
|
tuple = heap_form_tuple(tupdesc, values, isnull);
|
|
|
|
pfree_ext(filename);
|
|
|
|
PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
|
|
}
|
|
|
|
/*
|
|
* List a directory (returns the filenames only)
|
|
*/
|
|
Datum pg_ls_dir(PG_FUNCTION_ARGS)
|
|
{
|
|
FuncCallContext* funcctx = NULL;
|
|
struct dirent* de = NULL;
|
|
directory_fctx* fctx = NULL;
|
|
|
|
if (GetUserId() != BOOTSTRAP_SUPERUSERID)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be initial account to get directory listings"))));
|
|
|
|
if (SRF_IS_FIRSTCALL()) {
|
|
MemoryContext oldcontext;
|
|
|
|
funcctx = SRF_FIRSTCALL_INIT();
|
|
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
|
|
|
|
fctx = (directory_fctx*)palloc(sizeof(directory_fctx));
|
|
fctx->location = convert_and_check_filename(PG_GETARG_TEXT_P(0));
|
|
|
|
fctx->dirdesc = AllocateDir(fctx->location);
|
|
|
|
if (NULL == fctx->dirdesc)
|
|
ereport(ERROR, (errcode_for_file_access(), errmsg("could not open directory \"%s\": %m", fctx->location)));
|
|
|
|
funcctx->user_fctx = fctx;
|
|
MemoryContextSwitchTo(oldcontext);
|
|
}
|
|
|
|
funcctx = SRF_PERCALL_SETUP();
|
|
fctx = (directory_fctx*)funcctx->user_fctx;
|
|
|
|
while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL) {
|
|
if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
|
|
continue;
|
|
|
|
SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(de->d_name));
|
|
}
|
|
|
|
FreeDir(fctx->dirdesc);
|
|
|
|
SRF_RETURN_DONE(funcctx);
|
|
}
|
|
|
|
/*
|
|
* List directories recursive(returns the filenames and paths)
|
|
*/
|
|
static stated_file* pg_ls_dir_recursive(const char* location, stated_file* sd_file)
|
|
{
|
|
struct dirent* de = NULL;
|
|
DIR* dirdesc = NULL;
|
|
errno_t rc = 0;
|
|
if (GetUserId() != BOOTSTRAP_SUPERUSERID)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be initial account to get directory listings"))));
|
|
|
|
dirdesc = AllocateDir(location);
|
|
|
|
while ((de = ReadDir(dirdesc, location)) != NULL) {
|
|
if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
|
|
continue;
|
|
|
|
int maxPathLen = strlen(location) + strlen(de->d_name) + 2;
|
|
sd_file->path = (char*)palloc0(maxPathLen);
|
|
|
|
int filenameLen = strlen(de->d_name) + 1;
|
|
sd_file->filename = (char*)palloc0(filenameLen);
|
|
|
|
char* tmp_dir = (char*)palloc0(maxPathLen);
|
|
|
|
if (*location != '.') {
|
|
rc = strcpy_s(sd_file->path, maxPathLen, location);
|
|
securec_check(rc, "\0", "\0");
|
|
rc = snprintf_s(tmp_dir, maxPathLen, maxPathLen - 1, "%s/", sd_file->path);
|
|
securec_check_ss(rc, "\0", "\0");
|
|
}
|
|
|
|
rc = strcpy_s(sd_file->filename, filenameLen, de->d_name);
|
|
securec_check(rc, "\0", "\0");
|
|
rc = strcat_s(tmp_dir, maxPathLen, sd_file->filename);
|
|
securec_check(rc, "\0", "\0");
|
|
|
|
struct stated_file* next_file = (stated_file*)palloc0(sizeof(stated_file));
|
|
|
|
sd_file->next = next_file;
|
|
sd_file = next_file;
|
|
if (isDirExist(tmp_dir)) {
|
|
ereport(DEBUG3, (errmsg("current path %s.", tmp_dir)));
|
|
sd_file = pg_ls_dir_recursive(tmp_dir, sd_file);
|
|
}
|
|
}
|
|
FreeDir(dirdesc);
|
|
dirdesc = NULL;
|
|
return sd_file;
|
|
}
|
|
|
|
/*
|
|
* stat a few file
|
|
*/
|
|
Datum pg_stat_file_recursive(PG_FUNCTION_ARGS)
|
|
{
|
|
struct stat fst;
|
|
struct stated_file* sd_file = NULL;
|
|
Datum values[4];
|
|
bool isnull[4];
|
|
HeapTuple tuple;
|
|
TupleDesc tupdesc;
|
|
errno_t rc = 0;
|
|
|
|
FuncCallContext* funcctx = NULL;
|
|
StatFile_State* status = NULL;
|
|
|
|
if (GetUserId() != BOOTSTRAP_SUPERUSERID)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be initial account to get file information"))));
|
|
|
|
if (SRF_IS_FIRSTCALL()) {
|
|
MemoryContext oldcontext;
|
|
|
|
funcctx = SRF_FIRSTCALL_INIT();
|
|
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
|
|
|
|
directory_fctx* fctx = (directory_fctx*)palloc(sizeof(directory_fctx));
|
|
fctx->location = convert_and_check_filename(PG_GETARG_TEXT_P(0));
|
|
fctx->dirdesc = AllocateDir(fctx->location);
|
|
|
|
sd_file = (stated_file*)palloc0(sizeof(stated_file));
|
|
status = (StatFile_State*)palloc0(sizeof(StatFile_State));
|
|
status->cur_file = sd_file;
|
|
funcctx->user_fctx = (void*)status;
|
|
|
|
/*
|
|
* This record type had better match the output parameters declared for me
|
|
* in pg_proc.h.
|
|
*/
|
|
tupdesc = CreateTemplateTupleDesc(4, false, TAM_HEAP);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber)1, "path", TEXTOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber)2, "filename", TEXTOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber)3, "size", INT8OID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber)4, "isdir", BOOLOID, -1, 0);
|
|
funcctx->tuple_desc = BlessTupleDesc(tupdesc);
|
|
|
|
(void)pg_ls_dir_recursive(fctx->location, sd_file);
|
|
|
|
if (NULL == fctx->dirdesc) {
|
|
ereport(ERROR, (errcode_for_file_access(), errmsg("could not open directory \"%s\": %m", fctx->location)));
|
|
}
|
|
|
|
MemoryContextSwitchTo(oldcontext);
|
|
}
|
|
|
|
funcctx = SRF_PERCALL_SETUP();
|
|
status = (StatFile_State*)funcctx->user_fctx;
|
|
|
|
while (status->cur_file != NULL) {
|
|
/*Only for the last cur_file, palloced but not assigned yet.*/
|
|
if (status->cur_file->path == NULL || status->cur_file->filename == NULL) {
|
|
status->cur_file = status->cur_file->next;
|
|
continue;
|
|
}
|
|
|
|
int pathLen = strlen(status->cur_file->path) + strlen(status->cur_file->filename) + 2;
|
|
int buf_used = 0;
|
|
if (*(status->cur_file->path) != '\0') {
|
|
rc = strcat_s(status->cur_file->path, pathLen, "/");
|
|
buf_used++;
|
|
securec_check(rc, "\0", "\0");
|
|
}
|
|
rc = strcat_s(status->cur_file->path, pathLen, status->cur_file->filename);
|
|
buf_used += strlen(status->cur_file->filename);
|
|
securec_check(rc, "\0", "\0");
|
|
|
|
if (*(status->cur_file->path) == '\0') {
|
|
status->cur_file = status->cur_file->next;
|
|
continue;
|
|
}
|
|
if (stat(status->cur_file->path, &fst) < 0) {
|
|
/* If the file is not exist, maybe it's deleted, we will skip it. */
|
|
if (errno == ENOENT) {
|
|
status->cur_file = status->cur_file->next;
|
|
continue;
|
|
} else
|
|
ereport(ERROR,
|
|
(errcode_for_file_access(), errmsg("could not stat file \"%s\": %m", status->cur_file->path)));
|
|
}
|
|
|
|
// skip big file
|
|
if (fst.st_size > MAX_TAR_MEMBER_FILELEN) {
|
|
ereport(WARNING,
|
|
(errcode_for_file_access(),
|
|
errmsg("archive member \"%s\" too large for tar format", status->cur_file->path)));
|
|
status->cur_file = status->cur_file->next;
|
|
continue;
|
|
}
|
|
|
|
rc = memset_s(isnull, sizeof(isnull), false, sizeof(isnull));
|
|
securec_check(rc, "\0", "\0");
|
|
values[0] = CStringGetTextDatum(status->cur_file->path);
|
|
values[1] = CStringGetTextDatum(status->cur_file->filename);
|
|
values[2] = Int64GetDatum((int64)fst.st_size);
|
|
values[3] = BoolGetDatum(S_ISDIR(fst.st_mode));
|
|
|
|
status->cur_file = status->cur_file->next;
|
|
tuple = heap_form_tuple(funcctx->tuple_desc, values, isnull);
|
|
SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
|
|
}
|
|
|
|
SRF_RETURN_DONE(funcctx);
|
|
}
|