MediaWiki
REL1_23
|
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 var $fileName; 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 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 var $hplist; 00292 var $numentries, $pos; 00293 00297 public function __construct( $fileName ) { 00298 $this->realFileName = $fileName; 00299 $this->tmpFileName = $fileName . '.tmp.' . mt_rand( 0, 0x7fffffff ); 00300 $this->handle = fopen( $this->tmpFileName, 'wb' ); 00301 if ( !$this->handle ) { 00302 $this->throwException( 00303 'Unable to open CDB file "' . $this->tmpFileName . '" for write.' ); 00304 } 00305 $this->hplist = array(); 00306 $this->numentries = 0; 00307 $this->pos = 2048; // leaving space for the pointer array, 256 * 8 00308 if ( fseek( $this->handle, $this->pos ) == -1 ) { 00309 $this->throwException( 'fseek failed in file "' . $this->tmpFileName . '".' ); 00310 } 00311 } 00312 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 ( $this->isWindows() && 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 } 00455 } 00456 $hashtable[$where] = $hp; 00457 } 00458 00459 // Write the hashtable 00460 for ( $u = 0; $u < $len; ++$u ) { 00461 $buf = pack( 'vvV', 00462 $hashtable[$u]['h'] & 0xffff, 00463 CdbFunctions::unsignedShiftRight( $hashtable[$u]['h'], 16 ), 00464 $hashtable[$u]['p'] ); 00465 $this->write( $buf ); 00466 $this->posplus( 8 ); 00467 } 00468 } 00469 00470 // Write the pointer array at the start of the file 00471 rewind( $this->handle ); 00472 if ( ftell( $this->handle ) != 0 ) { 00473 $this->throwException( 'Error rewinding to start of file "' . $this->tmpFileName . '".' ); 00474 } 00475 $this->write( $final ); 00476 } 00477 00484 protected function throwException( $msg ) { 00485 if ( $this->handle ) { 00486 fclose( $this->handle ); 00487 unlink( $this->tmpFileName ); 00488 } 00489 throw new CdbException( $msg ); 00490 } 00491 }