plugin_datastore_mysql.c 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202
  1. /*
  2. This file is part of GNUnet
  3. Copyright (C) 2009, 2010, 2011 GNUnet e.V.
  4. GNUnet is free software: you can redistribute it and/or modify it
  5. under the terms of the GNU Affero General Public License as published
  6. by the Free Software Foundation, either version 3 of the License,
  7. or (at your option) any later version.
  8. GNUnet is distributed in the hope that it will be useful, but
  9. WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. Affero General Public License for more details.
  12. You should have received a copy of the GNU Affero General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. SPDX-License-Identifier: AGPL3.0-or-later
  15. */
  16. /**
  17. * @file datastore/plugin_datastore_mysql.c
  18. * @brief mysql-based datastore backend
  19. * @author Igor Wronsky
  20. * @author Christian Grothoff
  21. * @author Christophe Genevey
  22. *
  23. * NOTE: This db module does NOT work with mysql prior to 4.1 since
  24. * it uses prepared statements. MySQL 5.0.46 promises to fix a bug
  25. * in MyISAM that is causing us grief. At the time of this writing,
  26. * that version is yet to be released. In anticipation, the code
  27. * will use MyISAM with 5.0.46 (and higher). If you run such a
  28. * version, please run "make check" to verify that the MySQL bug
  29. * was actually fixed in your version (and if not, change the
  30. * code below to use MyISAM for gn071).
  31. *
  32. * HIGHLIGHTS
  33. *
  34. * Pros
  35. * + On up-to-date hardware where mysql can be used comfortably, this
  36. * module will have better performance than the other db choices
  37. * (according to our tests).
  38. * + Its often possible to recover the mysql database from internal
  39. * inconsistencies. The other db choices do not support repair!
  40. * Cons
  41. * - Memory usage (Comment: "I have 1G and it never caused me trouble")
  42. * - Manual setup
  43. *
  44. * MANUAL SETUP INSTRUCTIONS
  45. *
  46. * 1) in gnunet.conf, set
  47. * @verbatim
  48. [datastore]
  49. DATABASE = "mysql"
  50. @endverbatim
  51. * 2) Then access mysql as root,
  52. * @verbatim
  53. $ mysql -u root -p
  54. @endverbatim
  55. * and do the following. [You should replace $USER with the username
  56. * that will be running the gnunetd process].
  57. * @verbatim
  58. CREATE DATABASE gnunet;
  59. GRANT select,insert,update,delete,create,alter,drop,create temporary tables
  60. ON gnunet.* TO $USER@localhost;
  61. SET PASSWORD FOR $USER@localhost=PASSWORD('$the_password_you_like');
  62. FLUSH PRIVILEGES;
  63. @endverbatim
  64. * 3) In the $HOME directory of $USER, create a ".my.cnf" file
  65. * with the following lines
  66. * @verbatim
  67. [client]
  68. user=$USER
  69. password=$the_password_you_like
  70. @endverbatim
  71. *
  72. * Thats it. Note that .my.cnf file is a security risk unless its on
  73. * a safe partition etc. The $HOME/.my.cnf can of course be a symbolic
  74. * link. Even greater security risk can be achieved by setting no
  75. * password for $USER. Luckily $USER has only priviledges to mess
  76. * up GNUnet's tables, nothing else (unless you give them more,
  77. * of course).<p>
  78. *
  79. * 4) Still, perhaps you should briefly try if the DB connection
  80. * works. First, login as $USER. Then use,
  81. *
  82. * @verbatim
  83. $ mysql -u $USER -p $the_password_you_like
  84. mysql> use gnunet;
  85. @endverbatim
  86. *
  87. * If you get the message &quot;Database changed&quot; it probably works.
  88. *
  89. * [If you get &quot;ERROR 2002: Can't connect to local MySQL server
  90. * through socket '/tmp/mysql.sock' (2)&quot; it may be resolvable by
  91. * &quot;ln -s /var/run/mysqld/mysqld.sock /tmp/mysql.sock&quot;
  92. * so there may be some additional trouble depending on your mysql setup.]
  93. *
  94. * REPAIRING TABLES
  95. *
  96. * - Its probably healthy to check your tables for inconsistencies
  97. * every now and then.
  98. * - If you get odd SEGVs on gnunetd startup, it might be that the mysql
  99. * databases have been corrupted.
  100. * - The tables can be verified/fixed in two ways;
  101. * 1) by running mysqlcheck -A, or
  102. * 2) by executing (inside of mysql using the GNUnet database):
  103. * @verbatim
  104. mysql> REPAIR TABLE gn090;
  105. @endverbatim
  106. *
  107. * PROBLEMS?
  108. *
  109. * If you have problems related to the mysql module, your best
  110. * friend is probably the mysql manual. The first thing to check
  111. * is that mysql is basically operational, that you can connect
  112. * to it, create tables, issue queries etc.
  113. */
  114. #include "platform.h"
  115. #include "gnunet_datastore_plugin.h"
  116. #include "gnunet_util_lib.h"
  117. #include "gnunet_mysql_lib.h"
  118. #include "gnunet_my_lib.h"
  119. #define MAX_DATUM_SIZE 65536
  120. /**
  121. * Context for all functions in this plugin.
  122. */
  123. struct Plugin
  124. {
  125. /**
  126. * Our execution environment.
  127. */
  128. struct GNUNET_DATASTORE_PluginEnvironment *env;
  129. /**
  130. * Handle to talk to MySQL.
  131. */
  132. struct GNUNET_MYSQL_Context *mc;
  133. /**
  134. * Prepared statements.
  135. */
  136. #define INSERT_ENTRY "INSERT INTO gn090 (repl,type,prio,anonLevel,expire,rvalue,hash,vhash,value) VALUES (?,?,?,?,?,?,?,?,?)"
  137. struct GNUNET_MYSQL_StatementHandle *insert_entry;
  138. #define DELETE_ENTRY_BY_UID "DELETE FROM gn090 WHERE uid=?"
  139. struct GNUNET_MYSQL_StatementHandle *delete_entry_by_uid;
  140. #define DELETE_ENTRY_BY_HASH_VALUE "DELETE FROM gn090 "\
  141. "WHERE hash = ? AND "\
  142. "value = ? "\
  143. "LIMIT 1"
  144. struct GNUNET_MYSQL_StatementHandle *delete_entry_by_hash_value;
  145. #define RESULT_COLUMNS "repl, type, prio, anonLevel, expire, hash, value, uid"
  146. #define SELECT_ENTRY "SELECT " RESULT_COLUMNS " FROM gn090 "\
  147. "WHERE uid >= ? AND "\
  148. "(rvalue >= ? OR 0 = ?) "\
  149. "ORDER BY uid LIMIT 1"
  150. struct GNUNET_MYSQL_StatementHandle *select_entry;
  151. #define SELECT_ENTRY_BY_HASH "SELECT " RESULT_COLUMNS " FROM gn090 "\
  152. "FORCE INDEX (idx_hash_type_uid) "\
  153. "WHERE hash=? AND "\
  154. "uid >= ? AND "\
  155. "(rvalue >= ? OR 0 = ?) "\
  156. "ORDER BY uid LIMIT 1"
  157. struct GNUNET_MYSQL_StatementHandle *select_entry_by_hash;
  158. #define SELECT_ENTRY_BY_HASH_AND_TYPE "SELECT " RESULT_COLUMNS " FROM gn090 "\
  159. "FORCE INDEX (idx_hash_type_uid) "\
  160. "WHERE hash = ? AND "\
  161. "type = ? AND "\
  162. "uid >= ? AND "\
  163. "(rvalue >= ? OR 0 = ?) "\
  164. "ORDER BY uid LIMIT 1"
  165. struct GNUNET_MYSQL_StatementHandle *select_entry_by_hash_and_type;
  166. #define UPDATE_ENTRY "UPDATE gn090 SET "\
  167. "prio = prio + ?, "\
  168. "repl = repl + ?, "\
  169. "expire = GREATEST(expire, ?) "\
  170. "WHERE hash = ? AND vhash = ?"
  171. struct GNUNET_MYSQL_StatementHandle *update_entry;
  172. #define DEC_REPL "UPDATE gn090 SET repl=GREATEST (1, repl) - 1 WHERE uid=?"
  173. struct GNUNET_MYSQL_StatementHandle *dec_repl;
  174. #define SELECT_SIZE "SELECT SUM(LENGTH(value)+256) FROM gn090"
  175. struct GNUNET_MYSQL_StatementHandle *get_size;
  176. #define SELECT_IT_NON_ANONYMOUS "SELECT " RESULT_COLUMNS " FROM gn090 "\
  177. "FORCE INDEX (idx_anonLevel_type_rvalue) "\
  178. "WHERE anonLevel=0 AND "\
  179. "type=? AND "\
  180. "uid >= ? "\
  181. "ORDER BY uid LIMIT 1"
  182. struct GNUNET_MYSQL_StatementHandle *zero_iter;
  183. #define SELECT_IT_EXPIRATION "SELECT " RESULT_COLUMNS " FROM gn090 "\
  184. "FORCE INDEX (idx_expire) "\
  185. "WHERE expire < ? "\
  186. "ORDER BY expire ASC LIMIT 1"
  187. struct GNUNET_MYSQL_StatementHandle *select_expiration;
  188. #define SELECT_IT_PRIORITY "SELECT " RESULT_COLUMNS " FROM gn090 "\
  189. "FORCE INDEX (idx_prio) "\
  190. "ORDER BY prio ASC LIMIT 1"
  191. struct GNUNET_MYSQL_StatementHandle *select_priority;
  192. #define SELECT_IT_REPLICATION "SELECT " RESULT_COLUMNS " FROM gn090 "\
  193. "FORCE INDEX (idx_repl_rvalue) "\
  194. "WHERE repl=? AND "\
  195. " (rvalue>=? OR"\
  196. " NOT EXISTS (SELECT 1 FROM gn090 FORCE INDEX (idx_repl_rvalue) WHERE repl=? AND rvalue>=?)) "\
  197. "ORDER BY rvalue ASC "\
  198. "LIMIT 1"
  199. struct GNUNET_MYSQL_StatementHandle *select_replication;
  200. #define SELECT_MAX_REPL "SELECT MAX(repl) FROM gn090"
  201. struct GNUNET_MYSQL_StatementHandle *max_repl;
  202. #define GET_ALL_KEYS "SELECT hash from gn090"
  203. struct GNUNET_MYSQL_StatementHandle *get_all_keys;
  204. };
  205. #define MAX_PARAM 16
  206. /**
  207. * Delete an entry from the gn090 table.
  208. *
  209. * @param plugin plugin context
  210. * @param uid unique ID of the entry to delete
  211. * @return #GNUNET_OK on success, #GNUNET_NO if no such value exists, #GNUNET_SYSERR on error
  212. */
  213. static int
  214. do_delete_entry (struct Plugin *plugin,
  215. unsigned long long uid)
  216. {
  217. int ret;
  218. uint64_t uid64 = (uint64_t) uid;
  219. struct GNUNET_MY_QueryParam params_delete[] = {
  220. GNUNET_MY_query_param_uint64 (&uid64),
  221. GNUNET_MY_query_param_end
  222. };
  223. GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
  224. "Deleting value %llu from gn090 table\n",
  225. uid);
  226. ret = GNUNET_MY_exec_prepared (plugin->mc,
  227. plugin->delete_entry_by_uid,
  228. params_delete);
  229. if (ret >= 0)
  230. {
  231. return GNUNET_OK;
  232. }
  233. GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
  234. "Deleting value %llu from gn090 table failed\n",
  235. (unsigned long long) uid);
  236. return ret;
  237. }
  238. /**
  239. * Get an estimate of how much space the database is
  240. * currently using.
  241. *
  242. * @param cls our `struct Plugin *`
  243. * @return number of bytes used on disk
  244. */
  245. static void
  246. mysql_plugin_estimate_size (void *cls,
  247. unsigned long long *estimate)
  248. {
  249. struct Plugin *plugin = cls;
  250. uint64_t total;
  251. int ret;
  252. struct GNUNET_MY_QueryParam params_get[] = {
  253. GNUNET_MY_query_param_end
  254. };
  255. struct GNUNET_MY_ResultSpec results_get[] = {
  256. GNUNET_MY_result_spec_uint64 (&total),
  257. GNUNET_MY_result_spec_end
  258. };
  259. ret = GNUNET_MY_exec_prepared (plugin->mc,
  260. plugin->get_size,
  261. params_get);
  262. *estimate = 0;
  263. total = UINT64_MAX;
  264. if ( (GNUNET_OK == ret) &&
  265. (GNUNET_OK ==
  266. GNUNET_MY_extract_result (plugin->get_size,
  267. results_get)) )
  268. {
  269. *estimate = (unsigned long long) total;
  270. GNUNET_log (GNUNET_ERROR_TYPE_INFO,
  271. "Size estimate for MySQL payload is %lld\n",
  272. (long long) total);
  273. GNUNET_assert (UINT64_MAX != total);
  274. GNUNET_break (GNUNET_NO ==
  275. GNUNET_MY_extract_result (plugin->get_size,
  276. NULL));
  277. }
  278. }
  279. /**
  280. * Store an item in the datastore.
  281. *
  282. * @param cls closure
  283. * @param key key for the item
  284. * @param absent true if the key was not found in the bloom filter
  285. * @param size number of bytes in @a data
  286. * @param data content stored
  287. * @param type type of the content
  288. * @param priority priority of the content
  289. * @param anonymity anonymity-level for the content
  290. * @param replication replication-level for the content
  291. * @param expiration expiration time for the content
  292. * @param cont continuation called with success or failure status
  293. * @param cont_cls closure for @a cont
  294. */
  295. static void
  296. mysql_plugin_put (void *cls,
  297. const struct GNUNET_HashCode *key,
  298. bool absent,
  299. uint32_t size,
  300. const void *data,
  301. enum GNUNET_BLOCK_Type type,
  302. uint32_t priority,
  303. uint32_t anonymity,
  304. uint32_t replication,
  305. struct GNUNET_TIME_Absolute expiration,
  306. PluginPutCont cont,
  307. void *cont_cls)
  308. {
  309. struct Plugin *plugin = cls;
  310. uint64_t lexpiration = expiration.abs_value_us;
  311. struct GNUNET_HashCode vhash;
  312. GNUNET_CRYPTO_hash (data,
  313. size,
  314. &vhash);
  315. if (!absent)
  316. {
  317. struct GNUNET_MY_QueryParam params_update[] = {
  318. GNUNET_MY_query_param_uint32 (&priority),
  319. GNUNET_MY_query_param_uint32 (&replication),
  320. GNUNET_MY_query_param_uint64 (&lexpiration),
  321. GNUNET_MY_query_param_auto_from_type (key),
  322. GNUNET_MY_query_param_auto_from_type (&vhash),
  323. GNUNET_MY_query_param_end
  324. };
  325. if (GNUNET_OK !=
  326. GNUNET_MY_exec_prepared (plugin->mc,
  327. plugin->update_entry,
  328. params_update))
  329. {
  330. cont (cont_cls,
  331. key,
  332. size,
  333. GNUNET_SYSERR,
  334. _("MySQL statement run failure"));
  335. return;
  336. }
  337. MYSQL_STMT *stmt = GNUNET_MYSQL_statement_get_stmt (plugin->update_entry);
  338. my_ulonglong rows = mysql_stmt_affected_rows (stmt);
  339. GNUNET_break (GNUNET_NO ==
  340. GNUNET_MY_extract_result (plugin->update_entry,
  341. NULL));
  342. if (0 != rows)
  343. {
  344. cont (cont_cls,
  345. key,
  346. size,
  347. GNUNET_NO,
  348. NULL);
  349. return;
  350. }
  351. }
  352. uint64_t lrvalue = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
  353. UINT64_MAX);
  354. struct GNUNET_MY_QueryParam params_insert[] = {
  355. GNUNET_MY_query_param_uint32 (&replication),
  356. GNUNET_MY_query_param_uint32 (&type),
  357. GNUNET_MY_query_param_uint32 (&priority),
  358. GNUNET_MY_query_param_uint32 (&anonymity),
  359. GNUNET_MY_query_param_uint64 (&lexpiration),
  360. GNUNET_MY_query_param_uint64 (&lrvalue),
  361. GNUNET_MY_query_param_auto_from_type (key),
  362. GNUNET_MY_query_param_auto_from_type (&vhash),
  363. GNUNET_MY_query_param_fixed_size (data, size),
  364. GNUNET_MY_query_param_end
  365. };
  366. if (size > MAX_DATUM_SIZE)
  367. {
  368. GNUNET_break (0);
  369. cont (cont_cls, key, size, GNUNET_SYSERR, _("Data too large"));
  370. return;
  371. }
  372. if (GNUNET_OK !=
  373. GNUNET_MY_exec_prepared (plugin->mc,
  374. plugin->insert_entry,
  375. params_insert))
  376. {
  377. cont (cont_cls,
  378. key,
  379. size,
  380. GNUNET_SYSERR,
  381. _("MySQL statement run failure"));
  382. return;
  383. }
  384. GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
  385. "Inserted value `%s' with size %u into gn090 table\n",
  386. GNUNET_h2s (key),
  387. (unsigned int) size);
  388. if (size > 0)
  389. plugin->env->duc (plugin->env->cls,
  390. size);
  391. GNUNET_break (GNUNET_NO ==
  392. GNUNET_MY_extract_result (plugin->insert_entry,
  393. NULL));
  394. cont (cont_cls,
  395. key,
  396. size,
  397. GNUNET_OK,
  398. NULL);
  399. }
  400. /**
  401. * Run the given select statement and call 'proc' on the resulting
  402. * values (which must be in particular positions).
  403. *
  404. * @param plugin the plugin handle
  405. * @param stmt select statement to run
  406. * @param proc function to call on result
  407. * @param proc_cls closure for @a proc
  408. * @param params_select arguments to initialize stmt
  409. */
  410. static void
  411. execute_select (struct Plugin *plugin,
  412. struct GNUNET_MYSQL_StatementHandle *stmt,
  413. PluginDatumProcessor proc,
  414. void *proc_cls,
  415. struct GNUNET_MY_QueryParam *params_select)
  416. {
  417. int ret;
  418. uint32_t replication;
  419. uint32_t type;
  420. uint32_t priority;
  421. uint32_t anonymity;
  422. uint64_t uid;
  423. size_t value_size;
  424. void *value;
  425. struct GNUNET_HashCode key;
  426. struct GNUNET_TIME_Absolute expiration;
  427. struct GNUNET_MY_ResultSpec results_select[] = {
  428. GNUNET_MY_result_spec_uint32 (&replication),
  429. GNUNET_MY_result_spec_uint32 (&type),
  430. GNUNET_MY_result_spec_uint32 (&priority),
  431. GNUNET_MY_result_spec_uint32 (&anonymity),
  432. GNUNET_MY_result_spec_absolute_time (&expiration),
  433. GNUNET_MY_result_spec_auto_from_type (&key),
  434. GNUNET_MY_result_spec_variable_size (&value, &value_size),
  435. GNUNET_MY_result_spec_uint64 (&uid),
  436. GNUNET_MY_result_spec_end
  437. };
  438. ret = GNUNET_MY_exec_prepared (plugin->mc,
  439. stmt,
  440. params_select);
  441. if (GNUNET_OK != ret)
  442. {
  443. proc (proc_cls,
  444. NULL, 0, NULL, 0, 0, 0, 0, GNUNET_TIME_UNIT_ZERO_ABS, 0);
  445. return;
  446. }
  447. ret = GNUNET_MY_extract_result (stmt,
  448. results_select);
  449. if (GNUNET_OK != ret)
  450. {
  451. proc (proc_cls,
  452. NULL, 0, NULL, 0, 0, 0, 0, GNUNET_TIME_UNIT_ZERO_ABS, 0);
  453. return;
  454. }
  455. GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
  456. "Found %u-byte value under key `%s' with prio %u, anon %u, expire %s selecting from gn090 table\n",
  457. (unsigned int) value_size,
  458. GNUNET_h2s (&key),
  459. (unsigned int) priority,
  460. (unsigned int) anonymity,
  461. GNUNET_STRINGS_absolute_time_to_string (expiration));
  462. GNUNET_assert (value_size < MAX_DATUM_SIZE);
  463. GNUNET_break (GNUNET_NO ==
  464. GNUNET_MY_extract_result (stmt,
  465. NULL));
  466. ret = proc (proc_cls,
  467. &key,
  468. value_size,
  469. value,
  470. type,
  471. priority,
  472. anonymity,
  473. replication,
  474. expiration,
  475. uid);
  476. GNUNET_MY_cleanup_result (results_select);
  477. if (GNUNET_NO == ret)
  478. {
  479. do_delete_entry (plugin, uid);
  480. if (0 != value_size)
  481. plugin->env->duc (plugin->env->cls,
  482. - value_size);
  483. }
  484. }
  485. /**
  486. * Get one of the results for a particular key in the datastore.
  487. *
  488. * @param cls closure
  489. * @param next_uid return the result with lowest uid >= next_uid
  490. * @param random if true, return a random result instead of using next_uid
  491. * @param key key to match, never NULL
  492. * @param type entries of which type are relevant?
  493. * Use 0 for any type.
  494. * @param proc function to call on the matching value,
  495. * with NULL for if no value matches
  496. * @param proc_cls closure for @a proc
  497. */
  498. static void
  499. mysql_plugin_get_key (void *cls,
  500. uint64_t next_uid,
  501. bool random,
  502. const struct GNUNET_HashCode *key,
  503. enum GNUNET_BLOCK_Type type,
  504. PluginDatumProcessor proc,
  505. void *proc_cls)
  506. {
  507. struct Plugin *plugin = cls;
  508. uint64_t rvalue;
  509. if (random)
  510. {
  511. rvalue = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
  512. UINT64_MAX);
  513. next_uid = 0;
  514. }
  515. else
  516. rvalue = 0;
  517. if (NULL == key)
  518. {
  519. struct GNUNET_MY_QueryParam params_select[] = {
  520. GNUNET_MY_query_param_uint64 (&next_uid),
  521. GNUNET_MY_query_param_uint64 (&rvalue),
  522. GNUNET_MY_query_param_uint64 (&rvalue),
  523. GNUNET_MY_query_param_end
  524. };
  525. execute_select (plugin,
  526. plugin->select_entry,
  527. proc,
  528. proc_cls,
  529. params_select);
  530. }
  531. else if (type != GNUNET_BLOCK_TYPE_ANY)
  532. {
  533. struct GNUNET_MY_QueryParam params_select[] = {
  534. GNUNET_MY_query_param_auto_from_type (key),
  535. GNUNET_MY_query_param_uint32 (&type),
  536. GNUNET_MY_query_param_uint64 (&next_uid),
  537. GNUNET_MY_query_param_uint64 (&rvalue),
  538. GNUNET_MY_query_param_uint64 (&rvalue),
  539. GNUNET_MY_query_param_end
  540. };
  541. execute_select (plugin,
  542. plugin->select_entry_by_hash_and_type,
  543. proc,
  544. proc_cls,
  545. params_select);
  546. }
  547. else
  548. {
  549. struct GNUNET_MY_QueryParam params_select[] = {
  550. GNUNET_MY_query_param_auto_from_type (key),
  551. GNUNET_MY_query_param_uint64 (&next_uid),
  552. GNUNET_MY_query_param_uint64 (&rvalue),
  553. GNUNET_MY_query_param_uint64 (&rvalue),
  554. GNUNET_MY_query_param_end
  555. };
  556. execute_select (plugin,
  557. plugin->select_entry_by_hash,
  558. proc,
  559. proc_cls,
  560. params_select);
  561. }
  562. }
  563. /**
  564. * Get a zero-anonymity datum from the datastore.
  565. *
  566. * @param cls our `struct Plugin *`
  567. * @param next_uid return the result with lowest uid >= next_uid
  568. * @param type entries of which type should be considered?
  569. * Must not be zero (ANY).
  570. * @param proc function to call on a matching value;
  571. * will be called with NULL if no value matches
  572. * @param proc_cls closure for @a proc
  573. */
  574. static void
  575. mysql_plugin_get_zero_anonymity (void *cls,
  576. uint64_t next_uid,
  577. enum GNUNET_BLOCK_Type type,
  578. PluginDatumProcessor proc,
  579. void *proc_cls)
  580. {
  581. struct Plugin *plugin = cls;
  582. uint32_t typei = (uint32_t) type;
  583. struct GNUNET_MY_QueryParam params_zero_iter[] = {
  584. GNUNET_MY_query_param_uint32 (&typei),
  585. GNUNET_MY_query_param_uint64 (&next_uid),
  586. GNUNET_MY_query_param_end
  587. };
  588. execute_select (plugin,
  589. plugin->zero_iter,
  590. proc,
  591. proc_cls,
  592. params_zero_iter);
  593. }
  594. /**
  595. * Context for #repl_proc() function.
  596. */
  597. struct ReplCtx
  598. {
  599. /**
  600. * Plugin handle.
  601. */
  602. struct Plugin *plugin;
  603. /**
  604. * Function to call for the result (or the NULL).
  605. */
  606. PluginDatumProcessor proc;
  607. /**
  608. * Closure for @e proc.
  609. */
  610. void *proc_cls;
  611. };
  612. /**
  613. * Wrapper for the processor for #mysql_plugin_get_replication().
  614. * Decrements the replication counter and calls the original
  615. * iterator.
  616. *
  617. * @param cls closure
  618. * @param key key for the content
  619. * @param size number of bytes in @a data
  620. * @param data content stored
  621. * @param type type of the content
  622. * @param priority priority of the content
  623. * @param anonymity anonymity-level for the content
  624. * @param replication replication-level for the content
  625. * @param expiration expiration time for the content
  626. * @param uid unique identifier for the datum;
  627. * maybe 0 if no unique identifier is available
  628. * @return #GNUNET_SYSERR to abort the iteration, #GNUNET_OK to continue
  629. * (continue on call to "next", of course),
  630. * #GNUNET_NO to delete the item and continue (if supported)
  631. */
  632. static int
  633. repl_proc (void *cls,
  634. const struct GNUNET_HashCode *key,
  635. uint32_t size,
  636. const void *data,
  637. enum GNUNET_BLOCK_Type type,
  638. uint32_t priority,
  639. uint32_t anonymity,
  640. uint32_t replication,
  641. struct GNUNET_TIME_Absolute expiration,
  642. uint64_t uid)
  643. {
  644. struct ReplCtx *rc = cls;
  645. struct Plugin *plugin = rc->plugin;
  646. int ret;
  647. int iret;
  648. ret = rc->proc (rc->proc_cls,
  649. key,
  650. size,
  651. data,
  652. type,
  653. priority,
  654. anonymity,
  655. replication,
  656. expiration,
  657. uid);
  658. if (NULL != key)
  659. {
  660. struct GNUNET_MY_QueryParam params_proc[] = {
  661. GNUNET_MY_query_param_uint64 (&uid),
  662. GNUNET_MY_query_param_end
  663. };
  664. iret = GNUNET_MY_exec_prepared (plugin->mc,
  665. plugin->dec_repl,
  666. params_proc);
  667. if (GNUNET_SYSERR == iret)
  668. {
  669. GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
  670. "Failed to reduce replication counter\n");
  671. return GNUNET_SYSERR;
  672. }
  673. }
  674. return ret;
  675. }
  676. /**
  677. * Get a random item for replication. Returns a single, not expired,
  678. * random item from those with the highest replication counters. The
  679. * item's replication counter is decremented by one IF it was positive
  680. * before. Call @a proc with all values ZERO or NULL if the datastore
  681. * is empty.
  682. *
  683. * @param cls closure
  684. * @param proc function to call the value (once only).
  685. * @param proc_cls closure for @a proc
  686. */
  687. static void
  688. mysql_plugin_get_replication (void *cls,
  689. PluginDatumProcessor proc,
  690. void *proc_cls)
  691. {
  692. struct Plugin *plugin = cls;
  693. uint64_t rvalue;
  694. uint32_t repl;
  695. struct ReplCtx rc;
  696. struct GNUNET_MY_QueryParam params_get[] = {
  697. GNUNET_MY_query_param_end
  698. };
  699. struct GNUNET_MY_ResultSpec results_get[] = {
  700. GNUNET_MY_result_spec_uint32 (&repl),
  701. GNUNET_MY_result_spec_end
  702. };
  703. struct GNUNET_MY_QueryParam params_select[] = {
  704. GNUNET_MY_query_param_uint32 (&repl),
  705. GNUNET_MY_query_param_uint64 (&rvalue),
  706. GNUNET_MY_query_param_uint32 (&repl),
  707. GNUNET_MY_query_param_uint64 (&rvalue),
  708. GNUNET_MY_query_param_end
  709. };
  710. rc.plugin = plugin;
  711. rc.proc = proc;
  712. rc.proc_cls = proc_cls;
  713. if (1 !=
  714. GNUNET_MY_exec_prepared (plugin->mc,
  715. plugin->max_repl,
  716. params_get))
  717. {
  718. proc (proc_cls, NULL, 0, NULL, 0, 0, 0, 0, GNUNET_TIME_UNIT_ZERO_ABS, 0);
  719. return;
  720. }
  721. if (GNUNET_OK !=
  722. GNUNET_MY_extract_result (plugin->max_repl,
  723. results_get))
  724. {
  725. proc (proc_cls, NULL, 0, NULL, 0, 0, 0, 0, GNUNET_TIME_UNIT_ZERO_ABS, 0);
  726. return;
  727. }
  728. GNUNET_break (GNUNET_NO ==
  729. GNUNET_MY_extract_result (plugin->max_repl,
  730. NULL));
  731. rvalue = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
  732. UINT64_MAX);
  733. execute_select (plugin,
  734. plugin->select_replication,
  735. &repl_proc,
  736. &rc,
  737. params_select);
  738. }
  739. /**
  740. * Get all of the keys in the datastore.
  741. *
  742. * @param cls closure
  743. * @param proc function to call on each key
  744. * @param proc_cls closure for @a proc
  745. */
  746. static void
  747. mysql_plugin_get_keys (void *cls,
  748. PluginKeyProcessor proc,
  749. void *proc_cls)
  750. {
  751. struct Plugin *plugin = cls;
  752. int ret;
  753. MYSQL_STMT *statement;
  754. unsigned int cnt;
  755. struct GNUNET_HashCode key;
  756. struct GNUNET_HashCode last;
  757. struct GNUNET_MY_QueryParam params_select[] = {
  758. GNUNET_MY_query_param_end
  759. };
  760. struct GNUNET_MY_ResultSpec results_select[] = {
  761. GNUNET_MY_result_spec_auto_from_type (&key),
  762. GNUNET_MY_result_spec_end
  763. };
  764. GNUNET_assert (NULL != proc);
  765. statement = GNUNET_MYSQL_statement_get_stmt (plugin->get_all_keys);
  766. if (GNUNET_OK !=
  767. GNUNET_MY_exec_prepared (plugin->mc,
  768. plugin->get_all_keys,
  769. params_select))
  770. {
  771. GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
  772. _("`%s' for `%s' failed at %s:%d with error: %s\n"),
  773. "mysql_stmt_execute",
  774. GET_ALL_KEYS,
  775. __FILE__,
  776. __LINE__,
  777. mysql_stmt_error (statement));
  778. GNUNET_MYSQL_statements_invalidate (plugin->mc);
  779. proc (proc_cls, NULL, 0);
  780. return;
  781. }
  782. memset (&last, 0, sizeof (last)); /* make static analysis happy */
  783. ret = GNUNET_YES;
  784. cnt = 0;
  785. while (ret == GNUNET_YES)
  786. {
  787. ret = GNUNET_MY_extract_result (plugin->get_all_keys,
  788. results_select);
  789. if (0 != GNUNET_memcmp (&last,
  790. &key))
  791. {
  792. if (0 != cnt)
  793. proc (proc_cls,
  794. &last,
  795. cnt);
  796. cnt = 1;
  797. last = key;
  798. }
  799. else
  800. {
  801. cnt++;
  802. }
  803. }
  804. if (0 != cnt)
  805. proc (proc_cls,
  806. &last,
  807. cnt);
  808. /* finally, let app know we are done */
  809. proc (proc_cls,
  810. NULL,
  811. 0);
  812. if (GNUNET_SYSERR == ret)
  813. {
  814. GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
  815. _("`%s' failed at %s:%d with error: %s\n"),
  816. "mysql_stmt_fetch",
  817. __FILE__,
  818. __LINE__,
  819. mysql_stmt_error (statement));
  820. GNUNET_MYSQL_statements_invalidate (plugin->mc);
  821. return;
  822. }
  823. }
  824. /**
  825. * Context for #expi_proc() function.
  826. */
  827. struct ExpiCtx
  828. {
  829. /**
  830. * Plugin handle.
  831. */
  832. struct Plugin *plugin;
  833. /**
  834. * Function to call for the result (or the NULL).
  835. */
  836. PluginDatumProcessor proc;
  837. /**
  838. * Closure for @e proc.
  839. */
  840. void *proc_cls;
  841. };
  842. /**
  843. * Wrapper for the processor for #mysql_plugin_get_expiration().
  844. * If no expired value was found, we do a second query for
  845. * low-priority content.
  846. *
  847. * @param cls closure
  848. * @param key key for the content
  849. * @param size number of bytes in data
  850. * @param data content stored
  851. * @param type type of the content
  852. * @param priority priority of the content
  853. * @param anonymity anonymity-level for the content
  854. * @param replication replication-level for the content
  855. * @param expiration expiration time for the content
  856. * @param uid unique identifier for the datum;
  857. * maybe 0 if no unique identifier is available
  858. * @return #GNUNET_SYSERR to abort the iteration, #GNUNET_OK to continue
  859. * (continue on call to "next", of course),
  860. * #GNUNET_NO to delete the item and continue (if supported)
  861. */
  862. static int
  863. expi_proc (void *cls,
  864. const struct GNUNET_HashCode *key,
  865. uint32_t size,
  866. const void *data,
  867. enum GNUNET_BLOCK_Type type,
  868. uint32_t priority,
  869. uint32_t anonymity,
  870. uint32_t replication,
  871. struct GNUNET_TIME_Absolute expiration,
  872. uint64_t uid)
  873. {
  874. struct ExpiCtx *rc = cls;
  875. struct Plugin *plugin = rc->plugin;
  876. struct GNUNET_MY_QueryParam params_select[] = {
  877. GNUNET_MY_query_param_end
  878. };
  879. if (NULL == key)
  880. {
  881. execute_select (plugin,
  882. plugin->select_priority,
  883. rc->proc,
  884. rc->proc_cls,
  885. params_select);
  886. return GNUNET_SYSERR;
  887. }
  888. return rc->proc (rc->proc_cls,
  889. key,
  890. size,
  891. data,
  892. type,
  893. priority,
  894. anonymity,
  895. replication,
  896. expiration,
  897. uid);
  898. }
  899. /**
  900. * Get a random item for expiration.
  901. * Call @a proc with all values ZERO or NULL if the datastore is empty.
  902. *
  903. * @param cls closure
  904. * @param proc function to call the value (once only).
  905. * @param proc_cls closure for @a proc
  906. */
  907. static void
  908. mysql_plugin_get_expiration (void *cls,
  909. PluginDatumProcessor proc,
  910. void *proc_cls)
  911. {
  912. struct Plugin *plugin = cls;
  913. struct GNUNET_TIME_Absolute now;
  914. struct GNUNET_MY_QueryParam params_select[] = {
  915. GNUNET_MY_query_param_absolute_time (&now),
  916. GNUNET_MY_query_param_end
  917. };
  918. struct ExpiCtx rc;
  919. rc.plugin = plugin;
  920. rc.proc = proc;
  921. rc.proc_cls = proc_cls;
  922. now = GNUNET_TIME_absolute_get ();
  923. execute_select (plugin,
  924. plugin->select_expiration,
  925. expi_proc,
  926. &rc,
  927. params_select);
  928. }
  929. /**
  930. * Drop database.
  931. *
  932. * @param cls the `struct Plugin *`
  933. */
  934. static void
  935. mysql_plugin_drop (void *cls)
  936. {
  937. struct Plugin *plugin = cls;
  938. if (GNUNET_OK !=
  939. GNUNET_MYSQL_statement_run (plugin->mc,
  940. "DROP TABLE gn090"))
  941. return; /* error */
  942. plugin->env->duc (plugin->env->cls, 0);
  943. }
  944. /**
  945. * Remove a particular key in the datastore.
  946. *
  947. * @param cls closure
  948. * @param key key for the content
  949. * @param size number of bytes in data
  950. * @param data content stored
  951. * @param cont continuation called with success or failure status
  952. * @param cont_cls continuation closure for @a cont
  953. */
  954. static void
  955. mysql_plugin_remove_key (void *cls,
  956. const struct GNUNET_HashCode *key,
  957. uint32_t size,
  958. const void *data,
  959. PluginRemoveCont cont,
  960. void *cont_cls)
  961. {
  962. struct Plugin *plugin = cls;
  963. struct GNUNET_MY_QueryParam params_delete[] = {
  964. GNUNET_MY_query_param_auto_from_type (key),
  965. GNUNET_MY_query_param_fixed_size (data, size),
  966. GNUNET_MY_query_param_end
  967. };
  968. if (GNUNET_OK !=
  969. GNUNET_MY_exec_prepared (plugin->mc,
  970. plugin->delete_entry_by_hash_value,
  971. params_delete))
  972. {
  973. GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
  974. "Removing key `%s' from gn090 table failed\n",
  975. GNUNET_h2s (key));
  976. cont (cont_cls,
  977. key,
  978. size,
  979. GNUNET_SYSERR,
  980. _("MySQL statement run failure"));
  981. return;
  982. }
  983. MYSQL_STMT *stmt = GNUNET_MYSQL_statement_get_stmt (plugin->delete_entry_by_hash_value);
  984. my_ulonglong rows = mysql_stmt_affected_rows (stmt);
  985. if (0 == rows)
  986. {
  987. cont (cont_cls,
  988. key,
  989. size,
  990. GNUNET_NO,
  991. NULL);
  992. return;
  993. }
  994. plugin->env->duc (plugin->env->cls,
  995. -size);
  996. cont (cont_cls,
  997. key,
  998. size,
  999. GNUNET_OK,
  1000. NULL);
  1001. }
  1002. /**
  1003. * Entry point for the plugin.
  1004. *
  1005. * @param cls the `struct GNUNET_DATASTORE_PluginEnvironment *`
  1006. * @return our `struct Plugin *`
  1007. */
  1008. void *
  1009. libgnunet_plugin_datastore_mysql_init (void *cls)
  1010. {
  1011. struct GNUNET_DATASTORE_PluginEnvironment *env = cls;
  1012. struct GNUNET_DATASTORE_PluginFunctions *api;
  1013. struct Plugin *plugin;
  1014. plugin = GNUNET_new (struct Plugin);
  1015. plugin->env = env;
  1016. plugin->mc = GNUNET_MYSQL_context_create (env->cfg,
  1017. "datastore-mysql");
  1018. if (NULL == plugin->mc)
  1019. {
  1020. GNUNET_free (plugin);
  1021. return NULL;
  1022. }
  1023. #define MRUNS(a) (GNUNET_OK != GNUNET_MYSQL_statement_run (plugin->mc, a) )
  1024. #define PINIT(a,b) (NULL == (a = GNUNET_MYSQL_statement_prepare (plugin->mc, b)))
  1025. if (MRUNS
  1026. ("CREATE TABLE IF NOT EXISTS gn090 ("
  1027. " repl INT(11) UNSIGNED NOT NULL DEFAULT 0,"
  1028. " type INT(11) UNSIGNED NOT NULL DEFAULT 0,"
  1029. " prio INT(11) UNSIGNED NOT NULL DEFAULT 0,"
  1030. " anonLevel INT(11) UNSIGNED NOT NULL DEFAULT 0,"
  1031. " expire BIGINT UNSIGNED NOT NULL DEFAULT 0,"
  1032. " rvalue BIGINT UNSIGNED NOT NULL,"
  1033. " hash BINARY(64) NOT NULL DEFAULT '',"
  1034. " vhash BINARY(64) NOT NULL DEFAULT '',"
  1035. " value BLOB NOT NULL DEFAULT ''," " uid BIGINT NOT NULL AUTO_INCREMENT,"
  1036. " PRIMARY KEY (uid),"
  1037. " INDEX idx_hash_type_uid (hash(64),type,rvalue),"
  1038. " INDEX idx_prio (prio),"
  1039. " INDEX idx_repl_rvalue (repl,rvalue),"
  1040. " INDEX idx_expire (expire),"
  1041. " INDEX idx_anonLevel_type_rvalue (anonLevel,type,rvalue)"
  1042. ") ENGINE=InnoDB") || MRUNS ("SET AUTOCOMMIT = 1") ||
  1043. PINIT (plugin->insert_entry, INSERT_ENTRY) ||
  1044. PINIT (plugin->delete_entry_by_uid, DELETE_ENTRY_BY_UID) ||
  1045. PINIT (plugin->delete_entry_by_hash_value, DELETE_ENTRY_BY_HASH_VALUE) ||
  1046. PINIT (plugin->select_entry, SELECT_ENTRY) ||
  1047. PINIT (plugin->select_entry_by_hash, SELECT_ENTRY_BY_HASH) ||
  1048. PINIT (plugin->select_entry_by_hash_and_type,
  1049. SELECT_ENTRY_BY_HASH_AND_TYPE) ||
  1050. PINIT (plugin->get_size, SELECT_SIZE) ||
  1051. PINIT (plugin->update_entry, UPDATE_ENTRY) ||
  1052. PINIT (plugin->dec_repl, DEC_REPL) ||
  1053. PINIT (plugin->zero_iter, SELECT_IT_NON_ANONYMOUS) ||
  1054. PINIT (plugin->select_expiration, SELECT_IT_EXPIRATION) ||
  1055. PINIT (plugin->select_priority, SELECT_IT_PRIORITY) ||
  1056. PINIT (plugin->max_repl, SELECT_MAX_REPL) ||
  1057. PINIT (plugin->get_all_keys, GET_ALL_KEYS) ||
  1058. PINIT (plugin->select_replication, SELECT_IT_REPLICATION) ||
  1059. false)
  1060. {
  1061. GNUNET_MYSQL_context_destroy (plugin->mc);
  1062. GNUNET_free (plugin);
  1063. return NULL;
  1064. }
  1065. #undef PINIT
  1066. #undef MRUNS
  1067. api = GNUNET_new (struct GNUNET_DATASTORE_PluginFunctions);
  1068. api->cls = plugin;
  1069. api->estimate_size = &mysql_plugin_estimate_size;
  1070. api->put = &mysql_plugin_put;
  1071. api->get_key = &mysql_plugin_get_key;
  1072. api->get_replication = &mysql_plugin_get_replication;
  1073. api->get_expiration = &mysql_plugin_get_expiration;
  1074. api->get_zero_anonymity = &mysql_plugin_get_zero_anonymity;
  1075. api->get_keys = &mysql_plugin_get_keys;
  1076. api->drop = &mysql_plugin_drop;
  1077. api->remove_key = &mysql_plugin_remove_key;
  1078. GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, "mysql",
  1079. _("Mysql database running\n"));
  1080. return api;
  1081. }
  1082. /**
  1083. * Exit point from the plugin.
  1084. *
  1085. * @param cls our `struct Plugin *`
  1086. * @return always NULL
  1087. */
  1088. void *
  1089. libgnunet_plugin_datastore_mysql_done (void *cls)
  1090. {
  1091. struct GNUNET_DATASTORE_PluginFunctions *api = cls;
  1092. struct Plugin *plugin = api->cls;
  1093. GNUNET_MYSQL_context_destroy (plugin->mc);
  1094. GNUNET_free (plugin);
  1095. GNUNET_free (api);
  1096. return NULL;
  1097. }
  1098. /* end of plugin_datastore_mysql.c */