1/*-------------------------------------------------------------------------
4 * Builtin functions for open/close/read/write operations on large objects
6 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
11 * src/backend/libpq/be-fsstubs.c
14 * This should be moved to a more appropriate place. It is here
15 * for lack of a better place.
17 * These functions store LargeObjectDesc structs in a private MemoryContext,
18 * which means that large object descriptors hang around until we destroy
19 * the context at transaction end. It'd be possible to prolong the lifetime
20 * of the context so that LO FDs are good across transactions (for example,
21 * we could release the context only if we see that no FDs remain open).
22 * But we'd need additional state in order to do the right thing at the
23 * end of an aborted transaction. FDs opened during an aborted xact would
24 * still need to be closed, since they might not be pointing at valid
25 * relations at all. Locking semantics are also an interesting problem
26 * if LOs stay open across transactions. For now, we'll stick with the
27 * existing documented semantics of LO FDs: they're only good within a
30 * As of PostgreSQL 8.0, much of the angst expressed above is no longer
31 * relevant, and in fact it'd be pretty easy to allow LO FDs to stay
32 * open across transactions. (Snapshot relevancy would still be an issue.)
33 * However backwards compatibility suggests that we should stick to the
36 *-------------------------------------------------------------------------
58/* define this to enable debug logging */
60/* chunk size for lo_import/lo_export transfers */
64 * LO "FD"s are indexes into the cookies array.
66 * A non-null entry is a pointer to a LargeObjectDesc allocated in the
67 * LO private memory context "fscxt". The cookies array itself is also
68 * dynamically allocated in that context. Its current allocated size is
69 * cookies_size entries, of which any unused entries will be NULL.
82/*****************************************************************************
83 * File Interfaces for Large Objects
84 *****************************************************************************/
102 * Allocate a large object descriptor first. This will also create
103 * 'fscxt' if this is the first LO opened in this transaction.
111 * We must register the snapshot in TopTransaction's resowner so that it
112 * stays alive until the LO is closed rather than until the current portal
132 (
errcode(ERRCODE_UNDEFINED_OBJECT),
133 errmsg(
"invalid large-object descriptor: %d",
fd)));
145/*****************************************************************************
146 * Bare Read/Write operations --- these are not fmgr-callable!
148 * We assume the large object supports byte oriented reads and seeks so
149 * that our work is easier.
151 *****************************************************************************/
161 (
errcode(ERRCODE_UNDEFINED_OBJECT),
162 errmsg(
"invalid large-object descriptor: %d",
fd)));
166 * Check state. inv_read() would throw an error anyway, but we want the
167 * error to be about the FD's state not the underlying privilege; it might
168 * be that the privilege exists but user forgot to ask for read mode.
172 (
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
173 errmsg(
"large object descriptor %d was not opened for reading",
189 (
errcode(ERRCODE_UNDEFINED_OBJECT),
190 errmsg(
"invalid large-object descriptor: %d",
fd)));
193 /* see comment in lo_read() */
196 (
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
197 errmsg(
"large object descriptor %d was not opened for writing",
215 (
errcode(ERRCODE_UNDEFINED_OBJECT),
216 errmsg(
"invalid large-object descriptor: %d",
fd)));
220 /* guard against result overflow */
221 if (status != (
int32) status)
223 (
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
224 errmsg(
"lo_lseek result out of range for large-object descriptor %d",
240 (
errcode(ERRCODE_UNDEFINED_OBJECT),
241 errmsg(
"invalid large-object descriptor: %d",
fd)));
282 (
errcode(ERRCODE_UNDEFINED_OBJECT),
283 errmsg(
"invalid large-object descriptor: %d",
fd)));
287 /* guard against result overflow */
288 if (offset != (
int32) offset)
290 (
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
291 errmsg(
"lo_tell result out of range for large-object descriptor %d",
305 (
errcode(ERRCODE_UNDEFINED_OBJECT),
306 errmsg(
"invalid large-object descriptor: %d",
fd)));
322 (
errcode(ERRCODE_UNDEFINED_OBJECT),
323 errmsg(
"large object %u does not exist", lobjId)));
326 * Must be owner of the large object. It would be cleaner to check this
327 * in inv_drop(), but we want to throw the error before not after closing
333 (
errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
334 errmsg(
"must be owner of large object %u", lobjId)));
337 * If there are any open LO FDs referencing that ID, close 'em.
351 * inv_drop does not create a need for end-of-transaction cleanup and
352 * hence we don't need to set lo_cleanup_needed.
357/*****************************************************************************
358 * Read/Write using bytea
359 *****************************************************************************/
394/*****************************************************************************
395 * Import/Export of Large Object
396 *****************************************************************************/
400 * imports a file as an (inversion) large object.
411 * lo_import_with_oid -
412 * imports a file as an (inversion) large object specifying oid.
437 * open the file to be read in
444 errmsg(
"could not open server file \"%s\": %m",
448 * create an inversion object
454 * read in from the filesystem and write to the inversion object
467 errmsg(
"could not read server file \"%s\": %m",
475 errmsg(
"could not close file \"%s\": %m",
483 * exports an (inversion) large object.
499 * open the inversion object (no need to test for failure)
505 * open the file to be written to
507 * Note: we reduce backend's normal 077 umask to the slightly friendlier
508 * 022. This code used to drop it all the way to 0, but creating
509 * world-writable export files doesn't seem wise.
526 errmsg(
"could not create server file \"%s\": %m",
530 * read in from the inversion file and write to the filesystem
538 errmsg(
"could not write server file \"%s\": %m",
545 errmsg(
"could not close file \"%s\": %m",
555 * truncate a large object to a specified length
564 (
errcode(ERRCODE_UNDEFINED_OBJECT),
565 errmsg(
"invalid large-object descriptor: %d",
fd)));
568 /* see comment in lo_read() */
571 (
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
572 errmsg(
"large object descriptor %d was not opened for writing",
603 * AtEOXact_LargeObject -
604 * prepares large objects for transaction commit
612 return;
/* no LO operations in this xact */
615 * Close LO fds and clear cookies array so that LO fds are no longer good.
616 * The memory context and resource owner holding them are going away at
617 * the end-of-transaction anyway, but on commit, we need to close them to
618 * avoid warnings about leaked resources at commit. On abort we can skip
630 /* Needn't actually pfree since we're about to zap context */
634 /* Release the LO memory context to prevent permanent memory leaks. */
639 /* Give inv_api.c a chance to clean up, too */
646 * AtEOSubXact_LargeObject
647 * Take care of large objects at subtransaction commit/abort
649 * Reassign LOs created/opened during a committing subtransaction
650 * to the parent subtransaction. On abort, just close them.
658 if (
fscxt == NULL)
/* no LO operations in this xact */
665 if (lo != NULL && lo->
subid == mySubid)
668 lo->
subid = parentSubid;
675/*****************************************************************************
676 * Support routines for this file
677 *****************************************************************************/
691 /* Try to find a free slot */
698 /* No free slot, so make the array bigger */
701 /* First time through, arbitrarily make 64-element array */
709 /* Double size of array */
726 * Make sure we do not try to free twice if this errors out for some
727 * reason. Better a leak than a crash.
738/*****************************************************************************
739 * Wrappers oriented toward SQL callers
740 *****************************************************************************/
743 * Read [offset, offset+nbytes) within LO; when nbytes is -1, read to end.
752 bytea *result = NULL;
758 * Compute number of bytes we'll actually read, accommodating nbytes == -1
759 * and reads beyond the end of the LO.
761 loSize =
inv_seek(loDesc, 0, SEEK_END);
764 if (nbytes >= 0 && nbytes <= loSize - offset)
765 result_length = nbytes;
/* request is wholly inside LO */
767 result_length = loSize - offset;
/* adjust to end of LO */
770 result_length = 0;
/* request is wholly outside LO */
773 * A result_length calculated from loSize may not fit in a size_t. Check
774 * that the size will satisfy this and subsequently-enforced size limits.
778 (
errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
779 errmsg(
"large object read request is too large")));
785 Assert(total_read == result_length);
808 * Read range within LO
820 (
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
821 errmsg(
"requested length cannot be negative")));
829 * Create LO with initial contents given by a bytea argument
852 * Update range within LO
bool object_ownercheck(Oid classid, Oid objectid, Oid roleid)
static bytea * lo_get_fragment_internal(Oid loOid, int64 offset, int32 nbytes)
Datum be_lo_lseek(PG_FUNCTION_ARGS)
Datum be_loread(PG_FUNCTION_ARGS)
static MemoryContext fscxt
Datum be_lo_get(PG_FUNCTION_ARGS)
static void closeLOfd(int fd)
Datum be_lo_export(PG_FUNCTION_ARGS)
void AtEOXact_LargeObject(bool isCommit)
Datum be_lo_unlink(PG_FUNCTION_ARGS)
Datum be_lo_import_with_oid(PG_FUNCTION_ARGS)
Datum be_lo_truncate64(PG_FUNCTION_ARGS)
Datum be_lo_lseek64(PG_FUNCTION_ARGS)
Datum be_lo_tell64(PG_FUNCTION_ARGS)
int lo_write(int fd, const char *buf, int len)
Datum be_lo_from_bytea(PG_FUNCTION_ARGS)
int lo_read(int fd, char *buf, int len)
static Oid lo_import_internal(text *filename, Oid lobjOid)
Datum be_lowrite(PG_FUNCTION_ARGS)
Datum be_lo_creat(PG_FUNCTION_ARGS)
Datum be_lo_put(PG_FUNCTION_ARGS)
Datum be_lo_truncate(PG_FUNCTION_ARGS)
Datum be_lo_create(PG_FUNCTION_ARGS)
static bool lo_cleanup_needed
Datum be_lo_tell(PG_FUNCTION_ARGS)
Datum be_lo_import(PG_FUNCTION_ARGS)
Datum be_lo_get_fragment(PG_FUNCTION_ARGS)
static LargeObjectDesc ** cookies
Datum be_lo_close(PG_FUNCTION_ARGS)
static void lo_truncate_internal(int32 fd, int64 len)
void AtEOSubXact_LargeObject(bool isCommit, SubTransactionId mySubid, SubTransactionId parentSubid)
Datum be_lo_open(PG_FUNCTION_ARGS)
#define PG_USED_FOR_ASSERTS_ONLY
int errcode_for_file_access(void)
int errcode(int sqlerrcode)
int errmsg(const char *fmt,...)
#define ereport(elevel,...)
int CloseTransientFile(int fd)
int OpenTransientFile(const char *fileName, int fileFlags)
int OpenTransientFilePerm(const char *fileName, int fileFlags, mode_t fileMode)
#define PG_GETARG_BYTEA_PP(n)
#define PG_GETARG_TEXT_PP(n)
#define PG_RETURN_BYTEA_P(x)
#define PG_RETURN_INT64(x)
#define PG_GETARG_INT64(n)
#define PG_RETURN_INT32(x)
#define PG_GETARG_INT32(n)
Assert(PointerIsAligned(start, uint64))
LargeObjectDesc * inv_open(Oid lobjId, int flags, MemoryContext mcxt)
void inv_truncate(LargeObjectDesc *obj_desc, int64 len)
int inv_read(LargeObjectDesc *obj_desc, char *buf, int nbytes)
Oid inv_create(Oid lobjId)
int64 inv_seek(LargeObjectDesc *obj_desc, int64 offset, int whence)
void close_lo_relation(bool isCommit)
int inv_write(LargeObjectDesc *obj_desc, const char *buf, int nbytes)
int64 inv_tell(LargeObjectDesc *obj_desc)
bool lo_compat_privileges
void inv_close(LargeObjectDesc *obj_desc)
void * MemoryContextAllocZero(MemoryContext context, Size size)
MemoryContext TopMemoryContext
MemoryContext CurrentMemoryContext
void MemoryContextDelete(MemoryContext context)
#define AllocSetContextCreate
#define ALLOCSET_DEFAULT_SIZES
#define repalloc0_array(pointer, type, oldcount, count)
static PgChecksumMode mode
bool LargeObjectExists(Oid loid)
static int fd(const char *x, int i)
ResourceOwner TopTransactionResourceOwner
void UnregisterSnapshotFromOwner(Snapshot snapshot, ResourceOwner owner)
Snapshot RegisterSnapshotOnOwner(Snapshot snapshot, ResourceOwner owner)
void PreventCommandIfReadOnly(const char *cmdname)
static Size VARSIZE_ANY_EXHDR(const void *PTR)
static char * VARDATA(const void *PTR)
static char * VARDATA_ANY(const void *PTR)
static void SET_VARSIZE(void *PTR, Size len)
void text_to_cstring_buffer(const text *src, char *dst, size_t dst_len)
SubTransactionId GetCurrentSubTransactionId(void)