/*
  * UML SIGWINCH handling
  *
- * The point of this is to handle SIGWINCH on consoles which have host ttys and
- * relay them inside UML to whatever might be running on the console and cares
- * about the window size (since SIGWINCH notifies about terminal size changes).
+ * The point of this is to handle SIGWINCH on consoles which have host
+ * ttys and relay them inside UML to whatever might be running on the
+ * console and cares about the window size (since SIGWINCH notifies
+ * about terminal size changes).
  *
- * So, we have a separate thread for each host tty attached to a UML device
- * (side-issue - I'm annoyed that one thread can't have multiple controlling
- * ttys for purposed of handling SIGWINCH, but I imagine there are other reasons
- * that doesn't make any sense).
+ * So, we have a separate thread for each host tty attached to a UML
+ * device (side-issue - I'm annoyed that one thread can't have
+ * multiple controlling ttys for the purpose of handling SIGWINCH, but
+ * I imagine there are other reasons that doesn't make any sense).
  *
- * SIGWINCH can't be received synchronously, so you have to set up to receive it
- * as a signal.  That being the case, if you are going to wait for it, it is
- * convenient to sit in sigsuspend() and wait for the signal to bounce you out of
- * it (see below for how we make sure to exit only on SIGWINCH).
+ * SIGWINCH can't be received synchronously, so you have to set up to
+ * receive it as a signal.  That being the case, if you are going to
+ * wait for it, it is convenient to sit in sigsuspend() and wait for
+ * the signal to bounce you out of it (see below for how we make sure
+ * to exit only on SIGWINCH).
  */
 
 static void winch_handler(int sig)
 
        err = os_new_tty_pgrp(pty_fd, os_getpid());
        if(err < 0){
-               printk("winch_thread : new_tty_pgrp failed, err = %d\n", -err);
+               printk("winch_thread : new_tty_pgrp failed on fd %d, "
+                      "err = %d\n", pty_fd, -err);
                exit(1);
        }
 
                       "err = %d\n", -count);
 
        while(1){
-               /* This will be interrupted by SIGWINCH only, since other signals
-                * are blocked.*/
+               /* This will be interrupted by SIGWINCH only, since
+                * other signals are blocked.
+                */
                sigsuspend(&sigs);
 
                count = os_write_file(pipe_fd, &c, sizeof(c));
        }
 }
 
-static int winch_tramp(int fd, struct tty_struct *tty, int *fd_out)
+static int winch_tramp(int fd, struct tty_struct *tty, int *fd_out,
+                      unsigned long *stack_out)
 {
        struct winch_data data;
-       unsigned long stack;
        int fds[2], n, err;
        char c;
 
        data = ((struct winch_data) { .pty_fd           = fd,
                                      .pipe_fd          = fds[1] } );
        /* CLONE_FILES so this thread doesn't hold open files which are open
-        * now, but later closed.  This is a problem with /dev/net/tun.
+        * now, but later closed in a different thread.  This is a
+        * problem with /dev/net/tun, which if held open by this
+        * thread, prevents the TUN/TAP device from being reused.
         */
-       err = run_helper_thread(winch_thread, &data, CLONE_FILES, &stack, 0);
+       err = run_helper_thread(winch_thread, &data, CLONE_FILES, stack_out, 0);
        if(err < 0){
                printk("fork of winch_thread failed - errno = %d\n", -err);
                goto out_close;
 
 void register_winch(int fd, struct tty_struct *tty)
 {
-       int pid, thread, thread_fd = -1;
-       int count;
+       unsigned long stack;
+       int pid, thread, count, thread_fd = -1;
        char c = 1;
 
        if(!isatty(fd))
                return;
 
        pid = tcgetpgrp(fd);
-       if(!CHOOSE_MODE_PROC(is_tracer_winch, is_skas_winch, pid, fd,
-                            tty) && (pid == -1)){
-               thread = winch_tramp(fd, tty, &thread_fd);
-               if(thread > 0){
-                       register_winch_irq(thread_fd, fd, thread, tty);
-
-                       count = os_write_file(thread_fd, &c, sizeof(c));
-                       if(count != sizeof(c))
-                               printk("register_winch : failed to write "
-                                      "synchronization byte, err = %d\n",
-                                       -count);
-               }
+       if (!CHOOSE_MODE_PROC(is_tracer_winch, is_skas_winch, pid, fd, tty) &&
+           (pid == -1)) {
+               thread = winch_tramp(fd, tty, &thread_fd, &stack);
+               if (thread < 0)
+                       return;
+
+               register_winch_irq(thread_fd, fd, thread, tty, stack);
+
+               count = os_write_file(thread_fd, &c, sizeof(c));
+               if(count != sizeof(c))
+                       printk("register_winch : failed to write "
+                              "synchronization byte, err = %d\n", -count);
        }
 }
 
        int tty_fd;
        int pid;
        struct tty_struct *tty;
+       unsigned long stack;
 };
 
+static void free_winch(struct winch *winch, int free_irq_ok)
+{
+       list_del(&winch->list);
+
+       if (winch->pid != -1)
+               os_kill_process(winch->pid, 1);
+       if (winch->fd != -1)
+               os_close_file(winch->fd);
+       if (winch->stack != 0)
+               free_stack(winch->stack, 0);
+       if (free_irq_ok)
+               free_irq(WINCH_IRQ, winch);
+       kfree(winch);
+}
+
 static irqreturn_t winch_interrupt(int irq, void *data)
 {
        struct winch *winch = data;
                                       "errno = %d\n", -err);
                                printk("fd %d is losing SIGWINCH support\n",
                                       winch->tty_fd);
+                               free_winch(winch, 0);
                                return IRQ_HANDLED;
                        }
                        goto out;
                }
        }
-       tty  = winch->tty;
+       tty = winch->tty;
        if (tty != NULL) {
                line = tty->driver_data;
                chan_window_size(&line->chan_list, &tty->winsize.ws_row,
        return IRQ_HANDLED;
 }
 
-void register_winch_irq(int fd, int tty_fd, int pid, struct tty_struct *tty)
+void register_winch_irq(int fd, int tty_fd, int pid, struct tty_struct *tty,
+                       unsigned long stack)
 {
        struct winch *winch;
 
        winch = kmalloc(sizeof(*winch), GFP_KERNEL);
        if (winch == NULL) {
                printk("register_winch_irq - kmalloc failed\n");
-               return;
+               goto cleanup;
        }
 
        *winch = ((struct winch) { .list        = LIST_HEAD_INIT(winch->list),
                                   .fd          = fd,
                                   .tty_fd      = tty_fd,
                                   .pid         = pid,
-                                  .tty         = tty });
+                                  .tty         = tty,
+                                  .stack       = stack });
+
+       if (um_request_irq(WINCH_IRQ, fd, IRQ_READ, winch_interrupt,
+                          IRQF_DISABLED | IRQF_SHARED | IRQF_SAMPLE_RANDOM,
+                          "winch", winch) < 0) {
+               printk("register_winch_irq - failed to register IRQ\n");
+               goto out_free;
+       }
 
        spin_lock(&winch_handler_lock);
        list_add(&winch->list, &winch_handlers);
        spin_unlock(&winch_handler_lock);
 
-       if(um_request_irq(WINCH_IRQ, fd, IRQ_READ, winch_interrupt,
-                         IRQF_DISABLED | IRQF_SHARED | IRQF_SAMPLE_RANDOM,
-                         "winch", winch) < 0)
-               printk("register_winch_irq - failed to register IRQ\n");
-}
-
-static void free_winch(struct winch *winch)
-{
-       list_del(&winch->list);
-
-       if(winch->pid != -1)
-               os_kill_process(winch->pid, 1);
-       if(winch->fd != -1)
-               os_close_file(winch->fd);
+       return;
 
-       free_irq(WINCH_IRQ, winch);
+ out_free:
        kfree(winch);
+ cleanup:
+       os_kill_process(pid, 1);
+       os_close_file(fd);
+       if (stack != 0)
+               free_stack(stack, 0);
 }
 
 static void unregister_winch(struct tty_struct *tty)
        list_for_each(ele, &winch_handlers){
                winch = list_entry(ele, struct winch, list);
                 if(winch->tty == tty){
-                       free_winch(winch);
+                       free_winch(winch, 1);
                        break;
                 }
         }
 
        list_for_each_safe(ele, next, &winch_handlers){
                winch = list_entry(ele, struct winch, list);
-               free_winch(winch);
+               free_winch(winch, 1);
        }
 
        spin_unlock(&winch_handler_lock);