diff --git a/.gitignore b/.gitignore index 3b2ad4e..83d8252 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,7 @@ TAGS /tests/Makefile /utils/Makefile.in /utils/Makefile +/utils/iscsi-discard /utils/iscsi-inq /utils/iscsi-ls /utils/iscsi-perf diff --git a/utils/Makefile.am b/utils/Makefile.am index 271f5d2..931731a 100644 --- a/utils/Makefile.am +++ b/utils/Makefile.am @@ -3,7 +3,7 @@ AM_CFLAGS = $(WARN_CFLAGS) AM_LDFLAGS = -no-undefined LIBS = ../lib/libiscsi.la -bin_PROGRAMS = iscsi-inq iscsi-ls iscsi-swp iscsi-pr +bin_PROGRAMS = iscsi-inq iscsi-ls iscsi-swp iscsi-pr iscsi-discard if !TARGET_OS_IS_WIN32 bin_PROGRAMS += iscsi-perf iscsi-readcapacity16 endif diff --git a/utils/iscsi-discard.c b/utils/iscsi-discard.c new file mode 100644 index 0000000..f01e4e6 --- /dev/null +++ b/utils/iscsi-discard.c @@ -0,0 +1,216 @@ +/* + Copyright (C) 2023 by zhenwei pi + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "iscsi.h" +#include "scsi-lowlevel.h" + +const char *initiator = "iqn.2007-10.com.github:sahlberg:libiscsi:iscsi-discard"; + +void print_help(void) +{ + fprintf(stderr, "Usage: iscsi_readcapacity16 [OPTION...] \n"); + fprintf(stderr, " -i, --initiator-name=iqn-name Initiatorname to use\n"); + fprintf(stderr, " -o, --offset " + "Byte offset into the target from which to start discarding. " + "The provided value must be aligned to the target sector size. " + "The default value is zero.\n"); + fprintf(stderr, " -l, --length " + "The number of bytes to discard (counting from the starting point). " + "The provided value must be aligned to the target sector size. " + "If the specified value extends past the end of the device, " + "iscsi-discard will stop at the device size boundary. " + "The default value extends to the end of the device.\n"); + fprintf(stderr, " -z, --zeroout " + "Zero-fill rather than discard.\n"); + fprintf(stderr, " -d, --debug=integer debug level (0=disabled)\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "Help options:\n"); + fprintf(stderr, " -?, --help Show this help message\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "iSCSI URL format : %s\n", ISCSI_URL_SYNTAX); + fprintf(stderr, "\n"); + fprintf(stderr, " is either of:\n"); + fprintf(stderr, " \"hostname\" iscsi.example\n"); + fprintf(stderr, " \"ipv4-address\" 10.1.1.27\n"); + fprintf(stderr, " \"ipv6-address\" [fce0::1]\n"); + + exit(0); +} + +int main(int argc, char *argv[]) +{ + struct iscsi_context *iscsi; + char *url = NULL; + struct iscsi_url *iscsi_url = NULL; + int debug = 0, zeroout = 0; + int option_index, c; + unsigned int block_length; + long long offset = 0, length = 0, capacity, lba, blocks; + struct scsi_task *task; + struct scsi_readcapacity16 *rc16; + int ret = EINVAL; + + static struct option long_options[] = { + {"offset", required_argument, NULL, 'o'}, + {"length", required_argument, NULL, 'l'}, + {"zeroout", no_argument, NULL, 'z'}, + {"debug", required_argument, NULL, 'd'}, + {"help", no_argument, NULL, 'h'}, + {"initiator-name", required_argument, NULL, 'i'}, + {0, 0, 0, 0} + }; + + while ((c = getopt_long(argc, argv, "o:l:zd:i:h?", long_options, + &option_index)) != -1) { + switch (c) { + case 'o': + offset = strtoll(optarg, NULL, 0); + break; + case 'l': + length = strtoll(optarg, NULL, 0); + break; + case 'z': + zeroout = 1; + break; + case 'd': + debug = strtol(optarg, NULL, 0); + break; + case 'i': + initiator = optarg; + break; + case 'h': + case '?': + print_help(); + break; + default: + fprintf(stderr, "Unrecognized option '%c'\n\n", c); + print_help(); + break; + } + } + + iscsi = iscsi_create_context(initiator); + if (iscsi == NULL) { + fprintf(stderr, "Failed to create context\n"); + exit(EINVAL); + } + + if (debug > 0) { + iscsi_set_log_fn(iscsi, iscsi_log_to_stderr); + iscsi_set_log_level(iscsi, debug); + } + + if (argv[optind] != NULL) { + url = strdup(argv[optind]); + } + if (url == NULL) { + fprintf(stderr, "You must specify the URL\n"); + print_help(); + } + iscsi_url = iscsi_parse_full_url(iscsi, url); + + free(url); + + if (iscsi_url == NULL) { + fprintf(stderr, "Failed to parse URL: %s\n", + iscsi_get_error(iscsi)); + exit(EINVAL); + } + + iscsi_set_session_type(iscsi, ISCSI_SESSION_NORMAL); + iscsi_set_header_digest(iscsi, ISCSI_HEADER_DIGEST_NONE_CRC32C); + + if (iscsi_full_connect_sync(iscsi, iscsi_url->portal, iscsi_url->lun) != 0) { + fprintf(stderr, "Login Failed. %s\n", iscsi_get_error(iscsi)); + goto out; + } + + task = iscsi_readcapacity16_sync(iscsi, iscsi_url->lun); + if (task == NULL || task->status != SCSI_STATUS_GOOD) { + fprintf(stderr,"Failed to send readcapacity command\n"); + goto out; + } + + rc16 = scsi_datain_unmarshall(task); + if (rc16 == NULL) { + fprintf(stderr,"Failed to unmarshall readcapacity16 data\n"); + goto out; + } + + block_length = rc16->block_length; + if (offset & (block_length - 1)) { + fprintf(stderr,"Unaligned offset of %u\n", block_length); + goto free_task; + } + + capacity = block_length * (rc16->returned_lba + 1); + if (offset > capacity) { + fprintf(stderr,"Offset(%lld) exceeds capacity(%lld)\n", offset, capacity); + goto free_task; + } + + if (!length || (offset + length > capacity)) { + length = block_length * (rc16->returned_lba + 1) - offset; + } + + /* free readcapacity16 task */ + scsi_free_scsi_task(task); + + lba = offset / block_length; + blocks = length / block_length; + if (zeroout) { + void *zerobuf = calloc(block_length, 1); + + assert(zerobuf); + task = iscsi_writesame16_sync(iscsi, iscsi_url->lun, lba, zerobuf, + block_length, blocks, 0, 0, 0, 0); + if (task == NULL || task->status != SCSI_STATUS_GOOD) { + fprintf(stderr,"Failed to execute writesame16 command\n"); + goto out; + } + } else { + struct unmap_list list; + + list.lba = lba; + list.num = blocks; + task = iscsi_unmap_sync(iscsi, iscsi_url->lun, 0, 0, &list, 1); + if (task == NULL || task->status != SCSI_STATUS_GOOD) { + fprintf(stderr,"Failed to execute unmap command\n"); + goto out; + } + } + + ret = 0; + +free_task: + scsi_free_scsi_task(task); + +out: + iscsi_destroy_url(iscsi_url); + iscsi_logout_sync(iscsi); + iscsi_destroy_context(iscsi); + + return ret; +}