Vfs.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. /* ---------------------------------------------------------------------------
  2. Nullsoft Database Engine
  3. --------------------
  4. codename: Near Death Experience
  5. --------------------------------------------------------------------------- */
  6. /* ---------------------------------------------------------------------------
  7. Virtual File System
  8. --------------------------------------------------------------------------- */
  9. #include "../nde.h"
  10. #include "vfs.h"
  11. #include <malloc.h>
  12. #ifndef EOF
  13. #define EOF -1
  14. #endif
  15. #include <Sddl.h>
  16. #include <strsafe.h>
  17. #if defined(NDE_ALLOW_NONCACHED)
  18. size_t ReadFileN(void *buffer, size_t size, VFILE *f)
  19. {
  20. uint8_t *b = (uint8_t *) buffer;
  21. size_t total_size=0;
  22. while (size)
  23. {
  24. DWORD bytesRead = 0;
  25. DWORD toRead = min(0xffffffffUL, size);
  26. ReadFile(f->hfile, b, toRead, &bytesRead, NULL);
  27. if (bytesRead != toRead)
  28. {
  29. f->endoffile=true;
  30. // TODO: rewind
  31. return total_size+bytesRead;
  32. }
  33. size-=toRead;
  34. b+=toRead;
  35. total_size+=toRead;
  36. }
  37. return total_size;
  38. }
  39. size_t WriteFileN(void *buffer, size_t size, VFILE *f)
  40. {
  41. uint8_t *b = (uint8_t *) buffer;
  42. size_t total_size=0;
  43. while (size)
  44. {
  45. DWORD bytesRead = 0;
  46. DWORD toRead = min(0xffffffffUL, size);
  47. WriteFile(f->hfile, b, toRead, &bytesRead, NULL);
  48. if (bytesRead != toRead)
  49. {
  50. f->endoffile=true;
  51. // TODO: rewind
  52. return total_size+bytesRead;
  53. }
  54. size-=toRead;
  55. b+=toRead;
  56. total_size+=toRead;
  57. }
  58. return total_size;
  59. }
  60. #endif
  61. // benski> i havn't the slightest fucking clue why this works, it's copypasta code from the internets
  62. static LPCWSTR LOW_INTEGRITY_SDDL_SACL_W = L"S:(ML;;NW;;;LW)";
  63. static bool GetLowIntegrity(SECURITY_ATTRIBUTES *attributes)
  64. {
  65. PSECURITY_DESCRIPTOR pSD = NULL;
  66. if ( ConvertStringSecurityDescriptorToSecurityDescriptorW (
  67. LOW_INTEGRITY_SDDL_SACL_W, SDDL_REVISION_1, &pSD, NULL ) )
  68. {
  69. attributes->nLength = sizeof(SECURITY_ATTRIBUTES);
  70. attributes->lpSecurityDescriptor = pSD;
  71. attributes->bInheritHandle = FALSE;
  72. //LocalFree ( pSD );
  73. return true;
  74. }
  75. return false;
  76. }
  77. VFILE *Vfnew(const wchar_t *fl, const char *mode, BOOL Cached)
  78. {
  79. if (!fl) return NULL;
  80. VFILE *f = (VFILE *)calloc(1, sizeof(VFILE));
  81. if (!f) return NULL;
  82. #ifdef NDE_ALLOW_NONCACHED
  83. if (!Cached)
  84. {
  85. f->r.reserve(256); // heuristically determined
  86. }
  87. f->hfile = INVALID_HANDLE_VALUE;
  88. #endif
  89. #ifndef NO_TABLE_WIN32_LOCKING
  90. // TODO: should we retrieve a better filename, e.g. GetLongPathName, or GetFinalPathNameByHandle on vista+
  91. wchar_t mutex_name[1024] = {0};
  92. StringCbPrintfW(mutex_name, sizeof(mutex_name), L"Global\\nde-%s", fl);
  93. CharLowerW(mutex_name+7);
  94. wchar_t *sw = mutex_name+7;
  95. wchar_t *has_extension=0;
  96. while (sw && *sw)
  97. {
  98. if (*sw == L'\\')
  99. {
  100. has_extension=0;
  101. *sw = L'/';
  102. }
  103. else if (*sw == L'.')
  104. has_extension=sw;
  105. sw++;
  106. }
  107. if (has_extension)
  108. *has_extension = 0;
  109. SECURITY_ATTRIBUTES attr = {0};
  110. if (GetLowIntegrity(&attr))
  111. {
  112. f->mutex = CreateMutexW(&attr, FALSE, mutex_name);
  113. LocalFree(attr.lpSecurityDescriptor);
  114. }
  115. else
  116. f->mutex = CreateMutexW(0, FALSE, mutex_name);
  117. #endif
  118. return f;
  119. }
  120. //----------------------------------------------------------------------------
  121. VFILE *Vfopen(VFILE *f, wchar_t *fl, const char *mode, BOOL Cached)
  122. {
  123. if (!fl) return NULL;
  124. if (!f)
  125. {
  126. f = Vfnew(fl, mode, Cached);
  127. if (!f)
  128. return NULL;
  129. }
  130. #ifdef NDE_ALLOW_NONCACHED
  131. f->cached = Cached;
  132. #else
  133. f->cached = TRUE;
  134. #endif
  135. if (!strchr(mode, '+'))
  136. {
  137. if (strchr(mode, 'r'))
  138. f->mode = VFS_READ | VFS_MUSTEXIST;
  139. if (strchr(mode, 'w'))
  140. f->mode = VFS_WRITE | VFS_CREATE | VFS_NEWCONTENT;
  141. if (strchr(mode, 'a'))
  142. f->mode = VFS_WRITE | VFS_CREATE | VFS_SEEKEOF;
  143. }
  144. else
  145. {
  146. if (strstr(mode, "r+"))
  147. f->mode = VFS_WRITE | VFS_MUSTEXIST;
  148. if (strstr(mode, "w+"))
  149. f->mode = VFS_WRITE | VFS_CREATE | VFS_NEWCONTENT;
  150. if (strstr(mode, "a+"))
  151. f->mode = VFS_WRITE | VFS_CREATE | VFS_SEEKEOF;
  152. }
  153. if (f->mode == 0 || ((f->mode & VFS_READ) && (f->mode & VFS_WRITE)))
  154. {
  155. Vfdestroy(f);
  156. return NULL;
  157. }
  158. #ifdef NDE_ALLOW_NONCACHED
  159. if (!f->cached)
  160. {
  161. f->endoffile=false;
  162. int readFlags=GENERIC_READ, openFlags=0;
  163. if (f->mode & VFS_WRITE) readFlags|=GENERIC_WRITE;
  164. if (f->mode & VFS_MUSTEXIST) openFlags=OPEN_EXISTING;
  165. if (f->mode & VFS_CREATE) openFlags = OPEN_ALWAYS;
  166. if (f->mode & VFS_NEWCONTENT) openFlags = CREATE_ALWAYS;
  167. f->hfile=CreateFile(fl,readFlags,FILE_SHARE_READ,0,openFlags,0,0);
  168. if (f->hfile!=INVALID_HANDLE_VALUE)
  169. f->filename = _strdup(fl);
  170. else
  171. {
  172. Vfdestroy(f);
  173. return NULL;
  174. }
  175. return f;
  176. }
  177. #endif
  178. if (f->mode & VFS_MUSTEXIST)
  179. {
  180. if (GetFileAttributesW(fl) == INVALID_FILE_ATTRIBUTES)
  181. {
  182. Vfdestroy(f);
  183. return NULL;
  184. }
  185. }
  186. if (!(f->mode & VFS_NEWCONTENT))
  187. {
  188. int attempts=0;
  189. HANDLE hFile=INVALID_HANDLE_VALUE;
  190. again:
  191. if (attempts<100) // we'll try for 10 seconds
  192. {
  193. hFile=CreateFileW(fl,GENERIC_READ,FILE_SHARE_READ/*|FILE_SHARE_WRITE*/,0,OPEN_EXISTING,FILE_FLAG_SEQUENTIAL_SCAN,0);
  194. if (hFile == INVALID_HANDLE_VALUE && GetLastError() == ERROR_SHARING_VIOLATION)
  195. {
  196. Sleep(100); // let's try again
  197. goto again;
  198. }
  199. }
  200. else if (hFile == INVALID_HANDLE_VALUE && GetLastError() == ERROR_SHARING_VIOLATION)
  201. {
  202. // screwed up STILL? eeergh I bet it's another program locking it, let's try with more sharing flags
  203. hFile=CreateFileW(fl,GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,0,OPEN_EXISTING,FILE_FLAG_SEQUENTIAL_SCAN,0);
  204. }
  205. if (hFile==INVALID_HANDLE_VALUE)
  206. {
  207. f->data = (uint8_t *)calloc(VFILE_INC, 1);
  208. if (f->data == NULL)
  209. {
  210. Vfdestroy(f);
  211. return NULL;
  212. }
  213. f->filesize = 0;
  214. f->maxsize = VFILE_INC;
  215. }
  216. else
  217. {
  218. size_t fsize_ret_value=GetFileSize(hFile,NULL);
  219. if (fsize_ret_value==INVALID_FILE_SIZE)
  220. {
  221. Vfdestroy(f);
  222. return NULL;
  223. }
  224. f->filesize = (uint32_t)fsize_ret_value;
  225. f->data = (uint8_t *)calloc(f->filesize, 1);
  226. if (f->data == NULL)
  227. {
  228. CloseHandle(hFile);
  229. Vfdestroy(f);
  230. return NULL;
  231. }
  232. f->maxsize = f->filesize;
  233. DWORD r = 0;
  234. // TODO: benski> I think we should switch this to overlapped I/O (to allow I/O to happen as we're parsing)
  235. // or switch to a memory mapped file... but we'll need to check with the profiler
  236. if (!ReadFile(hFile,f->data,f->filesize,&r,NULL) || r != f->filesize)
  237. {
  238. CloseHandle(hFile);
  239. Vfdestroy(f);
  240. return NULL;
  241. }
  242. CloseHandle(hFile);
  243. }
  244. }
  245. if (f->mode & VFS_SEEKEOF)
  246. f->ptr = f->filesize;
  247. f->filename = fl;
  248. ndestring_retain(f->filename);
  249. return f;
  250. }
  251. //----------------------------------------------------------------------------
  252. void Vfclose(VFILE *f)
  253. {
  254. if (!f) return;
  255. #ifdef NDE_ALLOW_NONCACHED
  256. if (!f->cached)
  257. {
  258. if (f->hfile!=INVALID_HANDLE_VALUE)
  259. CloseHandle(f->hfile);
  260. f->hfile=INVALID_HANDLE_VALUE;
  261. }
  262. else
  263. #endif
  264. {
  265. if (f->mode & VFS_WRITE)
  266. {
  267. Vsync(f);
  268. }
  269. }
  270. ndestring_release(f->filename);
  271. f->filename=0;
  272. free(f->data);
  273. f->data = 0;
  274. f->ptr=0;
  275. f->filesize=0;
  276. f->maxsize=0;
  277. f->dirty=0;
  278. }
  279. void Vfdestroy(VFILE *f)
  280. {
  281. // benski> TODO:
  282. if (f)
  283. {
  284. Vfclose(f);
  285. while (f->locks)
  286. Vfunlock(f, 1);
  287. if (f->mutex) CloseHandle(f->mutex);
  288. free(f);
  289. }
  290. }
  291. //----------------------------------------------------------------------------
  292. size_t Vfread( void *ptr, size_t size, VFILE *f )
  293. {
  294. assert( ptr && f );
  295. #ifdef NDE_ALLOW_NONCACHED
  296. if ( !f->cached )
  297. {
  298. size_t read = f->r.read( ptr, size );
  299. ptr = (uint8_t *)ptr + read;
  300. size -= read;
  301. if ( size == 0 ) return 1; // yay fully buffered read
  302. // if we got here, the ring buffer is empty
  303. f->r.clear(); // reset back to normal
  304. if ( size > f->r.avail() )
  305. {
  306. return ReadFileN( ptr, size, f ) == size;
  307. }
  308. void *data = f->r.LockBuffer();
  309. size_t bytes_read = ReadFileN( data, f->r.avail(), f );
  310. f->r.UnlockBuffer( bytes_read );
  311. read = f->r.read( ptr, size );
  312. return read == size;
  313. }
  314. #endif
  315. //if (!size) return 0;
  316. if ( size + f->ptr > f->filesize )
  317. {
  318. //FUCKO: remove this
  319. if ( !( f->ptr < f->filesize ) )
  320. {
  321. #ifdef _DEBUG
  322. char buf[ 128 ] = { 0 };
  323. StringCbPrintfA( buf, sizeof( buf ), "NDE/VFS: VFS read at %d/%d (%d bytes) is bad\n", f->ptr, f->filesize, size );
  324. OutputDebugStringA( buf );
  325. #endif
  326. // if (!f->flushtable) // this would be ideal, if we could figure out f->flushtable
  327. // f->flushtable=MessageBox(g_hwnd,"DB read failed, DB may be corrupted.\r\n\r\n"
  328. // "Hit Retry to continue, or Cancel to clear the DB and start over","Winamp Library Error",MB_RETRYCANCEL) == IDCANCEL; //fucko
  329. //MessageBox(g_hwnd,"DB read failed, DB may be corrupted. If this error persists, remove all files from the library.",
  330. // "Winamp Library Error",MB_OK);
  331. return 0;
  332. }
  333. size = f->filesize - f->ptr;
  334. }
  335. memcpy( ptr, f->data + f->ptr, size );
  336. f->ptr += (uint32_t)size;
  337. return 1;
  338. }
  339. //----------------------------------------------------------------------------
  340. void Vfwrite(const void *ptr, size_t size, VFILE *f)
  341. {
  342. if (!ptr || !f) return;
  343. #ifdef NDE_ALLOW_NONCACHED
  344. if (!f->cached)
  345. {
  346. // TODO: with some cleverness we might be able to make this write to the read cache
  347. // if we're cached, then our file position is off and we need to adjust
  348. if (!f->r.empty())
  349. Vfseek(f, -(f->r.size()), SEEK_CUR);
  350. f->r.clear();
  351. WriteFileN(ptr, size, f);
  352. return;
  353. }
  354. #endif
  355. f->dirty=1;
  356. size_t s = (size);
  357. if (s + f->ptr > f->maxsize)
  358. {
  359. // grow f->data,f->maxsize to be (s + f->ptr + VFILE_INC-1)&~(VFILE_INC-1)
  360. // instead of calling Vgrow again which gets kinda slow
  361. size_t newsize=(s + f->ptr + VFILE_INC-1)&~(VFILE_INC-1);
  362. uint8_t *newdata=(uint8_t *)realloc(f->data,newsize);
  363. if (newdata == NULL) return;
  364. f->data = newdata;
  365. memset(f->data+f->maxsize,0,newsize-f->maxsize);
  366. f->maxsize=(uint32_t)newsize;
  367. }
  368. memcpy(f->data + f->ptr, ptr, s);
  369. f->ptr += (uint32_t)s;
  370. if (f->ptr > f->filesize)
  371. f->filesize = f->ptr;
  372. }
  373. //----------------------------------------------------------------------------
  374. void Vgrow(VFILE *f)
  375. {
  376. if (!f) return;
  377. #ifdef NDE_ALLOW_NONCACHED
  378. if (!f->cached) return;
  379. #endif
  380. uint8_t *newdata=(uint8_t *)realloc(f->data, f->maxsize + VFILE_INC);
  381. if (newdata == NULL) return;
  382. f->data = newdata;
  383. f->maxsize += VFILE_INC;
  384. }
  385. //----------------------------------------------------------------------------
  386. uint32_t Vftell(VFILE *f)
  387. {
  388. if (!f) return (unsigned)-1;
  389. #ifdef NDE_ALLOW_NONCACHED
  390. if (!f->cached)
  391. {
  392. return SetFilePointer(f->hfile, 0, NULL, FILE_CURRENT);
  393. }
  394. #endif
  395. return f->ptr;
  396. }
  397. //----------------------------------------------------------------------------
  398. void Vfseek(VFILE *f, uint32_t i, int whence)
  399. {
  400. if (!f) return;
  401. #ifdef NDE_ALLOW_NONCACHED
  402. if (!f->cached)
  403. {
  404. if (whence == SEEK_CUR && i > 0 && i <f->r.size())
  405. {
  406. f->r.advance(i);
  407. }
  408. else
  409. {
  410. f->r.clear();
  411. SetFilePointer(f->hfile, i, NULL, whence);
  412. f->endoffile = false;
  413. }
  414. return;
  415. }
  416. #endif
  417. switch (whence)
  418. {
  419. case SEEK_SET:
  420. f->ptr = i;
  421. break;
  422. case SEEK_CUR:
  423. f->ptr += i;
  424. break;
  425. case SEEK_END:
  426. f->ptr = f->filesize+i;
  427. break;
  428. }
  429. }
  430. //----------------------------------------------------------------------------
  431. int Vfeof(VFILE *f)
  432. {
  433. if (!f) return -1;
  434. #ifdef NDE_ALLOW_NONCACHED
  435. if (!f->cached)
  436. {
  437. return !!f->endoffile;
  438. }
  439. #endif
  440. return (f->ptr >= f->filesize);
  441. }
  442. //----------------------------------------------------------------------------
  443. int Vsync(VFILE *f)
  444. {
  445. if (!f) return 0;
  446. if (!f->dirty) return 0;
  447. if (f->mode & VFS_WRITE)
  448. {
  449. #ifdef NDE_ALLOW_NONCACHED
  450. if (!f->cached)
  451. {
  452. LONG p = SetFilePointer(f->hfile, 0, NULL, FILE_CURRENT);
  453. CloseHandle(f->hfile);
  454. f->hfile = CreateFileW(f->filename,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,0,NULL);
  455. if (f->hfile == INVALID_HANDLE_VALUE)
  456. return 1;
  457. SetFilePointer(f->hfile, p, NULL, SEEK_SET);
  458. f->endoffile=false;
  459. return 0;
  460. }
  461. #endif
  462. wchar_t newfn[MAX_PATH] = {0};
  463. wchar_t oldfn[MAX_PATH] = {0};
  464. DWORD mypid=GetCurrentProcessId();
  465. StringCchPrintfW(newfn, MAX_PATH, L"%s.n3w%08X",f->filename,mypid);
  466. StringCchPrintfW(oldfn, MAX_PATH, L"%s.o1d%08X",f->filename,mypid);
  467. DeleteFileW(newfn);
  468. DeleteFileW(oldfn);
  469. HANDLE hFile = CreateFileW(newfn,GENERIC_WRITE,0,0,CREATE_ALWAYS,0,NULL);
  470. int success=0;
  471. if (hFile != INVALID_HANDLE_VALUE)
  472. {
  473. DWORD o = 0;
  474. if (WriteFile(hFile,f->data,f->filesize,&o,NULL) && o == f->filesize) success++;
  475. CloseHandle(hFile);
  476. }
  477. if (!success)
  478. {
  479. DeleteFileW(newfn);
  480. return 1;
  481. }
  482. // TODO use this to keep a backup of the database file for edit fails, etc
  483. if (MoveFileW(f->filename,oldfn) == 0) // if the function fails
  484. {
  485. CopyFileW(f->filename,oldfn, FALSE);
  486. DeleteFileW(f->filename);
  487. }
  488. int rv=0;
  489. if (MoveFileW(newfn,f->filename) == 0 && CopyFileW(newfn,f->filename, FALSE) == 0)
  490. {
  491. MoveFileW(oldfn,f->filename); // restore old file
  492. rv=1;
  493. }
  494. else
  495. {
  496. f->dirty=0;
  497. }
  498. // clean up our temp files
  499. DeleteFileW(oldfn);
  500. DeleteFileW(newfn);
  501. return rv;
  502. }
  503. f->dirty=0;
  504. return 0;
  505. }
  506. // returns 0 on failure
  507. int Vflock(VFILE *fl, BOOL is_sync)
  508. {
  509. #ifndef NO_TABLE_WIN32_LOCKING
  510. if (!fl) return 0;
  511. if (!is_sync && fl->cached)
  512. return 1;
  513. // try for 10 seconds
  514. if (fl->locks++ == 0)
  515. {
  516. if (WaitForSingleObject(fl->mutex, 10000) != WAIT_OBJECT_0)
  517. {
  518. fl->locks--;
  519. return 0;
  520. }
  521. }
  522. #endif
  523. return 1;
  524. }
  525. void Vfunlock(VFILE *fl, BOOL is_sync)
  526. {
  527. #ifndef NO_TABLE_WIN32_LOCKING
  528. if (!is_sync && fl->cached)
  529. return;
  530. if (fl && fl->locks == 0)
  531. DebugBreak();
  532. if (--fl->locks == 0)
  533. {
  534. if (fl && fl->mutex)
  535. {
  536. ReleaseMutex(fl->mutex);
  537. }
  538. }
  539. #endif
  540. }