svn commit: r355372 - stable/12/sys/compat/linux

Vladimir Kondratyev wulf at FreeBSD.org
Tue Dec 3 23:11:41 UTC 2019


Author: wulf
Date: Tue Dec  3 23:11:40 2019
New Revision: 355372
URL: https://svnweb.freebsd.org/changeset/base/355372

Log:
  MFC r355065 - r355068: Linux epoll improvements.
  
  r355065:
  Linux epoll: Don't deregister file descriptor after EPOLLONESHOT is fired
  
  Linux epoll does not remove descriptor after one-shot event has been triggered.
  Set EV_DISPATCH kqueue flag rather then EV_ONESHOT to get the same behavior.
  
  Required by Linux Steam client.
  
  PR:		240590
  Reported by:	Alex S <iwtcex at gmail.com>
  Reviewed by:	emaste, imp
  Differential Revision:	https://reviews.freebsd.org/D22513
  
  r355066:
  Linux epoll: Check both read and write kqueue events existence in EPOLL_CTL_ADD
  
  Linux epoll EPOLL_CTL_ADD op handler should always check registration
  of both EVFILT_READ and EVFILT_WRITE kevents to deceide if supplied
  file descriptor fd is already registered with epoll instance.
  
  Reviewed by:	emaste
  Differential Revision:	https://reviews.freebsd.org/D22515
  
  r355067:
  Linux epoll: Register events with zero event mask
  
  Such an events are legal and should be interpreted as EPOLLERR | EPOLLHUP.
  Register a disabled kqueue event in that case as we do not support EPOLLHUP yet.
  
  Required by Linux Steam client.
  
  PR:		240590
  Reported by:	Alex S <iwtcex at gmail.com>
  Reviewed by:	emaste
  Differential Revision:	https://reviews.freebsd.org/D22516
  
  r355068:
  Linux epoll: Allow passing of any negative timeout value to epoll_wait
  
  Linux epoll allow passing of any negative timeout value to epoll_wait()
  to cause unbound blocking
  
  Reviewed by:	emaste
  Differential Revision:	https://reviews.freebsd.org/D22517

Modified:
  stable/12/sys/compat/linux/linux_event.c
  stable/12/sys/compat/linux/linux_event.h
Directory Properties:
  stable/12/   (props changed)

Modified: stable/12/sys/compat/linux/linux_event.c
==============================================================================
--- stable/12/sys/compat/linux/linux_event.c	Tue Dec  3 23:07:09 2019	(r355371)
+++ stable/12/sys/compat/linux/linux_event.c	Tue Dec  3 23:11:40 2019	(r355372)
@@ -98,14 +98,16 @@ __attribute__((packed))
 #define	LINUX_MAX_EVENTS	(INT_MAX / sizeof(struct epoll_event))
 
 static void	epoll_fd_install(struct thread *td, int fd, epoll_udata_t udata);
-static int	epoll_to_kevent(struct thread *td, struct file *epfp,
-		    int fd, struct epoll_event *l_event, int *kev_flags,
-		    struct kevent *kevent, int *nkevents);
+static int	epoll_to_kevent(struct thread *td, int fd,
+		    struct epoll_event *l_event, struct kevent *kevent,
+		    int *nkevents);
 static void	kevent_to_epoll(struct kevent *kevent, struct epoll_event *l_event);
 static int	epoll_kev_copyout(void *arg, struct kevent *kevp, int count);
 static int	epoll_kev_copyin(void *arg, struct kevent *kevp, int count);
-static int	epoll_delete_event(struct thread *td, struct file *epfp,
-		    int fd, int filter);
+static int	epoll_register_kevent(struct thread *td, struct file *epfp,
+		    int fd, int filter, unsigned int flags);
+static int	epoll_fd_registered(struct thread *td, struct file *epfp,
+		    int fd);
 static int	epoll_delete_all_events(struct thread *td, struct file *epfp,
 		    int fd);
 
@@ -296,33 +298,38 @@ linux_epoll_create1(struct thread *td, struct linux_ep
 
 /* Structure converting function from epoll to kevent. */
 static int
-epoll_to_kevent(struct thread *td, struct file *epfp,
-    int fd, struct epoll_event *l_event, int *kev_flags,
+epoll_to_kevent(struct thread *td, int fd, struct epoll_event *l_event,
     struct kevent *kevent, int *nkevents)
 {
 	uint32_t levents = l_event->events;
 	struct linux_pemuldata *pem;
 	struct proc *p;
+	unsigned short kev_flags = EV_ADD | EV_ENABLE;
 
 	/* flags related to how event is registered */
 	if ((levents & LINUX_EPOLLONESHOT) != 0)
-		*kev_flags |= EV_ONESHOT;
+		kev_flags |= EV_DISPATCH;
 	if ((levents & LINUX_EPOLLET) != 0)
-		*kev_flags |= EV_CLEAR;
+		kev_flags |= EV_CLEAR;
 	if ((levents & LINUX_EPOLLERR) != 0)
-		*kev_flags |= EV_ERROR;
+		kev_flags |= EV_ERROR;
 	if ((levents & LINUX_EPOLLRDHUP) != 0)
-		*kev_flags |= EV_EOF;
+		kev_flags |= EV_EOF;
 
 	/* flags related to what event is registered */
 	if ((levents & LINUX_EPOLL_EVRD) != 0) {
-		EV_SET(kevent++, fd, EVFILT_READ, *kev_flags, 0, 0, 0);
+		EV_SET(kevent++, fd, EVFILT_READ, kev_flags, 0, 0, 0);
 		++(*nkevents);
 	}
 	if ((levents & LINUX_EPOLL_EVWR) != 0) {
-		EV_SET(kevent++, fd, EVFILT_WRITE, *kev_flags, 0, 0, 0);
+		EV_SET(kevent++, fd, EVFILT_WRITE, kev_flags, 0, 0, 0);
 		++(*nkevents);
 	}
+	/* zero event mask is legal */
+	if ((levents & (LINUX_EPOLL_EVRD | LINUX_EPOLL_EVWR)) == 0) {
+		EV_SET(kevent++, fd, EVFILT_READ, EV_ADD|EV_DISABLE, 0, 0, 0);
+		++(*nkevents);
+	}
 
 	if ((levents & ~(LINUX_EPOLL_EVSUP)) != 0) {
 		p = td->td_proc;
@@ -451,7 +458,6 @@ linux_epoll_ctl(struct thread *td, struct linux_epoll_
 					epoll_kev_copyin};
 	struct epoll_event le;
 	cap_rights_t rights;
-	int kev_flags;
 	int nchanges = 0;
 	int error;
 
@@ -484,9 +490,7 @@ linux_epoll_ctl(struct thread *td, struct linux_epoll_
 	ciargs.changelist = kev;
 
 	if (args->op != LINUX_EPOLL_CTL_DEL) {
-		kev_flags = EV_ADD | EV_ENABLE;
-		error = epoll_to_kevent(td, epfp, args->fd, &le,
-		    &kev_flags, kev, &nchanges);
+		error = epoll_to_kevent(td, args->fd, &le, kev, &nchanges);
 		if (error != 0)
 			goto leave0;
 	}
@@ -499,18 +503,10 @@ linux_epoll_ctl(struct thread *td, struct linux_epoll_
 		break;
 
 	case LINUX_EPOLL_CTL_ADD:
-		/*
-		 * kqueue_register() return ENOENT if event does not exists
-		 * and the EV_ADD flag is not set.
-		 */
-		kev[0].flags &= ~EV_ADD;
-		error = kqfd_register(args->epfd, &kev[0], td, M_WAITOK);
-		if (error != ENOENT) {
+		if (epoll_fd_registered(td, epfp, args->fd)) {
 			error = EEXIST;
 			goto leave0;
 		}
-		error = 0;
-		kev[0].flags |= EV_ADD;
 		break;
 
 	case LINUX_EPOLL_CTL_DEL:
@@ -561,13 +557,13 @@ linux_epoll_wait_common(struct thread *td, int epfd, s
 		return (error);
 	if (epfp->f_type != DTYPE_KQUEUE) {
 		error = EINVAL;
-		goto leave1;
+		goto leave;
 	}
 	if (uset != NULL) {
 		error = kern_sigprocmask(td, SIG_SETMASK, uset,
 		    &omask, 0);
 		if (error != 0)
-			goto leave1;
+			goto leave;
 		td->td_pflags |= TDP_OLDMASK;
 		/*
 		 * Make sure that ast() is called on return to
@@ -585,11 +581,12 @@ linux_epoll_wait_common(struct thread *td, int epfd, s
 	coargs.count = 0;
 	coargs.error = 0;
 
-	if (timeout != -1) {
-		if (timeout < 0) {
-			error = EINVAL;
-			goto leave0;
-		}
+	/*
+	 * Linux epoll_wait(2) man page states that timeout of -1 causes caller
+	 * to block indefinitely. Real implementation does it if any negative
+	 * timeout value is passed.
+	 */
+	if (timeout >= 0) {
 		/* Convert from milliseconds to timespec. */
 		ts.tv_sec = timeout / 1000;
 		ts.tv_nsec = (timeout % 1000) * 1000000;
@@ -609,11 +606,10 @@ linux_epoll_wait_common(struct thread *td, int epfd, s
 	if (error == 0)
 		td->td_retval[0] = coargs.count;
 
-leave0:
 	if (uset != NULL)
 		error = kern_sigprocmask(td, SIG_SETMASK, &omask,
 		    NULL, 0);
-leave1:
+leave:
 	fdrop(epfp, td);
 	return (error);
 }
@@ -650,7 +646,8 @@ linux_epoll_pwait(struct thread *td, struct linux_epol
 }
 
 static int
-epoll_delete_event(struct thread *td, struct file *epfp, int fd, int filter)
+epoll_register_kevent(struct thread *td, struct file *epfp, int fd, int filter,
+    unsigned int flags)
 {
 	struct epoll_copyin_args ciargs;
 	struct kevent kev;
@@ -659,18 +656,36 @@ epoll_delete_event(struct thread *td, struct file *epf
 					epoll_kev_copyin};
 
 	ciargs.changelist = &kev;
-	EV_SET(&kev, fd, filter, EV_DELETE | EV_DISABLE, 0, 0, 0);
+	EV_SET(&kev, fd, filter, flags, 0, 0, 0);
 
 	return (kern_kevent_fp(td, epfp, 1, 0, &k_ops, NULL));
 }
 
 static int
+epoll_fd_registered(struct thread *td, struct file *epfp, int fd)
+{
+	/*
+	 * Set empty filter flags to avoid accidental modification of already
+	 * registered events. In the case of event re-registration:
+	 * 1. If event does not exists kevent() does nothing and returns ENOENT
+	 * 2. If event does exists, it's enabled/disabled state is preserved
+	 *    but fflags, data and udata fields are overwritten. So we can not
+	 *    set socket lowats and store user's context pointer in udata.
+	 */
+	if (epoll_register_kevent(td, epfp, fd, EVFILT_READ, 0) != ENOENT ||
+	    epoll_register_kevent(td, epfp, fd, EVFILT_WRITE, 0) != ENOENT)
+		return (1);
+
+	return (0);
+}
+
+static int
 epoll_delete_all_events(struct thread *td, struct file *epfp, int fd)
 {
 	int error1, error2;
 
-	error1 = epoll_delete_event(td, epfp, fd, EVFILT_READ);
-	error2 = epoll_delete_event(td, epfp, fd, EVFILT_WRITE);
+	error1 = epoll_register_kevent(td, epfp, fd, EVFILT_READ, EV_DELETE);
+	error2 = epoll_register_kevent(td, epfp, fd, EVFILT_WRITE, EV_DELETE);
 
 	/* return 0 if at least one result positive */
 	return (error1 == 0 ? 0 : error2);

Modified: stable/12/sys/compat/linux/linux_event.h
==============================================================================
--- stable/12/sys/compat/linux/linux_event.h	Tue Dec  3 23:07:09 2019	(r355371)
+++ stable/12/sys/compat/linux/linux_event.h	Tue Dec  3 23:11:40 2019	(r355372)
@@ -45,10 +45,10 @@
 #define	LINUX_EPOLLONESHOT	1u<<30
 #define	LINUX_EPOLLET		1u<<31
 
-#define	LINUX_EPOLL_EVRD	(LINUX_EPOLLIN|LINUX_EPOLLRDNORM	\
-		|LINUX_EPOLLHUP|LINUX_EPOLLERR|LINUX_EPOLLPRI)
+#define	LINUX_EPOLL_EVRD	(LINUX_EPOLLIN|LINUX_EPOLLRDNORM)
 #define	LINUX_EPOLL_EVWR	(LINUX_EPOLLOUT|LINUX_EPOLLWRNORM)
 #define	LINUX_EPOLL_EVSUP	(LINUX_EPOLLET|LINUX_EPOLLONESHOT	\
+		|LINUX_EPOLLHUP|LINUX_EPOLLERR|LINUX_EPOLLPRI		\
 		|LINUX_EPOLL_EVRD|LINUX_EPOLL_EVWR|LINUX_EPOLLRDHUP)
 
 #define	LINUX_EPOLL_CTL_ADD	1


More information about the svn-src-all mailing list