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