2 * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3 * Copyright (c) 1991-1999 University of Maryland at College Park
4 * Copyright (c) 2007-2012 Zmanda, Inc. All Rights Reserved.
7 * Permission to use, copy, modify, distribute, and sell this software and its
8 * documentation for any purpose is hereby granted without fee, provided that
9 * the above copyright notice appear in all copies and that both that
10 * copyright notice and this permission notice appear in supporting
11 * documentation, and that the name of U.M. not be used in advertising or
12 * publicity pertaining to distribution of the software without specific,
13 * written prior permission. U.M. makes no representations about the
14 * suitability of this software for any purpose. It is provided "as is"
15 * without express or implied warranty.
17 * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
19 * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
20 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
21 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
22 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24 * Authors: the Amanda Development Team. Its members are listed in a
25 * file named AUTHORS, in the root directory of this distribution.
27 /* $Id: chunker.c,v 1.36 2006/08/24 11:23:32 martinea Exp $
29 * requests remote amandad processes to dump filesystems
41 #include "fileheader.h"
42 #include "amfeatures.h"
43 #include "server_util.h"
46 #include "timestamp.h"
47 #include "sockaddr-util.h"
57 #define CONNECT_TIMEOUT 5*60
59 #define STARTUP_TIMEOUT 60
62 int fd; /* file to flush to */
63 char *filename; /* name of what fd points to */
64 int filename_seq; /* for chunking */
65 off_t split_size; /* when to chunk */
66 off_t chunk_size; /* size of each chunk */
67 off_t use; /* size to use on this disk */
68 char buf[DISK_BLOCK_BYTES];
69 char *datain; /* data buffer markers */
74 static char *handle = NULL;
76 static char *errstr = NULL;
77 static int abort_pending;
78 static off_t dumpsize;
79 static unsigned long headersize;
80 static off_t dumpbytes;
81 static off_t filesize;
83 static char *hostname = NULL;
84 static char *diskname = NULL;
85 static char *qdiskname = NULL;
86 static char *options = NULL;
87 static char *progname = NULL;
89 static char *dumpdate = NULL;
90 static struct cmdargs *command_in_transit = NULL;
91 static char *chunker_timestamp = NULL;
93 static dumpfile_t file;
96 int main(int, char **);
97 static ssize_t write_tapeheader(int, dumpfile_t *);
98 static void databuf_init(struct databuf *, int, char *, off_t, off_t);
99 static int databuf_flush(struct databuf *);
101 static int startup_chunker(char *, off_t, off_t, struct databuf *, int *, int *);
102 static int do_chunk(int, struct databuf *, int, int);
104 /* we use a function pointer for full_write, so that we can "shim" in
105 * full_write_with_fake_enospc for testing */
106 static size_t (*db_full_write)(int fd, const void *buf, size_t count);
107 static size_t full_write_with_fake_enospc(int fd, const void *buf, size_t count);
108 static off_t fake_enospc_at_byte = -1;
115 static struct databuf db;
116 struct cmdargs *cmdargs;
119 char *filename = NULL;
120 off_t chunksize, use;
122 am_feature_t *their_features = NULL;
124 config_overrides_t *cfg_ovr = NULL;
125 char *cfg_opt = NULL;
131 * Configure program for internationalization:
132 * 1) Only set the message locale for now.
133 * 2) Set textdomain for all amanda related programs to "amanda"
134 * We don't want to be forced to support dozens of message catalogs.
136 setlocale(LC_MESSAGES, "C");
137 textdomain("amanda");
141 set_pname("chunker");
143 dbopen(DBG_SUBDIR_SERVER);
145 /* Don't die when child closes pipe */
146 signal(SIGPIPE, SIG_IGN);
148 add_amanda_log_handler(amanda_log_stderr);
149 add_amanda_log_handler(amanda_log_trace_log);
151 cfg_ovr = extract_commandline_config_overrides(&argc, &argv);
156 set_config_overrides(cfg_ovr);
157 config_init(CONFIG_INIT_EXPLICIT_NAME | CONFIG_INIT_USE_CWD, cfg_opt);
159 if (config_errors(NULL) >= CFGERR_WARNINGS) {
160 config_print_errors();
161 if (config_errors(NULL) >= CFGERR_ERRORS) {
162 g_critical(_("errors processing config file"));
166 safe_cd(); /* do this *after* config_init() */
168 check_running_as(RUNNING_AS_DUMPUSER);
170 dbrename(get_config_name(), DBG_SUBDIR_SERVER);
172 log_add(L_INFO, "%s pid %ld", get_pname(), (long)getpid());
174 _("%s: pid %ld executable %s version %s\n"),
175 get_pname(), (long) getpid(),
179 /* now, make sure we are a valid user */
181 signal(SIGPIPE, SIG_IGN);
182 signal(SIGCHLD, SIG_IGN);
185 if(cmdargs->cmd == START) {
186 if(cmdargs->argc <= 1)
187 error(_("error [dumper START: not enough args: timestamp]"));
188 chunker_timestamp = newstralloc(chunker_timestamp, cmdargs->argv[1]);
191 log_add(L_INFO, "%s pid %ld", get_pname(), (long)getpid());
192 error(_("Didn't get START command"));
194 free_cmdargs(cmdargs);
196 /* set up a fake ENOSPC for testing purposes. Note that this counts
197 * headers as well as data written to disk. */
198 if (getenv("CHUNKER_FAKE_ENOSPC_AT")) {
199 char *env = getenv("CHUNKER_FAKE_ENOSPC_AT");
200 fake_enospc_at_byte = (off_t)atoi(env); /* these values are never > MAXINT */
201 db_full_write = full_write_with_fake_enospc;
202 g_debug("will trigger fake ENOSPC at byte %d", (int)fake_enospc_at_byte);
204 db_full_write = full_write;
210 switch(cmdargs->cmd) {
231 if(a >= cmdargs->argc) {
232 error(_("error [chunker PORT-WRITE: not enough args: handle]"));
235 handle = newstralloc(handle, cmdargs->argv[a++]);
237 if(a >= cmdargs->argc) {
238 error(_("error [chunker PORT-WRITE: not enough args: filename]"));
241 filename = newstralloc(filename, cmdargs->argv[a++]);
243 if(a >= cmdargs->argc) {
244 error(_("error [chunker PORT-WRITE: not enough args: hostname]"));
247 hostname = newstralloc(hostname, cmdargs->argv[a++]);
249 if(a >= cmdargs->argc) {
250 error(_("error [chunker PORT-WRITE: not enough args: features]"));
253 am_release_feature_set(their_features);
254 their_features = am_string_to_feature(cmdargs->argv[a++]);
255 if (!their_features) {
256 error(_("error [chunker PORT-WRITE: invalid feature string]"));
260 if(a >= cmdargs->argc) {
261 error(_("error [chunker PORT-WRITE: not enough args: diskname]"));
264 diskname = newstralloc(diskname, cmdargs->argv[a++]);
267 qdiskname = quote_string(diskname); /* qdiskname is a global */
269 if(a >= cmdargs->argc) {
270 error(_("error [chunker PORT-WRITE: not enough args: level]"));
273 level = atoi(cmdargs->argv[a++]);
275 if(a >= cmdargs->argc) {
276 error(_("error [chunker PORT-WRITE: not enough args: dumpdate]"));
279 dumpdate = newstralloc(dumpdate, cmdargs->argv[a++]);
281 if(a >= cmdargs->argc) {
282 error(_("error [chunker PORT-WRITE: not enough args: chunksize]"));
285 chunksize = OFF_T_ATOI(cmdargs->argv[a++]);
286 chunksize = am_floor(chunksize, (off_t)DISK_BLOCK_KB);
288 if(a >= cmdargs->argc) {
289 error(_("error [chunker PORT-WRITE: not enough args: progname]"));
292 progname = newstralloc(progname, cmdargs->argv[a++]);
294 if(a >= cmdargs->argc) {
295 error(_("error [chunker PORT-WRITE: not enough args: use]"));
298 use = am_floor(OFF_T_ATOI(cmdargs->argv[a++]), DISK_BLOCK_KB);
300 if(a >= cmdargs->argc) {
301 error(_("error [chunker PORT-WRITE: not enough args: options]"));
304 options = newstralloc(options, cmdargs->argv[a++]);
306 if(a != cmdargs->argc) {
307 error(_("error [chunker PORT-WRITE: too many args: %d != %d]"),
312 if ((header_fd = startup_chunker(filename, use, chunksize, &db,
313 &header_socket, &data_socket)) < 0) {
314 q = quote_string(vstrallocf(_("[chunker startup failed: %s]"), errstr));
315 putresult(TRYAGAIN, "%s %s\n", handle, q);
316 error("startup_chunker failed: %s", errstr);
318 command_in_transit = NULL;
319 if (header_fd >= 0 && do_chunk(header_fd, &db, header_socket, data_socket)) {
320 char kb_str[NUM_STR_SIZE];
321 char kps_str[NUM_STR_SIZE];
324 runtime = stopclock();
325 rt = g_timeval_to_double(runtime);
326 g_snprintf(kb_str, SIZEOF(kb_str), "%lld",
327 (long long)(dumpsize - (off_t)headersize));
328 g_snprintf(kps_str, SIZEOF(kps_str), "%3.1lf",
329 isnormal(rt) ? (double)dumpsize / rt : 0.0);
330 errstr = newvstrallocf(errstr, "sec %s kb %s kps %s",
331 walltime_str(runtime), kb_str, kps_str);
332 m = vstrallocf("[%s]", errstr);
335 free_cmdargs(cmdargs);
336 if(command_in_transit != NULL) {
337 cmdargs = command_in_transit;
338 command_in_transit = NULL;
342 switch(cmdargs->cmd) {
344 putresult(DONE, "%s %lld %s\n", handle,
345 (long long)(dumpsize - (off_t)headersize), q);
346 log_add(L_SUCCESS, "%s %s %s %d [%s]",
347 hostname, qdiskname, chunker_timestamp, level, errstr);
353 if(dumpsize > (off_t)DISK_BLOCK_KB) {
354 putresult(PARTIAL, "%s %lld %s\n", handle,
355 (long long)(dumpsize - (off_t)headersize),
357 log_add(L_PARTIAL, "%s %s %s %d [%s]",
358 hostname, qdiskname, chunker_timestamp, level, errstr);
361 errstr = newvstrallocf(errstr,
362 _("dumper returned %s"), cmdstr[cmdargs->cmd]);
364 m = vstrallocf("[%s]",errstr);
367 putresult(FAILED, "%s %s\n", handle, q);
368 log_add(L_FAIL, "%s %s %s %d [%s]",
369 hostname, qdiskname, chunker_timestamp, level, errstr);
374 } else if (header_fd != -2) {
376 m = vstrallocf("[%s]", errstr);
381 putresult(FAILED, "%s %s\n", handle, q);
383 log_add(L_FAIL, "%s %s %s %d [%s]",
384 hostname, qdiskname, chunker_timestamp, level, errstr);
392 if(cmdargs->argc >= 1) {
393 q = quote_string(cmdargs->argv[0]);
395 q = stralloc(_("(no input?)"));
397 putresult(BAD_COMMAND, "%s\n", q);
402 /* } while(cmdargs->cmd != QUIT); */
404 log_add(L_INFO, "pid-done %ld", (long)getpid());
407 amfree(chunker_timestamp);
415 free_cmdargs(cmdargs);
416 if (command_in_transit)
417 free_cmdargs(command_in_transit);
418 am_release_feature_set(their_features);
419 their_features = NULL;
423 return (0); /* exit */
427 * Returns a file descriptor to the incoming port
428 * on success, or -1 on error.
439 int header_fd, outfd;
440 char *tmp_filename, *pc;
441 in_port_t header_port, data_port;
442 int header_socket, data_socket;
444 struct addrinfo *res;
445 struct addrinfo *res_addr;
446 sockaddr_union *addr = NULL;
447 sockaddr_union data_addr;
451 if ((result = resolve_hostname("localhost", 0, &res, NULL) != 0)) {
452 errstr = newvstrallocf(errstr, _("could not resolve localhost: %s"),
453 gai_strerror(result));
456 for (res_addr = res; res_addr != NULL; res_addr = res_addr->ai_next) {
457 g_debug("ra: %s\n", str_sockaddr((sockaddr_union*)res_addr->ai_addr));
458 if (res_addr->ai_family == AF_INET) {
459 addr = (sockaddr_union *)res_addr->ai_addr;
464 addr = (sockaddr_union *)res->ai_addr;
465 g_debug("addr: %s\n", str_sockaddr(addr));
468 header_socket = stream_server(SU_GET_FAMILY(addr), &header_port, 0,
470 data_socket = stream_server(SU_GET_FAMILY(addr), &data_port, 0,
472 copy_sockaddr(&data_addr, addr);
474 SU_SET_PORT(&data_addr, data_port);
476 if (res) freeaddrinfo(res);
478 if (header_socket < 0) {
479 errstr = vstrallocf(_("error creating header stream server: %s"), strerror(errno));
484 if (data_socket < 0) {
485 errstr = vstrallocf(_("error creating data stream server: %s"), strerror(errno));
486 aclose(header_socket);
490 putresult(PORT, "%d %s\n", header_port, str_sockaddr(&data_addr));
492 header_fd = stream_accept(header_socket, CONNECT_TIMEOUT, 0,
494 if (header_fd == -1) {
495 errstr = vstrallocf(_("error accepting header stream: %s"),
497 aclose(header_socket);
502 tmp_filename = vstralloc(filename, ".tmp", NULL);
503 pc = strrchr(tmp_filename, '/');
504 g_assert(pc != NULL);
506 mkholdingdir(tmp_filename);
508 if ((outfd = open(tmp_filename, O_RDWR|O_CREAT|O_TRUNC, 0600)) < 0) {
509 int save_errno = errno;
510 char *m = vstrallocf(_("holding file \"%s\": %s"),
514 errstr = quote_string(m);
516 amfree(tmp_filename);
519 if(save_errno == ENOSPC) {
520 putresult(NO_ROOM, "%s %lld\n",
521 handle, (long long)use);
527 amfree(tmp_filename);
528 databuf_init(db, outfd, filename, use, chunksize);
530 *headersocket = header_socket;
531 *datasocket = data_socket;
544 char header_buf[DISK_BLOCK_BYTES];
548 dumpsize = dumpbytes = filesize = (off_t)0;
550 memset(header_buf, 0, sizeof(header_buf));
553 * The first thing we should receive is the file header, which we
554 * need to save into "file", as well as write out. Later, the
555 * chunk code will rewrite it.
557 nread = full_read(header_fd, header_buf, SIZEOF(header_buf));
559 aclose(header_socket);
560 if (nread != sizeof(header_buf)) {
562 errstr = vstrallocf(_("cannot read header: %s"), strerror(errno));
564 errstr = vstrallocf(_("cannot read header: got %zd bytes instead of %zd"),
565 nread, sizeof(header_buf));
570 parse_file_header(header_buf, &file, (size_t)nread);
571 if(write_tapeheader(db->fd, &file)) {
572 int save_errno = errno;
573 char *m = vstrallocf(_("write_tapeheader file %s: %s"),
574 db->filename, strerror(errno));
575 errstr = quote_string(m);
577 if(save_errno == ENOSPC) {
578 putresult(NO_ROOM, "%s %lld\n", handle,
579 (long long)(db->use+db->split_size-dumpsize));
584 dumpsize += (off_t)DISK_BLOCK_KB;
585 filesize = (off_t)DISK_BLOCK_KB;
586 headersize += DISK_BLOCK_KB;
588 /* open the data socket */
589 data_fd = stream_accept(data_socket, CONNECT_TIMEOUT, 0, STREAM_BUFSIZE);
592 errstr = vstrallocf(_("error accepting data stream: %s"),
599 * We've written the file header. Now, just write data until the
602 while ((nread = full_read(data_fd, db->buf,
603 (size_t)(db->datalimit - db->datain))) > 0) {
605 while(db->dataout < db->datain) {
606 if(!databuf_flush(db)) {
613 while(db->dataout < db->datain) {
614 if(!databuf_flush(db)) {
620 if(dumpbytes > (off_t)0) {
621 dumpsize += (off_t)1; /* count partial final KByte */
622 filesize += (off_t)1;
630 * Initialize a databuf. Takes a writeable file descriptor.
641 db->filename = stralloc(filename);
642 db->filename_seq = (off_t)0;
643 db->chunk_size = chunk_size;
644 db->split_size = (db->chunk_size > use) ? use : db->chunk_size;
645 db->use = (use > db->split_size) ? use - db->split_size : (off_t)0;
646 db->datain = db->dataout = db->buf;
647 db->datalimit = db->buf + SIZEOF(db->buf);
652 * Write out the buffer to the backing file
658 struct cmdargs *cmdargs = NULL;
660 size_t size_to_write;
663 char *arg_filename = NULL;
664 char *new_filename = NULL;
665 char *tmp_filename = NULL;
666 char sequence[NUM_STR_SIZE];
668 filetype_t save_type;
674 * If there's no data, do nothing.
676 if (db->dataout >= db->datain) {
681 * See if we need to split this file.
683 while (db->split_size > (off_t)0 && dumpsize >= db->split_size) {
684 if( db->use == (off_t)0 ) {
686 * Probably no more space on this disk. Request some more.
688 putresult(RQ_MORE_DISK, "%s\n", handle);
690 if(command_in_transit == NULL &&
691 (cmdargs->cmd == DONE || cmdargs->cmd == TRYAGAIN || cmdargs->cmd == FAILED)) {
692 command_in_transit = cmdargs;
695 if(cmdargs->cmd == CONTINUE) {
703 a = 2; /* skip CONTINUE and serial */
705 if(a >= cmdargs->argc) {
706 error(_("error [chunker CONTINUE: not enough args: filename]"));
709 arg_filename = newstralloc(arg_filename, cmdargs->argv[a++]);
711 if(a >= cmdargs->argc) {
712 error(_("error [chunker CONTINUE: not enough args: chunksize]"));
715 db->chunk_size = OFF_T_ATOI(cmdargs->argv[a++]);
716 db->chunk_size = am_floor(db->chunk_size, (off_t)DISK_BLOCK_KB);
718 if(a >= cmdargs->argc) {
719 error(_("error [chunker CONTINUE: not enough args: use]"));
722 db->use = OFF_T_ATOI(cmdargs->argv[a++]);
724 if(a != cmdargs->argc) {
725 error(_("error [chunker CONTINUE: too many args: %d != %d]"),
730 if(strcmp(db->filename, arg_filename) == 0) {
732 * Same disk, so use what room is left up to the
733 * next chunk boundary or the amount we were given,
736 left_in_chunk = db->chunk_size - filesize;
737 if(left_in_chunk > db->use) {
738 db->split_size += db->use;
741 db->split_size += left_in_chunk;
742 db->use -= left_in_chunk;
744 if(left_in_chunk > (off_t)0) {
746 * We still have space in this chunk.
752 * Different disk, so use new file.
754 db->filename = newstralloc(db->filename, arg_filename);
756 } else if(cmdargs->cmd == ABORT) {
758 errstr = newstralloc(errstr, cmdargs->argv[1]);
759 putresult(ABORT_FINISHED, "%s\n", handle);
763 if(cmdargs->argc >= 1) {
764 q = quote_string(cmdargs->argv[0]);
766 q = stralloc(_("(no input?)"));
768 error(_("error [bad command after RQ-MORE-DISK: \"%s\"]"), q);
774 * Time to use another file.
778 * First, open the new chunk file, and give it a new header
779 * that has no cont_filename pointer.
781 g_snprintf(sequence, SIZEOF(sequence), "%d", db->filename_seq);
782 new_filename = newvstralloc(new_filename,
787 tmp_filename = newvstralloc(tmp_filename,
791 pc = strrchr(tmp_filename, '/');
792 g_assert(pc != NULL); /* Only a problem if db->filename has no /. */
794 mkholdingdir(tmp_filename);
796 newfd = open(tmp_filename, O_RDWR|O_CREAT|O_TRUNC, 0600);
798 int save_errno = errno;
801 if(save_errno == ENOSPC) {
802 putresult(NO_ROOM, "%s %lld\n", handle,
803 (long long)(db->use+db->split_size-dumpsize));
804 db->use = (off_t)0; /* force RQ_MORE_DISK */
805 db->split_size = dumpsize;
808 m = vstrallocf(_("creating chunk holding file \"%s\": %s"),
811 errstr = quote_string(m);
817 save_type = file.type;
818 file.type = F_CONT_DUMPFILE;
819 file.cont_filename[0] = '\0';
820 if(write_tapeheader(newfd, &file)) {
821 int save_errno = errno;
825 if(save_errno == ENOSPC) {
826 if (unlink(tmp_filename) < 0) {
827 g_debug("could not delete '%s'; ignoring", tmp_filename);
829 putresult(NO_ROOM, "%s %lld\n", handle,
830 (long long)(db->use+db->split_size-dumpsize));
831 db->use = (off_t)0; /* force RQ_MORE DISK */
832 db->split_size = dumpsize;
833 file.type = save_type;
836 m = vstrallocf(_("write_tapeheader file %s: %s"),
839 errstr = quote_string(m);
846 * Now, update the header of the current file to point
847 * to the next chunk, and then close it.
849 if (lseek(db->fd, (off_t)0, SEEK_SET) < (off_t)0) {
850 char *m = vstrallocf(_("lseek holding file %s: %s"),
853 errstr = quote_string(m);
860 file.type = save_type;
861 strncpy(file.cont_filename, new_filename, SIZEOF(file.cont_filename));
862 file.cont_filename[SIZEOF(file.cont_filename)-1] = '\0';
863 if(write_tapeheader(db->fd, &file)) {
864 char * m = vstrallocf(_("write_tapeheader file \"%s\": %s"),
867 errstr = quote_string(m);
870 unlink(tmp_filename);
874 file.type = F_CONT_DUMPFILE;
877 * Now shift the file descriptor.
884 * Update when we need to chunk again
886 if(db->use <= (off_t)DISK_BLOCK_KB) {
888 * Cheat and use one more block than allowed so we can make
891 db->split_size += (off_t)(2 * DISK_BLOCK_KB);
893 } else if(db->chunk_size > db->use) {
894 db->split_size += db->use;
897 db->split_size += db->chunk_size;
898 db->use -= db->chunk_size;
902 amfree(tmp_filename);
903 amfree(new_filename);
904 dumpsize += (off_t)DISK_BLOCK_KB;
905 filesize = (off_t)DISK_BLOCK_KB;
906 headersize += DISK_BLOCK_KB;
911 * Write out the buffer
913 size_to_write = (size_t)(db->datain - db->dataout);
914 written = db_full_write(db->fd, db->dataout, size_to_write);
916 db->dataout += written;
917 dumpbytes += (off_t)written;
919 dumpsize += (dumpbytes / (off_t)1024);
920 filesize += (dumpbytes / (off_t)1024);
922 if (written < size_to_write) {
923 if (errno != ENOSPC) {
924 char *m = vstrallocf(_("data write: %s"), strerror(errno));
925 errstr = quote_string(m);
932 * NO-ROOM is informational only. Later, RQ_MORE_DISK will be
933 * issued to use another holding disk.
935 putresult(NO_ROOM, "%s %lld\n", handle,
936 (long long)(db->use+db->split_size-dumpsize));
937 db->use = (off_t)0; /* force RQ_MORE_DISK */
938 db->split_size = dumpsize;
941 if (db->datain == db->dataout) {
943 * We flushed the whole buffer so reset to use it all.
945 db->datain = db->dataout = db->buf;
951 free_cmdargs(cmdargs);
952 amfree(new_filename);
953 /*@i@*/ amfree(tmp_filename);
954 amfree(arg_filename);
960 * Send an Amanda dump header to the output file and set file->blocksize
970 file->blocksize = DISK_BLOCK_BYTES;
971 if (debug_chunker > 1)
972 dump_dumpfile_t(file);
973 buffer = build_header(file, NULL, DISK_BLOCK_BYTES);
974 if (!buffer) /* this shouldn't happen */
975 error(_("header does not fit in %zd bytes"), (size_t)DISK_BLOCK_BYTES);
977 written = db_full_write(outfd, buffer, DISK_BLOCK_BYTES);
979 if(written == DISK_BLOCK_BYTES) return 0;
981 /* fake ENOSPC when we get a short write without errno set */
989 full_write_with_fake_enospc(
996 //g_debug("HERE %zd %zd", count, (size_t)fake_enospc_at_byte);
998 if (count <= (size_t)fake_enospc_at_byte) {
999 fake_enospc_at_byte -= count;
1000 return full_write(fd, buf, count);
1003 /* if we get here, the caller has requested a size that is less
1004 * than fake_enospc_at_byte. */
1005 count = fake_enospc_at_byte;
1006 g_debug("returning fake ENOSPC");
1008 if (fake_enospc_at_byte) {
1009 rc = full_write(fd, buf, fake_enospc_at_byte);
1010 if (rc == (size_t)fake_enospc_at_byte) {
1011 /* full_write succeeded, so fake a failure */
1015 /* no bytes to write; just fake an error */
1020 /* switch back to calling full_write directly */
1021 fake_enospc_at_byte = -1;
1022 db_full_write = full_write;