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