Place expired and repacked objects into segments based on database.
[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                           int *group)
228 {
229     int rc;
230     sqlite3_stmt *stmt;
231     bool found = false;
232
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(),
236                       SQLITE_TRANSIENT);
237     sqlite3_bind_int64(stmt, 2, size);
238
239     rc = sqlite3_step(stmt);
240     if (rc == SQLITE_DONE) {
241         found = false;
242     } else if (rc == SQLITE_ROW) {
243         found = true;
244         *age = sqlite3_column_double(stmt, 2);
245         *group = sqlite3_column_int(stmt, 3);
246     } else {
247         fprintf(stderr, "Could not execute SELECT statement!\n");
248         ReportError(rc);
249     }
250
251     sqlite3_finalize(stmt);
252
253     return found;
254 }
255
256 /* Does this object still exist in the database (and not expired)? */
257 bool LocalDb::IsAvailable(const ObjectReference &ref)
258 {
259     int rc;
260     sqlite3_stmt *stmt;
261     bool found = false;
262
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);
268
269     rc = sqlite3_step(stmt);
270     if (rc == SQLITE_DONE) {
271         found = false;
272     } else if (rc == SQLITE_ROW) {
273         if (sqlite3_column_int(stmt, 0) > 0)
274             found = true;
275     } else {
276         fprintf(stderr, "Could not execute SELECT statement!\n");
277         ReportError(rc);
278     }
279
280     sqlite3_finalize(stmt);
281
282     return found;
283 }
284
285 void LocalDb::UseObject(const ObjectReference& ref)
286 {
287     int rc;
288     sqlite3_stmt *stmt;
289
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);
297
298     rc = sqlite3_step(stmt);
299     if (rc != SQLITE_DONE) {
300         fprintf(stderr, "Could not execute INSERT statement!\n");
301         ReportError(rc);
302     }
303
304     sqlite3_finalize(stmt);
305 }
306
307 void LocalDb::SetSegmentChecksum(const std::string &segment,
308                                  const std::string &path,
309                                  const std::string &checksum)
310 {
311     int rc;
312     sqlite3_stmt *stmt;
313
314     stmt = Prepare("update segments set path = ?, checksum = ? "
315                    "where segmentid = ?");
316     sqlite3_bind_text(stmt, 1, path.c_str(), path.size(),
317                       SQLITE_TRANSIENT);
318     sqlite3_bind_text(stmt, 2, checksum.c_str(), checksum.size(),
319                       SQLITE_TRANSIENT);
320     sqlite3_bind_int64(stmt, 3, SegmentToId(segment));
321
322     rc = sqlite3_step(stmt);
323     if (rc != SQLITE_DONE) {
324         fprintf(stderr, "Could not update segment checksum in database!\n");
325         ReportError(rc);
326     }
327
328     sqlite3_finalize(stmt);
329 }
330
331 bool LocalDb::GetSegmentChecksum(const string &segment,
332                                  string *seg_path,
333                                  string *seg_checksum)
334 {
335     int rc;
336     sqlite3_stmt *stmt;
337     ObjectReference ref;
338     int found = false;
339
340     stmt = Prepare("select path, checksum from segments where segment = ?");
341     sqlite3_bind_text(stmt, 1, segment.c_str(), segment.size(),
342                       SQLITE_TRANSIENT);
343
344     rc = sqlite3_step(stmt);
345     if (rc == SQLITE_DONE) {
346     } else if (rc == SQLITE_ROW) {
347         found = true;
348         const char *val;
349
350         val = (const char *)sqlite3_column_text(stmt, 0);
351         if (val == NULL)
352             found = false;
353         else
354             *seg_path = val;
355
356         val = (const char *)sqlite3_column_text(stmt, 1);
357         if (val == NULL)
358             found = false;
359         else
360             *seg_checksum = val;
361     } else {
362         fprintf(stderr, "Could not execute SELECT statement!\n");
363         ReportError(rc);
364     }
365
366     sqlite3_finalize(stmt);
367
368     return found;
369 }