| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| /*------------------------------------------------------------------------- |
| * |
| * filespace.c |
| * Commands to manipulate filespaces |
| * |
| *------------------------------------------------------------------------- |
| */ |
| #include "postgres.h" |
| #include "miscadmin.h" |
| |
| /* System libraries for file and directory operations */ |
| #include <unistd.h> |
| #include <dirent.h> |
| #include <sys/stat.h> |
| |
| #include "catalog/catalog.h" |
| #include "catalog/dependency.h" |
| #include "catalog/gp_segment_config.h" |
| #include "catalog/heap.h" |
| #include "catalog/indexing.h" |
| #include "catalog/pg_filespace.h" |
| #include "catalog/pg_filespace_entry.h" |
| #include "catalog/pg_filesystem.h" |
| #include "catalog/pg_tablespace.h" |
| #include "commands/comment.h" |
| #include "commands/filespace.h" |
| #include "commands/defrem.h" |
| #include "storage/fd.h" |
| #include "utils/acl.h" |
| #include "utils/builtins.h" |
| #include "utils/fmgroids.h" |
| #include "utils/lsyscache.h" |
| |
| #include "cdb/cdbdisp.h" |
| #include "cdb/cdbmirroredfilesysobj.h" |
| #include "cdb/cdbvars.h" |
| #include "cdb/cdbutil.h" |
| #include "catalog/catquery.h" |
| #include "access/genam.h" |
| #include "postmaster/primary_mirror_mode.h" |
| |
| static void checkPathFormat(char *path, bool url); |
| static void checkPathPermissions(char *path); |
| static void filespace_check_empty(Oid fsoid); |
| |
| static void DeleteFilespaceEntryTuples(Oid fsoid); |
| |
| |
| /* |
| * Calculate maximum filespace path length, Remember that we're going to append |
| * '/<tbsoid>/<dboid>/<relid>.<nnn>' |
| * |
| * 10 digits for each oid and the extension number, |
| * 1 digit for each slash and the '.' of the extension |
| * = 10*4 + 4 = +44 characters |
| * |
| * Note: This may be overly conservative. Do we ever form the whole path |
| * explicitly? |
| */ |
| #define MAX_FILESPACE_PATH (MAXPGPATH - 44) |
| |
| /* |
| * Set maximum allowed number of filespaces. |
| * |
| * Expected number of filespaces is < 10 |
| * |
| * Should probably be made into a guc. |
| */ |
| #define MAX_FILESPACES 64 |
| |
| |
| static bool isLocalFilesystem(Oid fsysid) |
| { |
| return !OidIsValid(fsysid); |
| } |
| |
| static int MAX_FILESPACE_PREFIX_LEN=128; |
| |
| static char * |
| EncodeFileLocations(char *fsysName, short rep, char *location) |
| { |
| /* local filesystem will store orig location */ |
| if (NULL == fsysName || pg_strcasecmp(fsysName, "local") == 0) |
| return location; |
| |
| /* otherwise, we need to encode location */ |
| char prefix[MAX_FILESPACE_PREFIX_LEN]; |
| char *writepos = prefix; |
| int writelen = 0; |
| int remainlen = MAX_FILESPACE_PREFIX_LEN; |
| int prefixlen = 0; |
| |
| /* for non local filesystem, we add protocol part, like 'hdfs://' */ |
| writelen = snprintf(writepos, remainlen, "%s://", fsysName); |
| if(writelen >= remainlen) |
| elog(ERROR, "internal error: filespace prefix too long \"%s\"", prefix); |
| writepos += writelen; |
| remainlen -= writelen; |
| |
| /* add options if needed. Options will be encoded like '{key=value,key=value}' */ |
| if (rep != FS_DEFAULT_REPLICA_NUM) |
| { |
| writelen = snprintf(writepos, remainlen, "{"); |
| if(writelen >= remainlen) |
| elog(ERROR, "internal error: filespace prefix too long \"%s\"", prefix); |
| writepos += writelen; |
| remainlen -= writelen; |
| |
| if(rep != FS_DEFAULT_REPLICA_NUM) |
| { |
| writelen = snprintf(writepos, remainlen, "replica=%d", rep); |
| if(writelen >= remainlen) |
| elog(ERROR, "internal error: filespace prefix too long \"%s\"", prefix); |
| writepos += writelen; |
| remainlen -= writelen; |
| } |
| |
| writelen = snprintf(writepos, remainlen, "}"); |
| if(writelen >= remainlen) |
| elog(ERROR, "internal error: filespace prefix too long \"%s\"", prefix); |
| writepos += writelen; |
| remainlen -= writelen; |
| } |
| |
| prefixlen = strlen(prefix); |
| if(prefixlen == 0) |
| return location; |
| |
| char *newlocation = NULL; |
| int reslen = prefixlen + strlen(location) + 1; |
| newlocation = palloc(reslen); |
| snprintf(newlocation, reslen, "%s%s", prefix, location); |
| location = newlocation; |
| |
| return location; |
| } |
| |
| /* |
| * Create a filespace |
| * |
| * Only superusers can create a filespace. 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 |
| */ |
| void |
| CreateFileSpace(CreateFileSpaceStmt *stmt) |
| { |
| Relation rel; |
| HeapTuple tuple; |
| NameData fsname; /* filespace name */ |
| Oid ownerId; /* OID of the OWNER of the filespace */ |
| Oid fsoid; /* OID of the created filespace */ |
| bool nulls[Natts_pg_filespace]; |
| Datum values[Natts_pg_filespace]; |
| bool enulls[Natts_pg_filespace_entry]; |
| Datum evalues[Natts_pg_filespace_entry]; |
| cqContext cqc; |
| cqContext *pcqCtx; |
| Oid fsysoid; /* OID of the filesystem type of this filespace */ |
| short fsrep; /* num of replication */ |
| ListCell *cell; |
| |
| if (Gp_role != GP_ROLE_DISPATCH) |
| elog(ERROR, "cannot create filespaces in utility mode"); |
| |
| /* Must be super user */ |
| if (!superuser()) |
| ereport(ERROR, |
| (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
| errmsg("permission denied to create filespace \"%s\"", |
| stmt->filespacename), |
| errhint("Must be superuser to create a filespace."))); |
| |
| /* However, the eventual owner of the filespace need not be */ |
| if (stmt->owner) |
| ownerId = get_roleid_checked(stmt->owner); |
| else |
| ownerId = GetUserId(); |
| |
| /* |
| * Disallow creation of filespaces named "pg_xxx"; we reserve this namespace |
| * for system purposes. |
| */ |
| if (!allowSystemTableModsDDL && IsReservedName(stmt->filespacename)) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_RESERVED_NAME), |
| errmsg("unacceptable filespace name \"%s\"", |
| stmt->filespacename), |
| errdetail("The prefix \"%s\" is reserved for system filespaces.", |
| GetReservedPrefix(stmt->filespacename)))); |
| } |
| namestrcpy(&fsname, stmt->filespacename); |
| |
| /* |
| * check filesystem on which this filespace is built on. |
| * InvalidOid for local filesystem |
| */ |
| fsysoid = InvalidOid; |
| if(stmt->fsysname && pg_strcasecmp(stmt->fsysname, "local") != 0) |
| { |
| /* |
| * get Oid of filesystem. if filesystem not found, |
| * LookupFileSystemOid will report error and exit |
| */ |
| fsysoid = LookupFileSystemOid(stmt->fsysname, false); |
| } |
| |
| /* |
| * get replication and option for filespace |
| */ |
| fsrep = FS_DEFAULT_REPLICA_NUM; |
| foreach(cell, stmt->options) |
| { |
| DefElem *defel = (DefElem *) lfirst(cell); |
| if (pg_strcasecmp(defel->defname, "NUMREPLICA") == 0) |
| { |
| int64 rep = defGetInt64(defel); |
| if(rep < 0 || rep >= FS_MAX_REPLICA_NUM) |
| ereport(ERROR, |
| (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), |
| errmsg("replica num "INT64_FORMAT" out of range", rep), |
| errdetail("Replica num should be in range [0, %d).", |
| FS_MAX_REPLICA_NUM))); |
| fsrep = (short)rep; |
| } |
| else |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("filesystem attribute \"%s\" not recognized", |
| defel->defname))); |
| } |
| } |
| |
| checkPathFormat(stmt->location, true); |
| if (false) |
| checkPathPermissions(stmt->location); |
| |
| /* |
| * Because rollback of filespace creation is unpleasant we prefer |
| * to ensure that we fully serialize CREATE FILESPACE operations. |
| * Therefore we take a big lock up-front. |
| * NOTE: AccessExclusiveLock, not RowExclusiveLock |
| */ |
| rel = heap_open(FileSpaceRelationId, AccessExclusiveLock); |
| |
| pcqCtx = |
| caql_beginscan( |
| caql_addrel(cqclr(&cqc), rel), |
| cql("INSERT INTO pg_filespace", |
| NULL)); |
| |
| /* Check that there is no other filespace by this name. */ |
| fsoid = get_filespace_oid(rel, stmt->filespacename); |
| if (OidIsValid(fsoid)) |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_OBJECT), |
| errmsg("filespace \"%s\" already exists", |
| stmt->filespacename))); |
| |
| /* The relation was opened up at the top of the function */ |
| Assert(rel); |
| |
| /* Insert tuple into pg_filespace */ |
| MemSet(nulls, false, sizeof(nulls)); |
| values[Anum_pg_filespace_fsname - 1] = NameGetDatum(&fsname); |
| values[Anum_pg_filespace_fsowner - 1] = ObjectIdGetDatum(ownerId); |
| values[Anum_pg_filespace_fsfsys - 1] = ObjectIdGetDatum(fsysoid); |
| values[Anum_pg_filespace_fsrep - 1] = Int16GetDatum(fsrep); |
| tuple = caql_form_tuple(pcqCtx, values, nulls); |
| |
| /* insert a new tuple */ |
| fsoid = caql_insert(pcqCtx, tuple); /* implicit update of index as well */ |
| Assert(OidIsValid(fsoid)); |
| |
| heap_freetuple(tuple); |
| |
| /* Record dependency on owner */ |
| recordDependencyOnOwner(FileSpaceRelationId, fsoid, ownerId); |
| |
| /* Keep the lock until commit/abort */ |
| caql_endscan(pcqCtx); |
| heap_close(rel, NoLock); |
| |
| rel = heap_open(FileSpaceEntryRelationId, RowExclusiveLock); |
| MemSet(enulls, false, sizeof(enulls)); |
| evalues[Anum_pg_filespace_entry_fsefsoid - 1] = ObjectIdGetDatum(fsoid); |
| |
| /* file system protocol enconding */ |
| if(strstr(stmt->location, "://") |
| || strstr(stmt->location, "{") |
| || strstr(stmt->location, "}")) |
| ereport(ERROR, |
| (errcode(ERRCODE_GP_COMMAND_ERROR), |
| errmsg("filespace location cannot contain \"://\" or any of these characters: \"{}\"" |
| "location:%s", stmt->location))); |
| |
| char *encoded = NULL; |
| encoded = EncodeFileLocations(stmt->fsysname, fsrep, stmt->location); |
| |
| bool existed; |
| if (HdfsPathExistAndNonEmpty(encoded, &existed, true)) /* skip hdfs trash directory */ |
| ereport(ERROR, |
| (errcode_for_file_access(), |
| errmsg("%s: File exists and non empty", encoded))); |
| add_catalog_filespace_entry(rel, fsoid, 0, encoded); |
| |
| heap_close(rel, RowExclusiveLock); |
| |
| /* MPP-6929: metadata tracking */ |
| MetaTrackAddObject(FileSpaceRelationId, |
| fsoid, |
| GetUserId(), |
| "CREATE", "FILESPACE" |
| ); |
| |
| /* Let the Mirrored File IO interfaces see our change to the catalog. */ |
| CommandCounterIncrement(); |
| |
| /* |
| * Update the gp_persistent_filespace_node table. |
| * |
| * The persistent object layer is responsible for ensuring that the |
| * directories are created and maintained in the filesystem. Most |
| * importantly this layer knows how to cleanup filesystem objects in the |
| * event that this transaction aborts and the rollback and recovery |
| * mechanisms know how to use this to cleanup after a hard failure or |
| * replay the creation for mirror resynchronisation. |
| */ |
| MirroredFileSysObj_TransactionCreateFilespaceDir(fsoid, encoded, !existed); |
| } |
| |
| /* |
| * Drop a filespace |
| * |
| * Be careful to check that the filespace is empty. |
| */ |
| void |
| RemoveFileSpace(List *names, DropBehavior behavior, bool missing_ok) |
| { |
| Relation rel; |
| char *fsname; |
| Oid fsoid; |
| ObjectAddress object; |
| bool shareStorage; |
| /* |
| * General DROP (object) syntax allows fully qualified names, but |
| * filespaces are global objects that do not live in schemas, so |
| * it is a syntax error if a fully qualified name was given. |
| */ |
| if (list_length(names) != 1) |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("filespace name may not be qualified"))); |
| fsname = strVal(linitial(names)); |
| |
| /* Disallow CASCADE */ |
| if (behavior == DROP_CASCADE) |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("syntax at or near \"cascade\""))); |
| |
| /* |
| * Because rollback of filespace operations are difficult and expected |
| * usage is anticipated to be light we remove concurency worries by |
| * taking a big lock up front. |
| */ |
| rel = heap_open(FileSpaceRelationId, AccessExclusiveLock); |
| |
| /* Lookup the name in pg_filespace */ |
| fsoid = get_filespace_oid(rel, fsname); |
| if (!OidIsValid(fsoid)) |
| { |
| heap_close(rel, AccessExclusiveLock); |
| |
| if (missing_ok) |
| { |
| ereport(NOTICE, |
| (errmsg("filespace \"%s\" does not exist, skipping", |
| fsname))); |
| return; |
| } |
| else |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("filespace \"%s\" does not exist", fsname))); |
| } |
| } |
| |
| /* Must be owner */ |
| if (!pg_filespace_ownercheck(fsoid, GetUserId())) |
| aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FILESPACE, fsname); |
| |
| /* Disallow drop of the standard filespaces, even by superuser */ |
| if (fsoid == SYSTEMFILESPACE_OID || strcmp(fsname, "dfs_system") == 0) |
| ereport(ERROR, |
| (errmsg("cannot drop filespace %s because it is required " |
| "by the database system", fsname))); |
| |
| /* |
| * performDeletion only drops things that have dependencies in |
| * pg_depend/pg_shdepend which does NOT include dependencies on tablespaces |
| * (perhaps pg_shdepend should). So we look for these dependencies by |
| * looking at the pg_tablespace table. |
| */ |
| filespace_check_empty(fsoid); |
| shareStorage = is_filespace_shared(fsoid); |
| |
| /* Check for dependencies and remove the filespace */ |
| object.classId = FileSpaceRelationId; |
| object.objectId = fsoid; |
| object.objectSubId = 0; |
| performDeletion(&object, DROP_RESTRICT); |
| |
| /* |
| * Remove any comments on this filespace |
| */ |
| DeleteSharedComments(fsoid, FileSpaceRelationId); |
| |
| /* |
| * Keep the lock until commit/abort |
| */ |
| heap_close(rel, NoLock); |
| |
| DeleteFilespaceEntryTuples(fsoid); |
| |
| /* MPP-6929: metadata tracking */ |
| MetaTrackDropObject(FileSpaceRelationId, |
| fsoid); |
| |
| /* |
| * The persistent object layer is responsible for actually managing the |
| * actual directory on disk. Tell it that this filespace is removed by |
| * this transaciton. This marks the filespace as pending delete and it |
| * will be deleted iff the transaction commits. |
| */ |
| MirroredFileSysObj_ScheduleDropFilespaceDir(fsoid, shareStorage); |
| } |
| |
| /* |
| * RemoveFileSpaceById |
| * Guts of Filespace Deletion, called by dependency.c |
| */ |
| void |
| RemoveFileSpaceById(Oid fsoid) |
| { |
| int numDel; |
| |
| numDel = caql_getcount( |
| NULL, |
| cql("DELETE FROM pg_filespace " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(fsoid))); |
| |
| if (numDel != 1) /* shouldn't happen */ |
| elog(ERROR, "cache lookup failed for filespace %u", fsoid); |
| } |
| |
| /* Return list of db_ids for each path. */ |
| static void |
| DeleteFilespaceEntryTuples(Oid fsoid) |
| { |
| int numDel; |
| |
| numDel = caql_getcount( |
| NULL, |
| cql("DELETE FROM pg_filespace_entry " |
| " WHERE fsefsoid = :1 ", |
| ObjectIdGetDatum(fsoid))); |
| } |
| |
| |
| /* |
| * Change filespace owner |
| */ |
| void |
| AlterFileSpaceOwner(List *names, Oid newOwnerId) |
| { |
| char *fsname; |
| Oid fsoid; |
| Relation rel; |
| Form_pg_filespace fsForm; |
| HeapTuple tup; |
| cqContext cqc; |
| cqContext *pcqCtx; |
| |
| /* |
| * This was from a generic AltrStmt node which allows for fully qualified |
| * object names, but filespaces don't exist inside schemas so fully |
| * qualified names are a syntax error. |
| */ |
| if (list_length(names) != 1) |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("filespace name may not be qualified"))); |
| fsname = strVal(linitial(names)); |
| |
| /* Search pg_filespace */ |
| rel = heap_open(FileSpaceRelationId, RowExclusiveLock); |
| |
| pcqCtx = caql_addrel(cqclr(&cqc), rel); |
| |
| tup = caql_getfirst( |
| pcqCtx, |
| cql("SELECT * FROM pg_filespace " |
| " WHERE fsname = :1 " |
| " FOR UPDATE ", |
| CStringGetDatum(fsname))); |
| |
| if (!HeapTupleIsValid(tup)) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("filespace \"%s\" does not exist", fsname))); |
| fsoid = HeapTupleGetOid(tup); |
| fsForm = (Form_pg_filespace) GETSTRUCT(tup); |
| |
| /* Cannot alter system filespaces */ |
| if (!allowSystemTableModsDDL && IsReservedName(fsname)) |
| ereport(ERROR, |
| (errcode(ERRCODE_RESERVED_NAME), |
| errmsg("permission denied: \"%s\" is a system filespace", |
| fsname))); |
| |
| /* |
| * If the new owner is the same as the existing owner, consider the |
| * command to have succeeded. This is for dump restoration purposes. |
| */ |
| if (fsForm->fsowner != newOwnerId) |
| { |
| Datum values[Natts_pg_filespace]; |
| bool nulls[Natts_pg_filespace]; |
| bool replace[Natts_pg_filespace]; |
| HeapTuple newtuple; |
| TupleDesc tupdesc; |
| |
| /* Otherwise, must be owner of the existing object */ |
| if (!pg_filespace_ownercheck(fsoid, GetUserId())) |
| aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FILESPACE, fsname); |
| |
| /* Must be able to become new owner */ |
| check_is_member_of_role(GetUserId(), newOwnerId); |
| |
| /* |
| * Normally we would also check for create permissions here, but there |
| * are none for filespaces so we follow what rename filespace does |
| * and omit the create permissions check. |
| * |
| * NOTE: Only superusers may create filespaces to begin with and so |
| * initially only a superuser would be able to change its ownership |
| * anyway. |
| */ |
| memset(nulls, false, sizeof(nulls)); |
| memset(replace, false, sizeof(replace)); |
| |
| replace[Anum_pg_filespace_fsowner - 1] = true; |
| values[Anum_pg_filespace_fsowner - 1] = ObjectIdGetDatum(newOwnerId); |
| |
| tupdesc = RelationGetDescr(rel); |
| newtuple = caql_modify_current(pcqCtx, values, nulls, replace); |
| |
| caql_update_current(pcqCtx, newtuple); |
| /* and Update indexes (implicit) */ |
| |
| /* MPP-6929: metadata tracking */ |
| if (Gp_role == GP_ROLE_DISPATCH) |
| MetaTrackUpdObject(FileSpaceRelationId, |
| fsoid, |
| GetUserId(), |
| "ALTER", "OWNER" |
| ); |
| |
| heap_freetuple(newtuple); |
| |
| /* Update owner dependency reference */ |
| changeDependencyOnOwner(FileSpaceRelationId, fsoid, newOwnerId); |
| } |
| |
| heap_close(rel, RowExclusiveLock); |
| } |
| |
| |
| /* |
| * Rename a filespace |
| */ |
| void |
| RenameFileSpace(const char *oldname, const char *newname) |
| { |
| Relation rel; |
| Oid fsoid; |
| HeapTuple newtuple; |
| cqContext cqc; |
| cqContext cqc2; |
| cqContext *pcqCtx; |
| int numFsname; |
| Form_pg_filespace newform; |
| |
| /* Search pg_filespace */ |
| rel = heap_open(FileSpaceRelationId, RowExclusiveLock); |
| |
| pcqCtx = caql_addrel(cqclr(&cqc), rel); |
| |
| newtuple = caql_getfirst( |
| pcqCtx, |
| cql("SELECT * FROM pg_filespace " |
| " WHERE fsname = :1 " |
| " FOR UPDATE ", |
| CStringGetDatum(oldname))); |
| if (!HeapTupleIsValid(newtuple)) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("filespace \"%s\" does not exist", |
| oldname))); |
| |
| newform = (Form_pg_filespace) GETSTRUCT(newtuple); |
| |
| /* Can't rename system filespaces */ |
| if (!allowSystemTableModsDDL && IsReservedName(oldname)) |
| ereport(ERROR, |
| (errcode(ERRCODE_RESERVED_NAME), |
| errmsg("permission denied: \"%s\" is a system filespace", |
| oldname))); |
| |
| /* Must be owner */ |
| fsoid = HeapTupleGetOid(newtuple); |
| if (!pg_filespace_ownercheck(fsoid, GetUserId())) |
| aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_FILESPACE, oldname); |
| |
| /* Validate new name */ |
| if (!allowSystemTableModsDDL && IsReservedName(newname)) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_RESERVED_NAME), |
| errmsg("unacceptable filespace name \"%s\"", newname), |
| errdetail("The prefix \"%s\" is reserved for system filespaces.", |
| GetReservedPrefix(newname)))); |
| } |
| |
| numFsname = caql_getcount( |
| caql_addrel(cqclr(&cqc2), rel), |
| cql("SELECT COUNT(*) FROM pg_filespace " |
| " WHERE fsname = :1 ", |
| CStringGetDatum(newname))); |
| |
| if (numFsname) |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_OBJECT), |
| errmsg("filespace \"%s\" already exists", newname))); |
| |
| /* OK, update the entry */ |
| namestrcpy(&(newform->fsname), newname); |
| |
| caql_update_current(pcqCtx, newtuple); |
| /* and Update indexes (implicit) */ |
| |
| /* MPP-6929: metadata tracking */ |
| if (Gp_role == GP_ROLE_DISPATCH) |
| MetaTrackUpdObject(FileSpaceRelationId, |
| fsoid, |
| GetUserId(), |
| "ALTER", "RENAME" |
| ); |
| |
| |
| heap_close(rel, RowExclusiveLock); |
| } |
| |
| |
| |
| /* |
| * get_filespace_name - given a filespace OID, look up the name |
| * |
| * Returns a palloc'd string, or NULL if no such filespace |
| */ |
| char * |
| get_filespace_name(Oid fsoid) |
| { |
| char *result; |
| |
| /* |
| * Search pg_filespace. We use a heapscan here even though there is an |
| * index on oid, on the theory that pg_filespace will usually have just a |
| * few entries and so an indexed lookup is a waste of effort. |
| */ |
| |
| result = caql_getcstring( |
| NULL, |
| cql("SELECT fsname FROM pg_filespace " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(fsoid))); |
| |
| /* We assume that there can be at most one matching tuple */ |
| |
| return result; |
| } |
| |
| /* |
| * is_filespace_shared - given a filespace oid, look up the shared. |
| */ |
| bool |
| is_filespace_shared(Oid fsoid) |
| { |
| Relation rel; |
| HeapScanDesc scandesc; |
| HeapTuple tuple; |
| TupleDesc tupledsc; |
| ScanKeyData entry[1]; |
| bool isnull; |
| Datum dfsysoid; |
| Oid fsysoid; |
| |
| /* |
| * Search pg_filespace. We use a heapscan here even though there is an |
| * index on oid, on the theory that pg_filespace will usually have just a |
| * few entries and so an indexed lookup is a waste of effort. |
| */ |
| rel = heap_open(FileSpaceRelationId, AccessShareLock); |
| tupledsc = RelationGetDescr(rel); |
| ScanKeyInit(&entry[0], |
| ObjectIdAttributeNumber, |
| BTEqualStrategyNumber, F_OIDEQ, |
| ObjectIdGetDatum(fsoid)); |
| scandesc = heap_beginscan(rel, SnapshotNow, 1, entry); |
| tuple = heap_getnext(scandesc, ForwardScanDirection); |
| |
| /* |
| * this case happens when master failed to create filespace. |
| * catalog already rollback |
| */ |
| if (!HeapTupleIsValid(tuple)) |
| { |
| heap_endscan(scandesc); |
| heap_close(rel, AccessShareLock); |
| |
| return fsoid != SYSTEMFILESPACE_OID; |
| } |
| |
| dfsysoid = heap_getattr(tuple, Anum_pg_filespace_fsfsys, tupledsc, &isnull); |
| fsysoid = (isnull ? InvalidOid : DatumGetObjectId(dfsysoid)); |
| |
| heap_endscan(scandesc); |
| heap_close(rel, AccessShareLock); |
| |
| return !isLocalFilesystem(fsysoid); |
| } |
| |
| |
| /* |
| * get_filespace_oid - given a filespace name, look up the OID |
| * |
| * Returns InvalidOid if filespace name not found. |
| */ |
| Oid |
| get_filespace_oid(Relation rel, const char *filespacename) |
| { |
| Oid result; |
| cqContext cqc; |
| |
| /* We assume that there can be at most one matching tuple */ |
| |
| result = caql_getoid( |
| caql_addrel(cqclr(&cqc), rel), |
| cql("SELECT oid FROM pg_filespace " |
| " WHERE fsname = :1 ", |
| CStringGetDatum(filespacename))); |
| |
| return result; |
| } |
| |
| /* |
| * checkPathFormat(path) |
| * |
| * Runs simple validations on a path supplied to CREATE FILESPACE: |
| * - Standardizes paths via canonicalize_path() |
| * - Disallow paths with single quotes |
| * - Disallow relative paths |
| * - Disallow paths that are too long. |
| * |
| * We have other checks to perform, but these are the only ones that we |
| * can run based only on the name without the local file system present. |
| */ |
| static void |
| checkPathFormat(char *path, bool url) |
| { |
| /* Unix-ify the offered path and strip any trailing slashes */ |
| if (!url) |
| canonicalize_path(path); |
| |
| /* disallow quotes, else CREATE DATABASE would be at risk */ |
| if (strchr(path, '\'')) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_NAME), |
| errmsg("filespace location \"%s\" " |
| "cannot contain single quotes", path))); |
| |
| /* |
| * Allowing relative paths seems risky |
| * |
| * this also helps us ensure that path is not empty or whitespace |
| */ |
| if (!url && !is_absolute_path(path)) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("filespace location \"%s\" " |
| "must be an absolute path", path))); |
| |
| /* |
| * Check that location isn't too long. |
| */ |
| if (strlen(path) >= MAX_FILESPACE_PATH) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("filespace location \"%s\" is too long", |
| path), |
| errhint("maximum length %d characters", |
| MAX_FILESPACE_PATH))); |
| } |
| |
| /* |
| * checkPathPermissions(path) |
| * |
| * Runs additional validations on a path supplied to CREATE FILESPACE. |
| * The assumption is that the path given to us is the original path |
| * that was specified in the gpfilespace command plus a path extension |
| * to uniquely identify this segment. We further assume that the original |
| * path exists, but that the segment extension does not and must be created. |
| * Or... if the extension path does exist then it must be an empty directory. |
| * |
| * We must: |
| * - Validate that the specified path does not exist |
| * - Validate that the parent directory exists |
| * - Validate that the parent is a directory. |
| * - Validate that the parent has apropriate permissions |
| * |
| * Note: Passing these checks does not guarantee that everything is good. |
| * In particular we have not checked anywhere that the paths are all |
| * unique on a given host. We omit this only because this is a difficult |
| * test when we don't have metadata about what segments are on the same host. |
| * |
| * If there is a conflict we should see it when we actually try to claim |
| * the directories for the segments. |
| * |
| * Note: May need to add additional checks that there is not a pending |
| * background delete on this directory location? |
| * |
| * Note: See FileRepMirror_Validation() in cdb/cdbfilerepmirror.c for the same |
| * checks run on the mirror side. |
| */ |
| static void |
| checkPathPermissions(char *path) |
| { |
| struct stat st; |
| char *parentdir; |
| |
| /* The specified path should not exist yet */ |
| if (stat(path, &st) >= 0) |
| { |
| ereport(ERROR, |
| (errcode_for_file_access(), |
| errmsg("%s: File exists", path))); |
| } |
| |
| /* Find the parent directory */ |
| parentdir = pstrdup(path); |
| get_parent_directory(parentdir); |
| |
| /* The parent directory must already exist */ |
| if (stat(parentdir, &st) < 0) |
| ereport(ERROR, |
| (errcode_for_file_access(), |
| errmsg("%s: No such file or directory", |
| parentdir))); |
| |
| /* The parent directory must be a directory */ |
| if (! S_ISDIR(st.st_mode)) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("%s: Not a directory", parentdir))); |
| |
| /* |
| * Check write permissions of the parent directory |
| * |
| * Note: Accornding to the BSD manual access shouldn't be used because it |
| * is a security hole, but what they are actually refering to is the fact |
| * that the permissions could change between the time of the check and the |
| * time an action is taken. This is primarily a courtousy check to produce |
| * a cleaner error message. If the filesystem should change between now |
| * and the actual mkdir() then the transaction will abort later with an |
| * uglier error message, but it is not actually a security hole. |
| */ |
| if (access(parentdir, W_OK|X_OK) < 0) |
| ereport(ERROR, |
| (errcode_for_file_access(), |
| errmsg("%s: Permission denied", path))); |
| } |
| |
| /* |
| * filespace_check_empty(fsoid): |
| * |
| * Checks the gp_persistent_tablespace_node table to determine if the specified |
| * filespace is empty. |
| */ |
| static void |
| filespace_check_empty(Oid fsoid) |
| { |
| if (caql_getcount( |
| NULL, |
| cql("SELECT COUNT(*) FROM pg_tablespace " |
| " WHERE spcfsoid = :1 ", |
| ObjectIdGetDatum(fsoid)))) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
| errmsg("filespace \"%s\" is not empty", |
| get_filespace_name(fsoid)))); |
| } |
| } |
| |
| /* Add a pg_filespace_entry for a given filespace definition. */ |
| void |
| add_catalog_filespace_entry(Relation rel, Oid fsoid, int16 dbid, char *location) |
| { |
| HeapTuple tuple; |
| Datum evalues[Natts_pg_filespace_entry]; |
| bool enulls[Natts_pg_filespace_entry]; |
| cqContext cqc; |
| cqContext *pcqCtx; |
| |
| /* NOTE: rel must have correct lock mode for INSERT */ |
| pcqCtx = |
| caql_beginscan( |
| caql_addrel(cqclr(&cqc), rel), |
| cql("INSERT INTO pg_filespace_entry", |
| NULL)); |
| |
| MemSet(enulls, false, sizeof(enulls)); |
| |
| evalues[Anum_pg_filespace_entry_fsefsoid - 1] = ObjectIdGetDatum(fsoid); |
| |
| evalues[Anum_pg_filespace_entry_fsedbid - 1] = Int16GetDatum(dbid); |
| evalues[Anum_pg_filespace_entry_fselocation - 1] = |
| DirectFunctionCall1(textin, CStringGetDatum(location)); |
| |
| tuple = caql_form_tuple(pcqCtx, evalues, enulls); |
| |
| /* insert a new tuple */ |
| caql_insert(pcqCtx, tuple); /* implicit update of index as well */ |
| |
| heap_freetuple(tuple); |
| caql_endscan(pcqCtx); |
| |
| } |
| |
| void |
| dbid_remove_filespace_entries(Relation rel, int16 dbid) |
| { |
| int numDel; |
| cqContext cqc; |
| |
| /* Use the index to scan only attributes of the target relation */ |
| numDel = caql_getcount( |
| caql_addrel(cqclr(&cqc), rel), |
| cql("DELETE FROM pg_filespace_entry " |
| " WHERE fsedbid = :1 ", |
| Int16GetDatum(dbid))); |
| } |
| |