chiark / gitweb /
sd-rtnl: make string returned by sd_rtnl_message_read_string() const
[elogind.git] / src / libsystemd / sd-rtnl / rtnl-message.c
index fc71ed90183c4547a2ccbf604c6651c09f595c88..c0a38e148b1ee6c8a1cb20349cee82f33f1f7c7f 100644 (file)
@@ -146,7 +146,7 @@ int sd_rtnl_message_new_route(sd_rtnl *rtnl, sd_rtnl_message **ret,
                 return r;
 
         if (nlmsg_type == RTM_NEWROUTE)
-                (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL;
+                (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_APPEND;
 
         rtm = NLMSG_DATA((*ret)->hdr);
 
@@ -213,6 +213,22 @@ int sd_rtnl_message_new_link(sd_rtnl *rtnl, sd_rtnl_message **ret,
         return 0;
 }
 
+int sd_rtnl_message_request_dump(sd_rtnl_message *m, int dump) {
+        assert_return(m, -EINVAL);
+        assert_return(m->hdr, -EINVAL);
+        assert_return(m->hdr->nlmsg_type == RTM_GETLINK ||
+                      m->hdr->nlmsg_type == RTM_GETADDR ||
+                      m->hdr->nlmsg_type == RTM_GETROUTE,
+                      -EINVAL);
+
+        if (dump)
+                m->hdr->nlmsg_flags |= NLM_F_DUMP;
+        else
+                m->hdr->nlmsg_flags &= ~NLM_F_DUMP;
+
+        return 0;
+}
+
 int sd_rtnl_message_addr_set_prefixlen(sd_rtnl_message *m, unsigned char prefixlen) {
         struct ifaddrmsg *ifa;
 
@@ -274,6 +290,21 @@ int sd_rtnl_message_addr_get_family(sd_rtnl_message *m, unsigned char *family) {
         return 0;
 }
 
+int sd_rtnl_message_addr_get_prefixlen(sd_rtnl_message *m, unsigned char *prefixlen) {
+        struct ifaddrmsg *ifa;
+
+        assert_return(m, -EINVAL);
+        assert_return(m->hdr, -EINVAL);
+        assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL);
+        assert_return(prefixlen, -EINVAL);
+
+        ifa = NLMSG_DATA(m->hdr);
+
+        *prefixlen = ifa->ifa_prefixlen;
+
+        return 0;
+}
+
 int sd_rtnl_message_addr_get_scope(sd_rtnl_message *m, unsigned char *scope) {
         struct ifaddrmsg *ifa;
 
@@ -326,15 +357,17 @@ int sd_rtnl_message_new_addr(sd_rtnl *rtnl, sd_rtnl_message **ret,
         int r;
 
         assert_return(rtnl_message_type_is_addr(nlmsg_type), -EINVAL);
-        assert_return(index > 0, -EINVAL);
-        assert_return(family == AF_INET || family == AF_INET6, -EINVAL);
+        assert_return((nlmsg_type == RTM_GETADDR && index == 0) ||
+                      index > 0, -EINVAL);
+        assert_return((nlmsg_type == RTM_GETADDR && family == AF_UNSPEC) ||
+                      family == AF_INET || family == AF_INET6, -EINVAL);
         assert_return(ret, -EINVAL);
 
         r = message_new(rtnl, ret, nlmsg_type);
         if (r < 0)
                 return r;
 
-        if (nlmsg_type == RTM_GETADDR && family == AF_INET)
+        if (nlmsg_type == RTM_GETADDR)
                 (*ret)->hdr->nlmsg_flags |= NLM_F_DUMP;
 
         ifa = NLMSG_DATA((*ret)->hdr);
@@ -378,6 +411,8 @@ sd_rtnl_message *sd_rtnl_message_unref(sd_rtnl_message *m) {
                 for (i = 0; i <= m->n_containers; i++)
                         free(m->rta_offset_tb[i]);
 
+                sd_rtnl_message_unref(m->next);
+
                 free(m);
         }
 
@@ -432,24 +467,28 @@ int sd_rtnl_message_link_get_flags(sd_rtnl_message *m, unsigned *flags) {
 /* If successful the updated message will be correctly aligned, if
    unsuccessful the old message is untouched. */
 static int add_rtattr(sd_rtnl_message *m, unsigned short type, const void *data, size_t data_length) {
-        uint32_t rta_length, message_length;
+        uint32_t rta_length;
+        size_t message_length, padding_length;
         struct nlmsghdr *new_hdr;
         struct rtattr *rta;
         char *padding;
         unsigned i;
+        int offset;
 
         assert(m);
         assert(m->hdr);
         assert(!m->sealed);
         assert(NLMSG_ALIGN(m->hdr->nlmsg_len) == m->hdr->nlmsg_len);
-        assert(!data || data_length > 0);
-        assert(data || m->n_containers < RTNL_CONTAINER_DEPTH);
+        assert(!data || data_length);
+
+        /* get offset of the new attribute */
+        offset = m->hdr->nlmsg_len;
 
         /* get the size of the new rta attribute (with padding at the end) */
         rta_length = RTA_LENGTH(data_length);
 
         /* get the new message size (with padding at the end) */
-        message_length = m->hdr->nlmsg_len + RTA_ALIGN(rta_length);
+        message_length = offset + RTA_ALIGN(rta_length);
 
         /* realloc to fit the new attribute */
         new_hdr = realloc(m->hdr, message_length);
@@ -458,33 +497,35 @@ static int add_rtattr(sd_rtnl_message *m, unsigned short type, const void *data,
         m->hdr = new_hdr;
 
         /* get pointer to the attribute we are about to add */
-        rta = (struct rtattr *) ((uint8_t *) m->hdr + m->hdr->nlmsg_len);
+        rta = (struct rtattr *) ((uint8_t *) m->hdr + offset);
 
         /* if we are inside containers, extend them */
         for (i = 0; i < m->n_containers; i++)
-                GET_CONTAINER(m, i)->rta_len += message_length - m->hdr->nlmsg_len;
+                GET_CONTAINER(m, i)->rta_len += message_length - offset;
 
         /* fill in the attribute */
         rta->rta_type = type;
         rta->rta_len = rta_length;
-        if (!data) {
-                //TODO: simply return this value rather than check for !data
-                /* this is the start of a new container */
-                m->container_offsets[m->n_containers ++] = m->hdr->nlmsg_len;
-        } else {
+        if (data)
                 /* we don't deal with the case where the user lies about the type
                  * and gives us too little data (so don't do that)
-                */
+                 */
                 padding = mempcpy(RTA_DATA(rta), data, data_length);
-                /* make sure also the padding at the end of the message is initialized */
-                memzero(padding,
-                        (uint8_t *) m->hdr + message_length - (uint8_t *) padding);
+        else {
+                /* if no data was passed, make sure we still initialize the padding
+                   note that we can have data_length > 0 (used by some containers) */
+                padding = RTA_DATA(rta);
+                data_length = 0;
         }
 
+        /* make sure also the padding at the end of the message is initialized */
+        padding_length = (uint8_t*)m->hdr + message_length - (uint8_t*)padding;
+        memzero(padding, padding_length);
+
         /* update message size */
         m->hdr->nlmsg_len = message_length;
 
-        return 0;
+        return offset;
 }
 
 static int message_attribute_has_type(sd_rtnl_message *m, uint16_t attribute_type, uint16_t data_type) {
@@ -659,6 +700,7 @@ int sd_rtnl_message_open_container(sd_rtnl_message *m, unsigned short type) {
 
         assert_return(m, -EINVAL);
         assert_return(!m->sealed, -EPERM);
+        assert_return(m->n_containers < RTNL_CONTAINER_DEPTH, -ERANGE);
 
         r = message_attribute_has_type(m, type, NLA_NESTED);
         if (r < 0)
@@ -676,6 +718,8 @@ int sd_rtnl_message_open_container(sd_rtnl_message *m, unsigned short type) {
         if (r < 0)
                 return r;
 
+        m->container_offsets[m->n_containers ++] = r;
+
         return 0;
 }
 
@@ -705,6 +749,8 @@ int sd_rtnl_message_open_container_union(sd_rtnl_message *m, unsigned short type
         if (r < 0)
                 return r;
 
+        m->container_offsets[m->n_containers ++] = r;
+
         return 0;
 }
 
@@ -740,7 +786,7 @@ int rtnl_message_read_internal(sd_rtnl_message *m, unsigned short type, void **d
         return RTA_PAYLOAD(rta);
 }
 
-int sd_rtnl_message_read_string(sd_rtnl_message *m, unsigned short type, char **data) {
+int sd_rtnl_message_read_string(sd_rtnl_message *m, unsigned short type, const char **data) {
         int r;
         void *attr_data;
 
@@ -754,7 +800,7 @@ int sd_rtnl_message_read_string(sd_rtnl_message *m, unsigned short type, char **
         else if (strnlen(attr_data, r) >= (size_t) r)
                 return -EIO;
 
-        *data = (char *) attr_data;
+        *data = (const char *) attr_data;
 
         return 0;
 }
@@ -916,7 +962,7 @@ int sd_rtnl_message_enter_container(sd_rtnl_message *m, unsigned short type) {
                         return r;
         } else if (nl_type->type == NLA_UNION) {
                 const NLTypeSystemUnion *type_system_union;
-                char *key;
+                const char *key;
 
                 r = type_system_get_type_system_union(m->container_type_system[m->n_containers],
                                                       &type_system_union,
@@ -995,26 +1041,6 @@ int sd_rtnl_message_get_errno(sd_rtnl_message *m) {
         return err->error;
 }
 
-static int message_receive_need(sd_rtnl *rtnl, size_t *need) {
-        assert(rtnl);
-        assert(need);
-
-        /* ioctl(rtnl->fd, FIONREAD, &need)
-           Does not appear to work on netlink sockets. libnl uses
-           MSG_PEEK instead. I don't know if that is worth the
-           extra roundtrip.
-
-           For now we simply use the maximum message size the kernel
-           may use (NLMSG_GOODSIZE), and then realloc to the actual
-           size after reading the message (hence avoiding huge memory
-           usage in case many small messages are kept around) */
-        *need = page_size();
-        if (*need > 8192UL)
-                *need = 8192UL;
-
-        return 0;
-}
-
 int rtnl_message_parse(sd_rtnl_message *m,
                        size_t **rta_offset_tb,
                        unsigned short *rta_tb_size,
@@ -1072,49 +1098,30 @@ int socket_write_message(sd_rtnl *nl, sd_rtnl_message *m) {
         return k;
 }
 
-/* On success, the number of bytes received is returned and *ret points to the received message
- * which has a valid header and the correct size.
- * If nothing useful was received 0 is returned.
- * On failure, a negative error code is returned.
- */
-int socket_read_message(sd_rtnl *rtnl) {
-        _cleanup_free_ void *buffer = NULL;
-        uint8_t cred_buffer[CMSG_SPACE(sizeof(struct ucred))];
-        struct iovec iov = {};
+static int socket_recv_message(int fd, struct iovec *iov, uint32_t *_group, bool peek) {
+        uint8_t cred_buffer[CMSG_SPACE(sizeof(struct ucred)) +
+                            CMSG_SPACE(sizeof(struct nl_pktinfo))];
         struct msghdr msg = {
-                .msg_iov = &iov,
+                .msg_iov = iov,
                 .msg_iovlen = 1,
                 .msg_control = cred_buffer,
                 .msg_controllen = sizeof(cred_buffer),
         };
         struct cmsghdr *cmsg;
+        uint32_t group = 0;
         bool auth = false;
-        struct nlmsghdr *new_msg;
-        size_t need, len;
-        int r, ret = 0;
-
-        assert(rtnl);
-
-        r = message_receive_need(rtnl, &need);
-        if (r < 0)
-                return r;
-
-        buffer = malloc0(need);
-        if (!buffer)
-                return -ENOMEM;
+        int r;
 
-        iov.iov_base = buffer;
-        iov.iov_len = need;
+        assert(fd >= 0);
+        assert(iov);
 
-        r = recvmsg(rtnl->fd, &msg, 0);
+        r = recvmsg(fd, &msg, MSG_TRUNC | (peek ? MSG_PEEK : 0));
         if (r < 0)
                 /* no data */
                 return (errno == EAGAIN) ? 0 : -errno;
         else if (r == 0)
                 /* connection was closed by the kernel */
                 return -ECONNRESET;
-        else
-                len = (size_t)r;
 
         for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
                 if (cmsg->cmsg_level == SOL_SOCKET &&
@@ -1123,10 +1130,15 @@ int socket_read_message(sd_rtnl *rtnl) {
                         struct ucred *ucred = (void *)CMSG_DATA(cmsg);
 
                         /* from the kernel */
-                        if (ucred->uid == 0 && ucred->pid == 0) {
+                        if (ucred->uid == 0 && ucred->pid == 0)
                                 auth = true;
-                                break;
-                        }
+                } else if (cmsg->cmsg_level == SOL_NETLINK &&
+                           cmsg->cmsg_type == NETLINK_PKTINFO &&
+                           cmsg->cmsg_len == CMSG_LEN(sizeof(struct nl_pktinfo))) {
+                        struct nl_pktinfo *pktinfo = (void *)CMSG_DATA(cmsg);
+
+                        /* multi-cast group */
+                        group = pktinfo->group;
                 }
         }
 
@@ -1134,18 +1146,88 @@ int socket_read_message(sd_rtnl *rtnl) {
                 /* not from the kernel, ignore */
                 return 0;
 
-        for (new_msg = buffer; NLMSG_OK(new_msg, len); new_msg = NLMSG_NEXT(new_msg, len)) {
+        if (group)
+                *_group = group;
+
+        return r;
+}
+
+/* On success, the number of bytes received is returned and *ret points to the received message
+ * which has a valid header and the correct size.
+ * If nothing useful was received 0 is returned.
+ * On failure, a negative error code is returned.
+ */
+int socket_read_message(sd_rtnl *rtnl) {
+        _cleanup_rtnl_message_unref_ sd_rtnl_message *first = NULL;
+        struct iovec iov = {};
+        uint32_t group = 0;
+        bool multi_part = false, done = false;
+        struct nlmsghdr *new_msg;
+        size_t len;
+        int r;
+        unsigned i = 0;
+
+        assert(rtnl);
+        assert(rtnl->rbuffer);
+        assert(rtnl->rbuffer_allocated >= sizeof(struct nlmsghdr));
+
+        /* read nothing, just get the pending message size */
+        r = socket_recv_message(rtnl->fd, &iov, &group, true);
+        if (r <= 0)
+                return r;
+        else
+                len = (size_t)r;
+
+        /* make room for the pending message */
+        if (!greedy_realloc((void **)&rtnl->rbuffer,
+                            &rtnl->rbuffer_allocated,
+                            len, sizeof(uint8_t)))
+                return -ENOMEM;
+
+        iov.iov_base = rtnl->rbuffer;
+        iov.iov_len = rtnl->rbuffer_allocated;
+
+        /* read the pending message */
+        r = socket_recv_message(rtnl->fd, &iov, &group, false);
+        if (r <= 0)
+                return r;
+        else
+                len = (size_t)r;
+
+        if (len > rtnl->rbuffer_allocated)
+                /* message did not fit in read buffer */
+                return -EIO;
+
+        if (NLMSG_OK(rtnl->rbuffer, len) && rtnl->rbuffer->nlmsg_flags & NLM_F_MULTI) {
+                multi_part = true;
+
+                for (i = 0; i < rtnl->rqueue_partial_size; i++) {
+                        if (rtnl_message_get_serial(rtnl->rqueue_partial[i]) ==
+                            rtnl->rbuffer->nlmsg_seq) {
+                                first = rtnl->rqueue_partial[i];
+                                break;
+                        }
+                }
+        }
+
+        for (new_msg = rtnl->rbuffer; NLMSG_OK(new_msg, len); new_msg = NLMSG_NEXT(new_msg, len)) {
                 _cleanup_rtnl_message_unref_ sd_rtnl_message *m = NULL;
                 const NLType *nl_type;
 
-                if (new_msg->nlmsg_pid && new_msg->nlmsg_pid != rtnl->sockaddr.nl.nl_pid)
+                if (!group && new_msg->nlmsg_pid != rtnl->sockaddr.nl.nl_pid)
                         /* not broadcast and not for us */
                         continue;
 
-                /* silently drop noop messages */
                 if (new_msg->nlmsg_type == NLMSG_NOOP)
+                        /* silently drop noop messages */
                         continue;
 
+                if (new_msg->nlmsg_type == NLMSG_DONE) {
+                        /* finished reading multi-part message */
+                        done = true;
+                        break;
+                }
+
                 /* check that we support this message type */
                 r = type_system_get_type(NULL, &nl_type, new_msg->nlmsg_type);
                 if (r < 0) {
@@ -1173,22 +1255,52 @@ int socket_read_message(sd_rtnl *rtnl) {
                 if (r < 0)
                         return r;
 
+                /* push the message onto the multi-part message stack */
+                if (first)
+                        m->next = first;
+                first = m;
+                m = NULL;
+        }
+
+        if (len)
+                log_debug("sd-rtnl: discarding %zu bytes of incoming message", len);
+
+        if (!first)
+                return 0;
+
+        if (!multi_part || done) {
+                /* we got a complete message, push it on the read queue */
                 r = rtnl_rqueue_make_room(rtnl);
                 if (r < 0)
                         return r;
 
-                rtnl->rqueue[rtnl->rqueue_size ++] = m;
-                m = NULL;
-                ret += new_msg->nlmsg_len;
+                rtnl->rqueue[rtnl->rqueue_size ++] = first;
+                first = NULL;
 
-                /* reached end of multi-part message, or not a multi-part
-                   message at all */
-                if (new_msg->nlmsg_type == NLMSG_DONE ||
-                    !(new_msg->nlmsg_flags & NLM_F_MULTI))
-                        break;
-        }
+                if (multi_part && (i < rtnl->rqueue_partial_size)) {
+                        /* remove the message form the partial read queue */
+                        memmove(rtnl->rqueue_partial + i,rtnl->rqueue_partial + i + 1,
+                                sizeof(sd_rtnl_message*) * (rtnl->rqueue_partial_size - i - 1));
+                        rtnl->rqueue_partial_size --;
+                }
+
+                return 1;
+        } else {
+                /* we only got a partial multi-part message, push it on the
+                   partial read queue */
+                if (i < rtnl->rqueue_partial_size) {
+                        rtnl->rqueue_partial[i] = first;
+                } else {
+                        r = rtnl_rqueue_partial_make_room(rtnl);
+                        if (r < 0)
+                                return r;
+
+                        rtnl->rqueue_partial[rtnl->rqueue_partial_size ++] = first;
+                }
+                first = NULL;
 
-        return ret;
+                return 0;
+        }
 }
 
 int sd_rtnl_message_rewind(sd_rtnl_message *m) {
@@ -1249,3 +1361,9 @@ void rtnl_message_seal(sd_rtnl_message *m) {
 
         m->sealed = true;
 }
+
+sd_rtnl_message *sd_rtnl_message_next(sd_rtnl_message *m) {
+        assert_return(m, NULL);
+
+        return m->next;
+}