| Directory: | cvmfs/ |
|---|---|
| File: | cvmfs/util/platform_linux.h |
| Date: | 2026-03-15 02:35:27 |
| Exec | Total | Coverage | |
|---|---|---|---|
| Lines: | 72 | 157 | 45.9% |
| Branches: | 10 | 100 | 10.0% |
| 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 | 63 | inline std::vector<std::string> platform_mountlist() { | |
| 48 | 63 | std::vector<std::string> result; | |
| 49 | 63 | FILE *fmnt = setmntent("/proc/mounts", "r"); | |
| 50 | struct mntent *mntbuf; // Static buffer managed by libc! | ||
| 51 |
2/2✓ Branch 1 taken 15372 times.
✓ Branch 2 taken 63 times.
|
15435 | while ((mntbuf = getmntent(fmnt)) != NULL) { |
| 52 |
2/4✓ Branch 2 taken 15372 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 15372 times.
✗ Branch 6 not taken.
|
15372 | result.push_back(mntbuf->mnt_dir); |
| 53 | } | ||
| 54 | 63 | endmntent(fmnt); | |
| 55 | 63 | 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 | 89 | inline int platform_spinlock_init(platform_spinlock *lock, int pshared) { | |
| 151 | 89 | 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 | 120 | inline int platform_spinlock_trylock(platform_spinlock *lock) { | |
| 159 | 120 | return pthread_spin_trylock(lock); | |
| 160 | } | ||
| 161 | |||
| 162 | 40 | inline void platform_spinlock_unlock(platform_spinlock *lock) { | |
| 163 | 40 | pthread_spin_unlock(lock); | |
| 164 | 40 | } | |
| 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 | * Set current process to be marked dumpable | ||
| 206 | * | ||
| 207 | * @return true when successful | ||
| 208 | */ | ||
| 209 | ✗ | inline bool platform_set_dumpable() { | |
| 210 | #ifdef PR_SET_DUMPABLE | ||
| 211 | ✗ | const int retval = prctl(PR_SET_DUMPABLE, 1, 0, 0, 0); | |
| 212 | ✗ | return (retval == 0); | |
| 213 | #else | ||
| 214 | // On other platforms this is currently a no-op | ||
| 215 | return true; | ||
| 216 | #endif | ||
| 217 | } | ||
| 218 | |||
| 219 | /** | ||
| 220 | * Set current process to be keep capabilities on uid switch | ||
| 221 | * | ||
| 222 | * @param set true to set keepcaps, false to reset it | ||
| 223 | * | ||
| 224 | * @return true when successful | ||
| 225 | */ | ||
| 226 | ✗ | inline bool platform_keepcaps(bool set) { | |
| 227 | #ifdef PR_SET_KEEPCAPS | ||
| 228 | ✗ | const int retval = prctl(PR_SET_KEEPCAPS, set ? 1 : 0, 0, 0, 0); | |
| 229 | ✗ | return (retval == 0); | |
| 230 | #else | ||
| 231 | // On other platforms this is currently a no-op | ||
| 232 | return true; | ||
| 233 | #endif | ||
| 234 | } | ||
| 235 | |||
| 236 | /** | ||
| 237 | * File system functions, ensure 64bit versions. | ||
| 238 | */ | ||
| 239 | typedef struct dirent64 platform_dirent64; | ||
| 240 | |||
| 241 | 8046778 | inline platform_dirent64 *platform_readdir(DIR *dirp) { | |
| 242 | 8046778 | return readdir64(dirp); | |
| 243 | } | ||
| 244 | |||
| 245 | typedef struct stat64 platform_stat64; | ||
| 246 | |||
| 247 | 506547 | inline int platform_stat(const char *path, platform_stat64 *buf) { | |
| 248 | 506547 | return stat64(path, buf); | |
| 249 | } | ||
| 250 | |||
| 251 | 3503594 | inline int platform_lstat(const char *path, platform_stat64 *buf) { | |
| 252 | 3503594 | return lstat64(path, buf); | |
| 253 | } | ||
| 254 | |||
| 255 | 41792 | inline int platform_fstat(int filedes, platform_stat64 *buf) { | |
| 256 | 41792 | return fstat64(filedes, buf); | |
| 257 | } | ||
| 258 | |||
| 259 | // TODO(jblomer): the translation from C to C++ should be done elsewhere | ||
| 260 | ✗ | inline bool platform_getxattr(const std::string &path, const std::string &name, | |
| 261 | std::string *value) { | ||
| 262 | ✗ | ssize_t size = 0; | |
| 263 | ✗ | void *buffer = NULL; | |
| 264 | ssize_t retval; | ||
| 265 | ✗ | retval = getxattr(path.c_str(), name.c_str(), buffer, size); | |
| 266 | ✗ | if (retval > 1) { | |
| 267 | ✗ | size = retval; | |
| 268 | ✗ | buffer = smalloc(size); | |
| 269 | ✗ | retval = getxattr(path.c_str(), name.c_str(), buffer, size); | |
| 270 | } | ||
| 271 | ✗ | if ((retval < 0) || (retval > size)) { | |
| 272 | ✗ | free(buffer); | |
| 273 | ✗ | return false; | |
| 274 | } | ||
| 275 | ✗ | if (retval > 0) { | |
| 276 | ✗ | value->assign(static_cast<const char *>(buffer), size); | |
| 277 | ✗ | free(buffer); | |
| 278 | } else { | ||
| 279 | ✗ | value->assign(""); | |
| 280 | } | ||
| 281 | ✗ | return true; | |
| 282 | } | ||
| 283 | |||
| 284 | // TODO(jblomer): the translation from C to C++ should be done elsewhere | ||
| 285 | 175 | inline bool platform_setxattr(const std::string &path, const std::string &name, | |
| 286 | const std::string &value) { | ||
| 287 | 175 | const int retval = setxattr(path.c_str(), name.c_str(), value.c_str(), | |
| 288 | value.size(), 0); | ||
| 289 | 175 | return retval == 0; | |
| 290 | } | ||
| 291 | |||
| 292 | inline bool platform_lsetxattr(const std::string &path, const std::string &name, | ||
| 293 | const std::string &value) { | ||
| 294 | const int retval = lsetxattr(path.c_str(), name.c_str(), value.c_str(), | ||
| 295 | value.size(), 0); | ||
| 296 | return retval == 0; | ||
| 297 | } | ||
| 298 | |||
| 299 | 350 | inline ssize_t platform_lgetxattr(const char *path, const char *name, | |
| 300 | void *value, size_t size) { | ||
| 301 | 350 | return lgetxattr(path, name, value, size); | |
| 302 | } | ||
| 303 | |||
| 304 | 315 | inline ssize_t platform_llistxattr(const char *path, char *list, size_t size) { | |
| 305 | 315 | return llistxattr(path, list, size); | |
| 306 | } | ||
| 307 | |||
| 308 | 3 | inline void platform_disable_kcache(int filedes) { | |
| 309 | 3 | (void)posix_fadvise(filedes, 0, 0, POSIX_FADV_RANDOM | POSIX_FADV_NOREUSE); | |
| 310 | 3 | } | |
| 311 | |||
| 312 | 6488 | inline ssize_t platform_readahead(int filedes) { | |
| 313 | 6488 | return readahead(filedes, 0, static_cast<size_t>(-1)); | |
| 314 | } | ||
| 315 | |||
| 316 | /** | ||
| 317 | * Advises the kernel to evict the given file region from the page cache. | ||
| 318 | * | ||
| 319 | * Note: Pages containing the data at `offset` and `offset + length` are NOT | ||
| 320 | * evicted by the kernel. This means that a few pages are not purged when | ||
| 321 | * offset and length are not exactly on page boundaries. See below: | ||
| 322 | * | ||
| 323 | * offset length | ||
| 324 | * | | | ||
| 325 | * +---------+----|----+---------+---------+---------+-----|---+---------+ | ||
| 326 | * | | | | xxxxxxx | xxxxxxx | xxxxxxx | | | | | ||
| 327 | * | | | | xxxxxxx | xxxxxxx | xxxxxxx | | | | | ||
| 328 | * +---------+----|----+---------+---------+---------+-----|---+---------+ | ||
| 329 | * 0 4096 | 8192 12288 16384 20480 | 24576 28672 | ||
| 330 | * | ||
| 331 | * git.kernel.org/cgit/linux/kernel/git/stable/linux-stable.git/tree/mm/fadvise.c#n115 | ||
| 332 | * | ||
| 333 | * TODO(rmeusel): figure out a clever way how to align `offset` and `length` | ||
| 334 | * | ||
| 335 | * @param fd file descriptor whose page cache should be (partially) evicted | ||
| 336 | * @param offset start offset of the pages to be evicted | ||
| 337 | * @param length number of bytes to be evicted | ||
| 338 | */ | ||
| 339 | 47679 | inline int platform_invalidate_kcache(const int fd, const off_t offset, | |
| 340 | const off_t length) { | ||
| 341 | 47679 | return posix_fadvise(fd, offset, length, POSIX_FADV_DONTNEED); | |
| 342 | } | ||
| 343 | |||
| 344 | ✗ | inline std::string platform_libname(const std::string &base_name) { | |
| 345 | ✗ | return "lib" + base_name + ".so"; | |
| 346 | } | ||
| 347 | |||
| 348 | 49 | inline std::string platform_getexepath() { | |
| 349 | char buf[PATH_MAX + 1]; | ||
| 350 | 49 | const ssize_t ret = readlink("/proc/self/exe", buf, PATH_MAX); | |
| 351 |
1/2✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
|
49 | if (ret > 0) { |
| 352 | 49 | buf[ret] = '\0'; | |
| 353 |
1/2✓ Branch 2 taken 49 times.
✗ Branch 3 not taken.
|
49 | return std::string(buf); |
| 354 | } | ||
| 355 | ✗ | return ""; | |
| 356 | } | ||
| 357 | |||
| 358 | 574065365 | inline struct timespec platform_time_with_clock(int clock) { | |
| 359 | struct timespec tp; | ||
| 360 | 574065365 | const int retval = clock_gettime(clock, &tp); | |
| 361 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 574065365 times.
|
574065365 | assert(retval == 0); |
| 362 | 574065365 | return tp; | |
| 363 | } | ||
| 364 | |||
| 365 | 574056921 | inline uint64_t platform_monotonic_time() { | |
| 366 | #ifdef CLOCK_MONOTONIC_COARSE | ||
| 367 | 574056921 | struct timespec const tp = platform_time_with_clock(CLOCK_MONOTONIC_COARSE); | |
| 368 | #else | ||
| 369 | struct timespec tp = platform_time_with_clock(CLOCK_MONOTONIC); | ||
| 370 | #endif | ||
| 371 | 574056921 | return tp.tv_sec + (tp.tv_nsec >= 500000000); | |
| 372 | } | ||
| 373 | |||
| 374 | 8388 | inline uint64_t platform_monotonic_time_ns() { | |
| 375 | 8388 | struct timespec const tp = platform_time_with_clock(CLOCK_MONOTONIC); | |
| 376 | 8388 | return static_cast<uint64_t>(static_cast<double>(tp.tv_sec) * 1e9 | |
| 377 | 8388 | + static_cast<double>(tp.tv_nsec)); | |
| 378 | } | ||
| 379 | |||
| 380 | 56 | inline uint64_t platform_realtime_ns() { | |
| 381 | 56 | struct timespec const tp = platform_time_with_clock(CLOCK_REALTIME); | |
| 382 | 56 | return static_cast<uint64_t>(static_cast<double>(tp.tv_sec) * 1e9 | |
| 383 | 56 | + static_cast<double>(tp.tv_nsec)); | |
| 384 | } | ||
| 385 | |||
| 386 | 1751 | inline uint64_t platform_memsize() { | |
| 387 | 1751 | return static_cast<uint64_t>(sysconf(_SC_PHYS_PAGES)) | |
| 388 | 1751 | * static_cast<uint64_t>(sysconf(_SC_PAGE_SIZE)); | |
| 389 | } | ||
| 390 | |||
| 391 | 350 | inline uint16_t platform_htole16(uint16_t host_16bits) { | |
| 392 | 350 | return htole16(host_16bits); | |
| 393 | } | ||
| 394 | |||
| 395 | 455 | inline uint16_t platform_le16toh(uint16_t little_endian_16bits) { | |
| 396 | 455 | return le16toh(little_endian_16bits); | |
| 397 | } | ||
| 398 | |||
| 399 | #ifdef CVMFS_NAMESPACE_GUARD | ||
| 400 | } // namespace CVMFS_NAMESPACE_GUARD | ||
| 401 | #endif | ||
| 402 | |||
| 403 | #endif // CVMFS_UTIL_PLATFORM_LINUX_H_ | ||
| 404 |