Files
openGauss-server/src/gausskernel/optimizer/commands/directory.cpp
2022-09-13 19:25:37 +08:00

508 lines
18 KiB
C++

/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
* Portions Copyright (c) 2021, openGauss Contributors
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
* -------------------------------------------------------------------------
*
* directory.cpp
* Commands for creating and dropping directory
* IDENTIFICATION
* src/gausskernel/optimizer/commands/directory.cpp
*
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "knl/knl_variable.h"
#include "access/heapam.h"
#include "access/tableam.h"
#include "access/sysattr.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_directory.h"
#include "commands/directory.h"
#include "miscadmin.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/snapmgr.h"
#ifdef PGXC
#include "pgxc/nodemgr.h"
#include "pgxc/pgxc.h"
#endif
#include "storage/lmgr.h"
/*
* @@GaussDB@@
* Brief : Check if a directory can be added as entry.
* Description : only absolute path is allowed, and three phases to check path legality
* Notes :
*/
static void directory_path_check(CreateDirectoryStmt* stmt, char* dirpath)
{
/* Three phases to check path legality */
/* Step 1: check if the directory path name has any special forbidden characters or sensitive words,it should be
* absolute path */
if (!is_absolute_path(dirpath)) {
ereport(ERROR, (errmodule(MOD_OPT), errcode(ERRCODE_INVALID_NAME),
errmsg("directory path cannot be relative"),
errdetail("N/A"), errcause("relative directory path is not supported"),
erraction("substitute relative path by absolute path")));
}
const char* danger_character_list[] = {"|", ";", "&", "$", "<", ">", "`","\\","\'","'",
"\"","{","}","(",")","[","]","~","*","?","!","\n", "pg_audit", NULL};
int i = 0;
for (i = 0; danger_character_list[i] != NULL; i++) {
if (strstr(dirpath, danger_character_list[i]) != NULL) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_NAME),
errmsg("directory path contains illegal string: \"%s\"", danger_character_list[i])));
}
}
/* Step 2: check if the directory exists, if not, create one */
struct stat st; /* directory attribute */
if (lstat(dirpath, &st) < 0) {
/* path does not exist */
ereport(WARNING,
(errmsg("could not get \"%s\" status, directory does not exist, must make sure directory existance before "
"using",
dirpath)));
} else {
/* Step 3: if path exists, check legality */
if (!S_ISDIR(st.st_mode)) {
/* check if it is a regular dir, if it is not, error */
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a directory, please check", dirpath)));
} else if (S_ISLNK(st.st_mode)) {
/* check if it is a symlink, if it is, error */
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is a symlink, cannot be added as directory", dirpath)));
} else if (-1 == access(dirpath, X_OK | R_OK | W_OK)) {
/* check if has permissions, if not, error */
ereport(WARNING,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("Permission denied for \"%s\", do not have full permissions (Read/Write/Exec) to this "
"directory",
dirpath)));
}
}
}
/*
* Check permission for create directory.
*/
static void CheckCreateDirectoryPermission()
{
/*
* When enable_access_server_directory is off, only initial user can create directory.
* When enable_access_server_directory is on, sysadmin and the member of gs_role_directory_create role
* can create directory.
*/
if (u_sess->attr.attr_storage.enable_access_server_directory) {
if (!superuser() && !is_member_of_role(GetUserId(), DEFAULT_ROLE_DIRECTORY_CREATE)) {
ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to create directory"),
errhint("must be sysadmin or a member of the gs_role_directory_create role "
"to create a directory")));
}
} else {
if (!initialuser()) {
ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to create directory"),
errhint("must be initial user to create a directory")));
}
}
}
/*
* @@GaussDB@@
* Brief : Create a directory.
* Description : Only superusers can create a directory. This seems a reasonable
* restriction since we're determining the system layout and,
* anyway, we probably have root if we're doing this kind of activity.
* Notes :
*/
void CreatePgDirectory(CreateDirectoryStmt* stmt)
{
Relation rel;
Datum values[Natts_pg_directory];
bool nulls[Natts_pg_directory];
bool replaces[Natts_pg_directory];
Oid directoryId = InvalidOid;
char* location = NULL;
Oid ownerId = InvalidOid;
Oid targetoid = InvalidOid;
HeapTuple oldtup = NULL;
HeapTuple tup = NULL;
TupleDesc tupDesc = NULL;
errno_t rc;
/* Permission check. */
CheckCreateDirectoryPermission();
/* get the current user id */
ownerId = GetUserId();
/* unix-ify the offered path, and strip any trailing slashes */
location = pstrdup(stmt->location);
canonicalize_path(location);
directory_path_check(stmt, location);
rc = memset_s(nulls, sizeof(nulls), false, sizeof(nulls));
securec_check(rc, "\0", "\0");
rc = memset_s(values, sizeof(values), (Datum)0, sizeof(values));
securec_check(rc, "\0", "\0");
rc = memset_s(replaces, sizeof(replaces), true, sizeof(replaces));
securec_check(rc, "\0", "\0");
values[Anum_pg_directory_directory_name - 1] = DirectFunctionCall1(namein, CStringGetDatum(stmt->directoryname));
values[Anum_pg_directory_directory_path - 1] = CStringGetTextDatum(location);
values[Anum_pg_directory_owner - 1] = ObjectIdGetDatum(ownerId);
nulls[Anum_pg_directory_directory_acl - 1] = true;
/*
* Check that there is no other directory by this name. If exists same name
* directory and the replace mode is true, it will update the real directory
* path.
*/
/* insert or update tuple into pg_directory */
rel = heap_open(PgDirectoryRelationId, RowExclusiveLock);
targetoid = get_directory_oid(stmt->directoryname, true);
LockDatabaseObject(PgDirectoryRelationId, targetoid, 0, AccessExclusiveLock);
if (OidIsValid(targetoid)) {
/* existing a entry before */
if (stmt->replace) {
replaces[Anum_pg_directory_directory_name - 1] = false;
oldtup = SearchSysCache1(DIRECTORYOID, targetoid);
tupDesc = RelationGetDescr(rel);
if (!HeapTupleIsValid(oldtup)) {
ereport(ERROR,
(errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for directory %u", targetoid)));
}
tup = (HeapTuple) tableam_tops_modify_tuple(oldtup, tupDesc, values, nulls, replaces);
simple_heap_update(rel, &tup->t_self, tup);
ReleaseSysCache(oldtup);
tableam_tops_free_tuple(tup);
pfree(location);
heap_close(rel, RowExclusiveLock);
} else {
pfree(location);
heap_close(rel, RowExclusiveLock);
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("directory \"%s\" is already used by an existing object", stmt->directoryname)));
}
} else {
/* create a new entry */
tup = heap_form_tuple(rel->rd_att, values, nulls);
directoryId = simple_heap_insert(rel, tup);
CatalogUpdateIndexes(rel, tup);
tableam_tops_free_tuple(tup);
/* Record dependency on owner */
recordDependencyOnOwner(PgDirectoryRelationId, directoryId, ownerId);
pfree(location);
heap_close(rel, RowExclusiveLock);
}
}
/*
* Check permission for drop directory.
*/
static void CheckDropDirectoryPermission(Oid directoryId, const char* directoryName)
{
/*
* When enable_access_server_directory is off, only initial user can drop directory,
* When enable_access_server_directory is on, directory owner or users have drop privileges of the directory or
* the member of the gs_role_directory_drop role can drop directory.
*/
if (u_sess->attr.attr_storage.enable_access_server_directory) {
AclResult aclresult = pg_directory_aclcheck(directoryId, GetUserId(), ACL_DROP);
if (aclresult != ACLCHECK_OK && !superuser() && !pg_directory_ownercheck(directoryId, GetUserId())
&& !is_member_of_role(GetUserId(), DEFAULT_ROLE_DIRECTORY_DROP)) {
aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_DIRECTORY, directoryName);
}
} else {
if (!initialuser()) {
ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to drop directory \"%s\"", directoryName),
errhint("must be initial user to drop a directory")));
}
}
}
/*
* @@GaussDB@@
* Brief : Drop a directory.
* Description : Drop a directory by its name. If the directory exists, then
* drop it.
*/
void DropPgDirectory(DropDirectoryStmt* stmt)
{
TableScanDesc scandesc = NULL;
Relation rel;
HeapTuple tuple = NULL;
ScanKeyData entry[1];
Oid directoryId = InvalidOid;
/* find the target tuple */
rel = heap_open(PgDirectoryRelationId, RowExclusiveLock);
ScanKeyInit(&entry[0],
Anum_pg_directory_directory_name,
BTEqualStrategyNumber,
F_NAMEEQ,
CStringGetDatum(stmt->directoryname));
scandesc = tableam_scan_begin(rel, SnapshotNow, 1, entry);
tuple = (HeapTuple) tableam_scan_getnexttuple(scandesc, ForwardScanDirection);
if (HeapTupleIsValid(tuple))
directoryId = HeapTupleGetOid(tuple);
else
directoryId = InvalidOid;
if (OidIsValid(directoryId)) {
/* Permission check. */
CheckDropDirectoryPermission(directoryId, stmt->directoryname);
/* Remove the pg_directory tuple (this will roll back if we fail below) */
simple_heap_delete(rel, &tuple->t_self);
/* Remove dependency on owner. */
deleteSharedDependencyRecordsFor(PgDirectoryRelationId, directoryId, 0);
tableam_scan_end(scandesc);
heap_close(rel, RowExclusiveLock);
} else {
tableam_scan_end(scandesc);
heap_close(rel, RowExclusiveLock);
if (!stmt->missing_ok)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("directory \"%s\" does not exist", stmt->directoryname)));
else
ereport(NOTICE, (errmsg("directory \"%s\" does not exist, skipping", stmt->directoryname)));
}
}
/*
* @@GaussDB@@
* Brief : Given a directory name, look up the OID.
* Description : If missing_ok is false, throw an error if directory name not
* found. If true, just return InvalidOid.
*/
Oid get_directory_oid(const char* directoryname, bool missing_ok)
{
Oid result = InvalidOid;
Relation rel;
TableScanDesc scandesc = NULL;
HeapTuple tuple = NULL;
ScanKeyData entry[1];
/* search the pg_directory to find the directory */
rel = heap_open(PgDirectoryRelationId, AccessShareLock);
ScanKeyInit(
&entry[0], Anum_pg_directory_directory_name, BTEqualStrategyNumber, F_NAMEEQ, CStringGetDatum(directoryname));
scandesc = tableam_scan_begin(rel, SnapshotNow, 1, entry);
tuple = (HeapTuple) tableam_scan_getnexttuple(scandesc, ForwardScanDirection);
/* We assume that there can be at most one matching tuple */
if (HeapTupleIsValid(tuple))
result = HeapTupleGetOid(tuple);
else
result = InvalidOid;
tableam_scan_end(scandesc);
heap_close(rel, AccessShareLock);
if (!OidIsValid(result) && !missing_ok)
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("directory \"%s\" does not exist", directoryname)));
return result;
}
/*
* @@GaussDB@@
* Brief : Given a directory OID, look up its name.
* Description :
*/
char* get_directory_name(Oid dir_oid)
{
char* result = NULL;
Relation rel;
TableScanDesc scandesc = NULL;
HeapTuple tuple = NULL;
ScanKeyData entry[1];
/* search pg_directory */
rel = heap_open(PgDirectoryRelationId, AccessShareLock);
ScanKeyInit(&entry[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(dir_oid));
scandesc = tableam_scan_begin(rel, SnapshotNow, 1, entry);
tuple = (HeapTuple) tableam_scan_getnexttuple(scandesc, ForwardScanDirection);
/* We assume that there can be at most one matching tuple */
if (HeapTupleIsValid(tuple))
result = pstrdup(NameStr(((Form_pg_directory)GETSTRUCT(tuple))->dirname));
else
result = NULL;
tableam_scan_end(scandesc);
heap_close(rel, AccessShareLock);
return result;
}
/*
* Guts of directory deletion.
*/
void RemoveDirectoryById(Oid dirOid)
{
Relation relation;
HeapTuple tup = NULL;
relation = heap_open(PgDirectoryRelationId, RowExclusiveLock);
tup = SearchSysCache1(DIRECTORYOID, ObjectIdGetDatum(dirOid));
if (!HeapTupleIsValid(tup)) /* should not happen */
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for directory %u", dirOid)));
simple_heap_delete(relation, &tup->t_self);
ReleaseSysCache(tup);
heap_close(relation, RowExclusiveLock);
}
static void AlterPgDirectoryOwner_internal(Relation rel, HeapTuple tuple, Oid newOwnerId)
{
Form_pg_directory dirForm = (Form_pg_directory)GETSTRUCT(tuple);
/*
* If the new owner is the same as the existing owner, consider the
* command to have succeeded. This is to be consistent with other
* objects.
*/
if (dirForm->owner == newOwnerId) {
return;
}
Datum repl_val[Natts_pg_directory];
bool repl_null[Natts_pg_directory];
bool repl_repl[Natts_pg_directory];
Acl* newAcl = NULL;
Datum aclDatum;
bool isNull = false;
HeapTuple newtuple;
errno_t rc;
if (u_sess->attr.attr_storage.enable_access_server_directory) {
/* must be sysadmin or owner of the existing object */
if (!superuser() && !pg_directory_ownercheck(HeapTupleGetOid(tuple), GetUserId())) {
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DIRECTORY, NameStr(dirForm->dirname));
}
} else {
if (!initialuser()) {
ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to change owner of directory"),
errhint("must be initial user to change owner of a directory")));
}
}
/* Must be able to become new owner */
check_is_member_of_role(GetUserId(), newOwnerId);
rc = memset_s(repl_null, sizeof(repl_null), false, sizeof(repl_null));
securec_check(rc, "\0", "\0");
rc = memset_s(repl_repl, sizeof(repl_repl), false, sizeof(repl_repl));
securec_check(rc, "\0", "\0");
repl_repl[Anum_pg_directory_owner - 1] = true;
repl_val[Anum_pg_directory_owner - 1] = ObjectIdGetDatum(newOwnerId);
/*
* Determine the modified ACL for the new owner. This is only
* necessary when the ACL is non-null.
*/
aclDatum = tableam_tops_tuple_getattr(tuple, Anum_pg_directory_directory_acl, RelationGetDescr(rel), &isNull);
if (!isNull) {
newAcl = aclnewowner(DatumGetAclP(aclDatum), dirForm->owner, newOwnerId);
repl_repl[Anum_pg_directory_directory_acl - 1] = true;
repl_val[Anum_pg_directory_directory_acl - 1] = PointerGetDatum(newAcl);
}
newtuple = (HeapTuple) tableam_tops_modify_tuple(tuple, RelationGetDescr(rel), repl_val, repl_null, repl_repl);
simple_heap_update(rel, &newtuple->t_self, newtuple);
CatalogUpdateIndexes(rel, newtuple);
tableam_tops_free_tuple(newtuple);
/* Update owner dependency reference */
changeDependencyOnOwner(PgDirectoryRelationId, HeapTupleGetOid(tuple), newOwnerId);
}
/*
* ALTER Directory name OWNER TO newowner
*/
void AlterDirectoryOwner(const char* dirname, Oid newOwnerId)
{
HeapTuple tuple = NULL;
Relation rel;
ScanKeyData scankey;
SysScanDesc scan = NULL;
/*
* Get the old tuple. We don't need a lock on the directory per se,
* because we're not going to do anything that would mess up incoming
* connections.
*/
rel = heap_open(PgDirectoryRelationId, RowExclusiveLock);
ScanKeyInit(&scankey, Anum_pg_directory_directory_name, BTEqualStrategyNumber, F_NAMEEQ, NameGetDatum(dirname));
scan = systable_beginscan(rel, PgDirectoryDirectoriesNameIndexId, true, NULL, 1, &scankey);
tuple = systable_getnext(scan);
if (!HeapTupleIsValid(tuple)) {
heap_close(rel, RowExclusiveLock);
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("directory \"%s\" does not exist", dirname)));
}
AlterPgDirectoryOwner_internal(rel, tuple, newOwnerId);
systable_endscan(scan);
/* Close pg_database, but keep lock till commit */
heap_close(rel, NoLock);
}
void AlterPgDirectoryOwner_oid(Oid dirOid, Oid newOwnerId)
{
Relation rel;
HeapTuple tup = NULL;
rel = heap_open(PgDirectoryRelationId, RowExclusiveLock);
tup = SearchSysCacheCopy1(DIRECTORYOID, ObjectIdGetDatum(dirOid));
if (!HeapTupleIsValid(tup)) /* should not happen */
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for directory %u", dirOid)));
AlterPgDirectoryOwner_internal(rel, tup, newOwnerId);
heap_freetuple(tup);
heap_close(rel, NoLock);
}