GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/libcvmfs.cc
Date: 2026-04-19 02:41:37
Exec Total Coverage
Lines: 155 278 55.8%
Branches: 84 258 32.6%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 *
4 * libcvmfs provides an API for the CernVM-FS client. This is an
5 * alternative to FUSE for reading a remote CernVM-FS repository.
6 */
7
8 #define _FILE_OFFSET_BITS 64
9
10 #ifndef __STDC_FORMAT_MACROS
11 #define __STDC_FORMAT_MACROS
12 #endif
13
14
15 #include "libcvmfs.h"
16
17 #include <errno.h>
18 #include <inttypes.h>
19 #include <stddef.h>
20 #include <sys/stat.h>
21
22 #include <cassert>
23 #include <cstdlib>
24 #include <string>
25
26 #include "libcvmfs_int.h"
27 #include "statistics.h"
28 #include "util/logging.h"
29 #include "util/posix.h"
30 #include "xattr.h"
31
32 using namespace std; // NOLINT
33
34 /**
35 * Create the cvmfs_attr struct which contains the same information
36 * as a stat, but also has pointers to the hash, symlink, and name.
37 */
38 102 struct cvmfs_attr *cvmfs_attr_init() {
39 struct cvmfs_attr *attr;
40 102 attr = reinterpret_cast<cvmfs_attr *>(calloc(1, sizeof(*attr)));
41 102 attr->version = 1;
42 102 attr->size = sizeof(*attr);
43 102 return attr;
44 }
45
46
47 /**
48 * Destroy the cvmfs_attr struct and frees the checksum, symlink,
49 * name, and xattrs.
50 */
51 102 void cvmfs_attr_free(struct cvmfs_attr *attr) {
52
1/2
✓ Branch 0 taken 102 times.
✗ Branch 1 not taken.
102 if (attr) {
53 102 free(attr->cvm_checksum);
54 102 free(attr->cvm_symlink);
55 102 free(attr->cvm_name);
56 102 free(attr->cvm_parent);
57
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 102 times.
102 delete reinterpret_cast<XattrList *>(attr->cvm_xattrs);
58 }
59 102 free(attr);
60 102 }
61
62
63 105 struct cvmfs_nc_attr *cvmfs_nc_attr_init() {
64 struct cvmfs_nc_attr *attr;
65 105 attr = reinterpret_cast<cvmfs_nc_attr *>(calloc(1, sizeof(*attr)));
66 105 return attr;
67 }
68
69 105 void cvmfs_nc_attr_free(struct cvmfs_nc_attr *nc_attr) {
70
1/2
✓ Branch 0 taken 105 times.
✗ Branch 1 not taken.
105 if (nc_attr) {
71 105 free(nc_attr->mountpoint);
72 105 free(nc_attr->hash);
73 }
74 105 free(nc_attr);
75 105 }
76
77
78 /**
79 * Expand symlinks in all levels of a path. Also, expand ".." and ".". This
80 * also has the side-effect of ensuring that cvmfs_getattr() is called on all
81 * parent paths, which is needed to ensure proper loading of nested catalogs
82 * before the child is accessed.
83 */
84 755 static int expand_path(const int depth,
85 LibContext *ctx,
86 char const *path,
87 string *expanded_path) {
88
2/4
✓ Branch 2 taken 755 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 755 times.
✗ Branch 6 not taken.
1510 const string p_path = GetParentPath(path);
89
2/4
✓ Branch 2 taken 755 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 755 times.
✗ Branch 6 not taken.
1510 const string fname = GetFileName(path);
90 int rc;
91
92
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 755 times.
755 if (fname == "..") {
93 rc = expand_path(depth, ctx, p_path.c_str(), expanded_path);
94 if (rc != 0) {
95 return -1;
96 }
97 if (*expanded_path == "/") {
98 // attempt to access parent path of the root of the repository
99 LogCvmfs(kLogCvmfs, kLogDebug,
100 "libcvmfs cannot resolve symlinks to paths outside of the repo: "
101 "%s",
102 path);
103 errno = ENOENT;
104 return -1;
105 }
106 *expanded_path = GetParentPath(*expanded_path);
107 if (*expanded_path == "") {
108 *expanded_path = "/";
109 }
110 return 0;
111 }
112
113 755 string buf;
114
2/2
✓ Branch 1 taken 310 times.
✓ Branch 2 taken 445 times.
755 if (p_path != "") {
115
1/2
✓ Branch 2 taken 310 times.
✗ Branch 3 not taken.
310 rc = expand_path(depth, ctx, p_path.c_str(), &buf);
116
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 310 times.
310 if (rc != 0) {
117 return -1;
118 }
119
120
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 310 times.
310 if (fname == ".") {
121 *expanded_path = buf;
122 return 0;
123 }
124 }
125
126
5/8
✓ Branch 1 taken 310 times.
✓ Branch 2 taken 445 times.
✓ Branch 5 taken 310 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 310 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 755 times.
✗ Branch 10 not taken.
755 if (buf.length() == 0 || buf[buf.length() - 1] != '/') {
127
1/2
✓ Branch 1 taken 755 times.
✗ Branch 2 not taken.
755 buf += "/";
128 }
129
1/2
✓ Branch 1 taken 755 times.
✗ Branch 2 not taken.
755 buf += fname;
130
131 struct stat st;
132
1/2
✓ Branch 2 taken 755 times.
✗ Branch 3 not taken.
755 rc = ctx->GetAttr(buf.c_str(), &st);
133
2/2
✓ Branch 0 taken 34 times.
✓ Branch 1 taken 721 times.
755 if (rc != 0) {
134 34 errno = -rc;
135 34 return -1;
136 }
137
138
1/2
✓ Branch 0 taken 721 times.
✗ Branch 1 not taken.
721 if (!S_ISLNK(st.st_mode)) {
139
1/2
✓ Branch 1 taken 721 times.
✗ Branch 2 not taken.
721 *expanded_path = buf;
140 721 return 0;
141 }
142
143 if (depth > 1000) {
144 // avoid unbounded recursion due to symlinks
145 LogCvmfs(kLogCvmfs, kLogDebug,
146 "libcvmfs hit its symlink recursion limit: %s", path);
147 errno = ELOOP;
148 return -1;
149 }
150
151 // expand symbolic link
152
153 char *ln_buf = reinterpret_cast<char *>(alloca(st.st_size + 2));
154 if (!ln_buf) {
155 errno = ENOMEM;
156 return -1;
157 }
158 rc = ctx->Readlink(buf.c_str(), ln_buf, st.st_size + 2);
159 if (rc != 0) {
160 errno = -rc;
161 return -1;
162 }
163 if (ln_buf[0] == '/') {
164 // symlink is absolute path, strip /cvmfs/$repo
165 const unsigned len = ctx->mount_point()->fqrn().length();
166 if (strncmp(ln_buf, ctx->mount_point()->fqrn().c_str(), len) == 0
167 && (ln_buf[len] == '/' || ln_buf[len] == '\0')) {
168 buf = ln_buf + len;
169 if (ln_buf[len] == '\0') {
170 buf += "/";
171 }
172 } else {
173 LogCvmfs(kLogCvmfs, kLogDebug,
174 "libcvmfs can't resolve symlinks to paths outside of the repo: "
175 "%s --> %s (mountpoint=%s)",
176 path, ln_buf, ctx->mount_point()->fqrn().c_str());
177 errno = ENOENT;
178 return -1;
179 }
180 } else {
181 // symlink is relative path
182 buf = GetParentPath(buf);
183 buf += "/";
184 buf += ln_buf;
185 }
186
187 // In case the symlink references other symlinks or contains ".."
188 // or "." we must now call expand_path on the result.
189
190 return expand_path(depth + 1, ctx, buf.c_str(), expanded_path);
191 755 }
192
193 /**
194 * Like expand_path(), but do not expand the final element of the path.
195 */
196 102 static int expand_ppath(LibContext *ctx,
197 const char *path,
198 string *expanded_path) {
199
2/4
✓ Branch 2 taken 102 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 102 times.
✗ Branch 6 not taken.
204 const string p_path = GetParentPath(path);
200
2/4
✓ Branch 2 taken 102 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 102 times.
✗ Branch 6 not taken.
204 const string fname = GetFileName(path);
201
202
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 102 times.
102 if (p_path == "") {
203 *expanded_path = path;
204 return 0;
205 }
206
207
1/2
✓ Branch 2 taken 102 times.
✗ Branch 3 not taken.
102 const int rc = expand_path(0, ctx, p_path.c_str(), expanded_path);
208
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 102 times.
102 if (rc != 0) {
209 return rc;
210 }
211
212
1/2
✓ Branch 1 taken 102 times.
✗ Branch 2 not taken.
102 (*expanded_path) += "/";
213
1/2
✓ Branch 1 taken 102 times.
✗ Branch 2 not taken.
102 (*expanded_path) += fname;
214
215 102 return 0;
216 102 }
217
218
219 int cvmfs_open(LibContext *ctx, const char *path) {
220 string lpath;
221 int rc;
222 rc = expand_path(0, ctx, path, &lpath);
223 if (rc < 0) {
224 return -1;
225 }
226 path = lpath.c_str();
227
228 rc = ctx->Open(path);
229 if (rc < 0) {
230 errno = -rc;
231 return -1;
232 }
233 return rc;
234 }
235
236
237 ssize_t cvmfs_pread(LibContext *ctx, int fd, void *buf, size_t size,
238 off_t off) {
239 const ssize_t nbytes = ctx->Pread(fd, buf, size, off);
240 if (nbytes < 0) {
241 errno = -nbytes;
242 return -1;
243 }
244 return nbytes;
245 }
246
247
248 int cvmfs_close(LibContext *ctx, int fd) {
249 const int rc = ctx->Close(fd);
250 if (rc < 0) {
251 errno = -rc;
252 return -1;
253 }
254 return 0;
255 }
256
257
258 int cvmfs_readlink(LibContext *ctx, const char *path, char *buf, size_t size) {
259 string lpath;
260 int rc;
261 rc = expand_ppath(ctx, path, &lpath);
262 if (rc < 0) {
263 return -1;
264 }
265 path = lpath.c_str();
266
267 rc = ctx->Readlink(path, buf, size);
268 if (rc < 0) {
269 errno = -rc;
270 return -1;
271 }
272 return 0;
273 }
274
275
276 102 int cvmfs_stat(LibContext *ctx, const char *path, struct stat *st) {
277 102 string lpath;
278 int rc;
279
1/2
✓ Branch 1 taken 102 times.
✗ Branch 2 not taken.
102 rc = expand_path(0, ctx, path, &lpath);
280
2/2
✓ Branch 0 taken 34 times.
✓ Branch 1 taken 68 times.
102 if (rc < 0) {
281 34 return -1;
282 }
283 68 path = lpath.c_str();
284
285
1/2
✓ Branch 1 taken 68 times.
✗ Branch 2 not taken.
68 rc = ctx->GetAttr(path, st);
286
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
68 if (rc < 0) {
287 errno = -rc;
288 return -1;
289 }
290 68 return 0;
291 102 }
292
293
294 int cvmfs_lstat(LibContext *ctx, const char *path, struct stat *st) {
295 string lpath;
296 int rc;
297 rc = expand_ppath(ctx, path, &lpath);
298 if (rc < 0) {
299 return -1;
300 }
301 path = lpath.c_str();
302
303 rc = ctx->GetAttr(path, st);
304 if (rc < 0) {
305 errno = -rc;
306 return -1;
307 }
308 return 0;
309 }
310
311
312 102 int cvmfs_stat_attr(LibContext *ctx,
313 const char *path,
314 struct cvmfs_attr *attr) {
315 102 string lpath;
316 int rc;
317
1/2
✓ Branch 1 taken 102 times.
✗ Branch 2 not taken.
102 rc = expand_ppath(ctx, path, &lpath);
318
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 102 times.
102 if (rc < 0) {
319 return -1;
320 }
321 102 path = lpath.c_str();
322
323
1/2
✓ Branch 1 taken 102 times.
✗ Branch 2 not taken.
102 rc = ctx->GetExtAttr(path, attr);
324
2/2
✓ Branch 0 taken 34 times.
✓ Branch 1 taken 68 times.
102 if (rc < 0) {
325 34 errno = -rc;
326 34 return -1;
327 }
328 68 return 0;
329 102 }
330
331
332 34 int cvmfs_listdir(LibContext *ctx,
333 const char *path,
334 char ***buf,
335 size_t *buflen) {
336 34 string lpath;
337 int rc;
338
1/2
✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
34 rc = expand_path(0, ctx, path, &lpath);
339
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 34 times.
34 if (rc < 0) {
340 return -1;
341 }
342 34 path = lpath.c_str();
343
344 34 size_t listsize = 0;
345
1/2
✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
34 rc = ctx->ListDirectory(path, buf, &listsize, buflen, true);
346
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 34 times.
34 if (rc < 0) {
347 errno = -rc;
348 return -1;
349 }
350 34 return 0;
351 34 }
352
353 int cvmfs_listdir_contents(LibContext *ctx,
354 const char *path,
355 char ***buf,
356 size_t *listlen,
357 size_t *buflen) {
358 string lpath;
359 int rc;
360 rc = expand_path(0, ctx, path, &lpath);
361 if (rc < 0) {
362 return -1;
363 }
364 path = lpath.c_str();
365
366 rc = ctx->ListDirectory(path, buf, listlen, buflen, false);
367 if (rc < 0) {
368 errno = -rc;
369 return -1;
370 }
371 return 0;
372 }
373
374 34 int cvmfs_listdir_stat(LibContext *ctx,
375 const char *path,
376 struct cvmfs_stat_t **buf,
377 size_t *listlen,
378 size_t *buflen) {
379 34 string lpath;
380 int rc;
381
1/2
✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
34 rc = expand_path(0, ctx, path, &lpath);
382
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 34 times.
34 if (rc < 0) {
383 return -1;
384 }
385 34 path = lpath.c_str();
386
387
1/2
✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
34 rc = ctx->ListDirectoryStat(path, buf, listlen, buflen);
388
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 34 times.
34 if (rc < 0) {
389 errno = -rc;
390 return -1;
391 }
392 34 return 0;
393 34 }
394
395
396 105 int cvmfs_stat_nc(LibContext *ctx,
397 const char *path,
398 struct cvmfs_nc_attr *nc_attr) {
399 105 string lpath;
400 int rc;
401
1/2
✓ Branch 1 taken 105 times.
✗ Branch 2 not taken.
105 rc = expand_path(0, ctx, path, &lpath);
402
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 105 times.
105 if (rc < 0) {
403 return -1;
404 }
405 105 path = lpath.c_str();
406
407
1/2
✓ Branch 1 taken 105 times.
✗ Branch 2 not taken.
105 rc = ctx->GetNestedCatalogAttr(path, nc_attr);
408
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 105 times.
105 if (rc < 0) {
409 errno = -rc;
410 return -1;
411 }
412 105 return 0;
413 105 }
414
415
416 68 int cvmfs_list_nc(LibContext *ctx,
417 const char *path,
418 char ***buf,
419 size_t *buflen) {
420 68 string lpath;
421 int rc;
422
1/2
✓ Branch 1 taken 68 times.
✗ Branch 2 not taken.
68 rc = expand_path(0, ctx, path, &lpath);
423
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
68 if (rc < 0) {
424 return -1;
425 }
426 68 path = lpath.c_str();
427
428
1/2
✓ Branch 1 taken 68 times.
✗ Branch 2 not taken.
68 rc = ctx->ListNestedCatalogs(path, buf, buflen);
429
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
68 if (rc < 0) {
430 errno = -rc;
431 return -1;
432 }
433 68 return 0;
434 68 }
435
436
437 102 void cvmfs_list_free(char **buf) {
438 // Quick return if base pointer is NULL
439
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 102 times.
102 if (!buf)
440 return;
441 102 size_t pos = 0;
442 // Iterate over each non-null entry and free
443 // This assumes no null entries, which don't currently exist
444
2/2
✓ Branch 0 taken 442 times.
✓ Branch 1 taken 102 times.
544 while (buf[pos]) {
445 442 free(buf[pos]);
446 442 pos++;
447 }
448 102 free(buf);
449 }
450
451 void cvmfs_enable_threaded(LibContext *ctx) { ctx->EnableMultiThreaded(); }
452
453 274 cvmfs_errors cvmfs_attach_repo_v2(const char *fqrn,
454 SimpleOptionsParser *opts,
455 LibContext **ctx) {
456
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 274 times.
274 assert(ctx != NULL);
457
4/8
✓ Branch 2 taken 274 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 274 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 274 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 274 times.
✗ Branch 12 not taken.
274 opts->SwitchTemplateManager(new DefaultOptionsTemplateManager(fqrn));
458
2/4
✓ Branch 2 taken 274 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 274 times.
✗ Branch 6 not taken.
274 *ctx = LibContext::Create(fqrn, opts);
459
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 274 times.
274 assert(*ctx != NULL);
460 274 const loader::Failures result = (*ctx)->mount_point()->boot_status();
461
2/2
✓ Branch 0 taken 68 times.
✓ Branch 1 taken 206 times.
274 if (result != loader::kFailOk) {
462
1/4
✓ Branch 2 taken 68 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
68 LogCvmfs(kLogCvmfs, kLogStderr, "Attaching %s failed: %s (%d)", fqrn,
463 136 (*ctx)->mount_point()->boot_error().c_str(), result);
464
1/2
✓ Branch 0 taken 68 times.
✗ Branch 1 not taken.
68 delete *ctx;
465 68 *ctx = NULL;
466 }
467 274 return static_cast<cvmfs_errors>(result);
468 }
469
470
471 void cvmfs_adopt_options(cvmfs_context *ctx, SimpleOptionsParser *opts) {
472 ctx->set_options_mgr(opts);
473 }
474
475
476
1/2
✓ Branch 0 taken 206 times.
✗ Branch 1 not taken.
206 void cvmfs_detach_repo(LibContext *ctx) { delete ctx; }
477
478
479 344 cvmfs_errors cvmfs_init_v2(SimpleOptionsParser *opts) {
480 344 const int result = LibGlobals::Initialize(opts);
481
2/2
✓ Branch 0 taken 35 times.
✓ Branch 1 taken 309 times.
344 if (result != loader::kFailOk) {
482
1/4
✓ Branch 2 taken 35 times.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
35 LogCvmfs(kLogCvmfs, kLogStderr, "Initialization failed: %s (%d)",
483 70 LibGlobals::GetInstance()->file_system()->boot_error().c_str(),
484 result);
485 35 LibGlobals::CleanupInstance();
486 }
487 344 return static_cast<cvmfs_errors>(result);
488 }
489
490
491 446 void cvmfs_fini() { LibGlobals::CleanupInstance(); }
492
493
494 static void (*ext_log_fn)(const char *msg) = NULL;
495
496
497 214352 static void libcvmfs_log_fn(const LogSource /*source*/,
498 const int /*mask*/,
499 const char *msg) {
500
1/2
✓ Branch 0 taken 214352 times.
✗ Branch 1 not taken.
214352 if (ext_log_fn) {
501 214352 (*ext_log_fn)(msg);
502 }
503 214352 }
504
505
506 957 void cvmfs_set_log_fn(void (*log_fn)(const char *msg)) {
507 957 ext_log_fn = log_fn;
508
2/2
✓ Branch 0 taken 478 times.
✓ Branch 1 taken 479 times.
957 if (log_fn == NULL) {
509 478 SetAltLogFunc(NULL);
510 } else {
511 479 SetAltLogFunc(libcvmfs_log_fn);
512 }
513 957 }
514
515
516 char *cvmfs_statistics_format(cvmfs_context *ctx) {
517 assert(ctx != NULL);
518 const std::string stats = ctx->mount_point()->statistics()->PrintList(
519 perf::Statistics::kPrintHeader);
520 return strdup(stats.c_str());
521 }
522
523
524 2 int cvmfs_remount(LibContext *ctx) {
525
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 assert(ctx != NULL);
526 2 return ctx->Remount();
527 }
528
529
530 3 uint64_t cvmfs_get_revision(LibContext *ctx) {
531
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 assert(ctx != NULL);
532 3 return ctx->GetRevision();
533 }
534