Write out a .sha1sums file with checksums for segments in this snapshot.
[cumulus.git] / localdb.cc
1 /* LBS: An LFS-inspired filesystem backup system
2  * Copyright (C) 2007  Michael Vrable
3  *
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.
8  *
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
11  * changed later. */
12
13 #include <assert.h>
14 #include <stdio.h>
15 #include <string.h>
16 #include <sqlite3.h>
17
18 #include <string>
19
20 #include "localdb.h"
21 #include "store.h"
22
23 using std::string;
24
25 /* Helper function to prepare a statement for execution in the current
26  * database. */
27 sqlite3_stmt *LocalDb::Prepare(const char *sql)
28 {
29     sqlite3_stmt *stmt;
30     int rc;
31     const char *tail;
32
33     rc = sqlite3_prepare_v2(db, sql, strlen(sql), &stmt, &tail);
34     if (rc != SQLITE_OK) {
35         throw IOException(string("Error preparing statement: ") + sql);
36     }
37
38     return stmt;
39 }
40
41 void LocalDb::ReportError(int rc)
42 {
43     fprintf(stderr, "Result code: %d\n", rc);
44     fprintf(stderr, "Error message: %s\n", sqlite3_errmsg(db));
45 }
46
47 void LocalDb::Open(const char *path, const char *snapshot_name,
48                    const char *snapshot_scheme)
49 {
50     int rc;
51
52     rc = sqlite3_open(path, &db);
53     if (rc) {
54         fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
55         sqlite3_close(db);
56         throw IOException("Error opening local database");
57     }
58
59     rc = sqlite3_exec(db, "begin", NULL, NULL, NULL);
60     if (rc) {
61         fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
62         sqlite3_close(db);
63         throw IOException("Error starting transaction");
64     }
65
66     sqlite3_extended_result_codes(db, 1);
67
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),
74                       SQLITE_TRANSIENT);
75     if (snapshot_scheme == NULL)
76         sqlite3_bind_null(stmt, 2);
77     else
78         sqlite3_bind_text(stmt, 2, snapshot_scheme, strlen(snapshot_scheme),
79                           SQLITE_TRANSIENT);
80
81     rc = sqlite3_step(stmt);
82     if (rc != SQLITE_DONE) {
83         ReportError(rc);
84         sqlite3_close(db);
85         throw IOException("Database execution error!");
86     }
87
88     snapshotid = sqlite3_last_insert_rowid(db);
89     sqlite3_finalize(stmt);
90     if (snapshotid == 0) {
91         ReportError(rc);
92         sqlite3_close(db);
93         throw IOException("Find snapshot id");
94     }
95 }
96
97 void LocalDb::Close()
98 {
99     int rc;
100     rc = sqlite3_exec(db, "commit", NULL, NULL, NULL);
101     if (rc != SQLITE_OK) {
102         fprintf(stderr, "DATABASE ERROR: Can't commit database!\n");
103         ReportError(rc);
104     }
105     sqlite3_close(db);
106 }
107
108 int64_t LocalDb::SegmentToId(const string &segment)
109 {
110     int rc;
111     sqlite3_stmt *stmt;
112     int64_t result;
113
114     stmt = Prepare("insert or ignore into segments(segment) values (?)");
115     sqlite3_bind_text(stmt, 1, segment.c_str(), segment.size(),
116                       SQLITE_TRANSIENT);
117     rc = sqlite3_step(stmt);
118     if (rc != SQLITE_DONE) {
119         throw IOException("Could not execute INSERT statement!");
120     }
121     sqlite3_finalize(stmt);
122
123     stmt = Prepare("select segmentid from segments where segment = ?");
124     sqlite3_bind_text(stmt, 1, segment.c_str(), segment.size(),
125                       SQLITE_TRANSIENT);
126
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);
132     } else {
133         throw IOException("Error executing find segment by id query");
134     }
135
136     sqlite3_finalize(stmt);
137
138     return result;
139 }
140
141 string LocalDb::IdToSegment(int64_t segmentid)
142 {
143     int rc;
144     sqlite3_stmt *stmt;
145     string result;
146
147     stmt = Prepare("select segment from segments where segmentid = ?");
148     sqlite3_bind_int64(stmt, 1, segmentid);
149
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);
155     } else {
156         throw IOException("Error executing find segment by id query");
157     }
158
159     sqlite3_finalize(stmt);
160
161     return result;
162 }
163
164 void LocalDb::StoreObject(const ObjectReference& ref,
165                           const string &checksum, int64_t size,
166                           double age)
167 {
168     int rc;
169     sqlite3_stmt *stmt;
170
171     if (age == 0.0) {
172         stmt = Prepare("insert into block_index("
173                        "segmentid, object, checksum, size, timestamp) "
174                        "values (?, ?, ?, ?, julianday('now'))");
175     } else {
176         stmt = Prepare("insert into block_index("
177                        "segmentid, object, checksum, size, timestamp) "
178                        "values (?, ?, ?, ?, ?)");
179     }
180
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(),
185                       SQLITE_TRANSIENT);
186     sqlite3_bind_int64(stmt, 4, size);
187     if (age != 0.0)
188         sqlite3_bind_double(stmt, 5, age);
189
190     rc = sqlite3_step(stmt);
191     if (rc != SQLITE_DONE) {
192         fprintf(stderr, "Could not execute INSERT statement!\n");
193         ReportError(rc);
194     }
195
196     sqlite3_finalize(stmt);
197 }
198
199 ObjectReference LocalDb::FindObject(const string &checksum, int64_t size)
200 {
201     int rc;
202     sqlite3_stmt *stmt;
203     ObjectReference ref;
204
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(),
208                       SQLITE_TRANSIENT);
209     sqlite3_bind_int64(stmt, 2, size);
210
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));
216     } else {
217         fprintf(stderr, "Could not execute SELECT statement!\n");
218         ReportError(rc);
219     }
220
221     sqlite3_finalize(stmt);
222
223     return ref;
224 }
225
226 bool LocalDb::IsOldObject(const string &checksum, int64_t size, double *age)
227 {
228     int rc;
229     sqlite3_stmt *stmt;
230     bool found = false;
231
232     stmt = Prepare("select segmentid, object, timestamp from block_index "
233                    "where checksum = ? and size = ?");
234     sqlite3_bind_text(stmt, 1, checksum.c_str(), checksum.size(),
235                       SQLITE_TRANSIENT);
236     sqlite3_bind_int64(stmt, 2, size);
237
238     rc = sqlite3_step(stmt);
239     if (rc == SQLITE_DONE) {
240         found = false;
241     } else if (rc == SQLITE_ROW) {
242         found = true;
243         *age = sqlite3_column_double(stmt, 2);
244     } else {
245         fprintf(stderr, "Could not execute SELECT statement!\n");
246         ReportError(rc);
247     }
248
249     sqlite3_finalize(stmt);
250
251     return found;
252 }
253
254 /* Does this object still exist in the database (and not expired)? */
255 bool LocalDb::IsAvailable(const ObjectReference &ref)
256 {
257     int rc;
258     sqlite3_stmt *stmt;
259     bool found = false;
260
261     stmt = Prepare("select count(*) from block_index "
262                    "where segmentid = ? and object = ? and expired is null");
263     sqlite3_bind_int64(stmt, 1, SegmentToId(ref.get_segment()));
264     sqlite3_bind_text(stmt, 2, ref.get_sequence().c_str(),
265                       ref.get_sequence().size(), SQLITE_TRANSIENT);
266
267     rc = sqlite3_step(stmt);
268     if (rc == SQLITE_DONE) {
269         found = false;
270     } else if (rc == SQLITE_ROW) {
271         if (sqlite3_column_int(stmt, 0) > 0)
272             found = true;
273     } else {
274         fprintf(stderr, "Could not execute SELECT statement!\n");
275         ReportError(rc);
276     }
277
278     sqlite3_finalize(stmt);
279
280     return found;
281 }
282
283 void LocalDb::UseObject(const ObjectReference& ref)
284 {
285     int rc;
286     sqlite3_stmt *stmt;
287
288     stmt = Prepare("insert or ignore into snapshot_contents "
289                    "select blockid, ? as snapshotid from block_index "
290                    "where segmentid = ? and object = ?");
291     sqlite3_bind_int64(stmt, 1, snapshotid);
292     sqlite3_bind_int64(stmt, 2, SegmentToId(ref.get_segment()));
293     string obj = ref.get_sequence();
294     sqlite3_bind_text(stmt, 3, obj.c_str(), obj.size(), SQLITE_TRANSIENT);
295
296     rc = sqlite3_step(stmt);
297     if (rc != SQLITE_DONE) {
298         fprintf(stderr, "Could not execute INSERT statement!\n");
299         ReportError(rc);
300     }
301
302     sqlite3_finalize(stmt);
303 }
304
305 void LocalDb::SetSegmentChecksum(const std::string &segment,
306                                  const std::string &path,
307                                  const std::string &checksum)
308 {
309     int rc;
310     sqlite3_stmt *stmt;
311
312     stmt = Prepare("update segments set path = ?, checksum = ? "
313                    "where segmentid = ?");
314     sqlite3_bind_text(stmt, 1, path.c_str(), path.size(),
315                       SQLITE_TRANSIENT);
316     sqlite3_bind_text(stmt, 2, checksum.c_str(), checksum.size(),
317                       SQLITE_TRANSIENT);
318     sqlite3_bind_int64(stmt, 3, SegmentToId(segment));
319
320     rc = sqlite3_step(stmt);
321     if (rc != SQLITE_DONE) {
322         fprintf(stderr, "Could not update segment checksum in database!\n");
323         ReportError(rc);
324     }
325
326     sqlite3_finalize(stmt);
327 }
328
329 bool LocalDb::GetSegmentChecksum(const string &segment,
330                                  string *seg_path,
331                                  string *seg_checksum)
332 {
333     int rc;
334     sqlite3_stmt *stmt;
335     ObjectReference ref;
336     int found = false;
337
338     stmt = Prepare("select path, checksum from segments where segment = ?");
339     sqlite3_bind_text(stmt, 1, segment.c_str(), segment.size(),
340                       SQLITE_TRANSIENT);
341
342     rc = sqlite3_step(stmt);
343     if (rc == SQLITE_DONE) {
344     } else if (rc == SQLITE_ROW) {
345         found = true;
346         const char *val;
347
348         val = (const char *)sqlite3_column_text(stmt, 0);
349         if (val == NULL)
350             found = false;
351         else
352             *seg_path = val;
353
354         val = (const char *)sqlite3_column_text(stmt, 1);
355         if (val == NULL)
356             found = false;
357         else
358             *seg_checksum = val;
359     } else {
360         fprintf(stderr, "Could not execute SELECT statement!\n");
361         ReportError(rc);
362     }
363
364     sqlite3_finalize(stmt);
365
366     return found;
367 }