| /*------------------------------------------------------------------------- |
| * |
| * fe-lobj.c |
| * Front-end large object interface |
| * |
| * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * |
| * IDENTIFICATION |
| * src/interfaces/libpq/fe-lobj.c |
| * |
| *------------------------------------------------------------------------- |
| */ |
| |
| #ifdef WIN32 |
| /* |
| * As unlink/rename are #define'd in port.h (via postgres_fe.h), io.h |
| * must be included first on MS C. Might as well do it for all WIN32's |
| * here. |
| */ |
| #include <io.h> |
| #endif |
| |
| #include "postgres_fe.h" |
| |
| #ifdef WIN32 |
| #include "win32.h" |
| #else |
| #include <unistd.h> |
| #endif |
| |
| #include <fcntl.h> |
| #include <limits.h> |
| #include <sys/stat.h> |
| |
| #include "libpq-fe.h" |
| #include "libpq-int.h" |
| #include "libpq/libpq-fs.h" /* must come after sys/stat.h */ |
| #include "port/pg_bswap.h" |
| |
| #define LO_BUFSIZE 8192 |
| |
| static int lo_initialize(PGconn *conn); |
| static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); |
| static pg_int64 lo_hton64(pg_int64 host64); |
| static pg_int64 lo_ntoh64(pg_int64 net64); |
| |
| /* |
| * lo_open |
| * opens an existing large object |
| * |
| * returns the file descriptor for use in later lo_* calls |
| * return -1 upon failure. |
| */ |
| int |
| lo_open(PGconn *conn, Oid lobjId, int mode) |
| { |
| int fd; |
| int result_len; |
| PQArgBlock argv[2]; |
| PGresult *res; |
| |
| if (lo_initialize(conn) < 0) |
| return -1; |
| |
| argv[0].isint = 1; |
| argv[0].len = 4; |
| argv[0].u.integer = lobjId; |
| |
| argv[1].isint = 1; |
| argv[1].len = 4; |
| argv[1].u.integer = mode; |
| |
| res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); |
| if (PQresultStatus(res) == PGRES_COMMAND_OK) |
| { |
| PQclear(res); |
| return fd; |
| } |
| else |
| { |
| PQclear(res); |
| return -1; |
| } |
| } |
| |
| /* |
| * lo_close |
| * closes an existing large object |
| * |
| * returns 0 upon success |
| * returns -1 upon failure. |
| */ |
| int |
| lo_close(PGconn *conn, int fd) |
| { |
| PQArgBlock argv[1]; |
| PGresult *res; |
| int retval; |
| int result_len; |
| |
| if (lo_initialize(conn) < 0) |
| return -1; |
| |
| argv[0].isint = 1; |
| argv[0].len = 4; |
| argv[0].u.integer = fd; |
| res = PQfn(conn, conn->lobjfuncs->fn_lo_close, |
| &retval, &result_len, 1, argv, 1); |
| if (PQresultStatus(res) == PGRES_COMMAND_OK) |
| { |
| PQclear(res); |
| return retval; |
| } |
| else |
| { |
| PQclear(res); |
| return -1; |
| } |
| } |
| |
| /* |
| * lo_truncate |
| * truncates an existing large object to the given size |
| * |
| * returns 0 upon success |
| * returns -1 upon failure |
| */ |
| int |
| lo_truncate(PGconn *conn, int fd, size_t len) |
| { |
| PQArgBlock argv[2]; |
| PGresult *res; |
| int retval; |
| int result_len; |
| |
| if (lo_initialize(conn) < 0) |
| return -1; |
| |
| /* Must check this on-the-fly because it's not there pre-8.3 */ |
| if (conn->lobjfuncs->fn_lo_truncate == 0) |
| { |
| libpq_append_conn_error(conn, "cannot determine OID of function %s", |
| "lo_truncate"); |
| return -1; |
| } |
| |
| /* |
| * Long ago, somebody thought it'd be a good idea to declare this function |
| * as taking size_t ... but the underlying backend function only accepts a |
| * signed int32 length. So throw error if the given value overflows |
| * int32. (A possible alternative is to automatically redirect the call |
| * to lo_truncate64; but if the caller wanted to rely on that backend |
| * function being available, he could have called lo_truncate64 for |
| * himself.) |
| */ |
| if (len > (size_t) INT_MAX) |
| { |
| libpq_append_conn_error(conn, "argument of lo_truncate exceeds integer range"); |
| return -1; |
| } |
| |
| argv[0].isint = 1; |
| argv[0].len = 4; |
| argv[0].u.integer = fd; |
| |
| argv[1].isint = 1; |
| argv[1].len = 4; |
| argv[1].u.integer = (int) len; |
| |
| res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, |
| &retval, &result_len, 1, argv, 2); |
| |
| if (PQresultStatus(res) == PGRES_COMMAND_OK) |
| { |
| PQclear(res); |
| return retval; |
| } |
| else |
| { |
| PQclear(res); |
| return -1; |
| } |
| } |
| |
| /* |
| * lo_truncate64 |
| * truncates an existing large object to the given size |
| * |
| * returns 0 upon success |
| * returns -1 upon failure |
| */ |
| int |
| lo_truncate64(PGconn *conn, int fd, pg_int64 len) |
| { |
| PQArgBlock argv[2]; |
| PGresult *res; |
| int retval; |
| int result_len; |
| |
| if (lo_initialize(conn) < 0) |
| return -1; |
| |
| if (conn->lobjfuncs->fn_lo_truncate64 == 0) |
| { |
| libpq_append_conn_error(conn, "cannot determine OID of function %s", |
| "lo_truncate64"); |
| return -1; |
| } |
| |
| argv[0].isint = 1; |
| argv[0].len = 4; |
| argv[0].u.integer = fd; |
| |
| len = lo_hton64(len); |
| argv[1].isint = 0; |
| argv[1].len = 8; |
| argv[1].u.ptr = (int *) &len; |
| |
| res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, |
| &retval, &result_len, 1, argv, 2); |
| |
| if (PQresultStatus(res) == PGRES_COMMAND_OK) |
| { |
| PQclear(res); |
| return retval; |
| } |
| else |
| { |
| PQclear(res); |
| return -1; |
| } |
| } |
| |
| /* |
| * lo_read |
| * read len bytes of the large object into buf |
| * |
| * returns the number of bytes read, or -1 on failure. |
| * the CALLER must have allocated enough space to hold the result returned |
| */ |
| |
| int |
| lo_read(PGconn *conn, int fd, char *buf, size_t len) |
| { |
| PQArgBlock argv[2]; |
| PGresult *res; |
| int result_len; |
| |
| if (lo_initialize(conn) < 0) |
| return -1; |
| |
| /* |
| * Long ago, somebody thought it'd be a good idea to declare this function |
| * as taking size_t ... but the underlying backend function only accepts a |
| * signed int32 length. So throw error if the given value overflows |
| * int32. |
| */ |
| if (len > (size_t) INT_MAX) |
| { |
| libpq_append_conn_error(conn, "argument of lo_read exceeds integer range"); |
| return -1; |
| } |
| |
| argv[0].isint = 1; |
| argv[0].len = 4; |
| argv[0].u.integer = fd; |
| |
| argv[1].isint = 1; |
| argv[1].len = 4; |
| argv[1].u.integer = (int) len; |
| |
| res = PQfn(conn, conn->lobjfuncs->fn_lo_read, |
| (void *) buf, &result_len, 0, argv, 2); |
| if (PQresultStatus(res) == PGRES_COMMAND_OK) |
| { |
| PQclear(res); |
| return result_len; |
| } |
| else |
| { |
| PQclear(res); |
| return -1; |
| } |
| } |
| |
| /* |
| * lo_write |
| * write len bytes of buf into the large object fd |
| * |
| * returns the number of bytes written, or -1 on failure. |
| */ |
| int |
| lo_write(PGconn *conn, int fd, const char *buf, size_t len) |
| { |
| PQArgBlock argv[2]; |
| PGresult *res; |
| int result_len; |
| int retval; |
| |
| if (lo_initialize(conn) < 0) |
| return -1; |
| |
| /* |
| * Long ago, somebody thought it'd be a good idea to declare this function |
| * as taking size_t ... but the underlying backend function only accepts a |
| * signed int32 length. So throw error if the given value overflows |
| * int32. |
| */ |
| if (len > (size_t) INT_MAX) |
| { |
| libpq_append_conn_error(conn, "argument of lo_write exceeds integer range"); |
| return -1; |
| } |
| |
| argv[0].isint = 1; |
| argv[0].len = 4; |
| argv[0].u.integer = fd; |
| |
| argv[1].isint = 0; |
| argv[1].len = (int) len; |
| argv[1].u.ptr = (int *) unconstify(char *, buf); |
| |
| res = PQfn(conn, conn->lobjfuncs->fn_lo_write, |
| &retval, &result_len, 1, argv, 2); |
| if (PQresultStatus(res) == PGRES_COMMAND_OK) |
| { |
| PQclear(res); |
| return retval; |
| } |
| else |
| { |
| PQclear(res); |
| return -1; |
| } |
| } |
| |
| /* |
| * lo_lseek |
| * change the current read or write location on a large object |
| */ |
| int |
| lo_lseek(PGconn *conn, int fd, int offset, int whence) |
| { |
| PQArgBlock argv[3]; |
| PGresult *res; |
| int retval; |
| int result_len; |
| |
| if (lo_initialize(conn) < 0) |
| return -1; |
| |
| argv[0].isint = 1; |
| argv[0].len = 4; |
| argv[0].u.integer = fd; |
| |
| argv[1].isint = 1; |
| argv[1].len = 4; |
| argv[1].u.integer = offset; |
| |
| argv[2].isint = 1; |
| argv[2].len = 4; |
| argv[2].u.integer = whence; |
| |
| res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, |
| &retval, &result_len, 1, argv, 3); |
| if (PQresultStatus(res) == PGRES_COMMAND_OK) |
| { |
| PQclear(res); |
| return retval; |
| } |
| else |
| { |
| PQclear(res); |
| return -1; |
| } |
| } |
| |
| /* |
| * lo_lseek64 |
| * change the current read or write location on a large object |
| */ |
| pg_int64 |
| lo_lseek64(PGconn *conn, int fd, pg_int64 offset, int whence) |
| { |
| PQArgBlock argv[3]; |
| PGresult *res; |
| pg_int64 retval; |
| int result_len; |
| |
| if (lo_initialize(conn) < 0) |
| return -1; |
| |
| if (conn->lobjfuncs->fn_lo_lseek64 == 0) |
| { |
| libpq_append_conn_error(conn, "cannot determine OID of function %s", |
| "lo_lseek64"); |
| return -1; |
| } |
| |
| argv[0].isint = 1; |
| argv[0].len = 4; |
| argv[0].u.integer = fd; |
| |
| offset = lo_hton64(offset); |
| argv[1].isint = 0; |
| argv[1].len = 8; |
| argv[1].u.ptr = (int *) &offset; |
| |
| argv[2].isint = 1; |
| argv[2].len = 4; |
| argv[2].u.integer = whence; |
| |
| res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek64, |
| (void *) &retval, &result_len, 0, argv, 3); |
| if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) |
| { |
| PQclear(res); |
| return lo_ntoh64(retval); |
| } |
| else |
| { |
| PQclear(res); |
| return -1; |
| } |
| } |
| |
| /* |
| * lo_creat |
| * create a new large object |
| * the mode is ignored (once upon a time it had a use) |
| * |
| * returns the oid of the large object created or |
| * InvalidOid upon failure |
| */ |
| Oid |
| lo_creat(PGconn *conn, int mode) |
| { |
| PQArgBlock argv[1]; |
| PGresult *res; |
| int retval; |
| int result_len; |
| |
| if (lo_initialize(conn) < 0) |
| return InvalidOid; |
| |
| argv[0].isint = 1; |
| argv[0].len = 4; |
| argv[0].u.integer = mode; |
| res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, |
| &retval, &result_len, 1, argv, 1); |
| if (PQresultStatus(res) == PGRES_COMMAND_OK) |
| { |
| PQclear(res); |
| return (Oid) retval; |
| } |
| else |
| { |
| PQclear(res); |
| return InvalidOid; |
| } |
| } |
| |
| /* |
| * lo_create |
| * create a new large object |
| * if lobjId isn't InvalidOid, it specifies the OID to (attempt to) create |
| * |
| * returns the oid of the large object created or |
| * InvalidOid upon failure |
| */ |
| Oid |
| lo_create(PGconn *conn, Oid lobjId) |
| { |
| PQArgBlock argv[1]; |
| PGresult *res; |
| int retval; |
| int result_len; |
| |
| if (lo_initialize(conn) < 0) |
| return InvalidOid; |
| |
| /* Must check this on-the-fly because it's not there pre-8.1 */ |
| if (conn->lobjfuncs->fn_lo_create == 0) |
| { |
| libpq_append_conn_error(conn, "cannot determine OID of function %s", |
| "lo_create"); |
| return InvalidOid; |
| } |
| |
| argv[0].isint = 1; |
| argv[0].len = 4; |
| argv[0].u.integer = lobjId; |
| res = PQfn(conn, conn->lobjfuncs->fn_lo_create, |
| &retval, &result_len, 1, argv, 1); |
| if (PQresultStatus(res) == PGRES_COMMAND_OK) |
| { |
| PQclear(res); |
| return (Oid) retval; |
| } |
| else |
| { |
| PQclear(res); |
| return InvalidOid; |
| } |
| } |
| |
| |
| /* |
| * lo_tell |
| * returns the current seek location of the large object |
| */ |
| int |
| lo_tell(PGconn *conn, int fd) |
| { |
| int retval; |
| PQArgBlock argv[1]; |
| PGresult *res; |
| int result_len; |
| |
| if (lo_initialize(conn) < 0) |
| return -1; |
| |
| argv[0].isint = 1; |
| argv[0].len = 4; |
| argv[0].u.integer = fd; |
| |
| res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, |
| &retval, &result_len, 1, argv, 1); |
| if (PQresultStatus(res) == PGRES_COMMAND_OK) |
| { |
| PQclear(res); |
| return retval; |
| } |
| else |
| { |
| PQclear(res); |
| return -1; |
| } |
| } |
| |
| /* |
| * lo_tell64 |
| * returns the current seek location of the large object |
| */ |
| pg_int64 |
| lo_tell64(PGconn *conn, int fd) |
| { |
| pg_int64 retval; |
| PQArgBlock argv[1]; |
| PGresult *res; |
| int result_len; |
| |
| if (lo_initialize(conn) < 0) |
| return -1; |
| |
| if (conn->lobjfuncs->fn_lo_tell64 == 0) |
| { |
| libpq_append_conn_error(conn, "cannot determine OID of function %s", |
| "lo_tell64"); |
| return -1; |
| } |
| |
| argv[0].isint = 1; |
| argv[0].len = 4; |
| argv[0].u.integer = fd; |
| |
| res = PQfn(conn, conn->lobjfuncs->fn_lo_tell64, |
| (void *) &retval, &result_len, 0, argv, 1); |
| if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) |
| { |
| PQclear(res); |
| return lo_ntoh64(retval); |
| } |
| else |
| { |
| PQclear(res); |
| return -1; |
| } |
| } |
| |
| /* |
| * lo_unlink |
| * delete a file |
| */ |
| |
| int |
| lo_unlink(PGconn *conn, Oid lobjId) |
| { |
| PQArgBlock argv[1]; |
| PGresult *res; |
| int result_len; |
| int retval; |
| |
| if (lo_initialize(conn) < 0) |
| return -1; |
| |
| argv[0].isint = 1; |
| argv[0].len = 4; |
| argv[0].u.integer = lobjId; |
| |
| res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, |
| &retval, &result_len, 1, argv, 1); |
| if (PQresultStatus(res) == PGRES_COMMAND_OK) |
| { |
| PQclear(res); |
| return retval; |
| } |
| else |
| { |
| PQclear(res); |
| return -1; |
| } |
| } |
| |
| /* |
| * lo_import - |
| * imports a file as an (inversion) large object. |
| * |
| * returns the oid of that object upon success, |
| * returns InvalidOid upon failure |
| */ |
| |
| Oid |
| lo_import(PGconn *conn, const char *filename) |
| { |
| return lo_import_internal(conn, filename, InvalidOid); |
| } |
| |
| /* |
| * lo_import_with_oid - |
| * imports a file as an (inversion) large object. |
| * large object id can be specified. |
| * |
| * returns the oid of that object upon success, |
| * returns InvalidOid upon failure |
| */ |
| |
| Oid |
| lo_import_with_oid(PGconn *conn, const char *filename, Oid lobjId) |
| { |
| return lo_import_internal(conn, filename, lobjId); |
| } |
| |
| static Oid |
| lo_import_internal(PGconn *conn, const char *filename, Oid oid) |
| { |
| int fd; |
| int nbytes, |
| tmp; |
| char buf[LO_BUFSIZE]; |
| Oid lobjOid; |
| int lobj; |
| char sebuf[PG_STRERROR_R_BUFLEN]; |
| |
| if (conn == NULL) |
| return InvalidOid; |
| |
| /* Since this is the beginning of a query cycle, reset the error state */ |
| pqClearConnErrorState(conn); |
| |
| /* |
| * open the file to be read in |
| */ |
| fd = open(filename, O_RDONLY | PG_BINARY, 0666); |
| if (fd < 0) |
| { /* error */ |
| libpq_append_conn_error(conn, "could not open file \"%s\": %s", |
| filename, strerror_r(errno, sebuf, sizeof(sebuf))); |
| return InvalidOid; |
| } |
| |
| /* |
| * create an inversion object |
| */ |
| if (oid == InvalidOid) |
| lobjOid = lo_creat(conn, INV_READ | INV_WRITE); |
| else |
| lobjOid = lo_create(conn, oid); |
| |
| if (lobjOid == InvalidOid) |
| { |
| /* we assume lo_create() already set a suitable error message */ |
| (void) close(fd); |
| return InvalidOid; |
| } |
| |
| lobj = lo_open(conn, lobjOid, INV_WRITE); |
| if (lobj == -1) |
| { |
| /* we assume lo_open() already set a suitable error message */ |
| (void) close(fd); |
| return InvalidOid; |
| } |
| |
| /* |
| * read in from the file and write to the large object |
| */ |
| while ((nbytes = read(fd, buf, LO_BUFSIZE)) > 0) |
| { |
| tmp = lo_write(conn, lobj, buf, nbytes); |
| if (tmp != nbytes) |
| { |
| /* |
| * If lo_write() failed, we are now in an aborted transaction so |
| * there's no need for lo_close(); furthermore, if we tried it |
| * we'd overwrite the useful error result with a useless one. So |
| * just nail the doors shut and get out of town. |
| */ |
| (void) close(fd); |
| return InvalidOid; |
| } |
| } |
| |
| if (nbytes < 0) |
| { |
| /* We must do lo_close before setting the errorMessage */ |
| int save_errno = errno; |
| |
| (void) lo_close(conn, lobj); |
| (void) close(fd); |
| /* deliberately overwrite any error from lo_close */ |
| pqClearConnErrorState(conn); |
| libpq_append_conn_error(conn, "could not read from file \"%s\": %s", |
| filename, |
| strerror_r(save_errno, sebuf, sizeof(sebuf))); |
| return InvalidOid; |
| } |
| |
| (void) close(fd); |
| |
| if (lo_close(conn, lobj) != 0) |
| { |
| /* we assume lo_close() already set a suitable error message */ |
| return InvalidOid; |
| } |
| |
| return lobjOid; |
| } |
| |
| /* |
| * lo_export - |
| * exports an (inversion) large object. |
| * returns -1 upon failure, 1 if OK |
| */ |
| int |
| lo_export(PGconn *conn, Oid lobjId, const char *filename) |
| { |
| int result = 1; |
| int fd; |
| int nbytes, |
| tmp; |
| char buf[LO_BUFSIZE]; |
| int lobj; |
| char sebuf[PG_STRERROR_R_BUFLEN]; |
| |
| /* |
| * open the large object. |
| */ |
| lobj = lo_open(conn, lobjId, INV_READ); |
| if (lobj == -1) |
| { |
| /* we assume lo_open() already set a suitable error message */ |
| return -1; |
| } |
| |
| /* |
| * create the file to be written to |
| */ |
| fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC | PG_BINARY, 0666); |
| if (fd < 0) |
| { |
| /* We must do lo_close before setting the errorMessage */ |
| int save_errno = errno; |
| |
| (void) lo_close(conn, lobj); |
| /* deliberately overwrite any error from lo_close */ |
| pqClearConnErrorState(conn); |
| libpq_append_conn_error(conn, "could not open file \"%s\": %s", |
| filename, |
| strerror_r(save_errno, sebuf, sizeof(sebuf))); |
| return -1; |
| } |
| |
| /* |
| * read in from the large object and write to the file |
| */ |
| while ((nbytes = lo_read(conn, lobj, buf, LO_BUFSIZE)) > 0) |
| { |
| tmp = write(fd, buf, nbytes); |
| if (tmp != nbytes) |
| { |
| /* We must do lo_close before setting the errorMessage */ |
| int save_errno = errno; |
| |
| (void) lo_close(conn, lobj); |
| (void) close(fd); |
| /* deliberately overwrite any error from lo_close */ |
| pqClearConnErrorState(conn); |
| libpq_append_conn_error(conn, "could not write to file \"%s\": %s", |
| filename, |
| strerror_r(save_errno, sebuf, sizeof(sebuf))); |
| return -1; |
| } |
| } |
| |
| /* |
| * If lo_read() failed, we are now in an aborted transaction so there's no |
| * need for lo_close(); furthermore, if we tried it we'd overwrite the |
| * useful error result with a useless one. So skip lo_close() if we got a |
| * failure result. |
| */ |
| if (nbytes < 0 || |
| lo_close(conn, lobj) != 0) |
| { |
| /* assume lo_read() or lo_close() left a suitable error message */ |
| result = -1; |
| } |
| |
| /* if we already failed, don't overwrite that msg with a close error */ |
| if (close(fd) != 0 && result >= 0) |
| { |
| libpq_append_conn_error(conn, "could not write to file \"%s\": %s", |
| filename, strerror_r(errno, sebuf, sizeof(sebuf))); |
| result = -1; |
| } |
| |
| return result; |
| } |
| |
| |
| /* |
| * lo_initialize |
| * |
| * Initialize for a new large-object operation on an existing connection. |
| * Return 0 if OK, -1 on failure. |
| * |
| * If we haven't previously done so, we collect the function OIDs from |
| * pg_proc for all functions that are required for large object operations. |
| */ |
| static int |
| lo_initialize(PGconn *conn) |
| { |
| PGresult *res; |
| PGlobjfuncs *lobjfuncs; |
| int n; |
| const char *query; |
| const char *fname; |
| Oid foid; |
| |
| /* Nothing we can do with no connection */ |
| if (conn == NULL) |
| return -1; |
| |
| /* Since this is the beginning of a query cycle, reset the error state */ |
| pqClearConnErrorState(conn); |
| |
| /* Nothing else to do if we already collected info */ |
| if (conn->lobjfuncs != NULL) |
| return 0; |
| |
| /* |
| * Allocate the structure to hold the function OIDs. We don't store it |
| * into the PGconn until it's successfully filled. |
| */ |
| lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); |
| if (lobjfuncs == NULL) |
| { |
| libpq_append_conn_error(conn, "out of memory"); |
| return -1; |
| } |
| MemSet((char *) lobjfuncs, 0, sizeof(PGlobjfuncs)); |
| |
| /* |
| * Execute the query to get all the functions at once. (Not all of them |
| * may exist in older server versions.) |
| */ |
| query = "select proname, oid from pg_catalog.pg_proc " |
| "where proname in (" |
| "'lo_open', " |
| "'lo_close', " |
| "'lo_creat', " |
| "'lo_create', " |
| "'lo_unlink', " |
| "'lo_lseek', " |
| "'lo_lseek64', " |
| "'lo_tell', " |
| "'lo_tell64', " |
| "'lo_truncate', " |
| "'lo_truncate64', " |
| "'loread', " |
| "'lowrite') " |
| "and pronamespace = (select oid from pg_catalog.pg_namespace " |
| "where nspname = 'pg_catalog')"; |
| |
| res = PQexec(conn, query); |
| if (res == NULL) |
| { |
| free(lobjfuncs); |
| return -1; |
| } |
| |
| if (res->resultStatus != PGRES_TUPLES_OK) |
| { |
| free(lobjfuncs); |
| PQclear(res); |
| libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); |
| return -1; |
| } |
| |
| /* |
| * Examine the result and put the OID's into the struct |
| */ |
| for (n = 0; n < PQntuples(res); n++) |
| { |
| fname = PQgetvalue(res, n, 0); |
| foid = (Oid) atoi(PQgetvalue(res, n, 1)); |
| if (strcmp(fname, "lo_open") == 0) |
| lobjfuncs->fn_lo_open = foid; |
| else if (strcmp(fname, "lo_close") == 0) |
| lobjfuncs->fn_lo_close = foid; |
| else if (strcmp(fname, "lo_creat") == 0) |
| lobjfuncs->fn_lo_creat = foid; |
| else if (strcmp(fname, "lo_create") == 0) |
| lobjfuncs->fn_lo_create = foid; |
| else if (strcmp(fname, "lo_unlink") == 0) |
| lobjfuncs->fn_lo_unlink = foid; |
| else if (strcmp(fname, "lo_lseek") == 0) |
| lobjfuncs->fn_lo_lseek = foid; |
| else if (strcmp(fname, "lo_lseek64") == 0) |
| lobjfuncs->fn_lo_lseek64 = foid; |
| else if (strcmp(fname, "lo_tell") == 0) |
| lobjfuncs->fn_lo_tell = foid; |
| else if (strcmp(fname, "lo_tell64") == 0) |
| lobjfuncs->fn_lo_tell64 = foid; |
| else if (strcmp(fname, "lo_truncate") == 0) |
| lobjfuncs->fn_lo_truncate = foid; |
| else if (strcmp(fname, "lo_truncate64") == 0) |
| lobjfuncs->fn_lo_truncate64 = foid; |
| else if (strcmp(fname, "loread") == 0) |
| lobjfuncs->fn_lo_read = foid; |
| else if (strcmp(fname, "lowrite") == 0) |
| lobjfuncs->fn_lo_write = foid; |
| } |
| |
| PQclear(res); |
| |
| /* |
| * Finally check that we got all required large object interface functions |
| * (ones that have been added later than the stone age are instead checked |
| * only if used) |
| */ |
| if (lobjfuncs->fn_lo_open == 0) |
| { |
| libpq_append_conn_error(conn, "cannot determine OID of function %s", |
| "lo_open"); |
| free(lobjfuncs); |
| return -1; |
| } |
| if (lobjfuncs->fn_lo_close == 0) |
| { |
| libpq_append_conn_error(conn, "cannot determine OID of function %s", |
| "lo_close"); |
| free(lobjfuncs); |
| return -1; |
| } |
| if (lobjfuncs->fn_lo_creat == 0) |
| { |
| libpq_append_conn_error(conn, "cannot determine OID of function %s", |
| "lo_creat"); |
| free(lobjfuncs); |
| return -1; |
| } |
| if (lobjfuncs->fn_lo_unlink == 0) |
| { |
| libpq_append_conn_error(conn, "cannot determine OID of function %s", |
| "lo_unlink"); |
| free(lobjfuncs); |
| return -1; |
| } |
| if (lobjfuncs->fn_lo_lseek == 0) |
| { |
| libpq_append_conn_error(conn, "cannot determine OID of function %s", |
| "lo_lseek"); |
| free(lobjfuncs); |
| return -1; |
| } |
| if (lobjfuncs->fn_lo_tell == 0) |
| { |
| libpq_append_conn_error(conn, "cannot determine OID of function %s", |
| "lo_tell"); |
| free(lobjfuncs); |
| return -1; |
| } |
| if (lobjfuncs->fn_lo_read == 0) |
| { |
| libpq_append_conn_error(conn, "cannot determine OID of function %s", |
| "loread"); |
| free(lobjfuncs); |
| return -1; |
| } |
| if (lobjfuncs->fn_lo_write == 0) |
| { |
| libpq_append_conn_error(conn, "cannot determine OID of function %s", |
| "lowrite"); |
| free(lobjfuncs); |
| return -1; |
| } |
| |
| /* |
| * Put the structure into the connection control |
| */ |
| conn->lobjfuncs = lobjfuncs; |
| return 0; |
| } |
| |
| /* |
| * lo_hton64 |
| * converts a 64-bit integer from host byte order to network byte order |
| */ |
| static pg_int64 |
| lo_hton64(pg_int64 host64) |
| { |
| union |
| { |
| pg_int64 i64; |
| uint32 i32[2]; |
| } swap; |
| uint32 t; |
| |
| /* High order half first, since we're doing MSB-first */ |
| t = (uint32) (host64 >> 32); |
| swap.i32[0] = pg_hton32(t); |
| |
| /* Now the low order half */ |
| t = (uint32) host64; |
| swap.i32[1] = pg_hton32(t); |
| |
| return swap.i64; |
| } |
| |
| /* |
| * lo_ntoh64 |
| * converts a 64-bit integer from network byte order to host byte order |
| */ |
| static pg_int64 |
| lo_ntoh64(pg_int64 net64) |
| { |
| union |
| { |
| pg_int64 i64; |
| uint32 i32[2]; |
| } swap; |
| pg_int64 result; |
| |
| swap.i64 = net64; |
| |
| result = (uint32) pg_ntoh32(swap.i32[0]); |
| result <<= 32; |
| result |= (uint32) pg_ntoh32(swap.i32[1]); |
| |
| return result; |
| } |