Commit | Line | Data |
---|---|---|
df038819 | 1 | /* |
ab5be9fa | 2 | * Copyright (C) 2018-2020 Jérémie Galarneau <jeremie.galarneau@efficios.com> |
df038819 | 3 | * |
ab5be9fa | 4 | * SPDX-License-Identifier: GPL-2.0-only |
df038819 | 5 | * |
df038819 JG |
6 | */ |
7 | ||
8 | #include <urcu.h> | |
9 | #include <urcu/list.h> | |
10 | #include <urcu/rculfhash.h> | |
11 | ||
df038819 | 12 | #include <fcntl.h> |
df038819 | 13 | #include <inttypes.h> |
5c1f54d1 JG |
14 | #include <pthread.h> |
15 | #include <stdbool.h> | |
16 | #include <sys/stat.h> | |
17 | #include <sys/types.h> | |
df038819 | 18 | |
f5ea0241 JG |
19 | #include <common/defaults.h> |
20 | #include <common/error.h> | |
1e4f5625 | 21 | #include <common/fs-handle-internal.h> |
f5ea0241 JG |
22 | #include <common/hashtable/hashtable.h> |
23 | #include <common/hashtable/utils.h> | |
24 | #include <common/macros.h> | |
1e4f5625 | 25 | #include <common/optional.h> |
df038819 JG |
26 | |
27 | #include "fd-tracker.h" | |
874a3441 | 28 | #include "inode.h" |
df038819 JG |
29 | |
30 | /* Tracker lock must be taken by the user. */ | |
5c1f54d1 JG |
31 | #define TRACKED_COUNT(tracker) \ |
32 | (tracker->count.suspendable.active + \ | |
33 | tracker->count.suspendable.suspended + \ | |
34 | tracker->count.unsuspendable) | |
df038819 JG |
35 | |
36 | /* Tracker lock must be taken by the user. */ | |
5c1f54d1 JG |
37 | #define ACTIVE_COUNT(tracker) \ |
38 | (tracker->count.suspendable.active + tracker->count.unsuspendable) | |
df038819 JG |
39 | |
40 | /* Tracker lock must be taken by the user. */ | |
5c1f54d1 | 41 | #define SUSPENDED_COUNT(tracker) (tracker->count.suspendable.suspended) |
df038819 JG |
42 | |
43 | /* Tracker lock must be taken by the user. */ | |
5c1f54d1 JG |
44 | #define SUSPENDABLE_COUNT(tracker) \ |
45 | (tracker->count.suspendable.active + \ | |
46 | tracker->count.suspendable.suspended) | |
df038819 JG |
47 | |
48 | /* Tracker lock must be taken by the user. */ | |
5c1f54d1 | 49 | #define UNSUSPENDABLE_COUNT(tracker) (tracker->count.unsuspendable) |
df038819 JG |
50 | |
51 | struct fd_tracker { | |
52 | pthread_mutex_t lock; | |
53 | struct { | |
5c1f54d1 | 54 | struct { |
df038819 JG |
55 | unsigned int active; |
56 | unsigned int suspended; | |
57 | } suspendable; | |
58 | unsigned int unsuspendable; | |
59 | } count; | |
60 | unsigned int capacity; | |
61 | struct { | |
62 | uint64_t uses; | |
63 | uint64_t misses; | |
64 | /* Failures to suspend or restore fs handles. */ | |
65 | uint64_t errors; | |
66 | } stats; | |
67 | /* | |
68 | * The head of the active_handles list is always the least recently | |
69 | * used active handle. When an handle is used, it is removed from the | |
70 | * list and added to the end. When a file has to be suspended, the | |
71 | * first element in the list is "popped", suspended, and added to the | |
72 | * list of suspended handles. | |
73 | */ | |
74 | struct cds_list_head active_handles; | |
75 | struct cds_list_head suspended_handles; | |
76 | struct cds_lfht *unsuspendable_fds; | |
874a3441 | 77 | struct lttng_inode_registry *inode_registry; |
f7c3ffd7 JG |
78 | /* Unlinked files are moved in this directory under a unique name. */ |
79 | struct lttng_directory_handle *unlink_directory_handle; | |
80 | struct lttng_unlinked_file_pool *unlinked_file_pool; | |
df038819 JG |
81 | }; |
82 | ||
83 | struct open_properties { | |
df038819 | 84 | int flags; |
1e4f5625 | 85 | LTTNG_OPTIONAL(mode_t) mode; |
df038819 JG |
86 | }; |
87 | ||
88 | /* | |
f5ea0241 | 89 | * A fs_handle_tracked is not ref-counted. Therefore, it is assumed that a |
df038819 JG |
90 | * handle is never in-use while it is being reclaimed. It can be |
91 | * shared by multiple threads, but external synchronization is required | |
92 | * to ensure it is not still being used when it is reclaimed (close method). | |
93 | * In this respect, it is not different from a regular file descriptor. | |
94 | * | |
95 | * The fs_handle lock always nests _within_ the tracker's lock. | |
96 | */ | |
f5ea0241 JG |
97 | struct fs_handle_tracked { |
98 | struct fs_handle parent; | |
df038819 JG |
99 | pthread_mutex_t lock; |
100 | /* | |
101 | * Weak reference to the tracker. All fs_handles are assumed to have | |
102 | * been closed at the moment of the destruction of the fd_tracker. | |
103 | */ | |
104 | struct fd_tracker *tracker; | |
105 | struct open_properties properties; | |
874a3441 | 106 | struct lttng_inode *inode; |
df038819 JG |
107 | int fd; |
108 | /* inode number of the file at the time of the handle's creation. */ | |
109 | uint64_t ino; | |
110 | bool in_use; | |
111 | /* Offset to which the file should be restored. */ | |
112 | off_t offset; | |
113 | struct cds_list_head handles_list_node; | |
114 | }; | |
115 | ||
116 | struct unsuspendable_fd { | |
117 | /* | |
118 | * Accesses are only performed through the tracker, which is protected | |
119 | * by its own lock. | |
120 | */ | |
121 | int fd; | |
122 | char *name; | |
123 | struct cds_lfht_node tracker_node; | |
124 | struct rcu_head rcu_head; | |
125 | }; | |
126 | ||
127 | static struct { | |
128 | pthread_mutex_t lock; | |
129 | bool initialized; | |
130 | unsigned long value; | |
131 | } seed = { | |
132 | .lock = PTHREAD_MUTEX_INITIALIZER, | |
133 | }; | |
134 | ||
135 | static int match_fd(struct cds_lfht_node *node, const void *key); | |
136 | static void unsuspendable_fd_destroy(struct unsuspendable_fd *entry); | |
5c1f54d1 JG |
137 | static struct unsuspendable_fd *unsuspendable_fd_create( |
138 | const char *name, int fd); | |
f7c3ffd7 | 139 | static int open_from_properties(const struct lttng_directory_handle *dir_handle, |
5c1f54d1 | 140 | const char *path, struct open_properties *properties); |
df038819 | 141 | |
f5ea0241 JG |
142 | static void fs_handle_tracked_log(struct fs_handle_tracked *handle); |
143 | static int fs_handle_tracked_suspend(struct fs_handle_tracked *handle); | |
144 | static int fs_handle_tracked_restore(struct fs_handle_tracked *handle); | |
145 | static int fs_handle_tracked_get_fd(struct fs_handle *_handle); | |
146 | static void fs_handle_tracked_put_fd(struct fs_handle *_handle); | |
147 | static int fs_handle_tracked_unlink(struct fs_handle *_handle); | |
148 | static int fs_handle_tracked_close(struct fs_handle *_handle); | |
df038819 | 149 | |
5c1f54d1 | 150 | static void fd_tracker_track( |
f5ea0241 | 151 | struct fd_tracker *tracker, struct fs_handle_tracked *handle); |
5c1f54d1 | 152 | static void fd_tracker_untrack( |
f5ea0241 | 153 | struct fd_tracker *tracker, struct fs_handle_tracked *handle); |
5c1f54d1 JG |
154 | static int fd_tracker_suspend_handles( |
155 | struct fd_tracker *tracker, unsigned int count); | |
156 | static int fd_tracker_restore_handle( | |
f5ea0241 JG |
157 | struct fd_tracker *tracker, struct fs_handle_tracked *handle); |
158 | ||
df038819 | 159 | /* Match function of the tracker's unsuspendable_fds hash table. */ |
5c1f54d1 | 160 | static int match_fd(struct cds_lfht_node *node, const void *key) |
df038819 | 161 | { |
5c1f54d1 JG |
162 | struct unsuspendable_fd *entry = caa_container_of( |
163 | node, struct unsuspendable_fd, tracker_node); | |
df038819 | 164 | |
5c1f54d1 JG |
165 | return hash_match_key_ulong( |
166 | (void *) (unsigned long) entry->fd, (void *) key); | |
df038819 JG |
167 | } |
168 | ||
5c1f54d1 | 169 | static void delete_unsuspendable_fd(struct rcu_head *head) |
df038819 | 170 | { |
5c1f54d1 JG |
171 | struct unsuspendable_fd *fd = caa_container_of( |
172 | head, struct unsuspendable_fd, rcu_head); | |
df038819 JG |
173 | |
174 | free(fd->name); | |
175 | free(fd); | |
176 | } | |
177 | ||
5c1f54d1 | 178 | static void unsuspendable_fd_destroy(struct unsuspendable_fd *entry) |
df038819 JG |
179 | { |
180 | if (!entry) { | |
181 | return; | |
182 | } | |
183 | call_rcu(&entry->rcu_head, delete_unsuspendable_fd); | |
184 | } | |
185 | ||
5c1f54d1 JG |
186 | static struct unsuspendable_fd *unsuspendable_fd_create( |
187 | const char *name, int fd) | |
df038819 | 188 | { |
5c1f54d1 | 189 | struct unsuspendable_fd *entry = zmalloc(sizeof(*entry)); |
df038819 JG |
190 | |
191 | if (!entry) { | |
192 | goto error; | |
193 | } | |
194 | if (name) { | |
195 | entry->name = strdup(name); | |
196 | if (!entry->name) { | |
197 | goto error; | |
198 | } | |
199 | } | |
200 | cds_lfht_node_init(&entry->tracker_node); | |
201 | entry->fd = fd; | |
202 | return entry; | |
203 | error: | |
204 | unsuspendable_fd_destroy(entry); | |
205 | return NULL; | |
206 | } | |
207 | ||
f5ea0241 | 208 | static void fs_handle_tracked_log(struct fs_handle_tracked *handle) |
df038819 | 209 | { |
874a3441 JG |
210 | const char *path; |
211 | ||
df038819 | 212 | pthread_mutex_lock(&handle->lock); |
f7c3ffd7 | 213 | lttng_inode_get_location(handle->inode, NULL, &path); |
874a3441 | 214 | |
df038819 | 215 | if (handle->fd >= 0) { |
5c1f54d1 | 216 | DBG_NO_LOC(" %s [active, fd %d%s]", path, handle->fd, |
df038819 JG |
217 | handle->in_use ? ", in use" : ""); |
218 | } else { | |
874a3441 | 219 | DBG_NO_LOC(" %s [suspended]", path); |
df038819 JG |
220 | } |
221 | pthread_mutex_unlock(&handle->lock); | |
222 | } | |
223 | ||
874a3441 | 224 | /* Tracker lock must be held by the caller. */ |
f5ea0241 | 225 | static int fs_handle_tracked_suspend(struct fs_handle_tracked *handle) |
df038819 JG |
226 | { |
227 | int ret = 0; | |
228 | struct stat fs_stat; | |
874a3441 | 229 | const char *path; |
f7c3ffd7 | 230 | const struct lttng_directory_handle *node_directory_handle; |
df038819 JG |
231 | |
232 | pthread_mutex_lock(&handle->lock); | |
f7c3ffd7 | 233 | lttng_inode_get_location(handle->inode, &node_directory_handle, &path); |
df038819 JG |
234 | assert(handle->fd >= 0); |
235 | if (handle->in_use) { | |
236 | /* This handle can't be suspended as it is currently in use. */ | |
237 | ret = -EAGAIN; | |
238 | goto end; | |
239 | } | |
240 | ||
f7c3ffd7 JG |
241 | ret = lttng_directory_handle_stat( |
242 | node_directory_handle, path, &fs_stat); | |
df038819 | 243 | if (ret) { |
5c1f54d1 | 244 | PERROR("Filesystem handle to %s cannot be suspended as stat() failed", |
874a3441 | 245 | path); |
df038819 JG |
246 | ret = -errno; |
247 | goto end; | |
248 | } | |
249 | ||
250 | if (fs_stat.st_ino != handle->ino) { | |
251 | /* Don't suspend as the handle would not be restorable. */ | |
252 | WARN("Filesystem handle to %s cannot be suspended as its inode changed", | |
874a3441 | 253 | path); |
df038819 JG |
254 | ret = -ENOENT; |
255 | goto end; | |
256 | } | |
257 | ||
5c1f54d1 | 258 | handle->offset = lseek(handle->fd, 0, SEEK_CUR); |
df038819 JG |
259 | if (handle->offset == -1) { |
260 | WARN("Filesystem handle to %s cannot be suspended as lseek() failed to sample its current position", | |
874a3441 | 261 | path); |
df038819 JG |
262 | ret = -errno; |
263 | goto end; | |
264 | } | |
265 | ||
266 | ret = close(handle->fd); | |
267 | if (ret) { | |
5c1f54d1 | 268 | PERROR("Filesystem handle to %s cannot be suspended as close() failed", |
874a3441 | 269 | path); |
df038819 JG |
270 | ret = -errno; |
271 | goto end; | |
272 | } | |
273 | DBG("Suspended filesystem handle to %s (fd %i) at position %" PRId64, | |
874a3441 | 274 | path, handle->fd, handle->offset); |
df038819 JG |
275 | handle->fd = -1; |
276 | end: | |
277 | if (ret) { | |
278 | handle->tracker->stats.errors++; | |
279 | } | |
280 | pthread_mutex_unlock(&handle->lock); | |
281 | return ret; | |
282 | } | |
283 | ||
284 | /* Caller must hold the tracker and handle's locks. */ | |
f5ea0241 | 285 | static int fs_handle_tracked_restore(struct fs_handle_tracked *handle) |
df038819 JG |
286 | { |
287 | int ret, fd = -1; | |
f7c3ffd7 JG |
288 | const char *path; |
289 | const struct lttng_directory_handle *node_directory_handle; | |
290 | ||
291 | lttng_inode_get_location(handle->inode, &node_directory_handle, &path); | |
df038819 JG |
292 | |
293 | assert(handle->fd == -1); | |
874a3441 | 294 | assert(path); |
f7c3ffd7 JG |
295 | ret = open_from_properties( |
296 | node_directory_handle, path, &handle->properties); | |
df038819 | 297 | if (ret < 0) { |
5c1f54d1 | 298 | PERROR("Failed to restore filesystem handle to %s, open() failed", |
874a3441 | 299 | path); |
df038819 JG |
300 | ret = -errno; |
301 | goto end; | |
302 | } | |
303 | fd = ret; | |
304 | ||
305 | ret = lseek(fd, handle->offset, SEEK_SET); | |
306 | if (ret < 0) { | |
5c1f54d1 | 307 | PERROR("Failed to restore filesystem handle to %s, lseek() failed", |
874a3441 | 308 | path); |
df038819 JG |
309 | ret = -errno; |
310 | goto end; | |
311 | } | |
312 | DBG("Restored filesystem handle to %s (fd %i) at position %" PRId64, | |
874a3441 | 313 | path, fd, handle->offset); |
df038819 JG |
314 | ret = 0; |
315 | handle->fd = fd; | |
316 | fd = -1; | |
317 | end: | |
318 | if (fd >= 0) { | |
319 | (void) close(fd); | |
320 | } | |
321 | return ret; | |
322 | } | |
323 | ||
f7c3ffd7 | 324 | static int open_from_properties(const struct lttng_directory_handle *dir_handle, |
5c1f54d1 | 325 | const char *path, struct open_properties *properties) |
df038819 JG |
326 | { |
327 | int ret; | |
328 | ||
329 | /* | |
330 | * open() ignores the 'flags' parameter unless the O_CREAT or O_TMPFILE | |
331 | * flags are set. O_TMPFILE would not make sense in the context of a | |
332 | * suspendable fs_handle as it would not be restorable (see OPEN(2)), | |
333 | * thus it is ignored here. | |
334 | */ | |
335 | if ((properties->flags & O_CREAT) && properties->mode.is_set) { | |
f7c3ffd7 JG |
336 | ret = lttng_directory_handle_open_file(dir_handle, path, |
337 | properties->flags, properties->mode.value); | |
df038819 | 338 | } else { |
f7c3ffd7 JG |
339 | ret = lttng_directory_handle_open_file(dir_handle, path, |
340 | properties->flags, 0); | |
df038819 JG |
341 | } |
342 | /* | |
343 | * Some flags should not be used beyond the initial open() of a | |
344 | * restorable file system handle. O_CREAT and O_TRUNC must | |
345 | * be cleared since it would be unexpected to re-use them | |
346 | * when the handle is retored: | |
347 | * - O_CREAT should not be needed as the file has been created | |
348 | * on the initial call to open(), | |
349 | * - O_TRUNC would destroy the file's contents by truncating it | |
350 | * to length 0. | |
351 | */ | |
352 | properties->flags &= ~(O_CREAT | O_TRUNC); | |
353 | if (ret < 0) { | |
354 | ret = -errno; | |
355 | goto end; | |
356 | } | |
357 | end: | |
358 | return ret; | |
359 | } | |
360 | ||
f7c3ffd7 JG |
361 | struct fd_tracker *fd_tracker_create(const char *unlinked_file_path, |
362 | unsigned int capacity) | |
df038819 JG |
363 | { |
364 | struct fd_tracker *tracker = zmalloc(sizeof(struct fd_tracker)); | |
365 | ||
366 | if (!tracker) { | |
367 | goto end; | |
368 | } | |
369 | ||
370 | pthread_mutex_lock(&seed.lock); | |
371 | if (!seed.initialized) { | |
372 | seed.value = (unsigned long) time(NULL); | |
373 | seed.initialized = true; | |
374 | } | |
375 | pthread_mutex_unlock(&seed.lock); | |
376 | ||
377 | CDS_INIT_LIST_HEAD(&tracker->active_handles); | |
378 | CDS_INIT_LIST_HEAD(&tracker->suspended_handles); | |
379 | tracker->capacity = capacity; | |
380 | tracker->unsuspendable_fds = cds_lfht_new(DEFAULT_HT_SIZE, 1, 0, | |
381 | CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); | |
874a3441 JG |
382 | if (!tracker->unsuspendable_fds) { |
383 | ERR("Failed to create fd-tracker's unsuspendable_fds hash table"); | |
384 | goto error; | |
385 | } | |
386 | tracker->inode_registry = lttng_inode_registry_create(); | |
387 | if (!tracker->inode_registry) { | |
388 | ERR("Failed to create fd-tracker's inode registry"); | |
389 | goto error; | |
390 | } | |
f7c3ffd7 JG |
391 | |
392 | tracker->unlinked_file_pool = | |
393 | lttng_unlinked_file_pool_create(unlinked_file_path); | |
394 | if (!tracker->unlinked_file_pool) { | |
395 | goto error; | |
396 | } | |
df038819 JG |
397 | DBG("File descriptor tracker created with a limit of %u simultaneously-opened FDs", |
398 | capacity); | |
399 | end: | |
400 | return tracker; | |
874a3441 JG |
401 | error: |
402 | fd_tracker_destroy(tracker); | |
403 | return NULL; | |
df038819 JG |
404 | } |
405 | ||
406 | void fd_tracker_log(struct fd_tracker *tracker) | |
407 | { | |
f5ea0241 | 408 | struct fs_handle_tracked *handle; |
df038819 JG |
409 | struct unsuspendable_fd *unsuspendable_fd; |
410 | struct cds_lfht_iter iter; | |
411 | ||
412 | pthread_mutex_lock(&tracker->lock); | |
413 | DBG_NO_LOC("File descriptor tracker"); | |
414 | DBG_NO_LOC(" Stats:"); | |
415 | DBG_NO_LOC(" uses: %" PRIu64, tracker->stats.uses); | |
416 | DBG_NO_LOC(" misses: %" PRIu64, tracker->stats.misses); | |
417 | DBG_NO_LOC(" errors: %" PRIu64, tracker->stats.errors); | |
418 | DBG_NO_LOC(" Tracked: %u", TRACKED_COUNT(tracker)); | |
419 | DBG_NO_LOC(" active: %u", ACTIVE_COUNT(tracker)); | |
420 | DBG_NO_LOC(" suspendable: %u", SUSPENDABLE_COUNT(tracker)); | |
421 | DBG_NO_LOC(" unsuspendable: %u", UNSUSPENDABLE_COUNT(tracker)); | |
422 | DBG_NO_LOC(" suspended: %u", SUSPENDED_COUNT(tracker)); | |
423 | DBG_NO_LOC(" capacity: %u", tracker->capacity); | |
424 | ||
425 | DBG_NO_LOC(" Tracked suspendable file descriptors"); | |
f5ea0241 | 426 | cds_list_for_each_entry( |
5c1f54d1 | 427 | handle, &tracker->active_handles, handles_list_node) { |
f5ea0241 | 428 | fs_handle_tracked_log(handle); |
df038819 | 429 | } |
f5ea0241 | 430 | cds_list_for_each_entry(handle, &tracker->suspended_handles, |
df038819 | 431 | handles_list_node) { |
f5ea0241 | 432 | fs_handle_tracked_log(handle); |
df038819 JG |
433 | } |
434 | if (!SUSPENDABLE_COUNT(tracker)) { | |
435 | DBG_NO_LOC(" None"); | |
436 | } | |
437 | ||
438 | DBG_NO_LOC(" Tracked unsuspendable file descriptors"); | |
439 | rcu_read_lock(); | |
f5ea0241 | 440 | cds_lfht_for_each_entry(tracker->unsuspendable_fds, &iter, |
df038819 | 441 | unsuspendable_fd, tracker_node) { |
5c1f54d1 JG |
442 | DBG_NO_LOC(" %s [active, fd %d]", |
443 | unsuspendable_fd->name ?: "Unnamed", | |
df038819 JG |
444 | unsuspendable_fd->fd); |
445 | } | |
446 | rcu_read_unlock(); | |
447 | if (!UNSUSPENDABLE_COUNT(tracker)) { | |
448 | DBG_NO_LOC(" None"); | |
449 | } | |
450 | ||
451 | pthread_mutex_unlock(&tracker->lock); | |
452 | } | |
453 | ||
454 | int fd_tracker_destroy(struct fd_tracker *tracker) | |
455 | { | |
456 | int ret = 0; | |
457 | ||
458 | /* | |
459 | * Refuse to destroy the tracker as fs_handles may still old | |
460 | * weak references to the tracker. | |
461 | */ | |
462 | pthread_mutex_lock(&tracker->lock); | |
463 | if (TRACKED_COUNT(tracker)) { | |
464 | ERR("A file descriptor leak has been detected: %u tracked file descriptors are still being tracked", | |
465 | TRACKED_COUNT(tracker)); | |
466 | pthread_mutex_unlock(&tracker->lock); | |
467 | fd_tracker_log(tracker); | |
468 | ret = -1; | |
469 | goto end; | |
470 | } | |
471 | pthread_mutex_unlock(&tracker->lock); | |
472 | ||
874a3441 JG |
473 | if (tracker->unsuspendable_fds) { |
474 | ret = cds_lfht_destroy(tracker->unsuspendable_fds, NULL); | |
475 | assert(!ret); | |
476 | } | |
660a216c JG |
477 | |
478 | lttng_inode_registry_destroy(tracker->inode_registry); | |
f7c3ffd7 | 479 | lttng_unlinked_file_pool_destroy(tracker->unlinked_file_pool); |
df038819 JG |
480 | pthread_mutex_destroy(&tracker->lock); |
481 | free(tracker); | |
482 | end: | |
483 | return ret; | |
484 | } | |
485 | ||
486 | struct fs_handle *fd_tracker_open_fs_handle(struct fd_tracker *tracker, | |
f7c3ffd7 | 487 | struct lttng_directory_handle *directory, |
5c1f54d1 JG |
488 | const char *path, |
489 | int flags, | |
490 | mode_t *mode) | |
df038819 JG |
491 | { |
492 | int ret; | |
f5ea0241 | 493 | struct fs_handle_tracked *handle = NULL; |
df038819 JG |
494 | struct stat fd_stat; |
495 | struct open_properties properties = { | |
df038819 JG |
496 | .flags = flags, |
497 | .mode.is_set = !!mode, | |
498 | .mode.value = mode ? *mode : 0, | |
499 | }; | |
500 | ||
df038819 JG |
501 | pthread_mutex_lock(&tracker->lock); |
502 | if (ACTIVE_COUNT(tracker) == tracker->capacity) { | |
503 | if (tracker->count.suspendable.active > 0) { | |
504 | ret = fd_tracker_suspend_handles(tracker, 1); | |
505 | if (ret) { | |
19380ea8 | 506 | goto end; |
df038819 JG |
507 | } |
508 | } else { | |
509 | /* | |
510 | * There are not enough active suspendable file | |
19380ea8 JG |
511 | * descriptors to open a new fd and still accommodate |
512 | * the tracker's capacity. | |
df038819 JG |
513 | */ |
514 | WARN("Cannot open file system handle, too many unsuspendable file descriptors are opened (%u)", | |
515 | tracker->count.unsuspendable); | |
19380ea8 | 516 | goto end; |
df038819 JG |
517 | } |
518 | } | |
519 | ||
520 | handle = zmalloc(sizeof(*handle)); | |
521 | if (!handle) { | |
522 | goto end; | |
523 | } | |
f7c3ffd7 JG |
524 | handle->parent = (typeof(handle->parent)) { |
525 | .get_fd = fs_handle_tracked_get_fd, | |
526 | .put_fd = fs_handle_tracked_put_fd, | |
527 | .unlink = fs_handle_tracked_unlink, | |
528 | .close = fs_handle_tracked_close, | |
529 | }; | |
530 | ||
660a216c | 531 | handle->tracker = tracker; |
df038819 JG |
532 | |
533 | ret = pthread_mutex_init(&handle->lock, NULL); | |
534 | if (ret) { | |
535 | PERROR("Failed to initialize handle mutex while creating fs handle"); | |
19380ea8 | 536 | goto error_mutex_init; |
df038819 JG |
537 | } |
538 | ||
f7c3ffd7 | 539 | handle->fd = open_from_properties(directory, path, &properties); |
df038819 JG |
540 | if (handle->fd < 0) { |
541 | PERROR("Failed to open fs handle to %s, open() returned", path); | |
19380ea8 | 542 | goto error; |
df038819 JG |
543 | } |
544 | ||
545 | handle->properties = properties; | |
874a3441 | 546 | |
f7c3ffd7 JG |
547 | handle->inode = lttng_inode_registry_get_inode(tracker->inode_registry, |
548 | directory, path, handle->fd, | |
549 | tracker->unlinked_file_pool); | |
874a3441 | 550 | if (!handle->inode) { |
5c1f54d1 | 551 | ERR("Failed to get lttng_inode corresponding to file %s", path); |
19380ea8 | 552 | goto error; |
874a3441 | 553 | } |
df038819 JG |
554 | |
555 | if (fstat(handle->fd, &fd_stat)) { | |
556 | PERROR("Failed to retrieve file descriptor inode while creating fs handle, fstat() returned"); | |
19380ea8 | 557 | goto error; |
df038819 JG |
558 | } |
559 | handle->ino = fd_stat.st_ino; | |
560 | ||
561 | fd_tracker_track(tracker, handle); | |
df038819 | 562 | end: |
19380ea8 | 563 | pthread_mutex_unlock(&tracker->lock); |
f5ea0241 | 564 | return handle ? &handle->parent : NULL; |
19380ea8 | 565 | error: |
660a216c JG |
566 | if (handle->inode) { |
567 | lttng_inode_put(handle->inode); | |
568 | } | |
19380ea8 JG |
569 | pthread_mutex_destroy(&handle->lock); |
570 | error_mutex_init: | |
660a216c | 571 | free(handle); |
df038819 JG |
572 | handle = NULL; |
573 | goto end; | |
574 | } | |
575 | ||
576 | /* Caller must hold the tracker's lock. */ | |
5c1f54d1 JG |
577 | static int fd_tracker_suspend_handles( |
578 | struct fd_tracker *tracker, unsigned int count) | |
df038819 JG |
579 | { |
580 | unsigned int left_to_close = count; | |
f7c3ffd7 | 581 | unsigned int attempts_left = tracker->count.suspendable.active; |
f5ea0241 | 582 | struct fs_handle_tracked *handle, *tmp; |
df038819 | 583 | |
f5ea0241 | 584 | cds_list_for_each_entry_safe(handle, tmp, &tracker->active_handles, |
df038819 JG |
585 | handles_list_node) { |
586 | int ret; | |
587 | ||
588 | fd_tracker_untrack(tracker, handle); | |
f5ea0241 | 589 | ret = fs_handle_tracked_suspend(handle); |
df038819 JG |
590 | fd_tracker_track(tracker, handle); |
591 | if (!ret) { | |
592 | left_to_close--; | |
593 | } | |
f7c3ffd7 | 594 | attempts_left--; |
df038819 | 595 | |
f7c3ffd7 | 596 | if (left_to_close == 0 || attempts_left == 0) { |
df038819 JG |
597 | break; |
598 | } | |
599 | } | |
600 | return left_to_close ? -EMFILE : 0; | |
601 | } | |
602 | ||
603 | int fd_tracker_open_unsuspendable_fd(struct fd_tracker *tracker, | |
5c1f54d1 JG |
604 | int *out_fds, |
605 | const char **names, | |
606 | unsigned int fd_count, | |
607 | fd_open_cb open, | |
608 | void *user_data) | |
df038819 JG |
609 | { |
610 | int ret, user_ret, i, fds_to_suspend; | |
611 | unsigned int active_fds; | |
b13a2b17 | 612 | struct unsuspendable_fd **entries; |
df038819 | 613 | |
b13a2b17 JG |
614 | entries = zmalloc(fd_count * sizeof(*entries)); |
615 | if (!entries) { | |
616 | ret = -1; | |
617 | goto end; | |
618 | } | |
df038819 JG |
619 | |
620 | pthread_mutex_lock(&tracker->lock); | |
621 | ||
622 | active_fds = ACTIVE_COUNT(tracker); | |
5c1f54d1 JG |
623 | fds_to_suspend = (int) active_fds + (int) fd_count - |
624 | (int) tracker->capacity; | |
df038819 JG |
625 | if (fds_to_suspend > 0) { |
626 | if (fds_to_suspend <= tracker->count.suspendable.active) { | |
5c1f54d1 JG |
627 | ret = fd_tracker_suspend_handles( |
628 | tracker, fds_to_suspend); | |
df038819 | 629 | if (ret) { |
b13a2b17 | 630 | goto end_unlock; |
df038819 JG |
631 | } |
632 | } else { | |
633 | /* | |
634 | * There are not enough active suspendable file | |
635 | * descriptors to open a new fd and still accomodate the | |
636 | * tracker's capacity. | |
637 | */ | |
638 | WARN("Cannot open unsuspendable fd, too many unsuspendable file descriptors are opened (%u)", | |
639 | tracker->count.unsuspendable); | |
640 | ret = -EMFILE; | |
b13a2b17 | 641 | goto end_unlock; |
df038819 JG |
642 | } |
643 | } | |
644 | ||
645 | user_ret = open(user_data, out_fds); | |
646 | if (user_ret) { | |
647 | ret = user_ret; | |
b13a2b17 | 648 | goto end_unlock; |
df038819 JG |
649 | } |
650 | ||
651 | /* | |
652 | * Add the fds returned by the user's callback to the hashtable | |
653 | * of unsuspendable fds. | |
654 | */ | |
655 | for (i = 0; i < fd_count; i++) { | |
5c1f54d1 JG |
656 | struct unsuspendable_fd *entry = unsuspendable_fd_create( |
657 | names ? names[i] : NULL, out_fds[i]); | |
df038819 JG |
658 | |
659 | if (!entry) { | |
660 | ret = -1; | |
661 | goto end_free_entries; | |
662 | } | |
663 | entries[i] = entry; | |
664 | } | |
665 | ||
666 | rcu_read_lock(); | |
667 | for (i = 0; i < fd_count; i++) { | |
668 | struct cds_lfht_node *node; | |
669 | struct unsuspendable_fd *entry = entries[i]; | |
670 | ||
5c1f54d1 JG |
671 | node = cds_lfht_add_unique(tracker->unsuspendable_fds, |
672 | hash_key_ulong((void *) (unsigned long) | |
673 | out_fds[i], | |
df038819 | 674 | seed.value), |
5c1f54d1 | 675 | match_fd, (void *) (unsigned long) out_fds[i], |
df038819 JG |
676 | &entry->tracker_node); |
677 | ||
678 | if (node != &entry->tracker_node) { | |
679 | ret = -EEXIST; | |
680 | rcu_read_unlock(); | |
681 | goto end_free_entries; | |
682 | } | |
683 | entries[i] = NULL; | |
684 | } | |
685 | tracker->count.unsuspendable += fd_count; | |
686 | rcu_read_unlock(); | |
687 | ret = user_ret; | |
b13a2b17 | 688 | end_unlock: |
df038819 | 689 | pthread_mutex_unlock(&tracker->lock); |
b13a2b17 JG |
690 | end: |
691 | free(entries); | |
df038819 JG |
692 | return ret; |
693 | end_free_entries: | |
694 | for (i = 0; i < fd_count; i++) { | |
695 | unsuspendable_fd_destroy(entries[i]); | |
696 | } | |
b13a2b17 | 697 | goto end_unlock; |
df038819 JG |
698 | } |
699 | ||
700 | int fd_tracker_close_unsuspendable_fd(struct fd_tracker *tracker, | |
5c1f54d1 JG |
701 | int *fds_in, |
702 | unsigned int fd_count, | |
703 | fd_close_cb close, | |
df038819 JG |
704 | void *user_data) |
705 | { | |
706 | int i, ret, user_ret; | |
b13a2b17 | 707 | int *fds = NULL; |
df038819 JG |
708 | |
709 | /* | |
710 | * Maintain a local copy of fds_in as the user's callback may modify its | |
711 | * contents (e.g. setting the fd(s) to -1 after close). | |
712 | */ | |
b13a2b17 JG |
713 | fds = malloc(sizeof(*fds) * fd_count); |
714 | if (!fds) { | |
715 | ret = -1; | |
716 | goto end; | |
717 | } | |
df038819 JG |
718 | memcpy(fds, fds_in, sizeof(*fds) * fd_count); |
719 | ||
720 | pthread_mutex_lock(&tracker->lock); | |
721 | rcu_read_lock(); | |
722 | ||
723 | /* Let the user close the file descriptors. */ | |
724 | user_ret = close(user_data, fds_in); | |
725 | if (user_ret) { | |
726 | ret = user_ret; | |
b13a2b17 | 727 | goto end_unlock; |
df038819 JG |
728 | } |
729 | ||
730 | /* Untrack the fds that were just closed by the user's callback. */ | |
731 | for (i = 0; i < fd_count; i++) { | |
732 | struct cds_lfht_node *node; | |
733 | struct cds_lfht_iter iter; | |
734 | struct unsuspendable_fd *entry; | |
735 | ||
736 | cds_lfht_lookup(tracker->unsuspendable_fds, | |
737 | hash_key_ulong((void *) (unsigned long) fds[i], | |
738 | seed.value), | |
5c1f54d1 | 739 | match_fd, (void *) (unsigned long) fds[i], |
df038819 JG |
740 | &iter); |
741 | node = cds_lfht_iter_get_node(&iter); | |
742 | if (!node) { | |
743 | /* Unknown file descriptor. */ | |
744 | WARN("Untracked file descriptor %d passed to fd_tracker_close_unsuspendable_fd()", | |
745 | fds[i]); | |
746 | ret = -EINVAL; | |
b13a2b17 | 747 | goto end_unlock; |
df038819 | 748 | } |
5c1f54d1 JG |
749 | entry = caa_container_of( |
750 | node, struct unsuspendable_fd, tracker_node); | |
df038819 JG |
751 | |
752 | cds_lfht_del(tracker->unsuspendable_fds, node); | |
753 | unsuspendable_fd_destroy(entry); | |
754 | fds[i] = -1; | |
755 | } | |
756 | ||
757 | tracker->count.unsuspendable -= fd_count; | |
758 | ret = 0; | |
b13a2b17 | 759 | end_unlock: |
df038819 JG |
760 | rcu_read_unlock(); |
761 | pthread_mutex_unlock(&tracker->lock); | |
b13a2b17 JG |
762 | free(fds); |
763 | end: | |
df038819 JG |
764 | return ret; |
765 | } | |
766 | ||
767 | /* Caller must have taken the tracker's and handle's locks. */ | |
5c1f54d1 | 768 | static void fd_tracker_track( |
f5ea0241 | 769 | struct fd_tracker *tracker, struct fs_handle_tracked *handle) |
df038819 JG |
770 | { |
771 | if (handle->fd >= 0) { | |
772 | tracker->count.suspendable.active++; | |
773 | cds_list_add_tail(&handle->handles_list_node, | |
774 | &tracker->active_handles); | |
775 | } else { | |
776 | tracker->count.suspendable.suspended++; | |
777 | cds_list_add_tail(&handle->handles_list_node, | |
778 | &tracker->suspended_handles); | |
779 | } | |
780 | } | |
781 | ||
782 | /* Caller must have taken the tracker's and handle's locks. */ | |
5c1f54d1 | 783 | static void fd_tracker_untrack( |
f5ea0241 | 784 | struct fd_tracker *tracker, struct fs_handle_tracked *handle) |
df038819 JG |
785 | { |
786 | if (handle->fd >= 0) { | |
787 | tracker->count.suspendable.active--; | |
788 | } else { | |
789 | tracker->count.suspendable.suspended--; | |
790 | } | |
791 | cds_list_del(&handle->handles_list_node); | |
792 | } | |
793 | ||
794 | /* Caller must have taken the tracker's and handle's locks. */ | |
5c1f54d1 | 795 | static int fd_tracker_restore_handle( |
f5ea0241 | 796 | struct fd_tracker *tracker, struct fs_handle_tracked *handle) |
df038819 JG |
797 | { |
798 | int ret; | |
799 | ||
800 | fd_tracker_untrack(tracker, handle); | |
801 | if (ACTIVE_COUNT(tracker) >= tracker->capacity) { | |
802 | ret = fd_tracker_suspend_handles(tracker, 1); | |
803 | if (ret) { | |
804 | goto end; | |
805 | } | |
806 | } | |
f5ea0241 | 807 | ret = fs_handle_tracked_restore(handle); |
df038819 JG |
808 | end: |
809 | fd_tracker_track(tracker, handle); | |
810 | return ret ? ret : handle->fd; | |
811 | } | |
812 | ||
f5ea0241 | 813 | static int fs_handle_tracked_get_fd(struct fs_handle *_handle) |
df038819 JG |
814 | { |
815 | int ret; | |
f5ea0241 JG |
816 | struct fs_handle_tracked *handle = |
817 | container_of(_handle, struct fs_handle_tracked, parent); | |
df038819 JG |
818 | |
819 | /* | |
820 | * TODO This should be optimized as it is a fairly hot path. | |
821 | * The fd-tracker's lock should only be taken when a fs_handle is | |
822 | * restored (slow path). On the fast path (fs_handle is active), | |
823 | * the only effect on the fd_tracker is marking the handle as the | |
824 | * most recently used. Currently, it is done by a call to the | |
825 | * track/untrack helpers, but it should be done atomically. | |
826 | * | |
827 | * Note that the lock's nesting order must still be respected here. | |
828 | * The handle's lock nests inside the tracker's lock. | |
829 | */ | |
830 | pthread_mutex_lock(&handle->tracker->lock); | |
831 | pthread_mutex_lock(&handle->lock); | |
832 | assert(!handle->in_use); | |
833 | ||
834 | handle->tracker->stats.uses++; | |
835 | if (handle->fd >= 0) { | |
836 | ret = handle->fd; | |
837 | /* Mark as most recently used. */ | |
838 | fd_tracker_untrack(handle->tracker, handle); | |
839 | fd_tracker_track(handle->tracker, handle); | |
840 | } else { | |
841 | handle->tracker->stats.misses++; | |
842 | ret = fd_tracker_restore_handle(handle->tracker, handle); | |
843 | if (ret < 0) { | |
844 | handle->tracker->stats.errors++; | |
845 | goto end; | |
846 | } | |
847 | } | |
848 | handle->in_use = true; | |
849 | end: | |
850 | pthread_mutex_unlock(&handle->lock); | |
851 | pthread_mutex_unlock(&handle->tracker->lock); | |
852 | return ret; | |
853 | } | |
854 | ||
f5ea0241 | 855 | static void fs_handle_tracked_put_fd(struct fs_handle *_handle) |
df038819 | 856 | { |
f5ea0241 JG |
857 | struct fs_handle_tracked *handle = |
858 | container_of(_handle, struct fs_handle_tracked, parent); | |
859 | ||
df038819 JG |
860 | pthread_mutex_lock(&handle->lock); |
861 | handle->in_use = false; | |
862 | pthread_mutex_unlock(&handle->lock); | |
863 | } | |
864 | ||
f5ea0241 | 865 | static int fs_handle_tracked_unlink(struct fs_handle *_handle) |
9d16fc7f JG |
866 | { |
867 | int ret; | |
f5ea0241 JG |
868 | struct fs_handle_tracked *handle = |
869 | container_of(_handle, struct fs_handle_tracked, parent); | |
9d16fc7f JG |
870 | |
871 | pthread_mutex_lock(&handle->tracker->lock); | |
872 | pthread_mutex_lock(&handle->lock); | |
f7c3ffd7 | 873 | ret = lttng_inode_unlink(handle->inode); |
9d16fc7f JG |
874 | pthread_mutex_unlock(&handle->lock); |
875 | pthread_mutex_unlock(&handle->tracker->lock); | |
876 | return ret; | |
877 | } | |
878 | ||
f5ea0241 | 879 | static int fs_handle_tracked_close(struct fs_handle *_handle) |
df038819 JG |
880 | { |
881 | int ret = 0; | |
660a216c | 882 | const char *path = NULL; |
f5ea0241 JG |
883 | struct fs_handle_tracked *handle = |
884 | container_of(_handle, struct fs_handle_tracked, parent); | |
df038819 JG |
885 | |
886 | if (!handle) { | |
887 | ret = -EINVAL; | |
888 | goto end; | |
889 | } | |
890 | ||
891 | pthread_mutex_lock(&handle->tracker->lock); | |
892 | pthread_mutex_lock(&handle->lock); | |
660a216c | 893 | if (handle->inode) { |
f7c3ffd7 | 894 | lttng_inode_get_location(handle->inode, NULL, &path); |
660a216c | 895 | } |
df038819 JG |
896 | fd_tracker_untrack(handle->tracker, handle); |
897 | if (handle->fd >= 0) { | |
898 | /* | |
899 | * The return value of close() is not propagated as there | |
900 | * isn't much the user can do about it. | |
901 | */ | |
902 | if (close(handle->fd)) { | |
903 | PERROR("Failed to close the file descritptor (%d) of fs handle to %s, close() returned", | |
660a216c | 904 | handle->fd, path ? path : "Unknown"); |
df038819 JG |
905 | } |
906 | handle->fd = -1; | |
907 | } | |
4c372c54 JG |
908 | if (handle->inode) { |
909 | lttng_inode_put(handle->inode); | |
910 | } | |
df038819 JG |
911 | pthread_mutex_unlock(&handle->lock); |
912 | pthread_mutex_destroy(&handle->lock); | |
913 | pthread_mutex_unlock(&handle->tracker->lock); | |
df038819 JG |
914 | free(handle); |
915 | end: | |
916 | return ret; | |
917 | } |