GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/util/platform_linux.h
Date: 2025-12-28 02:35:52
Exec Total Coverage
Lines: 72 151 47.7%
Branches: 10 98 10.2%

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