diff --git a/cmake/FindPsqlDevel.cmake b/cmake/FindPsqlDevel.cmake index 6762aea..9ba18a5 100644 --- a/cmake/FindPsqlDevel.cmake +++ b/cmake/FindPsqlDevel.cmake @@ -2,19 +2,41 @@ # postgresql-server-devel Support # +find_program(PG_CONFIG pg_config) + +if (NOT PG_CONFIG) + message(FATAL_ERROR "pg_config executable not found. Ensure PostgreSQL is installed and pg_config is in your PATH.") +endif() + +# Use pg_config to get the include and library directories +execute_process(COMMAND ${PG_CONFIG} --includedir + OUTPUT_VARIABLE PSQLDEVEL_INCLUDE_DIR + OUTPUT_STRIP_TRAILING_WHITESPACE) + +execute_process(COMMAND ${PG_CONFIG} --libdir + OUTPUT_VARIABLE PSQLDEVEL_LIBRARY_DIR + OUTPUT_STRIP_TRAILING_WHITESPACE) + find_path(PSQLDEVEL_INCLUDE_DIR NAMES libpq-fe.h - PATH_SUFFIXES postgresql + PATHS ${PSQLDEVEL_INCLUDE_DIR} ) find_library(PSQLDEVEL_LIBRARY NAMES pq + PATHS ${PSQLDEVEL_LIBRARY_DIR} ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(PsqlDevel DEFAULT_MSG PSQLDEVEL_INCLUDE_DIR PSQLDEVEL_LIBRARY) +if (PSQLDEVEL_INCLUDE_DIR AND PSQLDEVEL_LIBRARY) + set(PSQLDEVEL_FOUND TRUE) +else() + set(PSQLDEVEL_FOUND FALSE) +endif() + if (PSQLDEVEL_FOUND) set(PSQLDEVEL_INCLUDE_DIRS ${PSQLDEVEL_INCLUDE_DIR}) set(PSQLDEVEL_LIBRARIES ${PSQLDEVEL_LIBRARY}) diff --git a/doc/manual/user-03-functions.md b/doc/manual/user-03-functions.md index bbeba0f..566fb15 100644 --- a/doc/manual/user-03-functions.md +++ b/doc/manual/user-03-functions.md @@ -4,11 +4,12 @@ ## Security -After you create the extension `pgmoneta_ext` using the `postgres` role, you can test all functions as shown below. Some functions may require superuser privileges (For the column `Superuser`, the value is `true`), so if you log in with a role without superuser privileges, the function will return `false`. If you have superuser privileges, it will work as desired. +After you create the extension `pgmoneta_ext` using the `postgres` role, you can test all functions as shown below. Some functions may require specific privileges (For the column `Privilege`), so if you log in with a role without the specific privileges, the function will return `false`. If you have the specific privileges, it will work as desired. ## Extension functions -| Function | Superuser | Description | +| Function | Privilege | Description | |-----------------------------|-----------|--------------------------------------------------------| -| `pgmoneta_ext_version()` | false | Return the version number of `pgmoneta_ext` as a Datum.| -| `pgmoneta_ext_switch_wal()` | true | A function for switching to a new WAL file. | +| `pgmoneta_ext_version()` | Default | Return the version number of `pgmoneta_ext` as a Datum.| +| `pgmoneta_ext_switch_wal()` | SUPERUSER | A function for switching to a new WAL file. | +| `pgmoneta_ext_checkpoint()` | SUPERUSER
pg_checkpoint | A function which forces a checkpoint.
This function can only be executed by a `SUPERUSER` in PostgreSQL 13/14, but can also be executed by `pg_checkpoint` in PostgreSQL 15+.
You can use the SQL command `GRANT pg_checkpoint TO repl;` to assign the role in PostgreSQL 15+.| diff --git a/sql/pgmoneta_ext--0.1.0.sql b/sql/pgmoneta_ext--0.1.0.sql index e2c7d73..fe2d79b 100644 --- a/sql/pgmoneta_ext--0.1.0.sql +++ b/sql/pgmoneta_ext--0.1.0.sql @@ -7,4 +7,11 @@ CREATE FUNCTION pgmoneta_ext_switch_wal(OUT success bool, ) RETURNS record AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +CREATE FUNCTION pgmoneta_ext_checkpoint(OUT success bool, + OUT value text +) +RETURNS record +AS 'MODULE_PATHNAME' LANGUAGE C STRICT; \ No newline at end of file diff --git a/src/include/utils.h b/src/include/utils.h index e9ad1ee..7dc7483 100644 --- a/src/include/utils.h +++ b/src/include/utils.h @@ -39,14 +39,24 @@ extern "C" { #include "fmgr.h" /* system */ +#include /** * Check if the role has superuser privileges. * @param roleid The current role's ID - * @return 0 upon success, otherwise 1 + * @return The result */ -int -pgmoneta_ext_check_privilege(Oid roleid); +bool +pgmoneta_ext_check_superuser(Oid roleid); + +/** + * Check if the role has specific role. + * @param roleid The current role's ID + * @param rolename Specific role name + * @return The result + */ +bool +pgmoneta_ext_check_role(Oid roleid, const char* rolename); #ifdef __cplusplus } diff --git a/src/pgmoneta_ext/lib.c b/src/pgmoneta_ext/lib.c index 35f2351..2b683fb 100644 --- a/src/pgmoneta_ext/lib.c +++ b/src/pgmoneta_ext/lib.c @@ -27,7 +27,7 @@ * */ -/* pgmoneta */ +/* pgmoneta_ext */ #include #include @@ -52,6 +52,7 @@ PG_MODULE_MAGIC; PG_FUNCTION_INFO_V1(pgmoneta_ext_version); PG_FUNCTION_INFO_V1(pgmoneta_ext_switch_wal); +PG_FUNCTION_INFO_V1(pgmoneta_ext_checkpoint); Datum pgmoneta_ext_version(PG_FUNCTION_ARGS) @@ -78,15 +79,15 @@ pgmoneta_ext_switch_wal(PG_FUNCTION_ARGS) XLogRecPtr recptr; bool nulls[2]; char str_res[1024]; - int is_superuser; + bool is_superuser; memset(nulls, 0, sizeof(nulls)); roleid = GetUserId(); - is_superuser = pgmoneta_ext_check_privilege(roleid); + is_superuser = pgmoneta_ext_check_superuser(roleid); - if (!is_superuser) + if (is_superuser) { // Request to switch WAL with mark_unimportant set to false. recptr = RequestXLogSwitch(false); @@ -114,5 +115,64 @@ pgmoneta_ext_switch_wal(PG_FUNCTION_ARGS) tuple = heap_form_tuple(tupdesc, values, nulls); result = HeapTupleGetDatum(tuple); + PG_RETURN_DATUM(result); +} + +#ifndef RequestCheckpoint +extern void RequestCheckpoint(int flags); +#endif + +Datum +pgmoneta_ext_checkpoint(PG_FUNCTION_ARGS) +{ + Datum values[2]; + Datum result; + HeapTuple tuple; + Oid roleid; + TupleDesc tupdesc; + bool nulls[2]; + bool is_superuser; + bool is_pg_checkpoint; + char cp[1024]; + + memset(nulls, 0, sizeof(nulls)); + memset(&cp, 0, sizeof(cp)); + is_superuser = false; + is_pg_checkpoint = false; + + roleid = GetUserId(); + is_superuser = pgmoneta_ext_check_superuser(roleid); + +#if PG_VERSION_NUM >= 150000 + is_pg_checkpoint = pgmoneta_ext_check_role(roleid, "pg_checkpoint"); +#endif + + if (is_superuser || is_pg_checkpoint) + { + // Perform the checkpoint + RequestCheckpoint(CHECKPOINT_IMMEDIATE | CHECKPOINT_WAIT | CHECKPOINT_FORCE); + + values[0] = BoolGetDatum(true); + snprintf(&cp[0], sizeof(cp), "%s", "CHECKPOINT"); + values[1] = CStringGetTextDatum(cp); + } + else + { + ereport(LOG, errmsg_internal("pgmoneta_ext_checkpoint: Current role is not a superuser")); + + values[0] = BoolGetDatum(false); + nulls[1] = true; + } + + // Create a tuple descriptor for our result type + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + { + ereport(ERROR, errmsg_internal("pgmoneta_ext_checkpoint: Return type must be a row type")); + } + + // Build the result tuple + tuple = heap_form_tuple(tupdesc, values, nulls); + result = HeapTupleGetDatum(tuple); + PG_RETURN_DATUM(result); } \ No newline at end of file diff --git a/src/pgmoneta_ext/utils.c b/src/pgmoneta_ext/utils.c index 7bff953..ea88b7b 100644 --- a/src/pgmoneta_ext/utils.c +++ b/src/pgmoneta_ext/utils.c @@ -27,33 +27,37 @@ * */ -/* pgmoneta */ +/* pgmoneta_ext */ #include /* PostgreSQL */ #include "access/htup_details.h" #include "catalog/pg_authid.h" +#include "miscadmin.h" +#include "utils/acl.h" +#include "utils/builtins.h" #include "utils/elog.h" #include "utils/errcodes.h" #include "utils/relcache.h" #include "utils/syscache.h" /* system */ -#include -int -pgmoneta_ext_check_privilege(Oid roleid) +bool +pgmoneta_ext_check_superuser(Oid roleid) { bool is_superuser; HeapTuple roletuple; Form_pg_authid roleform; + is_superuser = false; + // Fetch the role's tuple from pg_authid roletuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); if (!HeapTupleIsValid(roletuple)) { - ereport(ERROR, errmsg_internal("pgmoneta_ext_switch_wal: Role with OID %u does not exist", roleid)); - return 1; + ereport(ERROR, errmsg_internal("pgmoneta_ext_check_superuser: Role with OID %u does not exist", roleid)); + return false; } // Get the role's superuser attribute @@ -64,5 +68,43 @@ pgmoneta_ext_check_privilege(Oid roleid) // Release the role tuple ReleaseSysCache(roletuple); - return !is_superuser; + return is_superuser; } + +bool +pgmoneta_ext_check_role(Oid roleid, const char* rolename) +{ + bool is_success; + HeapTuple roletuple; + List* role_oids; + ListCell* cell; + + is_success = false; + + roletuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); + if (!HeapTupleIsValid(roletuple)) + { + ereport(ERROR, errmsg_internal("pgmoneta_ext_check_role: Role with OID %u does not exist", roleid)); + return false; + } + + // Get role OIDs of a given role name + Oid role_oid = get_role_oid(rolename, false); + role_oids = list_make1_oid(role_oid); + + // Check if the role has the specific privilege + foreach (cell, role_oids) + { + Oid checkpoint_role_oid = lfirst_oid(cell); + if (is_member_of_role(roleid, checkpoint_role_oid)) + { + is_success = true; + break; + } + } + + ReleaseSysCache(roletuple); + list_free(role_oids); + + return is_success; +} \ No newline at end of file