1 /*-------------------------------------------------------------------------
4 * Builtin functions for open/close/read/write operations on large objects
6 * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
14 * This should be moved to a more appropriate place. It is here
15 * for lack of a better place.
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
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
36 *-------------------------------------------------------------------------
45 #include "libpq/be-fsstubs.h"
46 #include "libpq/libpq-fs.h"
47 #include "miscadmin.h"
48 #include "storage/fd.h"
49 #include "storage/large_object.h"
50 #include "utils/builtins.h"
51 #include "utils/memutils.h"
58 * LO "FD"s are indexes into the cookies array.
60 * A non-null entry is a pointer to a LargeObjectDesc allocated in the
61 * LO private memory context "fscxt". The cookies array itself is also
62 * dynamically allocated in that context. Its current allocated size is
63 * cookies_len entries, of which any unused entries will be NULL.
65 static LargeObjectDesc
**cookies
= NULL
;
66 static int cookies_size
= 0;
68 static MemoryContext fscxt
= NULL
;
70 #define CreateFSContext() \
73 fscxt = AllocSetContextCreate(TopMemoryContext, \
75 ALLOCSET_DEFAULT_MINSIZE, \
76 ALLOCSET_DEFAULT_INITSIZE, \
77 ALLOCSET_DEFAULT_MAXSIZE); \
81 static int newLOfd(LargeObjectDesc
*lobjCookie
);
82 static void deleteLOfd(int fd
);
83 static Oid
lo_import_internal(text
*filename
, Oid lobjOid
);
86 /*****************************************************************************
87 * File Interfaces for Large Objects
88 *****************************************************************************/
91 lo_open(PG_FUNCTION_ARGS
)
93 Oid lobjId
= PG_GETARG_OID(0);
94 int32 mode
= PG_GETARG_INT32(1);
95 LargeObjectDesc
*lobjDesc
;
99 elog(DEBUG4
, "lo_open(%u,%d)", lobjId
, mode
);
104 lobjDesc
= inv_open(lobjId
, mode
, fscxt
);
106 if (lobjDesc
== NULL
)
107 { /* lookup failed */
109 elog(DEBUG4
, "could not open large object %u", lobjId
);
114 fd
= newLOfd(lobjDesc
);
120 lo_close(PG_FUNCTION_ARGS
)
122 int32 fd
= PG_GETARG_INT32(0);
124 if (fd
< 0 || fd
>= cookies_size
|| cookies
[fd
] == NULL
)
126 (errcode(ERRCODE_UNDEFINED_OBJECT
),
127 errmsg("invalid large-object descriptor: %d", fd
)));
130 elog(DEBUG4
, "lo_close(%d)", fd
);
133 inv_close(cookies
[fd
]);
141 /*****************************************************************************
142 * Bare Read/Write operations --- these are not fmgr-callable!
144 * We assume the large object supports byte oriented reads and seeks so
145 * that our work is easier.
147 *****************************************************************************/
150 lo_read(int fd
, char *buf
, int len
)
154 if (fd
< 0 || fd
>= cookies_size
|| cookies
[fd
] == NULL
)
156 (errcode(ERRCODE_UNDEFINED_OBJECT
),
157 errmsg("invalid large-object descriptor: %d", fd
)));
159 status
= inv_read(cookies
[fd
], buf
, len
);
165 lo_write(int fd
, const char *buf
, int len
)
169 if (fd
< 0 || fd
>= cookies_size
|| cookies
[fd
] == NULL
)
171 (errcode(ERRCODE_UNDEFINED_OBJECT
),
172 errmsg("invalid large-object descriptor: %d", fd
)));
174 if ((cookies
[fd
]->flags
& IFS_WRLOCK
) == 0)
176 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE
),
177 errmsg("large object descriptor %d was not opened for writing",
180 status
= inv_write(cookies
[fd
], buf
, len
);
187 lo_lseek(PG_FUNCTION_ARGS
)
189 int32 fd
= PG_GETARG_INT32(0);
190 int32 offset
= PG_GETARG_INT32(1);
191 int32 whence
= PG_GETARG_INT32(2);
194 if (fd
< 0 || fd
>= cookies_size
|| cookies
[fd
] == NULL
)
196 (errcode(ERRCODE_UNDEFINED_OBJECT
),
197 errmsg("invalid large-object descriptor: %d", fd
)));
199 status
= inv_seek(cookies
[fd
], offset
, whence
);
201 PG_RETURN_INT32(status
);
205 lo_creat(PG_FUNCTION_ARGS
)
210 * We don't actually need to store into fscxt, but create it anyway to
211 * ensure that AtEOXact_LargeObject knows there is state to clean up
215 lobjId
= inv_create(InvalidOid
);
217 PG_RETURN_OID(lobjId
);
221 lo_create(PG_FUNCTION_ARGS
)
223 Oid lobjId
= PG_GETARG_OID(0);
226 * We don't actually need to store into fscxt, but create it anyway to
227 * ensure that AtEOXact_LargeObject knows there is state to clean up
231 lobjId
= inv_create(lobjId
);
233 PG_RETURN_OID(lobjId
);
237 lo_tell(PG_FUNCTION_ARGS
)
239 int32 fd
= PG_GETARG_INT32(0);
241 if (fd
< 0 || fd
>= cookies_size
|| cookies
[fd
] == NULL
)
243 (errcode(ERRCODE_UNDEFINED_OBJECT
),
244 errmsg("invalid large-object descriptor: %d", fd
)));
246 PG_RETURN_INT32(inv_tell(cookies
[fd
]));
250 lo_unlink(PG_FUNCTION_ARGS
)
252 Oid lobjId
= PG_GETARG_OID(0);
255 * If there are any open LO FDs referencing that ID, close 'em.
261 for (i
= 0; i
< cookies_size
; i
++)
263 if (cookies
[i
] != NULL
&& cookies
[i
]->id
== lobjId
)
265 inv_close(cookies
[i
]);
272 * inv_drop does not create a need for end-of-transaction cleanup and
273 * hence we don't need to have created fscxt.
275 PG_RETURN_INT32(inv_drop(lobjId
));
278 /*****************************************************************************
279 * Read/Write using bytea
280 *****************************************************************************/
283 loread(PG_FUNCTION_ARGS
)
285 int32 fd
= PG_GETARG_INT32(0);
286 int32 len
= PG_GETARG_INT32(1);
293 retval
= (bytea
*) palloc(VARHDRSZ
+ len
);
294 totalread
= lo_read(fd
, VARDATA(retval
), len
);
295 SET_VARSIZE(retval
, totalread
+ VARHDRSZ
);
297 PG_RETURN_BYTEA_P(retval
);
301 lowrite(PG_FUNCTION_ARGS
)
303 int32 fd
= PG_GETARG_INT32(0);
304 bytea
*wbuf
= PG_GETARG_BYTEA_P(1);
308 bytestowrite
= VARSIZE(wbuf
) - VARHDRSZ
;
309 totalwritten
= lo_write(fd
, VARDATA(wbuf
), bytestowrite
);
310 PG_RETURN_INT32(totalwritten
);
313 /*****************************************************************************
314 * Import/Export of Large Object
315 *****************************************************************************/
319 * imports a file as an (inversion) large object.
322 lo_import(PG_FUNCTION_ARGS
)
324 text
*filename
= PG_GETARG_TEXT_PP(0);
326 PG_RETURN_OID(lo_import_internal(filename
, InvalidOid
));
330 * lo_import_with_oid -
331 * imports a file as an (inversion) large object specifying oid.
334 lo_import_with_oid(PG_FUNCTION_ARGS
)
336 text
*filename
= PG_GETARG_TEXT_PP(0);
337 Oid oid
= PG_GETARG_OID(1);
339 PG_RETURN_OID(lo_import_internal(filename
, oid
));
343 lo_import_internal(text
*filename
, Oid lobjOid
)
349 char fnamebuf
[MAXPGPATH
];
350 LargeObjectDesc
*lobj
;
353 #ifndef ALLOW_DANGEROUS_LO_FUNCTIONS
356 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE
),
357 errmsg("must be superuser to use server-side lo_import()"),
358 errhint("Anyone can use the client-side lo_import() provided by libpq.")));
364 * open the file to be read in
366 text_to_cstring_buffer(filename
, fnamebuf
, sizeof(fnamebuf
));
367 fd
= PathNameOpenFile(fnamebuf
, O_RDONLY
| PG_BINARY
, 0666);
370 (errcode_for_file_access(),
371 errmsg("could not open server file \"%s\": %m",
375 * create an inversion object
377 oid
= inv_create(lobjOid
);
380 * read in from the filesystem and write to the inversion object
382 lobj
= inv_open(oid
, INV_WRITE
, fscxt
);
384 while ((nbytes
= FileRead(fd
, buf
, BUFSIZE
)) > 0)
386 tmp
= inv_write(lobj
, buf
, nbytes
);
387 Assert(tmp
== nbytes
);
392 (errcode_for_file_access(),
393 errmsg("could not read server file \"%s\": %m",
404 * exports an (inversion) large object.
407 lo_export(PG_FUNCTION_ARGS
)
409 Oid lobjId
= PG_GETARG_OID(0);
410 text
*filename
= PG_GETARG_TEXT_PP(1);
415 char fnamebuf
[MAXPGPATH
];
416 LargeObjectDesc
*lobj
;
419 #ifndef ALLOW_DANGEROUS_LO_FUNCTIONS
422 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE
),
423 errmsg("must be superuser to use server-side lo_export()"),
424 errhint("Anyone can use the client-side lo_export() provided by libpq.")));
430 * open the inversion object (no need to test for failure)
432 lobj
= inv_open(lobjId
, INV_READ
, fscxt
);
435 * open the file to be written to
437 * Note: we reduce backend's normal 077 umask to the slightly friendlier
438 * 022. This code used to drop it all the way to 0, but creating
439 * world-writable export files doesn't seem wise.
441 text_to_cstring_buffer(filename
, fnamebuf
, sizeof(fnamebuf
));
442 oumask
= umask((mode_t
) 0022);
443 fd
= PathNameOpenFile(fnamebuf
, O_CREAT
| O_WRONLY
| O_TRUNC
| PG_BINARY
, 0666);
447 (errcode_for_file_access(),
448 errmsg("could not create server file \"%s\": %m",
452 * read in from the inversion file and write to the filesystem
454 while ((nbytes
= inv_read(lobj
, buf
, BUFSIZE
)) > 0)
456 tmp
= FileWrite(fd
, buf
, nbytes
);
459 (errcode_for_file_access(),
460 errmsg("could not write server file \"%s\": %m",
472 * truncate a large object to a specified length
475 lo_truncate(PG_FUNCTION_ARGS
)
477 int32 fd
= PG_GETARG_INT32(0);
478 int32 len
= PG_GETARG_INT32(1);
480 if (fd
< 0 || fd
>= cookies_size
|| cookies
[fd
] == NULL
)
482 (errcode(ERRCODE_UNDEFINED_OBJECT
),
483 errmsg("invalid large-object descriptor: %d", fd
)));
485 inv_truncate(cookies
[fd
], len
);
491 * AtEOXact_LargeObject -
492 * prepares large objects for transaction commit
495 AtEOXact_LargeObject(bool isCommit
)
500 return; /* no LO operations in this xact */
503 * Close LO fds and clear cookies array so that LO fds are no longer good.
504 * On abort we skip the close step.
506 for (i
= 0; i
< cookies_size
; i
++)
508 if (cookies
[i
] != NULL
)
511 inv_close(cookies
[i
]);
516 /* Needn't actually pfree since we're about to zap context */
520 /* Release the LO memory context to prevent permanent memory leaks. */
521 MemoryContextDelete(fscxt
);
524 /* Give inv_api.c a chance to clean up, too */
525 close_lo_relation(isCommit
);
529 * AtEOSubXact_LargeObject
530 * Take care of large objects at subtransaction commit/abort
532 * Reassign LOs created/opened during a committing subtransaction
533 * to the parent subtransaction. On abort, just close them.
536 AtEOSubXact_LargeObject(bool isCommit
, SubTransactionId mySubid
,
537 SubTransactionId parentSubid
)
541 if (fscxt
== NULL
) /* no LO operations in this xact */
544 for (i
= 0; i
< cookies_size
; i
++)
546 LargeObjectDesc
*lo
= cookies
[i
];
548 if (lo
!= NULL
&& lo
->subid
== mySubid
)
551 lo
->subid
= parentSubid
;
555 * Make sure we do not call inv_close twice if it errors out
556 * for some reason. Better a leak than a crash.
565 /*****************************************************************************
566 * Support routines for this file
567 *****************************************************************************/
570 newLOfd(LargeObjectDesc
*lobjCookie
)
575 /* Try to find a free slot */
576 for (i
= 0; i
< cookies_size
; i
++)
578 if (cookies
[i
] == NULL
)
580 cookies
[i
] = lobjCookie
;
585 /* No free slot, so make the array bigger */
586 if (cookies_size
<= 0)
588 /* First time through, arbitrarily make 64-element array */
591 cookies
= (LargeObjectDesc
**)
592 MemoryContextAllocZero(fscxt
, newsize
* sizeof(LargeObjectDesc
*));
593 cookies_size
= newsize
;
597 /* Double size of array */
599 newsize
= cookies_size
* 2;
600 cookies
= (LargeObjectDesc
**)
601 repalloc(cookies
, newsize
* sizeof(LargeObjectDesc
*));
602 MemSet(cookies
+ cookies_size
, 0,
603 (newsize
- cookies_size
) * sizeof(LargeObjectDesc
*));
604 cookies_size
= newsize
;
607 Assert(cookies
[i
] == NULL
);
608 cookies
[i
] = lobjCookie
;