Directory: | cvmfs/ |
---|---|
File: | cvmfs/catalog_mgr_rw.cc |
Date: | 2025-04-20 02:34:28 |
Exec | Total | Coverage | |
---|---|---|---|
Lines: | 461 | 734 | 62.8% |
Branches: | 356 | 1107 | 32.2% |
Line | Branch | Exec | Source |
---|---|---|---|
1 | /** | ||
2 | * This file is part of the CernVM file system. | ||
3 | */ | ||
4 | |||
5 | #define __STDC_FORMAT_MACROS | ||
6 | |||
7 | #include "catalog_mgr_rw.h" | ||
8 | |||
9 | #include <inttypes.h> | ||
10 | #include <unistd.h> | ||
11 | |||
12 | #include <cassert> | ||
13 | #include <cstdio> | ||
14 | #include <cstdlib> | ||
15 | #include <string> | ||
16 | |||
17 | #include "catalog_balancer.h" | ||
18 | #include "catalog_rw.h" | ||
19 | #include "manifest.h" | ||
20 | #include "statistics.h" | ||
21 | #include "upload.h" | ||
22 | #include "util/exception.h" | ||
23 | #include "util/logging.h" | ||
24 | #include "util/posix.h" | ||
25 | #include "util/smalloc.h" | ||
26 | |||
27 | using namespace std; // NOLINT | ||
28 | |||
29 | namespace catalog { | ||
30 | |||
31 | 30 | WritableCatalogManager::WritableCatalogManager( | |
32 | const shash::Any &base_hash, | ||
33 | const std::string &stratum0, | ||
34 | const string &dir_temp, | ||
35 | upload::Spooler *spooler, | ||
36 | download::DownloadManager *download_manager, | ||
37 | bool enforce_limits, | ||
38 | const unsigned nested_kcatalog_limit, | ||
39 | const unsigned root_kcatalog_limit, | ||
40 | const unsigned file_mbyte_limit, | ||
41 | perf::Statistics *statistics, | ||
42 | bool is_balanceable, | ||
43 | unsigned max_weight, | ||
44 | unsigned min_weight, | ||
45 | 30 | const std::string &dir_cache) | |
46 | : SimpleCatalogManager(base_hash, stratum0, dir_temp, download_manager, | ||
47 | statistics, false, dir_cache, true /* copy to tmpdir */) | ||
48 | 30 | , spooler_(spooler) | |
49 | 30 | , enforce_limits_(enforce_limits) | |
50 | 30 | , nested_kcatalog_limit_(nested_kcatalog_limit) | |
51 | 30 | , root_kcatalog_limit_(root_kcatalog_limit) | |
52 | 30 | , file_mbyte_limit_(file_mbyte_limit) | |
53 | 30 | , is_balanceable_(is_balanceable) | |
54 | 30 | , max_weight_(max_weight) | |
55 | 30 | , min_weight_(min_weight) | |
56 | 30 | , balance_weight_(max_weight / 2) | |
57 | { | ||
58 | 30 | sync_lock_ = | |
59 | 30 | reinterpret_cast<pthread_mutex_t *>(smalloc(sizeof(pthread_mutex_t))); | |
60 | 30 | int retval = pthread_mutex_init(sync_lock_, NULL); | |
61 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 30 times.
|
30 | assert(retval == 0); |
62 | 30 | catalog_processing_lock_ = | |
63 | 30 | reinterpret_cast<pthread_mutex_t *>(smalloc(sizeof(pthread_mutex_t))); | |
64 | 30 | retval = pthread_mutex_init(catalog_processing_lock_, NULL); | |
65 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 30 times.
|
30 | assert(retval == 0); |
66 | 30 | } | |
67 | |||
68 | |||
69 | 120 | WritableCatalogManager::~WritableCatalogManager() { | |
70 | 60 | pthread_mutex_destroy(sync_lock_); | |
71 | 60 | free(sync_lock_); | |
72 | 60 | pthread_mutex_destroy(catalog_processing_lock_); | |
73 | 60 | free(catalog_processing_lock_); | |
74 | 120 | } | |
75 | |||
76 | |||
77 | /** | ||
78 | * This method is virtual in AbstractCatalogManager. It returns a new catalog | ||
79 | * structure in the form the different CatalogManagers need it. | ||
80 | * In this case it returns a stub for a WritableCatalog. | ||
81 | * @param mountpoint the mount point of the catalog stub to create | ||
82 | * @param catalog_hash the content hash of the catalog to create | ||
83 | * @param parent_catalog the parent of the catalog stub to create | ||
84 | * @return a pointer to the catalog stub structure created | ||
85 | */ | ||
86 | 76 | Catalog* WritableCatalogManager::CreateCatalog( | |
87 | const PathString &mountpoint, | ||
88 | const shash::Any &catalog_hash, | ||
89 | Catalog *parent_catalog) | ||
90 | { | ||
91 | 152 | return new WritableCatalog(mountpoint.ToString(), | |
92 | catalog_hash, | ||
93 |
2/4✓ Branch 1 taken 76 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 76 times.
✗ Branch 5 not taken.
|
152 | parent_catalog); |
94 | } | ||
95 | |||
96 | |||
97 | 76 | void WritableCatalogManager::ActivateCatalog(Catalog *catalog) { | |
98 | 76 | catalog->TakeDatabaseFileOwnership(); | |
99 | 76 | } | |
100 | |||
101 | |||
102 | /** | ||
103 | * This method is invoked if we create a completely new repository. | ||
104 | * The new root catalog will already contain a root entry. | ||
105 | * It is uploaded by a Forklift to the upstream storage. | ||
106 | * @return true on success, false otherwise | ||
107 | */ | ||
108 | 24 | manifest::Manifest *WritableCatalogManager::CreateRepository( | |
109 | const string &dir_temp, | ||
110 | const bool volatile_content, | ||
111 | const std::string &voms_authz, | ||
112 | upload::Spooler *spooler) | ||
113 | { | ||
114 | // Create a new root catalog at file_path | ||
115 |
1/2✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
|
24 | string file_path = dir_temp + "/new_root_catalog"; |
116 | |||
117 | 24 | shash::Algorithms hash_algorithm = spooler->GetHashAlgorithm(); | |
118 | |||
119 | // A newly created catalog always needs a root entry | ||
120 | // we create and configure this here | ||
121 |
1/2✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
|
24 | DirectoryEntry root_entry; |
122 | 24 | root_entry.inode_ = DirectoryEntry::kInvalidInode; | |
123 | 24 | root_entry.mode_ = 16877; | |
124 | 24 | root_entry.size_ = 4096; | |
125 | 24 | root_entry.mtime_ = time(NULL); | |
126 | 24 | root_entry.uid_ = getuid(); | |
127 | 24 | root_entry.gid_ = getgid(); | |
128 |
1/2✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
|
24 | root_entry.checksum_ = shash::Any(hash_algorithm); |
129 | 24 | root_entry.linkcount_ = 2; | |
130 |
1/2✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
|
24 | string root_path = ""; |
131 | |||
132 | // Create the database schema and the initial root entry | ||
133 | { | ||
134 |
2/4✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
|
24 | UniquePtr<CatalogDatabase> new_clg_db(CatalogDatabase::Create(file_path)); |
135 |
2/4✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 24 times.
|
48 | if (!new_clg_db.IsValid() || |
136 |
2/4✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 24 times.
|
24 | !new_clg_db->InsertInitialValues(root_path, |
137 | volatile_content, | ||
138 | voms_authz, | ||
139 | root_entry)) | ||
140 | { | ||
141 | ✗ | LogCvmfs(kLogCatalog, kLogStderr, "creation of catalog '%s' failed", | |
142 | file_path.c_str()); | ||
143 | ✗ | return NULL; | |
144 | } | ||
145 |
1/2✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
|
24 | } |
146 | |||
147 | // Compress root catalog; | ||
148 |
1/2✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
|
24 | int64_t catalog_size = GetFileSize(file_path); |
149 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
|
24 | if (catalog_size < 0) { |
150 | ✗ | unlink(file_path.c_str()); | |
151 | ✗ | return NULL; | |
152 | } | ||
153 |
1/2✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
|
24 | string file_path_compressed = file_path + ".compressed"; |
154 |
1/2✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
|
24 | shash::Any hash_catalog(hash_algorithm, shash::kSuffixCatalog); |
155 |
1/2✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
|
24 | bool retval = zlib::CompressPath2Path(file_path, file_path_compressed, |
156 | &hash_catalog); | ||
157 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
|
24 | if (!retval) { |
158 | ✗ | LogCvmfs(kLogCatalog, kLogStderr, "compression of catalog '%s' failed", | |
159 | file_path.c_str()); | ||
160 | ✗ | unlink(file_path.c_str()); | |
161 | ✗ | return NULL; | |
162 | } | ||
163 | 24 | unlink(file_path.c_str()); | |
164 | |||
165 | // Create manifest | ||
166 |
1/2✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
|
24 | const string manifest_path = dir_temp + "/manifest"; |
167 | manifest::Manifest *manifest = | ||
168 |
3/6✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 24 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 24 times.
✗ Branch 9 not taken.
|
24 | new manifest::Manifest(hash_catalog, catalog_size, ""); |
169 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
|
24 | if (!voms_authz.empty()) { |
170 | ✗ | manifest->set_has_alt_catalog_path(true); | |
171 | } | ||
172 | |||
173 | // Upload catalog | ||
174 |
3/6✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 24 times.
✗ Branch 8 not taken.
|
24 | spooler->Upload(file_path_compressed, "data/" + hash_catalog.MakePath()); |
175 |
1/2✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
|
24 | spooler->WaitForUpload(); |
176 | 24 | unlink(file_path_compressed.c_str()); | |
177 |
2/4✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 24 times.
|
24 | if (spooler->GetNumberOfErrors() > 0) { |
178 | ✗ | LogCvmfs(kLogCatalog, kLogStderr, "failed to commit catalog %s", | |
179 | file_path_compressed.c_str()); | ||
180 | ✗ | delete manifest; | |
181 | ✗ | return NULL; | |
182 | } | ||
183 | |||
184 | 24 | return manifest; | |
185 | 24 | } | |
186 | |||
187 | |||
188 | /** | ||
189 | * Retrieve the catalog containing the given path. | ||
190 | * Other than AbstractCatalogManager::FindCatalog() this mounts nested | ||
191 | * catalogs if necessary and returns WritableCatalog objects. | ||
192 | * Furthermore it optionally returns the looked-up DirectoryEntry. | ||
193 | * | ||
194 | * @param path the path to look for | ||
195 | * @param result the retrieved catalog (as a pointer) | ||
196 | * @param dirent is set to looked up DirectoryEntry for 'path' if non-NULL | ||
197 | * @return true if catalog was found | ||
198 | */ | ||
199 | 350 | bool WritableCatalogManager::FindCatalog(const string &path, | |
200 | WritableCatalog **result, | ||
201 | DirectoryEntry *dirent) { | ||
202 |
1/2✓ Branch 1 taken 350 times.
✗ Branch 2 not taken.
|
350 | const PathString ps_path(path); |
203 | |||
204 | Catalog *best_fit = | ||
205 |
1/2✓ Branch 1 taken 350 times.
✗ Branch 2 not taken.
|
350 | AbstractCatalogManager<Catalog>::FindCatalog(ps_path); |
206 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 350 times.
|
350 | assert(best_fit != NULL); |
207 | 350 | Catalog *catalog = NULL; | |
208 | bool retval = | ||
209 |
1/2✓ Branch 1 taken 350 times.
✗ Branch 2 not taken.
|
350 | MountSubtree(ps_path, best_fit, true /* is_listable */, &catalog); |
210 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 350 times.
|
350 | if (!retval) |
211 | ✗ | return false; | |
212 | |||
213 |
1/2✓ Branch 1 taken 350 times.
✗ Branch 2 not taken.
|
350 | catalog::DirectoryEntry dummy; |
214 |
2/2✓ Branch 0 taken 182 times.
✓ Branch 1 taken 168 times.
|
350 | if (NULL == dirent) { |
215 | 182 | dirent = &dummy; | |
216 | } | ||
217 |
1/2✓ Branch 1 taken 350 times.
✗ Branch 2 not taken.
|
350 | bool found = catalog->LookupPath(ps_path, dirent); |
218 |
6/8✓ Branch 0 taken 349 times.
✓ Branch 1 taken 1 times.
✓ Branch 3 taken 349 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 349 times.
✓ Branch 7 taken 1 times.
✓ Branch 8 taken 349 times.
|
350 | if (!found || !catalog->IsWritable()) |
219 | 1 | return false; | |
220 | |||
221 | 349 | *result = static_cast<WritableCatalog *>(catalog); | |
222 | 349 | return true; | |
223 | 350 | } | |
224 | |||
225 | |||
226 | ✗ | WritableCatalog *WritableCatalogManager::GetHostingCatalog( | |
227 | const std::string &path) | ||
228 | { | ||
229 | ✗ | WritableCatalog *result = NULL; | |
230 | ✗ | bool retval = FindCatalog(MakeRelativePath(path), &result, NULL); | |
231 | ✗ | if (!retval) return NULL; | |
232 | ✗ | return result; | |
233 | } | ||
234 | |||
235 | |||
236 | /** | ||
237 | * Remove the given file from the catalogs. | ||
238 | * @param file_path the full path to the file to be removed | ||
239 | * @return true on success, false otherwise | ||
240 | */ | ||
241 | 4 | void WritableCatalogManager::RemoveFile(const std::string &path) { | |
242 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
4 | const string file_path = MakeRelativePath(path); |
243 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
4 | const string parent_path = GetParentPath(file_path); |
244 | |||
245 | 4 | SyncLock(); | |
246 | WritableCatalog *catalog; | ||
247 |
2/4✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
|
4 | if (!FindCatalog(parent_path, &catalog)) { |
248 | ✗ | PANIC(kLogStderr, "catalog for file '%s' cannot be found", | |
249 | file_path.c_str()); | ||
250 | } | ||
251 | |||
252 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
4 | catalog->RemoveEntry(file_path); |
253 | 4 | SyncUnlock(); | |
254 | 4 | } | |
255 | |||
256 | |||
257 | /** | ||
258 | * Remove the given directory from the catalogs. | ||
259 | * @param directory_path the full path to the directory to be removed | ||
260 | * @return true on success, false otherwise | ||
261 | */ | ||
262 | 5 | void WritableCatalogManager::RemoveDirectory(const std::string &path) { | |
263 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
5 | const string directory_path = MakeRelativePath(path); |
264 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
5 | const string parent_path = GetParentPath(directory_path); |
265 | |||
266 | 5 | SyncLock(); | |
267 | WritableCatalog *catalog; | ||
268 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
5 | DirectoryEntry parent_entry; |
269 |
2/4✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
|
5 | if (!FindCatalog(parent_path, &catalog, &parent_entry)) { |
270 | ✗ | PANIC(kLogStderr, "catalog for directory '%s' cannot be found", | |
271 | directory_path.c_str()); | ||
272 | } | ||
273 | |||
274 | 5 | parent_entry.set_linkcount(parent_entry.linkcount() - 1); | |
275 | |||
276 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
5 | catalog->RemoveEntry(directory_path); |
277 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
5 | catalog->UpdateEntry(parent_entry, parent_path); |
278 |
2/2✓ Branch 1 taken 2 times.
✓ Branch 2 taken 3 times.
|
5 | if (parent_entry.IsNestedCatalogRoot()) { |
279 |
1/2✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | LogCvmfs(kLogCatalog, kLogVerboseMsg, "updating transition point %s", |
280 | parent_path.c_str()); | ||
281 | WritableCatalog *parent_catalog = | ||
282 | 2 | reinterpret_cast<WritableCatalog *>(catalog->parent()); | |
283 | 2 | parent_entry.set_is_nested_catalog_mountpoint(true); | |
284 | 2 | parent_entry.set_is_nested_catalog_root(false); | |
285 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
2 | parent_catalog->UpdateEntry(parent_entry, parent_path); |
286 | } | ||
287 | 5 | SyncUnlock(); | |
288 | 5 | } | |
289 | |||
290 | /** | ||
291 | * Clone the file called `source` changing its name into `destination`, the | ||
292 | * source file is keep intact. | ||
293 | * @params destination, the name of the new file, complete path | ||
294 | * @params source, the name of the file to clone, which must be already in the | ||
295 | * repository | ||
296 | * @return void | ||
297 | */ | ||
298 | ✗ | void WritableCatalogManager::Clone(const std::string destination, | |
299 | const std::string source) { | ||
300 | ✗ | const std::string relative_source = MakeRelativePath(source); | |
301 | |||
302 | ✗ | DirectoryEntry source_dirent; | |
303 | ✗ | if (!LookupPath(relative_source, kLookupDefault, &source_dirent)) { | |
304 | ✗ | PANIC(kLogStderr, "catalog for file '%s' cannot be found, aborting", | |
305 | source.c_str()); | ||
306 | } | ||
307 | ✗ | if (source_dirent.IsDirectory()) { | |
308 | ✗ | PANIC(kLogStderr, "Trying to clone a directory: '%s', aborting", | |
309 | source.c_str()); | ||
310 | } | ||
311 | |||
312 | // if the file is already there we remove it and we add it back | ||
313 | ✗ | DirectoryEntry check_dirent; | |
314 | bool destination_already_present = | ||
315 | ✗ | LookupPath(MakeRelativePath(destination), kLookupDefault, &check_dirent); | |
316 | ✗ | if (destination_already_present) { | |
317 | ✗ | this->RemoveFile(destination); | |
318 | } | ||
319 | |||
320 | ✗ | DirectoryEntry destination_dirent(source_dirent); | |
321 | ✗ | std::string destination_dirname; | |
322 | ✗ | std::string destination_filename; | |
323 | ✗ | SplitPath(destination, &destination_dirname, &destination_filename); | |
324 | |||
325 | ✗ | destination_dirent.name_.Assign( | |
326 | ✗ | NameString(destination_filename.c_str(), destination_filename.length())); | |
327 | |||
328 | // TODO(jblomer): clone is used by tarball engine and should eventually | ||
329 | // support extended attributes | ||
330 | ✗ | this->AddFile(destination_dirent, empty_xattrs, destination_dirname); | |
331 | } | ||
332 | |||
333 | |||
334 | /** | ||
335 | * Copies an entire directory tree from the existing from_dir to the | ||
336 | * non-existing to_dir. The destination's parent directory must exist. On the | ||
337 | * catalog level, the new entries will be identical to the old ones except | ||
338 | * for their path hash fields. | ||
339 | */ | ||
340 | 10 | void WritableCatalogManager::CloneTree(const std::string &from_dir, | |
341 | const std::string &to_dir) | ||
342 | { | ||
343 | // Sanitize input paths | ||
344 |
6/6✓ Branch 1 taken 8 times.
✓ Branch 2 taken 2 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 7 times.
✓ Branch 6 taken 3 times.
✓ Branch 7 taken 7 times.
|
10 | if (from_dir.empty() || to_dir.empty()) |
345 | 3 | PANIC(kLogStderr, "clone tree from or to root impossible"); | |
346 | |||
347 |
1/2✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
|
7 | const std::string relative_source = MakeRelativePath(from_dir); |
348 |
1/2✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
|
7 | const std::string relative_dest = MakeRelativePath(to_dir); |
349 | |||
350 |
2/2✓ Branch 1 taken 1 times.
✓ Branch 2 taken 6 times.
|
7 | if (relative_source == relative_dest) { |
351 | 1 | PANIC(kLogStderr, "cannot clone tree into itself ('%s')", to_dir.c_str()); | |
352 | } | ||
353 |
4/7✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 1 times.
✓ Branch 8 taken 5 times.
|
6 | if (HasPrefix(relative_dest, relative_source + "/", false /*ignore_case*/)) { |
354 | 1 | PANIC(kLogStderr, | |
355 | "cannot clone tree into sub directory of source '%s' --> '%s'", | ||
356 | from_dir.c_str(), to_dir.c_str()); | ||
357 | } | ||
358 | |||
359 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
5 | DirectoryEntry source_dirent; |
360 |
3/4✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 4 times.
|
5 | if (!LookupPath(relative_source, kLookupDefault, &source_dirent)) { |
361 | 1 | PANIC(kLogStderr, "path '%s' cannot be found, aborting", from_dir.c_str()); | |
362 | } | ||
363 |
2/2✓ Branch 1 taken 1 times.
✓ Branch 2 taken 3 times.
|
4 | if (!source_dirent.IsDirectory()) { |
364 | 1 | PANIC(kLogStderr, "CloneTree: source '%s' not a directory, aborting", | |
365 | from_dir.c_str()); | ||
366 | } | ||
367 | |||
368 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | DirectoryEntry dest_dirent; |
369 |
3/4✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 2 times.
|
3 | if (LookupPath(relative_dest, kLookupDefault, &dest_dirent)) { |
370 | 1 | PANIC(kLogStderr, "destination '%s' exists, aborting", to_dir.c_str()); | |
371 | } | ||
372 | |||
373 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
2 | const std::string dest_parent = GetParentPath(relative_dest); |
374 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
2 | DirectoryEntry dest_parent_dirent; |
375 |
3/4✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 1 times.
|
2 | if (!LookupPath(dest_parent, kLookupDefault, &dest_parent_dirent)) { |
376 | 1 | PANIC(kLogStderr, "destination '%s' not on a known path, aborting", | |
377 | to_dir.c_str()); | ||
378 | } | ||
379 | |||
380 |
2/4✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
|
1 | CloneTreeImpl(PathString(from_dir), |
381 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
2 | GetParentPath(to_dir), |
382 |
2/4✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
|
2 | NameString(GetFileName(to_dir))); |
383 | 21 | } | |
384 | |||
385 | |||
386 | /** | ||
387 | * Called from CloneTree(), assumes that from_dir and to_dir are sufficiently | ||
388 | * sanitized | ||
389 | */ | ||
390 | 8 | void WritableCatalogManager::CloneTreeImpl( | |
391 | const PathString &source_dir, | ||
392 | const std::string &dest_parent_dir, | ||
393 | const NameString &dest_name) | ||
394 | { | ||
395 |
1/2✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
|
8 | LogCvmfs(kLogCatalog, kLogDebug, "cloning %s --> %s/%s", source_dir.c_str(), |
396 |
1/2✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
|
16 | dest_parent_dir.c_str(), dest_name.ToString().c_str()); |
397 |
3/6✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 8 times.
✗ Branch 8 not taken.
|
16 | PathString relative_source(MakeRelativePath(source_dir.ToString())); |
398 | |||
399 |
1/2✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
|
8 | DirectoryEntry source_dirent; |
400 |
1/2✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
|
8 | bool retval = LookupPath(relative_source, kLookupDefault, &source_dirent); |
401 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | assert(retval); |
402 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
|
8 | assert(!source_dirent.IsBindMountpoint()); |
403 | |||
404 |
1/2✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
|
8 | DirectoryEntry dest_dirent(source_dirent); |
405 |
1/2✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
|
8 | dest_dirent.name_.Assign(dest_name); |
406 | // Just in case, reset the nested catalog markers | ||
407 | 8 | dest_dirent.set_is_nested_catalog_mountpoint(false); | |
408 | 8 | dest_dirent.set_is_nested_catalog_root(false); | |
409 | |||
410 | 8 | XattrList xattrs; | |
411 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
|
8 | if (source_dirent.HasXattrs()) { |
412 | ✗ | retval = LookupXattrs(relative_source, &xattrs); | |
413 | ✗ | assert(retval); | |
414 | } | ||
415 |
1/2✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
|
8 | AddDirectory(dest_dirent, xattrs, dest_parent_dir); |
416 | |||
417 |
1/2✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
|
8 | std::string dest_dir = dest_parent_dir; |
418 |
2/2✓ Branch 1 taken 7 times.
✓ Branch 2 taken 1 times.
|
8 | if (!dest_dir.empty()) |
419 |
1/2✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
|
7 | dest_dir.push_back('/'); |
420 |
2/4✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
|
8 | dest_dir += dest_name.ToString(); |
421 |
5/6✓ Branch 1 taken 5 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 5 times.
|
13 | if (source_dirent.IsNestedCatalogRoot() || |
422 | 5 | source_dirent.IsNestedCatalogMountpoint()) | |
423 | { | ||
424 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | CreateNestedCatalog(dest_dir); |
425 | } | ||
426 | |||
427 | 8 | DirectoryEntryList ls; | |
428 |
1/2✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
|
8 | retval = Listing(relative_source, &ls, false /* expand_symlink */); |
429 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | assert(retval); |
430 |
2/2✓ Branch 1 taken 17 times.
✓ Branch 2 taken 8 times.
|
25 | for (unsigned i = 0; i < ls.size(); ++i) { |
431 |
1/2✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
|
17 | PathString sub_path(source_dir); |
432 |
2/4✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 17 times.
|
17 | assert(!sub_path.IsEmpty()); |
433 |
1/2✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
|
17 | sub_path.Append("/", 1); |
434 |
3/6✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
✓ Branch 7 taken 17 times.
✗ Branch 8 not taken.
✓ Branch 11 taken 17 times.
✗ Branch 12 not taken.
|
17 | sub_path.Append(ls[i].name().GetChars(), ls[i].name().GetLength()); |
435 | |||
436 |
2/2✓ Branch 2 taken 7 times.
✓ Branch 3 taken 10 times.
|
17 | if (ls[i].IsDirectory()) { |
437 |
2/4✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 7 times.
✗ Branch 6 not taken.
|
7 | CloneTreeImpl(sub_path, dest_dir, ls[i].name()); |
438 | 7 | continue; | |
439 | } | ||
440 | |||
441 | // We break hard-links during cloning | ||
442 | 10 | ls[i].set_hardlink_group(0); | |
443 | 10 | ls[i].set_linkcount(1); | |
444 | |||
445 | 10 | xattrs.Clear(); | |
446 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 10 times.
|
10 | if (ls[i].HasXattrs()) { |
447 | ✗ | retval = LookupXattrs(sub_path, &xattrs); | |
448 | ✗ | assert(retval); | |
449 | } | ||
450 | |||
451 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 10 times.
|
10 | if (ls[i].IsChunkedFile()) { |
452 | ✗ | FileChunkList chunks; | |
453 | ✗ | std::string relative_sub_path = MakeRelativePath(sub_path.ToString()); | |
454 | ✗ | retval = ListFileChunks( | |
455 | ✗ | PathString(relative_sub_path), ls[i].hash_algorithm(), &chunks); | |
456 | ✗ | assert(retval); | |
457 | ✗ | AddChunkedFile(ls[i], xattrs, dest_dir, chunks); | |
458 | ✗ | } else { | |
459 |
1/2✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
|
10 | AddFile(ls[i], xattrs, dest_dir); |
460 | } | ||
461 |
2/2✓ Branch 1 taken 10 times.
✓ Branch 2 taken 7 times.
|
17 | } |
462 | 8 | } | |
463 | |||
464 | |||
465 | /** | ||
466 | * Add a new directory to the catalogs. | ||
467 | * @param entry a DirectoryEntry structure describing the new directory | ||
468 | * @param parent_directory the absolute path of the directory containing the | ||
469 | * directory to be created | ||
470 | * @return true on success, false otherwise | ||
471 | */ | ||
472 | 126 | void WritableCatalogManager::AddDirectory(const DirectoryEntryBase &entry, | |
473 | const XattrList &xattrs, | ||
474 | const std::string &parent_directory) | ||
475 | { | ||
476 |
1/2✓ Branch 1 taken 126 times.
✗ Branch 2 not taken.
|
126 | const string parent_path = MakeRelativePath(parent_directory); |
477 |
1/2✓ Branch 1 taken 126 times.
✗ Branch 2 not taken.
|
126 | string directory_path = parent_path + "/"; |
478 |
3/6✓ Branch 1 taken 126 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 126 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 126 times.
✗ Branch 10 not taken.
|
126 | directory_path.append(entry.name().GetChars(), entry.name().GetLength()); |
479 | |||
480 | 126 | SyncLock(); | |
481 | WritableCatalog *catalog; | ||
482 |
1/2✓ Branch 1 taken 126 times.
✗ Branch 2 not taken.
|
126 | DirectoryEntry parent_entry; |
483 |
2/4✓ Branch 1 taken 126 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 126 times.
|
126 | if (!FindCatalog(parent_path, &catalog, &parent_entry)) { |
484 | ✗ | PANIC(kLogStderr, "catalog for directory '%s' cannot be found", | |
485 | directory_path.c_str()); | ||
486 | } | ||
487 | |||
488 |
1/2✓ Branch 1 taken 126 times.
✗ Branch 2 not taken.
|
126 | DirectoryEntry fixed_hardlink_count(entry); |
489 | 126 | fixed_hardlink_count.set_linkcount(2); | |
490 |
1/2✓ Branch 1 taken 126 times.
✗ Branch 2 not taken.
|
126 | catalog->AddEntry(fixed_hardlink_count, xattrs, |
491 | directory_path, parent_path); | ||
492 | |||
493 | 126 | parent_entry.set_linkcount(parent_entry.linkcount() + 1); | |
494 |
1/2✓ Branch 1 taken 126 times.
✗ Branch 2 not taken.
|
126 | catalog->UpdateEntry(parent_entry, parent_path); |
495 |
2/2✓ Branch 1 taken 3 times.
✓ Branch 2 taken 123 times.
|
126 | if (parent_entry.IsNestedCatalogRoot()) { |
496 |
1/2✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | LogCvmfs(kLogCatalog, kLogVerboseMsg, "updating transition point %s", |
497 | parent_path.c_str()); | ||
498 | WritableCatalog *parent_catalog = | ||
499 | 3 | reinterpret_cast<WritableCatalog *>(catalog->parent()); | |
500 | 3 | parent_entry.set_is_nested_catalog_mountpoint(true); | |
501 | 3 | parent_entry.set_is_nested_catalog_root(false); | |
502 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | parent_catalog->UpdateEntry(parent_entry, parent_path); |
503 | } | ||
504 | 126 | SyncUnlock(); | |
505 | 126 | } | |
506 | |||
507 | /** | ||
508 | * Add a new file to the catalogs. | ||
509 | * @param entry a DirectoryEntry structure describing the new file | ||
510 | * @param parent_directory the absolute path of the directory containing the | ||
511 | * file to be created | ||
512 | * @return true on success, false otherwise | ||
513 | */ | ||
514 | 167 | void WritableCatalogManager::AddFile( | |
515 | const DirectoryEntry &entry, | ||
516 | const XattrList &xattrs, | ||
517 | const std::string &parent_directory) | ||
518 | { | ||
519 |
1/2✓ Branch 1 taken 167 times.
✗ Branch 2 not taken.
|
167 | const string parent_path = MakeRelativePath(parent_directory); |
520 |
1/2✓ Branch 1 taken 167 times.
✗ Branch 2 not taken.
|
167 | const string file_path = entry.GetFullPath(parent_path); |
521 | |||
522 | 167 | SyncLock(); | |
523 | WritableCatalog *catalog; | ||
524 |
2/4✓ Branch 1 taken 167 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 167 times.
|
167 | if (!FindCatalog(parent_path, &catalog)) { |
525 | ✗ | PANIC(kLogStderr, "catalog for file '%s' cannot be found", | |
526 | file_path.c_str()); | ||
527 | } | ||
528 | |||
529 |
4/6✓ Branch 1 taken 164 times.
✓ Branch 2 taken 3 times.
✓ Branch 4 taken 164 times.
✗ Branch 5 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 164 times.
|
167 | assert(!entry.IsRegular() || entry.IsChunkedFile() || |
530 | !entry.checksum().IsNull()); | ||
531 |
3/4✓ Branch 1 taken 3 times.
✓ Branch 2 taken 164 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 3 times.
|
167 | assert(entry.IsRegular() || !entry.IsExternalFile()); |
532 | |||
533 | // check if file is too big | ||
534 |
1/2✓ Branch 1 taken 167 times.
✗ Branch 2 not taken.
|
167 | unsigned mbytes = entry.size() / (1024 * 1024); |
535 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 167 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
167 | if ((file_mbyte_limit_ > 0) && (mbytes > file_mbyte_limit_)) { |
536 | ✗ | LogCvmfs(kLogCatalog, kLogStderr, | |
537 | "%s: file at %s is larger than %u megabytes (%u). " | ||
538 | "CernVM-FS works best with small files. " | ||
539 | "Please remove the file or increase the limit.", | ||
540 | ✗ | enforce_limits_ ? "FATAL" : "WARNING", file_path.c_str(), | |
541 | file_mbyte_limit_, mbytes); | ||
542 | ✗ | if (enforce_limits_) | |
543 | ✗ | PANIC(kLogStderr, "file at %s is larger than %u megabytes (%u).", | |
544 | file_path.c_str(), file_mbyte_limit_, mbytes); | ||
545 | } | ||
546 | |||
547 |
1/2✓ Branch 1 taken 167 times.
✗ Branch 2 not taken.
|
167 | catalog->AddEntry(entry, xattrs, file_path, parent_path); |
548 | 167 | SyncUnlock(); | |
549 | 167 | } | |
550 | |||
551 | |||
552 | ✗ | void WritableCatalogManager::AddChunkedFile( | |
553 | const DirectoryEntryBase &entry, | ||
554 | const XattrList &xattrs, | ||
555 | const std::string &parent_directory, | ||
556 | const FileChunkList &file_chunks) | ||
557 | { | ||
558 | ✗ | assert(file_chunks.size() > 0); | |
559 | |||
560 | ✗ | DirectoryEntry full_entry(entry); | |
561 | ✗ | full_entry.set_is_chunked_file(true); | |
562 | |||
563 | ✗ | AddFile(full_entry, xattrs, parent_directory); | |
564 | |||
565 | ✗ | const string parent_path = MakeRelativePath(parent_directory); | |
566 | ✗ | const string file_path = entry.GetFullPath(parent_path); | |
567 | |||
568 | ✗ | SyncLock(); | |
569 | WritableCatalog *catalog; | ||
570 | ✗ | if (!FindCatalog(parent_path, &catalog)) { | |
571 | ✗ | PANIC(kLogStderr, "catalog for file '%s' cannot be found", | |
572 | file_path.c_str()); | ||
573 | } | ||
574 | |||
575 | ✗ | for (unsigned i = 0; i < file_chunks.size(); ++i) { | |
576 | ✗ | catalog->AddFileChunk(file_path, *file_chunks.AtPtr(i)); | |
577 | } | ||
578 | ✗ | SyncUnlock(); | |
579 | } | ||
580 | |||
581 | |||
582 | /** | ||
583 | * Add a hardlink group to the catalogs. | ||
584 | * @param entries a list of DirectoryEntries describing the new files | ||
585 | * @param parent_directory the absolute path of the directory containing the | ||
586 | * files to be created | ||
587 | * @return true on success, false otherwise | ||
588 | */ | ||
589 | ✗ | void WritableCatalogManager::AddHardlinkGroup( | |
590 | const DirectoryEntryBaseList &entries, | ||
591 | const XattrList &xattrs, | ||
592 | const std::string &parent_directory, | ||
593 | const FileChunkList &file_chunks) | ||
594 | { | ||
595 | ✗ | assert(entries.size() >= 1); | |
596 | ✗ | assert(file_chunks.IsEmpty() || entries[0].IsRegular()); | |
597 | ✗ | if (entries.size() == 1) { | |
598 | ✗ | DirectoryEntry fix_linkcount(entries[0]); | |
599 | ✗ | fix_linkcount.set_linkcount(1); | |
600 | ✗ | if (file_chunks.IsEmpty()) | |
601 | ✗ | return AddFile(fix_linkcount, xattrs, parent_directory); | |
602 | ✗ | return AddChunkedFile(fix_linkcount, xattrs, parent_directory, file_chunks); | |
603 | } | ||
604 | |||
605 | ✗ | LogCvmfs(kLogCatalog, kLogVerboseMsg, "adding hardlink group %s/%s", | |
606 | ✗ | parent_directory.c_str(), entries[0].name().c_str()); | |
607 | |||
608 | // Hardlink groups have to reside in the same directory. | ||
609 | // Therefore we only have one parent directory here | ||
610 | ✗ | const string parent_path = MakeRelativePath(parent_directory); | |
611 | |||
612 | // check if hard link is too big | ||
613 | ✗ | unsigned mbytes = entries[0].size() / (1024 * 1024); | |
614 | ✗ | if ((file_mbyte_limit_ > 0) && (mbytes > file_mbyte_limit_)) { | |
615 | ✗ | LogCvmfs(kLogCatalog, kLogStderr, | |
616 | "%s: hard link at %s is larger than %u megabytes (%u). " | ||
617 | "CernVM-FS works best with small files. " | ||
618 | "Please remove the file or increase the limit.", | ||
619 | ✗ | enforce_limits_ ? "FATAL" : "WARNING", | |
620 | ✗ | (parent_path + entries[0].name().ToString()).c_str(), | |
621 | file_mbyte_limit_, | ||
622 | mbytes); | ||
623 | ✗ | if (enforce_limits_) | |
624 | ✗ | PANIC(kLogStderr, "hard link at %s is larger than %u megabytes (%u)", | |
625 | (parent_path + entries[0].name().ToString()).c_str(), | ||
626 | file_mbyte_limit_, mbytes); | ||
627 | } | ||
628 | |||
629 | ✗ | SyncLock(); | |
630 | WritableCatalog *catalog; | ||
631 | ✗ | if (!FindCatalog(parent_path, &catalog)) { | |
632 | ✗ | PANIC(kLogStderr, | |
633 | "catalog for hardlink group containing '%s' cannot be found", | ||
634 | parent_path.c_str()); | ||
635 | } | ||
636 | |||
637 | // Get a valid hardlink group id for the catalog the group will end up in | ||
638 | // TODO(unknown): Compaction | ||
639 | ✗ | uint32_t new_group_id = catalog->GetMaxLinkId() + 1; | |
640 | ✗ | LogCvmfs(kLogCatalog, kLogVerboseMsg, "hardlink group id %u issued", | |
641 | new_group_id); | ||
642 | ✗ | assert(new_group_id > 0); | |
643 | |||
644 | // Add the file entries to the catalog | ||
645 | ✗ | for (DirectoryEntryBaseList::const_iterator i = entries.begin(), | |
646 | ✗ | iEnd = entries.end(); i != iEnd; ++i) | |
647 | { | ||
648 | ✗ | string file_path = parent_path + "/"; | |
649 | ✗ | file_path.append(i->name().GetChars(), i->name().GetLength()); | |
650 | |||
651 | // create a fully fledged DirectoryEntry to add the hardlink group to it | ||
652 | // which is CVMFS specific meta data. | ||
653 | ✗ | DirectoryEntry hardlink(*i); | |
654 | ✗ | hardlink.set_hardlink_group(new_group_id); | |
655 | ✗ | hardlink.set_linkcount(entries.size()); | |
656 | ✗ | hardlink.set_is_chunked_file(!file_chunks.IsEmpty()); | |
657 | |||
658 | ✗ | catalog->AddEntry(hardlink, xattrs, file_path, parent_path); | |
659 | ✗ | if (hardlink.IsChunkedFile()) { | |
660 | ✗ | for (unsigned i = 0; i < file_chunks.size(); ++i) { | |
661 | ✗ | catalog->AddFileChunk(file_path, *file_chunks.AtPtr(i)); | |
662 | } | ||
663 | } | ||
664 | } | ||
665 | ✗ | SyncUnlock(); | |
666 | } | ||
667 | |||
668 | |||
669 | ✗ | void WritableCatalogManager::ShrinkHardlinkGroup(const string &remove_path) { | |
670 | ✗ | const string relative_path = MakeRelativePath(remove_path); | |
671 | |||
672 | ✗ | SyncLock(); | |
673 | WritableCatalog *catalog; | ||
674 | ✗ | if (!FindCatalog(relative_path, &catalog)) { | |
675 | ✗ | PANIC(kLogStderr, | |
676 | "catalog for hardlink group containing '%s' cannot be found", | ||
677 | remove_path.c_str()); | ||
678 | } | ||
679 | |||
680 | ✗ | catalog->IncLinkcount(relative_path, -1); | |
681 | ✗ | SyncUnlock(); | |
682 | } | ||
683 | |||
684 | |||
685 | /** | ||
686 | * Update entry meta data (mode, owner, ...). | ||
687 | * CVMFS specific meta data (i.e. nested catalog transition points) are NOT | ||
688 | * changed by this method, although transition points intrinsics are taken into | ||
689 | * account, to keep nested catalogs consistent. | ||
690 | * @param entry the directory entry to be touched | ||
691 | * @param path the path of the directory entry to be touched | ||
692 | */ | ||
693 | 1 | void WritableCatalogManager::TouchDirectory(const DirectoryEntryBase &entry, | |
694 | const XattrList &xattrs, | ||
695 | const std::string &directory_path) | ||
696 | { | ||
697 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
|
1 | assert(entry.IsDirectory()); |
698 | |||
699 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | const string entry_path = MakeRelativePath(directory_path); |
700 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | const string parent_path = GetParentPath(entry_path); |
701 | |||
702 | 1 | SyncLock(); | |
703 | // find the catalog to be updated | ||
704 | WritableCatalog *catalog; | ||
705 |
2/4✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
|
1 | if (!FindCatalog(parent_path, &catalog)) { |
706 | ✗ | PANIC(kLogStderr, "catalog for entry '%s' cannot be found", | |
707 | entry_path.c_str()); | ||
708 | } | ||
709 | |||
710 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | catalog->TouchEntry(entry, xattrs, entry_path); |
711 | |||
712 | // since we deal with a directory here, we might just touch a | ||
713 | // nested catalog transition point. If this is the case we would need to | ||
714 | // update two catalog entries: | ||
715 | // * the nested catalog MOUNTPOINT in the parent catalog | ||
716 | // * the nested catalog ROOT in the nested catalog | ||
717 | |||
718 | // first check if we really have a nested catalog transition point | ||
719 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | catalog::DirectoryEntry potential_transition_point; |
720 |
1/2✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
|
1 | PathString transition_path(entry_path.data(), entry_path.length()); |
721 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | bool retval = catalog->LookupPath(transition_path, |
722 | &potential_transition_point); | ||
723 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | assert(retval); |
724 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
|
1 | if (potential_transition_point.IsNestedCatalogMountpoint()) { |
725 | ✗ | LogCvmfs(kLogCatalog, kLogVerboseMsg, | |
726 | "updating transition point at %s", entry_path.c_str()); | ||
727 | |||
728 | // find and mount nested catalog associated to this transition point | ||
729 | ✗ | shash::Any nested_hash; | |
730 | uint64_t nested_size; | ||
731 | ✗ | retval = catalog->FindNested(transition_path, &nested_hash, &nested_size); | |
732 | ✗ | assert(retval); | |
733 | Catalog *nested_catalog; | ||
734 | ✗ | nested_catalog = MountCatalog(transition_path, nested_hash, catalog); | |
735 | ✗ | assert(nested_catalog != NULL); | |
736 | |||
737 | // update nested catalog root in the child catalog | ||
738 | reinterpret_cast<WritableCatalog *>(nested_catalog)-> | ||
739 | ✗ | TouchEntry(entry, xattrs, entry_path); | |
740 | } | ||
741 | |||
742 | 1 | SyncUnlock(); | |
743 | 1 | } | |
744 | |||
745 | |||
746 | /** | ||
747 | * Create a new nested catalog. Includes moving all entries belonging there | ||
748 | * from it's parent catalog. | ||
749 | * @param mountpoint the path of the directory to become a nested root | ||
750 | * @return true on success, false otherwise | ||
751 | */ | ||
752 | 35 | void WritableCatalogManager::CreateNestedCatalog(const std::string &mountpoint) | |
753 | { | ||
754 |
1/2✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
|
35 | const string nested_root_path = MakeRelativePath(mountpoint); |
755 |
1/2✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
|
35 | const PathString ps_nested_root_path(nested_root_path); |
756 | |||
757 | 35 | SyncLock(); | |
758 | // Find the catalog currently containing the directory structure, which | ||
759 | // will be represented as a new nested catalog and its root-entry/mountpoint | ||
760 | // along the way | ||
761 | 35 | WritableCatalog *old_catalog = NULL; | |
762 |
1/2✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
|
35 | DirectoryEntry new_root_entry; |
763 |
2/4✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 35 times.
|
35 | if (!FindCatalog(nested_root_path, &old_catalog, &new_root_entry)) { |
764 | ✗ | PANIC(kLogStderr, | |
765 | "failed to create nested catalog '%s': " | ||
766 | "mountpoint was not found in current catalog structure", | ||
767 | nested_root_path.c_str()); | ||
768 | } | ||
769 | |||
770 | // Create the database schema and the initial root entry | ||
771 | // for the new nested catalog | ||
772 |
1/2✓ Branch 2 taken 35 times.
✗ Branch 3 not taken.
|
35 | const string database_file_path = CreateTempPath(dir_temp() + "/catalog", |
773 |
1/2✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
|
35 | 0666); |
774 | 35 | const bool volatile_content = false; | |
775 |
1/2✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
|
35 | CatalogDatabase *new_catalog_db = CatalogDatabase::Create(database_file_path); |
776 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 35 times.
|
35 | assert(NULL != new_catalog_db); |
777 | // Note we do not set the external_data bit for nested catalogs | ||
778 | bool retval = | ||
779 |
2/4✓ Branch 2 taken 35 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 35 times.
✗ Branch 6 not taken.
|
35 | new_catalog_db->InsertInitialValues(nested_root_path, |
780 | volatile_content, | ||
781 | "", // At this point, only root | ||
782 | // catalog gets VOMS authz | ||
783 | new_root_entry); | ||
784 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 35 times.
|
35 | assert(retval); |
785 | // TODO(rmeusel): we need a way to attach a catalog directly from an open | ||
786 | // database to remove this indirection | ||
787 |
1/2✓ Branch 0 taken 35 times.
✗ Branch 1 not taken.
|
35 | delete new_catalog_db; |
788 | 35 | new_catalog_db = NULL; | |
789 | |||
790 | // Attach the just created nested catalog | ||
791 | Catalog *new_catalog = | ||
792 |
2/4✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 35 times.
✗ Branch 5 not taken.
|
35 | CreateCatalog(ps_nested_root_path, shash::Any(), old_catalog); |
793 |
1/2✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
|
35 | retval = AttachCatalog(database_file_path, new_catalog); |
794 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 35 times.
|
35 | assert(retval); |
795 | |||
796 |
2/4✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 35 times.
|
35 | assert(new_catalog->IsWritable()); |
797 | 35 | WritableCatalog *wr_new_catalog = static_cast<WritableCatalog *>(new_catalog); | |
798 | |||
799 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 35 times.
|
35 | if (new_root_entry.HasXattrs()) { |
800 | ✗ | XattrList xattrs; | |
801 | ✗ | retval = old_catalog->LookupXattrsPath(ps_nested_root_path, &xattrs); | |
802 | ✗ | assert(retval); | |
803 | ✗ | wr_new_catalog->TouchEntry(new_root_entry, xattrs, nested_root_path); | |
804 | } | ||
805 | |||
806 | // From now on, there are two catalogs, spanning the same directory structure | ||
807 | // we have to split the overlapping directory entries from the old catalog | ||
808 | // to the new catalog to re-gain a valid catalog structure | ||
809 |
1/2✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
|
35 | old_catalog->Partition(wr_new_catalog); |
810 | |||
811 | // Add the newly created nested catalog to the references of the containing | ||
812 | // catalog | ||
813 |
4/8✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 35 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 35 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 35 times.
✗ Branch 11 not taken.
|
35 | old_catalog->InsertNestedCatalog(new_catalog->mountpoint().ToString(), NULL, |
814 | 35 | shash::Any(spooler_->GetHashAlgorithm()), 0); | |
815 | |||
816 | // Fix subtree counters in new nested catalogs: subtree is the sum of all | ||
817 | // entries of all "grand-nested" catalogs | ||
818 | // Note: taking a copy of the nested catalog list here | ||
819 | const Catalog::NestedCatalogList &grand_nested = | ||
820 |
1/2✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
|
35 | wr_new_catalog->ListOwnNestedCatalogs(); |
821 |
1/2✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
|
35 | DeltaCounters fix_subtree_counters; |
822 | 70 | for (Catalog::NestedCatalogList::const_iterator i = grand_nested.begin(), | |
823 |
1/2✗ Branch 3 not taken.
✓ Branch 4 taken 35 times.
|
70 | iEnd = grand_nested.end(); i != iEnd; ++i) |
824 | { | ||
825 | WritableCatalog *grand_catalog; | ||
826 | ✗ | retval = FindCatalog(i->mountpoint.ToString(), &grand_catalog); | |
827 | ✗ | assert(retval); | |
828 | ✗ | const Counters &grand_counters = grand_catalog->GetCounters(); | |
829 | ✗ | grand_counters.AddAsSubtree(&fix_subtree_counters); | |
830 | } | ||
831 | 35 | DeltaCounters save_counters = wr_new_catalog->delta_counters_; | |
832 | 35 | wr_new_catalog->delta_counters_ = fix_subtree_counters; | |
833 |
1/2✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
|
35 | wr_new_catalog->UpdateCounters(); |
834 | 35 | wr_new_catalog->delta_counters_ = save_counters; | |
835 | |||
836 | 35 | SyncUnlock(); | |
837 | 35 | } | |
838 | |||
839 | |||
840 | /** | ||
841 | * Remove a nested catalog | ||
842 | * | ||
843 | * If the merged parameter is true, when you remove a nested catalog | ||
844 | * all entries currently held by it will be merged into its parent | ||
845 | * catalog. | ||
846 | * @param mountpoint - the path of the nested catalog to be removed | ||
847 | * @param merge - merge the subtree associated with the nested catalog | ||
848 | * into its parent catalog | ||
849 | * @return - true on success, false otherwise | ||
850 | */ | ||
851 | 3 | void WritableCatalogManager::RemoveNestedCatalog(const string &mountpoint, | |
852 | const bool merge) { | ||
853 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | const string nested_root_path = MakeRelativePath(mountpoint); |
854 | |||
855 | 3 | SyncLock(); | |
856 | // Find the catalog which should be removed | ||
857 | 3 | WritableCatalog *nested_catalog = NULL; | |
858 |
2/4✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
|
3 | if (!FindCatalog(nested_root_path, &nested_catalog)) { |
859 | ✗ | PANIC(kLogStderr, | |
860 | "failed to remove nested catalog '%s': " | ||
861 | "mountpoint was not found in current catalog structure", | ||
862 | nested_root_path.c_str()); | ||
863 | } | ||
864 | |||
865 | // Check if the found catalog is really the nested catalog to be deleted | ||
866 |
6/19✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 3 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 3 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✓ Branch 13 taken 3 times.
✗ Branch 14 not taken.
✓ Branch 16 taken 3 times.
✗ Branch 17 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
|
6 | assert(!nested_catalog->IsRoot() && |
867 | (nested_catalog->mountpoint().ToString() == nested_root_path)); | ||
868 | |||
869 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | if (merge) { |
870 | // Merge all data from the nested catalog into it's parent | ||
871 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | nested_catalog->MergeIntoParent(); |
872 | } else { | ||
873 | ✗ | nested_catalog->RemoveFromParent(); | |
874 | } | ||
875 | |||
876 | // Delete the catalog database file from the working copy | ||
877 |
2/6✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 3 times.
|
3 | if (unlink(nested_catalog->database_path().c_str()) != 0) { |
878 | ✗ | PANIC(kLogStderr, | |
879 | "unable to delete the removed nested catalog database file '%s'", | ||
880 | nested_catalog->database_path().c_str()); | ||
881 | } | ||
882 | |||
883 | // Remove the catalog from internal data structures | ||
884 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | DetachCatalog(nested_catalog); |
885 | 3 | SyncUnlock(); | |
886 | 3 | } | |
887 | |||
888 | |||
889 | /** | ||
890 | * Swap in a new nested catalog | ||
891 | * | ||
892 | * The old nested catalog must not have been already attached to the | ||
893 | * catalog tree. This method will not attach the new nested catalog | ||
894 | * to the catalog tree. | ||
895 | * | ||
896 | * @param mountpoint - the path of the nested catalog to be removed | ||
897 | * @param new_hash - the hash of the new nested catalog | ||
898 | * @param new_size - the size of the new nested catalog | ||
899 | */ | ||
900 | 7 | void WritableCatalogManager::SwapNestedCatalog(const string &mountpoint, | |
901 | const shash::Any &new_hash, | ||
902 | const uint64_t new_size) { | ||
903 |
1/2✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
|
7 | const string nested_root_path = MakeRelativePath(mountpoint); |
904 |
1/2✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
|
7 | const string parent_path = GetParentPath(nested_root_path); |
905 |
1/2✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
|
7 | const PathString nested_root_ps = PathString(nested_root_path); |
906 | |||
907 | 7 | SyncLock(); | |
908 | |||
909 | // Find the immediate parent catalog | ||
910 | 7 | WritableCatalog *parent = NULL; | |
911 |
3/4✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 6 times.
|
7 | if (!FindCatalog(parent_path, &parent)) { |
912 | 1 | SyncUnlock(); // this is needed for the unittest. otherwise they get stuck | |
913 | 1 | PANIC(kLogStderr, | |
914 | "failed to swap nested catalog '%s': could not find parent '%s'", | ||
915 | nested_root_path.c_str(), parent_path.c_str()); | ||
916 | } | ||
917 | |||
918 | // Get old nested catalog counters | ||
919 |
1/2✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
|
6 | Catalog *old_attached_catalog = parent->FindChild(nested_root_ps); |
920 |
1/2✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
|
6 | Counters old_counters; |
921 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 5 times.
|
6 | if (old_attached_catalog) { |
922 | // Old catalog was already attached (e.g. as a child catalog | ||
923 | // attached by a prior call to CreateNestedCatalog()). Ensure | ||
924 | // that it has not been modified, get counters, and detach it. | ||
925 | 1 | WritableCatalogList list; | |
926 |
2/4✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
|
1 | if (GetModifiedCatalogLeafsRecursively(old_attached_catalog, &list)) { |
927 | 1 | SyncUnlock(); | |
928 | 1 | PANIC(kLogStderr, | |
929 | "failed to swap nested catalog '%s': already modified", | ||
930 | nested_root_path.c_str()); | ||
931 | } | ||
932 | ✗ | old_counters = old_attached_catalog->GetCounters(); | |
933 | ✗ | DetachSubtree(old_attached_catalog); | |
934 | |||
935 | 1 | } else { | |
936 | // Old catalog was not attached. Download a freely attached | ||
937 | // version and get counters. | ||
938 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
5 | shash::Any old_hash; |
939 | uint64_t old_size; | ||
940 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
5 | const bool old_found = parent->FindNested(nested_root_ps, &old_hash, |
941 | &old_size); | ||
942 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
|
5 | if (!old_found) { |
943 | 1 | SyncUnlock(); | |
944 | 1 | PANIC(kLogStderr, | |
945 | "failed to swap nested catalog '%s': not found in parent", | ||
946 | nested_root_path.c_str()); | ||
947 | } | ||
948 | UniquePtr<Catalog> old_free_catalog( | ||
949 |
2/4✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
|
4 | LoadFreeCatalog(nested_root_ps, old_hash)); |
950 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
|
4 | if (!old_free_catalog.IsValid()) { |
951 | ✗ | SyncUnlock(); | |
952 | ✗ | PANIC(kLogStderr, | |
953 | "failed to swap nested catalog '%s': failed to load old catalog", | ||
954 | nested_root_path.c_str()); | ||
955 | } | ||
956 | 4 | old_counters = old_free_catalog->GetCounters(); | |
957 | 4 | } | |
958 | |||
959 | // Load freely attached new catalog | ||
960 |
3/4✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1 times.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
|
4 | UniquePtr<Catalog> new_catalog(LoadFreeCatalog(nested_root_ps, new_hash)); |
961 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
|
3 | if (!new_catalog.IsValid()) { |
962 | ✗ | SyncUnlock(); | |
963 | ✗ | PANIC(kLogStderr, | |
964 | "failed to swap nested catalog '%s': failed to load new catalog", | ||
965 | nested_root_path.c_str()); | ||
966 | } | ||
967 | |||
968 | // Get new catalog root directory entry | ||
969 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | DirectoryEntry dirent; |
970 | 3 | XattrList xattrs; | |
971 |
1/2✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | const bool dirent_found = new_catalog->LookupPath(nested_root_ps, &dirent); |
972 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (!dirent_found) { |
973 | ✗ | SyncUnlock(); | |
974 | ✗ | PANIC(kLogStderr, | |
975 | "failed to swap nested catalog '%s': missing dirent in new catalog", | ||
976 | nested_root_path.c_str()); | ||
977 | } | ||
978 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
|
3 | if (dirent.HasXattrs()) { |
979 | ✗ | const bool xattrs_found = new_catalog->LookupXattrsPath(nested_root_ps, | |
980 | &xattrs); | ||
981 | ✗ | if (!xattrs_found) { | |
982 | ✗ | SyncUnlock(); | |
983 | ✗ | PANIC(kLogStderr, | |
984 | "failed to swap nested catalog '%s': missing xattrs in new catalog", | ||
985 | nested_root_path.c_str()); | ||
986 | } | ||
987 | } | ||
988 | |||
989 | // Swap catalogs | ||
990 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | parent->RemoveNestedCatalog(nested_root_path, NULL); |
991 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | parent->InsertNestedCatalog(nested_root_path, NULL, new_hash, new_size); |
992 | |||
993 | // Update parent directory entry | ||
994 | 3 | dirent.set_is_nested_catalog_mountpoint(true); | |
995 | 3 | dirent.set_is_nested_catalog_root(false); | |
996 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | parent->UpdateEntry(dirent, nested_root_path); |
997 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | parent->TouchEntry(dirent, xattrs, nested_root_path); |
998 | |||
999 | // Update counters | ||
1000 |
1/2✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
|
3 | DeltaCounters delta = Counters::Diff(old_counters, |
1001 | new_catalog->GetCounters()); | ||
1002 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | delta.PopulateToParent(&parent->delta_counters_); |
1003 | |||
1004 | 3 | SyncUnlock(); | |
1005 | 15 | } | |
1006 | |||
1007 | /** | ||
1008 | * Install a nested catalog (catalog hierarchy) at a new, empty directory | ||
1009 | * | ||
1010 | * The mountpoint directory must not yet exist. Its parent directory, however | ||
1011 | * must exist. This method combines functionality from AddDirectory(), | ||
1012 | * CreateNestedCatalog() and SwapNestedCatalog(). | ||
1013 | * The new nested catalog won't get attached. | ||
1014 | * | ||
1015 | * @param mountpoint - the path where the nested catalog should be installed | ||
1016 | * @param new_hash - the hash of the new nested catalog | ||
1017 | * @param new_size - the size of the new nested catalog | ||
1018 | */ | ||
1019 | 3 | void WritableCatalogManager::GraftNestedCatalog(const string &mountpoint, | |
1020 | const shash::Any &new_hash, | ||
1021 | const uint64_t new_size) | ||
1022 | { | ||
1023 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | const string nested_root_path = MakeRelativePath(mountpoint); |
1024 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | const string parent_path = GetParentPath(nested_root_path); |
1025 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | const PathString nested_root_ps = PathString(nested_root_path); |
1026 | |||
1027 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
|
3 | assert(!nested_root_path.empty()); |
1028 | |||
1029 | // Load freely attached new catalog | ||
1030 |
2/4✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
|
3 | UniquePtr<Catalog> new_catalog(LoadFreeCatalog(nested_root_ps, new_hash)); |
1031 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
|
3 | if (!new_catalog.IsValid()) { |
1032 | ✗ | PANIC(kLogStderr, | |
1033 | "failed to graft nested catalog '%s': failed to load new catalog", | ||
1034 | nested_root_path.c_str()); | ||
1035 | } | ||
1036 |
4/7✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 1 times.
✓ Branch 9 taken 2 times.
|
3 | if (new_catalog->root_prefix() != nested_root_ps) { |
1037 |
2/4✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
|
3 | PANIC(kLogStderr, |
1038 | "invalid nested catalog for grafting at '%s': catalog rooted at '%s'", | ||
1039 | nested_root_path.c_str(), | ||
1040 | new_catalog->root_prefix().ToString().c_str()); | ||
1041 | } | ||
1042 | |||
1043 | // Get new catalog root directory entry | ||
1044 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
2 | DirectoryEntry dirent; |
1045 | 2 | XattrList xattrs; | |
1046 |
1/2✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | const bool dirent_found = new_catalog->LookupPath(nested_root_ps, &dirent); |
1047 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (!dirent_found) { |
1048 | ✗ | PANIC(kLogStderr, | |
1049 | "failed to swap nested catalog '%s': missing dirent in new catalog", | ||
1050 | nested_root_path.c_str()); | ||
1051 | } | ||
1052 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
|
2 | if (dirent.HasXattrs()) { |
1053 | ✗ | const bool xattrs_found = new_catalog->LookupXattrsPath(nested_root_ps, | |
1054 | &xattrs); | ||
1055 | ✗ | if (!xattrs_found) { | |
1056 | ✗ | PANIC(kLogStderr, | |
1057 | "failed to swap nested catalog '%s': missing xattrs in new catalog", | ||
1058 | nested_root_path.c_str()); | ||
1059 | } | ||
1060 | } | ||
1061 | // Transform the nested catalog root into a transition point to be inserted | ||
1062 | // in the parent catalog | ||
1063 | 2 | dirent.set_is_nested_catalog_root(false); | |
1064 | 2 | dirent.set_is_nested_catalog_mountpoint(true); | |
1065 | |||
1066 | // Add directory and nested catalog | ||
1067 | |||
1068 | 2 | SyncLock(); | |
1069 | WritableCatalog *parent_catalog; | ||
1070 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
2 | DirectoryEntry parent_entry; |
1071 |
2/4✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
|
2 | if (!FindCatalog(parent_path, &parent_catalog, &parent_entry)) { |
1072 | ✗ | SyncUnlock(); | |
1073 | ✗ | PANIC(kLogStderr, "catalog for directory '%s' cannot be found", | |
1074 | parent_path.c_str()); | ||
1075 | } | ||
1076 |
3/4✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 1 times.
|
2 | if (parent_catalog->LookupPath(nested_root_ps, NULL)) { |
1077 | 1 | SyncUnlock(); | |
1078 | 1 | PANIC(kLogStderr, "invalid attempt to graft nested catalog into existing " | |
1079 | "directory '%s'", nested_root_path.c_str()); | ||
1080 | } | ||
1081 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | parent_catalog->AddEntry(dirent, xattrs, nested_root_path, parent_path); |
1082 | 1 | parent_entry.set_linkcount(parent_entry.linkcount() + 1); | |
1083 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | parent_catalog->UpdateEntry(parent_entry, parent_path); |
1084 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | if (parent_entry.IsNestedCatalogRoot()) { |
1085 | WritableCatalog *grand_parent_catalog = | ||
1086 | 1 | reinterpret_cast<WritableCatalog *>(parent_catalog->parent()); | |
1087 | 1 | parent_entry.set_is_nested_catalog_root(false); | |
1088 | 1 | parent_entry.set_is_nested_catalog_mountpoint(true); | |
1089 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | grand_parent_catalog->UpdateEntry(parent_entry, parent_path); |
1090 | } | ||
1091 | |||
1092 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | parent_catalog->InsertNestedCatalog( |
1093 | nested_root_path, NULL, new_hash, new_size); | ||
1094 | |||
1095 | // Fix-up counters | ||
1096 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | Counters counters; |
1097 |
1/2✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
|
1 | DeltaCounters delta = Counters::Diff(counters, new_catalog->GetCounters()); |
1098 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | delta.PopulateToParent(&parent_catalog->delta_counters_); |
1099 | |||
1100 | 1 | SyncUnlock(); | |
1101 | 12 | } | |
1102 | |||
1103 | /** | ||
1104 | * Checks if a nested catalog starts at this path. The path must be valid. | ||
1105 | */ | ||
1106 | ✗ | bool WritableCatalogManager::IsTransitionPoint(const string &mountpoint) { | |
1107 | ✗ | const string path = MakeRelativePath(mountpoint); | |
1108 | |||
1109 | ✗ | SyncLock(); | |
1110 | WritableCatalog *catalog; | ||
1111 | ✗ | DirectoryEntry entry; | |
1112 | ✗ | if (!FindCatalog(path, &catalog, &entry)) { | |
1113 | ✗ | PANIC(kLogStderr, "catalog for directory '%s' cannot be found", | |
1114 | path.c_str()); | ||
1115 | } | ||
1116 | ✗ | const bool result = entry.IsNestedCatalogRoot(); | |
1117 | ✗ | SyncUnlock(); | |
1118 | ✗ | return result; | |
1119 | } | ||
1120 | |||
1121 | |||
1122 | ✗ | void WritableCatalogManager::PrecalculateListings() { | |
1123 | // TODO(jblomer): meant for micro catalogs | ||
1124 | } | ||
1125 | |||
1126 | |||
1127 | ✗ | void WritableCatalogManager::SetTTL(const uint64_t new_ttl) { | |
1128 | ✗ | SyncLock(); | |
1129 | ✗ | reinterpret_cast<WritableCatalog *>(GetRootCatalog())->SetTTL(new_ttl); | |
1130 | ✗ | SyncUnlock(); | |
1131 | } | ||
1132 | |||
1133 | |||
1134 | ✗ | bool WritableCatalogManager::SetVOMSAuthz(const std::string &voms_authz) { | |
1135 | bool result; | ||
1136 | ✗ | SyncLock(); | |
1137 | result = reinterpret_cast<WritableCatalog *>( | ||
1138 | ✗ | GetRootCatalog())->SetVOMSAuthz(voms_authz); | |
1139 | ✗ | SyncUnlock(); | |
1140 | ✗ | return result; | |
1141 | } | ||
1142 | |||
1143 | |||
1144 | 22 | bool WritableCatalogManager::Commit(const bool stop_for_tweaks, | |
1145 | const uint64_t manual_revision, | ||
1146 | manifest::Manifest *manifest) { | ||
1147 | WritableCatalog *root_catalog = | ||
1148 | 22 | reinterpret_cast<WritableCatalog *>(GetRootCatalog()); | |
1149 |
1/2✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
|
22 | root_catalog->SetDirty(); |
1150 | |||
1151 | // set root catalog revision to manually provided number if available | ||
1152 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
|
22 | if (manual_revision > 0) { |
1153 | ✗ | const uint64_t revision = root_catalog->GetRevision(); | |
1154 | ✗ | if (revision >= manual_revision) { | |
1155 | ✗ | LogCvmfs(kLogCatalog, kLogStderr, | |
1156 | "Manual revision (%" PRIu64 ") must not be " | ||
1157 | "smaller than the current root catalog's (%" PRIu64 | ||
1158 | "). Skipped!", manual_revision, revision); | ||
1159 | } else { | ||
1160 | // Gets incremented by FinalizeCatalog() afterwards! | ||
1161 | ✗ | root_catalog->SetRevision(manual_revision - 1); | |
1162 | } | ||
1163 | } | ||
1164 | |||
1165 | // do the actual catalog snapshotting and upload | ||
1166 |
1/2✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
|
22 | CatalogInfo root_catalog_info; |
1167 |
1/2✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
|
22 | if (getenv("_CVMFS_SERIALIZED_CATALOG_PROCESSING_") == NULL) |
1168 |
1/2✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
|
22 | root_catalog_info = SnapshotCatalogs(stop_for_tweaks); |
1169 | else | ||
1170 | ✗ | root_catalog_info = SnapshotCatalogsSerialized(stop_for_tweaks); | |
1171 |
2/4✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 22 times.
|
22 | if (spooler_->GetNumberOfErrors() > 0) { |
1172 | ✗ | LogCvmfs(kLogCatalog, kLogStderr, "failed to commit catalogs"); | |
1173 | ✗ | return false; | |
1174 | } | ||
1175 | |||
1176 | // .cvmfspublished export | ||
1177 |
1/2✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
|
22 | LogCvmfs(kLogCatalog, kLogVerboseMsg, "Committing repository manifest"); |
1178 | 22 | set_base_hash(root_catalog_info.content_hash); | |
1179 | |||
1180 | 22 | manifest->set_catalog_hash(root_catalog_info.content_hash); | |
1181 | 22 | manifest->set_catalog_size(root_catalog_info.size); | |
1182 |
2/4✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 22 times.
✗ Branch 6 not taken.
|
22 | manifest->set_root_path(""); |
1183 | 22 | manifest->set_ttl(root_catalog_info.ttl); | |
1184 | 22 | manifest->set_revision(root_catalog_info.revision); | |
1185 | |||
1186 | 22 | return true; | |
1187 | } | ||
1188 | |||
1189 | |||
1190 | /** | ||
1191 | * Handles the snapshotting of dirty (i.e. modified) catalogs while trying to | ||
1192 | * parallelize the compression and upload as much as possible. We use a parallel | ||
1193 | * depth first post order tree traversal based on 'continuations'. | ||
1194 | * | ||
1195 | * The idea is as follows: | ||
1196 | * 1. find all leaf-catalogs (i.e. dirty catalogs with no dirty children) | ||
1197 | * --> these can be processed and uploaded immediately and independently | ||
1198 | * see WritableCatalogManager::GetModifiedCatalogLeafs() | ||
1199 | * 2. annotate non-leaf catalogs with their number of dirty children | ||
1200 | * --> a finished child will notify it's parent and decrement this number | ||
1201 | * see WritableCatalogManager::CatalogUploadCallback() | ||
1202 | * 3. if a non-leaf catalog's dirty children number reaches 0, it is scheduled | ||
1203 | * for processing as well (continuation) | ||
1204 | * --> the parallel processing walks bottom-up through the catalog tree | ||
1205 | * see WritableCatalogManager::CatalogUploadCallback() | ||
1206 | * 4. when the root catalog is reached, we notify the main thread and return | ||
1207 | * --> done through a Future<> in WritableCatalogManager::SnapshotCatalogs | ||
1208 | * | ||
1209 | * Note: The catalog finalisation (see WritableCatalogManager::FinalizeCatalog) | ||
1210 | * happens in a worker thread (i.e. the callback method) for non-leaf | ||
1211 | * catalogs. | ||
1212 | * | ||
1213 | * TODO(rmeusel): since all leaf catalogs are finalized in the main thread, we | ||
1214 | * sacrifice some potential concurrency for simplicity. | ||
1215 | */ | ||
1216 | 22 | WritableCatalogManager::CatalogInfo WritableCatalogManager::SnapshotCatalogs( | |
1217 | const bool stop_for_tweaks) { | ||
1218 | // prepare environment for parallel processing | ||
1219 |
1/2✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
|
22 | Future<CatalogInfo> root_catalog_info_future; |
1220 | CatalogUploadContext upload_context; | ||
1221 | 22 | upload_context.root_catalog_info = &root_catalog_info_future; | |
1222 | 22 | upload_context.stop_for_tweaks = stop_for_tweaks; | |
1223 | |||
1224 |
1/2✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
|
22 | spooler_->RegisterListener( |
1225 | &WritableCatalogManager::CatalogUploadCallback, this, upload_context); | ||
1226 | |||
1227 | // find dirty leaf catalogs and annotate non-leaf catalogs (dirty child count) | ||
1228 | // post-condition: the entire catalog tree is ready for concurrent processing | ||
1229 | 22 | WritableCatalogList leafs_to_snapshot; | |
1230 |
1/2✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
|
22 | GetModifiedCatalogLeafs(&leafs_to_snapshot); |
1231 | |||
1232 | // finalize and schedule the catalog processing | ||
1233 | 22 | WritableCatalogList::const_iterator i = leafs_to_snapshot.begin(); | |
1234 | 22 | const WritableCatalogList::const_iterator iend = leafs_to_snapshot.end(); | |
1235 |
2/2✓ Branch 2 taken 33 times.
✓ Branch 3 taken 22 times.
|
55 | for (; i != iend; ++i) { |
1236 |
1/2✓ Branch 2 taken 33 times.
✗ Branch 3 not taken.
|
33 | FinalizeCatalog(*i, stop_for_tweaks); |
1237 |
1/2✓ Branch 2 taken 33 times.
✗ Branch 3 not taken.
|
33 | ScheduleCatalogProcessing(*i); |
1238 | } | ||
1239 | |||
1240 |
1/2✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
|
22 | LogCvmfs(kLogCatalog, kLogVerboseMsg, "waiting for upload of catalogs"); |
1241 |
1/2✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
|
22 | CatalogInfo& root_catalog_info = root_catalog_info_future.Get(); |
1242 |
1/2✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
|
22 | spooler_->WaitForUpload(); |
1243 | |||
1244 |
1/2✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
|
22 | spooler_->UnregisterListeners(); |
1245 | 22 | return root_catalog_info; | |
1246 | 22 | } | |
1247 | |||
1248 | |||
1249 | 54 | void WritableCatalogManager::FinalizeCatalog(WritableCatalog *catalog, | |
1250 | const bool stop_for_tweaks) { | ||
1251 | // update meta information of this catalog | ||
1252 |
1/3✓ Branch 2 taken 54 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
|
54 | LogCvmfs(kLogCatalog, kLogVerboseMsg, "creating snapshot of catalog '%s'", |
1253 | 108 | catalog->mountpoint().c_str()); | |
1254 | |||
1255 | 54 | catalog->UpdateCounters(); | |
1256 | 54 | catalog->UpdateLastModified(); | |
1257 | 54 | catalog->IncrementRevision(); | |
1258 | |||
1259 | // update the previous catalog revision pointer | ||
1260 |
2/2✓ Branch 1 taken 22 times.
✓ Branch 2 taken 32 times.
|
54 | if (catalog->IsRoot()) { |
1261 |
1/4✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
22 | LogCvmfs(kLogCatalog, kLogVerboseMsg, "setting '%s' as previous revision " |
1262 | "for root catalog", | ||
1263 | 44 | base_hash().ToStringWithSuffix().c_str()); | |
1264 | 22 | catalog->SetPreviousRevision(base_hash()); | |
1265 | } else { | ||
1266 | // Multiple catalogs might query the parent concurrently | ||
1267 | 32 | SyncLock(); | |
1268 |
1/2✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
|
32 | shash::Any hash_previous; |
1269 | uint64_t size_previous; | ||
1270 | const bool retval = | ||
1271 |
2/4✓ Branch 2 taken 32 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 32 times.
✗ Branch 6 not taken.
|
32 | catalog->parent()->FindNested(catalog->mountpoint(), |
1272 | &hash_previous, &size_previous); | ||
1273 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 32 times.
|
32 | assert(retval); |
1274 | 32 | SyncUnlock(); | |
1275 | |||
1276 |
1/8✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 32 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
|
64 | LogCvmfs(kLogCatalog, kLogVerboseMsg, "found '%s' as previous revision " |
1277 | "for nested catalog '%s'", | ||
1278 |
1/2✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
|
64 | hash_previous.ToStringWithSuffix().c_str(), |
1279 |
1/2✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
|
64 | catalog->mountpoint().c_str()); |
1280 |
1/2✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
|
32 | catalog->SetPreviousRevision(hash_previous); |
1281 | } | ||
1282 | 54 | catalog->Commit(); | |
1283 | |||
1284 | // check if catalog has too many entries | ||
1285 |
2/2✓ Branch 0 taken 22 times.
✓ Branch 1 taken 32 times.
|
54 | uint64_t catalog_limit = uint64_t(1000) * |
1286 | 54 | uint64_t((catalog->IsRoot() | |
1287 | 22 | ? root_kcatalog_limit_ | |
1288 | 32 | : nested_kcatalog_limit_)); | |
1289 |
2/6✗ Branch 0 not taken.
✓ Branch 1 taken 54 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 54 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
54 | if ((catalog_limit > 0) && |
1290 | ✗ | (catalog->GetCounters().GetSelfEntries() > catalog_limit)) { | |
1291 | ✗ | LogCvmfs(kLogCatalog, kLogStderr, | |
1292 | "%s: catalog at %s has more than %lu entries (%lu). " | ||
1293 | "Large catalogs stress the CernVM-FS transport infrastructure. " | ||
1294 | "Please split it into nested catalogs or increase the limit.", | ||
1295 | ✗ | enforce_limits_ ? "FATAL" : "WARNING", | |
1296 | ✗ | (catalog->IsRoot() ? "/" : catalog->mountpoint().c_str()), | |
1297 | ✗ | catalog_limit, catalog->GetCounters().GetSelfEntries()); | |
1298 | ✗ | if (enforce_limits_) | |
1299 | ✗ | PANIC(kLogStderr, "catalog at %s has more than %u entries (%u). ", | |
1300 | (catalog->IsRoot() ? "/" : catalog->mountpoint().c_str()), | ||
1301 | catalog_limit, catalog->GetCounters().GetSelfEntries()); | ||
1302 | } | ||
1303 | |||
1304 | // allow for manual adjustments in the catalog | ||
1305 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 54 times.
|
54 | if (stop_for_tweaks) { |
1306 | ✗ | LogCvmfs(kLogCatalog, kLogStdout, "Allowing for tweaks in %s at %s " | |
1307 | "(hit return to continue)", | ||
1308 | ✗ | catalog->database_path().c_str(), catalog->mountpoint().c_str()); | |
1309 | ✗ | int read_char = getchar(); | |
1310 | ✗ | assert(read_char != EOF); | |
1311 | } | ||
1312 | |||
1313 | // compaction of bloated catalogs (usually after high database churn) | ||
1314 | 54 | catalog->VacuumDatabaseIfNecessary(); | |
1315 | 54 | } | |
1316 | |||
1317 | |||
1318 | 54 | void WritableCatalogManager::ScheduleCatalogProcessing( | |
1319 | WritableCatalog *catalog) { | ||
1320 | { | ||
1321 | 54 | MutexLockGuard guard(catalog_processing_lock_); | |
1322 | // register catalog object for WritableCatalogManager::CatalogUploadCallback | ||
1323 |
2/4✓ Branch 1 taken 54 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 54 times.
✗ Branch 5 not taken.
|
54 | catalog_processing_map_[catalog->database_path()] = catalog; |
1324 | 54 | } | |
1325 |
1/2✓ Branch 2 taken 54 times.
✗ Branch 3 not taken.
|
54 | spooler_->ProcessCatalog(catalog->database_path()); |
1326 | 54 | } | |
1327 | |||
1328 | /** | ||
1329 | * Copy catalog to local cache.server | ||
1330 | * Must be an atomic write into the cache_dir | ||
1331 | * As such: create a temporary copy in cache_dir/txn and then do a | ||
1332 | * `rename` (which is atomic) to the actual cache path | ||
1333 | * | ||
1334 | * @returns true on success, otherwise false | ||
1335 | */ | ||
1336 | ✗ | bool WritableCatalogManager::CopyCatalogToLocalCache( | |
1337 | const upload::SpoolerResult &result) { | ||
1338 | ✗ | std::string tmp_catalog_path; | |
1339 | ✗ | const std::string cache_catalog_path = dir_cache_ + "/" | |
1340 | ✗ | + result.content_hash.MakePathWithoutSuffix(); | |
1341 | ✗ | FILE *fcatalog = CreateTempFile(dir_cache_ + "/txn/catalog", 0666, | |
1342 | "w", &tmp_catalog_path); | ||
1343 | ✗ | if (!fcatalog) { | |
1344 | ✗ | PANIC(kLogDebug | kLogStderr, | |
1345 | "Creating file for temporary catalog failed: %s", | ||
1346 | tmp_catalog_path.c_str()); | ||
1347 | } | ||
1348 | ✗ | CopyPath2File(result.local_path.c_str(), fcatalog); | |
1349 | ✗ | (void) fclose(fcatalog); | |
1350 | |||
1351 | ✗ | if (rename(tmp_catalog_path.c_str(), cache_catalog_path.c_str()) != 0) { | |
1352 | ✗ | PANIC(kLogDebug | kLogStderr, | |
1353 | "Failed to copy catalog from %s to cache %s", | ||
1354 | result.local_path.c_str(), cache_catalog_path.c_str()); | ||
1355 | } | ||
1356 | ✗ | return true; | |
1357 | } | ||
1358 | |||
1359 | 54 | void WritableCatalogManager::CatalogUploadCallback( | |
1360 | const upload::SpoolerResult &result, | ||
1361 | const CatalogUploadContext catalog_upload_context) { | ||
1362 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 54 times.
|
54 | if (result.return_code != 0) { |
1363 | ✗ | PANIC(kLogStderr, "failed to upload '%s' (retval: %d)", | |
1364 | result.local_path.c_str(), result.return_code); | ||
1365 | } | ||
1366 | |||
1367 | // retrieve the catalog object based on the callback information | ||
1368 | // see WritableCatalogManager::ScheduleCatalogProcessing() | ||
1369 | 54 | WritableCatalog *catalog = NULL; | |
1370 | { | ||
1371 | 54 | MutexLockGuard guard(catalog_processing_lock_); | |
1372 | std::map<std::string, WritableCatalog*>::iterator c = | ||
1373 |
1/2✓ Branch 1 taken 54 times.
✗ Branch 2 not taken.
|
54 | catalog_processing_map_.find(result.local_path); |
1374 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 54 times.
|
54 | assert(c != catalog_processing_map_.end()); |
1375 | 54 | catalog = c->second; | |
1376 | 54 | } | |
1377 | |||
1378 | 54 | uint64_t catalog_size = GetFileSize(result.local_path); | |
1379 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 54 times.
|
54 | assert(catalog_size > 0); |
1380 | |||
1381 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 54 times.
|
54 | if (UseLocalCache()) { |
1382 | ✗ | CopyCatalogToLocalCache(result); | |
1383 | } | ||
1384 | |||
1385 | 54 | SyncLock(); | |
1386 |
2/2✓ Branch 1 taken 32 times.
✓ Branch 2 taken 22 times.
|
54 | if (catalog->HasParent()) { |
1387 | // finalized nested catalogs will update their parent's pointer and schedule | ||
1388 | // them for processing (continuation) if the 'dirty children count' == 0 | ||
1389 | 32 | LogCvmfs(kLogCatalog, kLogVerboseMsg, "updating nested catalog link"); | |
1390 | 32 | WritableCatalog *parent = catalog->GetWritableParent(); | |
1391 | |||
1392 |
2/4✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 32 times.
✗ Branch 5 not taken.
|
32 | parent->UpdateNestedCatalog(catalog->mountpoint().ToString(), |
1393 | 32 | result.content_hash, | |
1394 | catalog_size, | ||
1395 | 32 | catalog->delta_counters_); | |
1396 | 32 | catalog->delta_counters_.SetZero(); | |
1397 | |||
1398 | const int remaining_dirty_children = | ||
1399 | 32 | catalog->GetWritableParent()->DecrementDirtyChildren(); | |
1400 | |||
1401 | 32 | SyncUnlock(); | |
1402 | |||
1403 | // continuation of the dirty catalog tree traversal | ||
1404 | // see WritableCatalogManager::SnapshotCatalogs() | ||
1405 |
2/2✓ Branch 0 taken 21 times.
✓ Branch 1 taken 11 times.
|
32 | if (remaining_dirty_children == 0) { |
1406 | 21 | FinalizeCatalog(parent, catalog_upload_context.stop_for_tweaks); | |
1407 | 21 | ScheduleCatalogProcessing(parent); | |
1408 | } | ||
1409 | |||
1410 |
1/2✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
|
22 | } else if (catalog->IsRoot()) { |
1411 | // once the root catalog is reached, we are done with processing and report | ||
1412 | // back to the main via a Future<> and provide the necessary information | ||
1413 |
1/2✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
|
22 | CatalogInfo root_catalog_info; |
1414 | 22 | root_catalog_info.size = catalog_size; | |
1415 |
1/2✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
|
22 | root_catalog_info.ttl = catalog->GetTTL(); |
1416 | 22 | root_catalog_info.content_hash = result.content_hash; | |
1417 |
1/2✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
|
22 | root_catalog_info.revision = catalog->GetRevision(); |
1418 |
1/2✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
|
22 | catalog_upload_context.root_catalog_info->Set(root_catalog_info); |
1419 | 22 | SyncUnlock(); | |
1420 | } else { | ||
1421 | ✗ | PANIC(kLogStderr, "inconsistent state detected"); | |
1422 | } | ||
1423 | 54 | } | |
1424 | |||
1425 | |||
1426 | /** | ||
1427 | * Finds dirty catalogs that can be snapshot right away and annotates all the | ||
1428 | * other catalogs with their number of dirty descendants. | ||
1429 | * Note that there is a convenience wrapper to start the recursion: | ||
1430 | * WritableCatalogManager::GetModifiedCatalogLeafs() | ||
1431 | * | ||
1432 | * @param catalog the catalog for this recursion step | ||
1433 | * @param result the result list to be appended to | ||
1434 | * @return true if 'catalog' is dirty | ||
1435 | */ | ||
1436 | 55 | bool WritableCatalogManager::GetModifiedCatalogLeafsRecursively( | |
1437 | Catalog *catalog, | ||
1438 | WritableCatalogList *result) const { | ||
1439 | 55 | WritableCatalog *wr_catalog = static_cast<WritableCatalog *>(catalog); | |
1440 | |||
1441 | // Look for dirty catalogs in the descendants of *catalog | ||
1442 | 55 | int dirty_children = 0; | |
1443 |
1/2✓ Branch 1 taken 55 times.
✗ Branch 2 not taken.
|
55 | CatalogList children = wr_catalog->GetChildren(); |
1444 | 55 | CatalogList::const_iterator i = children.begin(); | |
1445 | 55 | const CatalogList::const_iterator iend = children.end(); | |
1446 |
2/2✓ Branch 2 taken 32 times.
✓ Branch 3 taken 55 times.
|
87 | for (; i != iend; ++i) { |
1447 |
2/4✓ Branch 2 taken 32 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 32 times.
✗ Branch 5 not taken.
|
32 | if (GetModifiedCatalogLeafsRecursively(*i, result)) { |
1448 | 32 | ++dirty_children; | |
1449 | } | ||
1450 | } | ||
1451 | |||
1452 | // a catalog is dirty if itself or one of its children has changed | ||
1453 | // a leaf catalog doesn't have any dirty children | ||
1454 | 55 | wr_catalog->set_dirty_children(dirty_children); | |
1455 |
1/4✗ Branch 1 not taken.
✓ Branch 2 taken 55 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
|
55 | const bool is_dirty = wr_catalog->IsDirty() || dirty_children > 0; |
1456 | 55 | const bool is_leaf = dirty_children == 0; | |
1457 |
3/4✓ Branch 0 taken 55 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 34 times.
✓ Branch 3 taken 21 times.
|
55 | if (is_dirty && is_leaf) { |
1458 |
1/2✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
|
34 | result->push_back(const_cast<WritableCatalog *>(wr_catalog)); |
1459 | } | ||
1460 | |||
1461 | 55 | return is_dirty; | |
1462 | 55 | } | |
1463 | |||
1464 | |||
1465 | ✗ | void WritableCatalogManager::DoBalance() { | |
1466 | ✗ | CatalogList catalog_list = GetCatalogs(); | |
1467 | ✗ | reverse(catalog_list.begin(), catalog_list.end()); | |
1468 | ✗ | for (unsigned i = 0; i < catalog_list.size(); ++i) { | |
1469 | ✗ | FixWeight(static_cast<WritableCatalog*>(catalog_list[i])); | |
1470 | } | ||
1471 | } | ||
1472 | |||
1473 | ✗ | void WritableCatalogManager::FixWeight(WritableCatalog* catalog) { | |
1474 | // firstly check underflow because they can provoke overflows | ||
1475 | ✗ | if (catalog->GetNumEntries() < min_weight_ && | |
1476 | ✗ | !catalog->IsRoot() && | |
1477 | ✗ | catalog->IsAutogenerated()) { | |
1478 | ✗ | LogCvmfs(kLogCatalog, kLogStdout, | |
1479 | "Deleting an autogenerated catalog in '%s'", | ||
1480 | ✗ | catalog->mountpoint().c_str()); | |
1481 | // Remove the .cvmfscatalog and .cvmfsautocatalog files first | ||
1482 | ✗ | string path = catalog->mountpoint().ToString(); | |
1483 | ✗ | catalog->RemoveEntry(path + "/.cvmfscatalog"); | |
1484 | ✗ | catalog->RemoveEntry(path + "/.cvmfsautocatalog"); | |
1485 | // Remove the actual catalog | ||
1486 | ✗ | string catalog_path = catalog->mountpoint().ToString().substr(1); | |
1487 | ✗ | RemoveNestedCatalog(catalog_path); | |
1488 | ✗ | } else if (catalog->GetNumEntries() > max_weight_) { | |
1489 | ✗ | CatalogBalancer<WritableCatalogManager> catalog_balancer(this); | |
1490 | ✗ | catalog_balancer.Balance(catalog); | |
1491 | } | ||
1492 | } | ||
1493 | |||
1494 | |||
1495 | //**************************************************************************** | ||
1496 | // Workaround -- Serialized Catalog Committing | ||
1497 | |||
1498 | ✗ | int WritableCatalogManager::GetModifiedCatalogsRecursively( | |
1499 | const Catalog *catalog, | ||
1500 | WritableCatalogList *result) const | ||
1501 | { | ||
1502 | // A catalog must be snapshot, if itself or one of it's descendants is dirty. | ||
1503 | // So we traverse the catalog tree recursively and look for dirty catalogs | ||
1504 | // on the way. | ||
1505 | ✗ | const WritableCatalog *wr_catalog = | |
1506 | static_cast<const WritableCatalog *>(catalog); | ||
1507 | // This variable will contain the number of dirty catalogs in the sub tree | ||
1508 | // with *catalog as it's root. | ||
1509 | ✗ | int dirty_catalogs = (wr_catalog->IsDirty()) ? 1 : 0; | |
1510 | |||
1511 | // Look for dirty catalogs in the descendants of *catalog | ||
1512 | ✗ | CatalogList children = wr_catalog->GetChildren(); | |
1513 | ✗ | for (CatalogList::const_iterator i = children.begin(), iEnd = children.end(); | |
1514 | ✗ | i != iEnd; ++i) | |
1515 | { | ||
1516 | ✗ | dirty_catalogs += GetModifiedCatalogsRecursively(*i, result); | |
1517 | } | ||
1518 | |||
1519 | // If we found a dirty catalog in the checked sub tree, the root (*catalog) | ||
1520 | // must be snapshot and ends up in the result list | ||
1521 | ✗ | if (dirty_catalogs > 0) | |
1522 | ✗ | result->push_back(const_cast<WritableCatalog *>(wr_catalog)); | |
1523 | |||
1524 | // tell the upper layer about number of catalogs | ||
1525 | ✗ | return dirty_catalogs; | |
1526 | } | ||
1527 | |||
1528 | |||
1529 | ✗ | void WritableCatalogManager::CatalogUploadSerializedCallback( | |
1530 | const upload::SpoolerResult &result, | ||
1531 | const CatalogUploadContext unused) | ||
1532 | { | ||
1533 | ✗ | if (result.return_code != 0) { | |
1534 | ✗ | PANIC(kLogStderr, "failed to upload '%s' (retval: %d)", | |
1535 | result.local_path.c_str(), result.return_code); | ||
1536 | } | ||
1537 | |||
1538 | ✗ | if (UseLocalCache()) { | |
1539 | ✗ | CopyCatalogToLocalCache(result); | |
1540 | } | ||
1541 | |||
1542 | ✗ | unlink(result.local_path.c_str()); | |
1543 | } | ||
1544 | |||
1545 | |||
1546 | WritableCatalogManager::CatalogInfo | ||
1547 | ✗ | WritableCatalogManager::SnapshotCatalogsSerialized( | |
1548 | const bool stop_for_tweaks) | ||
1549 | { | ||
1550 | ✗ | LogCvmfs(kLogCvmfs, kLogStdout, "Serialized committing of file catalogs..."); | |
1551 | ✗ | reinterpret_cast<WritableCatalog *>(GetRootCatalog())->SetDirty(); | |
1552 | ✗ | WritableCatalogList catalogs_to_snapshot; | |
1553 | ✗ | GetModifiedCatalogs(&catalogs_to_snapshot); | |
1554 | CatalogUploadContext unused; | ||
1555 | ✗ | unused.root_catalog_info = NULL; | |
1556 | ✗ | unused.stop_for_tweaks = false; | |
1557 | ✗ | spooler_->RegisterListener( | |
1558 | &WritableCatalogManager::CatalogUploadSerializedCallback, this, unused); | ||
1559 | |||
1560 | ✗ | CatalogInfo root_catalog_info; | |
1561 | ✗ | WritableCatalogList::const_iterator i = catalogs_to_snapshot.begin(); | |
1562 | ✗ | const WritableCatalogList::const_iterator iend = catalogs_to_snapshot.end(); | |
1563 | ✗ | for (; i != iend; ++i) { | |
1564 | ✗ | FinalizeCatalog(*i, stop_for_tweaks); | |
1565 | |||
1566 | // Compress and upload catalog | ||
1567 | ✗ | shash::Any hash_catalog(spooler_->GetHashAlgorithm(), | |
1568 | ✗ | shash::kSuffixCatalog); | |
1569 | ✗ | if (!zlib::CompressPath2Null((*i)->database_path(), | |
1570 | &hash_catalog)) | ||
1571 | { | ||
1572 | ✗ | PANIC(kLogStderr, "could not compress catalog %s", | |
1573 | (*i)->mountpoint().ToString().c_str()); | ||
1574 | } | ||
1575 | |||
1576 | ✗ | int64_t catalog_size = GetFileSize((*i)->database_path()); | |
1577 | ✗ | assert(catalog_size > 0); | |
1578 | |||
1579 | ✗ | if ((*i)->HasParent()) { | |
1580 | ✗ | LogCvmfs(kLogCatalog, kLogVerboseMsg, "updating nested catalog link"); | |
1581 | ✗ | WritableCatalog *parent = (*i)->GetWritableParent(); | |
1582 | ✗ | parent->UpdateNestedCatalog((*i)->mountpoint().ToString(), hash_catalog, | |
1583 | ✗ | catalog_size, (*i)->delta_counters_); | |
1584 | ✗ | (*i)->delta_counters_.SetZero(); | |
1585 | ✗ | } else if ((*i)->IsRoot()) { | |
1586 | ✗ | root_catalog_info.size = catalog_size; | |
1587 | ✗ | root_catalog_info.ttl = (*i)->GetTTL(); | |
1588 | ✗ | root_catalog_info.content_hash = hash_catalog; | |
1589 | ✗ | root_catalog_info.revision = (*i)->GetRevision(); | |
1590 | } else { | ||
1591 | ✗ | PANIC(kLogStderr, "inconsistent state detected"); | |
1592 | } | ||
1593 | |||
1594 | ✗ | spooler_->ProcessCatalog((*i)->database_path()); | |
1595 | } | ||
1596 | ✗ | spooler_->WaitForUpload(); | |
1597 | |||
1598 | ✗ | spooler_->UnregisterListeners(); | |
1599 | ✗ | return root_catalog_info; | |
1600 | } | ||
1601 | |||
1602 | } // namespace catalog | ||
1603 |