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