/**************************************************************************** * net/local/local_recvmsg.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #if defined(CONFIG_NET) && defined(CONFIG_NET_LOCAL) #include #include #include #include #include #include #include #include #include #include #include #include #include "socket/socket.h" #include "local/local.h" #include "utils/utils.h" /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: psock_fifo_read * * Description: * A thin layer around local_fifo_read that handles socket-related loss-of- * connection events. * ****************************************************************************/ static int psock_fifo_read(FAR struct socket *psock, FAR void *buf, FAR size_t *readlen, int flags, bool once) { FAR struct local_conn_s *conn = psock->s_conn; int ret; if (flags & MSG_PEEK) { struct pipe_peek_s peek = { buf, *readlen }; ret = file_ioctl(&conn->lc_infile, PIPEIOC_PEEK, (unsigned long)((uintptr_t)&peek)); } else { ret = local_fifo_read(&conn->lc_infile, buf, readlen, once); } if (ret < 0) { /* -ECONNRESET is a special case. We may or not have received * data, then the peer closed the connection. */ if (ret == -ECONNRESET) { nerr("ERROR: Lost connection: %d\n", ret); /* Report an ungraceful loss of connection. This should * eventually be reported as ENOTCONN. */ conn->lc_conn.s_flags &= ~(_SF_CONNECTED | _SF_CLOSED); conn->lc_state = LOCAL_STATE_DISCONNECTED; /* Did we receive any data? */ if (*readlen <= 0) { /* No.. return the ECONNRESET error now. Otherwise, * process the received data and return ENOTCONN the * next time that psock_recvfrom() is called. */ return ret; } } else { if (ret == -EAGAIN) { nwarn("WARNING: Failed to read packet: %d\n", ret); } else { nerr("ERROR: Failed to read packet: %d\n", ret); } return ret; } } return OK; } /**************************************************************************** * Name: local_recvctl * * Description: * Handle the socket message conntrol field * * Input Parameters: * conn Local connection instance * msg Message to send * ****************************************************************************/ #ifdef CONFIG_NET_LOCAL_SCM static void local_recvctl(FAR struct local_conn_s *conn, FAR struct msghdr *msg, int flags) { FAR struct local_conn_s *peer; int count; int *fds; int i; net_lock(); if (conn->lc_peer == NULL) { peer = local_peerconn(conn); if (peer == NULL) { goto out; } } else { peer = conn; } if (peer->lc_cfpcount == 0) { goto out; } fds = cmsg_append(msg, SOL_SOCKET, SCM_RIGHTS, NULL, sizeof(int) * peer->lc_cfpcount); if (fds == NULL) { goto out; } count = peer->lc_cfpcount; for (i = 0; i < count; i++) { fds[i] = file_dup(peer->lc_cfps[i], 0, flags & MSG_CMSG_CLOEXEC ? O_CLOEXEC : 0); file_close(peer->lc_cfps[i]); kmm_free(peer->lc_cfps[i]); peer->lc_cfps[i] = NULL; peer->lc_cfpcount--; if (fds[i] < 0) { i++; break; } } if (i > 0) { if (peer->lc_cfpcount) { memmove(peer->lc_cfps[0], peer->lc_cfps[i], sizeof(FAR void *) * peer->lc_cfpcount); } } out: net_unlock(); } #endif /* CONFIG_NET_LOCAL_SCM */ /**************************************************************************** * Name: psock_stream_recvfrom * * Description: * psock_stream_recvfrom() receives messages from a local stream socket. * * Input Parameters: * psock A pointer to a NuttX-specific, internal socket structure * buf Buffer to receive data * len Length of buffer * flags Receive flags * from Address of source (may be NULL) * fromlen The length of the address structure * * Returned Value: * On success, returns the number of characters received. If no data is * available to be received and the peer has performed an orderly shutdown, * recv() will return 0. Otherwise, on errors, -1 is returned, and errno * is set appropriately (see receive from for the complete list). * ****************************************************************************/ #ifdef CONFIG_NET_LOCAL_STREAM static inline ssize_t psock_stream_recvfrom(FAR struct socket *psock, FAR void *buf, size_t len, int flags, FAR struct sockaddr *from, FAR socklen_t *fromlen) { FAR struct local_conn_s *conn = psock->s_conn; size_t readlen = len; int ret; /* Verify that this is a connected peer socket */ if (conn->lc_state != LOCAL_STATE_CONNECTED || conn->lc_infile.f_inode == NULL) { if (conn->lc_state == LOCAL_STATE_CONNECTING) { return -EAGAIN; } nerr("ERROR: not connected\n"); return -ENOTCONN; } /* If it is non-blocking mode, the data in fifo is 0 and * returns directly */ if (flags & MSG_DONTWAIT) { int data_len = 0; ret = file_ioctl(&conn->lc_infile, FIONREAD, &data_len); if (ret < 0) { return ret; } if (data_len == 0) { return -EAGAIN; } } /* Read the packet */ ret = psock_fifo_read(psock, buf, &readlen, flags, true); if (ret < 0) { return ret; } /* Return the address family */ if (from) { ret = local_getaddr(conn, from, fromlen); if (ret < 0) { return ret; } } return readlen; } #endif /* CONFIG_NET_LOCAL_STREAM */ /**************************************************************************** * Name: psock_dgram_recvfrom * * Description: * psock_dgram_recvfrom() receives messages from a local datagram socket. * * Input Parameters: * psock A pointer to a NuttX-specific, internal socket structure * buf Buffer to receive data * len Length of buffer * flags Receive flags * from Address of source (may be NULL) * fromlen The length of the address structure * * Returned Value: * On success, returns the number of characters received. Otherwise, on * errors, -1 is returned, and errno is set appropriately (see receive * from for the complete list). * ****************************************************************************/ #ifdef CONFIG_NET_LOCAL_DGRAM static inline ssize_t psock_dgram_recvfrom(FAR struct socket *psock, FAR void *buf, size_t len, int flags, FAR struct sockaddr *from, FAR socklen_t *fromlen) { FAR struct local_conn_s *conn = psock->s_conn; size_t readlen; bool bclose = false; int ret = 0; /* We keep packet sizes in a uint16_t, so there is a upper limit to the * 'len' that can be supported. */ DEBUGASSERT(len <= UINT16_MAX); /* Verify that this is a bound, un-connected peer socket */ if (conn->lc_state != LOCAL_STATE_BOUND && conn->lc_state != LOCAL_STATE_CONNECTED) { /* Either not bound to address or it is connected */ nerr("ERROR: Connected or not bound\n"); return -EISCONN; } if (conn->lc_infile.f_inode == NULL) { bclose = true; /* Make sure that half duplex FIFO has been created */ ret = local_create_halfduplex(conn, conn->lc_path); if (ret < 0) { nerr("ERROR: Failed to create FIFO for %s: %d\n", conn->lc_path, ret); return ret; } /* Open the receiving side of the transfer */ ret = local_open_receiver(conn, (flags & MSG_DONTWAIT) != 0 || _SS_ISNONBLOCK(conn->lc_conn.s_flags)); if (ret < 0) { nerr("ERROR: Failed to open FIFO for %s: %d\n", conn->lc_path, ret); goto errout_with_halfduplex; } } /* Sync to the start of the next packet in the stream and get the size of * the next packet. */ if (conn->pktlen <= 0) { ret = local_sync(&conn->lc_infile); if (ret < 0) { nerr("ERROR: Failed to get packet length: %d\n", ret); goto errout_with_infd; } else if (ret > UINT16_MAX) { nerr("ERROR: Packet is too big: %d\n", ret); goto errout_with_infd; } conn->pktlen = ret; } /* Read the packet */ readlen = MIN(conn->pktlen, len); ret = psock_fifo_read(psock, buf, &readlen, flags, false); if (ret < 0) { goto errout_with_infd; } /* If there are unread bytes remaining in the packet, flush the remainder * of the packet to the bit bucket. */ if (flags & MSG_PEEK) { goto skip_flush; } DEBUGASSERT(readlen <= conn->pktlen); if (readlen < conn->pktlen) { uint8_t bitbucket[32]; uint16_t remaining; size_t tmplen; remaining = conn->pktlen - readlen; do { /* Read 32 bytes into the bit bucket */ tmplen = MIN(remaining, 32); ret = psock_fifo_read(psock, bitbucket, &tmplen, flags, false); if (ret < 0) { goto errout_with_infd; } /* Adjust the number of bytes remaining to be read from the * packet */ DEBUGASSERT(tmplen <= remaining); remaining -= tmplen; } while (remaining > 0); } /* The fifo has been read and the pktlen needs to be cleared */ conn->pktlen = 0; skip_flush: /* Return the address family */ if (from) { ret = local_getaddr(conn, from, fromlen); if (ret < 0) { return ret; } } errout_with_infd: /* Close the read-only file descriptor */ if (bclose) { /* Now we can close the read-only file descriptor */ file_close(&conn->lc_infile); conn->lc_infile.f_inode = NULL; errout_with_halfduplex: /* Release our reference to the half duplex FIFO */ local_release_halfduplex(conn); } return ret < 0 ? ret : readlen; } #endif /* CONFIG_NET_LOCAL_STREAM */ /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: local_recvmsg * * Description: * recvmsg() receives messages from a local socket and may be used to * receive data on a socket whether or not it is connection-oriented. * * If from is not NULL, and the underlying protocol provides the source * address, this source address is filled in. The argument fromlen * initialized to the size of the buffer associated with from, and modified * on return to indicate the actual size of the address stored there. * * Input Parameters: * psock A pointer to a NuttX-specific, internal socket structure * msg Buffer to receive the message * flags Receive flags (ignored for now) * * Returned Value: * On success, returns the number of characters received. If no data is * available to be received and the peer has performed an orderly shutdown, * recvmsg() will return 0. Otherwise, on errors, a negated errno value is * returned (see recvmsg() for the list of appropriate error values). * ****************************************************************************/ ssize_t local_recvmsg(FAR struct socket *psock, FAR struct msghdr *msg, int flags) { FAR socklen_t *fromlen = &msg->msg_namelen; FAR struct sockaddr *from = msg->msg_name; FAR void *buf = msg->msg_iov->iov_base; size_t len = msg->msg_iov->iov_len; DEBUGASSERT(buf); /* Check for a stream socket */ #ifdef CONFIG_NET_LOCAL_STREAM if (psock->s_type == SOCK_STREAM) { len = psock_stream_recvfrom(psock, buf, len, flags, from, fromlen); } else #endif #ifdef CONFIG_NET_LOCAL_DGRAM if (psock->s_type == SOCK_DGRAM) { len = psock_dgram_recvfrom(psock, buf, len, flags, from, fromlen); } else #endif { DEBUGPANIC(); nerr("ERROR: Unrecognized socket type: %" PRIu8 "\n", psock->s_type); len = -EINVAL; } #ifdef CONFIG_NET_LOCAL_SCM /* Receive the control message */ if (len >= 0 && msg->msg_control && msg->msg_controllen > sizeof(struct cmsghdr)) { local_recvctl(psock->s_conn, msg, flags); } #endif /* CONFIG_NET_LOCAL_SCM */ return len; } #endif /* CONFIG_NET && CONFIG_NET_LOCAL */