From 677c663907ecbe2c073abee1515d0a59e561609a Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Mon, 6 Oct 2025 15:00:20 +0200 Subject: [SERVER] iscsi: Implement relaying requests to uplink servers --- src/server/iscsi.c | 111 ++++++++++++++++++++++++++++++++++++++++++----------- src/server/iscsi.h | 12 ++++-- 2 files changed, 97 insertions(+), 26 deletions(-) (limited to 'src') diff --git a/src/server/iscsi.c b/src/server/iscsi.c index b44f695..e9b0fc1 100644 --- a/src/server/iscsi.c +++ b/src/server/iscsi.c @@ -46,7 +46,9 @@ #include "ini.h" #include "iscsi.h" #include "locks.h" +#include "uplink.h" #include "threadpool.h" +#include "reference.h" /** * @file iscsi.c @@ -3850,13 +3852,13 @@ void iscsi_scsi_task_create(iscsi_scsi_task *scsi_task, iscsi_scsi_task_xfer_com scsi_task->target_port = NULL; scsi_task->init_port = NULL; scsi_task->cdb = NULL; + scsi_task->sense_data = NULL; scsi_task->xfer_complete_callback = xfer_complete_callback; scsi_task->destroy_callback = destroy_callback; scsi_task->io_complete_callback = NULL; scsi_task->io_wait.image = NULL; scsi_task->io_wait.callback = NULL; scsi_task->io_wait.user_data = NULL; - scsi_task->sense_data = NULL; scsi_task->buf = NULL; scsi_task->pos = 0UL; scsi_task->len = 0UL; @@ -5239,6 +5241,32 @@ static int iscsi_scsi_emu_queue_io_wait(iscsi_scsi_task *scsi_task, iscsi_scsi_e return iscsi_scsi_emu_io_queue( &scsi_task->io_wait ); } +/** + * @brief Called when data requested via an uplink server has arrived. + * + * This function is used to retrieve + * block data which is NOT locally + * available. + * + * @param[in] data Pointer to related scsi_task. May NOT + * be NULL, so be careful. + * @param[in] handle Uplink handle. + * @param[in] start Start of range in bytes. + * @param[in] length Length of range in bytes, as passed to + * uplink_request(). + * @param[in] buffer Data for requested range. + */ +static void iscsi_uplink_callback(void *data, uint64_t handle UNUSED, uint64_t start UNUSED, uint32_t length, const char *buffer) +{ + iscsi_scsi_task *scsi_task = (iscsi_scsi_task *) data; + + memcpy( scsi_task->buf, buffer, length ); + + pthread_mutex_lock( &scsi_task->uplink_mutex ); + pthread_cond_signal( &scsi_task->uplink_cond ); + pthread_mutex_unlock( &scsi_task->uplink_mutex ); +} + /** * @brief Converts offset and length specified by a block size to offset and length in bytes. * @@ -5279,9 +5307,6 @@ static uint64_t iscsi_scsi_emu_blocks_to_bytes(uint64_t *offset_bytes, const uin * @param[in] scsi_task Pointer to iSCSI SCSI task which * executes the I/O read operation, may * NOT be NULL, so be careful. - * @param[in] buf Pointer to buffer where to store - * the read data. NULL is NOT allowed - * here, take caution. * @param[in] image Pointer to DNBD3 image to read * data from and may NOT be NULL, so * be careful. @@ -5297,16 +5322,62 @@ static uint64_t iscsi_scsi_emu_blocks_to_bytes(uint64_t *offset_bytes, const uin * @return 0 on successful operation, a negative * error code otherwise. */ -int iscsi_scsi_emu_io_block_read(iscsi_scsi_task *scsi_task, uint8_t *buf, dnbd3_image_t *image, const uint64_t offset_blocks, const uint64_t num_blocks, const uint32_t block_size, iscsi_scsi_emu_io_complete_callback callback, uint8_t *user_data) +int iscsi_scsi_emu_io_block_read(iscsi_scsi_task *scsi_task, dnbd3_image_t *image, const uint64_t offset_blocks, const uint64_t num_blocks, const uint32_t block_size, iscsi_scsi_emu_io_complete_callback callback, uint8_t *user_data) { uint64_t offset_bytes; - const uint64_t num_bytes = iscsi_scsi_emu_blocks_to_bytes( &offset_bytes, offset_blocks, num_blocks, block_size ); - const int64_t len = pread( image->readFd, buf, (size_t) num_bytes, offset_bytes ); - const bool success = ((uint64_t) len == num_bytes); + const uint64_t num_bytes = iscsi_scsi_emu_blocks_to_bytes( &offset_bytes, offset_blocks, num_blocks, block_size ); + dnbd3_cache_map_t *cache = ref_get_cachemap( image ); + bool readFromFile; + bool success; + + if ( cache == NULL ) { + readFromFile = true; + } else { + // This is a proxyed image, check if we need to relay the request... + const uint64_t start = (offset_bytes & ~(uint64_t)(DNBD3_BLOCK_SIZE - 1)); + const uint64_t end = ((offset_bytes + num_bytes + DNBD3_BLOCK_SIZE - 1) & ~(uint64_t) (DNBD3_BLOCK_SIZE - 1)); + + readFromFile = image_isRangeCachedUnsafe( cache, start, end ); + ref_put( &cache->reference ); + + if ( !readFromFile ) { + // Not cached, request via uplink + + if ( pthread_mutex_init( &scsi_task->uplink_mutex, NULL ) != 0 ) { + logadd( LOG_ERROR, "iscsi_scsi_emu_io_block_read: Error while initializing DNBD3 uplink mutex for iSCSI SCSI task" ); + + return -ENOMEM; + } + + if ( pthread_cond_init( &scsi_task->uplink_cond, NULL ) != 0 ) { + logadd( LOG_ERROR, "iscsi_scsi_emu_io_block_read: Error while initializing DNBD3 uplink condition for iSCSI SCSI task" ); + + pthread_mutex_destroy( &scsi_task->uplink_mutex ); + + return -ENOMEM; + } + + pthread_mutex_lock( &scsi_task->uplink_mutex ); + success = uplink_request( image, (void *) scsi_task, iscsi_uplink_callback, 0ULL, offset_bytes, (uint32_t) num_bytes ); + + if ( success ) + pthread_cond_wait( &scsi_task->uplink_cond, &scsi_task->uplink_mutex ); + + pthread_mutex_unlock( &scsi_task->uplink_mutex ); + pthread_cond_destroy( &scsi_task->uplink_cond ); + pthread_mutex_destroy( &scsi_task->uplink_mutex ); + } + } + + if ( readFromFile ) { + const int64_t len = pread( image->readFd, scsi_task->buf, (size_t) num_bytes, offset_bytes ); + success = ((uint64_t) len == num_bytes); + } + iscsi_connection_exec_queue *exec_queue = (iscsi_connection_exec_queue *) malloc( sizeof(struct iscsi_connection_exec_queue) ); if ( exec_queue == NULL ) { - logadd( LOG_ERROR, "iscsi_scsi_emu_io_block_read: Out of memory while allocating execution queue for async I/O" ); + logadd( LOG_ERROR, "iscsi_scsi_emu_io_block_read: Out of memory while allocating execution queue for I/O read" ); return -ENOMEM; } @@ -5367,9 +5438,6 @@ uint8_t *iscsi_scsi_emu_block_read_complete_callback(dnbd3_image_t *image, uint8 * executes the I/O compare and write * operation, may NOT be NULL, so be * careful. - * @param[in] buf Pointer to buffer which contains - * the data to be written. NULL is NOT - * allowed here, take caution. * @param[in] cmp_buf Pointer to buffer which contains * the data to be compared and may NOT * be NULL, so be careful. @@ -5388,7 +5456,7 @@ uint8_t *iscsi_scsi_emu_block_read_complete_callback(dnbd3_image_t *image, uint8 * @return 0 on successful operation, a negative * error code otherwise. */ -int iscsi_scsi_emu_io_block_cmp_write(iscsi_scsi_task *scsi_task, uint8_t *buf, uint8_t *cmp_buf, dnbd3_image_t *image, const uint64_t offset_blocks, const uint64_t num_blocks, const uint32_t block_size, iscsi_scsi_emu_io_complete_callback callback, uint8_t *user_data) +int iscsi_scsi_emu_io_block_cmp_write(iscsi_scsi_task *scsi_task, uint8_t *cmp_buf, dnbd3_image_t *image, const uint64_t offset_blocks, const uint64_t num_blocks, const uint32_t block_size, iscsi_scsi_emu_io_complete_callback callback, uint8_t *user_data) { // TODO: Implement compare and write I/O. @@ -5440,9 +5508,6 @@ uint8_t *iscsi_scsi_emu_block_write_complete_callback(dnbd3_image_t *image, uint * @param[in] scsi_task Pointer to iSCSI SCSI task which * executes the I/O write operation, may * NOT be NULL, so be careful. - * @param[in] buf Pointer to buffer which contains - * the data to be written. NULL is NOT - * allowed here, take caution. * @param[in] image Pointer to DNBD3 image to write * data to and may NOT be NULL, so * be careful. @@ -5458,16 +5523,16 @@ uint8_t *iscsi_scsi_emu_block_write_complete_callback(dnbd3_image_t *image, uint * @return 0 on successful operation, a negative * error code otherwise. */ -int iscsi_scsi_emu_io_block_write(iscsi_scsi_task *scsi_task, uint8_t *buf, dnbd3_image_t *image, const uint64_t offset_blocks, const uint64_t num_blocks, const uint32_t block_size, iscsi_scsi_emu_io_complete_callback callback, uint8_t *user_data) +int iscsi_scsi_emu_io_block_write(iscsi_scsi_task *scsi_task, dnbd3_image_t *image, const uint64_t offset_blocks, const uint64_t num_blocks, const uint32_t block_size, iscsi_scsi_emu_io_complete_callback callback, uint8_t *user_data) { uint64_t offset_bytes; const uint64_t num_bytes = iscsi_scsi_emu_blocks_to_bytes( &offset_bytes, offset_blocks, num_blocks, block_size ); - const int64_t len = pwrite( image->readFd, buf, (size_t) num_bytes, offset_bytes ); + const int64_t len = pwrite( image->readFd, scsi_task->buf, (size_t) num_bytes, offset_bytes ); const bool success = ((uint64_t) len == num_bytes); iscsi_connection_exec_queue *exec_queue = (iscsi_connection_exec_queue *) malloc( sizeof(struct iscsi_connection_exec_queue) ); if ( exec_queue == NULL ) { - logadd( LOG_ERROR, "iscsi_scsi_emu_io_block_read: Out of memory while allocating execution queue for async I/O" ); + logadd( LOG_ERROR, "iscsi_scsi_emu_io_block_read: Out of memory while allocating execution queue for I/O write" ); return -ENOMEM; } @@ -5575,7 +5640,7 @@ static int iscsi_scsi_emu_block_read_write(dnbd3_image_t *image, iscsi_scsi_task return ISCSI_SCSI_TASK_RUN_PENDING; } - rc = iscsi_scsi_emu_io_block_read( scsi_task, scsi_task->buf, image, offset_blocks, num_blocks, block_size, iscsi_scsi_emu_block_read_complete_callback, (uint8_t *) scsi_task ); + rc = iscsi_scsi_emu_io_block_read( scsi_task, image, offset_blocks, num_blocks, block_size, iscsi_scsi_emu_block_read_complete_callback, (uint8_t *) scsi_task ); } else if ( iscsi_scsi_emu_io_type_is_supported( image, ISCSI_SCSI_EMU_IO_TYPE_PHYSICAL_READ_ONLY ) || iscsi_scsi_emu_io_type_is_supported( image, ISCSI_SCSI_EMU_IO_TYPE_WRITE_PROTECT ) ) { iscsi_scsi_task_status_set( scsi_task, ISCSI_SCSI_STATUS_CHECK_COND, ISCSI_SCSI_SENSE_KEY_DATA_PROTECT, ISCSI_SCSI_ASC_WRITE_PROTECTED, ISCSI_SCSI_ASCQ_CAUSE_NOT_REPORTABLE ); @@ -5589,9 +5654,9 @@ static int iscsi_scsi_emu_block_read_write(dnbd3_image_t *image, iscsi_scsi_task uint8_t *cmp_buf = (scsi_task->buf + block_size); - rc = iscsi_scsi_emu_io_block_cmp_write( scsi_task, scsi_task->buf, cmp_buf, image, offset_blocks, 1ULL, block_size, iscsi_scsi_emu_block_write_complete_callback, (uint8_t *) scsi_task ); + rc = iscsi_scsi_emu_io_block_cmp_write( scsi_task, cmp_buf, image, offset_blocks, 1ULL, block_size, iscsi_scsi_emu_block_write_complete_callback, (uint8_t *) scsi_task ); } else { - rc = iscsi_scsi_emu_io_block_write( scsi_task, scsi_task->buf, image, offset_blocks, num_blocks, block_size, iscsi_scsi_emu_block_write_complete_callback, (uint8_t *) scsi_task ); + rc = iscsi_scsi_emu_io_block_write( scsi_task, image, offset_blocks, num_blocks, block_size, iscsi_scsi_emu_block_write_complete_callback, (uint8_t *) scsi_task ); } if ( rc < 0 ) { @@ -5726,7 +5791,7 @@ static int iscsi_scsi_emu_block_write_same(dnbd3_image_t *image, iscsi_scsi_task * @retval true The DNBD3 image has been initialized * successfully and is readable. * @retval false The DNBD3 image has NOT been - * successfully and is read is not possible. + * successfully and reading is not possible. */ static bool iscsi_scsi_emu_image_init(iscsi_scsi_task *scsi_task, const bool access) { diff --git a/src/server/iscsi.h b/src/server/iscsi.h index 193b292..ded2d1d 100644 --- a/src/server/iscsi.h +++ b/src/server/iscsi.h @@ -10823,6 +10823,12 @@ typedef struct iscsi_scsi_task { /// I/O task wait. iscsi_scsi_emu_io_wait io_wait; + /// Uplink read mutex for synchronization. + pthread_mutex_t uplink_mutex; + + /// Conditional to signal uplink read complete. + pthread_cond_t uplink_cond; + /// Output buffer. uint8_t *buf; @@ -10935,11 +10941,11 @@ int iscsi_scsi_pr_in(iscsi_scsi_task *scsi_task, iscsi_scsi_pr_reserve_in_parame int iscsi_scsi_pr_reserve_scsi2(iscsi_scsi_task *scsi_task, const iscsi_scsi_cdb_pr_reserve_6 *cdb_pr_reserve_6); // Reserves an iSCSI SCSI Persistent Reservation (PR) of an iSCSI SCSI task int iscsi_scsi_pr_release_scsi2(iscsi_scsi_task *scsi_task); // Releases an iSCSI SCSI Persistent Reservation (PR) of an iSCSI SCSI task -int iscsi_scsi_emu_io_block_read(iscsi_scsi_task *scsi_task, uint8_t *buf, dnbd3_image_t *image, const uint64_t offset_blocks, const uint64_t num_blocks, const uint32_t block_size, iscsi_scsi_emu_io_complete_callback callback, uint8_t *user_data); // Reads a number of blocks from a block offset of a DNBD3 image to a specified buffer +int iscsi_scsi_emu_io_block_read(iscsi_scsi_task *scsi_task, dnbd3_image_t *image, const uint64_t offset_blocks, const uint64_t num_blocks, const uint32_t block_size, iscsi_scsi_emu_io_complete_callback callback, uint8_t *user_data); // Reads a number of blocks from a block offset of a DNBD3 image to a specified buffer uint8_t *iscsi_scsi_emu_block_read_complete_callback(dnbd3_image_t *image, uint8_t *user_data, const bool success); // Completes an iSCSI SCSI task after a finished I/O read operation -int iscsi_scsi_emu_io_block_cmp_write(iscsi_scsi_task *scsi_task, uint8_t *buf, uint8_t *cmp_buf, dnbd3_image_t *image, const uint64_t offset_blocks, const uint64_t num_blocks, const uint32_t block_size, iscsi_scsi_emu_io_complete_callback callback, uint8_t *user_data); // Compares and writes a number of blocks starting from a block offset in a DNBD3 image with specified buffers +int iscsi_scsi_emu_io_block_cmp_write(iscsi_scsi_task *scsi_task, uint8_t *cmp_buf, dnbd3_image_t *image, const uint64_t offset_blocks, const uint64_t num_blocks, const uint32_t block_size, iscsi_scsi_emu_io_complete_callback callback, uint8_t *user_data); // Compares and writes a number of blocks starting from a block offset in a DNBD3 image with specified buffers uint8_t *iscsi_scsi_emu_block_write_complete_callback(dnbd3_image_t *image, uint8_t *user_data, const bool success); // Completes an iSCSI SCSI task after a finished I/O write operation -int iscsi_scsi_emu_io_block_write(iscsi_scsi_task *scsi_task, uint8_t *buf, dnbd3_image_t *image, const uint64_t offset_blocks, const uint64_t num_blocks, const uint32_t block_size, iscsi_scsi_emu_io_complete_callback callback, uint8_t *user_data); // Writes a number of blocks from a block offset to a DNBD3 image of a specified buffer +int iscsi_scsi_emu_io_block_write(iscsi_scsi_task *scsi_task, dnbd3_image_t *image, const uint64_t offset_blocks, const uint64_t num_blocks, const uint32_t block_size, iscsi_scsi_emu_io_complete_callback callback, uint8_t *user_data); // Writes a number of blocks from a block offset to a DNBD3 image of a specified buffer int iscsi_scsi_emu_io_queue(iscsi_scsi_emu_io_wait *io_wait); // Enqueues an I/O wait in the thread pool to execute uint8_t *iscsi_scsi_emu_block_resubmit_process_callback(uint8_t *user_data); // Resubmits an iSCSI SCSI task for execution -- cgit v1.2.3-55-g7522