a652a76aa5e1a9ebb896e46a431a12d9f5abf7f9
[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             if (!their_features) {
242                 error(_("error [chunker PORT-WRITE: invalid feature string]"));
243                 /*NOTREACHED*/
244             }
245
246             if(a >= cmdargs->argc) {
247                 error(_("error [chunker PORT-WRITE: not enough args: diskname]"));
248                 /*NOTREACHED*/
249             }
250             diskname = newstralloc(diskname, cmdargs->argv[a++]);
251             if (qdiskname)
252                 amfree(qdiskname);
253             qdiskname = quote_string(diskname); /* qdiskname is a global */
254
255             if(a >= cmdargs->argc) {
256                 error(_("error [chunker PORT-WRITE: not enough args: level]"));
257                 /*NOTREACHED*/
258             }
259             level = atoi(cmdargs->argv[a++]);
260
261             if(a >= cmdargs->argc) {
262                 error(_("error [chunker PORT-WRITE: not enough args: dumpdate]"));
263                 /*NOTREACHED*/
264             }
265             dumpdate = newstralloc(dumpdate, cmdargs->argv[a++]);
266
267             if(a >= cmdargs->argc) {
268                 error(_("error [chunker PORT-WRITE: not enough args: chunksize]"));
269                 /*NOTREACHED*/
270             }
271             chunksize = OFF_T_ATOI(cmdargs->argv[a++]);
272             chunksize = am_floor(chunksize, (off_t)DISK_BLOCK_KB);
273
274             if(a >= cmdargs->argc) {
275                 error(_("error [chunker PORT-WRITE: not enough args: progname]"));
276                 /*NOTREACHED*/
277             }
278             progname = newstralloc(progname, cmdargs->argv[a++]);
279
280             if(a >= cmdargs->argc) {
281                 error(_("error [chunker PORT-WRITE: not enough args: use]"));
282                 /*NOTREACHED*/
283             }
284             use = am_floor(OFF_T_ATOI(cmdargs->argv[a++]), DISK_BLOCK_KB);
285
286             if(a >= cmdargs->argc) {
287                 error(_("error [chunker PORT-WRITE: not enough args: options]"));
288                 /*NOTREACHED*/
289             }
290             options = newstralloc(options, cmdargs->argv[a++]);
291
292             if(a != cmdargs->argc) {
293                 error(_("error [chunker PORT-WRITE: too many args: %d != %d]"),
294                       cmdargs->argc, a);
295                 /*NOTREACHED*/
296             }
297
298             if((infd = startup_chunker(filename, use, chunksize, &db)) < 0) {
299                 q = quote_string(vstrallocf(_("[chunker startup failed: %s]"), errstr));
300                 putresult(TRYAGAIN, "%s %s\n", handle, q);
301                 error("startup_chunker failed: %s", errstr);
302             }
303             command_in_transit = NULL;
304             if(infd >= 0 && do_chunk(infd, &db)) {
305                 char kb_str[NUM_STR_SIZE];
306                 char kps_str[NUM_STR_SIZE];
307                 double rt;
308
309                 runtime = stopclock();
310                 rt = g_timeval_to_double(runtime);
311                 g_snprintf(kb_str, SIZEOF(kb_str), "%lld",
312                          (long long)(dumpsize - (off_t)headersize));
313                 g_snprintf(kps_str, SIZEOF(kps_str), "%3.1lf",
314                                 isnormal(rt) ? (double)dumpsize / rt : 0.0);
315                 errstr = newvstrallocf(errstr, "sec %s kb %s kps %s",
316                                 walltime_str(runtime), kb_str, kps_str);
317                 m = vstrallocf("[%s]", errstr);
318                 q = quote_string(m);
319                 amfree(m);
320                 if(command_in_transit != NULL) {
321                     cmdargs = command_in_transit;
322                     command_in_transit = NULL;
323                 } else {
324                     cmdargs = getcmd();
325                 }
326                 switch(cmdargs->cmd) {
327                 case DONE:
328                     putresult(DONE, "%s %lld %s\n", handle,
329                              (long long)(dumpsize - (off_t)headersize), q);
330                     log_add(L_SUCCESS, "%s %s %s %d [%s]",
331                             hostname, qdiskname, chunker_timestamp, level, errstr);
332                     break;
333                 case BOGUS:
334                 case TRYAGAIN:
335                 case FAILED:
336                 case ABORT_FINISHED:
337                     if(dumpsize > (off_t)DISK_BLOCK_KB) {
338                         putresult(PARTIAL, "%s %lld %s\n", handle,
339                                  (long long)(dumpsize - (off_t)headersize),
340                                  q);
341                         log_add(L_PARTIAL, "%s %s %s %d [%s]",
342                                 hostname, qdiskname, chunker_timestamp, level, errstr);
343                     }
344                     else {
345                         errstr = newvstrallocf(errstr,
346                                         _("dumper returned %s"), cmdstr[cmdargs->cmd]);
347                         amfree(q);
348                         m = vstrallocf("[%s]",errstr);
349                         q = quote_string(m);
350                         amfree(m);
351                         putresult(FAILED, "%s %s\n", handle, q);
352                         log_add(L_FAIL, "%s %s %s %d [%s]",
353                                 hostname, qdiskname, chunker_timestamp, level, errstr);
354                     }
355                 default: break;
356                 }
357                 amfree(q);
358             } else if(infd != -2) {
359                 if(q == NULL) {
360                     m = vstrallocf("[%s]", errstr);
361                     q = quote_string(m);
362                     amfree(m);
363                 }
364                 if(!abort_pending) {
365                     putresult(FAILED, "%s %s\n", handle, q);
366                 }
367                 log_add(L_FAIL, "%s %s %s %d [%s]",
368                         hostname, qdiskname, chunker_timestamp, level, errstr);
369                 amfree(q);
370             }
371             amfree(filename);
372             amfree(db.filename);
373             break;
374
375         default:
376             if(cmdargs->argc >= 1) {
377                 q = quote_string(cmdargs->argv[0]);
378             } else {
379                 q = stralloc(_("(no input?)"));
380             }
381             putresult(BAD_COMMAND, "%s\n", q);
382             amfree(q);
383             break;
384         }
385
386 /*    } while(cmdargs->cmd != QUIT); */
387
388     log_add(L_INFO, "pid-done %ld", (long)getpid());
389
390     amfree(errstr);
391     amfree(chunker_timestamp);
392     amfree(handle);
393     amfree(hostname);
394     amfree(diskname);
395     amfree(qdiskname);
396     amfree(dumpdate);
397     amfree(progname);
398     amfree(options);
399     free_cmdargs(cmdargs);
400     if (command_in_transit)
401         free_cmdargs(command_in_transit);
402     am_release_feature_set(their_features);
403     their_features = NULL;
404
405     dbclose();
406
407     return (0); /* exit */
408 }
409
410 /*
411  * Returns a file descriptor to the incoming port
412  * on success, or -1 on error.
413  */
414 static int
415 startup_chunker(
416     char *              filename,
417     off_t               use,
418     off_t               chunksize,
419     struct databuf *    db)
420 {
421     int infd, outfd;
422     char *tmp_filename, *pc;
423     in_port_t data_port;
424     int data_socket;
425     int result;
426     struct addrinfo *res;
427
428     data_port = 0;
429     if ((result = resolve_hostname("localhost", 0, &res, NULL) != 0)) {
430         errstr = newvstrallocf(errstr, _("could not resolve localhost: %s"),
431                                gai_strerror(result));
432         return -1;
433     }
434     data_socket = stream_server(res->ai_family, &data_port, 0,
435                                 STREAM_BUFSIZE, 0);
436     if (res) freeaddrinfo(res);
437
438     if(data_socket < 0) {
439         errstr = vstrallocf(_("error creating stream server: %s"), strerror(errno));
440         return -1;
441     }
442
443     putresult(PORT, "%d\n", data_port);
444
445     infd = stream_accept(data_socket, CONNECT_TIMEOUT, 0, STREAM_BUFSIZE);
446     aclose(data_socket);
447     if(infd == -1) {
448         errstr = vstrallocf(_("error accepting stream: %s"), strerror(errno));
449         return -1;
450     }
451
452     tmp_filename = vstralloc(filename, ".tmp", NULL);
453     pc = strrchr(tmp_filename, '/');
454     g_assert(pc != NULL);
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         char *m = vstrallocf(_("holding file \"%s\": %s"),
461                          tmp_filename,
462                          strerror(errno));
463
464         errstr = quote_string(m);
465         amfree(m);
466         amfree(tmp_filename);
467         aclose(infd);
468         if(save_errno == ENOSPC) {
469             putresult(NO_ROOM, "%s %lld\n",
470                       handle, (long long)use);
471             return -2;
472         } else {
473             return -1;
474         }
475     }
476     amfree(tmp_filename);
477     databuf_init(db, outfd, filename, use, chunksize);
478     db->filename_seq++;
479     return infd;
480 }
481
482 static int
483 do_chunk(
484     int                 infd,
485     struct databuf *    db)
486 {
487     size_t nread;
488     char header_buf[DISK_BLOCK_BYTES];
489
490     startclock();
491
492     dumpsize = dumpbytes = filesize = (off_t)0;
493     headersize = 0;
494     memset(header_buf, 0, sizeof(header_buf));
495
496     /*
497      * The first thing we should receive is the file header, which we
498      * need to save into "file", as well as write out.  Later, the
499      * chunk code will rewrite it.
500      */
501     nread = full_read(infd, header_buf, SIZEOF(header_buf));
502     if (nread != sizeof(header_buf)) {
503         if(errno != 0) {
504             errstr = vstrallocf(_("cannot read header: %s"), strerror(errno));
505         } else {
506             errstr = vstrallocf(_("cannot read header: got %zd bytes instead of %zd"),
507                                 nread, sizeof(header_buf));
508         }
509         return 0;
510     }
511     parse_file_header(header_buf, &file, (size_t)nread);
512     if(write_tapeheader(db->fd, &file)) {
513         int save_errno = errno;
514         char *m = vstrallocf(_("write_tapeheader file %s: %s"),
515                          db->filename, strerror(errno));
516         errstr = quote_string(m);
517         amfree(m);
518         if(save_errno == ENOSPC) {
519             putresult(NO_ROOM, "%s %lld\n", handle, 
520                       (long long)(db->use+db->split_size-dumpsize));
521         }
522         return 0;
523     }
524     dumpsize += (off_t)DISK_BLOCK_KB;
525     filesize = (off_t)DISK_BLOCK_KB;
526     headersize += DISK_BLOCK_KB;
527
528     /*
529      * We've written the file header.  Now, just write data until the
530      * end.
531      */
532     while ((nread = full_read(infd, db->buf,
533                              (size_t)(db->datalimit - db->datain))) > 0) {
534         db->datain += nread;
535         while(db->dataout < db->datain) {
536             if(!databuf_flush(db)) {
537                 return 0;
538             }
539         }
540     }
541     while(db->dataout < db->datain) {
542         if(!databuf_flush(db)) {
543             return 0;
544         }
545     }
546     if(dumpbytes > (off_t)0) {
547         dumpsize += (off_t)1;                   /* count partial final KByte */
548         filesize += (off_t)1;
549     }
550     return 1;
551 }
552
553 /*
554  * Initialize a databuf.  Takes a writeable file descriptor.
555  */
556 static void
557 databuf_init(
558     struct databuf *    db,
559     int                 fd,
560     char *              filename,
561     off_t               use,
562     off_t               chunk_size)
563 {
564     db->fd = fd;
565     db->filename = stralloc(filename);
566     db->filename_seq = (off_t)0;
567     db->chunk_size = chunk_size;
568     db->split_size = (db->chunk_size > use) ? use : db->chunk_size;
569     db->use = (use > db->split_size) ? use - db->split_size : (off_t)0;
570     db->datain = db->dataout = db->buf;
571     db->datalimit = db->buf + SIZEOF(db->buf);
572 }
573
574
575 /*
576  * Write out the buffer to the backing file
577  */
578 static int
579 databuf_flush(
580     struct databuf *    db)
581 {
582     struct cmdargs *cmdargs = NULL;
583     int rc = 1;
584     size_t written;
585     off_t left_in_chunk;
586     char *arg_filename = NULL;
587     char *new_filename = NULL;
588     char *tmp_filename = NULL;
589     char sequence[NUM_STR_SIZE];
590     int newfd;
591     filetype_t save_type;
592     char *q;
593     int a;
594     char *pc;
595
596     /*
597      * If there's no data, do nothing.
598      */
599     if (db->dataout >= db->datain) {
600         goto common_exit;
601     }
602
603     /*
604      * See if we need to split this file.
605      */
606     while (db->split_size > (off_t)0 && dumpsize >= db->split_size) {
607         if( db->use == (off_t)0 ) {
608             /*
609              * Probably no more space on this disk.  Request some more.
610              */
611             putresult(RQ_MORE_DISK, "%s\n", handle);
612             cmdargs = getcmd();
613             if(command_in_transit == NULL &&
614                (cmdargs->cmd == DONE || cmdargs->cmd == TRYAGAIN || cmdargs->cmd == FAILED)) {
615                 command_in_transit = cmdargs;
616                 cmdargs = getcmd();
617             }
618             if(cmdargs->cmd == CONTINUE) {
619                 /*
620                  * CONTINUE
621                  *   serial
622                  *   filename
623                  *   chunksize
624                  *   use
625                  */
626                 a = 2; /* skip CONTINUE and serial */
627
628                 if(a >= cmdargs->argc) {
629                     error(_("error [chunker CONTINUE: not enough args: filename]"));
630                     /*NOTREACHED*/
631                 }
632                 arg_filename = newstralloc(arg_filename, cmdargs->argv[a++]);
633
634                 if(a >= cmdargs->argc) {
635                     error(_("error [chunker CONTINUE: not enough args: chunksize]"));
636                     /*NOTREACHED*/
637                 }
638                 db->chunk_size = OFF_T_ATOI(cmdargs->argv[a++]);
639                 db->chunk_size = am_floor(db->chunk_size, (off_t)DISK_BLOCK_KB);
640
641                 if(a >= cmdargs->argc) {
642                     error(_("error [chunker CONTINUE: not enough args: use]"));
643                     /*NOTREACHED*/
644                 }
645                 db->use = OFF_T_ATOI(cmdargs->argv[a++]);
646
647                 if(a != cmdargs->argc) {
648                     error(_("error [chunker CONTINUE: too many args: %d != %d]"),
649                           cmdargs->argc, a);
650                     /*NOTREACHED*/
651                 }
652
653                 if(strcmp(db->filename, arg_filename) == 0) {
654                     /*
655                      * Same disk, so use what room is left up to the
656                      * next chunk boundary or the amount we were given,
657                      * whichever is less.
658                      */
659                     left_in_chunk = db->chunk_size - filesize;
660                     if(left_in_chunk > db->use) {
661                         db->split_size += db->use;
662                         db->use = (off_t)0;
663                     } else {
664                         db->split_size += left_in_chunk;
665                         db->use -= left_in_chunk;
666                     }
667                     if(left_in_chunk > (off_t)0) {
668                         /*
669                          * We still have space in this chunk.
670                          */
671                         break;
672                     }
673                 } else {
674                     /*
675                      * Different disk, so use new file.
676                      */
677                     db->filename = newstralloc(db->filename, arg_filename);
678                 }
679             } else if(cmdargs->cmd == ABORT) {
680                 abort_pending = 1;
681                 errstr = newstralloc(errstr, cmdargs->argv[1]);
682                 putresult(ABORT_FINISHED, "%s\n", handle);
683                 rc = 0;
684                 goto common_exit;
685             } else {
686                 if(cmdargs->argc >= 1) {
687                     q = quote_string(cmdargs->argv[0]);
688                 } else {
689                     q = stralloc(_("(no input?)"));
690                 }
691                 error(_("error [bad command after RQ-MORE-DISK: \"%s\"]"), q);
692                 /*NOTREACHED*/
693             }
694         }
695
696         /*
697          * Time to use another file.
698          */
699
700         /*
701          * First, open the new chunk file, and give it a new header
702          * that has no cont_filename pointer.
703          */
704         g_snprintf(sequence, SIZEOF(sequence), "%d", db->filename_seq);
705         new_filename = newvstralloc(new_filename,
706                                     db->filename,
707                                     ".",
708                                     sequence,
709                                     NULL);
710         tmp_filename = newvstralloc(tmp_filename,
711                                     new_filename,
712                                     ".tmp",
713                                     NULL);
714         pc = strrchr(tmp_filename, '/');
715         g_assert(pc != NULL); /* Only a problem if db->filename has no /. */
716         *pc = '\0';
717         mkholdingdir(tmp_filename);
718         *pc = '/';
719         newfd = open(tmp_filename, O_RDWR|O_CREAT|O_TRUNC, 0600);
720         if (newfd == -1) {
721             int save_errno = errno;
722             char *m;
723
724             if(save_errno == ENOSPC) {
725                 putresult(NO_ROOM, "%s %lld\n", handle, 
726                           (long long)(db->use+db->split_size-dumpsize));
727                 db->use = (off_t)0;                     /* force RQ_MORE_DISK */
728                 db->split_size = dumpsize;
729                 continue;
730             }
731             m = vstrallocf(_("creating chunk holding file \"%s\": %s"),
732                              tmp_filename,
733                              strerror(errno));
734             errstr = quote_string(m);
735             amfree(m);
736             aclose(db->fd);
737             rc = 0;
738             goto common_exit;
739         }
740         save_type = file.type;
741         file.type = F_CONT_DUMPFILE;
742         file.cont_filename[0] = '\0';
743         if(write_tapeheader(newfd, &file)) {
744             int save_errno = errno;
745             char *m;
746
747             aclose(newfd);
748             if(save_errno == ENOSPC) {
749                 putresult(NO_ROOM, "%s %lld\n", handle, 
750                           (long long)(db->use+db->split_size-dumpsize));
751                 db->use = (off_t)0;                     /* force RQ_MORE DISK */
752                 db->split_size = dumpsize;
753                 continue;
754             }
755             m = vstrallocf(_("write_tapeheader file %s: %s"),
756                              tmp_filename,
757                              strerror(errno));
758             errstr = quote_string(m);
759             amfree(m);
760             rc = 0;
761             goto common_exit;
762         }
763
764         /*
765          * Now, update the header of the current file to point
766          * to the next chunk, and then close it.
767          */
768         if (lseek(db->fd, (off_t)0, SEEK_SET) < (off_t)0) {
769             char *m = vstrallocf(_("lseek holding file %s: %s"),
770                              db->filename,
771                              strerror(errno));
772             errstr = quote_string(m);
773             amfree(m);
774             aclose(newfd);
775             rc = 0;
776             goto common_exit;
777         }
778
779         file.type = save_type;
780         strncpy(file.cont_filename, new_filename, SIZEOF(file.cont_filename));
781         file.cont_filename[SIZEOF(file.cont_filename)-1] = '\0';
782         if(write_tapeheader(db->fd, &file)) {
783             char * m = vstrallocf(_("write_tapeheader file \"%s\": %s"),
784                              db->filename,
785                              strerror(errno));
786             errstr = quote_string(m);
787             amfree(m);
788             aclose(newfd);
789             unlink(tmp_filename);
790             rc = 0;
791             goto common_exit;
792         }
793         file.type = F_CONT_DUMPFILE;
794
795         /*
796          * Now shift the file descriptor.
797          */
798         aclose(db->fd);
799         db->fd = newfd;
800         newfd = -1;
801
802         /*
803          * Update when we need to chunk again
804          */
805         if(db->use <= (off_t)DISK_BLOCK_KB) {
806             /*
807              * Cheat and use one more block than allowed so we can make
808              * some progress.
809              */
810             db->split_size += (off_t)(2 * DISK_BLOCK_KB);
811             db->use = (off_t)0;
812         } else if(db->chunk_size > db->use) {
813             db->split_size += db->use;
814             db->use = (off_t)0;
815         } else {
816             db->split_size += db->chunk_size;
817             db->use -= db->chunk_size;
818         }
819
820
821         amfree(tmp_filename);
822         amfree(new_filename);
823         dumpsize += (off_t)DISK_BLOCK_KB;
824         filesize = (off_t)DISK_BLOCK_KB;
825         headersize += DISK_BLOCK_KB;
826         db->filename_seq++;
827     }
828
829     /*
830      * Write out the buffer
831      */
832     written = full_write(db->fd, db->dataout,
833                         (size_t)(db->datain - db->dataout));
834     if (written > 0) {
835         db->dataout += written;
836         dumpbytes += (off_t)written;
837     }
838     dumpsize += (dumpbytes / (off_t)1024);
839     filesize += (dumpbytes / (off_t)1024);
840     dumpbytes %= 1024;
841     if (written == 0) {
842         if (errno != ENOSPC) {
843             char *m = vstrallocf(_("data write: %s"), strerror(errno));
844             errstr = quote_string(m);
845             amfree(m);
846             rc = 0;
847             goto common_exit;
848         }
849
850         /*
851          * NO-ROOM is informational only.  Later, RQ_MORE_DISK will be
852          * issued to use another holding disk.
853          */
854         putresult(NO_ROOM, "%s %lld\n", handle,
855                   (long long)(db->use+db->split_size-dumpsize));
856         db->use = (off_t)0;                             /* force RQ_MORE_DISK */
857         db->split_size = dumpsize;
858         goto common_exit;
859     }
860     if (db->datain == db->dataout) {
861         /*
862          * We flushed the whole buffer so reset to use it all.
863          */
864         db->datain = db->dataout = db->buf;
865     }
866
867 common_exit:
868
869     if (cmdargs)
870         free_cmdargs(cmdargs);
871     amfree(new_filename);
872     /*@i@*/ amfree(tmp_filename);
873     amfree(arg_filename);
874     return rc;
875 }
876
877
878 /*
879  * Send an Amanda dump header to the output file and set file->blocksize
880  */
881 static ssize_t
882 write_tapeheader(
883     int         outfd,
884     dumpfile_t *file)
885 {
886     char *buffer;
887     size_t written;
888
889     file->blocksize = DISK_BLOCK_BYTES;
890     buffer = build_header(file, DISK_BLOCK_BYTES);
891
892     written = full_write(outfd, buffer, DISK_BLOCK_BYTES);
893     amfree(buffer);
894     if(written == DISK_BLOCK_BYTES) return 0;
895
896     /* fake ENOSPC when we get a short write without errno set */
897     if(errno == 0)
898         errno = ENOSPC;
899
900     return (ssize_t)-1;
901 }