508 lines
18 KiB
C++
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);
|
|
}
|
|
|