b90426b8df3b061231399d481cf67ec09fb25ec5
[cumulus.git] / tarstore.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 <stdio.h>
10 #include <sys/types.h>
11 #include <sys/stat.h>
12 #include <unistd.h>
13 #include <fcntl.h>
14 #include <time.h>
15 #include <uuid/uuid.h>
16
17 #include <string>
18 #include <iostream>
19
20 #include "tarstore.h"
21
22 using std::string;
23
24 Tarfile::Tarfile(const string &path, const string &segment)
25     : size(0),
26       segment_name(segment)
27 {
28     if (tar_open(&t, (char *)path.c_str(), NULL, O_WRONLY | O_CREAT, 0600,
29                  TAR_VERBOSE | TAR_GNU) == -1)
30         throw IOException("Error opening Tarfile");
31 }
32
33 Tarfile::~Tarfile()
34 {
35     string checksum_list = checksums.str();
36     internal_write_object(segment_name + "/checksums",
37                           checksum_list.data(), checksum_list.size());
38     tar_append_eof(t);
39
40     if (tar_close(t) != 0)
41         throw IOException("Error closing Tarfile");
42 }
43
44 void Tarfile::write_object(int id, const char *data, size_t len)
45 {
46     char buf[64];
47     sprintf(buf, "%08x", id);
48     string path = segment_name + "/" + buf;
49
50     internal_write_object(path, data, len);
51
52     // Compute a checksum for the data block, which will be stored at the end
53     // of the TAR file.
54     SHA1Checksum hash;
55     hash.process(data, len);
56     sprintf(buf, "%08x", id);
57     checksums << buf << " " << hash.checksum_str() << "\n";
58 }
59
60 void Tarfile::internal_write_object(const string &path,
61                                     const char *data, size_t len)
62 {
63     memset(&t->th_buf, 0, sizeof(struct tar_header));
64
65     th_set_type(t, S_IFREG | 0600);
66     th_set_user(t, 0);
67     th_set_group(t, 0);
68     th_set_mode(t, 0600);
69     th_set_size(t, len);
70     th_set_mtime(t, time(NULL));
71     th_set_path(t, const_cast<char *>(path.c_str()));
72     th_finish(t);
73
74     if (th_write(t) != 0)
75         throw IOException("Error writing tar header");
76
77     size += T_BLOCKSIZE;
78
79     if (len == 0)
80         return;
81
82     size_t blocks = (len + T_BLOCKSIZE - 1) / T_BLOCKSIZE;
83     size_t padding = blocks * T_BLOCKSIZE - len;
84
85     for (size_t i = 0; i < blocks - 1; i++) {
86         if (tar_block_write(t, &data[i * T_BLOCKSIZE]) == -1)
87             throw IOException("Error writing tar block");
88     }
89
90     char block[T_BLOCKSIZE];
91     memset(block, 0, sizeof(block));
92     memcpy(block, &data[T_BLOCKSIZE * (blocks - 1)], T_BLOCKSIZE - padding);
93     if (tar_block_write(t, block) == -1)
94         throw IOException("Error writing final tar block");
95
96     size += blocks * T_BLOCKSIZE;
97 }
98
99 static const size_t SEGMENT_SIZE = 4 * 1024 * 1024;
100
101 string TarSegmentStore::write_object(const char *data, size_t len, const
102                                      std::string &group)
103 {
104     struct segment_info *segment;
105
106     // Find the segment into which the object should be written, looking up by
107     // group.  If no segment exists yet, create one.
108     if (segments.find(group) == segments.end()) {
109         segment = new segment_info;
110
111         uint8_t uuid[16];
112         char uuid_buf[40];
113         uuid_generate(uuid);
114         uuid_unparse_lower(uuid, uuid_buf);
115         segment->name = uuid_buf;
116
117         string filename = path + "/" + segment->name + ".tar";
118         segment->file = new Tarfile(filename, segment->name);
119
120         segment->count = 0;
121
122         segments[group] = segment;
123     } else {
124         segment = segments[group];
125     }
126
127     int id = segment->count;
128     char id_buf[64];
129     sprintf(id_buf, "%08x", id);
130
131     segment->file->write_object(id, data, len);
132     segment->count++;
133
134     string full_name = segment->name + "/" + id_buf;
135
136     // If this segment meets or exceeds the size target, close it so that
137     // future objects will go into a new segment.
138     if (segment->file->size_estimate() >= SEGMENT_SIZE)
139         close_segment(group);
140
141     return full_name;
142 }
143
144 void TarSegmentStore::sync()
145 {
146     while (!segments.empty())
147         close_segment(segments.begin()->first);
148 }
149
150 void TarSegmentStore::close_segment(const string &group)
151 {
152     struct segment_info *segment = segments[group];
153     fprintf(stderr, "Closing segment group %s (%s)\n",
154             group.c_str(), segment->name.c_str());
155
156     delete segment->file;
157     segments.erase(segments.find(group));
158     delete segment;
159 }