Commit 817f2a58 authored by Tom Lane's avatar Tom Lane

Remove PL/Tcl's "module" facility.

PL/Tcl has long had a facility whereby Tcl code could be autoloaded from
a database table named "pltcl_modules".  However, nobody is using it, as
evidenced by the recent discovery that it's never been fixed to work with
standard_conforming_strings turned on.  Moreover, it's rather shaky from
a security standpoint, and the table design is very old and crufty (partly
because it dates from before we had TOAST).  A final problem is that
because the table-population scripts depend on the Tcl client library
Pgtcl, which we removed from the core distribution in 2004, it's
impossible to create a self-contained regression test for the feature.
Rather than try to surmount these problems, let's just remove it.

A follow-on patch will provide a way to execute user-defined
initialization code, similar to features that exist in plperl and plv8.
With that, it will be possible to implement this feature or similar ones
entirely in userspace, which is where it belongs.

Discussion: https://postgr.es/m/22067.1488046447@sss.pgh.pa.us
parent 2ed193c9
......@@ -902,51 +902,6 @@ if {[catch { spi_exec $sql_command }]} {
</para>
</sect1>
<sect1 id="pltcl-unknown">
<title>Modules and the <function>unknown</> Command</title>
<para>
PL/Tcl has support for autoloading Tcl code when used.
It recognizes a special table, <literal>pltcl_modules</>, which
is presumed to contain modules of Tcl code. If this table
exists, the module <literal>unknown</> is fetched from the table
and loaded into the Tcl interpreter immediately before the first
execution of a PL/Tcl function in a database session. (This
happens separately for each Tcl interpreter, if more than one is
used in a session; see <xref linkend="pltcl-global">.)
</para>
<para>
While the <literal>unknown</> module could actually contain any
initialization script you need, it normally defines a Tcl
<function>unknown</> procedure that is invoked whenever Tcl does
not recognize an invoked procedure name. <application>PL/Tcl</>'s standard version
of this procedure tries to find a module in <literal>pltcl_modules</>
that will define the required procedure. If one is found, it is
loaded into the interpreter, and then execution is allowed to
proceed with the originally attempted procedure call. A
secondary table <literal>pltcl_modfuncs</> provides an index of
which functions are defined by which modules, so that the lookup
is reasonably quick.
</para>
<para>
The <productname>PostgreSQL</productname> distribution includes
support scripts to maintain these tables:
<command>pltcl_loadmod</>, <command>pltcl_listmod</>,
<command>pltcl_delmod</>, as well as source for the standard
<literal>unknown</> module in <filename>share/unknown.pltcl</>. This module
must be loaded
into each database initially to support the autoloading mechanism.
</para>
<para>
The tables <literal>pltcl_modules</> and <literal>pltcl_modfuncs</>
must be readable by all, but it is wise to make them owned and
writable only by the database administrator. As a security
precaution, PL/Tcl will ignore <literal>pltcl_modules</> (and thus,
not attempt to load the <literal>unknown</> module) unless it is
owned by a superuser. But update privileges on this table can be
granted to other users, if you trust them sufficiently.
</para>
</sect1>
<sect1 id="pltcl-procnames">
<title>Tcl Procedure Names</title>
......
......@@ -53,7 +53,6 @@ include $(top_srcdir)/src/Makefile.shlib
all: all-lib
$(MAKE) -C modules $@
# Force this dependency to be known even without dependency info built:
pltcl.o: pltclerrcodes.h
......@@ -65,14 +64,11 @@ pltclerrcodes.h: $(top_srcdir)/src/backend/utils/errcodes.txt generate-pltclerrc
distprep: pltclerrcodes.h
install: all install-lib install-data
$(MAKE) -C modules $@
installdirs: installdirs-lib
$(MKDIR_P) '$(DESTDIR)$(datadir)/extension'
$(MAKE) -C modules $@
uninstall: uninstall-lib uninstall-data
$(MAKE) -C modules $@
install-data: installdirs
$(INSTALL_DATA) $(addprefix $(srcdir)/, $(DATA)) '$(DESTDIR)$(datadir)/extension/'
......@@ -100,7 +96,6 @@ clean distclean: clean-lib
ifeq ($(PORTNAME), win32)
rm -f $(tclwithver).def
endif
$(MAKE) -C modules $@
maintainer-clean: distclean
rm -f pltclerrcodes.h
/pltcl_delmod
/pltcl_listmod
/pltcl_loadmod
# src/pl/tcl/modules/Makefile
subdir = src/pl/tcl/modules
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
MODULES = pltcl_loadmod pltcl_delmod pltcl_listmod
all: $(MODULES)
$(MODULES): %: %.in $(top_builddir)/src/Makefile.global
sed 's,@TCLSH@,$(TCLSH),g' $< >$@
chmod a+x $@
install: all installdirs
$(INSTALL_SCRIPT) pltcl_loadmod '$(DESTDIR)$(bindir)/pltcl_loadmod'
$(INSTALL_SCRIPT) pltcl_delmod '$(DESTDIR)$(bindir)/pltcl_delmod'
$(INSTALL_SCRIPT) pltcl_listmod '$(DESTDIR)$(bindir)/pltcl_listmod'
$(INSTALL_DATA) $(srcdir)/unknown.pltcl '$(DESTDIR)$(datadir)/unknown.pltcl'
installdirs:
$(MKDIR_P) '$(DESTDIR)$(bindir)' '$(DESTDIR)$(datadir)'
uninstall:
rm -f '$(DESTDIR)$(bindir)/pltcl_loadmod' '$(DESTDIR)$(bindir)/pltcl_delmod' '$(DESTDIR)$(bindir)/pltcl_listmod' '$(DESTDIR)$(datadir)/unknown.pltcl'
clean distclean maintainer-clean:
rm -f $(MODULES)
src/pl/tcl/modules/README
Regular Tcl scripts of any size (over 8K :-) can be loaded into
the table pltcl_modules using the pltcl_loadmod script. The script
checks the modules that the procedure names don't overwrite
existing ones before doing anything. They also check for global
variables created at load time.
All procedures defined in the module files are automatically
added to the table pltcl_modfuncs. This table is used by the
unknown procedure to determine if an unknown command can be
loaded by sourcing a module. In that case the unknown procedure
will silently source in the module and reexecute the original
command that invoked unknown.
I know, this readme should be more explanatory - but time.
Jan
#! /bin/sh
# src/pl/tcl/modules/pltcl_delmod.in
#
# Start tclsh \
exec @TCLSH@ "$0" "$@"
#
# Code still has to be documented
#
#load /usr/local/pgsql/lib/libpgtcl.so
package require Pgtcl
#
# Check for minimum arguments
#
if {$argc < 1} {
puts stderr ""
puts stderr "usage: pltcl_delmod dbname \[options\] modulename \[...\]"
puts stderr ""
puts stderr "options:"
puts stderr " -host hostname"
puts stderr " -port portnumber"
puts stderr ""
exit 1
}
#
# Remember database name and initialize options
#
set dbname [lindex $argv 0]
set options ""
set errors 0
set opt ""
set val ""
set i 1
while {$i < $argc} {
if {[string compare [string index [lindex $argv $i] 0] "-"] != 0} {
break;
}
set opt [lindex $argv $i]
incr i
if {$i >= $argc} {
puts stderr "no value given for option $opt"
incr errors
continue
}
set val [lindex $argv $i]
incr i
switch -- $opt {
-host {
append options "-host \"$val\" "
}
-port {
append options "-port $val "
}
default {
puts stderr "unknown option '$opt'"
incr errors
}
}
}
#
# Final syntax check
#
if {$i >= $argc || $errors > 0} {
puts stderr ""
puts stderr "usage: pltcl_delmod dbname \[options\] modulename \[...\]"
puts stderr ""
puts stderr "options:"
puts stderr " -host hostname"
puts stderr " -port portnumber"
puts stderr ""
exit 1
}
proc delmodule {conn modname} {
set xname $modname
regsub -all {\\} $xname {\\} xname
regsub -all {'} $xname {''} xname
set found 0
pg_select $conn "select * from pltcl_modules where modname = '$xname'" \
MOD {
set found 1
break;
}
if {!$found} {
puts "Module $modname not found in pltcl_modules"
puts ""
return
}
pg_result \
[pg_exec $conn "delete from pltcl_modules where modname = '$xname'"] \
-clear
pg_result \
[pg_exec $conn "delete from pltcl_modfuncs where modname = '$xname'"] \
-clear
puts "Module $modname removed"
}
set conn [eval pg_connect $dbname $options]
while {$i < $argc} {
delmodule $conn [lindex $argv $i]
incr i
}
pg_disconnect $conn
#! /bin/sh
# src/pl/tcl/modules/pltcl_listmod.in
#
# Start tclsh \
exec @TCLSH@ "$0" "$@"
#
# Code still has to be documented
#
#load /usr/local/pgsql/lib/libpgtcl.so
package require Pgtcl
#
# Check for minimum arguments
#
if {$argc < 1} {
puts stderr ""
puts stderr "usage: pltcl_listmod dbname \[options\] \[modulename \[...\]\]"
puts stderr ""
puts stderr "options:"
puts stderr " -host hostname"
puts stderr " -port portnumber"
puts stderr ""
exit 1
}
#
# Remember database name and initialize options
#
set dbname [lindex $argv 0]
set options ""
set errors 0
set opt ""
set val ""
set i 1
while {$i < $argc} {
if {[string compare [string index [lindex $argv $i] 0] "-"] != 0} {
break;
}
set opt [lindex $argv $i]
incr i
if {$i >= $argc} {
puts stderr "no value given for option $opt"
incr errors
continue
}
set val [lindex $argv $i]
incr i
switch -- $opt {
-host {
append options "-host \"$val\" "
}
-port {
append options "-port $val "
}
default {
puts stderr "unknown option '$opt'"
incr errors
}
}
}
#
# Final syntax check
#
if {$errors > 0} {
puts stderr ""
puts stderr "usage: pltcl_listmod dbname \[options\] \[modulename \[...\]\]"
puts stderr ""
puts stderr "options:"
puts stderr " -host hostname"
puts stderr " -port portnumber"
puts stderr ""
exit 1
}
proc listmodule {conn modname} {
set xname $modname
regsub -all {\\} $xname {\\} xname
regsub -all {'} $xname {''} xname
set found 0
pg_select $conn "select * from pltcl_modules where modname = '$xname'" \
MOD {
set found 1
break;
}
if {!$found} {
puts "Module $modname not found in pltcl_modules"
puts ""
return
}
puts "Module $modname defines procedures:"
pg_select $conn "select funcname from pltcl_modfuncs \
where modname = '$xname' order by funcname" FUNC {
puts " $FUNC(funcname)"
}
puts ""
}
set conn [eval pg_connect $dbname $options]
if {$i == $argc} {
pg_select $conn "select distinct modname from pltcl_modules \
order by modname" \
MOD {
listmodule $conn $MOD(modname)
}
} else {
while {$i < $argc} {
listmodule $conn [lindex $argv $i]
incr i
}
}
pg_disconnect $conn
This diff is collapsed.
#---------------------------------------------------------------------
# Support for unknown command
#---------------------------------------------------------------------
proc unknown {proname args} {
upvar #0 __PLTcl_unknown_support_plan_modname p_mod
upvar #0 __PLTcl_unknown_support_plan_modsrc p_src
#-----------------------------------------------------------
# On first call prepare the plans
#-----------------------------------------------------------
if {![info exists p_mod]} {
set p_mod [spi_prepare \
"select modname from pltcl_modfuncs \
where funcname = \$1" name]
set p_src [spi_prepare \
"select modseq, modsrc from pltcl_modules \
where modname = \$1 \
order by modseq" name]
}
#-----------------------------------------------------------
# Lookup the requested function in pltcl_modfuncs
#-----------------------------------------------------------
set n [spi_execp -count 1 $p_mod [list [quote $proname]]]
if {$n != 1} {
#-----------------------------------------------------------
# Not found there either - now it's really unknown
#-----------------------------------------------------------
return -code error "unknown command '$proname'"
}
#-----------------------------------------------------------
# Collect the source pieces from pltcl_modules
#-----------------------------------------------------------
set src ""
spi_execp $p_src [list [quote $modname]] {
append src $modsrc
}
#-----------------------------------------------------------
# Load the source into the interpreter
#-----------------------------------------------------------
if {[catch {
uplevel #0 "$src"
} msg]} {
elog NOTICE "pltcl unknown: error while loading module $modname"
elog WARN $msg
}
#-----------------------------------------------------------
# This should never happen
#-----------------------------------------------------------
if {[catch {info args $proname}]} {
return -code error \
"unknown command '$proname' (still after loading module $modname)"
}
#-----------------------------------------------------------
# Finally simulate the initial procedure call
#-----------------------------------------------------------
return [uplevel 1 $proname $args]
}
......@@ -255,7 +255,6 @@ void _PG_init(void);
static void pltcl_init_interp(pltcl_interp_desc *interp_desc, bool pltrusted);
static pltcl_interp_desc *pltcl_fetch_interp(bool pltrusted);
static void pltcl_init_load_unknown(Tcl_Interp *interp);
static Datum pltcl_handler(PG_FUNCTION_ARGS, bool pltrusted);
......@@ -491,11 +490,6 @@ pltcl_init_interp(pltcl_interp_desc *interp_desc, bool pltrusted)
pltcl_SPI_execute_plan, NULL, NULL);
Tcl_CreateObjCommand(interp, "spi_lastoid",
pltcl_SPI_lastoid, NULL, NULL);
/************************************************************
* Try to load the unknown procedure from pltcl_modules
************************************************************/
pltcl_init_load_unknown(interp);
}
/**********************************************************************
......@@ -526,126 +520,6 @@ pltcl_fetch_interp(bool pltrusted)
return interp_desc;
}
/**********************************************************************
* pltcl_init_load_unknown() - Load the unknown procedure from
* table pltcl_modules (if it exists)
**********************************************************************/
static void
pltcl_init_load_unknown(Tcl_Interp *interp)
{
Relation pmrel;
char *pmrelname,
*nspname;
char *buf;
int buflen;
int spi_rc;
int tcl_rc;
Tcl_DString unknown_src;
char *part;
uint64 i;
int fno;
/************************************************************
* Check if table pltcl_modules exists
*
* We allow the table to be found anywhere in the search_path.
* This is for backwards compatibility. To ensure that the table
* is trustworthy, we require it to be owned by a superuser.
************************************************************/
pmrel = relation_openrv_extended(makeRangeVar(NULL, "pltcl_modules", -1),
AccessShareLock, true);
if (pmrel == NULL)
return;
/* sanity-check the relation kind */
if (!(pmrel->rd_rel->relkind == RELKIND_RELATION ||
pmrel->rd_rel->relkind == RELKIND_MATVIEW ||
pmrel->rd_rel->relkind == RELKIND_VIEW))
{
relation_close(pmrel, AccessShareLock);
return;
}
/* must be owned by superuser, else ignore */
if (!superuser_arg(pmrel->rd_rel->relowner))
{
relation_close(pmrel, AccessShareLock);
return;
}
/* get fully qualified table name for use in select command */
nspname = get_namespace_name(RelationGetNamespace(pmrel));
if (!nspname)
elog(ERROR, "cache lookup failed for namespace %u",
RelationGetNamespace(pmrel));
pmrelname = quote_qualified_identifier(nspname,
RelationGetRelationName(pmrel));
/************************************************************
* Read all the rows from it where modname = 'unknown',
* in the order of modseq
************************************************************/
buflen = strlen(pmrelname) + 100;
buf = (char *) palloc(buflen);
snprintf(buf, buflen,
"select modsrc from %s where modname = 'unknown' order by modseq",
pmrelname);
spi_rc = SPI_execute(buf, false, 0);
if (spi_rc != SPI_OK_SELECT)
elog(ERROR, "select from pltcl_modules failed");
pfree(buf);
/************************************************************
* If there's nothing, module unknown doesn't exist
************************************************************/
if (SPI_processed == 0)
{
SPI_freetuptable(SPI_tuptable);
ereport(WARNING,
(errmsg("module \"unknown\" not found in pltcl_modules")));
relation_close(pmrel, AccessShareLock);
return;
}
/************************************************************
* There is a module named unknown. Reassemble the
* source from the modsrc attributes and evaluate
* it in the Tcl interpreter
*
* leave this code as DString - it's only executed once per session
************************************************************/
fno = SPI_fnumber(SPI_tuptable->tupdesc, "modsrc");
Assert(fno > 0);
Tcl_DStringInit(&unknown_src);
for (i = 0; i < SPI_processed; i++)
{
part = SPI_getvalue(SPI_tuptable->vals[i],
SPI_tuptable->tupdesc, fno);
if (part != NULL)
{
UTF_BEGIN;
Tcl_DStringAppend(&unknown_src, UTF_E2U(part), -1);
UTF_END;
pfree(part);
}
}
tcl_rc = Tcl_EvalEx(interp, Tcl_DStringValue(&unknown_src),
Tcl_DStringLength(&unknown_src),
TCL_EVAL_GLOBAL);
Tcl_DStringFree(&unknown_src);
SPI_freetuptable(SPI_tuptable);
if (tcl_rc != TCL_OK)
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("could not load module \"unknown\": %s",
utf_u2e(Tcl_GetStringResult(interp)))));
relation_close(pmrel, AccessShareLock);
}
/**********************************************************************
* pltcl_call_handler - This is the only visible function
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment