Branch data 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-2026, 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 : 23 : be_lo_open(PG_FUNCTION_ARGS)
88 : : {
89 : 23 : Oid lobjId = PG_GETARG_OID(0);
90 : 23 : int32 mode = PG_GETARG_INT32(1);
91 : 23 : LargeObjectDesc *lobjDesc;
92 : 23 : int fd;
93 : :
94 : : #ifdef FSDB
95 : : elog(DEBUG4, "lo_open(%u,%d)", lobjId, mode);
96 : : #endif
97 : :
98 [ + + ]: 23 : if (mode & INV_WRITE)
99 : 19 : 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 : 23 : fd = newLOfd();
106 : :
107 : 23 : lobjDesc = inv_open(lobjId, mode, fscxt);
108 : 23 : 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 [ + + ]: 23 : if (lobjDesc->snapshot)
116 : 20 : lobjDesc->snapshot = RegisterSnapshotOnOwner(lobjDesc->snapshot,
117 : 10 : TopTransactionResourceOwner);
118 : :
119 [ + - ]: 23 : Assert(cookies[fd] == NULL);
120 : 23 : cookies[fd] = lobjDesc;
121 : :
122 : 46 : PG_RETURN_INT32(fd);
123 : 23 : }
124 : :
125 : : Datum
126 : 8 : be_lo_close(PG_FUNCTION_ARGS)
127 : : {
128 : 8 : int32 fd = PG_GETARG_INT32(0);
129 : :
130 [ + - ]: 8 : 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 : 8 : closeLOfd(fd);
140 : :
141 : 16 : PG_RETURN_INT32(0);
142 : 8 : }
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 : 97 : lo_read(int fd, char *buf, int len)
155 : : {
156 : 97 : int status;
157 : 97 : LargeObjectDesc *lobj;
158 : :
159 [ + - ]: 97 : 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 : 97 : 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 [ + - ]: 97 : 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 : 97 : status = inv_read(lobj, buf, len);
177 : :
178 : 194 : return status;
179 : 97 : }
180 : :
181 : : int
182 : 172 : lo_write(int fd, const char *buf, int len)
183 : : {
184 : 172 : int status;
185 : 172 : LargeObjectDesc *lobj;
186 : :
187 [ + - ]: 172 : 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 : 172 : lobj = cookies[fd];
192 : :
193 : : /* see comment in lo_read() */
194 [ + + ]: 172 : if ((lobj->flags & IFS_WRLOCK) == 0)
195 [ + - + - ]: 1 : 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 : 171 : status = inv_write(lobj, buf, len);
201 : :
202 : 342 : return status;
203 : 171 : }
204 : :
205 : : Datum
206 : 9 : be_lo_lseek(PG_FUNCTION_ARGS)
207 : : {
208 : 9 : int32 fd = PG_GETARG_INT32(0);
209 : 9 : int32 offset = PG_GETARG_INT32(1);
210 : 9 : int32 whence = PG_GETARG_INT32(2);
211 : 9 : int64 status;
212 : :
213 [ + - ]: 9 : 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 : 9 : status = inv_seek(cookies[fd], offset, whence);
219 : :
220 : : /* guard against result overflow */
221 [ + - ]: 9 : 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 : 18 : PG_RETURN_INT32((int32) status);
228 : 9 : }
229 : :
230 : : Datum
231 : 4 : be_lo_lseek64(PG_FUNCTION_ARGS)
232 : : {
233 : 4 : int32 fd = PG_GETARG_INT32(0);
234 : 4 : int64 offset = PG_GETARG_INT64(1);
235 : 4 : int32 whence = PG_GETARG_INT32(2);
236 : 4 : int64 status;
237 : :
238 [ + - ]: 4 : 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 : 4 : status = inv_seek(cookies[fd], offset, whence);
244 : :
245 : 8 : PG_RETURN_INT64(status);
246 : 4 : }
247 : :
248 : : Datum
249 : 4 : be_lo_creat(PG_FUNCTION_ARGS)
250 : : {
251 : 4 : Oid lobjId;
252 : :
253 : 4 : PreventCommandIfReadOnly("lo_creat()");
254 : :
255 : 4 : lo_cleanup_needed = true;
256 : 4 : lobjId = inv_create(InvalidOid);
257 : :
258 : 8 : PG_RETURN_OID(lobjId);
259 : 4 : }
260 : :
261 : : Datum
262 : 14 : be_lo_create(PG_FUNCTION_ARGS)
263 : : {
264 : 14 : Oid lobjId = PG_GETARG_OID(0);
265 : :
266 : 14 : PreventCommandIfReadOnly("lo_create()");
267 : :
268 : 14 : lo_cleanup_needed = true;
269 : 14 : lobjId = inv_create(lobjId);
270 : :
271 : 28 : PG_RETURN_OID(lobjId);
272 : 14 : }
273 : :
274 : : Datum
275 : 4 : be_lo_tell(PG_FUNCTION_ARGS)
276 : : {
277 : 4 : int32 fd = PG_GETARG_INT32(0);
278 : 4 : int64 offset;
279 : :
280 [ + - ]: 4 : 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 : 4 : offset = inv_tell(cookies[fd]);
286 : :
287 : : /* guard against result overflow */
288 [ + - ]: 4 : 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 : 8 : PG_RETURN_INT32((int32) offset);
295 : 4 : }
296 : :
297 : : Datum
298 : 4 : be_lo_tell64(PG_FUNCTION_ARGS)
299 : : {
300 : 4 : int32 fd = PG_GETARG_INT32(0);
301 : 4 : int64 offset;
302 : :
303 [ + - ]: 4 : 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 : 4 : offset = inv_tell(cookies[fd]);
309 : :
310 : 8 : PG_RETURN_INT64(offset);
311 : 4 : }
312 : :
313 : : Datum
314 : 16 : be_lo_unlink(PG_FUNCTION_ARGS)
315 : : {
316 : 16 : Oid lobjId = PG_GETARG_OID(0);
317 : :
318 : 16 : PreventCommandIfReadOnly("lo_unlink()");
319 : :
320 [ + - ]: 16 : 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 [ + + + + ]: 16 : if (!lo_compat_privileges &&
331 : 14 : !object_ownercheck(LargeObjectRelationId, lobjId, GetUserId()))
332 [ + - + - ]: 2 : 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 [ - + ]: 14 : if (fscxt != NULL)
340 : : {
341 : 0 : 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 : 0 : }
348 : 0 : }
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 : 28 : PG_RETURN_INT32(inv_drop(lobjId));
355 : 14 : }
356 : :
357 : : /*****************************************************************************
358 : : * Read/Write using bytea
359 : : *****************************************************************************/
360 : :
361 : : Datum
362 : 97 : be_loread(PG_FUNCTION_ARGS)
363 : : {
364 : 97 : int32 fd = PG_GETARG_INT32(0);
365 : 97 : int32 len = PG_GETARG_INT32(1);
366 : 97 : bytea *retval;
367 : 97 : int totalread;
368 : :
369 [ + - ]: 97 : if (len < 0)
370 : 0 : len = 0;
371 : :
372 : 97 : retval = (bytea *) palloc(VARHDRSZ + len);
373 : 97 : totalread = lo_read(fd, VARDATA(retval), len);
374 : 97 : SET_VARSIZE(retval, totalread + VARHDRSZ);
375 : :
376 : 194 : PG_RETURN_BYTEA_P(retval);
377 : 97 : }
378 : :
379 : : Datum
380 : 173 : be_lowrite(PG_FUNCTION_ARGS)
381 : : {
382 : 173 : int32 fd = PG_GETARG_INT32(0);
383 : 173 : bytea *wbuf = PG_GETARG_BYTEA_PP(1);
384 : 173 : int bytestowrite;
385 : 173 : int totalwritten;
386 : :
387 : 173 : PreventCommandIfReadOnly("lowrite()");
388 : :
389 : 173 : bytestowrite = VARSIZE_ANY_EXHDR(wbuf);
390 : 173 : totalwritten = lo_write(fd, VARDATA_ANY(wbuf), bytestowrite);
391 : 346 : PG_RETURN_INT32(totalwritten);
392 : 173 : }
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 : 2 : be_lo_import(PG_FUNCTION_ARGS)
404 : : {
405 : 2 : text *filename = PG_GETARG_TEXT_PP(0);
406 : :
407 : 4 : PG_RETURN_OID(lo_import_internal(filename, InvalidOid));
408 : 2 : }
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 : 0 : }
422 : :
423 : : static Oid
424 : 2 : lo_import_internal(text *filename, Oid lobjOid)
425 : : {
426 : 2 : int fd;
427 : 2 : int nbytes,
428 : : tmp PG_USED_FOR_ASSERTS_ONLY;
429 : 2 : char buf[BUFSIZE];
430 : 2 : char fnamebuf[MAXPGPATH];
431 : 2 : LargeObjectDesc *lobj;
432 : 2 : Oid oid;
433 : :
434 : 2 : PreventCommandIfReadOnly("lo_import()");
435 : :
436 : : /*
437 : : * open the file to be read in
438 : : */
439 : 2 : text_to_cstring_buffer(filename, fnamebuf, sizeof(fnamebuf));
440 : 2 : fd = OpenTransientFile(fnamebuf, O_RDONLY | PG_BINARY);
441 [ + - ]: 2 : 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 : 2 : lo_cleanup_needed = true;
451 : 2 : oid = inv_create(lobjOid);
452 : :
453 : : /*
454 : : * read in from the filesystem and write to the inversion object
455 : : */
456 : 2 : lobj = inv_open(oid, INV_WRITE, CurrentMemoryContext);
457 : :
458 [ + + ]: 84 : while ((nbytes = read(fd, buf, BUFSIZE)) > 0)
459 : : {
460 : 82 : tmp = inv_write(lobj, buf, nbytes);
461 [ + - ]: 82 : Assert(tmp == nbytes);
462 : : }
463 : :
464 [ + - ]: 2 : 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 : 2 : inv_close(lobj);
471 : :
472 [ + - ]: 2 : 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 : 4 : return oid;
479 : 2 : }
480 : :
481 : : /*
482 : : * lo_export -
483 : : * exports an (inversion) large object.
484 : : */
485 : : Datum
486 : 2 : be_lo_export(PG_FUNCTION_ARGS)
487 : : {
488 : 2 : Oid lobjId = PG_GETARG_OID(0);
489 : 2 : text *filename = PG_GETARG_TEXT_PP(1);
490 : 2 : int fd;
491 : 2 : int nbytes,
492 : : tmp;
493 : 2 : char buf[BUFSIZE];
494 : 2 : char fnamebuf[MAXPGPATH];
495 : 2 : LargeObjectDesc *lobj;
496 : 2 : mode_t oumask;
497 : :
498 : : /*
499 : : * open the inversion object (no need to test for failure)
500 : : */
501 : 2 : lo_cleanup_needed = true;
502 : 2 : 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 : 2 : text_to_cstring_buffer(filename, fnamebuf, sizeof(fnamebuf));
512 : 2 : oumask = umask(S_IWGRP | S_IWOTH);
513 [ - + ]: 2 : PG_TRY();
514 : : {
515 : 2 : fd = OpenTransientFilePerm(fnamebuf, O_CREAT | O_WRONLY | O_TRUNC | PG_BINARY,
516 : : S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
517 : : }
518 : 2 : PG_FINALLY();
519 : : {
520 : 2 : umask(oumask);
521 : : }
522 [ + - ]: 2 : PG_END_TRY();
523 [ + + ]: 2 : if (fd < 0)
524 [ + - + - ]: 1 : 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 [ + + ]: 83 : while ((nbytes = inv_read(lobj, buf, BUFSIZE)) > 0)
533 : : {
534 : 82 : tmp = write(fd, buf, nbytes);
535 [ + - ]: 82 : 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 [ + - ]: 1 : 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 : 1 : inv_close(lobj);
549 : :
550 : 2 : PG_RETURN_INT32(1);
551 : 1 : }
552 : :
553 : : /*
554 : : * lo_truncate -
555 : : * truncate a large object to a specified length
556 : : */
557 : : static void
558 : 7 : lo_truncate_internal(int32 fd, int64 len)
559 : : {
560 : 7 : LargeObjectDesc *lobj;
561 : :
562 [ + - ]: 7 : 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 : 7 : lobj = cookies[fd];
567 : :
568 : : /* see comment in lo_read() */
569 [ + - ]: 7 : 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 : 7 : inv_truncate(lobj, len);
576 : 7 : }
577 : :
578 : : Datum
579 : 6 : be_lo_truncate(PG_FUNCTION_ARGS)
580 : : {
581 : 6 : int32 fd = PG_GETARG_INT32(0);
582 : 6 : int32 len = PG_GETARG_INT32(1);
583 : :
584 : 6 : PreventCommandIfReadOnly("lo_truncate()");
585 : :
586 : 6 : lo_truncate_internal(fd, len);
587 : 12 : PG_RETURN_INT32(0);
588 : 6 : }
589 : :
590 : : Datum
591 : 3 : be_lo_truncate64(PG_FUNCTION_ARGS)
592 : : {
593 : 3 : int32 fd = PG_GETARG_INT32(0);
594 : 3 : int64 len = PG_GETARG_INT64(1);
595 : :
596 : 3 : PreventCommandIfReadOnly("lo_truncate64()");
597 : :
598 : 3 : lo_truncate_internal(fd, len);
599 : 6 : PG_RETURN_INT32(0);
600 : 3 : }
601 : :
602 : : /*
603 : : * AtEOXact_LargeObject -
604 : : * prepares large objects for transaction commit
605 : : */
606 : : void
607 : 57944 : AtEOXact_LargeObject(bool isCommit)
608 : : {
609 : 57944 : int i;
610 : :
611 [ + + ]: 57944 : if (!lo_cleanup_needed)
612 : 57886 : 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 [ + + ]: 58 : if (isCommit)
622 : : {
623 [ + + ]: 1324 : for (i = 0; i < cookies_size; i++)
624 : : {
625 [ + + ]: 1280 : if (cookies[i] != NULL)
626 : 12 : closeLOfd(i);
627 : 1280 : }
628 : 44 : }
629 : :
630 : : /* Needn't actually pfree since we're about to zap context */
631 : 58 : cookies = NULL;
632 : 58 : cookies_size = 0;
633 : :
634 : : /* Release the LO memory context to prevent permanent memory leaks. */
635 [ + + ]: 58 : if (fscxt)
636 : 31 : MemoryContextDelete(fscxt);
637 : 58 : fscxt = NULL;
638 : :
639 : : /* Give inv_api.c a chance to clean up, too */
640 : 58 : close_lo_relation(isCommit);
641 : :
642 : 58 : lo_cleanup_needed = false;
643 [ - + ]: 57944 : }
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 : 1665 : AtEOSubXact_LargeObject(bool isCommit, SubTransactionId mySubid,
654 : : SubTransactionId parentSubid)
655 : : {
656 : 1665 : int i;
657 : :
658 [ - + ]: 1665 : if (fscxt == NULL) /* no LO operations in this xact */
659 : 1665 : 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 : 0 : }
672 : 0 : }
673 [ - + ]: 1665 : }
674 : :
675 : : /*****************************************************************************
676 : : * Support routines for this file
677 : : *****************************************************************************/
678 : :
679 : : static int
680 : 31 : newLOfd(void)
681 : : {
682 : 31 : int i,
683 : : newsize;
684 : :
685 : 31 : lo_cleanup_needed = true;
686 [ - + ]: 31 : if (fscxt == NULL)
687 : 31 : fscxt = AllocSetContextCreate(TopMemoryContext,
688 : : "Filesystem",
689 : : ALLOCSET_DEFAULT_SIZES);
690 : :
691 : : /* Try to find a free slot */
692 [ - + ]: 31 : for (i = 0; i < cookies_size; i++)
693 : : {
694 [ # # ]: 0 : if (cookies[i] == NULL)
695 : 0 : return i;
696 : 0 : }
697 : :
698 : : /* No free slot, so make the array bigger */
699 [ + - ]: 31 : if (cookies_size <= 0)
700 : : {
701 : : /* First time through, arbitrarily make 64-element array */
702 : 31 : i = 0;
703 : 31 : newsize = 64;
704 : 31 : cookies = (LargeObjectDesc **)
705 : 31 : MemoryContextAllocZero(fscxt, newsize * sizeof(LargeObjectDesc *));
706 : 31 : }
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 : 31 : cookies_size = newsize;
716 : :
717 : 31 : return i;
718 : 31 : }
719 : :
720 : : static void
721 : 20 : closeLOfd(int fd)
722 : : {
723 : 20 : 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 : 20 : lobj = cookies[fd];
730 : 20 : cookies[fd] = NULL;
731 : :
732 [ + + ]: 20 : if (lobj->snapshot)
733 : 14 : UnregisterSnapshotFromOwner(lobj->snapshot,
734 : 7 : TopTransactionResourceOwner);
735 : 20 : inv_close(lobj);
736 : 20 : }
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 : 10 : lo_get_fragment_internal(Oid loOid, int64 offset, int32 nbytes)
747 : : {
748 : 10 : LargeObjectDesc *loDesc;
749 : 10 : int64 loSize;
750 : 10 : int64 result_length;
751 : 10 : int total_read PG_USED_FOR_ASSERTS_ONLY;
752 : 10 : bytea *result = NULL;
753 : :
754 : 10 : lo_cleanup_needed = true;
755 : 10 : 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 : 10 : loSize = inv_seek(loDesc, 0, SEEK_END);
762 [ + - ]: 10 : if (loSize > offset)
763 : : {
764 [ + + + + ]: 10 : if (nbytes >= 0 && nbytes <= loSize - offset)
765 : 3 : result_length = nbytes; /* request is wholly inside LO */
766 : : else
767 : 7 : result_length = loSize - offset; /* adjust to end of LO */
768 : 10 : }
769 : : else
770 : 0 : 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 [ + + ]: 10 : if (result_length > MaxAllocSize - VARHDRSZ)
777 [ + - + - ]: 1 : ereport(ERROR,
778 : : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
779 : : errmsg("large object read request is too large")));
780 : :
781 : 9 : result = (bytea *) palloc(VARHDRSZ + result_length);
782 : :
783 : 9 : inv_seek(loDesc, offset, SEEK_SET);
784 : 9 : total_read = inv_read(loDesc, VARDATA(result), result_length);
785 [ + - ]: 9 : Assert(total_read == result_length);
786 : 9 : SET_VARSIZE(result, result_length + VARHDRSZ);
787 : :
788 : 9 : inv_close(loDesc);
789 : :
790 : 18 : return result;
791 : 9 : }
792 : :
793 : : /*
794 : : * Read entire LO
795 : : */
796 : : Datum
797 : 6 : be_lo_get(PG_FUNCTION_ARGS)
798 : : {
799 : 6 : Oid loOid = PG_GETARG_OID(0);
800 : 6 : bytea *result;
801 : :
802 : 6 : result = lo_get_fragment_internal(loOid, 0, -1);
803 : :
804 : 12 : PG_RETURN_BYTEA_P(result);
805 : 6 : }
806 : :
807 : : /*
808 : : * Read range within LO
809 : : */
810 : : Datum
811 : 4 : be_lo_get_fragment(PG_FUNCTION_ARGS)
812 : : {
813 : 4 : Oid loOid = PG_GETARG_OID(0);
814 : 4 : int64 offset = PG_GETARG_INT64(1);
815 : 4 : int32 nbytes = PG_GETARG_INT32(2);
816 : 4 : bytea *result;
817 : :
818 [ + - ]: 4 : if (nbytes < 0)
819 [ # # # # ]: 0 : ereport(ERROR,
820 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
821 : : errmsg("requested length cannot be negative")));
822 : :
823 : 4 : result = lo_get_fragment_internal(loOid, offset, nbytes);
824 : :
825 : 8 : PG_RETURN_BYTEA_P(result);
826 : 4 : }
827 : :
828 : : /*
829 : : * Create LO with initial contents given by a bytea argument
830 : : */
831 : : Datum
832 : 3 : be_lo_from_bytea(PG_FUNCTION_ARGS)
833 : : {
834 : 3 : Oid loOid = PG_GETARG_OID(0);
835 : 3 : bytea *str = PG_GETARG_BYTEA_PP(1);
836 : 3 : LargeObjectDesc *loDesc;
837 : 3 : int written PG_USED_FOR_ASSERTS_ONLY;
838 : :
839 : 3 : PreventCommandIfReadOnly("lo_from_bytea()");
840 : :
841 : 3 : lo_cleanup_needed = true;
842 : 3 : loOid = inv_create(loOid);
843 : 3 : loDesc = inv_open(loOid, INV_WRITE, CurrentMemoryContext);
844 : 3 : written = inv_write(loDesc, VARDATA_ANY(str), VARSIZE_ANY_EXHDR(str));
845 [ + - ]: 3 : Assert(written == VARSIZE_ANY_EXHDR(str));
846 : 3 : inv_close(loDesc);
847 : :
848 : 6 : PG_RETURN_OID(loOid);
849 : 3 : }
850 : :
851 : : /*
852 : : * Update range within LO
853 : : */
854 : : Datum
855 : 2 : be_lo_put(PG_FUNCTION_ARGS)
856 : : {
857 : 2 : Oid loOid = PG_GETARG_OID(0);
858 : 2 : int64 offset = PG_GETARG_INT64(1);
859 : 2 : bytea *str = PG_GETARG_BYTEA_PP(2);
860 : 2 : LargeObjectDesc *loDesc;
861 : 2 : int written PG_USED_FOR_ASSERTS_ONLY;
862 : :
863 : 2 : PreventCommandIfReadOnly("lo_put()");
864 : :
865 : 2 : lo_cleanup_needed = true;
866 : 2 : loDesc = inv_open(loOid, INV_WRITE, CurrentMemoryContext);
867 : 2 : inv_seek(loDesc, offset, SEEK_SET);
868 : 2 : written = inv_write(loDesc, VARDATA_ANY(str), VARSIZE_ANY_EXHDR(str));
869 [ + - ]: 2 : Assert(written == VARSIZE_ANY_EXHDR(str));
870 : 2 : inv_close(loDesc);
871 : :
872 : 2 : PG_RETURN_VOID();
873 : 2 : }
|