GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/util/platform_linux.h
Date: 2026-07-26 02:35:14
Exec Total Coverage
Lines: 79 166 47.6%
Branches: 13 106 12.3%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 *
4 * Linux specific system/library calls.
5 */
6
7 #ifndef CVMFS_UTIL_PLATFORM_LINUX_H_
8 #define CVMFS_UTIL_PLATFORM_LINUX_H_
9
10 #include <sys/types.h> // contains ssize_t needed inside <attr/xattr.h>
11 // clang-format off
12 #include <sys/xattr.h>
13 // clang-format on
14
15 #include <dirent.h>
16 #include <endian.h>
17 #include <errno.h>
18 #include <fcntl.h>
19 #include <limits.h>
20 #include <mntent.h>
21 #include <pthread.h>
22 #include <signal.h>
23 #include <sys/file.h>
24 #include <sys/mount.h>
25 #include <sys/prctl.h>
26 #include <sys/random.h>
27 #include <sys/select.h>
28 #include <sys/stat.h>
29 #include <sys/utsname.h>
30 #include <unistd.h>
31
32 #include <cassert>
33 #include <cstdio>
34 #include <cstdlib>
35 #include <cstring>
36 #include <ctime>
37 #include <string>
38 #include <vector>
39
40 #include "util/smalloc.h"
41
42 #ifdef CVMFS_NAMESPACE_GUARD
43 namespace CVMFS_NAMESPACE_GUARD {
44 #endif
45
46 #define platform_sighandler_t sighandler_t
47
48 78 inline std::vector<std::string> platform_mountlist() {
49 78 std::vector<std::string> result;
50 78 FILE *fmnt = setmntent("/proc/mounts", "r");
51 struct mntent *mntbuf; // Static buffer managed by libc!
52
2/2
✓ Branch 1 taken 19032 times.
✓ Branch 2 taken 78 times.
19110 while ((mntbuf = getmntent(fmnt)) != NULL) {
53
2/4
✓ Branch 2 taken 19032 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 19032 times.
✗ Branch 6 not taken.
19032 result.push_back(mntbuf->mnt_dir);
54 }
55 78 endmntent(fmnt);
56 78 return result;
57 }
58
59 // glibc < 2.11
60 #ifndef MNT_DETACH
61 #define MNT_DETACH 0x00000002
62 #endif
63 inline bool platform_umount(const char *mountpoint, const bool lazy) {
64 struct stat64 mtab_info;
65 int retval = lstat64(_PATH_MOUNTED, &mtab_info);
66 // If /etc/mtab exists and is not a symlink to /proc/mounts
67 if ((retval == 0) && S_ISREG(mtab_info.st_mode)) {
68 // Lock the modification on /etc/mtab against concurrent
69 // crash unmount handlers (removing the lock file would result in a race)
70 const std::string lockfile = std::string(_PATH_MOUNTED) + ".cvmfslock";
71 const int fd_lockfile = open(lockfile.c_str(), O_RDONLY | O_CREAT, 0600);
72 if (fd_lockfile < 0)
73 return false;
74 int timeout = 10;
75 while ((flock(fd_lockfile, LOCK_EX | LOCK_NB) != 0) && (timeout > 0)) {
76 if (errno != EWOULDBLOCK) {
77 close(fd_lockfile);
78 return false;
79 }
80 struct timeval wait_for;
81 wait_for.tv_sec = 1;
82 wait_for.tv_usec = 0;
83 select(0, NULL, NULL, NULL, &wait_for);
84 timeout--;
85 }
86 if (timeout <= 0) {
87 close(fd_lockfile);
88 return false;
89 }
90
91 // Remove entry from /etc/mtab (create new file without entry)
92 const std::string mntnew = std::string(_PATH_MOUNTED) + ".cvmfstmp";
93 FILE *fmntold = setmntent(_PATH_MOUNTED, "r");
94 if (!fmntold) {
95 flock(fd_lockfile, LOCK_UN);
96 close(fd_lockfile);
97 return false;
98 }
99 FILE *fmntnew = setmntent(mntnew.c_str(), "w+");
100 if (!fmntnew && (chmod(mntnew.c_str(), mtab_info.st_mode) != 0)
101 && (chown(mntnew.c_str(), mtab_info.st_uid, mtab_info.st_gid) != 0)) {
102 endmntent(fmntold);
103 flock(fd_lockfile, LOCK_UN);
104 close(fd_lockfile);
105 return false;
106 }
107 struct mntent *mntbuf; // Static buffer managed by libc!
108 while ((mntbuf = getmntent(fmntold)) != NULL) {
109 if (strcmp(mntbuf->mnt_dir, mountpoint) != 0) {
110 retval = addmntent(fmntnew, mntbuf);
111 if (retval != 0) {
112 endmntent(fmntold);
113 endmntent(fmntnew);
114 unlink(mntnew.c_str());
115 flock(fd_lockfile, LOCK_UN);
116 close(fd_lockfile);
117 return false;
118 }
119 }
120 }
121 endmntent(fmntold);
122 endmntent(fmntnew);
123 retval = rename(mntnew.c_str(), _PATH_MOUNTED);
124 flock(fd_lockfile, LOCK_UN);
125 close(fd_lockfile);
126 if (retval != 0)
127 return false;
128 // Best effort
129 retval = chmod(_PATH_MOUNTED, mtab_info.st_mode);
130 (void)retval;
131 retval = chown(_PATH_MOUNTED, mtab_info.st_uid, mtab_info.st_gid);
132 (void)retval;
133 // We pickup these values only to silent warnings
134 }
135
136 const int flags = lazy ? MNT_DETACH : 0;
137 retval = umount2(mountpoint, flags);
138 return retval == 0;
139 }
140
141 inline bool platform_umount_lazy(const char *mountpoint) {
142 const int retval = umount2(mountpoint, MNT_DETACH);
143 return retval == 0;
144 }
145
146 /**
147 * Spinlocks are not necessarily part of pthread on all platforms.
148 */
149 typedef pthread_spinlock_t platform_spinlock;
150
151 87 inline int platform_spinlock_init(platform_spinlock *lock, int pshared) {
152 87 return pthread_spin_init(lock, pshared);
153 }
154
155 1 inline int platform_spinlock_destroy(platform_spinlock *lock) {
156 1 return pthread_spin_destroy(lock);
157 }
158
159 114 inline int platform_spinlock_trylock(platform_spinlock *lock) {
160 114 return pthread_spin_trylock(lock);
161 }
162
163 38 inline void platform_spinlock_unlock(platform_spinlock *lock) {
164 38 pthread_spin_unlock(lock);
165 38 }
166
167 /**
168 * pthread_self() is not necessarily an unsigned long.
169 */
170 inline pthread_t platform_gettid() { return pthread_self(); }
171
172 24 inline int platform_sigwait(const int signum) {
173 sigset_t sigset;
174 24 int retval = sigemptyset(&sigset);
175
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
24 assert(retval == 0);
176 24 retval = sigaddset(&sigset, signum);
177
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
24 assert(retval == 0);
178
1/2
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
24 retval = sigwaitinfo(&sigset, NULL);
179 24 return retval;
180 }
181
182 /**
183 * Grants a PID capabilities for ptrace() usage
184 *
185 * @param PID the PID of the process to be granted ptrace()-access
186 * (may be ignored)
187 * @return true when successful
188 */
189 inline bool platform_allow_ptrace(const pid_t pid) {
190 #ifdef PR_SET_PTRACER
191 // On Ubuntu, yama prevents all processes from ptracing other processes, even
192 // when they are owned by the same user. Therefore the watchdog would not be
193 // able to create a stacktrace, without this extra permission.
194 const int retval = prctl(PR_SET_PTRACER, pid, 0, 0, 0);
195 // On some platforms (e.g. CentOS7), PR_SET_PTRACER is defined but not
196 // supported by the kernel. That's fine and we don't have to care about it
197 // when it happens.
198 return (retval == 0) || (errno == EINVAL);
199 #else
200 // On other platforms this is currently a no-op
201 return true;
202 #endif
203 }
204
205 /**
206 * Set current process to be marked dumpable
207 *
208 * @return true when successful
209 */
210 inline bool platform_set_dumpable() {
211 #ifdef PR_SET_DUMPABLE
212 const int retval = prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
213 return (retval == 0);
214 #else
215 // On other platforms this is currently a no-op
216 return true;
217 #endif
218 }
219
220 /**
221 * Set current process to be keep capabilities on uid switch
222 *
223 * @param set true to set keepcaps, false to reset it
224 *
225 * @return true when successful
226 */
227 inline bool platform_keepcaps(bool set) {
228 #ifdef PR_SET_KEEPCAPS
229 const int retval = prctl(PR_SET_KEEPCAPS, set ? 1 : 0, 0, 0, 0);
230 return (retval == 0);
231 #else
232 // On other platforms this is currently a no-op
233 return true;
234 #endif
235 }
236
237 /**
238 * File system functions, ensure 64bit versions.
239 */
240 typedef struct dirent64 platform_dirent64;
241
242 5408984 inline platform_dirent64 *platform_readdir(DIR *dirp) {
243 5408984 return readdir64(dirp);
244 }
245
246 typedef struct stat64 platform_stat64;
247
248 13349387 inline int platform_stat(const char *path, platform_stat64 *buf) {
249 13349387 return stat64(path, buf);
250 }
251
252 2199851 inline int platform_lstat(const char *path, platform_stat64 *buf) {
253 2199851 return lstat64(path, buf);
254 }
255
256 12142 inline int platform_fstat(int filedes, platform_stat64 *buf) {
257 12142 return fstat64(filedes, buf);
258 }
259
260 // TODO(jblomer): the translation from C to C++ should be done elsewhere
261 inline bool platform_getxattr(const std::string &path, const std::string &name,
262 std::string *value) {
263 ssize_t size = 0;
264 void *buffer = NULL;
265 ssize_t retval;
266 retval = getxattr(path.c_str(), name.c_str(), buffer, size);
267 if (retval > 1) {
268 size = retval;
269 buffer = smalloc(size);
270 retval = getxattr(path.c_str(), name.c_str(), buffer, size);
271 }
272 if ((retval < 0) || (retval > size)) {
273 free(buffer);
274 return false;
275 }
276 if (retval > 0) {
277 value->assign(static_cast<const char *>(buffer), size);
278 free(buffer);
279 } else {
280 value->assign("");
281 }
282 return true;
283 }
284
285 // TODO(jblomer): the translation from C to C++ should be done elsewhere
286 180 inline bool platform_setxattr(const std::string &path, const std::string &name,
287 const std::string &value) {
288 180 const int retval = setxattr(path.c_str(), name.c_str(), value.c_str(),
289 value.size(), 0);
290 180 return retval == 0;
291 }
292
293 inline bool platform_lsetxattr(const std::string &path, const std::string &name,
294 const std::string &value) {
295 const int retval = lsetxattr(path.c_str(), name.c_str(), value.c_str(),
296 value.size(), 0);
297 return retval == 0;
298 }
299
300 360 inline ssize_t platform_lgetxattr(const char *path, const char *name,
301 void *value, size_t size) {
302 360 return lgetxattr(path, name, value, size);
303 }
304
305 324 inline ssize_t platform_llistxattr(const char *path, char *list, size_t size) {
306 324 return llistxattr(path, list, size);
307 }
308
309 9 inline void platform_disable_kcache(int filedes) {
310 9 (void)posix_fadvise(filedes, 0, 0, POSIX_FADV_RANDOM | POSIX_FADV_NOREUSE);
311 9 }
312
313 3479 inline ssize_t platform_readahead(int filedes) {
314 3479 return readahead(filedes, 0, static_cast<size_t>(-1));
315 }
316
317 /**
318 * Advises the kernel to evict the given file region from the page cache.
319 *
320 * Note: Pages containing the data at `offset` and `offset + length` are NOT
321 * evicted by the kernel. This means that a few pages are not purged when
322 * offset and length are not exactly on page boundaries. See below:
323 *
324 * offset length
325 * | |
326 * +---------+----|----+---------+---------+---------+-----|---+---------+
327 * | | | | xxxxxxx | xxxxxxx | xxxxxxx | | | |
328 * | | | | xxxxxxx | xxxxxxx | xxxxxxx | | | |
329 * +---------+----|----+---------+---------+---------+-----|---+---------+
330 * 0 4096 | 8192 12288 16384 20480 | 24576 28672
331 *
332 * git.kernel.org/cgit/linux/kernel/git/stable/linux-stable.git/tree/mm/fadvise.c#n115
333 *
334 * TODO(rmeusel): figure out a clever way how to align `offset` and `length`
335 *
336 * @param fd file descriptor whose page cache should be (partially) evicted
337 * @param offset start offset of the pages to be evicted
338 * @param length number of bytes to be evicted
339 */
340 11438 inline int platform_invalidate_kcache(const int fd, const off_t offset,
341 const off_t length) {
342 11438 return posix_fadvise(fd, offset, length, POSIX_FADV_DONTNEED);
343 }
344
345 inline std::string platform_libname(const std::string &base_name) {
346 return "lib" + base_name + ".so";
347 }
348
349 49 inline std::string platform_getexepath() {
350 char buf[PATH_MAX + 1];
351 49 const ssize_t ret = readlink("/proc/self/exe", buf, PATH_MAX);
352
1/2
✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
49 if (ret > 0) {
353 49 buf[ret] = '\0';
354
1/2
✓ Branch 2 taken 49 times.
✗ Branch 3 not taken.
49 return std::string(buf);
355 }
356 return "";
357 }
358
359 84136425 inline struct timespec platform_time_with_clock(int clock) {
360 struct timespec tp;
361 84136425 const int retval = clock_gettime(clock, &tp);
362
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 84136425 times.
84136425 assert(retval == 0);
363 84136425 return tp;
364 }
365
366 84129391 inline uint64_t platform_monotonic_time() {
367 #ifdef CLOCK_MONOTONIC_COARSE
368 84129391 struct timespec const tp = platform_time_with_clock(CLOCK_MONOTONIC_COARSE);
369 #else
370 struct timespec tp = platform_time_with_clock(CLOCK_MONOTONIC);
371 #endif
372 84129391 return tp.tv_sec + (tp.tv_nsec >= 500000000);
373 }
374
375 6694 inline uint64_t platform_monotonic_time_ns() {
376 6694 struct timespec const tp = platform_time_with_clock(CLOCK_MONOTONIC);
377 6694 return static_cast<uint64_t>(static_cast<double>(tp.tv_sec) * 1e9
378 6694 + static_cast<double>(tp.tv_nsec));
379 }
380
381 340 inline uint64_t platform_realtime_ns() {
382 340 struct timespec const tp = platform_time_with_clock(CLOCK_REALTIME);
383 340 return static_cast<uint64_t>(static_cast<double>(tp.tv_sec) * 1e9
384 340 + static_cast<double>(tp.tv_nsec));
385 }
386
387 973 inline uint64_t platform_memsize() {
388 973 return static_cast<uint64_t>(sysconf(_SC_PHYS_PAGES))
389 973 * static_cast<uint64_t>(sysconf(_SC_PAGE_SIZE));
390 }
391
392 360 inline uint16_t platform_htole16(uint16_t host_16bits) {
393 360 return htole16(host_16bits);
394 }
395
396 468 inline uint16_t platform_le16toh(uint16_t little_endian_16bits) {
397 468 return le16toh(little_endian_16bits);
398 }
399
400 300027 inline void platform_getrandom(void *buf, size_t length) {
401 // getrandom() can be interrupted by a signal while blocking on an
402 // uninitialized entropy pool and returns short for requests > 256 bytes
403 300027 size_t nbytes = 0;
404
2/2
✓ Branch 0 taken 300027 times.
✓ Branch 1 taken 300027 times.
600054 while (nbytes < length) {
405 const ssize_t retval =
406 300027 getrandom(static_cast<unsigned char *>(buf) + nbytes, length - nbytes,
407 0);
408
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 300027 times.
300027 if (retval < 0) {
409 assert(errno == EINTR);
410 continue;
411 }
412 300027 nbytes += retval;
413 }
414 300027 }
415
416 #ifdef CVMFS_NAMESPACE_GUARD
417 } // namespace CVMFS_NAMESPACE_GUARD
418 #endif
419
420 #endif // CVMFS_UTIL_PLATFORM_LINUX_H_
421