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