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