GCC Code Coverage Report


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