123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047 |
- /*
- ** 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(), and eponymous virtual type "fsdir".
- **
- ** WRITEFILE(FILE, DATA [, MODE [, MTIME]]):
- **
- ** If neither of the optional arguments is present, then this UDF
- ** function writes blob DATA to file FILE. If successful, the number
- ** of bytes written is returned. If an error occurs, NULL is returned.
- **
- ** If the first option argument - MODE - is present, then it must
- ** be passed an integer value that corresponds to a POSIX mode
- ** value (file type + permissions, as returned in the stat.st_mode
- ** field by the stat() system call). Three types of files may
- ** be written/created:
- **
- ** regular files: (mode & 0170000)==0100000
- ** symbolic links: (mode & 0170000)==0120000
- ** directories: (mode & 0170000)==0040000
- **
- ** For a directory, the DATA is ignored. For a symbolic link, it is
- ** interpreted as text and used as the target of the link. For a
- ** regular file, it is interpreted as a blob and written into the
- ** named file. Regardless of the type of file, its permissions are
- ** set to (mode & 0777) before returning.
- **
- ** If the optional MTIME argument is present, then it is interpreted
- ** as an integer - the number of seconds since the unix epoch. The
- ** modification-time of the target file is set to this value before
- ** returning.
- **
- ** If five or more arguments are passed to this function and an
- ** error is encountered, an exception is raised.
- **
- ** READFILE(FILE):
- **
- ** Read and return the contents of file FILE (type blob) from disk.
- **
- ** FSDIR:
- **
- ** Used as follows:
- **
- ** SELECT * FROM fsdir($path [, $dir]);
- **
- ** Parameter $path is an absolute or relative pathname. If the file that it
- ** refers to does not exist, it is an error. If the path refers to a regular
- ** file or symbolic link, it returns a single row. Or, if the path refers
- ** to a directory, it returns one row for the directory, and one row for each
- ** file within the hierarchy rooted at $path.
- **
- ** Each row has the following columns:
- **
- ** name: Path to file or directory (text value).
- ** mode: Value of stat.st_mode for directory entry (an integer).
- ** mtime: Value of stat.st_mtime for directory entry (an integer).
- ** 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.
- **
- ** If a non-NULL value is specified for the optional $dir parameter and
- ** $path is a relative path, then $path is interpreted relative to $dir.
- ** And the paths returned in the "name" column of the table are also
- ** relative to directory $dir.
- **
- ** Notes on building this extension for Windows:
- ** Unless linked statically with the SQLite library, a preprocessor
- ** symbol, FILEIO_WIN32_DLL, must be #define'd to create a stand-alone
- ** DLL form of this extension for WIN32. See its use below for details.
- */
- #include "sqlite3ext.h"
- SQLITE_EXTENSION_INIT1
- #include <stdio.h>
- #include <string.h>
- #include <assert.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #if !defined(_WIN32) && !defined(WIN32)
- # include <unistd.h>
- # include <dirent.h>
- # include <utime.h>
- # include <sys/time.h>
- #else
- # include "windows.h"
- # include <io.h>
- # include <direct.h>
- # include "test_windirent.h"
- # define dirent DIRENT
- # ifndef chmod
- # define chmod _chmod
- # endif
- # ifndef stat
- # define stat _stat
- # endif
- # define mkdir(path,mode) _mkdir(path)
- # define lstat(path,buf) stat(path,buf)
- #endif
- #include <time.h>
- #include <errno.h>
- /* When used as part of the CLI, the sqlite3_stdio.h module will have
- ** been included before this one. In that case use the sqlite3_stdio.h
- ** #defines. If not, create our own for fopen().
- */
- #ifndef _SQLITE3_STDIO_H_
- # define sqlite3_fopen fopen
- #endif
- /*
- ** Structure of the fsdir() table-valued function
- */
- /* 0 1 2 3 4 5 */
- #define FSDIR_SCHEMA "(name,mode,mtime,data,path HIDDEN,dir HIDDEN)"
- #define FSDIR_COLUMN_NAME 0 /* Name of the file */
- #define FSDIR_COLUMN_MODE 1 /* Access mode */
- #define FSDIR_COLUMN_MTIME 2 /* Last modification time */
- #define FSDIR_COLUMN_DATA 3 /* File content */
- #define FSDIR_COLUMN_PATH 4 /* Path to top of search */
- #define FSDIR_COLUMN_DIR 5 /* Path is relative to this directory */
- /*
- ** Set the result stored by context ctx to a blob containing the
- ** contents of file zName. Or, leave the result unchanged (NULL)
- ** if the file does not exist or is unreadable.
- **
- ** If the file exceeds the SQLite blob size limit, through an
- ** SQLITE_TOOBIG error.
- **
- ** Throw an SQLITE_IOERR if there are difficulties pulling the file
- ** off of disk.
- */
- static void readFileContents(sqlite3_context *ctx, const char *zName){
- FILE *in;
- sqlite3_int64 nIn;
- void *pBuf;
- sqlite3 *db;
- int mxBlob;
- in = sqlite3_fopen(zName, "rb");
- if( in==0 ){
- /* File does not exist or is unreadable. Leave the result set to NULL. */
- return;
- }
- fseek(in, 0, SEEK_END);
- nIn = ftell(in);
- rewind(in);
- db = sqlite3_context_db_handle(ctx);
- mxBlob = sqlite3_limit(db, SQLITE_LIMIT_LENGTH, -1);
- if( nIn>mxBlob ){
- sqlite3_result_error_code(ctx, SQLITE_TOOBIG);
- fclose(in);
- return;
- }
- pBuf = sqlite3_malloc64( nIn ? nIn : 1 );
- if( pBuf==0 ){
- sqlite3_result_error_nomem(ctx);
- fclose(in);
- return;
- }
- if( nIn==(sqlite3_int64)fread(pBuf, 1, (size_t)nIn, in) ){
- sqlite3_result_blob64(ctx, pBuf, nIn, sqlite3_free);
- }else{
- sqlite3_result_error_code(ctx, SQLITE_IOERR);
- 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);
- }
- /*
- ** Set the error message contained in context ctx to the results of
- ** vprintf(zFmt, ...).
- */
- static void ctxErrorMsg(sqlite3_context *ctx, const char *zFmt, ...){
- char *zMsg = 0;
- va_list ap;
- va_start(ap, zFmt);
- zMsg = sqlite3_vmprintf(zFmt, ap);
- sqlite3_result_error(ctx, zMsg, -1);
- sqlite3_free(zMsg);
- va_end(ap);
- }
- #if defined(_WIN32)
- /*
- ** This function is designed to convert a Win32 FILETIME structure into the
- ** number of seconds since the Unix Epoch (1970-01-01 00:00:00 UTC).
- */
- static sqlite3_uint64 fileTimeToUnixTime(
- LPFILETIME pFileTime
- ){
- SYSTEMTIME epochSystemTime;
- ULARGE_INTEGER epochIntervals;
- FILETIME epochFileTime;
- ULARGE_INTEGER fileIntervals;
- memset(&epochSystemTime, 0, sizeof(SYSTEMTIME));
- epochSystemTime.wYear = 1970;
- epochSystemTime.wMonth = 1;
- epochSystemTime.wDay = 1;
- SystemTimeToFileTime(&epochSystemTime, &epochFileTime);
- epochIntervals.LowPart = epochFileTime.dwLowDateTime;
- epochIntervals.HighPart = epochFileTime.dwHighDateTime;
- fileIntervals.LowPart = pFileTime->dwLowDateTime;
- fileIntervals.HighPart = pFileTime->dwHighDateTime;
- return (fileIntervals.QuadPart - epochIntervals.QuadPart) / 10000000;
- }
- #if defined(FILEIO_WIN32_DLL) && (defined(_WIN32) || defined(WIN32))
- # /* To allow a standalone DLL, use this next replacement function: */
- # undef sqlite3_win32_utf8_to_unicode
- # define sqlite3_win32_utf8_to_unicode utf8_to_utf16
- #
- LPWSTR utf8_to_utf16(const char *z){
- int nAllot = MultiByteToWideChar(CP_UTF8, 0, z, -1, NULL, 0);
- LPWSTR rv = sqlite3_malloc(nAllot * sizeof(WCHAR));
- if( rv!=0 && 0 < MultiByteToWideChar(CP_UTF8, 0, z, -1, rv, nAllot) )
- return rv;
- sqlite3_free(rv);
- return 0;
- }
- #endif
- /*
- ** This function attempts to normalize the time values found in the stat()
- ** buffer to UTC. This is necessary on Win32, where the runtime library
- ** appears to return these values as local times.
- */
- static void statTimesToUtc(
- const char *zPath,
- struct stat *pStatBuf
- ){
- HANDLE hFindFile;
- WIN32_FIND_DATAW fd;
- LPWSTR zUnicodeName;
- extern LPWSTR sqlite3_win32_utf8_to_unicode(const char*);
- zUnicodeName = sqlite3_win32_utf8_to_unicode(zPath);
- if( zUnicodeName ){
- memset(&fd, 0, sizeof(WIN32_FIND_DATAW));
- hFindFile = FindFirstFileW(zUnicodeName, &fd);
- if( hFindFile!=NULL ){
- pStatBuf->st_ctime = (time_t)fileTimeToUnixTime(&fd.ftCreationTime);
- pStatBuf->st_atime = (time_t)fileTimeToUnixTime(&fd.ftLastAccessTime);
- pStatBuf->st_mtime = (time_t)fileTimeToUnixTime(&fd.ftLastWriteTime);
- FindClose(hFindFile);
- }
- sqlite3_free(zUnicodeName);
- }
- }
- #endif
- /*
- ** This function is used in place of stat(). On Windows, special handling
- ** is required in order for the included time to be returned as UTC. On all
- ** other systems, this function simply calls stat().
- */
- static int fileStat(
- const char *zPath,
- struct stat *pStatBuf
- ){
- #if defined(_WIN32)
- int rc = stat(zPath, pStatBuf);
- if( rc==0 ) statTimesToUtc(zPath, pStatBuf);
- return rc;
- #else
- return stat(zPath, pStatBuf);
- #endif
- }
- /*
- ** This function is used in place of lstat(). On Windows, special handling
- ** is required in order for the included time to be returned as UTC. On all
- ** other systems, this function simply calls lstat().
- */
- static int fileLinkStat(
- const char *zPath,
- struct stat *pStatBuf
- ){
- #if defined(_WIN32)
- int rc = lstat(zPath, pStatBuf);
- if( rc==0 ) statTimesToUtc(zPath, pStatBuf);
- return rc;
- #else
- return lstat(zPath, pStatBuf);
- #endif
- }
- /*
- ** Argument zFile is the name of a file that will be created and/or written
- ** by SQL function writefile(). This function ensures that the directory
- ** zFile will be written to exists, creating it if required. The permissions
- ** for any path components created by this function are set in accordance
- ** with the current umask.
- **
- ** If an OOM condition is encountered, SQLITE_NOMEM is returned. Otherwise,
- ** SQLITE_OK is returned if the directory is successfully created, or
- ** SQLITE_ERROR otherwise.
- */
- static int makeDirectory(
- const char *zFile
- ){
- char *zCopy = sqlite3_mprintf("%s", zFile);
- int rc = SQLITE_OK;
- if( zCopy==0 ){
- rc = SQLITE_NOMEM;
- }else{
- int nCopy = (int)strlen(zCopy);
- int i = 1;
- while( rc==SQLITE_OK ){
- struct stat sStat;
- int rc2;
- for(; zCopy[i]!='/' && i<nCopy; i++);
- if( i==nCopy ) break;
- zCopy[i] = '\0';
- rc2 = fileStat(zCopy, &sStat);
- if( rc2!=0 ){
- if( mkdir(zCopy, 0777) ) rc = SQLITE_ERROR;
- }else{
- if( !S_ISDIR(sStat.st_mode) ) rc = SQLITE_ERROR;
- }
- zCopy[i] = '/';
- i++;
- }
- sqlite3_free(zCopy);
- }
- return rc;
- }
- /*
- ** This function does the work for the writefile() UDF. Refer to
- ** header comments at the top of this file for details.
- */
- static int writeFile(
- sqlite3_context *pCtx, /* Context to return bytes written in */
- const char *zFile, /* File to write */
- sqlite3_value *pData, /* Data to write */
- mode_t mode, /* MODE parameter passed to writefile() */
- sqlite3_int64 mtime /* MTIME parameter (or -1 to not set time) */
- ){
- if( zFile==0 ) return 1;
- #if !defined(_WIN32) && !defined(WIN32)
- if( S_ISLNK(mode) ){
- const char *zTo = (const char*)sqlite3_value_text(pData);
- if( zTo==0 ) return 1;
- unlink(zFile);
- if( symlink(zTo, zFile)<0 ) return 1;
- }else
- #endif
- {
- if( S_ISDIR(mode) ){
- if( mkdir(zFile, mode) ){
- /* The mkdir() call to create the directory failed. This might not
- ** be an error though - if there is already a directory at the same
- ** path and either the permissions already match or can be changed
- ** to do so using chmod(), it is not an error. */
- struct stat sStat;
- if( errno!=EEXIST
- || 0!=fileStat(zFile, &sStat)
- || !S_ISDIR(sStat.st_mode)
- || ((sStat.st_mode&0777)!=(mode&0777) && 0!=chmod(zFile, mode&0777))
- ){
- return 1;
- }
- }
- }else{
- sqlite3_int64 nWrite = 0;
- const char *z;
- int rc = 0;
- FILE *out = sqlite3_fopen(zFile, "wb");
- if( out==0 ) return 1;
- z = (const char*)sqlite3_value_blob(pData);
- if( z ){
- sqlite3_int64 n = fwrite(z, 1, sqlite3_value_bytes(pData), out);
- nWrite = sqlite3_value_bytes(pData);
- if( nWrite!=n ){
- rc = 1;
- }
- }
- fclose(out);
- if( rc==0 && mode && chmod(zFile, mode & 0777) ){
- rc = 1;
- }
- if( rc ) return 2;
- sqlite3_result_int64(pCtx, nWrite);
- }
- }
- if( mtime>=0 ){
- #if defined(_WIN32)
- #if !SQLITE_OS_WINRT
- /* Windows */
- FILETIME lastAccess;
- FILETIME lastWrite;
- SYSTEMTIME currentTime;
- LONGLONG intervals;
- HANDLE hFile;
- LPWSTR zUnicodeName;
- extern LPWSTR sqlite3_win32_utf8_to_unicode(const char*);
- GetSystemTime(¤tTime);
- SystemTimeToFileTime(¤tTime, &lastAccess);
- intervals = Int32x32To64(mtime, 10000000) + 116444736000000000;
- lastWrite.dwLowDateTime = (DWORD)intervals;
- lastWrite.dwHighDateTime = intervals >> 32;
- zUnicodeName = sqlite3_win32_utf8_to_unicode(zFile);
- if( zUnicodeName==0 ){
- return 1;
- }
- hFile = CreateFileW(
- zUnicodeName, FILE_WRITE_ATTRIBUTES, 0, NULL, OPEN_EXISTING,
- FILE_FLAG_BACKUP_SEMANTICS, NULL
- );
- sqlite3_free(zUnicodeName);
- if( hFile!=INVALID_HANDLE_VALUE ){
- BOOL bResult = SetFileTime(hFile, NULL, &lastAccess, &lastWrite);
- CloseHandle(hFile);
- return !bResult;
- }else{
- return 1;
- }
- #endif
- #elif defined(AT_FDCWD) && 0 /* utimensat() is not universally available */
- /* Recent unix */
- struct timespec times[2];
- times[0].tv_nsec = times[1].tv_nsec = 0;
- times[0].tv_sec = time(0);
- times[1].tv_sec = mtime;
- if( utimensat(AT_FDCWD, zFile, times, AT_SYMLINK_NOFOLLOW) ){
- return 1;
- }
- #else
- /* Legacy unix.
- **
- ** Do not use utimes() on a symbolic link - it sees through the link and
- ** modifies the timestamps on the target. Or fails if the target does
- ** not exist. */
- if( 0==S_ISLNK(mode) ){
- struct timeval times[2];
- times[0].tv_usec = times[1].tv_usec = 0;
- times[0].tv_sec = time(0);
- times[1].tv_sec = mtime;
- if( utimes(zFile, times) ){
- return 1;
- }
- }
- #endif
- }
- return 0;
- }
- /*
- ** Implementation of the "writefile(W,X[,Y[,Z]]])" SQL function.
- ** Refer to header comments at the top of this file for details.
- */
- static void writefileFunc(
- sqlite3_context *context,
- int argc,
- sqlite3_value **argv
- ){
- const char *zFile;
- mode_t mode = 0;
- int res;
- sqlite3_int64 mtime = -1;
- if( argc<2 || argc>4 ){
- sqlite3_result_error(context,
- "wrong number of arguments to function writefile()", -1
- );
- return;
- }
- zFile = (const char*)sqlite3_value_text(argv[0]);
- if( zFile==0 ) return;
- if( argc>=3 ){
- mode = (mode_t)sqlite3_value_int(argv[2]);
- }
- if( argc==4 ){
- mtime = sqlite3_value_int64(argv[3]);
- }
- res = writeFile(context, zFile, argv[1], mode, mtime);
- if( res==1 && errno==ENOENT ){
- if( makeDirectory(zFile)==SQLITE_OK ){
- res = writeFile(context, zFile, argv[1], mode, mtime);
- }
- }
- if( argc>2 && res!=0 ){
- if( S_ISLNK(mode) ){
- ctxErrorMsg(context, "failed to create symlink: %s", zFile);
- }else if( S_ISDIR(mode) ){
- ctxErrorMsg(context, "failed to create directory: %s", zFile);
- }else{
- ctxErrorMsg(context, "failed to write file: %s", zFile);
- }
- }
- }
- /*
- ** SQL function: lsmode(MODE)
- **
- ** Given a numberic st_mode from stat(), convert it into a human-readable
- ** text string in the style of "ls -l".
- */
- static void lsModeFunc(
- sqlite3_context *context,
- int argc,
- sqlite3_value **argv
- ){
- int i;
- int iMode = sqlite3_value_int(argv[0]);
- char z[16];
- (void)argc;
- if( S_ISLNK(iMode) ){
- z[0] = 'l';
- }else if( S_ISREG(iMode) ){
- z[0] = '-';
- }else if( S_ISDIR(iMode) ){
- z[0] = 'd';
- }else{
- z[0] = '?';
- }
- for(i=0; i<3; i++){
- int m = (iMode >> ((2-i)*3));
- char *a = &z[1 + i*3];
- a[0] = (m & 0x4) ? 'r' : '-';
- a[1] = (m & 0x2) ? 'w' : '-';
- a[2] = (m & 0x1) ? 'x' : '-';
- }
- z[10] = '\0';
- sqlite3_result_text(context, z, -1, SQLITE_TRANSIENT);
- }
- #ifndef SQLITE_OMIT_VIRTUALTABLE
- /*
- ** Cursor type for recursively iterating through a directory structure.
- */
- typedef struct fsdir_cursor fsdir_cursor;
- typedef struct FsdirLevel FsdirLevel;
- struct FsdirLevel {
- DIR *pDir; /* From opendir() */
- char *zDir; /* Name of directory (nul-terminated) */
- };
- struct fsdir_cursor {
- sqlite3_vtab_cursor base; /* Base class - must be first */
- int nLvl; /* Number of entries in aLvl[] array */
- int iLvl; /* Index of current entry */
- FsdirLevel *aLvl; /* Hierarchy of directories being traversed */
- const char *zBase;
- int nBase;
- struct stat sStat; /* Current lstat() results */
- char *zPath; /* Path to current entry */
- sqlite3_int64 iRowid; /* Current rowid */
- };
- typedef struct fsdir_tab fsdir_tab;
- struct fsdir_tab {
- sqlite3_vtab base; /* Base class - must be first */
- };
- /*
- ** 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;
- (void)pAux;
- (void)argc;
- (void)argv;
- (void)pzErr;
- rc = sqlite3_declare_vtab(db, "CREATE TABLE x" FSDIR_SCHEMA);
- if( rc==SQLITE_OK ){
- pNew = (fsdir_tab*)sqlite3_malloc( sizeof(*pNew) );
- if( pNew==0 ) return SQLITE_NOMEM;
- memset(pNew, 0, sizeof(*pNew));
- sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY);
- }
- *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;
- (void)p;
- pCur = sqlite3_malloc( sizeof(*pCur) );
- if( pCur==0 ) return SQLITE_NOMEM;
- memset(pCur, 0, sizeof(*pCur));
- pCur->iLvl = -1;
- *ppCursor = &pCur->base;
- return SQLITE_OK;
- }
- /*
- ** Reset a cursor back to the state it was in when first returned
- ** by fsdirOpen().
- */
- static void fsdirResetCursor(fsdir_cursor *pCur){
- int i;
- for(i=0; i<=pCur->iLvl; i++){
- FsdirLevel *pLvl = &pCur->aLvl[i];
- if( pLvl->pDir ) closedir(pLvl->pDir);
- sqlite3_free(pLvl->zDir);
- }
- sqlite3_free(pCur->zPath);
- sqlite3_free(pCur->aLvl);
- pCur->aLvl = 0;
- pCur->zPath = 0;
- pCur->zBase = 0;
- pCur->nBase = 0;
- pCur->nLvl = 0;
- pCur->iLvl = -1;
- pCur->iRowid = 1;
- }
- /*
- ** Destructor for an fsdir_cursor.
- */
- static int fsdirClose(sqlite3_vtab_cursor *cur){
- fsdir_cursor *pCur = (fsdir_cursor*)cur;
- fsdirResetCursor(pCur);
- sqlite3_free(pCur);
- return SQLITE_OK;
- }
- /*
- ** Set the error message for the virtual table associated with cursor
- ** pCur to the results of vprintf(zFmt, ...).
- */
- 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);
- }
- /*
- ** Advance an fsdir_cursor to its next row of output.
- */
- static int fsdirNext(sqlite3_vtab_cursor *cur){
- fsdir_cursor *pCur = (fsdir_cursor*)cur;
- mode_t m = pCur->sStat.st_mode;
- pCur->iRowid++;
- if( S_ISDIR(m) ){
- /* Descend into this directory */
- int iNew = pCur->iLvl + 1;
- FsdirLevel *pLvl;
- if( iNew>=pCur->nLvl ){
- int nNew = iNew+1;
- sqlite3_int64 nByte = nNew*sizeof(FsdirLevel);
- FsdirLevel *aNew = (FsdirLevel*)sqlite3_realloc64(pCur->aLvl, nByte);
- if( aNew==0 ) return SQLITE_NOMEM;
- memset(&aNew[pCur->nLvl], 0, sizeof(FsdirLevel)*(nNew-pCur->nLvl));
- pCur->aLvl = aNew;
- pCur->nLvl = nNew;
- }
- pCur->iLvl = iNew;
- pLvl = &pCur->aLvl[iNew];
-
- pLvl->zDir = pCur->zPath;
- pCur->zPath = 0;
- pLvl->pDir = opendir(pLvl->zDir);
- if( pLvl->pDir==0 ){
- fsdirSetErrmsg(pCur, "cannot read directory: %s", pCur->zPath);
- return SQLITE_ERROR;
- }
- }
- while( pCur->iLvl>=0 ){
- FsdirLevel *pLvl = &pCur->aLvl[pCur->iLvl];
- struct dirent *pEntry = readdir(pLvl->pDir);
- if( pEntry ){
- if( pEntry->d_name[0]=='.' ){
- if( pEntry->d_name[1]=='.' && pEntry->d_name[2]=='\0' ) continue;
- if( pEntry->d_name[1]=='\0' ) continue;
- }
- sqlite3_free(pCur->zPath);
- pCur->zPath = sqlite3_mprintf("%s/%s", pLvl->zDir, pEntry->d_name);
- if( pCur->zPath==0 ) return SQLITE_NOMEM;
- if( fileLinkStat(pCur->zPath, &pCur->sStat) ){
- fsdirSetErrmsg(pCur, "cannot stat file: %s", pCur->zPath);
- return SQLITE_ERROR;
- }
- return SQLITE_OK;
- }
- closedir(pLvl->pDir);
- sqlite3_free(pLvl->zDir);
- pLvl->pDir = 0;
- pLvl->zDir = 0;
- pCur->iLvl--;
- }
- /* EOF */
- sqlite3_free(pCur->zPath);
- pCur->zPath = 0;
- 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 FSDIR_COLUMN_NAME: {
- sqlite3_result_text(ctx, &pCur->zPath[pCur->nBase], -1, SQLITE_TRANSIENT);
- break;
- }
- case FSDIR_COLUMN_MODE:
- sqlite3_result_int64(ctx, pCur->sStat.st_mode);
- break;
- case FSDIR_COLUMN_MTIME:
- sqlite3_result_int64(ctx, pCur->sStat.st_mtime);
- break;
- case FSDIR_COLUMN_DATA: {
- mode_t m = pCur->sStat.st_mode;
- if( S_ISDIR(m) ){
- sqlite3_result_null(ctx);
- #if !defined(_WIN32) && !defined(WIN32)
- }else if( S_ISLNK(m) ){
- char aStatic[64];
- char *aBuf = aStatic;
- sqlite3_int64 nBuf = 64;
- int n;
- while( 1 ){
- n = readlink(pCur->zPath, aBuf, nBuf);
- if( n<nBuf ) break;
- if( aBuf!=aStatic ) sqlite3_free(aBuf);
- nBuf = nBuf*2;
- aBuf = sqlite3_malloc64(nBuf);
- if( aBuf==0 ){
- sqlite3_result_error_nomem(ctx);
- return SQLITE_NOMEM;
- }
- }
- sqlite3_result_text(ctx, aBuf, n, SQLITE_TRANSIENT);
- if( aBuf!=aStatic ) sqlite3_free(aBuf);
- #endif
- }else{
- readFileContents(ctx, pCur->zPath);
- }
- }
- case FSDIR_COLUMN_PATH:
- default: {
- /* The FSDIR_COLUMN_PATH and FSDIR_COLUMN_DIR are input parameters.
- ** always return their values as NULL */
- break;
- }
- }
- 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->zPath==0);
- }
- /*
- ** xFilter callback.
- **
- ** idxNum==1 PATH parameter only
- ** idxNum==2 Both PATH and DIR supplied
- */
- 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;
- (void)idxStr;
- fsdirResetCursor(pCur);
- if( idxNum==0 ){
- fsdirSetErrmsg(pCur, "table function fsdir requires an argument");
- return SQLITE_ERROR;
- }
- assert( argc==idxNum && (argc==1 || argc==2) );
- zDir = (const char*)sqlite3_value_text(argv[0]);
- if( zDir==0 ){
- fsdirSetErrmsg(pCur, "table function fsdir requires a non-NULL argument");
- return SQLITE_ERROR;
- }
- if( argc==2 ){
- pCur->zBase = (const char*)sqlite3_value_text(argv[1]);
- }
- if( pCur->zBase ){
- pCur->nBase = (int)strlen(pCur->zBase)+1;
- pCur->zPath = sqlite3_mprintf("%s/%s", pCur->zBase, zDir);
- }else{
- pCur->zPath = sqlite3_mprintf("%s", zDir);
- }
- if( pCur->zPath==0 ){
- return SQLITE_NOMEM;
- }
- if( fileLinkStat(pCur->zPath, &pCur->sStat) ){
- fsdirSetErrmsg(pCur, "cannot stat file: %s", pCur->zPath);
- return SQLITE_ERROR;
- }
- return SQLITE_OK;
- }
- /*
- ** 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 values of idxNum:
- **
- ** (1) The path value is supplied by argv[0]
- ** (2) Path is in argv[0] and dir is in argv[1]
- */
- static int fsdirBestIndex(
- sqlite3_vtab *tab,
- sqlite3_index_info *pIdxInfo
- ){
- int i; /* Loop over constraints */
- int idxPath = -1; /* Index in pIdxInfo->aConstraint of PATH= */
- int idxDir = -1; /* Index in pIdxInfo->aConstraint of DIR= */
- int seenPath = 0; /* True if an unusable PATH= constraint is seen */
- int seenDir = 0; /* True if an unusable DIR= constraint is seen */
- const struct sqlite3_index_constraint *pConstraint;
- (void)tab;
- pConstraint = pIdxInfo->aConstraint;
- for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){
- if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue;
- switch( pConstraint->iColumn ){
- case FSDIR_COLUMN_PATH: {
- if( pConstraint->usable ){
- idxPath = i;
- seenPath = 0;
- }else if( idxPath<0 ){
- seenPath = 1;
- }
- break;
- }
- case FSDIR_COLUMN_DIR: {
- if( pConstraint->usable ){
- idxDir = i;
- seenDir = 0;
- }else if( idxDir<0 ){
- seenDir = 1;
- }
- break;
- }
- }
- }
- if( seenPath || seenDir ){
- /* If input parameters are unusable, disallow this plan */
- return SQLITE_CONSTRAINT;
- }
- if( idxPath<0 ){
- pIdxInfo->idxNum = 0;
- /* The pIdxInfo->estimatedCost should have been initialized to a huge
- ** number. Leave it unchanged. */
- pIdxInfo->estimatedRows = 0x7fffffff;
- }else{
- pIdxInfo->aConstraintUsage[idxPath].omit = 1;
- pIdxInfo->aConstraintUsage[idxPath].argvIndex = 1;
- if( idxDir>=0 ){
- pIdxInfo->aConstraintUsage[idxDir].omit = 1;
- pIdxInfo->aConstraintUsage[idxDir].argvIndex = 2;
- pIdxInfo->idxNum = 2;
- pIdxInfo->estimatedCost = 10.0;
- }else{
- pIdxInfo->idxNum = 1;
- pIdxInfo->estimatedCost = 100.0;
- }
- }
- return SQLITE_OK;
- }
- /*
- ** Register the "fsdir" virtual table.
- */
- 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 */
- 0, /* xSavepoint */
- 0, /* xRelease */
- 0, /* xRollbackTo */
- 0, /* xShadowName */
- 0 /* xIntegrity */
- };
- int rc = sqlite3_create_module(db, "fsdir", &fsdirModule, 0);
- 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|SQLITE_DIRECTONLY, 0,
- readfileFunc, 0, 0);
- if( rc==SQLITE_OK ){
- rc = sqlite3_create_function(db, "writefile", -1,
- SQLITE_UTF8|SQLITE_DIRECTONLY, 0,
- writefileFunc, 0, 0);
- }
- if( rc==SQLITE_OK ){
- rc = sqlite3_create_function(db, "lsmode", 1, SQLITE_UTF8, 0,
- lsModeFunc, 0, 0);
- }
- if( rc==SQLITE_OK ){
- rc = fsdirRegister(db);
- }
- return rc;
- }
- #if defined(FILEIO_WIN32_DLL) && (defined(_WIN32) || defined(WIN32))
- /* To allow a standalone DLL, make test_windirent.c use the same
- * redefined SQLite API calls as the above extension code does.
- * Just pull in this .c to accomplish this. As a beneficial side
- * effect, this extension becomes a single translation unit. */
- # include "test_windirent.c"
- #endif
|