LCOV - code coverage report
Current view: top level - src/backend/libpq - be-fsstubs.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 86.0 % 271 233
Test Date: 2026-07-03 19:57:34 Functions: 96.6 % 29 28
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 46.7 % 184 86

             Branch data     Line data    Source code
       1                 :             : /*-------------------------------------------------------------------------
       2                 :             :  *
       3                 :             :  * be-fsstubs.c
       4                 :             :  *    Builtin functions for open/close/read/write operations on large objects
       5                 :             :  *
       6                 :             :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       7                 :             :  * Portions Copyright (c) 1994, Regents of the University of California
       8                 :             :  *
       9                 :             :  *
      10                 :             :  * IDENTIFICATION
      11                 :             :  *    src/backend/libpq/be-fsstubs.c
      12                 :             :  *
      13                 :             :  * NOTES
      14                 :             :  *    This should be moved to a more appropriate place.  It is here
      15                 :             :  *    for lack of a better place.
      16                 :             :  *
      17                 :             :  *    These functions store LargeObjectDesc structs in a private MemoryContext,
      18                 :             :  *    which means that large object descriptors hang around until we destroy
      19                 :             :  *    the context at transaction end.  It'd be possible to prolong the lifetime
      20                 :             :  *    of the context so that LO FDs are good across transactions (for example,
      21                 :             :  *    we could release the context only if we see that no FDs remain open).
      22                 :             :  *    But we'd need additional state in order to do the right thing at the
      23                 :             :  *    end of an aborted transaction.  FDs opened during an aborted xact would
      24                 :             :  *    still need to be closed, since they might not be pointing at valid
      25                 :             :  *    relations at all.  Locking semantics are also an interesting problem
      26                 :             :  *    if LOs stay open across transactions.  For now, we'll stick with the
      27                 :             :  *    existing documented semantics of LO FDs: they're only good within a
      28                 :             :  *    transaction.
      29                 :             :  *
      30                 :             :  *    As of PostgreSQL 8.0, much of the angst expressed above is no longer
      31                 :             :  *    relevant, and in fact it'd be pretty easy to allow LO FDs to stay
      32                 :             :  *    open across transactions.  (Snapshot relevancy would still be an issue.)
      33                 :             :  *    However backwards compatibility suggests that we should stick to the
      34                 :             :  *    status quo.
      35                 :             :  *
      36                 :             :  *-------------------------------------------------------------------------
      37                 :             :  */
      38                 :             : 
      39                 :             : #include "postgres.h"
      40                 :             : 
      41                 :             : #include <fcntl.h>
      42                 :             : #include <sys/stat.h>
      43                 :             : #include <unistd.h>
      44                 :             : 
      45                 :             : #include "access/xact.h"
      46                 :             : #include "catalog/pg_largeobject.h"
      47                 :             : #include "libpq/be-fsstubs.h"
      48                 :             : #include "libpq/libpq-fs.h"
      49                 :             : #include "miscadmin.h"
      50                 :             : #include "storage/fd.h"
      51                 :             : #include "storage/large_object.h"
      52                 :             : #include "utils/acl.h"
      53                 :             : #include "utils/builtins.h"
      54                 :             : #include "utils/memutils.h"
      55                 :             : #include "utils/snapmgr.h"
      56                 :             : #include "varatt.h"
      57                 :             : 
      58                 :             : /* define this to enable debug logging */
      59                 :             : /* #define FSDB 1 */
      60                 :             : /* chunk size for lo_import/lo_export transfers */
      61                 :             : #define BUFSIZE         8192
      62                 :             : 
      63                 :             : /*
      64                 :             :  * LO "FD"s are indexes into the cookies array.
      65                 :             :  *
      66                 :             :  * A non-null entry is a pointer to a LargeObjectDesc allocated in the
      67                 :             :  * LO private memory context "fscxt".  The cookies array itself is also
      68                 :             :  * dynamically allocated in that context.  Its current allocated size is
      69                 :             :  * cookies_size entries, of which any unused entries will be NULL.
      70                 :             :  */
      71                 :             : static LargeObjectDesc **cookies = NULL;
      72                 :             : static int  cookies_size = 0;
      73                 :             : 
      74                 :             : static bool lo_cleanup_needed = false;
      75                 :             : static MemoryContext fscxt = NULL;
      76                 :             : 
      77                 :             : static int  newLOfd(void);
      78                 :             : static void closeLOfd(int fd);
      79                 :             : static Oid  lo_import_internal(text *filename, Oid lobjOid);
      80                 :             : 
      81                 :             : 
      82                 :             : /*****************************************************************************
      83                 :             :  *  File Interfaces for Large Objects
      84                 :             :  *****************************************************************************/
      85                 :             : 
      86                 :             : Datum
      87                 :         234 : be_lo_open(PG_FUNCTION_ARGS)
      88                 :             : {
      89                 :         234 :     Oid         lobjId = PG_GETARG_OID(0);
      90                 :         234 :     int32       mode = PG_GETARG_INT32(1);
      91                 :             :     LargeObjectDesc *lobjDesc;
      92                 :             :     int         fd;
      93                 :             : 
      94                 :             : #ifdef FSDB
      95                 :             :     elog(DEBUG4, "lo_open(%u,%d)", lobjId, mode);
      96                 :             : #endif
      97                 :             : 
      98         [ +  + ]:         234 :     if (mode & INV_WRITE)
      99                 :          93 :         PreventCommandIfReadOnly("lo_open(INV_WRITE)");
     100                 :             : 
     101                 :             :     /*
     102                 :             :      * Allocate a large object descriptor first.  This will also create
     103                 :             :      * 'fscxt' if this is the first LO opened in this transaction.
     104                 :             :      */
     105                 :         230 :     fd = newLOfd();
     106                 :             : 
     107                 :         230 :     lobjDesc = inv_open(lobjId, mode, fscxt);
     108                 :         190 :     lobjDesc->subid = GetCurrentSubTransactionId();
     109                 :             : 
     110                 :             :     /*
     111                 :             :      * We must register the snapshot in TopTransaction's resowner so that it
     112                 :             :      * stays alive until the LO is closed rather than until the current portal
     113                 :             :      * shuts down.
     114                 :             :      */
     115         [ +  + ]:         190 :     if (lobjDesc->snapshot)
     116                 :         129 :         lobjDesc->snapshot = RegisterSnapshotOnOwner(lobjDesc->snapshot,
     117                 :             :                                                      TopTransactionResourceOwner);
     118                 :             : 
     119                 :             :     Assert(cookies[fd] == NULL);
     120                 :         190 :     cookies[fd] = lobjDesc;
     121                 :             : 
     122                 :         190 :     PG_RETURN_INT32(fd);
     123                 :             : }
     124                 :             : 
     125                 :             : Datum
     126                 :         118 : be_lo_close(PG_FUNCTION_ARGS)
     127                 :             : {
     128                 :         118 :     int32       fd = PG_GETARG_INT32(0);
     129                 :             : 
     130   [ +  -  +  -  :         118 :     if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
                   -  + ]
     131         [ #  # ]:           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                 :             : 
     139                 :         118 :     closeLOfd(fd);
     140                 :             : 
     141                 :         118 :     PG_RETURN_INT32(0);
     142                 :             : }
     143                 :             : 
     144                 :             : 
     145                 :             : /*****************************************************************************
     146                 :             :  *  Bare Read/Write operations --- these are not fmgr-callable!
     147                 :             :  *
     148                 :             :  *  We assume the large object supports byte oriented reads and seeks so
     149                 :             :  *  that our work is easier.
     150                 :             :  *
     151                 :             :  *****************************************************************************/
     152                 :             : 
     153                 :             : int
     154                 :         525 : lo_read(int fd, char *buf, int len)
     155                 :             : {
     156                 :             :     int         status;
     157                 :             :     LargeObjectDesc *lobj;
     158                 :             : 
     159   [ +  -  +  -  :         525 :     if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
                   -  + ]
     160         [ #  # ]:           0 :         ereport(ERROR,
     161                 :             :                 (errcode(ERRCODE_UNDEFINED_OBJECT),
     162                 :             :                  errmsg("invalid large-object descriptor: %d", fd)));
     163                 :         525 :     lobj = cookies[fd];
     164                 :             : 
     165                 :             :     /*
     166                 :             :      * Check state.  inv_read() would throw an error anyway, but we want the
     167                 :             :      * error to be about the FD's state not the underlying privilege; it might
     168                 :             :      * be that the privilege exists but user forgot to ask for read mode.
     169                 :             :      */
     170         [ -  + ]:         525 :     if ((lobj->flags & IFS_RDLOCK) == 0)
     171         [ #  # ]:           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                 :             : 
     176                 :         525 :     status = inv_read(lobj, buf, len);
     177                 :             : 
     178                 :         525 :     return status;
     179                 :             : }
     180                 :             : 
     181                 :             : int
     182                 :         693 : lo_write(int fd, const char *buf, int len)
     183                 :             : {
     184                 :             :     int         status;
     185                 :             :     LargeObjectDesc *lobj;
     186                 :             : 
     187   [ +  -  +  -  :         693 :     if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
                   -  + ]
     188         [ #  # ]:           0 :         ereport(ERROR,
     189                 :             :                 (errcode(ERRCODE_UNDEFINED_OBJECT),
     190                 :             :                  errmsg("invalid large-object descriptor: %d", fd)));
     191                 :         693 :     lobj = cookies[fd];
     192                 :             : 
     193                 :             :     /* see comment in lo_read() */
     194         [ +  + ]:         693 :     if ((lobj->flags & IFS_WRLOCK) == 0)
     195         [ +  - ]:           4 :         ereport(ERROR,
     196                 :             :                 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
     197                 :             :                  errmsg("large object descriptor %d was not opened for writing",
     198                 :             :                         fd)));
     199                 :             : 
     200                 :         689 :     status = inv_write(lobj, buf, len);
     201                 :             : 
     202                 :         689 :     return status;
     203                 :             : }
     204                 :             : 
     205                 :             : Datum
     206                 :          36 : be_lo_lseek(PG_FUNCTION_ARGS)
     207                 :             : {
     208                 :          36 :     int32       fd = PG_GETARG_INT32(0);
     209                 :          36 :     int32       offset = PG_GETARG_INT32(1);
     210                 :          36 :     int32       whence = PG_GETARG_INT32(2);
     211                 :             :     int64       status;
     212                 :             : 
     213   [ +  -  +  -  :          36 :     if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
                   -  + ]
     214         [ #  # ]:           0 :         ereport(ERROR,
     215                 :             :                 (errcode(ERRCODE_UNDEFINED_OBJECT),
     216                 :             :                  errmsg("invalid large-object descriptor: %d", fd)));
     217                 :             : 
     218                 :          36 :     status = inv_seek(cookies[fd], offset, whence);
     219                 :             : 
     220                 :             :     /* guard against result overflow */
     221         [ -  + ]:          36 :     if (status != (int32) status)
     222         [ #  # ]:           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                 :             : 
     227                 :          36 :     PG_RETURN_INT32((int32) status);
     228                 :             : }
     229                 :             : 
     230                 :             : Datum
     231                 :          16 : be_lo_lseek64(PG_FUNCTION_ARGS)
     232                 :             : {
     233                 :          16 :     int32       fd = PG_GETARG_INT32(0);
     234                 :          16 :     int64       offset = PG_GETARG_INT64(1);
     235                 :          16 :     int32       whence = PG_GETARG_INT32(2);
     236                 :             :     int64       status;
     237                 :             : 
     238   [ +  -  +  -  :          16 :     if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
                   -  + ]
     239         [ #  # ]:           0 :         ereport(ERROR,
     240                 :             :                 (errcode(ERRCODE_UNDEFINED_OBJECT),
     241                 :             :                  errmsg("invalid large-object descriptor: %d", fd)));
     242                 :             : 
     243                 :          16 :     status = inv_seek(cookies[fd], offset, whence);
     244                 :             : 
     245                 :          16 :     PG_RETURN_INT64(status);
     246                 :             : }
     247                 :             : 
     248                 :             : Datum
     249                 :          17 : be_lo_creat(PG_FUNCTION_ARGS)
     250                 :             : {
     251                 :             :     Oid         lobjId;
     252                 :             : 
     253                 :          17 :     PreventCommandIfReadOnly("lo_creat()");
     254                 :             : 
     255                 :          13 :     lo_cleanup_needed = true;
     256                 :          13 :     lobjId = inv_create(InvalidOid);
     257                 :             : 
     258                 :          13 :     PG_RETURN_OID(lobjId);
     259                 :             : }
     260                 :             : 
     261                 :             : Datum
     262                 :          62 : be_lo_create(PG_FUNCTION_ARGS)
     263                 :             : {
     264                 :          62 :     Oid         lobjId = PG_GETARG_OID(0);
     265                 :             : 
     266                 :          62 :     PreventCommandIfReadOnly("lo_create()");
     267                 :             : 
     268                 :          58 :     lo_cleanup_needed = true;
     269                 :          58 :     lobjId = inv_create(lobjId);
     270                 :             : 
     271                 :          58 :     PG_RETURN_OID(lobjId);
     272                 :             : }
     273                 :             : 
     274                 :             : Datum
     275                 :          16 : be_lo_tell(PG_FUNCTION_ARGS)
     276                 :             : {
     277                 :          16 :     int32       fd = PG_GETARG_INT32(0);
     278                 :             :     int64       offset;
     279                 :             : 
     280   [ +  -  +  -  :          16 :     if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
                   -  + ]
     281         [ #  # ]:           0 :         ereport(ERROR,
     282                 :             :                 (errcode(ERRCODE_UNDEFINED_OBJECT),
     283                 :             :                  errmsg("invalid large-object descriptor: %d", fd)));
     284                 :             : 
     285                 :          16 :     offset = inv_tell(cookies[fd]);
     286                 :             : 
     287                 :             :     /* guard against result overflow */
     288         [ -  + ]:          16 :     if (offset != (int32) offset)
     289         [ #  # ]:           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                 :             : 
     294                 :          16 :     PG_RETURN_INT32((int32) offset);
     295                 :             : }
     296                 :             : 
     297                 :             : Datum
     298                 :          16 : be_lo_tell64(PG_FUNCTION_ARGS)
     299                 :             : {
     300                 :          16 :     int32       fd = PG_GETARG_INT32(0);
     301                 :             :     int64       offset;
     302                 :             : 
     303   [ +  -  +  -  :          16 :     if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
                   -  + ]
     304         [ #  # ]:           0 :         ereport(ERROR,
     305                 :             :                 (errcode(ERRCODE_UNDEFINED_OBJECT),
     306                 :             :                  errmsg("invalid large-object descriptor: %d", fd)));
     307                 :             : 
     308                 :          16 :     offset = inv_tell(cookies[fd]);
     309                 :             : 
     310                 :          16 :     PG_RETURN_INT64(offset);
     311                 :             : }
     312                 :             : 
     313                 :             : Datum
     314                 :          77 : be_lo_unlink(PG_FUNCTION_ARGS)
     315                 :             : {
     316                 :          77 :     Oid         lobjId = PG_GETARG_OID(0);
     317                 :             : 
     318                 :          77 :     PreventCommandIfReadOnly("lo_unlink()");
     319                 :             : 
     320         [ -  + ]:          73 :     if (!LargeObjectExists(lobjId))
     321         [ #  # ]:           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                 :             :      */
     330         [ +  + ]:          73 :     if (!lo_compat_privileges &&
     331         [ +  + ]:          69 :         !object_ownercheck(LargeObjectRelationId, lobjId, GetUserId()))
     332         [ +  - ]:          16 :         ereport(ERROR,
     333                 :             :                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     334                 :             :                  errmsg("must be owner of large object %u", lobjId)));
     335                 :             : 
     336                 :             :     /*
     337                 :             :      * If there are any open LO FDs referencing that ID, close 'em.
     338                 :             :      */
     339         [ -  + ]:          57 :     if (fscxt != NULL)
     340                 :             :     {
     341                 :             :         int         i;
     342                 :             : 
     343         [ #  # ]:           0 :         for (i = 0; i < cookies_size; i++)
     344                 :             :         {
     345   [ #  #  #  # ]:           0 :             if (cookies[i] != NULL && cookies[i]->id == lobjId)
     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                 :             :      */
     354                 :          57 :     PG_RETURN_INT32(inv_drop(lobjId));
     355                 :             : }
     356                 :             : 
     357                 :             : /*****************************************************************************
     358                 :             :  *  Read/Write using bytea
     359                 :             :  *****************************************************************************/
     360                 :             : 
     361                 :             : Datum
     362                 :         525 : be_loread(PG_FUNCTION_ARGS)
     363                 :             : {
     364                 :         525 :     int32       fd = PG_GETARG_INT32(0);
     365                 :         525 :     int32       len = PG_GETARG_INT32(1);
     366                 :             :     bytea      *retval;
     367                 :             :     int         totalread;
     368                 :             : 
     369         [ -  + ]:         525 :     if (len < 0)
     370                 :           0 :         len = 0;
     371                 :             : 
     372                 :         525 :     retval = (bytea *) palloc(VARHDRSZ + len);
     373                 :         525 :     totalread = lo_read(fd, VARDATA(retval), len);
     374                 :         525 :     SET_VARSIZE(retval, totalread + VARHDRSZ);
     375                 :             : 
     376                 :         525 :     PG_RETURN_BYTEA_P(retval);
     377                 :             : }
     378                 :             : 
     379                 :             : Datum
     380                 :         697 : be_lowrite(PG_FUNCTION_ARGS)
     381                 :             : {
     382                 :         697 :     int32       fd = PG_GETARG_INT32(0);
     383                 :         697 :     bytea      *wbuf = PG_GETARG_BYTEA_PP(1);
     384                 :             :     int         bytestowrite;
     385                 :             :     int         totalwritten;
     386                 :             : 
     387                 :         697 :     PreventCommandIfReadOnly("lowrite()");
     388                 :             : 
     389                 :         693 :     bytestowrite = VARSIZE_ANY_EXHDR(wbuf);
     390                 :         693 :     totalwritten = lo_write(fd, VARDATA_ANY(wbuf), bytestowrite);
     391                 :         689 :     PG_RETURN_INT32(totalwritten);
     392                 :             : }
     393                 :             : 
     394                 :             : /*****************************************************************************
     395                 :             :  *   Import/Export of Large Object
     396                 :             :  *****************************************************************************/
     397                 :             : 
     398                 :             : /*
     399                 :             :  * lo_import -
     400                 :             :  *    imports a file as an (inversion) large object.
     401                 :             :  */
     402                 :             : Datum
     403                 :           8 : be_lo_import(PG_FUNCTION_ARGS)
     404                 :             : {
     405                 :           8 :     text       *filename = PG_GETARG_TEXT_PP(0);
     406                 :             : 
     407                 :           8 :     PG_RETURN_OID(lo_import_internal(filename, InvalidOid));
     408                 :             : }
     409                 :             : 
     410                 :             : /*
     411                 :             :  * lo_import_with_oid -
     412                 :             :  *    imports a file as an (inversion) large object specifying oid.
     413                 :             :  */
     414                 :             : Datum
     415                 :           0 : be_lo_import_with_oid(PG_FUNCTION_ARGS)
     416                 :             : {
     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                 :             : }
     422                 :             : 
     423                 :             : static Oid
     424                 :           8 : 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                 :             : 
     434                 :           8 :     PreventCommandIfReadOnly("lo_import()");
     435                 :             : 
     436                 :             :     /*
     437                 :             :      * open the file to be read in
     438                 :             :      */
     439                 :           4 :     text_to_cstring_buffer(filename, fnamebuf, sizeof(fnamebuf));
     440                 :           4 :     fd = OpenTransientFile(fnamebuf, O_RDONLY | PG_BINARY);
     441         [ -  + ]:           4 :     if (fd < 0)
     442         [ #  # ]:           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                 :             :      */
     450                 :           4 :     lo_cleanup_needed = true;
     451                 :           4 :     oid = inv_create(lobjOid);
     452                 :             : 
     453                 :             :     /*
     454                 :             :      * read in from the filesystem and write to the inversion object
     455                 :             :      */
     456                 :           4 :     lobj = inv_open(oid, INV_WRITE, CurrentMemoryContext);
     457                 :             : 
     458         [ +  + ]:         332 :     while ((nbytes = read(fd, buf, BUFSIZE)) > 0)
     459                 :             :     {
     460                 :         328 :         tmp = inv_write(lobj, buf, nbytes);
     461                 :             :         Assert(tmp == nbytes);
     462                 :             :     }
     463                 :             : 
     464         [ -  + ]:           4 :     if (nbytes < 0)
     465         [ #  # ]:           0 :         ereport(ERROR,
     466                 :             :                 (errcode_for_file_access(),
     467                 :             :                  errmsg("could not read server file \"%s\": %m",
     468                 :             :                         fnamebuf)));
     469                 :             : 
     470                 :           4 :     inv_close(lobj);
     471                 :             : 
     472         [ -  + ]:           4 :     if (CloseTransientFile(fd) != 0)
     473         [ #  # ]:           0 :         ereport(ERROR,
     474                 :             :                 (errcode_for_file_access(),
     475                 :             :                  errmsg("could not close file \"%s\": %m",
     476                 :             :                         fnamebuf)));
     477                 :             : 
     478                 :           4 :     return oid;
     479                 :             : }
     480                 :             : 
     481                 :             : /*
     482                 :             :  * lo_export -
     483                 :             :  *    exports an (inversion) large object.
     484                 :             :  */
     485                 :             : Datum
     486                 :           8 : be_lo_export(PG_FUNCTION_ARGS)
     487                 :             : {
     488                 :           8 :     Oid         lobjId = PG_GETARG_OID(0);
     489                 :           8 :     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                 :             :      */
     501                 :           8 :     lo_cleanup_needed = true;
     502                 :           8 :     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                 :           8 :     text_to_cstring_buffer(filename, fnamebuf, sizeof(fnamebuf));
     512                 :           8 :     oumask = umask(S_IWGRP | S_IWOTH);
     513         [ +  - ]:           8 :     PG_TRY();
     514                 :             :     {
     515                 :           8 :         fd = OpenTransientFilePerm(fnamebuf, O_CREAT | O_WRONLY | O_TRUNC | PG_BINARY,
     516                 :             :                                    S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
     517                 :             :     }
     518                 :           0 :     PG_FINALLY();
     519                 :             :     {
     520                 :           8 :         umask(oumask);
     521                 :             :     }
     522         [ -  + ]:           8 :     PG_END_TRY();
     523         [ +  + ]:           8 :     if (fd < 0)
     524         [ +  - ]:           4 :         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                 :             :      */
     532         [ +  + ]:         332 :     while ((nbytes = inv_read(lobj, buf, BUFSIZE)) > 0)
     533                 :             :     {
     534                 :         328 :         tmp = write(fd, buf, nbytes);
     535         [ -  + ]:         328 :         if (tmp != nbytes)
     536         [ #  # ]:           0 :             ereport(ERROR,
     537                 :             :                     (errcode_for_file_access(),
     538                 :             :                      errmsg("could not write server file \"%s\": %m",
     539                 :             :                             fnamebuf)));
     540                 :             :     }
     541                 :             : 
     542         [ -  + ]:           4 :     if (CloseTransientFile(fd) != 0)
     543         [ #  # ]:           0 :         ereport(ERROR,
     544                 :             :                 (errcode_for_file_access(),
     545                 :             :                  errmsg("could not close file \"%s\": %m",
     546                 :             :                         fnamebuf)));
     547                 :             : 
     548                 :           4 :     inv_close(lobj);
     549                 :             : 
     550                 :           4 :     PG_RETURN_INT32(1);
     551                 :             : }
     552                 :             : 
     553                 :             : /*
     554                 :             :  * lo_truncate -
     555                 :             :  *    truncate a large object to a specified length
     556                 :             :  */
     557                 :             : static void
     558                 :          32 : lo_truncate_internal(int32 fd, int64 len)
     559                 :             : {
     560                 :             :     LargeObjectDesc *lobj;
     561                 :             : 
     562   [ +  -  +  -  :          32 :     if (fd < 0 || fd >= cookies_size || cookies[fd] == NULL)
                   -  + ]
     563         [ #  # ]:           0 :         ereport(ERROR,
     564                 :             :                 (errcode(ERRCODE_UNDEFINED_OBJECT),
     565                 :             :                  errmsg("invalid large-object descriptor: %d", fd)));
     566                 :          32 :     lobj = cookies[fd];
     567                 :             : 
     568                 :             :     /* see comment in lo_read() */
     569         [ -  + ]:          32 :     if ((lobj->flags & IFS_WRLOCK) == 0)
     570         [ #  # ]:           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                 :             : 
     575                 :          32 :     inv_truncate(lobj, len);
     576                 :          32 : }
     577                 :             : 
     578                 :             : Datum
     579                 :          28 : be_lo_truncate(PG_FUNCTION_ARGS)
     580                 :             : {
     581                 :          28 :     int32       fd = PG_GETARG_INT32(0);
     582                 :          28 :     int32       len = PG_GETARG_INT32(1);
     583                 :             : 
     584                 :          28 :     PreventCommandIfReadOnly("lo_truncate()");
     585                 :             : 
     586                 :          24 :     lo_truncate_internal(fd, len);
     587                 :          24 :     PG_RETURN_INT32(0);
     588                 :             : }
     589                 :             : 
     590                 :             : Datum
     591                 :          12 : be_lo_truncate64(PG_FUNCTION_ARGS)
     592                 :             : {
     593                 :          12 :     int32       fd = PG_GETARG_INT32(0);
     594                 :          12 :     int64       len = PG_GETARG_INT64(1);
     595                 :             : 
     596                 :          12 :     PreventCommandIfReadOnly("lo_truncate64()");
     597                 :             : 
     598                 :           8 :     lo_truncate_internal(fd, len);
     599                 :           8 :     PG_RETURN_INT32(0);
     600                 :             : }
     601                 :             : 
     602                 :             : /*
     603                 :             :  * AtEOXact_LargeObject -
     604                 :             :  *       prepares large objects for transaction commit
     605                 :             :  */
     606                 :             : void
     607                 :      652038 : AtEOXact_LargeObject(bool isCommit)
     608                 :             : {
     609                 :             :     int         i;
     610                 :             : 
     611         [ +  + ]:      652038 :     if (!lo_cleanup_needed)
     612                 :      651679 :         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         [ +  + ]:         359 :     if (isCommit)
     622                 :             :     {
     623         [ +  + ]:        6193 :         for (i = 0; i < cookies_size; i++)
     624                 :             :         {
     625         [ +  + ]:        5952 :             if (cookies[i] != NULL)
     626                 :          60 :                 closeLOfd(i);
     627                 :             :         }
     628                 :             :     }
     629                 :             : 
     630                 :             :     /* Needn't actually pfree since we're about to zap context */
     631                 :         359 :     cookies = NULL;
     632                 :         359 :     cookies_size = 0;
     633                 :             : 
     634                 :             :     /* Release the LO memory context to prevent permanent memory leaks. */
     635         [ +  + ]:         359 :     if (fscxt)
     636                 :         190 :         MemoryContextDelete(fscxt);
     637                 :         359 :     fscxt = NULL;
     638                 :             : 
     639                 :             :     /* Give inv_api.c a chance to clean up, too */
     640                 :         359 :     close_lo_relation(isCommit);
     641                 :             : 
     642                 :         359 :     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
     653                 :       11060 : AtEOSubXact_LargeObject(bool isCommit, SubTransactionId mySubid,
     654                 :             :                         SubTransactionId parentSubid)
     655                 :             : {
     656                 :             :     int         i;
     657                 :             : 
     658         [ +  - ]:       11060 :     if (fscxt == NULL)          /* no LO operations in this xact */
     659                 :       11060 :         return;
     660                 :             : 
     661         [ #  # ]:           0 :     for (i = 0; i < cookies_size; i++)
     662                 :             :     {
     663                 :           0 :         LargeObjectDesc *lo = cookies[i];
     664                 :             : 
     665   [ #  #  #  # ]:           0 :         if (lo != NULL && lo->subid == mySubid)
     666                 :             :         {
     667         [ #  # ]:           0 :             if (isCommit)
     668                 :           0 :                 lo->subid = parentSubid;
     669                 :             :             else
     670                 :           0 :                 closeLOfd(i);
     671                 :             :         }
     672                 :             :     }
     673                 :             : }
     674                 :             : 
     675                 :             : /*****************************************************************************
     676                 :             :  *  Support routines for this file
     677                 :             :  *****************************************************************************/
     678                 :             : 
     679                 :             : static int
     680                 :         230 : newLOfd(void)
     681                 :             : {
     682                 :             :     int         i,
     683                 :             :                 newsize;
     684                 :             : 
     685                 :         230 :     lo_cleanup_needed = true;
     686         [ +  + ]:         230 :     if (fscxt == NULL)
     687                 :         190 :         fscxt = AllocSetContextCreate(TopMemoryContext,
     688                 :             :                                       "Filesystem",
     689                 :             :                                       ALLOCSET_DEFAULT_SIZES);
     690                 :             : 
     691                 :             :     /* Try to find a free slot */
     692         [ +  + ]:         230 :     for (i = 0; i < cookies_size; i++)
     693                 :             :     {
     694         [ +  - ]:          40 :         if (cookies[i] == NULL)
     695                 :          40 :             return i;
     696                 :             :     }
     697                 :             : 
     698                 :             :     /* No free slot, so make the array bigger */
     699         [ +  - ]:         190 :     if (cookies_size <= 0)
     700                 :             :     {
     701                 :             :         /* First time through, arbitrarily make 64-element array */
     702                 :         190 :         i = 0;
     703                 :         190 :         newsize = 64;
     704                 :         190 :         cookies = (LargeObjectDesc **)
     705                 :         190 :             MemoryContextAllocZero(fscxt, newsize * sizeof(LargeObjectDesc *));
     706                 :             :     }
     707                 :             :     else
     708                 :             :     {
     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                 :             :     }
     715                 :         190 :     cookies_size = newsize;
     716                 :             : 
     717                 :         190 :     return i;
     718                 :             : }
     719                 :             : 
     720                 :             : static void
     721                 :         178 : 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                 :         178 :     lobj = cookies[fd];
     730                 :         178 :     cookies[fd] = NULL;
     731                 :             : 
     732         [ +  + ]:         178 :     if (lobj->snapshot)
     733                 :         117 :         UnregisterSnapshotFromOwner(lobj->snapshot,
     734                 :             :                                     TopTransactionResourceOwner);
     735                 :         178 :     inv_close(lobj);
     736                 :         178 : }
     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                 :          70 : 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                 :          70 :     bytea      *result = NULL;
     753                 :             : 
     754                 :          70 :     lo_cleanup_needed = true;
     755                 :          70 :     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                 :          65 :     loSize = inv_seek(loDesc, 0, SEEK_END);
     762         [ +  + ]:          65 :     if (loSize > offset)
     763                 :             :     {
     764   [ +  +  +  + ]:          52 :         if (nbytes >= 0 && nbytes <= loSize - offset)
     765                 :          12 :             result_length = nbytes; /* request is wholly inside LO */
     766                 :             :         else
     767                 :          40 :             result_length = loSize - offset;    /* adjust to end of LO */
     768                 :             :     }
     769                 :             :     else
     770                 :          13 :         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         [ +  + ]:          65 :     if (result_length > MaxAllocSize - VARHDRSZ)
     777         [ +  - ]:           4 :         ereport(ERROR,
     778                 :             :                 (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
     779                 :             :                  errmsg("large object read request is too large")));
     780                 :             : 
     781                 :          61 :     result = (bytea *) palloc(VARHDRSZ + result_length);
     782                 :             : 
     783                 :          61 :     inv_seek(loDesc, offset, SEEK_SET);
     784                 :          61 :     total_read = inv_read(loDesc, VARDATA(result), result_length);
     785                 :             :     Assert(total_read == result_length);
     786                 :          61 :     SET_VARSIZE(result, result_length + VARHDRSZ);
     787                 :             : 
     788                 :          61 :     inv_close(loDesc);
     789                 :             : 
     790                 :          61 :     return result;
     791                 :             : }
     792                 :             : 
     793                 :             : /*
     794                 :             :  * Read entire LO
     795                 :             :  */
     796                 :             : Datum
     797                 :          54 : be_lo_get(PG_FUNCTION_ARGS)
     798                 :             : {
     799                 :          54 :     Oid         loOid = PG_GETARG_OID(0);
     800                 :             :     bytea      *result;
     801                 :             : 
     802                 :          54 :     result = lo_get_fragment_internal(loOid, 0, -1);
     803                 :             : 
     804                 :          45 :     PG_RETURN_BYTEA_P(result);
     805                 :             : }
     806                 :             : 
     807                 :             : /*
     808                 :             :  * Read range within LO
     809                 :             :  */
     810                 :             : Datum
     811                 :          16 : be_lo_get_fragment(PG_FUNCTION_ARGS)
     812                 :             : {
     813                 :          16 :     Oid         loOid = PG_GETARG_OID(0);
     814                 :          16 :     int64       offset = PG_GETARG_INT64(1);
     815                 :          16 :     int32       nbytes = PG_GETARG_INT32(2);
     816                 :             :     bytea      *result;
     817                 :             : 
     818         [ -  + ]:          16 :     if (nbytes < 0)
     819         [ #  # ]:           0 :         ereport(ERROR,
     820                 :             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     821                 :             :                  errmsg("requested length cannot be negative")));
     822                 :             : 
     823                 :          16 :     result = lo_get_fragment_internal(loOid, offset, nbytes);
     824                 :             : 
     825                 :          16 :     PG_RETURN_BYTEA_P(result);
     826                 :             : }
     827                 :             : 
     828                 :             : /*
     829                 :             :  * Create LO with initial contents given by a bytea argument
     830                 :             :  */
     831                 :             : Datum
     832                 :          33 : be_lo_from_bytea(PG_FUNCTION_ARGS)
     833                 :             : {
     834                 :          33 :     Oid         loOid = PG_GETARG_OID(0);
     835                 :          33 :     bytea      *str = PG_GETARG_BYTEA_PP(1);
     836                 :             :     LargeObjectDesc *loDesc;
     837                 :             :     int         written PG_USED_FOR_ASSERTS_ONLY;
     838                 :             : 
     839                 :          33 :     PreventCommandIfReadOnly("lo_from_bytea()");
     840                 :             : 
     841                 :          29 :     lo_cleanup_needed = true;
     842                 :          29 :     loOid = inv_create(loOid);
     843                 :          29 :     loDesc = inv_open(loOid, INV_WRITE, CurrentMemoryContext);
     844                 :          29 :     written = inv_write(loDesc, VARDATA_ANY(str), VARSIZE_ANY_EXHDR(str));
     845                 :             :     Assert(written == VARSIZE_ANY_EXHDR(str));
     846                 :          29 :     inv_close(loDesc);
     847                 :             : 
     848                 :          29 :     PG_RETURN_OID(loOid);
     849                 :             : }
     850                 :             : 
     851                 :             : /*
     852                 :             :  * Update range within LO
     853                 :             :  */
     854                 :             : Datum
     855                 :          24 : be_lo_put(PG_FUNCTION_ARGS)
     856                 :             : {
     857                 :          24 :     Oid         loOid = PG_GETARG_OID(0);
     858                 :          24 :     int64       offset = PG_GETARG_INT64(1);
     859                 :          24 :     bytea      *str = PG_GETARG_BYTEA_PP(2);
     860                 :             :     LargeObjectDesc *loDesc;
     861                 :             :     int         written PG_USED_FOR_ASSERTS_ONLY;
     862                 :             : 
     863                 :          24 :     PreventCommandIfReadOnly("lo_put()");
     864                 :             : 
     865                 :          20 :     lo_cleanup_needed = true;
     866                 :          20 :     loDesc = inv_open(loOid, INV_WRITE, CurrentMemoryContext);
     867                 :          12 :     inv_seek(loDesc, offset, SEEK_SET);
     868                 :          12 :     written = inv_write(loDesc, VARDATA_ANY(str), VARSIZE_ANY_EXHDR(str));
     869                 :             :     Assert(written == VARSIZE_ANY_EXHDR(str));
     870                 :          12 :     inv_close(loDesc);
     871                 :             : 
     872                 :          12 :     PG_RETURN_VOID();
     873                 :             : }
        

Generated by: LCOV version 2.0-1