Imported Upstream version 2.6.1p1
[debian/amanda] / server-src / taper.c
index b110522b5995398aea8eecbfd5d7e20f1c855748..c1b7cc6286e99de42cf84ae1e6a2677211a214b9 100644 (file)
  * Authors: the Amanda Development Team.  Its members are listed in a
  * file named AUTHORS, in the root directory of this distribution.
  */
-/* $Id: taper.c,v 1.47.2.14.4.8.2.17.2.1 2004/02/13 14:09:34 martinea Exp $
+/* $Id: taper.c 6512 2007-05-24 17:00:24Z ian $
  *
  * moves files from holding disk to tape, or from a socket to tape
  */
 
-#include "amanda.h"
-#include "conffile.h"
-#include "tapefile.h"
+/* FIXME: This file needs to use gettext. */
+
+#include <glib.h>
+#include "physmem.h"
+
+#include "changer.h"
 #include "clock.h"
-#include "stream.h"
+#include "conffile.h"
+#include "device.h"
 #include "logfile.h"
-#include "tapeio.h"
-#include "changer.h"
-#include "version.h"
-#include "arglist.h"
-#include "token.h"
-#include "amfeatures.h"
-#include "fileheader.h"
 #include "server_util.h"
-#ifdef HAVE_LIBVTBLC
-#include <vtblc.h>
-#include <strings.h>
-
-static int vtbl_no   = -1;
-static int len       =  0;
-static int offset    =  0;
-static char *datestr = NULL;
-static char start_datestr[20];
-time_t raw_time;
-struct tm tape_time;
-struct tm backup_time;
-struct tm *tape_timep = &tape_time;
-typedef struct vtbl_lbls {
-    u_int8_t  label[45];
-    u_int8_t  date[20];
-} vtbl_lbls;
-static vtbl_lbls vtbl_entry[MAX_VOLUMES];
-#endif /* HAVE_LIBVTBLC */
-/*
- * XXX update stat collection/printing
- * XXX advance to next tape first in next_tape
- * XXX label is being read twice?
- */
-
-/* NBUFS replaced by conf_tapebufs */
-/* #define NBUFS               20 */
-int conf_tapebufs;
-
-/* This is now the number of empties, not full bufs */
-#define THRESHOLD      1
-
-#define CONNECT_TIMEOUT 2*60
-
-
-
-#define EMPTY 1
-#define FILLING 2
-#define FULL 3
-
-typedef struct buffer_s {
-    long status;
-    unsigned int size;
-    char *buffer;
-} buffer_t;
-
-#define nextbuf(p)    ((p) == buftable+conf_tapebufs-1? buftable : (p)+1)
-#define prevbuf(p)    ((p) == buftable? buftable+conf_tapebufs-1 : (p)-1)
-
-/* major modules */
-int main P((int main_argc, char **main_argv));
-void file_reader_side P((int rdpipe, int wrpipe));
-void tape_writer_side P((int rdpipe, int wrpipe));
-
-/* shared-memory routines */
-char *attach_buffers P((unsigned int size));
-void detach_buffers P((char *bufp));
-void destroy_buffers P((void));
-
-/* synchronization pipe routines */
-void syncpipe_init P((int rd, int wr));
-char syncpipe_get P((void));
-int  syncpipe_getint P((void));
-char *syncpipe_getstr P((void));
-void syncpipe_put P((int ch));
-void syncpipe_putint P((int i));
-void syncpipe_putstr P((char *str));
-
-/* tape manipulation subsystem */
-int first_tape P((char *new_datestamp));
-int next_tape P((int writerr));
-int end_tape P((int writerr));
-int write_filemark P((void));
-
-/*
- * ========================================================================
- * GLOBAL STATE
- *
- */
-int interactive;
-int writerpid;
-times_t total_wait;
-#ifdef TAPER_DEBUG
-int bufdebug = 1;
-#else
-int bufdebug = 0;
-#endif
-
-char *buffers = NULL;
-buffer_t *buftable = NULL;
-
-char *procname = "parent";
-
-char *taper_datestamp = NULL;
-char *label = NULL;
-int filenum;
-char *errstr = NULL;
-int tape_fd = -1;
-char *tapedev = NULL;
-char *tapetype = NULL;
-tapetype_t *tt = NULL;
-long tt_blocksize;
-long tt_blocksize_kb;
-long buffer_size;
-int tt_file_pad;
-static unsigned long malloc_hist_1, malloc_size_1;
-static unsigned long malloc_hist_2, malloc_size_2;
-
-am_feature_t *their_features = NULL;
-
-int runtapes, cur_tape, have_changer, tapedays;
-char *labelstr, *conf_tapelist;
-#ifdef HAVE_LIBVTBLC
-char *rawtapedev;
-int first_seg, last_seg;
-#endif /* HAVE_LIBVTBLC */
+#include "stream.h"
+#include "tapefile.h"
+#include "taperscan.h"
+#include "taper-source.h"
+#include "timestamp.h"
+#include "util.h"
+#include "version.h"
+#include "queueing.h"
+#include "device-queueing.h"
+
+/* FIXME: This should not be here. */
+#define CONNECT_TIMEOUT (2*60)
+
+/* Use this instead of global variables, so that we are reentrant. */
+typedef struct {
+    Device * device;
+    char * driver_start_time;
+    int    cur_tape;
+    char * next_tape_label;
+    char * next_tape_device;
+    taper_scan_tracker_t * taper_scan_tracker;
+    char * last_errmsg;
+    off_t  total_bytes;
+    int have_changer;
+} taper_state_t;
+
+typedef struct {
+    char * handle;
+    char * hostname;
+    char * diskname;
+    int level;
+    char * timestamp;
+    char * id_string;
+    TaperSource * source;
+    int current_part;
+    GTimeVal total_time;
+    guint64 total_bytes;
+} dump_info_t;
+
+static gboolean label_new_tape(taper_state_t * state, dump_info_t * dump_info);
+
+static void init_taper_state(taper_state_t* state) {
+    state->device = NULL;
+    state->driver_start_time = NULL;
+    state->taper_scan_tracker = taper_scan_tracker_new();
+    state->last_errmsg = NULL;
+    state->total_bytes = 0;
+}
 
-/*
- * ========================================================================
- * MAIN PROGRAM
- *
- */
-int main(main_argc, main_argv)
-int main_argc;
-char **main_argv;
-{
-    int p2c[2], c2p[2];                /* parent-to-child, child-to-parent pipes */
-    char *conffile;
-    int fd;
-    unsigned int size;
-    int i;
-    int j;
-    int page_size;
-    char *first_buffer;
-
-    for(fd = 3; fd < FD_SETSIZE; fd++) {
-       /*
-        * Make sure nobody spoofs us with a lot of extra open files
-        * that would cause an open we do to get a very high file
-        * descriptor, which in turn might be used as an index into
-        * an array (e.g. an fd_set).
-        */
-       close(fd);
+static void cleanup(taper_state_t * state) {
+    amfree(state->driver_start_time);
+    amfree(state->next_tape_label);
+    amfree(state->next_tape_device);
+    amfree(state->last_errmsg);
+    taper_scan_tracker_free(state->taper_scan_tracker);
+    if (state->device != NULL) {
+        g_object_unref(state->device);
+        state->device = NULL;
     }
+}
 
-    set_pname("taper");
-
-    malloc_size_1 = malloc_inuse(&malloc_hist_1);
-
-    fprintf(stderr, "%s: pid %ld executable %s version %s\n",
-           get_pname(), (long) getpid(), main_argv[0], version());
-    fflush(stderr);
-
-    if (main_argc > 1 && main_argv[1][0] != '-') {
-       config_name = stralloc(main_argv[1]);
-       config_dir = vstralloc(CONFIG_DIR, "/", main_argv[1], "/", NULL);
-       main_argc--;
-       main_argv++;
-    } else {
-       char my_cwd[STR_SIZE];
-
-       if (getcwd(my_cwd, sizeof(my_cwd)) == NULL) {
-           error("cannot determine current working directory");
-       }
-       config_dir = stralloc2(my_cwd, "/");
-       if ((config_name = strrchr(my_cwd, '/')) != NULL) {
-           config_name = stralloc(config_name + 1);
-       }
+static void free_dump_info(dump_info_t * info) {
+    amfree(info->handle);
+    amfree(info->hostname);
+    amfree(info->diskname);
+    amfree(info->timestamp);
+    amfree(info->id_string);
+    if (info->source != NULL) {
+        g_object_unref(info->source);
+        info->source = NULL;
     }
+}
 
-    safe_cd();
-
-    /* print prompts and debug messages if running interactive */
+/* Validate that a command has the proper number of arguments, and
+   print a meaningful error message if not. It returns only if the
+   check is successful. */
+static void validate_args(struct cmdargs * cmdargs,
+                          char ** argnames) {
+    int len = g_strv_length(argnames);
 
-    interactive = (main_argc > 1 && strcmp(main_argv[1],"-t") == 0);
-    if(interactive) {
-       erroutput_type = ERR_INTERACTIVE;
-    } else {
-       erroutput_type = ERR_AMANDALOG;
-       set_logerror(logerror);
+    if (len > cmdargs->argc) {
+       error("error [taper %s: not enough args; first missing arg is %s]",
+             cmdstr[cmdargs->cmd], argnames[cmdargs->argc]);
     }
 
-    conffile = stralloc2(config_dir, CONFFILE_NAME);
-    if(read_conffile(conffile)) {
-       error("errors processing config file \"%s\"", conffile);
+    if (len < cmdargs->argc) {
+        error("error [taper %s: Too many args: Got %d, expected %d.]",
+              cmdstr[cmdargs->cmd], cmdargs->argc, len);
     }
-    amfree(conffile);
+}
 
-    conf_tapelist = getconf_str(CNF_TAPELIST);
-    if (*conf_tapelist == '/') {
-       conf_tapelist = stralloc(conf_tapelist);
-    } else {
-       conf_tapelist = stralloc2(config_dir, conf_tapelist);
-    }
-    if(read_tapelist(conf_tapelist)) {
-       error("could not load tapelist \"%s\"", conf_tapelist);
-    }
+/* Open a socket to the dumper. Returns TRUE if everything is happy, FALSE
+   otherwise. */
+static gboolean open_read_socket(dump_info_t * info, char * split_diskbuffer,
+                             guint64 splitsize, guint64 fallback_splitsize) {
+    in_port_t port = 0;
+    int socket;
+    int fd;
+    int result;
+    struct addrinfo *res;
 
-    tapedev    = getconf_str(CNF_TAPEDEV);
-    tapetype    = getconf_str(CNF_TAPETYPE);
-    tt         = lookup_tapetype(tapetype);
-#ifdef HAVE_LIBVTBLC
-    rawtapedev = getconf_str(CNF_RAWTAPEDEV);
-#endif /* HAVE_LIBVTBLC */
-    tapedays   = getconf_int(CNF_TAPECYCLE);
-    labelstr   = getconf_str(CNF_LABELSTR);
+    if ((result = resolve_hostname("localhost", 0, &res, NULL) != 0)) {
+        char *m;
+        char *q;
+       int save_errno = errno;
+        char *qdiskname = quote_string(info->diskname);
+
+        m = vstralloc("[localhost resolve failure: ",
+                      strerror(save_errno),
+                      "]",
+                      NULL);
+        q = quote_string(m);
+        putresult(TAPE_ERROR, "%s %s\n", info->handle, q);
+        log_add(L_FAIL, "%s %s %s %d %s",
+                info->hostname, qdiskname, info->timestamp,
+                info->level, q);
+        amfree(qdiskname);
+        amfree(m);
+        amfree(q);
+        return FALSE;
+    }
 
-    runtapes   = getconf_int(CNF_RUNTAPES);
-    cur_tape   = 0;
+    socket = stream_server(res->ai_family, &port, 0, STREAM_BUFSIZE, 0);
+    freeaddrinfo(res);
 
-    conf_tapebufs = getconf_int(CNF_TAPEBUFS);
+    if (socket < 0) {
+        char *m;
+        char *q;
+       int save_errno = errno;
+        char *qdiskname = quote_string(info->diskname);
+
+        m = vstralloc("[port create failure: ",
+                      strerror(save_errno),
+                      "]",
+                      NULL);
+        q = quote_string(m);
+        putresult(TAPE_ERROR, "%s %s\n", info->handle, q);
+        log_add(L_FAIL, "%s %s %s %d %s",
+                info->hostname, qdiskname, info->timestamp,
+                info->level, q);
+        amfree(qdiskname);
+        amfree(m);
+        amfree(q);
+        return FALSE;
+    }
 
-    tt_blocksize_kb = tt->blocksize;
-    tt_blocksize = tt_blocksize_kb * 1024;
-    tt_file_pad = tt->file_pad;
+    putresult(PORT, "%d\n", port);
 
-    if(interactive) {
-       fprintf(stderr,"taper: running in interactive test mode\n");
-       fflush(stderr);
-    }
+    fd = stream_accept(socket, CONNECT_TIMEOUT, 0, STREAM_BUFSIZE);
 
-    /* create read/write syncronization pipes */
-
-    if(pipe(p2c) || pipe(c2p))
-       error("creating sync pipes: %s", strerror(errno));
-
-    /* create shared memory segment */
-
-#if defined(HAVE_GETPAGESIZE)
-    page_size = getpagesize();
-    fprintf(stderr, "%s: page size is %d\n", get_pname(), page_size);
-#else
-    page_size = 1024;
-    fprintf(stderr, "%s: getpagesize() not available, using %d\n",
-           get_pname(),
-           page_size);
-#endif
-    buffer_size = am_round(tt_blocksize, page_size);
-    fprintf(stderr, "%s: buffer size is %ld\n", get_pname(), buffer_size);
-    while(conf_tapebufs > 0) {
-       size  = page_size;
-       size += conf_tapebufs * buffer_size;
-       size += conf_tapebufs * sizeof(buffer_t);
-       if((buffers = attach_buffers(size)) != NULL) {
-           break;
-       }
-       log_add(L_INFO, "attach_buffers: (%d tapebuf%s: %d bytes) %s",
-                       conf_tapebufs,
-                       (conf_tapebufs == 1) ? "" : "s",
-                       size,
-                       strerror(errno));
-       conf_tapebufs--;
-    }
-    if(buffers == NULL) {
-       error("cannot allocate shared memory");
-    }
-    i = (buffers - (char *)0) & (page_size - 1);  /* page boundary offset */
-    if(i != 0) {
-       first_buffer = buffers + page_size - i;
-       fprintf(stderr, "%s: shared memory at %p, first buffer at %p\n",
-               get_pname(),
-               buffers,
-               first_buffer);
-    } else {
-       first_buffer = buffers;
-    }
-    buftable = (buffer_t *)(first_buffer + conf_tapebufs * buffer_size);
-    memset(buftable, 0, conf_tapebufs * sizeof(buffer_t));
-    if(conf_tapebufs < 10) {
-       j = 1;
-    } else if(conf_tapebufs < 100) {
-       j = 2;
+    if (fd < 0) {
+        char *m, *q;
+       int save_errno = errno;
+        char *qdiskname = quote_string(info->diskname);
+        m = vstralloc("[port connect failure: ",
+                      strerror(save_errno),
+                      "]",
+                      NULL);
+        q = quote_string(m);
+        putresult(TAPE_ERROR, "%s %s\n", info->handle, q);
+        log_add(L_FAIL, "%s %s %s %d %s",
+                info->hostname, qdiskname, info->timestamp,
+                info->level, q);
+        amfree(qdiskname);
+        aclose(socket);
+        amfree(m);
+        amfree(q);
+        return FALSE;
     } else {
-       j = 3;
+        aclose(socket);
     }
-    for(i = 0; i < conf_tapebufs; i++) {
-       buftable[i].buffer = first_buffer + i * buffer_size;
-       fprintf(stderr, "%s: buffer[%0*d] at %p\n",
-               get_pname(),
-               j, i,
-               buftable[i].buffer);
-    }
-    fprintf(stderr, "%s: buffer structures at %p for %d bytes\n",
-           get_pname(),
-           buftable,
-           (int)(conf_tapebufs * sizeof(buffer_t)));
-
-    /* fork off child writer process, parent becomes reader process */
-
-    switch(writerpid = fork()) {
-    case -1:
-       error("fork: %s", strerror(errno));
 
-    case 0:    /* child */
-       aclose(p2c[1]);
-       aclose(c2p[0]);
+    info->source = taper_source_new(info->handle, PORT_WRITE, NULL, fd,
+                                    split_diskbuffer, splitsize,
+                                    fallback_splitsize);
+    /* FIXME: This should be handled properly. */
+    g_assert(info->source != NULL);
+    return TRUE;
+}
 
-       tape_writer_side(p2c[0], c2p[1]);
-       error("tape writer terminated unexpectedly");
+typedef struct {
+    ConsumerFunctor next_consumer;
+    gpointer next_consumer_data;
+    guint64 bytes_written;
+} CountingConsumerData;
 
-    default:   /* parent */
-       aclose(p2c[0]);
-       aclose(c2p[1]);
+/* A ConsumerFunctor. This consumer just passes its arguments on to a
+   second consumer, but counts the number of bytes successfully
+   written. */
+static ssize_t counting_consumer(gpointer user_data, queue_buffer_t * buffer) {
+    ssize_t result;
+    CountingConsumerData * data = user_data;
 
-       file_reader_side(c2p[0], p2c[1]);
-       error("file reader terminated unexpectedly");
+    result = data->next_consumer(data->next_consumer_data, buffer);
+    
+    if (result > 0) {
+        data->bytes_written += result;
     }
 
-    /* NOTREACHED */
-    return 0;
+    return result;
 }
 
-
-/*
- * ========================================================================
- * FILE READER SIDE
- *
- */
-void read_file P((int fd, char *handle,
-                 char *host, char *disk, char *datestamp, 
-                 int level, int port_flag));
-int taper_fill_buffer P((int fd, buffer_t *bp, int buflen));
-void dumpbufs P((char *str1));
-void dumpstatus P((buffer_t *bp));
-
-void file_reader_side(rdpipe, wrpipe)
-int rdpipe, wrpipe;
-{
-    cmd_t cmd;
-    struct cmdargs cmdargs;
-    char *handle = NULL;
-    char *filename = NULL;
-    char *hostname = NULL;
-    char *diskname = NULL;
-    char *result = NULL;
-    char *datestamp = NULL;
-    char tok;
-    char *q = NULL;
-    int level, fd, data_port, data_socket, wpid;
-    struct stat stat_file;
-    int tape_started;
-    int a;
-
-    procname = "reader";
-    syncpipe_init(rdpipe, wrpipe);
-
-    /* must get START_TAPER before beginning */
-
-    startclock();
-    cmd = getcmd(&cmdargs);
-    total_wait = stopclock();
-
-    if(cmd != START_TAPER || cmdargs.argc != 2) {
-       error("error [file_reader_side cmd %d argc %d]", cmd, cmdargs.argc);
-    }
-
-    /* pass start command on to tape writer */
-
-    taper_datestamp = newstralloc(taper_datestamp, cmdargs.argv[2]);
-
-    tape_started = 0;
-    syncpipe_put('S');
-    syncpipe_putstr(taper_datestamp);
-
-    /* get result of start command */
-
-    tok = syncpipe_get();
-    switch(tok) {
-    case 'S':
-       putresult(TAPER_OK, "\n");
-       tape_started = 1;
-       /* start is logged in writer */
-       break;
-    case 'E':
-       /* no tape, bail out */
-       result = syncpipe_getstr();
-       q = squotef("[%s]", result ? result : "(null)");
-       putresult(TAPE_ERROR, "%s\n", q);
-       amfree(q);
-       log_add(L_ERROR,"no-tape [%s]", result);
-       amfree(result);
-       syncpipe_put('e');                      /* ACK error */
-       break;
-    default:
-       error("expected 'S' or 'E' for START-TAPER, got '%c'", tok);
+static gboolean boolean_prolong(void * data) {
+    if (data == NULL) {
+        return TRUE; /* Do not interrupt. */
+    } else {
+        return *(gboolean*)data;
     }
+}
 
-    /* process further commands */
-
-    while(1) {
-       startclock();
-       cmd = getcmd(&cmdargs);
-       if(cmd != QUIT && !tape_started) {
-           error("error [file_reader_side cmd %d without tape ready]", cmd);
-       }
-       total_wait = timesadd(total_wait, stopclock());
-
-       switch(cmd) {
-       case PORT_WRITE:
-           /*
-            * PORT-WRITE
-            *   handle
-            *   hostname
-            *   features
-            *   diskname
-            *   level
-            *   datestamp
-            */
-           cmdargs.argc++;                     /* true count of args */
-           a = 2;
-
-           if(a >= cmdargs.argc) {
-               error("error [taper PORT-WRITE: not enough args: handle]");
-           }
-           handle = newstralloc(handle, cmdargs.argv[a++]);
-
-           if(a >= cmdargs.argc) {
-               error("error [taper PORT-WRITE: not enough args: hostname]");
-           }
-           hostname = newstralloc(hostname, cmdargs.argv[a++]);
-
-           if(a >= cmdargs.argc) {
-               error("error [taper PORT-WRITE: not enough args: features]");
-           }
-           am_release_feature_set(their_features);
-           their_features = am_string_to_feature(cmdargs.argv[a++]);
-
-           if(a >= cmdargs.argc) {
-               error("error [taper PORT-WRITE: not enough args: diskname]");
-           }
-           diskname = newstralloc(diskname, cmdargs.argv[a++]);
-
-           if(a >= cmdargs.argc) {
-               error("error [taper PORT-WRITE: not enough args: level]");
-           }
-           level = atoi(cmdargs.argv[a++]);
-
-           if(a >= cmdargs.argc) {
-               error("error [taper PORT-WRITE: not enough args: datestamp]");
-           }
-           datestamp = newstralloc(datestamp, cmdargs.argv[a++]);
-
-           if(a != cmdargs.argc) {
-               error("error [taper file_reader_side PORT-WRITE: too many args: %d != %d]",
-                     cmdargs.argc, a);
-           }
-
-           data_port = 0;
-           data_socket = stream_server(&data_port,
-                                       -1,
-                                       STREAM_BUFSIZE);        
-           if(data_socket < 0) {
-               char *m;
-
-               m = vstralloc("[port create failure: ",
-                             strerror(errno),
-                             "]",
-                             NULL);
-               q = squote(m);
-               putresult(TAPE_ERROR, "%s %s\n", handle, q);
-               amfree(m);
-               break;
-           }
-           putresult(PORT, "%d\n", data_port);
-
-           if((fd = stream_accept(data_socket, CONNECT_TIMEOUT,
-                                  -1, NETWORK_BLOCK_BYTES)) == -1) {
-               q = squote("[port connect timeout]");
-               putresult(TAPE_ERROR, "%s %s\n", handle, q);
-               aclose(data_socket);
-               break;
-           }
-           read_file(fd, handle, hostname, diskname, datestamp, level, 1);
-           aclose(data_socket);
-           break;
-
-       case FILE_WRITE:
-           /*
-            * FILE-WRITE
-            *   handle
-            *   filename
-            *   hostname
-            *   features
-            *   diskname
-            *   level
-            *   datestamp
-            */
-           cmdargs.argc++;                     /* true count of args */
-           a = 2;
-
-           if(a >= cmdargs.argc) {
-               error("error [taper FILE-WRITE: not enough args: handle]");
-           }
-           handle = newstralloc(handle, cmdargs.argv[a++]);
-
-           if(a >= cmdargs.argc) {
-               error("error [taper FILE-WRITE: not enough args: filename]");
-           }
-           filename = newstralloc(filename, cmdargs.argv[a++]);
-
-           if(a >= cmdargs.argc) {
-               error("error [taper FILE-WRITE: not enough args: hostname]");
-           }
-           hostname = newstralloc(hostname, cmdargs.argv[a++]);
-
-           if(a >= cmdargs.argc) {
-               error("error [taper FILE-WRITE: not enough args: features]");
-           }
-           am_release_feature_set(their_features);
-           their_features = am_string_to_feature(cmdargs.argv[a++]);
-
-           if(a >= cmdargs.argc) {
-               error("error [taper FILE-WRITE: not enough args: diskname]");
-           }
-           diskname = newstralloc(diskname, cmdargs.argv[a++]);
-
-           if(a >= cmdargs.argc) {
-               error("error [taper FILE-WRITE: not enough args: level]");
-           }
-           level = atoi(cmdargs.argv[a++]);
-
-           if(a >= cmdargs.argc) {
-               error("error [taper FILE-WRITE: not enough args: datestamp]");
-           }
-           datestamp = newstralloc(datestamp, cmdargs.argv[a++]);
-
-           if(a != cmdargs.argc) {
-               error("error [taper file_reader_side FILE-WRITE: too many args: %d != %d]",
-                     cmdargs.argc, a);
-           }
-
-           if(stat(filename,&stat_file)!=0) {
-               q = squotef("[%s]", strerror(errno));
-               putresult(TAPE_ERROR, "%s %s\n", handle, q);
-               break;
-           }
-           if((fd = open(filename, O_RDONLY)) == -1) {
-               q = squotef("[%s]", strerror(errno));
-               putresult(TAPE_ERROR, "%s %s\n", handle, q);
-               break;
-           }
-           read_file(fd, handle, hostname, diskname, datestamp, level, 0);
-           break;
-
-       case QUIT:
-           putresult(QUITTING, "\n");
-           fprintf(stderr,"taper: DONE [idle wait: %s secs]\n",
-                   walltime_str(total_wait));
-           fflush(stderr);
-           syncpipe_put('Q');  /* tell writer we're exiting gracefully */
-           aclose(wrpipe);
-
-           if((wpid = wait(NULL)) != writerpid) {
-               fprintf(stderr,
-                       "taper: writer wait returned %d instead of %d: %s\n",
-                       wpid, writerpid, strerror(errno));
-               fflush(stderr);
-           }
-
-           detach_buffers(buffers);
-           destroy_buffers();
-           amfree(datestamp);
-           amfree(label);
-           amfree(errstr);
-           amfree(changer_resultstr);
-           amfree(tapedev);
-           amfree(config_dir);
-           amfree(config_name);
-
-           malloc_size_2 = malloc_inuse(&malloc_hist_2);
-
-           if(malloc_size_1 != malloc_size_2) {
-               malloc_list(fileno(stderr), malloc_hist_1, malloc_hist_2);
-           }
-
-           exit(0);
+/* A (simpler) wrapper around taper_scan(). */
+static gboolean simple_taper_scan(taper_state_t * state,
+                                  gboolean* prolong, char ** error_message) {
+    char ** label = &(state->next_tape_label);
+    char ** device = &(state->next_tape_device);
+    char *timestamp = NULL;
+    int result;
+    result = taper_scan(NULL, label, &timestamp, device,
+                        state->taper_scan_tracker,
+                        CHAR_taperscan_output_callback,
+                        error_message, boolean_prolong, prolong);
+    if (prolong != NULL && !*prolong) {
+        g_fprintf(stderr, _("Cancelled taper scan.\n"));
+        return FALSE;
+    } else if (result < 0) {
+        g_fprintf(stderr, _("Failed taper scan: %s\n"), (*error_message)?(*error_message):_("(no error message)"));
+        amfree(timestamp);
+        return FALSE;
+    } else {
+        g_fprintf(stderr, _("taper: using label `%s' date `%s'\n"), *label,
+                state->driver_start_time);
+        if (result == 3) {
+            log_add(L_INFO,
+            _("Will write new label `%s' to new tape"),
+                    *label);
+        }
 
-       default:
-           if(cmdargs.argc >= 1) {
-               q = squote(cmdargs.argv[1]);
-           } else if(cmdargs.argc >= 0) {
-               q = squote(cmdargs.argv[0]);
-           } else {
-               q = stralloc("(no input?)");
-           }
-           putresult(BAD_COMMAND, "%s\n", q);
-           break;
-       }
     }
-    amfree(q);
-    amfree(handle);
-    amfree(filename);
-    amfree(hostname);
-    amfree(diskname);
+    amfree(timestamp);
+    return TRUE;
 }
 
-void dumpbufs(str1)
-char *str1;
-{
-    int i,j;
-    long v;
-
-    fprintf(stderr, "%s: state", str1);
-    for(i = j = 0; i < conf_tapebufs; i = j+1) {
-       v = buftable[i].status;
-       for(j = i; j < conf_tapebufs && buftable[j].status == v; j++);
-       j--;
-       if(i == j) fprintf(stderr, " %d:", i);
-       else fprintf(stderr, " %d-%d:", i, j);
-       switch(v) {
-       case FULL:      fputc('F', stderr); break;
-       case FILLING:   fputc('f', stderr); break;
-       case EMPTY:     fputc('E', stderr); break;
-       default:
-           fprintf(stderr, "%ld", v);
-           break;
-       }
+typedef struct {
+    taper_state_t * state;
+    gboolean prolong; /* scan stops when this is FALSE. */
+    char *errmsg;
+} tape_search_request_t;
 
-    }
-    fputc('\n', stderr);
-    fflush(stderr);
-}
+/* A GThread that runs taper_scan. */
+static gpointer tape_search_thread(gpointer data) {
+    tape_search_request_t * request = data;
 
-void dumpstatus(bp)
-buffer_t *bp;
-{
-    char pn[2];
-    char bt[NUM_STR_SIZE];
-    char status[NUM_STR_SIZE + 1];
-    char *str = NULL;
-
-    pn[0] = procname[0];
-    pn[1] = '\0';
-    ap_snprintf(bt, sizeof(bt), "%d", (int)(bp-buftable));
-
-    switch(bp->status) {
-    case FULL:         ap_snprintf(status, sizeof(status), "F%d", bp->size);
-                       break;
-    case FILLING:      status[0] = 'f'; status[1] = '\0'; break;
-    case EMPTY:                status[0] = 'E'; status[1] = '\0'; break;
-    default:
-       ap_snprintf(status, sizeof(status), "%ld", bp->status);
-       break;
+    if (request->state->next_tape_label != NULL &&
+        request->state->next_tape_device != NULL) {
+        return GINT_TO_POINTER(TRUE);
+    } else {
+        amfree(request->state->next_tape_label);
+        amfree(request->state->next_tape_device);
     }
 
-    str = vstralloc("taper: ", pn, ": [buf ", bt, ":=", status, "]", NULL);
-    dumpbufs(str);
-    amfree(str);
+    return GINT_TO_POINTER
+        (simple_taper_scan(request->state,
+                           &(request->prolong),
+                          &(request->errmsg)));
 }
 
-
-void read_file(fd, handle, hostname, diskname, datestamp, level, port_flag)
-    int fd, level, port_flag;
-    char *handle, *hostname, *diskname, *datestamp;
-{
-    buffer_t *bp;
-    char tok;
-    int rc, err, opening, closing, bufnum, need_closing;
-    long filesize;
-    times_t runtime;
-    char *strclosing = NULL;
-    char *str;
-    int header_read = 0;
-    int buflen;
-    dumpfile_t file;
-
-    char *q = NULL;
-
-#ifdef HAVE_LIBVTBLC
-    static char desc[45];
-    static char vol_date[20];
-    static char vol_label[45];
-#endif /* HAVE_LIBVTBLC */
-
-
-    /* initialize */
-
-    filesize = 0;
-    closing = 0;
-    need_closing = 0;
-    err = 0;
-    fh_init(&file);
-
-    if(bufdebug) {
-       fprintf(stderr, "taper: r: start file\n");
-       fflush(stderr);
-    }
-
-    for(bp = buftable; bp < buftable + conf_tapebufs; bp++) {
-       bp->status = EMPTY;
+static void log_taper_scan_errmsg(char * errmsg) {
+    char *c, *c1;
+    if (errmsg == NULL)
+        return;
+
+    c = c1 = errmsg;
+    while (*c != '\0') {
+        if (*c == '\n') {
+            *c = '\0';
+            log_add(L_WARNING,"%s", c1);
+            c1 = c+1;
+        }
+        c++;
     }
+    if (strlen(c1) > 1 )
+        log_add(L_WARNING,"%s", c1);
+    amfree(errmsg);
+}
 
-    bp = buftable;
-    if(interactive || bufdebug) dumpstatus(bp);
-
-    /* tell writer to open tape */
-
-    opening = 1;
-    syncpipe_put('O');
-    syncpipe_putstr(datestamp);
-    syncpipe_putstr(hostname);
-    syncpipe_putstr(diskname);
-    syncpipe_putint(level);
-
-    startclock();
-
-    /* read file in loop */
-
-    while(1) {
-       tok = syncpipe_get();
-       switch(tok) {
-
-       case 'O':
-           assert(opening);
-           opening = 0;
-           err = 0;
-           break;
-
-       case 'R':
-           bufnum = syncpipe_getint();
-
-           if(bufdebug) {
-               fprintf(stderr, "taper: r: got R%d\n", bufnum);
-               fflush(stderr);
-           }
-
-           if(need_closing) {
-               syncpipe_put('C');
-               closing = 1;
-               need_closing = 0;
-               break;
-           }
-
-           if(closing) break;  /* ignore extra read tokens */
-
-           assert(!opening);
-           if(bp->status != EMPTY || bufnum != bp-buftable) {
-               /* XXX this SHOULD NOT HAPPEN.  Famous last words. */
-               fprintf(stderr,"taper: panic: buffer mismatch at ofs %ld:\n",
-                       filesize);
-               if(bufnum != bp-buftable) {
-                   fprintf(stderr, "    my buf %d but writer buf %d\n",
-                           (int)(bp-buftable), bufnum);
-               }
-               else {
-                   fprintf(stderr,"buf %d state %s (%ld) instead of EMPTY\n",
-                           (int)(bp-buftable),
-                           bp->status == FILLING? "FILLING" :
-                           bp->status == FULL? "FULL" : "EMPTY!?!?",
-                           (long)bp->status);
-               }
-               dumpbufs("taper");
-               sleep(1);
-               dumpbufs("taper: after 1 sec");
-               if(bp->status == EMPTY)
-                   fprintf(stderr, "taper: result now correct!\n");
-               fflush(stderr);
-
-               errstr = newstralloc(errstr,
-                                    "[fatal buffer mismanagement bug]");
-               q = squote(errstr);
-               putresult(TRYAGAIN, "%s %s\n", handle, q);
-               amfree(q);
-               log_add(L_INFO, "retrying %s:%s.%d on new tape: %s",
-                       hostname, diskname, level, errstr);
-               closing = 1;
-               syncpipe_put('X');      /* X == buffer snafu, bail */
-               do {
-                   tok = syncpipe_get();
-                   if(tok == 'R')
-                       bufnum = syncpipe_getint();
-               } while(tok != 'x');
-               aclose(fd);
-               return;
-           }
-
-           bp->status = FILLING;
-           buflen = header_read ? tt_blocksize : DISK_BLOCK_BYTES;
-           if(interactive || bufdebug) dumpstatus(bp);
-           if((rc = taper_fill_buffer(fd, bp, buflen)) < 0) {
-               err = errno;
-               closing = 1;
-               strclosing = newvstralloc(strclosing,"Can't read data: ",NULL);
-               syncpipe_put('C');
-           } else {
-               if(rc < buflen) { /* switch to next file */
-                   int save_fd;
-                   struct stat stat_file;
-
-                   save_fd = fd;
-                   close(fd);
-                   if(file.cont_filename[0] == '\0') { /* no more file */
-                       err = 0;
-                       need_closing = 1;
-                   } else if(stat(file.cont_filename, &stat_file) != 0) {
-                       err = errno;
-                       need_closing = 1;
-                       strclosing = newvstralloc(strclosing,"can't stat: ",file.cont_filename,NULL);
-                   } else if((fd = open(file.cont_filename,O_RDONLY)) == -1) {
-                       err = errno;
-                       need_closing = 1;
-                       strclosing = newvstralloc(strclosing,"can't open: ",file.cont_filename,NULL);
-                   } else if((fd != save_fd) && dup2(fd, save_fd) == -1) {
-                       err = errno;
-                       need_closing = 1;
-                       strclosing = newvstralloc(strclosing,"can't dup2: ",file.cont_filename,NULL);
-                   } else {
-                       buffer_t bp1;
-                       int rc1;
-
-                       bp1.status = EMPTY;
-                       bp1.size = DISK_BLOCK_BYTES;
-                       bp1.buffer = malloc(DISK_BLOCK_BYTES);
-
-                       if(fd != save_fd) {
-                           close(fd);
-                           fd = save_fd;
-                       }
-
-                       rc1 = taper_fill_buffer(fd, &bp1, DISK_BLOCK_BYTES);
-                       if(rc1 <= 0) {
-                           amfree(bp1.buffer);
-                           err = (rc1 < 0) ? errno : 0;
-                           need_closing = 1;
-                           strclosing = newvstralloc(strclosing,
-                                                     "Can't read header: ",
-                                                     file.cont_filename,
-                                                     NULL);
-                       } else {
-                           parse_file_header(bp1.buffer, &file, rc1);
-
-                           amfree(bp1.buffer);
-                           bp1.buffer = bp->buffer + rc;
-
-                           rc1 = taper_fill_buffer(fd, &bp1, tt_blocksize - rc);
-                           if(rc1 <= 0) {
-                               err = (rc1 < 0) ? errno : 0;
-                               need_closing = 1;
-                               if(rc1 < 0) {
-                                   strclosing = newvstralloc(strclosing,
-                                                             "Can't read data: ",
-                                                             file.cont_filename,
-                                                             NULL);
-                               }
-                           }
-                           else {
-                               rc += rc1;
-                               bp->size = rc;
-                           }
-                       }
-                   }
-               }
-               if(rc > 0) {
-                   bp->status = FULL;
-                   if(header_read == 0) {
-                       char *cont_filename;
-
-                       parse_file_header(bp->buffer, &file, rc);
-                       cont_filename = stralloc(file.cont_filename);
-                       file.cont_filename[0] = '\0';
-                       file.blocksize = tt_blocksize;
-                       build_header(bp->buffer, &file, tt_blocksize);
-
-                       /* add CONT_FILENAME back to in-memory header */
-                       strncpy(file.cont_filename, cont_filename, 
-                               sizeof(file.cont_filename));
-                       if(interactive || bufdebug) dumpstatus(bp);
-                       bp->size = tt_blocksize; /* output a full tape block */
-                       header_read = 1;
-                       amfree(cont_filename);
-                   }
-                   else {
-                       filesize += am_round(rc, 1024) / 1024;
-                   }
-                   if(interactive || bufdebug) dumpstatus(bp);
-                   if(bufdebug) {
-                       fprintf(stderr,"taper: r: put W%d\n",(int)(bp-buftable));
-                       fflush(stderr);
-                   }
-                   syncpipe_put('W');
-                   syncpipe_putint(bp-buftable);
-                   bp = nextbuf(bp);
-               }
-               if(need_closing && rc <= 0) {
-                   syncpipe_put('C');
-                   need_closing = 0;
-                   closing = 1;
-               }
-           }
-           break;
-
-       case 'T':
-       case 'E':
-           syncpipe_put('e');  /* ACK error */
-
-           aclose(fd);
-           str = syncpipe_getstr();
-           errstr = newvstralloc(errstr, "[", str ? str : "(null)", "]", NULL);
-           amfree(str);
-
-           q = squote(errstr);
-           if(tok == 'T') {
-               putresult(TRYAGAIN, "%s %s\n", handle, q);
-               log_add(L_INFO, "retrying %s:%s.%d on new tape: %s",
-                       hostname, diskname, level, errstr);
-           } else {
-               putresult(TAPE_ERROR, "%s %s\n", handle, q);
-               log_add(L_FAIL, "%s %s %s %d [out of tape]",
-                       hostname, diskname, datestamp, level);
-               log_add(L_ERROR,"no-tape [%s]", errstr);
-           }
-           amfree(q);
-           return;
-
-       case 'C':
-           assert(!opening);
-           assert(closing);
-
-           str = syncpipe_getstr();
-           label = newstralloc(label, str ? str : "(null)");
-           amfree(str);
-           str = syncpipe_getstr();
-           filenum = atoi(str ? str : "-9876");        /* ??? */
-           amfree(str);
-           fprintf(stderr, "taper: reader-side: got label %s filenum %d\n",
-                   label, filenum);
-           fflush(stderr);
-
-           aclose(fd);
-           runtime = stopclock();
-           if(err) {
-               if(strclosing) {
-                   errstr = newvstralloc(errstr,
-                                         "[input: ", strclosing, ": ",
-                                         strerror(err), "]", NULL);
-                   amfree(strclosing);
-               }
-               else
-                   errstr = newvstralloc(errstr,
-                                         "[input: ", strerror(err), "]",
-                                         NULL);
-               q = squote(errstr);
-               putresult(TAPE_ERROR, "%s %s\n", handle, q);
-               amfree(q);
-               log_add(L_FAIL, "%s %s %s %d %s",
-                       hostname, diskname, datestamp, level, errstr);
-               str = syncpipe_getstr();        /* reap stats */
-               amfree(str);
-           } else {
-               char kb_str[NUM_STR_SIZE];
-               char kps_str[NUM_STR_SIZE];
-               double rt;
-
-               rt = runtime.r.tv_sec+runtime.r.tv_usec/1000000.0;
-               ap_snprintf(kb_str, sizeof(kb_str), "%ld", filesize);
-               ap_snprintf(kps_str, sizeof(kps_str), "%3.1f",
-                                    rt ? filesize / rt : 0.0);
-               str = syncpipe_getstr();
-               errstr = newvstralloc(errstr,
-                                     "[sec ", walltime_str(runtime),
-                                     " kb ", kb_str,
-                                     " kps ", kps_str,
-                                     " ", str ? str : "(null)",
-                                     "]",
-                                     NULL);
-               amfree(str);
-               q = squote(errstr);
-               putresult(DONE, "%s %s %d %s\n",
-                         handle, label, filenum, q);
-               amfree(q);
-               log_add(L_SUCCESS, "%s %s %s %d %s",
-                       hostname, diskname, datestamp, level, errstr);
-#ifdef HAVE_LIBVTBLC
-               /* 
-                *  We have 44 characters available for the label string:
-                *  use max 20 characters for hostname
-                *      max 20 characters for diskname 
-                *             (it could contain a samba share or dos path)
-                *           2 for level
-                */
-               memset(desc, '\0', 45);
-
-               strncpy(desc, hostname, 20);
-
-               if ((len = strlen(hostname)) <= 20) {
-                   memset(desc + len, ' ', 1);
-                   offset = len + 1;
-               }
-               else{
-                   memset(desc + 20, ' ', 1);
-                   offset = 21;
-               }
+/* If handle is NULL, then this function assumes that we are in startup mode.
+ * In that case it will wait for a command from driver. If handle is not NULL,
+ * this this function will ask for permission with REQUEST-NEW-TAPE. */
+static gboolean find_new_tape(taper_state_t * state, dump_info_t * dump) {
+    GThread * tape_search = NULL;
+    tape_search_request_t search_request;
+    gboolean use_threads;
+    struct cmdargs *cmdargs;
+    cmd_t cmd;
 
-               strncpy(desc + offset, diskname, 20);
+    if (state->device != NULL) {
+        return TRUE;
+    }
 
-               if ((len = strlen(diskname)) <= 20) {
-                   memset(desc + offset + len, ' ', 1);
-                   offset = offset + len + 1;
-               }
-               else{
-                   memset(desc + offset + 20, ' ', 1);
-                   offset = offset + 21;
-               }
+    /* We save the value here in case it changes while we're running. */
+    use_threads = g_thread_supported();
 
-               sprintf(desc + offset, "%i", level);
-
-               strncpy(vol_label, desc, 44);
-               fprintf(stderr, "taper: added vtbl label string %i: \"%s\"\n",
-                       filenum, vol_label);
-               fflush(stderr);
-
-               /* pass label string on to tape writer */
-               syncpipe_put('L');
-               syncpipe_putint(filenum);
-               syncpipe_putstr(vol_label);             
-
-               /* 
-                * reformat datestamp for later use with set_date from vtblc 
-                */
-               strptime(datestamp, "%Y%m%d", &backup_time);
-               strftime(vol_date, 20, "%T %D", &backup_time);
-               fprintf(stderr, 
-                       "taper: reformatted vtbl date string: \"%s\"->\"%s\"\n",
-                       datestamp,
-                       vol_date);
-
-               /* pass date string on to tape writer */                
-               syncpipe_put('D');
-               syncpipe_putint(filenum);
-               syncpipe_putstr(vol_date);
-
-#endif /* HAVE_LIBVTBLC */
-           }
-           return;
+    search_request.state = state;
+    search_request.prolong = TRUE;
+    search_request.errmsg = NULL;
+    if (use_threads) {
+        tape_search = g_thread_create(tape_search_thread,
+                                      &search_request, TRUE, NULL);
+    }
+    
+    putresult(REQUEST_NEW_TAPE, "%s\n", dump->handle);
+    cmdargs = getcmd();
+    cmd = cmdargs->cmd;
 
-       default:
-           assert(0);
-       }
+    switch (cmd) {
+    default:
+        g_fprintf(stderr, "taper: Got odd message from driver, expected NEW-TAPE or NO-NEW-TAPE.\n");
+        /* FALLTHROUGH. */
+    case NEW_TAPE: {
+        gboolean search_result;
+        if (use_threads) {
+            search_result = GPOINTER_TO_INT(g_thread_join(tape_search));
+        } else {
+            search_result =
+                GPOINTER_TO_INT(tape_search_thread(&search_request));
+        }
+        if (search_result) {
+            /* We don't say NEW_TAPE until we actually write the label. */
+           amfree(search_request.errmsg);
+           free_cmdargs(cmdargs);
+            return TRUE;
+        } else {
+            putresult(NO_NEW_TAPE, "%s\n", dump->handle);
+            log_taper_scan_errmsg(search_request.errmsg);
+           log_add(L_ERROR, "no-tape [%s]", "No more writable valid tape found");
+           free_cmdargs(cmdargs);
+            return FALSE;
+        }
+    }
+    case NO_NEW_TAPE:
+        search_request.prolong = FALSE;
+        if (use_threads) {
+            g_thread_join(tape_search);
+        }
+       log_add(L_ERROR, "no-tape [%s]", cmdargs->argv[1]);
+       state->last_errmsg = stralloc(cmdargs->argv[1]);
+       free_cmdargs(cmdargs);
+        return FALSE;
     }
+    /* NOTREACHED */
 }
 
-int taper_fill_buffer(fd, bp, buflen)
-int fd;
-buffer_t *bp;
-int buflen;
-{
-    char *curptr;
-    int spaceleft, cnt;
-
-    curptr = bp->buffer;
-    bp->size = 0;
-    spaceleft = buflen;
-
-    do {
-       cnt = read(fd, curptr, spaceleft);
-       switch(cnt) {
-       case 0: /* eof */
-           if(interactive) fputs("r0", stderr);
-           return bp->size;
-       case -1:        /* error on read, punt */
-           if(interactive) fputs("rE", stderr);
-           return -1;
-       default:
-           spaceleft -= cnt;
-           curptr += cnt;
-           bp->size += cnt;
-       }
-
-    } while(spaceleft > 0);
-
-    if(interactive) fputs("R", stderr);
-    return bp->size;
+/* Returns TRUE if the old volume details are not the same as the new ones. */
+static gboolean check_volume_changed(Device * device,
+                                     char * old_label, char * old_timestamp) {
+    /* If one is NULL and the other is not, something changed. */
+    if ((old_label == NULL) != (device->volume_label == NULL))
+        return TRUE;
+    if ((old_timestamp == NULL) != (device->volume_time == NULL))
+        return TRUE;
+    /* If details were not NULL and is now different, we have a difference. */
+    if (old_label != NULL && strcmp(old_label, device->volume_label) != 0)
+        return TRUE;
+    if (old_timestamp != NULL &&
+        strcmp(old_timestamp, device->volume_time) != 0)
+        return TRUE;
+
+    /* If we got here, everything is cool. */
+    return FALSE;
 }
 
-
-
-/*
- * ========================================================================
- * TAPE WRITER SIDE
- *
- */
-times_t idlewait, rdwait, wrwait, fmwait;
-long total_writes;
-double total_tape_used;
-int total_tape_fm;
-
-void write_file P((void));
-int write_buffer P((buffer_t *bp));
-
-void tape_writer_side(getp, putp)
-int getp, putp;
+static void
+update_tapelist(
+    taper_state_t *state)
 {
-    char tok;
-    int tape_started;
-    char *str;
-    char *hostname;
-    char *diskname;
-    char *datestamp;
-    int level;
-
-#ifdef HAVE_LIBVTBLC
-    char *vol_label;
-    char *vol_date;
-#endif /* HAVE_LIBVTBLC */
-
-    procname = "writer";
-    syncpipe_init(getp, putp);
-
-    tape_started = 0;
-    idlewait = times_zero;
-
-    while(1) {
-       startclock();
-       tok = syncpipe_get();
-       idlewait = timesadd(idlewait, stopclock());
-       if(tok != 'S' && tok != 'Q' && !tape_started) {
-           error("writer: token '%c' before start", tok);
-       }
-
-       switch(tok) {
-       case 'S':               /* start-tape */
-           if(tape_started) {
-               error("writer: multiple start requests");
-           }
-           str = syncpipe_getstr();
-           if(!first_tape(str ? str : "bad-datestamp")) {
-               if(tape_fd >= 0) {
-                   tapefd_close(tape_fd);
-                   tape_fd = -1;
-               }
-               syncpipe_put('E');
-               syncpipe_putstr(errstr);
-               /* wait for reader to acknowledge error */
-               do {
-                   tok = syncpipe_get();
-                   if(tok != 'e') {
-                       error("writer: got '%c' unexpectedly after error", tok);
-                   }
-               } while(tok != 'e');
-           } else {
-               syncpipe_put('S');
-               tape_started = 1;
-           }
-           amfree(str);
-
-           break;
-
-       case 'O':               /* open-output */
-           datestamp = syncpipe_getstr();
-           tapefd_setinfo_datestamp(tape_fd, datestamp);
-           amfree(datestamp);
-           hostname = syncpipe_getstr();
-           tapefd_setinfo_host(tape_fd, hostname);
-           amfree(hostname);
-           diskname = syncpipe_getstr();
-           tapefd_setinfo_disk(tape_fd, diskname);
-           amfree(diskname);
-           level = syncpipe_getint();
-           tapefd_setinfo_level(tape_fd, level);
-           write_file();
-           break;
-
-#ifdef HAVE_LIBVTBLC
-       case 'L':               /* read vtbl label */
-           vtbl_no = syncpipe_getint();
-           vol_label = syncpipe_getstr();
-           fprintf(stderr, "taper: read label string \"%s\" from pipe\n", 
-                   vol_label);
-           strncpy(vtbl_entry[vtbl_no].label, vol_label, 45);
-           break;
-
-       case 'D':               /* read vtbl date */
-           vtbl_no = syncpipe_getint();
-           vol_date = syncpipe_getstr();
-           fprintf(stderr, "taper: read date string \"%s\" from pipe\n", 
-                   vol_date);
-           strncpy(vtbl_entry[vtbl_no].date, vol_date, 20);
-           break;
-#endif /* HAVE_LIBVTBLC */
-
-       case 'Q':
-           end_tape(0);        /* XXX check results of end tape ?? */
-           clear_tapelist();
-           amfree(taper_datestamp);
-           amfree(label);
-           amfree(errstr);
-           amfree(changer_resultstr);
-           amfree(tapedev);
-           amfree(config_dir);
-           amfree(config_name);
-
-           malloc_size_2 = malloc_inuse(&malloc_hist_2);
-
-           if(malloc_size_1 != malloc_size_2) {
-               malloc_list(fileno(stderr), malloc_hist_1, malloc_hist_2);
-           }
-
-           exit(0);
+    char *tapelist_name = NULL;
+    char *tapelist_name_old = NULL;
+    tape_t *tp;
+    char *comment = NULL;
 
-       default:
-           assert(0);
-       }
+    tapelist_name = config_dir_relative(getconf_str(CNF_TAPELIST));
+    if (state->cur_tape == 0) {
+       tapelist_name_old = stralloc2(tapelist_name, ".yesterday");
+    } else {
+       char cur_str[NUM_STR_SIZE];
+       g_snprintf(cur_str, SIZEOF(cur_str), "%d", state->cur_tape - 1);
+       tapelist_name_old = vstralloc(tapelist_name,
+                                     ".today.", cur_str, NULL);
     }
-}
 
-void write_file()
-{
-    buffer_t *bp;
-    int full_buffers, i, bufnum;
-    char tok;
-    char number[NUM_STR_SIZE];
-    char *rdwait_str, *wrwait_str, *fmwait_str;
-
-    rdwait = wrwait = times_zero;
-    total_writes = 0;
-
-    bp = buftable;
-    full_buffers = 0;
-    tok = '?';
-
-    if(bufdebug) {
-       fprintf(stderr, "taper: w: start file\n");
-       fflush(stderr);
+   if (read_tapelist(tapelist_name) != 0) {
+        log_add(L_INFO, "pid-done %ld", (long)getpid());
+        error("could not load tapelist \"%s\"", tapelist_name);
+        /*NOTREACHED*/
     }
 
-    /*
-     * Tell the reader that the tape is open, and give it all the buffers.
-     */
-    syncpipe_put('O');
-    for(i = 0; i < conf_tapebufs; i++) {
-       if(bufdebug) {
-           fprintf(stderr, "taper: w: put R%d\n", i);
-           fflush(stderr);
-       }
-       syncpipe_put('R'); syncpipe_putint(i);
+    /* make a copy of the tapelist file */
+    if (write_tapelist(tapelist_name_old)) {
+        log_add(L_INFO, "pid-done %ld", (long)getpid());
+       error("could not write tapelist: %s", strerror(errno));
+       /*NOTREACHED*/
     }
-
-    /*
-     * We write the filemark at the start of the file rather than at the end,
-     * so that it can proceed in parallel with the reader's initial filling
-     * up of the buffers.
-     */
-
-    startclock();
-    if(!write_filemark())
-       goto tape_error;
-    fmwait = stopclock();
-
-    filenum += 1;
-
-    do {
-
-       /*
-        * STOPPED MODE
-        *
-        * At the start of the file, or if the input can't keep up with the
-        * tape, we enter STOPPED mode, which waits for most of the buffers
-        * to fill up before writing to tape.  This maximizes the amount of
-        * data written in chunks to the tape drive, minimizing the number
-        * of starts/stops, which in turn saves tape and time.
-        */
-
-       if(interactive) fputs("[WS]", stderr);
-       startclock();
-       while(full_buffers < conf_tapebufs - THRESHOLD) {
-           tok = syncpipe_get();
-           if(tok != 'W') break;
-           bufnum = syncpipe_getint();
-           if(bufdebug) {
-               fprintf(stderr,"taper: w: got W%d\n",bufnum);
-               fflush(stderr);
-           }
-           full_buffers++;
-       }
-       rdwait = timesadd(rdwait, stopclock());
-
-       /*
-        * STARTING MODE
-        *
-        * We start output when sufficient buffers have filled up, or at
-        * end-of-file, whichever comes first.  Here we drain all the buffers
-        * that were waited on in STOPPED mode.  If more full buffers come
-        * in, then we will be STREAMING.
-        */
-
-       while(full_buffers) {
-           if(tt_file_pad && bp->size < tt_blocksize) {
-               memset(bp->buffer+bp->size, 0, tt_blocksize - bp->size);
-               bp->size = tt_blocksize;
-           }
-           if(!write_buffer(bp)) goto tape_error;
-           full_buffers--;
-           bp = nextbuf(bp);
-       }
-
-       /*
-        * STREAMING MODE
-        *
-        * With any luck, the input source is faster than the tape drive.  In
-        * this case, full buffers will appear in the circular queue faster
-        * than we can write them, so the next buffer in the queue will always
-        * be marked FULL by the time we get to it.  If so, we'll stay in
-        * STREAMING mode.
-        *
-        * On the other hand, if we catch up to the input and thus would have
-        * to wait for buffers to fill, we are then STOPPED again.
-        */
-
-       while(tok == 'W' && bp->status == FULL) {
-           tok = syncpipe_get();
-           if(tok == 'W') {
-               bufnum = syncpipe_getint();
-               if(bufdebug) {
-                   fprintf(stderr,"taper: w: got W%d\n",bufnum);
-                   fflush(stderr);
-               }
-               if(bufnum != bp-buftable) {
-                   fprintf(stderr,
-                           "taper: tape-writer: my buf %d reader buf %d\n",
-                           (int)(bp-buftable), bufnum);
-                   fflush(stderr);
-                   syncpipe_put('E');
-                   syncpipe_putstr("writer-side buffer mismatch");
-                   goto error_ack;
-               }
-               if(tt_file_pad && bp->size < tt_blocksize) {
-                   memset(bp->buffer+bp->size, 0, tt_blocksize - bp->size);
-                   bp->size = tt_blocksize;
-               }
-               if(!write_buffer(bp)) goto tape_error;
-               bp = nextbuf(bp);
-           }
-           else if(tok == 'Q')
-               return;
-           else if(tok == 'X')
-               goto reader_buffer_snafu;
-           else
-               error("writer-side not expecting token: %c", tok);
-       }
-    } while(tok == 'W');
-
-    /* got close signal from reader, acknowledge it */
-
-    if(tok == 'X')
-       goto reader_buffer_snafu;
-
-    assert(tok == 'C');
-    syncpipe_put('C');
-
-    /* tell reader the tape and file number */
-
-    syncpipe_putstr(label);
-    ap_snprintf(number, sizeof(number), "%d", filenum);
-    syncpipe_putstr(number);
-
-    ap_snprintf(number, sizeof(number), "%ld", total_writes);
-    rdwait_str = stralloc(walltime_str(rdwait));
-    wrwait_str = stralloc(walltime_str(wrwait));
-    fmwait_str = stralloc(walltime_str(fmwait));
-    errstr = newvstralloc(errstr,
-                         "{wr:",
-                         " writers ", number,
-                         " rdwait ", rdwait_str,
-                         " wrwait ", wrwait_str,
-                         " filemark ", fmwait_str,
-                         "}",
-                         NULL);
-    amfree(rdwait_str);
-    amfree(wrwait_str);
-    amfree(fmwait_str);
-    syncpipe_putstr(errstr);
-
-    /* XXX go to next tape if past tape size? */
-
-    return;
-
- tape_error:
-    /* got tape error */
-    if(next_tape(1)) syncpipe_put('T');        /* next tape in place, try again */
-    else syncpipe_put('E');            /* no more tapes, fail */
-    syncpipe_putstr(errstr);
-
- error_ack:
-    /* wait for reader to acknowledge error */
-    do {
-       tok = syncpipe_get();
-       if(tok != 'W' && tok != 'C' && tok != 'e')
-           error("writer: got '%c' unexpectedly after error", tok);
-       if(tok == 'W')
-           syncpipe_getint();  /* eat buffer number */
-    } while(tok != 'e');
-    return;
-
- reader_buffer_snafu:
-    syncpipe_put('x');
-    return;
+    amfree(tapelist_name_old);
+
+    /* get a copy of the comment, before freeing the old record */
+    tp = lookup_tapelabel(state->device->volume_label);
+    if (tp && tp->comment)
+       comment = stralloc(tp->comment);
+
+    /* edit the tapelist and rewrite it */
+    remove_tapelabel(state->device->volume_label);
+    add_tapelabel(state->driver_start_time,
+                  state->device->volume_label,
+                 comment);
+    if (write_tapelist(tapelist_name)) {
+       error("could not write tapelist: %s", strerror(errno));
+       /*NOTREACHED*/
+    }
+    amfree(tapelist_name);
+    amfree(comment);
 }
 
-int write_buffer(bp)
-buffer_t *bp;
-{
-    int rc;
-
-    if(bp->status != FULL) {
-       /* XXX buffer management snafu */
-       assert(0);
+/* Find and label a new tape, if one is not already open. Returns TRUE
+ * if a tape could be written. */
+static gboolean find_and_label_new_tape(taper_state_t * state,
+                                        dump_info_t * dump_info) {
+    if (state->device != NULL) {
+        return TRUE;
     }
-
-    startclock();
-    rc = tapefd_write(tape_fd, bp->buffer, bp->size);
-    if(rc == bp->size) {
-#if defined(NEED_RESETOFS)
-       static double tape_used_modulus_2gb = 0;
-
-       /*
-        * If the next write will go over the 2 GByte boundary, reset
-        * the kernel concept of where we are to make sure it does not
-        * go silly on us.
-        */
-       tape_used_modulus_2gb += (double)rc;
-       if(tape_used_modulus_2gb + (double)rc > (double)0x7fffffff) {
-           tape_used_modulus_2gb = 0;
-           tapefd_resetofs(tape_fd);
-       }
-#endif
-       wrwait = timesadd(wrwait, stopclock());
-       total_writes += 1;
-       total_tape_used += (double)rc;
-       bp->status = EMPTY;
-       if(interactive || bufdebug) dumpstatus(bp);
-       if(interactive) fputs("W", stderr);
-
-       if(bufdebug) {
-           fprintf(stderr, "taper: w: put R%d\n", (int)(bp-buftable));
-           fflush(stderr);
-       }
-       syncpipe_put('R'); syncpipe_putint(bp-buftable);
-       return 1;
-    } else {
-       errstr = newvstralloc(errstr,
-                             "writing file: ",
-                             (rc != -1) ? "short write" : strerror(errno),
-                             NULL);
-       wrwait = timesadd(wrwait, stopclock());
-       if(interactive) fputs("[WE]", stderr);
-       return 0;
+    state->total_bytes = 0;
+    if (!find_new_tape(state, dump_info)) {
+        return FALSE;
     }
-}
-
-
-/*
- * ========================================================================
- * SHARED-MEMORY BUFFER SUBSYSTEM
- *
- */
 
-#ifdef HAVE_SYSVSHM
-
-int shmid = -1;
-
-char *attach_buffers(size)
-    unsigned int size;
-{
-    char *result;
+    return label_new_tape(state, dump_info);
+}
 
-    shmid = shmget(IPC_PRIVATE, size, IPC_CREAT|0700);
-    if(shmid == -1) {
-       return NULL;
+static gboolean label_new_tape(taper_state_t * state, dump_info_t * dump_info) {
+    char *old_volume_name = NULL;
+    char *old_volume_time = NULL;
+    tape_search_request_t request;
+    gboolean search_result;
+    DeviceStatusFlags status;
+
+    /* If we got here, it means that we have found a tape to label and
+     * have gotten permission from the driver to write it. But we
+     * still can say NO-NEW-TAPE if a problem shows up, and must still
+     * say NEW-TAPE if one doesn't. */
+
+    amfree(state->last_errmsg);
+    state->device = device_open(state->next_tape_device);
+    g_assert(state->device != NULL);
+    amfree(state->next_tape_device);
+
+    if (state->device->status != DEVICE_STATUS_SUCCESS)
+       goto skip_volume;
+
+    if (!device_configure(state->device, TRUE))
+       goto skip_volume;
+
+    /* if we have an error, and are sure it isn't just an unlabeled volume,
+     * then skip this volume */
+    status = device_read_label(state->device);
+    if ((status & ~DEVICE_STATUS_VOLUME_UNLABELED) &&
+       !(status & DEVICE_STATUS_VOLUME_UNLABELED))
+       goto skip_volume;
+
+    old_volume_name = g_strdup(state->device->volume_label);
+    old_volume_time = g_strdup(state->device->volume_time);
+
+    if (!device_start(state->device, ACCESS_WRITE, state->next_tape_label,
+                      state->driver_start_time)) {
+        gboolean tape_used;
+
+        /* Something broke, see if we can tell if the volume was erased or
+         * not. */
+        g_fprintf(stderr, "taper: Error writing label %s to device %s: %s.\n",
+                state->next_tape_label, state->device->device_name,
+               device_error_or_status(state->device));
+
+        if (!device_finish(state->device))
+           goto request_new_volume;
+
+       /* This time, if we can't read the label, assume we've overwritten
+        * the volume or otherwise corrupted it */
+       status = device_read_label(state->device);
+       if ((status & ~DEVICE_STATUS_VOLUME_UNLABELED) &&
+           !(status & DEVICE_STATUS_VOLUME_UNLABELED))
+           goto request_new_volume;
+
+        tape_used = check_volume_changed(state->device, old_volume_name, 
+                                         old_volume_time);
+
+        if (tape_used)
+           goto request_new_volume;
+        else
+           goto skip_volume;
     }
 
-    result = (char *)shmat(shmid, (SHM_ARG_TYPE *)NULL, 0);
+    amfree(old_volume_name);
+    amfree(old_volume_time);
+    amfree(state->next_tape_label);
 
-    if(result == (char *)-1) {
-       int save_errno = errno;
+    update_tapelist(state);
+    state->cur_tape++;
 
-       destroy_buffers();
-       errno = save_errno;
-       error("shmat: %s", strerror(errno));
+    if (state->have_changer &&
+       changer_label("UNKNOWN", state->device->volume_label) != 0) {
+       error(_("couldn't update barcode database"));
+       /*NOTREACHED*/
     }
 
-    return result;
-}
-
-
-void detach_buffers(bufp)
-    char *bufp;
-{
-    if(shmdt((SHM_ARG_TYPE *)bufp) == -1) {
-       error("shmdt: %s", strerror(errno));
+    log_add(L_START, "datestamp %s label %s tape %d",
+            state->driver_start_time, state->device->volume_label,
+            state->cur_tape);
+    putresult(NEW_TAPE, "%s %s\n", dump_info->handle,
+             state->device->volume_label);
+
+    return TRUE;
+
+request_new_volume:
+    /* Tell the driver we overwrote this volume, even if it was empty, and request
+     * a new volume. */
+    if (state->device)
+       state->last_errmsg = newstralloc(state->last_errmsg, device_error_or_status(state->device));
+    else
+       state->last_errmsg = newstralloc(state->last_errmsg, "(unknown)");
+
+    putresult(NEW_TAPE, "%s %s\n", dump_info->handle,
+             state->next_tape_label);
+    if (old_volume_name) {
+       log_add(L_WARNING, "Problem writing label '%s' to volume %s "
+               "(volume may be erased): %s\n",
+               state->next_tape_label, old_volume_name,
+               state->last_errmsg);
+    } else {
+       log_add(L_WARNING, "Problem writing label '%s' to new volume "
+               "(volume may be erased): %s\n", state->next_tape_label,
+               state->last_errmsg);
     }
-}
 
-void destroy_buffers()
-{
-    if(shmid == -1) return;    /* nothing to destroy */
-    if(shmctl(shmid, IPC_RMID, NULL) == -1) {
-       error("shmctl: %s", strerror(errno));
+    if (state->device) {
+        g_object_unref(state->device);
+        state->device = NULL;
     }
-}
 
-#else
-#ifdef HAVE_MMAP
+    amfree(state->next_tape_label);
+    amfree(old_volume_name);
+    amfree(old_volume_time);
 
-#ifdef HAVE_SYS_MMAN_H
-#include <sys/mman.h>
-#endif
+    return find_and_label_new_tape(state, dump_info);
 
-#ifndef MAP_ANON
-#  ifdef MAP_ANONYMOUS                 /* OSF/1-style */
-#    define MAP_ANON MAP_ANONYMOUS
-#  else                                        /* SunOS4-style */
-#    define MAP_ANON 0
-#    define ZERO_FILE "/dev/zero"
-#  endif
-#endif
-
-int shmfd = -1;
-unsigned int saved_size;
+skip_volume:
+    /* grab a new volume without talking to the driver again -- we do this if we're
+     * confident we didn't overwrite the last tape we got. */
+    if (state->device)
+       state->last_errmsg = newstralloc(state->last_errmsg, device_error_or_status(state->device));
+    else
+       state->last_errmsg = newstralloc(state->last_errmsg, "(unknown)");
 
-char *attach_buffers(size)
-    unsigned int size;
-{
-    char *shmbuf;
-
-#ifdef ZERO_FILE
-    shmfd = open(ZERO_FILE, O_RDWR);
-    if(shmfd == -1) {
-       error("attach_buffers: could not open %s: %s",
-             ZERO_FILE,
-             strerror(errno));
+    if (old_volume_name) {
+       log_add(L_WARNING, "Problem writing label '%s' to volume '%s' "
+               "(old volume data intact): %s\n",
+               state->next_tape_label, old_volume_name, state->last_errmsg);
+    } else {
+       log_add(L_WARNING, "Problem writing label '%s' to new volume "
+               "(old volume data intact): %s\n", state->next_tape_label,
+               state->last_errmsg);
     }
-#endif
-
-    saved_size = size;
-    shmbuf = (char *) mmap((void *) 0,
-                          size,
-                          PROT_READ|PROT_WRITE,
-                          MAP_ANON|MAP_SHARED,
-                          shmfd, 0);
 
-    return shmbuf;
-}
-
-void detach_buffers(bufp)
-char *bufp;
-{
-    if(munmap((void *)bufp, saved_size) == -1) {
-       error("detach_buffers: munmap: %s", strerror(errno));
+    if (state->device) {
+        g_object_unref(state->device);
+        state->device = NULL;
     }
 
-    aclose(shmfd);
-}
-
-void destroy_buffers()
-{
-}
-
-#else
-error: must define either HAVE_SYSVSHM or HAVE_MMAP!
-#endif
-#endif
-
-
-
-/*
- * ========================================================================
- * SYNC-PIPE SUBSYSTEM
- *
- */
-
-int getpipe, putpipe;
-
-void syncpipe_init(rd, wr)
-int rd, wr;
-{
-    getpipe = rd;
-    putpipe = wr;
+    amfree(state->next_tape_label);
+    amfree(old_volume_name);
+    amfree(old_volume_time);
+
+    request.state = state;
+    request.prolong = TRUE;
+    request.errmsg = NULL;
+    search_result = GPOINTER_TO_INT(tape_search_thread(&request));
+    if (search_result) {
+       amfree(request.errmsg);
+       return label_new_tape(state, dump_info);
+    } else {
+       /* Problem finding a new tape! */
+       log_taper_scan_errmsg(request.errmsg);
+       putresult(NO_NEW_TAPE, "%s\n", dump_info->handle);
+       return FALSE;
+    }
 }
 
-char syncpipe_get()
-{
-    int rc;
-    char buf[1];
-
-    rc = read(getpipe, buf, sizeof(buf));
-    if(rc == 0)                /* EOF */
-       error("syncpipe_get: %c: unexpected EOF", *procname);
-    else if(rc < 0)
-       error("syncpipe_get: %c: %s", *procname, strerror(errno));
-
-    if(bufdebug && *buf != 'R' && *buf != 'W') {
-       fprintf(stderr,"taper: %c: getc %c\n",*procname,*buf);
-       fflush(stderr);
+/* Find out if the dump is PARTIAL or not, and set the proper driver
+   and logfile tags for the dump. */
+static void find_completion_tags(dump_info_t * dump_info, /* IN */
+                                 cmd_t * result_cmd,      /* OUT */
+                                 logtype_t * result_log   /* OUT */) {
+    /* result_cmd is always DONE because the taper wrote all the input to
+     * the output. driver need to know if the taper completed its job.
+     * result_log is set to L_PARTIAL if the image is partial, the log
+     * must tell if the image is partial or complete.
+     */
+       
+    if (taper_source_is_partial(dump_info->source)) {
+        *result_cmd = DONE;
+        *result_log = L_PARTIAL;
+    } else {
+        *result_cmd = DONE;
+        *result_log = L_DONE;
     }
+}
 
-    return buf[0];
+/* Put an L_PARTIAL message to the logfile. */
+static void put_partial_log(dump_info_t * dump_info, double dump_time,
+                            guint64 dump_kbytes, char *errstr) {
+    char * qdiskname = quote_string(dump_info->diskname);
+
+    log_add(L_PARTIAL, "%s %s %s %d %d [sec %f kb %ju kps %f] %s",
+            dump_info->hostname, qdiskname, dump_info->timestamp,
+            dump_info->current_part, dump_info->level, dump_time,
+            (uintmax_t)dump_kbytes, dump_kbytes / dump_time,
+           errstr);
+    amfree(qdiskname);
 }
 
-int syncpipe_getint()
-{
-    int rc;
-    int i;
-    int len = sizeof(i);
-    char *p;
-
-    for(p = (char *)&i; len > 0; len -= rc, p += rc) {
-       if ((rc = read(getpipe, p, len)) <= 0) {
-           error("syncpipe_getint: %s",
-                 rc < 0 ? strerror(errno) : "short read");
+/* Figure out what to do after a part attempt. Returns TRUE if another
+   attempt should proceed for this dump; FALSE if we are done. */
+static gboolean finish_part_attempt(taper_state_t * taper_state,
+                                    dump_info_t * dump_info,
+                                    queue_result_flags queue_result,
+                                    GTimeVal run_time, guint64 run_bytes) {
+    double part_time = g_timeval_to_double(run_time);
+    guint64 part_kbytes = run_bytes / 1024;
+    double part_kbps = run_bytes / (1024 * part_time);
+        
+    char * qdiskname = quote_string(dump_info->diskname);
+
+    if (queue_result == QUEUE_SUCCESS) {
+        dump_info->total_time = timesadd(run_time, dump_info->total_time);
+        dump_info->total_bytes += run_bytes;
+
+        log_add(L_PART, "%s %d %s %s %s %d/%d %d [sec %f kb %ju kps %f]",
+                taper_state->device->volume_label,
+                taper_state->device->file, dump_info->hostname, qdiskname,
+                dump_info->timestamp, dump_info->current_part,
+                taper_source_predict_parts(dump_info->source),
+                dump_info->level, part_time, (uintmax_t)part_kbytes, part_kbps);
+        putresult(PARTDONE, "%s %s %d %ju \"[sec %f kb %ju kps %f]\"\n",
+                  dump_info->handle, taper_state->device->volume_label,
+                  taper_state->device->file, (uintmax_t)part_kbytes, part_time,
+                 (uintmax_t)part_kbytes, part_kbps);
+       taper_state->total_bytes += run_bytes;
+        
+        if (taper_source_get_end_of_data(dump_info->source)) {
+            cmd_t result_cmd;
+            logtype_t result_log;
+            double dump_time = g_timeval_to_double(dump_info->total_time);
+            guint64 dump_kbytes = dump_info->total_bytes / 1024;
+            double dump_kbps = dump_info->total_bytes / (1024 * dump_time);
+
+            find_completion_tags(dump_info, &result_cmd, &result_log);
+
+            g_object_unref(dump_info->source);
+            dump_info->source = NULL;
+        
+            log_add(result_log, "%s %s %s %d %d [sec %f kb %ju kps %f]",
+                    dump_info->hostname, qdiskname, dump_info->timestamp,
+                    dump_info->current_part, dump_info->level, dump_time,
+                   (uintmax_t)dump_kbytes, dump_kbps);
+            putresult(result_cmd, "%s INPUT-GOOD TAPE-GOOD "
+                      "\"[sec %f kb %ju kps %f]\" \"\" \"\"\n",
+                      dump_info->handle, dump_time, (uintmax_t)dump_kbytes,
+                      dump_kbps);
+            
+            amfree(qdiskname);
+            return FALSE;
+        } else if (taper_source_get_end_of_part(dump_info->source)) {
+            taper_source_start_new_part(dump_info->source);
+            dump_info->current_part ++;
+            amfree(qdiskname);
+            return TRUE;
+        }
+        /* If we didn't read EOF or EOP, then an error
+           occured. But we read QUEUE_SUCCESS, so something is
+           b0rked. */
+        g_assert_not_reached();
+    } else {
+        char * volume_label = strdup(taper_state->device->volume_label);
+        int file_number = taper_state->device->file;
+        double dump_time, dump_kbps;
+        guint64 dump_kbytes;
+       char *producer_errstr = quote_string(
+                                  taper_source_get_errmsg(dump_info->source));
+       char *consumer_errstr = quote_string(
+                                  device_error(taper_state->device));
+
+        log_add(L_PARTPARTIAL,
+                "%s %d %s %s %s %d/%d %d [sec %f kb %ju kps %f] %s",
+                volume_label, file_number, dump_info->hostname, qdiskname,
+                dump_info->timestamp, dump_info->current_part,
+                taper_source_predict_parts(dump_info->source),
+                dump_info->level, part_time, (uintmax_t)part_kbytes, part_kbps,
+               consumer_errstr);
+       log_add(L_INFO, "tape %s kb %lld fm %d [OK]\n",
+               volume_label,
+               (long long)((taper_state->total_bytes+(off_t)1023) / (off_t)1024),
+               taper_state->device->file);
+
+        /* A problem occured. */
+        if (queue_result & QUEUE_CONSUMER_ERROR) {
+           /* Make a note if this was EOM (we treat EOM the same as any other error,
+            * so it's just for debugging purposes */
+           if (taper_state->device->is_eof)
+               g_debug("device %s ran out of space", taper_state->device->device_name);
+
+            /* Close the device. */
+            device_finish(taper_state->device);
+            g_object_unref(taper_state->device);
+            taper_state->device = NULL;
+        }
+        
+        amfree(volume_label);
+        
+        if ((queue_result & QUEUE_CONSUMER_ERROR) &&
+            (!(queue_result & QUEUE_PRODUCER_ERROR)) &&
+            taper_source_seek_to_part_start(dump_info->source)) {
+            /* It is recoverable. */
+            log_add(L_INFO, "Will request retry of failed split part.");
+            if (find_and_label_new_tape(taper_state, dump_info)) {
+                /* dump_info->current_part is unchanged. */
+                amfree(qdiskname);
+                return TRUE;
+            }
+        }
+
+        dump_info->total_time = timesadd(run_time, dump_info->total_time);
+        dump_info->total_bytes += run_bytes;
+        dump_time = g_timeval_to_double(dump_info->total_time);
+        dump_kbytes = dump_info->total_bytes / 1024;
+        dump_kbps = dump_info->total_bytes / (1024 * dump_time);
+        
+        putresult(PARTIAL,
+                  "%s INPUT-%s TAPE-%s "
+                  "\"[sec %f kb %ju kps %f]\" %s %s\n",
+                  dump_info->handle,
+                  (queue_result & QUEUE_PRODUCER_ERROR) ? "ERROR" : "GOOD",
+                  (queue_result & QUEUE_CONSUMER_ERROR) ? "ERROR" : "GOOD",
+                  dump_time, (uintmax_t)dump_kbytes, dump_kbps,
+                 producer_errstr, consumer_errstr);
+       if (queue_result & QUEUE_CONSUMER_ERROR) {
+            put_partial_log(dump_info, dump_time, dump_kbytes,
+                           consumer_errstr);
+       } else {
+            put_partial_log(dump_info, dump_time, dump_kbytes,
+                           producer_errstr);
        }
+       amfree(producer_errstr);
+       amfree(consumer_errstr);
     }
 
-    return i;
+    amfree(qdiskname);
+    return FALSE;
 }
 
+/* Generate the actual header structure to write to tape. This means dropping
+ * bits related to the holding disk, and adding bits for split dumps. */
+static dumpfile_t * munge_headers(dump_info_t * dump_info) {
+    dumpfile_t * rval;
+    int expected_splits;
+    
+    rval = taper_source_get_first_header(dump_info->source);
 
-char *syncpipe_getstr()
-{
-    int rc;
-    int len;
-    char *p;
-    char *str;
-
-    if((len = syncpipe_getint()) <= 0) {
-       return NULL;
-    }
-
-    str = alloc(len);
-
-    for(p = str; len > 0; len -= rc, p += rc) {
-       if ((rc = read(getpipe, p, len)) <= 0) {
-           error("syncpipe_getstr: %s",
-                 rc < 0 ? strerror(errno) : "short read");
-       }
+    if (rval == NULL) {
+        return NULL;
     }
 
-    return str;
-}
-
+    rval->cont_filename[0] = '\0';
 
-void syncpipe_put(chi)
-int chi;
-{
-    int l, n, s;
-    char ch = chi;
-    char *item = &ch;
+    expected_splits = taper_source_predict_parts(dump_info->source);
 
-    if(bufdebug && chi != 'R' && chi != 'W') {
-       fprintf(stderr,"taper: %c: putc %c\n",*procname,chi);
-       fflush(stderr);
+    if (expected_splits != 1) {
+        rval->type = F_SPLIT_DUMPFILE;
+        rval->partnum = dump_info->current_part;
+        rval->totalparts = expected_splits;
     }
 
-    for(l = 0, n = sizeof(ch); l < n; l += s) {
-       if((s = write(putpipe, item + l, n - l)) < 0) {
-           error("syncpipe_put: %s", strerror(errno));
-       }
-    }
+    return rval;
 }
 
-void syncpipe_putint(i)
-int i;
+/* We call this when we can't find a tape to write data to. This could
+   happen with the first (or only) part of a file, but it could also
+   happen with an intermediate part of a split dump. dump_bytes
+   is 0 if this is the first part of a dump. */
+static void bail_no_volume(
+    dump_info_t *dump_info,
+    char *errmsg)
 {
-    int l, n, s;
-    char *item = (char *)&i;
-
-    for(l = 0, n = sizeof(i); l < n; l += s) {
-       if((s = write(putpipe, item + l, n - l)) < 0) {
-           error("syncpipe_putint: %s", strerror(errno));
-       }
+    char *errstr;
+    if (errmsg)
+       errstr = quote_string(errmsg);
+    else
+       errstr = quote_string("no new tape");
+    if (dump_info->total_bytes > 0) {
+        /* Second or later part of a split dump, so PARTIAL message. */
+        double dump_time = g_timeval_to_double(dump_info->total_time);
+        guint64 dump_kbytes = dump_info->total_bytes / 1024;
+        double dump_kbps = dump_kbytes / dump_time;
+        putresult(PARTIAL,
+                  "%s INPUT-GOOD TAPE-ERROR "
+                  "\"[sec %f kb %ju kps %f]\" \"\" %s\n",
+                  dump_info->handle, 
+                  dump_time, (uintmax_t)dump_kbytes, dump_kbps, errstr);
+        put_partial_log(dump_info, dump_time, dump_kbytes, errstr);
+    } else {
+        char * qdiskname = quote_string(dump_info->diskname);
+        putresult(FAILED,
+                  "%s INPUT-GOOD TAPE-ERROR \"\" %s\n",
+                  dump_info->handle, errstr);
+        log_add(L_FAIL, "%s %s %s %d %s",
+                dump_info->hostname, qdiskname, dump_info->timestamp,
+                dump_info->level, errstr);
+       amfree(qdiskname);
     }
+    amfree(errstr);
 }
 
-void syncpipe_putstr(item)
-char *item;
-{
-    int l, n, s;
-
-    n = strlen(item)+1;                                /* send '\0' as well */
-    syncpipe_putint(n);
-    for(l = 0, n = strlen(item)+1; l < n; l += s) {
-       if((s = write(putpipe, item + l, n - l)) < 0) {
-           error("syncpipe_putstr: %s", strerror(errno));
-       }
+/* Link up the TaperSource with the Device, including retries etc. */
+static void run_device_output(taper_state_t * taper_state,
+                              dump_info_t * dump_info) {
+    GValue val;
+    guint file_number;
+    dump_info->current_part = 1;
+    dump_info->total_time.tv_sec = 0;
+    dump_info->total_time.tv_usec = 0;
+    dump_info->total_bytes = 0;
+
+    for (;;) {
+        GTimeVal start_time, end_time, run_time;
+        StreamingRequirement streaming_mode;
+        queue_result_flags queue_result;
+        CountingConsumerData consumer_data;
+        dumpfile_t *this_header;
+        size_t max_memory;
+        
+        this_header = munge_headers(dump_info);
+        if (this_header == NULL) {
+            char * qdiskname = quote_string(dump_info->diskname);
+           char * errstr = taper_source_get_errmsg(dump_info->source);
+           if (!errstr)
+               errstr = "Failed reading dump header.";
+           errstr = quote_string(errstr);
+            putresult(FAILED,
+             "%s INPUT-ERROR TAPE-GOOD %s \"\"\n",
+                      dump_info->handle, errstr);
+            log_add(L_FAIL, "%s %s %s %d %s",
+                    dump_info->hostname, qdiskname, dump_info->timestamp,
+                    dump_info->level, errstr);
+            amfree(qdiskname);
+           amfree(errstr);
+            return;
+        }            
+
+        if (!find_and_label_new_tape(taper_state, dump_info)) {
+            bail_no_volume(dump_info, taper_state->last_errmsg);
+           dumpfile_free(this_header);
+            return;
+        }
+
+       while (!device_start_file(taper_state->device, this_header)) {
+            /* Close the device. */
+            device_finish(taper_state->device);
+            g_object_unref(taper_state->device);
+            taper_state->device = NULL;
+
+            if (!find_and_label_new_tape(taper_state, dump_info)) {
+               bail_no_volume(dump_info, taper_state->last_errmsg);
+               dumpfile_free(this_header);
+               return;
+            }
+        }
+       dumpfile_free(this_header);
+
+        bzero(&val, sizeof(val));
+        if (!device_property_get(taper_state->device, PROPERTY_STREAMING, &val)
+            || !G_VALUE_HOLDS(&val, STREAMING_REQUIREMENT_TYPE)) {
+            g_fprintf(stderr, "taper: Couldn't get streaming type!\n");
+            streaming_mode = STREAMING_REQUIREMENT_REQUIRED;
+        } else {
+            streaming_mode = g_value_get_enum(&val);
+        }
+    
+        file_number = taper_state->device->file;
+
+        consumer_data.next_consumer = device_write_consumer;
+        consumer_data.next_consumer_data = taper_state->device;
+        consumer_data.bytes_written = 0;
+
+        g_get_current_time(&start_time);
+
+        if (getconf_seen(CNF_DEVICE_OUTPUT_BUFFER_SIZE)) {
+            max_memory = getconf_size(CNF_DEVICE_OUTPUT_BUFFER_SIZE);
+            if (getconf_seen(CNF_TAPEBUFS)) {
+                g_fprintf(stderr,
+                        "Configuration directives 'device_output_buffer_size' "
+                        "and \n"
+                        "'tapebufs' are incompatible; using former.\n");
+            }
+        } else if (getconf_seen(CNF_TAPEBUFS)) {
+            max_memory = getconf_int(CNF_TAPEBUFS) *
+                taper_state->device->block_size;
+        } else {
+            /* Use default. */
+            max_memory = getconf_size(CNF_DEVICE_OUTPUT_BUFFER_SIZE);
+        }
+
+        queue_result = do_consumer_producer_queue_full
+            (taper_source_producer,
+             dump_info->source,
+             counting_consumer,
+             &consumer_data,
+             taper_state->device->block_size, max_memory,
+             streaming_mode);
+
+        g_get_current_time(&end_time);
+        run_time = timesub(end_time, start_time);
+
+        /* The device_write_consumer leaves the file open, so close it now. */
+        if (!device_finish_file(taper_state->device)) {
+            queue_result = queue_result | QUEUE_CONSUMER_ERROR;
+        }
+
+        if (!finish_part_attempt(taper_state, dump_info, queue_result,
+                                 run_time, consumer_data.bytes_written)) {
+            break;
+        }
     }
 }
 
-\f
-/*
- * ========================================================================
- * TAPE MANIPULATION SUBSYSTEM
- *
- */
-
-/* local functions */
-int scan_init P((int rc, int ns, int bk));
-int taperscan_slot P((int rc, char *slotstr, char *device));
-char *taper_scan P((void));
-int label_tape P((void));
-
-int label_tape()
-{
-    char *conf_tapelist_old = NULL;
-    char *olddatestamp = NULL;
-    char *result;
-    tape_t *tp;
-    static int first_call = 1;
-
-    if(have_changer) {
-       amfree(tapedev);
-       if ((tapedev = taper_scan()) == NULL) {
-           errstr = newstralloc(errstr, changer_resultstr);
-           return 0;
-       }
-    }
-
-#ifdef HAVE_LINUX_ZFTAPE_H
-    if (is_zftape(tapedev) == 1){
-       if((tape_fd = tape_open(tapedev, O_RDONLY)) == -1) {
-           errstr = newstralloc2(errstr, "taper: ",
-                                 (errno == EACCES) ? "tape is write-protected"
-                                 : strerror(errno));
-           return 0;
-       }
-       if((result = tapefd_rdlabel(tape_fd, &olddatestamp, &label)) != NULL) {
-           amfree(olddatestamp);
-           errstr = newstralloc(errstr, result);
-           return 0;
-       }
-       if(tapefd_rewind(tape_fd) == -1) { 
-           return 0;
-       } 
-       tapefd_close(tape_fd);
-       tape_fd = -1;
+/* Handle a PORT_WRITE command. */
+static void process_port_write(taper_state_t * state,
+                               struct cmdargs * cmdargs) {
+    dump_info_t dump_state;
+    guint64 splitsize;
+    guint64 fallback_splitsize;
+    char * split_diskbuffer;
+    char * argnames[] = {"command",               /* 0 */
+                        "handle",                /* 1 */
+                         "hostname",              /* 2 */
+                         "diskname",              /* 3 */
+                         "level",                 /* 4 */
+                         "datestamp",             /* 5 */
+                         "splitsize",             /* 6 */
+                         "split_diskbuffer",      /* 7 */
+                         "fallback_splitsize",    /* 8 */
+                          NULL };
+
+    validate_args(cmdargs, argnames);
+
+    dump_state.handle = g_strdup(cmdargs->argv[1]);
+    dump_state.hostname = g_strdup(cmdargs->argv[2]);
+    dump_state.diskname = g_strdup(cmdargs->argv[3]);
+    
+    errno = 0;
+    dump_state.level = strtol(cmdargs->argv[4], NULL, 10);
+    if (errno != 0) {
+        error("error [taper PORT-WRITE: Invalid dump level %s]",
+              cmdargs->argv[4]);
+        g_assert_not_reached();
     }
-    else
-#endif /* !HAVE_LINUX_ZFTAPE_H */
-    if((result = tape_rdlabel(tapedev, &olddatestamp, &label)) != NULL) {
-       amfree(olddatestamp);
-       errstr = newstralloc(errstr, result);
-       return 0;
+    
+    dump_state.timestamp = strdup(cmdargs->argv[5]);
+
+    errno = 0;
+    splitsize = g_ascii_strtoull(cmdargs->argv[6], NULL, 10);
+    if (errno != 0) {
+        error("error [taper PORT-WRITE: Invalid splitsize %s]",
+              cmdargs->argv[6]);
+        g_assert_not_reached();
     }
-
-    fprintf(stderr, "taper: read label `%s' date `%s'\n", label, olddatestamp);
-    fflush(stderr);
-    amfree(olddatestamp);
-
-    /* check against tape list */
-    if (strcmp(label, FAKE_LABEL) != 0) {
-       tp = lookup_tapelabel(label);
-       if(tp == NULL) {
-           errstr = newvstralloc(errstr,
-                                 "label ", label,
-               " match labelstr but it not listed in the tapelist file",
-                                 NULL);
-           return 0;
-       }
-       else if(tp != NULL && !reusable_tape(tp)) {
-           errstr = newvstralloc(errstr,
-                                 "cannot overwrite active tape ", label,
-                                 NULL);
-           return 0;
-       }
-
-       if(!match(labelstr, label)) {
-           errstr = newvstralloc(errstr,
-                                 "label ", label,
-                                 " doesn\'t match labelstr \"", labelstr, "\"",
-                                 NULL);
-           return 0;
-       }
+    
+    if (strcmp(cmdargs->argv[7], "NULL") == 0) {
+        split_diskbuffer = NULL;
+    } else {
+        split_diskbuffer = g_strdup(cmdargs->argv[7]);
     }
-
-    if((tape_fd = tape_open(tapedev, O_WRONLY)) == -1) {
-       if(errno == EACCES) {
-           errstr = newstralloc(errstr,
-                                "writing label: tape is write protected");
-       } else {
-           errstr = newstralloc2(errstr,
-                                 "writing label: ", strerror(errno));
-       }
-       return 0;
+    
+    errno = 0;
+    fallback_splitsize = g_ascii_strtoull(cmdargs->argv[8], NULL, 10);
+    if (errno != 0) {
+        error("error [taper PORT-WRITE: Invalid fallback_splitsize %s]",
+              cmdargs->argv[8]);
+        g_assert_not_reached();
     }
 
-    tapefd_setinfo_length(tape_fd, tt->length);
-
-    tapefd_setinfo_datestamp(tape_fd, taper_datestamp);
-    tapefd_setinfo_disk(tape_fd, label);
-    result = tapefd_wrlabel(tape_fd, taper_datestamp, label, tt_blocksize);
-    if(result != NULL) {
-       errstr = newstralloc(errstr, result);
-       return 0;
+    dump_state.id_string = g_strdup_printf("%s:%s.%d", dump_state.hostname,
+                                           dump_state.diskname,
+                                          dump_state.level);
+    
+    if (!open_read_socket(&dump_state, split_diskbuffer, splitsize,
+                          fallback_splitsize)) {
+        free(split_diskbuffer);
+        return;
     }
+    free(split_diskbuffer);
 
-    fprintf(stderr, "taper: wrote label `%s' date `%s'\n", label, taper_datestamp);
-    fflush(stderr);
-
-#ifdef HAVE_LIBVTBLC
-    /* store time for the first volume entry */ 
-    time(&raw_time);
-    tape_timep = localtime(&raw_time);
-    strftime(start_datestr, 20, "%T %D", tape_timep);
-    fprintf(stderr, "taper: got vtbl start time: %s\n", start_datestr);
-    fflush(stderr);
-#endif /* HAVE_LIBVTBLC */
-
-    /* write tape list */
-
-    /* XXX add cur_tape number to tape list structure */
-    if (strcmp(label, FAKE_LABEL) != 0) {
-
-       if(cur_tape == 0) {
-           conf_tapelist_old = stralloc2(conf_tapelist, ".yesterday");
-       } else {
-           char cur_str[NUM_STR_SIZE];
+    run_device_output(state, &dump_state);
 
-           ap_snprintf(cur_str, sizeof(cur_str), "%d", cur_tape - 1);
-           conf_tapelist_old = vstralloc(conf_tapelist,
-                                       ".today.", cur_str, NULL);
-       }
-       if(write_tapelist(conf_tapelist_old)) {
-           error("could not write tapelist: %s", strerror(errno));
-       }
-       amfree(conf_tapelist_old);
+    free_dump_info(&dump_state);
+}
 
-       remove_tapelabel(label);
-       add_tapelabel(atoi(taper_datestamp), label);
-       if(write_tapelist(conf_tapelist)) {
-           error("could not write tapelist: %s", strerror(errno));
-       }
+/* Handle a FILE_WRITE command. */
+static void process_file_write(taper_state_t * state,
+                               struct cmdargs * cmdargs) {
+    dump_info_t dump_state;
+    char * holding_disk_file;
+    guint64 splitsize;
+    char * argnames[] = {"command",               /* 0 */
+                        "handle",                /* 1 */
+                         "filename",              /* 2 */
+                         "hostname",              /* 3 */
+                         "diskname",              /* 4 */
+                         "level",                 /* 5 */
+                         "datestamp",             /* 6 */
+                         "splitsize",             /* 7 */
+                          NULL };
+
+    validate_args(cmdargs, argnames);
+
+    dump_state.handle = g_strdup(cmdargs->argv[1]);
+    holding_disk_file = g_strdup(cmdargs->argv[2]);
+    dump_state.hostname = g_strdup(cmdargs->argv[3]);
+    dump_state.diskname = g_strdup(cmdargs->argv[4]);
+    
+    errno = 0;
+    dump_state.level = strtol(cmdargs->argv[5], NULL, 10);
+    if (errno != 0) {
+        error("error [taper FILE-WRITE: Invalid dump level %s]",
+              cmdargs->argv[5]);
+        g_assert_not_reached();
     }
-
-    log_add(L_START, "datestamp %s label %s tape %d",
-           taper_datestamp, label, cur_tape);
-    if (first_call && strcmp(label, FAKE_LABEL) == 0) {
-       first_call = 0;
-       log_add(L_WARNING, "tapedev is %s, dumps will be thrown away", tapedev);
+    
+    dump_state.timestamp = strdup(cmdargs->argv[6]);
+
+    errno = 0;
+    splitsize = g_ascii_strtoull(cmdargs->argv[7], NULL, 10);
+    if (errno != 0) {
+        error("error [taper FILE-WRITE: Invalid splitsize %s]",
+              cmdargs->argv[7]);
+        g_assert_not_reached();
     }
 
-    total_tape_used=0.0;
-    total_tape_fm = 0;
-
-    return 1;
-}
-
-int first_tape(new_datestamp)
-char *new_datestamp;
-{
-    if((have_changer = changer_init()) < 0) {
-       error("changer initialization failed: %s", strerror(errno));
-    }
-    changer_debug = 1;
+    dump_state.id_string = g_strdup_printf("%s:%s.%d", dump_state.hostname,
+                                           dump_state.diskname,
+                                          dump_state.level);
+    
+    dump_state.source = taper_source_new(dump_state.handle, FILE_WRITE,
+                                         holding_disk_file, -1,
+                                         NULL, splitsize, -1);
+    /* FIXME: This should be handled properly. */
+    g_assert(dump_state.source != NULL);
 
-    taper_datestamp = newstralloc(taper_datestamp, new_datestamp);
+    run_device_output(state, &dump_state);
 
-    if(!label_tape())
-       return 0;
+    free_dump_info(&dump_state);
+    amfree(holding_disk_file);
+}
 
-    filenum = 0;
-    return 1;
+/* Send QUITTING message to driver and associated logging. Always
+   returns false. */
+static gboolean send_quitting(taper_state_t * state) {
+    putresult(QUITTING, "\n");
+    g_fprintf(stderr,"taper: DONE\n");
+    cleanup(state);
+    return FALSE;
 }
 
-int next_tape(writerror)
-int writerror;
-{
-    end_tape(writerror);
+/* This function recieves the START_TAPER command from driver, and
+   returns the attached timestamp. */
+static gboolean find_first_tape(taper_state_t * state) {
+    struct cmdargs *cmdargs;
+    tape_search_request_t search_request;
+    GThread * tape_search = NULL;
+    gboolean use_threads;
 
-    if(++cur_tape >= runtapes)
-       return 0;
+    /* We save the value here in case it changes while we're running. */
+    use_threads = g_thread_supported();
 
-    if(!label_tape()) {
-       return 0;
+    search_request.state = state;
+    search_request.prolong = TRUE;
+    search_request.errmsg = NULL;
+    
+    if (use_threads) {
+        tape_search = g_thread_create(tape_search_thread,
+                                      &search_request, TRUE, NULL);
     }
 
-    filenum = 0;
-    return 1;
-}
-
-
-int end_tape(writerror)
-int writerror;
-{
-    char *result;
-    int rc = 0;
-
-    if(tape_fd >= 0) {
-       log_add(L_INFO, "tape %s kb %ld fm %d %s", 
-               label,
-               (long) ((total_tape_used+1023.0) / 1024.0),
-               total_tape_fm,
-               writerror? errstr : "[OK]");
-
-       fprintf(stderr, "taper: writing end marker. [%s %s kb %ld fm %d]\n",
-               label,
-               writerror? "ERR" : "OK",
-               (long) ((total_tape_used+1023.0) / 1024.0),
-               total_tape_fm);
-       fflush(stderr);
-       if(! writerror) {
-           if(! write_filemark()) {
-               rc = 1;
-               goto common_exit;
-           }
-
-           result = tapefd_wrendmark(tape_fd, taper_datestamp, tt_blocksize);
-           if(result != NULL) {
-               errstr = newstralloc(errstr, result);
-               rc = 1;
-               goto common_exit;
+    cmdargs = getcmd();
+
+    switch (cmdargs->cmd) {
+    case START_TAPER: {
+        gboolean search_result;
+        state->driver_start_time = strdup(cmdargs->argv[1]);
+        if (use_threads) {
+            search_result = GPOINTER_TO_INT(g_thread_join(tape_search));
+        } else {
+            search_result =
+                GPOINTER_TO_INT(tape_search_thread(&search_request));
+        }
+        if (search_result) {
+            putresult(TAPER_OK, "\n");
+        } else {
+            putresult(TAPE_ERROR, "Could not find a tape to use.\n");
+           log_add(L_ERROR, "no-tape [%s]", "Could not find a tape to use");
+           if (search_request.errmsg != NULL) {
+               char *c, *c1;
+               c = c1 = search_request.errmsg;
+               while (*c != '\0') {
+                   if (*c == '\n') {
+                       *c = '\0';
+                       log_add(L_WARNING,"%s", c1);
+                       c1 = c+1;
+                   }
+                   c++;
+               }
+               if (strlen(c1) > 1 )
+                   log_add(L_WARNING,"%s", c1);
            }
-       }
+        }
+       amfree(search_request.errmsg);
+       free_cmdargs(cmdargs);
+        return TRUE;
+    }
+    case QUIT:
+        search_request.prolong = FALSE;
+        if (use_threads) {
+            g_thread_join(tape_search);
+        }
+       free_cmdargs(cmdargs);
+        return send_quitting(state);
+    default:
+        error("error [file_reader_side cmd %d argc %d]", cmdargs->cmd, cmdargs->argc);
     }
 
-#ifdef HAVE_LINUX_ZFTAPE_H
-    if (tape_fd >= 0 && is_zftape(tapedev) == 1) {
-       /* rewind the tape */
+    g_assert_not_reached();
+}
 
-       if(tapefd_rewind(tape_fd) == -1 ) {
-           errstr = newstralloc2(errstr, "rewinding tape: ", strerror(errno));
-           rc = 1;
-           goto common_exit;
+/* In running mode (not startup mode), get a command from driver and
+   deal with it. */
+static gboolean process_driver_command(taper_state_t * state) {
+    struct cmdargs *cmdargs;
+    char * q;
+
+    /* This will return QUIT if driver has died. */
+    cmdargs = getcmd();
+    switch (cmdargs->cmd) {
+    case PORT_WRITE:
+        /*
+         * PORT-WRITE
+         *   handle
+         *   hostname
+         *   features
+         *   diskname
+         *   level
+         *   datestamp
+         *   splitsize
+         *   split_diskbuffer
+         */
+        process_port_write(state, cmdargs);
+        break;
+        
+    case FILE_WRITE:
+        /*
+         * FILE-WRITE
+         *   handle
+         *   filename
+         *   hostname
+         *   features
+         *   diskname
+         *   level
+         *   datestamp
+         *   splitsize
+         */
+        process_file_write(state, cmdargs);
+        break;
+        
+    case QUIT:
+       free_cmdargs(cmdargs);
+       if (state->device && state->device->volume_label) {
+           log_add(L_INFO, "tape %s kb %lld fm %d [OK]\n",
+                   state->device->volume_label,
+                   (long long)((state->total_bytes+(off_t)1023) / (off_t)1024),
+                   state->device->file);
        }
-       /* close the tape */
+        return send_quitting(state);
+    default:
+        if (cmdargs->argc >= 1) {
+            q = quote_string(cmdargs->argv[0]);
+        } else {
+            q = stralloc("(no input?)");
+        }
+        putresult(BAD_COMMAND, "%s\n", q);
+        amfree(q);
+        break;
+    }
+    free_cmdargs(cmdargs);
 
-       if(tapefd_close(tape_fd) == -1) {
-           errstr = newstralloc2(errstr, "closing tape: ", strerror(errno));
-           rc = 1;
-           goto common_exit;
-       }
-       tape_fd = -1;
+    return TRUE;
+}
 
-#ifdef HAVE_LIBVTBLC
-       /* update volume table */
-       fprintf(stderr, "taper: updating volume table ...\n");
-       fflush(stderr);
-    
-       if ((tape_fd = raw_tape_open(rawtapedev, O_RDWR)) == -1) {
-           if(errno == EACCES) {
-               errstr = newstralloc(errstr,
-                                    "updating volume table: tape is write protected");
-           } else {
-               errstr = newstralloc2(errstr,
-                                     "updating volume table: ", 
-                                     strerror(errno));
-           }
-           rc = 1;
-           goto common_exit;
-       }
-       /* read volume table */
-       if ((num_volumes = read_vtbl(tape_fd, volumes, vtbl_buffer,
-                                    &first_seg, &last_seg)) == -1 ) {
-           errstr = newstralloc2(errstr,
-                                 "reading volume table: ", 
-                                 strerror(errno));
-           rc = 1;
-           goto common_exit;
-       }
-       /* set volume label and date for first entry */
-       vtbl_no = 0;
-       if(set_label(label, volumes, num_volumes, vtbl_no)){
-           errstr = newstralloc2(errstr,
-                                 "setting label for entry 1: ",
-                                 strerror(errno));
-           rc = 1;
-           goto common_exit;
-       }
-       /* date of start writing this tape */
-       if (set_date(start_datestr, volumes, num_volumes, vtbl_no)){
-           errstr = newstralloc2(errstr,
-                                 "setting date for entry 1: ", 
-                                 strerror(errno));
-           rc = 1;
-           goto common_exit;
-       }
-       /* set volume labels and dates for backup files */
-       for (vtbl_no = 1; vtbl_no <= num_volumes - 2; vtbl_no++){ 
-           fprintf(stderr,"taper: label %i: %s, date %s\n", 
-                   vtbl_no,
-                   vtbl_entry[vtbl_no].label,
-                   vtbl_entry[vtbl_no].date);
-           fflush(stderr);
-           if(set_label(vtbl_entry[vtbl_no].label, 
-                        volumes, num_volumes, vtbl_no)){
-               errstr = newstralloc2(errstr,
-                                     "setting label for entry i: ", 
-                                     strerror(errno));
-               rc = 1;
-               goto common_exit;
-           }
-           if(set_date(vtbl_entry[vtbl_no].date, 
-                       volumes, num_volumes, vtbl_no)){
-               errstr = newstralloc2(errstr,
-                                     "setting date for entry i: ",
-                                     strerror(errno));
-               rc = 1;
-               goto common_exit;
-           }
-       }
-       /* set volume label and date for last entry */
-       vtbl_no = num_volumes - 1;
-       if(set_label("AMANDA Tape End", volumes, num_volumes, vtbl_no)){
-           errstr = newstralloc2(errstr,
-                                 "setting label for last entry: ", 
-                                 strerror(errno));
-           rc = 1;
-           goto common_exit;
-       }
-       datestr = NULL; /* take current time */ 
-       if (set_date(datestr, volumes, num_volumes, vtbl_no)){
-           errstr = newstralloc2(errstr,
-                                 "setting date for last entry 1: ", 
-                                 strerror(errno));
-           rc = 1;
-           goto common_exit;
-       }
-       /* write volume table back */
-       if (write_vtbl(tape_fd, volumes, vtbl_buffer, num_volumes, first_seg,
-                      op_mode == trunc)) {
-           errstr = newstralloc2(errstr,
-                                 "writing volume table: ", 
-                                 strerror(errno));
-           rc = 1;
-           goto common_exit;
-       }  
-
-       fprintf(stderr, "taper: updating volume table: done.\n");
-       fflush(stderr);
-#endif /* HAVE_LIBVTBLC */
-    }
-#endif /* !HAVE_LINUX_ZFTAPE_H */
+int main(int argc, char ** argv) {
+    char * tapelist_name;
+    taper_state_t state;
+    config_overwrites_t *cfg_ovr = NULL;
+    char *cfg_opt = NULL;
 
-    /* close the tape and let the OS write the final filemarks */
+    /*
+     * Configure program for internationalization:
+     *   1) Only set the message locale for now.
+     *   2) Set textdomain for all amanda related programs to "amanda"
+     *      We don't want to be forced to support dozens of message catalogs.
+     */
+    setlocale(LC_MESSAGES, "C");
+    textdomain("amanda");
+    
+    safe_fd(-1, 0);
+    set_pname("taper");
 
-common_exit:
+    dbopen("server");
 
-    if(tape_fd >= 0 && tapefd_close(tape_fd) == -1 && ! writerror) {
-       errstr = newstralloc2(errstr, "closing tape: ", strerror(errno));
-       rc = 1;
-    }
-    tape_fd = -1;
-    amfree(label);
+    device_api_init();
+    init_taper_state(&state);
 
-    return rc;
-}
+    /* Don't die when child closes pipe */
+    signal(SIGPIPE, SIG_IGN);
 
+    g_fprintf(stderr, _("%s: pid %ld executable %s version %s\n"),
+           get_pname(), (long) getpid(), argv[0], version());
+    dbprintf(_("%s: pid %ld executable %s version %s\n"),
+              get_pname(), (long) getpid(), argv[0], version());
 
-int write_filemark()
-{
-    if(tapefd_weof(tape_fd, 1) == -1) {
-       errstr = newstralloc2(errstr, "writing filemark: ", strerror(errno));
-       return 0;
-    }
-    total_tape_fm++;
-    return 1;
-}
+    /* Process options */
 
+    cfg_ovr = extract_commandline_config_overwrites(&argc, &argv);
 
-/*
- * ========================================================================
- * TAPE CHANGER SCAN
- *
- */
-int nslots, backwards, found, got_match, tapedays;
-char *first_match_label = NULL, *first_match = NULL, *found_device = NULL;
-char *searchlabel, *labelstr;
-tape_t *tp;
+    if(argc > 2) {
+        error("Too many arguments!\n");
+        g_assert_not_reached();
+    }
+    if (argc > 1)
+       cfg_opt = argv[1];
+    config_init(CONFIG_INIT_EXPLICIT_NAME | CONFIG_INIT_USE_CWD, cfg_opt);
+    apply_config_overwrites(cfg_ovr);
 
-int scan_init(rc, ns, bk)
-int rc, ns, bk;
-{
-    if(rc) {
-       fprintf(stderr, "%s: could not get changer info: %s\n",
-               get_pname(), changer_resultstr);
-       return rc;
+    if (config_errors(NULL) >= CFGERR_ERRORS) {
+       g_critical(_("errors processing config file"));
     }
 
-    nslots = ns;
-    backwards = bk;
+    safe_cd();
 
-    return 0;
-}
+    set_logerror(logerror);
 
-int taperscan_slot(rc, slotstr, device)
-     int rc;
-     char *slotstr;
-     char *device;
-{
-    char *t_errstr;
-    char *scan_datestamp = NULL;
-
-    if(rc == 2) {
-       fprintf(stderr, "%s: fatal slot %s: %s\n",
-               get_pname(), slotstr, changer_resultstr);
-       fflush(stderr);
-       return 1;
-    }
-    else if(rc == 1) {
-       fprintf(stderr, "%s: slot %s: %s\n", get_pname(),
-               slotstr, changer_resultstr);
-       fflush(stderr);
-       return 0;
-    }
-    else {
-       if((t_errstr = tape_rdlabel(device, &scan_datestamp, &label)) != NULL) {
-           amfree(scan_datestamp);
-           fprintf(stderr, "%s: slot %s: %s\n",
-                   get_pname(), slotstr, t_errstr);
-           fflush(stderr);
-       }
-       else {
-           /* got an amanda tape */
-           fprintf(stderr, "%s: slot %s: date %-8s label %s",
-                   get_pname(), slotstr, scan_datestamp, label);
-           fflush(stderr);
-           amfree(scan_datestamp);
-           if(searchlabel != NULL
-              && (strcmp(label, FAKE_LABEL) == 0
-                  || strcmp(label, searchlabel) == 0)) {
-               /* it's the one we are looking for, stop here */
-               fprintf(stderr, " (exact label match)\n");
-               fflush(stderr);
-               found_device = newstralloc(found_device, device);
-               found = 1;
-               return 1;
-           }
-           else if(!match(labelstr, label)) {
-               fprintf(stderr, " (no match)\n");
-               fflush(stderr);
-           }
-           else {
-               /* not an exact label match, but a labelstr match */
-               /* check against tape list */
-               tp = lookup_tapelabel(label);
-               if(tp == NULL) {
-                   fprintf(stderr, "(not in tapelist)\n");
-                   fflush(stderr);
-               }
-               else if(!reusable_tape(tp)) {
-                   fprintf(stderr, " (active tape)\n");
-                   fflush(stderr);
-               }
-               else if(got_match == 0 && tp->datestamp == 0) {
-                   got_match = 1;
-                   first_match = newstralloc(first_match, slotstr);
-                   first_match_label = newstralloc(first_match_label, label);
-                   fprintf(stderr, " (new tape)\n");
-                   fflush(stderr);
-                   found = 3;
-                   found_device = newstralloc(found_device, device);
-                   return 1;
-               }
-               else if(got_match) {
-                   fprintf(stderr, " (labelstr match)\n");
-                   fflush(stderr);
-               }
-               else {
-                   got_match = 1;
-                   first_match = newstralloc(first_match, slotstr);
-                   first_match_label = newstralloc(first_match_label, label);
-                   fprintf(stderr, " (first labelstr match)\n");
-                   fflush(stderr);
-                   if(!backwards || !searchlabel) {
-                       found = 2;
-                       found_device = newstralloc(found_device, device);
-                       return 1;
-                   }
-               }
-           }
-       }
-    }
-    return 0;
-}
+    check_running_as(RUNNING_AS_DUMPUSER);
 
-char *taper_scan()
-{
-    char *outslot = NULL;
+    dbrename(get_config_name(), DBG_SUBDIR_SERVER);
 
-    if((tp = lookup_last_reusable_tape(0)) == NULL)
-       searchlabel = NULL;
-    else
-       searchlabel = tp->label;
+    log_add(L_INFO, "%s pid %ld", get_pname(), (long)getpid());
 
-    found = 0;
-    got_match = 0;
+    tapelist_name = config_dir_relative(getconf_str(CNF_TAPELIST));
 
-    if (searchlabel != NULL)
-      changer_find(scan_init, taperscan_slot, searchlabel);
-    else
-      changer_scan(scan_init, taperscan_slot);
-
-    if(found == 2 || found == 3)
-       searchlabel = first_match_label;
-    else if(!found && got_match) {
-       searchlabel = first_match_label;
-       amfree(found_device);
-       if(changer_loadslot(first_match, &outslot, &found_device) == 0) {
-           found = 1;
-       }
-       amfree(outslot);
+    if (read_tapelist(tapelist_name) != 0) {
+       log_add(L_INFO, "pid-done %ld", (long)getpid());
+        error("could not load tapelist \"%s\"", tapelist_name);
+        g_assert_not_reached();
     }
-    else if(!found) {
-       if(searchlabel) {
-           changer_resultstr = newvstralloc(changer_resultstr,
-                                            "label ", searchlabel,
-                                            " or new tape not found in rack",
-                                            NULL);
-       } else {
-           changer_resultstr = newstralloc(changer_resultstr,
-                                           "new tape not found in rack");
-       }
+    amfree(tapelist_name);
+
+    state.have_changer = changer_init();
+    if (state.have_changer < 0) {
+       log_add(L_INFO, "pid-done %ld", (long)getpid());
+        error("changer initialization failed: %s", strerror(errno));
+        g_assert_not_reached();
     }
 
-    if(found) {
-       outslot = found_device;
-       found_device = NULL;            /* forget about our copy */
-    } else {
-       outslot = NULL;
-       amfree(found_device);
+    state.next_tape_label = NULL;
+    state.next_tape_device = NULL;
+    state.cur_tape = 0;
+    
+    if (!find_first_tape(&state)) {
+       log_add(L_INFO, "pid-done %ld", (long)getpid());
+        return EXIT_SUCCESS;
     }
-    return outslot;
+
+    while (process_driver_command(&state));
+    log_add(L_INFO, "pid-done %ld", (long)getpid());
+    return EXIT_SUCCESS;
 }