1 |
|
|
/** |
2 |
|
|
* This file is part of the CernVM File System. |
3 |
|
|
*/ |
4 |
|
|
|
5 |
|
|
#include "swissknife_reflog.h" |
6 |
|
|
|
7 |
|
|
#include <cassert> |
8 |
|
|
|
9 |
|
|
#include <string> |
10 |
|
|
#include <vector> |
11 |
|
|
|
12 |
|
|
#include "manifest.h" |
13 |
|
|
#include "object_fetcher.h" |
14 |
|
|
#include "upload_facility.h" |
15 |
|
|
|
16 |
|
|
|
17 |
|
|
namespace swissknife { |
18 |
|
|
|
19 |
|
|
typedef HttpObjectFetcher<> ObjectFetcher; |
20 |
|
|
|
21 |
|
|
class RootChainWalker { |
22 |
|
|
public: |
23 |
|
|
typedef ObjectFetcher::CatalogTN CatalogTN; |
24 |
|
|
typedef ObjectFetcher::HistoryTN HistoryTN; |
25 |
|
|
|
26 |
|
|
public: |
27 |
|
|
RootChainWalker(const manifest::Manifest *manifest, |
28 |
|
|
ObjectFetcher *object_fetcher, |
29 |
|
|
manifest::Reflog *reflog) |
30 |
|
|
: object_fetcher_(object_fetcher) |
31 |
|
|
, reflog_(reflog) |
32 |
|
|
, manifest_(manifest) {} |
33 |
|
|
|
34 |
|
|
void FindObjectsAndPopulateReflog(); |
35 |
|
|
|
36 |
|
|
protected: |
37 |
|
|
typedef std::vector<shash::Any> CatalogList; |
38 |
|
|
|
39 |
|
|
protected: |
40 |
|
|
CatalogTN* FetchCatalog(const shash::Any catalog_hash); |
41 |
|
|
HistoryTN* FetchHistory(const shash::Any history_hash); |
42 |
|
|
|
43 |
|
|
void WalkRootCatalogs(const shash::Any &root_catalog_hash); |
44 |
|
|
void WalkHistories(const shash::Any &history_hash); |
45 |
|
|
|
46 |
|
|
bool WalkCatalogsInHistory(const HistoryTN *history); |
47 |
|
|
void WalkListedCatalogs(const CatalogList &catalog_list); |
48 |
|
|
|
49 |
|
|
template <class DatabaseT> |
50 |
|
|
DatabaseT* ReturnOrAbort(const ObjectFetcherFailures::Failures failure, |
51 |
|
|
const shash::Any &content_hash, |
52 |
|
|
DatabaseT *database); |
53 |
|
|
|
54 |
|
|
private: |
55 |
|
|
ObjectFetcher *object_fetcher_; |
56 |
|
|
manifest::Reflog *reflog_; |
57 |
|
|
const manifest::Manifest *manifest_; |
58 |
|
|
}; |
59 |
|
|
|
60 |
|
|
|
61 |
|
|
ParameterList CommandReconstructReflog::GetParams() const { |
62 |
|
|
ParameterList r; |
63 |
|
|
r.push_back(Parameter::Mandatory('r', "repository url")); |
64 |
|
|
r.push_back(Parameter::Mandatory('u', "spooler definition string")); |
65 |
|
|
r.push_back(Parameter::Mandatory('n', "fully qualified repository name")); |
66 |
|
|
r.push_back(Parameter::Mandatory('t', "temporary directory")); |
67 |
|
|
r.push_back(Parameter::Mandatory('k', "repository keychain")); |
68 |
|
|
r.push_back(Parameter::Mandatory('R', "path to reflog.chksum file")); |
69 |
|
|
return r; |
70 |
|
|
} |
71 |
|
|
|
72 |
|
|
|
73 |
|
|
int CommandReconstructReflog::Main(const ArgumentList &args) { |
74 |
|
|
const std::string &repo_url = *args.find('r')->second; |
75 |
|
|
const std::string &spooler = *args.find('u')->second; |
76 |
|
|
const std::string &repo_name = *args.find('n')->second; |
77 |
|
|
const std::string &tmp_dir = *args.find('t')->second; |
78 |
|
|
const std::string &repo_keys = *args.find('k')->second; |
79 |
|
|
const std::string &reflog_chksum_path = *args.find('R')->second; |
80 |
|
|
|
81 |
|
|
const bool follow_redirects = false; |
82 |
|
|
if (!this->InitDownloadManager(follow_redirects) || |
83 |
|
|
!this->InitVerifyingSignatureManager(repo_keys)) { |
84 |
|
|
LogCvmfs(kLogCvmfs, kLogStderr, "failed to init repo connection"); |
85 |
|
|
return 1; |
86 |
|
|
} |
87 |
|
|
|
88 |
|
|
ObjectFetcher object_fetcher(repo_name, |
89 |
|
|
repo_url, |
90 |
|
|
tmp_dir, |
91 |
|
|
download_manager(), |
92 |
|
|
signature_manager()); |
93 |
|
|
|
94 |
|
|
UniquePtr<manifest::Manifest> manifest; |
95 |
|
|
ObjectFetcher::Failures retval = object_fetcher.FetchManifest(&manifest); |
96 |
|
|
if (retval != ObjectFetcher::kFailOk) { |
97 |
|
|
LogCvmfs(kLogCvmfs, kLogStderr, "failed to load repository manifest " |
98 |
|
|
"(%d - %s)", |
99 |
|
|
retval, Code2Ascii(retval)); |
100 |
|
|
return 1; |
101 |
|
|
} |
102 |
|
|
|
103 |
|
|
const upload::SpoolerDefinition spooler_definition(spooler, shash::kAny); |
104 |
|
|
UniquePtr<upload::AbstractUploader> uploader( |
105 |
|
|
upload::AbstractUploader::Construct(spooler_definition)); |
106 |
|
|
|
107 |
|
|
if (!uploader.IsValid()) { |
108 |
|
|
LogCvmfs(kLogCvmfs, kLogStderr, "failed to initialize spooler for '%s'", |
109 |
|
|
spooler.c_str()); |
110 |
|
|
return 1; |
111 |
|
|
} |
112 |
|
|
|
113 |
|
|
UniquePtr<manifest::Reflog> reflog(CreateEmptyReflog(tmp_dir, repo_name)); |
114 |
|
|
reflog->TakeDatabaseFileOwnership(); |
115 |
|
|
|
116 |
|
|
reflog->BeginTransaction(); |
117 |
|
|
AddStaticManifestObjects(reflog.weak_ref(), manifest.weak_ref()); |
118 |
|
|
RootChainWalker walker(manifest.weak_ref(), |
119 |
|
|
&object_fetcher, |
120 |
|
|
reflog.weak_ref()); |
121 |
|
|
walker.FindObjectsAndPopulateReflog(); |
122 |
|
|
reflog->CommitTransaction(); |
123 |
|
|
|
124 |
|
|
LogCvmfs(kLogCvmfs, kLogStdout, "found %d entries", reflog->CountEntries()); |
125 |
|
|
|
126 |
|
|
reflog->DropDatabaseFileOwnership(); |
127 |
|
|
const std::string reflog_db = reflog->database_file(); |
128 |
|
|
reflog.Destroy(); |
129 |
|
|
uploader->Upload(reflog_db, ".cvmfsreflog"); |
130 |
|
|
shash::Any reflog_hash(manifest->GetHashAlgorithm()); |
131 |
|
|
manifest::Reflog::HashDatabase(reflog_db, &reflog_hash); |
132 |
|
|
uploader->WaitForUpload(); |
133 |
|
|
unlink(reflog_db.c_str()); |
134 |
|
|
|
135 |
|
|
const int errors = uploader->GetNumberOfErrors(); |
136 |
|
|
if (errors > 0) { |
137 |
|
|
LogCvmfs(kLogCvmfs, kLogStderr, "failed to upload generated Reflog"); |
138 |
|
|
} |
139 |
|
|
|
140 |
|
|
uploader->TearDown(); |
141 |
|
|
|
142 |
|
|
manifest::Reflog::WriteChecksum(reflog_chksum_path, reflog_hash); |
143 |
|
|
|
144 |
|
|
return (errors == 0) ? 0 : 1; |
145 |
|
|
} |
146 |
|
|
|
147 |
|
|
|
148 |
|
|
void CommandReconstructReflog::AddStaticManifestObjects( |
149 |
|
|
manifest::Reflog *reflog, |
150 |
|
|
manifest::Manifest *manifest) const { |
151 |
|
|
const shash::Any certificate = manifest->certificate(); |
152 |
|
|
const shash::Any meta_info = manifest->meta_info(); |
153 |
|
|
assert(!certificate.IsNull()); |
154 |
|
|
|
155 |
|
|
bool success = reflog->AddCertificate(certificate); |
156 |
|
|
assert(success); |
157 |
|
|
LogCvmfs(kLogCvmfs, kLogStdout, "Certificate: %s", |
158 |
|
|
certificate.ToString().c_str()); |
159 |
|
|
|
160 |
|
|
if (!meta_info.IsNull()) { |
161 |
|
|
success = reflog->AddMetainfo(meta_info); |
162 |
|
|
assert(success); |
163 |
|
|
LogCvmfs(kLogCvmfs, kLogStdout, "Metainfo: %s", |
164 |
|
|
meta_info.ToString().c_str()); |
165 |
|
|
} |
166 |
|
|
} |
167 |
|
|
|
168 |
|
|
|
169 |
|
|
void RootChainWalker::FindObjectsAndPopulateReflog() { |
170 |
|
|
const shash::Any root_catalog = manifest_->catalog_hash(); |
171 |
|
|
const shash::Any history = manifest_->history(); |
172 |
|
|
|
173 |
|
|
assert(!root_catalog.IsNull()); |
174 |
|
|
WalkRootCatalogs(root_catalog); |
175 |
|
|
|
176 |
|
|
if (!history.IsNull()) { |
177 |
|
|
WalkHistories(history); |
178 |
|
|
} |
179 |
|
|
} |
180 |
|
|
|
181 |
|
|
|
182 |
|
|
void RootChainWalker::WalkRootCatalogs(const shash::Any &root_catalog_hash) { |
183 |
|
|
shash::Any current_hash = root_catalog_hash; |
184 |
|
|
UniquePtr<CatalogTN> current_catalog; |
185 |
|
|
|
186 |
|
|
while (!current_hash.IsNull() && |
187 |
|
|
!reflog_->ContainsCatalog(current_hash) && |
188 |
|
|
(current_catalog = FetchCatalog(current_hash)).IsValid()) { |
189 |
|
|
LogCvmfs(kLogCvmfs, kLogStdout, "Catalog: %s Revision: %d", |
190 |
|
|
current_hash.ToString().c_str(), current_catalog->GetRevision()); |
191 |
|
|
|
192 |
|
|
const bool success = reflog_->AddCatalog(current_hash); |
193 |
|
|
assert(success); |
194 |
|
|
|
195 |
|
|
current_hash = current_catalog->GetPreviousRevision(); |
196 |
|
|
} |
197 |
|
|
} |
198 |
|
|
|
199 |
|
|
|
200 |
|
|
void RootChainWalker::WalkHistories(const shash::Any &history_hash) { |
201 |
|
|
shash::Any current_hash = history_hash; |
202 |
|
|
UniquePtr<HistoryTN> current_history; |
203 |
|
|
|
204 |
|
|
while (!current_hash.IsNull() && |
205 |
|
|
!reflog_->ContainsHistory(current_hash) && |
206 |
|
|
(current_history = FetchHistory(current_hash)).IsValid()) { |
207 |
|
|
LogCvmfs(kLogCvmfs, kLogStdout, "History: %s", |
208 |
|
|
current_hash.ToString().c_str()); |
209 |
|
|
|
210 |
|
|
bool cancel = WalkCatalogsInHistory(current_history); |
211 |
|
|
const bool success = reflog_->AddHistory(current_hash); |
212 |
|
|
assert(success); |
213 |
|
|
|
214 |
|
|
if (cancel) { |
215 |
|
|
current_hash = shash::Any(current_hash.algorithm); |
216 |
|
|
} else { |
217 |
|
|
current_hash = current_history->previous_revision(); |
218 |
|
|
} |
219 |
|
|
} |
220 |
|
|
} |
221 |
|
|
|
222 |
|
|
|
223 |
|
|
/** |
224 |
|
|
* If false is returned, it indicates that the history database comes from |
225 |
|
|
* an early pre-release of the history functionality. The WalkHistory() |
226 |
|
|
* iteration will subsequently stop. This is necessary, for instance, to |
227 |
|
|
* handle the cernvm-prod.cern.ch repository. |
228 |
|
|
*/ |
229 |
|
|
bool RootChainWalker::WalkCatalogsInHistory(const HistoryTN *history) { |
230 |
|
|
CatalogList tag_hashes; |
231 |
|
|
const bool list_success = history->GetHashes(&tag_hashes); |
232 |
|
|
assert(list_success); |
233 |
|
|
|
234 |
|
|
CatalogList bin_hashes; |
235 |
|
|
const bool bin_success = history->ListRecycleBin(&bin_hashes); |
236 |
|
|
if (!bin_success) { |
237 |
|
|
LogCvmfs(kLogCvmfs, kLogStderr, " Warning: 'recycle bin' table missing"); |
238 |
|
|
} |
239 |
|
|
|
240 |
|
|
WalkListedCatalogs(tag_hashes); |
241 |
|
|
WalkListedCatalogs(bin_hashes); |
242 |
|
|
|
243 |
|
|
return !bin_success; |
244 |
|
|
} |
245 |
|
|
|
246 |
|
|
|
247 |
|
|
void RootChainWalker::WalkListedCatalogs( |
248 |
|
|
const RootChainWalker::CatalogList &catalog_list) { |
249 |
|
|
CatalogList::const_iterator i = catalog_list.begin(); |
250 |
|
|
CatalogList::const_iterator iend = catalog_list.end(); |
251 |
|
|
for (; i != iend; ++i) { |
252 |
|
|
WalkRootCatalogs(*i); |
253 |
|
|
} |
254 |
|
|
} |
255 |
|
|
|
256 |
|
|
|
257 |
|
|
RootChainWalker::CatalogTN* RootChainWalker::FetchCatalog( |
258 |
|
|
const shash::Any catalog_hash) { |
259 |
|
|
CatalogTN *catalog = NULL; |
260 |
|
|
const char *root_path = ""; |
261 |
|
|
ObjectFetcherFailures::Failures failure = |
262 |
|
|
object_fetcher_->FetchCatalog(catalog_hash, root_path, &catalog); |
263 |
|
|
|
264 |
|
|
return ReturnOrAbort(failure, catalog_hash, catalog); |
265 |
|
|
} |
266 |
|
|
|
267 |
|
|
|
268 |
|
|
RootChainWalker::HistoryTN* RootChainWalker::FetchHistory( |
269 |
|
|
const shash::Any history_hash) { |
270 |
|
|
HistoryTN *history = NULL; |
271 |
|
|
ObjectFetcherFailures::Failures failure = |
272 |
|
|
object_fetcher_->FetchHistory(&history, history_hash); |
273 |
|
|
|
274 |
|
|
return ReturnOrAbort(failure, history_hash, history); |
275 |
|
|
} |
276 |
|
|
|
277 |
|
|
|
278 |
|
|
template <class DatabaseT> |
279 |
|
|
DatabaseT* RootChainWalker::ReturnOrAbort( |
280 |
|
|
const ObjectFetcherFailures::Failures failure, |
281 |
|
|
const shash::Any &content_hash, |
282 |
|
|
DatabaseT *database) { |
283 |
|
|
switch (failure) { |
284 |
|
|
case ObjectFetcherFailures::kFailOk: |
285 |
|
|
return database; |
286 |
|
|
case ObjectFetcherFailures::kFailNotFound: |
287 |
|
|
return NULL; |
288 |
|
|
default: |
289 |
|
|
LogCvmfs(kLogCvmfs, kLogStderr, "Failed to load object '%s' (%d - %s)", |
290 |
|
|
content_hash.ToStringWithSuffix().c_str(), |
291 |
|
|
failure, Code2Ascii(failure)); |
292 |
|
|
abort(); |
293 |
|
|
} |
294 |
|
|
} |
295 |
|
|
|
296 |
|
|
} // namespace swissknife |