diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 13:44:03 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 13:44:03 +0000 |
commit | 293913568e6a7a86fd1479e1cff8e2ecb58d6568 (patch) | |
tree | fc3b469a3ec5ab71b36ea97cc7aaddb838423a0c /src/backend/libpq/be-fsstubs.c | |
parent | Initial commit. (diff) | |
download | postgresql-16-293913568e6a7a86fd1479e1cff8e2ecb58d6568.tar.xz postgresql-16-293913568e6a7a86fd1479e1cff8e2ecb58d6568.zip |
Adding upstream version 16.2.upstream/16.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/backend/libpq/be-fsstubs.c')
-rw-r--r-- | src/backend/libpq/be-fsstubs.c | 880 |
1 files changed, 880 insertions, 0 deletions
diff --git a/src/backend/libpq/be-fsstubs.c b/src/backend/libpq/be-fsstubs.c new file mode 100644 index 0000000..230c657 --- /dev/null +++ b/src/backend/libpq/be-fsstubs.c @@ -0,0 +1,880 @@ +/*------------------------------------------------------------------------- + * + * be-fsstubs.c + * Builtin functions for open/close/read/write operations on large objects + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/libpq/be-fsstubs.c + * + * NOTES + * This should be moved to a more appropriate place. It is here + * for lack of a better place. + * + * These functions store LargeObjectDesc structs in a private MemoryContext, + * which means that large object descriptors hang around until we destroy + * the context at transaction end. It'd be possible to prolong the lifetime + * of the context so that LO FDs are good across transactions (for example, + * we could release the context only if we see that no FDs remain open). + * But we'd need additional state in order to do the right thing at the + * end of an aborted transaction. FDs opened during an aborted xact would + * still need to be closed, since they might not be pointing at valid + * relations at all. Locking semantics are also an interesting problem + * if LOs stay open across transactions. For now, we'll stick with the + * existing documented semantics of LO FDs: they're only good within a + * transaction. + * + * As of PostgreSQL 8.0, much of the angst expressed above is no longer + * relevant, and in fact it'd be pretty easy to allow LO FDs to stay + * open across transactions. (Snapshot relevancy would still be an issue.) + * However backwards compatibility suggests that we should stick to the + * status quo. + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include <fcntl.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "access/xact.h" +#include "catalog/pg_largeobject.h" +#include "libpq/be-fsstubs.h" +#include "libpq/libpq-fs.h" +#include "miscadmin.h" +#include "storage/fd.h" +#include "storage/large_object.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/memutils.h" +#include "utils/snapmgr.h" +#include "varatt.h" + +/* define this to enable debug logging */ +/* #define FSDB 1 */ +/* chunk size for lo_import/lo_export transfers */ +#define BUFSIZE 8192 + +/* + * LO "FD"s are indexes into the cookies array. + * + * A non-null entry is a pointer to a LargeObjectDesc allocated in the + * LO private memory context "fscxt". The cookies array itself is also + * dynamically allocated in that context. Its current allocated size is + * cookies_size entries, of which any unused entries will be NULL. + */ +static LargeObjectDesc **cookies = NULL; +static int cookies_size = 0; + +static bool lo_cleanup_needed = false; +static MemoryContext fscxt = NULL; + +static int newLOfd(void); +static void closeLOfd(int fd); +static Oid lo_import_internal(text *filename, Oid lobjOid); + + +/***************************************************************************** + * File Interfaces for Large Objects + *****************************************************************************/ + +Datum +be_lo_open(PG_FUNCTION_ARGS) +{ + Oid lobjId = PG_GETARG_OID(0); + int32 mode = PG_GETARG_INT32(1); + LargeObjectDesc *lobjDesc; + int fd; + +#ifdef FSDB + elog(DEBUG4, "lo_open(%u,%d)", lobjId, mode); +#endif + + if (mode & INV_WRITE) + PreventCommandIfReadOnly("lo_open(INV_WRITE)"); + + /* + * Allocate a large object descriptor first. This will also create + * 'fscxt' if this is the first LO opened in this transaction. + */ + fd = newLOfd(); + + lobjDesc = inv_open(lobjId, mode, fscxt); + lobjDesc->subid = GetCurrentSubTransactionId(); + + /* + * We must register the snapshot in TopTransaction's resowner so that it + * stays alive until the LO is closed rather than until the current portal + * shuts down. + */ + if (lobjDesc->snapshot) + lobjDesc->snapshot = RegisterSnapshotOnOwner(lobjDesc->snapshot, + TopTransactionResourceOwner); + + Assert(cookies[fd] == NULL); + cookies[fd] = lobjDesc; + + PG_RETURN_INT32(fd); +} + +Datum +be_lo_close(PG_FUNCTION_ARGS) +{ + int32 fd = PG_GETARG_INT32(0); + + if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("invalid large-object descriptor: %d", fd))); + +#ifdef FSDB + elog(DEBUG4, "lo_close(%d)", fd); +#endif + + closeLOfd(fd); + + PG_RETURN_INT32(0); +} + + +/***************************************************************************** + * Bare Read/Write operations --- these are not fmgr-callable! + * + * We assume the large object supports byte oriented reads and seeks so + * that our work is easier. + * + *****************************************************************************/ + +int +lo_read(int fd, char *buf, int len) +{ + int status; + LargeObjectDesc *lobj; + + if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("invalid large-object descriptor: %d", fd))); + lobj = cookies[fd]; + + /* + * Check state. inv_read() would throw an error anyway, but we want the + * error to be about the FD's state not the underlying privilege; it might + * be that the privilege exists but user forgot to ask for read mode. + */ + if ((lobj->flags & IFS_RDLOCK) == 0) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("large object descriptor %d was not opened for reading", + fd))); + + status = inv_read(lobj, buf, len); + + return status; +} + +int +lo_write(int fd, const char *buf, int len) +{ + int status; + LargeObjectDesc *lobj; + + if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("invalid large-object descriptor: %d", fd))); + lobj = cookies[fd]; + + /* see comment in lo_read() */ + if ((lobj->flags & IFS_WRLOCK) == 0) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("large object descriptor %d was not opened for writing", + fd))); + + status = inv_write(lobj, buf, len); + + return status; +} + +Datum +be_lo_lseek(PG_FUNCTION_ARGS) +{ + int32 fd = PG_GETARG_INT32(0); + int32 offset = PG_GETARG_INT32(1); + int32 whence = PG_GETARG_INT32(2); + int64 status; + + if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("invalid large-object descriptor: %d", fd))); + + status = inv_seek(cookies[fd], offset, whence); + + /* guard against result overflow */ + if (status != (int32) status) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("lo_lseek result out of range for large-object descriptor %d", + fd))); + + PG_RETURN_INT32((int32) status); +} + +Datum +be_lo_lseek64(PG_FUNCTION_ARGS) +{ + int32 fd = PG_GETARG_INT32(0); + int64 offset = PG_GETARG_INT64(1); + int32 whence = PG_GETARG_INT32(2); + int64 status; + + if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("invalid large-object descriptor: %d", fd))); + + status = inv_seek(cookies[fd], offset, whence); + + PG_RETURN_INT64(status); +} + +Datum +be_lo_creat(PG_FUNCTION_ARGS) +{ + Oid lobjId; + + PreventCommandIfReadOnly("lo_creat()"); + + lo_cleanup_needed = true; + lobjId = inv_create(InvalidOid); + + PG_RETURN_OID(lobjId); +} + +Datum +be_lo_create(PG_FUNCTION_ARGS) +{ + Oid lobjId = PG_GETARG_OID(0); + + PreventCommandIfReadOnly("lo_create()"); + + lo_cleanup_needed = true; + lobjId = inv_create(lobjId); + + PG_RETURN_OID(lobjId); +} + +Datum +be_lo_tell(PG_FUNCTION_ARGS) +{ + int32 fd = PG_GETARG_INT32(0); + int64 offset; + + if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("invalid large-object descriptor: %d", fd))); + + offset = inv_tell(cookies[fd]); + + /* guard against result overflow */ + if (offset != (int32) offset) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("lo_tell result out of range for large-object descriptor %d", + fd))); + + PG_RETURN_INT32((int32) offset); +} + +Datum +be_lo_tell64(PG_FUNCTION_ARGS) +{ + int32 fd = PG_GETARG_INT32(0); + int64 offset; + + if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("invalid large-object descriptor: %d", fd))); + + offset = inv_tell(cookies[fd]); + + PG_RETURN_INT64(offset); +} + +Datum +be_lo_unlink(PG_FUNCTION_ARGS) +{ + Oid lobjId = PG_GETARG_OID(0); + + PreventCommandIfReadOnly("lo_unlink()"); + + /* + * Must be owner of the large object. It would be cleaner to check this + * in inv_drop(), but we want to throw the error before not after closing + * relevant FDs. + */ + if (!lo_compat_privileges && + !object_ownercheck(LargeObjectRelationId, lobjId, GetUserId())) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be owner of large object %u", lobjId))); + + /* + * If there are any open LO FDs referencing that ID, close 'em. + */ + if (fscxt != NULL) + { + int i; + + for (i = 0; i < cookies_size; i++) + { + if (cookies[i] != NULL && cookies[i]->id == lobjId) + closeLOfd(i); + } + } + + /* + * inv_drop does not create a need for end-of-transaction cleanup and + * hence we don't need to set lo_cleanup_needed. + */ + PG_RETURN_INT32(inv_drop(lobjId)); +} + +/***************************************************************************** + * Read/Write using bytea + *****************************************************************************/ + +Datum +be_loread(PG_FUNCTION_ARGS) +{ + int32 fd = PG_GETARG_INT32(0); + int32 len = PG_GETARG_INT32(1); + bytea *retval; + int totalread; + + if (len < 0) + len = 0; + + retval = (bytea *) palloc(VARHDRSZ + len); + totalread = lo_read(fd, VARDATA(retval), len); + SET_VARSIZE(retval, totalread + VARHDRSZ); + + PG_RETURN_BYTEA_P(retval); +} + +Datum +be_lowrite(PG_FUNCTION_ARGS) +{ + int32 fd = PG_GETARG_INT32(0); + bytea *wbuf = PG_GETARG_BYTEA_PP(1); + int bytestowrite; + int totalwritten; + + PreventCommandIfReadOnly("lowrite()"); + + bytestowrite = VARSIZE_ANY_EXHDR(wbuf); + totalwritten = lo_write(fd, VARDATA_ANY(wbuf), bytestowrite); + PG_RETURN_INT32(totalwritten); +} + +/***************************************************************************** + * Import/Export of Large Object + *****************************************************************************/ + +/* + * lo_import - + * imports a file as an (inversion) large object. + */ +Datum +be_lo_import(PG_FUNCTION_ARGS) +{ + text *filename = PG_GETARG_TEXT_PP(0); + + PG_RETURN_OID(lo_import_internal(filename, InvalidOid)); +} + +/* + * lo_import_with_oid - + * imports a file as an (inversion) large object specifying oid. + */ +Datum +be_lo_import_with_oid(PG_FUNCTION_ARGS) +{ + text *filename = PG_GETARG_TEXT_PP(0); + Oid oid = PG_GETARG_OID(1); + + PG_RETURN_OID(lo_import_internal(filename, oid)); +} + +static Oid +lo_import_internal(text *filename, Oid lobjOid) +{ + int fd; + int nbytes, + tmp PG_USED_FOR_ASSERTS_ONLY; + char buf[BUFSIZE]; + char fnamebuf[MAXPGPATH]; + LargeObjectDesc *lobj; + Oid oid; + + PreventCommandIfReadOnly("lo_import()"); + + /* + * open the file to be read in + */ + text_to_cstring_buffer(filename, fnamebuf, sizeof(fnamebuf)); + fd = OpenTransientFile(fnamebuf, O_RDONLY | PG_BINARY); + if (fd < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open server file \"%s\": %m", + fnamebuf))); + + /* + * create an inversion object + */ + lo_cleanup_needed = true; + oid = inv_create(lobjOid); + + /* + * read in from the filesystem and write to the inversion object + */ + lobj = inv_open(oid, INV_WRITE, CurrentMemoryContext); + + while ((nbytes = read(fd, buf, BUFSIZE)) > 0) + { + tmp = inv_write(lobj, buf, nbytes); + Assert(tmp == nbytes); + } + + if (nbytes < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read server file \"%s\": %m", + fnamebuf))); + + inv_close(lobj); + + if (CloseTransientFile(fd) != 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not close file \"%s\": %m", + fnamebuf))); + + return oid; +} + +/* + * lo_export - + * exports an (inversion) large object. + */ +Datum +be_lo_export(PG_FUNCTION_ARGS) +{ + Oid lobjId = PG_GETARG_OID(0); + text *filename = PG_GETARG_TEXT_PP(1); + int fd; + int nbytes, + tmp; + char buf[BUFSIZE]; + char fnamebuf[MAXPGPATH]; + LargeObjectDesc *lobj; + mode_t oumask; + + /* + * open the inversion object (no need to test for failure) + */ + lo_cleanup_needed = true; + lobj = inv_open(lobjId, INV_READ, CurrentMemoryContext); + + /* + * open the file to be written to + * + * Note: we reduce backend's normal 077 umask to the slightly friendlier + * 022. This code used to drop it all the way to 0, but creating + * world-writable export files doesn't seem wise. + */ + text_to_cstring_buffer(filename, fnamebuf, sizeof(fnamebuf)); + oumask = umask(S_IWGRP | S_IWOTH); + PG_TRY(); + { + fd = OpenTransientFilePerm(fnamebuf, O_CREAT | O_WRONLY | O_TRUNC | PG_BINARY, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + } + PG_FINALLY(); + { + umask(oumask); + } + PG_END_TRY(); + if (fd < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not create server file \"%s\": %m", + fnamebuf))); + + /* + * read in from the inversion file and write to the filesystem + */ + while ((nbytes = inv_read(lobj, buf, BUFSIZE)) > 0) + { + tmp = write(fd, buf, nbytes); + if (tmp != nbytes) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not write server file \"%s\": %m", + fnamebuf))); + } + + if (CloseTransientFile(fd) != 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not close file \"%s\": %m", + fnamebuf))); + + inv_close(lobj); + + PG_RETURN_INT32(1); +} + +/* + * lo_truncate - + * truncate a large object to a specified length + */ +static void +lo_truncate_internal(int32 fd, int64 len) +{ + LargeObjectDesc *lobj; + + if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("invalid large-object descriptor: %d", fd))); + lobj = cookies[fd]; + + /* see comment in lo_read() */ + if ((lobj->flags & IFS_WRLOCK) == 0) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("large object descriptor %d was not opened for writing", + fd))); + + inv_truncate(lobj, len); +} + +Datum +be_lo_truncate(PG_FUNCTION_ARGS) +{ + int32 fd = PG_GETARG_INT32(0); + int32 len = PG_GETARG_INT32(1); + + PreventCommandIfReadOnly("lo_truncate()"); + + lo_truncate_internal(fd, len); + PG_RETURN_INT32(0); +} + +Datum +be_lo_truncate64(PG_FUNCTION_ARGS) +{ + int32 fd = PG_GETARG_INT32(0); + int64 len = PG_GETARG_INT64(1); + + PreventCommandIfReadOnly("lo_truncate64()"); + + lo_truncate_internal(fd, len); + PG_RETURN_INT32(0); +} + +/* + * AtEOXact_LargeObject - + * prepares large objects for transaction commit + */ +void +AtEOXact_LargeObject(bool isCommit) +{ + int i; + + if (!lo_cleanup_needed) + return; /* no LO operations in this xact */ + + /* + * Close LO fds and clear cookies array so that LO fds are no longer good. + * The memory context and resource owner holding them are going away at + * the end-of-transaction anyway, but on commit, we need to close them to + * avoid warnings about leaked resources at commit. On abort we can skip + * this step. + */ + if (isCommit) + { + for (i = 0; i < cookies_size; i++) + { + if (cookies[i] != NULL) + closeLOfd(i); + } + } + + /* Needn't actually pfree since we're about to zap context */ + cookies = NULL; + cookies_size = 0; + + /* Release the LO memory context to prevent permanent memory leaks. */ + if (fscxt) + MemoryContextDelete(fscxt); + fscxt = NULL; + + /* Give inv_api.c a chance to clean up, too */ + close_lo_relation(isCommit); + + lo_cleanup_needed = false; +} + +/* + * AtEOSubXact_LargeObject + * Take care of large objects at subtransaction commit/abort + * + * Reassign LOs created/opened during a committing subtransaction + * to the parent subtransaction. On abort, just close them. + */ +void +AtEOSubXact_LargeObject(bool isCommit, SubTransactionId mySubid, + SubTransactionId parentSubid) +{ + int i; + + if (fscxt == NULL) /* no LO operations in this xact */ + return; + + for (i = 0; i < cookies_size; i++) + { + LargeObjectDesc *lo = cookies[i]; + + if (lo != NULL && lo->subid == mySubid) + { + if (isCommit) + lo->subid = parentSubid; + else + closeLOfd(i); + } + } +} + +/***************************************************************************** + * Support routines for this file + *****************************************************************************/ + +static int +newLOfd(void) +{ + int i, + newsize; + + lo_cleanup_needed = true; + if (fscxt == NULL) + fscxt = AllocSetContextCreate(TopMemoryContext, + "Filesystem", + ALLOCSET_DEFAULT_SIZES); + + /* Try to find a free slot */ + for (i = 0; i < cookies_size; i++) + { + if (cookies[i] == NULL) + return i; + } + + /* No free slot, so make the array bigger */ + if (cookies_size <= 0) + { + /* First time through, arbitrarily make 64-element array */ + i = 0; + newsize = 64; + cookies = (LargeObjectDesc **) + MemoryContextAllocZero(fscxt, newsize * sizeof(LargeObjectDesc *)); + } + else + { + /* Double size of array */ + i = cookies_size; + newsize = cookies_size * 2; + cookies = + repalloc0_array(cookies, LargeObjectDesc *, cookies_size, newsize); + } + cookies_size = newsize; + + return i; +} + +static void +closeLOfd(int fd) +{ + LargeObjectDesc *lobj; + + /* + * Make sure we do not try to free twice if this errors out for some + * reason. Better a leak than a crash. + */ + lobj = cookies[fd]; + cookies[fd] = NULL; + + if (lobj->snapshot) + UnregisterSnapshotFromOwner(lobj->snapshot, + TopTransactionResourceOwner); + inv_close(lobj); +} + +/***************************************************************************** + * Wrappers oriented toward SQL callers + *****************************************************************************/ + +/* + * Read [offset, offset+nbytes) within LO; when nbytes is -1, read to end. + */ +static bytea * +lo_get_fragment_internal(Oid loOid, int64 offset, int32 nbytes) +{ + LargeObjectDesc *loDesc; + int64 loSize; + int64 result_length; + int total_read PG_USED_FOR_ASSERTS_ONLY; + bytea *result = NULL; + + lo_cleanup_needed = true; + loDesc = inv_open(loOid, INV_READ, CurrentMemoryContext); + + /* + * Compute number of bytes we'll actually read, accommodating nbytes == -1 + * and reads beyond the end of the LO. + */ + loSize = inv_seek(loDesc, 0, SEEK_END); + if (loSize > offset) + { + if (nbytes >= 0 && nbytes <= loSize - offset) + result_length = nbytes; /* request is wholly inside LO */ + else + result_length = loSize - offset; /* adjust to end of LO */ + } + else + result_length = 0; /* request is wholly outside LO */ + + /* + * A result_length calculated from loSize may not fit in a size_t. Check + * that the size will satisfy this and subsequently-enforced size limits. + */ + if (result_length > MaxAllocSize - VARHDRSZ) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("large object read request is too large"))); + + result = (bytea *) palloc(VARHDRSZ + result_length); + + inv_seek(loDesc, offset, SEEK_SET); + total_read = inv_read(loDesc, VARDATA(result), result_length); + Assert(total_read == result_length); + SET_VARSIZE(result, result_length + VARHDRSZ); + + inv_close(loDesc); + + return result; +} + +/* + * Read entire LO + */ +Datum +be_lo_get(PG_FUNCTION_ARGS) +{ + Oid loOid = PG_GETARG_OID(0); + bytea *result; + + result = lo_get_fragment_internal(loOid, 0, -1); + + PG_RETURN_BYTEA_P(result); +} + +/* + * Read range within LO + */ +Datum +be_lo_get_fragment(PG_FUNCTION_ARGS) +{ + Oid loOid = PG_GETARG_OID(0); + int64 offset = PG_GETARG_INT64(1); + int32 nbytes = PG_GETARG_INT32(2); + bytea *result; + + if (nbytes < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("requested length cannot be negative"))); + + result = lo_get_fragment_internal(loOid, offset, nbytes); + + PG_RETURN_BYTEA_P(result); +} + +/* + * Create LO with initial contents given by a bytea argument + */ +Datum +be_lo_from_bytea(PG_FUNCTION_ARGS) +{ + Oid loOid = PG_GETARG_OID(0); + bytea *str = PG_GETARG_BYTEA_PP(1); + LargeObjectDesc *loDesc; + int written PG_USED_FOR_ASSERTS_ONLY; + + PreventCommandIfReadOnly("lo_from_bytea()"); + + lo_cleanup_needed = true; + loOid = inv_create(loOid); + loDesc = inv_open(loOid, INV_WRITE, CurrentMemoryContext); + written = inv_write(loDesc, VARDATA_ANY(str), VARSIZE_ANY_EXHDR(str)); + Assert(written == VARSIZE_ANY_EXHDR(str)); + inv_close(loDesc); + + PG_RETURN_OID(loOid); +} + +/* + * Update range within LO + */ +Datum +be_lo_put(PG_FUNCTION_ARGS) +{ + Oid loOid = PG_GETARG_OID(0); + int64 offset = PG_GETARG_INT64(1); + bytea *str = PG_GETARG_BYTEA_PP(2); + LargeObjectDesc *loDesc; + int written PG_USED_FOR_ASSERTS_ONLY; + + PreventCommandIfReadOnly("lo_put()"); + + lo_cleanup_needed = true; + loDesc = inv_open(loOid, INV_WRITE, CurrentMemoryContext); + + /* Permission check */ + if (!lo_compat_privileges && + pg_largeobject_aclcheck_snapshot(loDesc->id, + GetUserId(), + ACL_UPDATE, + loDesc->snapshot) != ACLCHECK_OK) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied for large object %u", + loDesc->id))); + + inv_seek(loDesc, offset, SEEK_SET); + written = inv_write(loDesc, VARDATA_ANY(str), VARSIZE_ANY_EXHDR(str)); + Assert(written == VARSIZE_ANY_EXHDR(str)); + inv_close(loDesc); + + PG_RETURN_VOID(); +} |