MediaWiki
REL1_22
|
00001 <?php 00030 class CdbFunctions { 00040 public static function unsignedMod( $a, $b ) { 00041 if ( $a & 0x80000000 ) { 00042 $m = ( $a & 0x7fffffff ) % $b + 2 * ( 0x40000000 % $b ); 00043 return $m % $b; 00044 } else { 00045 return $a % $b; 00046 } 00047 } 00048 00055 public static function unsignedShiftRight( $a, $b ) { 00056 if ( $b == 0 ) { 00057 return $a; 00058 } 00059 if ( $a & 0x80000000 ) { 00060 return ( ( $a & 0x7fffffff ) >> $b ) | ( 0x40000000 >> ( $b - 1 ) ); 00061 } else { 00062 return $a >> $b; 00063 } 00064 } 00065 00073 public static function hash( $s ) { 00074 $h = 5381; 00075 for ( $i = 0; $i < strlen( $s ); $i++ ) { 00076 $h5 = ( $h << 5 ) & 0xffffffff; 00077 // Do a 32-bit sum 00078 // Inlined here for speed 00079 $sum = ( $h & 0x3fffffff ) + ( $h5 & 0x3fffffff ); 00080 $h = 00081 ( 00082 ( $sum & 0x40000000 ? 1 : 0 ) 00083 + ( $h & 0x80000000 ? 2 : 0 ) 00084 + ( $h & 0x40000000 ? 1 : 0 ) 00085 + ( $h5 & 0x80000000 ? 2 : 0 ) 00086 + ( $h5 & 0x40000000 ? 1 : 0 ) 00087 ) << 30 00088 | ( $sum & 0x3fffffff ); 00089 $h ^= ord( $s[$i] ); 00090 $h &= 0xffffffff; 00091 } 00092 return $h; 00093 } 00094 } 00095 00099 class CdbReader_PHP extends CdbReader { 00101 var $fileName; 00102 00104 var $handle; 00105 00106 /* number of hash slots searched under this key */ 00107 var $loop; 00108 00109 /* initialized if loop is nonzero */ 00110 var $khash; 00111 00112 /* initialized if loop is nonzero */ 00113 var $kpos; 00114 00115 /* initialized if loop is nonzero */ 00116 var $hpos; 00117 00118 /* initialized if loop is nonzero */ 00119 var $hslots; 00120 00121 /* initialized if findNext() returns true */ 00122 var $dpos; 00123 00124 /* initialized if cdb_findnext() returns 1 */ 00125 var $dlen; 00126 00131 function __construct( $fileName ) { 00132 $this->fileName = $fileName; 00133 $this->handle = fopen( $fileName, 'rb' ); 00134 if ( !$this->handle ) { 00135 throw new MWException( 'Unable to open CDB file "' . $this->fileName . '".' ); 00136 } 00137 $this->findStart(); 00138 } 00139 00140 function close() { 00141 if ( isset( $this->handle ) ) { 00142 fclose( $this->handle ); 00143 } 00144 unset( $this->handle ); 00145 } 00146 00151 public function get( $key ) { 00152 // strval is required 00153 if ( $this->find( strval( $key ) ) ) { 00154 return $this->read( $this->dlen, $this->dpos ); 00155 } else { 00156 return false; 00157 } 00158 } 00159 00165 protected function match( $key, $pos ) { 00166 $buf = $this->read( strlen( $key ), $pos ); 00167 return $buf === $key; 00168 } 00169 00170 protected function findStart() { 00171 $this->loop = 0; 00172 } 00173 00180 protected function read( $length, $pos ) { 00181 if ( fseek( $this->handle, $pos ) == -1 ) { 00182 // This can easily happen if the internal pointers are incorrect 00183 throw new MWException( 00184 'Seek failed, file "' . $this->fileName . '" may be corrupted.' ); 00185 } 00186 00187 if ( $length == 0 ) { 00188 return ''; 00189 } 00190 00191 $buf = fread( $this->handle, $length ); 00192 if ( $buf === false || strlen( $buf ) !== $length ) { 00193 throw new MWException( 00194 'Read from CDB file failed, file "' . $this->fileName . '" may be corrupted.' ); 00195 } 00196 return $buf; 00197 } 00198 00205 protected function unpack31( $s ) { 00206 $data = unpack( 'V', $s ); 00207 if ( $data[1] > 0x7fffffff ) { 00208 throw new MWException( 00209 'Error in CDB file "' . $this->fileName . '", integer too big.' ); 00210 } 00211 return $data[1]; 00212 } 00213 00219 protected function unpackSigned( $s ) { 00220 $data = unpack( 'va/vb', $s ); 00221 return $data['a'] | ( $data['b'] << 16 ); 00222 } 00223 00228 protected function findNext( $key ) { 00229 if ( !$this->loop ) { 00230 $u = CdbFunctions::hash( $key ); 00231 $buf = $this->read( 8, ( $u << 3 ) & 2047 ); 00232 $this->hslots = $this->unpack31( substr( $buf, 4 ) ); 00233 if ( !$this->hslots ) { 00234 return false; 00235 } 00236 $this->hpos = $this->unpack31( substr( $buf, 0, 4 ) ); 00237 $this->khash = $u; 00238 $u = CdbFunctions::unsignedShiftRight( $u, 8 ); 00239 $u = CdbFunctions::unsignedMod( $u, $this->hslots ); 00240 $u <<= 3; 00241 $this->kpos = $this->hpos + $u; 00242 } 00243 00244 while ( $this->loop < $this->hslots ) { 00245 $buf = $this->read( 8, $this->kpos ); 00246 $pos = $this->unpack31( substr( $buf, 4 ) ); 00247 if ( !$pos ) { 00248 return false; 00249 } 00250 $this->loop += 1; 00251 $this->kpos += 8; 00252 if ( $this->kpos == $this->hpos + ( $this->hslots << 3 ) ) { 00253 $this->kpos = $this->hpos; 00254 } 00255 $u = $this->unpackSigned( substr( $buf, 0, 4 ) ); 00256 if ( $u === $this->khash ) { 00257 $buf = $this->read( 8, $pos ); 00258 $keyLen = $this->unpack31( substr( $buf, 0, 4 ) ); 00259 if ( $keyLen == strlen( $key ) && $this->match( $key, $pos + 8 ) ) { 00260 // Found 00261 $this->dlen = $this->unpack31( substr( $buf, 4 ) ); 00262 $this->dpos = $pos + 8 + $keyLen; 00263 return true; 00264 } 00265 } 00266 } 00267 return false; 00268 } 00269 00274 protected function find( $key ) { 00275 $this->findStart(); 00276 return $this->findNext( $key ); 00277 } 00278 } 00279 00283 class CdbWriter_PHP extends CdbWriter { 00284 var $handle, $realFileName, $tmpFileName; 00285 00286 var $hplist; 00287 var $numentries, $pos; 00288 00292 function __construct( $fileName ) { 00293 $this->realFileName = $fileName; 00294 $this->tmpFileName = $fileName . '.tmp.' . mt_rand( 0, 0x7fffffff ); 00295 $this->handle = fopen( $this->tmpFileName, 'wb' ); 00296 if ( !$this->handle ) { 00297 $this->throwException( 00298 'Unable to open CDB file "' . $this->tmpFileName . '" for write.' ); 00299 } 00300 $this->hplist = array(); 00301 $this->numentries = 0; 00302 $this->pos = 2048; // leaving space for the pointer array, 256 * 8 00303 if ( fseek( $this->handle, $this->pos ) == -1 ) { 00304 $this->throwException( 'fseek failed in file "' . $this->tmpFileName . '".' ); 00305 } 00306 } 00307 00308 function __destruct() { 00309 if ( isset( $this->handle ) ) { 00310 $this->close(); 00311 } 00312 } 00313 00319 public function set( $key, $value ) { 00320 if ( strval( $key ) === '' ) { 00321 // DBA cross-check hack 00322 return; 00323 } 00324 $this->addbegin( strlen( $key ), strlen( $value ) ); 00325 $this->write( $key ); 00326 $this->write( $value ); 00327 $this->addend( strlen( $key ), strlen( $value ), CdbFunctions::hash( $key ) ); 00328 } 00329 00333 public function close() { 00334 $this->finish(); 00335 if ( isset( $this->handle ) ) { 00336 fclose( $this->handle ); 00337 } 00338 if ( wfIsWindows() && file_exists( $this->realFileName ) ) { 00339 unlink( $this->realFileName ); 00340 } 00341 if ( !rename( $this->tmpFileName, $this->realFileName ) ) { 00342 $this->throwException( 'Unable to move the new CDB file into place.' ); 00343 } 00344 unset( $this->handle ); 00345 } 00346 00351 protected function write( $buf ) { 00352 $len = fwrite( $this->handle, $buf ); 00353 if ( $len !== strlen( $buf ) ) { 00354 $this->throwException( 'Error writing to CDB file "' . $this->tmpFileName . '".' ); 00355 } 00356 } 00357 00362 protected function posplus( $len ) { 00363 $newpos = $this->pos + $len; 00364 if ( $newpos > 0x7fffffff ) { 00365 $this->throwException( 00366 'A value in the CDB file "' . $this->tmpFileName . '" is too large.' ); 00367 } 00368 $this->pos = $newpos; 00369 } 00370 00376 protected function addend( $keylen, $datalen, $h ) { 00377 $this->hplist[] = array( 00378 'h' => $h, 00379 'p' => $this->pos 00380 ); 00381 00382 $this->numentries++; 00383 $this->posplus( 8 ); 00384 $this->posplus( $keylen ); 00385 $this->posplus( $datalen ); 00386 } 00387 00393 protected function addbegin( $keylen, $datalen ) { 00394 if ( $keylen > 0x7fffffff ) { 00395 $this->throwException( 'Key length too long in file "' . $this->tmpFileName . '".' ); 00396 } 00397 if ( $datalen > 0x7fffffff ) { 00398 $this->throwException( 'Data length too long in file "' . $this->tmpFileName . '".' ); 00399 } 00400 $buf = pack( 'VV', $keylen, $datalen ); 00401 $this->write( $buf ); 00402 } 00403 00407 protected function finish() { 00408 // Hack for DBA cross-check 00409 $this->hplist = array_reverse( $this->hplist ); 00410 00411 // Calculate the number of items that will be in each hashtable 00412 $counts = array_fill( 0, 256, 0 ); 00413 foreach ( $this->hplist as $item ) { 00414 ++ $counts[255 & $item['h']]; 00415 } 00416 00417 // Fill in $starts with the *end* indexes 00418 $starts = array(); 00419 $pos = 0; 00420 for ( $i = 0; $i < 256; ++$i ) { 00421 $pos += $counts[$i]; 00422 $starts[$i] = $pos; 00423 } 00424 00425 // Excessively clever and indulgent code to simultaneously fill $packedTables 00426 // with the packed hashtables, and adjust the elements of $starts 00427 // to actually point to the starts instead of the ends. 00428 $packedTables = array_fill( 0, $this->numentries, false ); 00429 foreach ( $this->hplist as $item ) { 00430 $packedTables[--$starts[255 & $item['h']]] = $item; 00431 } 00432 00433 $final = ''; 00434 for ( $i = 0; $i < 256; ++$i ) { 00435 $count = $counts[$i]; 00436 00437 // The size of the hashtable will be double the item count. 00438 // The rest of the slots will be empty. 00439 $len = $count + $count; 00440 $final .= pack( 'VV', $this->pos, $len ); 00441 00442 $hashtable = array(); 00443 for ( $u = 0; $u < $len; ++$u ) { 00444 $hashtable[$u] = array( 'h' => 0, 'p' => 0 ); 00445 } 00446 00447 // Fill the hashtable, using the next empty slot if the hashed slot 00448 // is taken. 00449 for ( $u = 0; $u < $count; ++$u ) { 00450 $hp = $packedTables[$starts[$i] + $u]; 00451 $where = CdbFunctions::unsignedMod( 00452 CdbFunctions::unsignedShiftRight( $hp['h'], 8 ), $len ); 00453 while ( $hashtable[$where]['p'] ) { 00454 if ( ++$where == $len ) { 00455 $where = 0; 00456 } 00457 } 00458 $hashtable[$where] = $hp; 00459 } 00460 00461 // Write the hashtable 00462 for ( $u = 0; $u < $len; ++$u ) { 00463 $buf = pack( 'vvV', 00464 $hashtable[$u]['h'] & 0xffff, 00465 CdbFunctions::unsignedShiftRight( $hashtable[$u]['h'], 16 ), 00466 $hashtable[$u]['p'] ); 00467 $this->write( $buf ); 00468 $this->posplus( 8 ); 00469 } 00470 } 00471 00472 // Write the pointer array at the start of the file 00473 rewind( $this->handle ); 00474 if ( ftell( $this->handle ) != 0 ) { 00475 $this->throwException( 'Error rewinding to start of file "' . $this->tmpFileName . '".' ); 00476 } 00477 $this->write( $final ); 00478 } 00479 00486 protected function throwException( $msg ) { 00487 if ( $this->handle ) { 00488 fclose( $this->handle ); 00489 unlink( $this->tmpFileName ); 00490 } 00491 throw new MWException( $msg ); 00492 } 00493 }