Commit | Line | Data |
---|---|---|
b215e283 DL |
1 | /* |
2 | * fs/timerfd.c | |
3 | * | |
4 | * Copyright (C) 2007 Davide Libenzi <davidel@xmailserver.org> | |
5 | * | |
6 | * | |
7 | * Thanks to Thomas Gleixner for code reviews and useful comments. | |
8 | * | |
9 | */ | |
10 | ||
3c2a0909 | 11 | #include <linux/alarmtimer.h> |
b215e283 DL |
12 | #include <linux/file.h> |
13 | #include <linux/poll.h> | |
14 | #include <linux/init.h> | |
15 | #include <linux/fs.h> | |
16 | #include <linux/sched.h> | |
17 | #include <linux/kernel.h> | |
5a0e3ad6 | 18 | #include <linux/slab.h> |
b215e283 DL |
19 | #include <linux/list.h> |
20 | #include <linux/spinlock.h> | |
21 | #include <linux/time.h> | |
22 | #include <linux/hrtimer.h> | |
23 | #include <linux/anon_inodes.h> | |
24 | #include <linux/timerfd.h> | |
45cc2b96 | 25 | #include <linux/syscalls.h> |
9d94b9e2 | 26 | #include <linux/compat.h> |
9ec26907 | 27 | #include <linux/rcupdate.h> |
b215e283 DL |
28 | |
29 | struct timerfd_ctx { | |
3c2a0909 S |
30 | union { |
31 | struct hrtimer tmr; | |
32 | struct alarm alarm; | |
33 | } t; | |
b215e283 | 34 | ktime_t tintv; |
99ee5315 | 35 | ktime_t moffs; |
b215e283 | 36 | wait_queue_head_t wqh; |
4d672e7a | 37 | u64 ticks; |
b215e283 | 38 | int expired; |
4d672e7a | 39 | int clockid; |
9ec26907 TG |
40 | struct rcu_head rcu; |
41 | struct list_head clist; | |
ac421a87 | 42 | spinlock_t cancel_lock; |
99ee5315 | 43 | bool might_cancel; |
b215e283 DL |
44 | }; |
45 | ||
9ec26907 TG |
46 | static LIST_HEAD(cancel_list); |
47 | static DEFINE_SPINLOCK(cancel_lock); | |
48 | ||
3c2a0909 S |
49 | static inline bool isalarm(struct timerfd_ctx *ctx) |
50 | { | |
51 | return ctx->clockid == CLOCK_REALTIME_ALARM || | |
52 | ctx->clockid == CLOCK_BOOTTIME_ALARM; | |
53 | } | |
54 | ||
b215e283 DL |
55 | /* |
56 | * This gets called when the timer event triggers. We set the "expired" | |
57 | * flag, but we do not re-arm the timer (in case it's necessary, | |
4d672e7a | 58 | * tintv.tv64 != 0) until the timer is accessed. |
b215e283 | 59 | */ |
3c2a0909 | 60 | static void timerfd_triggered(struct timerfd_ctx *ctx) |
b215e283 | 61 | { |
b215e283 DL |
62 | unsigned long flags; |
63 | ||
18963c01 | 64 | spin_lock_irqsave(&ctx->wqh.lock, flags); |
b215e283 | 65 | ctx->expired = 1; |
4d672e7a | 66 | ctx->ticks++; |
b215e283 | 67 | wake_up_locked(&ctx->wqh); |
18963c01 | 68 | spin_unlock_irqrestore(&ctx->wqh.lock, flags); |
3c2a0909 | 69 | } |
b215e283 | 70 | |
3c2a0909 S |
71 | static enum hrtimer_restart timerfd_tmrproc(struct hrtimer *htmr) |
72 | { | |
73 | struct timerfd_ctx *ctx = container_of(htmr, struct timerfd_ctx, | |
74 | t.tmr); | |
75 | timerfd_triggered(ctx); | |
b215e283 DL |
76 | return HRTIMER_NORESTART; |
77 | } | |
78 | ||
3c2a0909 S |
79 | static enum alarmtimer_restart timerfd_alarmproc(struct alarm *alarm, |
80 | ktime_t now) | |
81 | { | |
82 | struct timerfd_ctx *ctx = container_of(alarm, struct timerfd_ctx, | |
83 | t.alarm); | |
84 | timerfd_triggered(ctx); | |
85 | return ALARMTIMER_NORESTART; | |
86 | } | |
87 | ||
9ec26907 TG |
88 | /* |
89 | * Called when the clock was set to cancel the timers in the cancel | |
1123d939 MA |
90 | * list. This will wake up processes waiting on these timers. The |
91 | * wake-up requires ctx->ticks to be non zero, therefore we increment | |
92 | * it before calling wake_up_locked(). | |
9ec26907 TG |
93 | */ |
94 | void timerfd_clock_was_set(void) | |
4d672e7a | 95 | { |
9ec26907 TG |
96 | ktime_t moffs = ktime_get_monotonic_offset(); |
97 | struct timerfd_ctx *ctx; | |
98 | unsigned long flags; | |
4d672e7a | 99 | |
9ec26907 TG |
100 | rcu_read_lock(); |
101 | list_for_each_entry_rcu(ctx, &cancel_list, clist) { | |
102 | if (!ctx->might_cancel) | |
103 | continue; | |
104 | spin_lock_irqsave(&ctx->wqh.lock, flags); | |
105 | if (ctx->moffs.tv64 != moffs.tv64) { | |
106 | ctx->moffs.tv64 = KTIME_MAX; | |
1123d939 | 107 | ctx->ticks++; |
9ec26907 TG |
108 | wake_up_locked(&ctx->wqh); |
109 | } | |
110 | spin_unlock_irqrestore(&ctx->wqh.lock, flags); | |
111 | } | |
112 | rcu_read_unlock(); | |
4d672e7a DL |
113 | } |
114 | ||
ac421a87 | 115 | static void __timerfd_remove_cancel(struct timerfd_ctx *ctx) |
99ee5315 | 116 | { |
9ec26907 TG |
117 | if (ctx->might_cancel) { |
118 | ctx->might_cancel = false; | |
119 | spin_lock(&cancel_lock); | |
120 | list_del_rcu(&ctx->clist); | |
121 | spin_unlock(&cancel_lock); | |
122 | } | |
123 | } | |
99ee5315 | 124 | |
ac421a87 TG |
125 | static void timerfd_remove_cancel(struct timerfd_ctx *ctx) |
126 | { | |
127 | spin_lock(&ctx->cancel_lock); | |
128 | __timerfd_remove_cancel(ctx); | |
129 | spin_unlock(&ctx->cancel_lock); | |
130 | } | |
131 | ||
9ec26907 TG |
132 | static bool timerfd_canceled(struct timerfd_ctx *ctx) |
133 | { | |
134 | if (!ctx->might_cancel || ctx->moffs.tv64 != KTIME_MAX) | |
99ee5315 | 135 | return false; |
9ec26907 TG |
136 | ctx->moffs = ktime_get_monotonic_offset(); |
137 | return true; | |
138 | } | |
99ee5315 | 139 | |
9ec26907 TG |
140 | static void timerfd_setup_cancel(struct timerfd_ctx *ctx, int flags) |
141 | { | |
ac421a87 | 142 | spin_lock(&ctx->cancel_lock); |
3c2a0909 S |
143 | if ((ctx->clockid == CLOCK_REALTIME || |
144 | ctx->clockid == CLOCK_REALTIME_ALARM) && | |
145 | (flags & TFD_TIMER_ABSTIME) && (flags & TFD_TIMER_CANCEL_ON_SET)) { | |
9ec26907 TG |
146 | if (!ctx->might_cancel) { |
147 | ctx->might_cancel = true; | |
148 | spin_lock(&cancel_lock); | |
149 | list_add_rcu(&ctx->clist, &cancel_list); | |
150 | spin_unlock(&cancel_lock); | |
151 | } | |
ac421a87 TG |
152 | } else { |
153 | __timerfd_remove_cancel(ctx); | |
9ec26907 | 154 | } |
ac421a87 | 155 | spin_unlock(&ctx->cancel_lock); |
9ec26907 | 156 | } |
99ee5315 | 157 | |
9ec26907 TG |
158 | static ktime_t timerfd_get_remaining(struct timerfd_ctx *ctx) |
159 | { | |
160 | ktime_t remaining; | |
99ee5315 | 161 | |
3c2a0909 S |
162 | if (isalarm(ctx)) |
163 | remaining = alarm_expires_remaining(&ctx->t.alarm); | |
164 | else | |
165 | remaining = hrtimer_expires_remaining(&ctx->t.tmr); | |
166 | ||
9ec26907 | 167 | return remaining.tv64 < 0 ? ktime_set(0, 0): remaining; |
99ee5315 TG |
168 | } |
169 | ||
170 | static int timerfd_setup(struct timerfd_ctx *ctx, int flags, | |
171 | const struct itimerspec *ktmr) | |
b215e283 DL |
172 | { |
173 | enum hrtimer_mode htmode; | |
174 | ktime_t texp; | |
99ee5315 | 175 | int clockid = ctx->clockid; |
b215e283 DL |
176 | |
177 | htmode = (flags & TFD_TIMER_ABSTIME) ? | |
178 | HRTIMER_MODE_ABS: HRTIMER_MODE_REL; | |
179 | ||
180 | texp = timespec_to_ktime(ktmr->it_value); | |
181 | ctx->expired = 0; | |
4d672e7a | 182 | ctx->ticks = 0; |
b215e283 | 183 | ctx->tintv = timespec_to_ktime(ktmr->it_interval); |
3c2a0909 S |
184 | |
185 | if (isalarm(ctx)) { | |
186 | alarm_init(&ctx->t.alarm, | |
187 | ctx->clockid == CLOCK_REALTIME_ALARM ? | |
188 | ALARM_REALTIME : ALARM_BOOTTIME, | |
189 | timerfd_alarmproc); | |
190 | } else { | |
191 | hrtimer_init(&ctx->t.tmr, clockid, htmode); | |
192 | hrtimer_set_expires(&ctx->t.tmr, texp); | |
193 | ctx->t.tmr.function = timerfd_tmrproc; | |
194 | } | |
195 | ||
99ee5315 | 196 | if (texp.tv64 != 0) { |
3c2a0909 S |
197 | if (isalarm(ctx)) { |
198 | if (flags & TFD_TIMER_ABSTIME) | |
199 | alarm_start(&ctx->t.alarm, texp); | |
200 | else | |
201 | alarm_start_relative(&ctx->t.alarm, texp); | |
202 | } else { | |
203 | hrtimer_start(&ctx->t.tmr, texp, htmode); | |
204 | } | |
205 | ||
99ee5315 TG |
206 | if (timerfd_canceled(ctx)) |
207 | return -ECANCELED; | |
208 | } | |
209 | return 0; | |
b215e283 DL |
210 | } |
211 | ||
212 | static int timerfd_release(struct inode *inode, struct file *file) | |
213 | { | |
214 | struct timerfd_ctx *ctx = file->private_data; | |
215 | ||
9ec26907 | 216 | timerfd_remove_cancel(ctx); |
3c2a0909 S |
217 | |
218 | if (isalarm(ctx)) | |
219 | alarm_cancel(&ctx->t.alarm); | |
220 | else | |
221 | hrtimer_cancel(&ctx->t.tmr); | |
9ec26907 | 222 | kfree_rcu(ctx, rcu); |
b215e283 DL |
223 | return 0; |
224 | } | |
225 | ||
226 | static unsigned int timerfd_poll(struct file *file, poll_table *wait) | |
227 | { | |
228 | struct timerfd_ctx *ctx = file->private_data; | |
229 | unsigned int events = 0; | |
230 | unsigned long flags; | |
231 | ||
232 | poll_wait(file, &ctx->wqh, wait); | |
233 | ||
18963c01 | 234 | spin_lock_irqsave(&ctx->wqh.lock, flags); |
4d672e7a | 235 | if (ctx->ticks) |
b215e283 | 236 | events |= POLLIN; |
18963c01 | 237 | spin_unlock_irqrestore(&ctx->wqh.lock, flags); |
b215e283 DL |
238 | |
239 | return events; | |
240 | } | |
241 | ||
242 | static ssize_t timerfd_read(struct file *file, char __user *buf, size_t count, | |
243 | loff_t *ppos) | |
244 | { | |
245 | struct timerfd_ctx *ctx = file->private_data; | |
246 | ssize_t res; | |
09828402 | 247 | u64 ticks = 0; |
b215e283 DL |
248 | |
249 | if (count < sizeof(ticks)) | |
250 | return -EINVAL; | |
18963c01 | 251 | spin_lock_irq(&ctx->wqh.lock); |
8120a8aa MN |
252 | if (file->f_flags & O_NONBLOCK) |
253 | res = -EAGAIN; | |
254 | else | |
255 | res = wait_event_interruptible_locked_irq(ctx->wqh, ctx->ticks); | |
99ee5315 | 256 | |
9ec26907 TG |
257 | /* |
258 | * If clock has changed, we do not care about the | |
259 | * ticks and we do not rearm the timer. Userspace must | |
260 | * reevaluate anyway. | |
261 | */ | |
262 | if (timerfd_canceled(ctx)) { | |
263 | ctx->ticks = 0; | |
264 | ctx->expired = 0; | |
265 | res = -ECANCELED; | |
266 | } | |
267 | ||
4d672e7a DL |
268 | if (ctx->ticks) { |
269 | ticks = ctx->ticks; | |
99ee5315 | 270 | |
4d672e7a | 271 | if (ctx->expired && ctx->tintv.tv64) { |
b215e283 DL |
272 | /* |
273 | * If tintv.tv64 != 0, this is a periodic timer that | |
274 | * needs to be re-armed. We avoid doing it in the timer | |
275 | * callback to avoid DoS attacks specifying a very | |
276 | * short timer period. | |
277 | */ | |
3c2a0909 S |
278 | if (isalarm(ctx)) { |
279 | ticks += alarm_forward_now( | |
280 | &ctx->t.alarm, ctx->tintv) - 1; | |
281 | alarm_restart(&ctx->t.alarm); | |
282 | } else { | |
283 | ticks += hrtimer_forward_now(&ctx->t.tmr, | |
284 | ctx->tintv) - 1; | |
285 | hrtimer_restart(&ctx->t.tmr); | |
286 | } | |
4d672e7a DL |
287 | } |
288 | ctx->expired = 0; | |
289 | ctx->ticks = 0; | |
b215e283 | 290 | } |
18963c01 | 291 | spin_unlock_irq(&ctx->wqh.lock); |
b215e283 | 292 | if (ticks) |
09828402 | 293 | res = put_user(ticks, (u64 __user *) buf) ? -EFAULT: sizeof(ticks); |
b215e283 DL |
294 | return res; |
295 | } | |
296 | ||
297 | static const struct file_operations timerfd_fops = { | |
298 | .release = timerfd_release, | |
299 | .poll = timerfd_poll, | |
300 | .read = timerfd_read, | |
6038f373 | 301 | .llseek = noop_llseek, |
b215e283 DL |
302 | }; |
303 | ||
2903ff01 | 304 | static int timerfd_fget(int fd, struct fd *p) |
4d672e7a | 305 | { |
2903ff01 AV |
306 | struct fd f = fdget(fd); |
307 | if (!f.file) | |
308 | return -EBADF; | |
309 | if (f.file->f_op != &timerfd_fops) { | |
310 | fdput(f); | |
311 | return -EINVAL; | |
4d672e7a | 312 | } |
2903ff01 AV |
313 | *p = f; |
314 | return 0; | |
4d672e7a DL |
315 | } |
316 | ||
836f92ad | 317 | SYSCALL_DEFINE2(timerfd_create, int, clockid, int, flags) |
b215e283 | 318 | { |
2030a42c | 319 | int ufd; |
b215e283 | 320 | struct timerfd_ctx *ctx; |
b215e283 | 321 | |
e38b36f3 UD |
322 | /* Check the TFD_* constants for consistency. */ |
323 | BUILD_BUG_ON(TFD_CLOEXEC != O_CLOEXEC); | |
324 | BUILD_BUG_ON(TFD_NONBLOCK != O_NONBLOCK); | |
325 | ||
610d18f4 DL |
326 | if ((flags & ~TFD_CREATE_FLAGS) || |
327 | (clockid != CLOCK_MONOTONIC && | |
3c2a0909 S |
328 | clockid != CLOCK_REALTIME && |
329 | clockid != CLOCK_REALTIME_ALARM && | |
330 | clockid != CLOCK_BOOTTIME && | |
331 | clockid != CLOCK_BOOTTIME_ALARM)) | |
b215e283 | 332 | return -EINVAL; |
4d672e7a DL |
333 | |
334 | ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); | |
335 | if (!ctx) | |
336 | return -ENOMEM; | |
337 | ||
338 | init_waitqueue_head(&ctx->wqh); | |
ac421a87 | 339 | spin_lock_init(&ctx->cancel_lock); |
4d672e7a | 340 | ctx->clockid = clockid; |
3c2a0909 S |
341 | |
342 | if (isalarm(ctx)) | |
343 | alarm_init(&ctx->t.alarm, | |
344 | ctx->clockid == CLOCK_REALTIME_ALARM ? | |
345 | ALARM_REALTIME : ALARM_BOOTTIME, | |
346 | timerfd_alarmproc); | |
347 | else | |
348 | hrtimer_init(&ctx->t.tmr, clockid, HRTIMER_MODE_ABS); | |
349 | ||
99ee5315 | 350 | ctx->moffs = ktime_get_monotonic_offset(); |
4d672e7a | 351 | |
11fcb6c1 | 352 | ufd = anon_inode_getfd("[timerfd]", &timerfd_fops, ctx, |
628ff7c1 | 353 | O_RDWR | (flags & TFD_SHARED_FCNTL_FLAGS)); |
2030a42c | 354 | if (ufd < 0) |
4d672e7a | 355 | kfree(ctx); |
4d672e7a DL |
356 | |
357 | return ufd; | |
358 | } | |
359 | ||
9d94b9e2 AV |
360 | static int do_timerfd_settime(int ufd, int flags, |
361 | const struct itimerspec *new, | |
362 | struct itimerspec *old) | |
4d672e7a | 363 | { |
2903ff01 | 364 | struct fd f; |
4d672e7a | 365 | struct timerfd_ctx *ctx; |
2903ff01 | 366 | int ret; |
4d672e7a | 367 | |
610d18f4 | 368 | if ((flags & ~TFD_SETTIME_FLAGS) || |
9d94b9e2 AV |
369 | !timespec_valid(&new->it_value) || |
370 | !timespec_valid(&new->it_interval)) | |
b215e283 DL |
371 | return -EINVAL; |
372 | ||
2903ff01 AV |
373 | ret = timerfd_fget(ufd, &f); |
374 | if (ret) | |
375 | return ret; | |
376 | ctx = f.file->private_data; | |
b215e283 | 377 | |
9ec26907 TG |
378 | timerfd_setup_cancel(ctx, flags); |
379 | ||
4d672e7a DL |
380 | /* |
381 | * We need to stop the existing timer before reprogramming | |
382 | * it to the new values. | |
383 | */ | |
384 | for (;;) { | |
385 | spin_lock_irq(&ctx->wqh.lock); | |
3c2a0909 S |
386 | |
387 | if (isalarm(ctx)) { | |
388 | if (alarm_try_to_cancel(&ctx->t.alarm) >= 0) | |
389 | break; | |
390 | } else { | |
391 | if (hrtimer_try_to_cancel(&ctx->t.tmr) >= 0) | |
392 | break; | |
393 | } | |
18963c01 | 394 | spin_unlock_irq(&ctx->wqh.lock); |
4d672e7a | 395 | cpu_relax(); |
b215e283 DL |
396 | } |
397 | ||
4d672e7a DL |
398 | /* |
399 | * If the timer is expired and it's periodic, we need to advance it | |
400 | * because the caller may want to know the previous expiration time. | |
401 | * We do not update "ticks" and "expired" since the timer will be | |
402 | * re-programmed again in the following timerfd_setup() call. | |
403 | */ | |
3c2a0909 S |
404 | if (ctx->expired && ctx->tintv.tv64) { |
405 | if (isalarm(ctx)) | |
406 | alarm_forward_now(&ctx->t.alarm, ctx->tintv); | |
407 | else | |
408 | hrtimer_forward_now(&ctx->t.tmr, ctx->tintv); | |
409 | } | |
b215e283 | 410 | |
9d94b9e2 AV |
411 | old->it_value = ktime_to_timespec(timerfd_get_remaining(ctx)); |
412 | old->it_interval = ktime_to_timespec(ctx->tintv); | |
4d672e7a DL |
413 | |
414 | /* | |
415 | * Re-program the timer to the new value ... | |
416 | */ | |
9d94b9e2 | 417 | ret = timerfd_setup(ctx, flags, new); |
4d672e7a DL |
418 | |
419 | spin_unlock_irq(&ctx->wqh.lock); | |
2903ff01 | 420 | fdput(f); |
99ee5315 | 421 | return ret; |
4d672e7a DL |
422 | } |
423 | ||
9d94b9e2 | 424 | static int do_timerfd_gettime(int ufd, struct itimerspec *t) |
4d672e7a | 425 | { |
2903ff01 | 426 | struct fd f; |
4d672e7a | 427 | struct timerfd_ctx *ctx; |
2903ff01 AV |
428 | int ret = timerfd_fget(ufd, &f); |
429 | if (ret) | |
430 | return ret; | |
431 | ctx = f.file->private_data; | |
4d672e7a DL |
432 | |
433 | spin_lock_irq(&ctx->wqh.lock); | |
434 | if (ctx->expired && ctx->tintv.tv64) { | |
435 | ctx->expired = 0; | |
3c2a0909 S |
436 | |
437 | if (isalarm(ctx)) { | |
438 | ctx->ticks += | |
439 | alarm_forward_now( | |
440 | &ctx->t.alarm, ctx->tintv) - 1; | |
441 | alarm_restart(&ctx->t.alarm); | |
442 | } else { | |
443 | ctx->ticks += | |
444 | hrtimer_forward_now(&ctx->t.tmr, ctx->tintv) | |
445 | - 1; | |
446 | hrtimer_restart(&ctx->t.tmr); | |
447 | } | |
4d672e7a | 448 | } |
9d94b9e2 AV |
449 | t->it_value = ktime_to_timespec(timerfd_get_remaining(ctx)); |
450 | t->it_interval = ktime_to_timespec(ctx->tintv); | |
4d672e7a | 451 | spin_unlock_irq(&ctx->wqh.lock); |
2903ff01 | 452 | fdput(f); |
9d94b9e2 AV |
453 | return 0; |
454 | } | |
455 | ||
456 | SYSCALL_DEFINE4(timerfd_settime, int, ufd, int, flags, | |
457 | const struct itimerspec __user *, utmr, | |
458 | struct itimerspec __user *, otmr) | |
459 | { | |
460 | struct itimerspec new, old; | |
461 | int ret; | |
462 | ||
463 | if (copy_from_user(&new, utmr, sizeof(new))) | |
464 | return -EFAULT; | |
465 | ret = do_timerfd_settime(ufd, flags, &new, &old); | |
466 | if (ret) | |
467 | return ret; | |
468 | if (otmr && copy_to_user(otmr, &old, sizeof(old))) | |
469 | return -EFAULT; | |
470 | ||
471 | return ret; | |
472 | } | |
4d672e7a | 473 | |
9d94b9e2 AV |
474 | SYSCALL_DEFINE2(timerfd_gettime, int, ufd, struct itimerspec __user *, otmr) |
475 | { | |
476 | struct itimerspec kotmr; | |
477 | int ret = do_timerfd_gettime(ufd, &kotmr); | |
478 | if (ret) | |
479 | return ret; | |
4d672e7a | 480 | return copy_to_user(otmr, &kotmr, sizeof(kotmr)) ? -EFAULT: 0; |
b215e283 DL |
481 | } |
482 | ||
0e803baf | 483 | #ifdef CONFIG_COMPAT |
9d94b9e2 | 484 | COMPAT_SYSCALL_DEFINE4(timerfd_settime, int, ufd, int, flags, |
0e803baf HC |
485 | const struct compat_itimerspec __user *, utmr, |
486 | struct compat_itimerspec __user *, otmr) | |
9d94b9e2 AV |
487 | { |
488 | struct itimerspec new, old; | |
489 | int ret; | |
490 | ||
491 | if (get_compat_itimerspec(&new, utmr)) | |
492 | return -EFAULT; | |
493 | ret = do_timerfd_settime(ufd, flags, &new, &old); | |
494 | if (ret) | |
495 | return ret; | |
496 | if (otmr && put_compat_itimerspec(otmr, &old)) | |
497 | return -EFAULT; | |
498 | return ret; | |
499 | } | |
500 | ||
501 | COMPAT_SYSCALL_DEFINE2(timerfd_gettime, int, ufd, | |
0e803baf | 502 | struct compat_itimerspec __user *, otmr) |
9d94b9e2 AV |
503 | { |
504 | struct itimerspec kotmr; | |
505 | int ret = do_timerfd_gettime(ufd, &kotmr); | |
506 | if (ret) | |
507 | return ret; | |
0e803baf | 508 | return put_compat_itimerspec(otmr, &kotmr) ? -EFAULT: 0; |
9d94b9e2 AV |
509 | } |
510 | #endif |