--- /dev/null
+/*
+** 2019 April 12
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+**
+** A read-only VFS that reads data from a server instead of a file
+** using a custom protocol over a tcp/ip socket. The VFS is named
+** "socket". The filename passed to sqlite3_open() is of the
+** form "host:portnumber". For example, to connect to the server
+** on port 23456 on the localhost:
+**
+** sqlite3_open_v2("localhost:23456", &db, SQLITE_OPEN_READONLY, "socket");
+**
+** Or, if using URIs:
+**
+** sqlite3_open("file:localhost:23456?vfs=socket", &db);
+**
+** The protocol is:
+**
+** * Client connects to tcp/ip server. Server immediately sends the
+** database file-size in bytes as a 64-bit big-endian integer.
+**
+** * To read from the file, client sends the byte offset and amount
+** of data required in bytes, both as 64-bit big-endian integers
+** (i.e. a 16-byte message). Server sends back the requested data.
+**
+** As well as the usual SQLite loadable extension entry point, this file
+** exports one more function:
+**
+** sqlite3_vfs *sqlite3_socketvfs(void);
+**
+** To install the "socket" VFS without loading the extension, link this file
+** into the application and invoke:
+**
+** int bDefault = 0; // Do not make "socket" the default VFS
+** sqlite3_vfs_register(sqlite3_socketvfs(), bDefault);
+**
+*/
+
+#include "sqlite3ext.h"
+SQLITE_EXTENSION_INIT1
+
+#include <assert.h>
+#include <string.h>
+
+#if defined(_WIN32)
+# if defined(_WIN32_WINNT)
+# undef _WIN32_WINNT
+# endif
+# define _WIN32_WINNT 0x501
+#endif
+#ifndef __EXTENSIONS__
+# define __EXTENSIONS__ 1 /* IPv6 won't compile on Solaris without this */
+#endif
+#if defined(_WIN32)
+# include <winsock2.h>
+# include <ws2tcpip.h>
+# include <Windows.h>
+# include <time.h>
+#else
+# include <netinet/in.h>
+# include <arpa/inet.h>
+# include <sys/socket.h>
+# include <netdb.h>
+# include <time.h>
+#endif
+#include <assert.h>
+#include <sys/types.h>
+#include <signal.h>
+
+#if !defined(_WIN32)
+# include <unistd.h>
+#endif
+
+/*
+** When using this VFS, the sqlite3_file* handles that SQLite uses are
+** actually pointers to instances of type SocketFile.
+*/
+typedef struct SocketFile SocketFile;
+struct SocketFile {
+ sqlite3_file base; /* Base class. Must be first. */
+ int iSocket; /* Socket used to talk to server. */
+ sqlite3_int64 szFile; /* Size of file in bytes */
+};
+
+static sqlite3_uint64 socketGetU64(const unsigned char *a){
+ return (((sqlite3_uint64)(a[0])) << 56)
+ + (((sqlite3_uint64)(a[1])) << 48)
+ + (((sqlite3_uint64)(a[2])) << 40)
+ + (((sqlite3_uint64)(a[3])) << 32)
+ + (((sqlite3_uint64)(a[4])) << 24)
+ + (((sqlite3_uint64)(a[5])) << 16)
+ + (((sqlite3_uint64)(a[6])) << 8)
+ + (((sqlite3_uint64)(a[7])) << 0);
+}
+
+static void socketPutU64(unsigned char *a, sqlite3_int64 i){
+ a[0] = ((i >> 56) & 0xFF);
+ a[1] = ((i >> 48) & 0xFF);
+ a[2] = ((i >> 40) & 0xFF);
+ a[3] = ((i >> 32) & 0xFF);
+ a[4] = ((i >> 24) & 0xFF);
+ a[5] = ((i >> 16) & 0xFF);
+ a[6] = ((i >> 8) & 0xFF);
+ a[7] = ((i >> 0) & 0xFF);
+}
+
+static void socket_close(int iSocket){
+ if( iSocket>=0 ){
+#if defined(_WIN32)
+ if( shutdown(iSocket,1)==0 ) shutdown(iSocket,0);
+ closesocket(iSocket);
+#else
+ close(iSocket);
+#endif
+ }
+}
+
+/*
+** Write nData bytes of data from buffer aData to socket iSocket. If
+** successful, return SQLITE_OK. Otherwise, SQLITE_IOERR_WRITE.
+*/
+static int socket_send(int iSocket, const unsigned char *aData, int nData){
+ int nWrite = 0;
+ do{
+ int res = send(iSocket, (const char*)&aData[nWrite], nData-nWrite, 0);
+ if( res<=0 ) return SQLITE_IOERR_WRITE;
+ nWrite += res;
+ }while( nWrite<nData );
+ return SQLITE_OK;
+}
+
+/*
+** Read nData bytes of data from socket iSocket into buffer aData. If
+** successful, return SQLITE_OK. Otherwise, SQLITE_IOERR_READ.
+*/
+static int socket_recv(int iSocket, unsigned char *aData, int nData){
+ int nRead = 0;
+ do{
+ int res = recv(iSocket, (char*)&aData[nRead], nData-nRead, 0);
+ if( res<=0 ) return SQLITE_IOERR_READ;
+ nRead += res;
+ }while( nRead<nData );
+ return SQLITE_OK;
+}
+
+/*
+** Close a SocketFile file.
+*/
+static int socketClose(sqlite3_file *pFile){
+ SocketFile *pSock = (SocketFile*)pFile;
+ socket_close(pSock->iSocket);
+ pSock->iSocket = -1;
+ return SQLITE_OK;
+}
+
+/*
+** Read data from a SocketFile file.
+*/
+static int socketRead(
+ sqlite3_file *pFile,
+ void *zBuf,
+ int iAmt,
+ sqlite3_int64 iOfst
+){
+ SocketFile *pSock = (SocketFile*)pFile;
+ unsigned char aRequest[16];
+ int rc = SQLITE_OK;
+ int nRead = iAmt;
+
+ if( iOfst+nRead>pSock->szFile ){
+ nRead = (int)(pSock->szFile - iOfst);
+ memset(zBuf, 0, iAmt);
+ rc = SQLITE_IOERR_SHORT_READ;
+ }
+
+ if( nRead>0 ){
+ socketPutU64(&aRequest[0], (sqlite3_uint64)iOfst);
+ socketPutU64(&aRequest[8], (sqlite3_uint64)nRead);
+ rc = socket_send(pSock->iSocket, aRequest, sizeof(aRequest));
+ if( rc==SQLITE_OK ){
+ rc = socket_recv(pSock->iSocket, zBuf, nRead);
+ }
+ }
+
+ return rc;
+}
+
+/*
+** Write to a file. This is a no-op, as this VFS is always opens files
+** read-only.
+*/
+static int socketWrite(
+ sqlite3_file *pFile,
+ const void *zBuf,
+ int iAmt,
+ sqlite3_int64 iOfst
+){
+ return SQLITE_IOERR_WRITE;
+}
+
+/*
+** Truncate a file. This is a no-op, as this VFS is always opens files
+** read-only.
+*/
+static int socketTruncate(sqlite3_file *pFile, sqlite3_int64 size){
+ return SQLITE_IOERR_TRUNCATE;
+}
+
+/*
+** Synk a file. This is a no-op, as this VFS is always opens files
+** read-only.
+*/
+static int socketSync(sqlite3_file *pFile, int flags){
+ return SQLITE_IOERR_FSYNC;
+}
+
+/*
+** Write the size of the file in bytes to *pSize.
+*/
+static int socketFileSize(sqlite3_file *pFile, sqlite3_int64 *pSize){
+ SocketFile *pSock = (SocketFile*)pFile;
+ *pSize = pSock->szFile;
+ return SQLITE_OK;
+}
+
+/*
+** Locking functions. All no-ops.
+*/
+static int socketLock(sqlite3_file *pFile, int eLock){
+ return SQLITE_OK;
+}
+static int socketUnlock(sqlite3_file *pFile, int eLock){
+ return SQLITE_OK;
+}
+static int socketCheckReservedLock(sqlite3_file *pFile, int *pResOut){
+ *pResOut = 0;
+ return SQLITE_OK;
+}
+
+/*
+** No xFileControl() verbs are implemented by this VFS.
+*/
+static int socketFileControl(sqlite3_file *pFile, int op, void *pArg){
+ return SQLITE_OK;
+}
+
+/*
+** The xSectorSize() and xDeviceCharacteristics() methods. These two
+** may return special values allowing SQLite to optimize file-system
+** access to some extent. But it is also safe to simply return 0.
+*/
+static int socketSectorSize(sqlite3_file *pFile){
+ return 0;
+}
+static int socketDeviceCharacteristics(sqlite3_file *pFile){
+ return 0;
+}
+
+/*
+** Open a SocketFile file.
+*/
+static int socketOpen(
+ sqlite3_vfs *pVfs, /* VFS */
+ const char *zName, /* File to open, or 0 for a temp file */
+ sqlite3_file *pFile, /* Pointer to SocketFile struct to populate */
+ int flags, /* Input SQLITE_OPEN_XXX flags */
+ int *pOutFlags /* Output SQLITE_OPEN_XXX flags (or NULL) */
+){
+ static const sqlite3_io_methods socketio = {
+ 1, /* iVersion */
+ socketClose, /* xClose */
+ socketRead, /* xRead */
+ socketWrite, /* xWrite */
+ socketTruncate, /* xTruncate */
+ socketSync, /* xSync */
+ socketFileSize, /* xFileSize */
+ socketLock, /* xLock */
+ socketUnlock, /* xUnlock */
+ socketCheckReservedLock, /* xCheckReservedLock */
+ socketFileControl, /* xFileControl */
+ socketSectorSize, /* xSectorSize */
+ socketDeviceCharacteristics /* xDeviceCharacteristics */
+ };
+
+ SocketFile *pSock = (SocketFile*)pFile;
+
+ char zHost[1024];
+ const char *zPort;
+ int i;
+
+ struct addrinfo hints;
+ struct addrinfo *ai = 0;
+ struct addrinfo *pInfo;
+ unsigned char aFileSize[8];
+
+ pSock->iSocket = -1;
+ if( (flags & SQLITE_OPEN_MAIN_DB)==0 ) return SQLITE_CANTOPEN;
+
+ /* Parse the argument and copy the results to zHost and zPort. It should be
+ ** "hostname:port". Anything else is an error. */
+ assert( sizeof(zHost)>=pVfs->mxPathname );
+ if( zName==0 ) return SQLITE_CANTOPEN;
+ for(i=0; zName[i] && zName[i]!=':'; i++);
+ if( zName[i]==0 ) return SQLITE_CANTOPEN;
+ memcpy(zHost, zName, i);
+ zHost[i] = '\0';
+ zPort = &zName[i+1];
+
+ /* Resolve the address */
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+ if( getaddrinfo(zHost, zPort, &hints, &ai) ){
+ return SQLITE_CANTOPEN;
+ }
+
+ /* Connect to the resolved address. Set SocketFile.iSocket to the tcp/ip
+ ** socket and return SQLITE_OK. */
+ for(pInfo=ai; pInfo; pInfo=pInfo->ai_next){
+ int sd = socket(pInfo->ai_family, pInfo->ai_socktype, pInfo->ai_protocol);
+ if( sd<0 ) continue;
+ if( connect(sd, pInfo->ai_addr, pInfo->ai_addrlen)<0 ){
+ socket_close(sd);
+ continue;
+ }
+ pSock->iSocket = sd;
+ break;
+ }
+
+ if( ai ) freeaddrinfo(ai);
+ if( pSock->iSocket<0 ) return SQLITE_CANTOPEN;
+
+ /* The server sends back the file size as a 64-bit big-endian */
+ if( socket_recv(pSock->iSocket, aFileSize, 8) ){
+ socket_close(pSock->iSocket);
+ return SQLITE_CANTOPEN;
+ }
+ pSock->szFile = (sqlite3_int64)socketGetU64(aFileSize);
+
+ *pOutFlags = flags & ~(SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE);
+ *pOutFlags |= SQLITE_OPEN_READONLY;
+ pSock->base.pMethods = &socketio;
+ return SQLITE_OK;
+}
+
+/*
+** Another no-op. This is a read-only VFS.
+*/
+static int socketDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
+ return SQLITE_IOERR_DELETE;
+}
+
+/*
+** This is used by SQLite to detect journal and wal files. Which cannot
+** exist for this VFS. So always set the output to false and return
+** SQLITE_OK.
+*/
+static int socketAccess(
+ sqlite3_vfs *pVfs,
+ const char *zPath,
+ int flags,
+ int *pResOut
+){
+ *pResOut = 0;
+ return SQLITE_OK;
+}
+
+/*
+** A no-op. Copy the input to the output.
+*/
+static int socketFullPathname(
+ sqlite3_vfs *pVfs, /* VFS */
+ const char *zPath, /* Input path (possibly a relative path) */
+ int nPathOut, /* Size of output buffer in bytes */
+ char *zPathOut /* Pointer to output buffer */
+){
+ int nByte = strlen(zPath);
+ if( nByte>=pVfs->mxPathname ) return SQLITE_IOERR;
+ memcpy(zPathOut, zPath, nByte+1);
+ return SQLITE_OK;
+}
+
+/*
+** The following four VFS methods:
+**
+** xDlOpen
+** xDlError
+** xDlSym
+** xDlClose
+**
+** are supposed to implement the functionality needed by SQLite to load
+** extensions compiled as shared objects. This simple VFS does not support
+** this functionality, so the following functions are no-ops.
+*/
+static void *socketDlOpen(sqlite3_vfs *pVfs, const char *zPath){
+ return 0;
+}
+static void socketDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){
+ sqlite3_snprintf(nByte, zErrMsg, "Loadable extensions are not supported");
+ zErrMsg[nByte-1] = '\0';
+}
+static void (*socketDlSym(sqlite3_vfs *pVfs, void *pH, const char *z))(void){
+ return 0;
+}
+static void socketDlClose(sqlite3_vfs *pVfs, void *pHandle){
+ return;
+}
+
+/*
+** Parameter zByte points to a buffer nByte bytes in size. Populate this
+** buffer with pseudo-random data.
+*/
+static int socketRandomness(sqlite3_vfs *pVfs, int nByte, char *zByte){
+ memset(zByte, 0, nByte);
+ return SQLITE_OK;
+}
+
+/*
+** Sleep for at least nMicro microseconds. Return the (approximate) number
+** of microseconds slept for.
+*/
+static int socketSleep(sqlite3_vfs *pVfs, int nMicro){
+#ifdef _WIN32
+ Sleep(nMicro/1000);
+#else
+ sleep(nMicro / 1000000);
+ usleep(nMicro % 1000000);
+#endif
+ return nMicro;
+}
+
+/*
+** Set *pTime to the current UTC time expressed as a Julian day. Return
+** SQLITE_OK if successful, or an error code otherwise.
+**
+** http://en.wikipedia.org/wiki/Julian_day
+**
+** This implementation is not very good. The current time is rounded to
+** an integer number of seconds. Also, assuming time_t is a signed 32-bit
+** value, it will stop working some time in the year 2038 AD (the so-called
+** "year 2038" problem that afflicts systems that store time this way).
+*/
+static int socketCurrentTime(sqlite3_vfs *pVfs, double *pTime){
+ time_t t = time(0);
+ *pTime = t/86400.0 + 2440587.5;
+ return SQLITE_OK;
+}
+
+/*
+** This function returns a pointer to the VFS implemented in this file.
+** To make the VFS available to SQLite:
+**
+** sqlite3_vfs_register(sqlite3_socketvfs(), 0);
+*/
+sqlite3_vfs *sqlite3_socketvfs(void){
+ static sqlite3_vfs socketvfs = {
+ 1, /* iVersion */
+ sizeof(SocketFile), /* szOsFile */
+ 512, /* mxPathname */
+ 0, /* pNext */
+ "socket", /* zName */
+ 0, /* pAppData */
+ socketOpen, /* xOpen */
+ socketDelete, /* xDelete */
+ socketAccess, /* xAccess */
+ socketFullPathname, /* xFullPathname */
+ socketDlOpen, /* xDlOpen */
+ socketDlError, /* xDlError */
+ socketDlSym, /* xDlSym */
+ socketDlClose, /* xDlClose */
+ socketRandomness, /* xRandomness */
+ socketSleep, /* xSleep */
+ socketCurrentTime, /* xCurrentTime */
+ };
+ return &socketvfs;
+}
+
+/*
+** Register the amatch virtual table
+*/
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+int sqlite3_socketvfs_init(
+ sqlite3 *db,
+ char **pzErrMsg,
+ const sqlite3_api_routines *pApi
+){
+ int rc = SQLITE_OK;
+ SQLITE_EXTENSION_INIT2(pApi);
+ (void)pzErrMsg; /* Not used */
+ sqlite3_vfs_register(sqlite3_socketvfs(), 0);
+ return SQLITE_OK_LOAD_PERMANENTLY;
+}
+