/* ** 2014-06-13 ** ** 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. ** ****************************************************************************** ** ** This SQLite extension implements SQL functions readfile() and ** writefile(). ** ** Also, an eponymous virtual table type "fsdir". Used as follows: ** ** SELECT * FROM fsdir($dirname); ** ** Returns one row for each entry in the directory $dirname. No row is ** returned for "." or "..". Row columns are as follows: ** ** name: Name of directory entry. ** mode: Value of stat.st_mode for directory entry. ** mtime: Value of stat.st_mtime for directory entry. ** data: For a regular file, a blob containing the file data. For a ** symlink, a text value containing the text of the link. For a ** directory, NULL. */ #include "sqlite3ext.h" SQLITE_EXTENSION_INIT1 #include #define FSDIR_SCHEMA "CREATE TABLE x(name,mode,mtime,data,dir HIDDEN)" static void readFileContents(sqlite3_context *ctx, const char *zName){ FILE *in; long nIn; void *pBuf; in = fopen(zName, "rb"); if( in==0 ) return; fseek(in, 0, SEEK_END); nIn = ftell(in); rewind(in); pBuf = sqlite3_malloc( nIn ); if( pBuf && 1==fread(pBuf, nIn, 1, in) ){ sqlite3_result_blob(ctx, pBuf, nIn, sqlite3_free); }else{ sqlite3_free(pBuf); } fclose(in); } /* ** Implementation of the "readfile(X)" SQL function. The entire content ** of the file named X is read and returned as a BLOB. NULL is returned ** if the file does not exist or is unreadable. */ static void readfileFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ const char *zName; (void)(argc); /* Unused parameter */ zName = (const char*)sqlite3_value_text(argv[0]); if( zName==0 ) return; readFileContents(context, zName); } /* ** Implementation of the "writefile(X,Y)" SQL function. The argument Y ** is written into file X. The number of bytes written is returned. Or ** NULL is returned if something goes wrong, such as being unable to open ** file X for writing. */ static void writefileFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ FILE *out; const char *z; sqlite3_int64 rc; const char *zFile; (void)(argc); /* Unused parameter */ zFile = (const char*)sqlite3_value_text(argv[0]); if( zFile==0 ) return; out = fopen(zFile, "wb"); if( out==0 ) return; z = (const char*)sqlite3_value_blob(argv[1]); if( z==0 ){ rc = 0; }else{ rc = fwrite(z, 1, sqlite3_value_bytes(argv[1]), out); } fclose(out); sqlite3_result_int64(context, rc); } #ifndef SQLITE_OMIT_VIRTUALTABLE #include #include #include #include /* */ typedef struct fsdir_cursor fsdir_cursor; struct fsdir_cursor { sqlite3_vtab_cursor base; /* Base class - must be first */ int eType; /* One of FSDIR_DIR or FSDIR_ENTRY */ DIR *pDir; /* From opendir() */ struct stat sStat; /* Current lstat() results */ char *zDir; /* Directory to read */ int nDir; /* Value of strlen(zDir) */ char *zPath; /* Path to current entry */ int bEof; sqlite3_int64 iRowid; /* Current rowid */ }; typedef struct fsdir_tab fsdir_tab; struct fsdir_tab { sqlite3_vtab base; /* Base class - must be first */ int eType; /* One of FSDIR_DIR or FSDIR_ENTRY */ }; #define FSDIR_DIR 0 #define FSDIR_ENTRY 1 /* ** Construct a new fsdir virtual table object. */ static int fsdirConnect( sqlite3 *db, void *pAux, int argc, const char *const*argv, sqlite3_vtab **ppVtab, char **pzErr ){ fsdir_tab *pNew = 0; int rc; rc = sqlite3_declare_vtab(db, FSDIR_SCHEMA); if( rc==SQLITE_OK ){ pNew = (fsdir_tab*)sqlite3_malloc( sizeof(*pNew) ); if( pNew==0 ) return SQLITE_NOMEM; memset(pNew, 0, sizeof(*pNew)); pNew->eType = (pAux==0 ? FSDIR_DIR : FSDIR_ENTRY); } *ppVtab = (sqlite3_vtab*)pNew; return rc; } /* ** This method is the destructor for fsdir vtab objects. */ static int fsdirDisconnect(sqlite3_vtab *pVtab){ sqlite3_free(pVtab); return SQLITE_OK; } /* ** Constructor for a new fsdir_cursor object. */ static int fsdirOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ fsdir_cursor *pCur; pCur = sqlite3_malloc( sizeof(*pCur) ); if( pCur==0 ) return SQLITE_NOMEM; memset(pCur, 0, sizeof(*pCur)); pCur->eType = ((fsdir_tab*)p)->eType; *ppCursor = &pCur->base; return SQLITE_OK; } /* ** Destructor for an fsdir_cursor. */ static int fsdirClose(sqlite3_vtab_cursor *cur){ fsdir_cursor *pCur = (fsdir_cursor*)cur; if( pCur->pDir ) closedir(pCur->pDir); sqlite3_free(pCur->zDir); sqlite3_free(pCur->zPath); sqlite3_free(pCur); return SQLITE_OK; } /* ** Advance an fsdir_cursor to its next row of output. */ static int fsdirNext(sqlite3_vtab_cursor *cur){ fsdir_cursor *pCur = (fsdir_cursor*)cur; struct dirent *pEntry; if( pCur->eType==FSDIR_ENTRY ){ pCur->bEof = 1; return SQLITE_OK; } sqlite3_free(pCur->zPath); pCur->zPath = 0; while( 1 ){ pEntry = readdir(pCur->pDir); if( pEntry ){ if( strcmp(pEntry->d_name, ".") && strcmp(pEntry->d_name, "..") ){ pCur->zPath = sqlite3_mprintf("%s/%s", pCur->zDir, pEntry->d_name); if( pCur->zPath==0 ) return SQLITE_NOMEM; lstat(pCur->zPath, &pCur->sStat); break; } }else{ pCur->bEof = 1; break; } } pCur->iRowid++; return SQLITE_OK; } /* ** Return values of columns for the row at which the series_cursor ** is currently pointing. */ static int fsdirColumn( sqlite3_vtab_cursor *cur, /* The cursor */ sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ int i /* Which column to return */ ){ fsdir_cursor *pCur = (fsdir_cursor*)cur; switch( i ){ case 0: { /* name */ const char *zName; if( pCur->eType==FSDIR_DIR ){ zName = &pCur->zPath[pCur->nDir+1]; }else{ zName = pCur->zPath; } sqlite3_result_text(ctx, zName, -1, SQLITE_TRANSIENT); break; } case 1: /* mode */ sqlite3_result_int64(ctx, pCur->sStat.st_mode); break; case 2: /* mode */ sqlite3_result_int64(ctx, pCur->sStat.st_mtime); break; case 3: { mode_t m = pCur->sStat.st_mode; if( S_ISDIR(m) ){ sqlite3_result_null(ctx); }else if( S_ISLNK(m) ){ char aStatic[64]; char *aBuf = aStatic; int nBuf = 64; int n; while( 1 ){ n = readlink(pCur->zPath, aBuf, nBuf); if( nzPath); } } } return SQLITE_OK; } /* ** Return the rowid for the current row. In this implementation, the ** first row returned is assigned rowid value 1, and each subsequent ** row a value 1 more than that of the previous. */ static int fsdirRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ fsdir_cursor *pCur = (fsdir_cursor*)cur; *pRowid = pCur->iRowid; return SQLITE_OK; } /* ** Return TRUE if the cursor has been moved off of the last ** row of output. */ static int fsdirEof(sqlite3_vtab_cursor *cur){ fsdir_cursor *pCur = (fsdir_cursor*)cur; return pCur->bEof; } static void fsdirSetErrmsg(fsdir_cursor *pCur, const char *zFmt, ...){ va_list ap; va_start(ap, zFmt); pCur->base.pVtab->zErrMsg = sqlite3_vmprintf(zFmt, ap); va_end(ap); } /* ** xFilter callback. */ static int fsdirFilter( sqlite3_vtab_cursor *cur, int idxNum, const char *idxStr, int argc, sqlite3_value **argv ){ const char *zDir = 0; fsdir_cursor *pCur = (fsdir_cursor*)cur; sqlite3_free(pCur->zDir); pCur->iRowid = 0; pCur->zDir = 0; pCur->bEof = 0; if( pCur->pDir ){ closedir(pCur->pDir); pCur->pDir = 0; } if( idxNum==0 ){ fsdirSetErrmsg(pCur, "table function fsdir requires an argument"); return SQLITE_ERROR; } assert( argc==1 ); zDir = (const char*)sqlite3_value_text(argv[0]); if( zDir==0 ){ fsdirSetErrmsg(pCur, "table function fsdir requires a non-NULL argument"); return SQLITE_ERROR; } pCur->zDir = sqlite3_mprintf("%s", zDir); if( pCur->zDir==0 ){ return SQLITE_NOMEM; } if( pCur->eType==FSDIR_ENTRY ){ int rc = lstat(pCur->zDir, &pCur->sStat); if( rc ){ fsdirSetErrmsg(pCur, "cannot stat file: %s", pCur->zDir); }else{ pCur->zPath = sqlite3_mprintf("%s", pCur->zDir); if( pCur->zPath==0 ) return SQLITE_NOMEM; } return SQLITE_OK; }else{ pCur->nDir = strlen(pCur->zDir); pCur->pDir = opendir(zDir); if( pCur->pDir==0 ){ fsdirSetErrmsg(pCur, "error in opendir(\"%s\")", zDir); return SQLITE_ERROR; } return fsdirNext(cur); } } /* ** SQLite will invoke this method one or more times while planning a query ** that uses the generate_series virtual table. This routine needs to create ** a query plan for each invocation and compute an estimated cost for that ** plan. ** ** In this implementation idxNum is used to represent the ** query plan. idxStr is unused. ** ** The query plan is represented by bits in idxNum: ** ** (1) start = $value -- constraint exists ** (2) stop = $value -- constraint exists ** (4) step = $value -- constraint exists ** (8) output in descending order */ static int fsdirBestIndex( sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo ){ int i; /* Loop over constraints */ const struct sqlite3_index_constraint *pConstraint; pConstraint = pIdxInfo->aConstraint; for(i=0; inConstraint; i++, pConstraint++){ if( pConstraint->usable==0 ) continue; if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; if( pConstraint->iColumn!=4 ) continue; break; } if( inConstraint ){ pIdxInfo->aConstraintUsage[i].omit = 1; pIdxInfo->aConstraintUsage[i].argvIndex = 1; pIdxInfo->idxNum = 1; pIdxInfo->estimatedCost = 10.0; }else{ pIdxInfo->idxNum = 0; pIdxInfo->estimatedCost = (double)(((sqlite3_int64)1) << 50); } return SQLITE_OK; } static int fsdirRegister(sqlite3 *db){ static sqlite3_module fsdirModule = { 0, /* iVersion */ 0, /* xCreate */ fsdirConnect, /* xConnect */ fsdirBestIndex, /* xBestIndex */ fsdirDisconnect, /* xDisconnect */ 0, /* xDestroy */ fsdirOpen, /* xOpen - open a cursor */ fsdirClose, /* xClose - close a cursor */ fsdirFilter, /* xFilter - configure scan constraints */ fsdirNext, /* xNext - advance a cursor */ fsdirEof, /* xEof - check for end of scan */ fsdirColumn, /* xColumn - read data */ fsdirRowid, /* xRowid - read data */ 0, /* xUpdate */ 0, /* xBegin */ 0, /* xSync */ 0, /* xCommit */ 0, /* xRollback */ 0, /* xFindMethod */ 0, /* xRename */ }; int rc = sqlite3_create_module(db, "fsdir", &fsdirModule, 0); if( rc==SQLITE_OK ){ rc = sqlite3_create_module(db, "fsentry", &fsdirModule, (void*)1); } return rc; } #else /* SQLITE_OMIT_VIRTUALTABLE */ # define fsdirRegister(x) SQLITE_OK #endif #ifdef _WIN32 __declspec(dllexport) #endif int sqlite3_fileio_init( sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi ){ int rc = SQLITE_OK; SQLITE_EXTENSION_INIT2(pApi); (void)pzErrMsg; /* Unused parameter */ rc = sqlite3_create_function(db, "readfile", 1, SQLITE_UTF8, 0, readfileFunc, 0, 0); if( rc==SQLITE_OK ){ rc = sqlite3_create_function(db, "writefile", 2, SQLITE_UTF8, 0, writefileFunc, 0, 0); } if( rc==SQLITE_OK ){ rc = fsdirRegister(db); } return rc; }