PG_CPPFLAGS = -I$(libpq_srcdir)
EXTENSION = adminpack
-DATA = adminpack--1.0.sql
+DATA = adminpack--1.0.sql adminpack--1.0--1.1.sql
PGFILEDESC = "adminpack - support functions for pgAdmin"
REGRESS = adminpack
--- /dev/null
+/* contrib/adminpack/adminpack--1.0--1.1.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION adminpack UPDATE TO '1.1'" to load this file. \quit
+
+/* ***********************************************
+ * Administrative functions for PostgreSQL
+ * *********************************************** */
+
+/* generic file access functions */
+
+CREATE OR REPLACE FUNCTION pg_catalog.pg_file_write(text, text, bool)
+RETURNS bigint
+AS 'MODULE_PATHNAME', 'pg_file_write_v1_1'
+LANGUAGE C VOLATILE STRICT;
+
+REVOKE EXECUTE ON FUNCTION pg_catalog.pg_file_write(text, text, bool) FROM PUBLIC;
+
+CREATE OR REPLACE FUNCTION pg_catalog.pg_file_rename(text, text, text)
+RETURNS bool
+AS 'MODULE_PATHNAME', 'pg_file_rename_v1_1'
+LANGUAGE C VOLATILE;
+
+REVOKE EXECUTE ON FUNCTION pg_catalog.pg_file_rename(text, text, text) FROM PUBLIC;
+
+CREATE OR REPLACE FUNCTION pg_catalog.pg_file_rename(text, text)
+RETURNS bool
+AS 'SELECT pg_catalog.pg_file_rename($1, $2, NULL::pg_catalog.text);'
+LANGUAGE SQL VOLATILE STRICT;
+
+CREATE OR REPLACE FUNCTION pg_catalog.pg_file_unlink(text)
+RETURNS bool
+AS 'MODULE_PATHNAME', 'pg_file_unlink_v1_1'
+LANGUAGE C VOLATILE STRICT;
+
+REVOKE EXECUTE ON FUNCTION pg_catalog.pg_file_unlink(text) FROM PUBLIC;
+
+CREATE OR REPLACE FUNCTION pg_catalog.pg_logdir_ls()
+RETURNS setof record
+AS 'MODULE_PATHNAME', 'pg_logdir_ls_v1_1'
+LANGUAGE C VOLATILE STRICT;
+
+REVOKE EXECUTE ON FUNCTION pg_catalog.pg_logdir_ls() FROM PUBLIC;
+
+/* These functions are now in the backend and callers should update to use those */
+
+DROP FUNCTION pg_file_read(text, bigint, bigint);
+
+DROP FUNCTION pg_file_length(text);
+
+DROP FUNCTION pg_logfile_rotate();
#include <sys/stat.h>
#include <unistd.h>
+#include "catalog/pg_authid.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "miscadmin.h"
PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(pg_file_write);
+PG_FUNCTION_INFO_V1(pg_file_write_v1_1);
PG_FUNCTION_INFO_V1(pg_file_rename);
+PG_FUNCTION_INFO_V1(pg_file_rename_v1_1);
PG_FUNCTION_INFO_V1(pg_file_unlink);
+PG_FUNCTION_INFO_V1(pg_file_unlink_v1_1);
PG_FUNCTION_INFO_V1(pg_logdir_ls);
+PG_FUNCTION_INFO_V1(pg_logdir_ls_v1_1);
+
+static int64 pg_file_write_internal(text *file, text *data, bool replace);
+static bool pg_file_rename_internal(text *file1, text *file2, text *file3);
+static Datum pg_logdir_ls_internal(FunctionCallInfo fcinfo);
typedef struct
{
canonicalize_path(filename); /* filename can change length here */
+ /*
+ * Members of the 'pg_write_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_WRITE_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/..' */
/* ------------------------------------
- * generic file handling functions
+ * pg_file_write - old version
+ *
+ * The superuser() check here must be kept as the library might be upgraded
+ * without the extension being upgraded, meaning that in pre-1.1 installations
+ * these functions could be called by any user.
*/
-
Datum
pg_file_write(PG_FUNCTION_ARGS)
{
- FILE *f;
- char *filename;
- text *data;
+ text *file = PG_GETARG_TEXT_PP(0);
+ text *data = PG_GETARG_TEXT_PP(1);
+ bool replace = PG_GETARG_BOOL(2);
int64 count = 0;
requireSuperuser();
- filename = convert_and_check_filename(PG_GETARG_TEXT_PP(0), false);
- data = PG_GETARG_TEXT_PP(1);
+ count = pg_file_write_internal(file, data, replace);
+
+ PG_RETURN_INT64(count);
+}
+
+/* ------------------------------------
+ * pg_file_write_v1_1 - Version 1.1
+ *
+ * As of adminpack version 1.1, we no longer need to check if the user
+ * is a superuser because we REVOKE EXECUTE on the function from PUBLIC.
+ * Users can then grant access to it based on their policies.
+ *
+ * Otherwise identical to pg_file_write (above).
+ */
+Datum
+pg_file_write_v1_1(PG_FUNCTION_ARGS)
+{
+ text *file = PG_GETARG_TEXT_PP(0);
+ text *data = PG_GETARG_TEXT_PP(1);
+ bool replace = PG_GETARG_BOOL(2);
+ int64 count = 0;
+
+ count = pg_file_write_internal(file, data, replace);
+
+ PG_RETURN_INT64(count);
+}
- if (!PG_GETARG_BOOL(2))
+/* ------------------------------------
+ * pg_file_write_internal - Workhorse for pg_file_write functions.
+ *
+ * This handles the actual work for pg_file_write.
+ */
+int64
+pg_file_write_internal(text *file, text *data, bool replace)
+{
+ FILE *f;
+ char *filename;
+ int64 count = 0;
+
+ filename = convert_and_check_filename(file, false);
+
+ if (!replace)
{
struct stat fst;
(errcode_for_file_access(),
errmsg("could not write file \"%s\": %m", filename)));
- PG_RETURN_INT64(count);
+ return (count);
}
-
+/* ------------------------------------
+ * pg_file_rename - old version
+ *
+ * The superuser() check here must be kept as the library might be upgraded
+ * without the extension being upgraded, meaning that in pre-1.1 installations
+ * these functions could be called by any user.
+ */
Datum
pg_file_rename(PG_FUNCTION_ARGS)
{
- char *fn1,
- *fn2,
- *fn3;
- int rc;
+ text *file1;
+ text *file2;
+ text *file3;
+ bool result;
requireSuperuser();
if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
PG_RETURN_NULL();
- fn1 = convert_and_check_filename(PG_GETARG_TEXT_PP(0), false);
- fn2 = convert_and_check_filename(PG_GETARG_TEXT_PP(1), false);
+ file1 = PG_GETARG_TEXT_PP(0);
+ file2 = PG_GETARG_TEXT_PP(1);
+
if (PG_ARGISNULL(2))
+ file3 = NULL;
+ else
+ file3 = PG_GETARG_TEXT_PP(2);
+
+ result = pg_file_rename_internal(file1, file2, file3);
+
+ PG_RETURN_BOOL(result);
+}
+
+/* ------------------------------------
+ * pg_file_rename_v1_1 - Version 1.1
+ *
+ * As of adminpack version 1.1, we no longer need to check if the user
+ * is a superuser because we REVOKE EXECUTE on the function from PUBLIC.
+ * Users can then grant access to it based on their policies.
+ *
+ * Otherwise identical to pg_file_write (above).
+ */
+Datum
+pg_file_rename_v1_1(PG_FUNCTION_ARGS)
+{
+ text *file1;
+ text *file2;
+ text *file3;
+ bool result;
+
+ if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
+ PG_RETURN_NULL();
+
+ file1 = PG_GETARG_TEXT_PP(0);
+ file2 = PG_GETARG_TEXT_PP(1);
+
+ if (PG_ARGISNULL(2))
+ file3 = NULL;
+ else
+ file3 = PG_GETARG_TEXT_PP(2);
+
+ result = pg_file_rename_internal(file1, file2, file3);
+
+ PG_RETURN_BOOL(result);
+}
+
+/* ------------------------------------
+ * pg_file_rename_internal - Workhorse for pg_file_rename functions.
+ *
+ * This handles the actual work for pg_file_rename.
+ */
+bool
+pg_file_rename_internal(text *file1, text *file2, text *file3)
+{
+ char *fn1,
+ *fn2,
+ *fn3;
+ int rc;
+
+ fn1 = convert_and_check_filename(file1, false);
+ fn2 = convert_and_check_filename(file2, false);
+
+ if (file3 == NULL)
fn3 = 0;
else
- fn3 = convert_and_check_filename(PG_GETARG_TEXT_PP(2), false);
+ fn3 = convert_and_check_filename(file3, false);
if (access(fn1, W_OK) < 0)
{
(errcode_for_file_access(),
errmsg("file \"%s\" is not accessible: %m", fn1)));
- PG_RETURN_BOOL(false);
+ return false;
}
if (fn3 && access(fn2, W_OK) < 0)
(errcode_for_file_access(),
errmsg("file \"%s\" is not accessible: %m", fn2)));
- PG_RETURN_BOOL(false);
+ return false;
}
rc = access(fn3 ? fn3 : fn2, 2);
errmsg("could not rename \"%s\" to \"%s\": %m", fn1, fn2)));
}
- PG_RETURN_BOOL(true);
+ return true;
}
+/* ------------------------------------
+ * pg_file_unlink - old version
+ *
+ * The superuser() check here must be kept as the library might be upgraded
+ * without the extension being upgraded, meaning that in pre-1.1 installations
+ * these functions could be called by any user.
+ */
Datum
pg_file_unlink(PG_FUNCTION_ARGS)
{
}
+/* ------------------------------------
+ * pg_file_unlink_v1_1 - Version 1.1
+ *
+ * As of adminpack version 1.1, we no longer need to check if the user
+ * is a superuser because we REVOKE EXECUTE on the function from PUBLIC.
+ * Users can then grant access to it based on their policies.
+ *
+ * Otherwise identical to pg_file_unlink (above).
+ */
Datum
-pg_logdir_ls(PG_FUNCTION_ARGS)
+pg_file_unlink_v1_1(PG_FUNCTION_ARGS)
{
- FuncCallContext *funcctx;
- struct dirent *de;
- directory_fctx *fctx;
+ char *filename;
+
+ filename = convert_and_check_filename(PG_GETARG_TEXT_PP(0), false);
+
+ if (access(filename, W_OK) < 0)
+ {
+ if (errno == ENOENT)
+ PG_RETURN_BOOL(false);
+ else
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("file \"%s\" is not accessible: %m", filename)));
+ }
+ if (unlink(filename) < 0)
+ {
+ ereport(WARNING,
+ (errcode_for_file_access(),
+ errmsg("could not unlink file \"%s\": %m", filename)));
+
+ PG_RETURN_BOOL(false);
+ }
+ PG_RETURN_BOOL(true);
+}
+
+/* ------------------------------------
+ * pg_logdir_ls - Old version
+ *
+ * The superuser() check here must be kept as the library might be upgraded
+ * without the extension being upgraded, meaning that in pre-1.1 installations
+ * these functions could be called by any user.
+ */
+Datum
+pg_logdir_ls(PG_FUNCTION_ARGS)
+{
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
(errmsg("only superuser can list the log directory"))));
+ return (pg_logdir_ls_internal(fcinfo));
+}
+
+/* ------------------------------------
+ * pg_logdir_ls_v1_1 - Version 1.1
+ *
+ * As of adminpack version 1.1, we no longer need to check if the user
+ * is a superuser because we REVOKE EXECUTE on the function from PUBLIC.
+ * Users can then grant access to it based on their policies.
+ *
+ * Otherwise identical to pg_logdir_ls (above).
+ */
+Datum
+pg_logdir_ls_v1_1(PG_FUNCTION_ARGS)
+{
+ return (pg_logdir_ls_internal(fcinfo));
+}
+
+Datum
+pg_logdir_ls_internal(FunctionCallInfo fcinfo)
+{
+ FuncCallContext *funcctx;
+ struct dirent *de;
+ directory_fctx *fctx;
+
if (strcmp(Log_filename, "postgresql-%Y-%m-%d_%H%M%S.log") != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
# adminpack extension
comment = 'administrative functions for PostgreSQL'
-default_version = '1.0'
+default_version = '1.1'
module_pathname = '$libdir/adminpack'
relocatable = false
schema = pg_catalog
test1test1
(1 row)
--- disallowed file paths
+-- disallowed file paths for non-superusers and users who are
+-- not members of pg_write_server_files
+CREATE ROLE regress_user1;
+GRANT pg_read_all_settings TO regress_user1;
+GRANT EXECUTE ON FUNCTION pg_file_write(text,text,bool) TO regress_user1;
+SET ROLE regress_user1;
SELECT pg_file_write('../test_file0', 'test0', false);
ERROR: path must be in or below the current directory
SELECT pg_file_write('/tmp/test_file0', 'test0', false);
SELECT pg_file_write(current_setting('data_directory') || '/../test_file4', 'test4', false);
ERROR: reference to parent directory ("..") not allowed
+RESET ROLE;
+REVOKE EXECUTE ON FUNCTION pg_file_write(text,text,bool) FROM regress_user1;
+REVOKE pg_read_all_settings FROM regress_user1;
+DROP ROLE regress_user1;
-- rename file
SELECT pg_file_rename('test_file1', 'test_file2');
pg_file_rename
CREATE USER regress_user1;
SET ROLE regress_user1;
SELECT pg_file_write('test_file0', 'test0', false);
-ERROR: only superuser may access generic file functions
+ERROR: permission denied for function pg_file_write
SELECT pg_file_rename('test_file0', 'test_file0');
-ERROR: only superuser may access generic file functions
+ERROR: permission denied for function pg_file_rename
CONTEXT: SQL function "pg_file_rename" statement 1
SELECT pg_file_unlink('test_file0');
-ERROR: only superuser may access generic file functions
+ERROR: permission denied for function pg_file_unlink
SELECT pg_logdir_ls();
-ERROR: only superuser can list the log directory
+ERROR: permission denied for function pg_logdir_ls
RESET ROLE;
DROP USER regress_user1;
-- no further tests for pg_logdir_ls() because it depends on the
SELECT pg_file_write('test_file1', 'test1', false);
SELECT pg_read_file('test_file1');
--- disallowed file paths
+-- disallowed file paths for non-superusers and users who are
+-- not members of pg_write_server_files
+CREATE ROLE regress_user1;
+
+GRANT pg_read_all_settings TO regress_user1;
+GRANT EXECUTE ON FUNCTION pg_file_write(text,text,bool) TO regress_user1;
+
+SET ROLE regress_user1;
SELECT pg_file_write('../test_file0', 'test0', false);
SELECT pg_file_write('/tmp/test_file0', 'test0', false);
SELECT pg_file_write(current_setting('data_directory') || '/test_file4', 'test4', false);
SELECT pg_file_write(current_setting('data_directory') || '/../test_file4', 'test4', false);
-
+RESET ROLE;
+REVOKE EXECUTE ON FUNCTION pg_file_write(text,text,bool) FROM regress_user1;
+REVOKE pg_read_all_settings FROM regress_user1;
+DROP ROLE regress_user1;
-- rename file
SELECT pg_file_rename('test_file1', 'test_file2');
<application>pgAdmin</application> and other administration and management tools can
use to provide additional functionality, such as remote management
of server log files.
- Use of all these functions is restricted to superusers.
+ Use of all these functions is only allowed to the superuser by default but may be
+ allowed to other users by using the <command>GRANT</command> command.
</para>
<para>
write access to files on the machine hosting the server. (See also the
functions in <xref linkend="functions-admin-genfile-table"/>, which
provide read-only access.)
- Only files within the database cluster directory can be accessed, but
- either a relative or absolute path is allowable.
+ Only files within the database cluster directory can be accessed, unless the
+ user is a superuser or given one of the pg_read_server_files, or pg_write_server_files
+ roles, as appropriate for the function, but either a relative or absolute path is
+ allowable.
</para>
<table id="functions-adminpack-table">
function.
</para>
- <para>
- The functions shown
- in <xref linkend="functions-adminpack-deprecated-table"/> are deprecated
- and should not be used in new applications; instead use those shown
- in <xref linkend="functions-admin-signal-table"/>
- and <xref linkend="functions-admin-genfile-table"/>. These functions are
- provided in <filename>adminpack</filename> only for compatibility with old
- versions of <application>pgAdmin</application>.
- </para>
-
- <table id="functions-adminpack-deprecated-table">
- <title>Deprecated <filename>adminpack</filename> Functions</title>
- <tgroup cols="3">
- <thead>
- <row><entry>Name</entry> <entry>Return Type</entry> <entry>Description</entry>
- </row>
- </thead>
-
- <tbody>
- <row>
- <entry><function>pg_catalog.pg_file_read(filename text, offset bigint, nbytes bigint)</function></entry>
- <entry><type>text</type></entry>
- <entry>
- Alternate name for <function>pg_read_file()</function>
- </entry>
- </row>
- <row>
- <entry><function>pg_catalog.pg_file_length(filename text)</function></entry>
- <entry><type>bigint</type></entry>
- <entry>
- Same as <structfield>size</structfield> column returned
- by <function>pg_stat_file()</function>
- </entry>
- </row>
- <row>
- <entry><function>pg_catalog.pg_logfile_rotate()</function></entry>
- <entry><type>integer</type></entry>
- <entry>
- Alternate name for <function>pg_rotate_logfile()</function>, but note that it
- returns integer 0 or 1 rather than <type>boolean</type>
- </entry>
- </row>
- </tbody>
- </tgroup>
- </table>
-
</sect1>
/*
* 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)
char *filename;
text *result;
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to read files with adminpack 1.0"),
+ errhint("Consider using pg_file_read(), which is part of core, instead."))));
+
+ /* 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)
{
/*
- * Wrapper functions for the 1 and 3 argument variants of pg_read_file()
+ * Wrapper functions for the 1 and 3 argument variants of pg_read_file_v2()
* and pg_binary_read_file().
*
* These are necessary to pass the sanity check in opr_sanity, which checks
Datum
pg_read_file_off_len(PG_FUNCTION_ARGS)
{
- return pg_read_file(fcinfo);
+ return pg_read_file_v2(fcinfo);
}
Datum
pg_read_file_all(PG_FUNCTION_ARGS)
{
- return pg_read_file(fcinfo);
+ return pg_read_file_v2(fcinfo);
}
Datum
}
+/*
+ * Rotate log file
+ *
+ * This function is kept to support adminpack 1.0.
+ */
+Datum
+pg_rotate_logfile(PG_FUNCTION_ARGS)
+{
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to rotate log files with adminpack 1.0"),
+ errhint("Consider using pg_logfile_rotate(), which is part of core, instead."))));
+
+ if (!Logging_collector)
+ {
+ ereport(WARNING,
+ (errmsg("rotation not possible because log collection not active")));
+ PG_RETURN_BOOL(false);
+ }
+
+ SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
+ PG_RETURN_BOOL(true);
+}
+
/*
* Rotate log file
*
* GRANT system.
*/
Datum
-pg_rotate_logfile(PG_FUNCTION_ARGS)
+pg_rotate_logfile_v2(PG_FUNCTION_ARGS)
{
if (!Logging_collector)
{
DATA(insert OID = 2621 ( pg_reload_conf PGNSP PGUID 12 1 0 0 0 f f f t f v s 0 0 16 "" _null_ _null_ _null_ _null_ _null_ pg_reload_conf _null_ _null_ _null_ ));
DESCR("reload configuration files");
-DATA(insert OID = 2622 ( pg_rotate_logfile PGNSP PGUID 12 1 0 0 0 f f f t f v s 0 0 16 "" _null_ _null_ _null_ _null_ _null_ pg_rotate_logfile _null_ _null_ _null_ ));
+DATA(insert OID = 2622 ( pg_rotate_logfile PGNSP PGUID 12 1 0 0 0 f f f t f v s 0 0 16 "" _null_ _null_ _null_ _null_ _null_ pg_rotate_logfile_v2 _null_ _null_ _null_ ));
DESCR("rotate log file");
+DATA(insert OID = 4099 ( pg_rotate_logfile_old PGNSP PGUID 12 1 0 0 0 f f f t f v s 0 0 16 "" _null_ _null_ _null_ _null_ _null_ pg_rotate_logfile _null_ _null_ _null_ ));
+DESCR("rotate log file - old version for adminpack 1.0");
DATA(insert OID = 3800 ( pg_current_logfile PGNSP PGUID 12 1 0 0 0 f f f f f v s 0 0 25 "" _null_ _null_ _null_ _null_ _null_ pg_current_logfile _null_ _null_ _null_ ));
DESCR("current logging collector file location");
DATA(insert OID = 3801 ( pg_current_logfile PGNSP PGUID 12 1 0 0 0 f f f f f v s 1 0 25 "25" _null_ _null_ _null_ _null_ _null_ pg_current_logfile_1arg _null_ _null_ _null_ ));
DESCR("get information about file");
DATA(insert OID = 2624 ( pg_read_file PGNSP PGUID 12 1 0 0 0 f f f t f v s 3 0 25 "25 20 20" _null_ _null_ _null_ _null_ _null_ pg_read_file_off_len _null_ _null_ _null_ ));
DESCR("read text from a file");
-DATA(insert OID = 3293 ( pg_read_file PGNSP PGUID 12 1 0 0 0 f f f t f v s 4 0 25 "25 20 20 16" _null_ _null_ _null_ _null_ _null_ pg_read_file _null_ _null_ _null_ ));
+DATA(insert OID = 3293 ( pg_read_file PGNSP PGUID 12 1 0 0 0 f f f t f v s 4 0 25 "25 20 20 16" _null_ _null_ _null_ _null_ _null_ pg_read_file_v2 _null_ _null_ _null_ ));
DESCR("read text from a file");
+DATA(insert OID = 4100 ( pg_read_file_old PGNSP PGUID 12 1 0 0 0 f f f t f v s 3 0 25 "25 20 20" _null_ _null_ _null_ _null_ _null_ pg_read_file _null_ _null_ _null_ ));
+DESCR("read text from a file - old version for adminpack 1.0");
DATA(insert OID = 3826 ( pg_read_file PGNSP PGUID 12 1 0 0 0 f f f t f v s 1 0 25 "25" _null_ _null_ _null_ _null_ _null_ pg_read_file_all _null_ _null_ _null_ ));
DESCR("read text from a file");
DATA(insert OID = 3827 ( pg_read_binary_file PGNSP PGUID 12 1 0 0 0 f f f t f v s 3 0 17 "25 20 20" _null_ _null_ _null_ _null_ _null_ pg_read_binary_file_off_len _null_ _null_ _null_ ));