diff options
author | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
---|---|---|
committer | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
commit | 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch) | |
tree | 12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/nde/win/Vfs.cpp | |
parent | 537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff) | |
download | winamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz |
Initial community commit
Diffstat (limited to 'Src/nde/win/Vfs.cpp')
-rw-r--r-- | Src/nde/win/Vfs.cpp | 599 |
1 files changed, 599 insertions, 0 deletions
diff --git a/Src/nde/win/Vfs.cpp b/Src/nde/win/Vfs.cpp new file mode 100644 index 00000000..3c5daf81 --- /dev/null +++ b/Src/nde/win/Vfs.cpp @@ -0,0 +1,599 @@ +/* --------------------------------------------------------------------------- +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 +}
\ No newline at end of file |