static int pg_signal_backend(int pid, int sig) { PGPROC *proc = BackendPidGetProc(pid); /* * BackendPidGetProc returns NULL if the pid isn't valid; but by the time * we reach kill(), a process for which we get a valid proc here might * have terminated on its own. There's no way to acquire a lock on an * arbitrary process to prevent that. But since so far all the callers of * this mechanism involve some request for ending the process anyway, that * it might end on its own first is not a problem. */ if (proc == NULL) { /* * This is just a warning so a loop-through-resultset will not abort * if one backend terminated on its own during the run. */ ereport(WARNING, (errmsg("PID %d is not a PostgreSQL server process", pid))); return SIGNAL_BACKEND_ERROR; } /* Only allow superusers to signal superuser-owned backends. */ if (superuser_arg(proc->roleId) && !superuser()) return SIGNAL_BACKEND_NOSUPERUSER; /* Users can signal backends they have role membership in. */ if (!has_privs_of_role(GetUserId(), proc->roleId) && !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID)) return SIGNAL_BACKEND_NOPERMISSION; /* * Can the process we just validated above end, followed by the pid being * recycled for a new process, before reaching here? Then we'd be trying * to kill the wrong thing. Seems near impossible when sequential pid * assignment and wraparound is used. Perhaps it could happen on a system * where pid re-use is randomized. That race condition possibility seems * too unlikely to worry about. */ /* If we have setsid(), signal the backend's whole process group */ #ifdef HAVE_SETSID if (kill(-pid, sig)) #else if (kill(pid, sig)) #endif { /* Again, just a warning to allow loops */ ereport(WARNING, (errmsg("could not send signal to process %d: %m", pid))); return SIGNAL_BACKEND_ERROR; } return SIGNAL_BACKEND_SUCCESS; }
Datum pg_stat_get_backend_client_port(PG_FUNCTION_ARGS) { int32 beid = PG_GETARG_INT32(0); PgBackendStatus *beentry; SockAddr zero_clientaddr; char remote_port[NI_MAXSERV]; int ret; if ((beentry = pgstat_fetch_stat_beentry(beid)) == NULL) PG_RETURN_NULL(); if (!has_privs_of_role(GetUserId(), beentry->st_userid)) PG_RETURN_NULL(); /* A zeroed client addr means we don't know */ memset(&zero_clientaddr, 0, sizeof(zero_clientaddr)); if (memcmp(&(beentry->st_clientaddr), &zero_clientaddr, sizeof(zero_clientaddr)) == 0) PG_RETURN_NULL(); switch (beentry->st_clientaddr.addr.ss_family) { case AF_INET: #ifdef HAVE_IPV6 case AF_INET6: #endif break; case AF_UNIX: PG_RETURN_INT32(-1); default: PG_RETURN_NULL(); } remote_port[0] = '\0'; ret = pg_getnameinfo_all(&beentry->st_clientaddr.addr, beentry->st_clientaddr.salen, NULL, 0, remote_port, sizeof(remote_port), NI_NUMERICHOST | NI_NUMERICSERV); if (ret != 0) PG_RETURN_NULL(); PG_RETURN_DATUM(DirectFunctionCall1(int4in, CStringGetDatum(remote_port))); }
Datum pg_stat_get_backend_waiting(PG_FUNCTION_ARGS) { int32 beid = PG_GETARG_INT32(0); bool result; PgBackendStatus *beentry; if ((beentry = pgstat_fetch_stat_beentry(beid)) == NULL) PG_RETURN_NULL(); if (!has_privs_of_role(GetUserId(), beentry->st_userid)) PG_RETURN_NULL(); result = beentry->st_waiting; PG_RETURN_BOOL(result); }
Datum pg_stat_get_backend_activity(PG_FUNCTION_ARGS) { int32 beid = PG_GETARG_INT32(0); PgBackendStatus *beentry; const char *activity; if ((beentry = pgstat_fetch_stat_beentry(beid)) == NULL) activity = "<backend information not available>"; else if (!has_privs_of_role(GetUserId(), beentry->st_userid)) activity = "<insufficient privilege>"; else if (*(beentry->st_activity) == '\0') activity = "<command string not enabled>"; else activity = beentry->st_activity; PG_RETURN_TEXT_P(cstring_to_text(activity)); }
Datum pg_stat_get_backend_start(PG_FUNCTION_ARGS) { int32 beid = PG_GETARG_INT32(0); TimestampTz result; PgBackendStatus *beentry; if ((beentry = pgstat_fetch_stat_beentry(beid)) == NULL) PG_RETURN_NULL(); if (!has_privs_of_role(GetUserId(), beentry->st_userid)) PG_RETURN_NULL(); result = beentry->st_proc_start_timestamp; if (result == 0) /* probably can't happen? */ PG_RETURN_NULL(); PG_RETURN_TIMESTAMPTZ(result); }
Datum pg_stat_get_backend_wait_event_type(PG_FUNCTION_ARGS) { int32 beid = PG_GETARG_INT32(0); PgBackendStatus *beentry; PGPROC *proc; const char *wait_event_type; if ((beentry = pgstat_fetch_stat_beentry(beid)) == NULL) wait_event_type = "<backend information not available>"; else if (!has_privs_of_role(GetUserId(), beentry->st_userid)) wait_event_type = "<insufficient privilege>"; else { proc = BackendPidGetProc(beentry->st_procpid); wait_event_type = pgstat_get_wait_event_type(proc->wait_event_info); } if (!wait_event_type) PG_RETURN_NULL(); PG_RETURN_TEXT_P(cstring_to_text(wait_event_type)); }
Datum pg_stat_get_backend_activity_start(PG_FUNCTION_ARGS) { int32 beid = PG_GETARG_INT32(0); TimestampTz result; PgBackendStatus *beentry; if ((beentry = pgstat_fetch_stat_beentry(beid)) == NULL) PG_RETURN_NULL(); if (!has_privs_of_role(GetUserId(), beentry->st_userid)) PG_RETURN_NULL(); result = beentry->st_activity_start_timestamp; /* * No time recorded for start of current query -- this is the case if the * user hasn't enabled query-level stats collection. */ if (result == 0) PG_RETURN_NULL(); PG_RETURN_TIMESTAMPTZ(result); }
/* * CommentObject -- * * This routine is used to add the associated comment into * pg_description for the object specified by the given SQL command. */ void CommentObject(CommentStmt *stmt) { ObjectAddress address; Relation relation; /* * When loading a dump, we may see a COMMENT ON DATABASE for the old name * of the database. Erroring out would prevent pg_restore from completing * (which is really pg_restore's fault, but for now we will work around * the problem here). Consensus is that the best fix is to treat wrong * database name as a WARNING not an ERROR; hence, the following special * case. (If the length of stmt->objname is not 1, get_object_address will * throw an error below; that's OK.) */ if (stmt->objtype == OBJECT_DATABASE && list_length(stmt->objname) == 1) { char *database = strVal(linitial(stmt->objname)); if (!OidIsValid(get_database_oid(database, true))) { ereport(WARNING, (errcode(ERRCODE_UNDEFINED_DATABASE), errmsg("database \"%s\" does not exist", database))); return; } } /* * Translate the parser representation which identifies this object into * an ObjectAddress. get_object_address() will throw an error if the * object does not exist, and will also acquire a lock on the target * to guard against concurrent DROP operations. */ address = get_object_address(stmt->objtype, stmt->objname, stmt->objargs, &relation, ShareUpdateExclusiveLock); /* Privilege and integrity checks. */ switch (stmt->objtype) { case OBJECT_INDEX: case OBJECT_SEQUENCE: case OBJECT_TABLE: case OBJECT_VIEW: if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, RelationGetRelationName(relation)); break; case OBJECT_COLUMN: CheckAttributeComment(relation); break; case OBJECT_DATABASE: if (!pg_database_ownercheck(address.objectId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE, strVal(linitial(stmt->objname))); break; case OBJECT_TYPE: if (!pg_type_ownercheck(address.objectId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TYPE, format_type_be(address.objectId)); break; case OBJECT_AGGREGATE: case OBJECT_FUNCTION: if (!pg_proc_ownercheck(address.objectId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, NameListToString(stmt->objname)); break; case OBJECT_OPERATOR: if (!pg_oper_ownercheck(address.objectId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPER, NameListToString(stmt->objname)); break; case OBJECT_RULE: case OBJECT_TRIGGER: case OBJECT_CONSTRAINT: if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, RelationGetRelationName(relation)); break; case OBJECT_SCHEMA: if (!pg_namespace_ownercheck(address.objectId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_NAMESPACE, strVal(linitial(stmt->objname))); break; case OBJECT_CONVERSION: if (!pg_conversion_ownercheck(address.objectId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CONVERSION, NameListToString(stmt->objname)); break; case OBJECT_LANGUAGE: if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to comment on procedural language"))); break; case OBJECT_OPCLASS: if (!pg_opclass_ownercheck(address.objectId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPCLASS, NameListToString(stmt->objname)); break; case OBJECT_OPFAMILY: if (!pg_opfamily_ownercheck(address.objectId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPFAMILY, NameListToString(stmt->objname)); break; case OBJECT_LARGEOBJECT: if (!lo_compat_privileges && !pg_largeobject_ownercheck(address.objectId, GetUserId())) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be owner of large object %u", address.objectId))); break; case OBJECT_CAST: CheckCastComment(stmt->objname, stmt->objargs); break; case OBJECT_TABLESPACE: if (!pg_tablespace_ownercheck(address.objectId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TABLESPACE, strVal(linitial(stmt->objname))); break; case OBJECT_ROLE: if (!has_privs_of_role(GetUserId(), address.objectId)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be member of role \"%s\" to comment upon it", strVal(linitial(stmt->objname))))); break; case OBJECT_TSPARSER: if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to comment on text search parser"))); break; case OBJECT_TSDICTIONARY: if (!pg_ts_dict_ownercheck(address.objectId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY, NameListToString(stmt->objname)); break; case OBJECT_TSTEMPLATE: if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to comment on text search template"))); break; case OBJECT_TSCONFIGURATION: if (!pg_ts_config_ownercheck(address.objectId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION, NameListToString(stmt->objname)); break; default: elog(ERROR, "unrecognized object type: %d", (int) stmt->objtype); } /* * Databases, tablespaces, and roles are cluster-wide objects, so any * comments on those objects are recorded in the shared pg_shdescription * catalog. Comments on all other objects are recorded in pg_description. */ if (stmt->objtype == OBJECT_DATABASE || stmt->objtype == OBJECT_TABLESPACE || stmt->objtype == OBJECT_ROLE) CreateSharedComments(address.objectId, address.classId, stmt->comment); else CreateComments(address.objectId, address.classId, address.objectSubId, stmt->comment); /* * If get_object_address() opened the relation for us, we close it to keep * the reference count correct - but we retain any locks acquired by * get_object_address() until commit time, to guard against concurrent * activity. */ if (relation != NULL) relation_close(relation, NoLock); }
/* * Returns activity of PG backends. */ Datum pg_stat_get_activity(PG_FUNCTION_ARGS) { #define PG_STAT_GET_ACTIVITY_COLS 22 int num_backends = pgstat_fetch_stat_numbackends(); int curr_backend; int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; TupleDesc tupdesc; Tuplestorestate *tupstore; MemoryContext per_query_ctx; MemoryContext oldcontext; /* check to see if caller supports us returning a tuplestore */ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("set-valued function called in context that cannot accept a set"))); if (!(rsinfo->allowedModes & SFRM_Materialize)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("materialize mode required, but it is not " \ "allowed in this context"))); /* Build a tuple descriptor for our result type */ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) elog(ERROR, "return type must be a row type"); per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldcontext = MemoryContextSwitchTo(per_query_ctx); tupstore = tuplestore_begin_heap(true, false, work_mem); rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = tupstore; rsinfo->setDesc = tupdesc; MemoryContextSwitchTo(oldcontext); /* 1-based index */ for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) { /* for each row */ Datum values[PG_STAT_GET_ACTIVITY_COLS]; bool nulls[PG_STAT_GET_ACTIVITY_COLS]; LocalPgBackendStatus *local_beentry; PgBackendStatus *beentry; MemSet(values, 0, sizeof(values)); MemSet(nulls, 0, sizeof(nulls)); if (pid != -1) { /* Skip any which are not the one we're looking for. */ PgBackendStatus *be = pgstat_fetch_stat_beentry(curr_backend); if (!be || be->st_procpid != pid) continue; } /* Get the next one in the list */ local_beentry = pgstat_fetch_stat_local_beentry(curr_backend); if (!local_beentry) continue; beentry = &local_beentry->backendStatus; if (!beentry) { int i; for (i = 0; i < sizeof(nulls) / sizeof(nulls[0]); i++) nulls[i] = true; nulls[5] = false; values[5] = CStringGetTextDatum("<backend information not available>"); tuplestore_putvalues(tupstore, tupdesc, values, nulls); continue; } /* Values available to all callers */ values[0] = ObjectIdGetDatum(beentry->st_databaseid); values[1] = Int32GetDatum(beentry->st_procpid); values[2] = ObjectIdGetDatum(beentry->st_userid); if (beentry->st_appname) values[3] = CStringGetTextDatum(beentry->st_appname); else nulls[3] = true; if (TransactionIdIsValid(local_beentry->backend_xid)) values[14] = TransactionIdGetDatum(local_beentry->backend_xid); else nulls[14] = true; if (TransactionIdIsValid(local_beentry->backend_xmin)) values[15] = TransactionIdGetDatum(local_beentry->backend_xmin); else nulls[15] = true; if (beentry->st_ssl) { values[16] = BoolGetDatum(true); /* ssl */ values[17] = CStringGetTextDatum(beentry->st_sslstatus->ssl_version); values[18] = CStringGetTextDatum(beentry->st_sslstatus->ssl_cipher); values[19] = Int32GetDatum(beentry->st_sslstatus->ssl_bits); values[20] = BoolGetDatum(beentry->st_sslstatus->ssl_compression); values[21] = CStringGetTextDatum(beentry->st_sslstatus->ssl_clientdn); } else { values[16] = BoolGetDatum(false); /* ssl */ nulls[17] = nulls[18] = nulls[19] = nulls[20] = nulls[21] = true; } /* Values only available to role member */ if (has_privs_of_role(GetUserId(), beentry->st_userid)) { SockAddr zero_clientaddr; switch (beentry->st_state) { case STATE_IDLE: values[4] = CStringGetTextDatum("idle"); break; case STATE_RUNNING: values[4] = CStringGetTextDatum("active"); break; case STATE_IDLEINTRANSACTION: values[4] = CStringGetTextDatum("idle in transaction"); break; case STATE_FASTPATH: values[4] = CStringGetTextDatum("fastpath function call"); break; case STATE_IDLEINTRANSACTION_ABORTED: values[4] = CStringGetTextDatum("idle in transaction (aborted)"); break; case STATE_DISABLED: values[4] = CStringGetTextDatum("disabled"); break; case STATE_UNDEFINED: nulls[4] = true; break; } values[5] = CStringGetTextDatum(beentry->st_activity); values[6] = BoolGetDatum(beentry->st_waiting); if (beentry->st_xact_start_timestamp != 0) values[7] = TimestampTzGetDatum(beentry->st_xact_start_timestamp); else nulls[7] = true; if (beentry->st_activity_start_timestamp != 0) values[8] = TimestampTzGetDatum(beentry->st_activity_start_timestamp); else nulls[8] = true; if (beentry->st_proc_start_timestamp != 0) values[9] = TimestampTzGetDatum(beentry->st_proc_start_timestamp); else nulls[9] = true; if (beentry->st_state_start_timestamp != 0) values[10] = TimestampTzGetDatum(beentry->st_state_start_timestamp); else nulls[10] = true; /* A zeroed client addr means we don't know */ memset(&zero_clientaddr, 0, sizeof(zero_clientaddr)); if (memcmp(&(beentry->st_clientaddr), &zero_clientaddr, sizeof(zero_clientaddr)) == 0) { nulls[11] = true; nulls[12] = true; nulls[13] = true; } else { if (beentry->st_clientaddr.addr.ss_family == AF_INET #ifdef HAVE_IPV6 || beentry->st_clientaddr.addr.ss_family == AF_INET6 #endif ) { char remote_host[NI_MAXHOST]; char remote_port[NI_MAXSERV]; int ret; remote_host[0] = '\0'; remote_port[0] = '\0'; ret = pg_getnameinfo_all(&beentry->st_clientaddr.addr, beentry->st_clientaddr.salen, remote_host, sizeof(remote_host), remote_port, sizeof(remote_port), NI_NUMERICHOST | NI_NUMERICSERV); if (ret == 0) { clean_ipv6_addr(beentry->st_clientaddr.addr.ss_family, remote_host); values[11] = DirectFunctionCall1(inet_in, CStringGetDatum(remote_host)); if (beentry->st_clienthostname && beentry->st_clienthostname[0]) values[12] = CStringGetTextDatum(beentry->st_clienthostname); else nulls[12] = true; values[13] = Int32GetDatum(atoi(remote_port)); } else { nulls[11] = true; nulls[12] = true; nulls[13] = true; } } else if (beentry->st_clientaddr.addr.ss_family == AF_UNIX) { /* * Unix sockets always reports NULL for host and -1 for * port, so it's possible to tell the difference to * connections we have no permissions to view, or with * errors. */ nulls[11] = true; nulls[12] = true; values[13] = DatumGetInt32(-1); } else { /* Unknown address type, should never happen */ nulls[11] = true; nulls[12] = true; nulls[13] = true; } } } else { /* No permissions to view data about this session */ values[5] = CStringGetTextDatum("<insufficient privilege>"); nulls[4] = true; nulls[6] = true; nulls[7] = true; nulls[8] = true; nulls[9] = true; nulls[10] = true; nulls[11] = true; nulls[12] = true; nulls[13] = true; } tuplestore_putvalues(tupstore, tupdesc, values, nulls); /* If only a single backend was requested, and we found it, break. */ if (pid != -1) break; } /* clean up and return the tuplestore */ tuplestore_donestoring(tupstore); return (Datum) 0; }
/* * Returns command progress information for the named command. */ Datum pg_stat_get_progress_info(PG_FUNCTION_ARGS) { #define PG_STAT_GET_PROGRESS_COLS PGSTAT_NUM_PROGRESS_PARAM + 3 int num_backends = pgstat_fetch_stat_numbackends(); int curr_backend; char *cmd = text_to_cstring(PG_GETARG_TEXT_PP(0)); ProgressCommandType cmdtype; TupleDesc tupdesc; Tuplestorestate *tupstore; ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; MemoryContext per_query_ctx; MemoryContext oldcontext; /* check to see if caller supports us returning a tuplestore */ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("set-valued function called in context that cannot accept a set"))); if (!(rsinfo->allowedModes & SFRM_Materialize)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("materialize mode required, but it is not " \ "allowed in this context"))); /* Build a tuple descriptor for our result type */ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) elog(ERROR, "return type must be a row type"); /* Translate command name into command type code. */ if (pg_strcasecmp(cmd, "VACUUM") == 0) cmdtype = PROGRESS_COMMAND_VACUUM; else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid command name: \"%s\"", cmd))); per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldcontext = MemoryContextSwitchTo(per_query_ctx); tupstore = tuplestore_begin_heap(true, false, work_mem); rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = tupstore; rsinfo->setDesc = tupdesc; MemoryContextSwitchTo(oldcontext); /* 1-based index */ for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) { LocalPgBackendStatus *local_beentry; PgBackendStatus *beentry; Datum values[PG_STAT_GET_PROGRESS_COLS]; bool nulls[PG_STAT_GET_PROGRESS_COLS]; int i; MemSet(values, 0, sizeof(values)); MemSet(nulls, 0, sizeof(nulls)); local_beentry = pgstat_fetch_stat_local_beentry(curr_backend); if (!local_beentry) continue; beentry = &local_beentry->backendStatus; /* * Report values for only those backends which are running the given * command. */ if (!beentry || beentry->st_progress_command != cmdtype) continue; /* Value available to all callers */ values[0] = Int32GetDatum(beentry->st_procpid); values[1] = ObjectIdGetDatum(beentry->st_databaseid); /* show rest of the values including relid only to role members */ if (has_privs_of_role(GetUserId(), beentry->st_userid)) { values[2] = ObjectIdGetDatum(beentry->st_progress_command_target); for(i = 0; i < PGSTAT_NUM_PROGRESS_PARAM; i++) values[i+3] = UInt32GetDatum(beentry->st_progress_param[i]); } else { nulls[2] = true; for (i = 0; i < PGSTAT_NUM_PROGRESS_PARAM; i++) nulls[i+3] = true; } tuplestore_putvalues(tupstore, tupdesc, values, nulls); } /* clean up and return the tuplestore */ tuplestore_donestoring(tupstore); return (Datum) 0; }