NetBSD-5.0.2/share/examples/refuse/iscsi-initiator/iscsifs.c

Compare this file to the similar file:
Show the results in this format:

/*
 * Copyright © 2007 Alistair Crooks.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote
 *    products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include <sys/types.h>

#define FUSE_USE_VERSION	26

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <fuse.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define EXTERN

#include "scsi_cmd_codes.h"
#include "iscsi.h"
#include "initiator.h"
#include "tests.h"

#include "virtdir.h"

#include "defs.h"

static int		 verbose; /* how chatty are we? */

static virtdir_t	 iscsi;

enum {
	VendorLen = 8,
	ProductLen = 16,
	VersionLen = 4,

	SGsize = 131072
};


/* this struct keeps information on the target */
typedef struct targetinfo_t {
	 char			*host;		/* resolvable host name */
	 char			*ip;		/* textual IP address */
	 char			*targetname;	/* name of iSCSI target program */
	 char			*stargetname;   /* short name of the target */ 
	 uint64_t		 target;	/* target number */
	 uint32_t		 lun;		/* LUN number */
	 uint32_t		 lbac;		/* number of LBAs */
	 uint32_t		 blocksize;	/* size of device blocks */
	 uint32_t		 devicetype;	/* SCSI device type */
	 char			 vendor[VendorLen + 1];	/* device vendor information */
	 char			 product[ProductLen + 1];	/* device product information */
	 char			 version[VersionLen + 1];	/* device version information */
	 char			*serial;	/* unit serial number */
} targetinfo_t;

DEFINE_ARRAY(targetv_t, targetinfo_t);

static targetv_t	tv;	/* target vector of targetinfo_t structs */

/* iqns and target addresses are returned as pairs in this dynamic array */
static strv_t		all_targets;

/* Small Target Info... */
typedef struct sti_t {
	struct stat             st;		/* normal stat info */
	uint64_t                target;		/* cached target number, so we don't have an expensive pathname-based lookup */
} sti_t;

#ifndef __UNCONST
#define __UNCONST(x)	(x)
#endif

/* read the capacity (maximum LBA and blocksize) from the target */
int 
read_capacity(uint64_t target, uint32_t lun, uint32_t *maxlba, uint32_t *blocklen)
{
	iscsi_scsi_cmd_args_t	args;
	initiator_cmd_t		cmd;
	uint8_t			data[8];
	uint8_t			cdb[16];

	(void) memset(cdb, 0x0, sizeof(cdb));
	cdb[0] = READ_CAPACITY;
	cdb[1] = lun << 5;

	(void) memset(&args, 0x0, sizeof(args));
	args.recv_data = data;
	args.input = 1;
	args.lun = lun;
	args.trans_len = 8;
	args.cdb = cdb;

	(void) memset(&cmd, 0, sizeof(initiator_cmd_t));

	cmd.isid = target;
	cmd.type = ISCSI_SCSI_CMD;
	cmd.ptr = &args;

	if (initiator_command(&cmd) != 0) {
		iscsi_trace_error(__FILE__, __LINE__, "initiator_command() failed\n");
		return -1;
	}
	if (args.status) {
		iscsi_trace_error(__FILE__, __LINE__, "READ_CAPACITY failed (status %#x)\n", args.status);
		return -1;
	}
	*maxlba = ISCSI_NTOHL(*((uint32_t *) (data)));
	*blocklen = ISCSI_NTOHL(*((uint32_t *) (data + 4)));
	if (*maxlba == 0) {
		iscsi_trace_error(__FILE__, __LINE__, "Device returned Maximum LBA of zero\n");
		return -1;
	}
	if (*blocklen % 2) {
		iscsi_trace_error(__FILE__, __LINE__, "Device returned strange block len: %u\n", *blocklen);
		return -1;
	}
	return 0;
}

/* send inquiry command to the target, to get it to identify itself */
static int 
inquiry(uint64_t target, uint32_t lun, uint8_t type, uint8_t inquire, uint8_t *data)
{
	iscsi_scsi_cmd_args_t	args;
	initiator_cmd_t		cmd;
	uint8_t			cdb[16];

	(void) memset(cdb, 0x0, sizeof(cdb));
	cdb[0] = INQUIRY;
	cdb[1] = type | (lun << 5);
	cdb[2] = inquire;
	cdb[4] = 256 - 1;

	(void) memset(&args, 0x0, sizeof(args));
	args.input = 1;
	args.trans_len = 256;
	args.cdb = cdb;
	args.lun = lun;
	args.recv_data = data;
	(void) memset(&cmd, 0x0, sizeof(cmd));
	cmd.isid = target;
	cmd.type = ISCSI_SCSI_CMD;
	cmd.ptr = &args;

	if (initiator_command(&cmd) != 0) {
		iscsi_trace_error(__FILE__, __LINE__, "initiator_command() failed\n");
		return -1;
	}
	if (args.status) {
		iscsi_trace_error(__FILE__, __LINE__, "INQUIRY failed (status %#x)\n", args.status);
		return -1;
	}

	return 0;
}

/* read or write a single block of information */
static int 
blockop(uint64_t target, uint32_t lun, uint32_t lba, uint32_t len,
	      uint32_t blocklen, uint8_t *data, int writing)
{
	iscsi_scsi_cmd_args_t	args;
	initiator_cmd_t		cmd;
	uint16_t   		readlen;
	uint8_t   		cdb[16];

	/* Build CDB */
	(void) memset(cdb, 0, 16);
	cdb[0] = (writing) ? WRITE_10 : READ_10;
	cdb[1] = lun << 5;
	readlen = (uint16_t) len;
	lba2cdb(cdb, &lba, &readlen);

	/* Build SCSI command */
	(void) memset(&args, 0x0, sizeof(args));
	if (writing) {
		args.send_data = data;
		args.output = 1;
	} else {
		args.recv_data = data;
		args.input = 1;
	}
	args.lun = lun;
	args.trans_len = len*blocklen;
	args.length = len*blocklen;
	args.cdb = cdb;
	(void) memset(&cmd, 0, sizeof(initiator_cmd_t));
	cmd.isid = target;
	cmd.type = ISCSI_SCSI_CMD;
	cmd.ptr = &args;
	/* Execute iSCSI command */
	if (initiator_command(&cmd) != 0) {
		iscsi_trace_error(__FILE__, __LINE__, "initiator_command() failed\n");
		return -1;
	}

	if (args.status) {
		iscsi_trace_error(__FILE__, __LINE__, "scsi_command() failed (status %#x)\n", args.status);
		return -1;
	}
	return 0;
}

/* perform a scatter/gather block operation */
static int 
sgblockop(uint64_t target, uint32_t lun, uint32_t lba, uint32_t len,
	      uint32_t blocklen, uint8_t *data, int sglen, int writing)
{
	iscsi_scsi_cmd_args_t	args;
	initiator_cmd_t		cmd;
	uint16_t		readlen;
	uint8_t			cdb[16];

	/* Build CDB */

	(void) memset(cdb, 0, 16);
	cdb[0] = (writing) ? WRITE_10 : READ_10;
	cdb[1] = lun << 5;
	readlen = (uint16_t) len;
	lba2cdb(cdb, &lba, &readlen);

	/* Build iSCSI command */
	(void) memset(&args, 0x0, sizeof(args));
	args.lun = lun;
	args.output = (writing) ? 1 : 0;
	args.input = (writing) ? 0 : 1;
	args.trans_len = len * blocklen;
	args.length = len * blocklen;
	args.send_data = (writing) ? data : NULL;
	args.send_sg_len = (writing) ? sglen : 0;
	args.recv_data = (writing) ? NULL : data;
	args.recv_sg_len = (writing) ? 0 : sglen;
	args.cdb = cdb;
	memset(&cmd, 0, sizeof(initiator_cmd_t));
	cmd.isid = target;
	cmd.ptr = &args;
	cmd.type = ISCSI_SCSI_CMD;

	/* Execute iSCSI command */

	if (initiator_command(&cmd) != 0) {
		iscsi_trace_error(__FILE__, __LINE__, "initiator_command() failed\n");
		return -1;
	}
	if (args.status) {
		iscsi_trace_error(__FILE__, __LINE__, "scsi_command() failed (status %#x)\n", args.status);
		return -1;
	}
	return 0;
}

/* read info from the target - method depends on size of data being read */
static int 
targetop(uint32_t t, uint64_t offset, uint32_t length, uint32_t request, char *buf, int writing)
{
	struct iovec 	*iov;
	int		 ioc;
	int		 i;
	int		 req_len;

	if (request > SGsize) {
		/* split up request into blocksize chunks */
		ioc = request / SGsize;
		if ((ioc * SGsize) < request)
			ioc++;
		if ((iov = iscsi_malloc(ioc * sizeof(*iov))) == NULL) {
			iscsi_trace_error(__FILE__, __LINE__, "out of memory\n");
			return -1;
		}

		for (i = 0 ; i < ioc ; i++) {
			iov[i].iov_base = &buf[i * SGsize];
			if (i == (ioc - 1)) { /* last one */
				iov[i].iov_len = request - (i * SGsize);
			} else {
				iov[i].iov_len = SGsize;
			}
		}

		if (sgblockop(tv.v[t].target, tv.v[t].lun, offset / tv.v[t].blocksize, (length / tv.v[t].blocksize), tv.v[t].blocksize, (uint8_t *) iov, ioc, writing) != 0) {
			iscsi_free(iov);
			iscsi_trace_error(__FILE__, __LINE__, "read_10() failed\n");
			return -1;
		}
		iscsi_free(iov);
	} else {
		req_len = length / tv.v[t].blocksize;
		if ((req_len * tv.v[t].blocksize) < length)
			req_len++;
		if (blockop(tv.v[t].target, tv.v[t].lun, offset / tv.v[t].blocksize, 
				req_len, tv.v[t].blocksize, (uint8_t *) buf, writing) != 0) {
			iscsi_trace_error(__FILE__, __LINE__, "read_10() failed\n");
			return -1;
		}
	}
	return 0;
}


/****************************************************************************/

/* perform the stat operation */
/* if this is the root, then just synthesise the data */
/* otherwise, retrieve the data, and be sure to fill in the size */
static int 
iscsifs_getattr(const char *path, struct stat *st)
{
	virt_dirent_t	*ep;
	sti_t		*p;

	if (strcmp(path, "/") == 0) {
		(void) memset(st, 0x0, sizeof(*st));
		st->st_mode = S_IFDIR | 0755;
		st->st_nlink = 2;
		return 0;
	}
	if ((ep = virtdir_find(&iscsi, path, strlen(path))) == NULL) {
		return -ENOENT;
	}
	switch(ep->type) {
	case 'b':
		(void) memcpy(st, &iscsi.file, sizeof(*st));
		st->st_mode = (S_IFBLK | 0644);
		break;
	case 'c':
		(void) memcpy(st, &iscsi.file, sizeof(*st));
		st->st_mode = (S_IFCHR | 0644);
		break;
	case 'd':
		(void) memcpy(st, &iscsi.dir, sizeof(*st));
		break;
	case 'f':
		(void) memcpy(st, &iscsi.file, sizeof(*st));
		p = (sti_t *) ep->tgt;
		st->st_size = p->st.st_size;
		break;
	case 'l':
		(void) memcpy(st, &iscsi.lnk, sizeof(*st));
		st->st_size = ep->tgtlen;
		break;
	default:
		warn("unknown directory type `%c'", ep->type);
		return -ENOENT;
	}
	st->st_ino = ep->ino;
	return 0;
}

/* readdir operation */
static int 
iscsifs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
	      off_t offset, struct fuse_file_info * fi)
{
	virt_dirent_t	*dp;
	VIRTDIR		*dirp;

	if ((dirp = openvirtdir(&iscsi, path)) == NULL) {
		return 0;
	}
	filler(buf, ".", NULL, 0);
	filler(buf, "..", NULL, 0);
	while ((dp = readvirtdir(dirp)) != NULL) {
		filler(buf, dp->d_name, NULL, 0);
	}
	closevirtdir(dirp);
	return 0;
}

/* open the file in the file system */
static int 
iscsifs_open(const char *path, struct fuse_file_info *fi)
{
	virt_dirent_t	*ep;
	const char	*slash;

	if ((ep = virtdir_find(&iscsi, path, strlen(path))) == NULL) {
		return -ENOENT;
	}
	/* check path is the correct one */
	if ((slash = strrchr(path, '/')) == NULL) {
		slash = path;
	} else {
		slash += 1;
	}
	if (strcmp(slash, "storage") != 0) {
		return -ENOENT;
	}
	return 0;
}

/* read the storage from the iSCSI target */
static int 
iscsifs_read(const char *path, char *buf, size_t size, off_t offset,
	   struct fuse_file_info * fi)
{
	virt_dirent_t	*ep;
	uint64_t target;
	sti_t *p;

	if ((ep = virtdir_find(&iscsi, path, strlen(path))) == NULL) {
		return -ENOENT;
	}

	p = (sti_t *)ep->tgt;
	target = p->target;

	if (targetop(target, offset, size, size, buf, 0) < 0) {
		return -EPERM;
	}
	return size;
}

/* write the file's contents to the file system */
static int 
iscsifs_write(const char *path, const char *buf, size_t size, off_t offset,
	   struct fuse_file_info * fi)
{
	virt_dirent_t	*ep;
        uint64_t	 target;   
        sti_t		*p;

	if ((ep = virtdir_find(&iscsi, path, strlen(path))) == NULL) {
		return -ENOENT;
	}
	
	p = (sti_t *)ep->tgt;
	target = p->target;

	if (targetop(target, offset, size, size, __UNCONST(buf), 1) < 0) {
		return -EPERM;
	}
	return size;
}

/* fill in the statvfs struct */
static int
iscsifs_statfs(const char *path, struct statvfs *st)
{
	(void) memset(st, 0x0, sizeof(*st));
	return 0;
}

/* read the symbolic link */
static int
iscsifs_readlink(const char *path, char *buf, size_t size)
{
	virt_dirent_t	*ep;

	if ((ep = virtdir_find(&iscsi, path, strlen(path))) == NULL) {
		return -ENOENT;
	}
	if (ep->tgt == NULL) {
		return -ENOENT;
	}
	(void) strlcpy(buf, ep->tgt, size);
	return 0;
}

/* operations struct */
static struct fuse_operations iscsiops = {
	.getattr = iscsifs_getattr,
	.readlink = iscsifs_readlink,
	.readdir = iscsifs_readdir,
	.open = iscsifs_open,
	.read = iscsifs_read,
	.write = iscsifs_write,
	.statfs = iscsifs_statfs
};

int 
main(int argc, char **argv)
{
	initiator_target_t	tinfo;
	sti_t			sti;
	uint32_t		lbac;
	uint32_t		blocksize;
	uint8_t			data[256];
	char			hostname[1024];
	char			name[1024];
	char			*colon;
	char		       *host;
	char		       *user;
	int			address_family;
	char			devtype;
	int			port;
	int             	target = -1;
        int			digest_type;
	int			discover;
        int			mutual_auth;
        int			auth_type;
	int			cc;
	int			i;

	(void) memset(&tinfo, 0x0, sizeof(tinfo));
	user = NULL;
	(void) gethostname(host = hostname, sizeof(hostname));
	digest_type = DigestNone;
	auth_type = AuthNone;
	address_family = ISCSI_UNSPEC;
	port = ISCSI_PORT;
	mutual_auth = 0;
	discover = 0;
	(void) stat("/etc/hosts", &sti.st);
	devtype = 'f';
	while ((i = getopt(argc, argv, "46a:bcd:Dfh:p:t:u:vV")) != -1) {
		switch(i) {
		case '4':
			address_family = ISCSI_IPv4;
			break;
		case '6':
			address_family = ISCSI_IPv6;
			break;
		case 'a':
			if (strcasecmp(optarg, "chap") == 0) {
				auth_type = AuthCHAP;
			} else if (strcasecmp(optarg, "kerberos") == 0) {
				auth_type = AuthKerberos;
			} else if (strcasecmp(optarg, "srp") == 0) {
				auth_type = AuthSRP;
			}
			break;
		case 'b':
			devtype = 'b';
			break;
		case 'c':
			devtype = 'c';
			break;
		case 'd':
			if (strcasecmp(optarg, "header") == 0) {
				digest_type = DigestHeader;
			} else if (strcasecmp(optarg, "data") == 0) {
				digest_type = DigestData;
			} else if (strcasecmp(optarg, "both") == 0 || strcasecmp(optarg, "all") == 0) {
				digest_type = (DigestHeader | DigestData);
			}
			break;
		case 'D':
			discover = 1;
			break;
		case 'f':
			devtype = 'f';
			break;
		case 'h':
			host = optarg;
			break;
		case 'p':
			port = atoi(optarg);
			break;
		case 't':
			target = atoi(optarg);
			break;
		case 'u':
			user = optarg;
			break;
		case 'V':
			(void) printf("\"%s\" %s\nPlease send all bug reports to %s\n", PACKAGE_NAME, PACKAGE_VERSION, PACKAGE_BUGREPORT);
			exit(EXIT_SUCCESS);
			/* NOTREACHED */
		case 'v':
			verbose += 1;
			break;
		default:
			(void) fprintf(stderr, "%s: unknown option `%c'", *argv, i);
		}
	}
	if (user == NULL) {
		iscsi_trace_error(__FILE__, __LINE__, "user must be specified with -u");
		exit(EXIT_FAILURE);
	}

	if (initiator_init(host, port, address_family, user, auth_type, mutual_auth, digest_type) == -1) {
		iscsi_trace_error(__FILE__, __LINE__, "initiator_init() failed\n");
		exit(EXIT_FAILURE);
	}


        if (initiator_discover(host, 0, 0) < 0) {
                printf("initiator_discover() in discover failed\n");
		exit(EXIT_FAILURE);
	}

        if (initiator_get_targets(0,&all_targets) == -1) {
 		iscsi_trace_error(__FILE__, __LINE__, "initiator_get_targets() failed\n");
               	exit(EXIT_FAILURE);
	}


        if (discover) {
		printf("Targets available from host %s:\n",host);
		for (i = 0; i < all_targets.c ; i+= 2) {
			printf("%s at %s\n", all_targets.v[i],
				all_targets.v[i+1]);
		}

                exit(EXIT_SUCCESS);
        }

	if (all_targets.c/2 > CONFIG_INITIATOR_NUM_TARGETS) {
		(void) fprintf(stderr, "CONFIG_INITIATOR_NUM_TARGETS in initiator.h is too small.  %d targets available, only %d configurable.\n", all_targets.c/2, CONFIG_INITIATOR_NUM_TARGETS);
		(void) fprintf(stderr, "Truncating number of targets to %d.\n", CONFIG_INITIATOR_NUM_TARGETS);
		all_targets.c = CONFIG_INITIATOR_NUM_TARGETS;
	}


	sti.st.st_ino = 0x15c51;

	for (i = 0 ; i < all_targets.c / 2 ; i++) {

		ALLOC(targetinfo_t, tv.v, tv.size, tv.c, 10, 10, "iscsifs", exit(EXIT_FAILURE));

		initiator_set_target_name(i, all_targets.v[i * 2]);

		if (initiator_discover(host, i, 0) < 0) {
			printf("initiator_discover() failed\n");
			break;
		}

		get_target_info(i, &tinfo);
		if ((colon = strrchr(tinfo.TargetName, ':')) == NULL) {
			colon = tinfo.TargetName;
		} else {
			colon += 1;
		}

		/* stuff size into st.st_size */
		(void) read_capacity(i, 0, &lbac, &blocksize);
		sti.st.st_size = ((uint64_t)lbac + 1) * blocksize;
		sti.target = i;

		tv.v[tv.c].host = strdup(tinfo.name);
		tv.v[tv.c].ip = strdup(tinfo.ip);
		tv.v[tv.c].targetname = strdup(tinfo.TargetName);
		tv.v[tv.c].stargetname = strdup(colon);
		tv.v[tv.c].target = i;
		tv.v[tv.c].lun = 0;
		tv.v[tv.c].lbac = lbac;
		tv.v[tv.c].blocksize = blocksize;

		/* get iSCSI target information */
		(void) memset(data, 0x0, sizeof(data));
		inquiry(i, 0, 0, 0, data);
		tv.v[tv.c].devicetype = (data[0] & 0x1f);
		(void) memcpy(tv.v[tv.c].vendor, &data[8], VendorLen);
		(void) memcpy(tv.v[tv.c].product, &data[8 + VendorLen], ProductLen);
		(void) memcpy(tv.v[tv.c].version, &data[8 + VendorLen + ProductLen], VersionLen);
		(void) memset(data, 0x0, sizeof(data));
		inquiry(i, 0, INQUIRY_EVPD_BIT, INQUIRY_UNIT_SERIAL_NUMBER_VPD, data);
		tv.v[tv.c].serial = strdup((char *)&data[4]);

		cc = snprintf(name, sizeof(name), "/%s/%s", host, colon);
		virtdir_add(&iscsi, name, cc, 'd', name, cc);
		cc = snprintf(name, sizeof(name), "/%s/%s/storage", host, colon);
		virtdir_add(&iscsi, name, cc, devtype, (void *)&sti, sizeof(sti));
		cc = snprintf(name, sizeof(name), "/%s/%s/hostname", host, colon);
		virtdir_add(&iscsi, name, cc, 'l', tinfo.name, strlen(tinfo.name));
		cc = snprintf(name, sizeof(name), "/%s/%s/ip", host, colon);
		virtdir_add(&iscsi, name, cc, 'l', tinfo.ip, strlen(tinfo.ip));
		cc = snprintf(name, sizeof(name), "/%s/%s/targetname", host, colon);
		virtdir_add(&iscsi, name, cc, 'l', tinfo.TargetName, strlen(tinfo.TargetName));
		cc = snprintf(name, sizeof(name), "/%s/%s/vendor", host, colon);
		virtdir_add(&iscsi, name, cc, 'l', tv.v[tv.c].vendor, strlen(tv.v[tv.c].vendor));
		cc = snprintf(name, sizeof(name), "/%s/%s/product", host, colon);
		virtdir_add(&iscsi, name, cc, 'l', tv.v[tv.c].product, strlen(tv.v[tv.c].product));
		cc = snprintf(name, sizeof(name), "/%s/%s/version", host, colon);
		virtdir_add(&iscsi, name, cc, 'l', tv.v[tv.c].version, strlen(tv.v[tv.c].version));
		if (tv.v[tv.c].serial[0] && tv.v[tv.c].serial[0] != ' ') {
			cc = snprintf(name, sizeof(name), "/%s/%s/serial", host, colon);
			virtdir_add(&iscsi, name, cc, 'l', tv.v[tv.c].serial, strlen(tv.v[tv.c].serial));
		}


		tv.c += 1;
	}
	return fuse_main(argc - optind, argv + optind, &iscsiops, NULL);
}