Extend tracking of used segments to cover metadata segments.
[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         ReportError(rc);
36         throw IOException(string("Error preparing statement: ") + sql);
37     }
38
39     return stmt;
40 }
41
42 void LocalDb::ReportError(int rc)
43 {
44     fprintf(stderr, "Result code: %d\n", rc);
45     fprintf(stderr, "Error message: %s\n", sqlite3_errmsg(db));
46 }
47
48 void LocalDb::Open(const char *path, const char *snapshot_name,
49                    const char *snapshot_scheme, double intent)
50 {
51     int rc;
52
53     rc = sqlite3_open(path, &db);
54     if (rc) {
55         fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
56         sqlite3_close(db);
57         throw IOException("Error opening local database");
58     }
59
60     rc = sqlite3_exec(db, "begin", NULL, NULL, NULL);
61     if (rc) {
62         fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
63         sqlite3_close(db);
64         throw IOException("Error starting transaction");
65     }
66
67     sqlite3_extended_result_codes(db, 1);
68
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, intent) "
73                                  "values (?, ?, julianday('now'), ?)");
74     sqlite3_bind_text(stmt, 1, snapshot_name, strlen(snapshot_name),
75                       SQLITE_TRANSIENT);
76     if (snapshot_scheme == NULL)
77         sqlite3_bind_null(stmt, 2);
78     else
79         sqlite3_bind_text(stmt, 2, snapshot_scheme, strlen(snapshot_scheme),
80                           SQLITE_TRANSIENT);
81     sqlite3_bind_double(stmt, 3, intent);
82
83     rc = sqlite3_step(stmt);
84     if (rc != SQLITE_DONE) {
85         ReportError(rc);
86         sqlite3_close(db);
87         throw IOException("Database execution error!");
88     }
89
90     snapshotid = sqlite3_last_insert_rowid(db);
91     sqlite3_finalize(stmt);
92     if (snapshotid == 0) {
93         ReportError(rc);
94         sqlite3_close(db);
95         throw IOException("Find snapshot id");
96     }
97
98     /* Create a temporary table which will be used to keep track of the objects
99      * used by this snapshot.  When the database is closed, we will summarize
100      * the results of this table into segments_used. */
101     rc = sqlite3_exec(db,
102                       "create temporary table snapshot_refs ("
103                       "    segmentid integer not null,"
104                       "    object text not null,"
105                       "    size integer not null"
106                       ")", NULL, NULL, NULL);
107     if (rc != SQLITE_OK) {
108         ReportError(rc);
109         sqlite3_close(db);
110         throw IOException("Database initialization");
111     }
112     rc = sqlite3_exec(db,
113                       "create unique index snapshot_refs_index "
114                       "on snapshot_refs(segmentid, object)",
115                       NULL, NULL, NULL);
116     if (rc != SQLITE_OK) {
117         ReportError(rc);
118         sqlite3_close(db);
119         throw IOException("Database initialization");
120     }
121 }
122
123 void LocalDb::Close()
124 {
125     int rc;
126
127     /* Summarize the snapshot_refs table into segments_used. */
128     sqlite3_stmt *stmt = Prepare(
129         "insert or replace into segments_used "
130         "select ? as snapshotid, segmentid, max(utilization) from ("
131         "    select segmentid, cast(used as real) / size as utilization "
132         "    from "
133         "    (select segmentid, sum(size) as used from snapshot_refs "
134         "       group by segmentid) "
135         "    join segments using (segmentid) "
136         "  union "
137         "    select segmentid, utilization from segments_used "
138         "    where snapshotid = ? "
139         ") group by segmentid"
140     );
141     sqlite3_bind_int64(stmt, 1, snapshotid);
142     sqlite3_bind_int64(stmt, 2, snapshotid);
143     rc = sqlite3_step(stmt);
144     if (rc != SQLITE_OK && rc != SQLITE_DONE) {
145         ReportError(rc);
146         sqlite3_close(db);
147         fprintf(stderr, "DATABASE ERROR: Unable to create segment summary!\n");
148     }
149     sqlite3_finalize(stmt);
150
151     /* Commit changes to the database and close. */
152     rc = sqlite3_exec(db, "commit", NULL, NULL, NULL);
153     if (rc != SQLITE_OK) {
154         fprintf(stderr, "DATABASE ERROR: Can't commit database!\n");
155         ReportError(rc);
156     }
157     sqlite3_close(db);
158 }
159
160 int64_t LocalDb::SegmentToId(const string &segment)
161 {
162     int rc;
163     sqlite3_stmt *stmt;
164     int64_t result;
165
166     stmt = Prepare("insert or ignore into segments(segment) values (?)");
167     sqlite3_bind_text(stmt, 1, segment.c_str(), segment.size(),
168                       SQLITE_TRANSIENT);
169     rc = sqlite3_step(stmt);
170     if (rc != SQLITE_DONE) {
171         throw IOException("Could not execute INSERT statement!");
172     }
173     sqlite3_finalize(stmt);
174
175     stmt = Prepare("select segmentid from segments where segment = ?");
176     sqlite3_bind_text(stmt, 1, segment.c_str(), segment.size(),
177                       SQLITE_TRANSIENT);
178
179     rc = sqlite3_step(stmt);
180     if (rc == SQLITE_DONE) {
181         throw IOException("No segment found by id");
182     } else if (rc == SQLITE_ROW) {
183         result = sqlite3_column_int64(stmt, 0);
184     } else {
185         throw IOException("Error executing find segment by id query");
186     }
187
188     sqlite3_finalize(stmt);
189
190     return result;
191 }
192
193 string LocalDb::IdToSegment(int64_t segmentid)
194 {
195     int rc;
196     sqlite3_stmt *stmt;
197     string result;
198
199     stmt = Prepare("select segment from segments where segmentid = ?");
200     sqlite3_bind_int64(stmt, 1, segmentid);
201
202     rc = sqlite3_step(stmt);
203     if (rc == SQLITE_DONE) {
204         throw IOException("No segment found by id");
205     } else if (rc == SQLITE_ROW) {
206         result = (const char *)sqlite3_column_text(stmt, 0);
207     } else {
208         throw IOException("Error executing find segment by id query");
209     }
210
211     sqlite3_finalize(stmt);
212
213     return result;
214 }
215
216 void LocalDb::StoreObject(const ObjectReference& ref,
217                           const string &checksum, int64_t size,
218                           double age)
219 {
220     int rc;
221     sqlite3_stmt *stmt;
222
223     if (age == 0.0) {
224         stmt = Prepare("insert into block_index("
225                        "segmentid, object, checksum, size, timestamp) "
226                        "values (?, ?, ?, ?, julianday('now'))");
227     } else {
228         stmt = Prepare("insert into block_index("
229                        "segmentid, object, checksum, size, timestamp) "
230                        "values (?, ?, ?, ?, ?)");
231     }
232
233     sqlite3_bind_int64(stmt, 1, SegmentToId(ref.get_segment()));
234     string obj = ref.get_sequence();
235     sqlite3_bind_text(stmt, 2, obj.c_str(), obj.size(), SQLITE_TRANSIENT);
236     sqlite3_bind_text(stmt, 3, checksum.c_str(), checksum.size(),
237                       SQLITE_TRANSIENT);
238     sqlite3_bind_int64(stmt, 4, size);
239     if (age != 0.0)
240         sqlite3_bind_double(stmt, 5, age);
241
242     rc = sqlite3_step(stmt);
243     if (rc != SQLITE_DONE) {
244         fprintf(stderr, "Could not execute INSERT statement!\n");
245         ReportError(rc);
246     }
247
248     sqlite3_finalize(stmt);
249
250     if (age != 0.0) {
251         stmt = Prepare("update segments set mtime = max(mtime, ?) "
252                        "where segmentid = ?");
253         sqlite3_bind_double(stmt, 1, age);
254         sqlite3_bind_int64(stmt, 2, SegmentToId(ref.get_segment()));
255         rc = sqlite3_step(stmt);
256         sqlite3_finalize(stmt);
257     }
258 }
259
260 ObjectReference LocalDb::FindObject(const string &checksum, int64_t size)
261 {
262     int rc;
263     sqlite3_stmt *stmt;
264     ObjectReference ref;
265
266     stmt = Prepare("select segmentid, object from block_index "
267                    "where checksum = ? and size = ? and expired is null");
268     sqlite3_bind_text(stmt, 1, checksum.c_str(), checksum.size(),
269                       SQLITE_TRANSIENT);
270     sqlite3_bind_int64(stmt, 2, size);
271
272     rc = sqlite3_step(stmt);
273     if (rc == SQLITE_DONE) {
274     } else if (rc == SQLITE_ROW) {
275         ref = ObjectReference(IdToSegment(sqlite3_column_int64(stmt, 0)),
276                               (const char *)sqlite3_column_text(stmt, 1));
277         ref.set_range(0, size);
278     } else {
279         fprintf(stderr, "Could not execute SELECT statement!\n");
280         ReportError(rc);
281     }
282
283     sqlite3_finalize(stmt);
284
285     return ref;
286 }
287
288 bool LocalDb::IsOldObject(const string &checksum, int64_t size, double *age,
289                           int *group)
290 {
291     int rc;
292     sqlite3_stmt *stmt;
293     bool found = false;
294
295     stmt = Prepare("select segmentid, object, timestamp, expired "
296                    "from block_index where checksum = ? and size = ?");
297     sqlite3_bind_text(stmt, 1, checksum.c_str(), checksum.size(),
298                       SQLITE_TRANSIENT);
299     sqlite3_bind_int64(stmt, 2, size);
300
301     rc = sqlite3_step(stmt);
302     if (rc == SQLITE_DONE) {
303         found = false;
304     } else if (rc == SQLITE_ROW) {
305         found = true;
306         *age = sqlite3_column_double(stmt, 2);
307         *group = sqlite3_column_int(stmt, 3);
308     } else {
309         fprintf(stderr, "Could not execute SELECT statement!\n");
310         ReportError(rc);
311     }
312
313     sqlite3_finalize(stmt);
314
315     return found;
316 }
317
318 /* Does this object still exist in the database (and not expired)? */
319 bool LocalDb::IsAvailable(const ObjectReference &ref)
320 {
321     int rc;
322     sqlite3_stmt *stmt;
323     bool found = false;
324
325     // Special objects (such as the zero object) aren't stored in segments, and
326     // so are always available.
327     if (!ref.is_normal())
328         return true;
329
330     stmt = Prepare("select count(*) from block_index "
331                    "where segmentid = ? and object = ? and expired is null");
332     sqlite3_bind_int64(stmt, 1, SegmentToId(ref.get_segment()));
333     sqlite3_bind_text(stmt, 2, ref.get_sequence().c_str(),
334                       ref.get_sequence().size(), SQLITE_TRANSIENT);
335
336     rc = sqlite3_step(stmt);
337     if (rc == SQLITE_DONE) {
338         found = false;
339     } else if (rc == SQLITE_ROW) {
340         if (sqlite3_column_int(stmt, 0) > 0)
341             found = true;
342     } else {
343         fprintf(stderr, "Could not execute SELECT statement!\n");
344         ReportError(rc);
345     }
346
347     sqlite3_finalize(stmt);
348
349     return found;
350 }
351
352 void LocalDb::UseObject(const ObjectReference& ref)
353 {
354     int rc;
355     sqlite3_stmt *stmt;
356
357     if (!ref.is_normal())
358         return;
359
360     stmt = Prepare("insert or ignore into snapshot_refs "
361                    "select segmentid, object, size from block_index "
362                    "where segmentid = ? and object = ?");
363     sqlite3_bind_int64(stmt, 1, SegmentToId(ref.get_segment()));
364     string obj = ref.get_sequence();
365     sqlite3_bind_text(stmt, 2, obj.c_str(), obj.size(), SQLITE_TRANSIENT);
366
367     rc = sqlite3_step(stmt);
368     if (rc != SQLITE_DONE) {
369         fprintf(stderr, "Could not execute INSERT statement!\n");
370         ReportError(rc);
371     }
372
373     sqlite3_finalize(stmt);
374 }
375
376 void LocalDb::UseSegment(const std::string &segment, double utilization)
377 {
378     int rc;
379     sqlite3_stmt *stmt;
380
381     stmt = Prepare("insert or replace "
382                    "into segments_used(snapshotid, segmentid, utilization) "
383                    "values (?, ?, ?)");
384     sqlite3_bind_int64(stmt, 1, snapshotid);
385     sqlite3_bind_int64(stmt, 2, SegmentToId(segment));
386     sqlite3_bind_double(stmt, 3, utilization);
387
388     rc = sqlite3_step(stmt);
389     if (rc != SQLITE_DONE) {
390         fprintf(stderr, "Could not insert segment use record!\n");
391         ReportError(rc);
392     }
393
394     sqlite3_finalize(stmt);
395 }
396
397 void LocalDb::SetSegmentChecksum(const std::string &segment,
398                                  const std::string &path,
399                                  const std::string &checksum,
400                                  int size)
401 {
402     int rc;
403     sqlite3_stmt *stmt;
404
405     stmt = Prepare("update segments set path = ?, checksum = ?, size = ?, "
406                    "mtime = coalesce(mtime, julianday('now')) "
407                    "where segmentid = ?");
408     sqlite3_bind_text(stmt, 1, path.c_str(), path.size(),
409                       SQLITE_TRANSIENT);
410     sqlite3_bind_text(stmt, 2, checksum.c_str(), checksum.size(),
411                       SQLITE_TRANSIENT);
412     sqlite3_bind_int64(stmt, 3, size);
413     sqlite3_bind_int64(stmt, 4, SegmentToId(segment));
414
415     rc = sqlite3_step(stmt);
416     if (rc != SQLITE_DONE) {
417         fprintf(stderr, "Could not update segment checksum in database!\n");
418         ReportError(rc);
419     }
420
421     sqlite3_finalize(stmt);
422 }
423
424 bool LocalDb::GetSegmentChecksum(const string &segment,
425                                  string *seg_path,
426                                  string *seg_checksum)
427 {
428     int rc;
429     sqlite3_stmt *stmt;
430     ObjectReference ref;
431     int found = false;
432
433     stmt = Prepare("select path, checksum from segments where segment = ?");
434     sqlite3_bind_text(stmt, 1, segment.c_str(), segment.size(),
435                       SQLITE_TRANSIENT);
436
437     rc = sqlite3_step(stmt);
438     if (rc == SQLITE_DONE) {
439     } else if (rc == SQLITE_ROW) {
440         found = true;
441         const char *val;
442
443         val = (const char *)sqlite3_column_text(stmt, 0);
444         if (val == NULL)
445             found = false;
446         else
447             *seg_path = val;
448
449         val = (const char *)sqlite3_column_text(stmt, 1);
450         if (val == NULL)
451             found = false;
452         else
453             *seg_checksum = val;
454     } else {
455         fprintf(stderr, "Could not execute SELECT statement!\n");
456         ReportError(rc);
457     }
458
459     sqlite3_finalize(stmt);
460
461     return found;
462 }