Imported Upstream version 3.2.0
[debian/amanda] / server-src / chunker.c
index 8d89fd74d08da3e70c9735e9d13d4c5b85e80aaa..bbf8676653fe4f2b747b5e93d8d7a93b2196a316 100644 (file)
 #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"
+#include "timestamp.h"
 
 #ifndef SEEK_SET
 #define SEEK_SET 0
@@ -86,7 +85,7 @@ static char *options = NULL;
 static char *progname = NULL;
 static int level;
 static char *dumpdate = NULL;
-static int command_in_transit;
+static struct cmdargs *command_in_transit = NULL;
 static char *chunker_timestamp = NULL;
 
 static dumpfile_t file;
@@ -97,31 +96,42 @@ static ssize_t write_tapeheader(int, dumpfile_t *);
 static void databuf_init(struct databuf *, int, char *, off_t, off_t);
 static int databuf_flush(struct databuf *);
 
-static int startup_chunker(char *, off_t, off_t, struct databuf *);
-static int do_chunk(int, struct databuf *);
+static int startup_chunker(char *, off_t, off_t, struct databuf *, int *);
+static int do_chunk(int, struct databuf *, int);
 
+/* we use a function pointer for full_write, so that we can "shim" in
+ * full_write_with_fake_enospc for testing */
+static size_t (*db_full_write)(int fd, const void *buf, size_t count);
+static size_t full_write_with_fake_enospc(int fd, const void *buf, size_t count);
+static off_t fake_enospc_at_byte = -1;
 
 int
 main(
-    int                main_argc,
-    char **    main_argv)
+    int                argc,
+    char **    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;
+    struct cmdargs *cmdargs;
+    int header_fd;
     char *q = NULL;
     char *filename = NULL;
-    char *qfilename = NULL;
     off_t chunksize, use;
     times_t runtime;
     am_feature_t *their_features = NULL;
     int a;
-    int    new_argc,   my_argc;
-    char **new_argv, **my_argv;
+    config_overrides_t *cfg_ovr = NULL;
+    char *cfg_opt = NULL;
+    char *m;
+    int data_socket;
+
+    /*
+     * 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);
 
@@ -132,74 +142,69 @@ main(
     /* Don't die when child closes pipe */
     signal(SIGPIPE, SIG_IGN);
 
-    malloc_size_1 = malloc_inuse(&malloc_hist_1);
+    add_amanda_log_handler(amanda_log_stderr);
+    add_amanda_log_handler(amanda_log_trace_log);
 
-    erroutput_type = (ERR_AMANDALOG|ERR_INTERACTIVE);
-    set_logerror(logerror);
+    cfg_ovr = extract_commandline_config_overrides(&argc, &argv);
 
-    parse_server_conf(main_argc, main_argv, &new_argc, &new_argv);
-    my_argc = new_argc;
-    my_argv = new_argv;
+    if (argc > 1) 
+       cfg_opt = argv[1];
 
-    if (my_argc > 1) {
-       config_name = stralloc(my_argv[1]);
-       config_dir = vstralloc(CONFIG_DIR, "/", config_name, "/", NULL);
-    } else {
-       char my_cwd[STR_SIZE];
+    set_config_overrides(cfg_ovr);
+    config_init(CONFIG_INIT_EXPLICIT_NAME | CONFIG_INIT_USE_CWD, cfg_opt);
 
-       if (getcwd(my_cwd, SIZEOF(my_cwd)) == NULL) {
-           error("cannot determine current working directory");
-           /*NOTREACHED*/
-       }
-       config_dir = stralloc2(my_cwd, "/");
-       if ((config_name = strrchr(my_cwd, '/')) != NULL) {
-           config_name = stralloc(config_name + 1);
+    if (config_errors(NULL) >= CFGERR_WARNINGS) {
+       config_print_errors();
+       if (config_errors(NULL) >= CFGERR_ERRORS) {
+           g_critical(_("errors processing config file"));
        }
     }
 
-    safe_cd();
-
-    conffile = stralloc2(config_dir, CONFFILE_NAME);
-    if(read_conffile(conffile)) {
-       error("errors processing config file \"%s\"", conffile);
-       /*NOTREACHED*/
-    }
-    amfree(conffile);
+    safe_cd(); /* do this *after* config_init() */
 
-    dbrename(config_name, DBG_SUBDIR_SERVER);
+    check_running_as(RUNNING_AS_DUMPUSER);
 
-    report_bad_conf_arg();
+    dbrename(get_config_name(), DBG_SUBDIR_SERVER);
 
-    fprintf(stderr,
-           "%s: pid %ld executable %s version %s\n",
+    log_add(L_INFO, "%s pid %ld", get_pname(), (long)getpid());
+    g_fprintf(stderr,
+           _("%s: pid %ld executable %s version %s\n"),
            get_pname(), (long) getpid(),
-           my_argv[0], version());
+           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());
-       /*NOTREACHED*/
-    }
-
     signal(SIGPIPE, SIG_IGN);
     signal(SIGCHLD, SIG_IGN);
 
-    cmd = getcmd(&cmdargs);
-    if(cmd == START) {
-       if(cmdargs.argc <= 1)
-           error("error [dumper START: not enough args: timestamp]");
-       chunker_timestamp = newstralloc(chunker_timestamp, cmdargs.argv[2]);
+    cmdargs = getcmd();
+    if(cmdargs->cmd == START) {
+       if(cmdargs->argc <= 1)
+           error(_("error [dumper START: not enough args: timestamp]"));
+       chunker_timestamp = newstralloc(chunker_timestamp, cmdargs->argv[1]);
     }
     else {
-       error("Didn't get START command");
+       log_add(L_INFO, "%s pid %ld", get_pname(), (long)getpid());
+       error(_("Didn't get START command"));
+    }
+    free_cmdargs(cmdargs);
+
+    /* set up a fake ENOSPC for testing purposes.  Note that this counts
+     * headers as well as data written to disk. */
+    if (getenv("CHUNKER_FAKE_ENOSPC_AT")) {
+       char *env = getenv("CHUNKER_FAKE_ENOSPC_AT");
+       fake_enospc_at_byte = (off_t)atoi(env); /* these values are never > MAXINT */
+       db_full_write = full_write_with_fake_enospc;
+       g_debug("will trigger fake ENOSPC at byte %d", (int)fake_enospc_at_byte);
+    } else {
+       db_full_write = full_write;
     }
 
 /*    do {*/
-       cmd = getcmd(&cmdargs);
+       cmdargs = getcmd();
 
-       switch(cmd) {
+       switch(cmdargs->cmd) {
        case QUIT:
            break;
 
@@ -218,122 +223,123 @@ main(
             *   use
             *   options
             */
-           cmdargs.argc++;                     /* true count of args */
-           a = 2;
+           a = 1;
 
-           if(a >= cmdargs.argc) {
-               error("error [chunker PORT-WRITE: not enough args: handle]");
+           if(a >= cmdargs->argc) {
+               error(_("error [chunker PORT-WRITE: not enough args: handle]"));
                /*NOTREACHED*/
            }
-           handle = newstralloc(handle, cmdargs.argv[a++]);
+           handle = newstralloc(handle, cmdargs->argv[a++]);
 
-           if(a >= cmdargs.argc) {
-               error("error [chunker PORT-WRITE: not enough args: filename]");
+           if(a >= cmdargs->argc) {
+               error(_("error [chunker PORT-WRITE: not enough args: filename]"));
                /*NOTREACHED*/
            }
-           qfilename = newstralloc(qfilename, cmdargs.argv[a++]);
-           if (filename != NULL)
-               amfree(filename);
-           filename = unquote_string(qfilename);
-           amfree(qfilename);
-
-           if(a >= cmdargs.argc) {
-               error("error [chunker PORT-WRITE: not enough args: hostname]");
+           filename = newstralloc(filename, cmdargs->argv[a++]);
+
+           if(a >= cmdargs->argc) {
+               error(_("error [chunker PORT-WRITE: not enough args: hostname]"));
                /*NOTREACHED*/
            }
-           hostname = newstralloc(hostname, cmdargs.argv[a++]);
+           hostname = newstralloc(hostname, cmdargs->argv[a++]);
 
-           if(a >= cmdargs.argc) {
-               error("error [chunker PORT-WRITE: not enough args: features]");
+           if(a >= cmdargs->argc) {
+               error(_("error [chunker PORT-WRITE: not enough args: features]"));
                /*NOTREACHED*/
            }
            am_release_feature_set(their_features);
-           their_features = am_string_to_feature(cmdargs.argv[a++]);
+           their_features = am_string_to_feature(cmdargs->argv[a++]);
+           if (!their_features) {
+               error(_("error [chunker PORT-WRITE: invalid feature string]"));
+               /*NOTREACHED*/
+           }
 
-           if(a >= cmdargs.argc) {
-               error("error [chunker PORT-WRITE: not enough args: diskname]");
+           if(a >= cmdargs->argc) {
+               error(_("error [chunker PORT-WRITE: not enough args: diskname]"));
                /*NOTREACHED*/
            }
-           qdiskname = newstralloc(qdiskname, cmdargs.argv[a++]);
-           if (diskname != NULL)
-               amfree(diskname);
-           diskname = unquote_string(qdiskname);
+           diskname = newstralloc(diskname, cmdargs->argv[a++]);
+           if (qdiskname)
+               amfree(qdiskname);
+           qdiskname = quote_string(diskname); /* qdiskname is a global */
 
-           if(a >= cmdargs.argc) {
-               error("error [chunker PORT-WRITE: not enough args: level]");
+           if(a >= cmdargs->argc) {
+               error(_("error [chunker PORT-WRITE: not enough args: level]"));
                /*NOTREACHED*/
            }
-           level = atoi(cmdargs.argv[a++]);
+           level = atoi(cmdargs->argv[a++]);
 
-           if(a >= cmdargs.argc) {
-               error("error [chunker PORT-WRITE: not enough args: dumpdate]");
+           if(a >= cmdargs->argc) {
+               error(_("error [chunker PORT-WRITE: not enough args: dumpdate]"));
                /*NOTREACHED*/
            }
-           dumpdate = newstralloc(dumpdate, cmdargs.argv[a++]);
+           dumpdate = newstralloc(dumpdate, cmdargs->argv[a++]);
 
-           if(a >= cmdargs.argc) {
-               error("error [chunker PORT-WRITE: not enough args: chunksize]");
+           if(a >= cmdargs->argc) {
+               error(_("error [chunker PORT-WRITE: not enough args: chunksize]"));
                /*NOTREACHED*/
            }
-           chunksize = OFF_T_ATOI(cmdargs.argv[a++]);
+           chunksize = OFF_T_ATOI(cmdargs->argv[a++]);
            chunksize = am_floor(chunksize, (off_t)DISK_BLOCK_KB);
 
-           if(a >= cmdargs.argc) {
-               error("error [chunker PORT-WRITE: not enough args: progname]");
+           if(a >= cmdargs->argc) {
+               error(_("error [chunker PORT-WRITE: not enough args: progname]"));
                /*NOTREACHED*/
            }
-           progname = newstralloc(progname, cmdargs.argv[a++]);
+           progname = newstralloc(progname, cmdargs->argv[a++]);
 
-           if(a >= cmdargs.argc) {
-               error("error [chunker PORT-WRITE: not enough args: use]");
+           if(a >= cmdargs->argc) {
+               error(_("error [chunker PORT-WRITE: not enough args: use]"));
                /*NOTREACHED*/
            }
-           use = am_floor(OFF_T_ATOI(cmdargs.argv[a++]), DISK_BLOCK_KB);
+           use = am_floor(OFF_T_ATOI(cmdargs->argv[a++]), DISK_BLOCK_KB);
 
-           if(a >= cmdargs.argc) {
-               error("error [chunker PORT-WRITE: not enough args: options]");
+           if(a >= cmdargs->argc) {
+               error(_("error [chunker PORT-WRITE: not enough args: options]"));
                /*NOTREACHED*/
            }
-           options = newstralloc(options, cmdargs.argv[a++]);
+           options = newstralloc(options, cmdargs->argv[a++]);
 
-           if(a != cmdargs.argc) {
-               error("error [chunker PORT-WRITE: too many args: %d != %d]",
-                     cmdargs.argc, a);
+           if(a != cmdargs->argc) {
+               error(_("error [chunker PORT-WRITE: too many args: %d != %d]"),
+                     cmdargs->argc, a);
                /*NOTREACHED*/
            }
 
-           if((infd = startup_chunker(filename, use, chunksize, &db)) < 0) {
-               q = squotef("[chunker startup failed: %s]", errstr);
+           if ((header_fd = startup_chunker(filename, use, chunksize, &db,
+                                            &data_socket)) < 0) {
+               q = quote_string(vstrallocf(_("[chunker startup failed: %s]"), errstr));
                putresult(TRYAGAIN, "%s %s\n", handle, q);
-               error("startup_chunker failed");
+               error("startup_chunker failed: %s", errstr);
            }
-           command_in_transit = -1;
-           if(infd >= 0 && do_chunk(infd, &db)) {
+           command_in_transit = NULL;
+           if (header_fd >= 0 && do_chunk(header_fd, &db, data_socket)) {
                char kb_str[NUM_STR_SIZE];
                char kps_str[NUM_STR_SIZE];
                double rt;
 
                runtime = stopclock();
-               rt = (double)(runtime.r.tv_sec) +
-                    ((double)(runtime.r.tv_usec) / 1000000.0);
-               snprintf(kb_str, SIZEOF(kb_str), OFF_T_FMT,
-                        (OFF_T_FMT_TYPE)(dumpsize - (off_t)headersize));
-               snprintf(kps_str, SIZEOF(kps_str), "%3.1lf",
+                rt = g_timeval_to_double(runtime);
+               g_snprintf(kb_str, SIZEOF(kb_str), "%lld",
+                        (long long)(dumpsize - (off_t)headersize));
+               g_snprintf(kps_str, SIZEOF(kps_str), "%3.1lf",
                                isnormal(rt) ? (double)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) {
+               errstr = newvstrallocf(errstr, "sec %s kb %s kps %s",
+                               walltime_str(runtime), kb_str, kps_str);
+               m = vstrallocf("[%s]", errstr);
+               q = quote_string(m);
+               amfree(m);
+               free_cmdargs(cmdargs);
+               if(command_in_transit != NULL) {
+                   cmdargs = command_in_transit;
+                   command_in_transit = NULL;
+               } else {
+                   cmdargs = getcmd();
+               }
+               switch(cmdargs->cmd) {
                case DONE:
-                   putresult(DONE, "%s " OFF_T_FMT " %s\n", handle,
-                            (OFF_T_FMT_TYPE)(dumpsize - (off_t)headersize), q);
+                   putresult(DONE, "%s %lld %s\n", handle,
+                            (long long)(dumpsize - (off_t)headersize), q);
                    log_add(L_SUCCESS, "%s %s %s %d [%s]",
                            hostname, qdiskname, chunker_timestamp, level, errstr);
                    break;
@@ -342,19 +348,19 @@ main(
                case FAILED:
                case ABORT_FINISHED:
                    if(dumpsize > (off_t)DISK_BLOCK_KB) {
-                       putresult(PARTIAL, "%s " OFF_T_FMT " %s\n", handle,
-                                (OFF_T_FMT_TYPE)(dumpsize - (off_t)headersize),
+                       putresult(PARTIAL, "%s %lld %s\n", handle,
+                                (long long)(dumpsize - (off_t)headersize),
                                 q);
                        log_add(L_PARTIAL, "%s %s %s %d [%s]",
                                hostname, qdiskname, chunker_timestamp, level, errstr);
                    }
                    else {
-                       errstr = newvstralloc(errstr,
-                                             "dumper returned ",
-                                             cmdstr[cmd],
-                                             NULL);
+                       errstr = newvstrallocf(errstr,
+                                       _("dumper returned %s"), cmdstr[cmdargs->cmd]);
                        amfree(q);
-                       q = squotef("[%s]",errstr);
+                       m = vstrallocf("[%s]",errstr);
+                       q = quote_string(m);
+                       amfree(m);
                        putresult(FAILED, "%s %s\n", handle, q);
                        log_add(L_FAIL, "%s %s %s %d [%s]",
                                hostname, qdiskname, chunker_timestamp, level, errstr);
@@ -362,38 +368,38 @@ main(
                default: break;
                }
                amfree(q);
-           } else if(infd != -2) {
+           } else if (header_fd != -2) {
+               if(q == NULL) {
+                   m = vstrallocf("[%s]", errstr);
+                   q = quote_string(m);
+                   amfree(m);
+               }
                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, qdiskname, chunker_timestamp, level, errstr);
-                   amfree(q);
                }
+               log_add(L_FAIL, "%s %s %s %d [%s]",
+                       hostname, qdiskname, chunker_timestamp, level, errstr);
+               amfree(q);
            }
            amfree(filename);
            amfree(db.filename);
            break;
 
        default:
-           if(cmdargs.argc >= 1) {
-               q = squote(cmdargs.argv[1]);
-           } else if(cmdargs.argc >= 0) {
-               q = squote(cmdargs.argv[0]);
+           if(cmdargs->argc >= 1) {
+               q = quote_string(cmdargs->argv[0]);
            } else {
-               q = stralloc("(no input?)");
+               q = stralloc(_("(no input?)"));
            }
            putresult(BAD_COMMAND, "%s\n", q);
            amfree(q);
            break;
        }
 
-/*    } while(cmd != QUIT); */
+/*    } while(cmdargs->cmd != QUIT); */
+
+    log_add(L_INFO, "pid-done %ld", (long)getpid());
 
-    free_new_argv(new_argc, new_argv);
-    free_server_config();
     amfree(errstr);
     amfree(chunker_timestamp);
     amfree(handle);
@@ -403,16 +409,12 @@ main(
     amfree(dumpdate);
     amfree(progname);
     amfree(options);
-    amfree(config_dir);
-    amfree(config_name);
+    free_cmdargs(cmdargs);
+    if (command_in_transit)
+       free_cmdargs(command_in_transit);
     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);
-
     dbclose();
 
     return (0); /* exit */
@@ -427,45 +429,74 @@ startup_chunker(
     char *             filename,
     off_t              use,
     off_t              chunksize,
-    struct databuf *   db)
+    struct databuf *   db,
+    int                *datasocket)
 {
-    int infd, outfd;
+    int header_fd, outfd;
     char *tmp_filename, *pc;
-    in_port_t data_port;
-    int data_socket;
+    in_port_t header_port, data_port;
+    int header_socket, data_socket;
+    int result;
+    struct addrinfo *res;
 
+    header_port = 0;
     data_port = 0;
-    data_socket = stream_server(&data_port, 0, STREAM_BUFSIZE, 0);
+    if ((result = resolve_hostname("localhost", 0, &res, NULL) != 0)) {
+       errstr = newvstrallocf(errstr, _("could not resolve localhost: %s"),
+                              gai_strerror(result));
+       return -1;
+    }
+    header_socket = stream_server(res->ai_family, &header_port, 0,
+                               STREAM_BUFSIZE, 0);
+    data_socket = stream_server(res->ai_family, &data_port, 0,
+                               STREAM_BUFSIZE, 0);
+    if (res) freeaddrinfo(res);
+
+    if (header_socket < 0) {
+       errstr = vstrallocf(_("error creating header stream server: %s"), strerror(errno));
+       aclose(data_socket);
+       return -1;
+    }
 
-    if(data_socket < 0) {
-       errstr = stralloc2("error creating stream server: ", strerror(errno));
+    if (data_socket < 0) {
+       errstr = vstrallocf(_("error creating data stream server: %s"), strerror(errno));
+       aclose(header_socket);
        return -1;
     }
 
-    putresult(PORT, "%d\n", data_port);
+    putresult(PORT, "%d 127.0.0.1:%d\n", header_port, data_port);
 
-    infd = stream_accept(data_socket, CONNECT_TIMEOUT, 0, STREAM_BUFSIZE);
-    if(infd == -1) {
-       errstr = stralloc2("error accepting stream: ", strerror(errno));
+    header_fd = stream_accept(header_socket, CONNECT_TIMEOUT, 0,
+                             STREAM_BUFSIZE);
+    if (header_fd == -1) {
+       errstr = vstrallocf(_("error accepting header stream: %s"),
+                           strerror(errno));
+       aclose(header_socket);
+       aclose(data_socket);
        return -1;
     }
+    aclose(header_socket);
 
     tmp_filename = vstralloc(filename, ".tmp", NULL);
     pc = strrchr(tmp_filename, '/');
+    g_assert(pc != NULL);
     *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",
+       char *m = vstrallocf(_("holding file \"%s\": %s"),
                         tmp_filename,
                         strerror(errno));
+
+       errstr = quote_string(m);
+       amfree(m);
        amfree(tmp_filename);
-       aclose(infd);
+       aclose(header_fd);
+       aclose(data_socket);
        if(save_errno == ENOSPC) {
-           putresult(NO_ROOM, "%s " OFF_T_FMT "\n",
-                     handle, (OFF_T_FMT_TYPE)use);
+           putresult(NO_ROOM, "%s %lld\n",
+                     handle, (long long)use);
            return -2;
        } else {
            return -1;
@@ -474,15 +505,18 @@ startup_chunker(
     amfree(tmp_filename);
     databuf_init(db, outfd, filename, use, chunksize);
     db->filename_seq++;
-    return infd;
+    *datasocket = data_socket;
+    return header_fd;
 }
 
 static int
 do_chunk(
-    int                        infd,
-    struct databuf *   db)
+    int                        header_fd,
+    struct databuf *   db,
+    int                 data_socket)
 {
-    ssize_t nread;
+    size_t nread;
+    int    data_fd;
     char header_buf[DISK_BLOCK_BYTES];
 
     startclock();
@@ -496,46 +530,51 @@ do_chunk(
      * 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));
+    nread = full_read(header_fd, header_buf, SIZEOF(header_buf));
+    if (nread != sizeof(header_buf)) {
+       if(errno != 0) {
+           errstr = vstrallocf(_("cannot read header: %s"), strerror(errno));
        } else {
-           snprintf(number1, SIZEOF(number1), SSIZE_T_FMT,
-                       (SSIZE_T_FMT_TYPE)nread);
-           snprintf(number2, SIZEOF(number2), "%d", DISK_BLOCK_BYTES);
-           errstr = vstralloc("cannot read header: got ",
-                              number1,
-                              " instead of ",
-                              number2,
-                              NULL);
+           errstr = vstrallocf(_("cannot read header: got %zd bytes instead of %zd"),
+                               nread, sizeof(header_buf));
        }
+       aclose(data_socket);
        return 0;
     }
     parse_file_header(header_buf, &file, (size_t)nread);
     if(write_tapeheader(db->fd, &file)) {
        int save_errno = errno;
-
-       errstr = squotef("write_tapeheader file %s: %s",
+       char *m = vstrallocf(_("write_tapeheader file %s: %s"),
                         db->filename, strerror(errno));
+       errstr = quote_string(m);
+       amfree(m);
        if(save_errno == ENOSPC) {
-           putresult(NO_ROOM, "%s " OFF_T_FMT "\n", handle, 
-                     (OFF_T_FMT_TYPE)(db->use+db->split_size-dumpsize));
+           putresult(NO_ROOM, "%s %lld\n", handle, 
+                     (long long)(db->use+db->split_size-dumpsize));
        }
+       aclose(data_socket);
        return 0;
     }
     dumpsize += (off_t)DISK_BLOCK_KB;
     filesize = (off_t)DISK_BLOCK_KB;
     headersize += DISK_BLOCK_KB;
 
+    /* open the data socket */
+    data_fd = stream_accept(data_socket, CONNECT_TIMEOUT, 0, STREAM_BUFSIZE);
+
+    if (data_fd == -1) {
+       errstr = vstrallocf(_("error accepting data stream: %s"),
+                           strerror(errno));
+       aclose(data_socket);
+       return 0;
+    }
+    aclose(data_socket);
+
     /*
      * We've written the file header.  Now, just write data until the
      * end.
      */
-    while ((nread = fullread(infd, db->buf,
+    while ((nread = full_read(data_fd, db->buf,
                             (size_t)(db->datalimit - db->datain))) > 0) {
        db->datain += nread;
        while(db->dataout < db->datain) {
@@ -585,12 +624,12 @@ static int
 databuf_flush(
     struct databuf *   db)
 {
-    struct cmdargs cmdargs;
+    struct cmdargs *cmdargs = NULL;
     int rc = 1;
-    ssize_t written;
+    size_t size_to_write;
+    size_t written;
     off_t left_in_chunk;
     char *arg_filename = NULL;
-    char *qarg_filename = NULL;
     char *new_filename = NULL;
     char *tmp_filename = NULL;
     char sequence[NUM_STR_SIZE];
@@ -615,16 +654,14 @@ databuf_flush(
            /*
             * 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);
+           cmdargs = getcmd();
+           if(command_in_transit == NULL &&
+              (cmdargs->cmd == DONE || cmdargs->cmd == TRYAGAIN || cmdargs->cmd == FAILED)) {
+               command_in_transit = cmdargs;
+               cmdargs = getcmd();
            }
-           if(cmd == CONTINUE) {
+           if(cmdargs->cmd == CONTINUE) {
                /*
                 * CONTINUE
                 *   serial
@@ -632,34 +669,30 @@ databuf_flush(
                 *   chunksize
                 *   use
                 */
-               cmdargs.argc++;                 /* true count of args */
-               a = 3;
+               a = 2; /* skip CONTINUE and serial */
 
-               if(a >= cmdargs.argc) {
-                   error("error [chunker CONTINUE: not enough args: filename]");
+               if(a >= cmdargs->argc) {
+                   error(_("error [chunker CONTINUE: not enough args: filename]"));
                    /*NOTREACHED*/
                }
-               qarg_filename = newstralloc(qarg_filename, cmdargs.argv[a++]);
-               if (arg_filename != NULL)
-                   amfree(arg_filename);
-               arg_filename = unquote_string(qarg_filename);
+               arg_filename = newstralloc(arg_filename, cmdargs->argv[a++]);
 
-               if(a >= cmdargs.argc) {
-                   error("error [chunker CONTINUE: not enough args: chunksize]");
+               if(a >= cmdargs->argc) {
+                   error(_("error [chunker CONTINUE: not enough args: chunksize]"));
                    /*NOTREACHED*/
                }
-               db->chunk_size = OFF_T_ATOI(cmdargs.argv[a++]);
+               db->chunk_size = OFF_T_ATOI(cmdargs->argv[a++]);
                db->chunk_size = am_floor(db->chunk_size, (off_t)DISK_BLOCK_KB);
 
-               if(a >= cmdargs.argc) {
-                   error("error [chunker CONTINUE: not enough args: use]");
+               if(a >= cmdargs->argc) {
+                   error(_("error [chunker CONTINUE: not enough args: use]"));
                    /*NOTREACHED*/
                }
-               db->use = OFF_T_ATOI(cmdargs.argv[a++]);
+               db->use = OFF_T_ATOI(cmdargs->argv[a++]);
 
-               if(a != cmdargs.argc) {
-                   error("error [chunker CONTINUE: too many args: %d != %d]",
-                         cmdargs.argc, a);
+               if(a != cmdargs->argc) {
+                   error(_("error [chunker CONTINUE: too many args: %d != %d]"),
+                         cmdargs->argc, a);
                    /*NOTREACHED*/
                }
 
@@ -689,21 +722,19 @@ databuf_flush(
                     */
                    db->filename = newstralloc(db->filename, arg_filename);
                }
-           } else if(cmd == ABORT) {
+           } else if(cmdargs->cmd == ABORT) {
                abort_pending = 1;
-               errstr = newstralloc(errstr, "ERROR");
+               errstr = newstralloc(errstr, cmdargs->argv[1]);
                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]);
+               if(cmdargs->argc >= 1) {
+                   q = quote_string(cmdargs->argv[0]);
                } else {
-                   q = stralloc("(no input?)");
+                   q = stralloc(_("(no input?)"));
                }
-               error("error [bad command after RQ-MORE-DISK: \"%s\"]", q);
+               error(_("error [bad command after RQ-MORE-DISK: \"%s\"]"), q);
                /*NOTREACHED*/
            }
        }
@@ -716,7 +747,7 @@ databuf_flush(
         * 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);
+       g_snprintf(sequence, SIZEOF(sequence), "%d", db->filename_seq);
        new_filename = newvstralloc(new_filename,
                                    db->filename,
                                    ".",
@@ -727,23 +758,27 @@ databuf_flush(
                                    ".tmp",
                                    NULL);
        pc = strrchr(tmp_filename, '/');
+        g_assert(pc != NULL); /* Only a problem if db->filename has no /. */
        *pc = '\0';
        mkholdingdir(tmp_filename);
        *pc = '/';
        newfd = open(tmp_filename, O_RDWR|O_CREAT|O_TRUNC, 0600);
        if (newfd == -1) {
            int save_errno = errno;
+           char *m;
 
            if(save_errno == ENOSPC) {
-               putresult(NO_ROOM, "%s " OFF_T_FMT "\n", handle, 
-                         (OFF_T_FMT_TYPE)(db->use+db->split_size-dumpsize));
+               putresult(NO_ROOM, "%s %lld\n", handle, 
+                         (long long)(db->use+db->split_size-dumpsize));
                db->use = (off_t)0;                     /* force RQ_MORE_DISK */
                db->split_size = dumpsize;
                continue;
            }
-           errstr = squotef("creating chunk holding file \"%s\": %s",
+           m = vstrallocf(_("creating chunk holding file \"%s\": %s"),
                             tmp_filename,
                             strerror(errno));
+           errstr = quote_string(m);
+           amfree(m);
            aclose(db->fd);
            rc = 0;
            goto common_exit;
@@ -753,18 +788,25 @@ databuf_flush(
        file.cont_filename[0] = '\0';
        if(write_tapeheader(newfd, &file)) {
            int save_errno = errno;
+           char *m;
 
            aclose(newfd);
            if(save_errno == ENOSPC) {
-               putresult(NO_ROOM, "%s " OFF_T_FMT "\n", handle, 
-                         (OFF_T_FMT_TYPE)(db->use+db->split_size-dumpsize));
+               if (unlink(tmp_filename) < 0) {
+                   g_debug("could not delete '%s'; ignoring", tmp_filename);
+               }
+               putresult(NO_ROOM, "%s %lld\n", handle, 
+                         (long long)(db->use+db->split_size-dumpsize));
                db->use = (off_t)0;                     /* force RQ_MORE DISK */
                db->split_size = dumpsize;
+               file.type = save_type;
                continue;
            }
-           errstr = squotef("write_tapeheader file %s: %s",
+           m = vstrallocf(_("write_tapeheader file %s: %s"),
                             tmp_filename,
                             strerror(errno));
+           errstr = quote_string(m);
+           amfree(m);
            rc = 0;
            goto common_exit;
        }
@@ -774,9 +816,11 @@ databuf_flush(
         * to the next chunk, and then close it.
         */
        if (lseek(db->fd, (off_t)0, SEEK_SET) < (off_t)0) {
-           errstr = squotef("lseek holding file %s: %s",
+           char *m = vstrallocf(_("lseek holding file %s: %s"),
                             db->filename,
                             strerror(errno));
+           errstr = quote_string(m);
+           amfree(m);
            aclose(newfd);
            rc = 0;
            goto common_exit;
@@ -784,11 +828,13 @@ databuf_flush(
 
        file.type = save_type;
        strncpy(file.cont_filename, new_filename, SIZEOF(file.cont_filename));
-       file.cont_filename[SIZEOF(file.cont_filename)] = '\0';
+       file.cont_filename[SIZEOF(file.cont_filename)-1] = '\0';
        if(write_tapeheader(db->fd, &file)) {
-           errstr = squotef("write_tapeheader file \"%s\": %s",
+           char * m = vstrallocf(_("write_tapeheader file \"%s\": %s"),
                             db->filename,
                             strerror(errno));
+           errstr = quote_string(m);
+           amfree(m);
            aclose(newfd);
            unlink(tmp_filename);
            rc = 0;
@@ -833,8 +879,8 @@ databuf_flush(
     /*
      * Write out the buffer
      */
-    written = fullwrite(db->fd, db->dataout,
-                       (size_t)(db->datain - db->dataout));
+    size_to_write = (size_t)(db->datain - db->dataout);
+    written = db_full_write(db->fd, db->dataout, size_to_write);
     if (written > 0) {
        db->dataout += written;
        dumpbytes += (off_t)written;
@@ -842,9 +888,11 @@ databuf_flush(
     dumpsize += (dumpbytes / (off_t)1024);
     filesize += (dumpbytes / (off_t)1024);
     dumpbytes %= 1024;
-    if (written < 0) {
+    if (written < size_to_write) {
        if (errno != ENOSPC) {
-           errstr = squotef("data write: %s", strerror(errno));
+           char *m = vstrallocf(_("data write: %s"), strerror(errno));
+           errstr = quote_string(m);
+           amfree(m);
            rc = 0;
            goto common_exit;
        }
@@ -853,8 +901,8 @@ databuf_flush(
         * NO-ROOM is informational only.  Later, RQ_MORE_DISK will be
         * issued to use another holding disk.
         */
-       putresult(NO_ROOM, "%s " OFF_T_FMT "\n", handle,
-                 (OFF_T_FMT_TYPE)(db->use+db->split_size-dumpsize));
+       putresult(NO_ROOM, "%s %lld\n", handle,
+                 (long long)(db->use+db->split_size-dumpsize));
        db->use = (off_t)0;                             /* force RQ_MORE_DISK */
        db->split_size = dumpsize;
        goto common_exit;
@@ -868,35 +916,78 @@ databuf_flush(
 
 common_exit:
 
+    if (cmdargs)
+       free_cmdargs(cmdargs);
     amfree(new_filename);
     /*@i@*/ amfree(tmp_filename);
     amfree(arg_filename);
-    amfree(qarg_filename);
     return rc;
 }
 
 
 /*
- * Send an Amanda dump header to the output file.
+ * Send an Amanda dump header to the output file and set file->blocksize
  */
 static ssize_t
 write_tapeheader(
     int                outfd,
     dumpfile_t *file)
 {
-    char buffer[DISK_BLOCK_BYTES];
-    ssize_t written;
+    char *buffer;
+    size_t written;
 
     file->blocksize = DISK_BLOCK_BYTES;
-    build_header(buffer, file, SIZEOF(buffer));
+    if (debug_chunker > 1)
+       dump_dumpfile_t(file);
+    buffer = build_header(file, NULL, DISK_BLOCK_BYTES);
+    if (!buffer) /* this shouldn't happen */
+       error(_("header does not fit in %zd bytes"), (size_t)DISK_BLOCK_BYTES);
 
-    written = fullwrite(outfd, buffer, SIZEOF(buffer));
-    if(written == (ssize_t)sizeof(buffer))
-       return 0;
+    written = db_full_write(outfd, buffer, DISK_BLOCK_BYTES);
+    amfree(buffer);
+    if(written == DISK_BLOCK_BYTES) return 0;
 
-    if(written < 0)
-       return written;
+    /* fake ENOSPC when we get a short write without errno set */
+    if(errno == 0)
+       errno = ENOSPC;
 
-    errno = ENOSPC;
     return (ssize_t)-1;
 }
+
+static size_t
+full_write_with_fake_enospc(
+    int fd,
+    const void *buf,
+    size_t count)
+{
+    size_t rc;
+
+    //g_debug("HERE %zd %zd", count, (size_t)fake_enospc_at_byte);
+
+    if (count <= (size_t)fake_enospc_at_byte) {
+       fake_enospc_at_byte -= count;
+       return full_write(fd, buf, count);
+    }
+
+    /* if we get here, the caller has requested a size that is less
+     * than fake_enospc_at_byte. */
+    count = fake_enospc_at_byte;
+    g_debug("returning fake ENOSPC");
+
+    if (fake_enospc_at_byte) {
+       rc = full_write(fd, buf, fake_enospc_at_byte);
+       if (rc == (size_t)fake_enospc_at_byte) {
+           /* full_write succeeded, so fake a failure */
+           errno = ENOSPC;
+       }
+    } else {
+       /* no bytes to write; just fake an error */
+       errno = ENOSPC;
+       rc = 0;
+    }
+
+    /* switch back to calling full_write directly */
+    fake_enospc_at_byte = -1;
+    db_full_write = full_write;
+    return rc;
+}