GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/libcvmfs.cc
Date: 2026-08-09 02:40:25
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 45 struct cvmfs_attr *cvmfs_attr_init() {
35 struct cvmfs_attr *attr;
36 45 attr = reinterpret_cast<cvmfs_attr *>(calloc(1, sizeof(*attr)));
37 45 attr->version = 1;
38 45 attr->size = sizeof(*attr);
39 45 return attr;
40 }
41
42
43 /**
44 * Destroy the cvmfs_attr struct and frees the checksum, symlink,
45 * name, and xattrs.
46 */
47 45 void cvmfs_attr_free(struct cvmfs_attr *attr) {
48
1/2
✓ Branch 0 taken 45 times.
✗ Branch 1 not taken.
45 if (attr) {
49 45 free(attr->cvm_checksum);
50 45 free(attr->cvm_symlink);
51 45 free(attr->cvm_name);
52 45 free(attr->cvm_parent);
53
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 45 times.
45 delete reinterpret_cast<XattrList *>(attr->cvm_xattrs);
54 }
55 45 free(attr);
56 45 }
57
58
59 48 struct cvmfs_nc_attr *cvmfs_nc_attr_init() {
60 struct cvmfs_nc_attr *attr;
61 48 attr = reinterpret_cast<cvmfs_nc_attr *>(calloc(1, sizeof(*attr)));
62 48 return attr;
63 }
64
65 48 void cvmfs_nc_attr_free(struct cvmfs_nc_attr *nc_attr) {
66
1/2
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
48 if (nc_attr) {
67 48 free(nc_attr->mountpoint);
68 48 free(nc_attr->hash);
69 }
70 48 free(nc_attr);
71 48 }
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 349 static int expand_path(const int depth,
81 LibContext *ctx,
82 char const *path,
83 string *expanded_path) {
84
2/4
✓ Branch 2 taken 349 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 349 times.
✗ Branch 6 not taken.
698 const string p_path = GetParentPath(path);
85
2/4
✓ Branch 2 taken 349 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 349 times.
✗ Branch 6 not taken.
698 const string fname = GetFileName(path);
86 int rc;
87
88
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 349 times.
349 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 349 string buf;
110
2/2
✓ Branch 1 taken 144 times.
✓ Branch 2 taken 205 times.
349 if (p_path != "") {
111
1/2
✓ Branch 2 taken 144 times.
✗ Branch 3 not taken.
144 rc = expand_path(depth, ctx, p_path.c_str(), &buf);
112
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 144 times.
144 if (rc != 0) {
113 return -1;
114 }
115
116
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 144 times.
144 if (fname == ".") {
117 *expanded_path = buf;
118 return 0;
119 }
120 }
121
122
5/8
✓ Branch 1 taken 144 times.
✓ Branch 2 taken 205 times.
✓ Branch 5 taken 144 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 144 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 349 times.
✗ Branch 10 not taken.
349 if (buf.length() == 0 || buf[buf.length() - 1] != '/') {
123
1/2
✓ Branch 1 taken 349 times.
✗ Branch 2 not taken.
349 buf += "/";
124 }
125
1/2
✓ Branch 1 taken 349 times.
✗ Branch 2 not taken.
349 buf += fname;
126
127 struct stat st;
128
1/2
✓ Branch 2 taken 349 times.
✗ Branch 3 not taken.
349 rc = ctx->GetAttr(buf.c_str(), &st);
129
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 333 times.
349 if (rc != 0) {
130 16 errno = -rc;
131 16 return -1;
132 }
133
134
1/2
✓ Branch 0 taken 333 times.
✗ Branch 1 not taken.
333 if (!S_ISLNK(st.st_mode)) {
135
1/2
✓ Branch 1 taken 333 times.
✗ Branch 2 not taken.
333 *expanded_path = buf;
136 333 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 349 }
188
189 /**
190 * Like expand_path(), but do not expand the final element of the path.
191 */
192 45 static int expand_ppath(LibContext *ctx,
193 const char *path,
194 string *expanded_path) {
195
2/4
✓ Branch 2 taken 45 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 45 times.
✗ Branch 6 not taken.
90 const string p_path = GetParentPath(path);
196
2/4
✓ Branch 2 taken 45 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 45 times.
✗ Branch 6 not taken.
90 const string fname = GetFileName(path);
197
198
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 45 times.
45 if (p_path == "") {
199 *expanded_path = path;
200 return 0;
201 }
202
203
1/2
✓ Branch 2 taken 45 times.
✗ Branch 3 not taken.
45 const int rc = expand_path(0, ctx, p_path.c_str(), expanded_path);
204
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 45 times.
45 if (rc != 0) {
205 return rc;
206 }
207
208
1/2
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
45 (*expanded_path) += "/";
209
1/2
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
45 (*expanded_path) += fname;
210
211 45 return 0;
212 45 }
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 48 int cvmfs_stat(LibContext *ctx, const char *path, struct stat *st) {
273 48 string lpath;
274 int rc;
275
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
48 rc = expand_path(0, ctx, path, &lpath);
276
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 32 times.
48 if (rc < 0) {
277 16 return -1;
278 }
279 32 path = lpath.c_str();
280
281
1/2
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
32 rc = ctx->GetAttr(path, st);
282
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 32 times.
32 if (rc < 0) {
283 errno = -rc;
284 return -1;
285 }
286 32 return 0;
287 48 }
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 45 int cvmfs_stat_attr(LibContext *ctx,
309 const char *path,
310 struct cvmfs_attr *attr) {
311 45 string lpath;
312 int rc;
313
1/2
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
45 rc = expand_ppath(ctx, path, &lpath);
314
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 45 times.
45 if (rc < 0) {
315 return -1;
316 }
317 45 path = lpath.c_str();
318
319
1/2
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
45 rc = ctx->GetExtAttr(path, attr);
320
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 30 times.
45 if (rc < 0) {
321 15 errno = -rc;
322 15 return -1;
323 }
324 30 return 0;
325 45 }
326
327
328 16 int cvmfs_listdir(LibContext *ctx,
329 const char *path,
330 char ***buf,
331 size_t *buflen) {
332 16 string lpath;
333 int rc;
334
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
16 rc = expand_path(0, ctx, path, &lpath);
335
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 if (rc < 0) {
336 return -1;
337 }
338 16 path = lpath.c_str();
339
340 16 size_t listsize = 0;
341
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
16 rc = ctx->ListDirectory(path, buf, &listsize, buflen, true);
342
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 if (rc < 0) {
343 errno = -rc;
344 return -1;
345 }
346 16 return 0;
347 16 }
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 16 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 16 string lpath;
376 int rc;
377
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
16 rc = expand_path(0, ctx, path, &lpath);
378
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 if (rc < 0) {
379 return -1;
380 }
381 16 path = lpath.c_str();
382
383
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
16 rc = ctx->ListDirectoryStat(path, buf, listlen, buflen);
384
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 if (rc < 0) {
385 errno = -rc;
386 return -1;
387 }
388 16 return 0;
389 16 }
390
391
392 48 int cvmfs_stat_nc(LibContext *ctx,
393 const char *path,
394 struct cvmfs_nc_attr *nc_attr) {
395 48 string lpath;
396 int rc;
397
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
48 rc = expand_path(0, ctx, path, &lpath);
398
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
48 if (rc < 0) {
399 return -1;
400 }
401 48 path = lpath.c_str();
402
403
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
48 rc = ctx->GetNestedCatalogAttr(path, nc_attr);
404
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
48 if (rc < 0) {
405 errno = -rc;
406 return -1;
407 }
408 48 return 0;
409 48 }
410
411
412 32 int cvmfs_list_nc(LibContext *ctx,
413 const char *path,
414 char ***buf,
415 size_t *buflen) {
416 32 string lpath;
417 int rc;
418
1/2
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
32 rc = expand_path(0, ctx, path, &lpath);
419
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 32 times.
32 if (rc < 0) {
420 return -1;
421 }
422 32 path = lpath.c_str();
423
424
1/2
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
32 rc = ctx->ListNestedCatalogs(path, buf, buflen);
425
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 32 times.
32 if (rc < 0) {
426 errno = -rc;
427 return -1;
428 }
429 32 return 0;
430 32 }
431
432
433 48 void cvmfs_list_free(char **buf) {
434 // Quick return if base pointer is NULL
435
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
48 if (!buf)
436 return;
437 48 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 208 times.
✓ Branch 1 taken 48 times.
256 while (buf[pos]) {
441 208 free(buf[pos]);
442 208 pos++;
443 }
444 48 free(buf);
445 }
446
447 void cvmfs_enable_threaded(LibContext *ctx) { ctx->EnableMultiThreaded(); }
448
449 128 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 128 times.
128 assert(ctx != NULL);
453
4/8
✓ Branch 2 taken 128 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 128 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 128 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 128 times.
✗ Branch 12 not taken.
128 opts->SwitchTemplateManager(new DefaultOptionsTemplateManager(fqrn));
454
2/4
✓ Branch 2 taken 128 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 128 times.
✗ Branch 6 not taken.
128 *ctx = LibContext::Create(fqrn, opts);
455
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 128 times.
128 assert(*ctx != NULL);
456 128 const loader::Failures result = (*ctx)->mount_point()->boot_status();
457
2/2
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 96 times.
128 if (result != loader::kFailOk) {
458
1/4
✓ Branch 2 taken 32 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
32 LogCvmfs(kLogCvmfs, kLogStderr, "Attaching %s failed: %s (%d)", fqrn,
459 64 (*ctx)->mount_point()->boot_error().c_str(), result);
460
1/2
✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
32 delete *ctx;
461 32 *ctx = NULL;
462 }
463 128 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 96 times.
✗ Branch 1 not taken.
96 void cvmfs_detach_repo(LibContext *ctx) { delete ctx; }
473
474
475 160 cvmfs_errors cvmfs_init_v2(SimpleOptionsParser *opts) {
476 160 const int result = LibGlobals::Initialize(opts);
477
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 144 times.
160 if (result != loader::kFailOk) {
478
1/4
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
16 LogCvmfs(kLogCvmfs, kLogStderr, "Initialization failed: %s (%d)",
479 32 LibGlobals::GetInstance()->file_system()->boot_error().c_str(),
480 result);
481 16 LibGlobals::CleanupInstance();
482 }
483 160 return static_cast<cvmfs_errors>(result);
484 }
485
486
487 207 void cvmfs_fini() { LibGlobals::CleanupInstance(); }
488
489
490 static void (*ext_log_fn)(const char *msg) = NULL;
491
492
493 91146 static void libcvmfs_log_fn(const LogSource /*source*/,
494 const int /*mask*/,
495 const char *msg) {
496
1/2
✓ Branch 0 taken 91146 times.
✗ Branch 1 not taken.
91146 if (ext_log_fn) {
497 91146 (*ext_log_fn)(msg);
498 }
499 91146 }
500
501
502 443 void cvmfs_set_log_fn(void (*log_fn)(const char *msg)) {
503 443 ext_log_fn = log_fn;
504
2/2
✓ Branch 0 taken 221 times.
✓ Branch 1 taken 222 times.
443 if (log_fn == NULL) {
505 221 SetAltLogFunc(NULL);
506 } else {
507 222 SetAltLogFunc(libcvmfs_log_fn);
508 }
509 443 }
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