winamp/Src/nde/win/Vfs.cpp
2024-09-24 14:54:57 +02:00

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
}