Imported Upstream version 2.6.0p2
[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 #include "timestamp.h"
48
49 #define chunker_debug(i, ...) do {      \
50         if ((i) <= debug_chunker) {     \
51             dbprintf(__VA_ARGS__);      \
52         }                               \
53 } while (0)
54
55 #ifndef SEEK_SET
56 #define SEEK_SET 0
57 #endif
58
59 #ifndef SEEK_CUR
60 #define SEEK_CUR 1
61 #endif
62
63 #define CONNECT_TIMEOUT 5*60
64
65 #define STARTUP_TIMEOUT 60
66
67 struct databuf {
68     int fd;                     /* file to flush to */
69     char *filename;             /* name of what fd points to */
70     int filename_seq;           /* for chunking */
71     off_t split_size;           /* when to chunk */
72     off_t chunk_size;           /* size of each chunk */
73     off_t use;                  /* size to use on this disk */
74     char buf[DISK_BLOCK_BYTES];
75     char *datain;               /* data buffer markers */
76     char *dataout;
77     char *datalimit;
78 };
79
80 static char *handle = NULL;
81
82 static char *errstr = NULL;
83 static int abort_pending;
84 static off_t dumpsize;
85 static unsigned long headersize;
86 static off_t dumpbytes;
87 static off_t filesize;
88
89 static char *hostname = NULL;
90 static char *diskname = NULL;
91 static char *qdiskname = NULL;
92 static char *options = NULL;
93 static char *progname = NULL;
94 static int level;
95 static char *dumpdate = NULL;
96 static int command_in_transit;
97 static char *chunker_timestamp = NULL;
98
99 static dumpfile_t file;
100
101 /* local functions */
102 int main(int, char **);
103 static ssize_t write_tapeheader(int, dumpfile_t *);
104 static void databuf_init(struct databuf *, int, char *, off_t, off_t);
105 static int databuf_flush(struct databuf *);
106
107 static int startup_chunker(char *, off_t, off_t, struct databuf *);
108 static int do_chunk(int, struct databuf *);
109
110
111 int
112 main(
113     int         argc,
114     char **     argv)
115 {
116     static struct databuf db;
117     struct cmdargs cmdargs;
118     cmd_t cmd;
119     int infd;
120     char *q = NULL;
121     char *filename = NULL;
122     char *qfilename = NULL;
123     off_t chunksize, use;
124     times_t runtime;
125     am_feature_t *their_features = NULL;
126     int a;
127     config_overwrites_t *cfg_ovr = NULL;
128     char *cfg_opt = NULL;
129
130     /*
131      * Configure program for internationalization:
132      *   1) Only set the message locale for now.
133      *   2) Set textdomain for all amanda related programs to "amanda"
134      *      We don't want to be forced to support dozens of message catalogs.
135      */  
136     setlocale(LC_MESSAGES, "C");
137     textdomain("amanda"); 
138
139     safe_fd(-1, 0);
140
141     set_pname("chunker");
142
143     dbopen(DBG_SUBDIR_SERVER);
144
145     /* Don't die when child closes pipe */
146     signal(SIGPIPE, SIG_IGN);
147
148     erroutput_type = (ERR_AMANDALOG|ERR_INTERACTIVE);
149     set_logerror(logerror);
150
151     cfg_ovr = extract_commandline_config_overwrites(&argc, &argv);
152
153     if (argc > 1) 
154         cfg_opt = argv[1];
155
156     config_init(CONFIG_INIT_EXPLICIT_NAME | CONFIG_INIT_USE_CWD | CONFIG_INIT_FATAL,
157                 cfg_opt);
158     apply_config_overwrites(cfg_ovr);
159
160     safe_cd(); /* do this *after* config_init() */
161
162     check_running_as(RUNNING_AS_DUMPUSER);
163
164     dbrename(config_name, DBG_SUBDIR_SERVER);
165
166     g_fprintf(stderr,
167             _("%s: pid %ld executable %s version %s\n"),
168             get_pname(), (long) getpid(),
169             argv[0], version());
170     fflush(stderr);
171
172     /* now, make sure we are a valid user */
173
174     signal(SIGPIPE, SIG_IGN);
175     signal(SIGCHLD, SIG_IGN);
176
177     cmd = getcmd(&cmdargs);
178     if(cmd == START) {
179         if(cmdargs.argc <= 1)
180             error(_("error [dumper START: not enough args: timestamp]"));
181         chunker_timestamp = newstralloc(chunker_timestamp, cmdargs.argv[2]);
182     }
183     else {
184         error(_("Didn't get START command"));
185     }
186
187 /*    do {*/
188         cmd = getcmd(&cmdargs);
189
190         switch(cmd) {
191         case QUIT:
192             break;
193
194         case PORT_WRITE:
195             /*
196              * PORT-WRITE
197              *   handle
198              *   filename
199              *   host
200              *   features
201              *   disk
202              *   level
203              *   dumpdate
204              *   chunksize
205              *   progname
206              *   use
207              *   options
208              */
209             cmdargs.argc++;                     /* true count of args */
210             a = 2;
211
212             if(a >= cmdargs.argc) {
213                 error(_("error [chunker PORT-WRITE: not enough args: handle]"));
214                 /*NOTREACHED*/
215             }
216             handle = newstralloc(handle, cmdargs.argv[a++]);
217
218             if(a >= cmdargs.argc) {
219                 error(_("error [chunker PORT-WRITE: not enough args: filename]"));
220                 /*NOTREACHED*/
221             }
222             qfilename = newstralloc(qfilename, cmdargs.argv[a++]);
223             if (filename != NULL)
224                 amfree(filename);
225             filename = unquote_string(qfilename);
226             amfree(qfilename);
227
228             if(a >= cmdargs.argc) {
229                 error(_("error [chunker PORT-WRITE: not enough args: hostname]"));
230                 /*NOTREACHED*/
231             }
232             hostname = newstralloc(hostname, cmdargs.argv[a++]);
233
234             if(a >= cmdargs.argc) {
235                 error(_("error [chunker PORT-WRITE: not enough args: features]"));
236                 /*NOTREACHED*/
237             }
238             am_release_feature_set(their_features);
239             their_features = am_string_to_feature(cmdargs.argv[a++]);
240
241             if(a >= cmdargs.argc) {
242                 error(_("error [chunker PORT-WRITE: not enough args: diskname]"));
243                 /*NOTREACHED*/
244             }
245             qdiskname = newstralloc(qdiskname, cmdargs.argv[a++]);
246             if (diskname != NULL)
247                 amfree(diskname);
248             diskname = unquote_string(qdiskname);
249
250             if(a >= cmdargs.argc) {
251                 error(_("error [chunker PORT-WRITE: not enough args: level]"));
252                 /*NOTREACHED*/
253             }
254             level = atoi(cmdargs.argv[a++]);
255
256             if(a >= cmdargs.argc) {
257                 error(_("error [chunker PORT-WRITE: not enough args: dumpdate]"));
258                 /*NOTREACHED*/
259             }
260             dumpdate = newstralloc(dumpdate, cmdargs.argv[a++]);
261
262             if(a >= cmdargs.argc) {
263                 error(_("error [chunker PORT-WRITE: not enough args: chunksize]"));
264                 /*NOTREACHED*/
265             }
266             chunksize = OFF_T_ATOI(cmdargs.argv[a++]);
267             chunksize = am_floor(chunksize, (off_t)DISK_BLOCK_KB);
268
269             if(a >= cmdargs.argc) {
270                 error(_("error [chunker PORT-WRITE: not enough args: progname]"));
271                 /*NOTREACHED*/
272             }
273             progname = newstralloc(progname, cmdargs.argv[a++]);
274
275             if(a >= cmdargs.argc) {
276                 error(_("error [chunker PORT-WRITE: not enough args: use]"));
277                 /*NOTREACHED*/
278             }
279             use = am_floor(OFF_T_ATOI(cmdargs.argv[a++]), DISK_BLOCK_KB);
280
281             if(a >= cmdargs.argc) {
282                 error(_("error [chunker PORT-WRITE: not enough args: options]"));
283                 /*NOTREACHED*/
284             }
285             options = newstralloc(options, cmdargs.argv[a++]);
286
287             if(a != cmdargs.argc) {
288                 error(_("error [chunker PORT-WRITE: too many args: %d != %d]"),
289                       cmdargs.argc, a);
290                 /*NOTREACHED*/
291             }
292
293             if((infd = startup_chunker(filename, use, chunksize, &db)) < 0) {
294                 q = squotef(_("[chunker startup failed: %s]"), errstr);
295                 putresult(TRYAGAIN, "%s %s\n", handle, q);
296                 error("startup_chunker failed");
297             }
298             command_in_transit = -1;
299             if(infd >= 0 && do_chunk(infd, &db)) {
300                 char kb_str[NUM_STR_SIZE];
301                 char kps_str[NUM_STR_SIZE];
302                 double rt;
303
304                 runtime = stopclock();
305                 rt = g_timeval_to_double(runtime);
306                 g_snprintf(kb_str, SIZEOF(kb_str), "%lld",
307                          (long long)(dumpsize - (off_t)headersize));
308                 g_snprintf(kps_str, SIZEOF(kps_str), "%3.1lf",
309                                 isnormal(rt) ? (double)dumpsize / rt : 0.0);
310                 errstr = newvstrallocf(errstr, "sec %s kb %s kps %s",
311                                 walltime_str(runtime), kb_str, kps_str);
312                 q = squotef("[%s]", errstr);
313                 if(command_in_transit != -1)
314                     cmd = command_in_transit;
315                 else
316                     cmd = getcmd(&cmdargs);
317                 switch(cmd) {
318                 case DONE:
319                     putresult(DONE, "%s %lld %s\n", handle,
320                              (long long)(dumpsize - (off_t)headersize), q);
321                     log_add(L_SUCCESS, "%s %s %s %d [%s]",
322                             hostname, qdiskname, chunker_timestamp, level, errstr);
323                     break;
324                 case BOGUS:
325                 case TRYAGAIN:
326                 case FAILED:
327                 case ABORT_FINISHED:
328                     if(dumpsize > (off_t)DISK_BLOCK_KB) {
329                         putresult(PARTIAL, "%s %lld %s\n", handle,
330                                  (long long)(dumpsize - (off_t)headersize),
331                                  q);
332                         log_add(L_PARTIAL, "%s %s %s %d [%s]",
333                                 hostname, qdiskname, chunker_timestamp, level, errstr);
334                     }
335                     else {
336                         errstr = newvstrallocf(errstr,
337                                         _("dumper returned %s"), cmdstr[cmd]);
338                         amfree(q);
339                         q = squotef("[%s]",errstr);
340                         putresult(FAILED, "%s %s\n", handle, q);
341                         log_add(L_FAIL, "%s %s %s %d [%s]",
342                                 hostname, qdiskname, chunker_timestamp, level, errstr);
343                     }
344                 default: break;
345                 }
346                 amfree(q);
347             } else if(infd != -2) {
348                 if(!abort_pending) {
349                     if(q == NULL) {
350                         q = squotef("[%s]", errstr);
351                     }
352                     putresult(FAILED, "%s %s\n", handle, q);
353                     log_add(L_FAIL, "%s %s %s %d [%s]",
354                             hostname, qdiskname, chunker_timestamp, level, errstr);
355                     amfree(q);
356                 }
357             }
358             amfree(filename);
359             amfree(db.filename);
360             break;
361
362         default:
363             if(cmdargs.argc >= 1) {
364                 q = squote(cmdargs.argv[1]);
365             } else if(cmdargs.argc >= 0) {
366                 q = squote(cmdargs.argv[0]);
367             } else {
368                 q = stralloc(_("(no input?)"));
369             }
370             putresult(BAD_COMMAND, "%s\n", q);
371             amfree(q);
372             break;
373         }
374
375 /*    } while(cmd != QUIT); */
376
377     amfree(errstr);
378     amfree(chunker_timestamp);
379     amfree(handle);
380     amfree(hostname);
381     amfree(diskname);
382     amfree(qdiskname);
383     amfree(dumpdate);
384     amfree(progname);
385     amfree(options);
386     am_release_feature_set(their_features);
387     their_features = NULL;
388
389     dbclose();
390
391     return (0); /* exit */
392 }
393
394 /*
395  * Returns a file descriptor to the incoming port
396  * on success, or -1 on error.
397  */
398 static int
399 startup_chunker(
400     char *              filename,
401     off_t               use,
402     off_t               chunksize,
403     struct databuf *    db)
404 {
405     int infd, outfd;
406     char *tmp_filename, *pc;
407     in_port_t data_port;
408     int data_socket;
409     int result;
410     struct addrinfo *res;
411
412     data_port = 0;
413     if ((result = resolve_hostname("localhost", 0, &res, NULL) != 0)) {
414         errstr = newvstrallocf(errstr, _("could not resolve localhost: %s"),
415                                gai_strerror(result));
416         return -1;
417     }
418     data_socket = stream_server(res->ai_family, &data_port, 0,
419                                 STREAM_BUFSIZE, 0);
420
421     if(data_socket < 0) {
422         errstr = vstrallocf(_("error creating stream server: %s"), strerror(errno));
423         return -1;
424     }
425
426     putresult(PORT, "%d\n", data_port);
427
428     infd = stream_accept(data_socket, CONNECT_TIMEOUT, 0, STREAM_BUFSIZE);
429     if(infd == -1) {
430         errstr = vstrallocf(_("error accepting stream: %s"), strerror(errno));
431         return -1;
432     }
433
434     tmp_filename = vstralloc(filename, ".tmp", NULL);
435     pc = strrchr(tmp_filename, '/');
436     *pc = '\0';
437     mkholdingdir(tmp_filename);
438     *pc = '/';
439     if ((outfd = open(tmp_filename, O_RDWR|O_CREAT|O_TRUNC, 0600)) < 0) {
440         int save_errno = errno;
441
442         errstr = squotef(_("holding file \"%s\": %s"),
443                          tmp_filename,
444                          strerror(errno));
445         amfree(tmp_filename);
446         aclose(infd);
447         if(save_errno == ENOSPC) {
448             putresult(NO_ROOM, "%s %lld\n",
449                       handle, (long long)use);
450             return -2;
451         } else {
452             return -1;
453         }
454     }
455     amfree(tmp_filename);
456     databuf_init(db, outfd, filename, use, chunksize);
457     db->filename_seq++;
458     return infd;
459 }
460
461 static int
462 do_chunk(
463     int                 infd,
464     struct databuf *    db)
465 {
466     ssize_t nread;
467     char header_buf[DISK_BLOCK_BYTES];
468
469     startclock();
470
471     dumpsize = dumpbytes = filesize = (off_t)0;
472     headersize = 0;
473     memset(header_buf, 0, sizeof(header_buf));
474
475     /*
476      * The first thing we should receive is the file header, which we
477      * need to save into "file", as well as write out.  Later, the
478      * chunk code will rewrite it.
479      */
480     nread = fullread(infd, header_buf, SIZEOF(header_buf));
481     if (nread != DISK_BLOCK_BYTES) {
482         if(nread < 0) {
483             errstr = vstrallocf(_("cannot read header: %s"), strerror(errno));
484         } else {
485             errstr = vstrallocf(_("cannot read header: got %zd bytes instead of %d"),
486                                 nread,
487                                 DISK_BLOCK_BYTES);
488         }
489         return 0;
490     }
491     parse_file_header(header_buf, &file, (size_t)nread);
492     if(write_tapeheader(db->fd, &file)) {
493         int save_errno = errno;
494
495         errstr = squotef(_("write_tapeheader file %s: %s"),
496                          db->filename, strerror(errno));
497         if(save_errno == ENOSPC) {
498             putresult(NO_ROOM, "%s %lld\n", handle, 
499                       (long long)(db->use+db->split_size-dumpsize));
500         }
501         return 0;
502     }
503     dumpsize += (off_t)DISK_BLOCK_KB;
504     filesize = (off_t)DISK_BLOCK_KB;
505     headersize += DISK_BLOCK_KB;
506
507     /*
508      * We've written the file header.  Now, just write data until the
509      * end.
510      */
511     while ((nread = fullread(infd, db->buf,
512                              (size_t)(db->datalimit - db->datain))) > 0) {
513         db->datain += nread;
514         while(db->dataout < db->datain) {
515             if(!databuf_flush(db)) {
516                 return 0;
517             }
518         }
519     }
520     while(db->dataout < db->datain) {
521         if(!databuf_flush(db)) {
522             return 0;
523         }
524     }
525     if(dumpbytes > (off_t)0) {
526         dumpsize += (off_t)1;                   /* count partial final KByte */
527         filesize += (off_t)1;
528     }
529     return 1;
530 }
531
532 /*
533  * Initialize a databuf.  Takes a writeable file descriptor.
534  */
535 static void
536 databuf_init(
537     struct databuf *    db,
538     int                 fd,
539     char *              filename,
540     off_t               use,
541     off_t               chunk_size)
542 {
543     db->fd = fd;
544     db->filename = stralloc(filename);
545     db->filename_seq = (off_t)0;
546     db->chunk_size = chunk_size;
547     db->split_size = (db->chunk_size > use) ? use : db->chunk_size;
548     db->use = (use > db->split_size) ? use - db->split_size : (off_t)0;
549     db->datain = db->dataout = db->buf;
550     db->datalimit = db->buf + SIZEOF(db->buf);
551 }
552
553
554 /*
555  * Write out the buffer to the backing file
556  */
557 static int
558 databuf_flush(
559     struct databuf *    db)
560 {
561     struct cmdargs cmdargs;
562     int rc = 1;
563     ssize_t written;
564     off_t left_in_chunk;
565     char *arg_filename = NULL;
566     char *qarg_filename = NULL;
567     char *new_filename = NULL;
568     char *tmp_filename = NULL;
569     char sequence[NUM_STR_SIZE];
570     int newfd;
571     filetype_t save_type;
572     char *q;
573     int a;
574     char *pc;
575
576     /*
577      * If there's no data, do nothing.
578      */
579     if (db->dataout >= db->datain) {
580         goto common_exit;
581     }
582
583     /*
584      * See if we need to split this file.
585      */
586     while (db->split_size > (off_t)0 && dumpsize >= db->split_size) {
587         if( db->use == (off_t)0 ) {
588             /*
589              * Probably no more space on this disk.  Request some more.
590              */
591             cmd_t cmd;
592
593             putresult(RQ_MORE_DISK, "%s\n", handle);
594             cmd = getcmd(&cmdargs);
595             if(command_in_transit == -1 &&
596                (cmd == DONE || cmd == TRYAGAIN || cmd == FAILED)) {
597                 command_in_transit = cmd;
598                 cmd = getcmd(&cmdargs);
599             }
600             if(cmd == CONTINUE) {
601                 /*
602                  * CONTINUE
603                  *   serial
604                  *   filename
605                  *   chunksize
606                  *   use
607                  */
608                 cmdargs.argc++;                 /* true count of args */
609                 a = 3;
610
611                 if(a >= cmdargs.argc) {
612                     error(_("error [chunker CONTINUE: not enough args: filename]"));
613                     /*NOTREACHED*/
614                 }
615                 qarg_filename = newstralloc(qarg_filename, cmdargs.argv[a++]);
616                 if (arg_filename != NULL)
617                     amfree(arg_filename);
618                 arg_filename = unquote_string(qarg_filename);
619
620                 if(a >= cmdargs.argc) {
621                     error(_("error [chunker CONTINUE: not enough args: chunksize]"));
622                     /*NOTREACHED*/
623                 }
624                 db->chunk_size = OFF_T_ATOI(cmdargs.argv[a++]);
625                 db->chunk_size = am_floor(db->chunk_size, (off_t)DISK_BLOCK_KB);
626
627                 if(a >= cmdargs.argc) {
628                     error(_("error [chunker CONTINUE: not enough args: use]"));
629                     /*NOTREACHED*/
630                 }
631                 db->use = OFF_T_ATOI(cmdargs.argv[a++]);
632
633                 if(a != cmdargs.argc) {
634                     error(_("error [chunker CONTINUE: too many args: %d != %d]"),
635                           cmdargs.argc, a);
636                     /*NOTREACHED*/
637                 }
638
639                 if(strcmp(db->filename, arg_filename) == 0) {
640                     /*
641                      * Same disk, so use what room is left up to the
642                      * next chunk boundary or the amount we were given,
643                      * whichever is less.
644                      */
645                     left_in_chunk = db->chunk_size - filesize;
646                     if(left_in_chunk > db->use) {
647                         db->split_size += db->use;
648                         db->use = (off_t)0;
649                     } else {
650                         db->split_size += left_in_chunk;
651                         db->use -= left_in_chunk;
652                     }
653                     if(left_in_chunk > (off_t)0) {
654                         /*
655                          * We still have space in this chunk.
656                          */
657                         break;
658                     }
659                 } else {
660                     /*
661                      * Different disk, so use new file.
662                      */
663                     db->filename = newstralloc(db->filename, arg_filename);
664                 }
665             } else if(cmd == ABORT) {
666                 abort_pending = 1;
667                 errstr = newstralloc(errstr, "ERROR");
668                 putresult(ABORT_FINISHED, "%s\n", handle);
669                 rc = 0;
670                 goto common_exit;
671             } else {
672                 if(cmdargs.argc >= 1) {
673                     q = squote(cmdargs.argv[1]);
674                 } else if(cmdargs.argc >= 0) {
675                     q = squote(cmdargs.argv[0]);
676                 } else {
677                     q = stralloc(_("(no input?)"));
678                 }
679                 error(_("error [bad command after RQ-MORE-DISK: \"%s\"]"), q);
680                 /*NOTREACHED*/
681             }
682         }
683
684         /*
685          * Time to use another file.
686          */
687
688         /*
689          * First, open the new chunk file, and give it a new header
690          * that has no cont_filename pointer.
691          */
692         g_snprintf(sequence, SIZEOF(sequence), "%d", db->filename_seq);
693         new_filename = newvstralloc(new_filename,
694                                     db->filename,
695                                     ".",
696                                     sequence,
697                                     NULL);
698         tmp_filename = newvstralloc(tmp_filename,
699                                     new_filename,
700                                     ".tmp",
701                                     NULL);
702         pc = strrchr(tmp_filename, '/');
703         *pc = '\0';
704         mkholdingdir(tmp_filename);
705         *pc = '/';
706         newfd = open(tmp_filename, O_RDWR|O_CREAT|O_TRUNC, 0600);
707         if (newfd == -1) {
708             int save_errno = errno;
709
710             if(save_errno == ENOSPC) {
711                 putresult(NO_ROOM, "%s %lld\n", handle, 
712                           (long long)(db->use+db->split_size-dumpsize));
713                 db->use = (off_t)0;                     /* force RQ_MORE_DISK */
714                 db->split_size = dumpsize;
715                 continue;
716             }
717             errstr = squotef(_("creating chunk holding file \"%s\": %s"),
718                              tmp_filename,
719                              strerror(errno));
720             aclose(db->fd);
721             rc = 0;
722             goto common_exit;
723         }
724         save_type = file.type;
725         file.type = F_CONT_DUMPFILE;
726         file.cont_filename[0] = '\0';
727         if(write_tapeheader(newfd, &file)) {
728             int save_errno = errno;
729
730             aclose(newfd);
731             if(save_errno == ENOSPC) {
732                 putresult(NO_ROOM, "%s %lld\n", handle, 
733                           (long long)(db->use+db->split_size-dumpsize));
734                 db->use = (off_t)0;                     /* force RQ_MORE DISK */
735                 db->split_size = dumpsize;
736                 continue;
737             }
738             errstr = squotef(_("write_tapeheader file %s: %s"),
739                              tmp_filename,
740                              strerror(errno));
741             rc = 0;
742             goto common_exit;
743         }
744
745         /*
746          * Now, update the header of the current file to point
747          * to the next chunk, and then close it.
748          */
749         if (lseek(db->fd, (off_t)0, SEEK_SET) < (off_t)0) {
750             errstr = squotef(_("lseek holding file %s: %s"),
751                              db->filename,
752                              strerror(errno));
753             aclose(newfd);
754             rc = 0;
755             goto common_exit;
756         }
757
758         file.type = save_type;
759         strncpy(file.cont_filename, new_filename, SIZEOF(file.cont_filename));
760         file.cont_filename[SIZEOF(file.cont_filename)-1] = '\0';
761         if(write_tapeheader(db->fd, &file)) {
762             errstr = squotef(_("write_tapeheader file \"%s\": %s"),
763                              db->filename,
764                              strerror(errno));
765             aclose(newfd);
766             unlink(tmp_filename);
767             rc = 0;
768             goto common_exit;
769         }
770         file.type = F_CONT_DUMPFILE;
771
772         /*
773          * Now shift the file descriptor.
774          */
775         aclose(db->fd);
776         db->fd = newfd;
777         newfd = -1;
778
779         /*
780          * Update when we need to chunk again
781          */
782         if(db->use <= (off_t)DISK_BLOCK_KB) {
783             /*
784              * Cheat and use one more block than allowed so we can make
785              * some progress.
786              */
787             db->split_size += (off_t)(2 * DISK_BLOCK_KB);
788             db->use = (off_t)0;
789         } else if(db->chunk_size > db->use) {
790             db->split_size += db->use;
791             db->use = (off_t)0;
792         } else {
793             db->split_size += db->chunk_size;
794             db->use -= db->chunk_size;
795         }
796
797
798         amfree(tmp_filename);
799         amfree(new_filename);
800         dumpsize += (off_t)DISK_BLOCK_KB;
801         filesize = (off_t)DISK_BLOCK_KB;
802         headersize += DISK_BLOCK_KB;
803         db->filename_seq++;
804     }
805
806     /*
807      * Write out the buffer
808      */
809     written = fullwrite(db->fd, db->dataout,
810                         (size_t)(db->datain - db->dataout));
811     if (written > 0) {
812         db->dataout += written;
813         dumpbytes += (off_t)written;
814     }
815     dumpsize += (dumpbytes / (off_t)1024);
816     filesize += (dumpbytes / (off_t)1024);
817     dumpbytes %= 1024;
818     if (written < 0) {
819         if (errno != ENOSPC) {
820             errstr = squotef(_("data write: %s"), strerror(errno));
821             rc = 0;
822             goto common_exit;
823         }
824
825         /*
826          * NO-ROOM is informational only.  Later, RQ_MORE_DISK will be
827          * issued to use another holding disk.
828          */
829         putresult(NO_ROOM, "%s %lld\n", handle,
830                   (long long)(db->use+db->split_size-dumpsize));
831         db->use = (off_t)0;                             /* force RQ_MORE_DISK */
832         db->split_size = dumpsize;
833         goto common_exit;
834     }
835     if (db->datain == db->dataout) {
836         /*
837          * We flushed the whole buffer so reset to use it all.
838          */
839         db->datain = db->dataout = db->buf;
840     }
841
842 common_exit:
843
844     amfree(new_filename);
845     /*@i@*/ amfree(tmp_filename);
846     amfree(arg_filename);
847     amfree(qarg_filename);
848     return rc;
849 }
850
851
852 /*
853  * Send an Amanda dump header to the output file and set file->blocksize
854  */
855 static ssize_t
856 write_tapeheader(
857     int         outfd,
858     dumpfile_t *file)
859 {
860     char *buffer;
861     ssize_t written;
862
863     file->blocksize = DISK_BLOCK_BYTES;
864     buffer = build_header(file, DISK_BLOCK_BYTES);
865
866     written = fullwrite(outfd, buffer, DISK_BLOCK_BYTES);
867     amfree(buffer);
868     if(written == DISK_BLOCK_BYTES) return 0;
869     if(written < 0) return written;
870     errno = ENOSPC;
871     return (ssize_t)-1;
872 }