#include #include #include #include #include #include #include #include #include #include #include #include #include #include "sqlite3ext.h" typedef unsigned char u8; typedef unsigned short u16; SQLITE_EXTENSION_INIT1 struct vtab { sqlite3_vtab base; /* Base class - must be first */ sqlite3 *db; /* Database connection */ char *dbname; /* Name of database holding this table */ char *tablename; /* Name of the virtual table */ char *dirname; /* Name of the virtual table */ char dbname_data[1]; /* storage for dbname */ }; struct vtab_cursor { sqlite3_vtab_cursor base; /* Base class - must be first */ struct vtab *pvtab; /* Virtual table of this cursor */ DIR *pdir; struct dirent *pent; int feof; }; static const char create_table[] = "CREATE TABLE tablename" "( fileno INTEGER" ", type CHAR(1)" ", name VARCHAR(512)" ", fullname VARCHAR(512)" ")"; /* * argv[0] -> module name ("readdir") * argv[1] -> database name * argv[2] -> table name * argv[3] -> directory name */ static int xCreate(sqlite3 *db, void *pAux, int argc, const char * const *argv, sqlite3_vtab **ppVTab, char **pzErr) { ///const char *module = argv[0]; const char *dbname = argv[1]; const char *tablename = argv[2]; const char *dirname = argv[3]; struct vtab *pvtab; if( argc < 4 ) return SQLITE_ERROR; if( opendir(dirname) == NULL ) { char *fmt = "%s: %s"; if( dirname[0] == '$' ) { const char * dir = getenv(dirname + 1); if( dir && opendir(dir) ) { dirname = dir; goto DirnameOK; } else { fmt = "%s: %s (and not an envar)"; } } *pzErr = sqlite3_mprintf(fmt, dirname, strerror(errno)); return SQLITE_ERROR; } DirnameOK: /* * Allocate a vtable with room at the end for the dbname. * Initialize dbname pointer to the address of the additional storage. */ if( (pvtab = sqlite3_malloc(1 + sizeof(*pvtab) + strlen(dbname))) == NULL ) { return SQLITE_NOMEM; } memset(pvtab, '\0', sizeof(*pvtab)); pvtab->db = db; strcpy( pvtab->dbname_data, dbname ); pvtab->dbname = pvtab->dbname_data; if( (pvtab->tablename = sqlite3_mprintf("%s", tablename)) == NULL ) { sqlite3_free(pvtab); return SQLITE_NOMEM; } if( (pvtab->dirname = sqlite3_mprintf("%s", dirname)) == NULL ) { sqlite3_free(pvtab->tablename); sqlite3_free(pvtab); return SQLITE_NOMEM; } if( sqlite3_declare_vtab(db, create_table) != SQLITE_OK ) { sqlite3_free(pvtab->tablename); sqlite3_free(pvtab->dirname); sqlite3_free(pvtab); return SQLITE_ERROR; } *ppVTab = (sqlite3_vtab *)pvtab; return SQLITE_OK; } static int xConnect( sqlite3 *db, void *pAux, int argc, const char* const *argv, sqlite3_vtab **ppVTab, char **pzErr ) { return xCreate(db, pAux, argc, argv, ppVTab, pzErr); } static int xBestIndex(sqlite3_vtab *pvtab, sqlite3_index_info* pi) { #if 0 /* Inputs */ pi->nConstraint; *pi->aConstraint; /* Table of WHERE clause constraints */ pi->nOrderBy; *pi->aOrderBy; /* The ORDER BY clause */ /* Outputs */ *pi->aConstraintUsage; pi->idxNum; /* Number used to identify the index */ pi->idxStr; /* String, possibly obtained from sqlite3_malloc */ pi->needToFreeIdxStr; /* Free idxStr using sqlite3_free() if true */ pi->orderByConsumed; /* True if output is already ordered */ pi->estimatedCost; /* Estimated cost of using this index */ #endif /* we're only going to iterate over the directory entries each time */ pi->idxNum = 0; pi->idxStr = "any"; pi->needToFreeIdxStr = 0; pi->orderByConsumed = 0; pi->estimatedCost = 1; return SQLITE_OK; } static int xDisconnect(sqlite3_vtab *pVTab) { return SQLITE_OK; } static int xDestroy(sqlite3_vtab *pVTab) { struct vtab *pvtab = (struct vtab *)pVTab; sqlite3_free(pvtab->tablename); sqlite3_free(pvtab->dirname); sqlite3_free(pvtab); return SQLITE_OK; } static int xOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor) { struct vtab *pvtab = (struct vtab *)pVTab; struct vtab_cursor *pcursor = sqlite3_malloc( sizeof(struct vtab_cursor) ); if( pcursor == NULL ) return SQLITE_NOMEM; pcursor->pvtab = pvtab; if( (pcursor->pdir = opendir(pvtab->dirname)) == NULL ) { return SQLITE_ERROR; } pcursor->feof = 1; *ppCursor = &pcursor->base; return SQLITE_OK; } static int xClose(sqlite3_vtab_cursor *pCursor) { struct vtab_cursor *pcur = (struct vtab_cursor *)pCursor; int erc = closedir(pcur->pdir); if( erc == -1 ) { sqlite3_free(pcur); return SQLITE_ERROR; } sqlite3_free(pcur); return SQLITE_OK; } static int xEof(sqlite3_vtab_cursor *pCursor) { return ((struct vtab_cursor *)pCursor)->feof; } static int xFilter(sqlite3_vtab_cursor *pCursor, int idxNum, const char *idxStr, int argc, sqlite3_value **argv) { struct vtab_cursor *pcur = (struct vtab_cursor *)pCursor; pcur->feof = (pcur->pent = readdir(pcur->pdir)) == NULL; if( pcur->feof ) { return SQLITE_OK; // not an error, we think } return SQLITE_OK; } static int xNext(sqlite3_vtab_cursor* pCursor) { struct vtab_cursor *pcur = (struct vtab_cursor *)pCursor; pcur->feof = (pcur->pent = readdir(pcur->pdir )) == NULL; if( pcur->feof ) { return SQLITE_OK; // not an error, we think } return SQLITE_OK; } static char mode_char(unsigned int mode) { switch(mode) { case DT_BLK: return 'b'; /* Block special file. */ case DT_CHR: return 'c'; /* Character special file. */ case DT_DIR: return 'd'; /* Directory. */ case DT_LNK: return 'l'; /* Symbolic link. */ case DT_SOCK: return 's'; /* Socket link. */ case DT_FIFO: return 'p'; /* FIFO. */ case DT_WHT: return 'w'; /* Whiteout. */ case DT_REG: return 'r'; /* Regular file. */ case DT_UNKNOWN: default: assert(0); break; } return '?'; } static int xColumn(sqlite3_vtab_cursor *pCursor, sqlite3_context *pcontext, int N) { struct vtab_cursor *pcur = (struct vtab_cursor *)pCursor; char mode[1]; switch(N) { case 0: sqlite3_result_int(pcontext, pcur->pent->d_fileno); break; case 1: mode[0] = mode_char(pcur->pent->d_type); sqlite3_result_text(pcontext, mode, 1, SQLITE_TRANSIENT); break; case 2: sqlite3_result_text(pcontext, pcur->pent->d_name, -1, SQLITE_TRANSIENT); break; case 3: { char *fullname = sqlite3_mprintf("%s/%s", pcur->pvtab->dirname, pcur->pent->d_name); sqlite3_result_text(pcontext, fullname, -1, sqlite3_free); } break; default: sqlite3_result_error_code(pcontext, SQLITE_ERROR); return SQLITE_ERROR; break; } return SQLITE_OK; } static int xRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid) { struct vtab_cursor *pcur = (struct vtab_cursor *)pCursor; *pRowid = pcur->pent->d_fileno; return SQLITE_OK; } #if 0 static int xUpdate(sqlite3_vtab *pVTab, int argc, sqlite3_value **argv, sqlite_int64 *pRowid) { /* could implement touch(1) this way */ return SQLITE_OK; } #endif typedef int (*xNoUpdate_t)(sqlite3_vtab *pVTab, int argc, sqlite3_value **argv, sqlite_int64 *pRowid ); static const xNoUpdate_t xNoUpdate = NULL; /* * Rename the table. */ static int xRename(sqlite3_vtab *pVTab, const char *zNew) { struct vtab *pvtab = (struct vtab *)pVTab; char *newname = sqlite3_mprintf("%s", zNew); int erc; if( newname == NULL ){ return SQLITE_NOMEM; } char *sql = sqlite3_mprintf("ALTER TABLE \"%w\".\"%w\" RENAME TO \"%w\"", pvtab->dbname, pvtab->tablename, newname); if( sql == NULL ) { return SQLITE_NOMEM; } if( (erc = sqlite3_exec(pvtab->db, sql, 0, 0, 0)) != SQLITE_OK ) { sqlite3_free(sql); return erc; } sqlite3_free(pvtab->tablename); pvtab->tablename = newname; return SQLITE_OK; } /* * A virtual table module that renders a directory */ static sqlite3_module readdirModule = { 0, /* iVersion */ xCreate, /* xCreate - handle CREATE VIRTUAL TABLE */ xConnect, /* xConnect - reconnected to an existing table */ xBestIndex, /* xBestIndex - figure out how to do a query */ xDisconnect, /* xDisconnect - close a connection */ xDestroy, /* xDestroy - handle DROP TABLE */ xOpen, /* xOpen - open a cursor */ xClose, /* xClose - close a cursor */ xFilter, /* xFilter - configure scan constraints */ xNext, /* xNext - advance a cursor */ xEof, /* xEof - check for end of scan */ xColumn, /* xColumn - read data */ xRowid, /* xRowid - read data */ 0, /* xUpdate */ 0, /* xBegin */ 0, /* xSync */ 0, /* xCommit */ 0, /* xRollback */ 0, /* xFindMethod */ xRename, /* xRename */ }; struct inode_property_t { const char *name; size_t offset; }; struct inode_property_t inode_properties[] = { { "mode", offsetof(struct stat, st_mode)} , { "nlink", offsetof(struct stat, st_nlink)} , { "uid", offsetof(struct stat, st_uid)} , { "gid", offsetof(struct stat, st_gid)} , { "rdev", offsetof(struct stat, st_rdev)} , { "atime", offsetof(struct stat, st_atime)} , { "atimensec", offsetof(struct stat, st_atimensec)} , { "mtime", offsetof(struct stat, st_mtime)} , { "mtimensec", offsetof(struct stat, st_mtimensec)} , { "ctime", offsetof(struct stat, st_ctime)} , { "ctimensec", offsetof(struct stat, st_ctimensec)} , { "size", offsetof(struct stat, st_size)} , { "blocks", offsetof(struct stat, st_blocks)} , { "blksize", offsetof(struct stat, st_blksize)} , { "flags", offsetof(struct stat, st_flags)} , { "gen", offsetof(struct stat, st_gen) } , { "birthtime", offsetof(struct stat, st_birthtime)} , { "birthtimensec", offsetof(struct stat, st_birthtimensec)} }; static size_t ninode_properties = sizeof(inode_properties)/sizeof(inode_properties[0]); static int inode_property_comp( const void *a, const void *b ) { return strcmp( ((struct inode_property_t*)a)->name, ((struct inode_property_t*)b)->name ); } static void inode_stat( sqlite3_context* context, int argc, sqlite3_value** argv ) { int erc; struct stat sb; char *filename = (char*)sqlite3_value_text(argv[0]); if( (erc = stat(filename, &sb)) == -1 ) { char *oops = sqlite3_mprintf("%s: %s", filename, strerror(errno)); sqlite3_result_error(context, oops, -1); sqlite3_free(oops); return; } assert(argc == 2); struct inode_property_t key = { (char *)sqlite3_value_text(argv[1]), 0 }; struct inode_property_t *p = lsearch(&key, inode_properties, &ninode_properties, sizeof(inode_properties[0]), inode_property_comp); switch(p->offset) { case offsetof(struct stat, st_mode): sqlite3_result_int64(context, sb.st_mode); break; case offsetof(struct stat, st_nlink): sqlite3_result_int64(context, sb.st_nlink); break; case offsetof(struct stat, st_uid): sqlite3_result_int64(context, sb.st_uid); break; case offsetof(struct stat, st_gid): sqlite3_result_int64(context, sb.st_gid); break; case offsetof(struct stat, st_rdev): sqlite3_result_int64(context, sb.st_rdev); break; case offsetof(struct stat, st_atime): sqlite3_result_int64(context, sb.st_atime); break; case offsetof(struct stat, st_mtime): sqlite3_result_int64(context, sb.st_mtime); break; case offsetof(struct stat, st_ctime): sqlite3_result_int64(context, sb.st_ctime); break; case offsetof(struct stat, st_size): sqlite3_result_int64(context, sb.st_size); break; case offsetof(struct stat, st_flags): sqlite3_result_int64(context, sb.st_flags); break; case offsetof(struct stat, st_gen): sqlite3_result_int64(context, sb.st_gen); break; case offsetof(struct stat, st_birthtime): sqlite3_result_int64(context, sb.st_birthtime); break; case offsetof(struct stat, st_birthtimensec): sqlite3_result_int64(context, sb.st_birthtimensec); break; default: assert(0); } } static void inode_strmode( sqlite3_context* context, int argc, sqlite3_value** argv ) { char bp[12]; mode_t mode = sqlite3_value_int(argv[0]); strmode(mode, bp); sqlite3_result_text(context, bp, -1, SQLITE_TRANSIENT); } struct func_desc_t { const char *name; int narg; void *papp; void (*xfunc)(sqlite3_context*,int,sqlite3_value**); void (*xstep)(sqlite3_context*,int,sqlite3_value**); void (*xfinal)(sqlite3_context*); }; /* * functions implemented */ static struct func_desc_t functions[] = { { "stat", 2, NULL, inode_stat, NULL, NULL } , { "strmode", 1, NULL, inode_strmode, NULL, NULL } }; static size_t nfunctions = sizeof(functions)/sizeof(functions[0]); enum { eTextRep=SQLITE_UTF8 }; /* * Register functions and the virtual table. */ static int readdirRegister(sqlite3 *db) { int erc = SQLITE_OK; struct func_desc_t *pf; for( pf=functions; pf < functions + nfunctions; pf++ ) { erc = sqlite3_create_function( db, pf->name, pf->narg, eTextRep, pf->papp, pf->xfunc, pf->xstep, pf->xfinal ); if( erc != SQLITE_OK ) { sqlite3_log(erc, "error loading UDF '%s'", pf->name); return erc; } } if( erc == SQLITE_OK ) { erc = sqlite3_create_module(db, "readdir", &readdirModule, 0); } return erc; } /* * Extension load function. */ int sqlite3_extension_init( sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi ) { SQLITE_EXTENSION_INIT2(pApi); return readdirRegister(db); }