PageRenderTime 45ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/wikimedia/cdb/src/Writer/PHP.php

https://gitlab.com/link233/bootmw
PHP | 241 lines | 139 code | 25 blank | 77 comment | 23 complexity | 6133b29a3ee39460b7e01b0fca28d49e MD5 | raw file
  1. <?php
  2. namespace Cdb\Writer;
  3. use Cdb\Exception;
  4. use Cdb\Util;
  5. use Cdb\Writer;
  6. /**
  7. * This is a port of D.J. Bernstein's CDB to PHP. It's based on the copy that
  8. * appears in PHP 5.3. Changes are:
  9. * * Error returns replaced with exceptions
  10. * * Exception thrown if sizes or offsets are between 2GB and 4GB
  11. * * Some variables renamed
  12. *
  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.
  17. *
  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.
  22. *
  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
  27. *
  28. * @file
  29. */
  30. /**
  31. * CDB writer class
  32. */
  33. class PHP extends Writer {
  34. protected $hplist;
  35. protected $numentries;
  36. protected $pos;
  37. /**
  38. * @param string $fileName
  39. */
  40. public function __construct( $fileName ) {
  41. $this->realFileName = $fileName;
  42. $this->tmpFileName = $fileName . '.tmp.' . mt_rand( 0, 0x7fffffff );
  43. $this->handle = fopen( $this->tmpFileName, 'wb' );
  44. if ( !$this->handle ) {
  45. $this->throwException(
  46. 'Unable to open CDB file "' . $this->tmpFileName . '" for write.' );
  47. }
  48. $this->hplist = array();
  49. $this->numentries = 0;
  50. $this->pos = 2048; // leaving space for the pointer array, 256 * 8
  51. if ( fseek( $this->handle, $this->pos ) == -1 ) {
  52. $this->throwException( 'fseek failed in file "' . $this->tmpFileName . '".' );
  53. }
  54. }
  55. /**
  56. * @param string $key
  57. * @param string $value
  58. */
  59. public function set( $key, $value ) {
  60. if ( strval( $key ) === '' ) {
  61. // DBA cross-check hack
  62. return;
  63. }
  64. $this->addbegin( strlen( $key ), strlen( $value ) );
  65. $this->write( $key );
  66. $this->write( $value );
  67. $this->addend( strlen( $key ), strlen( $value ), Util::hash( $key ) );
  68. }
  69. /**
  70. * @throws Exception
  71. */
  72. public function close() {
  73. $this->finish();
  74. if ( isset( $this->handle ) ) {
  75. fclose( $this->handle );
  76. }
  77. if ( $this->isWindows() && file_exists( $this->realFileName ) ) {
  78. unlink( $this->realFileName );
  79. }
  80. if ( !rename( $this->tmpFileName, $this->realFileName ) ) {
  81. $this->throwException( 'Unable to move the new CDB file into place.' );
  82. }
  83. unset( $this->handle );
  84. }
  85. /**
  86. * @throws Exception
  87. * @param string $buf
  88. */
  89. protected function write( $buf ) {
  90. $len = fwrite( $this->handle, $buf );
  91. if ( $len !== strlen( $buf ) ) {
  92. $this->throwException( 'Error writing to CDB file "' . $this->tmpFileName . '".' );
  93. }
  94. }
  95. /**
  96. * @throws Exception
  97. * @param int $len
  98. */
  99. protected function posplus( $len ) {
  100. $newpos = $this->pos + $len;
  101. if ( $newpos > 0x7fffffff ) {
  102. $this->throwException(
  103. 'A value in the CDB file "' . $this->tmpFileName . '" is too large.' );
  104. }
  105. $this->pos = $newpos;
  106. }
  107. /**
  108. * @param int $keylen
  109. * @param int $datalen
  110. * @param int $h
  111. */
  112. protected function addend( $keylen, $datalen, $h ) {
  113. $this->hplist[] = array(
  114. 'h' => $h,
  115. 'p' => $this->pos
  116. );
  117. $this->numentries++;
  118. $this->posplus( 8 );
  119. $this->posplus( $keylen );
  120. $this->posplus( $datalen );
  121. }
  122. /**
  123. * @throws Exception
  124. * @param int $keylen
  125. * @param int $datalen
  126. */
  127. protected function addbegin( $keylen, $datalen ) {
  128. if ( $keylen > 0x7fffffff ) {
  129. $this->throwException( 'Key length too long in file "' . $this->tmpFileName . '".' );
  130. }
  131. if ( $datalen > 0x7fffffff ) {
  132. $this->throwException( 'Data length too long in file "' . $this->tmpFileName . '".' );
  133. }
  134. $buf = pack( 'VV', $keylen, $datalen );
  135. $this->write( $buf );
  136. }
  137. /**
  138. * @throws Exception
  139. */
  140. protected function finish() {
  141. // Hack for DBA cross-check
  142. $this->hplist = array_reverse( $this->hplist );
  143. // Calculate the number of items that will be in each hashtable
  144. $counts = array_fill( 0, 256, 0 );
  145. foreach ( $this->hplist as $item ) {
  146. ++$counts[255 & $item['h']];
  147. }
  148. // Fill in $starts with the *end* indexes
  149. $starts = array();
  150. $pos = 0;
  151. for ( $i = 0; $i < 256; ++$i ) {
  152. $pos += $counts[$i];
  153. $starts[$i] = $pos;
  154. }
  155. // Excessively clever and indulgent code to simultaneously fill $packedTables
  156. // with the packed hashtables, and adjust the elements of $starts
  157. // to actually point to the starts instead of the ends.
  158. $packedTables = array_fill( 0, $this->numentries, false );
  159. foreach ( $this->hplist as $item ) {
  160. $packedTables[--$starts[255 & $item['h']]] = $item;
  161. }
  162. $final = '';
  163. for ( $i = 0; $i < 256; ++$i ) {
  164. $count = $counts[$i];
  165. // The size of the hashtable will be double the item count.
  166. // The rest of the slots will be empty.
  167. $len = $count + $count;
  168. $final .= pack( 'VV', $this->pos, $len );
  169. $hashtable = array();
  170. for ( $u = 0; $u < $len; ++$u ) {
  171. $hashtable[$u] = array( 'h' => 0, 'p' => 0 );
  172. }
  173. // Fill the hashtable, using the next empty slot if the hashed slot
  174. // is taken.
  175. for ( $u = 0; $u < $count; ++$u ) {
  176. $hp = $packedTables[$starts[$i] + $u];
  177. $where = Util::unsignedMod(
  178. Util::unsignedShiftRight( $hp['h'], 8 ), $len );
  179. while ( $hashtable[$where]['p'] ) {
  180. if ( ++$where == $len ) {
  181. $where = 0;
  182. }
  183. }
  184. $hashtable[$where] = $hp;
  185. }
  186. // Write the hashtable
  187. for ( $u = 0; $u < $len; ++$u ) {
  188. $buf = pack( 'vvV',
  189. $hashtable[$u]['h'] & 0xffff,
  190. Util::unsignedShiftRight( $hashtable[$u]['h'], 16 ),
  191. $hashtable[$u]['p'] );
  192. $this->write( $buf );
  193. $this->posplus( 8 );
  194. }
  195. }
  196. // Write the pointer array at the start of the file
  197. rewind( $this->handle );
  198. if ( ftell( $this->handle ) != 0 ) {
  199. $this->throwException( 'Error rewinding to start of file "' . $this->tmpFileName . '".' );
  200. }
  201. $this->write( $final );
  202. }
  203. /**
  204. * Clean up the temp file and throw an exception
  205. *
  206. * @param string $msg
  207. * @throws Exception
  208. */
  209. protected function throwException( $msg ) {
  210. if ( $this->handle ) {
  211. fclose( $this->handle );
  212. unlink( $this->tmpFileName );
  213. }
  214. throw new Exception( $msg );
  215. }
  216. }