Extend tracking of used segments to cover metadata segments.
[cumulus.git] / metadata.cc
1 /* LBS: An LFS-inspired filesystem backup system
2  * Copyright (C) 2007  Michael Vrable
3  *
4  * Handling of metadata written to backup snapshots.  This manages the writing
5  * of file metadata into new backup snapshots, including breaking the metadata
6  * log apart across separate objects.  Eventually this should include unified
7  * handling of the statcache, and re-use of metadata between snapshots.
8  */
9
10 #include <string>
11 #include <iostream>
12 #include <map>
13
14 #include "metadata.h"
15 #include "localdb.h"
16 #include "ref.h"
17 #include "store.h"
18 #include "util.h"
19
20 using std::list;
21 using std::map;
22 using std::string;
23 using std::ostream;
24 using std::ostringstream;
25
26 static const size_t LBS_METADATA_BLOCK_SIZE = 65536;
27
28 // If true, forces a full write of metadata: will not include pointers to
29 // metadata in old snapshots.
30 bool flag_full_metadata = false;
31
32 /* TODO: Move to header file */
33 extern LocalDb *db;
34 void add_segment(const string& segment);
35
36 /* Like strcmp, but sorts in the order that files will be visited in the
37  * filesystem.  That is, we break paths apart at slashes, and compare path
38  * components separately. */
39 static int pathcmp(const char *path1, const char *path2)
40 {
41     /* Find the first component in each path. */
42     const char *slash1 = strchr(path1, '/');
43     const char *slash2 = strchr(path2, '/');
44
45     {
46         string comp1, comp2;
47         if (slash1 == NULL)
48             comp1 = path1;
49         else
50             comp1 = string(path1, slash1 - path1);
51
52         if (slash2 == NULL)
53             comp2 = path2;
54         else
55             comp2 = string(path2, slash2 - path2);
56
57         /* Directly compare the two components first. */
58         if (comp1 < comp2)
59             return -1;
60         if (comp1 > comp2)
61             return 1;
62     }
63
64     if (slash1 == NULL && slash2 == NULL)
65         return 0;
66     if (slash1 == NULL)
67         return -1;
68     if (slash2 == NULL)
69         return 1;
70
71     return pathcmp(slash1 + 1, slash2 + 1);
72 }
73
74 /* Encode a dictionary of string key/value pairs into a sequence of lines of
75  * the form "key: value".  If it exists, the key "name" is treated specially
76  * and will be listed first. */
77 static string encode_dict(const map<string, string>& dict)
78 {
79     string result;
80
81     if (dict.find("name") != dict.end()) {
82         result += "name: " + dict.at("name") + "\n";
83     }
84
85     for (map<string, string>::const_iterator i = dict.begin();
86          i != dict.end(); ++i) {
87         if (i->first == "name")
88             continue;
89         result += i->first + ": " + i->second + "\n";
90     }
91
92     return result;
93 }
94
95 MetadataWriter::MetadataWriter(TarSegmentStore *store,
96                                const char *path,
97                                const char *snapshot_name,
98                                const char *snapshot_scheme)
99 {
100     statcache_path = path;
101     statcache_path += "/statcache2";
102     if (snapshot_scheme != NULL)
103         statcache_path = statcache_path + "-" + snapshot_scheme;
104     statcache_tmp_path = statcache_path + "." + snapshot_name;
105
106     statcache_in = fopen(statcache_path.c_str(), "r");
107
108     statcache_out = fopen(statcache_tmp_path.c_str(), "w");
109     if (statcache_out == NULL) {
110         fprintf(stderr, "Error opening statcache %s: %m\n",
111                 statcache_tmp_path.c_str());
112         throw IOException("Error opening statcache");
113     }
114
115     old_metadata_eof = false;
116
117     this->store = store;
118     chunk_size = 0;
119 }
120
121 /* Read the next entry from the old statcache file, loading it into
122  * old_metadata. */
123 void MetadataWriter::read_statcache()
124 {
125     if (statcache_in == NULL) {
126         old_metadata_eof = true;
127         return;
128     }
129
130     old_metadata.clear();
131
132     char *buf = NULL;
133     size_t n = 0;
134     string field = "";          // Last field to be read in
135
136     /* Look for a first line starting with "@@", which tells where the metadata
137      * can be found in the metadata log of an old snapshot. */
138     if (getline(&buf, &n, statcache_in) < 0
139         || buf == NULL || buf[0] != '@' || buf[1] != '@') {
140         old_metadata_eof = true;
141         return;
142     }
143
144     if (strchr(buf, '\n') != NULL)
145         *strchr(buf, '\n') = '\0';
146     old_metadata_loc = buf + 2;
147
148     /* After the initial line follows the metadata, as key-value pairs. */
149     while (!feof(statcache_in)) {
150         if (getline(&buf, &n, statcache_in) < 0)
151             break;
152
153         char *eol = strchr(buf, '\n');
154         if (eol != NULL)
155             *eol = '\0';
156
157         /* Is the line blank?  If so, we have reached the end of this entry. */
158         if (buf[0] == '\0')
159             break;
160
161         /* Is this a continuation line?  (Does it start with whitespace?) */
162         if (isspace(buf[0]) && field != "") {
163             old_metadata[field] += string("\n") + buf;
164             continue;
165         }
166
167         /* For lines of the form "Key: Value" look for ':' and split the line
168          * apart. */
169         char *value = strchr(buf, ':');
170         if (value == NULL)
171             continue;
172         *value = '\0';
173         field = buf;
174
175         value++;
176         while (isspace(*value))
177             value++;
178
179         old_metadata[field] = value;
180     }
181
182     if (feof(statcache_in) && old_metadata.size() == 0) {
183         old_metadata_eof = true;
184     }
185
186     free(buf);
187 }
188
189 bool MetadataWriter::find(const string& path)
190 {
191     const char *path_str = path.c_str();
192     while (!old_metadata_eof) {
193         string old_path = uri_decode(old_metadata["name"]);
194         int cmp = pathcmp(old_path.c_str(), path_str);
195         if (cmp == 0)
196             return true;
197         else if (cmp > 0)
198             return false;
199         else
200             read_statcache();
201     }
202
203     return false;
204 }
205
206 /* Does a file appear to be unchanged from the previous time it was backed up,
207  * based on stat information? */
208 bool MetadataWriter::is_unchanged(const struct stat *stat_buf)
209 {
210     if (old_metadata.find("volatile") != old_metadata.end()
211         && parse_int(old_metadata["volatile"]) != 0)
212         return false;
213
214     if (old_metadata.find("ctime") == old_metadata.end())
215         return false;
216     if (stat_buf->st_ctime != parse_int(old_metadata["ctime"]))
217         return false;
218
219     if (old_metadata.find("mtime") == old_metadata.end())
220         return false;
221     if (stat_buf->st_mtime != parse_int(old_metadata["mtime"]))
222         return false;
223
224     if (old_metadata.find("size") == old_metadata.end())
225         return false;
226     if (stat_buf->st_size != parse_int(old_metadata["size"]))
227         return false;
228
229     if (old_metadata.find("inode") == old_metadata.end())
230         return false;
231     string inode = encode_int(major(stat_buf->st_dev))
232         + "/" + encode_int(minor(stat_buf->st_dev))
233         + "/" + encode_int(stat_buf->st_ino);
234     if (inode != old_metadata["inode"])
235         return false;
236
237     return true;
238 }
239
240 list<ObjectReference> MetadataWriter::get_blocks()
241 {
242     list<ObjectReference> blocks;
243
244     /* Parse the list of blocks. */
245     const char *s = old_metadata["data"].c_str();
246     while (*s != '\0') {
247         if (isspace(*s)) {
248             s++;
249             continue;
250         }
251
252         string ref = "";
253         while (*s != '\0' && !isspace(*s)) {
254             char buf[2];
255             buf[0] = *s;
256             buf[1] = '\0';
257             ref += buf;
258             s++;
259         }
260
261         ObjectReference r = ObjectReference::parse(ref);
262         if (!r.is_null())
263             blocks.push_back(r);
264     }
265
266     return blocks;
267 }
268
269 /* Ensure contents of metadata are flushed to an object. */
270 void MetadataWriter::metadata_flush()
271 {
272     int offset = 0;
273
274     ostringstream metadata;
275     ObjectReference indirect;
276     for (list<MetadataItem>::iterator i = items.begin();
277          i != items.end(); ++i) {
278         // If indirectly referencing any other metadata logs, be sure those
279         // segments are properly referenced.
280         if (i->reused) {
281             add_segment(i->ref.get_segment());
282             db->UseSegment(i->ref.get_segment(), 1.0);
283         }
284
285         // Write out an indirect reference to any previous objects which could
286         // be reused
287         if (!i->reused || !indirect.merge(i->ref)) {
288             if (!indirect.is_null()) {
289                 string refstr = indirect.to_string();
290                 metadata << "@" << refstr << "\n";
291                 offset += refstr.size() + 2;
292                 if (!i->reused) {
293                     metadata << "\n";
294                     offset += 1;
295                 }
296             }
297             if (i->reused)
298                 indirect = i->ref;
299             else
300                 indirect = ObjectReference();
301         }
302
303         if (!i->reused) {
304             metadata << i->text;
305             i->offset = offset;
306             offset += i->text.size();
307         }
308     }
309     if (!indirect.is_null()) {
310         string refstr = indirect.to_string();
311         metadata << "@" << refstr << "\n";
312         offset += refstr.size() + 2;
313         indirect = ObjectReference();
314     }
315
316     string m = metadata.str();
317     if (m.size() == 0)
318         return;
319
320     /* Write current metadata information to a new object. */
321     LbsObject *meta = new LbsObject;
322     meta->set_group("metadata");
323     meta->set_data(m.data(), m.size());
324     meta->write(store);
325     meta->checksum();
326
327     /* Write a reference to this block in the root. */
328     ObjectReference ref = meta->get_ref();
329     metadata_root << "@" << ref.to_string() << "\n";
330     add_segment(ref.get_segment());
331     db->UseSegment(ref.get_segment(), 1.0);
332
333     delete meta;
334
335     /* Write these files out to the statcache, and include a reference to where
336      * the metadata lives (so we can re-use it if it has not changed). */
337     for (list<MetadataItem>::const_iterator i = items.begin();
338          i != items.end(); ++i) {
339         ObjectReference r = ref;
340         r.set_range(i->offset, i->text.size());
341
342         if (i->reused)
343             r = i->ref;
344
345         string refstr = r.to_string();
346         fprintf(statcache_out, "@@%s\n%s", refstr.c_str(), i->text.c_str());
347     }
348
349     chunk_size = 0;
350     items.clear();
351 }
352
353 void MetadataWriter::add(dictionary info)
354 {
355     MetadataItem item;
356     item.offset = 0;
357     item.reused = false;
358     item.text += encode_dict(info) + "\n";
359
360     if (info == old_metadata && !flag_full_metadata) {
361         ObjectReference ref = ObjectReference::parse(old_metadata_loc);
362         if (!ref.is_null()) {
363             item.reused = true;
364             item.ref = ref;
365         }
366     }
367
368     items.push_back(item);
369     chunk_size += item.text.size();
370
371     if (chunk_size > LBS_METADATA_BLOCK_SIZE)
372         metadata_flush();
373 }
374
375 ObjectReference MetadataWriter::close()
376 {
377     metadata_flush();
378     const string root_data = metadata_root.str();
379
380     LbsObject *root = new LbsObject;
381     root->set_group("metadata");
382     root->set_data(root_data.data(), root_data.size());
383     root->write(store);
384     root->checksum();
385     add_segment(root->get_ref().get_segment());
386     db->UseSegment(root->get_ref().get_segment(), 1.0);
387
388     ObjectReference ref = root->get_ref();
389     delete root;
390
391     fclose(statcache_out);
392     if (rename(statcache_tmp_path.c_str(), statcache_path.c_str()) < 0) {
393         fprintf(stderr, "Error renaming statcache from %s to %s: %m\n",
394                 statcache_tmp_path.c_str(), statcache_path.c_str());
395     }
396
397     return ref;
398 }