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(db, sql, strlen(sql), &stmt, &tail);
34 if (rc != SQLITE_OK) {
35 throw IOException(string("Error preparing statement: ") + sql);
41 void LocalDb::Open(const char *path, const char *snapshot_name,
42 const char *snapshot_scheme)
46 rc = sqlite3_open(path, &db);
48 fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
50 throw IOException("Error opening local database");
53 rc = sqlite3_exec(db, "begin", NULL, NULL, NULL);
55 fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
57 throw IOException("Error starting transaction");
60 /* Insert this snapshot into the database, and determine the integer key
61 * which will be used to identify it. */
62 sqlite3_stmt *stmt = Prepare("insert into "
63 "snapshots(name, scheme, timestamp) "
64 "values (?, ?, julianday('now'))");
65 sqlite3_bind_text(stmt, 1, snapshot_name, strlen(snapshot_name),
67 if (snapshot_scheme == NULL)
68 sqlite3_bind_null(stmt, 2);
70 sqlite3_bind_text(stmt, 2, snapshot_scheme, strlen(snapshot_scheme),
73 rc = sqlite3_step(stmt);
74 if (rc != SQLITE_DONE) {
76 throw IOException("Database execution error!");
79 snapshotid = sqlite3_last_insert_rowid(db);
80 sqlite3_finalize(stmt);
81 if (snapshotid == 0) {
83 throw IOException("Find snapshot id");
90 rc = sqlite3_exec(db, "commit", NULL, NULL, NULL);
91 if (rc != SQLITE_OK) {
92 fprintf(stderr, "Can't commit database!\n");
97 int64_t LocalDb::SegmentToId(const string &segment)
103 stmt = Prepare("insert or ignore into segments(segment) values (?)");
104 sqlite3_bind_text(stmt, 1, segment.c_str(), segment.size(),
106 rc = sqlite3_step(stmt);
107 if (rc != SQLITE_DONE) {
108 throw IOException("Could not execute INSERT statement!");
110 sqlite3_finalize(stmt);
112 stmt = Prepare("select segmentid from segments where segment = ?");
113 sqlite3_bind_text(stmt, 1, segment.c_str(), segment.size(),
116 rc = sqlite3_step(stmt);
117 if (rc == SQLITE_DONE) {
118 throw IOException("No segment found by id");
119 } else if (rc == SQLITE_ROW) {
120 result = sqlite3_column_int64(stmt, 0);
122 throw IOException("Error executing find segment by id query");
125 sqlite3_finalize(stmt);
130 string LocalDb::IdToSegment(int64_t segmentid)
136 stmt = Prepare("select segment from segments where segmentid = ?");
137 sqlite3_bind_int64(stmt, 1, segmentid);
139 rc = sqlite3_step(stmt);
140 if (rc == SQLITE_DONE) {
141 throw IOException("No segment found by id");
142 } else if (rc == SQLITE_ROW) {
143 result = (const char *)sqlite3_column_text(stmt, 0);
145 throw IOException("Error executing find segment by id query");
148 sqlite3_finalize(stmt);
153 void LocalDb::StoreObject(const ObjectReference& ref,
154 const string &checksum, int64_t size,
161 stmt = Prepare("insert into block_index("
162 "segmentid, object, checksum, size, timestamp) "
163 "values (?, ?, ?, ?, julianday('now'))");
165 stmt = Prepare("insert into block_index("
166 "segmentid, object, checksum, size, timestamp) "
167 "values (?, ?, ?, ?, ?)");
170 sqlite3_bind_int64(stmt, 1, SegmentToId(ref.get_segment()));
171 string obj = ref.get_sequence();
172 sqlite3_bind_text(stmt, 2, obj.c_str(), obj.size(), SQLITE_TRANSIENT);
173 sqlite3_bind_text(stmt, 3, checksum.c_str(), checksum.size(),
175 sqlite3_bind_int64(stmt, 4, size);
177 sqlite3_bind_double(stmt, 5, age);
179 rc = sqlite3_step(stmt);
180 if (rc != SQLITE_DONE) {
181 fprintf(stderr, "Could not execute INSERT statement!\n");
184 sqlite3_finalize(stmt);
187 ObjectReference LocalDb::FindObject(const string &checksum, int64_t size)
193 stmt = Prepare("select segmentid, object from block_index "
194 "where checksum = ? and size = ? and expired is null");
195 sqlite3_bind_text(stmt, 1, checksum.c_str(), checksum.size(),
197 sqlite3_bind_int64(stmt, 2, size);
199 rc = sqlite3_step(stmt);
200 if (rc == SQLITE_DONE) {
201 } else if (rc == SQLITE_ROW) {
202 ref = ObjectReference(IdToSegment(sqlite3_column_int64(stmt, 0)),
203 (const char *)sqlite3_column_text(stmt, 1));
205 fprintf(stderr, "Could not execute SELECT statement!\n");
208 sqlite3_finalize(stmt);
213 bool LocalDb::IsOldObject(const string &checksum, int64_t size, double *age)
219 stmt = Prepare("select segmentid, object, timestamp from block_index "
220 "where checksum = ? and size = ?");
221 sqlite3_bind_text(stmt, 1, checksum.c_str(), checksum.size(),
223 sqlite3_bind_int64(stmt, 2, size);
225 rc = sqlite3_step(stmt);
226 if (rc == SQLITE_DONE) {
228 } else if (rc == SQLITE_ROW) {
230 *age = sqlite3_column_double(stmt, 2);
232 fprintf(stderr, "Could not execute SELECT statement!\n");
235 sqlite3_finalize(stmt);
240 /* Does this object still exist in the database (and not expired)? */
241 bool LocalDb::IsAvailable(const ObjectReference &ref)
247 stmt = Prepare("select count(*) from block_index "
248 "where segmentid = ? and object = ? and expired is null");
249 sqlite3_bind_int64(stmt, 1, SegmentToId(ref.get_segment()));
250 sqlite3_bind_text(stmt, 2, ref.get_sequence().c_str(),
251 ref.get_sequence().size(), SQLITE_TRANSIENT);
253 rc = sqlite3_step(stmt);
254 if (rc == SQLITE_DONE) {
256 } else if (rc == SQLITE_ROW) {
257 if (sqlite3_column_int(stmt, 0) > 0)
260 fprintf(stderr, "Could not execute SELECT statement!\n");
263 sqlite3_finalize(stmt);
268 void LocalDb::UseObject(const ObjectReference& ref)
273 stmt = Prepare("insert or ignore into snapshot_contents "
274 "select blockid, ? as snapshotid from block_index "
275 "where segmentid = ? and object = ?");
276 sqlite3_bind_int64(stmt, 1, snapshotid);
277 sqlite3_bind_int64(stmt, 2, SegmentToId(ref.get_segment()));
278 string obj = ref.get_sequence();
279 sqlite3_bind_text(stmt, 3, obj.c_str(), obj.size(), SQLITE_TRANSIENT);
281 rc = sqlite3_step(stmt);
282 if (rc != SQLITE_DONE) {
283 fprintf(stderr, "Could not execute INSERT statement!\n");
286 sqlite3_finalize(stmt);