ALSA: seq: Fix racy pool initializations
[pandora-kernel.git] / sound / core / seq / seq_clientmgr.c
index f2436d3..8cc1831 100644 (file)
@@ -22,6 +22,7 @@
  */
 
 #include <linux/init.h>
+#include <linux/export.h>
 #include <linux/slab.h>
 #include <sound/core.h>
 #include <sound/minors.h>
@@ -235,6 +236,7 @@ static struct snd_seq_client *seq_create_client1(int client_index, int poolsize)
        rwlock_init(&client->ports_lock);
        mutex_init(&client->ports_mutex);
        INIT_LIST_HEAD(&client->ports_list_head);
+       mutex_init(&client->ioctl_mutex);
 
        /* find free slot in the client table */
        spin_lock_irqsave(&clients_lock, flags);
@@ -675,8 +677,11 @@ static int deliver_to_subscribers(struct snd_seq_client *client,
        if (atomic)
                read_lock(&grp->list_lock);
        else
-               down_read(&grp->list_mutex);
+               down_read_nested(&grp->list_mutex, hop);
        list_for_each_entry(subs, &grp->list_head, src_list) {
+               /* both ports ready? */
+               if (atomic_read(&subs->ref_count) != 2)
+                       continue;
                event->dest = subs->info.dest;
                if (subs->info.flags & SNDRV_SEQ_PORT_SUBS_TIMESTAMP)
                        /* convert time according to flag with subscription */
@@ -995,7 +1000,7 @@ static ssize_t snd_seq_write(struct file *file, const char __user *buf,
 {
        struct snd_seq_client *client = file->private_data;
        int written = 0, len;
-       int err = -EINVAL;
+       int err;
        struct snd_seq_event event;
 
        if (!(snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_OUTPUT))
@@ -1010,11 +1015,15 @@ static ssize_t snd_seq_write(struct file *file, const char __user *buf,
 
        /* allocate the pool now if the pool is not allocated yet */ 
        if (client->pool->size > 0 && !snd_seq_write_pool_allocated(client)) {
-               if (snd_seq_pool_init(client->pool) < 0)
+               mutex_lock(&client->ioctl_mutex);
+               err = snd_seq_pool_init(client->pool);
+               mutex_unlock(&client->ioctl_mutex);
+               if (err < 0)
                        return -ENOMEM;
        }
 
        /* only process whole events */
+       err = -EINVAL;
        while (count >= sizeof(struct snd_seq_event)) {
                /* Read in the event header from the user */
                len = sizeof(event);
@@ -1244,6 +1253,7 @@ static int snd_seq_ioctl_create_port(struct snd_seq_client *client,
        struct snd_seq_client_port *port;
        struct snd_seq_port_info info;
        struct snd_seq_port_callback *callback;
+       int port_idx;
 
        if (copy_from_user(&info, arg, sizeof(info)))
                return -EFAULT;
@@ -1257,7 +1267,9 @@ static int snd_seq_ioctl_create_port(struct snd_seq_client *client,
                return -ENOMEM;
 
        if (client->type == USER_CLIENT && info.kernel) {
-               snd_seq_delete_port(client, port->addr.port);
+               port_idx = port->addr.port;
+               snd_seq_port_unlock(port);
+               snd_seq_delete_port(client, port_idx);
                return -EINVAL;
        }
        if (client->type == KERNEL_CLIENT) {
@@ -1279,6 +1291,7 @@ static int snd_seq_ioctl_create_port(struct snd_seq_client *client,
 
        snd_seq_set_port_info(port, &info);
        snd_seq_system_client_ev_port_start(port->addr.client, port->addr.port);
+       snd_seq_port_unlock(port);
 
        if (copy_to_user(arg, &info, sizeof(info)))
                return -EFAULT;
@@ -1905,6 +1918,7 @@ static int snd_seq_ioctl_set_client_pool(struct snd_seq_client *client,
             info.output_pool != client->pool->size)) {
                if (snd_seq_write_pool_allocated(client)) {
                        /* remove all existing cells */
+                       snd_seq_pool_mark_closing(client->pool);
                        snd_seq_queue_client_leave_cells(client->number);
                        snd_seq_pool_done(client->pool);
                }
@@ -1949,7 +1963,7 @@ static int snd_seq_ioctl_remove_events(struct snd_seq_client *client,
                 * No restrictions so for a user client we can clear
                 * the whole fifo
                 */
-               if (client->type == USER_CLIENT)
+               if (client->type == USER_CLIENT && client->data.user.fifo)
                        snd_seq_fifo_clear(client->data.user.fifo);
        }
 
@@ -2204,11 +2218,15 @@ static int snd_seq_do_ioctl(struct snd_seq_client *client, unsigned int cmd,
 static long snd_seq_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 {
        struct snd_seq_client *client = file->private_data;
+       long ret;
 
        if (snd_BUG_ON(!client))
                return -ENXIO;
                
-       return snd_seq_do_ioctl(client, cmd, (void __user *) arg);
+       mutex_lock(&client->ioctl_mutex);
+       ret = snd_seq_do_ioctl(client, cmd, (void __user *) arg);
+       mutex_unlock(&client->ioctl_mutex);
+       return ret;
 }
 
 #ifdef CONFIG_COMPAT