1 /* LBS: An LFS-inspired filesystem backup system
2 * Copyright (C) 2007 Michael Vrable
4 * When creating backup snapshots, maintain a local database of data blocks and
5 * checksums, in addition to the data contents (which may be stored remotely).
6 * This database is consulted when attempting to build incremental snapshots,
7 * as it says which objects can be reused.
9 * The database is implemented as an SQLite3 database, but this implementation
10 * detail is kept internal to this file, so that the storage format may be
25 /* Helper function to prepare a statement for execution in the current
27 sqlite3_stmt *LocalDb::Prepare(const char *sql)
33 rc = sqlite3_prepare_v2(db, sql, strlen(sql), &stmt, &tail);
34 if (rc != SQLITE_OK) {
36 throw IOException(string("Error preparing statement: ") + sql);
42 void LocalDb::ReportError(int rc)
44 fprintf(stderr, "Result code: %d\n", rc);
45 fprintf(stderr, "Error message: %s\n", sqlite3_errmsg(db));
48 void LocalDb::Open(const char *path, const char *snapshot_name,
49 const char *snapshot_scheme)
53 rc = sqlite3_open(path, &db);
55 fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
57 throw IOException("Error opening local database");
60 rc = sqlite3_exec(db, "begin", NULL, NULL, NULL);
62 fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
64 throw IOException("Error starting transaction");
67 sqlite3_extended_result_codes(db, 1);
69 /* Insert this snapshot into the database, and determine the integer key
70 * which will be used to identify it. */
71 sqlite3_stmt *stmt = Prepare("insert into "
72 "snapshots(name, scheme, timestamp) "
73 "values (?, ?, julianday('now'))");
74 sqlite3_bind_text(stmt, 1, snapshot_name, strlen(snapshot_name),
76 if (snapshot_scheme == NULL)
77 sqlite3_bind_null(stmt, 2);
79 sqlite3_bind_text(stmt, 2, snapshot_scheme, strlen(snapshot_scheme),
82 rc = sqlite3_step(stmt);
83 if (rc != SQLITE_DONE) {
86 throw IOException("Database execution error!");
89 snapshotid = sqlite3_last_insert_rowid(db);
90 sqlite3_finalize(stmt);
91 if (snapshotid == 0) {
94 throw IOException("Find snapshot id");
97 /* Create a temporary table which will be used to keep track of the objects
98 * used by this snapshot. When the database is closed, we will summarize
99 * the results of this table into segments_used. */
100 rc = sqlite3_exec(db,
101 "create temporary table snapshot_refs ("
102 " segmentid integer not null,"
103 " object text not null,"
104 " size integer not null"
105 ")", NULL, NULL, NULL);
106 if (rc != SQLITE_OK) {
109 throw IOException("Database initialization");
111 rc = sqlite3_exec(db,
112 "create unique index snapshot_refs_index "
113 "on snapshot_refs(segmentid, object)",
115 if (rc != SQLITE_OK) {
118 throw IOException("Database initialization");
122 void LocalDb::Close()
126 /* Summarize the snapshot_refs table into segments_used. */
127 sqlite3_stmt *stmt = Prepare("insert into segments_used "
128 "select ? as snapshotid, segmentid, "
129 "cast(used as real) / size as utilization "
131 "(select segmentid, sum(size) as used "
132 "from snapshot_refs group by segmentid) "
133 "join segments using (segmentid)");
134 sqlite3_bind_int64(stmt, 1, snapshotid);
135 rc = sqlite3_step(stmt);
136 if (rc != SQLITE_OK && rc != SQLITE_DONE) {
139 fprintf(stderr, "DATABASE ERROR: Unable to create segment summary!\n");
141 sqlite3_finalize(stmt);
143 /* Commit changes to the database and close. */
144 rc = sqlite3_exec(db, "commit", NULL, NULL, NULL);
145 if (rc != SQLITE_OK) {
146 fprintf(stderr, "DATABASE ERROR: Can't commit database!\n");
152 int64_t LocalDb::SegmentToId(const string &segment)
158 stmt = Prepare("insert or ignore into segments(segment) values (?)");
159 sqlite3_bind_text(stmt, 1, segment.c_str(), segment.size(),
161 rc = sqlite3_step(stmt);
162 if (rc != SQLITE_DONE) {
163 throw IOException("Could not execute INSERT statement!");
165 sqlite3_finalize(stmt);
167 stmt = Prepare("select segmentid from segments where segment = ?");
168 sqlite3_bind_text(stmt, 1, segment.c_str(), segment.size(),
171 rc = sqlite3_step(stmt);
172 if (rc == SQLITE_DONE) {
173 throw IOException("No segment found by id");
174 } else if (rc == SQLITE_ROW) {
175 result = sqlite3_column_int64(stmt, 0);
177 throw IOException("Error executing find segment by id query");
180 sqlite3_finalize(stmt);
185 string LocalDb::IdToSegment(int64_t segmentid)
191 stmt = Prepare("select segment from segments where segmentid = ?");
192 sqlite3_bind_int64(stmt, 1, segmentid);
194 rc = sqlite3_step(stmt);
195 if (rc == SQLITE_DONE) {
196 throw IOException("No segment found by id");
197 } else if (rc == SQLITE_ROW) {
198 result = (const char *)sqlite3_column_text(stmt, 0);
200 throw IOException("Error executing find segment by id query");
203 sqlite3_finalize(stmt);
208 void LocalDb::StoreObject(const ObjectReference& ref,
209 const string &checksum, int64_t size,
216 stmt = Prepare("insert into block_index("
217 "segmentid, object, checksum, size, timestamp) "
218 "values (?, ?, ?, ?, julianday('now'))");
220 stmt = Prepare("insert into block_index("
221 "segmentid, object, checksum, size, timestamp) "
222 "values (?, ?, ?, ?, ?)");
225 sqlite3_bind_int64(stmt, 1, SegmentToId(ref.get_segment()));
226 string obj = ref.get_sequence();
227 sqlite3_bind_text(stmt, 2, obj.c_str(), obj.size(), SQLITE_TRANSIENT);
228 sqlite3_bind_text(stmt, 3, checksum.c_str(), checksum.size(),
230 sqlite3_bind_int64(stmt, 4, size);
232 sqlite3_bind_double(stmt, 5, age);
234 rc = sqlite3_step(stmt);
235 if (rc != SQLITE_DONE) {
236 fprintf(stderr, "Could not execute INSERT statement!\n");
240 sqlite3_finalize(stmt);
243 ObjectReference LocalDb::FindObject(const string &checksum, int64_t size)
249 stmt = Prepare("select segmentid, object from block_index "
250 "where checksum = ? and size = ? and expired is null");
251 sqlite3_bind_text(stmt, 1, checksum.c_str(), checksum.size(),
253 sqlite3_bind_int64(stmt, 2, size);
255 rc = sqlite3_step(stmt);
256 if (rc == SQLITE_DONE) {
257 } else if (rc == SQLITE_ROW) {
258 ref = ObjectReference(IdToSegment(sqlite3_column_int64(stmt, 0)),
259 (const char *)sqlite3_column_text(stmt, 1));
261 fprintf(stderr, "Could not execute SELECT statement!\n");
265 sqlite3_finalize(stmt);
270 bool LocalDb::IsOldObject(const string &checksum, int64_t size, double *age,
277 stmt = Prepare("select segmentid, object, timestamp, expired "
278 "from block_index where checksum = ? and size = ?");
279 sqlite3_bind_text(stmt, 1, checksum.c_str(), checksum.size(),
281 sqlite3_bind_int64(stmt, 2, size);
283 rc = sqlite3_step(stmt);
284 if (rc == SQLITE_DONE) {
286 } else if (rc == SQLITE_ROW) {
288 *age = sqlite3_column_double(stmt, 2);
289 *group = sqlite3_column_int(stmt, 3);
291 fprintf(stderr, "Could not execute SELECT statement!\n");
295 sqlite3_finalize(stmt);
300 /* Does this object still exist in the database (and not expired)? */
301 bool LocalDb::IsAvailable(const ObjectReference &ref)
307 stmt = Prepare("select count(*) from block_index "
308 "where segmentid = ? and object = ? and expired is null");
309 sqlite3_bind_int64(stmt, 1, SegmentToId(ref.get_segment()));
310 sqlite3_bind_text(stmt, 2, ref.get_sequence().c_str(),
311 ref.get_sequence().size(), SQLITE_TRANSIENT);
313 rc = sqlite3_step(stmt);
314 if (rc == SQLITE_DONE) {
316 } else if (rc == SQLITE_ROW) {
317 if (sqlite3_column_int(stmt, 0) > 0)
320 fprintf(stderr, "Could not execute SELECT statement!\n");
324 sqlite3_finalize(stmt);
329 void LocalDb::UseObject(const ObjectReference& ref)
334 stmt = Prepare("insert or ignore into snapshot_refs "
335 "select segmentid, object, size from block_index "
336 "where segmentid = ? and object = ?");
337 sqlite3_bind_int64(stmt, 1, SegmentToId(ref.get_segment()));
338 string obj = ref.get_sequence();
339 sqlite3_bind_text(stmt, 2, obj.c_str(), obj.size(), SQLITE_TRANSIENT);
341 rc = sqlite3_step(stmt);
342 if (rc != SQLITE_DONE) {
343 fprintf(stderr, "Could not execute INSERT statement!\n");
347 sqlite3_finalize(stmt);
350 void LocalDb::SetSegmentChecksum(const std::string &segment,
351 const std::string &path,
352 const std::string &checksum)
357 stmt = Prepare("update segments set path = ?, checksum = ?, "
358 "size = (select sum(size) from block_index "
359 " where segmentid = ?) "
360 "where segmentid = ?");
361 sqlite3_bind_text(stmt, 1, path.c_str(), path.size(),
363 sqlite3_bind_text(stmt, 2, checksum.c_str(), checksum.size(),
365 sqlite3_bind_int64(stmt, 3, SegmentToId(segment));
366 sqlite3_bind_int64(stmt, 4, SegmentToId(segment));
368 rc = sqlite3_step(stmt);
369 if (rc != SQLITE_DONE) {
370 fprintf(stderr, "Could not update segment checksum in database!\n");
374 sqlite3_finalize(stmt);
377 bool LocalDb::GetSegmentChecksum(const string &segment,
379 string *seg_checksum)
386 stmt = Prepare("select path, checksum from segments where segment = ?");
387 sqlite3_bind_text(stmt, 1, segment.c_str(), segment.size(),
390 rc = sqlite3_step(stmt);
391 if (rc == SQLITE_DONE) {
392 } else if (rc == SQLITE_ROW) {
396 val = (const char *)sqlite3_column_text(stmt, 0);
402 val = (const char *)sqlite3_column_text(stmt, 1);
408 fprintf(stderr, "Could not execute SELECT statement!\n");
412 sqlite3_finalize(stmt);