599 lines
13 KiB
C++
599 lines
13 KiB
C++
/* ---------------------------------------------------------------------------
|
|
Nullsoft Database Engine
|
|
--------------------
|
|
codename: Near Death Experience
|
|
--------------------------------------------------------------------------- */
|
|
|
|
/* ---------------------------------------------------------------------------
|
|
|
|
Virtual File System
|
|
|
|
--------------------------------------------------------------------------- */
|
|
#include "../nde.h"
|
|
|
|
#include "vfs.h"
|
|
#include <malloc.h>
|
|
#ifndef EOF
|
|
#define EOF -1
|
|
#endif
|
|
#include <Sddl.h>
|
|
#include <strsafe.h>
|
|
|
|
#if defined(NDE_ALLOW_NONCACHED)
|
|
size_t ReadFileN(void *buffer, size_t size, VFILE *f)
|
|
{
|
|
uint8_t *b = (uint8_t *) buffer;
|
|
size_t total_size=0;
|
|
while (size)
|
|
{
|
|
DWORD bytesRead = 0;
|
|
DWORD toRead = min(0xffffffffUL, size);
|
|
ReadFile(f->hfile, b, toRead, &bytesRead, NULL);
|
|
if (bytesRead != toRead)
|
|
{
|
|
f->endoffile=true;
|
|
// TODO: rewind
|
|
return total_size+bytesRead;
|
|
}
|
|
size-=toRead;
|
|
b+=toRead;
|
|
total_size+=toRead;
|
|
}
|
|
return total_size;
|
|
}
|
|
|
|
size_t WriteFileN(void *buffer, size_t size, VFILE *f)
|
|
{
|
|
uint8_t *b = (uint8_t *) buffer;
|
|
size_t total_size=0;
|
|
while (size)
|
|
{
|
|
DWORD bytesRead = 0;
|
|
DWORD toRead = min(0xffffffffUL, size);
|
|
WriteFile(f->hfile, b, toRead, &bytesRead, NULL);
|
|
if (bytesRead != toRead)
|
|
{
|
|
f->endoffile=true;
|
|
// TODO: rewind
|
|
return total_size+bytesRead;
|
|
}
|
|
size-=toRead;
|
|
b+=toRead;
|
|
total_size+=toRead;
|
|
}
|
|
|
|
return total_size;
|
|
}
|
|
#endif
|
|
|
|
// benski> i havn't the slightest fucking clue why this works, it's copypasta code from the internets
|
|
static LPCWSTR LOW_INTEGRITY_SDDL_SACL_W = L"S:(ML;;NW;;;LW)";
|
|
static bool GetLowIntegrity(SECURITY_ATTRIBUTES *attributes)
|
|
{
|
|
PSECURITY_DESCRIPTOR pSD = NULL;
|
|
|
|
if ( ConvertStringSecurityDescriptorToSecurityDescriptorW (
|
|
LOW_INTEGRITY_SDDL_SACL_W, SDDL_REVISION_1, &pSD, NULL ) )
|
|
{
|
|
attributes->nLength = sizeof(SECURITY_ATTRIBUTES);
|
|
attributes->lpSecurityDescriptor = pSD;
|
|
attributes->bInheritHandle = FALSE;
|
|
|
|
//LocalFree ( pSD );
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
VFILE *Vfnew(const wchar_t *fl, const char *mode, BOOL Cached)
|
|
{
|
|
if (!fl) return NULL;
|
|
VFILE *f = (VFILE *)calloc(1, sizeof(VFILE));
|
|
if (!f) return NULL;
|
|
|
|
#ifdef NDE_ALLOW_NONCACHED
|
|
if (!Cached)
|
|
{
|
|
f->r.reserve(256); // heuristically determined
|
|
}
|
|
f->hfile = INVALID_HANDLE_VALUE;
|
|
#endif
|
|
|
|
#ifndef NO_TABLE_WIN32_LOCKING
|
|
// TODO: should we retrieve a better filename, e.g. GetLongPathName, or GetFinalPathNameByHandle on vista+
|
|
wchar_t mutex_name[1024] = {0};
|
|
StringCbPrintfW(mutex_name, sizeof(mutex_name), L"Global\\nde-%s", fl);
|
|
|
|
CharLowerW(mutex_name+7);
|
|
wchar_t *sw = mutex_name+7;
|
|
wchar_t *has_extension=0;
|
|
while (sw && *sw)
|
|
{
|
|
if (*sw == L'\\')
|
|
{
|
|
has_extension=0;
|
|
*sw = L'/';
|
|
}
|
|
else if (*sw == L'.')
|
|
has_extension=sw;
|
|
sw++;
|
|
}
|
|
if (has_extension)
|
|
*has_extension = 0;
|
|
|
|
SECURITY_ATTRIBUTES attr = {0};
|
|
if (GetLowIntegrity(&attr))
|
|
{
|
|
f->mutex = CreateMutexW(&attr, FALSE, mutex_name);
|
|
LocalFree(attr.lpSecurityDescriptor);
|
|
}
|
|
else
|
|
f->mutex = CreateMutexW(0, FALSE, mutex_name);
|
|
|
|
#endif
|
|
return f;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
VFILE *Vfopen(VFILE *f, wchar_t *fl, const char *mode, BOOL Cached)
|
|
{
|
|
if (!fl) return NULL;
|
|
if (!f)
|
|
{
|
|
f = Vfnew(fl, mode, Cached);
|
|
if (!f)
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef NDE_ALLOW_NONCACHED
|
|
f->cached = Cached;
|
|
#else
|
|
f->cached = TRUE;
|
|
#endif
|
|
|
|
if (!strchr(mode, '+'))
|
|
{
|
|
if (strchr(mode, 'r'))
|
|
f->mode = VFS_READ | VFS_MUSTEXIST;
|
|
if (strchr(mode, 'w'))
|
|
f->mode = VFS_WRITE | VFS_CREATE | VFS_NEWCONTENT;
|
|
if (strchr(mode, 'a'))
|
|
f->mode = VFS_WRITE | VFS_CREATE | VFS_SEEKEOF;
|
|
}
|
|
else
|
|
{
|
|
if (strstr(mode, "r+"))
|
|
f->mode = VFS_WRITE | VFS_MUSTEXIST;
|
|
if (strstr(mode, "w+"))
|
|
f->mode = VFS_WRITE | VFS_CREATE | VFS_NEWCONTENT;
|
|
if (strstr(mode, "a+"))
|
|
f->mode = VFS_WRITE | VFS_CREATE | VFS_SEEKEOF;
|
|
}
|
|
|
|
if (f->mode == 0 || ((f->mode & VFS_READ) && (f->mode & VFS_WRITE)))
|
|
{
|
|
Vfdestroy(f);
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef NDE_ALLOW_NONCACHED
|
|
if (!f->cached)
|
|
{
|
|
f->endoffile=false;
|
|
int readFlags=GENERIC_READ, openFlags=0;
|
|
if (f->mode & VFS_WRITE) readFlags|=GENERIC_WRITE;
|
|
if (f->mode & VFS_MUSTEXIST) openFlags=OPEN_EXISTING;
|
|
if (f->mode & VFS_CREATE) openFlags = OPEN_ALWAYS;
|
|
if (f->mode & VFS_NEWCONTENT) openFlags = CREATE_ALWAYS;
|
|
f->hfile=CreateFile(fl,readFlags,FILE_SHARE_READ,0,openFlags,0,0);
|
|
if (f->hfile!=INVALID_HANDLE_VALUE)
|
|
f->filename = _strdup(fl);
|
|
else
|
|
{
|
|
Vfdestroy(f);
|
|
return NULL;
|
|
}
|
|
return f;
|
|
}
|
|
#endif
|
|
|
|
if (f->mode & VFS_MUSTEXIST)
|
|
{
|
|
if (GetFileAttributesW(fl) == INVALID_FILE_ATTRIBUTES)
|
|
{
|
|
Vfdestroy(f);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (!(f->mode & VFS_NEWCONTENT))
|
|
{
|
|
int attempts=0;
|
|
HANDLE hFile=INVALID_HANDLE_VALUE;
|
|
again:
|
|
if (attempts<100) // we'll try for 10 seconds
|
|
{
|
|
hFile=CreateFileW(fl,GENERIC_READ,FILE_SHARE_READ/*|FILE_SHARE_WRITE*/,0,OPEN_EXISTING,FILE_FLAG_SEQUENTIAL_SCAN,0);
|
|
if (hFile == INVALID_HANDLE_VALUE && GetLastError() == ERROR_SHARING_VIOLATION)
|
|
{
|
|
Sleep(100); // let's try again
|
|
goto again;
|
|
}
|
|
}
|
|
else if (hFile == INVALID_HANDLE_VALUE && GetLastError() == ERROR_SHARING_VIOLATION)
|
|
{
|
|
// screwed up STILL? eeergh I bet it's another program locking it, let's try with more sharing flags
|
|
hFile=CreateFileW(fl,GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,0,OPEN_EXISTING,FILE_FLAG_SEQUENTIAL_SCAN,0);
|
|
}
|
|
|
|
if (hFile==INVALID_HANDLE_VALUE)
|
|
{
|
|
f->data = (uint8_t *)calloc(VFILE_INC, 1);
|
|
if (f->data == NULL)
|
|
{
|
|
Vfdestroy(f);
|
|
return NULL;
|
|
}
|
|
f->filesize = 0;
|
|
f->maxsize = VFILE_INC;
|
|
}
|
|
else
|
|
{
|
|
size_t fsize_ret_value=GetFileSize(hFile,NULL);
|
|
if (fsize_ret_value==INVALID_FILE_SIZE)
|
|
{
|
|
Vfdestroy(f);
|
|
return NULL;
|
|
}
|
|
f->filesize = (uint32_t)fsize_ret_value;
|
|
f->data = (uint8_t *)calloc(f->filesize, 1);
|
|
if (f->data == NULL)
|
|
{
|
|
CloseHandle(hFile);
|
|
Vfdestroy(f);
|
|
return NULL;
|
|
}
|
|
f->maxsize = f->filesize;
|
|
DWORD r = 0;
|
|
// TODO: benski> I think we should switch this to overlapped I/O (to allow I/O to happen as we're parsing)
|
|
// or switch to a memory mapped file... but we'll need to check with the profiler
|
|
if (!ReadFile(hFile,f->data,f->filesize,&r,NULL) || r != f->filesize)
|
|
{
|
|
CloseHandle(hFile);
|
|
Vfdestroy(f);
|
|
return NULL;
|
|
}
|
|
CloseHandle(hFile);
|
|
}
|
|
}
|
|
|
|
if (f->mode & VFS_SEEKEOF)
|
|
f->ptr = f->filesize;
|
|
|
|
f->filename = fl;
|
|
ndestring_retain(f->filename);
|
|
return f;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
void Vfclose(VFILE *f)
|
|
{
|
|
if (!f) return;
|
|
|
|
#ifdef NDE_ALLOW_NONCACHED
|
|
if (!f->cached)
|
|
{
|
|
if (f->hfile!=INVALID_HANDLE_VALUE)
|
|
CloseHandle(f->hfile);
|
|
f->hfile=INVALID_HANDLE_VALUE;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
if (f->mode & VFS_WRITE)
|
|
{
|
|
Vsync(f);
|
|
}
|
|
}
|
|
|
|
ndestring_release(f->filename);
|
|
f->filename=0;
|
|
free(f->data);
|
|
f->data = 0;
|
|
f->ptr=0;
|
|
f->filesize=0;
|
|
f->maxsize=0;
|
|
f->dirty=0;
|
|
}
|
|
|
|
void Vfdestroy(VFILE *f)
|
|
{
|
|
// benski> TODO:
|
|
if (f)
|
|
{
|
|
Vfclose(f);
|
|
|
|
while (f->locks)
|
|
Vfunlock(f, 1);
|
|
|
|
if (f->mutex) CloseHandle(f->mutex);
|
|
free(f);
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
size_t Vfread( void *ptr, size_t size, VFILE *f )
|
|
{
|
|
assert( ptr && f );
|
|
#ifdef NDE_ALLOW_NONCACHED
|
|
if ( !f->cached )
|
|
{
|
|
size_t read = f->r.read( ptr, size );
|
|
ptr = (uint8_t *)ptr + read;
|
|
size -= read;
|
|
if ( size == 0 ) return 1; // yay fully buffered read
|
|
// if we got here, the ring buffer is empty
|
|
f->r.clear(); // reset back to normal
|
|
if ( size > f->r.avail() )
|
|
{
|
|
return ReadFileN( ptr, size, f ) == size;
|
|
}
|
|
void *data = f->r.LockBuffer();
|
|
size_t bytes_read = ReadFileN( data, f->r.avail(), f );
|
|
f->r.UnlockBuffer( bytes_read );
|
|
read = f->r.read( ptr, size );
|
|
return read == size;
|
|
}
|
|
#endif
|
|
//if (!size) return 0;
|
|
if ( size + f->ptr > f->filesize )
|
|
{
|
|
//FUCKO: remove this
|
|
if ( !( f->ptr < f->filesize ) )
|
|
{
|
|
#ifdef _DEBUG
|
|
char buf[ 128 ] = { 0 };
|
|
StringCbPrintfA( buf, sizeof( buf ), "NDE/VFS: VFS read at %d/%d (%d bytes) is bad\n", f->ptr, f->filesize, size );
|
|
OutputDebugStringA( buf );
|
|
#endif
|
|
|
|
// if (!f->flushtable) // this would be ideal, if we could figure out f->flushtable
|
|
// f->flushtable=MessageBox(g_hwnd,"DB read failed, DB may be corrupted.\r\n\r\n"
|
|
// "Hit Retry to continue, or Cancel to clear the DB and start over","Winamp Library Error",MB_RETRYCANCEL) == IDCANCEL; //fucko
|
|
//MessageBox(g_hwnd,"DB read failed, DB may be corrupted. If this error persists, remove all files from the library.",
|
|
// "Winamp Library Error",MB_OK);
|
|
return 0;
|
|
}
|
|
|
|
size = f->filesize - f->ptr;
|
|
}
|
|
|
|
memcpy( ptr, f->data + f->ptr, size );
|
|
f->ptr += (uint32_t)size;
|
|
|
|
return 1;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
void Vfwrite(const void *ptr, size_t size, VFILE *f)
|
|
{
|
|
if (!ptr || !f) return;
|
|
#ifdef NDE_ALLOW_NONCACHED
|
|
if (!f->cached)
|
|
{
|
|
// TODO: with some cleverness we might be able to make this write to the read cache
|
|
|
|
// if we're cached, then our file position is off and we need to adjust
|
|
if (!f->r.empty())
|
|
Vfseek(f, -(f->r.size()), SEEK_CUR);
|
|
|
|
f->r.clear();
|
|
WriteFileN(ptr, size, f);
|
|
return;
|
|
}
|
|
#endif
|
|
f->dirty=1;
|
|
size_t s = (size);
|
|
if (s + f->ptr > f->maxsize)
|
|
{
|
|
// grow f->data,f->maxsize to be (s + f->ptr + VFILE_INC-1)&~(VFILE_INC-1)
|
|
// instead of calling Vgrow again which gets kinda slow
|
|
size_t newsize=(s + f->ptr + VFILE_INC-1)&~(VFILE_INC-1);
|
|
uint8_t *newdata=(uint8_t *)realloc(f->data,newsize);
|
|
if (newdata == NULL) return;
|
|
f->data = newdata;
|
|
memset(f->data+f->maxsize,0,newsize-f->maxsize);
|
|
f->maxsize=(uint32_t)newsize;
|
|
}
|
|
memcpy(f->data + f->ptr, ptr, s);
|
|
f->ptr += (uint32_t)s;
|
|
if (f->ptr > f->filesize)
|
|
f->filesize = f->ptr;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
void Vgrow(VFILE *f)
|
|
{
|
|
if (!f) return;
|
|
#ifdef NDE_ALLOW_NONCACHED
|
|
if (!f->cached) return;
|
|
#endif
|
|
uint8_t *newdata=(uint8_t *)realloc(f->data, f->maxsize + VFILE_INC);
|
|
if (newdata == NULL) return;
|
|
f->data = newdata;
|
|
f->maxsize += VFILE_INC;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
uint32_t Vftell(VFILE *f)
|
|
{
|
|
if (!f) return (unsigned)-1;
|
|
#ifdef NDE_ALLOW_NONCACHED
|
|
if (!f->cached)
|
|
{
|
|
return SetFilePointer(f->hfile, 0, NULL, FILE_CURRENT);
|
|
}
|
|
#endif
|
|
return f->ptr;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
void Vfseek(VFILE *f, uint32_t i, int whence)
|
|
{
|
|
if (!f) return;
|
|
#ifdef NDE_ALLOW_NONCACHED
|
|
if (!f->cached)
|
|
{
|
|
if (whence == SEEK_CUR && i > 0 && i <f->r.size())
|
|
{
|
|
f->r.advance(i);
|
|
}
|
|
else
|
|
{
|
|
f->r.clear();
|
|
SetFilePointer(f->hfile, i, NULL, whence);
|
|
f->endoffile = false;
|
|
}
|
|
return;
|
|
}
|
|
#endif
|
|
switch (whence)
|
|
{
|
|
case SEEK_SET:
|
|
f->ptr = i;
|
|
break;
|
|
case SEEK_CUR:
|
|
f->ptr += i;
|
|
break;
|
|
case SEEK_END:
|
|
f->ptr = f->filesize+i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
int Vfeof(VFILE *f)
|
|
{
|
|
if (!f) return -1;
|
|
#ifdef NDE_ALLOW_NONCACHED
|
|
if (!f->cached)
|
|
{
|
|
return !!f->endoffile;
|
|
}
|
|
#endif
|
|
return (f->ptr >= f->filesize);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
int Vsync(VFILE *f)
|
|
{
|
|
if (!f) return 0;
|
|
if (!f->dirty) return 0;
|
|
|
|
if (f->mode & VFS_WRITE)
|
|
{
|
|
#ifdef NDE_ALLOW_NONCACHED
|
|
if (!f->cached)
|
|
{
|
|
LONG p = SetFilePointer(f->hfile, 0, NULL, FILE_CURRENT);
|
|
CloseHandle(f->hfile);
|
|
f->hfile = CreateFileW(f->filename,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,0,NULL);
|
|
if (f->hfile == INVALID_HANDLE_VALUE)
|
|
return 1;
|
|
SetFilePointer(f->hfile, p, NULL, SEEK_SET);
|
|
f->endoffile=false;
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
wchar_t newfn[MAX_PATH] = {0};
|
|
wchar_t oldfn[MAX_PATH] = {0};
|
|
DWORD mypid=GetCurrentProcessId();
|
|
|
|
StringCchPrintfW(newfn, MAX_PATH, L"%s.n3w%08X",f->filename,mypid);
|
|
StringCchPrintfW(oldfn, MAX_PATH, L"%s.o1d%08X",f->filename,mypid);
|
|
|
|
DeleteFileW(newfn);
|
|
DeleteFileW(oldfn);
|
|
|
|
HANDLE hFile = CreateFileW(newfn,GENERIC_WRITE,0,0,CREATE_ALWAYS,0,NULL);
|
|
int success=0;
|
|
if (hFile != INVALID_HANDLE_VALUE)
|
|
{
|
|
DWORD o = 0;
|
|
if (WriteFile(hFile,f->data,f->filesize,&o,NULL) && o == f->filesize) success++;
|
|
CloseHandle(hFile);
|
|
}
|
|
if (!success)
|
|
{
|
|
DeleteFileW(newfn);
|
|
return 1;
|
|
}
|
|
|
|
// TODO use this to keep a backup of the database file for edit fails, etc
|
|
if (MoveFileW(f->filename,oldfn) == 0) // if the function fails
|
|
{
|
|
CopyFileW(f->filename,oldfn, FALSE);
|
|
DeleteFileW(f->filename);
|
|
}
|
|
|
|
int rv=0;
|
|
if (MoveFileW(newfn,f->filename) == 0 && CopyFileW(newfn,f->filename, FALSE) == 0)
|
|
{
|
|
MoveFileW(oldfn,f->filename); // restore old file
|
|
rv=1;
|
|
}
|
|
else
|
|
{
|
|
f->dirty=0;
|
|
}
|
|
|
|
// clean up our temp files
|
|
DeleteFileW(oldfn);
|
|
DeleteFileW(newfn);
|
|
|
|
return rv;
|
|
}
|
|
f->dirty=0;
|
|
return 0;
|
|
}
|
|
|
|
// returns 0 on failure
|
|
int Vflock(VFILE *fl, BOOL is_sync)
|
|
{
|
|
#ifndef NO_TABLE_WIN32_LOCKING
|
|
if (!fl) return 0;
|
|
if (!is_sync && fl->cached)
|
|
return 1;
|
|
// try for 10 seconds
|
|
if (fl->locks++ == 0)
|
|
{
|
|
if (WaitForSingleObject(fl->mutex, 10000) != WAIT_OBJECT_0)
|
|
{
|
|
fl->locks--;
|
|
return 0;
|
|
}
|
|
}
|
|
#endif
|
|
return 1;
|
|
}
|
|
|
|
void Vfunlock(VFILE *fl, BOOL is_sync)
|
|
{
|
|
#ifndef NO_TABLE_WIN32_LOCKING
|
|
if (!is_sync && fl->cached)
|
|
return;
|
|
|
|
if (fl && fl->locks == 0)
|
|
DebugBreak();
|
|
if (--fl->locks == 0)
|
|
{
|
|
if (fl && fl->mutex)
|
|
{
|
|
ReleaseMutex(fl->mutex);
|
|
}
|
|
}
|
|
#endif
|
|
} |