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