1 /*-------------------------------------------------------------------------
2  *
3  * be-fsstubs.c
4  *	  Builtin functions for open/close/read/write operations on large objects
5  *
6  * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
7  * Portions Copyright (c) 1994, Regents of the University of California
8  *
9  *
10  * IDENTIFICATION
11  *	  src/backend/libpq/be-fsstubs.c
12  *
13  * NOTES
14  *	  This should be moved to a more appropriate place.  It is here
15  *	  for lack of a better place.
16  *
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
28  *	  transaction.
29  *
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
34  *	  status quo.
35  *
36  *-------------------------------------------------------------------------
37  */
38 
39 #include "postgres.h"
40 
41 #include <fcntl.h>
42 #include <sys/stat.h>
43 #include <unistd.h>
44 
45 #include "access/xact.h"
46 #include "libpq/be-fsstubs.h"
47 #include "libpq/libpq-fs.h"
48 #include "miscadmin.h"
49 #include "storage/fd.h"
50 #include "storage/large_object.h"
51 #include "utils/acl.h"
52 #include "utils/builtins.h"
53 #include "utils/memutils.h"
54 #include "utils/snapmgr.h"
55 
56 /* define this to enable debug logging */
57 /* #define FSDB 1 */
58 /* chunk size for lo_import/lo_export transfers */
59 #define BUFSIZE			8192
60 
61 /*
62  * LO "FD"s are indexes into the cookies array.
63  *
64  * A non-null entry is a pointer to a LargeObjectDesc allocated in the
65  * LO private memory context "fscxt".  The cookies array itself is also
66  * dynamically allocated in that context.  Its current allocated size is
67  * cookies_size entries, of which any unused entries will be NULL.
68  */
69 static LargeObjectDesc **cookies = NULL;
70 static int	cookies_size = 0;
71 
72 static bool lo_cleanup_needed = false;
73 static MemoryContext fscxt = NULL;
74 
75 static int	newLOfd(void);
76 static void closeLOfd(int fd);
77 static Oid	lo_import_internal(text *filename, Oid lobjOid);
78 
79 
80 /*****************************************************************************
81  *	File Interfaces for Large Objects
82  *****************************************************************************/
83 
84 Datum
be_lo_open(PG_FUNCTION_ARGS)85 be_lo_open(PG_FUNCTION_ARGS)
86 {
87 	Oid			lobjId = PG_GETARG_OID(0);
88 	int32		mode = PG_GETARG_INT32(1);
89 	LargeObjectDesc *lobjDesc;
90 	int			fd;
91 
92 #ifdef FSDB
93 	elog(DEBUG4, "lo_open(%u,%d)", lobjId, mode);
94 #endif
95 
96 	/*
97 	 * Allocate a large object descriptor first.  This will also create
98 	 * 'fscxt' if this is the first LO opened in this transaction.
99 	 */
100 	fd = newLOfd();
101 
102 	lobjDesc = inv_open(lobjId, mode, fscxt);
103 	lobjDesc->subid = GetCurrentSubTransactionId();
104 
105 	/*
106 	 * We must register the snapshot in TopTransaction's resowner so that it
107 	 * stays alive until the LO is closed rather than until the current portal
108 	 * shuts down.
109 	 */
110 	if (lobjDesc->snapshot)
111 		lobjDesc->snapshot = RegisterSnapshotOnOwner(lobjDesc->snapshot,
112 													 TopTransactionResourceOwner);
113 
114 	Assert(cookies[fd] == NULL);
115 	cookies[fd] = lobjDesc;
116 
117 	PG_RETURN_INT32(fd);
118 }
119 
120 Datum
be_lo_close(PG_FUNCTION_ARGS)121 be_lo_close(PG_FUNCTION_ARGS)
122 {
123 	int32		fd = PG_GETARG_INT32(0);
124 
125 	if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
126 		ereport(ERROR,
127 				(errcode(ERRCODE_UNDEFINED_OBJECT),
128 				 errmsg("invalid large-object descriptor: %d", fd)));
129 
130 #ifdef FSDB
131 	elog(DEBUG4, "lo_close(%d)", fd);
132 #endif
133 
134 	closeLOfd(fd);
135 
136 	PG_RETURN_INT32(0);
137 }
138 
139 
140 /*****************************************************************************
141  *	Bare Read/Write operations --- these are not fmgr-callable!
142  *
143  *	We assume the large object supports byte oriented reads and seeks so
144  *	that our work is easier.
145  *
146  *****************************************************************************/
147 
148 int
lo_read(int fd,char * buf,int len)149 lo_read(int fd, char *buf, int len)
150 {
151 	int			status;
152 	LargeObjectDesc *lobj;
153 
154 	if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
155 		ereport(ERROR,
156 				(errcode(ERRCODE_UNDEFINED_OBJECT),
157 				 errmsg("invalid large-object descriptor: %d", fd)));
158 	lobj = cookies[fd];
159 
160 	/*
161 	 * Check state.  inv_read() would throw an error anyway, but we want the
162 	 * error to be about the FD's state not the underlying privilege; it might
163 	 * be that the privilege exists but user forgot to ask for read mode.
164 	 */
165 	if ((lobj->flags & IFS_RDLOCK) == 0)
166 		ereport(ERROR,
167 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
168 				 errmsg("large object descriptor %d was not opened for reading",
169 						fd)));
170 
171 	status = inv_read(lobj, buf, len);
172 
173 	return status;
174 }
175 
176 int
lo_write(int fd,const char * buf,int len)177 lo_write(int fd, const char *buf, int len)
178 {
179 	int			status;
180 	LargeObjectDesc *lobj;
181 
182 	if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
183 		ereport(ERROR,
184 				(errcode(ERRCODE_UNDEFINED_OBJECT),
185 				 errmsg("invalid large-object descriptor: %d", fd)));
186 	lobj = cookies[fd];
187 
188 	/* see comment in lo_read() */
189 	if ((lobj->flags & IFS_WRLOCK) == 0)
190 		ereport(ERROR,
191 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
192 				 errmsg("large object descriptor %d was not opened for writing",
193 						fd)));
194 
195 	status = inv_write(lobj, buf, len);
196 
197 	return status;
198 }
199 
200 Datum
be_lo_lseek(PG_FUNCTION_ARGS)201 be_lo_lseek(PG_FUNCTION_ARGS)
202 {
203 	int32		fd = PG_GETARG_INT32(0);
204 	int32		offset = PG_GETARG_INT32(1);
205 	int32		whence = PG_GETARG_INT32(2);
206 	int64		status;
207 
208 	if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
209 		ereport(ERROR,
210 				(errcode(ERRCODE_UNDEFINED_OBJECT),
211 				 errmsg("invalid large-object descriptor: %d", fd)));
212 
213 	status = inv_seek(cookies[fd], offset, whence);
214 
215 	/* guard against result overflow */
216 	if (status != (int32) status)
217 		ereport(ERROR,
218 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
219 				 errmsg("lo_lseek result out of range for large-object descriptor %d",
220 						fd)));
221 
222 	PG_RETURN_INT32((int32) status);
223 }
224 
225 Datum
be_lo_lseek64(PG_FUNCTION_ARGS)226 be_lo_lseek64(PG_FUNCTION_ARGS)
227 {
228 	int32		fd = PG_GETARG_INT32(0);
229 	int64		offset = PG_GETARG_INT64(1);
230 	int32		whence = PG_GETARG_INT32(2);
231 	int64		status;
232 
233 	if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
234 		ereport(ERROR,
235 				(errcode(ERRCODE_UNDEFINED_OBJECT),
236 				 errmsg("invalid large-object descriptor: %d", fd)));
237 
238 	status = inv_seek(cookies[fd], offset, whence);
239 
240 	PG_RETURN_INT64(status);
241 }
242 
243 Datum
be_lo_creat(PG_FUNCTION_ARGS)244 be_lo_creat(PG_FUNCTION_ARGS)
245 {
246 	Oid			lobjId;
247 
248 	lo_cleanup_needed = true;
249 	lobjId = inv_create(InvalidOid);
250 
251 	PG_RETURN_OID(lobjId);
252 }
253 
254 Datum
be_lo_create(PG_FUNCTION_ARGS)255 be_lo_create(PG_FUNCTION_ARGS)
256 {
257 	Oid			lobjId = PG_GETARG_OID(0);
258 
259 	lo_cleanup_needed = true;
260 	lobjId = inv_create(lobjId);
261 
262 	PG_RETURN_OID(lobjId);
263 }
264 
265 Datum
be_lo_tell(PG_FUNCTION_ARGS)266 be_lo_tell(PG_FUNCTION_ARGS)
267 {
268 	int32		fd = PG_GETARG_INT32(0);
269 	int64		offset;
270 
271 	if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
272 		ereport(ERROR,
273 				(errcode(ERRCODE_UNDEFINED_OBJECT),
274 				 errmsg("invalid large-object descriptor: %d", fd)));
275 
276 	offset = inv_tell(cookies[fd]);
277 
278 	/* guard against result overflow */
279 	if (offset != (int32) offset)
280 		ereport(ERROR,
281 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
282 				 errmsg("lo_tell result out of range for large-object descriptor %d",
283 						fd)));
284 
285 	PG_RETURN_INT32((int32) offset);
286 }
287 
288 Datum
be_lo_tell64(PG_FUNCTION_ARGS)289 be_lo_tell64(PG_FUNCTION_ARGS)
290 {
291 	int32		fd = PG_GETARG_INT32(0);
292 	int64		offset;
293 
294 	if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
295 		ereport(ERROR,
296 				(errcode(ERRCODE_UNDEFINED_OBJECT),
297 				 errmsg("invalid large-object descriptor: %d", fd)));
298 
299 	offset = inv_tell(cookies[fd]);
300 
301 	PG_RETURN_INT64(offset);
302 }
303 
304 Datum
be_lo_unlink(PG_FUNCTION_ARGS)305 be_lo_unlink(PG_FUNCTION_ARGS)
306 {
307 	Oid			lobjId = PG_GETARG_OID(0);
308 
309 	/*
310 	 * Must be owner of the large object.  It would be cleaner to check this
311 	 * in inv_drop(), but we want to throw the error before not after closing
312 	 * relevant FDs.
313 	 */
314 	if (!lo_compat_privileges &&
315 		!pg_largeobject_ownercheck(lobjId, GetUserId()))
316 		ereport(ERROR,
317 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
318 				 errmsg("must be owner of large object %u", lobjId)));
319 
320 	/*
321 	 * If there are any open LO FDs referencing that ID, close 'em.
322 	 */
323 	if (fscxt != NULL)
324 	{
325 		int			i;
326 
327 		for (i = 0; i < cookies_size; i++)
328 		{
329 			if (cookies[i] != NULL && cookies[i]->id == lobjId)
330 				closeLOfd(i);
331 		}
332 	}
333 
334 	/*
335 	 * inv_drop does not create a need for end-of-transaction cleanup and
336 	 * hence we don't need to set lo_cleanup_needed.
337 	 */
338 	PG_RETURN_INT32(inv_drop(lobjId));
339 }
340 
341 /*****************************************************************************
342  *	Read/Write using bytea
343  *****************************************************************************/
344 
345 Datum
be_loread(PG_FUNCTION_ARGS)346 be_loread(PG_FUNCTION_ARGS)
347 {
348 	int32		fd = PG_GETARG_INT32(0);
349 	int32		len = PG_GETARG_INT32(1);
350 	bytea	   *retval;
351 	int			totalread;
352 
353 	if (len < 0)
354 		len = 0;
355 
356 	retval = (bytea *) palloc(VARHDRSZ + len);
357 	totalread = lo_read(fd, VARDATA(retval), len);
358 	SET_VARSIZE(retval, totalread + VARHDRSZ);
359 
360 	PG_RETURN_BYTEA_P(retval);
361 }
362 
363 Datum
be_lowrite(PG_FUNCTION_ARGS)364 be_lowrite(PG_FUNCTION_ARGS)
365 {
366 	int32		fd = PG_GETARG_INT32(0);
367 	bytea	   *wbuf = PG_GETARG_BYTEA_PP(1);
368 	int			bytestowrite;
369 	int			totalwritten;
370 
371 	bytestowrite = VARSIZE_ANY_EXHDR(wbuf);
372 	totalwritten = lo_write(fd, VARDATA_ANY(wbuf), bytestowrite);
373 	PG_RETURN_INT32(totalwritten);
374 }
375 
376 /*****************************************************************************
377  *	 Import/Export of Large Object
378  *****************************************************************************/
379 
380 /*
381  * lo_import -
382  *	  imports a file as an (inversion) large object.
383  */
384 Datum
be_lo_import(PG_FUNCTION_ARGS)385 be_lo_import(PG_FUNCTION_ARGS)
386 {
387 	text	   *filename = PG_GETARG_TEXT_PP(0);
388 
389 	PG_RETURN_OID(lo_import_internal(filename, InvalidOid));
390 }
391 
392 /*
393  * lo_import_with_oid -
394  *	  imports a file as an (inversion) large object specifying oid.
395  */
396 Datum
be_lo_import_with_oid(PG_FUNCTION_ARGS)397 be_lo_import_with_oid(PG_FUNCTION_ARGS)
398 {
399 	text	   *filename = PG_GETARG_TEXT_PP(0);
400 	Oid			oid = PG_GETARG_OID(1);
401 
402 	PG_RETURN_OID(lo_import_internal(filename, oid));
403 }
404 
405 static Oid
lo_import_internal(text * filename,Oid lobjOid)406 lo_import_internal(text *filename, Oid lobjOid)
407 {
408 	int			fd;
409 	int			nbytes,
410 				tmp PG_USED_FOR_ASSERTS_ONLY;
411 	char		buf[BUFSIZE];
412 	char		fnamebuf[MAXPGPATH];
413 	LargeObjectDesc *lobj;
414 	Oid			oid;
415 
416 	/*
417 	 * open the file to be read in
418 	 */
419 	text_to_cstring_buffer(filename, fnamebuf, sizeof(fnamebuf));
420 	fd = OpenTransientFile(fnamebuf, O_RDONLY | PG_BINARY);
421 	if (fd < 0)
422 		ereport(ERROR,
423 				(errcode_for_file_access(),
424 				 errmsg("could not open server file \"%s\": %m",
425 						fnamebuf)));
426 
427 	/*
428 	 * create an inversion object
429 	 */
430 	lo_cleanup_needed = true;
431 	oid = inv_create(lobjOid);
432 
433 	/*
434 	 * read in from the filesystem and write to the inversion object
435 	 */
436 	lobj = inv_open(oid, INV_WRITE, CurrentMemoryContext);
437 
438 	while ((nbytes = read(fd, buf, BUFSIZE)) > 0)
439 	{
440 		tmp = inv_write(lobj, buf, nbytes);
441 		Assert(tmp == nbytes);
442 	}
443 
444 	if (nbytes < 0)
445 		ereport(ERROR,
446 				(errcode_for_file_access(),
447 				 errmsg("could not read server file \"%s\": %m",
448 						fnamebuf)));
449 
450 	inv_close(lobj);
451 
452 	if (CloseTransientFile(fd) != 0)
453 		ereport(ERROR,
454 				(errcode_for_file_access(),
455 				 errmsg("could not close file \"%s\": %m",
456 						fnamebuf)));
457 
458 	return oid;
459 }
460 
461 /*
462  * lo_export -
463  *	  exports an (inversion) large object.
464  */
465 Datum
be_lo_export(PG_FUNCTION_ARGS)466 be_lo_export(PG_FUNCTION_ARGS)
467 {
468 	Oid			lobjId = PG_GETARG_OID(0);
469 	text	   *filename = PG_GETARG_TEXT_PP(1);
470 	int			fd;
471 	int			nbytes,
472 				tmp;
473 	char		buf[BUFSIZE];
474 	char		fnamebuf[MAXPGPATH];
475 	LargeObjectDesc *lobj;
476 	mode_t		oumask;
477 
478 	/*
479 	 * open the inversion object (no need to test for failure)
480 	 */
481 	lo_cleanup_needed = true;
482 	lobj = inv_open(lobjId, INV_READ, CurrentMemoryContext);
483 
484 	/*
485 	 * open the file to be written to
486 	 *
487 	 * Note: we reduce backend's normal 077 umask to the slightly friendlier
488 	 * 022. This code used to drop it all the way to 0, but creating
489 	 * world-writable export files doesn't seem wise.
490 	 */
491 	text_to_cstring_buffer(filename, fnamebuf, sizeof(fnamebuf));
492 	oumask = umask(S_IWGRP | S_IWOTH);
493 	PG_TRY();
494 	{
495 		fd = OpenTransientFilePerm(fnamebuf, O_CREAT | O_WRONLY | O_TRUNC | PG_BINARY,
496 								   S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
497 	}
498 	PG_FINALLY();
499 	{
500 		umask(oumask);
501 	}
502 	PG_END_TRY();
503 	if (fd < 0)
504 		ereport(ERROR,
505 				(errcode_for_file_access(),
506 				 errmsg("could not create server file \"%s\": %m",
507 						fnamebuf)));
508 
509 	/*
510 	 * read in from the inversion file and write to the filesystem
511 	 */
512 	while ((nbytes = inv_read(lobj, buf, BUFSIZE)) > 0)
513 	{
514 		tmp = write(fd, buf, nbytes);
515 		if (tmp != nbytes)
516 			ereport(ERROR,
517 					(errcode_for_file_access(),
518 					 errmsg("could not write server file \"%s\": %m",
519 							fnamebuf)));
520 	}
521 
522 	if (CloseTransientFile(fd) != 0)
523 		ereport(ERROR,
524 				(errcode_for_file_access(),
525 				 errmsg("could not close file \"%s\": %m",
526 						fnamebuf)));
527 
528 	inv_close(lobj);
529 
530 	PG_RETURN_INT32(1);
531 }
532 
533 /*
534  * lo_truncate -
535  *	  truncate a large object to a specified length
536  */
537 static void
lo_truncate_internal(int32 fd,int64 len)538 lo_truncate_internal(int32 fd, int64 len)
539 {
540 	LargeObjectDesc *lobj;
541 
542 	if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
543 		ereport(ERROR,
544 				(errcode(ERRCODE_UNDEFINED_OBJECT),
545 				 errmsg("invalid large-object descriptor: %d", fd)));
546 	lobj = cookies[fd];
547 
548 	/* see comment in lo_read() */
549 	if ((lobj->flags & IFS_WRLOCK) == 0)
550 		ereport(ERROR,
551 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
552 				 errmsg("large object descriptor %d was not opened for writing",
553 						fd)));
554 
555 	inv_truncate(lobj, len);
556 }
557 
558 Datum
be_lo_truncate(PG_FUNCTION_ARGS)559 be_lo_truncate(PG_FUNCTION_ARGS)
560 {
561 	int32		fd = PG_GETARG_INT32(0);
562 	int32		len = PG_GETARG_INT32(1);
563 
564 	lo_truncate_internal(fd, len);
565 	PG_RETURN_INT32(0);
566 }
567 
568 Datum
be_lo_truncate64(PG_FUNCTION_ARGS)569 be_lo_truncate64(PG_FUNCTION_ARGS)
570 {
571 	int32		fd = PG_GETARG_INT32(0);
572 	int64		len = PG_GETARG_INT64(1);
573 
574 	lo_truncate_internal(fd, len);
575 	PG_RETURN_INT32(0);
576 }
577 
578 /*
579  * AtEOXact_LargeObject -
580  *		 prepares large objects for transaction commit
581  */
582 void
AtEOXact_LargeObject(bool isCommit)583 AtEOXact_LargeObject(bool isCommit)
584 {
585 	int			i;
586 
587 	if (!lo_cleanup_needed)
588 		return;					/* no LO operations in this xact */
589 
590 	/*
591 	 * Close LO fds and clear cookies array so that LO fds are no longer good.
592 	 * The memory context and resource owner holding them are going away at
593 	 * the end-of-transaction anyway, but on commit, we need to close them to
594 	 * avoid warnings about leaked resources at commit.  On abort we can skip
595 	 * this step.
596 	 */
597 	if (isCommit)
598 	{
599 		for (i = 0; i < cookies_size; i++)
600 		{
601 			if (cookies[i] != NULL)
602 				closeLOfd(i);
603 		}
604 	}
605 
606 	/* Needn't actually pfree since we're about to zap context */
607 	cookies = NULL;
608 	cookies_size = 0;
609 
610 	/* Release the LO memory context to prevent permanent memory leaks. */
611 	if (fscxt)
612 		MemoryContextDelete(fscxt);
613 	fscxt = NULL;
614 
615 	/* Give inv_api.c a chance to clean up, too */
616 	close_lo_relation(isCommit);
617 
618 	lo_cleanup_needed = false;
619 }
620 
621 /*
622  * AtEOSubXact_LargeObject
623  *		Take care of large objects at subtransaction commit/abort
624  *
625  * Reassign LOs created/opened during a committing subtransaction
626  * to the parent subtransaction.  On abort, just close them.
627  */
628 void
AtEOSubXact_LargeObject(bool isCommit,SubTransactionId mySubid,SubTransactionId parentSubid)629 AtEOSubXact_LargeObject(bool isCommit, SubTransactionId mySubid,
630 						SubTransactionId parentSubid)
631 {
632 	int			i;
633 
634 	if (fscxt == NULL)			/* no LO operations in this xact */
635 		return;
636 
637 	for (i = 0; i < cookies_size; i++)
638 	{
639 		LargeObjectDesc *lo = cookies[i];
640 
641 		if (lo != NULL && lo->subid == mySubid)
642 		{
643 			if (isCommit)
644 				lo->subid = parentSubid;
645 			else
646 				closeLOfd(i);
647 		}
648 	}
649 }
650 
651 /*****************************************************************************
652  *	Support routines for this file
653  *****************************************************************************/
654 
655 static int
newLOfd(void)656 newLOfd(void)
657 {
658 	int			i,
659 				newsize;
660 
661 	lo_cleanup_needed = true;
662 	if (fscxt == NULL)
663 		fscxt = AllocSetContextCreate(TopMemoryContext,
664 									  "Filesystem",
665 									  ALLOCSET_DEFAULT_SIZES);
666 
667 	/* Try to find a free slot */
668 	for (i = 0; i < cookies_size; i++)
669 	{
670 		if (cookies[i] == NULL)
671 			return i;
672 	}
673 
674 	/* No free slot, so make the array bigger */
675 	if (cookies_size <= 0)
676 	{
677 		/* First time through, arbitrarily make 64-element array */
678 		i = 0;
679 		newsize = 64;
680 		cookies = (LargeObjectDesc **)
681 			MemoryContextAllocZero(fscxt, newsize * sizeof(LargeObjectDesc *));
682 		cookies_size = newsize;
683 	}
684 	else
685 	{
686 		/* Double size of array */
687 		i = cookies_size;
688 		newsize = cookies_size * 2;
689 		cookies = (LargeObjectDesc **)
690 			repalloc(cookies, newsize * sizeof(LargeObjectDesc *));
691 		MemSet(cookies + cookies_size, 0,
692 			   (newsize - cookies_size) * sizeof(LargeObjectDesc *));
693 		cookies_size = newsize;
694 	}
695 
696 	return i;
697 }
698 
699 static void
closeLOfd(int fd)700 closeLOfd(int fd)
701 {
702 	LargeObjectDesc *lobj;
703 
704 	/*
705 	 * Make sure we do not try to free twice if this errors out for some
706 	 * reason.  Better a leak than a crash.
707 	 */
708 	lobj = cookies[fd];
709 	cookies[fd] = NULL;
710 
711 	if (lobj->snapshot)
712 		UnregisterSnapshotFromOwner(lobj->snapshot,
713 									TopTransactionResourceOwner);
714 	inv_close(lobj);
715 }
716 
717 /*****************************************************************************
718  *	Wrappers oriented toward SQL callers
719  *****************************************************************************/
720 
721 /*
722  * Read [offset, offset+nbytes) within LO; when nbytes is -1, read to end.
723  */
724 static bytea *
lo_get_fragment_internal(Oid loOid,int64 offset,int32 nbytes)725 lo_get_fragment_internal(Oid loOid, int64 offset, int32 nbytes)
726 {
727 	LargeObjectDesc *loDesc;
728 	int64		loSize;
729 	int64		result_length;
730 	int			total_read PG_USED_FOR_ASSERTS_ONLY;
731 	bytea	   *result = NULL;
732 
733 	lo_cleanup_needed = true;
734 	loDesc = inv_open(loOid, INV_READ, CurrentMemoryContext);
735 
736 	/*
737 	 * Compute number of bytes we'll actually read, accommodating nbytes == -1
738 	 * and reads beyond the end of the LO.
739 	 */
740 	loSize = inv_seek(loDesc, 0, SEEK_END);
741 	if (loSize > offset)
742 	{
743 		if (nbytes >= 0 && nbytes <= loSize - offset)
744 			result_length = nbytes; /* request is wholly inside LO */
745 		else
746 			result_length = loSize - offset;	/* adjust to end of LO */
747 	}
748 	else
749 		result_length = 0;		/* request is wholly outside LO */
750 
751 	/*
752 	 * A result_length calculated from loSize may not fit in a size_t.  Check
753 	 * that the size will satisfy this and subsequently-enforced size limits.
754 	 */
755 	if (result_length > MaxAllocSize - VARHDRSZ)
756 		ereport(ERROR,
757 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
758 				 errmsg("large object read request is too large")));
759 
760 	result = (bytea *) palloc(VARHDRSZ + result_length);
761 
762 	inv_seek(loDesc, offset, SEEK_SET);
763 	total_read = inv_read(loDesc, VARDATA(result), result_length);
764 	Assert(total_read == result_length);
765 	SET_VARSIZE(result, result_length + VARHDRSZ);
766 
767 	inv_close(loDesc);
768 
769 	return result;
770 }
771 
772 /*
773  * Read entire LO
774  */
775 Datum
be_lo_get(PG_FUNCTION_ARGS)776 be_lo_get(PG_FUNCTION_ARGS)
777 {
778 	Oid			loOid = PG_GETARG_OID(0);
779 	bytea	   *result;
780 
781 	result = lo_get_fragment_internal(loOid, 0, -1);
782 
783 	PG_RETURN_BYTEA_P(result);
784 }
785 
786 /*
787  * Read range within LO
788  */
789 Datum
be_lo_get_fragment(PG_FUNCTION_ARGS)790 be_lo_get_fragment(PG_FUNCTION_ARGS)
791 {
792 	Oid			loOid = PG_GETARG_OID(0);
793 	int64		offset = PG_GETARG_INT64(1);
794 	int32		nbytes = PG_GETARG_INT32(2);
795 	bytea	   *result;
796 
797 	if (nbytes < 0)
798 		ereport(ERROR,
799 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
800 				 errmsg("requested length cannot be negative")));
801 
802 	result = lo_get_fragment_internal(loOid, offset, nbytes);
803 
804 	PG_RETURN_BYTEA_P(result);
805 }
806 
807 /*
808  * Create LO with initial contents given by a bytea argument
809  */
810 Datum
be_lo_from_bytea(PG_FUNCTION_ARGS)811 be_lo_from_bytea(PG_FUNCTION_ARGS)
812 {
813 	Oid			loOid = PG_GETARG_OID(0);
814 	bytea	   *str = PG_GETARG_BYTEA_PP(1);
815 	LargeObjectDesc *loDesc;
816 	int			written PG_USED_FOR_ASSERTS_ONLY;
817 
818 	lo_cleanup_needed = true;
819 	loOid = inv_create(loOid);
820 	loDesc = inv_open(loOid, INV_WRITE, CurrentMemoryContext);
821 	written = inv_write(loDesc, VARDATA_ANY(str), VARSIZE_ANY_EXHDR(str));
822 	Assert(written == VARSIZE_ANY_EXHDR(str));
823 	inv_close(loDesc);
824 
825 	PG_RETURN_OID(loOid);
826 }
827 
828 /*
829  * Update range within LO
830  */
831 Datum
be_lo_put(PG_FUNCTION_ARGS)832 be_lo_put(PG_FUNCTION_ARGS)
833 {
834 	Oid			loOid = PG_GETARG_OID(0);
835 	int64		offset = PG_GETARG_INT64(1);
836 	bytea	   *str = PG_GETARG_BYTEA_PP(2);
837 	LargeObjectDesc *loDesc;
838 	int			written PG_USED_FOR_ASSERTS_ONLY;
839 
840 	lo_cleanup_needed = true;
841 	loDesc = inv_open(loOid, INV_WRITE, CurrentMemoryContext);
842 
843 	/* Permission check */
844 	if (!lo_compat_privileges &&
845 		pg_largeobject_aclcheck_snapshot(loDesc->id,
846 										 GetUserId(),
847 										 ACL_UPDATE,
848 										 loDesc->snapshot) != ACLCHECK_OK)
849 		ereport(ERROR,
850 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
851 				 errmsg("permission denied for large object %u",
852 						loDesc->id)));
853 
854 	inv_seek(loDesc, offset, SEEK_SET);
855 	written = inv_write(loDesc, VARDATA_ANY(str), VARSIZE_ANY_EXHDR(str));
856 	Assert(written == VARSIZE_ANY_EXHDR(str));
857 	inv_close(loDesc);
858 
859 	PG_RETURN_VOID();
860 }
861