Changeset View
Changeset View
Standalone View
Standalone View
sys/compat/linux/linux_event.c
| Show First 20 Lines • Show All 47 Lines • ▼ Show 20 Lines | |||||
| #include <sys/errno.h> | #include <sys/errno.h> | ||||
| #include <sys/event.h> | #include <sys/event.h> | ||||
| #include <sys/poll.h> | #include <sys/poll.h> | ||||
| #include <sys/proc.h> | #include <sys/proc.h> | ||||
| #include <sys/selinfo.h> | #include <sys/selinfo.h> | ||||
| #include <sys/specialfd.h> | #include <sys/specialfd.h> | ||||
| #include <sys/sx.h> | #include <sys/sx.h> | ||||
| #include <sys/syscallsubr.h> | #include <sys/syscallsubr.h> | ||||
| #include <sys/timerfd.h> | |||||
| #include <sys/timespec.h> | #include <sys/timespec.h> | ||||
| #include <sys/eventfd.h> | #include <sys/eventfd.h> | ||||
| #ifdef COMPAT_LINUX32 | #ifdef COMPAT_LINUX32 | ||||
| #include <machine/../linux32/linux.h> | #include <machine/../linux32/linux.h> | ||||
| #include <machine/../linux32/linux32_proto.h> | #include <machine/../linux32/linux32_proto.h> | ||||
| #else | #else | ||||
| #include <machine/../linux/linux.h> | #include <machine/../linux/linux.h> | ||||
| Show All 39 Lines | |||||
| struct epoll_copyout_args { | struct epoll_copyout_args { | ||||
| struct epoll_event *leventlist; | struct epoll_event *leventlist; | ||||
| struct proc *p; | struct proc *p; | ||||
| uint32_t count; | uint32_t count; | ||||
| int error; | int error; | ||||
| }; | }; | ||||
| /* timerfd */ | |||||
| typedef uint64_t timerfd_t; | |||||
| static fo_rdwr_t timerfd_read; | |||||
| static fo_ioctl_t timerfd_ioctl; | |||||
| static fo_poll_t timerfd_poll; | |||||
| static fo_kqfilter_t timerfd_kqfilter; | |||||
| static fo_stat_t timerfd_stat; | |||||
| static fo_close_t timerfd_close; | |||||
| static fo_fill_kinfo_t timerfd_fill_kinfo; | |||||
| static struct fileops timerfdops = { | |||||
| .fo_read = timerfd_read, | |||||
| .fo_write = invfo_rdwr, | |||||
| .fo_truncate = invfo_truncate, | |||||
| .fo_ioctl = timerfd_ioctl, | |||||
| .fo_poll = timerfd_poll, | |||||
| .fo_kqfilter = timerfd_kqfilter, | |||||
| .fo_stat = timerfd_stat, | |||||
| .fo_close = timerfd_close, | |||||
| .fo_chmod = invfo_chmod, | |||||
| .fo_chown = invfo_chown, | |||||
| .fo_sendfile = invfo_sendfile, | |||||
| .fo_fill_kinfo = timerfd_fill_kinfo, | |||||
| .fo_flags = DFLAG_PASSABLE | |||||
| }; | |||||
| static void filt_timerfddetach(struct knote *kn); | |||||
| static int filt_timerfdread(struct knote *kn, long hint); | |||||
| static struct filterops timerfd_rfiltops = { | |||||
| .f_isfd = 1, | |||||
| .f_detach = filt_timerfddetach, | |||||
| .f_event = filt_timerfdread | |||||
| }; | |||||
| struct timerfd { | |||||
| clockid_t tfd_clockid; | |||||
| struct itimerspec tfd_time; | |||||
| struct callout tfd_callout; | |||||
| timerfd_t tfd_count; | |||||
| bool tfd_canceled; | |||||
| struct selinfo tfd_sel; | |||||
| struct mtx tfd_lock; | |||||
| }; | |||||
| static void linux_timerfd_expire(void *); | |||||
| static void linux_timerfd_curval(struct timerfd *, struct itimerspec *); | |||||
| static int | static int | ||||
| epoll_create_common(struct thread *td, int flags) | epoll_create_common(struct thread *td, int flags) | ||||
| { | { | ||||
| return (kern_kqueue(td, flags, NULL)); | return (kern_kqueue(td, flags, NULL)); | ||||
| } | } | ||||
| #ifdef LINUX_LEGACY_SYSCALLS | #ifdef LINUX_LEGACY_SYSCALLS | ||||
| ▲ Show 20 Lines • Show All 485 Lines • ▼ Show 20 Lines | if ((args->flags & LINUX_EFD_SEMAPHORE) != 0) | ||||
| flags |= EFD_SEMAPHORE; | flags |= EFD_SEMAPHORE; | ||||
| return (eventfd_create_file(td, args->initval, flags)); | return (eventfd_create_file(td, args->initval, flags)); | ||||
| } | } | ||||
| int | int | ||||
| linux_timerfd_create(struct thread *td, struct linux_timerfd_create_args *args) | linux_timerfd_create(struct thread *td, struct linux_timerfd_create_args *args) | ||||
| { | { | ||||
| struct timerfd *tfd; | |||||
| struct file *fp; | |||||
| clockid_t clockid; | clockid_t clockid; | ||||
| int fflags, fd, error; | int error; | ||||
| if ((args->flags & ~LINUX_TFD_CREATE_FLAGS) != 0) | |||||
| return (EINVAL); | |||||
| error = linux_to_native_clockid(&clockid, args->clockid); | error = linux_to_native_clockid(&clockid, args->clockid); | ||||
| if (error != 0) | if (error != 0) | ||||
| return (error); | return (error); | ||||
| if (clockid != CLOCK_REALTIME && clockid != CLOCK_MONOTONIC) | |||||
| return (EINVAL); | |||||
| fflags = 0; | return (timerfd_create_file(td, clockid, args->flags)); | ||||
| if ((args->flags & LINUX_TFD_CLOEXEC) != 0) | |||||
| fflags |= O_CLOEXEC; | |||||
| error = falloc(td, &fp, &fd, fflags); | |||||
| if (error != 0) | |||||
| return (error); | |||||
| tfd = malloc(sizeof(*tfd), M_EPOLL, M_WAITOK | M_ZERO); | |||||
| tfd->tfd_clockid = clockid; | |||||
| mtx_init(&tfd->tfd_lock, "timerfd", NULL, MTX_DEF); | |||||
| callout_init_mtx(&tfd->tfd_callout, &tfd->tfd_lock, 0); | |||||
| knlist_init_mtx(&tfd->tfd_sel.si_note, &tfd->tfd_lock); | |||||
| fflags = FREAD; | |||||
| if ((args->flags & LINUX_O_NONBLOCK) != 0) | |||||
| fflags |= FNONBLOCK; | |||||
| finit(fp, fflags, DTYPE_LINUXTFD, tfd, &timerfdops); | |||||
| fdrop(fp, td); | |||||
| td->td_retval[0] = fd; | |||||
| return (error); | |||||
| } | } | ||||
| static int | |||||
| timerfd_close(struct file *fp, struct thread *td) | |||||
| { | |||||
| struct timerfd *tfd; | |||||
| tfd = fp->f_data; | |||||
| if (fp->f_type != DTYPE_LINUXTFD || tfd == NULL) | |||||
| return (EINVAL); | |||||
| timespecclear(&tfd->tfd_time.it_value); | |||||
| timespecclear(&tfd->tfd_time.it_interval); | |||||
| callout_drain(&tfd->tfd_callout); | |||||
| seldrain(&tfd->tfd_sel); | |||||
| knlist_destroy(&tfd->tfd_sel.si_note); | |||||
| fp->f_ops = &badfileops; | |||||
| mtx_destroy(&tfd->tfd_lock); | |||||
| free(tfd, M_EPOLL); | |||||
| return (0); | |||||
| } | |||||
| static int | |||||
| timerfd_read(struct file *fp, struct uio *uio, struct ucred *active_cred, | |||||
| int flags, struct thread *td) | |||||
| { | |||||
| struct timerfd *tfd; | |||||
| timerfd_t count; | |||||
| int error; | |||||
| tfd = fp->f_data; | |||||
| if (fp->f_type != DTYPE_LINUXTFD || tfd == NULL) | |||||
| return (EINVAL); | |||||
| if (uio->uio_resid < sizeof(timerfd_t)) | |||||
| return (EINVAL); | |||||
| error = 0; | |||||
| mtx_lock(&tfd->tfd_lock); | |||||
| retry: | |||||
| if (tfd->tfd_canceled) { | |||||
| tfd->tfd_count = 0; | |||||
| mtx_unlock(&tfd->tfd_lock); | |||||
| return (ECANCELED); | |||||
| } | |||||
| if (tfd->tfd_count == 0) { | |||||
| if ((fp->f_flag & FNONBLOCK) != 0) { | |||||
| mtx_unlock(&tfd->tfd_lock); | |||||
| return (EAGAIN); | |||||
| } | |||||
| error = mtx_sleep(&tfd->tfd_count, &tfd->tfd_lock, PCATCH, "ltfdrd", 0); | |||||
| if (error == 0) | |||||
| goto retry; | |||||
| } | |||||
| if (error == 0) { | |||||
| count = tfd->tfd_count; | |||||
| tfd->tfd_count = 0; | |||||
| mtx_unlock(&tfd->tfd_lock); | |||||
| error = uiomove(&count, sizeof(timerfd_t), uio); | |||||
| } else | |||||
| mtx_unlock(&tfd->tfd_lock); | |||||
| return (error); | |||||
| } | |||||
| static int | |||||
| timerfd_poll(struct file *fp, int events, struct ucred *active_cred, | |||||
| struct thread *td) | |||||
| { | |||||
| struct timerfd *tfd; | |||||
| int revents = 0; | |||||
| tfd = fp->f_data; | |||||
| if (fp->f_type != DTYPE_LINUXTFD || tfd == NULL) | |||||
| return (POLLERR); | |||||
| mtx_lock(&tfd->tfd_lock); | |||||
| if ((events & (POLLIN|POLLRDNORM)) && tfd->tfd_count > 0) | |||||
| revents |= events & (POLLIN|POLLRDNORM); | |||||
| if (revents == 0) | |||||
| selrecord(td, &tfd->tfd_sel); | |||||
| mtx_unlock(&tfd->tfd_lock); | |||||
| return (revents); | |||||
| } | |||||
| static int | |||||
| timerfd_kqfilter(struct file *fp, struct knote *kn) | |||||
| { | |||||
| struct timerfd *tfd; | |||||
| tfd = fp->f_data; | |||||
| if (fp->f_type != DTYPE_LINUXTFD || tfd == NULL) | |||||
| return (EINVAL); | |||||
| if (kn->kn_filter == EVFILT_READ) | |||||
| kn->kn_fop = &timerfd_rfiltops; | |||||
| else | |||||
| return (EINVAL); | |||||
| kn->kn_hook = tfd; | |||||
| knlist_add(&tfd->tfd_sel.si_note, kn, 0); | |||||
| return (0); | |||||
| } | |||||
| static void | |||||
| filt_timerfddetach(struct knote *kn) | |||||
| { | |||||
| struct timerfd *tfd = kn->kn_hook; | |||||
| mtx_lock(&tfd->tfd_lock); | |||||
| knlist_remove(&tfd->tfd_sel.si_note, kn, 1); | |||||
| mtx_unlock(&tfd->tfd_lock); | |||||
| } | |||||
| static int | |||||
| filt_timerfdread(struct knote *kn, long hint) | |||||
| { | |||||
| struct timerfd *tfd = kn->kn_hook; | |||||
| return (tfd->tfd_count > 0); | |||||
| } | |||||
| static int | |||||
| timerfd_ioctl(struct file *fp, u_long cmd, void *data, | |||||
| struct ucred *active_cred, struct thread *td) | |||||
| { | |||||
| if (fp->f_data == NULL || fp->f_type != DTYPE_LINUXTFD) | |||||
| return (EINVAL); | |||||
| switch (cmd) { | |||||
| case FIONBIO: | |||||
| case FIOASYNC: | |||||
| return (0); | |||||
| } | |||||
| return (ENOTTY); | |||||
| } | |||||
| static int | |||||
| timerfd_stat(struct file *fp, struct stat *st, struct ucred *active_cred) | |||||
| { | |||||
| return (ENXIO); | |||||
| } | |||||
| static int | |||||
| timerfd_fill_kinfo(struct file *fp, struct kinfo_file *kif, struct filedesc *fdp) | |||||
| { | |||||
| kif->kf_type = KF_TYPE_UNKNOWN; | |||||
| return (0); | |||||
| } | |||||
| static void | |||||
| linux_timerfd_clocktime(struct timerfd *tfd, struct timespec *ts) | |||||
| { | |||||
| if (tfd->tfd_clockid == CLOCK_REALTIME) | |||||
| getnanotime(ts); | |||||
| else /* CLOCK_MONOTONIC */ | |||||
| getnanouptime(ts); | |||||
| } | |||||
| static void | |||||
| linux_timerfd_curval(struct timerfd *tfd, struct itimerspec *ots) | |||||
| { | |||||
| struct timespec cts; | |||||
| linux_timerfd_clocktime(tfd, &cts); | |||||
| *ots = tfd->tfd_time; | |||||
| if (ots->it_value.tv_sec != 0 || ots->it_value.tv_nsec != 0) { | |||||
| timespecsub(&ots->it_value, &cts, &ots->it_value); | |||||
| if (ots->it_value.tv_sec < 0 || | |||||
| (ots->it_value.tv_sec == 0 && | |||||
| ots->it_value.tv_nsec == 0)) { | |||||
| ots->it_value.tv_sec = 0; | |||||
| ots->it_value.tv_nsec = 1; | |||||
| } | |||||
| } | |||||
| } | |||||
| static int | |||||
| linux_timerfd_gettime_common(struct thread *td, int fd, struct itimerspec *ots) | |||||
| { | |||||
| struct timerfd *tfd; | |||||
| struct file *fp; | |||||
| int error; | |||||
| error = fget(td, fd, &cap_read_rights, &fp); | |||||
| if (error != 0) | |||||
| return (error); | |||||
| tfd = fp->f_data; | |||||
| if (fp->f_type != DTYPE_LINUXTFD || tfd == NULL) { | |||||
| error = EINVAL; | |||||
| goto out; | |||||
| } | |||||
| mtx_lock(&tfd->tfd_lock); | |||||
| linux_timerfd_curval(tfd, ots); | |||||
| mtx_unlock(&tfd->tfd_lock); | |||||
| out: | |||||
| fdrop(fp, td); | |||||
| return (error); | |||||
| } | |||||
| int | int | ||||
| linux_timerfd_gettime(struct thread *td, struct linux_timerfd_gettime_args *args) | linux_timerfd_gettime(struct thread *td, struct linux_timerfd_gettime_args *args) | ||||
| { | { | ||||
| struct l_itimerspec lots; | struct l_itimerspec lots; | ||||
| struct itimerspec ots; | struct itimerspec ots; | ||||
| struct file *fp; | |||||
| int error; | int error; | ||||
| error = linux_timerfd_gettime_common(td, args->fd, &ots); | error = fget(td, args->fd, &cap_read_rights, &fp); | ||||
| if (error != 0) | if (error != 0) | ||||
| return (error); | return (error); | ||||
| error = timerfd_gettime_common(fp, &ots); | |||||
| if (error == 0) { | |||||
| error = native_to_linux_itimerspec(&lots, &ots); | error = native_to_linux_itimerspec(&lots, &ots); | ||||
| if (error == 0) | if (error == 0) | ||||
| error = copyout(&lots, args->old_value, sizeof(lots)); | error = copyout(&lots, args->old_value, sizeof(lots)); | ||||
| } | |||||
| fdrop(fp, td); | |||||
| return (error); | return (error); | ||||
| } | } | ||||
| #if defined(__i386__) || (defined(__amd64__) && defined(COMPAT_LINUX32)) | #if defined(__i386__) || (defined(__amd64__) && defined(COMPAT_LINUX32)) | ||||
| int | int | ||||
| linux_timerfd_gettime64(struct thread *td, struct linux_timerfd_gettime64_args *args) | linux_timerfd_gettime64(struct thread *td, struct linux_timerfd_gettime64_args *args) | ||||
| { | { | ||||
| struct l_itimerspec64 lots; | struct l_itimerspec64 lots; | ||||
| struct itimerspec ots; | struct itimerspec ots; | ||||
| struct file *fp; | |||||
| int error; | int error; | ||||
| error = linux_timerfd_gettime_common(td, args->fd, &ots); | error = fget(td, args->fd, &cap_read_rights, &fp); | ||||
| if (error != 0) | if (error != 0) | ||||
| return (error); | return (error); | ||||
| error = timerfd_gettime_common(fp, &ots); | |||||
| if (error == 0) { | |||||
| error = native_to_linux_itimerspec64(&lots, &ots); | error = native_to_linux_itimerspec64(&lots, &ots); | ||||
| if (error == 0) | if (error == 0) | ||||
| error = copyout(&lots, args->old_value, sizeof(lots)); | error = copyout(&lots, args->old_value, sizeof(lots)); | ||||
| return (error); | |||||
| } | } | ||||
| #endif | |||||
| static int | |||||
| linux_timerfd_settime_common(struct thread *td, int fd, int flags, | |||||
| struct itimerspec *nts, struct itimerspec *oval) | |||||
| { | |||||
| struct timespec cts, ts; | |||||
| struct timerfd *tfd; | |||||
| struct timeval tv; | |||||
| struct file *fp; | |||||
| int error; | |||||
| if ((flags & ~LINUX_TFD_SETTIME_FLAGS) != 0) | |||||
| return (EINVAL); | |||||
| error = fget(td, fd, &cap_write_rights, &fp); | |||||
| if (error != 0) | |||||
| return (error); | |||||
| tfd = fp->f_data; | |||||
| if (fp->f_type != DTYPE_LINUXTFD || tfd == NULL) { | |||||
| error = EINVAL; | |||||
| goto out; | |||||
| } | |||||
| mtx_lock(&tfd->tfd_lock); | |||||
| if (!timespecisset(&nts->it_value)) | |||||
| timespecclear(&nts->it_interval); | |||||
| if (oval != NULL) | |||||
| linux_timerfd_curval(tfd, oval); | |||||
| bcopy(nts, &tfd->tfd_time, sizeof(*nts)); | |||||
| tfd->tfd_count = 0; | |||||
| if (timespecisset(&nts->it_value)) { | |||||
| linux_timerfd_clocktime(tfd, &cts); | |||||
| ts = nts->it_value; | |||||
| if ((flags & LINUX_TFD_TIMER_ABSTIME) == 0) { | |||||
| timespecadd(&tfd->tfd_time.it_value, &cts, | |||||
| &tfd->tfd_time.it_value); | |||||
| } else { | |||||
| timespecsub(&ts, &cts, &ts); | |||||
| } | |||||
| TIMESPEC_TO_TIMEVAL(&tv, &ts); | |||||
| callout_reset(&tfd->tfd_callout, tvtohz(&tv), | |||||
| linux_timerfd_expire, tfd); | |||||
| tfd->tfd_canceled = false; | |||||
| } else { | |||||
| tfd->tfd_canceled = true; | |||||
| callout_stop(&tfd->tfd_callout); | |||||
| } | |||||
| mtx_unlock(&tfd->tfd_lock); | |||||
| out: | |||||
| fdrop(fp, td); | fdrop(fp, td); | ||||
| return (error); | return (error); | ||||
| } | } | ||||
| #endif | |||||
| int | int | ||||
| linux_timerfd_settime(struct thread *td, struct linux_timerfd_settime_args *args) | linux_timerfd_settime(struct thread *td, struct linux_timerfd_settime_args *args) | ||||
| { | { | ||||
| struct l_itimerspec lots; | struct l_itimerspec lots; | ||||
| struct itimerspec nts, ots, *pots; | struct itimerspec nts, ots, *pots; | ||||
| struct file *fp; | |||||
| int error; | int error; | ||||
| error = copyin(args->new_value, &lots, sizeof(lots)); | error = copyin(args->new_value, &lots, sizeof(lots)); | ||||
| if (error != 0) | if (error != 0) | ||||
| return (error); | return (error); | ||||
| error = linux_to_native_itimerspec(&nts, &lots); | error = linux_to_native_itimerspec(&nts, &lots); | ||||
| if (error != 0) | if (error != 0) | ||||
| return (error); | return (error); | ||||
| error = fget(td, args->fd, &cap_write_rights, &fp); | |||||
| if (error != 0) | |||||
| return (error); | |||||
| pots = (args->old_value != NULL ? &ots : NULL); | pots = (args->old_value != NULL ? &ots : NULL); | ||||
| error = linux_timerfd_settime_common(td, args->fd, args->flags, | error = timerfd_settime_common(fp, args->flags, &nts, pots); | ||||
| &nts, pots); | |||||
| if (error == 0 && args->old_value != NULL) { | if (error == 0 && args->old_value != NULL) { | ||||
| error = native_to_linux_itimerspec(&lots, &ots); | error = native_to_linux_itimerspec(&lots, &ots); | ||||
| if (error == 0) | if (error == 0) | ||||
| error = copyout(&lots, args->old_value, sizeof(lots)); | error = copyout(&lots, args->old_value, sizeof(lots)); | ||||
| } | } | ||||
| fdrop(fp, td); | |||||
| return (error); | return (error); | ||||
| } | } | ||||
| #if defined(__i386__) || (defined(__amd64__) && defined(COMPAT_LINUX32)) | #if defined(__i386__) || (defined(__amd64__) && defined(COMPAT_LINUX32)) | ||||
| int | int | ||||
| linux_timerfd_settime64(struct thread *td, struct linux_timerfd_settime64_args *args) | linux_timerfd_settime64(struct thread *td, struct linux_timerfd_settime64_args *args) | ||||
| { | { | ||||
| struct l_itimerspec64 lots; | struct l_itimerspec64 lots; | ||||
| struct itimerspec nts, ots, *pots; | struct itimerspec nts, ots, *pots; | ||||
| struct file *fp; | |||||
| int error; | int error; | ||||
| error = copyin(args->new_value, &lots, sizeof(lots)); | error = copyin(args->new_value, &lots, sizeof(lots)); | ||||
| if (error != 0) | if (error != 0) | ||||
| return (error); | return (error); | ||||
| error = linux_to_native_itimerspec64(&nts, &lots); | error = linux_to_native_itimerspec64(&nts, &lots); | ||||
| if (error != 0) | if (error != 0) | ||||
| return (error); | return (error); | ||||
| error = fget(td, args->fd, &cap_write_rights, &fp); | |||||
| if (error != 0) | |||||
| return (error); | |||||
| pots = (args->old_value != NULL ? &ots : NULL); | pots = (args->old_value != NULL ? &ots : NULL); | ||||
| error = linux_timerfd_settime_common(td, args->fd, args->flags, | error = timerfd_settime_common(fp, args->flags, &nts, pots); | ||||
| &nts, pots); | |||||
| if (error == 0 && args->old_value != NULL) { | if (error == 0 && args->old_value != NULL) { | ||||
| error = native_to_linux_itimerspec64(&lots, &ots); | error = native_to_linux_itimerspec64(&lots, &ots); | ||||
| if (error == 0) | if (error == 0) | ||||
| error = copyout(&lots, args->old_value, sizeof(lots)); | error = copyout(&lots, args->old_value, sizeof(lots)); | ||||
| } | } | ||||
| fdrop(fp, td); | |||||
| return (error); | return (error); | ||||
| } | } | ||||
| #endif | #endif | ||||
| static void | |||||
| linux_timerfd_expire(void *arg) | |||||
| { | |||||
| struct timespec cts, ts; | |||||
| struct timeval tv; | |||||
| struct timerfd *tfd; | |||||
| tfd = (struct timerfd *)arg; | |||||
| linux_timerfd_clocktime(tfd, &cts); | |||||
| if (timespeccmp(&cts, &tfd->tfd_time.it_value, >=)) { | |||||
| if (timespecisset(&tfd->tfd_time.it_interval)) | |||||
| timespecadd(&tfd->tfd_time.it_value, | |||||
| &tfd->tfd_time.it_interval, | |||||
| &tfd->tfd_time.it_value); | |||||
| else | |||||
| /* single shot timer */ | |||||
| timespecclear(&tfd->tfd_time.it_value); | |||||
| if (timespecisset(&tfd->tfd_time.it_value)) { | |||||
| timespecsub(&tfd->tfd_time.it_value, &cts, &ts); | |||||
| TIMESPEC_TO_TIMEVAL(&tv, &ts); | |||||
| callout_reset(&tfd->tfd_callout, tvtohz(&tv), | |||||
| linux_timerfd_expire, tfd); | |||||
| } | |||||
| tfd->tfd_count++; | |||||
| KNOTE_LOCKED(&tfd->tfd_sel.si_note, 0); | |||||
| selwakeup(&tfd->tfd_sel); | |||||
| wakeup(&tfd->tfd_count); | |||||
| } else if (timespecisset(&tfd->tfd_time.it_value)) { | |||||
| timespecsub(&tfd->tfd_time.it_value, &cts, &ts); | |||||
| TIMESPEC_TO_TIMEVAL(&tv, &ts); | |||||
| callout_reset(&tfd->tfd_callout, tvtohz(&tv), | |||||
| linux_timerfd_expire, tfd); | |||||
| } | |||||
| } | |||||