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 |