MediaWiki
REL1_20
|
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 00130 function __construct( $fileName ) { 00131 $this->fileName = $fileName; 00132 $this->handle = fopen( $fileName, 'rb' ); 00133 if ( !$this->handle ) { 00134 throw new MWException( 'Unable to open CDB file "' . $this->fileName . '".' ); 00135 } 00136 $this->findStart(); 00137 } 00138 00139 function close() { 00140 if( isset( $this->handle ) ) { 00141 fclose( $this->handle ); 00142 } 00143 unset( $this->handle ); 00144 } 00145 00150 public function get( $key ) { 00151 // strval is required 00152 if ( $this->find( strval( $key ) ) ) { 00153 return $this->read( $this->dlen, $this->dpos ); 00154 } else { 00155 return false; 00156 } 00157 } 00158 00164 protected function match( $key, $pos ) { 00165 $buf = $this->read( strlen( $key ), $pos ); 00166 return $buf === $key; 00167 } 00168 00169 protected function findStart() { 00170 $this->loop = 0; 00171 } 00172 00179 protected function read( $length, $pos ) { 00180 if ( fseek( $this->handle, $pos ) == -1 ) { 00181 // This can easily happen if the internal pointers are incorrect 00182 throw new MWException( 00183 'Seek failed, file "' . $this->fileName . '" may be corrupted.' ); 00184 } 00185 00186 if ( $length == 0 ) { 00187 return ''; 00188 } 00189 00190 $buf = fread( $this->handle, $length ); 00191 if ( $buf === false || strlen( $buf ) !== $length ) { 00192 throw new MWException( 00193 'Read from CDB file failed, file "' . $this->fileName . '" may be corrupted.' ); 00194 } 00195 return $buf; 00196 } 00197 00203 protected function unpack31( $s ) { 00204 $data = unpack( 'V', $s ); 00205 if ( $data[1] > 0x7fffffff ) { 00206 throw new MWException( 00207 'Error in CDB file "' . $this->fileName . '", integer too big.' ); 00208 } 00209 return $data[1]; 00210 } 00211 00217 protected function unpackSigned( $s ) { 00218 $data = unpack( 'va/vb', $s ); 00219 return $data['a'] | ( $data['b'] << 16 ); 00220 } 00221 00226 protected function findNext( $key ) { 00227 if ( !$this->loop ) { 00228 $u = CdbFunctions::hash( $key ); 00229 $buf = $this->read( 8, ( $u << 3 ) & 2047 ); 00230 $this->hslots = $this->unpack31( substr( $buf, 4 ) ); 00231 if ( !$this->hslots ) { 00232 return false; 00233 } 00234 $this->hpos = $this->unpack31( substr( $buf, 0, 4 ) ); 00235 $this->khash = $u; 00236 $u = CdbFunctions::unsignedShiftRight( $u, 8 ); 00237 $u = CdbFunctions::unsignedMod( $u, $this->hslots ); 00238 $u <<= 3; 00239 $this->kpos = $this->hpos + $u; 00240 } 00241 00242 while ( $this->loop < $this->hslots ) { 00243 $buf = $this->read( 8, $this->kpos ); 00244 $pos = $this->unpack31( substr( $buf, 4 ) ); 00245 if ( !$pos ) { 00246 return false; 00247 } 00248 $this->loop += 1; 00249 $this->kpos += 8; 00250 if ( $this->kpos == $this->hpos + ( $this->hslots << 3 ) ) { 00251 $this->kpos = $this->hpos; 00252 } 00253 $u = $this->unpackSigned( substr( $buf, 0, 4 ) ); 00254 if ( $u === $this->khash ) { 00255 $buf = $this->read( 8, $pos ); 00256 $keyLen = $this->unpack31( substr( $buf, 0, 4 ) ); 00257 if ( $keyLen == strlen( $key ) && $this->match( $key, $pos + 8 ) ) { 00258 // Found 00259 $this->dlen = $this->unpack31( substr( $buf, 4 ) ); 00260 $this->dpos = $pos + 8 + $keyLen; 00261 return true; 00262 } 00263 } 00264 } 00265 return false; 00266 } 00267 00272 protected function find( $key ) { 00273 $this->findStart(); 00274 return $this->findNext( $key ); 00275 } 00276 } 00277 00281 class CdbWriter_PHP extends CdbWriter { 00282 var $handle, $realFileName, $tmpFileName; 00283 00284 var $hplist; 00285 var $numentries, $pos; 00286 00290 function __construct( $fileName ) { 00291 $this->realFileName = $fileName; 00292 $this->tmpFileName = $fileName . '.tmp.' . mt_rand( 0, 0x7fffffff ); 00293 $this->handle = fopen( $this->tmpFileName, 'wb' ); 00294 if ( !$this->handle ) { 00295 $this->throwException( 00296 'Unable to open CDB file "' . $this->tmpFileName . '" for write.' ); 00297 } 00298 $this->hplist = array(); 00299 $this->numentries = 0; 00300 $this->pos = 2048; // leaving space for the pointer array, 256 * 8 00301 if ( fseek( $this->handle, $this->pos ) == -1 ) { 00302 $this->throwException( 'fseek failed in file "' . $this->tmpFileName . '".' ); 00303 } 00304 } 00305 00306 function __destruct() { 00307 if ( isset( $this->handle ) ) { 00308 $this->close(); 00309 } 00310 } 00311 00317 public function set( $key, $value ) { 00318 if ( strval( $key ) === '' ) { 00319 // DBA cross-check hack 00320 return; 00321 } 00322 $this->addbegin( strlen( $key ), strlen( $value ) ); 00323 $this->write( $key ); 00324 $this->write( $value ); 00325 $this->addend( strlen( $key ), strlen( $value ), CdbFunctions::hash( $key ) ); 00326 } 00327 00331 public function close() { 00332 $this->finish(); 00333 if( isset($this->handle) ) { 00334 fclose( $this->handle ); 00335 } 00336 if ( wfIsWindows() && file_exists($this->realFileName) ) { 00337 unlink( $this->realFileName ); 00338 } 00339 if ( !rename( $this->tmpFileName, $this->realFileName ) ) { 00340 $this->throwException( 'Unable to move the new CDB file into place.' ); 00341 } 00342 unset( $this->handle ); 00343 } 00344 00349 protected function write( $buf ) { 00350 $len = fwrite( $this->handle, $buf ); 00351 if ( $len !== strlen( $buf ) ) { 00352 $this->throwException( 'Error writing to CDB file "'.$this->tmpFileName.'".' ); 00353 } 00354 } 00355 00360 protected function posplus( $len ) { 00361 $newpos = $this->pos + $len; 00362 if ( $newpos > 0x7fffffff ) { 00363 $this->throwException( 00364 'A value in the CDB file "'.$this->tmpFileName.'" is too large.' ); 00365 } 00366 $this->pos = $newpos; 00367 } 00368 00374 protected function addend( $keylen, $datalen, $h ) { 00375 $this->hplist[] = array( 00376 'h' => $h, 00377 'p' => $this->pos 00378 ); 00379 00380 $this->numentries++; 00381 $this->posplus( 8 ); 00382 $this->posplus( $keylen ); 00383 $this->posplus( $datalen ); 00384 } 00385 00391 protected function addbegin( $keylen, $datalen ) { 00392 if ( $keylen > 0x7fffffff ) { 00393 $this->throwException( 'Key length too long in file "'.$this->tmpFileName.'".' ); 00394 } 00395 if ( $datalen > 0x7fffffff ) { 00396 $this->throwException( 'Data length too long in file "'.$this->tmpFileName.'".' ); 00397 } 00398 $buf = pack( 'VV', $keylen, $datalen ); 00399 $this->write( $buf ); 00400 } 00401 00405 protected function finish() { 00406 // Hack for DBA cross-check 00407 $this->hplist = array_reverse( $this->hplist ); 00408 00409 // Calculate the number of items that will be in each hashtable 00410 $counts = array_fill( 0, 256, 0 ); 00411 foreach ( $this->hplist as $item ) { 00412 ++ $counts[ 255 & $item['h'] ]; 00413 } 00414 00415 // Fill in $starts with the *end* indexes 00416 $starts = array(); 00417 $pos = 0; 00418 for ( $i = 0; $i < 256; ++$i ) { 00419 $pos += $counts[$i]; 00420 $starts[$i] = $pos; 00421 } 00422 00423 // Excessively clever and indulgent code to simultaneously fill $packedTables 00424 // with the packed hashtables, and adjust the elements of $starts 00425 // to actually point to the starts instead of the ends. 00426 $packedTables = array_fill( 0, $this->numentries, false ); 00427 foreach ( $this->hplist as $item ) { 00428 $packedTables[--$starts[255 & $item['h']]] = $item; 00429 } 00430 00431 $final = ''; 00432 for ( $i = 0; $i < 256; ++$i ) { 00433 $count = $counts[$i]; 00434 00435 // The size of the hashtable will be double the item count. 00436 // The rest of the slots will be empty. 00437 $len = $count + $count; 00438 $final .= pack( 'VV', $this->pos, $len ); 00439 00440 $hashtable = array(); 00441 for ( $u = 0; $u < $len; ++$u ) { 00442 $hashtable[$u] = array( 'h' => 0, 'p' => 0 ); 00443 } 00444 00445 // Fill the hashtable, using the next empty slot if the hashed slot 00446 // is taken. 00447 for ( $u = 0; $u < $count; ++$u ) { 00448 $hp = $packedTables[$starts[$i] + $u]; 00449 $where = CdbFunctions::unsignedMod( 00450 CdbFunctions::unsignedShiftRight( $hp['h'], 8 ), $len ); 00451 while ( $hashtable[$where]['p'] ) 00452 if ( ++$where == $len ) 00453 $where = 0; 00454 $hashtable[$where] = $hp; 00455 } 00456 00457 // Write the hashtable 00458 for ( $u = 0; $u < $len; ++$u ) { 00459 $buf = pack( 'vvV', 00460 $hashtable[$u]['h'] & 0xffff, 00461 CdbFunctions::unsignedShiftRight( $hashtable[$u]['h'], 16 ), 00462 $hashtable[$u]['p'] ); 00463 $this->write( $buf ); 00464 $this->posplus( 8 ); 00465 } 00466 } 00467 00468 // Write the pointer array at the start of the file 00469 rewind( $this->handle ); 00470 if ( ftell( $this->handle ) != 0 ) { 00471 $this->throwException( 'Error rewinding to start of file "'.$this->tmpFileName.'".' ); 00472 } 00473 $this->write( $final ); 00474 } 00475 00482 protected function throwException( $msg ) { 00483 if ( $this->handle ) { 00484 fclose( $this->handle ); 00485 unlink( $this->tmpFileName ); 00486 } 00487 throw new MWException( $msg ); 00488 } 00489 }