af_iucv: Support data in IUCV msg parameter lists (IPRMDATA)
[cascardo/linux.git] / net / iucv / af_iucv.c
index eb8a2a0..5fc077e 100644 (file)
 #include <net/iucv/iucv.h>
 #include <net/iucv/af_iucv.h>
 
-#define CONFIG_IUCV_SOCK_DEBUG 1
-
-#define IPRMDATA 0x80
-#define VERSION "1.0"
+#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);
 
@@ -54,6 +55,7 @@ static void iucv_callback_connack(struct iucv_path *, u8 ipuser[16]);
 static int iucv_callback_connreq(struct iucv_path *, u8 ipvmid[8],
                                 u8 ipuser[16]);
 static void iucv_callback_connrej(struct iucv_path *, u8 ipuser[16]);
+static void iucv_callback_shutdown(struct iucv_path *, u8 ipuser[16]);
 
 static struct iucv_sock_list iucv_sk_list = {
        .lock = __RW_LOCK_UNLOCKED(iucv_sk_list.lock),
@@ -65,7 +67,8 @@ static struct iucv_handler af_iucv_handler = {
        .path_complete    = iucv_callback_connack,
        .path_severed     = iucv_callback_connrej,
        .message_pending  = iucv_callback_rx,
-       .message_complete = iucv_callback_txdone
+       .message_complete = iucv_callback_txdone,
+       .path_quiesced    = iucv_callback_shutdown,
 };
 
 static inline void high_nmcpy(unsigned char *dst, char *src)
@@ -78,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)
 {
@@ -224,6 +258,7 @@ static struct sock *iucv_sock_alloc(struct socket *sock, int proto, gfp_t prio)
        spin_lock_init(&iucv_sk(sk)->message_q.lock);
        skb_queue_head_init(&iucv_sk(sk)->backlog_skb_q);
        iucv_sk(sk)->send_tag = 0;
+       iucv_sk(sk)->flags = 0;
 
        sk->sk_destruct = iucv_sock_destruct;
        sk->sk_sndtimeo = IUCV_CONN_TIMEOUT;
@@ -484,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;
@@ -518,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) {
@@ -633,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)
 {
@@ -674,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;
@@ -741,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) {
@@ -764,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;
                }
        }
 
@@ -779,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);
@@ -925,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++;
 
@@ -947,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:
@@ -1001,6 +1085,78 @@ static int iucv_sock_release(struct socket *sock)
        return err;
 }
 
+/* getsockopt and setsockopt */
+static int iucv_sock_setsockopt(struct socket *sock, int level, int optname,
+                               char __user *optval, int optlen)
+{
+       struct sock *sk = sock->sk;
+       struct iucv_sock *iucv = iucv_sk(sk);
+       int val;
+       int rc;
+
+       if (level != SOL_IUCV)
+               return -ENOPROTOOPT;
+
+       if (optlen < sizeof(int))
+               return -EINVAL;
+
+       if (get_user(val, (int __user *) optval))
+               return -EFAULT;
+
+       rc = 0;
+
+       lock_sock(sk);
+       switch (optname) {
+       case SO_IPRMDATA_MSG:
+               if (val)
+                       iucv->flags |= IUCV_IPRMDATA;
+               else
+                       iucv->flags &= ~IUCV_IPRMDATA;
+               break;
+       default:
+               rc = -ENOPROTOOPT;
+               break;
+       }
+       release_sock(sk);
+
+       return rc;
+}
+
+static int iucv_sock_getsockopt(struct socket *sock, int level, int optname,
+                               char __user *optval, int __user *optlen)
+{
+       struct sock *sk = sock->sk;
+       struct iucv_sock *iucv = iucv_sk(sk);
+       int val, len;
+
+       if (level != SOL_IUCV)
+               return -ENOPROTOOPT;
+
+       if (get_user(len, optlen))
+               return -EFAULT;
+
+       if (len < 0)
+               return -EINVAL;
+
+       len = min_t(unsigned int, len, sizeof(int));
+
+       switch (optname) {
+       case SO_IPRMDATA_MSG:
+               val = (iucv->flags & IUCV_IPRMDATA) ? 1 : 0;
+               break;
+       default:
+               return -ENOPROTOOPT;
+       }
+
+       if (put_user(len, optlen))
+               return -EFAULT;
+       if (copy_to_user(optval, &val, len))
+               return -EFAULT;
+
+       return 0;
+}
+
+
 /* Callback wrappers - called from iucv base support */
 static int iucv_callback_connreq(struct iucv_path *path,
                                 u8 ipvmid[8], u8 ipuser[16])
@@ -1121,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;
 
@@ -1171,8 +1327,7 @@ static void iucv_callback_txdone(struct iucv_path *path,
 
                spin_unlock_irqrestore(&list->lock, flags);
 
-               if (this)
-                       kfree_skb(this);
+               kfree_skb(this);
        }
        BUG_ON(!this);
 
@@ -1197,6 +1352,21 @@ static void iucv_callback_connrej(struct iucv_path *path, u8 ipuser[16])
        sk->sk_state_change(sk);
 }
 
+/* called if the other communication side shuts down its RECV direction;
+ * in turn, the callback sets SEND_SHUTDOWN to disable sending of data.
+ */
+static void iucv_callback_shutdown(struct iucv_path *path, u8 ipuser[16])
+{
+       struct sock *sk = path->private;
+
+       bh_lock_sock(sk);
+       if (sk->sk_state != IUCV_CLOSED) {
+               sk->sk_shutdown |= SEND_SHUTDOWN;
+               sk->sk_state_change(sk);
+       }
+       bh_unlock_sock(sk);
+}
+
 static struct proto_ops iucv_sock_ops = {
        .family         = PF_IUCV,
        .owner          = THIS_MODULE,
@@ -1213,8 +1383,8 @@ static struct proto_ops iucv_sock_ops = {
        .mmap           = sock_no_mmap,
        .socketpair     = sock_no_socketpair,
        .shutdown       = iucv_sock_shutdown,
-       .setsockopt     = sock_no_setsockopt,
-       .getsockopt     = sock_no_getsockopt
+       .setsockopt     = iucv_sock_setsockopt,
+       .getsockopt     = iucv_sock_getsockopt,
 };
 
 static struct net_proto_family iucv_sock_family_ops = {