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