blob: f6d7ba8bf76031b21ab6c1df3f4a0abfe3fb71b7 [file] [log] [blame]
Alexander Afanasyev71b43e72012-12-27 01:03:43 -08001/* -*- Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil -*- */
2/*
3 * Copyright (c) 2012 University of California, Los Angeles
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 2 as
7 * published by the Free Software Foundation;
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 *
18 * Author: Alexander Afanasyev <alexander.afanasyev@ucla.edu>
19 * Zhenkai Zhu <zhenkai@cs.ucla.edu>
20 */
21
Alexander Afanasyev242f8772013-01-01 23:26:31 -080022#include "db-helper.h"
Alexander Afanasyev71b43e72012-12-27 01:03:43 -080023// #include "sync-log.h"
Alexander Afanasyevae43c502012-12-29 17:26:37 -080024#include <boost/make_shared.hpp>
25#include <boost/ref.hpp>
Alexander Afanasyev71b43e72012-12-27 01:03:43 -080026
27// Other options: VP_md2, EVP_md5, EVP_sha, EVP_sha1, EVP_sha256, EVP_dss, EVP_dss1, EVP_mdc2, EVP_ripemd160
28#define HASH_FUNCTION EVP_sha256
29
30#include <boost/throw_exception.hpp>
31typedef boost::error_info<struct tag_errmsg, std::string> errmsg_info_str;
32// typedef boost::error_info<struct tag_errmsg, int> errmsg_info_int;
33
Alexander Afanasyevae43c502012-12-29 17:26:37 -080034using namespace boost;
Alexander Afanasyev71b43e72012-12-27 01:03:43 -080035
36const std::string INIT_DATABASE = "\
Alexander Afanasyev8811b352013-01-02 12:51:15 -080037PRAGMA foreign_keys = ON; \n\
38 \n\
39CREATE TABLE \n\
40 SyncNodes( \n\
41 device_id INTEGER PRIMARY KEY AUTOINCREMENT, \n\
42 device_name TEXT NOT NULL, \n\
43 description TEXT, \n\
44 seq_no INTEGER NOT NULL, \n\
45 last_known_tdi TEXT, \n\
46 last_update TIMESTAMP \n\
47 ); \n\
48 \n\
49CREATE TRIGGER SyncNodesUpdater_trigger \n\
50 BEFORE INSERT ON SyncNodes \n\
51 FOR EACH ROW \n\
52 WHEN (SELECT device_id \n\
53 FROM SyncNodes \n\
54 WHERE device_name=NEW.device_name) \n\
55 IS NOT NULL \n\
56 BEGIN \n\
57 UPDATE SyncNodes \n\
58 SET seq_no=max(seq_no,NEW.seq_no) \n\
59 WHERE device_name=NEW.device_name; \n\
60 SELECT RAISE(IGNORE); \n\
61 END; \n\
62 \n\
63CREATE INDEX SyncNodes_device_name ON SyncNodes (device_name); \n\
64 \n\
65CREATE TABLE SyncLog( \n\
66 state_id INTEGER PRIMARY KEY AUTOINCREMENT, \n\
67 state_hash BLOB NOT NULL UNIQUE, \n\
68 last_update TIMESTAMP NOT NULL \n\
69 ); \n\
70 \n\
71CREATE TABLE \n\
72 SyncStateNodes( \n\
73 id INTEGER PRIMARY KEY AUTOINCREMENT, \n\
74 state_id INTEGER NOT NULL \n\
75 REFERENCES SyncLog (state_id) ON UPDATE CASCADE ON DELETE CASCADE, \n\
76 device_id INTEGER NOT NULL \n\
77 REFERENCES SyncNodes (device_id) ON UPDATE CASCADE ON DELETE CASCADE, \n\
78 seq_no INTEGER NOT NULL \n\
79 ); \n\
80 \n\
81CREATE INDEX SyncStateNodes_device_id ON SyncStateNodes (device_id); \n\
82CREATE INDEX SyncStateNodes_state_id ON SyncStateNodes (state_id); \n\
83CREATE INDEX SyncStateNodes_seq_no ON SyncStateNodes (seq_no); \n\
84 \n\
85CREATE TRIGGER SyncLogGuard_trigger \n\
86 BEFORE INSERT ON SyncLog \n\
87 FOR EACH ROW \n\
88 WHEN (SELECT state_hash \n\
89 FROM SyncLog \n\
90 WHERE state_hash=NEW.state_hash) \n\
91 IS NOT NULL \n\
92 BEGIN \n\
93 DELETE FROM SyncLog WHERE state_hash=NEW.state_hash; \n\
94 END; \n\
95 \n\
96CREATE TABLE ActionLog ( \n\
97 device_id INTEGER NOT NULL, \n\
98 seq_no INTEGER NOT NULL, \n\
99 \n\
100 action CHAR(1) NOT NULL, /* 0 for \"update\", 1 for \"delete\". */ \n\
101 filename TEXT NOT NULL, \n\
102 \n\
103 version INTEGER NOT NULL, \n\
104 action_timestamp TIMESTAMP NOT NULL, \n\
105 \n\
106 file_hash BLOB, /* NULL if action is \"delete\" */ \n\
107 file_atime TIMESTAMP, \n\
108 file_mtime TIMESTAMP, \n\
109 file_ctime TIMESTAMP, \n\
110 file_chmod INTEGER, \n\
111 \n\
112 parent_device_id INTEGER, \n\
113 parent_seq_no INTEGER, \n\
114 \n\
115 action_name TEXT NOT NULL, \n\
116 action_content_object BLOB NOT NULL, \n\
117 \n\
118 PRIMARY KEY (device_id, seq_no), \n\
119 \n\
120 FOREIGN KEY (parent_device_id, parent_seq_no) \n\
121 REFERENCES ActionLog (device_id, parent_seq_no) \n\
122 ON UPDATE RESTRICT \n\
123 ON DELETE SET NULL \n\
124); \n\
125 \n\
126CREATE INDEX ActionLog_filename_version ON ActionLog (filename,version); \n\
127CREATE INDEX ActionLog_parent ON ActionLog (parent_device_id, parent_seq_no); \n\
128CREATE INDEX ActionLog_action_name ON ActionLog (action_name); \n\
Alexander Afanasyev71b43e72012-12-27 01:03:43 -0800129";
130
131DbHelper::DbHelper (const std::string &path)
132{
133 int res = sqlite3_open((path+"chronoshare.db").c_str (), &m_db);
134 if (res != SQLITE_OK)
135 {
136 BOOST_THROW_EXCEPTION (Error::Db ()
137 << errmsg_info_str ("Cannot open/create dabatabase: [" + path + "chronoshare.db" + "]"));
138 }
139
140 res = sqlite3_create_function (m_db, "hash", 2, SQLITE_ANY, 0, 0,
Alexander Afanasyevae43c502012-12-29 17:26:37 -0800141 DbHelper::hash_xStep, DbHelper::hash_xFinal);
Alexander Afanasyev71b43e72012-12-27 01:03:43 -0800142 if (res != SQLITE_OK)
143 {
144 BOOST_THROW_EXCEPTION (Error::Db ()
145 << errmsg_info_str ("Cannot create function ``hash''"));
146 }
147
Alexander Afanasyevae43c502012-12-29 17:26:37 -0800148 res = sqlite3_create_function (m_db, "hash2str", 2, SQLITE_ANY, 0,
149 DbHelper::hash2str_Func,
150 0, 0);
151 if (res != SQLITE_OK)
152 {
153 BOOST_THROW_EXCEPTION (Error::Db ()
154 << errmsg_info_str ("Cannot create function ``hash''"));
155 }
156
157 res = sqlite3_create_function (m_db, "str2hash", 2, SQLITE_ANY, 0,
158 DbHelper::str2hash_Func,
159 0, 0);
160 if (res != SQLITE_OK)
161 {
162 BOOST_THROW_EXCEPTION (Error::Db ()
163 << errmsg_info_str ("Cannot create function ``hash''"));
164 }
165
Alexander Afanasyev71b43e72012-12-27 01:03:43 -0800166 // Alex: determine if tables initialized. if not, initialize... not sure what is the best way to go...
167 // for now, just attempt to create everything
168
169 char *errmsg = 0;
170 res = sqlite3_exec (m_db, INIT_DATABASE.c_str (), NULL, NULL, &errmsg);
Alexander Afanasyev71b43e72012-12-27 01:03:43 -0800171 if (res != SQLITE_OK && errmsg != 0)
172 {
173 std::cerr << "DEBUG: " << errmsg << std::endl;
174 sqlite3_free (errmsg);
175 }
176}
177
178DbHelper::~DbHelper ()
179{
180 int res = sqlite3_close (m_db);
181 if (res != SQLITE_OK)
182 {
183 // complain
184 }
185}
186
Alexander Afanasyevae43c502012-12-29 17:26:37 -0800187HashPtr
188DbHelper::RememberStateInStateLog ()
Alexander Afanasyevde1cdd02012-12-29 14:41:46 -0800189{
Alexander Afanasyevae43c502012-12-29 17:26:37 -0800190 int res = sqlite3_exec (m_db, "BEGIN TRANSACTION;", 0,0,0);
191
192 res += sqlite3_exec (m_db, "\
193INSERT INTO SyncLog \
194 (state_hash, last_update) \
Alexander Afanasyevde1cdd02012-12-29 14:41:46 -0800195 SELECT \
196 hash(device_name, seq_no), datetime('now') \
197 FROM SyncNodes \
198 ORDER BY device_name; \
Alexander Afanasyevae43c502012-12-29 17:26:37 -0800199", 0,0,0);
200
201 if (res != SQLITE_OK)
202 {
203 BOOST_THROW_EXCEPTION (Error::Db ()
204 << errmsg_info_str ("1"));
205 }
206
207 sqlite3_int64 rowId = sqlite3_last_insert_rowid (m_db);
208
209 sqlite3_stmt *insertStmt;
210 res += sqlite3_prepare (m_db, "\
Alexander Afanasyevde1cdd02012-12-29 14:41:46 -0800211INSERT INTO SyncStateNodes \
212 (state_id, device_id, seq_no) \
Alexander Afanasyevae43c502012-12-29 17:26:37 -0800213 SELECT ?, device_id, seq_no \
Alexander Afanasyevde1cdd02012-12-29 14:41:46 -0800214 FROM SyncNodes; \
Alexander Afanasyevae43c502012-12-29 17:26:37 -0800215", -1, &insertStmt, 0);
216
217 res += sqlite3_bind_int64 (insertStmt, 1, rowId);
218 sqlite3_step (insertStmt);
219
220 if (res != SQLITE_OK)
Alexander Afanasyevde1cdd02012-12-29 14:41:46 -0800221 {
Alexander Afanasyevae43c502012-12-29 17:26:37 -0800222 BOOST_THROW_EXCEPTION (Error::Db ()
223 << errmsg_info_str ("4"));
Alexander Afanasyevde1cdd02012-12-29 14:41:46 -0800224 }
Alexander Afanasyevae43c502012-12-29 17:26:37 -0800225 sqlite3_finalize (insertStmt);
226
227 sqlite3_stmt *getHashStmt;
228 res += sqlite3_prepare (m_db, "\
229SELECT state_hash FROM SyncLog WHERE state_id = ?\
230", -1, &getHashStmt, 0);
231 res += sqlite3_bind_int64 (getHashStmt, 1, rowId);
232
233 HashPtr retval;
234 int stepRes = sqlite3_step (getHashStmt);
235 if (stepRes == SQLITE_ROW)
236 {
237 retval = make_shared<Hash> (sqlite3_column_blob (getHashStmt, 0),
238 sqlite3_column_bytes (getHashStmt, 0));
239 }
240 sqlite3_finalize (getHashStmt);
241 res += sqlite3_exec (m_db, "COMMIT;", 0,0,0);
242
243 if (res != SQLITE_OK)
244 {
245 BOOST_THROW_EXCEPTION (Error::Db ()
246 << errmsg_info_str ("Some error with rememberStateInStateLog"));
247 }
248
249 return retval;
Alexander Afanasyevde1cdd02012-12-29 14:41:46 -0800250}
251
252
253void
Alexander Afanasyev71b43e72012-12-27 01:03:43 -0800254DbHelper::hash_xStep (sqlite3_context *context, int argc, sqlite3_value **argv)
255{
256 if (argc != 2)
257 {
258 // _LOG_ERROR ("Wrong arguments are supplied for ``hash'' function");
259 sqlite3_result_error (context, "Wrong arguments are supplied for ``hash'' function", -1);
260 return;
261 }
262 if (sqlite3_value_type (argv[0]) != SQLITE_TEXT ||
263 sqlite3_value_type (argv[1]) != SQLITE_INTEGER)
264 {
265 // _LOG_ERROR ("Hash expects (text,integer) parameters");
266 sqlite3_result_error (context, "Hash expects (text,integer) parameters", -1);
267 return;
268 }
269
270 EVP_MD_CTX **hash_context = reinterpret_cast<EVP_MD_CTX **> (sqlite3_aggregate_context (context, sizeof (EVP_MD_CTX *)));
271
272 if (hash_context == 0)
273 {
274 sqlite3_result_error_nomem (context);
275 return;
276 }
277
278 if (*hash_context == 0)
279 {
280 *hash_context = EVP_MD_CTX_create ();
281 EVP_DigestInit_ex (*hash_context, HASH_FUNCTION (), 0);
282 }
283
284 int nameBytes = sqlite3_value_bytes (argv[0]);
285 const unsigned char *name = sqlite3_value_text (argv[0]);
286 sqlite3_int64 seqno = sqlite3_value_int64 (argv[1]);
287
288 EVP_DigestUpdate (*hash_context, name, nameBytes);
289 EVP_DigestUpdate (*hash_context, &seqno, sizeof(sqlite3_int64));
290}
291
292void
293DbHelper::hash_xFinal (sqlite3_context *context)
294{
295 EVP_MD_CTX **hash_context = reinterpret_cast<EVP_MD_CTX **> (sqlite3_aggregate_context (context, sizeof (EVP_MD_CTX *)));
296
297 if (hash_context == 0)
298 {
299 sqlite3_result_error_nomem (context);
300 return;
301 }
302
303 if (*hash_context == 0) // no rows
304 {
Alexander Afanasyevde1cdd02012-12-29 14:41:46 -0800305 char charNullResult = 0;
306 sqlite3_result_blob (context, &charNullResult, 1, SQLITE_TRANSIENT); //SQLITE_TRANSIENT forces to make a copy
Alexander Afanasyev71b43e72012-12-27 01:03:43 -0800307 return;
308 }
309
310 unsigned char *hash = new unsigned char [EVP_MAX_MD_SIZE];
311 unsigned int hashLength = 0;
312
313 int ok = EVP_DigestFinal_ex (*hash_context,
314 hash, &hashLength);
315
316 sqlite3_result_blob (context, hash, hashLength, SQLITE_TRANSIENT); //SQLITE_TRANSIENT forces to make a copy
317 delete [] hash;
318
319 EVP_MD_CTX_destroy (*hash_context);
320}
Alexander Afanasyevde1cdd02012-12-29 14:41:46 -0800321
322void
323DbHelper::hash2str_Func (sqlite3_context *context, int argc, sqlite3_value **argv)
324{
325 if (argc != 1 || sqlite3_value_type (argv[0]) != SQLITE_BLOB)
326 {
327 sqlite3_result_error (context, "Wrong arguments are supplied for ``hash2str'' function", -1);
328 return;
329 }
330
331 int hashBytes = sqlite3_value_bytes (argv[0]);
332 const void *hash = sqlite3_value_blob (argv[0]);
333
334 std::ostringstream os;
Alexander Afanasyevae43c502012-12-29 17:26:37 -0800335 Hash tmpHash (hash, hashBytes);
336 os << tmpHash;
Alexander Afanasyevde1cdd02012-12-29 14:41:46 -0800337 sqlite3_result_text (context, os.str ().c_str (), -1, SQLITE_TRANSIENT);
338}
339
340void
341DbHelper::str2hash_Func (sqlite3_context *context, int argc, sqlite3_value **argv)
342{
343 if (argc != 1 || sqlite3_value_type (argv[0]) != SQLITE_TEXT)
344 {
345 sqlite3_result_error (context, "Wrong arguments are supplied for ``str2hash'' function", -1);
346 return;
347 }
348
349 size_t hashTextBytes = sqlite3_value_bytes (argv[0]);
350 const unsigned char *hashText = sqlite3_value_text (argv[0]);
351
352 Hash hash (std::string (reinterpret_cast<const char*> (hashText), hashTextBytes));
353 sqlite3_result_blob (context, hash.GetHash (), hash.GetHashBytes (), SQLITE_TRANSIENT);
354}
355
Alexander Afanasyevae43c502012-12-29 17:26:37 -0800356
357sqlite3_int64
358DbHelper::LookupSyncLog (const std::string &stateHash)
359{
360 Hash tmpHash (stateHash);
361 return LookupSyncLog (stateHash);
362}
363
364sqlite3_int64
365DbHelper::LookupSyncLog (const Hash &stateHash)
366{
367 sqlite3_stmt *stmt;
368 int res = sqlite3_prepare (m_db, "SELECT state_id FROM SyncLog WHERE state_hash = ?",
369 -1, &stmt, 0);
370
371 if (res != SQLITE_OK)
372 {
373 BOOST_THROW_EXCEPTION (Error::Db ()
374 << errmsg_info_str ("Cannot prepare statement"));
375 }
376
377 res = sqlite3_bind_blob (stmt, 1, stateHash.GetHash (), stateHash.GetHashBytes (), SQLITE_STATIC);
378 if (res != SQLITE_OK)
379 {
380 BOOST_THROW_EXCEPTION (Error::Db ()
381 << errmsg_info_str ("Cannot bind"));
382 }
383
384 sqlite3_int64 row = 0; // something bad
385
386 if (sqlite3_step (stmt) == SQLITE_ROW)
387 {
388 row = sqlite3_column_int64 (stmt, 0);
389 }
390
391 sqlite3_finalize (stmt);
392
393 return row;
394}
395
396void
397DbHelper::UpdateDeviceSeqno (const std::string &name, uint64_t seqNo)
398{
399 sqlite3_stmt *stmt;
400 // update is performed using trigger
401 int res = sqlite3_prepare (m_db, "INSERT INTO SyncNodes (device_name, seq_no) VALUES (?,?);",
402 -1, &stmt, 0);
403
404 res += sqlite3_bind_text (stmt, 1, name.c_str (), name.size (), SQLITE_STATIC);
405 res += sqlite3_bind_int64 (stmt, 2, seqNo);
406 sqlite3_step (stmt);
407
408 if (res != SQLITE_OK)
409 {
410 BOOST_THROW_EXCEPTION (Error::Db ()
411 << errmsg_info_str ("Some error with UpdateDeviceSeqno"));
412 }
413 sqlite3_finalize (stmt);
414}
415
416void
417DbHelper::FindStateDifferences (const std::string &oldHash, const std::string &newHash)
418{
419 Hash tmpOldHash (oldHash);
420 Hash tmpNewHash (newHash);
421
422 FindStateDifferences (tmpOldHash, tmpNewHash);
423}
424
425void
426DbHelper::FindStateDifferences (const Hash &oldHash, const Hash &newHash)
427{
428 sqlite3_stmt *stmt;
429
430 int res = sqlite3_prepare_v2 (m_db, "\
431SELECT sn.device_name, s_old.seq_no, s_new.seq_no \
432 FROM (SELECT * \
433 FROM SyncStateNodes \
434 WHERE state_id=(SELECT state_id \
435 FROM SyncLog \
436 WHERE state_hash=:old_hash)) s_old \
437 LEFT JOIN (SELECT * \
438 FROM SyncStateNodes \
439 WHERE state_id=(SELECT state_id \
440 FROM SyncLog \
441 WHERE state_hash=:new_hash)) s_new \
442 \
443 ON s_old.device_id = s_new.device_id \
444 JOIN SyncNodes sn ON sn.device_id = s_old.device_id \
445 \
446 WHERE s_new.seq_no IS NULL OR \
447 s_old.seq_no != s_new.seq_no \
448 \
449UNION ALL \
450 \
451SELECT sn.device_name, s_old.seq_no, s_new.seq_no \
452 FROM (SELECT * \
453 FROM SyncStateNodes \
454 WHERE state_id=(SELECT state_id \
455 FROM SyncLog \
456 WHERE state_hash=:new_hash )) s_new \
457 LEFT JOIN (SELECT * \
458 FROM SyncStateNodes \
459 WHERE state_id=(SELECT state_id \
460 FROM SyncLog \
461 WHERE state_hash=:old_hash)) s_old \
462 \
463 ON s_old.device_id = s_new.device_id \
464 JOIN SyncNodes sn ON sn.device_id = s_new.device_id \
465 \
466 WHERE s_old.seq_no IS NULL \
467", -1, &stmt, 0);
468
469 if (res != SQLITE_OK)
470 {
471 BOOST_THROW_EXCEPTION (Error::Db ()
472 << errmsg_info_str ("Some error with FindStateDifferences"));
473 }
474
475 res += sqlite3_bind_blob (stmt, 1, oldHash.GetHash (), oldHash.GetHashBytes (), SQLITE_STATIC);
476 res += sqlite3_bind_blob (stmt, 2, newHash.GetHash (), newHash.GetHashBytes (), SQLITE_STATIC);
477
478 while (sqlite3_step (stmt) == SQLITE_ROW)
479 {
480 std::cout << sqlite3_column_text (stmt, 0) <<
481 ": from " << sqlite3_column_int64 (stmt, 1) <<
482 " to " << sqlite3_column_int64 (stmt, 2) <<
483 std::endl;
484 }
485 sqlite3_finalize (stmt);
486
487}