MediaWiki
REL1_19
|
00001 <?php 00015 class CdbFunctions { 00025 public static function unsignedMod( $a, $b ) { 00026 if ( $a & 0x80000000 ) { 00027 $m = ( $a & 0x7fffffff ) % $b + 2 * ( 0x40000000 % $b ); 00028 return $m % $b; 00029 } else { 00030 return $a % $b; 00031 } 00032 } 00033 00040 public static function unsignedShiftRight( $a, $b ) { 00041 if ( $b == 0 ) { 00042 return $a; 00043 } 00044 if ( $a & 0x80000000 ) { 00045 return ( ( $a & 0x7fffffff ) >> $b ) | ( 0x40000000 >> ( $b - 1 ) ); 00046 } else { 00047 return $a >> $b; 00048 } 00049 } 00050 00058 public static function hash( $s ) { 00059 $h = 5381; 00060 for ( $i = 0; $i < strlen( $s ); $i++ ) { 00061 $h5 = ($h << 5) & 0xffffffff; 00062 // Do a 32-bit sum 00063 // Inlined here for speed 00064 $sum = ($h & 0x3fffffff) + ($h5 & 0x3fffffff); 00065 $h = 00066 ( 00067 ( $sum & 0x40000000 ? 1 : 0 ) 00068 + ( $h & 0x80000000 ? 2 : 0 ) 00069 + ( $h & 0x40000000 ? 1 : 0 ) 00070 + ( $h5 & 0x80000000 ? 2 : 0 ) 00071 + ( $h5 & 0x40000000 ? 1 : 0 ) 00072 ) << 30 00073 | ( $sum & 0x3fffffff ); 00074 $h ^= ord( $s[$i] ); 00075 $h &= 0xffffffff; 00076 } 00077 return $h; 00078 } 00079 } 00080 00084 class CdbReader_PHP extends CdbReader { 00086 var $fileName; 00087 00089 var $handle; 00090 00091 /* number of hash slots searched under this key */ 00092 var $loop; 00093 00094 /* initialized if loop is nonzero */ 00095 var $khash; 00096 00097 /* initialized if loop is nonzero */ 00098 var $kpos; 00099 00100 /* initialized if loop is nonzero */ 00101 var $hpos; 00102 00103 /* initialized if loop is nonzero */ 00104 var $hslots; 00105 00106 /* initialized if findNext() returns true */ 00107 var $dpos; 00108 00109 /* initialized if cdb_findnext() returns 1 */ 00110 var $dlen; 00111 00115 function __construct( $fileName ) { 00116 $this->fileName = $fileName; 00117 $this->handle = fopen( $fileName, 'rb' ); 00118 if ( !$this->handle ) { 00119 throw new MWException( 'Unable to open CDB file "' . $this->fileName . '".' ); 00120 } 00121 $this->findStart(); 00122 } 00123 00124 function close() { 00125 if( isset( $this->handle ) ) { 00126 fclose( $this->handle ); 00127 } 00128 unset( $this->handle ); 00129 } 00130 00135 public function get( $key ) { 00136 // strval is required 00137 if ( $this->find( strval( $key ) ) ) { 00138 return $this->read( $this->dlen, $this->dpos ); 00139 } else { 00140 return false; 00141 } 00142 } 00143 00149 protected function match( $key, $pos ) { 00150 $buf = $this->read( strlen( $key ), $pos ); 00151 return $buf === $key; 00152 } 00153 00154 protected function findStart() { 00155 $this->loop = 0; 00156 } 00157 00164 protected function read( $length, $pos ) { 00165 if ( fseek( $this->handle, $pos ) == -1 ) { 00166 // This can easily happen if the internal pointers are incorrect 00167 throw new MWException( 00168 'Seek failed, file "' . $this->fileName . '" may be corrupted.' ); 00169 } 00170 00171 if ( $length == 0 ) { 00172 return ''; 00173 } 00174 00175 $buf = fread( $this->handle, $length ); 00176 if ( $buf === false || strlen( $buf ) !== $length ) { 00177 throw new MWException( 00178 'Read from CDB file failed, file "' . $this->fileName . '" may be corrupted.' ); 00179 } 00180 return $buf; 00181 } 00182 00188 protected function unpack31( $s ) { 00189 $data = unpack( 'V', $s ); 00190 if ( $data[1] > 0x7fffffff ) { 00191 throw new MWException( 00192 'Error in CDB file "' . $this->fileName . '", integer too big.' ); 00193 } 00194 return $data[1]; 00195 } 00196 00202 protected function unpackSigned( $s ) { 00203 $data = unpack( 'va/vb', $s ); 00204 return $data['a'] | ( $data['b'] << 16 ); 00205 } 00206 00211 protected function findNext( $key ) { 00212 if ( !$this->loop ) { 00213 $u = CdbFunctions::hash( $key ); 00214 $buf = $this->read( 8, ( $u << 3 ) & 2047 ); 00215 $this->hslots = $this->unpack31( substr( $buf, 4 ) ); 00216 if ( !$this->hslots ) { 00217 return false; 00218 } 00219 $this->hpos = $this->unpack31( substr( $buf, 0, 4 ) ); 00220 $this->khash = $u; 00221 $u = CdbFunctions::unsignedShiftRight( $u, 8 ); 00222 $u = CdbFunctions::unsignedMod( $u, $this->hslots ); 00223 $u <<= 3; 00224 $this->kpos = $this->hpos + $u; 00225 } 00226 00227 while ( $this->loop < $this->hslots ) { 00228 $buf = $this->read( 8, $this->kpos ); 00229 $pos = $this->unpack31( substr( $buf, 4 ) ); 00230 if ( !$pos ) { 00231 return false; 00232 } 00233 $this->loop += 1; 00234 $this->kpos += 8; 00235 if ( $this->kpos == $this->hpos + ( $this->hslots << 3 ) ) { 00236 $this->kpos = $this->hpos; 00237 } 00238 $u = $this->unpackSigned( substr( $buf, 0, 4 ) ); 00239 if ( $u === $this->khash ) { 00240 $buf = $this->read( 8, $pos ); 00241 $keyLen = $this->unpack31( substr( $buf, 0, 4 ) ); 00242 if ( $keyLen == strlen( $key ) && $this->match( $key, $pos + 8 ) ) { 00243 // Found 00244 $this->dlen = $this->unpack31( substr( $buf, 4 ) ); 00245 $this->dpos = $pos + 8 + $keyLen; 00246 return true; 00247 } 00248 } 00249 } 00250 return false; 00251 } 00252 00257 protected function find( $key ) { 00258 $this->findStart(); 00259 return $this->findNext( $key ); 00260 } 00261 } 00262 00266 class CdbWriter_PHP extends CdbWriter { 00267 var $handle, $realFileName, $tmpFileName; 00268 00269 var $hplist; 00270 var $numentries, $pos; 00271 00275 function __construct( $fileName ) { 00276 $this->realFileName = $fileName; 00277 $this->tmpFileName = $fileName . '.tmp.' . mt_rand( 0, 0x7fffffff ); 00278 $this->handle = fopen( $this->tmpFileName, 'wb' ); 00279 if ( !$this->handle ) { 00280 $this->throwException( 00281 'Unable to open CDB file "' . $this->tmpFileName . '" for write.' ); 00282 } 00283 $this->hplist = array(); 00284 $this->numentries = 0; 00285 $this->pos = 2048; // leaving space for the pointer array, 256 * 8 00286 if ( fseek( $this->handle, $this->pos ) == -1 ) { 00287 $this->throwException( 'fseek failed in file "' . $this->tmpFileName . '".' ); 00288 } 00289 } 00290 00291 function __destruct() { 00292 if ( isset( $this->handle ) ) { 00293 $this->close(); 00294 } 00295 } 00296 00302 public function set( $key, $value ) { 00303 if ( strval( $key ) === '' ) { 00304 // DBA cross-check hack 00305 return; 00306 } 00307 $this->addbegin( strlen( $key ), strlen( $value ) ); 00308 $this->write( $key ); 00309 $this->write( $value ); 00310 $this->addend( strlen( $key ), strlen( $value ), CdbFunctions::hash( $key ) ); 00311 } 00312 00316 public function close() { 00317 $this->finish(); 00318 if( isset($this->handle) ) { 00319 fclose( $this->handle ); 00320 } 00321 if ( wfIsWindows() && file_exists($this->realFileName) ) { 00322 unlink( $this->realFileName ); 00323 } 00324 if ( !rename( $this->tmpFileName, $this->realFileName ) ) { 00325 $this->throwException( 'Unable to move the new CDB file into place.' ); 00326 } 00327 unset( $this->handle ); 00328 } 00329 00334 protected function write( $buf ) { 00335 $len = fwrite( $this->handle, $buf ); 00336 if ( $len !== strlen( $buf ) ) { 00337 $this->throwException( 'Error writing to CDB file "'.$this->tmpFileName.'".' ); 00338 } 00339 } 00340 00345 protected function posplus( $len ) { 00346 $newpos = $this->pos + $len; 00347 if ( $newpos > 0x7fffffff ) { 00348 $this->throwException( 00349 'A value in the CDB file "'.$this->tmpFileName.'" is too large.' ); 00350 } 00351 $this->pos = $newpos; 00352 } 00353 00359 protected function addend( $keylen, $datalen, $h ) { 00360 $this->hplist[] = array( 00361 'h' => $h, 00362 'p' => $this->pos 00363 ); 00364 00365 $this->numentries++; 00366 $this->posplus( 8 ); 00367 $this->posplus( $keylen ); 00368 $this->posplus( $datalen ); 00369 } 00370 00376 protected function addbegin( $keylen, $datalen ) { 00377 if ( $keylen > 0x7fffffff ) { 00378 $this->throwException( 'Key length too long in file "'.$this->tmpFileName.'".' ); 00379 } 00380 if ( $datalen > 0x7fffffff ) { 00381 $this->throwException( 'Data length too long in file "'.$this->tmpFileName.'".' ); 00382 } 00383 $buf = pack( 'VV', $keylen, $datalen ); 00384 $this->write( $buf ); 00385 } 00386 00390 protected function finish() { 00391 // Hack for DBA cross-check 00392 $this->hplist = array_reverse( $this->hplist ); 00393 00394 // Calculate the number of items that will be in each hashtable 00395 $counts = array_fill( 0, 256, 0 ); 00396 foreach ( $this->hplist as $item ) { 00397 ++ $counts[ 255 & $item['h'] ]; 00398 } 00399 00400 // Fill in $starts with the *end* indexes 00401 $starts = array(); 00402 $pos = 0; 00403 for ( $i = 0; $i < 256; ++$i ) { 00404 $pos += $counts[$i]; 00405 $starts[$i] = $pos; 00406 } 00407 00408 // Excessively clever and indulgent code to simultaneously fill $packedTables 00409 // with the packed hashtables, and adjust the elements of $starts 00410 // to actually point to the starts instead of the ends. 00411 $packedTables = array_fill( 0, $this->numentries, false ); 00412 foreach ( $this->hplist as $item ) { 00413 $packedTables[--$starts[255 & $item['h']]] = $item; 00414 } 00415 00416 $final = ''; 00417 for ( $i = 0; $i < 256; ++$i ) { 00418 $count = $counts[$i]; 00419 00420 // The size of the hashtable will be double the item count. 00421 // The rest of the slots will be empty. 00422 $len = $count + $count; 00423 $final .= pack( 'VV', $this->pos, $len ); 00424 00425 $hashtable = array(); 00426 for ( $u = 0; $u < $len; ++$u ) { 00427 $hashtable[$u] = array( 'h' => 0, 'p' => 0 ); 00428 } 00429 00430 // Fill the hashtable, using the next empty slot if the hashed slot 00431 // is taken. 00432 for ( $u = 0; $u < $count; ++$u ) { 00433 $hp = $packedTables[$starts[$i] + $u]; 00434 $where = CdbFunctions::unsignedMod( 00435 CdbFunctions::unsignedShiftRight( $hp['h'], 8 ), $len ); 00436 while ( $hashtable[$where]['p'] ) 00437 if ( ++$where == $len ) 00438 $where = 0; 00439 $hashtable[$where] = $hp; 00440 } 00441 00442 // Write the hashtable 00443 for ( $u = 0; $u < $len; ++$u ) { 00444 $buf = pack( 'vvV', 00445 $hashtable[$u]['h'] & 0xffff, 00446 CdbFunctions::unsignedShiftRight( $hashtable[$u]['h'], 16 ), 00447 $hashtable[$u]['p'] ); 00448 $this->write( $buf ); 00449 $this->posplus( 8 ); 00450 } 00451 } 00452 00453 // Write the pointer array at the start of the file 00454 rewind( $this->handle ); 00455 if ( ftell( $this->handle ) != 0 ) { 00456 $this->throwException( 'Error rewinding to start of file "'.$this->tmpFileName.'".' ); 00457 } 00458 $this->write( $final ); 00459 } 00460 00467 protected function throwException( $msg ) { 00468 if ( $this->handle ) { 00469 fclose( $this->handle ); 00470 unlink( $this->tmpFileName ); 00471 } 00472 throw new MWException( $msg ); 00473 } 00474 }