1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * fs/timerfd.c |
4 | * |
5 | * Copyright (C) 2007 Davide Libenzi <davidel@xmailserver.org> |
6 | * |
7 | * |
8 | * Thanks to Thomas Gleixner for code reviews and useful comments. |
9 | * |
10 | */ |
11 | |
12 | #include <linux/alarmtimer.h> |
13 | #include <linux/file.h> |
14 | #include <linux/poll.h> |
15 | #include <linux/init.h> |
16 | #include <linux/fs.h> |
17 | #include <linux/sched.h> |
18 | #include <linux/kernel.h> |
19 | #include <linux/slab.h> |
20 | #include <linux/list.h> |
21 | #include <linux/spinlock.h> |
22 | #include <linux/time.h> |
23 | #include <linux/hrtimer.h> |
24 | #include <linux/anon_inodes.h> |
25 | #include <linux/timerfd.h> |
26 | #include <linux/syscalls.h> |
27 | #include <linux/compat.h> |
28 | #include <linux/rcupdate.h> |
29 | #include <linux/time_namespace.h> |
30 | |
31 | struct timerfd_ctx { |
32 | union { |
33 | struct hrtimer tmr; |
34 | struct alarm alarm; |
35 | } t; |
36 | ktime_t tintv; |
37 | ktime_t moffs; |
38 | wait_queue_head_t wqh; |
39 | u64 ticks; |
40 | int clockid; |
41 | short unsigned expired; |
42 | short unsigned settime_flags; /* to show in fdinfo */ |
43 | struct rcu_head rcu; |
44 | struct list_head clist; |
45 | spinlock_t cancel_lock; |
46 | bool might_cancel; |
47 | }; |
48 | |
49 | static LIST_HEAD(cancel_list); |
50 | static DEFINE_SPINLOCK(cancel_lock); |
51 | |
52 | static inline bool isalarm(struct timerfd_ctx *ctx) |
53 | { |
54 | return ctx->clockid == CLOCK_REALTIME_ALARM || |
55 | ctx->clockid == CLOCK_BOOTTIME_ALARM; |
56 | } |
57 | |
58 | /* |
59 | * This gets called when the timer event triggers. We set the "expired" |
60 | * flag, but we do not re-arm the timer (in case it's necessary, |
61 | * tintv != 0) until the timer is accessed. |
62 | */ |
63 | static void timerfd_triggered(struct timerfd_ctx *ctx) |
64 | { |
65 | unsigned long flags; |
66 | |
67 | spin_lock_irqsave(&ctx->wqh.lock, flags); |
68 | ctx->expired = 1; |
69 | ctx->ticks++; |
70 | wake_up_locked_poll(&ctx->wqh, EPOLLIN); |
71 | spin_unlock_irqrestore(lock: &ctx->wqh.lock, flags); |
72 | } |
73 | |
74 | static enum hrtimer_restart timerfd_tmrproc(struct hrtimer *htmr) |
75 | { |
76 | struct timerfd_ctx *ctx = container_of(htmr, struct timerfd_ctx, |
77 | t.tmr); |
78 | timerfd_triggered(ctx); |
79 | return HRTIMER_NORESTART; |
80 | } |
81 | |
82 | static enum alarmtimer_restart timerfd_alarmproc(struct alarm *alarm, |
83 | ktime_t now) |
84 | { |
85 | struct timerfd_ctx *ctx = container_of(alarm, struct timerfd_ctx, |
86 | t.alarm); |
87 | timerfd_triggered(ctx); |
88 | return ALARMTIMER_NORESTART; |
89 | } |
90 | |
91 | /* |
92 | * Called when the clock was set to cancel the timers in the cancel |
93 | * list. This will wake up processes waiting on these timers. The |
94 | * wake-up requires ctx->ticks to be non zero, therefore we increment |
95 | * it before calling wake_up_locked(). |
96 | */ |
97 | void timerfd_clock_was_set(void) |
98 | { |
99 | ktime_t moffs = ktime_mono_to_real(mono: 0); |
100 | struct timerfd_ctx *ctx; |
101 | unsigned long flags; |
102 | |
103 | rcu_read_lock(); |
104 | list_for_each_entry_rcu(ctx, &cancel_list, clist) { |
105 | if (!ctx->might_cancel) |
106 | continue; |
107 | spin_lock_irqsave(&ctx->wqh.lock, flags); |
108 | if (ctx->moffs != moffs) { |
109 | ctx->moffs = KTIME_MAX; |
110 | ctx->ticks++; |
111 | wake_up_locked_poll(&ctx->wqh, EPOLLIN); |
112 | } |
113 | spin_unlock_irqrestore(lock: &ctx->wqh.lock, flags); |
114 | } |
115 | rcu_read_unlock(); |
116 | } |
117 | |
118 | static void timerfd_resume_work(struct work_struct *work) |
119 | { |
120 | timerfd_clock_was_set(); |
121 | } |
122 | |
123 | static DECLARE_WORK(timerfd_work, timerfd_resume_work); |
124 | |
125 | /* |
126 | * Invoked from timekeeping_resume(). Defer the actual update to work so |
127 | * timerfd_clock_was_set() runs in task context. |
128 | */ |
129 | void timerfd_resume(void) |
130 | { |
131 | schedule_work(work: &timerfd_work); |
132 | } |
133 | |
134 | static void __timerfd_remove_cancel(struct timerfd_ctx *ctx) |
135 | { |
136 | if (ctx->might_cancel) { |
137 | ctx->might_cancel = false; |
138 | spin_lock(lock: &cancel_lock); |
139 | list_del_rcu(entry: &ctx->clist); |
140 | spin_unlock(lock: &cancel_lock); |
141 | } |
142 | } |
143 | |
144 | static void timerfd_remove_cancel(struct timerfd_ctx *ctx) |
145 | { |
146 | spin_lock(lock: &ctx->cancel_lock); |
147 | __timerfd_remove_cancel(ctx); |
148 | spin_unlock(lock: &ctx->cancel_lock); |
149 | } |
150 | |
151 | static bool timerfd_canceled(struct timerfd_ctx *ctx) |
152 | { |
153 | if (!ctx->might_cancel || ctx->moffs != KTIME_MAX) |
154 | return false; |
155 | ctx->moffs = ktime_mono_to_real(mono: 0); |
156 | return true; |
157 | } |
158 | |
159 | static void timerfd_setup_cancel(struct timerfd_ctx *ctx, int flags) |
160 | { |
161 | spin_lock(lock: &ctx->cancel_lock); |
162 | if ((ctx->clockid == CLOCK_REALTIME || |
163 | ctx->clockid == CLOCK_REALTIME_ALARM) && |
164 | (flags & TFD_TIMER_ABSTIME) && (flags & TFD_TIMER_CANCEL_ON_SET)) { |
165 | if (!ctx->might_cancel) { |
166 | ctx->might_cancel = true; |
167 | spin_lock(lock: &cancel_lock); |
168 | list_add_rcu(new: &ctx->clist, head: &cancel_list); |
169 | spin_unlock(lock: &cancel_lock); |
170 | } |
171 | } else { |
172 | __timerfd_remove_cancel(ctx); |
173 | } |
174 | spin_unlock(lock: &ctx->cancel_lock); |
175 | } |
176 | |
177 | static ktime_t timerfd_get_remaining(struct timerfd_ctx *ctx) |
178 | { |
179 | ktime_t remaining; |
180 | |
181 | if (isalarm(ctx)) |
182 | remaining = alarm_expires_remaining(alarm: &ctx->t.alarm); |
183 | else |
184 | remaining = hrtimer_expires_remaining_adjusted(timer: &ctx->t.tmr); |
185 | |
186 | return remaining < 0 ? 0: remaining; |
187 | } |
188 | |
189 | static int timerfd_setup(struct timerfd_ctx *ctx, int flags, |
190 | const struct itimerspec64 *ktmr) |
191 | { |
192 | enum hrtimer_mode htmode; |
193 | ktime_t texp; |
194 | int clockid = ctx->clockid; |
195 | |
196 | htmode = (flags & TFD_TIMER_ABSTIME) ? |
197 | HRTIMER_MODE_ABS: HRTIMER_MODE_REL; |
198 | |
199 | texp = timespec64_to_ktime(ts: ktmr->it_value); |
200 | ctx->expired = 0; |
201 | ctx->ticks = 0; |
202 | ctx->tintv = timespec64_to_ktime(ts: ktmr->it_interval); |
203 | |
204 | if (isalarm(ctx)) { |
205 | alarm_init(alarm: &ctx->t.alarm, |
206 | type: ctx->clockid == CLOCK_REALTIME_ALARM ? |
207 | ALARM_REALTIME : ALARM_BOOTTIME, |
208 | function: timerfd_alarmproc); |
209 | } else { |
210 | hrtimer_init(timer: &ctx->t.tmr, which_clock: clockid, mode: htmode); |
211 | hrtimer_set_expires(timer: &ctx->t.tmr, time: texp); |
212 | ctx->t.tmr.function = timerfd_tmrproc; |
213 | } |
214 | |
215 | if (texp != 0) { |
216 | if (flags & TFD_TIMER_ABSTIME) |
217 | texp = timens_ktime_to_host(clockid, tim: texp); |
218 | if (isalarm(ctx)) { |
219 | if (flags & TFD_TIMER_ABSTIME) |
220 | alarm_start(alarm: &ctx->t.alarm, start: texp); |
221 | else |
222 | alarm_start_relative(alarm: &ctx->t.alarm, start: texp); |
223 | } else { |
224 | hrtimer_start(timer: &ctx->t.tmr, tim: texp, mode: htmode); |
225 | } |
226 | |
227 | if (timerfd_canceled(ctx)) |
228 | return -ECANCELED; |
229 | } |
230 | |
231 | ctx->settime_flags = flags & TFD_SETTIME_FLAGS; |
232 | return 0; |
233 | } |
234 | |
235 | static int timerfd_release(struct inode *inode, struct file *file) |
236 | { |
237 | struct timerfd_ctx *ctx = file->private_data; |
238 | |
239 | timerfd_remove_cancel(ctx); |
240 | |
241 | if (isalarm(ctx)) |
242 | alarm_cancel(alarm: &ctx->t.alarm); |
243 | else |
244 | hrtimer_cancel(timer: &ctx->t.tmr); |
245 | kfree_rcu(ctx, rcu); |
246 | return 0; |
247 | } |
248 | |
249 | static __poll_t timerfd_poll(struct file *file, poll_table *wait) |
250 | { |
251 | struct timerfd_ctx *ctx = file->private_data; |
252 | __poll_t events = 0; |
253 | unsigned long flags; |
254 | |
255 | poll_wait(filp: file, wait_address: &ctx->wqh, p: wait); |
256 | |
257 | spin_lock_irqsave(&ctx->wqh.lock, flags); |
258 | if (ctx->ticks) |
259 | events |= EPOLLIN; |
260 | spin_unlock_irqrestore(lock: &ctx->wqh.lock, flags); |
261 | |
262 | return events; |
263 | } |
264 | |
265 | static ssize_t timerfd_read(struct file *file, char __user *buf, size_t count, |
266 | loff_t *ppos) |
267 | { |
268 | struct timerfd_ctx *ctx = file->private_data; |
269 | ssize_t res; |
270 | u64 ticks = 0; |
271 | |
272 | if (count < sizeof(ticks)) |
273 | return -EINVAL; |
274 | spin_lock_irq(lock: &ctx->wqh.lock); |
275 | if (file->f_flags & O_NONBLOCK) |
276 | res = -EAGAIN; |
277 | else |
278 | res = wait_event_interruptible_locked_irq(ctx->wqh, ctx->ticks); |
279 | |
280 | /* |
281 | * If clock has changed, we do not care about the |
282 | * ticks and we do not rearm the timer. Userspace must |
283 | * reevaluate anyway. |
284 | */ |
285 | if (timerfd_canceled(ctx)) { |
286 | ctx->ticks = 0; |
287 | ctx->expired = 0; |
288 | res = -ECANCELED; |
289 | } |
290 | |
291 | if (ctx->ticks) { |
292 | ticks = ctx->ticks; |
293 | |
294 | if (ctx->expired && ctx->tintv) { |
295 | /* |
296 | * If tintv != 0, this is a periodic timer that |
297 | * needs to be re-armed. We avoid doing it in the timer |
298 | * callback to avoid DoS attacks specifying a very |
299 | * short timer period. |
300 | */ |
301 | if (isalarm(ctx)) { |
302 | ticks += alarm_forward_now( |
303 | alarm: &ctx->t.alarm, interval: ctx->tintv) - 1; |
304 | alarm_restart(alarm: &ctx->t.alarm); |
305 | } else { |
306 | ticks += hrtimer_forward_now(timer: &ctx->t.tmr, |
307 | interval: ctx->tintv) - 1; |
308 | hrtimer_restart(timer: &ctx->t.tmr); |
309 | } |
310 | } |
311 | ctx->expired = 0; |
312 | ctx->ticks = 0; |
313 | } |
314 | spin_unlock_irq(lock: &ctx->wqh.lock); |
315 | if (ticks) |
316 | res = put_user(ticks, (u64 __user *) buf) ? -EFAULT: sizeof(ticks); |
317 | return res; |
318 | } |
319 | |
320 | #ifdef CONFIG_PROC_FS |
321 | static void timerfd_show(struct seq_file *m, struct file *file) |
322 | { |
323 | struct timerfd_ctx *ctx = file->private_data; |
324 | struct timespec64 value, interval; |
325 | |
326 | spin_lock_irq(lock: &ctx->wqh.lock); |
327 | value = ktime_to_timespec64(timerfd_get_remaining(ctx)); |
328 | interval = ktime_to_timespec64(ctx->tintv); |
329 | spin_unlock_irq(lock: &ctx->wqh.lock); |
330 | |
331 | seq_printf(m, |
332 | fmt: "clockid: %d\n" |
333 | "ticks: %llu\n" |
334 | "settime flags: 0%o\n" |
335 | "it_value: (%llu, %llu)\n" |
336 | "it_interval: (%llu, %llu)\n" , |
337 | ctx->clockid, |
338 | (unsigned long long)ctx->ticks, |
339 | ctx->settime_flags, |
340 | (unsigned long long)value.tv_sec, |
341 | (unsigned long long)value.tv_nsec, |
342 | (unsigned long long)interval.tv_sec, |
343 | (unsigned long long)interval.tv_nsec); |
344 | } |
345 | #else |
346 | #define timerfd_show NULL |
347 | #endif |
348 | |
349 | #ifdef CONFIG_CHECKPOINT_RESTORE |
350 | static long timerfd_ioctl(struct file *file, unsigned int cmd, unsigned long arg) |
351 | { |
352 | struct timerfd_ctx *ctx = file->private_data; |
353 | int ret = 0; |
354 | |
355 | switch (cmd) { |
356 | case TFD_IOC_SET_TICKS: { |
357 | u64 ticks; |
358 | |
359 | if (copy_from_user(to: &ticks, from: (u64 __user *)arg, n: sizeof(ticks))) |
360 | return -EFAULT; |
361 | if (!ticks) |
362 | return -EINVAL; |
363 | |
364 | spin_lock_irq(lock: &ctx->wqh.lock); |
365 | if (!timerfd_canceled(ctx)) { |
366 | ctx->ticks = ticks; |
367 | wake_up_locked_poll(&ctx->wqh, EPOLLIN); |
368 | } else |
369 | ret = -ECANCELED; |
370 | spin_unlock_irq(lock: &ctx->wqh.lock); |
371 | break; |
372 | } |
373 | default: |
374 | ret = -ENOTTY; |
375 | break; |
376 | } |
377 | |
378 | return ret; |
379 | } |
380 | #else |
381 | #define timerfd_ioctl NULL |
382 | #endif |
383 | |
384 | static const struct file_operations timerfd_fops = { |
385 | .release = timerfd_release, |
386 | .poll = timerfd_poll, |
387 | .read = timerfd_read, |
388 | .llseek = noop_llseek, |
389 | .show_fdinfo = timerfd_show, |
390 | .unlocked_ioctl = timerfd_ioctl, |
391 | }; |
392 | |
393 | static int timerfd_fget(int fd, struct fd *p) |
394 | { |
395 | struct fd f = fdget(fd); |
396 | if (!f.file) |
397 | return -EBADF; |
398 | if (f.file->f_op != &timerfd_fops) { |
399 | fdput(fd: f); |
400 | return -EINVAL; |
401 | } |
402 | *p = f; |
403 | return 0; |
404 | } |
405 | |
406 | SYSCALL_DEFINE2(timerfd_create, int, clockid, int, flags) |
407 | { |
408 | int ufd; |
409 | struct timerfd_ctx *ctx; |
410 | |
411 | /* Check the TFD_* constants for consistency. */ |
412 | BUILD_BUG_ON(TFD_CLOEXEC != O_CLOEXEC); |
413 | BUILD_BUG_ON(TFD_NONBLOCK != O_NONBLOCK); |
414 | |
415 | if ((flags & ~TFD_CREATE_FLAGS) || |
416 | (clockid != CLOCK_MONOTONIC && |
417 | clockid != CLOCK_REALTIME && |
418 | clockid != CLOCK_REALTIME_ALARM && |
419 | clockid != CLOCK_BOOTTIME && |
420 | clockid != CLOCK_BOOTTIME_ALARM)) |
421 | return -EINVAL; |
422 | |
423 | if ((clockid == CLOCK_REALTIME_ALARM || |
424 | clockid == CLOCK_BOOTTIME_ALARM) && |
425 | !capable(CAP_WAKE_ALARM)) |
426 | return -EPERM; |
427 | |
428 | ctx = kzalloc(size: sizeof(*ctx), GFP_KERNEL); |
429 | if (!ctx) |
430 | return -ENOMEM; |
431 | |
432 | init_waitqueue_head(&ctx->wqh); |
433 | spin_lock_init(&ctx->cancel_lock); |
434 | ctx->clockid = clockid; |
435 | |
436 | if (isalarm(ctx)) |
437 | alarm_init(alarm: &ctx->t.alarm, |
438 | type: ctx->clockid == CLOCK_REALTIME_ALARM ? |
439 | ALARM_REALTIME : ALARM_BOOTTIME, |
440 | function: timerfd_alarmproc); |
441 | else |
442 | hrtimer_init(timer: &ctx->t.tmr, which_clock: clockid, mode: HRTIMER_MODE_ABS); |
443 | |
444 | ctx->moffs = ktime_mono_to_real(mono: 0); |
445 | |
446 | ufd = anon_inode_getfd(name: "[timerfd]" , fops: &timerfd_fops, priv: ctx, |
447 | O_RDWR | (flags & TFD_SHARED_FCNTL_FLAGS)); |
448 | if (ufd < 0) |
449 | kfree(objp: ctx); |
450 | |
451 | return ufd; |
452 | } |
453 | |
454 | static int do_timerfd_settime(int ufd, int flags, |
455 | const struct itimerspec64 *new, |
456 | struct itimerspec64 *old) |
457 | { |
458 | struct fd f; |
459 | struct timerfd_ctx *ctx; |
460 | int ret; |
461 | |
462 | if ((flags & ~TFD_SETTIME_FLAGS) || |
463 | !itimerspec64_valid(its: new)) |
464 | return -EINVAL; |
465 | |
466 | ret = timerfd_fget(fd: ufd, p: &f); |
467 | if (ret) |
468 | return ret; |
469 | ctx = f.file->private_data; |
470 | |
471 | if (isalarm(ctx) && !capable(CAP_WAKE_ALARM)) { |
472 | fdput(fd: f); |
473 | return -EPERM; |
474 | } |
475 | |
476 | timerfd_setup_cancel(ctx, flags); |
477 | |
478 | /* |
479 | * We need to stop the existing timer before reprogramming |
480 | * it to the new values. |
481 | */ |
482 | for (;;) { |
483 | spin_lock_irq(lock: &ctx->wqh.lock); |
484 | |
485 | if (isalarm(ctx)) { |
486 | if (alarm_try_to_cancel(alarm: &ctx->t.alarm) >= 0) |
487 | break; |
488 | } else { |
489 | if (hrtimer_try_to_cancel(timer: &ctx->t.tmr) >= 0) |
490 | break; |
491 | } |
492 | spin_unlock_irq(lock: &ctx->wqh.lock); |
493 | |
494 | if (isalarm(ctx)) |
495 | hrtimer_cancel_wait_running(timer: &ctx->t.alarm.timer); |
496 | else |
497 | hrtimer_cancel_wait_running(timer: &ctx->t.tmr); |
498 | } |
499 | |
500 | /* |
501 | * If the timer is expired and it's periodic, we need to advance it |
502 | * because the caller may want to know the previous expiration time. |
503 | * We do not update "ticks" and "expired" since the timer will be |
504 | * re-programmed again in the following timerfd_setup() call. |
505 | */ |
506 | if (ctx->expired && ctx->tintv) { |
507 | if (isalarm(ctx)) |
508 | alarm_forward_now(alarm: &ctx->t.alarm, interval: ctx->tintv); |
509 | else |
510 | hrtimer_forward_now(timer: &ctx->t.tmr, interval: ctx->tintv); |
511 | } |
512 | |
513 | old->it_value = ktime_to_timespec64(timerfd_get_remaining(ctx)); |
514 | old->it_interval = ktime_to_timespec64(ctx->tintv); |
515 | |
516 | /* |
517 | * Re-program the timer to the new value ... |
518 | */ |
519 | ret = timerfd_setup(ctx, flags, ktmr: new); |
520 | |
521 | spin_unlock_irq(lock: &ctx->wqh.lock); |
522 | fdput(fd: f); |
523 | return ret; |
524 | } |
525 | |
526 | static int do_timerfd_gettime(int ufd, struct itimerspec64 *t) |
527 | { |
528 | struct fd f; |
529 | struct timerfd_ctx *ctx; |
530 | int ret = timerfd_fget(fd: ufd, p: &f); |
531 | if (ret) |
532 | return ret; |
533 | ctx = f.file->private_data; |
534 | |
535 | spin_lock_irq(lock: &ctx->wqh.lock); |
536 | if (ctx->expired && ctx->tintv) { |
537 | ctx->expired = 0; |
538 | |
539 | if (isalarm(ctx)) { |
540 | ctx->ticks += |
541 | alarm_forward_now( |
542 | alarm: &ctx->t.alarm, interval: ctx->tintv) - 1; |
543 | alarm_restart(alarm: &ctx->t.alarm); |
544 | } else { |
545 | ctx->ticks += |
546 | hrtimer_forward_now(timer: &ctx->t.tmr, interval: ctx->tintv) |
547 | - 1; |
548 | hrtimer_restart(timer: &ctx->t.tmr); |
549 | } |
550 | } |
551 | t->it_value = ktime_to_timespec64(timerfd_get_remaining(ctx)); |
552 | t->it_interval = ktime_to_timespec64(ctx->tintv); |
553 | spin_unlock_irq(lock: &ctx->wqh.lock); |
554 | fdput(fd: f); |
555 | return 0; |
556 | } |
557 | |
558 | SYSCALL_DEFINE4(timerfd_settime, int, ufd, int, flags, |
559 | const struct __kernel_itimerspec __user *, utmr, |
560 | struct __kernel_itimerspec __user *, otmr) |
561 | { |
562 | struct itimerspec64 new, old; |
563 | int ret; |
564 | |
565 | if (get_itimerspec64(it: &new, uit: utmr)) |
566 | return -EFAULT; |
567 | ret = do_timerfd_settime(ufd, flags, new: &new, old: &old); |
568 | if (ret) |
569 | return ret; |
570 | if (otmr && put_itimerspec64(it: &old, uit: otmr)) |
571 | return -EFAULT; |
572 | |
573 | return ret; |
574 | } |
575 | |
576 | SYSCALL_DEFINE2(timerfd_gettime, int, ufd, struct __kernel_itimerspec __user *, otmr) |
577 | { |
578 | struct itimerspec64 kotmr; |
579 | int ret = do_timerfd_gettime(ufd, t: &kotmr); |
580 | if (ret) |
581 | return ret; |
582 | return put_itimerspec64(it: &kotmr, uit: otmr) ? -EFAULT : 0; |
583 | } |
584 | |
585 | #ifdef CONFIG_COMPAT_32BIT_TIME |
586 | SYSCALL_DEFINE4(timerfd_settime32, int, ufd, int, flags, |
587 | const struct old_itimerspec32 __user *, utmr, |
588 | struct old_itimerspec32 __user *, otmr) |
589 | { |
590 | struct itimerspec64 new, old; |
591 | int ret; |
592 | |
593 | if (get_old_itimerspec32(its: &new, uits: utmr)) |
594 | return -EFAULT; |
595 | ret = do_timerfd_settime(ufd, flags, new: &new, old: &old); |
596 | if (ret) |
597 | return ret; |
598 | if (otmr && put_old_itimerspec32(its: &old, uits: otmr)) |
599 | return -EFAULT; |
600 | return ret; |
601 | } |
602 | |
603 | SYSCALL_DEFINE2(timerfd_gettime32, int, ufd, |
604 | struct old_itimerspec32 __user *, otmr) |
605 | { |
606 | struct itimerspec64 kotmr; |
607 | int ret = do_timerfd_gettime(ufd, t: &kotmr); |
608 | if (ret) |
609 | return ret; |
610 | return put_old_itimerspec32(its: &kotmr, uits: otmr) ? -EFAULT : 0; |
611 | } |
612 | #endif |
613 | |