Imported Upstream version 2.5.0
[debian/amanda] / server-src / chunker.c
diff --git a/server-src/chunker.c b/server-src/chunker.c
new file mode 100644 (file)
index 0000000..fc783ba
--- /dev/null
@@ -0,0 +1,826 @@
+/*
+ * Amanda, The Advanced Maryland Automatic Network Disk Archiver
+ * Copyright (c) 1991-1999 University of Maryland at College Park
+ * All Rights Reserved.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of U.M. not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  U.M. makes no representations about the
+ * suitability of this software for any purpose.  It is provided "as is"
+ * without express or implied warranty.
+ *
+ * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
+ * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors: the Amanda Development Team.  Its members are listed in a
+ * file named AUTHORS, in the root directory of this distribution.
+ */
+/* $Id: chunker.c,v 1.25 2006/03/21 13:23:35 martinea Exp $
+ *
+ * requests remote amandad processes to dump filesystems
+ */
+#include "amanda.h"
+#include "arglist.h"
+#include "clock.h"
+#include "conffile.h"
+#include "event.h"
+#include "logfile.h"
+#include "packet.h"
+#include "protocol.h"
+#include "security.h"
+#include "stream.h"
+#include "token.h"
+#include "version.h"
+#include "fileheader.h"
+#include "amfeatures.h"
+#include "server_util.h"
+#include "util.h"
+#include "holding.h"
+
+#ifndef SEEK_SET
+#define SEEK_SET 0
+#endif
+
+#ifndef SEEK_CUR
+#define SEEK_CUR 1
+#endif
+
+#define CONNECT_TIMEOUT        5*60
+
+#define STARTUP_TIMEOUT 60
+
+struct databuf {
+    int fd;                    /* file to flush to */
+    char *filename;            /* name of what fd points to */
+    int filename_seq;          /* for chunking */
+    long split_size;           /* when to chunk */
+    long chunk_size;           /* size of each chunk */
+    long use;                  /* size to use on this disk */
+    char buf[DISK_BLOCK_BYTES];
+    char *datain;              /* data buffer markers */
+    char *dataout;
+    char *datalimit;
+};
+
+static char *handle = NULL;
+
+static char *errstr = NULL;
+static int abort_pending;
+static long dumpsize, headersize;
+static long dumpbytes;
+static long filesize;
+
+static char *hostname = NULL;
+static char *diskname = NULL;
+static char *options = NULL;
+static char *progname = NULL;
+static int level;
+static char *dumpdate = NULL;
+static char *datestamp;
+static int command_in_transit;
+
+static dumpfile_t file;
+
+/* local functions */
+int main P((int, char **));
+static int write_tapeheader P((int, dumpfile_t *));
+static void databuf_init P((struct databuf *, int, char *, long, long));
+static int databuf_flush P((struct databuf *));
+
+static int startup_chunker P((char *, long, long, struct databuf *));
+static int do_chunk P((int, struct databuf *));
+
+
+int
+main(main_argc, main_argv)
+    int main_argc;
+    char **main_argv;
+{
+    static struct databuf db;
+    struct cmdargs cmdargs;
+    cmd_t cmd;
+    int infd;
+    unsigned long malloc_hist_1, malloc_size_1;
+    unsigned long malloc_hist_2, malloc_size_2;
+    char *conffile;
+    char *q = NULL;
+    char *filename;
+    long chunksize, use;
+    times_t runtime;
+    am_feature_t *their_features = NULL;
+    int a;
+
+    safe_fd(-1, 0);
+
+    set_pname("chunker");
+
+    /* Don't die when child closes pipe */
+    signal(SIGPIPE, SIG_IGN);
+
+    malloc_size_1 = malloc_inuse(&malloc_hist_1);
+
+    erroutput_type = (ERR_AMANDALOG|ERR_INTERACTIVE);
+    set_logerror(logerror);
+
+    if (main_argc > 1) {
+       config_name = stralloc(main_argv[1]);
+       config_dir = vstralloc(CONFIG_DIR, "/", config_name, "/", NULL);
+    } 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);
+       }
+    }
+
+    safe_cd();
+
+    conffile = stralloc2(config_dir, CONFFILE_NAME);
+    if(read_conffile(conffile)) {
+       error("errors processing config file \"%s\"", conffile);
+    }
+    amfree(conffile);
+
+    fprintf(stderr,
+           "%s: pid %ld executable %s version %s\n",
+           get_pname(), (long) getpid(),
+           main_argv[0], version());
+    fflush(stderr);
+
+    /* now, make sure we are a valid user */
+
+    if (getpwuid(getuid()) == NULL)
+       error("can't get login name for my uid %ld", (long)getuid());
+
+    signal(SIGPIPE, SIG_IGN);
+    signal(SIGCHLD, SIG_IGN);
+
+    datestamp = construct_datestamp(NULL);
+
+/*    do {*/
+       cmd = getcmd(&cmdargs);
+
+       switch(cmd) {
+       case QUIT:
+           break;
+
+       case PORT_WRITE:
+           /*
+            * PORT-WRITE
+            *   handle
+            *   filename
+            *   host
+            *   features
+            *   disk
+            *   level
+            *   dumpdate
+            *   chunksize
+            *   progname
+            *   use
+            *   options
+            */
+           cmdargs.argc++;                     /* true count of args */
+           a = 2;
+
+           if(a >= cmdargs.argc) {
+               error("error [chunker PORT-WRITE: not enough args: handle]");
+           }
+           handle = newstralloc(handle, cmdargs.argv[a++]);
+
+           if(a >= cmdargs.argc) {
+               error("error [chunker PORT-WRITE: not enough args: filename]");
+           }
+           filename = cmdargs.argv[a++];
+
+           if(a >= cmdargs.argc) {
+               error("error [chunker PORT-WRITE: not enough args: hostname]");
+           }
+           hostname = newstralloc(hostname, cmdargs.argv[a++]);
+
+           if(a >= cmdargs.argc) {
+               error("error [chunker 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 [chunker PORT-WRITE: not enough args: diskname]");
+           }
+           diskname = newstralloc(diskname, cmdargs.argv[a++]);
+
+           if(a >= cmdargs.argc) {
+               error("error [chunker PORT-WRITE: not enough args: level]");
+           }
+           level = atoi(cmdargs.argv[a++]);
+
+           if(a >= cmdargs.argc) {
+               error("error [chunker PORT-WRITE: not enough args: dumpdate]");
+           }
+           dumpdate = newstralloc(dumpdate, cmdargs.argv[a++]);
+
+           if(a >= cmdargs.argc) {
+               error("error [chunker PORT-WRITE: not enough args: chunksize]");
+           }
+           chunksize = atoi(cmdargs.argv[a++]);
+           chunksize = am_floor(chunksize, DISK_BLOCK_KB);
+
+           if(a >= cmdargs.argc) {
+               error("error [chunker PORT-WRITE: not enough args: progname]");
+           }
+           progname = newstralloc(progname, cmdargs.argv[a++]);
+
+           if(a >= cmdargs.argc) {
+               error("error [chunker PORT-WRITE: not enough args: use]");
+           }
+           use = am_floor(atoi(cmdargs.argv[a++]), DISK_BLOCK_KB);
+
+           if(a >= cmdargs.argc) {
+               error("error [chunker PORT-WRITE: not enough args: options]");
+           }
+           options = newstralloc(options, cmdargs.argv[a++]);
+
+           if(a != cmdargs.argc) {
+               error("error [chunker PORT-WRITE: too many args: %d != %d]",
+                     cmdargs.argc, a);
+           }
+
+           if((infd = startup_chunker(filename, use, chunksize, &db)) < 0) {
+               q = squotef("[chunker startup failed: %s]", errstr);
+               putresult(TRYAGAIN, "%s %s\n", handle, q);
+               error("startup_chunker failed");
+           }
+           command_in_transit = -1;
+           if(infd >= 0 && do_chunk(infd, &db)) {
+               char kb_str[NUM_STR_SIZE];
+               char kps_str[NUM_STR_SIZE];
+               double rt;
+
+               runtime = stopclock();
+               rt = runtime.r.tv_sec+runtime.r.tv_usec/1000000.0;
+               snprintf(kb_str, sizeof(kb_str), "%ld", dumpsize - headersize);
+               snprintf(kps_str, sizeof(kps_str), "%3.1f",
+                               rt ? dumpsize / rt : 0.0);
+               errstr = newvstralloc(errstr,
+                                     "sec ", walltime_str(runtime),
+                                     " kb ", kb_str,
+                                     " kps ", kps_str,
+                                     NULL);
+               q = squotef("[%s]", errstr);
+               if(command_in_transit != -1)
+                   cmd = command_in_transit;
+               else
+                   cmd = getcmd(&cmdargs);
+               switch(cmd) {
+               case DONE:
+                   putresult(DONE, "%s %ld %s\n",
+                             handle, dumpsize - headersize, q);
+                   log_add(L_SUCCESS, "%s %s %s %d [%s]",
+                           hostname, diskname, datestamp, level, errstr);
+                   break;
+               case BOGUS:
+               case TRYAGAIN:
+               case FAILED:
+               case ABORT_FINISHED:
+                   if(dumpsize > DISK_BLOCK_KB) {
+                       putresult(PARTIAL, "%s %ld %s\n",
+                                 handle, dumpsize - headersize, q);
+                       log_add(L_PARTIAL, "%s %s %s %d [%s]",
+                               hostname, diskname, datestamp, level, errstr);
+                   }
+                   else {
+                       errstr = newvstralloc(errstr,
+                                             "dumper returned ",
+                                             cmdstr[cmd],
+                                             NULL);
+                       amfree(q);
+                       q = squotef("[%s]",errstr);
+                       putresult(FAILED, "%s %s\n", handle, q);
+                       log_add(L_FAIL, "%s %s %s %d [%s]",
+                               hostname, diskname, datestamp, level, errstr);
+                   }
+               default: break;
+               }
+               amfree(q);
+           } else if(infd != -2) {
+               if(!abort_pending) {
+                   if(q == NULL) {
+                       q = squotef("[%s]", errstr);
+                   }
+                   putresult(FAILED, "%s %s\n", handle, q);
+                   log_add(L_FAIL, "%s %s %s %d [%s]",
+                           hostname, diskname, datestamp, level, errstr);
+                   amfree(q);
+               }
+           }
+           break;
+
+       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);
+           amfree(q);
+           break;
+       }
+
+/*    } while(cmd != QUIT); */
+
+    amfree(errstr);
+    amfree(datestamp);
+    amfree(handle);
+    amfree(hostname);
+    amfree(diskname);
+    amfree(dumpdate);
+    amfree(progname);
+    amfree(options);
+    amfree(config_dir);
+    amfree(config_name);
+    am_release_feature_set(their_features);
+    their_features = NULL;
+
+    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);
+}
+
+/*
+ * Returns a file descriptor to the incoming port
+ * on success, or -1 on error.
+ */
+static int
+startup_chunker(filename, use, chunksize, db)
+    char *filename;
+    long use;
+    long chunksize;
+    struct databuf *db;
+{
+    int infd, outfd;
+    char *tmp_filename, *pc;
+    int data_port, data_socket;
+
+    data_port = 0;
+    data_socket = stream_server(&data_port, -1, STREAM_BUFSIZE);
+
+    if(data_socket < 0) {
+       errstr = stralloc2("error creating stream server: ", strerror(errno));
+       return -1;
+    }
+
+    putresult(PORT, "%d\n", data_port);
+
+    infd = stream_accept(data_socket, CONNECT_TIMEOUT, -1, NETWORK_BLOCK_BYTES);
+    if(infd == -1) {
+       errstr = stralloc2("error accepting stream: ", strerror(errno));
+       return -1;
+    }
+
+    tmp_filename = vstralloc(filename, ".tmp", NULL);
+    pc = strrchr(tmp_filename, '/');
+    *pc = '\0';
+    mkholdingdir(tmp_filename);
+    *pc = '/';
+    if ((outfd = open(tmp_filename, O_RDWR|O_CREAT|O_TRUNC, 0600)) < 0) {
+       int save_errno = errno;
+
+       errstr = squotef("holding file \"%s\": %s",
+                        tmp_filename,
+                        strerror(errno));
+       amfree(tmp_filename);
+       aclose(infd);
+       if(save_errno == ENOSPC) {
+           putresult(NO_ROOM, "%s %lu", handle, use);
+           return -2;
+       } else {
+           return -1;
+       }
+    }
+    amfree(tmp_filename);
+    databuf_init(db, outfd, filename, use, chunksize);
+    db->filename_seq++;
+    return infd;
+}
+
+static int
+do_chunk(infd, db)
+    int infd;
+    struct databuf *db;
+{
+    int nread;
+    char header_buf[DISK_BLOCK_BYTES];
+
+    startclock();
+
+    dumpsize = headersize = dumpbytes = filesize = 0;
+
+    /*
+     * The first thing we should receive is the file header, which we
+     * need to save into "file", as well as write out.  Later, the
+     * chunk code will rewrite it.
+     */
+    nread = fullread(infd, header_buf, sizeof(header_buf));
+    if (nread != DISK_BLOCK_BYTES) {
+       char number1[NUM_STR_SIZE];
+       char number2[NUM_STR_SIZE];
+
+       if(nread < 0) {
+           errstr = stralloc2("cannot read header: ", strerror(errno));
+       } else {
+           snprintf(number1, sizeof(number1), "%d", nread);
+           snprintf(number2, sizeof(number2), "%d", DISK_BLOCK_BYTES);
+           errstr = vstralloc("cannot read header: got ",
+                              number1,
+                              " instead of ",
+                              number2,
+                              NULL);
+       }
+       return 0;
+    }
+    parse_file_header(header_buf, &file, nread);
+    if(write_tapeheader(db->fd, &file)) {
+       int save_errno = errno;
+
+       errstr = squotef("write_tapeheader file \"%s\": %s",
+                        db->filename, strerror(errno));
+       if(save_errno == ENOSPC) {
+           putresult(NO_ROOM, "%s %lu\n", handle, 
+                     db->use+db->split_size-dumpsize);
+       }
+       return 0;
+    }
+    dumpsize += DISK_BLOCK_KB;
+    filesize = DISK_BLOCK_KB;
+    headersize += DISK_BLOCK_KB;
+
+    /*
+     * We've written the file header.  Now, just write data until the
+     * end.
+     */
+    while ((nread = fullread(infd, db->buf, db->datalimit - db->datain)) > 0) {
+       db->datain += nread;
+       while(db->dataout < db->datain) {
+           if(!databuf_flush(db)) {
+               return 0;
+           }
+       }
+    }
+    while(db->dataout < db->datain) {
+       if(!databuf_flush(db)) {
+           return 0;
+       }
+    }
+    if(dumpbytes > 0) {
+       dumpsize++;                     /* count partial final KByte */
+       filesize++;
+    }
+    return 1;
+}
+
+/*
+ * Initialize a databuf.  Takes a writeable file descriptor.
+ */
+static void
+databuf_init(db, fd, filename, use, chunk_size)
+    struct databuf *db;
+    int fd;
+    char *filename;
+    long use;
+    long chunk_size;
+{
+    db->fd = fd;
+    db->filename = stralloc(filename);
+    db->filename_seq = 0;
+    db->chunk_size = chunk_size;
+    db->split_size = (db->chunk_size > use) ? use : db->chunk_size;
+    db->use = (use>db->split_size) ? use - db->split_size : 0;
+    db->datain = db->dataout = db->buf;
+    db->datalimit = db->buf + sizeof(db->buf);
+}
+
+
+/*
+ * Write out the buffer to the backing file
+ */
+static int
+databuf_flush(db)
+    struct databuf *db;
+{
+    struct cmdargs cmdargs;
+    int rc = 1;
+    int written;
+    long left_in_chunk;
+    char *arg_filename = NULL;
+    char *new_filename = NULL;
+    char *tmp_filename = NULL;
+    char sequence[NUM_STR_SIZE];
+    int newfd;
+    filetype_t save_type;
+    char *q;
+    int a;
+    char *pc;
+
+    /*
+     * If there's no data, do nothing.
+     */
+    if (db->dataout >= db->datain) {
+       goto common_exit;
+    }
+
+    /*
+     * See if we need to split this file.
+     */
+    while (db->split_size > 0 && dumpsize >= db->split_size) {
+       if( db->use == 0 ) {
+           /*
+            * Probably no more space on this disk.  Request some more.
+            */
+           cmd_t cmd;
+
+           putresult(RQ_MORE_DISK, "%s\n", handle);
+           cmd = getcmd(&cmdargs);
+           if(command_in_transit == -1 &&
+              (cmd == DONE || cmd == TRYAGAIN || cmd == FAILED)) {
+               command_in_transit = cmd;
+               cmd = getcmd(&cmdargs);
+           }
+           if(cmd == CONTINUE) {
+               /*
+                * CONTINUE
+                *   serial
+                *   filename
+                *   chunksize
+                *   use
+                */
+               cmdargs.argc++;                 /* true count of args */
+               a = 3;
+
+               if(a >= cmdargs.argc) {
+                   error("error [chunker CONTINUE: not enough args: filename]");
+               }
+               arg_filename = newstralloc(arg_filename, cmdargs.argv[a++]);
+
+               if(a >= cmdargs.argc) {
+                   error("error [chunker CONTINUE: not enough args: chunksize]");
+               }
+               db->chunk_size = atoi(cmdargs.argv[a++]);
+               db->chunk_size = am_floor(db->chunk_size, DISK_BLOCK_KB);
+
+               if(a >= cmdargs.argc) {
+                   error("error [chunker CONTINUE: not enough args: use]");
+               }
+               db->use = atoi(cmdargs.argv[a++]);
+
+               if(a != cmdargs.argc) {
+                   error("error [chunker CONTINUE: too many args: %d != %d]",
+                         cmdargs.argc, a);
+               }
+
+               if(strcmp(db->filename, arg_filename) == 0) {
+                   /*
+                    * Same disk, so use what room is left up to the
+                    * next chunk boundary or the amount we were given,
+                    * whichever is less.
+                    */
+                   left_in_chunk = db->chunk_size - filesize;
+                   if(left_in_chunk > db->use) {
+                       db->split_size += db->use;
+                       db->use = 0;
+                   } else {
+                       db->split_size += left_in_chunk;
+                       db->use -= left_in_chunk;
+                   }
+                   if(left_in_chunk > 0) {
+                       /*
+                        * We still have space in this chunk.
+                        */
+                       break;
+                   }
+               } else {
+                   /*
+                    * Different disk, so use new file.
+                    */
+                   db->filename = newstralloc(db->filename, arg_filename);
+               }
+           } else if(cmd == ABORT) {
+               abort_pending = 1;
+               errstr = newstralloc(errstr, "ERROR");
+               putresult(ABORT_FINISHED, "%s\n", handle);
+               rc = 0;
+               goto common_exit;
+           } else {
+               if(cmdargs.argc >= 1) {
+                   q = squote(cmdargs.argv[1]);
+               } else if(cmdargs.argc >= 0) {
+                   q = squote(cmdargs.argv[0]);
+               } else {
+                   q = stralloc("(no input?)");
+               }
+               error("error [bad command after RQ-MORE-DISK: \"%s\"]", q);
+           }
+       }
+
+       /*
+        * Time to use another file.
+        */
+
+       /*
+        * First, open the new chunk file, and give it a new header
+        * that has no cont_filename pointer.
+        */
+       snprintf(sequence, sizeof(sequence), "%d", db->filename_seq);
+       new_filename = newvstralloc(new_filename,
+                                   db->filename,
+                                   ".",
+                                   sequence,
+                                   NULL);
+       tmp_filename = newvstralloc(tmp_filename,
+                                   new_filename,
+                                   ".tmp",
+                                   NULL);
+       pc = strrchr(tmp_filename, '/');
+       *pc = '\0';
+       mkholdingdir(tmp_filename);
+       *pc = '/';
+       newfd = open(tmp_filename, O_RDWR|O_CREAT|O_TRUNC, 0600);
+       if (newfd == -1) {
+           int save_errno = errno;
+
+           if(save_errno == ENOSPC) {
+               putresult(NO_ROOM, "%s %lu\n",
+                         handle, 
+                         db->use+db->split_size-dumpsize);
+               db->use = 0;                    /* force RQ_MORE_DISK */
+               db->split_size = dumpsize;
+               continue;
+           }
+           errstr = squotef("creating chunk holding file \"%s\": %s",
+                            tmp_filename,
+                            strerror(errno));
+           aclose(db->fd);
+           rc = 0;
+           goto common_exit;
+       }
+       save_type = file.type;
+       file.type = F_CONT_DUMPFILE;
+       file.cont_filename[0] = '\0';
+       if(write_tapeheader(newfd, &file)) {
+           int save_errno = errno;
+
+           aclose(newfd);
+           if(save_errno == ENOSPC) {
+               putresult(NO_ROOM, "%s %lu\n",
+                         handle, 
+                         db->use+db->split_size-dumpsize);
+               db->use = 0;                    /* force RQ_MORE DISK */
+               db->split_size = dumpsize;
+               continue;
+           }
+           errstr = squotef("write_tapeheader file \"%s\": %s",
+                            tmp_filename,
+                            strerror(errno));
+           rc = 0;
+           goto common_exit;
+       }
+
+       /*
+        * Now, update the header of the current file to point
+        * to the next chunk, and then close it.
+        */
+       if (lseek(db->fd, (off_t)0, SEEK_SET) < 0) {
+           errstr = squotef("lseek holding file \"%s\": %s",
+                            db->filename,
+                            strerror(errno));
+           aclose(newfd);
+           rc = 0;
+           goto common_exit;
+       }
+
+       file.type = save_type;
+       strncpy(file.cont_filename, new_filename, sizeof(file.cont_filename));
+       file.cont_filename[sizeof(file.cont_filename)] = '\0';
+       if(write_tapeheader(db->fd, &file)) {
+           errstr = squotef("write_tapeheader file \"%s\": %s",
+                            db->filename,
+                            strerror(errno));
+           aclose(newfd);
+           unlink(tmp_filename);
+           rc = 0;
+           goto common_exit;
+       }
+       file.type = F_CONT_DUMPFILE;
+
+       /*
+        * Now shift the file descriptor.
+        */
+       aclose(db->fd);
+       db->fd = newfd;
+       newfd = -1;
+
+       /*
+        * Update when we need to chunk again
+        */
+       if(db->use <= DISK_BLOCK_KB) {
+           /*
+            * Cheat and use one more block than allowed so we can make
+            * some progress.
+            */
+           db->split_size += 2 * DISK_BLOCK_KB;
+           db->use = 0;
+       } else if(db->chunk_size > db->use) {
+           db->split_size += db->use;
+           db->use = 0;
+       } else {
+           db->split_size += db->chunk_size;
+           db->use -= db->chunk_size;
+       }
+
+
+       amfree(tmp_filename);
+       amfree(new_filename);
+       dumpsize += DISK_BLOCK_KB;
+       filesize = DISK_BLOCK_KB;
+       headersize += DISK_BLOCK_KB;
+       db->filename_seq++;
+    }
+
+    /*
+     * Write out the buffer
+     */
+    written = fullwrite(db->fd, db->dataout, db->datain - db->dataout);
+    if (written > 0) {
+       db->dataout += written;
+       dumpbytes += written;
+    }
+    dumpsize += (dumpbytes / 1024);
+    filesize += (dumpbytes / 1024);
+    dumpbytes %= 1024;
+    if (written < 0) {
+       if (errno != ENOSPC) {
+           errstr = squotef("data write: %s", strerror(errno));
+           rc = 0;
+           goto common_exit;
+       }
+
+       /*
+        * NO-ROOM is informational only.  Later, RQ_MORE_DISK will be
+        * issued to use another holding disk.
+        */
+       putresult(NO_ROOM, "%s %lu\n", handle, db->use+db->split_size-dumpsize);
+       db->use = 0;                            /* force RQ_MORE_DISK */
+       db->split_size = dumpsize;
+       goto common_exit;
+    }
+    if (db->datain == db->dataout) {
+       /*
+        * We flushed the whole buffer so reset to use it all.
+        */
+       db->datain = db->dataout = db->buf;
+    }
+
+common_exit:
+
+    amfree(new_filename);
+    amfree(tmp_filename);
+    amfree(arg_filename);
+    return rc;
+}
+
+
+/*
+ * Send an Amanda dump header to the output file.
+ */
+static int
+write_tapeheader(outfd, file)
+    int outfd;
+    dumpfile_t *file;
+{
+    char buffer[DISK_BLOCK_BYTES];
+    int written;
+
+    file->blocksize = DISK_BLOCK_BYTES;
+    build_header(buffer, file, sizeof(buffer));
+
+    written = fullwrite(outfd, buffer, sizeof(buffer));
+    if(written == sizeof(buffer)) return 0;
+    if(written < 0) return written;
+    errno = ENOSPC;
+    return -1;
+}