/* PSPP - a program for statistical analysis.
-   Copyright (C) 1997-9, 2000, 2007 Free Software Foundation, Inc.
+   Copyright (C) 1997-9, 2000, 2007, 2009 Free Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
                            const struct string *val);
 
 static bool
-ascii_open_driver (struct outp_driver *this, struct substring options)
+ascii_open_driver (const char *name, int types, struct substring options)
 {
+  struct outp_driver *this;
   struct ascii_driver_ext *x;
   int i;
 
+  this = outp_allocate_driver (&ascii_class, name, types);
   this->width = 79;
   this->font_height = 1;
   this->prop_em_width = 1;
         x->box[i] = pool_strdup (x->pool, s);
       }
 
+  outp_register_driver (this);
+
   return true;
 
  error:
   pool_destroy (x->pool);
+  outp_free_driver (this);
   return false;
 }
 
 
 /* Driver initialization. */
 
 static bool
-xr_open_driver (struct outp_driver *this, struct substring options)
+xr_open_driver (const char *name, int types, struct substring options)
 {
   cairo_surface_t *surface;
   cairo_status_t status;
+  struct outp_driver *this;
   struct xr_driver_ext *x;
   double width_pt, length_pt;
   size_t i;
 
+  this = outp_allocate_driver (&cairo_class, name, types);
   this->width = this->length = 0;
   this->font_height = XR_POINT * 10;
 
   memcpy (this->vert_line_width, this->horiz_line_width,
           sizeof this->vert_line_width);
 
+  outp_register_driver (this);
   return true;
 
  error:
   this->class->close_driver (this);
+  outp_free_driver (this);
   return false;
 }
 
 
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Copyright (C) 1997-9, 2000, 2009 Free Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
                              const char *content);
 
 static bool
-html_open_driver (struct outp_driver *this, struct substring options)
+html_open_driver (const char *name, int types, struct substring options)
 {
+  struct outp_driver *this;
   struct html_driver_ext *x;
 
+  this = outp_allocate_driver (&html_class, name, types);
   this->ext = x = xmalloc (sizeof *x);
   x->file_name = xstrdup ("pspp.html");
   x->chart_file_name = xstrdup ("pspp-#.png");
   print_title_tag (x->file, "H1", outp_title);
   print_title_tag (x->file, "H2", outp_subtitle);
 
+  outp_register_driver (this);
   return true;
 
  error:
   this->class->close_driver (this);
+  outp_free_driver (this);
   return false;
 }
 
 
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
 
-/* FIXME? Should the output configuration format be changed to
-   drivername:classname:devicetype:options, where devicetype is zero
-   or more of screen, printer, listing? */
-
-/* FIXME: Have the reentrancy problems been solved? */
-
 /* Where the output driver name came from. */
 enum
   {
   };
 
 static struct outp_driver_class_list *outp_class_list;
-static struct outp_driver *outp_driver_list;
+static struct ll_list outp_driver_list = LL_INITIALIZER (outp_driver_list);
 
 char *outp_title;
 char *outp_subtitle;
 void
 outp_init (void)
 {
-  extern struct outp_class ascii_class;
-  extern struct outp_class postscript_class;
-#ifdef HAVE_CAIRO
-  extern struct outp_class cairo_class;
-#endif
-
   char def[] = "default";
 
   add_class (&html_class);
 
   if (result)
     {
-      if (outp_driver_list == NULL)
+      if (ll_is_empty (&outp_driver_list))
         error (0, 0, _("no active output drivers"));
     }
   else
     error (0, 0, _("error reading device definition file"));
 
-  if (!result || outp_driver_list == NULL)
+  if (!result || ll_is_empty (&outp_driver_list))
     init_default_drivers ();
 }
 
   outp_macros = d;
 }
 
-/* Destroys all the drivers in driver list *DL and sets *DL to
-   NULL. */
-static void
-destroy_list (struct outp_driver ** dl)
-{
-  struct outp_driver *d, *next;
-
-  for (d = *dl; d; d = next)
-    {
-      destroy_driver (d);
-      next = d->next;
-      free (d);
-    }
-  *dl = NULL;
-}
-
 /* Closes all the output drivers. */
 void
 outp_done (void)
 {
   struct outp_driver_class_list *n = outp_class_list ;
   outp_configure_clear ();
-  destroy_list (&outp_driver_list);
+  while (!ll_is_empty (&outp_driver_list))
+    {
+      struct outp_driver *d = ll_data (ll_head (&outp_driver_list),
+                                       struct outp_driver, node);
+      destroy_driver (d);
+    }
 
   while (n)
     {
 find_driver (char *name)
 {
   struct outp_driver *d;
-
-  for (d = outp_driver_list; d; d = d->next)
+  ll_for_each (d, struct outp_driver, node, &outp_driver_list)
     if (!strcmp (d->name, name))
       return d;
   return NULL;
 configure_driver (struct substring driver_name, struct substring class_name,
                   struct substring device_type, struct substring options)
 {
-  struct outp_driver *d, *iter;
   struct outp_driver_class_list *c;
-
   struct substring token;
   size_t save_idx = 0;
+  char *name;
   int device;
 
   /* Find class. */
       error (0, 0, _("unknown device type `%.*s'"),
              (int) ss_length (token), ss_data (token));
 
-  /* Open the device. */
-  d = xmalloc (sizeof *d);
-  d->next = d->prev = NULL;
-  d->class = c->class;
-  d->name = ss_xstrdup (driver_name);
+  /* Open driver. */
+  name = ss_xstrdup (driver_name);
+  if (!c->class->open_driver (name, device, options))
+    error (0, 0, _("cannot initialize output driver `%s' of class `%s'"),
+           name, c->class->name);
+  free (name);
+}
+
+/* Allocates and returns a new outp_driver for a device with the
+   given NAME and CLASS and the OUTP_DEV_* type(s) in TYPES
+
+   This function is intended to be used by output drivers, not
+   by their clients. */
+struct outp_driver *
+outp_allocate_driver (const struct outp_class *class,
+                      const char *name, int types)
+{
+  struct outp_driver *d = xmalloc (sizeof *d);
+  d->class = class;
+  d->name = xstrdup (name);
   d->page_open = false;
-  d->device = device;
+  d->device = types;
   d->cp_x = d->cp_y = 0;
   d->ext = NULL;
   d->prc = NULL;
+  return d;
+}
 
-  /* Open driver. */
-  if (!d->class->open_driver (d, options))
-    {
-      error (0, 0, _("cannot initialize output driver `%s' of class `%s'"),
-             d->name, d->class->name);
-      free (d->name);
-      free (d);
-      return;
-    }
+/* Frees driver D and the data that it owns directly.  The
+   driver's class must already have unregistered D (if it was
+   registered) and freed data private to its class.
+
+   This function is intended to be used by output drivers, not
+   by their clients. */
+void
+outp_free_driver (struct outp_driver *d)
+{
+  free (d->name);
+  free (d);
+}
+
+/* Adds D to the list of drivers that will be used for output. */
+void
+outp_register_driver (struct outp_driver *d)
+{
+  struct outp_driver *victim;
 
   /* Find like-named driver and delete. */
-  iter = find_driver (d->name);
-  if (iter != NULL)
-    destroy_driver (iter);
+  victim = find_driver (d->name);
+  if (victim != NULL)
+    destroy_driver (victim);
 
-  /* Add to list. */
-  d->next = outp_driver_list;
-  d->prev = NULL;
-  if (outp_driver_list != NULL)
-    outp_driver_list->prev = d;
-  outp_driver_list = d;
+  /* Add D to list. */
+  ll_push_tail (&outp_driver_list, &d->node);
+}
+
+/* Remove driver D from the list of drivers that will be used for
+   output. */
+void
+outp_unregister_driver (struct outp_driver *d)
+{
+  ll_remove (&d->node);
 }
 
 /* String LINE is in format:
 destroy_driver (struct outp_driver *d)
 {
   outp_close_page (d);
-  if (d->class)
-    {
-      struct outp_driver_class_list *c;
-
-      d->class->close_driver (d);
-
-      for (c = outp_class_list; c; c = c->next)
-       if (c->class == d->class)
-         break;
-      assert (c != NULL);
-    }
-  free (d->name);
-
-  /* Remove this driver from the global driver list. */
-  if (d->prev)
-    d->prev->next = d->next;
-  if (d->next)
-    d->next->prev = d->prev;
-  if (d == outp_driver_list)
-    outp_driver_list = d->next;
+  if (d->class && d->class->close_driver)
+    d->class->close_driver (d);
+  outp_unregister_driver (d);
+  outp_free_driver (d);
 }
 
 /* Tries to match S as one of the keywords in TAB, with
 struct outp_driver *
 outp_drivers (struct outp_driver *d)
 {
-  for (;;)
+  do
     {
-      if (d == NULL)
-       d = outp_driver_list;
-      else
-       d = d->next;
+      struct ll *next;
+
+      next = d == NULL ? ll_head (&outp_driver_list) : ll_next (&d->node);
+      if (next == ll_null (&outp_driver_list))
+        return NULL;
 
-      if (d == NULL
-         || (d->device == 0 || (d->device & disabled_devices) != d->device))
-       break;
+      d = ll_data (next, struct outp_driver, node);
     }
+  while (d->device != 0 && (d->device & disabled_devices) == d->device);
 
   return d;
 }
 
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 1997-9, 2000, 2007 Free Software Foundation, Inc.
+   Copyright (C) 1997-9, 2000, 2007, 2009 Free Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
 #ifndef OUTPUT_OUTPUT_H
 #define OUTPUT_OUTPUT_H 1
 
+#include <libpspp/ll.h>
 #include <libpspp/str.h>
 
-
 /* Line styles.  */
 enum outp_line_style
   {
     const char *name;          /* Name of this driver class. */
     int special;               /* Boolean value. */
 
-    bool (*open_driver) (struct outp_driver *, struct substring options);
+    bool (*open_driver) (const char *name, int types,
+                         struct substring options);
     bool (*close_driver) (struct outp_driver *);
 
     void (*open_page) (struct outp_driver *);
 /* Defines the configuration of an output driver. */
 struct outp_driver
   {
-    struct outp_driver *next, *prev; /* List of drivers. */
+    struct ll node;             /* Node in list of drivers. */
     const struct outp_class *class;    /* Driver class. */
     char *name;                        /* Name of this driver. */
     bool page_open;            /* 1=page is open, 0=page is closed. */
 extern char *outp_subtitle;
 
 void outp_init (void);
+void outp_done (void);
 void outp_read_devices (void);
 void outp_configure_driver_line (struct substring);
-void outp_done (void);
+
+struct outp_driver *outp_allocate_driver (const struct outp_class *class,
+                                          const char *name, int types);
+void outp_free_driver (struct outp_driver *);
+void outp_register_driver (struct outp_driver *);
+void outp_unregister_driver (struct outp_driver *);
 
 void outp_configure_clear (void);
 void outp_configure_add (char *);
 /* Imported from som-frnt.c. */
 void som_destroy_driver (struct outp_driver *);
 
+/* Common drivers. */
+extern const struct outp_class ascii_class;
+extern const struct outp_class postscript_class;
+#ifdef HAVE_CAIRO
+extern const struct outp_class cairo_class;
+#endif
+
 #endif /* output/output.h */
 
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 1997-9, 2000, 2006, 2007 Free Software Foundation, Inc.
+   Copyright (C) 1997-9, 2000, 2006, 2007, 2009 Free Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
 /* Driver initialization. */
 
 static bool
-ps_open_driver (struct outp_driver *this, struct substring options)
+ps_open_driver (const char *name, int types, struct substring options)
 {
+  struct outp_driver *this;
   struct ps_driver_ext *x;
   size_t i;
 
+  this = outp_allocate_driver (&postscript_class, name, types);
   this->width = this->length = 0;
   this->font_height = PSUS * 10 / 72;
 
 
   write_ps_prologue (this);
 
+  outp_register_driver (this);
   return true;
 
  error:
   this->class->close_driver (this);
+  outp_free_driver (this);
   return false;
 }