Coverage Report

Created: 2025-06-15 06:31

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