Directory: | cvmfs/ |
---|---|
File: | cvmfs/util/platform_linux.h |
Date: | 2025-02-09 02:34:19 |
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 732 times.
✓ Branch 2 taken 3 times.
|
735 | while ((mntbuf = getmntent(fmnt)) != NULL) { |
49 |
2/4✓ Branch 2 taken 732 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 732 times.
✗ Branch 6 not taken.
|
732 | 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 | 223490 | inline platform_dirent64 *platform_readdir(DIR *dirp) { | |
205 | 223490 | return readdir64(dirp); | |
206 | } | ||
207 | |||
208 | typedef struct stat64 platform_stat64; | ||
209 | |||
210 | 372488 | inline int platform_stat(const char *path, platform_stat64 *buf) { | |
211 | 372488 | return stat64(path, buf); | |
212 | } | ||
213 | |||
214 | 102791 | inline int platform_lstat(const char *path, platform_stat64 *buf) { | |
215 | 102791 | return lstat64(path, buf); | |
216 | } | ||
217 | |||
218 | 1054 | inline int platform_fstat(int filedes, platform_stat64 *buf) { | |
219 | 1054 | 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 | 8 | inline ssize_t platform_lgetxattr(const char *path, const char *name, | |
263 | void *value, size_t size) { | ||
264 | 8 | 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 | 12 | inline void platform_disable_kcache(int filedes) { | |
272 | 12 | (void)posix_fadvise(filedes, 0, 0, POSIX_FADV_RANDOM | POSIX_FADV_NOREUSE); | |
273 | 12 | } | |
274 | |||
275 | 179 | inline ssize_t platform_readahead(int filedes) { | |
276 | 179 | 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 | 11523835 | inline struct timespec platform_time_with_clock(int clock) { | |
322 | struct timespec tp; | ||
323 | 11523835 | int retval = clock_gettime(clock, &tp); | |
324 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 11523835 times.
|
11523835 | assert(retval == 0); |
325 | 11523835 | return tp; | |
326 | } | ||
327 | |||
328 | 11523615 | inline uint64_t platform_monotonic_time() { | |
329 | #ifdef CLOCK_MONOTONIC_COARSE | ||
330 | 11523615 | 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 | 11523615 | return tp.tv_sec + (tp.tv_nsec >= 500000000); | |
335 | } | ||
336 | |||
337 | 215 | inline uint64_t platform_monotonic_time_ns() { | |
338 | 215 | struct timespec tp = platform_time_with_clock(CLOCK_MONOTONIC); | |
339 | 215 | return static_cast<uint64_t>(static_cast<double>(tp.tv_sec) * 1e9 + | |
340 | 215 | 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 |