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