1 |
|
|
/** |
2 |
|
|
* This file is part of the CernVM File System. |
3 |
|
|
*/ |
4 |
|
|
|
5 |
|
|
#include "manifest.h" |
6 |
|
|
|
7 |
|
|
#include <cstdio> |
8 |
|
|
#include <map> |
9 |
|
|
|
10 |
|
|
#include "catalog.h" |
11 |
|
|
#include "util/posix.h" |
12 |
|
|
|
13 |
|
|
using namespace std; // NOLINT |
14 |
|
|
|
15 |
|
|
namespace manifest { |
16 |
|
|
|
17 |
|
36 |
Manifest *Manifest::LoadMem(const unsigned char *buffer, |
18 |
|
|
const unsigned length) |
19 |
|
|
{ |
20 |
|
36 |
map<char, string> content; |
21 |
|
36 |
ParseKeyvalMem(buffer, length, &content); |
22 |
|
|
|
23 |
|
36 |
return Load(content); |
24 |
|
|
} |
25 |
|
|
|
26 |
|
|
|
27 |
|
8 |
Manifest *Manifest::LoadFile(const std::string &from_path) { |
28 |
|
8 |
map<char, string> content; |
29 |
✗✓ |
8 |
if (!ParseKeyvalPath(from_path, &content)) |
30 |
|
|
return NULL; |
31 |
|
|
|
32 |
|
8 |
return Load(content); |
33 |
|
|
} |
34 |
|
|
|
35 |
|
|
|
36 |
|
44 |
Manifest *Manifest::Load(const map<char, string> &content) { |
37 |
|
44 |
map<char, string>::const_iterator iter; |
38 |
|
|
|
39 |
|
|
// Required keys |
40 |
|
44 |
shash::Any catalog_hash; |
41 |
|
44 |
shash::Md5 root_path; |
42 |
|
|
uint32_t ttl; |
43 |
|
|
uint64_t revision; |
44 |
|
|
|
45 |
|
44 |
iter = content.find('C'); |
46 |
✗✓ |
44 |
if ((iter = content.find('C')) == content.end()) |
47 |
|
|
return NULL; |
48 |
|
|
catalog_hash = MkFromHexPtr(shash::HexPtr(iter->second), |
49 |
✗✓ |
44 |
shash::kSuffixCatalog); |
50 |
✗✓ |
44 |
if ((iter = content.find('R')) == content.end()) |
51 |
|
|
return NULL; |
52 |
✗✓ |
44 |
root_path = shash::Md5(shash::HexPtr(iter->second)); |
53 |
✗✓ |
44 |
if ((iter = content.find('D')) == content.end()) |
54 |
|
|
return NULL; |
55 |
|
44 |
ttl = String2Uint64(iter->second); |
56 |
✗✓ |
44 |
if ((iter = content.find('S')) == content.end()) |
57 |
|
|
return NULL; |
58 |
|
44 |
revision = String2Uint64(iter->second); |
59 |
|
|
|
60 |
|
|
|
61 |
|
|
// Optional keys |
62 |
|
44 |
uint64_t catalog_size = 0; |
63 |
|
44 |
shash::Any micro_catalog_hash; |
64 |
|
44 |
string repository_name; |
65 |
|
44 |
shash::Any certificate; |
66 |
|
44 |
shash::Any history; |
67 |
|
44 |
uint64_t publish_timestamp = 0; |
68 |
|
44 |
bool garbage_collectable = false; |
69 |
|
44 |
bool has_alt_catalog_path = false; |
70 |
|
44 |
shash::Any meta_info; |
71 |
|
|
|
72 |
✓✗ |
44 |
if ((iter = content.find('B')) != content.end()) |
73 |
|
44 |
catalog_size = String2Uint64(iter->second); |
74 |
✗✓ |
44 |
if ((iter = content.find('L')) != content.end()) |
75 |
|
|
micro_catalog_hash = MkFromHexPtr(shash::HexPtr(iter->second), |
76 |
|
|
shash::kSuffixMicroCatalog); |
77 |
✓✗ |
44 |
if ((iter = content.find('N')) != content.end()) |
78 |
|
44 |
repository_name = iter->second; |
79 |
✓✗ |
44 |
if ((iter = content.find('X')) != content.end()) |
80 |
|
|
certificate = MkFromHexPtr(shash::HexPtr(iter->second), |
81 |
✗✓ |
44 |
shash::kSuffixCertificate); |
82 |
✓✗ |
44 |
if ((iter = content.find('H')) != content.end()) |
83 |
|
|
history = MkFromHexPtr(shash::HexPtr(iter->second), |
84 |
✗✓ |
44 |
shash::kSuffixHistory); |
85 |
✓✓ |
44 |
if ((iter = content.find('T')) != content.end()) |
86 |
|
30 |
publish_timestamp = String2Uint64(iter->second); |
87 |
✓✗ |
44 |
if ((iter = content.find('G')) != content.end()) |
88 |
|
44 |
garbage_collectable = (iter->second == "yes"); |
89 |
✓✗ |
44 |
if ((iter = content.find('A')) != content.end()) |
90 |
|
44 |
has_alt_catalog_path = (iter->second == "yes"); |
91 |
✗✓ |
44 |
if ((iter = content.find('M')) != content.end()) |
92 |
|
|
meta_info = MkFromHexPtr(shash::HexPtr(iter->second), |
93 |
|
|
shash::kSuffixMetainfo); |
94 |
|
|
|
95 |
|
|
return new Manifest(catalog_hash, catalog_size, root_path, ttl, revision, |
96 |
|
|
micro_catalog_hash, repository_name, certificate, |
97 |
|
|
history, publish_timestamp, garbage_collectable, |
98 |
|
44 |
has_alt_catalog_path, meta_info); |
99 |
|
|
} |
100 |
|
|
|
101 |
|
|
|
102 |
|
195 |
Manifest::Manifest(const shash::Any &catalog_hash, |
103 |
|
|
const uint64_t catalog_size, |
104 |
|
|
const string &root_path) |
105 |
|
|
: catalog_hash_(catalog_hash) |
106 |
|
|
, catalog_size_(catalog_size) |
107 |
|
|
, root_path_(shash::Md5(shash::AsciiPtr(root_path))) |
108 |
|
|
, ttl_(catalog::Catalog::kDefaultTTL) |
109 |
|
|
, revision_(0) |
110 |
|
|
, publish_timestamp_(0) |
111 |
|
|
, garbage_collectable_(false) |
112 |
|
195 |
, has_alt_catalog_path_(false) |
113 |
|
|
{ } |
114 |
|
|
|
115 |
|
|
|
116 |
|
|
/** |
117 |
|
|
* Creates the manifest string |
118 |
|
|
*/ |
119 |
|
41 |
string Manifest::ExportString() const { |
120 |
|
|
string manifest = |
121 |
|
|
"C" + catalog_hash_.ToString() + "\n" + |
122 |
|
|
"B" + StringifyInt(catalog_size_) + "\n" + |
123 |
|
|
"R" + root_path_.ToString() + "\n" + |
124 |
|
|
"D" + StringifyInt(ttl_) + "\n" + |
125 |
|
|
"S" + StringifyInt(revision_) + "\n" + |
126 |
|
|
"G" + StringifyBool(garbage_collectable_) + "\n" + |
127 |
|
41 |
"A" + StringifyBool(has_alt_catalog_path_) + "\n"; |
128 |
|
|
|
129 |
✗✓ |
41 |
if (!micro_catalog_hash_.IsNull()) |
130 |
|
|
manifest += "L" + micro_catalog_hash_.ToString() + "\n"; |
131 |
✓✗ |
41 |
if (repository_name_ != "") |
132 |
|
41 |
manifest += "N" + repository_name_ + "\n"; |
133 |
✓✗ |
41 |
if (!certificate_.IsNull()) |
134 |
|
41 |
manifest += "X" + certificate_.ToString() + "\n"; |
135 |
✓✗ |
41 |
if (!history_.IsNull()) |
136 |
|
41 |
manifest += "H" + history_.ToString() + "\n"; |
137 |
✓✓ |
41 |
if (publish_timestamp_ > 0) |
138 |
|
21 |
manifest += "T" + StringifyInt(publish_timestamp_) + "\n"; |
139 |
✗✓ |
41 |
if (!meta_info_.IsNull()) |
140 |
|
|
manifest += "M" + meta_info_.ToString() + "\n"; |
141 |
|
|
// Reserved: Z -> for identification of channel tips |
142 |
|
|
|
143 |
|
41 |
return manifest; |
144 |
|
|
} |
145 |
|
|
|
146 |
|
|
|
147 |
|
|
|
148 |
|
|
/** |
149 |
|
|
* Writes the .cvmfspublished file (unsigned). |
150 |
|
|
*/ |
151 |
|
1 |
bool Manifest::Export(const std::string &path) const { |
152 |
|
1 |
FILE *fmanifest = fopen(path.c_str(), "w"); |
153 |
✗✓ |
1 |
if (!fmanifest) |
154 |
|
|
return false; |
155 |
|
|
|
156 |
|
1 |
string manifest = ExportString(); |
157 |
|
|
|
158 |
✗✓ |
1 |
if (fwrite(manifest.data(), 1, manifest.length(), fmanifest) != |
159 |
|
|
manifest.length()) |
160 |
|
|
{ |
161 |
|
|
fclose(fmanifest); |
162 |
|
|
unlink(path.c_str()); |
163 |
|
|
return false; |
164 |
|
|
} |
165 |
|
1 |
fclose(fmanifest); |
166 |
|
|
|
167 |
|
1 |
return true; |
168 |
|
|
} |
169 |
|
|
|
170 |
|
|
|
171 |
|
|
/** |
172 |
|
|
* Writes the cvmfschecksum.$repository file. Atomic store. |
173 |
|
|
*/ |
174 |
|
11 |
bool Manifest::ExportChecksum(const string &directory, const int mode) const { |
175 |
|
|
string checksum_path = MakeCanonicalPath(directory) + "/cvmfschecksum." + |
176 |
|
11 |
repository_name_; |
177 |
|
11 |
string checksum_tmp_path; |
178 |
|
11 |
FILE *fchksum = CreateTempFile(checksum_path, mode, "w", &checksum_tmp_path); |
179 |
✗✓ |
11 |
if (fchksum == NULL) |
180 |
|
|
return false; |
181 |
|
|
string cache_checksum = catalog_hash_.ToString() + "T" + |
182 |
|
11 |
StringifyInt(publish_timestamp_); |
183 |
|
|
int written = fwrite(&(cache_checksum[0]), 1, cache_checksum.length(), |
184 |
|
11 |
fchksum); |
185 |
|
11 |
fclose(fchksum); |
186 |
✗✓ |
11 |
if (static_cast<unsigned>(written) != cache_checksum.length()) { |
187 |
|
|
unlink(checksum_tmp_path.c_str()); |
188 |
|
|
return false; |
189 |
|
|
} |
190 |
|
11 |
int retval = rename(checksum_tmp_path.c_str(), checksum_path.c_str()); |
191 |
✗✓ |
11 |
if (retval != 0) { |
192 |
|
|
unlink(checksum_tmp_path.c_str()); |
193 |
|
|
return false; |
194 |
|
|
} |
195 |
|
11 |
return true; |
196 |
|
|
} |
197 |
|
|
|
198 |
|
|
|
199 |
|
|
/** |
200 |
|
|
* Read the hash and the last-modified time stamp from the |
201 |
|
|
* cvmfschecksum.$repository file in the given directory. |
202 |
|
|
*/ |
203 |
|
29 |
bool Manifest::ReadChecksum( |
204 |
|
|
const std::string &repo_name, |
205 |
|
|
const std::string &directory, |
206 |
|
|
shash::Any *hash, |
207 |
|
|
uint64_t *last_modified) |
208 |
|
|
{ |
209 |
|
29 |
bool result = false; |
210 |
|
29 |
const string checksum_path = directory + "/cvmfschecksum." + repo_name; |
211 |
|
29 |
FILE *file_checksum = fopen(checksum_path.c_str(), "r"); |
212 |
|
|
char tmp[128]; |
213 |
|
|
int read_bytes; |
214 |
✓✓✓✓ ✓✓ |
29 |
if (file_checksum && (read_bytes = fread(tmp, 1, 128, file_checksum)) > 0) { |
215 |
|
|
// Separate hash from timestamp |
216 |
|
5 |
int separator_pos = 0; |
217 |
✓✓✓✓ ✓✓ |
5 |
for (; (separator_pos < read_bytes) && (tmp[separator_pos] != 'T'); |
218 |
|
|
++separator_pos) { } |
219 |
|
|
*hash = shash::MkFromHexPtr(shash::HexPtr(string(tmp, separator_pos)), |
220 |
✗✓ |
5 |
shash::kSuffixCatalog); |
221 |
|
|
|
222 |
|
|
// Get local last modified time |
223 |
|
5 |
string str_modified; |
224 |
✓✓✓✓
|
5 |
if ((tmp[separator_pos] == 'T') && (read_bytes > (separator_pos+1))) { |
225 |
|
|
str_modified = string(tmp+separator_pos+1, |
226 |
|
3 |
read_bytes-(separator_pos+1)); |
227 |
|
3 |
*last_modified = String2Uint64(str_modified); |
228 |
|
3 |
result = true; |
229 |
|
|
} |
230 |
|
|
} |
231 |
✓✓ |
29 |
if (file_checksum) fclose(file_checksum); |
232 |
|
|
|
233 |
|
29 |
return result; |
234 |
|
|
} |
235 |
|
|
|
236 |
|
|
} // namespace manifest |