1 /* LBS: An LFS-inspired filesystem backup system
2 * Copyright (C) 2007 Michael Vrable
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.
16 #include "statcache.h"
22 using std::ostringstream;
24 static const size_t LBS_METADATA_BLOCK_SIZE = 65536;
26 /* TODO: Move to header file */
27 void add_segment(const string& segment);
29 /* Like strcmp, but sorts in the order that files will be visited in the
30 * filesystem. That is, we break paths apart at slashes, and compare path
31 * components separately. */
32 static int pathcmp(const char *path1, const char *path2)
34 /* Find the first component in each path. */
35 const char *slash1 = strchr(path1, '/');
36 const char *slash2 = strchr(path2, '/');
43 comp1 = string(path1, slash1 - path1);
48 comp2 = string(path2, slash2 - path2);
50 /* Directly compare the two components first. */
57 if (slash1 == NULL && slash2 == NULL)
64 return pathcmp(slash1 + 1, slash2 + 1);
67 MetadataWriter::MetadataWriter(TarSegmentStore *store,
69 const char *snapshot_name,
70 const char *snapshot_scheme)
72 statcache_path = path;
73 statcache_path += "/statcache2";
74 if (snapshot_scheme != NULL)
75 statcache_path = statcache_path + "-" + snapshot_scheme;
76 statcache_tmp_path = statcache_path + "." + snapshot_name;
78 statcache_in = fopen(statcache_path.c_str(), "r");
80 statcache_out = fopen(statcache_tmp_path.c_str(), "w");
81 if (statcache_out == NULL) {
82 fprintf(stderr, "Error opening statcache %s: %m\n",
83 statcache_tmp_path.c_str());
84 throw IOException("Error opening statcache");
87 old_metadata_eof = false;
93 /* Read the next entry from the old statcache file, loading it into
95 void MetadataWriter::read_statcache()
97 if (statcache_in == NULL) {
98 old_metadata_eof = true;
102 old_metadata.clear();
106 string field = ""; // Last field to be read in
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;
116 if (strchr(buf, '\n') != NULL)
117 *strchr(buf, '\n') = '\0';
118 old_metadata_loc = buf + 2;
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)
125 char *eol = strchr(buf, '\n');
129 /* Is the line blank? If so, we have reached the end of this entry. */
133 /* Is this a continuation line? (Does it start with whitespace?) */
134 if (isspace(buf[0]) && field != "") {
135 old_metadata[field] += string("\n") + buf;
139 /* For lines of the form "Key: Value" look for ':' and split the line
141 char *value = strchr(buf, ':');
148 while (isspace(*value))
151 old_metadata[field] = value;
154 if (feof(statcache_in) && old_metadata.size() == 0) {
155 old_metadata_eof = true;
161 bool MetadataWriter::find(const string& path)
163 const char *path_str = path.c_str();
164 while (!old_metadata_eof) {
165 string old_path = uri_decode(old_metadata["path"]);
166 int cmp = pathcmp(old_path.c_str(), path_str);
178 /* Ensure contents of metadata are flushed to an object. */
179 void MetadataWriter::metadata_flush()
183 ostringstream metadata;
184 ObjectReference indirect;
185 for (list<MetadataItem>::iterator i = items.begin();
186 i != items.end(); ++i) {
187 // Write out an indirect reference to any previous objects which could
189 if (!i->reused || !indirect.merge(i->ref)) {
190 if (!indirect.is_null()) {
191 string refstr = indirect.to_string();
192 metadata << "@" << refstr << "\n";
193 offset += refstr.size() + 2;
202 indirect = ObjectReference();
208 offset += i->text.size();
211 if (!indirect.is_null()) {
212 string refstr = indirect.to_string();
213 metadata << "@" << refstr << "\n";
214 offset += refstr.size() + 2;
215 indirect = ObjectReference();
218 string m = metadata.str();
222 /* Write current metadata information to a new object. */
223 LbsObject *meta = new LbsObject;
224 meta->set_group("metadata");
225 meta->set_data(m.data(), m.size());
229 /* Write a reference to this block in the root. */
230 ObjectReference ref = meta->get_ref();
231 metadata_root << "@" << ref.to_string() << "\n";
232 add_segment(ref.get_segment());
236 /* Write these files out to the statcache, and include a reference to where
237 * the metadata lives (so we can re-use it if it has not changed). */
238 for (list<MetadataItem>::const_iterator i = items.begin();
239 i != items.end(); ++i) {
240 ObjectReference r = ref;
241 r.set_range(i->offset, i->text.size());
246 string refstr = r.to_string();
247 fprintf(statcache_out, "@@%s\n%s", refstr.c_str(), i->text.c_str());
254 void MetadataWriter::add(dictionary info)
259 item.text += encode_dict(info) + "\n";
261 if (info == old_metadata) {
262 ObjectReference *ref = ObjectReference::parse(old_metadata_loc);
270 items.push_back(item);
271 chunk_size += item.text.size();
273 if (chunk_size > LBS_METADATA_BLOCK_SIZE)
277 ObjectReference MetadataWriter::close()
280 const string root_data = metadata_root.str();
282 LbsObject *root = new LbsObject;
283 root->set_group("metadata");
284 root->set_data(root_data.data(), root_data.size());
287 add_segment(root->get_ref().get_segment());
289 ObjectReference ref = root->get_ref();
292 fclose(statcache_out);
293 if (rename(statcache_tmp_path.c_str(), statcache_path.c_str()) < 0) {
294 fprintf(stderr, "Error renaming statcache from %s to %s: %m\n",
295 statcache_tmp_path.c_str(), statcache_path.c_str());