GCC Code Coverage Report


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