Imported Upstream version 2.5.1
[debian/amanda] / server-src / chunker.c
1 /*
2  * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3  * Copyright (c) 1991-1999 University of Maryland at College Park
4  * All Rights Reserved.
5  *
6  * Permission to use, copy, modify, distribute, and sell this software and its
7  * documentation for any purpose is hereby granted without fee, provided that
8  * the above copyright notice appear in all copies and that both that
9  * copyright notice and this permission notice appear in supporting
10  * documentation, and that the name of U.M. not be used in advertising or
11  * publicity pertaining to distribution of the software without specific,
12  * written prior permission.  U.M. makes no representations about the
13  * suitability of this software for any purpose.  It is provided "as is"
14  * without express or implied warranty.
15  *
16  * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
18  * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
20  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
21  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22  *
23  * Authors: the Amanda Development Team.  Its members are listed in a
24  * file named AUTHORS, in the root directory of this distribution.
25  */
26 /* $Id: chunker.c,v 1.36 2006/08/24 11:23:32 martinea Exp $
27  *
28  * requests remote amandad processes to dump filesystems
29  */
30 #include "amanda.h"
31 #include "arglist.h"
32 #include "clock.h"
33 #include "conffile.h"
34 #include "event.h"
35 #include "logfile.h"
36 #include "packet.h"
37 #include "protocol.h"
38 #include "security.h"
39 #include "stream.h"
40 #include "token.h"
41 #include "version.h"
42 #include "fileheader.h"
43 #include "amfeatures.h"
44 #include "server_util.h"
45 #include "util.h"
46 #include "holding.h"
47
48 #ifndef SEEK_SET
49 #define SEEK_SET 0
50 #endif
51
52 #ifndef SEEK_CUR
53 #define SEEK_CUR 1
54 #endif
55
56 #define CONNECT_TIMEOUT 5*60
57
58 #define STARTUP_TIMEOUT 60
59
60 struct databuf {
61     int fd;                     /* file to flush to */
62     char *filename;             /* name of what fd points to */
63     int filename_seq;           /* for chunking */
64     off_t split_size;           /* when to chunk */
65     off_t chunk_size;           /* size of each chunk */
66     off_t use;                  /* size to use on this disk */
67     char buf[DISK_BLOCK_BYTES];
68     char *datain;               /* data buffer markers */
69     char *dataout;
70     char *datalimit;
71 };
72
73 static char *handle = NULL;
74
75 static char *errstr = NULL;
76 static int abort_pending;
77 static off_t dumpsize;
78 static unsigned long headersize;
79 static off_t dumpbytes;
80 static off_t filesize;
81
82 static char *hostname = NULL;
83 static char *diskname = NULL;
84 static char *qdiskname = NULL;
85 static char *options = NULL;
86 static char *progname = NULL;
87 static int level;
88 static char *dumpdate = NULL;
89 static int command_in_transit;
90 static char *chunker_timestamp = NULL;
91
92 static dumpfile_t file;
93
94 /* local functions */
95 int main(int, char **);
96 static ssize_t write_tapeheader(int, dumpfile_t *);
97 static void databuf_init(struct databuf *, int, char *, off_t, off_t);
98 static int databuf_flush(struct databuf *);
99
100 static int startup_chunker(char *, off_t, off_t, struct databuf *);
101 static int do_chunk(int, struct databuf *);
102
103
104 int
105 main(
106     int         main_argc,
107     char **     main_argv)
108 {
109     static struct databuf db;
110     struct cmdargs cmdargs;
111     cmd_t cmd;
112     int infd;
113     unsigned long malloc_hist_1, malloc_size_1;
114     unsigned long malloc_hist_2, malloc_size_2;
115     char *conffile;
116     char *q = NULL;
117     char *filename = NULL;
118     char *qfilename = NULL;
119     off_t chunksize, use;
120     times_t runtime;
121     am_feature_t *their_features = NULL;
122     int a;
123     int    new_argc,   my_argc;
124     char **new_argv, **my_argv;
125
126     safe_fd(-1, 0);
127
128     set_pname("chunker");
129
130     dbopen(DBG_SUBDIR_SERVER);
131
132     /* Don't die when child closes pipe */
133     signal(SIGPIPE, SIG_IGN);
134
135     malloc_size_1 = malloc_inuse(&malloc_hist_1);
136
137     erroutput_type = (ERR_AMANDALOG|ERR_INTERACTIVE);
138     set_logerror(logerror);
139
140     parse_server_conf(main_argc, main_argv, &new_argc, &new_argv);
141     my_argc = new_argc;
142     my_argv = new_argv;
143
144     if (my_argc > 1) {
145         config_name = stralloc(my_argv[1]);
146         config_dir = vstralloc(CONFIG_DIR, "/", config_name, "/", NULL);
147     } else {
148         char my_cwd[STR_SIZE];
149
150         if (getcwd(my_cwd, SIZEOF(my_cwd)) == NULL) {
151             error("cannot determine current working directory");
152             /*NOTREACHED*/
153         }
154         config_dir = stralloc2(my_cwd, "/");
155         if ((config_name = strrchr(my_cwd, '/')) != NULL) {
156             config_name = stralloc(config_name + 1);
157         }
158     }
159
160     safe_cd();
161
162     conffile = stralloc2(config_dir, CONFFILE_NAME);
163     if(read_conffile(conffile)) {
164         error("errors processing config file \"%s\"", conffile);
165         /*NOTREACHED*/
166     }
167     amfree(conffile);
168
169     dbrename(config_name, DBG_SUBDIR_SERVER);
170
171     report_bad_conf_arg();
172
173     fprintf(stderr,
174             "%s: pid %ld executable %s version %s\n",
175             get_pname(), (long) getpid(),
176             my_argv[0], version());
177     fflush(stderr);
178
179     /* now, make sure we are a valid user */
180
181     if (getpwuid(getuid()) == NULL) {
182         error("can't get login name for my uid %ld", (long)getuid());
183         /*NOTREACHED*/
184     }
185
186     signal(SIGPIPE, SIG_IGN);
187     signal(SIGCHLD, SIG_IGN);
188
189     cmd = getcmd(&cmdargs);
190     if(cmd == START) {
191         if(cmdargs.argc <= 1)
192             error("error [dumper START: not enough args: timestamp]");
193         chunker_timestamp = newstralloc(chunker_timestamp, cmdargs.argv[2]);
194     }
195     else {
196         error("Didn't get START command");
197     }
198
199 /*    do {*/
200         cmd = getcmd(&cmdargs);
201
202         switch(cmd) {
203         case QUIT:
204             break;
205
206         case PORT_WRITE:
207             /*
208              * PORT-WRITE
209              *   handle
210              *   filename
211              *   host
212              *   features
213              *   disk
214              *   level
215              *   dumpdate
216              *   chunksize
217              *   progname
218              *   use
219              *   options
220              */
221             cmdargs.argc++;                     /* true count of args */
222             a = 2;
223
224             if(a >= cmdargs.argc) {
225                 error("error [chunker PORT-WRITE: not enough args: handle]");
226                 /*NOTREACHED*/
227             }
228             handle = newstralloc(handle, cmdargs.argv[a++]);
229
230             if(a >= cmdargs.argc) {
231                 error("error [chunker PORT-WRITE: not enough args: filename]");
232                 /*NOTREACHED*/
233             }
234             qfilename = newstralloc(qfilename, cmdargs.argv[a++]);
235             if (filename != NULL)
236                 amfree(filename);
237             filename = unquote_string(qfilename);
238             amfree(qfilename);
239
240             if(a >= cmdargs.argc) {
241                 error("error [chunker PORT-WRITE: not enough args: hostname]");
242                 /*NOTREACHED*/
243             }
244             hostname = newstralloc(hostname, cmdargs.argv[a++]);
245
246             if(a >= cmdargs.argc) {
247                 error("error [chunker PORT-WRITE: not enough args: features]");
248                 /*NOTREACHED*/
249             }
250             am_release_feature_set(their_features);
251             their_features = am_string_to_feature(cmdargs.argv[a++]);
252
253             if(a >= cmdargs.argc) {
254                 error("error [chunker PORT-WRITE: not enough args: diskname]");
255                 /*NOTREACHED*/
256             }
257             qdiskname = newstralloc(qdiskname, cmdargs.argv[a++]);
258             if (diskname != NULL)
259                 amfree(diskname);
260             diskname = unquote_string(qdiskname);
261
262             if(a >= cmdargs.argc) {
263                 error("error [chunker PORT-WRITE: not enough args: level]");
264                 /*NOTREACHED*/
265             }
266             level = atoi(cmdargs.argv[a++]);
267
268             if(a >= cmdargs.argc) {
269                 error("error [chunker PORT-WRITE: not enough args: dumpdate]");
270                 /*NOTREACHED*/
271             }
272             dumpdate = newstralloc(dumpdate, cmdargs.argv[a++]);
273
274             if(a >= cmdargs.argc) {
275                 error("error [chunker PORT-WRITE: not enough args: chunksize]");
276                 /*NOTREACHED*/
277             }
278             chunksize = OFF_T_ATOI(cmdargs.argv[a++]);
279             chunksize = am_floor(chunksize, (off_t)DISK_BLOCK_KB);
280
281             if(a >= cmdargs.argc) {
282                 error("error [chunker PORT-WRITE: not enough args: progname]");
283                 /*NOTREACHED*/
284             }
285             progname = newstralloc(progname, cmdargs.argv[a++]);
286
287             if(a >= cmdargs.argc) {
288                 error("error [chunker PORT-WRITE: not enough args: use]");
289                 /*NOTREACHED*/
290             }
291             use = am_floor(OFF_T_ATOI(cmdargs.argv[a++]), DISK_BLOCK_KB);
292
293             if(a >= cmdargs.argc) {
294                 error("error [chunker PORT-WRITE: not enough args: options]");
295                 /*NOTREACHED*/
296             }
297             options = newstralloc(options, cmdargs.argv[a++]);
298
299             if(a != cmdargs.argc) {
300                 error("error [chunker PORT-WRITE: too many args: %d != %d]",
301                       cmdargs.argc, a);
302                 /*NOTREACHED*/
303             }
304
305             if((infd = startup_chunker(filename, use, chunksize, &db)) < 0) {
306                 q = squotef("[chunker startup failed: %s]", errstr);
307                 putresult(TRYAGAIN, "%s %s\n", handle, q);
308                 error("startup_chunker failed");
309             }
310             command_in_transit = -1;
311             if(infd >= 0 && do_chunk(infd, &db)) {
312                 char kb_str[NUM_STR_SIZE];
313                 char kps_str[NUM_STR_SIZE];
314                 double rt;
315
316                 runtime = stopclock();
317                 rt = (double)(runtime.r.tv_sec) +
318                      ((double)(runtime.r.tv_usec) / 1000000.0);
319                 snprintf(kb_str, SIZEOF(kb_str), OFF_T_FMT,
320                          (OFF_T_FMT_TYPE)(dumpsize - (off_t)headersize));
321                 snprintf(kps_str, SIZEOF(kps_str), "%3.1lf",
322                                 isnormal(rt) ? (double)dumpsize / rt : 0.0);
323                 errstr = newvstralloc(errstr,
324                                       "sec ", walltime_str(runtime),
325                                       " kb ", kb_str,
326                                       " kps ", kps_str,
327                                       NULL);
328                 q = squotef("[%s]", errstr);
329                 if(command_in_transit != -1)
330                     cmd = command_in_transit;
331                 else
332                     cmd = getcmd(&cmdargs);
333                 switch(cmd) {
334                 case DONE:
335                     putresult(DONE, "%s " OFF_T_FMT " %s\n", handle,
336                              (OFF_T_FMT_TYPE)(dumpsize - (off_t)headersize), q);
337                     log_add(L_SUCCESS, "%s %s %s %d [%s]",
338                             hostname, qdiskname, chunker_timestamp, level, errstr);
339                     break;
340                 case BOGUS:
341                 case TRYAGAIN:
342                 case FAILED:
343                 case ABORT_FINISHED:
344                     if(dumpsize > (off_t)DISK_BLOCK_KB) {
345                         putresult(PARTIAL, "%s " OFF_T_FMT " %s\n", handle,
346                                  (OFF_T_FMT_TYPE)(dumpsize - (off_t)headersize),
347                                  q);
348                         log_add(L_PARTIAL, "%s %s %s %d [%s]",
349                                 hostname, qdiskname, chunker_timestamp, level, errstr);
350                     }
351                     else {
352                         errstr = newvstralloc(errstr,
353                                               "dumper returned ",
354                                               cmdstr[cmd],
355                                               NULL);
356                         amfree(q);
357                         q = squotef("[%s]",errstr);
358                         putresult(FAILED, "%s %s\n", handle, q);
359                         log_add(L_FAIL, "%s %s %s %d [%s]",
360                                 hostname, qdiskname, chunker_timestamp, level, errstr);
361                     }
362                 default: break;
363                 }
364                 amfree(q);
365             } else if(infd != -2) {
366                 if(!abort_pending) {
367                     if(q == NULL) {
368                         q = squotef("[%s]", errstr);
369                     }
370                     putresult(FAILED, "%s %s\n", handle, q);
371                     log_add(L_FAIL, "%s %s %s %d [%s]",
372                             hostname, qdiskname, chunker_timestamp, level, errstr);
373                     amfree(q);
374                 }
375             }
376             amfree(filename);
377             amfree(db.filename);
378             break;
379
380         default:
381             if(cmdargs.argc >= 1) {
382                 q = squote(cmdargs.argv[1]);
383             } else if(cmdargs.argc >= 0) {
384                 q = squote(cmdargs.argv[0]);
385             } else {
386                 q = stralloc("(no input?)");
387             }
388             putresult(BAD_COMMAND, "%s\n", q);
389             amfree(q);
390             break;
391         }
392
393 /*    } while(cmd != QUIT); */
394
395     free_new_argv(new_argc, new_argv);
396     free_server_config();
397     amfree(errstr);
398     amfree(chunker_timestamp);
399     amfree(handle);
400     amfree(hostname);
401     amfree(diskname);
402     amfree(qdiskname);
403     amfree(dumpdate);
404     amfree(progname);
405     amfree(options);
406     amfree(config_dir);
407     amfree(config_name);
408     am_release_feature_set(their_features);
409     their_features = NULL;
410
411     malloc_size_2 = malloc_inuse(&malloc_hist_2);
412
413     if (malloc_size_1 != malloc_size_2)
414         malloc_list(fileno(stderr), malloc_hist_1, malloc_hist_2);
415
416     dbclose();
417
418     return (0); /* exit */
419 }
420
421 /*
422  * Returns a file descriptor to the incoming port
423  * on success, or -1 on error.
424  */
425 static int
426 startup_chunker(
427     char *              filename,
428     off_t               use,
429     off_t               chunksize,
430     struct databuf *    db)
431 {
432     int infd, outfd;
433     char *tmp_filename, *pc;
434     in_port_t data_port;
435     int data_socket;
436
437     data_port = 0;
438     data_socket = stream_server(&data_port, 0, STREAM_BUFSIZE, 0);
439
440     if(data_socket < 0) {
441         errstr = stralloc2("error creating stream server: ", strerror(errno));
442         return -1;
443     }
444
445     putresult(PORT, "%d\n", data_port);
446
447     infd = stream_accept(data_socket, CONNECT_TIMEOUT, 0, STREAM_BUFSIZE);
448     if(infd == -1) {
449         errstr = stralloc2("error accepting stream: ", strerror(errno));
450         return -1;
451     }
452
453     tmp_filename = vstralloc(filename, ".tmp", NULL);
454     pc = strrchr(tmp_filename, '/');
455     *pc = '\0';
456     mkholdingdir(tmp_filename);
457     *pc = '/';
458     if ((outfd = open(tmp_filename, O_RDWR|O_CREAT|O_TRUNC, 0600)) < 0) {
459         int save_errno = errno;
460
461         errstr = squotef("holding file \"%s\": %s",
462                          tmp_filename,
463                          strerror(errno));
464         amfree(tmp_filename);
465         aclose(infd);
466         if(save_errno == ENOSPC) {
467             putresult(NO_ROOM, "%s " OFF_T_FMT "\n",
468                       handle, (OFF_T_FMT_TYPE)use);
469             return -2;
470         } else {
471             return -1;
472         }
473     }
474     amfree(tmp_filename);
475     databuf_init(db, outfd, filename, use, chunksize);
476     db->filename_seq++;
477     return infd;
478 }
479
480 static int
481 do_chunk(
482     int                 infd,
483     struct databuf *    db)
484 {
485     ssize_t nread;
486     char header_buf[DISK_BLOCK_BYTES];
487
488     startclock();
489
490     dumpsize = dumpbytes = filesize = (off_t)0;
491     headersize = 0;
492     memset(header_buf, 0, sizeof(header_buf));
493
494     /*
495      * The first thing we should receive is the file header, which we
496      * need to save into "file", as well as write out.  Later, the
497      * chunk code will rewrite it.
498      */
499     nread = fullread(infd, header_buf, SIZEOF(header_buf));
500     if (nread != DISK_BLOCK_BYTES) {
501         char number1[NUM_STR_SIZE];
502         char number2[NUM_STR_SIZE];
503
504         if(nread < 0) {
505             errstr = stralloc2("cannot read header: ", strerror(errno));
506         } else {
507             snprintf(number1, SIZEOF(number1), SSIZE_T_FMT,
508                         (SSIZE_T_FMT_TYPE)nread);
509             snprintf(number2, SIZEOF(number2), "%d", DISK_BLOCK_BYTES);
510             errstr = vstralloc("cannot read header: got ",
511                                number1,
512                                " instead of ",
513                                number2,
514                                NULL);
515         }
516         return 0;
517     }
518     parse_file_header(header_buf, &file, (size_t)nread);
519     if(write_tapeheader(db->fd, &file)) {
520         int save_errno = errno;
521
522         errstr = squotef("write_tapeheader file %s: %s",
523                          db->filename, strerror(errno));
524         if(save_errno == ENOSPC) {
525             putresult(NO_ROOM, "%s " OFF_T_FMT "\n", handle, 
526                       (OFF_T_FMT_TYPE)(db->use+db->split_size-dumpsize));
527         }
528         return 0;
529     }
530     dumpsize += (off_t)DISK_BLOCK_KB;
531     filesize = (off_t)DISK_BLOCK_KB;
532     headersize += DISK_BLOCK_KB;
533
534     /*
535      * We've written the file header.  Now, just write data until the
536      * end.
537      */
538     while ((nread = fullread(infd, db->buf,
539                              (size_t)(db->datalimit - db->datain))) > 0) {
540         db->datain += nread;
541         while(db->dataout < db->datain) {
542             if(!databuf_flush(db)) {
543                 return 0;
544             }
545         }
546     }
547     while(db->dataout < db->datain) {
548         if(!databuf_flush(db)) {
549             return 0;
550         }
551     }
552     if(dumpbytes > (off_t)0) {
553         dumpsize += (off_t)1;                   /* count partial final KByte */
554         filesize += (off_t)1;
555     }
556     return 1;
557 }
558
559 /*
560  * Initialize a databuf.  Takes a writeable file descriptor.
561  */
562 static void
563 databuf_init(
564     struct databuf *    db,
565     int                 fd,
566     char *              filename,
567     off_t               use,
568     off_t               chunk_size)
569 {
570     db->fd = fd;
571     db->filename = stralloc(filename);
572     db->filename_seq = (off_t)0;
573     db->chunk_size = chunk_size;
574     db->split_size = (db->chunk_size > use) ? use : db->chunk_size;
575     db->use = (use > db->split_size) ? use - db->split_size : (off_t)0;
576     db->datain = db->dataout = db->buf;
577     db->datalimit = db->buf + SIZEOF(db->buf);
578 }
579
580
581 /*
582  * Write out the buffer to the backing file
583  */
584 static int
585 databuf_flush(
586     struct databuf *    db)
587 {
588     struct cmdargs cmdargs;
589     int rc = 1;
590     ssize_t written;
591     off_t left_in_chunk;
592     char *arg_filename = NULL;
593     char *qarg_filename = NULL;
594     char *new_filename = NULL;
595     char *tmp_filename = NULL;
596     char sequence[NUM_STR_SIZE];
597     int newfd;
598     filetype_t save_type;
599     char *q;
600     int a;
601     char *pc;
602
603     /*
604      * If there's no data, do nothing.
605      */
606     if (db->dataout >= db->datain) {
607         goto common_exit;
608     }
609
610     /*
611      * See if we need to split this file.
612      */
613     while (db->split_size > (off_t)0 && dumpsize >= db->split_size) {
614         if( db->use == (off_t)0 ) {
615             /*
616              * Probably no more space on this disk.  Request some more.
617              */
618             cmd_t cmd;
619
620             putresult(RQ_MORE_DISK, "%s\n", handle);
621             cmd = getcmd(&cmdargs);
622             if(command_in_transit == -1 &&
623                (cmd == DONE || cmd == TRYAGAIN || cmd == FAILED)) {
624                 command_in_transit = cmd;
625                 cmd = getcmd(&cmdargs);
626             }
627             if(cmd == CONTINUE) {
628                 /*
629                  * CONTINUE
630                  *   serial
631                  *   filename
632                  *   chunksize
633                  *   use
634                  */
635                 cmdargs.argc++;                 /* true count of args */
636                 a = 3;
637
638                 if(a >= cmdargs.argc) {
639                     error("error [chunker CONTINUE: not enough args: filename]");
640                     /*NOTREACHED*/
641                 }
642                 qarg_filename = newstralloc(qarg_filename, cmdargs.argv[a++]);
643                 if (arg_filename != NULL)
644                     amfree(arg_filename);
645                 arg_filename = unquote_string(qarg_filename);
646
647                 if(a >= cmdargs.argc) {
648                     error("error [chunker CONTINUE: not enough args: chunksize]");
649                     /*NOTREACHED*/
650                 }
651                 db->chunk_size = OFF_T_ATOI(cmdargs.argv[a++]);
652                 db->chunk_size = am_floor(db->chunk_size, (off_t)DISK_BLOCK_KB);
653
654                 if(a >= cmdargs.argc) {
655                     error("error [chunker CONTINUE: not enough args: use]");
656                     /*NOTREACHED*/
657                 }
658                 db->use = OFF_T_ATOI(cmdargs.argv[a++]);
659
660                 if(a != cmdargs.argc) {
661                     error("error [chunker CONTINUE: too many args: %d != %d]",
662                           cmdargs.argc, a);
663                     /*NOTREACHED*/
664                 }
665
666                 if(strcmp(db->filename, arg_filename) == 0) {
667                     /*
668                      * Same disk, so use what room is left up to the
669                      * next chunk boundary or the amount we were given,
670                      * whichever is less.
671                      */
672                     left_in_chunk = db->chunk_size - filesize;
673                     if(left_in_chunk > db->use) {
674                         db->split_size += db->use;
675                         db->use = (off_t)0;
676                     } else {
677                         db->split_size += left_in_chunk;
678                         db->use -= left_in_chunk;
679                     }
680                     if(left_in_chunk > (off_t)0) {
681                         /*
682                          * We still have space in this chunk.
683                          */
684                         break;
685                     }
686                 } else {
687                     /*
688                      * Different disk, so use new file.
689                      */
690                     db->filename = newstralloc(db->filename, arg_filename);
691                 }
692             } else if(cmd == ABORT) {
693                 abort_pending = 1;
694                 errstr = newstralloc(errstr, "ERROR");
695                 putresult(ABORT_FINISHED, "%s\n", handle);
696                 rc = 0;
697                 goto common_exit;
698             } else {
699                 if(cmdargs.argc >= 1) {
700                     q = squote(cmdargs.argv[1]);
701                 } else if(cmdargs.argc >= 0) {
702                     q = squote(cmdargs.argv[0]);
703                 } else {
704                     q = stralloc("(no input?)");
705                 }
706                 error("error [bad command after RQ-MORE-DISK: \"%s\"]", q);
707                 /*NOTREACHED*/
708             }
709         }
710
711         /*
712          * Time to use another file.
713          */
714
715         /*
716          * First, open the new chunk file, and give it a new header
717          * that has no cont_filename pointer.
718          */
719         snprintf(sequence, SIZEOF(sequence), "%d", db->filename_seq);
720         new_filename = newvstralloc(new_filename,
721                                     db->filename,
722                                     ".",
723                                     sequence,
724                                     NULL);
725         tmp_filename = newvstralloc(tmp_filename,
726                                     new_filename,
727                                     ".tmp",
728                                     NULL);
729         pc = strrchr(tmp_filename, '/');
730         *pc = '\0';
731         mkholdingdir(tmp_filename);
732         *pc = '/';
733         newfd = open(tmp_filename, O_RDWR|O_CREAT|O_TRUNC, 0600);
734         if (newfd == -1) {
735             int save_errno = errno;
736
737             if(save_errno == ENOSPC) {
738                 putresult(NO_ROOM, "%s " OFF_T_FMT "\n", handle, 
739                           (OFF_T_FMT_TYPE)(db->use+db->split_size-dumpsize));
740                 db->use = (off_t)0;                     /* force RQ_MORE_DISK */
741                 db->split_size = dumpsize;
742                 continue;
743             }
744             errstr = squotef("creating chunk holding file \"%s\": %s",
745                              tmp_filename,
746                              strerror(errno));
747             aclose(db->fd);
748             rc = 0;
749             goto common_exit;
750         }
751         save_type = file.type;
752         file.type = F_CONT_DUMPFILE;
753         file.cont_filename[0] = '\0';
754         if(write_tapeheader(newfd, &file)) {
755             int save_errno = errno;
756
757             aclose(newfd);
758             if(save_errno == ENOSPC) {
759                 putresult(NO_ROOM, "%s " OFF_T_FMT "\n", handle, 
760                           (OFF_T_FMT_TYPE)(db->use+db->split_size-dumpsize));
761                 db->use = (off_t)0;                     /* force RQ_MORE DISK */
762                 db->split_size = dumpsize;
763                 continue;
764             }
765             errstr = squotef("write_tapeheader file %s: %s",
766                              tmp_filename,
767                              strerror(errno));
768             rc = 0;
769             goto common_exit;
770         }
771
772         /*
773          * Now, update the header of the current file to point
774          * to the next chunk, and then close it.
775          */
776         if (lseek(db->fd, (off_t)0, SEEK_SET) < (off_t)0) {
777             errstr = squotef("lseek holding file %s: %s",
778                              db->filename,
779                              strerror(errno));
780             aclose(newfd);
781             rc = 0;
782             goto common_exit;
783         }
784
785         file.type = save_type;
786         strncpy(file.cont_filename, new_filename, SIZEOF(file.cont_filename));
787         file.cont_filename[SIZEOF(file.cont_filename)] = '\0';
788         if(write_tapeheader(db->fd, &file)) {
789             errstr = squotef("write_tapeheader file \"%s\": %s",
790                              db->filename,
791                              strerror(errno));
792             aclose(newfd);
793             unlink(tmp_filename);
794             rc = 0;
795             goto common_exit;
796         }
797         file.type = F_CONT_DUMPFILE;
798
799         /*
800          * Now shift the file descriptor.
801          */
802         aclose(db->fd);
803         db->fd = newfd;
804         newfd = -1;
805
806         /*
807          * Update when we need to chunk again
808          */
809         if(db->use <= (off_t)DISK_BLOCK_KB) {
810             /*
811              * Cheat and use one more block than allowed so we can make
812              * some progress.
813              */
814             db->split_size += (off_t)(2 * DISK_BLOCK_KB);
815             db->use = (off_t)0;
816         } else if(db->chunk_size > db->use) {
817             db->split_size += db->use;
818             db->use = (off_t)0;
819         } else {
820             db->split_size += db->chunk_size;
821             db->use -= db->chunk_size;
822         }
823
824
825         amfree(tmp_filename);
826         amfree(new_filename);
827         dumpsize += (off_t)DISK_BLOCK_KB;
828         filesize = (off_t)DISK_BLOCK_KB;
829         headersize += DISK_BLOCK_KB;
830         db->filename_seq++;
831     }
832
833     /*
834      * Write out the buffer
835      */
836     written = fullwrite(db->fd, db->dataout,
837                         (size_t)(db->datain - db->dataout));
838     if (written > 0) {
839         db->dataout += written;
840         dumpbytes += (off_t)written;
841     }
842     dumpsize += (dumpbytes / (off_t)1024);
843     filesize += (dumpbytes / (off_t)1024);
844     dumpbytes %= 1024;
845     if (written < 0) {
846         if (errno != ENOSPC) {
847             errstr = squotef("data write: %s", strerror(errno));
848             rc = 0;
849             goto common_exit;
850         }
851
852         /*
853          * NO-ROOM is informational only.  Later, RQ_MORE_DISK will be
854          * issued to use another holding disk.
855          */
856         putresult(NO_ROOM, "%s " OFF_T_FMT "\n", handle,
857                   (OFF_T_FMT_TYPE)(db->use+db->split_size-dumpsize));
858         db->use = (off_t)0;                             /* force RQ_MORE_DISK */
859         db->split_size = dumpsize;
860         goto common_exit;
861     }
862     if (db->datain == db->dataout) {
863         /*
864          * We flushed the whole buffer so reset to use it all.
865          */
866         db->datain = db->dataout = db->buf;
867     }
868
869 common_exit:
870
871     amfree(new_filename);
872     /*@i@*/ amfree(tmp_filename);
873     amfree(arg_filename);
874     amfree(qarg_filename);
875     return rc;
876 }
877
878
879 /*
880  * Send an Amanda dump header to the output file.
881  */
882 static ssize_t
883 write_tapeheader(
884     int         outfd,
885     dumpfile_t *file)
886 {
887     char buffer[DISK_BLOCK_BYTES];
888     ssize_t written;
889
890     file->blocksize = DISK_BLOCK_BYTES;
891     build_header(buffer, file, SIZEOF(buffer));
892
893     written = fullwrite(outfd, buffer, SIZEOF(buffer));
894     if(written == (ssize_t)sizeof(buffer))
895         return 0;
896
897     if(written < 0)
898         return written;
899
900     errno = ENOSPC;
901     return (ssize_t)-1;
902 }