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