LCOV - code coverage report
Current view: top level - src/backend/libpq - be-fsstubs.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 86.8 % 357 310
Test Date: 2026-01-26 10:56:24 Functions: 96.6 % 29 28
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 36.3 % 212 77

             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                 :          23 : be_lo_open(PG_FUNCTION_ARGS)
      88                 :             : {
      89                 :          23 :         Oid                     lobjId = PG_GETARG_OID(0);
      90                 :          23 :         int32           mode = PG_GETARG_INT32(1);
      91                 :          23 :         LargeObjectDesc *lobjDesc;
      92                 :          23 :         int                     fd;
      93                 :             : 
      94                 :             : #ifdef FSDB
      95                 :             :         elog(DEBUG4, "lo_open(%u,%d)", lobjId, mode);
      96                 :             : #endif
      97                 :             : 
      98         [ +  + ]:          23 :         if (mode & INV_WRITE)
      99                 :          19 :                 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                 :          23 :         fd = newLOfd();
     106                 :             : 
     107                 :          23 :         lobjDesc = inv_open(lobjId, mode, fscxt);
     108                 :          23 :         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         [ +  + ]:          23 :         if (lobjDesc->snapshot)
     116                 :          20 :                 lobjDesc->snapshot = RegisterSnapshotOnOwner(lobjDesc->snapshot,
     117                 :          10 :                                                                                                          TopTransactionResourceOwner);
     118                 :             : 
     119         [ +  - ]:          23 :         Assert(cookies[fd] == NULL);
     120                 :          23 :         cookies[fd] = lobjDesc;
     121                 :             : 
     122                 :          46 :         PG_RETURN_INT32(fd);
     123                 :          23 : }
     124                 :             : 
     125                 :             : Datum
     126                 :           8 : be_lo_close(PG_FUNCTION_ARGS)
     127                 :             : {
     128                 :           8 :         int32           fd = PG_GETARG_INT32(0);
     129                 :             : 
     130         [ +  - ]:           8 :         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                 :           8 :         closeLOfd(fd);
     140                 :             : 
     141                 :          16 :         PG_RETURN_INT32(0);
     142                 :           8 : }
     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                 :          97 : lo_read(int fd, char *buf, int len)
     155                 :             : {
     156                 :          97 :         int                     status;
     157                 :          97 :         LargeObjectDesc *lobj;
     158                 :             : 
     159         [ +  - ]:          97 :         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                 :          97 :         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         [ +  - ]:          97 :         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                 :          97 :         status = inv_read(lobj, buf, len);
     177                 :             : 
     178                 :         194 :         return status;
     179                 :          97 : }
     180                 :             : 
     181                 :             : int
     182                 :         172 : lo_write(int fd, const char *buf, int len)
     183                 :             : {
     184                 :         172 :         int                     status;
     185                 :         172 :         LargeObjectDesc *lobj;
     186                 :             : 
     187         [ +  - ]:         172 :         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                 :         172 :         lobj = cookies[fd];
     192                 :             : 
     193                 :             :         /* see comment in lo_read() */
     194         [ +  + ]:         172 :         if ((lobj->flags & IFS_WRLOCK) == 0)
     195   [ +  -  +  - ]:           1 :                 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                 :         171 :         status = inv_write(lobj, buf, len);
     201                 :             : 
     202                 :         342 :         return status;
     203                 :         171 : }
     204                 :             : 
     205                 :             : Datum
     206                 :           9 : be_lo_lseek(PG_FUNCTION_ARGS)
     207                 :             : {
     208                 :           9 :         int32           fd = PG_GETARG_INT32(0);
     209                 :           9 :         int32           offset = PG_GETARG_INT32(1);
     210                 :           9 :         int32           whence = PG_GETARG_INT32(2);
     211                 :           9 :         int64           status;
     212                 :             : 
     213         [ +  - ]:           9 :         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                 :           9 :         status = inv_seek(cookies[fd], offset, whence);
     219                 :             : 
     220                 :             :         /* guard against result overflow */
     221         [ +  - ]:           9 :         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                 :          18 :         PG_RETURN_INT32((int32) status);
     228                 :           9 : }
     229                 :             : 
     230                 :             : Datum
     231                 :           4 : be_lo_lseek64(PG_FUNCTION_ARGS)
     232                 :             : {
     233                 :           4 :         int32           fd = PG_GETARG_INT32(0);
     234                 :           4 :         int64           offset = PG_GETARG_INT64(1);
     235                 :           4 :         int32           whence = PG_GETARG_INT32(2);
     236                 :           4 :         int64           status;
     237                 :             : 
     238         [ +  - ]:           4 :         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                 :           4 :         status = inv_seek(cookies[fd], offset, whence);
     244                 :             : 
     245                 :           8 :         PG_RETURN_INT64(status);
     246                 :           4 : }
     247                 :             : 
     248                 :             : Datum
     249                 :           4 : be_lo_creat(PG_FUNCTION_ARGS)
     250                 :             : {
     251                 :           4 :         Oid                     lobjId;
     252                 :             : 
     253                 :           4 :         PreventCommandIfReadOnly("lo_creat()");
     254                 :             : 
     255                 :           4 :         lo_cleanup_needed = true;
     256                 :           4 :         lobjId = inv_create(InvalidOid);
     257                 :             : 
     258                 :           8 :         PG_RETURN_OID(lobjId);
     259                 :           4 : }
     260                 :             : 
     261                 :             : Datum
     262                 :          14 : be_lo_create(PG_FUNCTION_ARGS)
     263                 :             : {
     264                 :          14 :         Oid                     lobjId = PG_GETARG_OID(0);
     265                 :             : 
     266                 :          14 :         PreventCommandIfReadOnly("lo_create()");
     267                 :             : 
     268                 :          14 :         lo_cleanup_needed = true;
     269                 :          14 :         lobjId = inv_create(lobjId);
     270                 :             : 
     271                 :          28 :         PG_RETURN_OID(lobjId);
     272                 :          14 : }
     273                 :             : 
     274                 :             : Datum
     275                 :           4 : be_lo_tell(PG_FUNCTION_ARGS)
     276                 :             : {
     277                 :           4 :         int32           fd = PG_GETARG_INT32(0);
     278                 :           4 :         int64           offset;
     279                 :             : 
     280         [ +  - ]:           4 :         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                 :           4 :         offset = inv_tell(cookies[fd]);
     286                 :             : 
     287                 :             :         /* guard against result overflow */
     288         [ +  - ]:           4 :         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                 :           8 :         PG_RETURN_INT32((int32) offset);
     295                 :           4 : }
     296                 :             : 
     297                 :             : Datum
     298                 :           4 : be_lo_tell64(PG_FUNCTION_ARGS)
     299                 :             : {
     300                 :           4 :         int32           fd = PG_GETARG_INT32(0);
     301                 :           4 :         int64           offset;
     302                 :             : 
     303         [ +  - ]:           4 :         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                 :           4 :         offset = inv_tell(cookies[fd]);
     309                 :             : 
     310                 :           8 :         PG_RETURN_INT64(offset);
     311                 :           4 : }
     312                 :             : 
     313                 :             : Datum
     314                 :          16 : be_lo_unlink(PG_FUNCTION_ARGS)
     315                 :             : {
     316                 :          16 :         Oid                     lobjId = PG_GETARG_OID(0);
     317                 :             : 
     318                 :          16 :         PreventCommandIfReadOnly("lo_unlink()");
     319                 :             : 
     320         [ +  - ]:          16 :         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   [ +  +  +  + ]:          16 :         if (!lo_compat_privileges &&
     331                 :          14 :                 !object_ownercheck(LargeObjectRelationId, lobjId, GetUserId()))
     332   [ +  -  +  - ]:           2 :                 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         [ -  + ]:          14 :         if (fscxt != NULL)
     340                 :             :         {
     341                 :           0 :                 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                 :           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                 :          28 :         PG_RETURN_INT32(inv_drop(lobjId));
     355                 :          14 : }
     356                 :             : 
     357                 :             : /*****************************************************************************
     358                 :             :  *      Read/Write using bytea
     359                 :             :  *****************************************************************************/
     360                 :             : 
     361                 :             : Datum
     362                 :          97 : be_loread(PG_FUNCTION_ARGS)
     363                 :             : {
     364                 :          97 :         int32           fd = PG_GETARG_INT32(0);
     365                 :          97 :         int32           len = PG_GETARG_INT32(1);
     366                 :          97 :         bytea      *retval;
     367                 :          97 :         int                     totalread;
     368                 :             : 
     369         [ +  - ]:          97 :         if (len < 0)
     370                 :           0 :                 len = 0;
     371                 :             : 
     372                 :          97 :         retval = (bytea *) palloc(VARHDRSZ + len);
     373                 :          97 :         totalread = lo_read(fd, VARDATA(retval), len);
     374                 :          97 :         SET_VARSIZE(retval, totalread + VARHDRSZ);
     375                 :             : 
     376                 :         194 :         PG_RETURN_BYTEA_P(retval);
     377                 :          97 : }
     378                 :             : 
     379                 :             : Datum
     380                 :         173 : be_lowrite(PG_FUNCTION_ARGS)
     381                 :             : {
     382                 :         173 :         int32           fd = PG_GETARG_INT32(0);
     383                 :         173 :         bytea      *wbuf = PG_GETARG_BYTEA_PP(1);
     384                 :         173 :         int                     bytestowrite;
     385                 :         173 :         int                     totalwritten;
     386                 :             : 
     387                 :         173 :         PreventCommandIfReadOnly("lowrite()");
     388                 :             : 
     389                 :         173 :         bytestowrite = VARSIZE_ANY_EXHDR(wbuf);
     390                 :         173 :         totalwritten = lo_write(fd, VARDATA_ANY(wbuf), bytestowrite);
     391                 :         346 :         PG_RETURN_INT32(totalwritten);
     392                 :         173 : }
     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                 :           2 : be_lo_import(PG_FUNCTION_ARGS)
     404                 :             : {
     405                 :           2 :         text       *filename = PG_GETARG_TEXT_PP(0);
     406                 :             : 
     407                 :           4 :         PG_RETURN_OID(lo_import_internal(filename, InvalidOid));
     408                 :           2 : }
     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                 :           0 : }
     422                 :             : 
     423                 :             : static Oid
     424                 :           2 : lo_import_internal(text *filename, Oid lobjOid)
     425                 :             : {
     426                 :           2 :         int                     fd;
     427                 :           2 :         int                     nbytes,
     428                 :             :                                 tmp PG_USED_FOR_ASSERTS_ONLY;
     429                 :           2 :         char            buf[BUFSIZE];
     430                 :           2 :         char            fnamebuf[MAXPGPATH];
     431                 :           2 :         LargeObjectDesc *lobj;
     432                 :           2 :         Oid                     oid;
     433                 :             : 
     434                 :           2 :         PreventCommandIfReadOnly("lo_import()");
     435                 :             : 
     436                 :             :         /*
     437                 :             :          * open the file to be read in
     438                 :             :          */
     439                 :           2 :         text_to_cstring_buffer(filename, fnamebuf, sizeof(fnamebuf));
     440                 :           2 :         fd = OpenTransientFile(fnamebuf, O_RDONLY | PG_BINARY);
     441         [ +  - ]:           2 :         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                 :           2 :         lo_cleanup_needed = true;
     451                 :           2 :         oid = inv_create(lobjOid);
     452                 :             : 
     453                 :             :         /*
     454                 :             :          * read in from the filesystem and write to the inversion object
     455                 :             :          */
     456                 :           2 :         lobj = inv_open(oid, INV_WRITE, CurrentMemoryContext);
     457                 :             : 
     458         [ +  + ]:          84 :         while ((nbytes = read(fd, buf, BUFSIZE)) > 0)
     459                 :             :         {
     460                 :          82 :                 tmp = inv_write(lobj, buf, nbytes);
     461         [ +  - ]:          82 :                 Assert(tmp == nbytes);
     462                 :             :         }
     463                 :             : 
     464         [ +  - ]:           2 :         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                 :           2 :         inv_close(lobj);
     471                 :             : 
     472         [ +  - ]:           2 :         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                 :           2 : }
     480                 :             : 
     481                 :             : /*
     482                 :             :  * lo_export -
     483                 :             :  *        exports an (inversion) large object.
     484                 :             :  */
     485                 :             : Datum
     486                 :           2 : be_lo_export(PG_FUNCTION_ARGS)
     487                 :             : {
     488                 :           2 :         Oid                     lobjId = PG_GETARG_OID(0);
     489                 :           2 :         text       *filename = PG_GETARG_TEXT_PP(1);
     490                 :           2 :         int                     fd;
     491                 :           2 :         int                     nbytes,
     492                 :             :                                 tmp;
     493                 :           2 :         char            buf[BUFSIZE];
     494                 :           2 :         char            fnamebuf[MAXPGPATH];
     495                 :           2 :         LargeObjectDesc *lobj;
     496                 :           2 :         mode_t          oumask;
     497                 :             : 
     498                 :             :         /*
     499                 :             :          * open the inversion object (no need to test for failure)
     500                 :             :          */
     501                 :           2 :         lo_cleanup_needed = true;
     502                 :           2 :         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                 :           2 :         text_to_cstring_buffer(filename, fnamebuf, sizeof(fnamebuf));
     512                 :           2 :         oumask = umask(S_IWGRP | S_IWOTH);
     513         [ -  + ]:           2 :         PG_TRY();
     514                 :             :         {
     515                 :           2 :                 fd = OpenTransientFilePerm(fnamebuf, O_CREAT | O_WRONLY | O_TRUNC | PG_BINARY,
     516                 :             :                                                                    S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
     517                 :             :         }
     518                 :           2 :         PG_FINALLY();
     519                 :             :         {
     520                 :           2 :                 umask(oumask);
     521                 :             :         }
     522         [ +  - ]:           2 :         PG_END_TRY();
     523         [ +  + ]:           2 :         if (fd < 0)
     524   [ +  -  +  - ]:           1 :                 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         [ +  + ]:          83 :         while ((nbytes = inv_read(lobj, buf, BUFSIZE)) > 0)
     533                 :             :         {
     534                 :          82 :                 tmp = write(fd, buf, nbytes);
     535         [ +  - ]:          82 :                 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         [ +  - ]:           1 :         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                 :           1 :         inv_close(lobj);
     549                 :             : 
     550                 :           2 :         PG_RETURN_INT32(1);
     551                 :           1 : }
     552                 :             : 
     553                 :             : /*
     554                 :             :  * lo_truncate -
     555                 :             :  *        truncate a large object to a specified length
     556                 :             :  */
     557                 :             : static void
     558                 :           7 : lo_truncate_internal(int32 fd, int64 len)
     559                 :             : {
     560                 :           7 :         LargeObjectDesc *lobj;
     561                 :             : 
     562         [ +  - ]:           7 :         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                 :           7 :         lobj = cookies[fd];
     567                 :             : 
     568                 :             :         /* see comment in lo_read() */
     569         [ +  - ]:           7 :         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                 :           7 :         inv_truncate(lobj, len);
     576                 :           7 : }
     577                 :             : 
     578                 :             : Datum
     579                 :           6 : be_lo_truncate(PG_FUNCTION_ARGS)
     580                 :             : {
     581                 :           6 :         int32           fd = PG_GETARG_INT32(0);
     582                 :           6 :         int32           len = PG_GETARG_INT32(1);
     583                 :             : 
     584                 :           6 :         PreventCommandIfReadOnly("lo_truncate()");
     585                 :             : 
     586                 :           6 :         lo_truncate_internal(fd, len);
     587                 :          12 :         PG_RETURN_INT32(0);
     588                 :           6 : }
     589                 :             : 
     590                 :             : Datum
     591                 :           3 : be_lo_truncate64(PG_FUNCTION_ARGS)
     592                 :             : {
     593                 :           3 :         int32           fd = PG_GETARG_INT32(0);
     594                 :           3 :         int64           len = PG_GETARG_INT64(1);
     595                 :             : 
     596                 :           3 :         PreventCommandIfReadOnly("lo_truncate64()");
     597                 :             : 
     598                 :           3 :         lo_truncate_internal(fd, len);
     599                 :           6 :         PG_RETURN_INT32(0);
     600                 :           3 : }
     601                 :             : 
     602                 :             : /*
     603                 :             :  * AtEOXact_LargeObject -
     604                 :             :  *               prepares large objects for transaction commit
     605                 :             :  */
     606                 :             : void
     607                 :       57944 : AtEOXact_LargeObject(bool isCommit)
     608                 :             : {
     609                 :       57944 :         int                     i;
     610                 :             : 
     611         [ +  + ]:       57944 :         if (!lo_cleanup_needed)
     612                 :       57886 :                 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         [ +  + ]:          58 :         if (isCommit)
     622                 :             :         {
     623         [ +  + ]:        1324 :                 for (i = 0; i < cookies_size; i++)
     624                 :             :                 {
     625         [ +  + ]:        1280 :                         if (cookies[i] != NULL)
     626                 :          12 :                                 closeLOfd(i);
     627                 :        1280 :                 }
     628                 :          44 :         }
     629                 :             : 
     630                 :             :         /* Needn't actually pfree since we're about to zap context */
     631                 :          58 :         cookies = NULL;
     632                 :          58 :         cookies_size = 0;
     633                 :             : 
     634                 :             :         /* Release the LO memory context to prevent permanent memory leaks. */
     635         [ +  + ]:          58 :         if (fscxt)
     636                 :          31 :                 MemoryContextDelete(fscxt);
     637                 :          58 :         fscxt = NULL;
     638                 :             : 
     639                 :             :         /* Give inv_api.c a chance to clean up, too */
     640                 :          58 :         close_lo_relation(isCommit);
     641                 :             : 
     642                 :          58 :         lo_cleanup_needed = false;
     643         [ -  + ]:       57944 : }
     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                 :        1665 : AtEOSubXact_LargeObject(bool isCommit, SubTransactionId mySubid,
     654                 :             :                                                 SubTransactionId parentSubid)
     655                 :             : {
     656                 :        1665 :         int                     i;
     657                 :             : 
     658         [ -  + ]:        1665 :         if (fscxt == NULL)                      /* no LO operations in this xact */
     659                 :        1665 :                 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                 :           0 :                 }
     672                 :           0 :         }
     673         [ -  + ]:        1665 : }
     674                 :             : 
     675                 :             : /*****************************************************************************
     676                 :             :  *      Support routines for this file
     677                 :             :  *****************************************************************************/
     678                 :             : 
     679                 :             : static int
     680                 :          31 : newLOfd(void)
     681                 :             : {
     682                 :          31 :         int                     i,
     683                 :             :                                 newsize;
     684                 :             : 
     685                 :          31 :         lo_cleanup_needed = true;
     686         [ -  + ]:          31 :         if (fscxt == NULL)
     687                 :          31 :                 fscxt = AllocSetContextCreate(TopMemoryContext,
     688                 :             :                                                                           "Filesystem",
     689                 :             :                                                                           ALLOCSET_DEFAULT_SIZES);
     690                 :             : 
     691                 :             :         /* Try to find a free slot */
     692         [ -  + ]:          31 :         for (i = 0; i < cookies_size; i++)
     693                 :             :         {
     694         [ #  # ]:           0 :                 if (cookies[i] == NULL)
     695                 :           0 :                         return i;
     696                 :           0 :         }
     697                 :             : 
     698                 :             :         /* No free slot, so make the array bigger */
     699         [ +  - ]:          31 :         if (cookies_size <= 0)
     700                 :             :         {
     701                 :             :                 /* First time through, arbitrarily make 64-element array */
     702                 :          31 :                 i = 0;
     703                 :          31 :                 newsize = 64;
     704                 :          31 :                 cookies = (LargeObjectDesc **)
     705                 :          31 :                         MemoryContextAllocZero(fscxt, newsize * sizeof(LargeObjectDesc *));
     706                 :          31 :         }
     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                 :          31 :         cookies_size = newsize;
     716                 :             : 
     717                 :          31 :         return i;
     718                 :          31 : }
     719                 :             : 
     720                 :             : static void
     721                 :          20 : closeLOfd(int fd)
     722                 :             : {
     723                 :          20 :         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                 :          20 :         lobj = cookies[fd];
     730                 :          20 :         cookies[fd] = NULL;
     731                 :             : 
     732         [ +  + ]:          20 :         if (lobj->snapshot)
     733                 :          14 :                 UnregisterSnapshotFromOwner(lobj->snapshot,
     734                 :           7 :                                                                         TopTransactionResourceOwner);
     735                 :          20 :         inv_close(lobj);
     736                 :          20 : }
     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                 :          10 : lo_get_fragment_internal(Oid loOid, int64 offset, int32 nbytes)
     747                 :             : {
     748                 :          10 :         LargeObjectDesc *loDesc;
     749                 :          10 :         int64           loSize;
     750                 :          10 :         int64           result_length;
     751                 :          10 :         int                     total_read PG_USED_FOR_ASSERTS_ONLY;
     752                 :          10 :         bytea      *result = NULL;
     753                 :             : 
     754                 :          10 :         lo_cleanup_needed = true;
     755                 :          10 :         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                 :          10 :         loSize = inv_seek(loDesc, 0, SEEK_END);
     762         [ +  - ]:          10 :         if (loSize > offset)
     763                 :             :         {
     764   [ +  +  +  + ]:          10 :                 if (nbytes >= 0 && nbytes <= loSize - offset)
     765                 :           3 :                         result_length = nbytes; /* request is wholly inside LO */
     766                 :             :                 else
     767                 :           7 :                         result_length = loSize - offset;        /* adjust to end of LO */
     768                 :          10 :         }
     769                 :             :         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         [ +  + ]:          10 :         if (result_length > MaxAllocSize - VARHDRSZ)
     777   [ +  -  +  - ]:           1 :                 ereport(ERROR,
     778                 :             :                                 (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
     779                 :             :                                  errmsg("large object read request is too large")));
     780                 :             : 
     781                 :           9 :         result = (bytea *) palloc(VARHDRSZ + result_length);
     782                 :             : 
     783                 :           9 :         inv_seek(loDesc, offset, SEEK_SET);
     784                 :           9 :         total_read = inv_read(loDesc, VARDATA(result), result_length);
     785         [ +  - ]:           9 :         Assert(total_read == result_length);
     786                 :           9 :         SET_VARSIZE(result, result_length + VARHDRSZ);
     787                 :             : 
     788                 :           9 :         inv_close(loDesc);
     789                 :             : 
     790                 :          18 :         return result;
     791                 :           9 : }
     792                 :             : 
     793                 :             : /*
     794                 :             :  * Read entire LO
     795                 :             :  */
     796                 :             : Datum
     797                 :           6 : be_lo_get(PG_FUNCTION_ARGS)
     798                 :             : {
     799                 :           6 :         Oid                     loOid = PG_GETARG_OID(0);
     800                 :           6 :         bytea      *result;
     801                 :             : 
     802                 :           6 :         result = lo_get_fragment_internal(loOid, 0, -1);
     803                 :             : 
     804                 :          12 :         PG_RETURN_BYTEA_P(result);
     805                 :           6 : }
     806                 :             : 
     807                 :             : /*
     808                 :             :  * Read range within LO
     809                 :             :  */
     810                 :             : Datum
     811                 :           4 : be_lo_get_fragment(PG_FUNCTION_ARGS)
     812                 :             : {
     813                 :           4 :         Oid                     loOid = PG_GETARG_OID(0);
     814                 :           4 :         int64           offset = PG_GETARG_INT64(1);
     815                 :           4 :         int32           nbytes = PG_GETARG_INT32(2);
     816                 :           4 :         bytea      *result;
     817                 :             : 
     818         [ +  - ]:           4 :         if (nbytes < 0)
     819   [ #  #  #  # ]:           0 :                 ereport(ERROR,
     820                 :             :                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     821                 :             :                                  errmsg("requested length cannot be negative")));
     822                 :             : 
     823                 :           4 :         result = lo_get_fragment_internal(loOid, offset, nbytes);
     824                 :             : 
     825                 :           8 :         PG_RETURN_BYTEA_P(result);
     826                 :           4 : }
     827                 :             : 
     828                 :             : /*
     829                 :             :  * Create LO with initial contents given by a bytea argument
     830                 :             :  */
     831                 :             : Datum
     832                 :           3 : be_lo_from_bytea(PG_FUNCTION_ARGS)
     833                 :             : {
     834                 :           3 :         Oid                     loOid = PG_GETARG_OID(0);
     835                 :           3 :         bytea      *str = PG_GETARG_BYTEA_PP(1);
     836                 :           3 :         LargeObjectDesc *loDesc;
     837                 :           3 :         int                     written PG_USED_FOR_ASSERTS_ONLY;
     838                 :             : 
     839                 :           3 :         PreventCommandIfReadOnly("lo_from_bytea()");
     840                 :             : 
     841                 :           3 :         lo_cleanup_needed = true;
     842                 :           3 :         loOid = inv_create(loOid);
     843                 :           3 :         loDesc = inv_open(loOid, INV_WRITE, CurrentMemoryContext);
     844                 :           3 :         written = inv_write(loDesc, VARDATA_ANY(str), VARSIZE_ANY_EXHDR(str));
     845         [ +  - ]:           3 :         Assert(written == VARSIZE_ANY_EXHDR(str));
     846                 :           3 :         inv_close(loDesc);
     847                 :             : 
     848                 :           6 :         PG_RETURN_OID(loOid);
     849                 :           3 : }
     850                 :             : 
     851                 :             : /*
     852                 :             :  * Update range within LO
     853                 :             :  */
     854                 :             : Datum
     855                 :           2 : be_lo_put(PG_FUNCTION_ARGS)
     856                 :             : {
     857                 :           2 :         Oid                     loOid = PG_GETARG_OID(0);
     858                 :           2 :         int64           offset = PG_GETARG_INT64(1);
     859                 :           2 :         bytea      *str = PG_GETARG_BYTEA_PP(2);
     860                 :           2 :         LargeObjectDesc *loDesc;
     861                 :           2 :         int                     written PG_USED_FOR_ASSERTS_ONLY;
     862                 :             : 
     863                 :           2 :         PreventCommandIfReadOnly("lo_put()");
     864                 :             : 
     865                 :           2 :         lo_cleanup_needed = true;
     866                 :           2 :         loDesc = inv_open(loOid, INV_WRITE, CurrentMemoryContext);
     867                 :           2 :         inv_seek(loDesc, offset, SEEK_SET);
     868                 :           2 :         written = inv_write(loDesc, VARDATA_ANY(str), VARSIZE_ANY_EXHDR(str));
     869         [ +  - ]:           2 :         Assert(written == VARSIZE_ANY_EXHDR(str));
     870                 :           2 :         inv_close(loDesc);
     871                 :             : 
     872                 :           2 :         PG_RETURN_VOID();
     873                 :           2 : }
        

Generated by: LCOV version 2.3.2-1