示例#1
0
文件: taper.c 项目: regina/amanda
/* We call this when we can't find a tape to write data to. This could
   happen with the first (or only) part of a file, but it could also
   happen with an intermediate part of a split dump. dump_bytes
   is 0 if this is the first part of a dump. */
static void bail_no_volume(
    dump_info_t *dump_info,
    char *errmsg)
{
    char *errstr;
    if (errmsg)
	errstr = quote_string(errmsg);
    else
	errstr = quote_string("no new tape");
    if (dump_info->total_bytes > 0) {
        /* Second or later part of a split dump, so PARTIAL message. */
        double dump_time = g_timeval_to_double(dump_info->total_time);
        guint64 dump_kbytes = dump_info->total_bytes / 1024;
        double dump_kbps = get_kbps(dump_kbytes, dump_time);
        putresult(PARTIAL,
                  "%s INPUT-GOOD TAPE-ERROR "
                  "\"[sec %f kb %ju kps %f]\" \"\" %s\n",
                  dump_info->handle, 
                  dump_time, (uintmax_t)dump_kbytes, dump_kbps, errstr);
        put_partial_log(dump_info, dump_time, dump_kbytes, errstr);
    } else {
        char * qdiskname = quote_string(dump_info->diskname);
        putresult(FAILED,
                  "%s INPUT-GOOD TAPE-ERROR \"\" %s\n",
                  dump_info->handle, errstr);
        log_add(L_FAIL, "%s %s %s %d %s",
                dump_info->hostname, qdiskname, dump_info->timestamp,
                dump_info->level, errstr);
	amfree(qdiskname);
    }
    amfree(errstr);
}
示例#2
0
文件: taper.c 项目: regina/amanda
/* In running mode (not startup mode), get a command from driver and
   deal with it. */
static gboolean process_driver_command(taper_state_t * state) {
    struct cmdargs *cmdargs;
    char * q;

    /* This will return QUIT if driver has died. */
    cmdargs = getcmd();
    switch (cmdargs->cmd) {
    case PORT_WRITE:
        /*
         * PORT-WRITE
         *   handle
         *   hostname
         *   features
         *   diskname
         *   level
         *   datestamp
         *   splitsize
         *   split_diskbuffer
         */
        process_port_write(state, cmdargs);
        break;
        
    case FILE_WRITE:
        /*
         * FILE-WRITE
         *   handle
         *   filename
         *   hostname
         *   features
         *   diskname
         *   level
         *   datestamp
         *   splitsize
         */
        process_file_write(state, cmdargs);
        break;
        
    case QUIT:
	free_cmdargs(cmdargs);
	if (state->device && state->device->volume_label) {
	    log_add(L_INFO, "tape %s kb %lld fm %d [OK]\n",
		    state->device->volume_label,
		    (long long)((state->total_bytes+(off_t)1023) / (off_t)1024),
		    state->device->file);
	}
        return send_quitting(state);
    default:
        if (cmdargs->argc >= 1) {
            q = quote_string(cmdargs->argv[0]);
        } else {
            q = stralloc("(no input?)");
        }
        putresult(BAD_COMMAND, "%s\n", q);
        amfree(q);
        break;
    }
    free_cmdargs(cmdargs);

    return TRUE;
}
示例#3
0
/*
 * Do a task group: fork the processes, then wait for them.
 */
static
void
runtaskgroup(unsigned count,
	     void (*prep)(unsigned, unsigned),
	     void (*task)(unsigned, unsigned),
	     void (*cleanup)(unsigned, unsigned),
	     unsigned groupid)
{
	pid_t mypids[count];
	unsigned i;
	unsigned failures = 0;
	time_t secs;
	unsigned long nsecs;

	prep(groupid, count);

	for (i=0; i<count; i++) {
		mypids[i] = fork();
		if (mypids[i] < 0) {
			err(1, "fork");
		}
		if (mypids[i] == 0) {
			/* child (of second fork) */
			task(groupid, i);
			exit(0);
		}
		/* parent (of second fork) - continue */
	}

	/*
	 * now wait for the task to finish
	 */

	for (i=0; i<count; i++) {
		failures += dowait(mypids[i]);
	}

	/*
	 * Store the end time.
	 */

	__time(&secs, &nsecs);
	openresultsfile(O_WRONLY);
	putresult(groupid, secs, nsecs);
	closeresultsfile();

	cleanup(groupid, count);

	exit(failures ? 1 : 0);
}
示例#4
0
文件: taper.c 项目: regina/amanda
/* Link up the TaperSource with the Device, including retries etc. */
static void run_device_output(taper_state_t * taper_state,
                              dump_info_t * dump_info) {
    GValue val;
    guint file_number;
    dump_info->current_part = 1;
    dump_info->total_time.tv_sec = 0;
    dump_info->total_time.tv_usec = 0;
    dump_info->total_bytes = 0;

    for (;;) {
        GTimeVal start_time, end_time, run_time;
        StreamingRequirement streaming_mode;
        queue_result_flags queue_result;
        CountingConsumerData consumer_data;
        dumpfile_t *this_header;
        size_t max_memory;
        
        this_header = munge_headers(dump_info);
        if (this_header == NULL) {
            char * qdiskname = quote_string(dump_info->diskname);
	    char * errstr = taper_source_get_errmsg(dump_info->source);
	    if (!errstr)
		errstr = "Failed reading dump header.";
	    errstr = quote_string(errstr);
            putresult(FAILED,
             "%s INPUT-ERROR TAPE-GOOD %s \"\"\n",
                      dump_info->handle, errstr);
            log_add(L_FAIL, "%s %s %s %d %s",
                    dump_info->hostname, qdiskname, dump_info->timestamp,
                    dump_info->level, errstr);
            amfree(qdiskname);
	    amfree(errstr);
            return;
        }            

        if (!find_and_label_new_tape(taper_state, dump_info)) {
            bail_no_volume(dump_info, taper_state->last_errmsg);
	    dumpfile_free(this_header);
            return;
        }

	while (!device_start_file(taper_state->device, this_header)) {
            /* Close the device. */
            device_finish(taper_state->device);
            g_object_unref(taper_state->device);
            taper_state->device = NULL;

            if (!find_and_label_new_tape(taper_state, dump_info)) {
		bail_no_volume(dump_info, taper_state->last_errmsg);
		dumpfile_free(this_header);
		return;
            }
        }
	dumpfile_free(this_header);

        bzero(&val, sizeof(val));
        if (!device_property_get(taper_state->device, PROPERTY_STREAMING, &val)
            || !G_VALUE_HOLDS(&val, STREAMING_REQUIREMENT_TYPE)) {
            g_fprintf(stderr, "taper: Couldn't get streaming type!\n");
            streaming_mode = STREAMING_REQUIREMENT_REQUIRED;
        } else {
            streaming_mode = g_value_get_enum(&val);
        }
    
        file_number = taper_state->device->file;

        consumer_data.next_consumer = device_write_consumer;
        consumer_data.next_consumer_data = taper_state->device;
        consumer_data.bytes_written = 0;

        g_get_current_time(&start_time);

        if (getconf_seen(CNF_DEVICE_OUTPUT_BUFFER_SIZE)) {
            max_memory = getconf_size(CNF_DEVICE_OUTPUT_BUFFER_SIZE);
            if (getconf_seen(CNF_TAPEBUFS)) {
                g_fprintf(stderr,
                        "Configuration directives 'device_output_buffer_size' "
                        "and \n"
                        "'tapebufs' are incompatible; using former.\n");
            }
        } else if (getconf_seen(CNF_TAPEBUFS)) {
            max_memory = getconf_int(CNF_TAPEBUFS) *
                taper_state->device->block_size;
        } else {
            /* Use default. */
            max_memory = getconf_size(CNF_DEVICE_OUTPUT_BUFFER_SIZE);
        }

        queue_result = do_consumer_producer_queue_full
            (taper_source_producer,
             dump_info->source,
             counting_consumer,
             &consumer_data,
             taper_state->device->block_size, max_memory,
             streaming_mode);

        g_get_current_time(&end_time);
        run_time = timesub(end_time, start_time);

        /* The device_write_consumer leaves the file open, so close it now. */
        if (!device_finish_file(taper_state->device)) {
            queue_result = queue_result | QUEUE_CONSUMER_ERROR;
        }

        if (!finish_part_attempt(taper_state, dump_info, queue_result,
                                 run_time, consumer_data.bytes_written)) {
            break;
        }
    }
}
示例#5
0
文件: taper.c 项目: regina/amanda
/* Figure out what to do after a part attempt. Returns TRUE if another
   attempt should proceed for this dump; FALSE if we are done. */
static gboolean finish_part_attempt(taper_state_t * taper_state,
                                    dump_info_t * dump_info,
                                    queue_result_flags queue_result,
                                    GTimeVal run_time, guint64 run_bytes) {
    double part_time = g_timeval_to_double(run_time);
    guint64 part_kbytes = run_bytes / 1024;
    double part_kbps = get_kbps((double)run_bytes / 1024.0, part_time);
        
    char * qdiskname = quote_string(dump_info->diskname);

    if (queue_result == QUEUE_SUCCESS) {
        dump_info->total_time = timesadd(run_time, dump_info->total_time);
        dump_info->total_bytes += run_bytes;

        log_add(L_PART, "%s %d %s %s %s %d/%d %d [sec %f kb %ju kps %f]",
                taper_state->device->volume_label,
                taper_state->device->file, dump_info->hostname, qdiskname,
                dump_info->timestamp, dump_info->current_part,
                taper_source_predict_parts(dump_info->source),
                dump_info->level, part_time, (uintmax_t)part_kbytes, part_kbps);
        putresult(PARTDONE, "%s %s %d %ju \"[sec %f kb %ju kps %f]\"\n",
                  dump_info->handle, taper_state->device->volume_label,
                  taper_state->device->file, (uintmax_t)part_kbytes, part_time,
		  (uintmax_t)part_kbytes, part_kbps);
	taper_state->total_bytes += run_bytes;
        
        if (taper_source_get_end_of_data(dump_info->source)) {
            cmd_t result_cmd;
            logtype_t result_log;
            double dump_time = g_timeval_to_double(dump_info->total_time);
            guint64 dump_kbytes = dump_info->total_bytes / 1024;
            double dump_kbps = get_kbps((double)dump_info->total_bytes / 1024.0, dump_time);

            find_completion_tags(dump_info, &result_cmd, &result_log);

            g_object_unref(dump_info->source);
            dump_info->source = NULL;
        
            log_add(result_log, "%s %s %s %d %d [sec %f kb %ju kps %f]",
                    dump_info->hostname, qdiskname, dump_info->timestamp,
                    dump_info->current_part, dump_info->level, dump_time,
		    (uintmax_t)dump_kbytes, dump_kbps);
            putresult(result_cmd, "%s INPUT-GOOD TAPE-GOOD "
                      "\"[sec %f kb %ju kps %f]\" \"\" \"\"\n",
                      dump_info->handle, dump_time, (uintmax_t)dump_kbytes,
                      dump_kbps);
            
            amfree(qdiskname);
            return FALSE;
        } else if (taper_source_get_end_of_part(dump_info->source)) {
            taper_source_start_new_part(dump_info->source);
            dump_info->current_part ++;
            amfree(qdiskname);
            return TRUE;
        }
        /* If we didn't read EOF or EOP, then an error
           occured. But we read QUEUE_SUCCESS, so something is
           b0rked. */
        g_assert_not_reached();
    } else {
        char * volume_label = strdup(taper_state->device->volume_label);
        int file_number = taper_state->device->file;
        double dump_time, dump_kbps;
        guint64 dump_kbytes;
	char *producer_errstr = quote_string(
				   taper_source_get_errmsg(dump_info->source));
	char *consumer_errstr = quote_string(
				   device_error(taper_state->device));

        log_add(L_PARTPARTIAL,
                "%s %d %s %s %s %d/%d %d [sec %f kb %ju kps %f] %s",
                volume_label, file_number, dump_info->hostname, qdiskname,
                dump_info->timestamp, dump_info->current_part,
                taper_source_predict_parts(dump_info->source),
                dump_info->level, part_time, (uintmax_t)part_kbytes, part_kbps,
		consumer_errstr);
	log_add(L_INFO, "tape %s kb %lld fm %d [OK]\n",
		volume_label,
		(long long)((taper_state->total_bytes+(off_t)1023) / (off_t)1024),
		taper_state->device->file);

        /* A problem occured. */
        if (queue_result & QUEUE_CONSUMER_ERROR) {
	    /* Make a note if this was EOM (we treat EOM the same as any other error,
	     * so it's just for debugging purposes */
	    if (taper_state->device->is_eof)
		g_debug("device %s ran out of space", taper_state->device->device_name);

            /* Close the device. */
            device_finish(taper_state->device);
            g_object_unref(taper_state->device);
            taper_state->device = NULL;
        }
        
        amfree(volume_label);
        
        if ((queue_result & QUEUE_CONSUMER_ERROR) &&
            (!(queue_result & QUEUE_PRODUCER_ERROR)) &&
            taper_source_seek_to_part_start(dump_info->source)) {
            /* It is recoverable. */
            log_add(L_INFO, "Will request retry of failed split part.");
            if (find_and_label_new_tape(taper_state, dump_info)) {
                /* dump_info->current_part is unchanged. */
                amfree(qdiskname);
                return TRUE;
            }
        }

        dump_time = g_timeval_to_double(dump_info->total_time);
        dump_kbytes = dump_info->total_bytes / 1024;
        dump_kbps = get_kbps((double)dump_info->total_bytes / 1024.0, dump_time);
        
        putresult(PARTIAL,
                  "%s INPUT-%s TAPE-%s "
                  "\"[sec %f kb %ju kps %f]\" %s %s\n",
                  dump_info->handle,
                  (queue_result & QUEUE_PRODUCER_ERROR) ? "ERROR" : "GOOD",
                  (queue_result & QUEUE_CONSUMER_ERROR) ? "ERROR" : "GOOD",
                  dump_time, (uintmax_t)dump_kbytes, dump_kbps,
		  producer_errstr, consumer_errstr);
	if (queue_result & QUEUE_CONSUMER_ERROR) {
            put_partial_log(dump_info, dump_time, dump_kbytes,
			    consumer_errstr);
	} else {
            put_partial_log(dump_info, dump_time, dump_kbytes,
			    producer_errstr);
	}
	amfree(producer_errstr);
	amfree(consumer_errstr);
    }

    amfree(qdiskname);
    return FALSE;
}
示例#6
0
文件: taper.c 项目: regina/amanda
static gboolean label_new_tape(taper_state_t * state, dump_info_t * dump_info) {
    char *old_volume_name = NULL;
    char *old_volume_time = NULL;
    tape_search_request_t request;
    gboolean search_result;
    DeviceStatusFlags status;

    /* If we got here, it means that we have found a tape to label and
     * have gotten permission from the driver to write it. But we
     * still can say NO-NEW-TAPE if a problem shows up, and must still
     * say NEW-TAPE if one doesn't. */

    amfree(state->last_errmsg);
    state->device = device_open(state->next_tape_device);
    g_assert(state->device != NULL);
    amfree(state->next_tape_device);

    if (state->device->status != DEVICE_STATUS_SUCCESS)
	goto skip_volume;

    if (!device_configure(state->device, TRUE))
	goto skip_volume;

    /* if we have an error, and are sure it isn't just an unlabeled volume,
     * then skip this volume */
    status = device_read_label(state->device);
    if ((status & ~DEVICE_STATUS_VOLUME_UNLABELED) &&
	!(status & DEVICE_STATUS_VOLUME_UNLABELED))
	goto skip_volume;

    old_volume_name = g_strdup(state->device->volume_label);
    old_volume_time = g_strdup(state->device->volume_time);

    if (!device_start(state->device, ACCESS_WRITE, state->next_tape_label,
                      state->driver_start_time)) {
        gboolean tape_used;

        /* Something broke, see if we can tell if the volume was erased or
         * not. */
        g_fprintf(stderr, "taper: Error writing label %s to device %s: %s.\n",
                state->next_tape_label, state->device->device_name,
		device_error_or_status(state->device));

        if (!device_finish(state->device))
	    goto request_new_volume;

	/* This time, if we can't read the label, assume we've overwritten
	 * the volume or otherwise corrupted it */
	status = device_read_label(state->device);
	if ((status & ~DEVICE_STATUS_VOLUME_UNLABELED) &&
	    !(status & DEVICE_STATUS_VOLUME_UNLABELED))
	    goto request_new_volume;

        tape_used = check_volume_changed(state->device, old_volume_name, 
                                         old_volume_time);

        if (tape_used)
	    goto request_new_volume;
        else
	    goto skip_volume;
    }

    amfree(old_volume_name);
    amfree(old_volume_time);
    amfree(state->next_tape_label);

    update_tapelist(state);
    state->cur_tape++;

    if (state->have_changer &&
	changer_label("UNKNOWN", state->device->volume_label) != 0) {
	error(_("couldn't update barcode database"));
	/*NOTREACHED*/
    }

    log_add(L_START, "datestamp %s label %s tape %d",
            state->driver_start_time, state->device->volume_label,
            state->cur_tape);
    putresult(NEW_TAPE, "%s %s\n", dump_info->handle,
	      state->device->volume_label);

    return TRUE;

request_new_volume:
    /* Tell the driver we overwrote this volume, even if it was empty, and request
     * a new volume. */
    if (state->device)
	state->last_errmsg = newstralloc(state->last_errmsg, device_error_or_status(state->device));
    else
	state->last_errmsg = newstralloc(state->last_errmsg, "(unknown)");

    putresult(NEW_TAPE, "%s %s\n", dump_info->handle,
	      state->next_tape_label);
    if (old_volume_name) {
	log_add(L_WARNING, "Problem writing label '%s' to volume %s "
		"(volume may be erased): %s\n",
		state->next_tape_label, old_volume_name,
		state->last_errmsg);
    } else {
	log_add(L_WARNING, "Problem writing label '%s' to new volume "
		"(volume may be erased): %s\n", state->next_tape_label,
		state->last_errmsg);
    }

    if (state->device) {
        g_object_unref(state->device);
        state->device = NULL;
    }

    amfree(state->next_tape_label);
    amfree(old_volume_name);
    amfree(old_volume_time);

    return find_and_label_new_tape(state, dump_info);

skip_volume:
    /* grab a new volume without talking to the driver again -- we do this if we're
     * confident we didn't overwrite the last tape we got. */
    if (state->device)
	state->last_errmsg = newstralloc(state->last_errmsg, device_error_or_status(state->device));
    else
	state->last_errmsg = newstralloc(state->last_errmsg, "(unknown)");

    if (old_volume_name) {
	log_add(L_WARNING, "Problem writing label '%s' to volume '%s' "
		"(old volume data intact): %s\n",
		state->next_tape_label, old_volume_name, state->last_errmsg);
    } else {
	log_add(L_WARNING, "Problem writing label '%s' to new volume "
		"(old volume data intact): %s\n", state->next_tape_label,
		state->last_errmsg);
    }

    if (state->device) {
        g_object_unref(state->device);
        state->device = NULL;
    }

    amfree(state->next_tape_label);
    amfree(old_volume_name);
    amfree(old_volume_time);

    request.state = state;
    request.prolong = TRUE;
    request.errmsg = NULL;
    search_result = GPOINTER_TO_INT(tape_search_thread(&request));
    if (search_result) {
	amfree(request.errmsg);
	return label_new_tape(state, dump_info);
    } else {
	/* Problem finding a new tape! */
	log_taper_scan_errmsg(request.errmsg);
	putresult(NO_NEW_TAPE, "%s\n", dump_info->handle);
	return FALSE;
    }
}
示例#7
0
文件: taper.c 项目: regina/amanda
/* If handle is NULL, then this function assumes that we are in startup mode.
 * In that case it will wait for a command from driver. If handle is not NULL,
 * this this function will ask for permission with REQUEST-NEW-TAPE. */
static gboolean find_new_tape(taper_state_t * state, dump_info_t * dump) {
    GThread * tape_search = NULL;
    tape_search_request_t search_request;
    gboolean use_threads;
    struct cmdargs *cmdargs;
    cmd_t cmd;

    if (state->device != NULL) {
        return TRUE;
    }

    /* We save the value here in case it changes while we're running. */
    use_threads = g_thread_supported();

    search_request.state = state;
    search_request.prolong = TRUE;
    search_request.errmsg = NULL;
    if (use_threads) {
        tape_search = g_thread_create(tape_search_thread,
                                      &search_request, TRUE, NULL);
    }
    
    putresult(REQUEST_NEW_TAPE, "%s\n", dump->handle);
    cmdargs = getcmd();
    cmd = cmdargs->cmd;

    switch (cmd) {
    default:
        g_fprintf(stderr, "taper: Got odd message from driver, expected NEW-TAPE or NO-NEW-TAPE.\n");
        /* FALLTHROUGH. */
    case NEW_TAPE: {
        gboolean search_result;
        if (use_threads) {
            search_result = GPOINTER_TO_INT(g_thread_join(tape_search));
        } else {
            search_result =
                GPOINTER_TO_INT(tape_search_thread(&search_request));
        }
        if (search_result) {
            /* We don't say NEW_TAPE until we actually write the label. */
	    amfree(search_request.errmsg);
	    free_cmdargs(cmdargs);
            return TRUE;
        } else {
            putresult(NO_NEW_TAPE, "%s\n", dump->handle);
            log_taper_scan_errmsg(search_request.errmsg);
	    log_add(L_ERROR, "no-tape [%s]", "No more writable valid tape found");
	    free_cmdargs(cmdargs);
            return FALSE;
        }
    }
    case NO_NEW_TAPE:
        search_request.prolong = FALSE;
        if (use_threads) {
            g_thread_join(tape_search);
        }
	log_add(L_ERROR, "no-tape [%s]", cmdargs->argv[1]);
	state->last_errmsg = stralloc(cmdargs->argv[1]);
	free_cmdargs(cmdargs);
        return FALSE;
    }
    /* NOTREACHED */
}
示例#8
0
文件: taper.c 项目: regina/amanda
/* Open a socket to the dumper. Returns TRUE if everything is happy, FALSE
   otherwise. */
static gboolean open_read_socket(dump_info_t * info, char * split_diskbuffer,
                             guint64 splitsize, guint64 fallback_splitsize) {
    in_port_t port = 0;
    int socket;
    int fd;
    int result;
    struct addrinfo *res;

    if ((result = resolve_hostname("localhost", 0, &res, NULL) != 0)) {
        char *m;
        char *q;
	int save_errno = errno;
        char *qdiskname = quote_string(info->diskname);

        m = vstralloc("[localhost resolve failure: ",
                      strerror(save_errno),
                      "]",
                      NULL);
        q = quote_string(m);
        putresult(TAPE_ERROR, "%s %s\n", info->handle, q);
        log_add(L_FAIL, "%s %s %s %d %s",
                info->hostname, qdiskname, info->timestamp,
                info->level, q);
        amfree(qdiskname);
        amfree(m);
        amfree(q);
        return FALSE;
    }

    socket = stream_server(res->ai_family, &port, 0, STREAM_BUFSIZE, 0);
    freeaddrinfo(res);

    if (socket < 0) {
        char *m;
        char *q;
	int save_errno = errno;
        char *qdiskname = quote_string(info->diskname);

        m = vstralloc("[port create failure: ",
                      strerror(save_errno),
                      "]",
                      NULL);
        q = quote_string(m);
        putresult(TAPE_ERROR, "%s %s\n", info->handle, q);
        log_add(L_FAIL, "%s %s %s %d %s",
                info->hostname, qdiskname, info->timestamp,
                info->level, q);
        amfree(qdiskname);
        amfree(m);
        amfree(q);
        return FALSE;
    }

    putresult(PORT, "%d\n", port);

    fd = stream_accept(socket, CONNECT_TIMEOUT, 0, STREAM_BUFSIZE);

    if (fd < 0) {
        char *m, *q;
	int save_errno = errno;
        char *qdiskname = quote_string(info->diskname);
        m = vstralloc("[port connect failure: ",
                      strerror(save_errno),
                      "]",
                      NULL);
        q = quote_string(m);
        putresult(TAPE_ERROR, "%s %s\n", info->handle, q);
        log_add(L_FAIL, "%s %s %s %d %s",
                info->hostname, qdiskname, info->timestamp,
                info->level, q);
        amfree(qdiskname);
        aclose(socket);
        amfree(m);
        amfree(q);
        return FALSE;
    } else {
        aclose(socket);
    }

    info->source = taper_source_new(info->handle, PORT_WRITE, NULL, fd,
                                    split_diskbuffer, splitsize,
                                    fallback_splitsize);
    /* FIXME: This should be handled properly. */
    g_assert(info->source != NULL);
    return TRUE;
}
示例#9
0
文件: taper.c 项目: regina/amanda
/* This function recieves the START_TAPER command from driver, and
   returns the attached timestamp. */
static gboolean find_first_tape(taper_state_t * state) {
    struct cmdargs *cmdargs;
    tape_search_request_t search_request;
    GThread * tape_search = NULL;
    gboolean use_threads;

    /* We save the value here in case it changes while we're running. */
    use_threads = g_thread_supported();

    search_request.state = state;
    search_request.prolong = TRUE;
    search_request.errmsg = NULL;
    
    if (use_threads) {
        tape_search = g_thread_create(tape_search_thread,
                                      &search_request, TRUE, NULL);
    }

    cmdargs = getcmd();

    switch (cmdargs->cmd) {
    case START_TAPER: {
        gboolean search_result;
        state->driver_start_time = strdup(cmdargs->argv[1]);
        if (use_threads) {
            search_result = GPOINTER_TO_INT(g_thread_join(tape_search));
        } else {
            search_result =
                GPOINTER_TO_INT(tape_search_thread(&search_request));
        }
        if (search_result) {
            putresult(TAPER_OK, "\n");
        } else {
	    char *msg = quote_string(_("Could not find a tape to use"));
            putresult(TAPE_ERROR, "99-99999 %s\n", msg);
	    log_add(L_ERROR, "no-tape [%s]", "Could not find a tape to use");
	    if (search_request.errmsg != NULL) {
		char *c, *c1;
		c = c1 = search_request.errmsg;
		while (*c != '\0') {
		    if (*c == '\n') {
			*c = '\0';
			log_add(L_WARNING,"%s", c1);
			c1 = c+1;
		    }
		    c++;
		}
		if (strlen(c1) > 1 )
		    log_add(L_WARNING,"%s", c1);
	    }
        }
	amfree(search_request.errmsg);
	free_cmdargs(cmdargs);
        return TRUE;
    }
    case QUIT:
        search_request.prolong = FALSE;
        if (use_threads) {
            g_thread_join(tape_search);
        }
	free_cmdargs(cmdargs);
        return send_quitting(state);
    default:
        error("error [file_reader_side cmd %d argc %d]", cmdargs->cmd, cmdargs->argc);
    }

    g_assert_not_reached();
}
示例#10
0
文件: taper.c 项目: regina/amanda
/* Send QUITTING message to driver and associated logging. Always
   returns false. */
static gboolean send_quitting(taper_state_t * state) {
    putresult(QUITTING, "\n");
    g_fprintf(stderr,"taper: DONE\n");
    cleanup(state);
    return FALSE;
}
示例#11
0
文件: chunker.c 项目: samco/amanda
/*
 * Write out the buffer to the backing file
 */
static int
databuf_flush(
    struct databuf *	db)
{
    struct cmdargs *cmdargs = NULL;
    int rc = 1;
    size_t size_to_write;
    size_t written;
    off_t left_in_chunk;
    char *arg_filename = NULL;
    char *new_filename = NULL;
    char *tmp_filename = NULL;
    char sequence[NUM_STR_SIZE];
    int newfd;
    filetype_t save_type;
    char *q;
    int a;
    char *pc;

    /*
     * If there's no data, do nothing.
     */
    if (db->dataout >= db->datain) {
        goto common_exit;
    }

    /*
     * See if we need to split this file.
     */
    while (db->split_size > (off_t)0 && dumpsize >= db->split_size) {
        if( db->use == (off_t)0 ) {
            /*
             * Probably no more space on this disk.  Request some more.
             */
            putresult(RQ_MORE_DISK, "%s\n", handle);
            cmdargs = getcmd();
            if(command_in_transit == NULL &&
                    (cmdargs->cmd == DONE || cmdargs->cmd == TRYAGAIN || cmdargs->cmd == FAILED)) {
                command_in_transit = cmdargs;
                cmdargs = getcmd();
            }
            if(cmdargs->cmd == CONTINUE) {
                /*
                 * CONTINUE
                 *   serial
                 *   filename
                 *   chunksize
                 *   use
                 */
                a = 2; /* skip CONTINUE and serial */

                if(a >= cmdargs->argc) {
                    error(_("error [chunker CONTINUE: not enough args: filename]"));
                    /*NOTREACHED*/
                }
                arg_filename = newstralloc(arg_filename, cmdargs->argv[a++]);

                if(a >= cmdargs->argc) {
                    error(_("error [chunker CONTINUE: not enough args: chunksize]"));
                    /*NOTREACHED*/
                }
                db->chunk_size = OFF_T_ATOI(cmdargs->argv[a++]);
                db->chunk_size = am_floor(db->chunk_size, (off_t)DISK_BLOCK_KB);

                if(a >= cmdargs->argc) {
                    error(_("error [chunker CONTINUE: not enough args: use]"));
                    /*NOTREACHED*/
                }
                db->use = OFF_T_ATOI(cmdargs->argv[a++]);

                if(a != cmdargs->argc) {
                    error(_("error [chunker CONTINUE: too many args: %d != %d]"),
                          cmdargs->argc, a);
                    /*NOTREACHED*/
                }

                if(strcmp(db->filename, arg_filename) == 0) {
                    /*
                     * Same disk, so use what room is left up to the
                     * next chunk boundary or the amount we were given,
                     * whichever is less.
                     */
                    left_in_chunk = db->chunk_size - filesize;
                    if(left_in_chunk > db->use) {
                        db->split_size += db->use;
                        db->use = (off_t)0;
                    } else {
                        db->split_size += left_in_chunk;
                        db->use -= left_in_chunk;
                    }
                    if(left_in_chunk > (off_t)0) {
                        /*
                         * We still have space in this chunk.
                         */
                        break;
                    }
                } else {
                    /*
                     * Different disk, so use new file.
                     */
                    db->filename = newstralloc(db->filename, arg_filename);
                }
            } else if(cmdargs->cmd == ABORT) {
                abort_pending = 1;
                errstr = newstralloc(errstr, cmdargs->argv[1]);
                putresult(ABORT_FINISHED, "%s\n", handle);
                rc = 0;
                goto common_exit;
            } else {
                if(cmdargs->argc >= 1) {
                    q = quote_string(cmdargs->argv[0]);
                } else {
                    q = stralloc(_("(no input?)"));
                }
                error(_("error [bad command after RQ-MORE-DISK: \"%s\"]"), q);
                /*NOTREACHED*/
            }
        }

        /*
         * Time to use another file.
         */

        /*
         * First, open the new chunk file, and give it a new header
         * that has no cont_filename pointer.
         */
        g_snprintf(sequence, SIZEOF(sequence), "%d", db->filename_seq);
        new_filename = newvstralloc(new_filename,
                                    db->filename,
                                    ".",
                                    sequence,
                                    NULL);
        tmp_filename = newvstralloc(tmp_filename,
                                    new_filename,
                                    ".tmp",
                                    NULL);
        pc = strrchr(tmp_filename, '/');
        g_assert(pc != NULL); /* Only a problem if db->filename has no /. */
        *pc = '\0';
        mkholdingdir(tmp_filename);
        *pc = '/';
        newfd = open(tmp_filename, O_RDWR|O_CREAT|O_TRUNC, 0600);
        if (newfd == -1) {
            int save_errno = errno;
            char *m;

            if(save_errno == ENOSPC) {
                putresult(NO_ROOM, "%s %lld\n", handle,
                          (long long)(db->use+db->split_size-dumpsize));
                db->use = (off_t)0;			/* force RQ_MORE_DISK */
                db->split_size = dumpsize;
                continue;
            }
            m = vstrallocf(_("creating chunk holding file \"%s\": %s"),
                           tmp_filename,
                           strerror(errno));
            errstr = quote_string(m);
            amfree(m);
            aclose(db->fd);
            rc = 0;
            goto common_exit;
        }
        save_type = file.type;
        file.type = F_CONT_DUMPFILE;
        file.cont_filename[0] = '\0';
        if(write_tapeheader(newfd, &file)) {
            int save_errno = errno;
            char *m;

            aclose(newfd);
            if(save_errno == ENOSPC) {
                putresult(NO_ROOM, "%s %lld\n", handle,
                          (long long)(db->use+db->split_size-dumpsize));
                db->use = (off_t)0;			/* force RQ_MORE DISK */
                db->split_size = dumpsize;
                continue;
            }
            m = vstrallocf(_("write_tapeheader file %s: %s"),
                           tmp_filename,
                           strerror(errno));
            errstr = quote_string(m);
            amfree(m);
            rc = 0;
            goto common_exit;
        }

        /*
         * Now, update the header of the current file to point
         * to the next chunk, and then close it.
         */
        if (lseek(db->fd, (off_t)0, SEEK_SET) < (off_t)0) {
            char *m = vstrallocf(_("lseek holding file %s: %s"),
                                 db->filename,
                                 strerror(errno));
            errstr = quote_string(m);
            amfree(m);
            aclose(newfd);
            rc = 0;
            goto common_exit;
        }

        file.type = save_type;
        strncpy(file.cont_filename, new_filename, SIZEOF(file.cont_filename));
        file.cont_filename[SIZEOF(file.cont_filename)-1] = '\0';
        if(write_tapeheader(db->fd, &file)) {
            char * m = vstrallocf(_("write_tapeheader file \"%s\": %s"),
                                  db->filename,
                                  strerror(errno));
            errstr = quote_string(m);
            amfree(m);
            aclose(newfd);
            unlink(tmp_filename);
            rc = 0;
            goto common_exit;
        }
        file.type = F_CONT_DUMPFILE;

        /*
         * Now shift the file descriptor.
         */
        aclose(db->fd);
        db->fd = newfd;
        newfd = -1;

        /*
         * Update when we need to chunk again
         */
        if(db->use <= (off_t)DISK_BLOCK_KB) {
            /*
             * Cheat and use one more block than allowed so we can make
             * some progress.
             */
            db->split_size += (off_t)(2 * DISK_BLOCK_KB);
            db->use = (off_t)0;
        } else if(db->chunk_size > db->use) {
            db->split_size += db->use;
            db->use = (off_t)0;
        } else {
            db->split_size += db->chunk_size;
            db->use -= db->chunk_size;
        }


        amfree(tmp_filename);
        amfree(new_filename);
        dumpsize += (off_t)DISK_BLOCK_KB;
        filesize = (off_t)DISK_BLOCK_KB;
        headersize += DISK_BLOCK_KB;
        db->filename_seq++;
    }

    /*
     * Write out the buffer
     */
    size_to_write = (size_t)(db->datain - db->dataout);
    written = full_write(db->fd, db->dataout, size_to_write);
    if (written > 0) {
        db->dataout += written;
        dumpbytes += (off_t)written;
    }
    dumpsize += (dumpbytes / (off_t)1024);
    filesize += (dumpbytes / (off_t)1024);
    dumpbytes %= 1024;
    if (written < size_to_write) {
        if (errno != ENOSPC) {
            char *m = vstrallocf(_("data write: %s"), strerror(errno));
            errstr = quote_string(m);
            amfree(m);
            rc = 0;
            goto common_exit;
        }

        /*
         * NO-ROOM is informational only.  Later, RQ_MORE_DISK will be
         * issued to use another holding disk.
         */
        putresult(NO_ROOM, "%s %lld\n", handle,
                  (long long)(db->use+db->split_size-dumpsize));
        db->use = (off_t)0;				/* force RQ_MORE_DISK */
        db->split_size = dumpsize;
        goto common_exit;
    }
    if (db->datain == db->dataout) {
        /*
         * We flushed the whole buffer so reset to use it all.
         */
        db->datain = db->dataout = db->buf;
    }

common_exit:

    if (cmdargs)
        free_cmdargs(cmdargs);
    amfree(new_filename);
    /*@i@*/ amfree(tmp_filename);
    amfree(arg_filename);
    return rc;
}
示例#12
0
文件: chunker.c 项目: samco/amanda
static int
do_chunk(
    int			infd,
    struct databuf *	db)
{
    size_t nread;
    char header_buf[DISK_BLOCK_BYTES];

    startclock();

    dumpsize = dumpbytes = filesize = (off_t)0;
    headersize = 0;
    memset(header_buf, 0, sizeof(header_buf));

    /*
     * The first thing we should receive is the file header, which we
     * need to save into "file", as well as write out.  Later, the
     * chunk code will rewrite it.
     */
    nread = full_read(infd, header_buf, SIZEOF(header_buf));
    if (nread != sizeof(header_buf)) {
        if(errno != 0) {
            errstr = vstrallocf(_("cannot read header: %s"), strerror(errno));
        } else {
            errstr = vstrallocf(_("cannot read header: got %zd bytes instead of %zd"),
                                nread, sizeof(header_buf));
        }
        return 0;
    }
    parse_file_header(header_buf, &file, (size_t)nread);
    if(write_tapeheader(db->fd, &file)) {
        int save_errno = errno;
        char *m = vstrallocf(_("write_tapeheader file %s: %s"),
                             db->filename, strerror(errno));
        errstr = quote_string(m);
        amfree(m);
        if(save_errno == ENOSPC) {
            putresult(NO_ROOM, "%s %lld\n", handle,
                      (long long)(db->use+db->split_size-dumpsize));
        }
        return 0;
    }
    dumpsize += (off_t)DISK_BLOCK_KB;
    filesize = (off_t)DISK_BLOCK_KB;
    headersize += DISK_BLOCK_KB;

    /*
     * We've written the file header.  Now, just write data until the
     * end.
     */
    while ((nread = full_read(infd, db->buf,
                              (size_t)(db->datalimit - db->datain))) > 0) {
        db->datain += nread;
        while(db->dataout < db->datain) {
            if(!databuf_flush(db)) {
                return 0;
            }
        }
    }
    while(db->dataout < db->datain) {
        if(!databuf_flush(db)) {
            return 0;
        }
    }
    if(dumpbytes > (off_t)0) {
        dumpsize += (off_t)1;			/* count partial final KByte */
        filesize += (off_t)1;
    }
    return 1;
}
示例#13
0
文件: chunker.c 项目: samco/amanda
/*
 * Returns a file descriptor to the incoming port
 * on success, or -1 on error.
 */
static int
startup_chunker(
    char *		filename,
    off_t		use,
    off_t		chunksize,
    struct databuf *	db)
{
    int infd, outfd;
    char *tmp_filename, *pc;
    in_port_t data_port;
    int data_socket;
    int result;
    struct addrinfo *res;

    data_port = 0;
    if ((result = resolve_hostname("localhost", 0, &res, NULL) != 0)) {
        errstr = newvstrallocf(errstr, _("could not resolve localhost: %s"),
                               gai_strerror(result));
        return -1;
    }
    data_socket = stream_server(res->ai_family, &data_port, 0,
                                STREAM_BUFSIZE, 0);
    if (res) freeaddrinfo(res);

    if(data_socket < 0) {
        errstr = vstrallocf(_("error creating stream server: %s"), strerror(errno));
        return -1;
    }

    putresult(PORT, "%d\n", data_port);

    infd = stream_accept(data_socket, CONNECT_TIMEOUT, 0, STREAM_BUFSIZE);
    aclose(data_socket);
    if(infd == -1) {
        errstr = vstrallocf(_("error accepting stream: %s"), strerror(errno));
        return -1;
    }

    tmp_filename = vstralloc(filename, ".tmp", NULL);
    pc = strrchr(tmp_filename, '/');
    g_assert(pc != NULL);
    *pc = '\0';
    mkholdingdir(tmp_filename);
    *pc = '/';
    if ((outfd = open(tmp_filename, O_RDWR|O_CREAT|O_TRUNC, 0600)) < 0) {
        int save_errno = errno;
        char *m = vstrallocf(_("holding file \"%s\": %s"),
                             tmp_filename,
                             strerror(errno));

        errstr = quote_string(m);
        amfree(m);
        amfree(tmp_filename);
        aclose(infd);
        if(save_errno == ENOSPC) {
            putresult(NO_ROOM, "%s %lld\n",
                      handle, (long long)use);
            return -2;
        } else {
            return -1;
        }
    }
    amfree(tmp_filename);
    databuf_init(db, outfd, filename, use, chunksize);
    db->filename_seq++;
    return infd;
}
示例#14
0
文件: chunker.c 项目: samco/amanda
int
main(
    int		argc,
    char **	argv)
{
    static struct databuf db;
    struct cmdargs *cmdargs;
    int infd;
    char *q = NULL;
    char *filename = NULL;
    off_t chunksize, use;
    times_t runtime;
    am_feature_t *their_features = NULL;
    int a;
    config_overrides_t *cfg_ovr = NULL;
    char *cfg_opt = NULL;
    char *m;

    /*
     * Configure program for internationalization:
     *   1) Only set the message locale for now.
     *   2) Set textdomain for all amanda related programs to "amanda"
     *      We don't want to be forced to support dozens of message catalogs.
     */
    setlocale(LC_MESSAGES, "C");
    textdomain("amanda");

    safe_fd(-1, 0);

    set_pname("chunker");

    dbopen(DBG_SUBDIR_SERVER);

    /* Don't die when child closes pipe */
    signal(SIGPIPE, SIG_IGN);

    add_amanda_log_handler(amanda_log_stderr);
    add_amanda_log_handler(amanda_log_trace_log);

    cfg_ovr = extract_commandline_config_overrides(&argc, &argv);

    if (argc > 1)
        cfg_opt = argv[1];

    config_init(CONFIG_INIT_EXPLICIT_NAME | CONFIG_INIT_USE_CWD, cfg_opt);
    apply_config_overrides(cfg_ovr);

    if (config_errors(NULL) >= CFGERR_WARNINGS) {
        config_print_errors();
        if (config_errors(NULL) >= CFGERR_ERRORS) {
            g_critical(_("errors processing config file"));
        }
    }

    safe_cd(); /* do this *after* config_init() */

    check_running_as(RUNNING_AS_DUMPUSER);

    dbrename(get_config_name(), DBG_SUBDIR_SERVER);

    log_add(L_INFO, "%s pid %ld", get_pname(), (long)getpid());
    g_fprintf(stderr,
              _("%s: pid %ld executable %s version %s\n"),
              get_pname(), (long) getpid(),
              argv[0], VERSION);
    fflush(stderr);

    /* now, make sure we are a valid user */

    signal(SIGPIPE, SIG_IGN);
    signal(SIGCHLD, SIG_IGN);

    cmdargs = getcmd();
    if(cmdargs->cmd == START) {
        if(cmdargs->argc <= 1)
            error(_("error [dumper START: not enough args: timestamp]"));
        chunker_timestamp = newstralloc(chunker_timestamp, cmdargs->argv[1]);
    }
    else {
        log_add(L_INFO, "%s pid %ld", get_pname(), (long)getpid());
        error(_("Didn't get START command"));
    }

    /*    do {*/
    cmdargs = getcmd();

    switch(cmdargs->cmd) {
    case QUIT:
        break;

    case PORT_WRITE:
        /*
         * PORT-WRITE
         *   handle
         *   filename
         *   host
         *   features
         *   disk
         *   level
         *   dumpdate
         *   chunksize
         *   progname
         *   use
         *   options
         */
        a = 1;

        if(a >= cmdargs->argc) {
            error(_("error [chunker PORT-WRITE: not enough args: handle]"));
            /*NOTREACHED*/
        }
        handle = newstralloc(handle, cmdargs->argv[a++]);

        if(a >= cmdargs->argc) {
            error(_("error [chunker PORT-WRITE: not enough args: filename]"));
            /*NOTREACHED*/
        }
        filename = newstralloc(filename, cmdargs->argv[a++]);

        if(a >= cmdargs->argc) {
            error(_("error [chunker PORT-WRITE: not enough args: hostname]"));
            /*NOTREACHED*/
        }
        hostname = newstralloc(hostname, cmdargs->argv[a++]);

        if(a >= cmdargs->argc) {
            error(_("error [chunker PORT-WRITE: not enough args: features]"));
            /*NOTREACHED*/
        }
        am_release_feature_set(their_features);
        their_features = am_string_to_feature(cmdargs->argv[a++]);
        if (!their_features) {
            error(_("error [chunker PORT-WRITE: invalid feature string]"));
            /*NOTREACHED*/
        }

        if(a >= cmdargs->argc) {
            error(_("error [chunker PORT-WRITE: not enough args: diskname]"));
            /*NOTREACHED*/
        }
        diskname = newstralloc(diskname, cmdargs->argv[a++]);
        if (qdiskname)
            amfree(qdiskname);
        qdiskname = quote_string(diskname); /* qdiskname is a global */

        if(a >= cmdargs->argc) {
            error(_("error [chunker PORT-WRITE: not enough args: level]"));
            /*NOTREACHED*/
        }
        level = atoi(cmdargs->argv[a++]);

        if(a >= cmdargs->argc) {
            error(_("error [chunker PORT-WRITE: not enough args: dumpdate]"));
            /*NOTREACHED*/
        }
        dumpdate = newstralloc(dumpdate, cmdargs->argv[a++]);

        if(a >= cmdargs->argc) {
            error(_("error [chunker PORT-WRITE: not enough args: chunksize]"));
            /*NOTREACHED*/
        }
        chunksize = OFF_T_ATOI(cmdargs->argv[a++]);
        chunksize = am_floor(chunksize, (off_t)DISK_BLOCK_KB);

        if(a >= cmdargs->argc) {
            error(_("error [chunker PORT-WRITE: not enough args: progname]"));
            /*NOTREACHED*/
        }
        progname = newstralloc(progname, cmdargs->argv[a++]);

        if(a >= cmdargs->argc) {
            error(_("error [chunker PORT-WRITE: not enough args: use]"));
            /*NOTREACHED*/
        }
        use = am_floor(OFF_T_ATOI(cmdargs->argv[a++]), DISK_BLOCK_KB);

        if(a >= cmdargs->argc) {
            error(_("error [chunker PORT-WRITE: not enough args: options]"));
            /*NOTREACHED*/
        }
        options = newstralloc(options, cmdargs->argv[a++]);

        if(a != cmdargs->argc) {
            error(_("error [chunker PORT-WRITE: too many args: %d != %d]"),
                  cmdargs->argc, a);
            /*NOTREACHED*/
        }

        if((infd = startup_chunker(filename, use, chunksize, &db)) < 0) {
            q = quote_string(vstrallocf(_("[chunker startup failed: %s]"), errstr));
            putresult(TRYAGAIN, "%s %s\n", handle, q);
            error("startup_chunker failed: %s", errstr);
        }
        command_in_transit = NULL;
        if(infd >= 0 && do_chunk(infd, &db)) {
            char kb_str[NUM_STR_SIZE];
            char kps_str[NUM_STR_SIZE];
            double rt;

            runtime = stopclock();
            rt = g_timeval_to_double(runtime);
            g_snprintf(kb_str, SIZEOF(kb_str), "%lld",
                       (long long)(dumpsize - (off_t)headersize));
            g_snprintf(kps_str, SIZEOF(kps_str), "%3.1lf",
                       isnormal(rt) ? (double)dumpsize / rt : 0.0);
            errstr = newvstrallocf(errstr, "sec %s kb %s kps %s",
                                   walltime_str(runtime), kb_str, kps_str);
            m = vstrallocf("[%s]", errstr);
            q = quote_string(m);
            amfree(m);
            if(command_in_transit != NULL) {
                cmdargs = command_in_transit;
                command_in_transit = NULL;
            } else {
                cmdargs = getcmd();
            }
            switch(cmdargs->cmd) {
            case DONE:
                putresult(DONE, "%s %lld %s\n", handle,
                          (long long)(dumpsize - (off_t)headersize), q);
                log_add(L_SUCCESS, "%s %s %s %d [%s]",
                        hostname, qdiskname, chunker_timestamp, level, errstr);
                break;
            case BOGUS:
            case TRYAGAIN:
            case FAILED:
            case ABORT_FINISHED:
                if(dumpsize > (off_t)DISK_BLOCK_KB) {
                    putresult(PARTIAL, "%s %lld %s\n", handle,
                              (long long)(dumpsize - (off_t)headersize),
                              q);
                    log_add(L_PARTIAL, "%s %s %s %d [%s]",
                            hostname, qdiskname, chunker_timestamp, level, errstr);
                }
                else {
                    errstr = newvstrallocf(errstr,
                                           _("dumper returned %s"), cmdstr[cmdargs->cmd]);
                    amfree(q);
                    m = vstrallocf("[%s]",errstr);
                    q = quote_string(m);
                    amfree(m);
                    putresult(FAILED, "%s %s\n", handle, q);
                    log_add(L_FAIL, "%s %s %s %d [%s]",
                            hostname, qdiskname, chunker_timestamp, level, errstr);
                }
            default:
                break;
            }
            amfree(q);
        } else if(infd != -2) {
            if(q == NULL) {
                m = vstrallocf("[%s]", errstr);
                q = quote_string(m);
                amfree(m);
            }
            if(!abort_pending) {
                putresult(FAILED, "%s %s\n", handle, q);
            }
            log_add(L_FAIL, "%s %s %s %d [%s]",
                    hostname, qdiskname, chunker_timestamp, level, errstr);
            amfree(q);
        }
        amfree(filename);
        amfree(db.filename);
        break;

    default:
        if(cmdargs->argc >= 1) {
            q = quote_string(cmdargs->argv[0]);
        } else {
            q = stralloc(_("(no input?)"));
        }
        putresult(BAD_COMMAND, "%s\n", q);
        amfree(q);
        break;
    }

    /*    } while(cmdargs->cmd != QUIT); */

    log_add(L_INFO, "pid-done %ld", (long)getpid());

    amfree(errstr);
    amfree(chunker_timestamp);
    amfree(handle);
    amfree(hostname);
    amfree(diskname);
    amfree(qdiskname);
    amfree(dumpdate);
    amfree(progname);
    amfree(options);
    free_cmdargs(cmdargs);
    if (command_in_transit)
        free_cmdargs(command_in_transit);
    am_release_feature_set(their_features);
    their_features = NULL;

    dbclose();

    return (0); /* exit */
}