|
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* genfile.c
|
|
|
|
|
* Functions for direct access to files
|
|
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
* Copyright (c) 2004-2020, PostgreSQL Global Development Group
|
|
|
|
|
*
|
|
|
|
|
* Author: Andreas Pflug <pgadmin@pse-consulting.de>
|
|
|
|
|
*
|
|
|
|
|
* IDENTIFICATION
|
|
|
|
|
* src/backend/utils/adt/genfile.c
|
|
|
|
|
*
|
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
#include "postgres.h"
|
|
|
|
|
|
|
|
|
|
#include <sys/file.h>
|
|
|
|
|
#include <sys/stat.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <dirent.h>
|
|
|
|
|
|
|
|
|
|
#include "access/htup_details.h"
|
|
|
|
|
#include "access/xlog_internal.h"
|
|
|
|
|
#include "catalog/pg_authid.h"
|
|
|
|
|
#include "catalog/pg_tablespace_d.h"
|
|
|
|
|
#include "catalog/pg_type.h"
|
|
|
|
|
#include "funcapi.h"
|
|
|
|
|
#include "mb/pg_wchar.h"
|
|
|
|
|
#include "miscadmin.h"
|
|
|
|
|
#include "postmaster/syslogger.h"
|
|
|
|
|
#include "storage/fd.h"
|
|
|
|
|
#include "utils/acl.h"
|
|
|
|
|
#include "utils/builtins.h"
|
|
|
|
|
#include "utils/memutils.h"
|
|
|
|
|
#include "utils/syscache.h"
|
|
|
|
|
#include "utils/timestamp.h"
|
|
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
|
{
|
|
|
|
|
char *location;
|
|
|
|
|
DIR *dirdesc;
|
|
|
|
|
bool include_dot_dirs;
|
|
|
|
|
} directory_fctx;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Convert a "text" filename argument to C string, and check it's allowable.
|
|
|
|
|
*
|
|
|
|
|
* Filename may be absolute or relative to the DataDir, but we only allow
|
|
|
|
|
* absolute paths that match DataDir or Log_directory.
|
|
|
|
|
*
|
|
|
|
|
* This does a privilege check against the 'pg_read_server_files' role, so
|
|
|
|
|
* this function is really only appropriate for callers who are only checking
|
|
|
|
|
* 'read' access. Do not use this function if you are looking for a check
|
|
|
|
|
* for 'write' or 'program' access without updating it to access the type
|
|
|
|
|
* of check as an argument and checking the appropriate role membership.
|
|
|
|
|
*/
|
|
|
|
|
static char *
|
|
|
|
|
convert_and_check_filename(text *arg)
|
|
|
|
|
{
|
|
|
|
|
char *filename;
|
|
|
|
|
|
|
|
|
|
filename = text_to_cstring(arg);
|
|
|
|
|
canonicalize_path(filename); /* filename can change length here */
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Members of the 'pg_read_server_files' role are allowed to access any
|
|
|
|
|
* files on the server as the PG user, so no need to do any further checks
|
|
|
|
|
* here.
|
|
|
|
|
*/
|
|
|
|
|
if (is_member_of_role(GetUserId(), DEFAULT_ROLE_READ_SERVER_FILES))
|
|
|
|
|
return filename;
|
|
|
|
|
|
|
|
|
|
/* User isn't a member of the default role, so check if it's allowable */
|
|
|
|
|
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 DataDir or Log_directory, even
|
|
|
|
|
* though Log_directory might be outside DataDir.
|
|
|
|
|
*/
|
|
|
|
|
if (!path_is_prefix_of_path(DataDir, filename) &&
|
|
|
|
|
(!is_absolute_path(Log_directory) ||
|
|
|
|
|
!path_is_prefix_of_path(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.
|
|
|
|
|
*/
|
|
|
|
|
static bytea *
|
|
|
|
|
read_binary_file(const char *filename, int64 seek_offset, int64 bytes_to_read,
|
|
|
|
|
bool missing_ok)
|
|
|
|
|
{
|
|
|
|
|
bytea *buf;
|
|
|
|
|
size_t nbytes;
|
|
|
|
|
FILE *file;
|
|
|
|
|
|
|
|
|
|
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(),
|
Phase 3 of pgindent updates.
Don't move parenthesized lines to the left, even if that means they
flow past the right margin.
By default, BSD indent lines up statement continuation lines that are
within parentheses so that they start just to the right of the preceding
left parenthesis. However, traditionally, if that resulted in the
continuation line extending to the right of the desired right margin,
then indent would push it left just far enough to not overrun the margin,
if it could do so without making the continuation line start to the left of
the current statement indent. That makes for a weird mix of indentations
unless one has been completely rigid about never violating the 80-column
limit.
This behavior has been pretty universally panned by Postgres developers.
Hence, disable it with indent's new -lpl switch, so that parenthesized
lines are always lined up with the preceding left paren.
This patch is much less interesting than the first round of indent
changes, but also bulkier, so I thought it best to separate the effects.
Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org
Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
9 years ago
|
|
|
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 (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)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)));
|
|
|
|
|
|
|
|
|
|
buf = (bytea *) palloc((Size) bytes_to_read + VARHDRSZ);
|
|
|
|
|
|
|
|
|
|
nbytes = fread(VARDATA(buf), 1, (size_t) bytes_to_read, file);
|
|
|
|
|
|
|
|
|
|
if (ferror(file))
|
|
|
|
|
ereport(ERROR,
|
|
|
|
|
(errcode_for_file_access(),
|
|
|
|
|
errmsg("could not read file \"%s\": %m", filename)));
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
bool missing_ok)
|
|
|
|
|
{
|
|
|
|
|
bytea *buf;
|
|
|
|
|
|
|
|
|
|
buf = read_binary_file(filename, seek_offset, bytes_to_read, missing_ok);
|
|
|
|
|
|
|
|
|
|
if (buf != NULL)
|
|
|
|
|
{
|
|
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Read a section of a file, returning it as text
|
|
|
|
|
*
|
|
|
|
|
* This function is kept to support adminpack 1.0.
|
|
|
|
|
*/
|
|
|
|
|
Datum
|
|
|
|
|
pg_read_file(PG_FUNCTION_ARGS)
|
|
|
|
|
{
|
|
|
|
|
text *filename_t = PG_GETARG_TEXT_PP(0);
|
|
|
|
|
int64 seek_offset = 0;
|
|
|
|
|
int64 bytes_to_read = -1;
|
|
|
|
|
bool missing_ok = false;
|
|
|
|
|
char *filename;
|
|
|
|
|
text *result;
|
|
|
|
|
|
|
|
|
|
if (!superuser())
|
|
|
|
|
ereport(ERROR,
|
|
|
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
|
|
|
errmsg("must be superuser to read files with adminpack 1.0"),
|
|
|
|
|
/* translator: %s is a SQL function name */
|
|
|
|
|
errhint("Consider using %s, which is part of core, instead.",
|
|
|
|
|
"pg_file_read()")));
|
|
|
|
|
|
|
|
|
|
/* 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_text_file(filename, seek_offset, bytes_to_read, missing_ok);
|
|
|
|
|
if (result)
|
|
|
|
|
PG_RETURN_TEXT_P(result);
|
|
|
|
|
else
|
|
|
|
|
PG_RETURN_NULL();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Read a section of a file, returning it as text
|
|
|
|
|
*
|
|
|
|
|
* No superuser check done here- instead privileges are handled by the
|
|
|
|
|
* GRANT system.
|
|
|
|
|
*/
|
|
|
|
|
Datum
|
|
|
|
|
pg_read_file_v2(PG_FUNCTION_ARGS)
|
|
|
|
|
{
|
|
|
|
|
text *filename_t = PG_GETARG_TEXT_PP(0);
|
|
|
|
|
int64 seek_offset = 0;
|
|
|
|
|
int64 bytes_to_read = -1;
|
|
|
|
|
bool missing_ok = false;
|
|
|
|
|
char *filename;
|
|
|
|
|
text *result;
|
|
|
|
|
|
|
|
|
|
/* 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_text_file(filename, seek_offset, bytes_to_read, missing_ok);
|
|
|
|
|
if (result)
|
|
|
|
|
PG_RETURN_TEXT_P(result);
|
|
|
|
|
else
|
|
|
|
|
PG_RETURN_NULL();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Read a section of a file, returning it as bytea
|
|
|
|
|
*/
|
|
|
|
|
Datum
|
|
|
|
|
pg_read_binary_file(PG_FUNCTION_ARGS)
|
|
|
|
|
{
|
|
|
|
|
text *filename_t = PG_GETARG_TEXT_PP(0);
|
|
|
|
|
int64 seek_offset = 0;
|
|
|
|
|
int64 bytes_to_read = -1;
|
|
|
|
|
bool missing_ok = false;
|
|
|
|
|
char *filename;
|
|
|
|
|
bytea *result;
|
|
|
|
|
|
|
|
|
|
/* 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);
|
|
|
|
|
if (result)
|
|
|
|
|
PG_RETURN_BYTEA_P(result);
|
|
|
|
|
else
|
|
|
|
|
PG_RETURN_NULL();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Wrapper functions for the 1 and 3 argument variants of pg_read_file_v2()
|
|
|
|
|
* and pg_read_binary_file().
|
|
|
|
|
*
|
|
|
|
|
* These are necessary to pass the sanity check in opr_sanity, which checks
|
|
|
|
|
* that all built-in functions that share the implementing C function take
|
|
|
|
|
* the same number of arguments.
|
|
|
|
|
*/
|
|
|
|
|
Datum
|
|
|
|
|
pg_read_file_off_len(PG_FUNCTION_ARGS)
|
|
|
|
|
{
|
|
|
|
|
return pg_read_file_v2(fcinfo);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Datum
|
|
|
|
|
pg_read_file_all(PG_FUNCTION_ARGS)
|
|
|
|
|
{
|
|
|
|
|
return pg_read_file_v2(fcinfo);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Datum
|
|
|
|
|
pg_read_binary_file_off_len(PG_FUNCTION_ARGS)
|
|
|
|
|
{
|
|
|
|
|
return pg_read_binary_file(fcinfo);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Datum
|
|
|
|
|
pg_read_binary_file_all(PG_FUNCTION_ARGS)
|
|
|
|
|
{
|
|
|
|
|
return pg_read_binary_file(fcinfo);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* stat a file
|
|
|
|
|
*/
|
|
|
|
|
Datum
|
|
|
|
|
pg_stat_file(PG_FUNCTION_ARGS)
|
|
|
|
|
{
|
|
|
|
|
text *filename_t = PG_GETARG_TEXT_PP(0);
|
|
|
|
|
char *filename;
|
|
|
|
|
struct stat fst;
|
|
|
|
|
Datum values[6];
|
|
|
|
|
bool isnull[6];
|
|
|
|
|
HeapTuple tuple;
|
|
|
|
|
TupleDesc tupdesc;
|
|
|
|
|
bool missing_ok = false;
|
|
|
|
|
|
|
|
|
|
/* check the optional argument */
|
|
|
|
|
if (PG_NARGS() == 2)
|
|
|
|
|
missing_ok = PG_GETARG_BOOL(1);
|
|
|
|
|
|
|
|
|
|
filename = convert_and_check_filename(filename_t);
|
|
|
|
|
|
|
|
|
|
if (stat(filename, &fst) < 0)
|
|
|
|
|
{
|
|
|
|
|
if (missing_ok && errno == ENOENT)
|
|
|
|
|
PG_RETURN_NULL();
|
|
|
|
|
else
|
|
|
|
|
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.
|
|
|
|
|
*/
|
Remove WITH OIDS support, change oid catalog column visibility.
Previously tables declared WITH OIDS, including a significant fraction
of the catalog tables, stored the oid column not as a normal column,
but as part of the tuple header.
This special column was not shown by default, which was somewhat odd,
as it's often (consider e.g. pg_class.oid) one of the more important
parts of a row. Neither pg_dump nor COPY included the contents of the
oid column by default.
The fact that the oid column was not an ordinary column necessitated a
significant amount of special case code to support oid columns. That
already was painful for the existing, but upcoming work aiming to make
table storage pluggable, would have required expanding and duplicating
that "specialness" significantly.
WITH OIDS has been deprecated since 2005 (commit ff02d0a05280e0).
Remove it.
Removing includes:
- CREATE TABLE and ALTER TABLE syntax for declaring the table to be
WITH OIDS has been removed (WITH (oids[ = true]) will error out)
- pg_dump does not support dumping tables declared WITH OIDS and will
issue a warning when dumping one (and ignore the oid column).
- restoring an pg_dump archive with pg_restore will warn when
restoring a table with oid contents (and ignore the oid column)
- COPY will refuse to load binary dump that includes oids.
- pg_upgrade will error out when encountering tables declared WITH
OIDS, they have to be altered to remove the oid column first.
- Functionality to access the oid of the last inserted row (like
plpgsql's RESULT_OID, spi's SPI_lastoid, ...) has been removed.
The syntax for declaring a table WITHOUT OIDS (or WITH (oids = false)
for CREATE TABLE) is still supported. While that requires a bit of
support code, it seems unnecessary to break applications / dumps that
do not use oids, and are explicit about not using them.
The biggest user of WITH OID columns was postgres' catalog. This
commit changes all 'magic' oid columns to be columns that are normally
declared and stored. To reduce unnecessary query breakage all the
newly added columns are still named 'oid', even if a table's column
naming scheme would indicate 'reloid' or such. This obviously
requires adapting a lot code, mostly replacing oid access via
HeapTupleGetOid() with access to the underlying Form_pg_*->oid column.
The bootstrap process now assigns oids for all oid columns in
genbki.pl that do not have an explicit value (starting at the largest
oid previously used), only oids assigned later by oids will be above
FirstBootstrapObjectId. As the oid column now is a normal column the
special bootstrap syntax for oids has been removed.
Oids are not automatically assigned during insertion anymore, all
backend code explicitly assigns oids with GetNewOidWithIndex(). For
the rare case that insertions into the catalog via SQL are called for
the new pg_nextoid() function can be used (which only works on catalog
tables).
The fact that oid columns on system tables are now normal columns
means that they will be included in the set of columns expanded
by * (i.e. SELECT * FROM pg_class will now include the table's oid,
previously it did not). It'd not technically be hard to hide oid
column by default, but that'd mean confusing behavior would either
have to be carried forward forever, or it'd cause breakage down the
line.
While it's not unlikely that further adjustments are needed, the
scope/invasiveness of the patch makes it worthwhile to get merge this
now. It's painful to maintain externally, too complicated to commit
after the code code freeze, and a dependency of a number of other
patches.
Catversion bump, for obvious reasons.
Author: Andres Freund, with contributions by John Naylor
Discussion: https://postgr.es/m/20180930034810.ywp2c7awz7opzcfr@alap3.anarazel.de
7 years ago
|
|
|
tupdesc = CreateTemplateTupleDesc(6);
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
memset(isnull, false, sizeof(isnull));
|
|
|
|
|
|
|
|
|
|
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
|
Fix a number of places that were making file-type tests infelicitously.
The places that did, eg,
(statbuf.st_mode & S_IFMT) == S_IFDIR
were correct, but there is no good reason not to use S_ISDIR() instead,
especially when that's what the other 90% of our code does. The places
that did, eg,
(statbuf.st_mode & S_IFDIR)
were flat out *wrong* and would fail in various platform-specific ways,
eg a symlink could be mistaken for a regular file on most Unixen.
The actual impact of this is probably small, since the problem cases
seem to always involve symlinks or sockets, which are unlikely to be
found in the directories that PG code might be scanning. But it's
clearly trouble waiting to happen, so patch all the way back anyway.
(There seem to be no occurrences of the mistake in 7.4.)
18 years ago
|
|
|
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
|
|
|
|
|
|
|
|
|
|
tuple = heap_form_tuple(tupdesc, values, isnull);
|
|
|
|
|
|
|
|
|
|
pfree(filename);
|
|
|
|
|
|
|
|
|
|
PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* stat a file (1 argument version)
|
|
|
|
|
*
|
|
|
|
|
* note: this wrapper is necessary to pass the sanity check in opr_sanity,
|
|
|
|
|
* which checks that all built-in functions that share the implementing C
|
|
|
|
|
* function take the same number of arguments
|
|
|
|
|
*/
|
|
|
|
|
Datum
|
|
|
|
|
pg_stat_file_1arg(PG_FUNCTION_ARGS)
|
|
|
|
|
{
|
|
|
|
|
return pg_stat_file(fcinfo);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* List a directory (returns the filenames only)
|
|
|
|
|
*/
|
|
|
|
|
Datum
|
|
|
|
|
pg_ls_dir(PG_FUNCTION_ARGS)
|
|
|
|
|
{
|
|
|
|
|
FuncCallContext *funcctx;
|
|
|
|
|
struct dirent *de;
|
|
|
|
|
directory_fctx *fctx;
|
|
|
|
|
MemoryContext oldcontext;
|
|
|
|
|
|
|
|
|
|
if (SRF_IS_FIRSTCALL())
|
|
|
|
|
{
|
|
|
|
|
bool missing_ok = false;
|
|
|
|
|
bool include_dot_dirs = false;
|
|
|
|
|
|
|
|
|
|
/* check the optional arguments */
|
|
|
|
|
if (PG_NARGS() == 3)
|
|
|
|
|
{
|
|
|
|
|
if (!PG_ARGISNULL(1))
|
|
|
|
|
missing_ok = PG_GETARG_BOOL(1);
|
|
|
|
|
if (!PG_ARGISNULL(2))
|
|
|
|
|
include_dot_dirs = PG_GETARG_BOOL(2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
funcctx = SRF_FIRSTCALL_INIT();
|
|
|
|
|
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
|
|
|
|
|
|
|
|
|
|
fctx = palloc(sizeof(directory_fctx));
|
|
|
|
|
fctx->location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
|
|
|
|
|
|
|
|
|
|
fctx->include_dot_dirs = include_dot_dirs;
|
|
|
|
|
fctx->dirdesc = AllocateDir(fctx->location);
|
|
|
|
|
|
|
|
|
|
if (!fctx->dirdesc)
|
|
|
|
|
{
|
|
|
|
|
if (missing_ok && errno == ENOENT)
|
|
|
|
|
{
|
|
|
|
|
MemoryContextSwitchTo(oldcontext);
|
|
|
|
|
SRF_RETURN_DONE(funcctx);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
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 (!fctx->include_dot_dirs &&
|
|
|
|
|
(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 a directory (1 argument version)
|
|
|
|
|
*
|
|
|
|
|
* note: this wrapper is necessary to pass the sanity check in opr_sanity,
|
|
|
|
|
* which checks that all built-in functions that share the implementing C
|
|
|
|
|
* function take the same number of arguments.
|
|
|
|
|
*/
|
|
|
|
|
Datum
|
|
|
|
|
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
|
|
|
|
|
{
|
|
|
|
|
return pg_ls_dir(fcinfo);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Generic function to return a directory listing of files */
|
|
|
|
|
static Datum
|
|
|
|
|
pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
|
|
|
|
|
{
|
|
|
|
|
FuncCallContext *funcctx;
|
|
|
|
|
struct dirent *de;
|
|
|
|
|
directory_fctx *fctx;
|
|
|
|
|
|
|
|
|
|
if (SRF_IS_FIRSTCALL())
|
|
|
|
|
{
|
|
|
|
|
MemoryContext oldcontext;
|
|
|
|
|
TupleDesc tupdesc;
|
|
|
|
|
|
|
|
|
|
funcctx = SRF_FIRSTCALL_INIT();
|
|
|
|
|
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
|
|
|
|
|
|
|
|
|
|
fctx = palloc(sizeof(directory_fctx));
|
|
|
|
|
|
Remove WITH OIDS support, change oid catalog column visibility.
Previously tables declared WITH OIDS, including a significant fraction
of the catalog tables, stored the oid column not as a normal column,
but as part of the tuple header.
This special column was not shown by default, which was somewhat odd,
as it's often (consider e.g. pg_class.oid) one of the more important
parts of a row. Neither pg_dump nor COPY included the contents of the
oid column by default.
The fact that the oid column was not an ordinary column necessitated a
significant amount of special case code to support oid columns. That
already was painful for the existing, but upcoming work aiming to make
table storage pluggable, would have required expanding and duplicating
that "specialness" significantly.
WITH OIDS has been deprecated since 2005 (commit ff02d0a05280e0).
Remove it.
Removing includes:
- CREATE TABLE and ALTER TABLE syntax for declaring the table to be
WITH OIDS has been removed (WITH (oids[ = true]) will error out)
- pg_dump does not support dumping tables declared WITH OIDS and will
issue a warning when dumping one (and ignore the oid column).
- restoring an pg_dump archive with pg_restore will warn when
restoring a table with oid contents (and ignore the oid column)
- COPY will refuse to load binary dump that includes oids.
- pg_upgrade will error out when encountering tables declared WITH
OIDS, they have to be altered to remove the oid column first.
- Functionality to access the oid of the last inserted row (like
plpgsql's RESULT_OID, spi's SPI_lastoid, ...) has been removed.
The syntax for declaring a table WITHOUT OIDS (or WITH (oids = false)
for CREATE TABLE) is still supported. While that requires a bit of
support code, it seems unnecessary to break applications / dumps that
do not use oids, and are explicit about not using them.
The biggest user of WITH OID columns was postgres' catalog. This
commit changes all 'magic' oid columns to be columns that are normally
declared and stored. To reduce unnecessary query breakage all the
newly added columns are still named 'oid', even if a table's column
naming scheme would indicate 'reloid' or such. This obviously
requires adapting a lot code, mostly replacing oid access via
HeapTupleGetOid() with access to the underlying Form_pg_*->oid column.
The bootstrap process now assigns oids for all oid columns in
genbki.pl that do not have an explicit value (starting at the largest
oid previously used), only oids assigned later by oids will be above
FirstBootstrapObjectId. As the oid column now is a normal column the
special bootstrap syntax for oids has been removed.
Oids are not automatically assigned during insertion anymore, all
backend code explicitly assigns oids with GetNewOidWithIndex(). For
the rare case that insertions into the catalog via SQL are called for
the new pg_nextoid() function can be used (which only works on catalog
tables).
The fact that oid columns on system tables are now normal columns
means that they will be included in the set of columns expanded
by * (i.e. SELECT * FROM pg_class will now include the table's oid,
previously it did not). It'd not technically be hard to hide oid
column by default, but that'd mean confusing behavior would either
have to be carried forward forever, or it'd cause breakage down the
line.
While it's not unlikely that further adjustments are needed, the
scope/invasiveness of the patch makes it worthwhile to get merge this
now. It's painful to maintain externally, too complicated to commit
after the code code freeze, and a dependency of a number of other
patches.
Catversion bump, for obvious reasons.
Author: Andres Freund, with contributions by John Naylor
Discussion: https://postgr.es/m/20180930034810.ywp2c7awz7opzcfr@alap3.anarazel.de
7 years ago
|
|
|
tupdesc = CreateTemplateTupleDesc(3);
|
|
|
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name",
|
|
|
|
|
TEXTOID, -1, 0);
|
|
|
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "size",
|
|
|
|
|
INT8OID, -1, 0);
|
|
|
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 3, "modification",
|
|
|
|
|
TIMESTAMPTZOID, -1, 0);
|
|
|
|
|
funcctx->tuple_desc = BlessTupleDesc(tupdesc);
|
|
|
|
|
|
|
|
|
|
fctx->location = pstrdup(dir);
|
|
|
|
|
fctx->dirdesc = AllocateDir(fctx->location);
|
|
|
|
|
|
|
|
|
|
if (!fctx->dirdesc)
|
|
|
|
|
{
|
|
|
|
|
if (missing_ok && errno == ENOENT)
|
|
|
|
|
{
|
|
|
|
|
MemoryContextSwitchTo(oldcontext);
|
|
|
|
|
SRF_RETURN_DONE(funcctx);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
Datum values[3];
|
|
|
|
|
bool nulls[3];
|
|
|
|
|
char path[MAXPGPATH * 2];
|
|
|
|
|
struct stat attrib;
|
|
|
|
|
HeapTuple tuple;
|
|
|
|
|
|
|
|
|
|
/* Skip hidden files */
|
|
|
|
|
if (de->d_name[0] == '.')
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
/* Get the file info */
|
|
|
|
|
snprintf(path, sizeof(path), "%s/%s", fctx->location, de->d_name);
|
|
|
|
|
if (stat(path, &attrib) < 0)
|
|
|
|
|
ereport(ERROR,
|
|
|
|
|
(errcode_for_file_access(),
|
|
|
|
|
errmsg("could not stat directory \"%s\": %m", dir)));
|
|
|
|
|
|
|
|
|
|
/* Ignore anything but regular files */
|
|
|
|
|
if (!S_ISREG(attrib.st_mode))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
values[0] = CStringGetTextDatum(de->d_name);
|
|
|
|
|
values[1] = Int64GetDatum((int64) attrib.st_size);
|
|
|
|
|
values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
|
|
|
|
|
memset(nulls, 0, sizeof(nulls));
|
|
|
|
|
|
|
|
|
|
tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
|
|
|
|
|
SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FreeDir(fctx->dirdesc);
|
|
|
|
|
SRF_RETURN_DONE(funcctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Function to return the list of files in the log directory */
|
|
|
|
|
Datum
|
|
|
|
|
pg_ls_logdir(PG_FUNCTION_ARGS)
|
|
|
|
|
{
|
|
|
|
|
return pg_ls_dir_files(fcinfo, Log_directory, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Function to return the list of files in the WAL directory */
|
|
|
|
|
Datum
|
|
|
|
|
pg_ls_waldir(PG_FUNCTION_ARGS)
|
|
|
|
|
{
|
|
|
|
|
return pg_ls_dir_files(fcinfo, XLOGDIR, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Generic function to return the list of files in pgsql_tmp
|
|
|
|
|
*/
|
|
|
|
|
static Datum
|
|
|
|
|
pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
|
|
|
|
|
{
|
|
|
|
|
char path[MAXPGPATH];
|
|
|
|
|
|
|
|
|
|
if (!SearchSysCacheExists1(TABLESPACEOID, ObjectIdGetDatum(tblspc)))
|
|
|
|
|
ereport(ERROR,
|
|
|
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
|
|
|
errmsg("tablespace with OID %u does not exist",
|
|
|
|
|
tblspc)));
|
|
|
|
|
|
|
|
|
|
TempTablespacePath(path, tblspc);
|
|
|
|
|
return pg_ls_dir_files(fcinfo, path, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Function to return the list of temporary files in the pg_default tablespace's
|
|
|
|
|
* pgsql_tmp directory
|
|
|
|
|
*/
|
|
|
|
|
Datum
|
|
|
|
|
pg_ls_tmpdir_noargs(PG_FUNCTION_ARGS)
|
|
|
|
|
{
|
|
|
|
|
return pg_ls_tmpdir(fcinfo, DEFAULTTABLESPACE_OID);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Function to return the list of temporary files in the specified tablespace's
|
|
|
|
|
* pgsql_tmp directory
|
|
|
|
|
*/
|
|
|
|
|
Datum
|
|
|
|
|
pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
|
|
|
|
|
{
|
|
|
|
|
return pg_ls_tmpdir(fcinfo, PG_GETARG_OID(0));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Function to return the list of files in the WAL archive status directory.
|
|
|
|
|
*/
|
|
|
|
|
Datum
|
|
|
|
|
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
|
|
|
|
|
{
|
|
|
|
|
return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
|
|
|
|
|
}
|