Add accept4() system call.

The accept4() function, compared to accept(), allows setting the new file
descriptor atomically close-on-exec and explicitly controlling the
non-blocking status on the new socket. (Note that the latter point means
that accept() is not equivalent to any form of accept4().)

The linuxulator's accept4 implementation leaves a race window where the new
file descriptor is not close-on-exec because it calls sys_accept(). This
implementation leaves no such race window (by using falloc() flags). The
linuxulator could be fixed and simplified by using the new code.

Like accept(), accept4() is async-signal-safe, a cancellation point and
permitted in capability mode.
This commit is contained in:
Jilles Tjoelker 2013-05-01 20:10:21 +00:00
parent 14f525595c
commit da7d2afb6d
11 changed files with 143 additions and 28 deletions

View File

@ -270,6 +270,7 @@ MAN+= sctp_generic_recvmsg.2 \
wait.2 \ wait.2 \
write.2 write.2
MLINKS+=accept.2 accept4.2
MLINKS+=access.2 eaccess.2 \ MLINKS+=access.2 eaccess.2 \
access.2 faccessat.2 access.2 faccessat.2
MLINKS+=brk.2 sbrk.2 MLINKS+=brk.2 sbrk.2

View File

@ -378,6 +378,7 @@ FBSD_1.2 {
}; };
FBSD_1.3 { FBSD_1.3 {
accept4;
bindat; bindat;
cap_fcntls_get; cap_fcntls_get;
cap_fcntls_limit; cap_fcntls_limit;
@ -461,6 +462,8 @@ FBSDprivate_1.0 {
__sys_abort2; __sys_abort2;
_accept; _accept;
__sys_accept; __sys_accept;
_accept4;
__sys_accept4;
_access; _access;
__sys_access; __sys_access;
_acct; _acct;

View File

@ -41,6 +41,8 @@
.In sys/socket.h .In sys/socket.h
.Ft int .Ft int
.Fn accept "int s" "struct sockaddr * restrict addr" "socklen_t * restrict addrlen" .Fn accept "int s" "struct sockaddr * restrict addr" "socklen_t * restrict addrlen"
.Ft int
.Fn accept4 "int s" "struct sockaddr * restrict addr" "socklen_t * restrict addrlen" "int flags"
.Sh DESCRIPTION .Sh DESCRIPTION
The argument The argument
.Fa s .Fa s
@ -66,6 +68,26 @@ and
signals from the original socket signals from the original socket
.Fa s . .Fa s .
.Pp .Pp
The
.Fn accept4
system call is similar,
but the
.Dv O_NONBLOCK
property of the new socket is instead determined by the
.Dv SOCK_NONBLOCK
flag in the
.Fa flags
argument,
the
.Dv O_ASYNC
property is cleared,
the signal destination is cleared
and the close-on-exec flag on the new file descriptor can be set via the
.Dv SOCK_CLOEXEC
flag in the
.Fa flags
argument.
.Pp
If no pending connections are If no pending connections are
present on the queue, and the original socket present on the queue, and the original socket
is not marked as non-blocking, is not marked as non-blocking,
@ -141,13 +163,15 @@ properties and the signal destination being inherited,
but should set them explicitly using but should set them explicitly using
.Xr fcntl 2 . .Xr fcntl 2 .
.Sh RETURN VALUES .Sh RETURN VALUES
The call returns \-1 on error. These calls return \-1 on error.
If it succeeds, it returns a non-negative If they succeed, they return a non-negative
integer that is a descriptor for the accepted socket. integer that is a descriptor for the accepted socket.
.Sh ERRORS .Sh ERRORS
The The
.Fn accept .Fn accept
system call will fail if: and
.Fn accept4
system calls will fail if:
.Bl -tag -width Er .Bl -tag -width Er
.It Bq Er EBADF .It Bq Er EBADF
The descriptor is invalid. The descriptor is invalid.
@ -176,6 +200,16 @@ are present to be accepted.
A connection arrived, but it was closed while waiting A connection arrived, but it was closed while waiting
on the listen queue. on the listen queue.
.El .El
.Pp
The
.Fn accept4
system call will also fail if:
.Bl -tag -width Er
.It Bq Er EINVAL
The
.Fa flags
argument is invalid.
.El
.Sh SEE ALSO .Sh SEE ALSO
.Xr bind 2 , .Xr bind 2 ,
.Xr connect 2 , .Xr connect 2 ,
@ -190,3 +224,8 @@ The
.Fn accept .Fn accept
system call appeared in system call appeared in
.Bx 4.2 . .Bx 4.2 .
.Pp
The
.Fn accept4
system call appeared in
.Fx 10.0 .

View File

@ -181,6 +181,7 @@ FBSDprivate_1.0 {
___wait; ___wait;
___waitpid; ___waitpid;
__accept; __accept;
__accept4;
__aio_suspend; __aio_suspend;
__close; __close;
__connect; __connect;
@ -408,3 +409,7 @@ FBSD_1.2 {
setcontext; setcontext;
swapcontext; swapcontext;
}; };
FBSD_1.3 {
accept4;
};

View File

@ -101,6 +101,7 @@ extern pid_t __waitpid(pid_t, int *, int);
extern int __sys_aio_suspend(const struct aiocb * const[], int, extern int __sys_aio_suspend(const struct aiocb * const[], int,
const struct timespec *); const struct timespec *);
extern int __sys_accept(int, struct sockaddr *, socklen_t *); extern int __sys_accept(int, struct sockaddr *, socklen_t *);
extern int __sys_accept4(int, struct sockaddr *, socklen_t *, int);
extern int __sys_connect(int, const struct sockaddr *, socklen_t); extern int __sys_connect(int, const struct sockaddr *, socklen_t);
extern int __sys_fsync(int); extern int __sys_fsync(int);
extern int __sys_msync(void *, size_t, int); extern int __sys_msync(void *, size_t, int);
@ -129,6 +130,7 @@ int ___usleep(useconds_t useconds);
pid_t ___wait(int *); pid_t ___wait(int *);
pid_t ___waitpid(pid_t, int *, int); pid_t ___waitpid(pid_t, int *, int);
int __accept(int, struct sockaddr *, socklen_t *); int __accept(int, struct sockaddr *, socklen_t *);
int __accept4(int, struct sockaddr *, socklen_t *, int);
int __aio_suspend(const struct aiocb * const iocbs[], int, int __aio_suspend(const struct aiocb * const iocbs[], int,
const struct timespec *); const struct timespec *);
int __close(int); int __close(int);
@ -176,6 +178,26 @@ __accept(int s, struct sockaddr *addr, socklen_t *addrlen)
return (ret); return (ret);
} }
__weak_reference(__accept4, accept4);
/*
* Cancellation behavior:
* If thread is canceled, no socket is created.
*/
int
__accept4(int s, struct sockaddr *addr, socklen_t *addrlen, int flags)
{
struct pthread *curthread;
int ret;
curthread = _get_curthread();
_thr_cancel_enter(curthread);
ret = __sys_accept4(s, addr, addrlen, flags);
_thr_cancel_leave(curthread, ret == -1);
return (ret);
}
__weak_reference(__aio_suspend, aio_suspend); __weak_reference(__aio_suspend, aio_suspend);
int int

View File

@ -1022,3 +1022,7 @@
int namelen); } int namelen); }
540 AUE_CHFLAGSAT NOPROTO { int chflagsat(int fd, const char *path, \ 540 AUE_CHFLAGSAT NOPROTO { int chflagsat(int fd, const char *path, \
u_long flags, int atflag); } u_long flags, int atflag); }
541 AUE_ACCEPT NOPROTO { int accept4(int s, \
struct sockaddr * __restrict name, \
__socklen_t * __restrict anamelen, \
int flags); }

View File

@ -78,6 +78,7 @@ abort2
## relies on existing bindings on a socket, subject to capability rights. ## relies on existing bindings on a socket, subject to capability rights.
## ##
accept accept
accept4
## ##
## Allow AIO operations by file descriptor, subject to capability rights. ## Allow AIO operations by file descriptor, subject to capability rights.

View File

@ -972,5 +972,9 @@
int namelen); } int namelen); }
540 AUE_CHFLAGSAT STD { int chflagsat(int fd, const char *path, \ 540 AUE_CHFLAGSAT STD { int chflagsat(int fd, const char *path, \
u_long flags, int atflag); } u_long flags, int atflag); }
541 AUE_ACCEPT STD { int accept4(int s, \
struct sockaddr * __restrict name, \
__socklen_t * __restrict anamelen, \
int flags); }
; Please copy any additions and changes to the following compatability tables: ; Please copy any additions and changes to the following compatability tables:
; sys/compat/freebsd32/syscalls.master ; sys/compat/freebsd32/syscalls.master

View File

@ -97,10 +97,18 @@ __FBSDID("$FreeBSD$");
#endif /* SCTP */ #endif /* SCTP */
#endif /* INET || INET6 */ #endif /* INET || INET6 */
/*
* Flags for accept1() and kern_accept4(), in addition to SOCK_CLOEXEC
* and SOCK_NONBLOCK.
*/
#define ACCEPT4_INHERIT 0x1
#define ACCEPT4_COMPAT 0x2
static int sendit(struct thread *td, int s, struct msghdr *mp, int flags); static int sendit(struct thread *td, int s, struct msghdr *mp, int flags);
static int recvit(struct thread *td, int s, struct msghdr *mp, void *namelenp); static int recvit(struct thread *td, int s, struct msghdr *mp, void *namelenp);
static int accept1(struct thread *td, struct accept_args *uap, int compat); static int accept1(struct thread *td, int s, struct sockaddr *uname,
socklen_t *anamelen, int flags);
static int do_sendfile(struct thread *td, struct sendfile_args *uap, int compat); static int do_sendfile(struct thread *td, struct sendfile_args *uap, int compat);
static int getsockname1(struct thread *td, struct getsockname_args *uap, static int getsockname1(struct thread *td, struct getsockname_args *uap,
int compat); int compat);
@ -317,49 +325,46 @@ sys_listen(td, uap)
* accept1() * accept1()
*/ */
static int static int
accept1(td, uap, compat) accept1(td, s, uname, anamelen, flags)
struct thread *td; struct thread *td;
struct accept_args /* { int s;
int s; struct sockaddr *uname;
struct sockaddr * __restrict name; socklen_t *anamelen;
socklen_t * __restrict anamelen; int flags;
} */ *uap;
int compat;
{ {
struct sockaddr *name; struct sockaddr *name;
socklen_t namelen; socklen_t namelen;
struct file *fp; struct file *fp;
int error; int error;
if (uap->name == NULL) if (uname == NULL)
return (kern_accept(td, uap->s, NULL, NULL, NULL)); return (kern_accept4(td, s, NULL, NULL, flags, NULL));
error = copyin(uap->anamelen, &namelen, sizeof (namelen)); error = copyin(anamelen, &namelen, sizeof (namelen));
if (error) if (error)
return (error); return (error);
error = kern_accept(td, uap->s, &name, &namelen, &fp); error = kern_accept4(td, s, &name, &namelen, flags, &fp);
/* /*
* return a namelen of zero for older code which might * return a namelen of zero for older code which might
* ignore the return value from accept. * ignore the return value from accept.
*/ */
if (error) { if (error) {
(void) copyout(&namelen, (void) copyout(&namelen, anamelen, sizeof(*anamelen));
uap->anamelen, sizeof(*uap->anamelen));
return (error); return (error);
} }
if (error == 0 && name != NULL) { if (error == 0 && uname != NULL) {
#ifdef COMPAT_OLDSOCK #ifdef COMPAT_OLDSOCK
if (compat) if (flags & ACCEPT4_COMPAT)
((struct osockaddr *)name)->sa_family = ((struct osockaddr *)name)->sa_family =
name->sa_family; name->sa_family;
#endif #endif
error = copyout(name, uap->name, namelen); error = copyout(name, uname, namelen);
} }
if (error == 0) if (error == 0)
error = copyout(&namelen, uap->anamelen, error = copyout(&namelen, anamelen,
sizeof(namelen)); sizeof(namelen));
if (error) if (error)
fdclose(td->td_proc->p_fd, fp, td->td_retval[0], td); fdclose(td->td_proc->p_fd, fp, td->td_retval[0], td);
@ -371,6 +376,13 @@ accept1(td, uap, compat)
int int
kern_accept(struct thread *td, int s, struct sockaddr **name, kern_accept(struct thread *td, int s, struct sockaddr **name,
socklen_t *namelen, struct file **fp) socklen_t *namelen, struct file **fp)
{
return (kern_accept4(td, s, name, namelen, ACCEPT4_INHERIT, fp));
}
int
kern_accept4(struct thread *td, int s, struct sockaddr **name,
socklen_t *namelen, int flags, struct file **fp)
{ {
struct filedesc *fdp; struct filedesc *fdp;
struct file *headfp, *nfp = NULL; struct file *headfp, *nfp = NULL;
@ -400,7 +412,7 @@ kern_accept(struct thread *td, int s, struct sockaddr **name,
if (error != 0) if (error != 0)
goto done; goto done;
#endif #endif
error = falloc(td, &nfp, &fd, 0); error = falloc(td, &nfp, &fd, (flags & SOCK_CLOEXEC) ? O_CLOEXEC : 0);
if (error) if (error)
goto done; goto done;
ACCEPT_LOCK(); ACCEPT_LOCK();
@ -441,7 +453,10 @@ kern_accept(struct thread *td, int s, struct sockaddr **name,
TAILQ_REMOVE(&head->so_comp, so, so_list); TAILQ_REMOVE(&head->so_comp, so, so_list);
head->so_qlen--; head->so_qlen--;
so->so_state |= (head->so_state & SS_NBIO); if (flags & ACCEPT4_INHERIT)
so->so_state |= (head->so_state & SS_NBIO);
else
so->so_state |= (flags & SOCK_NONBLOCK) ? SS_NBIO : 0;
so->so_qstate &= ~SQ_COMP; so->so_qstate &= ~SQ_COMP;
so->so_head = NULL; so->so_head = NULL;
@ -454,9 +469,15 @@ kern_accept(struct thread *td, int s, struct sockaddr **name,
/* connection has been removed from the listen queue */ /* connection has been removed from the listen queue */
KNOTE_UNLOCKED(&head->so_rcv.sb_sel.si_note, 0); KNOTE_UNLOCKED(&head->so_rcv.sb_sel.si_note, 0);
pgid = fgetown(&head->so_sigio); if (flags & ACCEPT4_INHERIT) {
if (pgid != 0) pgid = fgetown(&head->so_sigio);
fsetown(pgid, &so->so_sigio); if (pgid != 0)
fsetown(pgid, &so->so_sigio);
} else {
fflag &= ~(FNONBLOCK | FASYNC);
if (flags & SOCK_NONBLOCK)
fflag |= FNONBLOCK;
}
finit(nfp, fflag, DTYPE_SOCKET, so, &socketops); finit(nfp, fflag, DTYPE_SOCKET, so, &socketops);
/* Sync socket nonblocking/async state with file flags */ /* Sync socket nonblocking/async state with file flags */
@ -527,7 +548,18 @@ sys_accept(td, uap)
struct accept_args *uap; struct accept_args *uap;
{ {
return (accept1(td, uap, 0)); return (accept1(td, uap->s, uap->name, uap->anamelen, ACCEPT4_INHERIT));
}
int
sys_accept4(td, uap)
struct thread *td;
struct accept4_args *uap;
{
if (uap->flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
return (EINVAL);
return (accept1(td, uap->s, uap->name, uap->anamelen, uap->flags));
} }
#ifdef COMPAT_OLDSOCK #ifdef COMPAT_OLDSOCK
@ -537,7 +569,8 @@ oaccept(td, uap)
struct accept_args *uap; struct accept_args *uap;
{ {
return (accept1(td, uap, 1)); return (accept1(td, uap->s, uap->name, uap->anamelen,
ACCEPT4_INHERIT | ACCEPT4_COMPAT));
} }
#endif /* COMPAT_OLDSOCK */ #endif /* COMPAT_OLDSOCK */

View File

@ -634,6 +634,7 @@ int accept(int, struct sockaddr * __restrict, socklen_t * __restrict);
int bind(int, const struct sockaddr *, socklen_t); int bind(int, const struct sockaddr *, socklen_t);
int connect(int, const struct sockaddr *, socklen_t); int connect(int, const struct sockaddr *, socklen_t);
#if __BSD_VISIBLE #if __BSD_VISIBLE
int accept4(int, struct sockaddr * __restrict, socklen_t * __restrict, int);
int bindat(int, int, const struct sockaddr *, socklen_t); int bindat(int, int, const struct sockaddr *, socklen_t);
int connectat(int, int, const struct sockaddr *, socklen_t); int connectat(int, int, const struct sockaddr *, socklen_t);
#endif #endif

View File

@ -60,6 +60,8 @@ int kern___getcwd(struct thread *td, u_char *buf, enum uio_seg bufseg,
u_int buflen); u_int buflen);
int kern_accept(struct thread *td, int s, struct sockaddr **name, int kern_accept(struct thread *td, int s, struct sockaddr **name,
socklen_t *namelen, struct file **fp); socklen_t *namelen, struct file **fp);
int kern_accept4(struct thread *td, int s, struct sockaddr **name,
socklen_t *namelen, int flags, struct file **fp);
int kern_access(struct thread *td, char *path, enum uio_seg pathseg, int kern_access(struct thread *td, char *path, enum uio_seg pathseg,
int flags); int flags);
int kern_accessat(struct thread *td, int fd, char *path, int kern_accessat(struct thread *td, int fd, char *path,