GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/cvmfs_fsck.cc
Date: 2026-04-26 02:35:59
Exec Total Coverage
Lines: 0 177 0.0%
Branches: 0 120 0.0%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 *
4 * This tool checks a cvmfs cache directory for consistency.
5 * If necessary, the managed cache db is removed so that
6 * it will be rebuilt on next mount.
7 */
8
9 #define _FILE_OFFSET_BITS 64
10
11
12 #include <dirent.h>
13 #include <errno.h>
14 #include <fcntl.h>
15 #include <pthread.h>
16 #include <stdint.h>
17 #include <sys/stat.h>
18 #include <unistd.h>
19
20 #include <cstdio>
21 #include <cstdlib>
22 #include <cstring>
23 #include <string>
24
25 #include "compression/compression.h"
26 #include "crypto/hash.h"
27 #include "util/atomic.h"
28 #include "util/logging.h"
29 #include "util/platform.h"
30 #include "util/posix.h"
31 #include "util/smalloc.h"
32
33 using namespace std; // NOLINT
34
35 enum Errors {
36 kErrorOk = 0,
37 kErrorFixed = 1,
38 kErrorReboot = 2,
39 kErrorUnfixed = 4,
40 kErrorOperational = 8,
41 kErrorUsage = 16,
42 };
43
44 string *g_cache_dir;
45 atomic_int32 g_num_files;
46 atomic_int32 g_num_err_fixed;
47 atomic_int32 g_num_err_unfixed;
48 atomic_int32 g_num_err_operational;
49 atomic_int32 g_num_tmp_catalog;
50 /**
51 * Traversal of the file system tree is serialized.
52 */
53 pthread_mutex_t g_lock_traverse = PTHREAD_MUTEX_INITIALIZER;
54 DIR *g_DIRP_current = NULL;
55 int g_num_dirs = -1; /**< Number of cache directories already examined. */
56 string *g_current_dir; /**< Current cache sub directory */
57
58 int g_num_threads = 1;
59 bool g_fix_errors = false;
60 bool g_verbose = false;
61 atomic_int32 g_force_rebuild;
62 atomic_int32 g_modified_cache;
63
64
65 static void Usage() {
66 LogCvmfs(kLogCvmfs, kLogStdout,
67 "CernVM File System consistency checker, version %s\n\n"
68 "This tool checks a cvmfs cache directory for consistency.\n"
69 "If necessary, the managed cache db is removed so that\n"
70 "it will be rebuilt on next mount.\n\n"
71 "Usage: cvmfs_fsck [-v] [-p] [-f] [-j #threads] <cache directory>\n"
72 "Options:\n"
73 " -v verbose output\n"
74 " -p try to fix automatically\n"
75 " -f force rebuild of managed cache db on next mount\n"
76 " -j number of concurrent integrity check worker threads\n",
77 CVMFS_VERSION);
78 }
79
80
81 static bool GetNextFile(string *relative_path, string *hash_name) {
82 platform_dirent64 *d = NULL;
83
84 const MutexLockGuard m(&g_lock_traverse);
85 get_next_file_again:
86 while (g_DIRP_current && ((d = platform_readdir(g_DIRP_current)) != NULL)) {
87 const string name = d->d_name;
88 if ((name == ".") || (name == ".."))
89 continue;
90
91 platform_stat64 info;
92 *relative_path = *g_current_dir + "/" + name;
93 *hash_name = *g_current_dir + name;
94 const string path = *g_cache_dir + "/" + *relative_path;
95 if (platform_lstat(relative_path->c_str(), &info) != 0) {
96 LogCvmfs(kLogCvmfs, kLogStdout, "Warning: failed to stat() %s (%d)",
97 path.c_str(), errno);
98 continue;
99 }
100
101 if (!S_ISREG(info.st_mode)) {
102 LogCvmfs(kLogCvmfs, kLogStdout, "Warning: %s is not a regular file",
103 path.c_str());
104 continue;
105 }
106
107 break;
108 }
109
110 if (!d) {
111 if (g_DIRP_current) {
112 closedir(g_DIRP_current);
113 g_DIRP_current = NULL;
114 }
115 g_num_dirs++;
116 if (g_num_dirs < 256) {
117 char hex[3];
118 snprintf(hex, sizeof(hex), "%02x", g_num_dirs);
119 *g_current_dir = string(hex, 2);
120
121 if (g_verbose)
122 LogCvmfs(kLogCvmfs, kLogStdout, "Entering %s", g_current_dir->c_str());
123 if ((g_DIRP_current = opendir(hex)) == NULL) {
124 LogCvmfs(kLogCvmfs, kLogStderr,
125 "Invalid cache directory, %s/%s does not exist",
126 g_cache_dir->c_str(), g_current_dir->c_str());
127 exit(kErrorUnfixed);
128 }
129 goto get_next_file_again;
130 }
131 }
132
133 return d != NULL;
134 }
135
136
137 static void *MainCheck(void *data __attribute__((unused))) {
138 string relative_path;
139 string hash_name;
140
141 while (GetNextFile(&relative_path, &hash_name)) {
142 const string path = *g_cache_dir + "/" + relative_path;
143
144 const int n = atomic_xadd32(&g_num_files, 1);
145 if (g_verbose)
146 LogCvmfs(kLogCvmfs, kLogStdout, "Checking file %s", path.c_str());
147 if (!g_verbose && ((n % 1000) == 0))
148 LogCvmfs(kLogCvmfs, kLogStdout | kLogNoLinebreak, ".");
149
150 if (relative_path[relative_path.length() - 1] == 'T') {
151 LogCvmfs(kLogCvmfs, kLogStdout,
152 "Warning: temporary file catalog %s found", path.c_str());
153 atomic_inc32(&g_num_tmp_catalog);
154 if (g_fix_errors) {
155 if (unlink(relative_path.c_str()) == 0) {
156 LogCvmfs(kLogCvmfs, kLogStdout, "Fix: %s unlinked", path.c_str());
157 atomic_inc32(&g_num_err_fixed);
158 } else {
159 LogCvmfs(kLogCvmfs, kLogStdout, "Error: failed to unlink %s",
160 path.c_str());
161 atomic_inc32(&g_num_err_unfixed);
162 }
163 }
164 continue;
165 }
166
167 const int fd_src = open(relative_path.c_str(), O_RDONLY);
168 if (fd_src < 0) {
169 LogCvmfs(kLogCvmfs, kLogStdout, "Error: cannot open %s", path.c_str());
170 atomic_inc32(&g_num_err_operational);
171 continue;
172 }
173 // Don't thrash kernel buffers
174 platform_disable_kcache(fd_src);
175
176 // Compress every file and calculate SHA-1 of stream
177 const shash::Any expected_hash = shash::MkFromHexPtr(
178 shash::HexPtr(hash_name));
179 shash::Any hash(expected_hash.algorithm);
180 if (!zlib::CompressFd2Null(fd_src, &hash)) {
181 LogCvmfs(kLogCvmfs, kLogStdout, "Error: could not compress %s",
182 path.c_str());
183 atomic_inc32(&g_num_err_operational);
184 } else {
185 if (hash != expected_hash) {
186 // If the hashes don't match, try hashing the uncompressed file
187 if (!shash::HashFile(relative_path, &hash)) {
188 LogCvmfs(kLogCvmfs, kLogStdout, "Error: could not hash %s",
189 path.c_str());
190 atomic_inc32(&g_num_err_operational);
191 }
192 if (hash != expected_hash) {
193 if (g_fix_errors) {
194 const string quarantaine_path = "./quarantaine/" + hash_name;
195 bool fixed = false;
196 if (rename(relative_path.c_str(), quarantaine_path.c_str()) == 0) {
197 LogCvmfs(kLogCvmfs, kLogStdout,
198 "Fix: %s is corrupted, moved to quarantaine folder",
199 path.c_str());
200 fixed = true;
201 } else {
202 LogCvmfs(kLogCvmfs, kLogStdout,
203 "Warning: failed to move %s into quarantaine folder",
204 path.c_str());
205 if (unlink(relative_path.c_str()) == 0) {
206 LogCvmfs(kLogCvmfs, kLogStdout,
207 "Fix: %s is corrupted, file unlinked", path.c_str());
208 fixed = true;
209 } else {
210 LogCvmfs(kLogCvmfs, kLogStdout,
211 "Error: %s is corrupted, could not unlink",
212 path.c_str());
213 }
214 }
215
216 if (fixed) {
217 atomic_inc32(&g_num_err_fixed);
218
219 // Changes made, we have to rebuild the managed cache db
220 atomic_cas32(&g_force_rebuild, 0, 1);
221 atomic_cas32(&g_modified_cache, 0, 1);
222 } else {
223 atomic_inc32(&g_num_err_unfixed);
224 }
225 } else {
226 LogCvmfs(kLogCvmfs, kLogStdout,
227 "Error: %s has compressed checksum %s, "
228 "delete this file from cache directory!",
229 path.c_str(), hash.ToString().c_str());
230 atomic_inc32(&g_num_err_unfixed);
231 }
232 }
233 }
234 }
235 close(fd_src);
236 }
237
238 return NULL;
239 }
240
241
242 int main(int argc, char **argv) {
243 atomic_init32(&g_force_rebuild);
244 atomic_init32(&g_modified_cache);
245 g_current_dir = new string();
246
247 int c;
248 while ((c = getopt(argc, argv, "hvpfj:")) != -1) {
249 switch (c) {
250 case 'h':
251 Usage();
252 return kErrorOk;
253 case 'v':
254 g_verbose = true;
255 break;
256 case 'p':
257 g_fix_errors = true;
258 break;
259 case 'f':
260 atomic_cas32(&g_force_rebuild, 0, 1);
261 break;
262 case 'j':
263 g_num_threads = atoi(optarg);
264 if (g_num_threads < 1) {
265 LogCvmfs(kLogCvmfs, kLogStdout,
266 "There is at least one worker thread required");
267 return kErrorUsage;
268 }
269 break;
270 case '?':
271 default:
272 Usage();
273 return kErrorUsage;
274 }
275 }
276
277 // Switch to cache directory
278 if (optind >= argc) {
279 Usage();
280 return kErrorUsage;
281 }
282 g_cache_dir = new string(MakeCanonicalPath(argv[optind]));
283 if (chdir(g_cache_dir->c_str()) != 0) {
284 LogCvmfs(kLogCvmfs, kLogStderr, "Could not chdir to %s",
285 g_cache_dir->c_str());
286 return kErrorOperational;
287 }
288
289 // Check if txn directory is empty
290 DIR *dirp_txn;
291 if ((dirp_txn = opendir("txn")) == NULL) {
292 LogCvmfs(kLogCvmfs, kLogStderr,
293 "Invalid cache directory, %s/txn does not exist",
294 g_cache_dir->c_str());
295 return kErrorOperational;
296 }
297 platform_dirent64 *d;
298 while ((d = platform_readdir(dirp_txn)) != NULL) {
299 const string name = d->d_name;
300 if ((name == ".") || (name == ".."))
301 continue;
302
303 LogCvmfs(kLogCvmfs, kLogStdout,
304 "Warning: temporary directory %s/txn is not empty\n"
305 "If this repository is currently _not_ mounted, "
306 "you can remove its contents",
307 g_cache_dir->c_str());
308 break;
309 }
310 closedir(dirp_txn);
311
312 // Run workers to recalculate checksums
313 atomic_init32(&g_num_files);
314 atomic_init32(&g_num_err_fixed);
315 atomic_init32(&g_num_err_unfixed);
316 atomic_init32(&g_num_err_operational);
317 atomic_init32(&g_num_tmp_catalog);
318 pthread_t *workers = reinterpret_cast<pthread_t *>(
319 smalloc(g_num_threads * sizeof(pthread_t)));
320 if (!g_verbose)
321 LogCvmfs(kLogCvmfs, kLogStdout | kLogNoLinebreak, "Verifying: ");
322 for (int i = 0; i < g_num_threads; ++i) {
323 if (g_verbose)
324 LogCvmfs(kLogCvmfs, kLogStdout, "Starting worker %d", i + 1);
325 if (pthread_create(&workers[i], NULL, MainCheck, NULL) != 0) {
326 LogCvmfs(kLogCvmfs, kLogStdout, "Fatal: could not create worker thread");
327 return kErrorOperational;
328 }
329 }
330 for (int i = g_num_threads - 1; i >= 0; --i) {
331 pthread_join(workers[i], NULL);
332 if (g_verbose)
333 LogCvmfs(kLogCvmfs, kLogStdout, "Stopping worker %d", i + 1);
334 }
335 free(workers);
336 if (!g_verbose)
337 LogCvmfs(kLogCvmfs, kLogStdout | kLogNoLinebreak, "\n");
338 LogCvmfs(kLogCvmfs, kLogStdout, "Verified %d files",
339 atomic_read32(&g_num_files));
340
341 if (atomic_read32(&g_num_tmp_catalog) > 0)
342 LogCvmfs(kLogCvmfs, kLogStdout, "Temporary file catalogs were found.");
343
344 if (atomic_read32(&g_force_rebuild)) {
345 if (unlink("cachedb") == 0) {
346 LogCvmfs(kLogCvmfs, kLogStdout,
347 "Fix: managed cache db unlinked, will be rebuilt on next mount");
348 atomic_inc32(&g_num_err_fixed);
349 } else {
350 if (errno != ENOENT) {
351 LogCvmfs(kLogCvmfs, kLogStdout,
352 "Error: could not unlink managed cache database (%d)", errno);
353 atomic_inc32(&g_num_err_unfixed);
354 }
355 }
356 }
357
358 if (atomic_read32(&g_modified_cache)) {
359 LogCvmfs(kLogCvmfs, kLogStdout,
360 "\n"
361 "WARNING: There might by corrupted files in the kernel buffers.\n"
362 "Remount CernVM-FS or run 'echo 3 > /proc/sys/vm/drop_caches'"
363 "\n\n");
364 }
365
366 int retval = 0;
367 if (atomic_read32(&g_num_err_fixed) > 0)
368 retval |= kErrorFixed;
369 if (atomic_read32(&g_num_err_unfixed) > 0)
370 retval |= kErrorUnfixed;
371 if (atomic_read32(&g_num_err_operational) > 0)
372 retval |= kErrorOperational;
373
374 return retval;
375 }
376