10 * This is a port of D.J. Bernstein's CDB to PHP. It's based on the copy that
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
23 * You should have received a copy of the GNU General Public License along
24 * with this program; if not, write to the Free Software Foundation, Inc.,
25 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26 * http://www.gnu.org/copyleft/gpl.html
34 class PHP extends Reader {
36 /** @var string The file name of the CDB file. **/
39 /** @var string First 2048b of CDB file, containing pointers to hash table. **/
42 /** @var int Offset in file where value of found key starts. **/
45 /** @var int Byte length of found key's value. **/
48 /** @var int File position indicator when iterating over keys. **/
49 protected $keyIterPos = 2048;
51 /** @var int Offset in file where hash tables start. **/
52 protected $keyIterStop;
54 /** @var string Read buffer for CDB file. **/
57 /** @var int File offset where read buffer starts. **/
60 /** @var int File handle position indicator **/
61 protected $filePos = 2048;
66 * @param string $fileName
67 * @throws Exception If CDB file cannot be opened or if it contains fewer
68 * than 2048 bytes of data.
70 public function __construct( $fileName ) {
71 $this->fileName = $fileName;
72 $this->handle = fopen( $fileName, 'rb' );
73 if ( !$this->handle ) {
74 throw new Exception( 'Unable to open CDB file "' . $this->fileName . '".' );
76 $this->index = fread( $this->handle, 2048 );
77 if ( strlen( $this->index ) !== 2048 ) {
78 throw new Exception( 'CDB file contains fewer than 2048 bytes of data.' );
83 * Close the handle on the CDB file.
85 public function close() {
86 if ( isset( $this->handle ) ) {
87 fclose( $this->handle );
89 unset( $this->handle );
93 * Get the value of a key.
96 * @return bool|string The key's value or false if not found.
98 public function get( $key ) {
100 if ( $this->find( strval( $key ) ) ) {
101 return $this->read( $this->dataPos, $this->dataLen );
108 * Read data from the CDB file.
110 * @throws Exception When attempting to read past the end of the file.
111 * @param int $start Start reading from this position.
112 * @param int $len Number of bytes to read.
113 * @return string Read data.
115 protected function read( $start, $len ) {
116 $end = $start + $len;
118 // The first 2048 bytes are the lookup table, which is read into
119 // memory on initialization.
120 if ( $end <= 2048 ) {
121 return substr( $this->index, $start, $len );
124 // Read data from the internal buffer first.
126 if ( $this->buf && $start >= $this->bufStart ) {
127 $bytes .= substr( $this->buf, $start - $this->bufStart, $len );
128 $bytesRead = strlen( $bytes );
130 $start += $bytesRead;
139 // Many reads are sequential, so the file position indicator may
140 // already be in the right place, in which case we can avoid the
142 if ( $start !== $this->filePos ) {
143 if ( fseek( $this->handle, $start ) === -1 ) {
144 // This can easily happen if the internal pointers are incorrect
146 'Seek failed, file "' . $this->fileName . '" may be corrupted.' );
150 $buf = fread( $this->handle, max( $len, 1024 ) );
151 if ( $buf === false ) {
155 $bytes .= substr( $buf, 0, $len );
156 if ( strlen( $bytes ) !== $len + $bytesRead ) {
158 'Read from CDB file failed, file "' . $this->fileName . '" may be corrupted.' );
161 $this->filePos = $end;
162 $this->bufStart = $start;
169 * Unpack an unsigned integer and throw an exception if it needs more than 31 bits.
171 * @param int $pos Position to read from.
172 * @throws Exception When the integer cannot be represented in 31 bits.
175 protected function readInt31( $pos = 0 ) {
176 $uint31 = $this->readInt32( $pos );
177 if ( $uint31 > 0x7fffffff ) {
179 'Error in CDB file "' . $this->fileName . '", integer too big.' );
186 * Unpack a 32-bit integer.
191 protected function readInt32( $pos = 0 ) {
196 for ( $i = 1; $i < 256; $i++ ) {
197 $lookups[ chr( $i ) ] = $i;
201 $buf = $this->read( $pos, 4 );
205 if ( $buf[0] !== "\x0" ) {
206 $rv = $lookups[ $buf[0] ];
208 if ( $buf[1] !== "\x0" ) {
209 $rv |= ( $lookups[ $buf[1] ] << 8 );
211 if ( $buf[2] !== "\x0" ) {
212 $rv |= ( $lookups[ $buf[2] ] << 16 );
214 if ( $buf[3] !== "\x0" ) {
215 $rv |= ( $lookups[ $buf[3] ] << 24 );
222 * Search the CDB file for a key.
224 * Sets `dataLen` and `dataPos` properties if successful.
227 * @return bool Whether the key was found.
229 protected function find( $key ) {
230 $keyLen = strlen( $key );
232 $u = Util::hash( $key );
233 $upos = ( $u << 3 ) & 2047;
234 $hashSlots = $this->readInt31( $upos + 4 );
238 $hashPos = $this->readInt31( $upos );
240 $u = Util::unsignedShiftRight( $u, 8 );
241 $u = Util::unsignedMod( $u, $hashSlots );
243 $keyPos = $hashPos + $u;
245 for ( $i = 0; $i < $hashSlots; $i++ ) {
246 $hash = $this->readInt32( $keyPos );
247 $pos = $this->readInt31( $keyPos + 4 );
252 if ( $keyPos == $hashPos + ( $hashSlots << 3 ) ) {
255 if ( $hash === $keyHash ) {
256 if ( $keyLen === $this->readInt31( $pos ) ) {
257 $dataLen = $this->readInt31( $pos + 4 );
258 $dataPos = $pos + 8 + $keyLen;
259 $foundKey = $this->read( $pos + 8, $keyLen );
260 if ( $foundKey === $key ) {
262 $this->dataLen = $dataLen;
263 $this->dataPos = $dataPos;
275 * Check if a key exists in the CDB file.
278 * @return bool Whether the key exists.
280 public function exists( $key ) {
281 return $this->find( strval( $key ) );
285 * Get the first key from the CDB file and reset the key iterator.
287 * @return string|bool Key, or false if no keys in file.
289 public function firstkey() {
290 $this->keyIterPos = 4;
292 if ( !$this->keyIterStop ) {
294 for ( $i = 0; $i < 2048; $i+= 8 ) {
295 $pos = min( $this->readInt31( $i ), $pos );
297 $this->keyIterStop = $pos;
300 $this->keyIterPos = 2048;
301 return $this->nextkey();
305 * Get the next key from the CDB file.
307 * @return string|bool Key, or false if no more keys.
309 public function nextkey() {
310 if ( $this->keyIterPos >= $this->keyIterStop ) {
313 $keyLen = $this->readInt31( $this->keyIterPos );
314 $dataLen = $this->readInt31( $this->keyIterPos + 4 );
315 $key = $this->read( $this->keyIterPos + 8, $keyLen );
316 $this->keyIterPos += 8 + $keyLen + $dataLen;