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) {
35 throw IOException(string("Error preparing statement: ") + sql);
41 void LocalDb::ReportError(int rc)
43 fprintf(stderr, "Result code: %d\n", rc);
44 fprintf(stderr, "Error message: %s\n", sqlite3_errmsg(db));
47 void LocalDb::Open(const char *path, const char *snapshot_name,
48 const char *snapshot_scheme)
52 rc = sqlite3_open(path, &db);
54 fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
56 throw IOException("Error opening local database");
59 rc = sqlite3_exec(db, "begin", NULL, NULL, NULL);
61 fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
63 throw IOException("Error starting transaction");
66 sqlite3_extended_result_codes(db, 1);
68 /* Insert this snapshot into the database, and determine the integer key
69 * which will be used to identify it. */
70 sqlite3_stmt *stmt = Prepare("insert into "
71 "snapshots(name, scheme, timestamp) "
72 "values (?, ?, julianday('now'))");
73 sqlite3_bind_text(stmt, 1, snapshot_name, strlen(snapshot_name),
75 if (snapshot_scheme == NULL)
76 sqlite3_bind_null(stmt, 2);
78 sqlite3_bind_text(stmt, 2, snapshot_scheme, strlen(snapshot_scheme),
81 rc = sqlite3_step(stmt);
82 if (rc != SQLITE_DONE) {
85 throw IOException("Database execution error!");
88 snapshotid = sqlite3_last_insert_rowid(db);
89 sqlite3_finalize(stmt);
90 if (snapshotid == 0) {
93 throw IOException("Find snapshot id");
100 rc = sqlite3_exec(db, "commit", NULL, NULL, NULL);
101 if (rc != SQLITE_OK) {
102 fprintf(stderr, "DATABASE ERROR: Can't commit database!\n");
108 int64_t LocalDb::SegmentToId(const string &segment)
114 stmt = Prepare("insert or ignore into segments(segment) values (?)");
115 sqlite3_bind_text(stmt, 1, segment.c_str(), segment.size(),
117 rc = sqlite3_step(stmt);
118 if (rc != SQLITE_DONE) {
119 throw IOException("Could not execute INSERT statement!");
121 sqlite3_finalize(stmt);
123 stmt = Prepare("select segmentid from segments where segment = ?");
124 sqlite3_bind_text(stmt, 1, segment.c_str(), segment.size(),
127 rc = sqlite3_step(stmt);
128 if (rc == SQLITE_DONE) {
129 throw IOException("No segment found by id");
130 } else if (rc == SQLITE_ROW) {
131 result = sqlite3_column_int64(stmt, 0);
133 throw IOException("Error executing find segment by id query");
136 sqlite3_finalize(stmt);
141 string LocalDb::IdToSegment(int64_t segmentid)
147 stmt = Prepare("select segment from segments where segmentid = ?");
148 sqlite3_bind_int64(stmt, 1, segmentid);
150 rc = sqlite3_step(stmt);
151 if (rc == SQLITE_DONE) {
152 throw IOException("No segment found by id");
153 } else if (rc == SQLITE_ROW) {
154 result = (const char *)sqlite3_column_text(stmt, 0);
156 throw IOException("Error executing find segment by id query");
159 sqlite3_finalize(stmt);
164 void LocalDb::StoreObject(const ObjectReference& ref,
165 const string &checksum, int64_t size,
172 stmt = Prepare("insert into block_index("
173 "segmentid, object, checksum, size, timestamp) "
174 "values (?, ?, ?, ?, julianday('now'))");
176 stmt = Prepare("insert into block_index("
177 "segmentid, object, checksum, size, timestamp) "
178 "values (?, ?, ?, ?, ?)");
181 sqlite3_bind_int64(stmt, 1, SegmentToId(ref.get_segment()));
182 string obj = ref.get_sequence();
183 sqlite3_bind_text(stmt, 2, obj.c_str(), obj.size(), SQLITE_TRANSIENT);
184 sqlite3_bind_text(stmt, 3, checksum.c_str(), checksum.size(),
186 sqlite3_bind_int64(stmt, 4, size);
188 sqlite3_bind_double(stmt, 5, age);
190 rc = sqlite3_step(stmt);
191 if (rc != SQLITE_DONE) {
192 fprintf(stderr, "Could not execute INSERT statement!\n");
196 sqlite3_finalize(stmt);
199 ObjectReference LocalDb::FindObject(const string &checksum, int64_t size)
205 stmt = Prepare("select segmentid, object from block_index "
206 "where checksum = ? and size = ? and expired is null");
207 sqlite3_bind_text(stmt, 1, checksum.c_str(), checksum.size(),
209 sqlite3_bind_int64(stmt, 2, size);
211 rc = sqlite3_step(stmt);
212 if (rc == SQLITE_DONE) {
213 } else if (rc == SQLITE_ROW) {
214 ref = ObjectReference(IdToSegment(sqlite3_column_int64(stmt, 0)),
215 (const char *)sqlite3_column_text(stmt, 1));
217 fprintf(stderr, "Could not execute SELECT statement!\n");
221 sqlite3_finalize(stmt);
226 bool LocalDb::IsOldObject(const string &checksum, int64_t size, double *age,
233 stmt = Prepare("select segmentid, object, timestamp, expired "
234 "from block_index where checksum = ? and size = ?");
235 sqlite3_bind_text(stmt, 1, checksum.c_str(), checksum.size(),
237 sqlite3_bind_int64(stmt, 2, size);
239 rc = sqlite3_step(stmt);
240 if (rc == SQLITE_DONE) {
242 } else if (rc == SQLITE_ROW) {
244 *age = sqlite3_column_double(stmt, 2);
245 *group = sqlite3_column_int(stmt, 3);
247 fprintf(stderr, "Could not execute SELECT statement!\n");
251 sqlite3_finalize(stmt);
256 /* Does this object still exist in the database (and not expired)? */
257 bool LocalDb::IsAvailable(const ObjectReference &ref)
263 stmt = Prepare("select count(*) from block_index "
264 "where segmentid = ? and object = ? and expired is null");
265 sqlite3_bind_int64(stmt, 1, SegmentToId(ref.get_segment()));
266 sqlite3_bind_text(stmt, 2, ref.get_sequence().c_str(),
267 ref.get_sequence().size(), SQLITE_TRANSIENT);
269 rc = sqlite3_step(stmt);
270 if (rc == SQLITE_DONE) {
272 } else if (rc == SQLITE_ROW) {
273 if (sqlite3_column_int(stmt, 0) > 0)
276 fprintf(stderr, "Could not execute SELECT statement!\n");
280 sqlite3_finalize(stmt);
285 void LocalDb::UseObject(const ObjectReference& ref)
290 stmt = Prepare("insert or ignore into snapshot_contents "
291 "select blockid, ? as snapshotid from block_index "
292 "where segmentid = ? and object = ?");
293 sqlite3_bind_int64(stmt, 1, snapshotid);
294 sqlite3_bind_int64(stmt, 2, SegmentToId(ref.get_segment()));
295 string obj = ref.get_sequence();
296 sqlite3_bind_text(stmt, 3, obj.c_str(), obj.size(), SQLITE_TRANSIENT);
298 rc = sqlite3_step(stmt);
299 if (rc != SQLITE_DONE) {
300 fprintf(stderr, "Could not execute INSERT statement!\n");
304 sqlite3_finalize(stmt);
307 void LocalDb::SetSegmentChecksum(const std::string &segment,
308 const std::string &path,
309 const std::string &checksum)
314 stmt = Prepare("update segments set path = ?, checksum = ? "
315 "where segmentid = ?");
316 sqlite3_bind_text(stmt, 1, path.c_str(), path.size(),
318 sqlite3_bind_text(stmt, 2, checksum.c_str(), checksum.size(),
320 sqlite3_bind_int64(stmt, 3, SegmentToId(segment));
322 rc = sqlite3_step(stmt);
323 if (rc != SQLITE_DONE) {
324 fprintf(stderr, "Could not update segment checksum in database!\n");
328 sqlite3_finalize(stmt);
331 bool LocalDb::GetSegmentChecksum(const string &segment,
333 string *seg_checksum)
340 stmt = Prepare("select path, checksum from segments where segment = ?");
341 sqlite3_bind_text(stmt, 1, segment.c_str(), segment.size(),
344 rc = sqlite3_step(stmt);
345 if (rc == SQLITE_DONE) {
346 } else if (rc == SQLITE_ROW) {
350 val = (const char *)sqlite3_column_text(stmt, 0);
356 val = (const char *)sqlite3_column_text(stmt, 1);
362 fprintf(stderr, "Could not execute SELECT statement!\n");
366 sqlite3_finalize(stmt);