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-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
3175 peter_e@gmx.net 87 :CBC 183 : be_lo_open(PG_FUNCTION_ARGS)
88 : : {
9220 tgl@sss.pgh.pa.us 89 : 183 : Oid lobjId = PG_GETARG_OID(0);
90 : 183 : 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 : :
1160 michael@paquier.xyz 98 [ + + ]: 183 : if (mode & INV_WRITE)
99 : 58 : 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 : : */
1403 heikki.linnakangas@i 105 : 180 : fd = newLOfd();
106 : :
7073 tgl@sss.pgh.pa.us 107 : 180 : lobjDesc = inv_open(lobjId, mode, fscxt);
1403 heikki.linnakangas@i 108 : 156 : 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 [ + + ]: 156 : if (lobjDesc->snapshot)
116 : 116 : lobjDesc->snapshot = RegisterSnapshotOnOwner(lobjDesc->snapshot,
117 : : TopTransactionResourceOwner);
118 : :
119 [ - + ]: 156 : Assert(cookies[fd] == NULL);
120 : 156 : cookies[fd] = lobjDesc;
121 : :
9220 tgl@sss.pgh.pa.us 122 : 156 : PG_RETURN_INT32(fd);
123 : : }
124 : :
125 : : Datum
3175 peter_e@gmx.net 126 : 111 : be_lo_close(PG_FUNCTION_ARGS)
127 : : {
9220 tgl@sss.pgh.pa.us 128 : 111 : int32 fd = PG_GETARG_INT32(0);
129 : :
9083 130 [ + - + - : 111 : if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
- + ]
8082 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 : :
1403 heikki.linnakangas@i 139 :CBC 111 : closeLOfd(fd);
140 : :
9220 tgl@sss.pgh.pa.us 141 : 111 : 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
10651 scrappy@hub.org 154 : 422 : lo_read(int fd, char *buf, int len)
155 : : {
156 : : int status;
157 : : LargeObjectDesc *lobj;
158 : :
9083 tgl@sss.pgh.pa.us 159 [ + - + - : 422 : if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
- + ]
8082 tgl@sss.pgh.pa.us 160 [ # # ]:UBC 0 : ereport(ERROR,
161 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
162 : : errmsg("invalid large-object descriptor: %d", fd)));
4715 tgl@sss.pgh.pa.us 163 :CBC 422 : 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 : : */
2858 170 [ - + ]: 422 : if ((lobj->flags & IFS_RDLOCK) == 0)
2858 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 : :
4715 tgl@sss.pgh.pa.us 176 :CBC 422 : status = inv_read(lobj, buf, len);
177 : :
9220 178 : 422 : return status;
179 : : }
180 : :
181 : : int
6939 bruce@momjian.us 182 : 517 : lo_write(int fd, const char *buf, int len)
183 : : {
184 : : int status;
185 : : LargeObjectDesc *lobj;
186 : :
9083 tgl@sss.pgh.pa.us 187 [ + - + - : 517 : if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
- + ]
8082 tgl@sss.pgh.pa.us 188 [ # # ]:UBC 0 : ereport(ERROR,
189 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
190 : : errmsg("invalid large-object descriptor: %d", fd)));
4715 tgl@sss.pgh.pa.us 191 :CBC 517 : lobj = cookies[fd];
192 : :
193 : : /* see comment in lo_read() */
194 [ + + ]: 517 : if ((lobj->flags & IFS_WRLOCK) == 0)
7390 195 [ + - ]: 3 : ereport(ERROR,
196 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
197 : : errmsg("large object descriptor %d was not opened for writing",
198 : : fd)));
199 : :
4715 200 : 514 : status = inv_write(lobj, buf, len);
201 : :
9220 202 : 514 : return status;
203 : : }
204 : :
205 : : Datum
3175 peter_e@gmx.net 206 : 27 : be_lo_lseek(PG_FUNCTION_ARGS)
207 : : {
9220 tgl@sss.pgh.pa.us 208 : 27 : int32 fd = PG_GETARG_INT32(0);
209 : 27 : int32 offset = PG_GETARG_INT32(1);
210 : 27 : int32 whence = PG_GETARG_INT32(2);
211 : : int64 status;
212 : :
9083 213 [ + - + - : 27 : if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
- + ]
8082 tgl@sss.pgh.pa.us 214 [ # # ]:UBC 0 : ereport(ERROR,
215 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
216 : : errmsg("invalid large-object descriptor: %d", fd)));
217 : :
9595 tgl@sss.pgh.pa.us 218 :CBC 27 : status = inv_seek(cookies[fd], offset, whence);
219 : :
220 : : /* guard against result overflow */
4716 221 [ - + ]: 27 : if (status != (int32) status)
4717 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 : :
4716 tgl@sss.pgh.pa.us 227 :CBC 27 : PG_RETURN_INT32((int32) status);
228 : : }
229 : :
230 : : Datum
3175 peter_e@gmx.net 231 : 12 : be_lo_lseek64(PG_FUNCTION_ARGS)
232 : : {
4717 ishii@postgresql.org 233 : 12 : int32 fd = PG_GETARG_INT32(0);
234 : 12 : int64 offset = PG_GETARG_INT64(1);
235 : 12 : int32 whence = PG_GETARG_INT32(2);
236 : : int64 status;
237 : :
238 [ + - + - : 12 : if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
- + ]
4717 ishii@postgresql.org 239 [ # # ]:UBC 0 : ereport(ERROR,
240 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
241 : : errmsg("invalid large-object descriptor: %d", fd)));
242 : :
4717 ishii@postgresql.org 243 :CBC 12 : status = inv_seek(cookies[fd], offset, whence);
244 : :
245 : 12 : PG_RETURN_INT64(status);
246 : : }
247 : :
248 : : Datum
3175 peter_e@gmx.net 249 : 13 : be_lo_creat(PG_FUNCTION_ARGS)
250 : : {
251 : : Oid lobjId;
252 : :
1160 michael@paquier.xyz 253 : 13 : PreventCommandIfReadOnly("lo_creat()");
254 : :
1403 heikki.linnakangas@i 255 : 10 : lo_cleanup_needed = true;
7390 tgl@sss.pgh.pa.us 256 : 10 : lobjId = inv_create(InvalidOid);
257 : :
258 : 10 : PG_RETURN_OID(lobjId);
259 : : }
260 : :
261 : : Datum
3175 peter_e@gmx.net 262 : 48 : be_lo_create(PG_FUNCTION_ARGS)
263 : : {
7390 tgl@sss.pgh.pa.us 264 : 48 : Oid lobjId = PG_GETARG_OID(0);
265 : :
1160 michael@paquier.xyz 266 : 48 : PreventCommandIfReadOnly("lo_create()");
267 : :
1403 heikki.linnakangas@i 268 : 45 : lo_cleanup_needed = true;
7390 tgl@sss.pgh.pa.us 269 : 45 : lobjId = inv_create(lobjId);
270 : :
9220 271 : 45 : PG_RETURN_OID(lobjId);
272 : : }
273 : :
274 : : Datum
3175 peter_e@gmx.net 275 : 12 : be_lo_tell(PG_FUNCTION_ARGS)
276 : : {
9220 tgl@sss.pgh.pa.us 277 : 12 : int32 fd = PG_GETARG_INT32(0);
278 : : int64 offset;
279 : :
9083 280 [ + - + - : 12 : if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
- + ]
8082 tgl@sss.pgh.pa.us 281 [ # # ]:UBC 0 : ereport(ERROR,
282 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
283 : : errmsg("invalid large-object descriptor: %d", fd)));
284 : :
4717 ishii@postgresql.org 285 :CBC 12 : offset = inv_tell(cookies[fd]);
286 : :
287 : : /* guard against result overflow */
4716 tgl@sss.pgh.pa.us 288 [ - + ]: 12 : if (offset != (int32) offset)
4717 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 : :
4716 tgl@sss.pgh.pa.us 294 :CBC 12 : PG_RETURN_INT32((int32) offset);
295 : : }
296 : :
297 : : Datum
3175 peter_e@gmx.net 298 : 12 : be_lo_tell64(PG_FUNCTION_ARGS)
299 : : {
4717 ishii@postgresql.org 300 : 12 : int32 fd = PG_GETARG_INT32(0);
301 : : int64 offset;
302 : :
303 [ + - + - : 12 : if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
- + ]
4717 ishii@postgresql.org 304 [ # # ]:UBC 0 : ereport(ERROR,
305 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
306 : : errmsg("invalid large-object descriptor: %d", fd)));
307 : :
4716 tgl@sss.pgh.pa.us 308 :CBC 12 : offset = inv_tell(cookies[fd]);
309 : :
310 : 12 : PG_RETURN_INT64(offset);
311 : : }
312 : :
313 : : Datum
3175 peter_e@gmx.net 314 : 53 : be_lo_unlink(PG_FUNCTION_ARGS)
315 : : {
9220 tgl@sss.pgh.pa.us 316 : 53 : Oid lobjId = PG_GETARG_OID(0);
317 : :
1160 michael@paquier.xyz 318 : 53 : PreventCommandIfReadOnly("lo_unlink()");
319 : :
234 peter@eisentraut.org 320 [ - + ]: 50 : if (!LargeObjectExists(lobjId))
234 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 : : */
5748 itagaki.takahiro@gma 330 [ + + ]:CBC 50 : if (!lo_compat_privileges &&
631 tgl@sss.pgh.pa.us 331 [ + + ]: 47 : !object_ownercheck(LargeObjectRelationId, lobjId, GetUserId()))
5748 itagaki.takahiro@gma 332 [ + - ]: 6 : 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 : : */
9083 tgl@sss.pgh.pa.us 339 [ - + ]: 44 : if (fscxt != NULL)
340 : : {
341 : : int i;
342 : :
9083 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)
1403 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 : : */
9220 tgl@sss.pgh.pa.us 354 :CBC 44 : PG_RETURN_INT32(inv_drop(lobjId));
355 : : }
356 : :
357 : : /*****************************************************************************
358 : : * Read/Write using bytea
359 : : *****************************************************************************/
360 : :
361 : : Datum
3175 peter_e@gmx.net 362 : 422 : be_loread(PG_FUNCTION_ARGS)
363 : : {
9220 tgl@sss.pgh.pa.us 364 : 422 : int32 fd = PG_GETARG_INT32(0);
365 : 422 : int32 len = PG_GETARG_INT32(1);
366 : : bytea *retval;
367 : : int totalread;
368 : :
369 [ - + ]: 422 : if (len < 0)
9220 tgl@sss.pgh.pa.us 370 :UBC 0 : len = 0;
371 : :
8413 tgl@sss.pgh.pa.us 372 :CBC 422 : retval = (bytea *) palloc(VARHDRSZ + len);
10226 bruce@momjian.us 373 : 422 : totalread = lo_read(fd, VARDATA(retval), len);
6766 tgl@sss.pgh.pa.us 374 : 422 : SET_VARSIZE(retval, totalread + VARHDRSZ);
375 : :
8413 376 : 422 : PG_RETURN_BYTEA_P(retval);
377 : : }
378 : :
379 : : Datum
3175 peter_e@gmx.net 380 : 520 : be_lowrite(PG_FUNCTION_ARGS)
381 : : {
8934 bruce@momjian.us 382 : 520 : int32 fd = PG_GETARG_INT32(0);
3100 noah@leadboat.com 383 : 520 : bytea *wbuf = PG_GETARG_BYTEA_PP(1);
384 : : int bytestowrite;
385 : : int totalwritten;
386 : :
1160 michael@paquier.xyz 387 : 520 : PreventCommandIfReadOnly("lowrite()");
388 : :
3100 noah@leadboat.com 389 [ - + - - : 517 : bytestowrite = VARSIZE_ANY_EXHDR(wbuf);
- - - - -
+ ]
390 [ - + ]: 517 : totalwritten = lo_write(fd, VARDATA_ANY(wbuf), bytestowrite);
9220 tgl@sss.pgh.pa.us 391 : 514 : 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
3175 peter_e@gmx.net 403 : 6 : be_lo_import(PG_FUNCTION_ARGS)
404 : : {
6374 tgl@sss.pgh.pa.us 405 : 6 : text *filename = PG_GETARG_TEXT_PP(0);
406 : :
6377 ishii@postgresql.org 407 : 6 : 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
3175 peter_e@gmx.net 415 :UBC 0 : be_lo_import_with_oid(PG_FUNCTION_ARGS)
416 : : {
6374 tgl@sss.pgh.pa.us 417 : 0 : text *filename = PG_GETARG_TEXT_PP(0);
5931 bruce@momjian.us 418 : 0 : Oid oid = PG_GETARG_OID(1);
419 : :
6377 ishii@postgresql.org 420 : 0 : PG_RETURN_OID(lo_import_internal(filename, oid));
421 : : }
422 : :
423 : : static Oid
6377 ishii@postgresql.org 424 :CBC 6 : 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 : :
1160 michael@paquier.xyz 434 : 6 : PreventCommandIfReadOnly("lo_import()");
435 : :
436 : : /*
437 : : * open the file to be read in
438 : : */
6374 tgl@sss.pgh.pa.us 439 : 3 : text_to_cstring_buffer(filename, fnamebuf, sizeof(fnamebuf));
2905 peter_e@gmx.net 440 : 3 : fd = OpenTransientFile(fnamebuf, O_RDONLY | PG_BINARY);
10226 bruce@momjian.us 441 [ - + ]: 3 : if (fd < 0)
8082 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 : : */
1403 heikki.linnakangas@i 450 :CBC 3 : lo_cleanup_needed = true;
6377 ishii@postgresql.org 451 : 3 : oid = inv_create(lobjOid);
452 : :
453 : : /*
454 : : * read in from the filesystem and write to the inversion object
455 : : */
1403 heikki.linnakangas@i 456 : 3 : lobj = inv_open(oid, INV_WRITE, CurrentMemoryContext);
457 : :
4666 458 [ + + ]: 249 : while ((nbytes = read(fd, buf, BUFSIZE)) > 0)
459 : : {
10226 bruce@momjian.us 460 : 246 : tmp = inv_write(lobj, buf, nbytes);
8082 tgl@sss.pgh.pa.us 461 [ - + ]: 246 : Assert(tmp == nbytes);
462 : : }
463 : :
464 [ - + ]: 3 : if (nbytes < 0)
8082 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 : :
10226 bruce@momjian.us 470 :CBC 3 : inv_close(lobj);
471 : :
2254 peter@eisentraut.org 472 [ - + ]: 3 : if (CloseTransientFile(fd) != 0)
2373 michael@paquier.xyz 473 [ # # ]:UBC 0 : ereport(ERROR,
474 : : (errcode_for_file_access(),
475 : : errmsg("could not close file \"%s\": %m",
476 : : fnamebuf)));
477 : :
6377 ishii@postgresql.org 478 :CBC 3 : return oid;
479 : : }
480 : :
481 : : /*
482 : : * lo_export -
483 : : * exports an (inversion) large object.
484 : : */
485 : : Datum
3175 peter_e@gmx.net 486 : 6 : be_lo_export(PG_FUNCTION_ARGS)
487 : : {
9220 tgl@sss.pgh.pa.us 488 : 6 : Oid lobjId = PG_GETARG_OID(0);
6374 489 : 6 : 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 : : */
1403 heikki.linnakangas@i 501 : 6 : lo_cleanup_needed = true;
502 : 6 : 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 : : */
6374 tgl@sss.pgh.pa.us 511 : 6 : text_to_cstring_buffer(filename, fnamebuf, sizeof(fnamebuf));
5384 512 : 6 : oumask = umask(S_IWGRP | S_IWOTH);
2906 peter_e@gmx.net 513 [ + - ]: 6 : PG_TRY();
514 : : {
2905 515 : 6 : fd = OpenTransientFilePerm(fnamebuf, O_CREAT | O_WRONLY | O_TRUNC | PG_BINARY,
516 : : S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
517 : : }
2136 peter@eisentraut.org 518 :UBC 0 : PG_FINALLY();
519 : : {
2906 peter_e@gmx.net 520 :CBC 6 : umask(oumask);
521 : : }
522 [ - + ]: 6 : PG_END_TRY();
10226 bruce@momjian.us 523 [ + + ]: 6 : if (fd < 0)
8082 tgl@sss.pgh.pa.us 524 [ + - ]: 3 : 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 : : */
10226 bruce@momjian.us 532 [ + + ]: 249 : while ((nbytes = inv_read(lobj, buf, BUFSIZE)) > 0)
533 : : {
4666 heikki.linnakangas@i 534 : 246 : tmp = write(fd, buf, nbytes);
9083 tgl@sss.pgh.pa.us 535 [ - + ]: 246 : if (tmp != nbytes)
8082 tgl@sss.pgh.pa.us 536 [ # # ]:UBC 0 : ereport(ERROR,
537 : : (errcode_for_file_access(),
538 : : errmsg("could not write server file \"%s\": %m",
539 : : fnamebuf)));
540 : : }
541 : :
2254 peter@eisentraut.org 542 [ - + ]:CBC 3 : if (CloseTransientFile(fd) != 0)
2373 michael@paquier.xyz 543 [ # # ]:UBC 0 : ereport(ERROR,
544 : : (errcode_for_file_access(),
545 : : errmsg("could not close file \"%s\": %m",
546 : : fnamebuf)));
547 : :
8082 tgl@sss.pgh.pa.us 548 :CBC 3 : inv_close(lobj);
549 : :
9220 550 : 3 : PG_RETURN_INT32(1);
551 : : }
552 : :
553 : : /*
554 : : * lo_truncate -
555 : : * truncate a large object to a specified length
556 : : */
557 : : static void
4715 558 : 21 : lo_truncate_internal(int32 fd, int64 len)
559 : : {
560 : : LargeObjectDesc *lobj;
561 : :
6762 bruce@momjian.us 562 [ + - + - : 21 : if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
- + ]
6762 bruce@momjian.us 563 [ # # ]:UBC 0 : ereport(ERROR,
564 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
565 : : errmsg("invalid large-object descriptor: %d", fd)));
4715 tgl@sss.pgh.pa.us 566 :CBC 21 : lobj = cookies[fd];
567 : :
568 : : /* see comment in lo_read() */
569 [ - + ]: 21 : if ((lobj->flags & IFS_WRLOCK) == 0)
4717 ishii@postgresql.org 570 [ # # ]:UBC 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 : :
4715 tgl@sss.pgh.pa.us 575 :CBC 21 : inv_truncate(lobj, len);
576 : 21 : }
577 : :
578 : : Datum
3175 peter_e@gmx.net 579 : 18 : be_lo_truncate(PG_FUNCTION_ARGS)
580 : : {
4715 tgl@sss.pgh.pa.us 581 : 18 : int32 fd = PG_GETARG_INT32(0);
582 : 18 : int32 len = PG_GETARG_INT32(1);
583 : :
1160 michael@paquier.xyz 584 : 18 : PreventCommandIfReadOnly("lo_truncate()");
585 : :
4715 tgl@sss.pgh.pa.us 586 : 15 : lo_truncate_internal(fd, len);
4717 ishii@postgresql.org 587 : 15 : PG_RETURN_INT32(0);
588 : : }
589 : :
590 : : Datum
3175 peter_e@gmx.net 591 : 9 : be_lo_truncate64(PG_FUNCTION_ARGS)
592 : : {
4717 ishii@postgresql.org 593 : 9 : int32 fd = PG_GETARG_INT32(0);
594 : 9 : int64 len = PG_GETARG_INT64(1);
595 : :
1160 michael@paquier.xyz 596 : 9 : PreventCommandIfReadOnly("lo_truncate64()");
597 : :
4715 tgl@sss.pgh.pa.us 598 : 6 : lo_truncate_internal(fd, len);
6762 bruce@momjian.us 599 : 6 : PG_RETURN_INT32(0);
600 : : }
601 : :
602 : : /*
603 : : * AtEOXact_LargeObject -
604 : : * prepares large objects for transaction commit
605 : : */
606 : : void
7710 tgl@sss.pgh.pa.us 607 : 317332 : AtEOXact_LargeObject(bool isCommit)
608 : : {
609 : : int i;
610 : :
1403 heikki.linnakangas@i 611 [ + + ]: 317332 : if (!lo_cleanup_needed)
9595 tgl@sss.pgh.pa.us 612 : 317091 : 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 : : */
1403 heikki.linnakangas@i 621 [ + + ]: 241 : if (isCommit)
622 : : {
623 [ + + ]: 4053 : for (i = 0; i < cookies_size; i++)
624 : : {
625 [ + + ]: 3904 : if (cookies[i] != NULL)
626 : 36 : closeLOfd(i);
627 : : }
628 : : }
629 : :
630 : : /* Needn't actually pfree since we're about to zap context */
9083 tgl@sss.pgh.pa.us 631 : 241 : cookies = NULL;
632 : 241 : cookies_size = 0;
633 : :
634 : : /* Release the LO memory context to prevent permanent memory leaks. */
1403 heikki.linnakangas@i 635 [ + + ]: 241 : if (fscxt)
636 : 139 : MemoryContextDelete(fscxt);
9595 tgl@sss.pgh.pa.us 637 : 241 : fscxt = NULL;
638 : :
639 : : /* Give inv_api.c a chance to clean up, too */
7710 640 : 241 : close_lo_relation(isCommit);
641 : :
1403 heikki.linnakangas@i 642 : 241 : 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
7660 tgl@sss.pgh.pa.us 653 : 9090 : AtEOSubXact_LargeObject(bool isCommit, SubTransactionId mySubid,
654 : : SubTransactionId parentSubid)
655 : : {
656 : : int i;
657 : :
7710 658 [ + - ]: 9090 : if (fscxt == NULL) /* no LO operations in this xact */
659 : 9090 : return;
660 : :
7710 tgl@sss.pgh.pa.us 661 [ # # ]:UBC 0 : for (i = 0; i < cookies_size; i++)
662 : : {
663 : 0 : LargeObjectDesc *lo = cookies[i];
664 : :
7660 665 [ # # # # ]: 0 : if (lo != NULL && lo->subid == mySubid)
666 : : {
7710 667 [ # # ]: 0 : if (isCommit)
7660 668 : 0 : lo->subid = parentSubid;
669 : : else
1403 heikki.linnakangas@i 670 : 0 : closeLOfd(i);
671 : : }
672 : : }
673 : : }
674 : :
675 : : /*****************************************************************************
676 : : * Support routines for this file
677 : : *****************************************************************************/
678 : :
679 : : static int
1403 heikki.linnakangas@i 680 :CBC 180 : newLOfd(void)
681 : : {
682 : : int i,
683 : : newsize;
684 : :
685 : 180 : lo_cleanup_needed = true;
686 [ + + ]: 180 : if (fscxt == NULL)
687 : 139 : fscxt = AllocSetContextCreate(TopMemoryContext,
688 : : "Filesystem",
689 : : ALLOCSET_DEFAULT_SIZES);
690 : :
691 : : /* Try to find a free slot */
9083 tgl@sss.pgh.pa.us 692 [ + + ]: 180 : for (i = 0; i < cookies_size; i++)
693 : : {
10226 bruce@momjian.us 694 [ + - ]: 41 : if (cookies[i] == NULL)
695 : 41 : return i;
696 : : }
697 : :
698 : : /* No free slot, so make the array bigger */
9083 tgl@sss.pgh.pa.us 699 [ + - ]: 139 : if (cookies_size <= 0)
700 : : {
701 : : /* First time through, arbitrarily make 64-element array */
702 : 139 : i = 0;
703 : 139 : newsize = 64;
704 : 139 : cookies = (LargeObjectDesc **)
7073 705 : 139 : MemoryContextAllocZero(fscxt, newsize * sizeof(LargeObjectDesc *));
706 : : }
707 : : else
708 : : {
709 : : /* Double size of array */
9083 tgl@sss.pgh.pa.us 710 :UBC 0 : i = cookies_size;
711 : 0 : newsize = cookies_size * 2;
1029 peter@eisentraut.org 712 : 0 : cookies =
713 : 0 : repalloc0_array(cookies, LargeObjectDesc *, cookies_size, newsize);
714 : : }
1029 peter@eisentraut.org 715 :CBC 139 : cookies_size = newsize;
716 : :
9083 tgl@sss.pgh.pa.us 717 : 139 : return i;
718 : : }
719 : :
720 : : static void
1403 heikki.linnakangas@i 721 : 147 : 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 : 147 : lobj = cookies[fd];
10226 bruce@momjian.us 730 : 147 : cookies[fd] = NULL;
731 : :
1403 heikki.linnakangas@i 732 [ + + ]: 147 : if (lobj->snapshot)
733 : 107 : UnregisterSnapshotFromOwner(lobj->snapshot,
734 : : TopTransactionResourceOwner);
735 : 147 : inv_close(lobj);
10651 scrappy@hub.org 736 : 147 : }
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 *
4332 noah@leadboat.com 746 : 44 : 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 : 44 : bytea *result = NULL;
753 : :
1403 heikki.linnakangas@i 754 : 44 : lo_cleanup_needed = true;
755 : 44 : 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 : : */
4332 noah@leadboat.com 761 : 39 : loSize = inv_seek(loDesc, 0, SEEK_END);
762 [ + + ]: 39 : if (loSize > offset)
763 : : {
764 [ + + + + ]: 30 : if (nbytes >= 0 && nbytes <= loSize - offset)
2999 tgl@sss.pgh.pa.us 765 : 9 : result_length = nbytes; /* request is wholly inside LO */
766 : : else
4332 noah@leadboat.com 767 : 21 : result_length = loSize - offset; /* adjust to end of LO */
768 : : }
769 : : else
770 : 9 : 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 [ + + ]: 39 : if (result_length > MaxAllocSize - VARHDRSZ)
777 [ + - ]: 3 : ereport(ERROR,
778 : : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
779 : : errmsg("large object read request is too large")));
780 : :
781 : 36 : result = (bytea *) palloc(VARHDRSZ + result_length);
782 : :
783 : 36 : inv_seek(loDesc, offset, SEEK_SET);
784 : 36 : total_read = inv_read(loDesc, VARDATA(result), result_length);
785 [ - + ]: 36 : Assert(total_read == result_length);
786 : 36 : SET_VARSIZE(result, result_length + VARHDRSZ);
787 : :
788 : 36 : inv_close(loDesc);
789 : :
790 : 36 : return result;
791 : : }
792 : :
793 : : /*
794 : : * Read entire LO
795 : : */
796 : : Datum
3175 peter_e@gmx.net 797 : 32 : be_lo_get(PG_FUNCTION_ARGS)
798 : : {
4332 noah@leadboat.com 799 : 32 : Oid loOid = PG_GETARG_OID(0);
800 : : bytea *result;
801 : :
802 : 32 : result = lo_get_fragment_internal(loOid, 0, -1);
803 : :
804 : 24 : PG_RETURN_BYTEA_P(result);
805 : : }
806 : :
807 : : /*
808 : : * Read range within LO
809 : : */
810 : : Datum
3175 peter_e@gmx.net 811 : 12 : be_lo_get_fragment(PG_FUNCTION_ARGS)
812 : : {
4332 noah@leadboat.com 813 : 12 : Oid loOid = PG_GETARG_OID(0);
814 : 12 : int64 offset = PG_GETARG_INT64(1);
815 : 12 : int32 nbytes = PG_GETARG_INT32(2);
816 : : bytea *result;
817 : :
818 [ - + ]: 12 : if (nbytes < 0)
4332 noah@leadboat.com 819 [ # # ]:UBC 0 : ereport(ERROR,
820 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
821 : : errmsg("requested length cannot be negative")));
822 : :
4332 noah@leadboat.com 823 :CBC 12 : result = lo_get_fragment_internal(loOid, offset, nbytes);
824 : :
825 : 12 : PG_RETURN_BYTEA_P(result);
826 : : }
827 : :
828 : : /*
829 : : * Create LO with initial contents given by a bytea argument
830 : : */
831 : : Datum
3175 peter_e@gmx.net 832 : 13 : be_lo_from_bytea(PG_FUNCTION_ARGS)
833 : : {
4332 noah@leadboat.com 834 : 13 : Oid loOid = PG_GETARG_OID(0);
835 : 13 : bytea *str = PG_GETARG_BYTEA_PP(1);
836 : : LargeObjectDesc *loDesc;
837 : : int written PG_USED_FOR_ASSERTS_ONLY;
838 : :
1160 michael@paquier.xyz 839 : 13 : PreventCommandIfReadOnly("lo_from_bytea()");
840 : :
1403 heikki.linnakangas@i 841 : 10 : lo_cleanup_needed = true;
4332 noah@leadboat.com 842 : 10 : loOid = inv_create(loOid);
1403 heikki.linnakangas@i 843 : 10 : loDesc = inv_open(loOid, INV_WRITE, CurrentMemoryContext);
4332 noah@leadboat.com 844 [ - + - - : 10 : written = inv_write(loDesc, VARDATA_ANY(str), VARSIZE_ANY_EXHDR(str));
- - - - -
+ - + ]
845 [ - + - - : 10 : Assert(written == VARSIZE_ANY_EXHDR(str));
- - - - -
+ - + ]
846 : 10 : inv_close(loDesc);
847 : :
848 : 10 : PG_RETURN_OID(loOid);
849 : : }
850 : :
851 : : /*
852 : : * Update range within LO
853 : : */
854 : : Datum
3175 peter_e@gmx.net 855 : 12 : be_lo_put(PG_FUNCTION_ARGS)
856 : : {
4332 noah@leadboat.com 857 : 12 : Oid loOid = PG_GETARG_OID(0);
858 : 12 : int64 offset = PG_GETARG_INT64(1);
859 : 12 : bytea *str = PG_GETARG_BYTEA_PP(2);
860 : : LargeObjectDesc *loDesc;
861 : : int written PG_USED_FOR_ASSERTS_ONLY;
862 : :
1160 michael@paquier.xyz 863 : 12 : PreventCommandIfReadOnly("lo_put()");
864 : :
1403 heikki.linnakangas@i 865 : 9 : lo_cleanup_needed = true;
866 : 9 : loDesc = inv_open(loOid, INV_WRITE, CurrentMemoryContext);
4332 noah@leadboat.com 867 : 6 : inv_seek(loDesc, offset, SEEK_SET);
868 [ - + - - : 6 : written = inv_write(loDesc, VARDATA_ANY(str), VARSIZE_ANY_EXHDR(str));
- - - - -
+ - + ]
869 [ - + - - : 6 : Assert(written == VARSIZE_ANY_EXHDR(str));
- - - - -
+ - + ]
870 : 6 : inv_close(loDesc);
871 : :
872 : 6 : PG_RETURN_VOID();
873 : : }
|