Example #1
0
static void
use_device_impl(
    XferSourceRecovery *xdtself,
    Device *device)
{
    XferSourceRecovery *self = XFER_SOURCE_RECOVERY(xdtself);

    g_assert(self->paused);

    /* short-circuit if nothing is changing */
    if (self->device == device)
	return;

    if (self->device)
	g_object_unref(self->device);
    self->device = NULL;

    /* if we already have a connection, then make this device use it */
    if (self->conn) {
	if (!device_use_connection(device, self->conn)) {
	    /* queue up an error for later, and set device_bad.
	     * start_part will see this and fail silently */
	    self->device_bad = TRUE;
	    xfer_cancel_with_error(XFER_ELEMENT(self),
		_("Cannot continue onto new volume: %s"),
		device_error_or_status(device));
	    return;
	}
    }

    self->device = device;
    g_object_ref(device);
}
Example #2
0
static gpointer
directtcp_listen_thread(
	gpointer data)
{
    XferSourceRecovery *self = XFER_SOURCE_RECOVERY(data);
    XferElement *elt = XFER_ELEMENT(self);
    int result;

    DBG(1, "(this is directtcp_listen_thread)");

    /* we need to make an outgoing connection to downstream; we do this while
     * holding the start_part_mutex, so that a part doesn't get started until
     * we're finished with the device */
    g_mutex_lock(self->start_part_mutex);

    if (elt->cancelled) {
	g_mutex_unlock(self->start_part_mutex);
	goto send_done;
    }

    g_assert(self->device != NULL); /* have a device */
    g_assert(elt->downstream->input_listen_addrs != NULL); /* downstream listening */

    DBG(2, "making DirectTCP connection on device %s", self->device->device_name);
    result = device_connect(self->device, FALSE,
			    elt->downstream->input_listen_addrs,
			    &self->conn, &elt->cancelled,
			    self->start_part_mutex, self->abort_cond);
    if (result == 1 && !elt->cancelled) {
	xfer_cancel_with_error(elt,
	    _("error making DirectTCP connection: %s"),
	    device_error_or_status(self->device));
	g_mutex_unlock(self->start_part_mutex);
	wait_until_xfer_cancelled(elt->xfer);
	goto send_done;
    } else if (result == 2 || elt->cancelled) {
	g_mutex_unlock(self->start_part_mutex);
	wait_until_xfer_cancelled(elt->xfer);
	goto send_done;
    }
    DBG(2, "DirectTCP connect succeeded");

    return directtcp_common_thread(self);

send_done:
    xfer_queue_message(elt->xfer, xmsg_new(elt, XMSG_DONE, 0));
    return NULL;
}
Example #3
0
static gboolean
do_block(
    XferDestDevice *self,
    guint size,
    gpointer data)
{
    XferElement *elt = XFER_ELEMENT(self);

    if (device_write_block(self->device, size, data) != WRITE_SUCCEED) {
	xfer_cancel_with_error(elt, "%s: %s",
		self->device->device_name, device_error_or_status(self->device));
	wait_until_xfer_cancelled(elt->xfer);
	return FALSE;
    }

    /* check for LEOM */
    if (self->cancel_at_leom && self->device->is_eom) {
	xfer_cancel_with_error(elt, "%s: LEOM detected", self->device->device_name);
	wait_until_xfer_cancelled(elt->xfer);
	return FALSE;
    }

    return TRUE;
}
Example #4
0
static gboolean
setup_impl(
    XferElement *elt)
{
    XferSourceRecovery *self = XFER_SOURCE_RECOVERY(elt);

    if (elt->output_mech == XFER_MECH_DIRECTTCP_CONNECT) {
	g_assert(self->device != NULL);
	DBG(2, "listening for DirectTCP connection on device %s", self->device->device_name);
	if (!device_listen(self->device, FALSE, &elt->output_listen_addrs)) {
	    xfer_cancel_with_error(elt,
		_("error listening for DirectTCP connection: %s"),
		device_error_or_status(self->device));
	    return FALSE;
	}
	self->listen_ok = TRUE;
    } else {
	/* no output_listen_addrs for either XFER_MECH_DIRECTTCP_LISTEN or
	 * XFER_MECH_PULL_BUFFER */
	elt->output_listen_addrs = NULL;
    }

    return TRUE;
}
Example #5
0
/* This function checks the label of a single tape, which may or may not
 * have been loaded by the changer. With the addition of char *dev, and *slot,
 * it has the same interface as taper_scan. slot should be the slot where
 * this tape is found, or NULL if no changer is in use.
 * Return value is the same as taper_scan.
 */
int scan_read_label(
    char *dev,
    char *slot,
    char *desired_label,
    char** label,
    char** timestamp,
    char** error_message)
{
    Device * device;
    char *labelstr;
    DeviceStatusFlags device_status;
    char *new_label_errmsg;

    g_return_val_if_fail(dev != NULL, -1);

    if (*error_message == NULL)
        *error_message = stralloc("");

    *label = *timestamp = NULL;
    device = device_open(dev);
    g_assert(device != NULL);

    if (device->status != DEVICE_STATUS_SUCCESS ) {
        *error_message = newvstrallocf(*error_message,
                                       _("%sError opening device %s: %s.\n"),
                                       *error_message, dev,
                                       device_error_or_status(device));
        g_object_unref(device);
        amfree(*timestamp);
        amfree(*label);
        return -1;
    }

    if (!device_configure(device, TRUE)) {
        *error_message = newvstrallocf(*error_message,
                                       _("%sError configuring device %s: %s.\n"),
                                       *error_message, dev,
                                       device_error_or_status(device));
        g_object_unref(device);
        amfree(*timestamp);
        amfree(*label);
        return -1;
    }

    device_status = device_read_label(device);

    if (device_status == DEVICE_STATUS_SUCCESS && device->volume_label != NULL) {
        *label = g_strdup(device->volume_label);
        *timestamp = strdup(device->volume_time);
    } else if (device_status & DEVICE_STATUS_VOLUME_UNLABELED) {
        if (!getconf_seen(CNF_LABEL_NEW_TAPES)) {
            *error_message = newvstrallocf(*error_message,
                                           _("%sFound an empty or non-amanda tape.\n"),
                                           *error_message);
            g_object_unref(device);
            return -1;
        }

        /* If we got a header, but the Device doesn't think it's labeled, then this
         * tape probably has some data on it, so refuse to automatically label it */
        if (device->volume_header && device->volume_header->type != F_EMPTY) {
            *error_message = newvstrallocf(*error_message,
                                           _("%sFound a non-amanda tape; check and relabel it with 'amlabel -f'\n"),
                                           *error_message);
            g_object_unref(device);
            return -1;
        }
        g_object_unref(device);

        *label = find_brand_new_tape_label(&new_label_errmsg);
        if (*label != NULL) {
            *timestamp = stralloc("X");
            *error_message = newvstrallocf(*error_message,
                                           _("%sFound an empty tape, will label it `%s'.\n"),
                                           *error_message, *label);

            return 3;
        }
        *error_message = newvstrallocf(*error_message,
                                       _("%s%s.\n"),
                                       *error_message, new_label_errmsg);

        return -1;
    } else {
        char * label_errstr;
        label_errstr = g_strdup_printf(_("Error reading label: %s.\n"),
                                       device_error_or_status(device));
        *error_message = newvstralloc(*error_message, *error_message,
                                      label_errstr, NULL);
        g_free(label_errstr);
        return -1;
    }

    g_assert(*label != NULL && *timestamp != NULL);
    g_object_unref(device);

    *error_message = newvstrallocf(*error_message,
                                   _("%sread label `%s', date `%s'.\n"),
                                   *error_message, *label, *timestamp);

    /* Register this with the barcode database, even if its not ours. */
    if (slot != NULL) {
        changer_label(slot, *label);
    }

    if (desired_label != NULL && strcmp(*label, desired_label) == 0) {
        /* Got desired label. */
        return 1;
    }

    /* Is this actually an acceptable tape? */
    labelstr = getconf_str(CNF_LABELSTR);
    if(!match(labelstr, *label)) {
        *error_message = newvstrallocf(*error_message,
                                       _("%slabel \"%s\" doesn't match \"%s\".\n"),
                                       *error_message, *label, labelstr);

        return -1;
    } else {
        tape_t *tp;
        if (strcmp(*timestamp, "X") == 0) {
            /* new, labeled tape. */
            return 1;
        }

        tp = lookup_tapelabel(*label);

        if(tp == NULL) {
            *error_message =
                newvstrallocf(*error_message,
                              _("%slabel \"%s\" matches labelstr but it is"
                                " not listed in the tapelist file.\n"),
                              *error_message, *label);
            return -1;
        } else if(tp != NULL && !reusable_tape(tp)) {
            *error_message =
                newvstrallocf(*error_message,
                              _("%sTape with label %s is still active"
                                " and cannot be overwritten.\n"),
                              *error_message, *label);
            return -1;
        }
    }

    /* Yay! We got a good tape! */
    return 2;
}
Example #6
0
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;
    }
}
Example #7
0
static gpointer
pull_buffer_impl(
    XferElement *elt,
    size_t *size)
{
    XferSourceDevice *self = (XferSourceDevice *)elt;
    gpointer buf = NULL;
    int result;
    int devsize;
    int max_block;

    /* indicate EOF on an cancel */
    if (elt->cancelled) {
	*size = 0;
	return NULL;
    }

    /* get the device block size */
    if (self->block_size == 0) {
	self->block_size = self->device->block_size;
    }

    do {
	buf = g_try_malloc(self->block_size);
	if (buf == NULL) {
	    xfer_cancel_with_error(elt,
		_("%s: cannot allocate memory"),
		self->device->device_name);
	    wait_until_xfer_cancelled(elt->xfer);
	    return NULL;
	}
	devsize = (int)self->block_size;
	if (elt->size < 0)
	    max_block = -1;
	else
	    max_block = (elt->size+self->block_size-1)/self->block_size;
	result = device_read_block(self->device, buf, &devsize, max_block);
	*size = devsize;

	/* if the buffer was too small, loop around again */
	if (result == 0) {
	    g_assert(*size > self->block_size);
	    self->block_size = devsize;
	    amfree(buf);
	}
    } while (result == 0);

    if (result < 0) {
	amfree(buf);

	/* if we're not at EOF, it's an error */
	if (!self->device->is_eof) {
	    xfer_cancel_with_error(elt,
		_("error reading from %s: %s"),
		self->device->device_name,
		device_error_or_status(self->device));
	    wait_until_xfer_cancelled(elt->xfer);
	}

	*size = 0;
	return NULL;
    }

    return buf;
}
Example #8
0
static gpointer
pull_buffer_impl(
    XferElement *elt,
    size_t *size)
{
    XferSourceRecovery *self = XFER_SOURCE_RECOVERY(elt);
    gpointer buf = NULL;
    int result;
    int devsize;
    XMsg *msg;

    g_assert(elt->output_mech == XFER_MECH_PULL_BUFFER);

    g_mutex_lock(self->start_part_mutex);

    if (elt->size == 0) {
	if (elt->offset == 0 && elt->orig_size == 0) {
	    self->paused = TRUE;
	} else {
	    DBG(2, "xfer-source-recovery sending XMSG_CRC message");
	    DBG(2, "xfer-source-recovery CRC: %08x     size %lld",
		crc32_finish(&elt->crc), (long long)elt->crc.size);
	    msg = xmsg_new(XFER_ELEMENT(self), XMSG_CRC, 0);
	    msg->crc = crc32_finish(&elt->crc);
	    msg->size = elt->crc.size;
	    xfer_queue_message(elt->xfer, msg);

	    /* the device has signalled EOF (really end-of-part), so clean up instance
	     * variables and report the EOP to the caller in the form of an xmsg */
	    DBG(2, "pull_buffer hit EOF; sending XMSG_SEGMENT_DONE");
	    msg = xmsg_new(XFER_ELEMENT(self), XMSG_SEGMENT_DONE, 0);
	    msg->size = self->part_size;
	    if (self->part_timer) {
		msg->duration = g_timer_elapsed(self->part_timer, NULL);
		g_timer_destroy(self->part_timer);
		self->part_timer = NULL;
	    }
	    msg->partnum = 0;
	    msg->fileno = self->device->file;
	    msg->successful = TRUE;
	    msg->eof = FALSE;

	    self->paused = TRUE;
	    device_clear_bytes_read(self->device);
	    self->bytes_read += self->part_size;
	    self->part_size = 0;
	    self->block_size = 0;

	    /* don't queue the XMSG_PART_DONE until we've adjusted all of our
	     * instance variables appropriately */
	    xfer_queue_message(elt->xfer, msg);

	    if (self->device->is_eof) {
		DBG(2, "pull_buffer hit EOF; sending XMSG_PART_DONE");
		msg = xmsg_new(XFER_ELEMENT(self), XMSG_PART_DONE, 0);
		msg->size = self->part_size;
		if (self->part_timer) {
		    msg->duration = g_timer_elapsed(self->part_timer, NULL);
		    g_timer_destroy(self->part_timer);
		    self->part_timer = NULL;
		}
		msg->partnum = 0;
		msg->fileno = self->device->file;
		msg->successful = TRUE;
		msg->eof = FALSE;

		xfer_queue_message(elt->xfer, msg);
	    }
	}
    }

    while (1) {
	/* make sure we have a device */
	while (self->paused && !elt->cancelled)
	    g_cond_wait(self->start_part_cond, self->start_part_mutex);

	/* indicate EOF on an cancel or when there are no more parts */
	if (elt->cancelled) {
            goto error;
	}
	if (self->done)
	    goto error;

	/* start the timer if this is the first pull_buffer of this part */
	if (!self->part_timer) {
	    DBG(2, "first pull_buffer of new part");
	    self->part_timer = g_timer_new();
	}
	if (elt->size == 0) {
	    result = -1;
	} else {
	    /* loop until we read a full block, in case the blocks are larger
	     * than  expected */
	    if (self->block_size == 0)
		self->block_size = (size_t)self->device->block_size;

	    do {
		int max_block;
		buf = g_malloc(self->block_size);
		if (buf == NULL) {
		    xfer_cancel_with_error(elt,
				_("%s: cannot allocate memory"),
				self->device->device_name);
		    g_mutex_unlock(self->start_part_mutex);
		    wait_until_xfer_cancelled(elt->xfer);
		    goto error_unlocked;
		}
		devsize = (int)self->block_size;
		if (elt->size < 0)
		    max_block = -1;
		else
		    max_block = (elt->size+self->block_size-1)/self->block_size;
		result = device_read_block(self->device, buf, &devsize, max_block);
		*size = devsize;

		if (result == 0) {
		    g_assert(*size > self->block_size);
		    self->block_size = devsize;
		    amfree(buf);
		}
	    } while (result == 0);

	    if (result > 0 &&
		(elt->offset ||
		 (elt->size > 0 && (long long unsigned)elt->size < *size))) {
		gpointer buf1 = g_malloc(self->block_size);
		if ((long long unsigned)elt->offset > *size) {
		    g_debug("offset > *size");
		} else if ((long long unsigned)elt->offset == *size) {
		    g_debug("offset == *size");
		}
		*size -= elt->offset;
		if (elt->size > 0 && (size_t)elt->size < *size)
		    *size = elt->size;
		memmove(buf1, buf + elt->offset, *size);
		elt->offset = 0;
		g_free(buf);
		buf = buf1;
	    }
	    if (result > 0)
		elt->size -= *size;

	}

	/* if this block was successful, return it */
	if (result > 0) {
	    self->part_size += *size;
	    break;
	}

	if (result < 0) {
	    amfree(buf);

	    /* if we're not at EOF, it's an error */
	    if (!self->device->is_eof && elt->size != 0) {
		xfer_cancel_with_error(elt,
		    _("error reading from %s: %s"),
		    self->device->device_name,
		    device_error_or_status(self->device));
		g_mutex_unlock(self->start_part_mutex);
		wait_until_xfer_cancelled(elt->xfer);
                goto error_unlocked;
	    }

	    DBG(2, "xfer-source-recovery sending XMSG_CRC message");
	    DBG(2, "xfer-source-recovery CRC: %08x     size %lld",
		crc32_finish(&elt->crc), (long long)elt->crc.size);
	    msg = xmsg_new(XFER_ELEMENT(self), XMSG_CRC, 0);
	    msg->crc = crc32_finish(&elt->crc);
	    msg->size = elt->crc.size;
	    xfer_queue_message(elt->xfer, msg);

	    /* the device has signalled EOF (really end-of-part), so clean up instance
	     * variables and report the EOP to the caller in the form of an xmsg */
	    DBG(2, "pull_buffer hit EOF; sending XMSG_PART_DONE");
	    msg = xmsg_new(XFER_ELEMENT(self), XMSG_PART_DONE, 0);
	    msg->size = self->part_size;
	    msg->duration = g_timer_elapsed(self->part_timer, NULL);
	    msg->partnum = 0;
	    msg->fileno = self->device->file;
	    msg->successful = TRUE;
	    msg->eof = FALSE;

	    self->paused = TRUE;
	    self->bytes_read += self->part_size;
	    device_clear_bytes_read(self->device);
	    self->part_size = 0;
	    self->block_size = 0;
	    if (self->part_timer) {
		g_timer_destroy(self->part_timer);
		self->part_timer = NULL;
	    }

	    /* don't queue the XMSG_PART_DONE until we've adjusted all of our
	     * instance variables appropriately */
	    xfer_queue_message(elt->xfer, msg);
	    if (elt->size == 0) {
		g_mutex_unlock(self->start_part_mutex);
		return NULL;
	    }
	}
    }

    g_mutex_unlock(self->start_part_mutex);

    if (buf) {
	crc32_add(buf, *size, &elt->crc);
    }

    return buf;
error:
    g_mutex_unlock(self->start_part_mutex);
error_unlocked:
    *size = 0;
    return NULL;
}
Example #9
0
/* common code for both directtcp_listen_thread and directtcp_connect_thread;
 * this is called after self->conn is filled in and carries out the data
 * transfer over that connection.  NOTE: start_part_mutex is HELD when this
 * function begins */
static gpointer
directtcp_common_thread(
	XferSourceRecovery *self)
{
    XferElement *elt = XFER_ELEMENT(self);
    char *errmsg = NULL;
    int result;

    /* send XMSG_READY to indicate it's OK to call start_part now */
    DBG(2, "directtcp_common_thread sending XMSG_READY");
    xfer_queue_message(elt->xfer, xmsg_new(elt, XMSG_READY, 0));

    /* now we sit around waiting for signals to write a part */
    while (1) {
	guint64 actual_size;
	XMsg *msg;

	while (self->paused && !elt->cancelled) {
	    DBG(9, "directtcp_common_thread waiting to be un-paused");
	    g_cond_wait(self->start_part_cond, self->start_part_mutex);
	}
	DBG(9, "directtcp_common_thread done waiting");

	if (elt->cancelled) {
	    g_mutex_unlock(self->start_part_mutex);
	    goto close_conn_and_send_done;
	}

	/* if the device is NULL, we're done */
	if (!self->device)
	    break;

	/* read the part */
	self->part_timer = g_timer_new();

	while (1) {
	    DBG(2, "directtcp_common_thread reading part from %s", self->device->device_name);
	    result = device_read_to_connection(self->device, G_MAXUINT64,
			&actual_size, &elt->cancelled,
			self->start_part_mutex, self->abort_cond);
	    if (result == 1 && !elt->cancelled) {
		xfer_cancel_with_error(elt, _("error reading from device: %s"),
		    device_error_or_status(self->device));
		g_mutex_unlock(self->start_part_mutex);
		goto close_conn_and_send_done;
	    } else if (result == 2 || elt->cancelled) {
		g_mutex_unlock(self->start_part_mutex);
		goto close_conn_and_send_done;
	    }

	    /* break on EOF; otherwise do another read_to_connection */
	    if (self->device->is_eof) {
		break;
	    }
	}
	DBG(2, "done reading part; sending XMSG_PART_DONE");

	/* the device has signalled EOF (really end-of-part), so clean up instance
	 * variables and report the EOP to the caller in the form of an xmsg */
	msg = xmsg_new(XFER_ELEMENT(self), XMSG_PART_DONE, 0);
	msg->size = actual_size;
	msg->duration = g_timer_elapsed(self->part_timer, NULL);
	msg->partnum = 0;
	msg->fileno = self->device->file;
	msg->successful = TRUE;
	msg->eof = FALSE;

	self->paused = TRUE;
	g_object_unref(self->device);
	self->device = NULL;
	self->part_size = 0;
	self->block_size = 0;
	g_timer_destroy(self->part_timer);
	self->part_timer = NULL;

	xfer_queue_message(elt->xfer, msg);
    }
    g_mutex_unlock(self->start_part_mutex);

close_conn_and_send_done:
    if (self->conn) {
	errmsg = directtcp_connection_close(self->conn);
	g_object_unref(self->conn);
	self->conn = NULL;
	if (errmsg) {
	    xfer_cancel_with_error(elt, _("error closing DirectTCP connection: %s"), errmsg);
	    wait_until_xfer_cancelled(elt->xfer);
	}
    }

    xfer_queue_message(elt->xfer, xmsg_new(elt, XMSG_DONE, 0));

    return NULL;
}