/src/postgres/src/backend/libpq/be-fsstubs.c
Line | Count | Source (jump to first uncovered line) |
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 | 0 | #define BUFSIZE 8192 |
62 | | |
63 | | /* |
64 | | * LO "FD"s are indexes into the cookies array. |
65 | | * |
66 | | * A non-null entry is a pointer to a LargeObjectDesc allocated in the |
67 | | * LO private memory context "fscxt". The cookies array itself is also |
68 | | * dynamically allocated in that context. Its current allocated size is |
69 | | * cookies_size entries, of which any unused entries will be NULL. |
70 | | */ |
71 | | static LargeObjectDesc **cookies = NULL; |
72 | | static int cookies_size = 0; |
73 | | |
74 | | static bool lo_cleanup_needed = false; |
75 | | static MemoryContext fscxt = NULL; |
76 | | |
77 | | static int newLOfd(void); |
78 | | static void closeLOfd(int fd); |
79 | | static Oid lo_import_internal(text *filename, Oid lobjOid); |
80 | | |
81 | | |
82 | | /***************************************************************************** |
83 | | * File Interfaces for Large Objects |
84 | | *****************************************************************************/ |
85 | | |
86 | | Datum |
87 | | be_lo_open(PG_FUNCTION_ARGS) |
88 | 0 | { |
89 | 0 | Oid lobjId = PG_GETARG_OID(0); |
90 | 0 | int32 mode = PG_GETARG_INT32(1); |
91 | 0 | LargeObjectDesc *lobjDesc; |
92 | 0 | int fd; |
93 | |
|
94 | | #ifdef FSDB |
95 | | elog(DEBUG4, "lo_open(%u,%d)", lobjId, mode); |
96 | | #endif |
97 | |
|
98 | 0 | if (mode & INV_WRITE) |
99 | 0 | PreventCommandIfReadOnly("lo_open(INV_WRITE)"); |
100 | | |
101 | | /* |
102 | | * Allocate a large object descriptor first. This will also create |
103 | | * 'fscxt' if this is the first LO opened in this transaction. |
104 | | */ |
105 | 0 | fd = newLOfd(); |
106 | |
|
107 | 0 | lobjDesc = inv_open(lobjId, mode, fscxt); |
108 | 0 | 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 | 0 | if (lobjDesc->snapshot) |
116 | 0 | lobjDesc->snapshot = RegisterSnapshotOnOwner(lobjDesc->snapshot, |
117 | 0 | TopTransactionResourceOwner); |
118 | |
|
119 | 0 | Assert(cookies[fd] == NULL); |
120 | 0 | cookies[fd] = lobjDesc; |
121 | |
|
122 | 0 | PG_RETURN_INT32(fd); |
123 | 0 | } |
124 | | |
125 | | Datum |
126 | | be_lo_close(PG_FUNCTION_ARGS) |
127 | 0 | { |
128 | 0 | int32 fd = PG_GETARG_INT32(0); |
129 | |
|
130 | 0 | if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL) |
131 | 0 | ereport(ERROR, |
132 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
133 | 0 | errmsg("invalid large-object descriptor: %d", fd))); |
134 | | |
135 | | #ifdef FSDB |
136 | | elog(DEBUG4, "lo_close(%d)", fd); |
137 | | #endif |
138 | | |
139 | 0 | closeLOfd(fd); |
140 | |
|
141 | 0 | PG_RETURN_INT32(0); |
142 | 0 | } |
143 | | |
144 | | |
145 | | /***************************************************************************** |
146 | | * Bare Read/Write operations --- these are not fmgr-callable! |
147 | | * |
148 | | * We assume the large object supports byte oriented reads and seeks so |
149 | | * that our work is easier. |
150 | | * |
151 | | *****************************************************************************/ |
152 | | |
153 | | int |
154 | | lo_read(int fd, char *buf, int len) |
155 | 0 | { |
156 | 0 | int status; |
157 | 0 | LargeObjectDesc *lobj; |
158 | |
|
159 | 0 | if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL) |
160 | 0 | ereport(ERROR, |
161 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
162 | 0 | errmsg("invalid large-object descriptor: %d", fd))); |
163 | 0 | lobj = cookies[fd]; |
164 | | |
165 | | /* |
166 | | * Check state. inv_read() would throw an error anyway, but we want the |
167 | | * error to be about the FD's state not the underlying privilege; it might |
168 | | * be that the privilege exists but user forgot to ask for read mode. |
169 | | */ |
170 | 0 | if ((lobj->flags & IFS_RDLOCK) == 0) |
171 | 0 | ereport(ERROR, |
172 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
173 | 0 | errmsg("large object descriptor %d was not opened for reading", |
174 | 0 | fd))); |
175 | | |
176 | 0 | status = inv_read(lobj, buf, len); |
177 | |
|
178 | 0 | return status; |
179 | 0 | } |
180 | | |
181 | | int |
182 | | lo_write(int fd, const char *buf, int len) |
183 | 0 | { |
184 | 0 | int status; |
185 | 0 | LargeObjectDesc *lobj; |
186 | |
|
187 | 0 | if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL) |
188 | 0 | ereport(ERROR, |
189 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
190 | 0 | errmsg("invalid large-object descriptor: %d", fd))); |
191 | 0 | lobj = cookies[fd]; |
192 | | |
193 | | /* see comment in lo_read() */ |
194 | 0 | if ((lobj->flags & IFS_WRLOCK) == 0) |
195 | 0 | ereport(ERROR, |
196 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
197 | 0 | errmsg("large object descriptor %d was not opened for writing", |
198 | 0 | fd))); |
199 | | |
200 | 0 | status = inv_write(lobj, buf, len); |
201 | |
|
202 | 0 | return status; |
203 | 0 | } |
204 | | |
205 | | Datum |
206 | | be_lo_lseek(PG_FUNCTION_ARGS) |
207 | 0 | { |
208 | 0 | int32 fd = PG_GETARG_INT32(0); |
209 | 0 | int32 offset = PG_GETARG_INT32(1); |
210 | 0 | int32 whence = PG_GETARG_INT32(2); |
211 | 0 | int64 status; |
212 | |
|
213 | 0 | if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL) |
214 | 0 | ereport(ERROR, |
215 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
216 | 0 | errmsg("invalid large-object descriptor: %d", fd))); |
217 | | |
218 | 0 | status = inv_seek(cookies[fd], offset, whence); |
219 | | |
220 | | /* guard against result overflow */ |
221 | 0 | if (status != (int32) status) |
222 | 0 | ereport(ERROR, |
223 | 0 | (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), |
224 | 0 | errmsg("lo_lseek result out of range for large-object descriptor %d", |
225 | 0 | fd))); |
226 | | |
227 | 0 | PG_RETURN_INT32((int32) status); |
228 | 0 | } |
229 | | |
230 | | Datum |
231 | | be_lo_lseek64(PG_FUNCTION_ARGS) |
232 | 0 | { |
233 | 0 | int32 fd = PG_GETARG_INT32(0); |
234 | 0 | int64 offset = PG_GETARG_INT64(1); |
235 | 0 | int32 whence = PG_GETARG_INT32(2); |
236 | 0 | int64 status; |
237 | |
|
238 | 0 | if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL) |
239 | 0 | ereport(ERROR, |
240 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
241 | 0 | errmsg("invalid large-object descriptor: %d", fd))); |
242 | | |
243 | 0 | status = inv_seek(cookies[fd], offset, whence); |
244 | |
|
245 | 0 | PG_RETURN_INT64(status); |
246 | 0 | } |
247 | | |
248 | | Datum |
249 | | be_lo_creat(PG_FUNCTION_ARGS) |
250 | 0 | { |
251 | 0 | Oid lobjId; |
252 | |
|
253 | 0 | PreventCommandIfReadOnly("lo_creat()"); |
254 | |
|
255 | 0 | lo_cleanup_needed = true; |
256 | 0 | lobjId = inv_create(InvalidOid); |
257 | |
|
258 | 0 | PG_RETURN_OID(lobjId); |
259 | 0 | } |
260 | | |
261 | | Datum |
262 | | be_lo_create(PG_FUNCTION_ARGS) |
263 | 0 | { |
264 | 0 | Oid lobjId = PG_GETARG_OID(0); |
265 | |
|
266 | 0 | PreventCommandIfReadOnly("lo_create()"); |
267 | |
|
268 | 0 | lo_cleanup_needed = true; |
269 | 0 | lobjId = inv_create(lobjId); |
270 | |
|
271 | 0 | PG_RETURN_OID(lobjId); |
272 | 0 | } |
273 | | |
274 | | Datum |
275 | | be_lo_tell(PG_FUNCTION_ARGS) |
276 | 0 | { |
277 | 0 | int32 fd = PG_GETARG_INT32(0); |
278 | 0 | int64 offset; |
279 | |
|
280 | 0 | if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL) |
281 | 0 | ereport(ERROR, |
282 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
283 | 0 | errmsg("invalid large-object descriptor: %d", fd))); |
284 | | |
285 | 0 | offset = inv_tell(cookies[fd]); |
286 | | |
287 | | /* guard against result overflow */ |
288 | 0 | if (offset != (int32) offset) |
289 | 0 | ereport(ERROR, |
290 | 0 | (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), |
291 | 0 | errmsg("lo_tell result out of range for large-object descriptor %d", |
292 | 0 | fd))); |
293 | | |
294 | 0 | PG_RETURN_INT32((int32) offset); |
295 | 0 | } |
296 | | |
297 | | Datum |
298 | | be_lo_tell64(PG_FUNCTION_ARGS) |
299 | 0 | { |
300 | 0 | int32 fd = PG_GETARG_INT32(0); |
301 | 0 | int64 offset; |
302 | |
|
303 | 0 | if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL) |
304 | 0 | ereport(ERROR, |
305 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
306 | 0 | errmsg("invalid large-object descriptor: %d", fd))); |
307 | | |
308 | 0 | offset = inv_tell(cookies[fd]); |
309 | |
|
310 | 0 | PG_RETURN_INT64(offset); |
311 | 0 | } |
312 | | |
313 | | Datum |
314 | | be_lo_unlink(PG_FUNCTION_ARGS) |
315 | 0 | { |
316 | 0 | Oid lobjId = PG_GETARG_OID(0); |
317 | |
|
318 | 0 | PreventCommandIfReadOnly("lo_unlink()"); |
319 | |
|
320 | 0 | if (!LargeObjectExists(lobjId)) |
321 | 0 | ereport(ERROR, |
322 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
323 | 0 | errmsg("large object %u does not exist", lobjId))); |
324 | | |
325 | | /* |
326 | | * Must be owner of the large object. It would be cleaner to check this |
327 | | * in inv_drop(), but we want to throw the error before not after closing |
328 | | * relevant FDs. |
329 | | */ |
330 | 0 | if (!lo_compat_privileges && |
331 | 0 | !object_ownercheck(LargeObjectRelationId, lobjId, GetUserId())) |
332 | 0 | ereport(ERROR, |
333 | 0 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
334 | 0 | errmsg("must be owner of large object %u", lobjId))); |
335 | | |
336 | | /* |
337 | | * If there are any open LO FDs referencing that ID, close 'em. |
338 | | */ |
339 | 0 | if (fscxt != NULL) |
340 | 0 | { |
341 | 0 | int i; |
342 | |
|
343 | 0 | for (i = 0; i < cookies_size; i++) |
344 | 0 | { |
345 | 0 | if (cookies[i] != NULL && cookies[i]->id == lobjId) |
346 | 0 | closeLOfd(i); |
347 | 0 | } |
348 | 0 | } |
349 | | |
350 | | /* |
351 | | * inv_drop does not create a need for end-of-transaction cleanup and |
352 | | * hence we don't need to set lo_cleanup_needed. |
353 | | */ |
354 | 0 | PG_RETURN_INT32(inv_drop(lobjId)); |
355 | 0 | } |
356 | | |
357 | | /***************************************************************************** |
358 | | * Read/Write using bytea |
359 | | *****************************************************************************/ |
360 | | |
361 | | Datum |
362 | | be_loread(PG_FUNCTION_ARGS) |
363 | 0 | { |
364 | 0 | int32 fd = PG_GETARG_INT32(0); |
365 | 0 | int32 len = PG_GETARG_INT32(1); |
366 | 0 | bytea *retval; |
367 | 0 | int totalread; |
368 | |
|
369 | 0 | if (len < 0) |
370 | 0 | len = 0; |
371 | |
|
372 | 0 | retval = (bytea *) palloc(VARHDRSZ + len); |
373 | 0 | totalread = lo_read(fd, VARDATA(retval), len); |
374 | 0 | SET_VARSIZE(retval, totalread + VARHDRSZ); |
375 | |
|
376 | 0 | PG_RETURN_BYTEA_P(retval); |
377 | 0 | } |
378 | | |
379 | | Datum |
380 | | be_lowrite(PG_FUNCTION_ARGS) |
381 | 0 | { |
382 | 0 | int32 fd = PG_GETARG_INT32(0); |
383 | 0 | bytea *wbuf = PG_GETARG_BYTEA_PP(1); |
384 | 0 | int bytestowrite; |
385 | 0 | int totalwritten; |
386 | |
|
387 | 0 | PreventCommandIfReadOnly("lowrite()"); |
388 | |
|
389 | 0 | bytestowrite = VARSIZE_ANY_EXHDR(wbuf); |
390 | 0 | totalwritten = lo_write(fd, VARDATA_ANY(wbuf), bytestowrite); |
391 | 0 | PG_RETURN_INT32(totalwritten); |
392 | 0 | } |
393 | | |
394 | | /***************************************************************************** |
395 | | * Import/Export of Large Object |
396 | | *****************************************************************************/ |
397 | | |
398 | | /* |
399 | | * lo_import - |
400 | | * imports a file as an (inversion) large object. |
401 | | */ |
402 | | Datum |
403 | | be_lo_import(PG_FUNCTION_ARGS) |
404 | 0 | { |
405 | 0 | text *filename = PG_GETARG_TEXT_PP(0); |
406 | |
|
407 | 0 | PG_RETURN_OID(lo_import_internal(filename, InvalidOid)); |
408 | 0 | } |
409 | | |
410 | | /* |
411 | | * lo_import_with_oid - |
412 | | * imports a file as an (inversion) large object specifying oid. |
413 | | */ |
414 | | Datum |
415 | | be_lo_import_with_oid(PG_FUNCTION_ARGS) |
416 | 0 | { |
417 | 0 | text *filename = PG_GETARG_TEXT_PP(0); |
418 | 0 | Oid oid = PG_GETARG_OID(1); |
419 | |
|
420 | 0 | PG_RETURN_OID(lo_import_internal(filename, oid)); |
421 | 0 | } |
422 | | |
423 | | static Oid |
424 | | lo_import_internal(text *filename, Oid lobjOid) |
425 | 0 | { |
426 | 0 | int fd; |
427 | 0 | int nbytes, |
428 | 0 | tmp PG_USED_FOR_ASSERTS_ONLY; |
429 | 0 | char buf[BUFSIZE]; |
430 | 0 | char fnamebuf[MAXPGPATH]; |
431 | 0 | LargeObjectDesc *lobj; |
432 | 0 | Oid oid; |
433 | |
|
434 | 0 | PreventCommandIfReadOnly("lo_import()"); |
435 | | |
436 | | /* |
437 | | * open the file to be read in |
438 | | */ |
439 | 0 | text_to_cstring_buffer(filename, fnamebuf, sizeof(fnamebuf)); |
440 | 0 | fd = OpenTransientFile(fnamebuf, O_RDONLY | PG_BINARY); |
441 | 0 | if (fd < 0) |
442 | 0 | ereport(ERROR, |
443 | 0 | (errcode_for_file_access(), |
444 | 0 | errmsg("could not open server file \"%s\": %m", |
445 | 0 | fnamebuf))); |
446 | | |
447 | | /* |
448 | | * create an inversion object |
449 | | */ |
450 | 0 | lo_cleanup_needed = true; |
451 | 0 | oid = inv_create(lobjOid); |
452 | | |
453 | | /* |
454 | | * read in from the filesystem and write to the inversion object |
455 | | */ |
456 | 0 | lobj = inv_open(oid, INV_WRITE, CurrentMemoryContext); |
457 | |
|
458 | 0 | while ((nbytes = read(fd, buf, BUFSIZE)) > 0) |
459 | 0 | { |
460 | 0 | tmp = inv_write(lobj, buf, nbytes); |
461 | 0 | Assert(tmp == nbytes); |
462 | 0 | } |
463 | |
|
464 | 0 | if (nbytes < 0) |
465 | 0 | ereport(ERROR, |
466 | 0 | (errcode_for_file_access(), |
467 | 0 | errmsg("could not read server file \"%s\": %m", |
468 | 0 | fnamebuf))); |
469 | | |
470 | 0 | inv_close(lobj); |
471 | |
|
472 | 0 | if (CloseTransientFile(fd) != 0) |
473 | 0 | ereport(ERROR, |
474 | 0 | (errcode_for_file_access(), |
475 | 0 | errmsg("could not close file \"%s\": %m", |
476 | 0 | fnamebuf))); |
477 | | |
478 | 0 | return oid; |
479 | 0 | } |
480 | | |
481 | | /* |
482 | | * lo_export - |
483 | | * exports an (inversion) large object. |
484 | | */ |
485 | | Datum |
486 | | be_lo_export(PG_FUNCTION_ARGS) |
487 | 0 | { |
488 | 0 | Oid lobjId = PG_GETARG_OID(0); |
489 | 0 | text *filename = PG_GETARG_TEXT_PP(1); |
490 | 0 | int fd; |
491 | 0 | int nbytes, |
492 | 0 | tmp; |
493 | 0 | char buf[BUFSIZE]; |
494 | 0 | char fnamebuf[MAXPGPATH]; |
495 | 0 | LargeObjectDesc *lobj; |
496 | 0 | mode_t oumask; |
497 | | |
498 | | /* |
499 | | * open the inversion object (no need to test for failure) |
500 | | */ |
501 | 0 | lo_cleanup_needed = true; |
502 | 0 | lobj = inv_open(lobjId, INV_READ, CurrentMemoryContext); |
503 | | |
504 | | /* |
505 | | * open the file to be written to |
506 | | * |
507 | | * Note: we reduce backend's normal 077 umask to the slightly friendlier |
508 | | * 022. This code used to drop it all the way to 0, but creating |
509 | | * world-writable export files doesn't seem wise. |
510 | | */ |
511 | 0 | text_to_cstring_buffer(filename, fnamebuf, sizeof(fnamebuf)); |
512 | 0 | oumask = umask(S_IWGRP | S_IWOTH); |
513 | 0 | PG_TRY(); |
514 | 0 | { |
515 | 0 | fd = OpenTransientFilePerm(fnamebuf, O_CREAT | O_WRONLY | O_TRUNC | PG_BINARY, |
516 | 0 | S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
517 | 0 | } |
518 | 0 | PG_FINALLY(); |
519 | 0 | { |
520 | 0 | umask(oumask); |
521 | 0 | } |
522 | 0 | PG_END_TRY(); |
523 | 0 | if (fd < 0) |
524 | 0 | ereport(ERROR, |
525 | 0 | (errcode_for_file_access(), |
526 | 0 | errmsg("could not create server file \"%s\": %m", |
527 | 0 | fnamebuf))); |
528 | | |
529 | | /* |
530 | | * read in from the inversion file and write to the filesystem |
531 | | */ |
532 | 0 | while ((nbytes = inv_read(lobj, buf, BUFSIZE)) > 0) |
533 | 0 | { |
534 | 0 | tmp = write(fd, buf, nbytes); |
535 | 0 | if (tmp != nbytes) |
536 | 0 | ereport(ERROR, |
537 | 0 | (errcode_for_file_access(), |
538 | 0 | errmsg("could not write server file \"%s\": %m", |
539 | 0 | fnamebuf))); |
540 | 0 | } |
541 | | |
542 | 0 | if (CloseTransientFile(fd) != 0) |
543 | 0 | ereport(ERROR, |
544 | 0 | (errcode_for_file_access(), |
545 | 0 | errmsg("could not close file \"%s\": %m", |
546 | 0 | fnamebuf))); |
547 | | |
548 | 0 | inv_close(lobj); |
549 | |
|
550 | 0 | PG_RETURN_INT32(1); |
551 | 0 | } |
552 | | |
553 | | /* |
554 | | * lo_truncate - |
555 | | * truncate a large object to a specified length |
556 | | */ |
557 | | static void |
558 | | lo_truncate_internal(int32 fd, int64 len) |
559 | 0 | { |
560 | 0 | LargeObjectDesc *lobj; |
561 | |
|
562 | 0 | if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL) |
563 | 0 | ereport(ERROR, |
564 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
565 | 0 | errmsg("invalid large-object descriptor: %d", fd))); |
566 | 0 | lobj = cookies[fd]; |
567 | | |
568 | | /* see comment in lo_read() */ |
569 | 0 | if ((lobj->flags & IFS_WRLOCK) == 0) |
570 | 0 | ereport(ERROR, |
571 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
572 | 0 | errmsg("large object descriptor %d was not opened for writing", |
573 | 0 | fd))); |
574 | | |
575 | 0 | inv_truncate(lobj, len); |
576 | 0 | } |
577 | | |
578 | | Datum |
579 | | be_lo_truncate(PG_FUNCTION_ARGS) |
580 | 0 | { |
581 | 0 | int32 fd = PG_GETARG_INT32(0); |
582 | 0 | int32 len = PG_GETARG_INT32(1); |
583 | |
|
584 | 0 | PreventCommandIfReadOnly("lo_truncate()"); |
585 | |
|
586 | 0 | lo_truncate_internal(fd, len); |
587 | 0 | PG_RETURN_INT32(0); |
588 | 0 | } |
589 | | |
590 | | Datum |
591 | | be_lo_truncate64(PG_FUNCTION_ARGS) |
592 | 0 | { |
593 | 0 | int32 fd = PG_GETARG_INT32(0); |
594 | 0 | int64 len = PG_GETARG_INT64(1); |
595 | |
|
596 | 0 | PreventCommandIfReadOnly("lo_truncate64()"); |
597 | |
|
598 | 0 | lo_truncate_internal(fd, len); |
599 | 0 | PG_RETURN_INT32(0); |
600 | 0 | } |
601 | | |
602 | | /* |
603 | | * AtEOXact_LargeObject - |
604 | | * prepares large objects for transaction commit |
605 | | */ |
606 | | void |
607 | | AtEOXact_LargeObject(bool isCommit) |
608 | 0 | { |
609 | 0 | int i; |
610 | |
|
611 | 0 | if (!lo_cleanup_needed) |
612 | 0 | return; /* no LO operations in this xact */ |
613 | | |
614 | | /* |
615 | | * Close LO fds and clear cookies array so that LO fds are no longer good. |
616 | | * The memory context and resource owner holding them are going away at |
617 | | * the end-of-transaction anyway, but on commit, we need to close them to |
618 | | * avoid warnings about leaked resources at commit. On abort we can skip |
619 | | * this step. |
620 | | */ |
621 | 0 | if (isCommit) |
622 | 0 | { |
623 | 0 | for (i = 0; i < cookies_size; i++) |
624 | 0 | { |
625 | 0 | if (cookies[i] != NULL) |
626 | 0 | closeLOfd(i); |
627 | 0 | } |
628 | 0 | } |
629 | | |
630 | | /* Needn't actually pfree since we're about to zap context */ |
631 | 0 | cookies = NULL; |
632 | 0 | cookies_size = 0; |
633 | | |
634 | | /* Release the LO memory context to prevent permanent memory leaks. */ |
635 | 0 | if (fscxt) |
636 | 0 | MemoryContextDelete(fscxt); |
637 | 0 | fscxt = NULL; |
638 | | |
639 | | /* Give inv_api.c a chance to clean up, too */ |
640 | 0 | close_lo_relation(isCommit); |
641 | |
|
642 | 0 | lo_cleanup_needed = false; |
643 | 0 | } |
644 | | |
645 | | /* |
646 | | * AtEOSubXact_LargeObject |
647 | | * Take care of large objects at subtransaction commit/abort |
648 | | * |
649 | | * Reassign LOs created/opened during a committing subtransaction |
650 | | * to the parent subtransaction. On abort, just close them. |
651 | | */ |
652 | | void |
653 | | AtEOSubXact_LargeObject(bool isCommit, SubTransactionId mySubid, |
654 | | SubTransactionId parentSubid) |
655 | 0 | { |
656 | 0 | int i; |
657 | |
|
658 | 0 | if (fscxt == NULL) /* no LO operations in this xact */ |
659 | 0 | return; |
660 | | |
661 | 0 | for (i = 0; i < cookies_size; i++) |
662 | 0 | { |
663 | 0 | LargeObjectDesc *lo = cookies[i]; |
664 | |
|
665 | 0 | if (lo != NULL && lo->subid == mySubid) |
666 | 0 | { |
667 | 0 | if (isCommit) |
668 | 0 | lo->subid = parentSubid; |
669 | 0 | else |
670 | 0 | closeLOfd(i); |
671 | 0 | } |
672 | 0 | } |
673 | 0 | } |
674 | | |
675 | | /***************************************************************************** |
676 | | * Support routines for this file |
677 | | *****************************************************************************/ |
678 | | |
679 | | static int |
680 | | newLOfd(void) |
681 | 0 | { |
682 | 0 | int i, |
683 | 0 | newsize; |
684 | |
|
685 | 0 | lo_cleanup_needed = true; |
686 | 0 | if (fscxt == NULL) |
687 | 0 | fscxt = AllocSetContextCreate(TopMemoryContext, |
688 | 0 | "Filesystem", |
689 | 0 | ALLOCSET_DEFAULT_SIZES); |
690 | | |
691 | | /* Try to find a free slot */ |
692 | 0 | for (i = 0; i < cookies_size; i++) |
693 | 0 | { |
694 | 0 | if (cookies[i] == NULL) |
695 | 0 | return i; |
696 | 0 | } |
697 | | |
698 | | /* No free slot, so make the array bigger */ |
699 | 0 | if (cookies_size <= 0) |
700 | 0 | { |
701 | | /* First time through, arbitrarily make 64-element array */ |
702 | 0 | i = 0; |
703 | 0 | newsize = 64; |
704 | 0 | cookies = (LargeObjectDesc **) |
705 | 0 | MemoryContextAllocZero(fscxt, newsize * sizeof(LargeObjectDesc *)); |
706 | 0 | } |
707 | 0 | else |
708 | 0 | { |
709 | | /* Double size of array */ |
710 | 0 | i = cookies_size; |
711 | 0 | newsize = cookies_size * 2; |
712 | 0 | cookies = |
713 | 0 | repalloc0_array(cookies, LargeObjectDesc *, cookies_size, newsize); |
714 | 0 | } |
715 | 0 | cookies_size = newsize; |
716 | |
|
717 | 0 | return i; |
718 | 0 | } |
719 | | |
720 | | static void |
721 | | closeLOfd(int fd) |
722 | 0 | { |
723 | 0 | 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 | 0 | lobj = cookies[fd]; |
730 | 0 | cookies[fd] = NULL; |
731 | |
|
732 | 0 | if (lobj->snapshot) |
733 | 0 | UnregisterSnapshotFromOwner(lobj->snapshot, |
734 | 0 | TopTransactionResourceOwner); |
735 | 0 | inv_close(lobj); |
736 | 0 | } |
737 | | |
738 | | /***************************************************************************** |
739 | | * Wrappers oriented toward SQL callers |
740 | | *****************************************************************************/ |
741 | | |
742 | | /* |
743 | | * Read [offset, offset+nbytes) within LO; when nbytes is -1, read to end. |
744 | | */ |
745 | | static bytea * |
746 | | lo_get_fragment_internal(Oid loOid, int64 offset, int32 nbytes) |
747 | 0 | { |
748 | 0 | LargeObjectDesc *loDesc; |
749 | 0 | int64 loSize; |
750 | 0 | int64 result_length; |
751 | 0 | int total_read PG_USED_FOR_ASSERTS_ONLY; |
752 | 0 | bytea *result = NULL; |
753 | |
|
754 | 0 | lo_cleanup_needed = true; |
755 | 0 | loDesc = inv_open(loOid, INV_READ, CurrentMemoryContext); |
756 | | |
757 | | /* |
758 | | * Compute number of bytes we'll actually read, accommodating nbytes == -1 |
759 | | * and reads beyond the end of the LO. |
760 | | */ |
761 | 0 | loSize = inv_seek(loDesc, 0, SEEK_END); |
762 | 0 | if (loSize > offset) |
763 | 0 | { |
764 | 0 | if (nbytes >= 0 && nbytes <= loSize - offset) |
765 | 0 | result_length = nbytes; /* request is wholly inside LO */ |
766 | 0 | else |
767 | 0 | result_length = loSize - offset; /* adjust to end of LO */ |
768 | 0 | } |
769 | 0 | else |
770 | 0 | result_length = 0; /* request is wholly outside LO */ |
771 | | |
772 | | /* |
773 | | * A result_length calculated from loSize may not fit in a size_t. Check |
774 | | * that the size will satisfy this and subsequently-enforced size limits. |
775 | | */ |
776 | 0 | if (result_length > MaxAllocSize - VARHDRSZ) |
777 | 0 | ereport(ERROR, |
778 | 0 | (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), |
779 | 0 | errmsg("large object read request is too large"))); |
780 | | |
781 | 0 | result = (bytea *) palloc(VARHDRSZ + result_length); |
782 | |
|
783 | 0 | inv_seek(loDesc, offset, SEEK_SET); |
784 | 0 | total_read = inv_read(loDesc, VARDATA(result), result_length); |
785 | 0 | Assert(total_read == result_length); |
786 | 0 | SET_VARSIZE(result, result_length + VARHDRSZ); |
787 | |
|
788 | 0 | inv_close(loDesc); |
789 | |
|
790 | 0 | return result; |
791 | 0 | } |
792 | | |
793 | | /* |
794 | | * Read entire LO |
795 | | */ |
796 | | Datum |
797 | | be_lo_get(PG_FUNCTION_ARGS) |
798 | 0 | { |
799 | 0 | Oid loOid = PG_GETARG_OID(0); |
800 | 0 | bytea *result; |
801 | |
|
802 | 0 | result = lo_get_fragment_internal(loOid, 0, -1); |
803 | |
|
804 | 0 | PG_RETURN_BYTEA_P(result); |
805 | 0 | } |
806 | | |
807 | | /* |
808 | | * Read range within LO |
809 | | */ |
810 | | Datum |
811 | | be_lo_get_fragment(PG_FUNCTION_ARGS) |
812 | 0 | { |
813 | 0 | Oid loOid = PG_GETARG_OID(0); |
814 | 0 | int64 offset = PG_GETARG_INT64(1); |
815 | 0 | int32 nbytes = PG_GETARG_INT32(2); |
816 | 0 | bytea *result; |
817 | |
|
818 | 0 | if (nbytes < 0) |
819 | 0 | ereport(ERROR, |
820 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
821 | 0 | errmsg("requested length cannot be negative"))); |
822 | | |
823 | 0 | result = lo_get_fragment_internal(loOid, offset, nbytes); |
824 | |
|
825 | 0 | PG_RETURN_BYTEA_P(result); |
826 | 0 | } |
827 | | |
828 | | /* |
829 | | * Create LO with initial contents given by a bytea argument |
830 | | */ |
831 | | Datum |
832 | | be_lo_from_bytea(PG_FUNCTION_ARGS) |
833 | 0 | { |
834 | 0 | Oid loOid = PG_GETARG_OID(0); |
835 | 0 | bytea *str = PG_GETARG_BYTEA_PP(1); |
836 | 0 | LargeObjectDesc *loDesc; |
837 | 0 | int written PG_USED_FOR_ASSERTS_ONLY; |
838 | |
|
839 | 0 | PreventCommandIfReadOnly("lo_from_bytea()"); |
840 | |
|
841 | 0 | lo_cleanup_needed = true; |
842 | 0 | loOid = inv_create(loOid); |
843 | 0 | loDesc = inv_open(loOid, INV_WRITE, CurrentMemoryContext); |
844 | 0 | written = inv_write(loDesc, VARDATA_ANY(str), VARSIZE_ANY_EXHDR(str)); |
845 | 0 | Assert(written == VARSIZE_ANY_EXHDR(str)); |
846 | 0 | inv_close(loDesc); |
847 | |
|
848 | 0 | PG_RETURN_OID(loOid); |
849 | 0 | } |
850 | | |
851 | | /* |
852 | | * Update range within LO |
853 | | */ |
854 | | Datum |
855 | | be_lo_put(PG_FUNCTION_ARGS) |
856 | 0 | { |
857 | 0 | Oid loOid = PG_GETARG_OID(0); |
858 | 0 | int64 offset = PG_GETARG_INT64(1); |
859 | 0 | bytea *str = PG_GETARG_BYTEA_PP(2); |
860 | 0 | LargeObjectDesc *loDesc; |
861 | 0 | int written PG_USED_FOR_ASSERTS_ONLY; |
862 | |
|
863 | 0 | PreventCommandIfReadOnly("lo_put()"); |
864 | |
|
865 | 0 | lo_cleanup_needed = true; |
866 | 0 | loDesc = inv_open(loOid, INV_WRITE, CurrentMemoryContext); |
867 | 0 | inv_seek(loDesc, offset, SEEK_SET); |
868 | 0 | written = inv_write(loDesc, VARDATA_ANY(str), VARSIZE_ANY_EXHDR(str)); |
869 | 0 | Assert(written == VARSIZE_ANY_EXHDR(str)); |
870 | 0 | inv_close(loDesc); |
871 | |
|
872 | 0 | PG_RETURN_VOID(); |
873 | 0 | } |