Ensure the "name:" key shows up first in metadata output.
[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     found_match = false;
110     old_metadata_eof = false;
111
112     this->store = store;
113     chunk_size = 0;
114 }
115
116 /* Read the next entry from the old statcache file, loading it into
117  * old_metadata. */
118 void MetadataWriter::read_statcache()
119 {
120     if (statcache_in == NULL) {
121         old_metadata_eof = true;
122         return;
123     }
124
125     old_metadata.clear();
126
127     char *buf = NULL;
128     size_t n = 0;
129     string field = "";          // Last field to be read in
130
131     /* Look for a first line starting with "@@", which tells where the metadata
132      * can be found in the metadata log of an old snapshot. */
133     if (getline(&buf, &n, statcache_in) < 0
134         || buf == NULL || buf[0] != '@' || buf[1] != '@') {
135         old_metadata_eof = true;
136         return;
137     }
138
139     if (strchr(buf, '\n') != NULL)
140         *strchr(buf, '\n') = '\0';
141     old_metadata_loc = buf + 2;
142
143     /* After the initial line follows the metadata, as key-value pairs. */
144     while (!feof(statcache_in)) {
145         if (getline(&buf, &n, statcache_in) < 0)
146             break;
147
148         char *eol = strchr(buf, '\n');
149         if (eol != NULL)
150             *eol = '\0';
151
152         /* Is the line blank?  If so, we have reached the end of this entry. */
153         if (buf[0] == '\0')
154             break;
155
156         /* Is this a continuation line?  (Does it start with whitespace?) */
157         if (isspace(buf[0]) && field != "") {
158             old_metadata[field] += string("\n") + buf;
159             continue;
160         }
161
162         /* For lines of the form "Key: Value" look for ':' and split the line
163          * apart. */
164         char *value = strchr(buf, ':');
165         if (value == NULL)
166             continue;
167         *value = '\0';
168         field = buf;
169
170         value++;
171         while (isspace(*value))
172             value++;
173
174         old_metadata[field] = value;
175     }
176
177     if (feof(statcache_in) && old_metadata.size() == 0) {
178         old_metadata_eof = true;
179     }
180
181     free(buf);
182 }
183
184 bool MetadataWriter::find(const string& path)
185 {
186     const char *path_str = path.c_str();
187     while (!old_metadata_eof) {
188         string old_path = uri_decode(old_metadata["name"]);
189         int cmp = pathcmp(old_path.c_str(), path_str);
190         if (cmp == 0) {
191             found_match = true;
192             return true;
193         } else if (cmp > 0) {
194             found_match = false;
195             return false;
196         } else {
197             read_statcache();
198         }
199     }
200
201     found_match = false;
202     return false;
203 }
204
205 /* Does a file appear to be unchanged from the previous time it was backed up,
206  * based on stat information?
207  *
208  * TODO: Notice files that were modified as they were being backed up the last
209  * time. */
210 bool MetadataWriter::is_unchanged(const struct stat *stat_buf)
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 != NULL) {
261             blocks.push_back(*r);
262             delete r;
263         }
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         // Write out an indirect reference to any previous objects which could
279         // be reused
280         if (!i->reused || !indirect.merge(i->ref)) {
281             if (!indirect.is_null()) {
282                 string refstr = indirect.to_string();
283                 metadata << "@" << refstr << "\n";
284                 offset += refstr.size() + 2;
285                 if (!i->reused) {
286                     metadata << "\n";
287                     offset += 1;
288                 }
289             }
290             if (i->reused)
291                 indirect = i->ref;
292             else
293                 indirect = ObjectReference();
294         }
295
296         if (!i->reused) {
297             metadata << i->text;
298             i->offset = offset;
299             offset += i->text.size();
300         }
301     }
302     if (!indirect.is_null()) {
303         string refstr = indirect.to_string();
304         metadata << "@" << refstr << "\n";
305         offset += refstr.size() + 2;
306         indirect = ObjectReference();
307     }
308
309     string m = metadata.str();
310     if (m.size() == 0)
311         return;
312
313     /* Write current metadata information to a new object. */
314     LbsObject *meta = new LbsObject;
315     meta->set_group("metadata");
316     meta->set_data(m.data(), m.size());
317     meta->write(store);
318     meta->checksum();
319
320     /* Write a reference to this block in the root. */
321     ObjectReference ref = meta->get_ref();
322     metadata_root << "@" << ref.to_string() << "\n";
323     add_segment(ref.get_segment());
324
325     delete meta;
326
327     /* Write these files out to the statcache, and include a reference to where
328      * the metadata lives (so we can re-use it if it has not changed). */
329     for (list<MetadataItem>::const_iterator i = items.begin();
330          i != items.end(); ++i) {
331         ObjectReference r = ref;
332         r.set_range(i->offset, i->text.size());
333
334         if (i->reused)
335             r = i->ref;
336
337         string refstr = r.to_string();
338         fprintf(statcache_out, "@@%s\n%s", refstr.c_str(), i->text.c_str());
339     }
340
341     chunk_size = 0;
342     items.clear();
343 }
344
345 void MetadataWriter::add(dictionary info)
346 {
347     MetadataItem item;
348     item.offset = 0;
349     item.reused = false;
350     item.text += encode_dict(info) + "\n";
351
352     if (info == old_metadata) {
353         ObjectReference *ref = ObjectReference::parse(old_metadata_loc);
354         if (ref != NULL) {
355             item.reused = true;
356             item.ref = *ref;
357             delete ref;
358         }
359     }
360
361     items.push_back(item);
362     chunk_size += item.text.size();
363
364     if (chunk_size > LBS_METADATA_BLOCK_SIZE)
365         metadata_flush();
366 }
367
368 ObjectReference MetadataWriter::close()
369 {
370     metadata_flush();
371     const string root_data = metadata_root.str();
372
373     LbsObject *root = new LbsObject;
374     root->set_group("metadata");
375     root->set_data(root_data.data(), root_data.size());
376     root->write(store);
377     root->checksum();
378     add_segment(root->get_ref().get_segment());
379
380     ObjectReference ref = root->get_ref();
381     delete root;
382
383     fclose(statcache_out);
384     if (rename(statcache_tmp_path.c_str(), statcache_path.c_str()) < 0) {
385         fprintf(stderr, "Error renaming statcache from %s to %s: %m\n",
386                 statcache_tmp_path.c_str(), statcache_path.c_str());
387     }
388
389     return ref;
390 }