前言
hook机制来源于Windows平台。
hook俗称钩子,是一个 Windows 消息的拦截机制。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先截获该消息。这时钩子函数就可以加工处理(改变该消息),也可以强制结束消息的传递(屏蔽消息)
钩子的种类很多,每种钩子可以截获相应的消息,如键盘钩子可以截获键盘消息,外壳钩子可以截取、启动和关闭应用程序的消息等。钩子可以分为线程钩子和系统钩子,线程钩子可以监视指定线程的事件消息,系统钩子监视系统中的所有线程的事件消息。因为系统钩子会影响系统中所有的应用程序,所以钩子函数必须放在独立的动态链接库 (DLL) 中。
所以说,hook(钩子)就是一个 Windows 消息的拦截机制,可以拦截单个进程的消息 (线程钩子),也可以拦截所有进程的消息 (系统钩子),也可以对拦截的消息进行自定义的处理。
常用的hook
该机制提供了一种修改PostgreSQL内核功能却不必修改内核代码的手段,且可以轻松的加载和还原。


hook机制,提供了一种修改PostgreSQL内核功能却不必修改内核代码的手段,且可以轻松的加载和还原。
在PG中也可以把hook看作是一种内置的过滤器,但他是个空壳,过滤器里什么都没有,可以实现这个hook,也就是往过滤器里加东西。
PostgreSQL使用HOOK时,根据HOOK的名字找到对应的定义,通常是HOOK被定义时,将跳转到对应的代码执行用户在插件中植入的代码片段
内部机制
- PG 中 hook 如何工作
Hooks 存在一个全局函数指针,并初始为 NULL,表示hook未被使用。当PostgreSQL执行到hook处的时候,检查这个全局函数指针,如果hook被设置为某个函数的地址时,转而执行新增的函数;否则执行标准函数
拿ProcessUtility来说,有下面这样一段代码:
逻辑 - 如果有钩子函数,就执行钩子函数;否则执行标准函数
- 如何设置函数指针
hook 函数定义在共享库中。在加载共享库时,PG 会调用共享库中的_PG_init() 函数。通过_PG_init 函数设置函数指针(一般会把原来的指针保存)。
可以通过LOAD命令来实现动态加载共享库文件,也可以通过修改session_preload_libraries 或 shared_preload_libraries 参数来加载
- 如何清除函数指针
在卸载时,PG 调用共享库中的_PG_fini() 函数
_PG_fini 函数需要重新设置函数指针(通常将原来的指针恢复)。
这里要提一句,PG15版本自封装的扩展已经不包含 _PG_fini() 函数。且在官方文档里也指出:
a dynamically loaded file can contain an initialization function. If the file includes a function named _PG_init, that function will be called immediately after loading the file. The function receives no parameters and should return void. There is presently no way to unload a dynamically loaded file.目前没有办法卸载动态加载的文件。
所以在编写函数文件时
_PG_fini()函数可以忽略
举例说明
那么问题来了,如何写一个被PostgreSQL所接受的共享库呢?
需求
删除数据库foodb时,只能使用foo用户删除,其他用户(包括超级用户)均不能删除此数据库。
思路
删除数据库的命令是drop database,属于Utility命令,而PG提供了ProcessUtility_hook钩子可供使用
1)_PG_init() 函数
void _PG_init(void)
{
prev_utility_hook = ProcessUtility_hook;
ProcessUtility_hook = dbrestrict_utility;
}
2)_PG_fini()函数(可选)
移除hook并且把它重置为之前的指针值
void _PG_fini(void)
{
ProcessUtility_hook = prev_utility_hook;
}
3)自定义的hook函数 - dbrestrict_utility()
保存当前的hook值(保证你移除修改后能够还原),并将你的hook挂在到PostgreSQL的hook函数指针上。
static void dbrestrict_utility(PlannedStmt *pstmt, const char *queryString,bool readOnlyTree,
ProcessUtilityContext context, ParamListInfo params,
QueryEnvironment *queryEnv,
DestReceiver *dest, QueryCompletion *qc)
{
/* 检查是否为空 */
switch (nodeTag(pstmt->utilityStmt))
{
/* 该命令是 DROP DATABASE */
case T_DropdbStmt:
{
DropdbStmt *stmt = (DropdbStmt *) pstmt->utilityStmt;
/* 获取 username */
char *username = GetUserNameFromId(GetUserId(),false);
/*
* Check that only the authorized superuser foo can
* drop the database undroppable_foodb.
*/
/* 只有当 superuser 权限的 foo用户可以 drop foodb 数据库*/
if (strcmp(stmt->dbname, undroppabledb) == 0 &&
strcmp(username, supersuperuser) != 0)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("Only super-superuser \"%s\" can drop database \"%s\"",
supersuperuser, undroppabledb)));
break;
}
default:
break;
}
/*
* Fallback to normal process, be it the previous hook loaded
* or the in-core code path if the previous hook does not exist.
*/
if (prev_utility_hook)
(*prev_utility_hook) (pstmt, queryString, readOnlyTree, context, params, queryEnv, dest, qc);
else
standard_ProcessUtility(pstmt, queryString, readOnlyTree, context, params, queryEnv, dest, qc);
}
上述三个函数加在一起,构成了最基础的PostgreSQL的hook
4)Makefile文件
MODULE_big = dbrestrict
OBJS = $(WIN32RES) dbrestrict.o
# postgres build stuff
ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)
else
subdir = contrib/dbrestrict
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
include $(top_srcdir)/contrib/contrib-global.mk
endif
MODULE_big:根据OBJS 里面的.o 文件生成的动态库
make & make install
生成 .so 文件并拷贝到lib文件夹下
测试验证
-- 创建测试数据库
postgres=# create database foodb ;
CREATE DATABASE
--- 加载前:成功 drop
postgres=# drop database foodb ;
DROP DATABASE
--- 动态加载共享库文件:dbrestrict.so
postgres=# load 'dbrestrict.so';
LOAD
-- 创建测试数据库
postgres=# create database foodb ;
CREATE DATABASE
--- drop 失败
postgres=# drop database foodb ;
ERROR: Only super-superuser "foo" can drop database "foodb"
-- 创建带有superuser属性的foo用户
create user foo superuser ;
--- drop 成功
postgres=# \c - foo
You are now connected to database "postgres" as user "foo".
postgres=# drop database foodb ;
DROP DATABASE
--# 需求测试完成!
参考
https://paquier.xyz/postgresql-2/hooks-in-postgres-super-superuser-restrictions/
https://wiki.postgresql.org/images/e/e3/Hooks_in_postgresql.pdf
https://www.cnblogs.com/flying-tiger/p/7801258.html
https://developer.aliyun.com/article/603965?scm=20140722.184.2.173
(13条消息) postgresql Hook机制加实例详解_liguangxian2018的博客-CSDN博客_hook postgresql
附录
两个文件:
-
dbrestrict.c
/* * dbrestrict.c * Restrict drop of a given database to a given super-superuser only. * Under license "do whatever you want with that" */ #include "postgres.h" #include "miscadmin.h" #include "tcop/utility.h" PG_MODULE_MAGIC; // 声明函数 void _PG_init(void); void _PG_fini(void); static char *undroppabledb = "foodb"; static char *supersuperuser = "foo"; //全局变量初始化为NULL static ProcessUtility_hook_type prev_utility_hook = NULL; //声明函数 static void dbrestrict_utility(PlannedStmt *pstmt, const char *queryString,bool readOnlyTree, ProcessUtilityContext context, ParamListInfo params, QueryEnvironment *queryEnv, DestReceiver *dest, QueryCompletion *qc); /* * Personal process for DB drop restriction */ static void dbrestrict_utility(PlannedStmt *pstmt, const char *queryString,bool readOnlyTree, ProcessUtilityContext context, ParamListInfo params, QueryEnvironment *queryEnv, DestReceiver *dest, QueryCompletion *qc) { /* Do our custom process on drop database */ switch (nodeTag(pstmt->utilityStmt)) { case T_DropdbStmt: { DropdbStmt *stmt = (DropdbStmt *) pstmt->utilityStmt; char *username = GetUserNameFromId(GetUserId(),false); /* * Check that only the authorized superuser foo can * drop the database undroppable_foodb. */ if (strcmp(stmt->dbname, undroppabledb) == 0 && strcmp(username, supersuperuser) != 0) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Only super-superuser \"%s\" can drop database \"%s\"", supersuperuser, undroppabledb))); break; } default: break; } /* * Fallback to normal process, be it the previous hook loaded * or the in-core code path if the previous hook does not exist. */ if (prev_utility_hook) (*prev_utility_hook) (pstmt, queryString, readOnlyTree, context, params, queryEnv, dest, qc); else standard_ProcessUtility(pstmt, queryString, readOnlyTree, context, params, queryEnv, dest, qc); } /* * _PG_init * Install the hook. */ void _PG_init(void) { prev_utility_hook = ProcessUtility_hook; ProcessUtility_hook = dbrestrict_utility; } /* * _PG_fini * Uninstall the hook. * 如果卸载则调用该函数,实际上是将ProcessUtility_hook赋回原值 */ void _PG_fini(void) { ProcessUtility_hook = prev_utility_hook; } -
Makefile
这里只是对 HOOK 机制做个初步的了解





