Add format support for efficient sparse file handling.
[cumulus.git] / ref.cc
1 /* LBS: An LFS-inspired filesystem backup system
2  * Copyright (C) 2007  Michael Vrable
3  *
4  * Backups are structured as a collection of objects, which may refer to other
5  * objects.  Object references are used to name other objects or parts of them.
6  * This file defines the class for representing object references and the
7  * textual representation of these references. */
8
9 #include <assert.h>
10 #include <stdio.h>
11 #include <uuid/uuid.h>
12
13 #include <string>
14
15 #include "ref.h"
16
17 using std::string;
18
19 /* Generate a new UUID, and return the text representation of it.  This is
20  * suitable for generating the name for a new segment. */
21 string generate_uuid()
22 {
23     uint8_t uuid[16];
24     char buf[40];
25
26     uuid_generate(uuid);
27     uuid_unparse_lower(uuid, buf);
28     return string(buf);
29 }
30
31 ObjectReference::ObjectReference()
32     : type(REF_NULL), segment(""), object("")
33 {
34     clear_checksum();
35     clear_range();
36 }
37
38 ObjectReference::ObjectReference(RefType t)
39     : type(t), segment(""), object("")
40 {
41     clear_checksum();
42     clear_range();
43 }
44
45 ObjectReference::ObjectReference(const std::string& segment, int sequence)
46     : type(REF_NORMAL), segment(segment)
47 {
48     char seq_buf[64];
49     sprintf(seq_buf, "%08x", sequence);
50     object = seq_buf;
51
52     clear_checksum();
53     clear_range();
54 }
55
56 ObjectReference::ObjectReference(const std::string& segment,
57                                  const std::string& sequence)
58     : type(REF_NORMAL), segment(segment), object(sequence)
59 {
60     clear_checksum();
61     clear_range();
62 }
63
64 string ObjectReference::to_string() const
65 {
66     if (type == REF_NULL)
67         return "null";
68
69     string result;
70     if (type == REF_ZERO) {
71         result = "zero";
72     } else if (type == REF_NORMAL) {
73         result = segment + "/" + object;
74
75         if (checksum_valid)
76             result += "(" + checksum + ")";
77     }
78
79     if (range_valid) {
80         char buf[64];
81         sprintf(buf, "[%zu+%zu]", range_start, range_length);
82         result += buf;
83     }
84
85     return result;
86 }
87
88 /* Parse a string object reference and return a pointer to a new
89  * ObjectReference.  The caller is responsible for freeing the object.  NULL is
90  * returned if there is an error in the syntax. */
91 ObjectReference ObjectReference::parse(const std::string& str)
92 {
93     const char *s = str.c_str();
94     const char *t;
95     ObjectReference::RefType type = ObjectReference::REF_NORMAL;
96
97     // Special case: explicit zero objects
98     if (strncmp(s, "zero", 4) == 0) {
99         type = ObjectReference::REF_ZERO;
100         s += 4;
101     }
102
103     // Segment
104     t = s;
105     if (type == ObjectReference::REF_NORMAL) {
106         while ((*t >= '0' && *t <= '9') || (*t >= 'a' && *t <= 'f')
107                || (*t == '-'))
108             t++;
109         if (*t != '/')
110             return ObjectReference();
111     }
112     string segment(s, t - s);
113
114     // Object sequence number
115     if (type == ObjectReference::REF_NORMAL) {
116         t++;
117         s = t;
118         while ((*t >= '0' && *t <= '9') || (*t >= 'a' && *t <= 'f'))
119             t++;
120         if (*t != '\0' && *t != '(' && *t != '[')
121             return ObjectReference();
122     }
123     string object(s, t - s);
124
125     // Checksum
126     string checksum;
127     if (*t == '(') {
128         t++;
129         s = t;
130         while (*t != ')' && *t != '\0')
131             t++;
132         if (*t != ')')
133             return ObjectReference();
134         checksum = string(s, t - s);
135         t++;
136     }
137
138     // Range
139     bool have_range = false;
140     int64_t range1 = 0, range2 = 0;
141     if (*t == '[') {
142         t++;
143         s = t;
144         while (*t >= '0' && *t <= '9')
145             t++;
146         if (*t != '+')
147             return ObjectReference();
148
149         string val(s, t - s);
150         range1 = atoll(val.c_str());
151
152         t++;
153         s = t;
154         while (*t >= '0' && *t <= '9')
155             t++;
156         if (*t != ']')
157             return ObjectReference();
158
159         val = string(s, t - s);
160         range2 = atoll(val.c_str());
161
162         have_range = true;
163     }
164
165     ObjectReference ref;
166     switch (type) {
167     case ObjectReference::REF_ZERO:
168         ref = ObjectReference(ObjectReference::REF_ZERO);
169         break;
170     case ObjectReference::REF_NORMAL:
171         ref = ObjectReference(segment, object);
172         break;
173     default:
174         return ObjectReference();
175     }
176
177     if (checksum.size() > 0)
178         ref.set_checksum(checksum);
179
180     if (have_range)
181         ref.set_range(range1, range2);
182
183     return ref;
184 }
185
186 /* Attempt to merge a new object reference into the current one.  Returns a
187  * boolean indicating success; if successful this reference is modified so that
188  * it refers to the range of bytes originally covered by this reference plus
189  * the reference passed in.  Merging only succeeds if both references refer to
190  * the same object and the byte ranges are contiguous. */
191 bool ObjectReference::merge(ObjectReference ref)
192 {
193     // Exception: We can always merge into a null object
194     if (is_null()) {
195         *this = ref;
196         return true;
197     }
198
199     if (segment != ref.segment)
200         return false;
201     if (object != ref.object)
202         return false;
203
204     // TODO: Allow the case where only one checksum was filled in
205     if (checksum_valid != ref.checksum_valid || checksum != ref.checksum)
206         return false;
207
208     if (!range_valid || !ref.range_valid)
209         return false;
210
211     if (range_start + range_length == ref.range_start) {
212         range_length += ref.range_length;
213         return true;
214     } else {
215         return false;
216     }
217 }