1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-30 19:03:16 +03:00

Have the writefile() function optionally set the modification-time of the

files it writes or creates. And many small fixes to the new code on this
branch.

FossilOrigin-Name: 7b51269caebe1492885fe9b965892f49a3f8bdb1d666b0203d594c30f9e83938
This commit is contained in:
dan
2017-12-14 19:15:07 +00:00
parent 0d0547fec6
commit ac15e2d7cc
5 changed files with 177 additions and 83 deletions

View File

@ -11,21 +11,67 @@
******************************************************************************
**
** This SQLite extension implements SQL functions readfile() and
** writefile().
** writefile(), and eponymous virtual type "fsdir".
**
** Also, an eponymous virtual table type "fsdir". Used as follows:
** WRITEFILE(FILE, DATA [, MODE [, MTIME]]):
**
** SELECT * FROM fsdir($dirname);
** 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.
**
** Returns one row for each entry in the directory $dirname. No row is
** returned for "." or "..". Row columns are as follows:
** 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:
**
** 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.
** 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 three 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.
*/
#include "sqlite3ext.h"
SQLITE_EXTENSION_INIT1
@ -45,6 +91,10 @@ SQLITE_EXTENSION_INIT1
#define FSDIR_SCHEMA "(name,mode,mtime,data,path HIDDEN,dir HIDDEN)"
/*
** Set the result stored by context ctx to a blob containing the
** contents of file zName.
*/
static void readFileContents(sqlite3_context *ctx, const char *zName){
FILE *in;
long nIn;
@ -81,6 +131,10 @@ static void readfileFunc(
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;
@ -138,11 +192,16 @@ static int makeDirectory(
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,
const char *zFile,
mode_t mode,
sqlite3_value *pData
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( S_ISLNK(mode) ){
const char *zTo = (const char*)sqlite3_value_text(pData);
@ -178,22 +237,30 @@ static int writeFile(
}
}
fclose(out);
if( rc==0 && chmod(zFile, mode & 0777) ){
if( rc==0 && mode && chmod(zFile, mode & 0777) ){
rc = 1;
}
if( rc ) return 2;
sqlite3_result_int64(pCtx, nWrite);
}
}
if( mtime>=0 ){
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;
}
}
return 0;
}
/*
** Implementation of the "writefile(W,X[,Y]])" SQL function.
**
** The argument X is written into file W. 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.
** 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,
@ -203,8 +270,9 @@ static void writefileFunc(
const char *zFile;
mode_t mode = 0;
int res;
sqlite3_int64 mtime = -1;
if( argc<2 || argc>3 ){
if( argc<2 || argc>4 ){
sqlite3_result_error(context,
"wrong number of arguments to function writefile()", -1
);
@ -214,18 +282,20 @@ static void writefileFunc(
zFile = (const char*)sqlite3_value_text(argv[0]);
if( zFile==0 ) return;
if( argc>=3 ){
sqlite3_result_int(context, 0);
mode = sqlite3_value_int(argv[2]);
}
if( argc==4 ){
mtime = sqlite3_value_int64(argv[3]);
}
res = writeFile(context, zFile, mode, argv[1]);
res = writeFile(context, zFile, argv[1], mode, mtime);
if( res==1 && errno==ENOENT ){
if( makeDirectory(zFile, mode)==SQLITE_OK ){
res = writeFile(context, zFile, mode, argv[1]);
res = writeFile(context, zFile, argv[1], mode, mtime);
}
}
if( res!=0 ){
if( argc>2 && res!=0 ){
if( S_ISLNK(mode) ){
ctxErrorMsg(context, "failed to create symlink: %s", zFile);
}else if( S_ISDIR(mode) ){
@ -313,6 +383,10 @@ static int fsdirOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
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++){
@ -341,6 +415,10 @@ static int fsdirClose(sqlite3_vtab_cursor *cur){
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);
@ -586,6 +664,9 @@ static int fsdirBestIndex(
return SQLITE_OK;
}
/*
** Register the "fsdir" virtual table.
*/
static int fsdirRegister(sqlite3 *db){
static sqlite3_module fsdirModule = {
0, /* iVersion */
@ -611,9 +692,6 @@ static int fsdirRegister(sqlite3 *db){
};
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 */

View File

@ -1,5 +1,5 @@
C Improve\serror\sand\susage\smessages\soutput\sby\sthe\sshell\s".ar"\scommand.
D 2017-12-14T15:40:42.931
C Have\sthe\swritefile()\sfunction\soptionally\sset\sthe\smodification-time\sof\sthe\nfiles\sit\swrites\sor\screates.\sAnd\smany\ssmall\sfixes\sto\sthe\snew\scode\son\sthis\nbranch.
D 2017-12-14T19:15:07.381
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F Makefile.in 6a879cbf01e37f9eac131414955f71774b566502d9a57ded1b8585b507503cb8
@ -270,7 +270,7 @@ F ext/misc/compress.c 122faa92d25033d6c3f07c39231de074ab3d2e83
F ext/misc/csv.c 1a009b93650732e22334edc92459c4630b9fa703397cbb3c8ca279921a36ca11
F ext/misc/dbdump.c 3509fa6b8932d04e932d6b6b827b6a82ca362781b8e8f3c77336f416793e215e
F ext/misc/eval.c f971962e92ebb8b0a4e6b62949463ee454d88fa2
F ext/misc/fileio.c 29b7fc94752fff6245cf4a81455f98cf6778ec1102ca7e67bf693d41a7db4307
F ext/misc/fileio.c 014152d4133e7b29eab8eb39d0c640659c23a6d23d882b4778f487ae7d1a457b
F ext/misc/fuzzer.c 7c64b8197bb77b7d64eff7cac7848870235d4c25
F ext/misc/ieee754.c f190d0cc5182529acb15babd177781be1ac1718c
F ext/misc/json1.c dbe086615b9546c156bf32b9378fc09383b58bd17513b866cfd24c1e15281984
@ -475,7 +475,7 @@ F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384
F src/resolve.c bbee7e31d369a18a2f4836644769882e9c5d40ef4a3af911db06410b65cb3730
F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac
F src/select.c 17e220191860a64a18c084141e1a8b7309e166a6f2d42c02021af27ea080d157
F src/shell.c.in 4bdd2efe722005180365698f2a3de51e22ae1e9bb61c868006bc8f2c5e02eb98
F src/shell.c.in 074b2129559a0aa712a367317f7e7daf4740925ec2c123b529800628eb10dc73
F src/sqlite.h.in 364515dd186285f3c01f5cab42e7db7edc47c70e87b6a25de389a2e6b8c413fd
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
F src/sqlite3ext.h c02d628cca67f3889c689d82d25c3eb45e2c155db08e4c6089b5840d64687d34
@ -1215,7 +1215,7 @@ F test/shell4.test 89ad573879a745974ff2df20ff97c5d6ffffbd5d
F test/shell5.test 23939a4c51f0421330ea61dbd3c74f9c215f5f8d3d1a94846da6ffc777a35458
F test/shell6.test 1ceb51b2678c472ba6cf1e5da96679ce8347889fe2c3bf93a0e0fa73f00b00d3
F test/shell7.test 115132f66d0463417f408562cc2cf534f6bbc6d83a6d50f0072a9eb171bae97f
F test/shell8.test 0f7dfc5b33bde7143df8e37cbb4ae6ccc7e91f87232dc8e5e02be03117cdebb8
F test/shell8.test 96f35965fe84d633fb2338696f5cbc1bcf6bdbdd79677244bc617a8452851dc7
F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3
F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5
F test/shrink.test 1b4330b1fd9e818c04726d45cb28db73087535ce
@ -1683,7 +1683,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P 803156cba8b056a1cb8d1bb186a57454afe72341abe7de1dfe529234c3415cd2
R 69895ee1194373ab4ba11578d798fe26
P b9d2d5d97291bf3d1392232e3705cca89dc7b918db2b08067b2b013ea39320e0
R 975f9981c88e18e3ddfa1fe4bb6e7fae
U dan
Z 5ea750b0cf239f9442948a3eda934166
Z 1e84576de6ef44efce8abf1d868c1119

View File

@ -1 +1 @@
b9d2d5d97291bf3d1392232e3705cca89dc7b918db2b08067b2b013ea39320e0
7b51269caebe1492885fe9b965892f49a3f8bdb1d666b0203d594c30f9e83938

View File

@ -4092,23 +4092,25 @@ static void shellPrepare(
}
}
static void shellPrepare2(
static void shellPreparePrintf(
sqlite3 *db,
int *pRc,
const char *zSql,
const char *zTail,
sqlite3_stmt **ppStmt
sqlite3_stmt **ppStmt,
const char *zFmt,
...
){
if( *pRc==SQLITE_OK && zTail ){
char *z = sqlite3_mprintf("%s %s", zSql, zTail);
*ppStmt = 0;
if( *pRc==SQLITE_OK ){
va_list ap;
char *z;
va_start(ap, zFmt);
z = sqlite3_vmprintf(zFmt, ap);
if( z==0 ){
*pRc = SQLITE_NOMEM;
}else{
shellPrepare(db, pRc, z, ppStmt);
sqlite3_free(z);
}
}else{
shellPrepare(db, pRc, zSql, ppStmt);
}
}
@ -4429,23 +4431,27 @@ static int arCheckEntries(sqlite3 *db, ArCommand *pAr){
static void arWhereClause(
int *pRc,
ArCommand *pAr,
char **pzWhere /* OUT: New WHERE clause (or NULL) */
char **pzWhere /* OUT: New WHERE clause */
){
char *zWhere = 0;
if( *pRc==SQLITE_OK ){
int i;
const char *zSep = "WHERE ";
for(i=0; i<pAr->nArg; i++){
const char *z = pAr->azArg[i];
zWhere = sqlite3_mprintf(
"%z%s name = '%q' OR name BETWEEN '%q/' AND '%q0'",
zWhere, zSep, z, z, z
);
if( zWhere==0 ){
*pRc = SQLITE_NOMEM;
break;
if( pAr->nArg==0 ){
zWhere = sqlite3_mprintf("1");
}else{
int i;
const char *zSep = "";
for(i=0; i<pAr->nArg; i++){
const char *z = pAr->azArg[i];
zWhere = sqlite3_mprintf(
"%z%s name = '%q' OR name BETWEEN '%q/' AND '%q0'",
zWhere, zSep, z, z, z
);
if( zWhere==0 ){
*pRc = SQLITE_NOMEM;
break;
}
zSep = " OR ";
}
zSep = " OR ";
}
}
*pzWhere = zWhere;
@ -4455,7 +4461,7 @@ static void arWhereClause(
** Implementation of .ar "lisT" command.
*/
static int arListCommand(ShellState *p, sqlite3 *db, ArCommand *pAr){
const char *zSql = "SELECT name FROM sqlar";
const char *zSql = "SELECT name FROM sqlar WHERE %s";
char *zWhere = 0;
sqlite3_stmt *pSql = 0;
int rc;
@ -4463,7 +4469,7 @@ static int arListCommand(ShellState *p, sqlite3 *db, ArCommand *pAr){
rc = arCheckEntries(db, pAr);
arWhereClause(&rc, pAr, &zWhere);
shellPrepare2(db, &rc, zSql, zWhere, &pSql);
shellPreparePrintf(db, &rc, &pSql, zSql, zWhere);
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
raw_printf(p->out, "%s\n", sqlite3_column_text(pSql, 0));
}
@ -4482,14 +4488,14 @@ static int arExtractCommand(ShellState *p, sqlite3 *db, ArCommand *pAr){
"ELSE"
" data "
"END, "
"mode) FROM sqlar";
const char *zSql2 = "SELECT :1 || name, mtime FROM sqlar";
"mode, mtime) FROM sqlar WHERE (%s) AND (data IS NULL OR :2 = 0)";
struct timespec times[2];
sqlite3_stmt *pSql = 0;
int rc = SQLITE_OK;
char *zDir = 0;
char *zWhere = 0;
int i;
/* If arguments are specified, check that they actually exist within
** the archive before proceeding. And formulate a WHERE clause to
@ -4509,31 +4515,26 @@ static int arExtractCommand(ShellState *p, sqlite3 *db, ArCommand *pAr){
memset(times, 0, sizeof(times));
times[0].tv_sec = time(0);
shellPrepare2(db, &rc, zSql1, zWhere, &pSql);
shellPreparePrintf(db, &rc, &pSql, zSql1, zWhere);
if( rc==SQLITE_OK ){
sqlite3_bind_text(pSql, 1, zDir, -1, SQLITE_STATIC);
}
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
if( pAr->bVerbose ){
raw_printf(p->out, "%s\n", sqlite3_column_text(pSql, 0));
}
}
shellFinalize(&rc, pSql);
shellPrepare2(db, &rc, zSql2, zWhere, &pSql);
if( rc==SQLITE_OK ){
sqlite3_bind_text(pSql, 1, zDir, -1, SQLITE_STATIC);
}
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
const char *zPath = (const char*)sqlite3_column_text(pSql, 0);
times[1].tv_sec = (time_t)sqlite3_column_int64(pSql, 1);
if( utimensat(AT_FDCWD, zPath, times, AT_SYMLINK_NOFOLLOW) ){
raw_printf(stderr, "failed to set timestamp for %s\n", zPath);
rc = SQLITE_ERROR;
break;
/* Run the SELECT statement twice. The first time, writefile() is called
** for all archive members that should be extracted. The second time,
** only for the directories. This is because the timestamps for
** extracted directories must be reset after they are populated (as
** populating them changes the timestamp). */
for(i=0; i<2; i++){
sqlite3_bind_int(pSql, 2, i);
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
if( i==0 && pAr->bVerbose ){
raw_printf(p->out, "%s\n", sqlite3_column_text(pSql, 0));
}
}
shellReset(&rc, pSql);
}
shellFinalize(&rc, pSql);
}
shellFinalize(&rc, pSql);
sqlite3_free(zDir);
sqlite3_free(zWhere);

View File

@ -139,6 +139,21 @@ foreach {tn tcl} {
catchcmd ":memory:" $x3
dir_to_list ar3
} $expected
# This is a repeat of test 1.$tn.1, except that there is a 2 second
# pause between creating the archive and extracting its contents.
# This is to test that timestamps are set correctly.
#
# Because it is slow, only do this for $tn==1.
if {$tn==1} {
do_test 1.$tn.1 {
catchcmd test_ar.db $c1
file delete -force ar1
after 2000
catchcmd test_ar.db $x1
dir_to_list ar1
} $expected
}
}
finish_test