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