cdb: One class per file
[lhc/web/wiklou.git] / includes / libs / cdb / CdbReaderPHP.php
1 <?php
2 /**
3 * This is a port of D.J. Bernstein's CDB to PHP. It's based on the copy that
4 * appears in PHP 5.3. Changes are:
5 * * Error returns replaced with exceptions
6 * * Exception thrown if sizes or offsets are between 2GB and 4GB
7 * * Some variables renamed
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License along
20 * with this program; if not, write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 * http://www.gnu.org/copyleft/gpl.html
23 *
24 * @file
25 */
26
27 /**
28 * CDB reader class
29 */
30 class CdbReaderPHP extends CdbReader {
31 /** The filename */
32 protected $fileName;
33
34 /* number of hash slots searched under this key */
35 protected $loop;
36
37 /* initialized if loop is nonzero */
38 protected $khash;
39
40 /* initialized if loop is nonzero */
41 protected $kpos;
42
43 /* initialized if loop is nonzero */
44 protected $hpos;
45
46 /* initialized if loop is nonzero */
47 protected $hslots;
48
49 /* initialized if findNext() returns true */
50 protected $dpos;
51
52 /* initialized if cdb_findnext() returns 1 */
53 protected $dlen;
54
55 /**
56 * @param string $fileName
57 * @throws CdbException
58 */
59 public function __construct( $fileName ) {
60 $this->fileName = $fileName;
61 $this->handle = fopen( $fileName, 'rb' );
62 if ( !$this->handle ) {
63 throw new CdbException( 'Unable to open CDB file "' . $this->fileName . '".' );
64 }
65 $this->findStart();
66 }
67
68 public function close() {
69 if ( isset( $this->handle ) ) {
70 fclose( $this->handle );
71 }
72 unset( $this->handle );
73 }
74
75 /**
76 * @param mixed $key
77 * @return bool|string
78 */
79 public function get( $key ) {
80 // strval is required
81 if ( $this->find( strval( $key ) ) ) {
82 return $this->read( $this->dlen, $this->dpos );
83 } else {
84 return false;
85 }
86 }
87
88 /**
89 * @param string $key
90 * @param int $pos
91 * @return bool
92 */
93 protected function match( $key, $pos ) {
94 $buf = $this->read( strlen( $key ), $pos );
95
96 return $buf === $key;
97 }
98
99 protected function findStart() {
100 $this->loop = 0;
101 }
102
103 /**
104 * @throws CdbException
105 * @param int $length
106 * @param int $pos
107 * @return string
108 */
109 protected function read( $length, $pos ) {
110 if ( fseek( $this->handle, $pos ) == -1 ) {
111 // This can easily happen if the internal pointers are incorrect
112 throw new CdbException(
113 'Seek failed, file "' . $this->fileName . '" may be corrupted.' );
114 }
115
116 if ( $length == 0 ) {
117 return '';
118 }
119
120 $buf = fread( $this->handle, $length );
121 if ( $buf === false || strlen( $buf ) !== $length ) {
122 throw new CdbException(
123 'Read from CDB file failed, file "' . $this->fileName . '" may be corrupted.' );
124 }
125
126 return $buf;
127 }
128
129 /**
130 * Unpack an unsigned integer and throw an exception if it needs more than 31 bits
131 * @param string $s
132 * @throws CdbException
133 * @return mixed
134 */
135 protected function unpack31( $s ) {
136 $data = unpack( 'V', $s );
137 if ( $data[1] > 0x7fffffff ) {
138 throw new CdbException(
139 'Error in CDB file "' . $this->fileName . '", integer too big.' );
140 }
141
142 return $data[1];
143 }
144
145 /**
146 * Unpack a 32-bit signed integer
147 * @param string $s
148 * @return int
149 */
150 protected function unpackSigned( $s ) {
151 $data = unpack( 'va/vb', $s );
152
153 return $data['a'] | ( $data['b'] << 16 );
154 }
155
156 /**
157 * @param string $key
158 * @return bool
159 */
160 protected function findNext( $key ) {
161 if ( !$this->loop ) {
162 $u = CdbFunctions::hash( $key );
163 $buf = $this->read( 8, ( $u << 3 ) & 2047 );
164 $this->hslots = $this->unpack31( substr( $buf, 4 ) );
165 if ( !$this->hslots ) {
166 return false;
167 }
168 $this->hpos = $this->unpack31( substr( $buf, 0, 4 ) );
169 $this->khash = $u;
170 $u = CdbFunctions::unsignedShiftRight( $u, 8 );
171 $u = CdbFunctions::unsignedMod( $u, $this->hslots );
172 $u <<= 3;
173 $this->kpos = $this->hpos + $u;
174 }
175
176 while ( $this->loop < $this->hslots ) {
177 $buf = $this->read( 8, $this->kpos );
178 $pos = $this->unpack31( substr( $buf, 4 ) );
179 if ( !$pos ) {
180 return false;
181 }
182 $this->loop += 1;
183 $this->kpos += 8;
184 if ( $this->kpos == $this->hpos + ( $this->hslots << 3 ) ) {
185 $this->kpos = $this->hpos;
186 }
187 $u = $this->unpackSigned( substr( $buf, 0, 4 ) );
188 if ( $u === $this->khash ) {
189 $buf = $this->read( 8, $pos );
190 $keyLen = $this->unpack31( substr( $buf, 0, 4 ) );
191 if ( $keyLen == strlen( $key ) && $this->match( $key, $pos + 8 ) ) {
192 // Found
193 $this->dlen = $this->unpack31( substr( $buf, 4 ) );
194 $this->dpos = $pos + 8 + $keyLen;
195
196 return true;
197 }
198 }
199 }
200
201 return false;
202 }
203
204 /**
205 * @param mixed $key
206 * @return bool
207 */
208 protected function find( $key ) {
209 $this->findStart();
210
211 return $this->findNext( $key );
212 }
213 }
214