af_iucv: Support data in IUCV msg parameter lists (IPRMDATA)
[cascardo/linux.git] / net / iucv / af_iucv.c
index b7c40c9..5fc077e 100644 (file)
@@ -29,9 +29,6 @@
 #include <net/iucv/iucv.h>
 #include <net/iucv/af_iucv.h>
 
-#define CONFIG_IUCV_SOCK_DEBUG 1
-
-#define IPRMDATA 0x80
 #define VERSION "1.1"
 
 static char iucv_userid[80];
@@ -44,6 +41,10 @@ static struct proto iucv_proto = {
        .obj_size       = sizeof(struct iucv_sock),
 };
 
+/* special AF_IUCV IPRM messages */
+static const u8 iprm_shutdown[8] =
+       {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01};
+
 static void iucv_sock_kill(struct sock *sk);
 static void iucv_sock_close(struct sock *sk);
 
@@ -80,6 +81,37 @@ static inline void low_nmcpy(unsigned char *dst, char *src)
        memcpy(&dst[8], src, 8);
 }
 
+/**
+ * iucv_msg_length() - Returns the length of an iucv message.
+ * @msg:       Pointer to struct iucv_message, MUST NOT be NULL
+ *
+ * The function returns the length of the specified iucv message @msg of data
+ * stored in a buffer and of data stored in the parameter list (PRMDATA).
+ *
+ * For IUCV_IPRMDATA, AF_IUCV uses the following convention to transport socket
+ * data:
+ *     PRMDATA[0..6]   socket data (max 7 bytes);
+ *     PRMDATA[7]      socket data length value (len is 0xff - PRMDATA[7])
+ *
+ * The socket data length is computed by substracting the socket data length
+ * value from 0xFF.
+ * If the socket data len is greater 7, then PRMDATA can be used for special
+ * notifications (see iucv_sock_shutdown); and further,
+ * if the socket data len is > 7, the function returns 8.
+ *
+ * Use this function to allocate socket buffers to store iucv message data.
+ */
+static inline size_t iucv_msg_length(struct iucv_message *msg)
+{
+       size_t datalen;
+
+       if (msg->flags & IUCV_IPRMDATA) {
+               datalen = 0xff - msg->rmmsg[7];
+               return (datalen < 8) ? datalen : 8;
+       }
+       return msg->length;
+}
+
 /* Timers */
 static void iucv_sock_timeout(unsigned long arg)
 {
@@ -487,7 +519,7 @@ static int iucv_sock_connect(struct socket *sock, struct sockaddr *addr,
        iucv = iucv_sk(sk);
        /* Create path. */
        iucv->path = iucv_path_alloc(IUCV_QUEUELEN_DEFAULT,
-                                    IPRMDATA, GFP_KERNEL);
+                                    IUCV_IPRMDATA, GFP_KERNEL);
        if (!iucv->path) {
                err = -ENOMEM;
                goto done;
@@ -521,8 +553,7 @@ static int iucv_sock_connect(struct socket *sock, struct sockaddr *addr,
        }
 
        if (sk->sk_state == IUCV_DISCONN) {
-               release_sock(sk);
-               return -ECONNREFUSED;
+               err = -ECONNREFUSED;
        }
 
        if (err) {
@@ -636,6 +667,30 @@ static int iucv_sock_getname(struct socket *sock, struct sockaddr *addr,
        return 0;
 }
 
+/**
+ * iucv_send_iprm() - Send socket data in parameter list of an iucv message.
+ * @path:      IUCV path
+ * @msg:       Pointer to a struct iucv_message
+ * @skb:       The socket data to send, skb->len MUST BE <= 7
+ *
+ * Send the socket data in the parameter list in the iucv message
+ * (IUCV_IPRMDATA). The socket data is stored at index 0 to 6 in the parameter
+ * list and the socket data len at index 7 (last byte).
+ * See also iucv_msg_length().
+ *
+ * Returns the error code from the iucv_message_send() call.
+ */
+static int iucv_send_iprm(struct iucv_path *path, struct iucv_message *msg,
+                         struct sk_buff *skb)
+{
+       u8 prmdata[8];
+
+       memcpy(prmdata, (void *) skb->data, skb->len);
+       prmdata[7] = 0xff - (u8) skb->len;
+       return iucv_message_send(path, msg, IUCV_IPRMDATA, 0,
+                                (void *) prmdata, 8);
+}
+
 static int iucv_sock_sendmsg(struct kiocb *iocb, struct socket *sock,
                             struct msghdr *msg, size_t len)
 {
@@ -677,8 +732,29 @@ static int iucv_sock_sendmsg(struct kiocb *iocb, struct socket *sock,
                txmsg.tag = iucv->send_tag++;
                memcpy(skb->cb, &txmsg.tag, 4);
                skb_queue_tail(&iucv->send_skb_q, skb);
-               err = iucv_message_send(iucv->path, &txmsg, 0, 0,
-                                       (void *) skb->data, skb->len);
+
+               if (((iucv->path->flags & IUCV_IPRMDATA) & iucv->flags)
+                   && skb->len <= 7) {
+                       err = iucv_send_iprm(iucv->path, &txmsg, skb);
+
+                       /* on success: there is no message_complete callback
+                        * for an IPRMDATA msg; remove skb from send queue */
+                       if (err == 0) {
+                               skb_unlink(skb, &iucv->send_skb_q);
+                               kfree_skb(skb);
+                       }
+
+                       /* this error should never happen since the
+                        * IUCV_IPRMDATA path flag is set... sever path */
+                       if (err == 0x15) {
+                               iucv_path_sever(iucv->path, NULL);
+                               skb_unlink(skb, &iucv->send_skb_q);
+                               err = -EPIPE;
+                               goto fail;
+                       }
+               } else
+                       err = iucv_message_send(iucv->path, &txmsg, 0, 0,
+                                               (void *) skb->data, skb->len);
                if (err) {
                        if (err == 3) {
                                user_id[8] = 0;
@@ -744,19 +820,25 @@ static void iucv_process_message(struct sock *sk, struct sk_buff *skb,
                                 struct iucv_message *msg)
 {
        int rc;
+       unsigned int len;
+
+       len = iucv_msg_length(msg);
 
-       if (msg->flags & IPRMDATA) {
-               skb->data = NULL;
-               skb->len = 0;
+       /* check for special IPRM messages (e.g. iucv_sock_shutdown) */
+       if ((msg->flags & IUCV_IPRMDATA) && len > 7) {
+               if (memcmp(msg->rmmsg, iprm_shutdown, 8) == 0) {
+                       skb->data = NULL;
+                       skb->len = 0;
+               }
        } else {
-               rc = iucv_message_receive(path, msg, 0, skb->data,
-                                         msg->length, NULL);
+               rc = iucv_message_receive(path, msg, msg->flags & IUCV_IPRMDATA,
+                                         skb->data, len, NULL);
                if (rc) {
                        kfree_skb(skb);
                        return;
                }
                if (skb->truesize >= sk->sk_rcvbuf / 4) {
-                       rc = iucv_fragment_skb(sk, skb, msg->length);
+                       rc = iucv_fragment_skb(sk, skb, len);
                        kfree_skb(skb);
                        skb = NULL;
                        if (rc) {
@@ -767,7 +849,7 @@ static void iucv_process_message(struct sock *sk, struct sk_buff *skb,
                } else {
                        skb_reset_transport_header(skb);
                        skb_reset_network_header(skb);
-                       skb->len = msg->length;
+                       skb->len = len;
                }
        }
 
@@ -782,7 +864,7 @@ static void iucv_process_message_q(struct sock *sk)
        struct sock_msg_q *p, *n;
 
        list_for_each_entry_safe(p, n, &iucv->message_q.list, list) {
-               skb = alloc_skb(p->msg.length, GFP_ATOMIC | GFP_DMA);
+               skb = alloc_skb(iucv_msg_length(&p->msg), GFP_ATOMIC | GFP_DMA);
                if (!skb)
                        break;
                iucv_process_message(sk, skb, p->path, &p->msg);
@@ -928,7 +1010,6 @@ static int iucv_sock_shutdown(struct socket *sock, int how)
        struct iucv_sock *iucv = iucv_sk(sk);
        struct iucv_message txmsg;
        int err = 0;
-       u8 prmmsg[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01};
 
        how++;
 
@@ -950,7 +1031,7 @@ static int iucv_sock_shutdown(struct socket *sock, int how)
                txmsg.class = 0;
                txmsg.tag = 0;
                err = iucv_message_send(iucv->path, &txmsg, IUCV_IPRMDATA, 0,
-                                       (void *) prmmsg, 8);
+                                       (void *) iprm_shutdown, 8);
                if (err) {
                        switch (err) {
                        case 1:
@@ -1196,11 +1277,11 @@ static void iucv_callback_rx(struct iucv_path *path, struct iucv_message *msg)
                goto save_message;
 
        len = atomic_read(&sk->sk_rmem_alloc);
-       len += msg->length + sizeof(struct sk_buff);
+       len += iucv_msg_length(msg) + sizeof(struct sk_buff);
        if (len > sk->sk_rcvbuf)
                goto save_message;
 
-       skb = alloc_skb(msg->length, GFP_ATOMIC | GFP_DMA);
+       skb = alloc_skb(iucv_msg_length(msg), GFP_ATOMIC | GFP_DMA);
        if (!skb)
                goto save_message;