Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

520 lines
18 KiB

  1. /*++
  2. Copyright (c) 1996 Microsoft Corp.
  3. Module Name: hashutil.cxx
  4. Abstract:
  5. Implementation of linked list of hash tables for cache index lookup.
  6. Author:
  7. Rajeev Dujari (rajeevd) 22-Oct-96
  8. --*/
  9. #include <cache.hxx>
  10. #define SIG_HASH ('H'|('A'<<8)|('S'<<16)|('H'<<24))
  11. typedef LIST_FILEMAP_ENTRY HASH_FILEMAP_ENTRY;
  12. // hash table parameters
  13. #define BYTES_PER_PAGE 4096
  14. #define ITEMS_PER_BUCKET ((BYTES_PER_PAGE - sizeof(HASH_FILEMAP_ENTRY))\
  15. / (SLOT_COUNT * sizeof(HASH_ITEM)))
  16. #define BYTES_PER_TABLE (sizeof(HASH_FILEMAP_ENTRY) \
  17. + SLOT_COUNT * ITEMS_PER_BUCKET * sizeof(HASH_ITEM))
  18. //
  19. // Hash Function: Pearson's method
  20. //
  21. PRIVATE DWORD HashKey (LPCSTR lpsz, DWORD dwAddedHash)
  22. {
  23. union
  24. {
  25. DWORD dw;
  26. BYTE c[4];
  27. }
  28. Hash, Hash2;
  29. const static BYTE bTranslate[256] =
  30. {
  31. 1, 14,110, 25, 97,174,132,119,138,170,125,118, 27,233,140, 51,
  32. 87,197,177,107,234,169, 56, 68, 30, 7,173, 73,188, 40, 36, 65,
  33. 49,213,104,190, 57,211,148,223, 48,115, 15, 2, 67,186,210, 28,
  34. 12,181,103, 70, 22, 58, 75, 78,183,167,238,157,124,147,172,144,
  35. 176,161,141, 86, 60, 66,128, 83,156,241, 79, 46,168,198, 41,254,
  36. 178, 85,253,237,250,154,133, 88, 35,206, 95,116,252,192, 54,221,
  37. 102,218,255,240, 82,106,158,201, 61, 3, 89, 9, 42,155,159, 93,
  38. 166, 80, 50, 34,175,195,100, 99, 26,150, 16,145, 4, 33, 8,189,
  39. 121, 64, 77, 72,208,245,130,122,143, 55,105,134, 29,164,185,194,
  40. 193,239,101,242, 5,171,126, 11, 74, 59,137,228,108,191,232,139,
  41. 6, 24, 81, 20,127, 17, 91, 92,251,151,225,207, 21, 98,113,112,
  42. 84,226, 18,214,199,187, 13, 32, 94,220,224,212,247,204,196, 43,
  43. 249,236, 45,244,111,182,153,136,129, 90,217,202, 19,165,231, 71,
  44. 230,142, 96,227, 62,179,246,114,162, 53,160,215,205,180, 47,109,
  45. 44, 38, 31,149,135, 0,216, 52, 63, 23, 37, 69, 39,117,146,184,
  46. 163,200,222,235,248,243,219, 10,152,131,123,229,203, 76,120,209
  47. };
  48. // Seed the hash values based on the first character.
  49. Hash.c[0] = bTranslate[ *lpsz];
  50. Hash.c[1] = bTranslate[(*lpsz+1) & 255];
  51. Hash.c[2] = bTranslate[(*lpsz+2) & 255];
  52. Hash.c[3] = bTranslate[(*lpsz+3) & 255];
  53. Hash.dw += dwAddedHash;
  54. while (*++lpsz)
  55. {
  56. // Allow URLs differing only by trailing slash to collide.
  57. if (lpsz[0] == '/' && lpsz[1] == 0)
  58. break;
  59. Hash2.c[0] = Hash.c[0] ^ *lpsz;
  60. Hash2.c[1] = Hash.c[1] ^ *lpsz;
  61. Hash2.c[2] = Hash.c[2] ^ *lpsz;
  62. Hash2.c[3] = Hash.c[3] ^ *lpsz;
  63. Hash.c[0] = bTranslate[Hash2.c[0]];
  64. Hash.c[1] = bTranslate[Hash2.c[1]];
  65. Hash.c[2] = bTranslate[Hash2.c[2]];
  66. Hash.c[3] = bTranslate[Hash2.c[3]];
  67. }
  68. return Hash.dw;
  69. }
  70. //
  71. // HashLookupItem support functions specific to urlcache:
  72. // AllocTable
  73. // IsMatch
  74. //
  75. PRIVATE HASH_FILEMAP_ENTRY* AllocTable
  76. (LPVOID pAllocObj, LPBYTE* ppBase, LPDWORD* ppdwOffset)
  77. {
  78. // Save the offset to the table offset.
  79. DWORD_PTR dpOffsetToTableOffset = (LPBYTE)*ppdwOffset - *ppBase; // 64BIT
  80. // Ask for BYTES_PER_PAGE instead of BYTES_PER_TABLE
  81. // so the allocator knows to align on a page boundary.
  82. INET_ASSERT (BYTES_PER_PAGE >= BYTES_PER_TABLE);
  83. MEMMAP_FILE* pmmf = (MEMMAP_FILE*) pAllocObj;
  84. HASH_FILEMAP_ENTRY* pTable =
  85. (HASH_FILEMAP_ENTRY *) pmmf->AllocateEntry (BYTES_PER_PAGE);
  86. if (!pTable)
  87. return NULL;
  88. INET_ASSERT (! (((LPBYTE) pTable - *pmmf->GetHeapStart()) & (BYTES_PER_PAGE-1)) );
  89. // Chain new table to previous table.
  90. *ppBase = *pmmf->GetHeapStart();
  91. *ppdwOffset = (DWORD*) (*ppBase + dpOffsetToTableOffset);
  92. **ppdwOffset = (DWORD) ((LPBYTE)pTable - *ppBase); // 64BIT
  93. // Initialize the header.
  94. pTable->dwSig = SIG_HASH;
  95. pTable->dwNext = 0;
  96. // Fill the rest of the entry with HASH_END
  97. DWORD* pdw = (DWORD *) (pTable + 1);
  98. DWORD cdw = SLOT_COUNT * ITEMS_PER_BUCKET * (sizeof(HASH_ITEM)/sizeof(DWORD));
  99. INET_ASSERT (!(sizeof(HASH_ITEM) % sizeof(DWORD)));
  100. while (cdw--)
  101. *pdw++ = HASH_END;
  102. // Return the new table.
  103. return pTable;
  104. }
  105. //
  106. // IsMatch: determine if hash table item with a matching 32-bit hash value
  107. // is an actual match or return NULL if a collision.
  108. //
  109. PRIVATE HASH_ITEM* URL_CONTAINER::IsMatch
  110. (HASH_ITEM *pItem, LPCSTR pszKey, DWORD dwFlags)
  111. {
  112. MEMMAP_FILE* pmmf = _UrlObjStorage;
  113. dwFlags &= (LOOKUP_BIT_REDIR | LOOKUP_BIT_CREATE);
  114. if (pmmf->IsBadOffset (pItem->dwOffset))
  115. {
  116. // Fix up a bad hash table item. This could happen if a thread
  117. // died between allocating a hash table item and setting the offset.
  118. pItem->MarkFree();
  119. return NULL;
  120. }
  121. FILEMAP_ENTRY* pEntry = (FILEMAP_ENTRY*)
  122. (*pmmf->GetHeapStart() + pItem->dwOffset);
  123. switch (pEntry->dwSig)
  124. {
  125. case SIG_URL:
  126. {
  127. // Fail if lookup flags are inconsistent with url entry type.
  128. INET_ASSERT (!(pItem->dwHash & HASH_BIT_NOTURL));
  129. // Get pointer to URL.
  130. URL_FILEMAP_ENTRY *pUrlEntry = (URL_FILEMAP_ENTRY *) pEntry;
  131. LPSTR pszUrl = ((LPSTR) pUrlEntry) + pUrlEntry->UrlNameOffset;
  132. LPCSTR pszKey2 = pszKey, pszUrl2 = pszUrl;
  133. while ( *pszKey2 && *pszUrl2 && *pszKey2 == *pszUrl2 )
  134. {
  135. pszKey2++;
  136. pszUrl2++;
  137. }
  138. if (!*pszKey2 && ! *pszUrl2)
  139. {
  140. // Found exact match.
  141. if (dwFlags == LOOKUP_REDIR_CREATE)
  142. {
  143. // We are have a cache entry for a URL which is now
  144. // redirecting. Delete the cache entry.
  145. DeleteUrlEntry (pUrlEntry, pItem, SIG_DELETE);
  146. return NULL;
  147. }
  148. return pItem;
  149. }
  150. // If redirects allowed, check for trailing slash match.
  151. if ((dwFlags == LOOKUP_URL_TRANSLATE)
  152. && (pItem->dwHash & HASH_BIT_REDIR))
  153. {
  154. DWORD cbUrl = strlen (pszUrl);
  155. DWORD cbKey = strlen (pszKey);
  156. INET_ASSERT (cbUrl && pszUrl[cbUrl - 1] == '/');
  157. if (cbUrl == (cbKey + 1) && !memcmp (pszUrl, pszKey, cbKey))
  158. return pItem;
  159. }
  160. return NULL;
  161. }
  162. case SIG_REDIR:
  163. {
  164. // When online, filter out offline redirect entries.
  165. if (dwFlags == LOOKUP_URL_NOCREATE)
  166. return NULL;
  167. // Check that redirect URL matches exactly.
  168. REDIR_FILEMAP_ENTRY* pRedir = (REDIR_FILEMAP_ENTRY *) pEntry;
  169. if (lstrcmp (pszKey, pRedir->szUrl))
  170. return NULL;
  171. switch (dwFlags)
  172. {
  173. case LOOKUP_URL_CREATE:
  174. // We are creating a new entry for a URL that once
  175. // redirected. Delete the stale redirect entry.
  176. pmmf->FreeEntry (pRedir);
  177. pItem->MarkFree();
  178. return NULL;
  179. case LOOKUP_REDIR_CREATE:
  180. // Return the redirect item if we're looking for it.
  181. return pItem;
  182. case LOOKUP_URL_TRANSLATE:
  183. // Otherwise, translate through the redirect item.
  184. pItem = (HASH_ITEM *)
  185. (*pmmf->GetHeapStart() + pRedir->dwItemOffset);
  186. // Perform some consistency checks.
  187. if (pItem->dwHash & HASH_BIT_NOTURL)
  188. return NULL; // not an URL entry
  189. if ((pItem->dwHash & ~SLOT_MASK) != pRedir->dwHashValue)
  190. return NULL; // not a matching URL entry
  191. return pItem;
  192. default:
  193. INET_ASSERT (FALSE);
  194. }
  195. }
  196. default:
  197. {
  198. // Fix up a bad hash table entry. This can happen if a thread
  199. // died between allocating a hash table item and setting the offset.
  200. pItem->MarkFree();
  201. return NULL;
  202. }
  203. }
  204. }
  205. //
  206. // HashFindItem: finds a matching entry or else the first free slot
  207. //
  208. BOOL URL_CONTAINER::HashFindItem
  209. (LPCSTR pszKey, DWORD dwFlags, HASH_ITEM** ppItem)
  210. {
  211. INET_ASSERT(!((dwFlags & LOOKUP_URL_DONT_FOLLOW) && (dwFlags & LOOKUP_BIT_CREATE)));
  212. DWORD dwFind = 0;
  213. BOOL fLookAgain = !(dwFlags & LOOKUP_URL_DONT_FOLLOW) && GlobalIdentity;
  214. again:
  215. LPVOID pAllocObj = (LPVOID) _UrlObjStorage;
  216. LPBYTE pBase = *_UrlObjStorage->GetHeapStart();
  217. LPDWORD pdwTableOffset = _UrlObjStorage->GetPtrToHashTableOffset();
  218. // Scan flags.
  219. BOOL fCreate = dwFlags & LOOKUP_BIT_CREATE;
  220. HASH_ITEM* pFree = NULL;
  221. DWORD nBlock = 0;
  222. // Hash the URL and calculate the slot.
  223. DWORD dwHash = HashKey(pszKey, dwFind);
  224. DWORD iSlot = dwHash & SLOT_MASK;
  225. dwHash &= ~SLOT_MASK;
  226. // Walk through the list of hash tables.
  227. while (*pdwTableOffset && !_UrlObjStorage->IsBadOffset(*pdwTableOffset))
  228. {
  229. // Calculate offset to next hash table and validate signature.
  230. HASH_FILEMAP_ENTRY* pTable =
  231. (HASH_FILEMAP_ENTRY*) (pBase + *pdwTableOffset);
  232. if (pTable->dwSig != SIG_HASH || pTable->nBlock != nBlock++)
  233. break;
  234. // Calculate offset to bucket in this table.
  235. HASH_ITEM* pItem = ((HASH_ITEM*) (pTable + 1)) + iSlot * ITEMS_PER_BUCKET;
  236. // Scan the bucket.
  237. for (DWORD iSeat=0; iSeat<ITEMS_PER_BUCKET; iSeat++, pItem++)
  238. {
  239. // No reserved bits should ever be set on an item.
  240. INET_ASSERT (!(pItem->dwHash & HASH_BIT_RESERVED));
  241. switch (pItem->dwHash)
  242. {
  243. case HASH_FREE: // free item but more items might follow
  244. {
  245. INET_ASSERT (!(pItem->dwHash & ~SLOT_MASK));
  246. // If caller wants a free item, record the first one we find.
  247. if (!pFree && fCreate)
  248. pFree = pItem;
  249. }
  250. continue;
  251. case HASH_END: // first previously unused free item; no more to follow
  252. {
  253. INET_ASSERT (!(pItem->dwHash & ~SLOT_MASK));
  254. if (!fCreate)
  255. *ppItem = NULL;
  256. else
  257. {
  258. // Hand out the first free slot.
  259. if (pFree)
  260. {
  261. // Invalidate offset in case caller neglects to set it.
  262. pFree->dwOffset = HASH_END;
  263. *ppItem = pFree;
  264. }
  265. else
  266. {
  267. // The first free slot has never been used before.
  268. INET_ASSERT (pItem->dwOffset == HASH_END);
  269. *ppItem = pItem;
  270. }
  271. (*ppItem)->dwHash = dwHash;
  272. }
  273. }
  274. return FALSE;
  275. default:
  276. {
  277. // Check if the key matches.
  278. if (dwHash == (pItem->dwHash & ~SLOT_MASK))
  279. {
  280. if (dwFlags & INTERNET_CACHE_FLAG_ALLOW_COLLISIONS)
  281. {
  282. *ppItem = pItem;
  283. return TRUE;
  284. }
  285. HASH_ITEM* pItem2 = IsMatch(pItem, pszKey, dwFlags);
  286. if (pItem2)
  287. {
  288. LPURL_FILEMAP_ENTRY pEntry =
  289. (URL_FILEMAP_ENTRY*)(*(((MEMMAP_FILE*)_UrlObjStorage)->GetHeapStart())
  290. + pItem2->dwOffset);
  291. // This will check for a ~U: header or IDENTITY_CACHE_ENTRY
  292. // We use the first for compatibility with Wininet5
  293. // We use IDENTITY_CACHE_ENTRY for entries we wish to hide from Wininet5
  294. if ((pEntry->dwSig==SIG_URL)
  295. && (!(dwFlags & LOOKUP_URL_DONT_FOLLOW)
  296. && ((pEntry->CacheEntryType & IDENTITY_CACHE_ENTRY)
  297. || IsPerUserEntry(pEntry))))
  298. {
  299. // We'll search again for an entry corresponding to GlobalIdentity
  300. if (fLookAgain)
  301. {
  302. fLookAgain = FALSE;
  303. dwFind = GlobalIdentity;
  304. goto again;
  305. }
  306. // Guarantee that this is what we want
  307. if (pEntry->GetIdentity()!=GlobalIdentity)
  308. {
  309. continue;
  310. }
  311. // If we're looking for an identity-0 cache entry
  312. // and there is no filename, we need to do the following:
  313. // 1. if we're trying to create an entry, then return this
  314. // 2. otherwise, say No, the entry is not present
  315. if (!pEntry->InternalFileNameOffset && !fCreate)
  316. {
  317. *ppItem = NULL;
  318. return FALSE;
  319. }
  320. }
  321. *ppItem = pItem2;
  322. return TRUE;
  323. }
  324. }
  325. }
  326. continue;
  327. } // end switch
  328. } // end for loop to scan seats in bucket
  329. // Follow the link to the next table.
  330. pdwTableOffset = &pTable->dwNext;
  331. } // end while (*pdwTableOffset)
  332. // If we've encountered a corrupt table, we'll have to recover
  333. if (*pdwTableOffset)
  334. {
  335. INET_ASSERT(FALSE);
  336. *pdwTableOffset = 0;
  337. }
  338. // We are out a buckets, so an item hasn't been found.
  339. if (fCreate && !pFree)
  340. {
  341. // Caller wanted a free item but we didn't find one.
  342. HASH_FILEMAP_ENTRY* pTable = AllocTable
  343. (pAllocObj, &pBase, &pdwTableOffset);
  344. //////////////////////////////////////////////////////////////////////
  345. // WARNING: the file might have grown and remapped, so any pointers //
  346. // hereafter must be recalculated by offsets from the new base. //
  347. //////////////////////////////////////////////////////////////////////
  348. if (pTable)
  349. {
  350. pTable->nBlock = nBlock;
  351. // Calculate next free slot.
  352. pFree = ((HASH_ITEM*) (pTable + 1)) + iSlot * ITEMS_PER_BUCKET;
  353. INET_ASSERT (pFree->dwHash == HASH_END);
  354. INET_ASSERT (pFree->dwOffset == HASH_END);
  355. }
  356. }
  357. // Return free item if desired and indicate no item found.
  358. if (pFree)
  359. {
  360. INET_ASSERT (fCreate);
  361. pFree->dwHash = dwHash;
  362. pFree->dwOffset = HASH_END; // invalid in case caller neglects to set it
  363. }
  364. *ppItem = pFree;
  365. return FALSE;
  366. }
  367. //
  368. // HashFindNextItem: scans the table for the next valid URL item
  369. //
  370. PUBLIC
  371. HASH_ITEM*
  372. HashGetNextItem
  373. (
  374. IN LPVOID pAllocObj, // allocator object
  375. IN LPBYTE pBase, // base for all offsets
  376. IN OUT LPDWORD pdwItemOffset, // current item offset
  377. IN DWORD dwFlags // include redirects?
  378. )
  379. {
  380. INET_ASSERT (!(dwFlags & ~LOOKUP_BIT_REDIR));
  381. // Check if there if the hash table is empty (or we are at the end already.)
  382. if (!*pdwItemOffset)
  383. return NULL;
  384. HASH_ITEM* pItem = (HASH_ITEM*) (pBase + *pdwItemOffset);
  385. // Calculate current table offset, assuming it's the previous page boundary.
  386. INET_ASSERT (BYTES_PER_TABLE <= BYTES_PER_PAGE);
  387. HASH_FILEMAP_ENTRY* pTable =
  388. (HASH_FILEMAP_ENTRY*) (((DWORD_PTR)pItem) & ~(BYTES_PER_PAGE - 1));
  389. // Advance item pointer to next location.
  390. if (pItem == (HASH_ITEM*) pTable)
  391. pItem = (HASH_ITEM*) (pTable + 1); // first location in table
  392. else
  393. pItem++; // next location in table
  394. do // Scan the list of tables.
  395. {
  396. if (pTable->dwSig != SIG_HASH)
  397. break;
  398. // Scan the current table.
  399. for (; (LPBYTE) pItem < ((LPBYTE) pTable) + BYTES_PER_TABLE; pItem++)
  400. {
  401. // No reserved bits should be set.
  402. INET_ASSERT (!(pItem->dwHash & HASH_BIT_RESERVED));
  403. if (!(pItem->dwHash & HASH_BIT_NOTURL)
  404. || (dwFlags /* & LOOKUP_BIT_REDIR */)
  405. && ((pItem->dwHash & HASH_FLAG_MASK) == HASH_REDIR))
  406. {
  407. // Found a valid entry.
  408. *pdwItemOffset = (DWORD) ((LPBYTE)pItem - pBase); // 64BIT
  409. return pItem;
  410. }
  411. }
  412. // Follow the link to the next table.
  413. if (!pTable->dwNext)
  414. pTable = NULL;
  415. else
  416. {
  417. // Validate the table signature and sequence number.
  418. DWORD nBlock = pTable->nBlock;
  419. pTable = (HASH_FILEMAP_ENTRY*) (pBase + pTable->dwNext);
  420. if (pTable->dwSig != SIG_HASH || pTable->nBlock != nBlock + 1)
  421. pTable = NULL;
  422. // Set pointer to first location in table.
  423. pItem = (HASH_ITEM*) (pTable + 1);
  424. }
  425. }
  426. while (pTable);
  427. // We reached the end of the last table.
  428. *pdwItemOffset = 0;
  429. return NULL;
  430. }