| 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 |