Age Owner Branch data TLA 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
3497 peter_e@gmx.net 87 :CBC 234 : be_lo_open(PG_FUNCTION_ARGS)
88 : : {
9542 tgl@sss.pgh.pa.us 89 : 234 : Oid lobjId = PG_GETARG_OID(0);
90 : 234 : 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 : :
1482 michael@paquier.xyz 98 [ + + ]: 234 : if (mode & INV_WRITE)
99 : 93 : 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 : : */
1725 heikki.linnakangas@i 105 : 230 : fd = newLOfd();
106 : :
7395 tgl@sss.pgh.pa.us 107 : 230 : lobjDesc = inv_open(lobjId, mode, fscxt);
1725 heikki.linnakangas@i 108 : 190 : 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 [ + + ]: 190 : if (lobjDesc->snapshot)
116 : 129 : lobjDesc->snapshot = RegisterSnapshotOnOwner(lobjDesc->snapshot,
117 : : TopTransactionResourceOwner);
118 : :
119 [ - + ]: 190 : Assert(cookies[fd] == NULL);
120 : 190 : cookies[fd] = lobjDesc;
121 : :
9542 tgl@sss.pgh.pa.us 122 : 190 : PG_RETURN_INT32(fd);
123 : : }
124 : :
125 : : Datum
3497 peter_e@gmx.net 126 : 118 : be_lo_close(PG_FUNCTION_ARGS)
127 : : {
9542 tgl@sss.pgh.pa.us 128 : 118 : int32 fd = PG_GETARG_INT32(0);
129 : :
9405 130 [ + - + - : 118 : if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
- + ]
8404 tgl@sss.pgh.pa.us 131 [ # # ]:UBC 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 : :
1725 heikki.linnakangas@i 139 :CBC 118 : closeLOfd(fd);
140 : :
9542 tgl@sss.pgh.pa.us 141 : 118 : 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
10973 scrappy@hub.org 154 : 525 : lo_read(int fd, char *buf, int len)
155 : : {
156 : : int status;
157 : : LargeObjectDesc *lobj;
158 : :
9405 tgl@sss.pgh.pa.us 159 [ + - + - : 525 : if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
- + ]
8404 tgl@sss.pgh.pa.us 160 [ # # ]:UBC 0 : ereport(ERROR,
161 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
162 : : errmsg("invalid large-object descriptor: %d", fd)));
5037 tgl@sss.pgh.pa.us 163 :CBC 525 : 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 : : */
3180 170 [ - + ]: 525 : if ((lobj->flags & IFS_RDLOCK) == 0)
3180 tgl@sss.pgh.pa.us 171 [ # # ]:UBC 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 : :
5037 tgl@sss.pgh.pa.us 176 :CBC 525 : status = inv_read(lobj, buf, len);
177 : :
9542 178 : 525 : return status;
179 : : }
180 : :
181 : : int
7261 bruce@momjian.us 182 : 693 : lo_write(int fd, const char *buf, int len)
183 : : {
184 : : int status;
185 : : LargeObjectDesc *lobj;
186 : :
9405 tgl@sss.pgh.pa.us 187 [ + - + - : 693 : if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
- + ]
8404 tgl@sss.pgh.pa.us 188 [ # # ]:UBC 0 : ereport(ERROR,
189 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
190 : : errmsg("invalid large-object descriptor: %d", fd)));
5037 tgl@sss.pgh.pa.us 191 :CBC 693 : lobj = cookies[fd];
192 : :
193 : : /* see comment in lo_read() */
194 [ + + ]: 693 : if ((lobj->flags & IFS_WRLOCK) == 0)
7712 195 [ + - ]: 4 : ereport(ERROR,
196 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
197 : : errmsg("large object descriptor %d was not opened for writing",
198 : : fd)));
199 : :
5037 200 : 689 : status = inv_write(lobj, buf, len);
201 : :
9542 202 : 689 : return status;
203 : : }
204 : :
205 : : Datum
3497 peter_e@gmx.net 206 : 36 : be_lo_lseek(PG_FUNCTION_ARGS)
207 : : {
9542 tgl@sss.pgh.pa.us 208 : 36 : int32 fd = PG_GETARG_INT32(0);
209 : 36 : int32 offset = PG_GETARG_INT32(1);
210 : 36 : int32 whence = PG_GETARG_INT32(2);
211 : : int64 status;
212 : :
9405 213 [ + - + - : 36 : if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
- + ]
8404 tgl@sss.pgh.pa.us 214 [ # # ]:UBC 0 : ereport(ERROR,
215 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
216 : : errmsg("invalid large-object descriptor: %d", fd)));
217 : :
9917 tgl@sss.pgh.pa.us 218 :CBC 36 : status = inv_seek(cookies[fd], offset, whence);
219 : :
220 : : /* guard against result overflow */
5038 221 [ - + ]: 36 : if (status != (int32) status)
5039 ishii@postgresql.org 222 [ # # ]:UBC 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 : :
5038 tgl@sss.pgh.pa.us 227 :CBC 36 : PG_RETURN_INT32((int32) status);
228 : : }
229 : :
230 : : Datum
3497 peter_e@gmx.net 231 : 16 : be_lo_lseek64(PG_FUNCTION_ARGS)
232 : : {
5039 ishii@postgresql.org 233 : 16 : int32 fd = PG_GETARG_INT32(0);
234 : 16 : int64 offset = PG_GETARG_INT64(1);
235 : 16 : int32 whence = PG_GETARG_INT32(2);
236 : : int64 status;
237 : :
238 [ + - + - : 16 : if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
- + ]
5039 ishii@postgresql.org 239 [ # # ]:UBC 0 : ereport(ERROR,
240 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
241 : : errmsg("invalid large-object descriptor: %d", fd)));
242 : :
5039 ishii@postgresql.org 243 :CBC 16 : status = inv_seek(cookies[fd], offset, whence);
244 : :
245 : 16 : PG_RETURN_INT64(status);
246 : : }
247 : :
248 : : Datum
3497 peter_e@gmx.net 249 : 17 : be_lo_creat(PG_FUNCTION_ARGS)
250 : : {
251 : : Oid lobjId;
252 : :
1482 michael@paquier.xyz 253 : 17 : PreventCommandIfReadOnly("lo_creat()");
254 : :
1725 heikki.linnakangas@i 255 : 13 : lo_cleanup_needed = true;
7712 tgl@sss.pgh.pa.us 256 : 13 : lobjId = inv_create(InvalidOid);
257 : :
258 : 13 : PG_RETURN_OID(lobjId);
259 : : }
260 : :
261 : : Datum
3497 peter_e@gmx.net 262 : 62 : be_lo_create(PG_FUNCTION_ARGS)
263 : : {
7712 tgl@sss.pgh.pa.us 264 : 62 : Oid lobjId = PG_GETARG_OID(0);
265 : :
1482 michael@paquier.xyz 266 : 62 : PreventCommandIfReadOnly("lo_create()");
267 : :
1725 heikki.linnakangas@i 268 : 58 : lo_cleanup_needed = true;
7712 tgl@sss.pgh.pa.us 269 : 58 : lobjId = inv_create(lobjId);
270 : :
9542 271 : 58 : PG_RETURN_OID(lobjId);
272 : : }
273 : :
274 : : Datum
3497 peter_e@gmx.net 275 : 16 : be_lo_tell(PG_FUNCTION_ARGS)
276 : : {
9542 tgl@sss.pgh.pa.us 277 : 16 : int32 fd = PG_GETARG_INT32(0);
278 : : int64 offset;
279 : :
9405 280 [ + - + - : 16 : if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
- + ]
8404 tgl@sss.pgh.pa.us 281 [ # # ]:UBC 0 : ereport(ERROR,
282 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
283 : : errmsg("invalid large-object descriptor: %d", fd)));
284 : :
5039 ishii@postgresql.org 285 :CBC 16 : offset = inv_tell(cookies[fd]);
286 : :
287 : : /* guard against result overflow */
5038 tgl@sss.pgh.pa.us 288 [ - + ]: 16 : if (offset != (int32) offset)
5039 ishii@postgresql.org 289 [ # # ]:UBC 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 : :
5038 tgl@sss.pgh.pa.us 294 :CBC 16 : PG_RETURN_INT32((int32) offset);
295 : : }
296 : :
297 : : Datum
3497 peter_e@gmx.net 298 : 16 : be_lo_tell64(PG_FUNCTION_ARGS)
299 : : {
5039 ishii@postgresql.org 300 : 16 : int32 fd = PG_GETARG_INT32(0);
301 : : int64 offset;
302 : :
303 [ + - + - : 16 : if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
- + ]
5039 ishii@postgresql.org 304 [ # # ]:UBC 0 : ereport(ERROR,
305 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
306 : : errmsg("invalid large-object descriptor: %d", fd)));
307 : :
5038 tgl@sss.pgh.pa.us 308 :CBC 16 : offset = inv_tell(cookies[fd]);
309 : :
310 : 16 : PG_RETURN_INT64(offset);
311 : : }
312 : :
313 : : Datum
3497 peter_e@gmx.net 314 : 77 : be_lo_unlink(PG_FUNCTION_ARGS)
315 : : {
9542 tgl@sss.pgh.pa.us 316 : 77 : Oid lobjId = PG_GETARG_OID(0);
317 : :
1482 michael@paquier.xyz 318 : 77 : PreventCommandIfReadOnly("lo_unlink()");
319 : :
556 peter@eisentraut.org 320 [ - + ]: 73 : if (!LargeObjectExists(lobjId))
556 peter@eisentraut.org 321 [ # # ]:UBC 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 : : */
6070 itagaki.takahiro@gma 330 [ + + ]:CBC 73 : if (!lo_compat_privileges &&
953 tgl@sss.pgh.pa.us 331 [ + + ]: 69 : !object_ownercheck(LargeObjectRelationId, lobjId, GetUserId()))
6070 itagaki.takahiro@gma 332 [ + - ]: 16 : 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 : : */
9405 tgl@sss.pgh.pa.us 339 [ - + ]: 57 : if (fscxt != NULL)
340 : : {
341 : : int i;
342 : :
9405 tgl@sss.pgh.pa.us 343 [ # # ]:UBC 0 : for (i = 0; i < cookies_size; i++)
344 : : {
345 [ # # # # ]: 0 : if (cookies[i] != NULL && cookies[i]->id == lobjId)
1725 heikki.linnakangas@i 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 : : */
9542 tgl@sss.pgh.pa.us 354 :CBC 57 : PG_RETURN_INT32(inv_drop(lobjId));
355 : : }
356 : :
357 : : /*****************************************************************************
358 : : * Read/Write using bytea
359 : : *****************************************************************************/
360 : :
361 : : Datum
3497 peter_e@gmx.net 362 : 525 : be_loread(PG_FUNCTION_ARGS)
363 : : {
9542 tgl@sss.pgh.pa.us 364 : 525 : int32 fd = PG_GETARG_INT32(0);
365 : 525 : int32 len = PG_GETARG_INT32(1);
366 : : bytea *retval;
367 : : int totalread;
368 : :
369 [ - + ]: 525 : if (len < 0)
9542 tgl@sss.pgh.pa.us 370 :UBC 0 : len = 0;
371 : :
8735 tgl@sss.pgh.pa.us 372 :CBC 525 : retval = (bytea *) palloc(VARHDRSZ + len);
10548 bruce@momjian.us 373 : 525 : totalread = lo_read(fd, VARDATA(retval), len);
7088 tgl@sss.pgh.pa.us 374 : 525 : SET_VARSIZE(retval, totalread + VARHDRSZ);
375 : :
8735 376 : 525 : PG_RETURN_BYTEA_P(retval);
377 : : }
378 : :
379 : : Datum
3497 peter_e@gmx.net 380 : 697 : be_lowrite(PG_FUNCTION_ARGS)
381 : : {
9256 bruce@momjian.us 382 : 697 : int32 fd = PG_GETARG_INT32(0);
3422 noah@leadboat.com 383 : 697 : bytea *wbuf = PG_GETARG_BYTEA_PP(1);
384 : : int bytestowrite;
385 : : int totalwritten;
386 : :
1482 michael@paquier.xyz 387 : 697 : PreventCommandIfReadOnly("lowrite()");
388 : :
3422 noah@leadboat.com 389 : 693 : bytestowrite = VARSIZE_ANY_EXHDR(wbuf);
390 : 693 : totalwritten = lo_write(fd, VARDATA_ANY(wbuf), bytestowrite);
9542 tgl@sss.pgh.pa.us 391 : 689 : 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
3497 peter_e@gmx.net 403 : 8 : be_lo_import(PG_FUNCTION_ARGS)
404 : : {
6696 tgl@sss.pgh.pa.us 405 : 8 : text *filename = PG_GETARG_TEXT_PP(0);
406 : :
6699 ishii@postgresql.org 407 : 8 : 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
3497 peter_e@gmx.net 415 :UBC 0 : be_lo_import_with_oid(PG_FUNCTION_ARGS)
416 : : {
6696 tgl@sss.pgh.pa.us 417 : 0 : text *filename = PG_GETARG_TEXT_PP(0);
6253 bruce@momjian.us 418 : 0 : Oid oid = PG_GETARG_OID(1);
419 : :
6699 ishii@postgresql.org 420 : 0 : PG_RETURN_OID(lo_import_internal(filename, oid));
421 : : }
422 : :
423 : : static Oid
6699 ishii@postgresql.org 424 :CBC 8 : lo_import_internal(text *filename, Oid lobjOid)
425 : : {
426 : : int fd;
427 : : ssize_t nbytes;
428 : : int tmp PG_USED_FOR_ASSERTS_ONLY;
429 : : char buf[BUFSIZE];
430 : : char fnamebuf[MAXPGPATH];
431 : : LargeObjectDesc *lobj;
432 : : Oid oid;
433 : :
1482 michael@paquier.xyz 434 : 8 : PreventCommandIfReadOnly("lo_import()");
435 : :
436 : : /*
437 : : * open the file to be read in
438 : : */
6696 tgl@sss.pgh.pa.us 439 : 4 : text_to_cstring_buffer(filename, fnamebuf, sizeof(fnamebuf));
3227 peter_e@gmx.net 440 : 4 : fd = OpenTransientFile(fnamebuf, O_RDONLY | PG_BINARY);
10548 bruce@momjian.us 441 [ - + ]: 4 : if (fd < 0)
8404 tgl@sss.pgh.pa.us 442 [ # # ]:UBC 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 : : */
1725 heikki.linnakangas@i 450 :CBC 4 : lo_cleanup_needed = true;
6699 ishii@postgresql.org 451 : 4 : oid = inv_create(lobjOid);
452 : :
453 : : /*
454 : : * read in from the filesystem and write to the inversion object
455 : : */
1725 heikki.linnakangas@i 456 : 4 : lobj = inv_open(oid, INV_WRITE, CurrentMemoryContext);
457 : :
4988 458 [ + + ]: 332 : while ((nbytes = read(fd, buf, BUFSIZE)) > 0)
459 : : {
10548 bruce@momjian.us 460 : 328 : tmp = inv_write(lobj, buf, nbytes);
8404 tgl@sss.pgh.pa.us 461 [ - + ]: 328 : Assert(tmp == nbytes);
462 : : }
463 : :
464 [ - + ]: 4 : if (nbytes < 0)
8404 tgl@sss.pgh.pa.us 465 [ # # ]:UBC 0 : ereport(ERROR,
466 : : (errcode_for_file_access(),
467 : : errmsg("could not read server file \"%s\": %m",
468 : : fnamebuf)));
469 : :
10548 bruce@momjian.us 470 :CBC 4 : inv_close(lobj);
471 : :
2576 peter@eisentraut.org 472 [ - + ]: 4 : if (CloseTransientFile(fd) != 0)
2695 michael@paquier.xyz 473 [ # # ]:UBC 0 : ereport(ERROR,
474 : : (errcode_for_file_access(),
475 : : errmsg("could not close file \"%s\": %m",
476 : : fnamebuf)));
477 : :
6699 ishii@postgresql.org 478 :CBC 4 : return oid;
479 : : }
480 : :
481 : : /*
482 : : * lo_export -
483 : : * exports an (inversion) large object.
484 : : */
485 : : Datum
3497 peter_e@gmx.net 486 : 8 : be_lo_export(PG_FUNCTION_ARGS)
487 : : {
9542 tgl@sss.pgh.pa.us 488 : 8 : Oid lobjId = PG_GETARG_OID(0);
6696 489 : 8 : text *filename = PG_GETARG_TEXT_PP(1);
490 : : int fd;
491 : : int nbytes;
492 : : char buf[BUFSIZE];
493 : : char fnamebuf[MAXPGPATH];
494 : : LargeObjectDesc *lobj;
495 : : mode_t oumask;
496 : :
497 : : /*
498 : : * open the inversion object (no need to test for failure)
499 : : */
1725 heikki.linnakangas@i 500 : 8 : lo_cleanup_needed = true;
501 : 8 : lobj = inv_open(lobjId, INV_READ, CurrentMemoryContext);
502 : :
503 : : /*
504 : : * open the file to be written to
505 : : *
506 : : * Note: we reduce backend's normal 077 umask to the slightly friendlier
507 : : * 022. This code used to drop it all the way to 0, but creating
508 : : * world-writable export files doesn't seem wise.
509 : : */
6696 tgl@sss.pgh.pa.us 510 : 8 : text_to_cstring_buffer(filename, fnamebuf, sizeof(fnamebuf));
5706 511 : 8 : oumask = umask(S_IWGRP | S_IWOTH);
3228 peter_e@gmx.net 512 [ + - ]: 8 : PG_TRY();
513 : : {
3227 514 : 8 : fd = OpenTransientFilePerm(fnamebuf, O_CREAT | O_WRONLY | O_TRUNC | PG_BINARY,
515 : : S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
516 : : }
2458 peter@eisentraut.org 517 :UBC 0 : PG_FINALLY();
518 : : {
3228 peter_e@gmx.net 519 :CBC 8 : umask(oumask);
520 : : }
521 [ - + ]: 8 : PG_END_TRY();
10548 bruce@momjian.us 522 [ + + ]: 8 : if (fd < 0)
8404 tgl@sss.pgh.pa.us 523 [ + - ]: 4 : ereport(ERROR,
524 : : (errcode_for_file_access(),
525 : : errmsg("could not create server file \"%s\": %m",
526 : : fnamebuf)));
527 : :
528 : : /*
529 : : * read in from the inversion file and write to the filesystem
530 : : */
10548 bruce@momjian.us 531 [ + + ]: 332 : while ((nbytes = inv_read(lobj, buf, BUFSIZE)) > 0)
532 : : {
533 : : ssize_t tmp;
534 : :
4988 heikki.linnakangas@i 535 : 328 : tmp = write(fd, buf, nbytes);
9405 tgl@sss.pgh.pa.us 536 [ - + ]: 328 : if (tmp != nbytes)
8404 tgl@sss.pgh.pa.us 537 [ # # ]:UBC 0 : ereport(ERROR,
538 : : (errcode_for_file_access(),
539 : : errmsg("could not write server file \"%s\": %m",
540 : : fnamebuf)));
541 : : }
542 : :
2576 peter@eisentraut.org 543 [ - + ]:CBC 4 : if (CloseTransientFile(fd) != 0)
2695 michael@paquier.xyz 544 [ # # ]:UBC 0 : ereport(ERROR,
545 : : (errcode_for_file_access(),
546 : : errmsg("could not close file \"%s\": %m",
547 : : fnamebuf)));
548 : :
8404 tgl@sss.pgh.pa.us 549 :CBC 4 : inv_close(lobj);
550 : :
9542 551 : 4 : PG_RETURN_INT32(1);
552 : : }
553 : :
554 : : /*
555 : : * lo_truncate -
556 : : * truncate a large object to a specified length
557 : : */
558 : : static void
5037 559 : 32 : lo_truncate_internal(int32 fd, int64 len)
560 : : {
561 : : LargeObjectDesc *lobj;
562 : :
7084 bruce@momjian.us 563 [ + - + - : 32 : if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
- + ]
7084 bruce@momjian.us 564 [ # # ]:UBC 0 : ereport(ERROR,
565 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
566 : : errmsg("invalid large-object descriptor: %d", fd)));
5037 tgl@sss.pgh.pa.us 567 :CBC 32 : lobj = cookies[fd];
568 : :
569 : : /* see comment in lo_read() */
570 [ - + ]: 32 : if ((lobj->flags & IFS_WRLOCK) == 0)
5039 ishii@postgresql.org 571 [ # # ]:UBC 0 : ereport(ERROR,
572 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
573 : : errmsg("large object descriptor %d was not opened for writing",
574 : : fd)));
575 : :
5037 tgl@sss.pgh.pa.us 576 :CBC 32 : inv_truncate(lobj, len);
577 : 32 : }
578 : :
579 : : Datum
3497 peter_e@gmx.net 580 : 28 : be_lo_truncate(PG_FUNCTION_ARGS)
581 : : {
5037 tgl@sss.pgh.pa.us 582 : 28 : int32 fd = PG_GETARG_INT32(0);
583 : 28 : int32 len = PG_GETARG_INT32(1);
584 : :
1482 michael@paquier.xyz 585 : 28 : PreventCommandIfReadOnly("lo_truncate()");
586 : :
5037 tgl@sss.pgh.pa.us 587 : 24 : lo_truncate_internal(fd, len);
5039 ishii@postgresql.org 588 : 24 : PG_RETURN_INT32(0);
589 : : }
590 : :
591 : : Datum
3497 peter_e@gmx.net 592 : 12 : be_lo_truncate64(PG_FUNCTION_ARGS)
593 : : {
5039 ishii@postgresql.org 594 : 12 : int32 fd = PG_GETARG_INT32(0);
595 : 12 : int64 len = PG_GETARG_INT64(1);
596 : :
1482 michael@paquier.xyz 597 : 12 : PreventCommandIfReadOnly("lo_truncate64()");
598 : :
5037 tgl@sss.pgh.pa.us 599 : 8 : lo_truncate_internal(fd, len);
7084 bruce@momjian.us 600 : 8 : PG_RETURN_INT32(0);
601 : : }
602 : :
603 : : /*
604 : : * AtEOXact_LargeObject -
605 : : * prepares large objects for transaction commit
606 : : */
607 : : void
8032 tgl@sss.pgh.pa.us 608 : 427869 : AtEOXact_LargeObject(bool isCommit)
609 : : {
610 : : int i;
611 : :
1725 heikki.linnakangas@i 612 [ + + ]: 427869 : if (!lo_cleanup_needed)
9917 tgl@sss.pgh.pa.us 613 : 427510 : return; /* no LO operations in this xact */
614 : :
615 : : /*
616 : : * Close LO fds and clear cookies array so that LO fds are no longer good.
617 : : * The memory context and resource owner holding them are going away at
618 : : * the end-of-transaction anyway, but on commit, we need to close them to
619 : : * avoid warnings about leaked resources at commit. On abort we can skip
620 : : * this step.
621 : : */
1725 heikki.linnakangas@i 622 [ + + ]: 359 : if (isCommit)
623 : : {
624 [ + + ]: 6193 : for (i = 0; i < cookies_size; i++)
625 : : {
626 [ + + ]: 5952 : if (cookies[i] != NULL)
627 : 60 : closeLOfd(i);
628 : : }
629 : : }
630 : :
631 : : /* Needn't actually pfree since we're about to zap context */
9405 tgl@sss.pgh.pa.us 632 : 359 : cookies = NULL;
633 : 359 : cookies_size = 0;
634 : :
635 : : /* Release the LO memory context to prevent permanent memory leaks. */
1725 heikki.linnakangas@i 636 [ + + ]: 359 : if (fscxt)
637 : 190 : MemoryContextDelete(fscxt);
9917 tgl@sss.pgh.pa.us 638 : 359 : fscxt = NULL;
639 : :
640 : : /* Give inv_api.c a chance to clean up, too */
8032 641 : 359 : close_lo_relation(isCommit);
642 : :
1725 heikki.linnakangas@i 643 : 359 : lo_cleanup_needed = false;
644 : : }
645 : :
646 : : /*
647 : : * AtEOSubXact_LargeObject
648 : : * Take care of large objects at subtransaction commit/abort
649 : : *
650 : : * Reassign LOs created/opened during a committing subtransaction
651 : : * to the parent subtransaction. On abort, just close them.
652 : : */
653 : : void
7982 tgl@sss.pgh.pa.us 654 : 12687 : AtEOSubXact_LargeObject(bool isCommit, SubTransactionId mySubid,
655 : : SubTransactionId parentSubid)
656 : : {
657 : : int i;
658 : :
8032 659 [ + - ]: 12687 : if (fscxt == NULL) /* no LO operations in this xact */
660 : 12687 : return;
661 : :
8032 tgl@sss.pgh.pa.us 662 [ # # ]:UBC 0 : for (i = 0; i < cookies_size; i++)
663 : : {
664 : 0 : LargeObjectDesc *lo = cookies[i];
665 : :
7982 666 [ # # # # ]: 0 : if (lo != NULL && lo->subid == mySubid)
667 : : {
8032 668 [ # # ]: 0 : if (isCommit)
7982 669 : 0 : lo->subid = parentSubid;
670 : : else
1725 heikki.linnakangas@i 671 : 0 : closeLOfd(i);
672 : : }
673 : : }
674 : : }
675 : :
676 : : /*****************************************************************************
677 : : * Support routines for this file
678 : : *****************************************************************************/
679 : :
680 : : static int
1725 heikki.linnakangas@i 681 :CBC 230 : newLOfd(void)
682 : : {
683 : : int i,
684 : : newsize;
685 : :
686 : 230 : lo_cleanup_needed = true;
687 [ + + ]: 230 : if (fscxt == NULL)
688 : 190 : fscxt = AllocSetContextCreate(TopMemoryContext,
689 : : "Filesystem",
690 : : ALLOCSET_DEFAULT_SIZES);
691 : :
692 : : /* Try to find a free slot */
9405 tgl@sss.pgh.pa.us 693 [ + + ]: 230 : for (i = 0; i < cookies_size; i++)
694 : : {
10548 bruce@momjian.us 695 [ + - ]: 40 : if (cookies[i] == NULL)
696 : 40 : return i;
697 : : }
698 : :
699 : : /* No free slot, so make the array bigger */
9405 tgl@sss.pgh.pa.us 700 [ + - ]: 190 : if (cookies_size <= 0)
701 : : {
702 : : /* First time through, arbitrarily make 64-element array */
703 : 190 : i = 0;
704 : 190 : newsize = 64;
705 : 190 : cookies = (LargeObjectDesc **)
7395 706 : 190 : MemoryContextAllocZero(fscxt, newsize * sizeof(LargeObjectDesc *));
707 : : }
708 : : else
709 : : {
710 : : /* Double size of array */
9405 tgl@sss.pgh.pa.us 711 :UBC 0 : i = cookies_size;
712 : 0 : newsize = cookies_size * 2;
1351 peter@eisentraut.org 713 : 0 : cookies =
714 : 0 : repalloc0_array(cookies, LargeObjectDesc *, cookies_size, newsize);
715 : : }
1351 peter@eisentraut.org 716 :CBC 190 : cookies_size = newsize;
717 : :
9405 tgl@sss.pgh.pa.us 718 : 190 : return i;
719 : : }
720 : :
721 : : static void
1725 heikki.linnakangas@i 722 : 178 : closeLOfd(int fd)
723 : : {
724 : : LargeObjectDesc *lobj;
725 : :
726 : : /*
727 : : * Make sure we do not try to free twice if this errors out for some
728 : : * reason. Better a leak than a crash.
729 : : */
730 : 178 : lobj = cookies[fd];
10548 bruce@momjian.us 731 : 178 : cookies[fd] = NULL;
732 : :
1725 heikki.linnakangas@i 733 [ + + ]: 178 : if (lobj->snapshot)
734 : 117 : UnregisterSnapshotFromOwner(lobj->snapshot,
735 : : TopTransactionResourceOwner);
736 : 178 : inv_close(lobj);
10973 scrappy@hub.org 737 : 178 : }
738 : :
739 : : /*****************************************************************************
740 : : * Wrappers oriented toward SQL callers
741 : : *****************************************************************************/
742 : :
743 : : /*
744 : : * Read [offset, offset+nbytes) within LO; when nbytes is -1, read to end.
745 : : */
746 : : static bytea *
4654 noah@leadboat.com 747 : 70 : lo_get_fragment_internal(Oid loOid, int64 offset, int32 nbytes)
748 : : {
749 : : LargeObjectDesc *loDesc;
750 : : int64 loSize;
751 : : int64 result_length;
752 : : int total_read PG_USED_FOR_ASSERTS_ONLY;
753 : 70 : bytea *result = NULL;
754 : :
1725 heikki.linnakangas@i 755 : 70 : lo_cleanup_needed = true;
756 : 70 : loDesc = inv_open(loOid, INV_READ, CurrentMemoryContext);
757 : :
758 : : /*
759 : : * Compute number of bytes we'll actually read, accommodating nbytes == -1
760 : : * and reads beyond the end of the LO.
761 : : */
4654 noah@leadboat.com 762 : 65 : loSize = inv_seek(loDesc, 0, SEEK_END);
763 [ + + ]: 65 : if (loSize > offset)
764 : : {
765 [ + + + + ]: 52 : if (nbytes >= 0 && nbytes <= loSize - offset)
3321 tgl@sss.pgh.pa.us 766 : 12 : result_length = nbytes; /* request is wholly inside LO */
767 : : else
4654 noah@leadboat.com 768 : 40 : result_length = loSize - offset; /* adjust to end of LO */
769 : : }
770 : : else
771 : 13 : result_length = 0; /* request is wholly outside LO */
772 : :
773 : : /*
774 : : * A result_length calculated from loSize may not fit in a size_t. Check
775 : : * that the size will satisfy this and subsequently-enforced size limits.
776 : : */
777 [ + + ]: 65 : if (result_length > MaxAllocSize - VARHDRSZ)
778 [ + - ]: 4 : ereport(ERROR,
779 : : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
780 : : errmsg("large object read request is too large")));
781 : :
782 : 61 : result = (bytea *) palloc(VARHDRSZ + result_length);
783 : :
784 : 61 : inv_seek(loDesc, offset, SEEK_SET);
785 : 61 : total_read = inv_read(loDesc, VARDATA(result), result_length);
786 [ - + ]: 61 : Assert(total_read == result_length);
787 : 61 : SET_VARSIZE(result, result_length + VARHDRSZ);
788 : :
789 : 61 : inv_close(loDesc);
790 : :
791 : 61 : return result;
792 : : }
793 : :
794 : : /*
795 : : * Read entire LO
796 : : */
797 : : Datum
3497 peter_e@gmx.net 798 : 54 : be_lo_get(PG_FUNCTION_ARGS)
799 : : {
4654 noah@leadboat.com 800 : 54 : Oid loOid = PG_GETARG_OID(0);
801 : : bytea *result;
802 : :
803 : 54 : result = lo_get_fragment_internal(loOid, 0, -1);
804 : :
805 : 45 : PG_RETURN_BYTEA_P(result);
806 : : }
807 : :
808 : : /*
809 : : * Read range within LO
810 : : */
811 : : Datum
3497 peter_e@gmx.net 812 : 16 : be_lo_get_fragment(PG_FUNCTION_ARGS)
813 : : {
4654 noah@leadboat.com 814 : 16 : Oid loOid = PG_GETARG_OID(0);
815 : 16 : int64 offset = PG_GETARG_INT64(1);
816 : 16 : int32 nbytes = PG_GETARG_INT32(2);
817 : : bytea *result;
818 : :
819 [ - + ]: 16 : if (nbytes < 0)
4654 noah@leadboat.com 820 [ # # ]:UBC 0 : ereport(ERROR,
821 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
822 : : errmsg("requested length cannot be negative")));
823 : :
4654 noah@leadboat.com 824 :CBC 16 : result = lo_get_fragment_internal(loOid, offset, nbytes);
825 : :
826 : 16 : PG_RETURN_BYTEA_P(result);
827 : : }
828 : :
829 : : /*
830 : : * Create LO with initial contents given by a bytea argument
831 : : */
832 : : Datum
3497 peter_e@gmx.net 833 : 33 : be_lo_from_bytea(PG_FUNCTION_ARGS)
834 : : {
4654 noah@leadboat.com 835 : 33 : Oid loOid = PG_GETARG_OID(0);
836 : 33 : bytea *str = PG_GETARG_BYTEA_PP(1);
837 : : LargeObjectDesc *loDesc;
838 : : int written PG_USED_FOR_ASSERTS_ONLY;
839 : :
1482 michael@paquier.xyz 840 : 33 : PreventCommandIfReadOnly("lo_from_bytea()");
841 : :
1725 heikki.linnakangas@i 842 : 29 : lo_cleanup_needed = true;
4654 noah@leadboat.com 843 : 29 : loOid = inv_create(loOid);
1725 heikki.linnakangas@i 844 : 29 : loDesc = inv_open(loOid, INV_WRITE, CurrentMemoryContext);
4654 noah@leadboat.com 845 : 29 : written = inv_write(loDesc, VARDATA_ANY(str), VARSIZE_ANY_EXHDR(str));
846 [ - + ]: 29 : Assert(written == VARSIZE_ANY_EXHDR(str));
847 : 29 : inv_close(loDesc);
848 : :
849 : 29 : PG_RETURN_OID(loOid);
850 : : }
851 : :
852 : : /*
853 : : * Update range within LO
854 : : */
855 : : Datum
3497 peter_e@gmx.net 856 : 24 : be_lo_put(PG_FUNCTION_ARGS)
857 : : {
4654 noah@leadboat.com 858 : 24 : Oid loOid = PG_GETARG_OID(0);
859 : 24 : int64 offset = PG_GETARG_INT64(1);
860 : 24 : bytea *str = PG_GETARG_BYTEA_PP(2);
861 : : LargeObjectDesc *loDesc;
862 : : int written PG_USED_FOR_ASSERTS_ONLY;
863 : :
1482 michael@paquier.xyz 864 : 24 : PreventCommandIfReadOnly("lo_put()");
865 : :
1725 heikki.linnakangas@i 866 : 20 : lo_cleanup_needed = true;
867 : 20 : loDesc = inv_open(loOid, INV_WRITE, CurrentMemoryContext);
4654 noah@leadboat.com 868 : 12 : inv_seek(loDesc, offset, SEEK_SET);
869 : 12 : written = inv_write(loDesc, VARDATA_ANY(str), VARSIZE_ANY_EXHDR(str));
870 [ - + ]: 12 : Assert(written == VARSIZE_ANY_EXHDR(str));
871 : 12 : inv_close(loDesc);
872 : :
873 : 12 : PG_RETURN_VOID();
874 : : }
|