PG14:adminpack 插件源码分析

adminpack 提供了大量支持功能,pgAdmin 和其他管理工具可以使用这些功能提供额外功能,例如远程管理服务器日志文件。默认情况下,只有数据库超级用户才能使用所有这些功能,但其他用户也可以使用 GRANT 命令使用这些功能。

我们先来看一下他支持的函数,可以通过 \dx+ adminpack 来进行查看

  • function pg_file_rename(text,text) 重命名文件
  • function pg_file_rename(text,text,text) 重命名文件,如果新文件存在,将将其命名为第三个参数的名字
  • function pg_file_sync(text) 文件刷入磁盘
  • function pg_file_unlink(text) 删除文件
  • function pg_file_write(text,text,boolean) 写文件
  • function pg_logdir_ls() 列出日志目录下的文件

pg_file_rename(text,text)

用于重命名文件,我们看一下 sql 代码

CREATE 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;

这里我们看到两个参数版本的 pg_file_rename 直接调用来三参数版本的 pg_file_rename, 因此我们直接查看三参数版本的 SQL 代码

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;

这个 SQL 代码直接调用来 C 函数 pg_file_rename_v1_1 来实现文件重命名。

现在我们来看一下 C 函数 pg_file_rename_v1_1

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 这个函数

static bool
pg_file_rename_internal(text *file1, text *file2, text *file3)
{
	char	   *fn1,
			   *fn2,
			   *fn3;
	int			rc;

	fn1 = convert_and_check_filename(file1);
	fn2 = convert_and_check_filename(file2);

	if (file3 == NULL)
		fn3 = NULL;
	else
		fn3 = convert_and_check_filename(file3);

	if (access(fn1, W_OK) < 0)
	{
		ereport(WARNING,
				(errcode_for_file_access(),
				 errmsg("file \"%s\" is not accessible: %m", fn1)));

		return false;
	}

	if (fn3 && access(fn2, W_OK) < 0)
	{
		ereport(WARNING,
				(errcode_for_file_access(),
				 errmsg("file \"%s\" is not accessible: %m", fn2)));

		return false;
	}

	rc = access(fn3 ? fn3 : fn2, W_OK);
	if (rc >= 0 || errno != ENOENT)
	{
		ereport(ERROR,
				(errcode(ERRCODE_DUPLICATE_FILE),
				 errmsg("cannot rename to target file \"%s\"",
						fn3 ? fn3 : fn2)));
	}

	if (fn3)
	{
		if (rename(fn2, fn3) != 0)
		{
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not rename \"%s\" to \"%s\": %m",
							fn2, fn3)));
		}
		if (rename(fn1, fn2) != 0)
		{
			ereport(WARNING,
					(errcode_for_file_access(),
					 errmsg("could not rename \"%s\" to \"%s\": %m",
							fn1, fn2)));

			if (rename(fn3, fn2) != 0)
			{
				ereport(ERROR,
						(errcode_for_file_access(),
						 errmsg("could not rename \"%s\" back to \"%s\": %m",
								fn3, fn2)));
			}
			else
			{
				ereport(ERROR,
						(errcode(ERRCODE_UNDEFINED_FILE),
						 errmsg("renaming \"%s\" to \"%s\" was reverted",
								fn2, fn3)));
			}
		}
	}
	else if (rename(fn1, fn2) != 0)
	{
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not rename \"%s\" to \"%s\": %m", fn1, fn2)));
	}

	return true;
}

这个函数的整体逻辑为,先将 text* 类型的数据转换为 char* 类型的数据,会在这个转换的过程中处理一些路径相关和权限验证的问题。

然后先判断一下 fn1 是不是存在,如果不存在那肯定是没法重命名的,然后判断一下 fn3 是不是为空,并且 fn2 是不是存在,如果不存在,那么将 fn2 重命名为 fn3 也会失败。

然后判断一下 fn3 是不是存在,如果存在,那么说明文件已经存在,肯定不能重命名,也会报一个 DUPLICATE 的错误。如果 fn3 为空,那么就判断 fn2 文件是不是存在,如果存在那也是不能重命名的。

接下来,我们就可以将 fn2 重命名成 fn3, 然后将 fn1 重命名为 fn2,

如果 fn1 重命名为 fn2 出错,则将 fn3 重名名为 fn2 ,即撤消之前的修改操作。

那如果 fn3 为空,直接将 fn1 重命名为 fn2 就可以了,

热门相关:全家变身大作战   死亡回放   两面神:欲望的两张脸   鲁斯丁   年轻的女仆