Miscellaneous changes.
[cumulus.git] / store.cc
1 /* LBS: An LFS-inspired filesystem backup system
2  * Copyright (C) 2007  Michael Vrable
3  *
4  * Backup data is stored in a collection of objects, which are grouped together
5  * into segments for storage purposes.  This implementation of the object store
6  * is built on top of libtar, and represents segments as TAR files and objects
7  * as files within them. */
8
9 #include <assert.h>
10 #include <stdio.h>
11 #include <sys/types.h>
12 #include <sys/stat.h>
13 #include <unistd.h>
14 #include <fcntl.h>
15 #include <time.h>
16 #include <uuid/uuid.h>
17
18 #include <list>
19 #include <set>
20 #include <string>
21 #include <iostream>
22
23 #include "store.h"
24
25 using std::list;
26 using std::set;
27 using std::string;
28
29 list<string> TarSegmentStore::norefs;
30
31 Tarfile::Tarfile(const string &path, const string &segment)
32     : size(0),
33       segment_name(segment)
34 {
35     if (tar_open(&t, (char *)path.c_str(), NULL, O_WRONLY | O_CREAT, 0600,
36                  TAR_VERBOSE | TAR_GNU) == -1)
37         throw IOException("Error opening Tarfile");
38 }
39
40 Tarfile::~Tarfile()
41 {
42     string checksum_list = checksums.str();
43     internal_write_object(segment_name + "/checksums",
44                           checksum_list.data(), checksum_list.size());
45     tar_append_eof(t);
46
47     if (tar_close(t) != 0)
48         throw IOException("Error closing Tarfile");
49 }
50
51 void Tarfile::write_object(int id, const char *data, size_t len)
52 {
53     char buf[64];
54     sprintf(buf, "%08x", id);
55     string path = segment_name + "/" + buf;
56
57     internal_write_object(path, data, len);
58
59     // Compute a checksum for the data block, which will be stored at the end
60     // of the TAR file.
61     SHA1Checksum hash;
62     hash.process(data, len);
63     sprintf(buf, "%08x", id);
64     checksums << buf << " " << hash.checksum_str() << "\n";
65 }
66
67 void Tarfile::internal_write_object(const string &path,
68                                     const char *data, size_t len)
69 {
70     memset(&t->th_buf, 0, sizeof(struct tar_header));
71
72     th_set_type(t, S_IFREG | 0600);
73     th_set_user(t, 0);
74     th_set_group(t, 0);
75     th_set_mode(t, 0600);
76     th_set_size(t, len);
77     th_set_mtime(t, time(NULL));
78     th_set_path(t, const_cast<char *>(path.c_str()));
79     th_finish(t);
80
81     if (th_write(t) != 0)
82         throw IOException("Error writing tar header");
83
84     size += T_BLOCKSIZE;
85
86     if (len == 0)
87         return;
88
89     size_t blocks = (len + T_BLOCKSIZE - 1) / T_BLOCKSIZE;
90     size_t padding = blocks * T_BLOCKSIZE - len;
91
92     for (size_t i = 0; i < blocks - 1; i++) {
93         if (tar_block_write(t, &data[i * T_BLOCKSIZE]) == -1)
94             throw IOException("Error writing tar block");
95     }
96
97     char block[T_BLOCKSIZE];
98     memset(block, 0, sizeof(block));
99     memcpy(block, &data[T_BLOCKSIZE * (blocks - 1)], T_BLOCKSIZE - padding);
100     if (tar_block_write(t, block) == -1)
101         throw IOException("Error writing final tar block");
102
103     size += blocks * T_BLOCKSIZE;
104 }
105
106 static const size_t SEGMENT_SIZE = 4 * 1024 * 1024;
107
108 string TarSegmentStore::write_object(const char *data, size_t len, const
109                                      std::string &group,
110                                      const std::list<std::string> &refs)
111 {
112     struct segment_info *segment;
113
114     // Find the segment into which the object should be written, looking up by
115     // group.  If no segment exists yet, create one.
116     if (segments.find(group) == segments.end()) {
117         segment = new segment_info;
118
119         uint8_t uuid[16];
120         char uuid_buf[40];
121         uuid_generate(uuid);
122         uuid_unparse_lower(uuid, uuid_buf);
123         segment->name = uuid_buf;
124
125         string filename = path + "/" + segment->name + ".tar";
126         segment->file = new Tarfile(filename, segment->name);
127
128         segment->count = 0;
129
130         segments[group] = segment;
131     } else {
132         segment = segments[group];
133     }
134
135     int id = segment->count;
136     char id_buf[64];
137     sprintf(id_buf, "%08x", id);
138
139     segment->file->write_object(id, data, len);
140     segment->count++;
141
142     string full_name = segment->name + "/" + id_buf;
143
144     // Store any dependencies this object has on other segments, so they can be
145     // written when the segment is closed.
146     for (list<string>::const_iterator i = refs.begin(); i != refs.end(); ++i) {
147         segment->refs.insert(*i);
148     }
149
150     // If this segment meets or exceeds the size target, close it so that
151     // future objects will go into a new segment.
152     if (segment->file->size_estimate() >= SEGMENT_SIZE)
153         close_segment(group);
154
155     return full_name;
156 }
157
158 void TarSegmentStore::sync()
159 {
160     while (!segments.empty())
161         close_segment(segments.begin()->first);
162 }
163
164 void TarSegmentStore::close_segment(const string &group)
165 {
166     struct segment_info *segment = segments[group];
167     fprintf(stderr, "Closing segment group %s (%s)\n",
168             group.c_str(), segment->name.c_str());
169
170     string reflist;
171     for (set<string>::iterator i = segment->refs.begin();
172          i != segment->refs.end(); ++i) {
173         reflist += *i + "\n";
174     }
175     segment->file->internal_write_object(segment->name + "/references",
176                                          reflist.data(), reflist.size());
177
178     delete segment->file;
179     segments.erase(segments.find(group));
180     delete segment;
181 }
182
183 string TarSegmentStore::object_reference_to_segment(const string &object)
184 {
185     return object;
186 }
187
188 LbsObject::LbsObject()
189     : group(""), data(NULL), data_len(0), written(false)
190 {
191 }
192
193 LbsObject::~LbsObject()
194 {
195 }
196
197 void LbsObject::add_reference(const LbsObject *o)
198 {
199     refs.insert(o->get_name());
200 }
201
202 void LbsObject::write(TarSegmentStore *store)
203 {
204     assert(data != NULL);
205     assert(!written);
206
207     list<string> reflist;
208     for (set<string>::iterator i = refs.begin(); i != refs.end(); ++i) {
209         reflist.push_back(*i);
210     }
211
212     name = store->write_object(data, data_len, group, reflist);
213
214     written = true;
215     data = NULL;
216 }