GCC Code Coverage Report


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