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-2025, 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 94 : if (!LargeObjectExists(lobjId))
321 0 : ereport(ERROR,
322 : (errcode(ERRCODE_UNDEFINED_OBJECT),
323 : errmsg("large object %u does not exist", lobjId)));
324 :
325 : /*
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
328 : * relevant FDs.
329 : */
330 94 : if (!lo_compat_privileges &&
331 88 : !object_ownercheck(LargeObjectRelationId, lobjId, GetUserId()))
332 12 : ereport(ERROR,
333 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
334 : errmsg("must be owner of large object %u", lobjId)));
335 :
336 : /*
337 : * If there are any open LO FDs referencing that ID, close 'em.
338 : */
339 82 : if (fscxt != NULL)
340 : {
341 : int i;
342 :
343 0 : for (i = 0; i < cookies_size; i++)
344 : {
345 0 : if (cookies[i] != NULL && cookies[i]->id == lobjId)
346 0 : closeLOfd(i);
347 : }
348 : }
349 :
350 : /*
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.
353 : */
354 82 : PG_RETURN_INT32(inv_drop(lobjId));
355 : }
356 :
357 : /*****************************************************************************
358 : * Read/Write using bytea
359 : *****************************************************************************/
360 :
361 : Datum
362 808 : be_loread(PG_FUNCTION_ARGS)
363 : {
364 808 : int32 fd = PG_GETARG_INT32(0);
365 808 : int32 len = PG_GETARG_INT32(1);
366 : bytea *retval;
367 : int totalread;
368 :
369 808 : if (len < 0)
370 0 : len = 0;
371 :
372 808 : retval = (bytea *) palloc(VARHDRSZ + len);
373 808 : totalread = lo_read(fd, VARDATA(retval), len);
374 808 : SET_VARSIZE(retval, totalread + VARHDRSZ);
375 :
376 808 : PG_RETURN_BYTEA_P(retval);
377 : }
378 :
379 : Datum
380 1040 : be_lowrite(PG_FUNCTION_ARGS)
381 : {
382 1040 : int32 fd = PG_GETARG_INT32(0);
383 1040 : bytea *wbuf = PG_GETARG_BYTEA_PP(1);
384 : int bytestowrite;
385 : int totalwritten;
386 :
387 1040 : PreventCommandIfReadOnly("lowrite()");
388 :
389 1034 : bytestowrite = VARSIZE_ANY_EXHDR(wbuf);
390 1034 : totalwritten = lo_write(fd, VARDATA_ANY(wbuf), bytestowrite);
391 1028 : PG_RETURN_INT32(totalwritten);
392 : }
393 :
394 : /*****************************************************************************
395 : * Import/Export of Large Object
396 : *****************************************************************************/
397 :
398 : /*
399 : * lo_import -
400 : * imports a file as an (inversion) large object.
401 : */
402 : Datum
403 12 : be_lo_import(PG_FUNCTION_ARGS)
404 : {
405 12 : text *filename = PG_GETARG_TEXT_PP(0);
406 :
407 12 : PG_RETURN_OID(lo_import_internal(filename, InvalidOid));
408 : }
409 :
410 : /*
411 : * lo_import_with_oid -
412 : * imports a file as an (inversion) large object specifying oid.
413 : */
414 : Datum
415 0 : be_lo_import_with_oid(PG_FUNCTION_ARGS)
416 : {
417 0 : text *filename = PG_GETARG_TEXT_PP(0);
418 0 : Oid oid = PG_GETARG_OID(1);
419 :
420 0 : PG_RETURN_OID(lo_import_internal(filename, oid));
421 : }
422 :
423 : static Oid
424 12 : lo_import_internal(text *filename, Oid lobjOid)
425 : {
426 : int fd;
427 : int nbytes,
428 : tmp PG_USED_FOR_ASSERTS_ONLY;
429 : char buf[BUFSIZE];
430 : char fnamebuf[MAXPGPATH];
431 : LargeObjectDesc *lobj;
432 : Oid oid;
433 :
434 12 : PreventCommandIfReadOnly("lo_import()");
435 :
436 : /*
437 : * open the file to be read in
438 : */
439 6 : text_to_cstring_buffer(filename, fnamebuf, sizeof(fnamebuf));
440 6 : fd = OpenTransientFile(fnamebuf, O_RDONLY | PG_BINARY);
441 6 : if (fd < 0)
442 0 : ereport(ERROR,
443 : (errcode_for_file_access(),
444 : errmsg("could not open server file \"%s\": %m",
445 : fnamebuf)));
446 :
447 : /*
448 : * create an inversion object
449 : */
450 6 : lo_cleanup_needed = true;
451 6 : oid = inv_create(lobjOid);
452 :
453 : /*
454 : * read in from the filesystem and write to the inversion object
455 : */
456 6 : lobj = inv_open(oid, INV_WRITE, CurrentMemoryContext);
457 :
458 498 : while ((nbytes = read(fd, buf, BUFSIZE)) > 0)
459 : {
460 492 : tmp = inv_write(lobj, buf, nbytes);
461 : Assert(tmp == nbytes);
462 : }
463 :
464 6 : if (nbytes < 0)
465 0 : ereport(ERROR,
466 : (errcode_for_file_access(),
467 : errmsg("could not read server file \"%s\": %m",
468 : fnamebuf)));
469 :
470 6 : inv_close(lobj);
471 :
472 6 : if (CloseTransientFile(fd) != 0)
473 0 : ereport(ERROR,
474 : (errcode_for_file_access(),
475 : errmsg("could not close file \"%s\": %m",
476 : fnamebuf)));
477 :
478 6 : return oid;
479 : }
480 :
481 : /*
482 : * lo_export -
483 : * exports an (inversion) large object.
484 : */
485 : Datum
486 12 : be_lo_export(PG_FUNCTION_ARGS)
487 : {
488 12 : Oid lobjId = PG_GETARG_OID(0);
489 12 : text *filename = PG_GETARG_TEXT_PP(1);
490 : int fd;
491 : int nbytes,
492 : tmp;
493 : char buf[BUFSIZE];
494 : char fnamebuf[MAXPGPATH];
495 : LargeObjectDesc *lobj;
496 : mode_t oumask;
497 :
498 : /*
499 : * open the inversion object (no need to test for failure)
500 : */
501 12 : lo_cleanup_needed = true;
502 12 : lobj = inv_open(lobjId, INV_READ, CurrentMemoryContext);
503 :
504 : /*
505 : * open the file to be written to
506 : *
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.
510 : */
511 12 : text_to_cstring_buffer(filename, fnamebuf, sizeof(fnamebuf));
512 12 : oumask = umask(S_IWGRP | S_IWOTH);
513 12 : PG_TRY();
514 : {
515 12 : fd = OpenTransientFilePerm(fnamebuf, O_CREAT | O_WRONLY | O_TRUNC | PG_BINARY,
516 : S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
517 : }
518 0 : PG_FINALLY();
519 : {
520 12 : umask(oumask);
521 : }
522 12 : PG_END_TRY();
523 12 : if (fd < 0)
524 6 : ereport(ERROR,
525 : (errcode_for_file_access(),
526 : errmsg("could not create server file \"%s\": %m",
527 : fnamebuf)));
528 :
529 : /*
530 : * read in from the inversion file and write to the filesystem
531 : */
532 498 : while ((nbytes = inv_read(lobj, buf, BUFSIZE)) > 0)
533 : {
534 492 : tmp = write(fd, buf, nbytes);
535 492 : if (tmp != nbytes)
536 0 : ereport(ERROR,
537 : (errcode_for_file_access(),
538 : errmsg("could not write server file \"%s\": %m",
539 : fnamebuf)));
540 : }
541 :
542 6 : if (CloseTransientFile(fd) != 0)
543 0 : ereport(ERROR,
544 : (errcode_for_file_access(),
545 : errmsg("could not close file \"%s\": %m",
546 : fnamebuf)));
547 :
548 6 : inv_close(lobj);
549 :
550 6 : PG_RETURN_INT32(1);
551 : }
552 :
553 : /*
554 : * lo_truncate -
555 : * truncate a large object to a specified length
556 : */
557 : static void
558 42 : lo_truncate_internal(int32 fd, int64 len)
559 : {
560 : LargeObjectDesc *lobj;
561 :
562 42 : if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
563 0 : ereport(ERROR,
564 : (errcode(ERRCODE_UNDEFINED_OBJECT),
565 : errmsg("invalid large-object descriptor: %d", fd)));
566 42 : lobj = cookies[fd];
567 :
568 : /* see comment in lo_read() */
569 42 : if ((lobj->flags & IFS_WRLOCK) == 0)
570 0 : ereport(ERROR,
571 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
572 : errmsg("large object descriptor %d was not opened for writing",
573 : fd)));
574 :
575 42 : inv_truncate(lobj, len);
576 42 : }
577 :
578 : Datum
579 36 : be_lo_truncate(PG_FUNCTION_ARGS)
580 : {
581 36 : int32 fd = PG_GETARG_INT32(0);
582 36 : int32 len = PG_GETARG_INT32(1);
583 :
584 36 : PreventCommandIfReadOnly("lo_truncate()");
585 :
586 30 : lo_truncate_internal(fd, len);
587 30 : PG_RETURN_INT32(0);
588 : }
589 :
590 : Datum
591 18 : be_lo_truncate64(PG_FUNCTION_ARGS)
592 : {
593 18 : int32 fd = PG_GETARG_INT32(0);
594 18 : int64 len = PG_GETARG_INT64(1);
595 :
596 18 : PreventCommandIfReadOnly("lo_truncate64()");
597 :
598 12 : lo_truncate_internal(fd, len);
599 12 : PG_RETURN_INT32(0);
600 : }
601 :
602 : /*
603 : * AtEOXact_LargeObject -
604 : * prepares large objects for transaction commit
605 : */
606 : void
607 786380 : AtEOXact_LargeObject(bool isCommit)
608 : {
609 : int i;
610 :
611 786380 : if (!lo_cleanup_needed)
612 785938 : return; /* no LO operations in this xact */
613 :
614 : /*
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
619 : * this step.
620 : */
621 442 : if (isCommit)
622 : {
623 8092 : for (i = 0; i < cookies_size; i++)
624 : {
625 7808 : if (cookies[i] != NULL)
626 72 : closeLOfd(i);
627 : }
628 : }
629 :
630 : /* Needn't actually pfree since we're about to zap context */
631 442 : cookies = NULL;
632 442 : cookies_size = 0;
633 :
634 : /* Release the LO memory context to prevent permanent memory leaks. */
635 442 : if (fscxt)
636 264 : MemoryContextDelete(fscxt);
637 442 : fscxt = NULL;
638 :
639 : /* Give inv_api.c a chance to clean up, too */
640 442 : close_lo_relation(isCommit);
641 :
642 442 : lo_cleanup_needed = false;
643 : }
644 :
645 : /*
646 : * AtEOSubXact_LargeObject
647 : * Take care of large objects at subtransaction commit/abort
648 : *
649 : * Reassign LOs created/opened during a committing subtransaction
650 : * to the parent subtransaction. On abort, just close them.
651 : */
652 : void
653 20040 : AtEOSubXact_LargeObject(bool isCommit, SubTransactionId mySubid,
654 : SubTransactionId parentSubid)
655 : {
656 : int i;
657 :
658 20040 : if (fscxt == NULL) /* no LO operations in this xact */
659 20040 : return;
660 :
661 0 : for (i = 0; i < cookies_size; i++)
662 : {
663 0 : LargeObjectDesc *lo = cookies[i];
664 :
665 0 : if (lo != NULL && lo->subid == mySubid)
666 : {
667 0 : if (isCommit)
668 0 : lo->subid = parentSubid;
669 : else
670 0 : closeLOfd(i);
671 : }
672 : }
673 : }
674 :
675 : /*****************************************************************************
676 : * Support routines for this file
677 : *****************************************************************************/
678 :
679 : static int
680 336 : newLOfd(void)
681 : {
682 : int i,
683 : newsize;
684 :
685 336 : lo_cleanup_needed = true;
686 336 : if (fscxt == NULL)
687 264 : fscxt = AllocSetContextCreate(TopMemoryContext,
688 : "Filesystem",
689 : ALLOCSET_DEFAULT_SIZES);
690 :
691 : /* Try to find a free slot */
692 336 : for (i = 0; i < cookies_size; i++)
693 : {
694 72 : if (cookies[i] == NULL)
695 72 : return i;
696 : }
697 :
698 : /* No free slot, so make the array bigger */
699 264 : if (cookies_size <= 0)
700 : {
701 : /* First time through, arbitrarily make 64-element array */
702 264 : i = 0;
703 264 : newsize = 64;
704 264 : cookies = (LargeObjectDesc **)
705 264 : MemoryContextAllocZero(fscxt, newsize * sizeof(LargeObjectDesc *));
706 : }
707 : else
708 : {
709 : /* Double size of array */
710 0 : i = cookies_size;
711 0 : newsize = cookies_size * 2;
712 0 : cookies =
713 0 : repalloc0_array(cookies, LargeObjectDesc *, cookies_size, newsize);
714 : }
715 264 : cookies_size = newsize;
716 :
717 264 : return i;
718 : }
719 :
720 : static void
721 270 : closeLOfd(int fd)
722 : {
723 : LargeObjectDesc *lobj;
724 :
725 : /*
726 : * Make sure we do not try to free twice if this errors out for some
727 : * reason. Better a leak than a crash.
728 : */
729 270 : lobj = cookies[fd];
730 270 : cookies[fd] = NULL;
731 :
732 270 : if (lobj->snapshot)
733 190 : UnregisterSnapshotFromOwner(lobj->snapshot,
734 : TopTransactionResourceOwner);
735 270 : inv_close(lobj);
736 270 : }
737 :
738 : /*****************************************************************************
739 : * Wrappers oriented toward SQL callers
740 : *****************************************************************************/
741 :
742 : /*
743 : * Read [offset, offset+nbytes) within LO; when nbytes is -1, read to end.
744 : */
745 : static bytea *
746 72 : lo_get_fragment_internal(Oid loOid, int64 offset, int32 nbytes)
747 : {
748 : LargeObjectDesc *loDesc;
749 : int64 loSize;
750 : int64 result_length;
751 : int total_read PG_USED_FOR_ASSERTS_ONLY;
752 72 : bytea *result = NULL;
753 :
754 72 : lo_cleanup_needed = true;
755 72 : loDesc = inv_open(loOid, INV_READ, CurrentMemoryContext);
756 :
757 : /*
758 : * Compute number of bytes we'll actually read, accommodating nbytes == -1
759 : * and reads beyond the end of the LO.
760 : */
761 68 : loSize = inv_seek(loDesc, 0, SEEK_END);
762 68 : if (loSize > offset)
763 : {
764 60 : if (nbytes >= 0 && nbytes <= loSize - offset)
765 18 : result_length = nbytes; /* request is wholly inside LO */
766 : else
767 42 : result_length = loSize - offset; /* adjust to end of LO */
768 : }
769 : else
770 8 : result_length = 0; /* request is wholly outside LO */
771 :
772 : /*
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.
775 : */
776 68 : if (result_length > MaxAllocSize - VARHDRSZ)
777 6 : ereport(ERROR,
778 : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
779 : errmsg("large object read request is too large")));
780 :
781 62 : result = (bytea *) palloc(VARHDRSZ + result_length);
782 :
783 62 : inv_seek(loDesc, offset, SEEK_SET);
784 62 : total_read = inv_read(loDesc, VARDATA(result), result_length);
785 : Assert(total_read == result_length);
786 62 : SET_VARSIZE(result, result_length + VARHDRSZ);
787 :
788 62 : inv_close(loDesc);
789 :
790 62 : return result;
791 : }
792 :
793 : /*
794 : * Read entire LO
795 : */
796 : Datum
797 48 : be_lo_get(PG_FUNCTION_ARGS)
798 : {
799 48 : Oid loOid = PG_GETARG_OID(0);
800 : bytea *result;
801 :
802 48 : result = lo_get_fragment_internal(loOid, 0, -1);
803 :
804 38 : PG_RETURN_BYTEA_P(result);
805 : }
806 :
807 : /*
808 : * Read range within LO
809 : */
810 : Datum
811 24 : be_lo_get_fragment(PG_FUNCTION_ARGS)
812 : {
813 24 : Oid loOid = PG_GETARG_OID(0);
814 24 : int64 offset = PG_GETARG_INT64(1);
815 24 : int32 nbytes = PG_GETARG_INT32(2);
816 : bytea *result;
817 :
818 24 : if (nbytes < 0)
819 0 : ereport(ERROR,
820 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
821 : errmsg("requested length cannot be negative")));
822 :
823 24 : result = lo_get_fragment_internal(loOid, offset, nbytes);
824 :
825 24 : PG_RETURN_BYTEA_P(result);
826 : }
827 :
828 : /*
829 : * Create LO with initial contents given by a bytea argument
830 : */
831 : Datum
832 26 : be_lo_from_bytea(PG_FUNCTION_ARGS)
833 : {
834 26 : Oid loOid = PG_GETARG_OID(0);
835 26 : bytea *str = PG_GETARG_BYTEA_PP(1);
836 : LargeObjectDesc *loDesc;
837 : int written PG_USED_FOR_ASSERTS_ONLY;
838 :
839 26 : PreventCommandIfReadOnly("lo_from_bytea()");
840 :
841 20 : lo_cleanup_needed = true;
842 20 : loOid = inv_create(loOid);
843 20 : loDesc = inv_open(loOid, INV_WRITE, CurrentMemoryContext);
844 20 : written = inv_write(loDesc, VARDATA_ANY(str), VARSIZE_ANY_EXHDR(str));
845 : Assert(written == VARSIZE_ANY_EXHDR(str));
846 20 : inv_close(loDesc);
847 :
848 20 : PG_RETURN_OID(loOid);
849 : }
850 :
851 : /*
852 : * Update range within LO
853 : */
854 : Datum
855 24 : be_lo_put(PG_FUNCTION_ARGS)
856 : {
857 24 : Oid loOid = PG_GETARG_OID(0);
858 24 : int64 offset = PG_GETARG_INT64(1);
859 24 : bytea *str = PG_GETARG_BYTEA_PP(2);
860 : LargeObjectDesc *loDesc;
861 : int written PG_USED_FOR_ASSERTS_ONLY;
862 :
863 24 : PreventCommandIfReadOnly("lo_put()");
864 :
865 18 : lo_cleanup_needed = true;
866 18 : loDesc = inv_open(loOid, INV_WRITE, CurrentMemoryContext);
867 12 : inv_seek(loDesc, offset, SEEK_SET);
868 12 : written = inv_write(loDesc, VARDATA_ANY(str), VARSIZE_ANY_EXHDR(str));
869 : Assert(written == VARSIZE_ANY_EXHDR(str));
870 12 : inv_close(loDesc);
871 :
872 12 : PG_RETURN_VOID();
873 : }
|