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-2024, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : *
10 : : * IDENTIFICATION
11 : : * src/backend/libpq/be-fsstubs.c
12 : : *
13 : : * NOTES
14 : : * This should be moved to a more appropriate place. It is here
15 : : * for lack of a better place.
16 : : *
17 : : * These functions store LargeObjectDesc structs in a private MemoryContext,
18 : : * which means that large object descriptors hang around until we destroy
19 : : * the context at transaction end. It'd be possible to prolong the lifetime
20 : : * of the context so that LO FDs are good across transactions (for example,
21 : : * we could release the context only if we see that no FDs remain open).
22 : : * But we'd need additional state in order to do the right thing at the
23 : : * end of an aborted transaction. FDs opened during an aborted xact would
24 : : * still need to be closed, since they might not be pointing at valid
25 : : * relations at all. Locking semantics are also an interesting problem
26 : : * if LOs stay open across transactions. For now, we'll stick with the
27 : : * existing documented semantics of LO FDs: they're only good within a
28 : : * transaction.
29 : : *
30 : : * As of PostgreSQL 8.0, much of the angst expressed above is no longer
31 : : * relevant, and in fact it'd be pretty easy to allow LO FDs to stay
32 : : * open across transactions. (Snapshot relevancy would still be an issue.)
33 : : * However backwards compatibility suggests that we should stick to the
34 : : * status quo.
35 : : *
36 : : *-------------------------------------------------------------------------
37 : : */
38 : :
39 : : #include "postgres.h"
40 : :
41 : : #include <fcntl.h>
42 : : #include <sys/stat.h>
43 : : #include <unistd.h>
44 : :
45 : : #include "access/xact.h"
46 : : #include "catalog/pg_largeobject.h"
47 : : #include "libpq/be-fsstubs.h"
48 : : #include "libpq/libpq-fs.h"
49 : : #include "miscadmin.h"
50 : : #include "storage/fd.h"
51 : : #include "storage/large_object.h"
52 : : #include "utils/acl.h"
53 : : #include "utils/builtins.h"
54 : : #include "utils/memutils.h"
55 : : #include "utils/snapmgr.h"
56 : : #include "varatt.h"
57 : :
58 : : /* define this to enable debug logging */
59 : : /* #define FSDB 1 */
60 : : /* chunk size for lo_import/lo_export transfers */
61 : : #define BUFSIZE 8192
62 : :
63 : : /*
64 : : * LO "FD"s are indexes into the cookies array.
65 : : *
66 : : * A non-null entry is a pointer to a LargeObjectDesc allocated in the
67 : : * LO private memory context "fscxt". The cookies array itself is also
68 : : * dynamically allocated in that context. Its current allocated size is
69 : : * cookies_size entries, of which any unused entries will be NULL.
70 : : */
71 : : static LargeObjectDesc **cookies = NULL;
72 : : static int cookies_size = 0;
73 : :
74 : : static bool lo_cleanup_needed = false;
75 : : static MemoryContext fscxt = NULL;
76 : :
77 : : static int newLOfd(void);
78 : : static void closeLOfd(int fd);
79 : : static Oid lo_import_internal(text *filename, Oid lobjOid);
80 : :
81 : :
82 : : /*****************************************************************************
83 : : * File Interfaces for Large Objects
84 : : *****************************************************************************/
85 : :
86 : : Datum
2665 peter_e@gmx.net 87 :CBC 177 : be_lo_open(PG_FUNCTION_ARGS)
88 : : {
8710 tgl@sss.pgh.pa.us 89 : 177 : Oid lobjId = PG_GETARG_OID(0);
90 : 177 : 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 : :
650 michael@paquier.xyz 98 [ + + ]: 177 : 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 : : */
893 heikki.linnakangas@i 105 : 174 : fd = newLOfd();
106 : :
6563 tgl@sss.pgh.pa.us 107 : 174 : lobjDesc = inv_open(lobjId, mode, fscxt);
893 heikki.linnakangas@i 108 : 150 : 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 [ + + ]: 150 : if (lobjDesc->snapshot)
116 : 110 : lobjDesc->snapshot = RegisterSnapshotOnOwner(lobjDesc->snapshot,
117 : : TopTransactionResourceOwner);
118 : :
119 [ - + ]: 150 : Assert(cookies[fd] == NULL);
120 : 150 : cookies[fd] = lobjDesc;
121 : :
8710 tgl@sss.pgh.pa.us 122 : 150 : PG_RETURN_INT32(fd);
123 : : }
124 : :
125 : : Datum
2665 peter_e@gmx.net 126 : 105 : be_lo_close(PG_FUNCTION_ARGS)
127 : : {
8710 tgl@sss.pgh.pa.us 128 : 105 : int32 fd = PG_GETARG_INT32(0);
129 : :
8573 130 [ + - + - : 105 : if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
- + ]
7572 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 : :
893 heikki.linnakangas@i 139 :CBC 105 : closeLOfd(fd);
140 : :
8710 tgl@sss.pgh.pa.us 141 : 105 : 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
10141 scrappy@hub.org 154 : 413 : lo_read(int fd, char *buf, int len)
155 : : {
156 : : int status;
157 : : LargeObjectDesc *lobj;
158 : :
8573 tgl@sss.pgh.pa.us 159 [ + - + - : 413 : if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
- + ]
7572 tgl@sss.pgh.pa.us 160 [ # # ]:UBC 0 : ereport(ERROR,
161 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
162 : : errmsg("invalid large-object descriptor: %d", fd)));
4205 tgl@sss.pgh.pa.us 163 :CBC 413 : 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 : : */
2348 170 [ - + ]: 413 : if ((lobj->flags & IFS_RDLOCK) == 0)
2348 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 : :
4205 tgl@sss.pgh.pa.us 176 :CBC 413 : status = inv_read(lobj, buf, len);
177 : :
8710 178 : 413 : return status;
179 : : }
180 : :
181 : : int
6429 bruce@momjian.us 182 : 517 : lo_write(int fd, const char *buf, int len)
183 : : {
184 : : int status;
185 : : LargeObjectDesc *lobj;
186 : :
8573 tgl@sss.pgh.pa.us 187 [ + - + - : 517 : if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
- + ]
7572 tgl@sss.pgh.pa.us 188 [ # # ]:UBC 0 : ereport(ERROR,
189 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
190 : : errmsg("invalid large-object descriptor: %d", fd)));
4205 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)
6880 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 : :
4205 200 : 514 : status = inv_write(lobj, buf, len);
201 : :
8710 202 : 514 : return status;
203 : : }
204 : :
205 : : Datum
2665 peter_e@gmx.net 206 : 27 : be_lo_lseek(PG_FUNCTION_ARGS)
207 : : {
8710 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 : :
8573 213 [ + - + - : 27 : if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
- + ]
7572 tgl@sss.pgh.pa.us 214 [ # # ]:UBC 0 : ereport(ERROR,
215 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
216 : : errmsg("invalid large-object descriptor: %d", fd)));
217 : :
9085 tgl@sss.pgh.pa.us 218 :CBC 27 : status = inv_seek(cookies[fd], offset, whence);
219 : :
220 : : /* guard against result overflow */
4206 221 [ - + ]: 27 : if (status != (int32) status)
4207 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 : :
4206 tgl@sss.pgh.pa.us 227 :CBC 27 : PG_RETURN_INT32((int32) status);
228 : : }
229 : :
230 : : Datum
2665 peter_e@gmx.net 231 : 12 : be_lo_lseek64(PG_FUNCTION_ARGS)
232 : : {
4207 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)
- + ]
4207 ishii@postgresql.org 239 [ # # ]:UBC 0 : ereport(ERROR,
240 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
241 : : errmsg("invalid large-object descriptor: %d", fd)));
242 : :
4207 ishii@postgresql.org 243 :CBC 12 : status = inv_seek(cookies[fd], offset, whence);
244 : :
245 : 12 : PG_RETURN_INT64(status);
246 : : }
247 : :
248 : : Datum
2665 peter_e@gmx.net 249 : 13 : be_lo_creat(PG_FUNCTION_ARGS)
250 : : {
251 : : Oid lobjId;
252 : :
650 michael@paquier.xyz 253 : 13 : PreventCommandIfReadOnly("lo_creat()");
254 : :
893 heikki.linnakangas@i 255 : 10 : lo_cleanup_needed = true;
6880 tgl@sss.pgh.pa.us 256 : 10 : lobjId = inv_create(InvalidOid);
257 : :
258 : 10 : PG_RETURN_OID(lobjId);
259 : : }
260 : :
261 : : Datum
2665 peter_e@gmx.net 262 : 36 : be_lo_create(PG_FUNCTION_ARGS)
263 : : {
6880 tgl@sss.pgh.pa.us 264 : 36 : Oid lobjId = PG_GETARG_OID(0);
265 : :
650 michael@paquier.xyz 266 : 36 : PreventCommandIfReadOnly("lo_create()");
267 : :
893 heikki.linnakangas@i 268 : 33 : lo_cleanup_needed = true;
6880 tgl@sss.pgh.pa.us 269 : 33 : lobjId = inv_create(lobjId);
270 : :
8710 271 : 33 : PG_RETURN_OID(lobjId);
272 : : }
273 : :
274 : : Datum
2665 peter_e@gmx.net 275 : 12 : be_lo_tell(PG_FUNCTION_ARGS)
276 : : {
8710 tgl@sss.pgh.pa.us 277 : 12 : int32 fd = PG_GETARG_INT32(0);
278 : : int64 offset;
279 : :
8573 280 [ + - + - : 12 : if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
- + ]
7572 tgl@sss.pgh.pa.us 281 [ # # ]:UBC 0 : ereport(ERROR,
282 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
283 : : errmsg("invalid large-object descriptor: %d", fd)));
284 : :
4207 ishii@postgresql.org 285 :CBC 12 : offset = inv_tell(cookies[fd]);
286 : :
287 : : /* guard against result overflow */
4206 tgl@sss.pgh.pa.us 288 [ - + ]: 12 : if (offset != (int32) offset)
4207 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 : :
4206 tgl@sss.pgh.pa.us 294 :CBC 12 : PG_RETURN_INT32((int32) offset);
295 : : }
296 : :
297 : : Datum
2665 peter_e@gmx.net 298 : 12 : be_lo_tell64(PG_FUNCTION_ARGS)
299 : : {
4207 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)
- + ]
4207 ishii@postgresql.org 304 [ # # ]:UBC 0 : ereport(ERROR,
305 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
306 : : errmsg("invalid large-object descriptor: %d", fd)));
307 : :
4206 tgl@sss.pgh.pa.us 308 :CBC 12 : offset = inv_tell(cookies[fd]);
309 : :
310 : 12 : PG_RETURN_INT64(offset);
311 : : }
312 : :
313 : : Datum
2665 peter_e@gmx.net 314 : 50 : be_lo_unlink(PG_FUNCTION_ARGS)
315 : : {
8710 tgl@sss.pgh.pa.us 316 : 50 : Oid lobjId = PG_GETARG_OID(0);
317 : :
650 michael@paquier.xyz 318 : 50 : PreventCommandIfReadOnly("lo_unlink()");
319 : :
320 : : /*
321 : : * Must be owner of the large object. It would be cleaner to check this
322 : : * in inv_drop(), but we want to throw the error before not after closing
323 : : * relevant FDs.
324 : : */
5238 itagaki.takahiro@gma 325 [ + + ]: 47 : if (!lo_compat_privileges &&
121 tgl@sss.pgh.pa.us 326 [ + + ]: 44 : !object_ownercheck(LargeObjectRelationId, lobjId, GetUserId()))
5238 itagaki.takahiro@gma 327 [ + - ]: 6 : ereport(ERROR,
328 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
329 : : errmsg("must be owner of large object %u", lobjId)));
330 : :
331 : : /*
332 : : * If there are any open LO FDs referencing that ID, close 'em.
333 : : */
8573 tgl@sss.pgh.pa.us 334 [ - + ]: 41 : if (fscxt != NULL)
335 : : {
336 : : int i;
337 : :
8573 tgl@sss.pgh.pa.us 338 [ # # ]:UBC 0 : for (i = 0; i < cookies_size; i++)
339 : : {
340 [ # # # # ]: 0 : if (cookies[i] != NULL && cookies[i]->id == lobjId)
893 heikki.linnakangas@i 341 : 0 : closeLOfd(i);
342 : : }
343 : : }
344 : :
345 : : /*
346 : : * inv_drop does not create a need for end-of-transaction cleanup and
347 : : * hence we don't need to set lo_cleanup_needed.
348 : : */
8710 tgl@sss.pgh.pa.us 349 :CBC 41 : PG_RETURN_INT32(inv_drop(lobjId));
350 : : }
351 : :
352 : : /*****************************************************************************
353 : : * Read/Write using bytea
354 : : *****************************************************************************/
355 : :
356 : : Datum
2665 peter_e@gmx.net 357 : 413 : be_loread(PG_FUNCTION_ARGS)
358 : : {
8710 tgl@sss.pgh.pa.us 359 : 413 : int32 fd = PG_GETARG_INT32(0);
360 : 413 : int32 len = PG_GETARG_INT32(1);
361 : : bytea *retval;
362 : : int totalread;
363 : :
364 [ - + ]: 413 : if (len < 0)
8710 tgl@sss.pgh.pa.us 365 :UBC 0 : len = 0;
366 : :
7903 tgl@sss.pgh.pa.us 367 :CBC 413 : retval = (bytea *) palloc(VARHDRSZ + len);
9716 bruce@momjian.us 368 : 413 : totalread = lo_read(fd, VARDATA(retval), len);
6256 tgl@sss.pgh.pa.us 369 : 413 : SET_VARSIZE(retval, totalread + VARHDRSZ);
370 : :
7903 371 : 413 : PG_RETURN_BYTEA_P(retval);
372 : : }
373 : :
374 : : Datum
2665 peter_e@gmx.net 375 : 520 : be_lowrite(PG_FUNCTION_ARGS)
376 : : {
8424 bruce@momjian.us 377 : 520 : int32 fd = PG_GETARG_INT32(0);
2590 noah@leadboat.com 378 : 520 : bytea *wbuf = PG_GETARG_BYTEA_PP(1);
379 : : int bytestowrite;
380 : : int totalwritten;
381 : :
650 michael@paquier.xyz 382 : 520 : PreventCommandIfReadOnly("lowrite()");
383 : :
2590 noah@leadboat.com 384 [ - + - - : 517 : bytestowrite = VARSIZE_ANY_EXHDR(wbuf);
- - - - -
+ ]
385 [ - + ]: 517 : totalwritten = lo_write(fd, VARDATA_ANY(wbuf), bytestowrite);
8710 tgl@sss.pgh.pa.us 386 : 514 : PG_RETURN_INT32(totalwritten);
387 : : }
388 : :
389 : : /*****************************************************************************
390 : : * Import/Export of Large Object
391 : : *****************************************************************************/
392 : :
393 : : /*
394 : : * lo_import -
395 : : * imports a file as an (inversion) large object.
396 : : */
397 : : Datum
2665 peter_e@gmx.net 398 : 6 : be_lo_import(PG_FUNCTION_ARGS)
399 : : {
5864 tgl@sss.pgh.pa.us 400 : 6 : text *filename = PG_GETARG_TEXT_PP(0);
401 : :
5867 ishii@postgresql.org 402 : 6 : PG_RETURN_OID(lo_import_internal(filename, InvalidOid));
403 : : }
404 : :
405 : : /*
406 : : * lo_import_with_oid -
407 : : * imports a file as an (inversion) large object specifying oid.
408 : : */
409 : : Datum
2665 peter_e@gmx.net 410 :UBC 0 : be_lo_import_with_oid(PG_FUNCTION_ARGS)
411 : : {
5864 tgl@sss.pgh.pa.us 412 : 0 : text *filename = PG_GETARG_TEXT_PP(0);
5421 bruce@momjian.us 413 : 0 : Oid oid = PG_GETARG_OID(1);
414 : :
5867 ishii@postgresql.org 415 : 0 : PG_RETURN_OID(lo_import_internal(filename, oid));
416 : : }
417 : :
418 : : static Oid
5867 ishii@postgresql.org 419 :CBC 6 : lo_import_internal(text *filename, Oid lobjOid)
420 : : {
421 : : int fd;
422 : : int nbytes,
423 : : tmp PG_USED_FOR_ASSERTS_ONLY;
424 : : char buf[BUFSIZE];
425 : : char fnamebuf[MAXPGPATH];
426 : : LargeObjectDesc *lobj;
427 : : Oid oid;
428 : :
650 michael@paquier.xyz 429 : 6 : PreventCommandIfReadOnly("lo_import()");
430 : :
431 : : /*
432 : : * open the file to be read in
433 : : */
5864 tgl@sss.pgh.pa.us 434 : 3 : text_to_cstring_buffer(filename, fnamebuf, sizeof(fnamebuf));
2395 peter_e@gmx.net 435 : 3 : fd = OpenTransientFile(fnamebuf, O_RDONLY | PG_BINARY);
9716 bruce@momjian.us 436 [ - + ]: 3 : if (fd < 0)
7572 tgl@sss.pgh.pa.us 437 [ # # ]:UBC 0 : ereport(ERROR,
438 : : (errcode_for_file_access(),
439 : : errmsg("could not open server file \"%s\": %m",
440 : : fnamebuf)));
441 : :
442 : : /*
443 : : * create an inversion object
444 : : */
893 heikki.linnakangas@i 445 :CBC 3 : lo_cleanup_needed = true;
5867 ishii@postgresql.org 446 : 3 : oid = inv_create(lobjOid);
447 : :
448 : : /*
449 : : * read in from the filesystem and write to the inversion object
450 : : */
893 heikki.linnakangas@i 451 : 3 : lobj = inv_open(oid, INV_WRITE, CurrentMemoryContext);
452 : :
4156 453 [ + + ]: 249 : while ((nbytes = read(fd, buf, BUFSIZE)) > 0)
454 : : {
9716 bruce@momjian.us 455 : 246 : tmp = inv_write(lobj, buf, nbytes);
7572 tgl@sss.pgh.pa.us 456 [ - + ]: 246 : Assert(tmp == nbytes);
457 : : }
458 : :
459 [ - + ]: 3 : if (nbytes < 0)
7572 tgl@sss.pgh.pa.us 460 [ # # ]:UBC 0 : ereport(ERROR,
461 : : (errcode_for_file_access(),
462 : : errmsg("could not read server file \"%s\": %m",
463 : : fnamebuf)));
464 : :
9716 bruce@momjian.us 465 :CBC 3 : inv_close(lobj);
466 : :
1744 peter@eisentraut.org 467 [ - + ]: 3 : if (CloseTransientFile(fd) != 0)
1863 michael@paquier.xyz 468 [ # # ]:UBC 0 : ereport(ERROR,
469 : : (errcode_for_file_access(),
470 : : errmsg("could not close file \"%s\": %m",
471 : : fnamebuf)));
472 : :
5867 ishii@postgresql.org 473 :CBC 3 : return oid;
474 : : }
475 : :
476 : : /*
477 : : * lo_export -
478 : : * exports an (inversion) large object.
479 : : */
480 : : Datum
2665 peter_e@gmx.net 481 : 6 : be_lo_export(PG_FUNCTION_ARGS)
482 : : {
8710 tgl@sss.pgh.pa.us 483 : 6 : Oid lobjId = PG_GETARG_OID(0);
5864 484 : 6 : text *filename = PG_GETARG_TEXT_PP(1);
485 : : int fd;
486 : : int nbytes,
487 : : tmp;
488 : : char buf[BUFSIZE];
489 : : char fnamebuf[MAXPGPATH];
490 : : LargeObjectDesc *lobj;
491 : : mode_t oumask;
492 : :
493 : : /*
494 : : * open the inversion object (no need to test for failure)
495 : : */
893 heikki.linnakangas@i 496 : 6 : lo_cleanup_needed = true;
497 : 6 : lobj = inv_open(lobjId, INV_READ, CurrentMemoryContext);
498 : :
499 : : /*
500 : : * open the file to be written to
501 : : *
502 : : * Note: we reduce backend's normal 077 umask to the slightly friendlier
503 : : * 022. This code used to drop it all the way to 0, but creating
504 : : * world-writable export files doesn't seem wise.
505 : : */
5864 tgl@sss.pgh.pa.us 506 : 6 : text_to_cstring_buffer(filename, fnamebuf, sizeof(fnamebuf));
4874 507 : 6 : oumask = umask(S_IWGRP | S_IWOTH);
2396 peter_e@gmx.net 508 [ + - ]: 6 : PG_TRY();
509 : : {
2395 510 : 6 : fd = OpenTransientFilePerm(fnamebuf, O_CREAT | O_WRONLY | O_TRUNC | PG_BINARY,
511 : : S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
512 : : }
1626 peter@eisentraut.org 513 :UBC 0 : PG_FINALLY();
514 : : {
2396 peter_e@gmx.net 515 :CBC 6 : umask(oumask);
516 : : }
517 [ - + ]: 6 : PG_END_TRY();
9716 bruce@momjian.us 518 [ + + ]: 6 : if (fd < 0)
7572 tgl@sss.pgh.pa.us 519 [ + - ]: 3 : ereport(ERROR,
520 : : (errcode_for_file_access(),
521 : : errmsg("could not create server file \"%s\": %m",
522 : : fnamebuf)));
523 : :
524 : : /*
525 : : * read in from the inversion file and write to the filesystem
526 : : */
9716 bruce@momjian.us 527 [ + + ]: 249 : while ((nbytes = inv_read(lobj, buf, BUFSIZE)) > 0)
528 : : {
4156 heikki.linnakangas@i 529 : 246 : tmp = write(fd, buf, nbytes);
8573 tgl@sss.pgh.pa.us 530 [ - + ]: 246 : if (tmp != nbytes)
7572 tgl@sss.pgh.pa.us 531 [ # # ]:UBC 0 : ereport(ERROR,
532 : : (errcode_for_file_access(),
533 : : errmsg("could not write server file \"%s\": %m",
534 : : fnamebuf)));
535 : : }
536 : :
1744 peter@eisentraut.org 537 [ - + ]:CBC 3 : if (CloseTransientFile(fd) != 0)
1863 michael@paquier.xyz 538 [ # # ]:UBC 0 : ereport(ERROR,
539 : : (errcode_for_file_access(),
540 : : errmsg("could not close file \"%s\": %m",
541 : : fnamebuf)));
542 : :
7572 tgl@sss.pgh.pa.us 543 :CBC 3 : inv_close(lobj);
544 : :
8710 545 : 3 : PG_RETURN_INT32(1);
546 : : }
547 : :
548 : : /*
549 : : * lo_truncate -
550 : : * truncate a large object to a specified length
551 : : */
552 : : static void
4205 553 : 21 : lo_truncate_internal(int32 fd, int64 len)
554 : : {
555 : : LargeObjectDesc *lobj;
556 : :
6252 bruce@momjian.us 557 [ + - + - : 21 : if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
- + ]
6252 bruce@momjian.us 558 [ # # ]:UBC 0 : ereport(ERROR,
559 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
560 : : errmsg("invalid large-object descriptor: %d", fd)));
4205 tgl@sss.pgh.pa.us 561 :CBC 21 : lobj = cookies[fd];
562 : :
563 : : /* see comment in lo_read() */
564 [ - + ]: 21 : if ((lobj->flags & IFS_WRLOCK) == 0)
4207 ishii@postgresql.org 565 [ # # ]:UBC 0 : ereport(ERROR,
566 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
567 : : errmsg("large object descriptor %d was not opened for writing",
568 : : fd)));
569 : :
4205 tgl@sss.pgh.pa.us 570 :CBC 21 : inv_truncate(lobj, len);
571 : 21 : }
572 : :
573 : : Datum
2665 peter_e@gmx.net 574 : 18 : be_lo_truncate(PG_FUNCTION_ARGS)
575 : : {
4205 tgl@sss.pgh.pa.us 576 : 18 : int32 fd = PG_GETARG_INT32(0);
577 : 18 : int32 len = PG_GETARG_INT32(1);
578 : :
650 michael@paquier.xyz 579 : 18 : PreventCommandIfReadOnly("lo_truncate()");
580 : :
4205 tgl@sss.pgh.pa.us 581 : 15 : lo_truncate_internal(fd, len);
4207 ishii@postgresql.org 582 : 15 : PG_RETURN_INT32(0);
583 : : }
584 : :
585 : : Datum
2665 peter_e@gmx.net 586 : 9 : be_lo_truncate64(PG_FUNCTION_ARGS)
587 : : {
4207 ishii@postgresql.org 588 : 9 : int32 fd = PG_GETARG_INT32(0);
589 : 9 : int64 len = PG_GETARG_INT64(1);
590 : :
650 michael@paquier.xyz 591 : 9 : PreventCommandIfReadOnly("lo_truncate64()");
592 : :
4205 tgl@sss.pgh.pa.us 593 : 6 : lo_truncate_internal(fd, len);
6252 bruce@momjian.us 594 : 6 : PG_RETURN_INT32(0);
595 : : }
596 : :
597 : : /*
598 : : * AtEOXact_LargeObject -
599 : : * prepares large objects for transaction commit
600 : : */
601 : : void
7200 tgl@sss.pgh.pa.us 602 : 432814 : AtEOXact_LargeObject(bool isCommit)
603 : : {
604 : : int i;
605 : :
893 heikki.linnakangas@i 606 [ + + ]: 432814 : if (!lo_cleanup_needed)
9085 tgl@sss.pgh.pa.us 607 : 432589 : return; /* no LO operations in this xact */
608 : :
609 : : /*
610 : : * Close LO fds and clear cookies array so that LO fds are no longer good.
611 : : * The memory context and resource owner holding them are going away at
612 : : * the end-of-transaction anyway, but on commit, we need to close them to
613 : : * avoid warnings about leaked resources at commit. On abort we can skip
614 : : * this step.
615 : : */
893 heikki.linnakangas@i 616 [ + + ]: 225 : if (isCommit)
617 : : {
618 [ + + ]: 4046 : for (i = 0; i < cookies_size; i++)
619 : : {
620 [ + + ]: 3904 : if (cookies[i] != NULL)
621 : 36 : closeLOfd(i);
622 : : }
623 : : }
624 : :
625 : : /* Needn't actually pfree since we're about to zap context */
8573 tgl@sss.pgh.pa.us 626 : 225 : cookies = NULL;
627 : 225 : cookies_size = 0;
628 : :
629 : : /* Release the LO memory context to prevent permanent memory leaks. */
893 heikki.linnakangas@i 630 [ + + ]: 225 : if (fscxt)
631 : 136 : MemoryContextDelete(fscxt);
9085 tgl@sss.pgh.pa.us 632 : 225 : fscxt = NULL;
633 : :
634 : : /* Give inv_api.c a chance to clean up, too */
7200 635 : 225 : close_lo_relation(isCommit);
636 : :
893 heikki.linnakangas@i 637 : 225 : lo_cleanup_needed = false;
638 : : }
639 : :
640 : : /*
641 : : * AtEOSubXact_LargeObject
642 : : * Take care of large objects at subtransaction commit/abort
643 : : *
644 : : * Reassign LOs created/opened during a committing subtransaction
645 : : * to the parent subtransaction. On abort, just close them.
646 : : */
647 : : void
7150 tgl@sss.pgh.pa.us 648 : 9927 : AtEOSubXact_LargeObject(bool isCommit, SubTransactionId mySubid,
649 : : SubTransactionId parentSubid)
650 : : {
651 : : int i;
652 : :
7200 653 [ + - ]: 9927 : if (fscxt == NULL) /* no LO operations in this xact */
654 : 9927 : return;
655 : :
7200 tgl@sss.pgh.pa.us 656 [ # # ]:UBC 0 : for (i = 0; i < cookies_size; i++)
657 : : {
658 : 0 : LargeObjectDesc *lo = cookies[i];
659 : :
7150 660 [ # # # # ]: 0 : if (lo != NULL && lo->subid == mySubid)
661 : : {
7200 662 [ # # ]: 0 : if (isCommit)
7150 663 : 0 : lo->subid = parentSubid;
664 : : else
893 heikki.linnakangas@i 665 : 0 : closeLOfd(i);
666 : : }
667 : : }
668 : : }
669 : :
670 : : /*****************************************************************************
671 : : * Support routines for this file
672 : : *****************************************************************************/
673 : :
674 : : static int
893 heikki.linnakangas@i 675 :CBC 174 : newLOfd(void)
676 : : {
677 : : int i,
678 : : newsize;
679 : :
680 : 174 : lo_cleanup_needed = true;
681 [ + + ]: 174 : if (fscxt == NULL)
682 : 136 : fscxt = AllocSetContextCreate(TopMemoryContext,
683 : : "Filesystem",
684 : : ALLOCSET_DEFAULT_SIZES);
685 : :
686 : : /* Try to find a free slot */
8573 tgl@sss.pgh.pa.us 687 [ + + ]: 174 : for (i = 0; i < cookies_size; i++)
688 : : {
9716 bruce@momjian.us 689 [ + - ]: 38 : if (cookies[i] == NULL)
690 : 38 : return i;
691 : : }
692 : :
693 : : /* No free slot, so make the array bigger */
8573 tgl@sss.pgh.pa.us 694 [ + - ]: 136 : if (cookies_size <= 0)
695 : : {
696 : : /* First time through, arbitrarily make 64-element array */
697 : 136 : i = 0;
698 : 136 : newsize = 64;
699 : 136 : cookies = (LargeObjectDesc **)
6563 700 : 136 : MemoryContextAllocZero(fscxt, newsize * sizeof(LargeObjectDesc *));
701 : : }
702 : : else
703 : : {
704 : : /* Double size of array */
8573 tgl@sss.pgh.pa.us 705 :UBC 0 : i = cookies_size;
706 : 0 : newsize = cookies_size * 2;
519 peter@eisentraut.org 707 : 0 : cookies =
708 : 0 : repalloc0_array(cookies, LargeObjectDesc *, cookies_size, newsize);
709 : : }
519 peter@eisentraut.org 710 :CBC 136 : cookies_size = newsize;
711 : :
8573 tgl@sss.pgh.pa.us 712 : 136 : return i;
713 : : }
714 : :
715 : : static void
893 heikki.linnakangas@i 716 : 141 : closeLOfd(int fd)
717 : : {
718 : : LargeObjectDesc *lobj;
719 : :
720 : : /*
721 : : * Make sure we do not try to free twice if this errors out for some
722 : : * reason. Better a leak than a crash.
723 : : */
724 : 141 : lobj = cookies[fd];
9716 bruce@momjian.us 725 : 141 : cookies[fd] = NULL;
726 : :
893 heikki.linnakangas@i 727 [ + + ]: 141 : if (lobj->snapshot)
728 : 101 : UnregisterSnapshotFromOwner(lobj->snapshot,
729 : : TopTransactionResourceOwner);
730 : 141 : inv_close(lobj);
10141 scrappy@hub.org 731 : 141 : }
732 : :
733 : : /*****************************************************************************
734 : : * Wrappers oriented toward SQL callers
735 : : *****************************************************************************/
736 : :
737 : : /*
738 : : * Read [offset, offset+nbytes) within LO; when nbytes is -1, read to end.
739 : : */
740 : : static bytea *
3822 noah@leadboat.com 741 : 36 : lo_get_fragment_internal(Oid loOid, int64 offset, int32 nbytes)
742 : : {
743 : : LargeObjectDesc *loDesc;
744 : : int64 loSize;
745 : : int64 result_length;
746 : : int total_read PG_USED_FOR_ASSERTS_ONLY;
747 : 36 : bytea *result = NULL;
748 : :
893 heikki.linnakangas@i 749 : 36 : lo_cleanup_needed = true;
750 : 36 : loDesc = inv_open(loOid, INV_READ, CurrentMemoryContext);
751 : :
752 : : /*
753 : : * Compute number of bytes we'll actually read, accommodating nbytes == -1
754 : : * and reads beyond the end of the LO.
755 : : */
3822 noah@leadboat.com 756 : 34 : loSize = inv_seek(loDesc, 0, SEEK_END);
757 [ + + ]: 34 : if (loSize > offset)
758 : : {
759 [ + + + + ]: 30 : if (nbytes >= 0 && nbytes <= loSize - offset)
2489 tgl@sss.pgh.pa.us 760 : 9 : result_length = nbytes; /* request is wholly inside LO */
761 : : else
3822 noah@leadboat.com 762 : 21 : result_length = loSize - offset; /* adjust to end of LO */
763 : : }
764 : : else
765 : 4 : result_length = 0; /* request is wholly outside LO */
766 : :
767 : : /*
768 : : * A result_length calculated from loSize may not fit in a size_t. Check
769 : : * that the size will satisfy this and subsequently-enforced size limits.
770 : : */
771 [ + + ]: 34 : if (result_length > MaxAllocSize - VARHDRSZ)
772 [ + - ]: 3 : ereport(ERROR,
773 : : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
774 : : errmsg("large object read request is too large")));
775 : :
776 : 31 : result = (bytea *) palloc(VARHDRSZ + result_length);
777 : :
778 : 31 : inv_seek(loDesc, offset, SEEK_SET);
779 : 31 : total_read = inv_read(loDesc, VARDATA(result), result_length);
780 [ - + ]: 31 : Assert(total_read == result_length);
781 : 31 : SET_VARSIZE(result, result_length + VARHDRSZ);
782 : :
783 : 31 : inv_close(loDesc);
784 : :
785 : 31 : return result;
786 : : }
787 : :
788 : : /*
789 : : * Read entire LO
790 : : */
791 : : Datum
2665 peter_e@gmx.net 792 : 24 : be_lo_get(PG_FUNCTION_ARGS)
793 : : {
3822 noah@leadboat.com 794 : 24 : Oid loOid = PG_GETARG_OID(0);
795 : : bytea *result;
796 : :
797 : 24 : result = lo_get_fragment_internal(loOid, 0, -1);
798 : :
799 : 19 : PG_RETURN_BYTEA_P(result);
800 : : }
801 : :
802 : : /*
803 : : * Read range within LO
804 : : */
805 : : Datum
2665 peter_e@gmx.net 806 : 12 : be_lo_get_fragment(PG_FUNCTION_ARGS)
807 : : {
3822 noah@leadboat.com 808 : 12 : Oid loOid = PG_GETARG_OID(0);
809 : 12 : int64 offset = PG_GETARG_INT64(1);
810 : 12 : int32 nbytes = PG_GETARG_INT32(2);
811 : : bytea *result;
812 : :
813 [ - + ]: 12 : if (nbytes < 0)
3822 noah@leadboat.com 814 [ # # ]:UBC 0 : ereport(ERROR,
815 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
816 : : errmsg("requested length cannot be negative")));
817 : :
3822 noah@leadboat.com 818 :CBC 12 : result = lo_get_fragment_internal(loOid, offset, nbytes);
819 : :
820 : 12 : PG_RETURN_BYTEA_P(result);
821 : : }
822 : :
823 : : /*
824 : : * Create LO with initial contents given by a bytea argument
825 : : */
826 : : Datum
2665 peter_e@gmx.net 827 : 13 : be_lo_from_bytea(PG_FUNCTION_ARGS)
828 : : {
3822 noah@leadboat.com 829 : 13 : Oid loOid = PG_GETARG_OID(0);
830 : 13 : bytea *str = PG_GETARG_BYTEA_PP(1);
831 : : LargeObjectDesc *loDesc;
832 : : int written PG_USED_FOR_ASSERTS_ONLY;
833 : :
650 michael@paquier.xyz 834 : 13 : PreventCommandIfReadOnly("lo_from_bytea()");
835 : :
893 heikki.linnakangas@i 836 : 10 : lo_cleanup_needed = true;
3822 noah@leadboat.com 837 : 10 : loOid = inv_create(loOid);
893 heikki.linnakangas@i 838 : 10 : loDesc = inv_open(loOid, INV_WRITE, CurrentMemoryContext);
3822 noah@leadboat.com 839 [ - + - - : 10 : written = inv_write(loDesc, VARDATA_ANY(str), VARSIZE_ANY_EXHDR(str));
- - - - -
+ - + ]
840 [ - + - - : 10 : Assert(written == VARSIZE_ANY_EXHDR(str));
- - - - -
+ - + ]
841 : 10 : inv_close(loDesc);
842 : :
843 : 10 : PG_RETURN_OID(loOid);
844 : : }
845 : :
846 : : /*
847 : : * Update range within LO
848 : : */
849 : : Datum
2665 peter_e@gmx.net 850 : 12 : be_lo_put(PG_FUNCTION_ARGS)
851 : : {
3822 noah@leadboat.com 852 : 12 : Oid loOid = PG_GETARG_OID(0);
853 : 12 : int64 offset = PG_GETARG_INT64(1);
854 : 12 : bytea *str = PG_GETARG_BYTEA_PP(2);
855 : : LargeObjectDesc *loDesc;
856 : : int written PG_USED_FOR_ASSERTS_ONLY;
857 : :
650 michael@paquier.xyz 858 : 12 : PreventCommandIfReadOnly("lo_put()");
859 : :
893 heikki.linnakangas@i 860 : 9 : lo_cleanup_needed = true;
861 : 9 : loDesc = inv_open(loOid, INV_WRITE, CurrentMemoryContext);
862 : :
863 : : /* Permission check */
2442 tgl@sss.pgh.pa.us 864 [ + - - + ]: 12 : if (!lo_compat_privileges &&
865 : 6 : pg_largeobject_aclcheck_snapshot(loDesc->id,
866 : : GetUserId(),
867 : : ACL_UPDATE,
868 : : loDesc->snapshot) != ACLCHECK_OK)
2442 tgl@sss.pgh.pa.us 869 [ # # ]:UBC 0 : ereport(ERROR,
870 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
871 : : errmsg("permission denied for large object %u",
872 : : loDesc->id)));
873 : :
3822 noah@leadboat.com 874 :CBC 6 : inv_seek(loDesc, offset, SEEK_SET);
875 [ - + - - : 6 : written = inv_write(loDesc, VARDATA_ANY(str), VARSIZE_ANY_EXHDR(str));
- - - - -
+ - + ]
876 [ - + - - : 6 : Assert(written == VARSIZE_ANY_EXHDR(str));
- - - - -
+ - + ]
877 : 6 : inv_close(loDesc);
878 : :
879 : 6 : PG_RETURN_VOID();
880 : : }
|