/* $NetBSD: disk.c,v 1.8 2012/09/29 07:18:21 mlelstv Exp $ */ /*- * Copyright (c) 2006, 2007, 2008, 2009 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Alistair Crooks (agc@netbsd.org) * * 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. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``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 FOUNDATION OR CONTRIBUTORS * 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. */ /* * IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. * By downloading, copying, installing or using the software you agree * to this license. If you do not agree to this license, do not * download, install, copy or use the software. * * Intel License Agreement * * Copyright (c) 2000, Intel Corporation * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * -Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * -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. * * -The name of Intel Corporation may not be used to endorse or * promote products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "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 INTEL * OR CONTRIBUTORS 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 "config.h" #ifdef HAVE_INTTYPES_H #include #endif #include #ifdef HAVE_SYS_PARAM_H #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #ifdef HAVE_SYS_UIO_H #include #endif #ifdef HAVE_SYS_TIME_H #include #endif #ifdef HAVE_SYS_MMAN_H #include #endif #ifdef HAVE_NETINET_IN_H #include #endif #include #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_FCNTL_H #include #endif #include #include #include #ifdef HAVE_STRING_H #include #endif #include #include "scsi_cmd_codes.h" #include "iscsiprotocol.h" #include "compat.h" #include "iscsiutil.h" #include "device.h" #include "target.h" #include "defs.h" #include "storage.h" #define iSCSI_DEFAULT_LUNS 1 #define iSCSI_DEFAULT_BLOCKLEN 512 /* End disk configuration */ /* * Globals */ enum { MAX_RESERVATIONS = 32, ISCSI_FS = 0x03, ISCSI_CONTROL = 0x04 }; #define MB(x) ((x) * 1024 * 1024) /* this struct describes an iscsi LUN */ typedef struct iscsi_disk_t { int type; /* type of disk - fs/mmap and fs */ char filename[MAXPATHLEN]; /* filename for the disk */ uint64_t blockc; /* # of blocks */ uint64_t blocklen; /* block size */ uint64_t luns; /* # of luns */ uint64_t size; /* size of complete disk */ nbuuid_t uuid; /* disk's uuid */ char *uuid_string; /* uuid string */ targv_t *lunv; /* the component devices and extents */ uint32_t resc; /* # of reservation keys */ uint64_t reskeys[MAX_RESERVATIONS]; /* reservation keys */ } iscsi_disk_t; DEFINE_ARRAY(disks_t, iscsi_disk_t); static disks_t disks; static iscsi_disk_t defaults; #ifndef FDATASYNC /* this means that we probably don't have the fsync_range(2) system call, but no matter - define this here to preserve the abstraction for the disk/extent code */ #define FDATASYNC 0x0010 #endif /* * Private Interface */ static int disk_read(target_session_t *, iscsi_scsi_cmd_args_t *, uint32_t, uint16_t, uint8_t); static int disk_write(target_session_t *, iscsi_scsi_cmd_args_t *, uint8_t, uint32_t, uint32_t); /* return the de index and offset within the device for RAID0 */ static int raid0_getoff(disc_device_t *dp, uint64_t off, uint32_t *d, uint64_t *de_off) { uint64_t o; for (o = 0, *d = 0 ; *d < dp->c ; o += dp->xv[*d].size, (*d)++) { if (off >= o && off < o + dp->xv[*d].size) { break; } } *de_off = off - o; return (*d < dp->c); } /* open the extent's device */ static int extent_open(disc_extent_t *xp, int mode, int flags) { return xp->fd = open(xp->dev, mode, flags); } /* (recursively) open the device's devices */ static int device_open(disc_device_t *dp, int flags, int mode) { int fd; uint32_t i; for (fd = -1, i = 0 ; i < dp->c ; i++) { switch (dp->xv[i].type) { case DE_DEVICE: fd = device_open(dp->xv[i].u.dp, flags, mode); if (fd < 0) { return -1; } break; case DE_EXTENT: fd = extent_open(dp->xv[i].u.xp, flags, mode); if (fd < 0) { return -1; } break; default: break; } } return fd; } /* and for the undecided... */ static int de_open(disc_de_t *dp, int flags, int mode) { switch(dp->type) { case DE_DEVICE: return device_open(dp->u.dp, flags, mode); case DE_EXTENT: return extent_open(dp->u.xp, flags, mode); default: return -1; } } /* lseek on the extent */ static off_t extent_lseek(disc_extent_t *xp, off_t off, int whence) { return lseek(xp->fd, (long long)(xp->sacred + off), whence); } /* (recursively) lseek on the device's devices */ static off_t device_lseek(disc_device_t *dp, off_t off, int whence) { uint64_t suboff; off_t ret; uint32_t d; ret = -1; switch(dp->raid) { case 0: if (raid0_getoff(dp, (uint64_t) off, &d, &suboff)) { switch (dp->xv[d].type) { case DE_DEVICE: ret = device_lseek(dp->xv[d].u.dp, (off_t) suboff, whence); if (ret < 0) { return -1; } break; case DE_EXTENT: ret = extent_lseek(dp->xv[d].u.xp, (off_t) suboff, whence); if (ret < 0) { return -1; } break; default: break; } } break; case 1: for (d = 0 ; d < dp->c ; d++) { switch (dp->xv[d].type) { case DE_DEVICE: ret = device_lseek(dp->xv[d].u.dp, (off_t)off, whence); if (ret < 0) { return -1; } break; case DE_EXTENT: ret = extent_lseek(dp->xv[d].u.xp, (off_t)off, whence); if (ret < 0) { return -1; } break; default: break; } } break; default: break; } return dp->off = ret; } /* and for the undecided... */ static off_t de_lseek(disc_de_t *dp, off_t off, int whence) { switch(dp->type) { case DE_DEVICE: return device_lseek(dp->u.dp, off, whence); case DE_EXTENT: return extent_lseek(dp->u.xp, off, whence); default: return -1; } } /* fsync_range on the extent */ static int extent_fsync_range(disc_extent_t *xp, int how, off_t from, off_t len) { #ifdef HAVE_FSYNC_RANGE return fsync_range(xp->fd, how, (off_t)(xp->sacred + from), len); #else return fsync(xp->fd); #endif } /* (recursively) fsync_range on the device's devices */ static int device_fsync_range(disc_device_t *dp, int how, off_t from, off_t len) { uint64_t suboff; int ret; uint32_t d; ret = -1; switch(dp->raid) { case 0: if (raid0_getoff(dp, (uint64_t) from, &d, &suboff)) { switch (dp->xv[d].type) { case DE_DEVICE: ret = device_fsync_range(dp->xv[d].u.dp, how, (off_t)suboff, len); if (ret < 0) { return -1; } break; case DE_EXTENT: ret = extent_fsync_range(dp->xv[d].u.xp, how, (off_t)suboff, len); if (ret < 0) { return -1; } break; default: break; } } break; case 1: for (d = 0 ; d < dp->c ; d++) { switch (dp->xv[d].type) { case DE_DEVICE: ret = device_fsync_range(dp->xv[d].u.dp, how, from, len); if (ret < 0) { return -1; } break; case DE_EXTENT: ret = extent_fsync_range(dp->xv[d].u.xp, how, from, len); if (ret < 0) { return -1; } break; default: break; } } break; default: break; } dp->off = (uint64_t) ret; return ret; } /* and for the undecided... */ static int de_fsync_range(disc_de_t *dp, int how, off_t from, off_t len) { switch(dp->type) { case DE_DEVICE: return device_fsync_range(dp->u.dp, how, from, len); case DE_EXTENT: return extent_fsync_range(dp->u.xp, how, from, len); default: return -1; } } /* read from the extent */ static ssize_t extent_read(disc_extent_t *xp, void *buf, size_t cc) { return read(xp->fd, buf, cc); } /* (recursively) read from the device's devices */ static ssize_t device_read(disc_device_t *dp, void *buf, size_t cc) { uint64_t suboff; uint64_t got; uint32_t d; ssize_t ret; size_t subcc; char *cbuf; ret = -1; switch(dp->raid) { case 0: for (cbuf = (char *) buf, got = 0 ; got < cc ; got += ret) { if (!raid0_getoff(dp, dp->off, &d, &suboff)) { return -1; } if (device_lseek(dp, (off_t)dp->off, SEEK_SET) < 0) { return -1; } subcc = MIN(cc - (size_t)got, (size_t)(dp->len - (size_t)dp->off)); switch (dp->xv[d].type) { case DE_DEVICE: ret = device_read(dp->xv[d].u.dp, &cbuf[(int)got], subcc); if (ret < 0) { return -1; } break; case DE_EXTENT: ret = extent_read(dp->xv[d].u.xp, &cbuf[(int)got], subcc); if (ret < 0) { return -1; } break; default: break; } dp->off += ret; } ret = (ssize_t)got; break; case 1: for (d = 0 ; d < dp->c ; d++) { switch (dp->xv[d].type) { case DE_DEVICE: ret = device_read(dp->xv[d].u.dp, buf, cc); if (ret < 0) { return -1; } break; case DE_EXTENT: ret = extent_read(dp->xv[d].u.xp, buf, cc); if (ret < 0) { return -1; } break; default: break; } } dp->off += ret; break; default: break; } return ret; } /* and for the undecided... */ static ssize_t de_read(disc_de_t *dp, void *buf, size_t cc) { switch(dp->type) { case DE_DEVICE: return device_read(dp->u.dp, buf, cc); case DE_EXTENT: return extent_read(dp->u.xp, buf, cc); default: return -1; } } /* write to the extent */ static ssize_t extent_write(disc_extent_t *xp, void *buf, size_t cc) { return write(xp->fd, buf, cc); } /* (recursively) write to the device's devices */ static ssize_t device_write(disc_device_t *dp, void *buf, size_t cc) { uint64_t suboff; uint64_t done; uint32_t d; ssize_t ret; size_t subcc; char *cbuf; ret = -1; switch(dp->raid) { case 0: for (cbuf = (char *) buf, done = 0 ; done < cc ; done += ret) { if (!raid0_getoff(dp, dp->off, &d, &suboff)) { return -1; } subcc = (size_t)MIN(cc - (size_t)done, (size_t)(dp->len - dp->off)); if (device_lseek(dp, (off_t)dp->off, SEEK_SET) < 0) { return -1; } switch (dp->xv[d].type) { case DE_DEVICE: ret = device_write(dp->xv[d].u.dp, &cbuf[(int)done], subcc); if (ret < 0) { return -1; } break; case DE_EXTENT: ret = extent_write(dp->xv[d].u.xp, &cbuf[(int)done], subcc); if (ret < 0) { return -1; } break; default: break; } dp->off += ret; } ret = (ssize_t) done; break; case 1: for (d = 0 ; d < dp->c ; d++) { switch (dp->xv[d].type) { case DE_DEVICE: ret = device_write(dp->xv[d].u.dp, buf, cc); if (ret < 0) { iscsi_err(__FILE__, __LINE__, "device_write RAID1 device " "write failure\n"); return -1; } break; case DE_EXTENT: ret = extent_write(dp->xv[d].u.xp, buf, cc); if (ret < 0) { iscsi_err(__FILE__, __LINE__, "device_write RAID1 extent " "write failure\n"); return -1; } break; default: break; } } dp->off += ret; break; default: break; } return ret; } /* and for the undecided... */ static ssize_t de_write(disc_de_t *dp, void *buf, size_t cc) { switch(dp->type) { case DE_DEVICE: return device_write(dp->u.dp, buf, cc); case DE_EXTENT: return extent_write(dp->u.xp, buf, cc); default: return -1; } } /* return non-zero if the target is writable */ static int target_writable(disc_target_t *tp) { return !(tp->flags & TARGET_READONLY); } /* return size of the extent */ static uint64_t extent_getsize(disc_extent_t *xp) { return xp->len; } /* (recursively) return the size of the device's devices */ static uint64_t device_getsize(disc_device_t *dp) { uint64_t size; uint32_t d; size = 0; switch(dp->raid) { case 0: for (d = 0 ; d < dp->c ; d++) { switch (dp->xv[d].type) { case DE_DEVICE: size += device_getsize(dp->xv[d].u.dp); break; case DE_EXTENT: size += extent_getsize(dp->xv[d].u.xp); break; default: break; } } break; case 1: size = dp->len; break; default: break; } return size; } /* and for the undecided... */ static int64_t de_getsize(disc_de_t *dp) { switch(dp->type) { case DE_DEVICE: return device_getsize(dp->u.dp); case DE_EXTENT: return extent_getsize(dp->u.xp); default: return -1; } } /* return a filename for the device or extent */ static char * disc_get_filename(disc_de_t *de) { switch (de->type) { case DE_EXTENT: return de->u.xp->dev; case DE_DEVICE: return disc_get_filename(&de->u.dp->xv[0]); default: return NULL; } } /* * Public Interface (called by utarget and ktarket) */ /* set various global variables */ void device_set_var(const char *var, const char *arg) { if (strcmp(var, "blocklen") == 0) { defaults.blocklen = strtoll(arg, (char **)NULL, 10); } else if (strcmp(var, "blocks") == 0) { defaults.blockc = strtoll(arg, (char **)NULL, 10); } else if (strcmp(var, "luns") == 0) { defaults.luns = strtoll(arg, (char **)NULL, 10); } else { (void) fprintf(stderr, "Unrecognised variable: `%s'\n", var); } } /* allocate some space for a disk/extent, using an lseek, read and * write combination */ static int de_allocate(disc_de_t *de, char *filename, uint64_t blocklen) { off_t size; char *block; block = malloc(blocklen); size = de_getsize(de); if (de_lseek(de, size - blocklen, SEEK_SET) == -1) { iscsi_err(__FILE__, __LINE__, "error seeking \"%s\"\n", filename); free(block); return 0; } if (de_read(de, block, blocklen) == -1) { iscsi_err(__FILE__, __LINE__, "error reading \"%s\"\n", filename); free(block); return 0; } if (de_write(de, block, blocklen) == -1) { iscsi_err(__FILE__, __LINE__, "error writing \"%s\"\n", filename); free(block); return 0; } free(block); return 1; } /* allocate space as desired */ static int allocate_space(disc_target_t *tp, uint64_t blocklen) { uint32_t i; /* Don't perform check for writability in the target here, as the following write() in de_allocate is non-destructive */ switch(tp->de.type) { case DE_EXTENT: return de_allocate(&tp->de, tp->target, blocklen); case DE_DEVICE: for (i = 0 ; i < tp->de.u.dp->c ; i++) { if (!de_allocate(&tp->de.u.dp->xv[i], tp->target, blocklen)) { return 0; } } return 1; default: break; } return 0; } /* copy src to dst, of size `n' bytes, padding any extra with `pad' */ static void strpadcpy(uint8_t *dst, size_t dstlen, const char *src, const size_t srclen, char pad) { if (srclen < dstlen) { (void) memcpy(dst, src, srclen); (void) memset(&dst[srclen], pad, dstlen - srclen); } else { (void) memcpy(dst, src, dstlen); } } /* handle REPORT LUNs SCSI command */ static int report_luns(uint8_t *data, uint64_t luns) { uint64_t i; uint32_t len; len = 8; for (i = 0; i < luns; i++) { uint8_t *p = &data[len]; if (i < 256) { memset(p, 0, 8); p[1] = (uint8_t)(i & 0xff); len += 8; } else if (i < 16384) { memset(p, 0, 8); p[0] = (uint8_t)(0x40 | ((i >> 8) & 0x3f)); p[1] = (uint8_t)(i & 0xff); len += 8; } else { /* XXX */ } } *((uint32_t *)(void *)data) = ISCSI_HTONL(len - 8); memset(&data[4], 0, 4); return len; } /* handle persistent reserve in command */ static int persistent_reserve_in(uint8_t action, uint8_t *data) { uint64_t key; switch(action) { case PERSISTENT_RESERVE_IN_READ_KEYS: key = 0; /* simulate "just powered on" */ *((uint32_t *)(void *)data) = (uint32_t)ISCSI_HTONL((uint32_t) 0); *((uint32_t *) (void *)data + 4) = (uint32_t) ISCSI_HTONL((uint32_t) sizeof(key)); /* length in bytes of list of keys */ *((uint64_t *) (void *)data + 8) = (uint64_t) ISCSI_HTONLL(key); return 8 + sizeof(key); case PERSISTENT_RESERVE_IN_REPORT_CAPABILITIES: (void) memset(data, 0x0, 8); /* length is fixed at 8 bytes */ *((uint16_t *)(void *)data) = (uint16_t)ISCSI_HTONS((uint16_t)8); data[2] = PERSISTENT_RESERVE_IN_CRH; /* also SIP_C, ATP_C and PTPL_C here */ data[3] = 0; /* also TMV and PTPL_A here */ data[4] = 0; /* also WR_EX_AR, EX_AC_RD, WR_EX_RD, EX_AC, WR_EX */ data[5] = 0; /* also EX_AC_AR here */ return 8; default: iscsi_err(__FILE__, __LINE__, "persistent_reserve_in: action %x unrecognised\n", action); return 0; } } /* initialise the device */ int device_init(iscsi_target_t *tgt, targv_t *tvp, disc_target_t *tp) { iscsi_disk_t *idisk; int mode; ALLOC(iscsi_disk_t, disks.v, disks.size, disks.c, 10, 10, "device_init", ;); idisk = &disks.v[disks.c]; idisk->lunv = tvp; if ((idisk->luns = defaults.luns) == 0) { idisk->luns = iSCSI_DEFAULT_LUNS; } idisk->blocklen = atoi(iscsi_target_getvar(tgt, "blocklen")); switch(idisk->blocklen) { case 512: case 1024: case 2048: case 4096: case 8192: break; default: iscsi_err(__FILE__, __LINE__, "Invalid block len %" PRIu64 ". Choose one of 512, 1024, 2048, 4096, or 8192.\n", idisk->blocklen); return -1; } idisk->size = de_getsize(&tp->de); idisk->blockc = idisk->size / idisk->blocklen; idisk->type = ISCSI_FS; printf("DISK: %" PRIu64 " logical unit%s (%" PRIu64 " blocks, %" PRIu64 " bytes/block), type %s\n", idisk->luns, (idisk->luns == 1) ? "" : "s", idisk->blockc, idisk->blocklen, (idisk->type == ISCSI_FS) ? "iscsi fs" : "iscsi fs mmap"); printf("DISK: LUN 0: "); (void) strlcpy(idisk->filename, disc_get_filename(&tp->de), sizeof(idisk->filename)); mode = (tp->flags & TARGET_READONLY) ? O_RDONLY : (O_CREAT | O_RDWR); if (de_open(&tp->de, mode, 0666) == -1) { iscsi_err(__FILE__, __LINE__, "error opening \"%s\"\n", idisk->filename); return -1; } if (!(tp->flags & TARGET_READONLY) && !allocate_space(tp, idisk->blocklen)) { iscsi_err(__FILE__, __LINE__, "error allocating space for \"%s\"\n", tp->target); return -1; } printf("%" PRIu64 " MB %sdisk storage for \"%s\"\n", (de_getsize(&tp->de) / MB(1)), (tp->flags & TARGET_READONLY) ? "readonly " : "", tp->target); return disks.c++; } static void cdb2lba(uint32_t *lba, uint16_t *len, uint8_t *cdb) { /* Some platforms (like strongarm) aligns on */ /* word boundaries. So HTONL and NTOHL won't */ /* work here. */ int little_endian = 1; if (*(char *) (void *) &little_endian) { /* little endian */ ((uint8_t *) (void *) lba)[0] = cdb[5]; ((uint8_t *) (void *) lba)[1] = cdb[4]; ((uint8_t *) (void *) lba)[2] = cdb[3]; ((uint8_t *) (void *) lba)[3] = cdb[2]; ((uint8_t *) (void *) len)[0] = cdb[8]; ((uint8_t *) (void *) len)[1] = cdb[7]; } else { ((uint8_t *) (void *) lba)[0] = cdb[2]; ((uint8_t *) (void *) lba)[1] = cdb[3]; ((uint8_t *) (void *) lba)[2] = cdb[4]; ((uint8_t *) (void *) lba)[3] = cdb[5]; ((uint8_t *) (void *) len)[0] = cdb[7]; ((uint8_t *) (void *) len)[1] = cdb[8]; } } /* handle MODE_SENSE_6 and MODE_SENSE_10 commands */ static int mode_sense(const int bytes, target_cmd_t *cmd) { iscsi_scsi_cmd_args_t *args = cmd->scsi_cmd; uint16_t len; uint8_t *cp; uint8_t *cdb = args->cdb; size_t mode_data_len; switch(bytes) { case 6: cp = args->send_data; len = ISCSI_MODE_SENSE_LEN; mode_data_len = len + 3; iscsi_trace(TRACE_SCSI_CMD, "MODE_SENSE_6\n"); (void) memset(cp, 0x0, mode_data_len); cp[0] = mode_data_len; cp[1] = 0; cp[2] = 0; cp[3] = 8; /* block descriptor length */ cp[10] = 2; /* density code and block length */ args->input = 1; args->length = (unsigned)len; args->status = SCSI_SUCCESS; return 1; case 10: cp = args->send_data; len = ISCSI_MODE_SENSE_LEN; mode_data_len = len + 3; iscsi_trace(TRACE_SCSI_CMD, "MODE_SENSE_10\n"); (void) memset(cp, 0x0, mode_data_len); if (cdb[4] == 0) { /* zero length cdb means just return success */ args->input = 1; args->length = (unsigned)(mode_data_len); args->status = SCSI_SUCCESS; return 1; } if ((cdb[2] & PAGE_CONTROL_MASK) == PAGE_CONTROL_CHANGEABLE_VALUES) { /* just send back a CHECK CONDITION */ args->input = 1; args->length = (unsigned)(len); args->status = SCSI_CHECK_CONDITION; cp[2] = SCSI_SKEY_ILLEGAL_REQUEST; cp[12] = ASC_LUN_UNSUPPORTED; cp[13] = ASCQ_LUN_UNSUPPORTED; return 1; } iscsi_trace(TRACE_SCSI_CMD, "PC %02x\n", cdb[2]); cp[0] = mode_data_len; cp[1] = 0; cp[2] = 0; cp[3] = 8; /* block descriptor length */ cp[10] = 2; /* density code and block length */ args->input = 1; args->length = (unsigned)(len); args->status = SCSI_SUCCESS; return 1; } return 0; } /* fill in the device serial number vital product data */ static uint8_t serial_vpd(uint8_t *data) { uint8_t len; data[0] = DISK_PERIPHERAL_DEVICE; data[1] = INQUIRY_DEVICE_IDENTIFICATION_VPD; len = 16; /* add target device's Unit Serial Number */ /* section 7.6.10 of SPC-3 says that if there is no serial number, * use spaces */ strpadcpy(&data[4], (size_t)len, " ", strlen(" "), ' '); return len; } /* fill in the device identification vital product data */ static void device_vpd(iscsi_target_t *tgt, uint8_t *data, uint8_t *rspc, uint8_t *cdbsize, uint8_t lun, char *uuid) { uint16_t len; uint8_t *cp; data[0] = DISK_PERIPHERAL_DEVICE; data[1] = INQUIRY_DEVICE_IDENTIFICATION_VPD; *rspc = 0; cp = &data[4]; /* add target device's IQN */ cp[0] = (INQUIRY_DEVICE_ISCSI_PROTOCOL << 4) | INQUIRY_DEVICE_CODESET_UTF8; cp[1] = (INQUIRY_DEVICE_PIV << 7) | (INQUIRY_DEVICE_ASSOCIATION_TARGET_DEVICE << 4) | INQUIRY_DEVICE_IDENTIFIER_SCSI_NAME; len = (uint8_t) snprintf((char *)&cp[4], (unsigned)(*cdbsize - (int)(cp - &data[4])), "%s", iscsi_target_getvar(tgt, "iqn")); cp[3] = len; *rspc += len + 4; cp += len + 4; /* add target port's IQN + LUN */ cp[0] = (INQUIRY_DEVICE_ISCSI_PROTOCOL << 4) | INQUIRY_DEVICE_CODESET_UTF8; cp[1] = (INQUIRY_DEVICE_PIV << 7) | (INQUIRY_DEVICE_ASSOCIATION_TARGET_PORT << 4) | INQUIRY_DEVICE_IDENTIFIER_SCSI_NAME; len = (uint8_t) snprintf((char *)&cp[4], (unsigned)(*cdbsize - (int)(cp - &data[4])), "%s,t,%#x", iscsi_target_getvar(tgt, "iqn"), lun); cp[3] = len; *rspc += len + 4; cp += len + 4; /* add target port's IQN + LUN extension */ cp[0] = (INQUIRY_DEVICE_ISCSI_PROTOCOL << 4) | INQUIRY_DEVICE_CODESET_UTF8; cp[1] = (INQUIRY_DEVICE_PIV << 7) | (INQUIRY_DEVICE_ASSOCIATION_LOGICAL_UNIT << 4) | INQUIRY_DEVICE_IDENTIFIER_SCSI_NAME; len = (uint8_t) snprintf((char *)&cp[4], (unsigned) (*cdbsize - (int)(cp - &data[4])), "%s,L,0x%8.8s%4.4s%4.4s", iscsi_target_getvar(tgt, "iqn"), uuid, &uuid[9], &uuid[14]); cp[3] = len; *rspc += len + 4; cp += len + 4; /* add target's uuid as a T10 identifier */ cp[0] = (INQUIRY_DEVICE_ISCSI_PROTOCOL << 4) | INQUIRY_DEVICE_CODESET_UTF8; cp[1] = (INQUIRY_DEVICE_PIV << 7) | (INQUIRY_DEVICE_ASSOCIATION_TARGET_DEVICE << 4) | INQUIRY_IDENTIFIER_TYPE_T10; strpadcpy(&cp[4], 8, ISCSI_VENDOR, strlen(ISCSI_VENDOR), ' '); len = 8; len += (uint8_t) snprintf((char *)&cp[8 + 4], (unsigned)(*cdbsize - (int)(cp - &data[4])), "0x%8.8s%4.4s%4.4s", uuid, &uuid[9], &uuid[14]); cp[3] = len; *rspc += len + 4; } static void version_inquiry(uint8_t *data, uint8_t *cdbsize) { char versionstr[8]; data[0] = DISK_PERIPHERAL_DEVICE; data[2] = SCSI_VERSION_SPC; data[4] = *cdbsize - 4; /* Additional length */ data[7] |= (WIDE_BUS_32 | WIDE_BUS_16); strpadcpy(&data[8], 8, ISCSI_VENDOR, strlen(ISCSI_VENDOR), ' '); strpadcpy(&data[16], 16, ISCSI_PRODUCT, strlen(ISCSI_PRODUCT), ' '); (void) snprintf(versionstr, sizeof(versionstr), "%d", ISCSI_VERSION); strpadcpy(&data[32], 4, versionstr, strlen(versionstr), ' '); } int device_command(target_session_t *sess, target_cmd_t *cmd) { iscsi_scsi_cmd_args_t *args = cmd->scsi_cmd; uint32_t status; uint32_t lba; uint16_t len; uint8_t *cdbsize; uint8_t *rspc; uint8_t *data; uint8_t *cdb; uint8_t lun; cdb = args->cdb; lun = (uint8_t) (args->lun >> 48); cdbsize = &cdb[4]; /* * added section to return no device equivalent for lun request * beyond available lun */ if (lun >= disks.v[sess->d].luns) { data = args->send_data; (void) memset(data, 0x0, (size_t) *cdbsize); /* * data[0] = 0x7F; means no device */ data[0] = 0x1F; /* device type */ data[0] |= 0x60;/* peripheral qualifier */ args->input = 1; args->length = cdb[4] + 1; args->status = SCSI_SUCCESS; return 0; } lun = (uint8_t) sess->d; iscsi_trace(TRACE_SCSI_CMD, "SCSI op %#x (lun %d): \n", cdb[0], lun); switch (cdb[0]) { case TEST_UNIT_READY: iscsi_trace(TRACE_SCSI_CMD, "TEST_UNIT_READY\n"); args->status = SCSI_SUCCESS; args->length = 0; break; case INQUIRY: iscsi_trace(TRACE_SCSI_CMD, "INQUIRY%s\n", (cdb[1] & INQUIRY_EVPD_BIT) ? " for Vital Product Data" : ""); data = args->send_data; args->status = SCSI_SUCCESS; /* Clear allocated buffer */ (void) memset(data, 0x0, (unsigned) *cdbsize); if (cdb[1] & INQUIRY_EVPD_BIT) { rspc = &data[3]; switch(cdb[2]) { case INQUIRY_UNIT_SERIAL_NUMBER_VPD: *rspc = serial_vpd(data); args->length = 16; break; case INQUIRY_DEVICE_IDENTIFICATION_VPD: if (disks.v[sess->d].uuid_string == NULL) { nbuuid_create(&disks.v[sess->d].uuid, &status); nbuuid_to_string(&disks.v[sess->d].uuid, &disks.v[sess->d].uuid_string, &status); } device_vpd(sess->target, data, rspc, cdbsize, lun, disks.v[sess->d].uuid_string); args->length = *rspc + 6; break; case INQUIRY_SUPPORTED_VPD_PAGES: data[0] = DISK_PERIPHERAL_DEVICE; data[1] = INQUIRY_SUPPORTED_VPD_PAGES; *rspc = 3; /* # of supported pages */ data[4] = INQUIRY_SUPPORTED_VPD_PAGES; data[5] = INQUIRY_DEVICE_IDENTIFICATION_VPD; data[6] = EXTENDED_INQUIRY_DATA_VPD; args->length = *cdbsize + 1; break; case EXTENDED_INQUIRY_DATA_VPD: data[0] = DISK_PERIPHERAL_DEVICE; data[1] = EXTENDED_INQUIRY_DATA_VPD; data[3] = 0x3c; /* length is defined to be 60 */ data[4] = 0; data[5] = 0; args->length = 64; break; default: iscsi_err(__FILE__, __LINE__, "Unsupported INQUIRY VPD page %x\n", cdb[2]); args->status = SCSI_CHECK_CONDITION; break; } } else { version_inquiry(data, cdbsize); args->length = cdb[4] + 1; } if (args->status == SCSI_SUCCESS) { args->input = 1; } break; case MODE_SELECT_6: iscsi_trace(TRACE_SCSI_CMD, "MODE_SELECT_6\n"); args->status = SCSI_SUCCESS; args->length = 0; break; case STOP_START_UNIT: iscsi_trace(TRACE_SCSI_CMD, "STOP_START_UNIT\n"); args->status = SCSI_SUCCESS; args->length = 0; break; case READ_CAPACITY: iscsi_trace(TRACE_SCSI_CMD, "READ_CAPACITY\n"); data = args->send_data; *((uint32_t *)(void *)data) = (uint32_t) ISCSI_HTONL( (uint32_t) disks.v[sess->d].blockc - 1); /* Max LBA */ *((uint32_t *)(void *)(data + 4)) = (uint32_t) ISCSI_HTONL( (uint32_t) disks.v[sess->d].blocklen); /* Block len */ args->input = 8; args->length = 8; args->status = SCSI_SUCCESS; break; case WRITE_6: lba = ISCSI_NTOHL(*((uint32_t *) (void *)cdb)) & 0x001fffff; if ((len = *cdbsize) == 0) { len = 256; } iscsi_trace(TRACE_SCSI_CMD, "WRITE_6(lba %u, len %u blocks)\n", lba, len); if (disk_write(sess, args, lun, lba, (unsigned) len) != 0) { iscsi_err(__FILE__, __LINE__, "disk_write() failed\n"); args->status = SCSI_CHECK_CONDITION; } args->length = 0; break; case READ_6: lba = ISCSI_NTOHL(*((uint32_t *)(void *)cdb)) & 0x001fffff; if ((len = *cdbsize) == 0) { len = 256; } iscsi_trace(TRACE_SCSI_CMD, "READ_6(lba %u, len %u blocks)\n", lba, len); if (disk_read(sess, args, lba, len, lun) != 0) { iscsi_err(__FILE__, __LINE__, "disk_read() failed\n"); args->status = SCSI_CHECK_CONDITION; } args->input = 1; break; case MODE_SENSE_6: mode_sense(6, cmd); break; case WRITE_10: case WRITE_VERIFY: cdb2lba(&lba, &len, cdb); iscsi_trace(TRACE_SCSI_CMD, "WRITE_10 | WRITE_VERIFY(lba %u, len %u blocks)\n", lba, len); if (disk_write(sess, args, lun, lba, (unsigned) len) != 0) { iscsi_err(__FILE__, __LINE__, "disk_write() failed\n"); args->status = SCSI_CHECK_CONDITION; } args->length = 0; break; case READ_10: cdb2lba(&lba, &len, cdb); iscsi_trace(TRACE_SCSI_CMD, "READ_10(lba %u, len %u blocks)\n", lba, len); if (disk_read(sess, args, lba, len, lun) != 0) { iscsi_err(__FILE__, __LINE__, "disk_read() failed\n"); args->status = SCSI_CHECK_CONDITION; } args->input = 1; break; case VERIFY: /* For now just set the status to success. */ args->status = SCSI_SUCCESS; break; case SYNC_CACHE: cdb2lba(&lba, &len, cdb); iscsi_trace(TRACE_SCSI_CMD, "SYNC_CACHE (lba %u, len %u blocks)\n", lba, len); if (de_fsync_range(&disks.v[sess->d].lunv->v[lun].de, FDATASYNC, lba, (off_t)(len * disks.v[sess->d].blocklen)) < 0) { iscsi_err(__FILE__, __LINE__, "disk_read() failed\n"); args->status = SCSI_CHECK_CONDITION; } else { args->status = SCSI_SUCCESS; args->length = 0; } break; case LOG_SENSE: iscsi_trace(TRACE_SCSI_CMD, "LOG_SENSE\n"); args->status = SCSI_SUCCESS; args->length = 0; break; case MODE_SENSE_10: mode_sense(10, cmd); break; case MODE_SELECT_10: /* XXX still to do */ iscsi_trace(TRACE_SCSI_CMD, "MODE_SELECT_10\n"); args->status = SCSI_SUCCESS; args->length = 0; break; case PERSISTENT_RESERVE_IN: iscsi_trace(TRACE_SCSI_CMD, "PERSISTENT_RESERVE_IN\n"); args->length = persistent_reserve_in((cdb[1] & PERSISTENT_RESERVE_IN_SERVICE_ACTION_MASK), args->send_data); args->status = SCSI_SUCCESS; break; case REPORT_LUNS: iscsi_trace(TRACE_SCSI_CMD, "REPORT LUNS\n"); args->length = report_luns(args->send_data, disks.v[sess->d].luns); args->input = 8; args->status = SCSI_SUCCESS; break; case RESERVE_6: iscsi_trace(TRACE_SCSI_CMD, "RESERVE_6\n"); args->status = SCSI_SUCCESS; args->length = 0; break; case RELEASE_6: iscsi_trace(TRACE_SCSI_CMD, "RELEASE_6\n"); args->status = SCSI_SUCCESS; args->length = 0; break; case RESERVE_10: iscsi_trace(TRACE_SCSI_CMD, "RESERVE_10\n"); args->status = SCSI_SUCCESS; args->length = 0; break; case RELEASE_10: iscsi_trace(TRACE_SCSI_CMD, "RELEASE_10\n"); args->status = SCSI_SUCCESS; args->length = 0; break; default: iscsi_err(__FILE__, __LINE__, "UNKNOWN OPCODE %#x\n", cdb[0]); /* to not cause confusion with some initiators */ args->status = SCSI_CHECK_CONDITION; break; } iscsi_trace(TRACE_SCSI_DEBUG, "SCSI op %#x: done (status %#x)\n", cdb[0], args->status); return 0; } int device_shutdown(target_session_t *sess) { USE_ARG(sess); return 1; } /* * Private Interface */ static int disk_write(target_session_t *sess, iscsi_scsi_cmd_args_t *args, uint8_t lun, uint32_t lba, uint32_t len) { struct iovec sg; uint64_t byte_offset; uint64_t bytec; uint8_t *ptr; int result; byte_offset = lba * disks.v[sess->d].blocklen; bytec = len * disks.v[sess->d].blocklen; ptr = NULL; iscsi_trace(TRACE_SCSI_DATA, "writing %" PRIu64 " bytes from socket into device at byte offset %" PRIu64 "\n", bytec, byte_offset); if ((unsigned) bytec > MB(1)) { iscsi_err(__FILE__, __LINE__, "bytec > %" PRIu64 "m\n", bytec); NO_CLEANUP; return -1; } /* Assign ptr for write data */ ptr = malloc(MB(1)); /* Have target do data transfer */ sg.iov_base = ptr; sg.iov_len = (unsigned)bytec; if (target_transfer_data(sess, args, &sg, 1) != 0) { iscsi_err(__FILE__, __LINE__, "target_transfer_data() failed\n"); result = -1; goto out; } /* Finish up write */ if (de_lseek(&disks.v[sess->d].lunv->v[lun].de, (off_t)byte_offset, SEEK_SET) == -1) { iscsi_err(__FILE__, __LINE__, "lseek() to offset %" PRIu64 " failed\n", byte_offset); result = -1; goto out; } if (!target_writable(&disks.v[sess->d].lunv->v[lun])) { iscsi_err(__FILE__, __LINE__, "write() of %" PRIu64 " bytes failed at offset %" PRIu64 ", size %" PRIu64 "[READONLY TARGET]\n", bytec, byte_offset, de_getsize(&disks.v[sess->d].lunv->v[lun].de)); result = -1; goto out; } if ((uint64_t)de_write(&disks.v[sess->d].lunv->v[lun].de, ptr, (unsigned) bytec) != bytec) { iscsi_err(__FILE__, __LINE__, "write() of %" PRIu64 " bytes failed at offset %" PRIu64 ", size %" PRIu64 "\n", bytec, byte_offset, de_getsize(&disks.v[sess->d].lunv->v[lun].de)); result = -1; goto out; } iscsi_trace(TRACE_SCSI_DATA, "wrote %" PRIu64 " bytes to device OK\n", bytec); result = 0; out: free(ptr); return result; } static int disk_read(target_session_t *sess, iscsi_scsi_cmd_args_t *args, uint32_t lba, uint16_t len, uint8_t lun) { uint64_t byte_offset; uint64_t bytec; uint64_t extra; uint8_t *ptr; uint32_t n; int rc; int result; assert(args->send_buffer == NULL); byte_offset = lba * disks.v[sess->d].blocklen; bytec = len * disks.v[sess->d].blocklen; extra = 0; if (len == 0) { iscsi_err(__FILE__, __LINE__, "Zero \"len\"\n"); NO_CLEANUP; return -1; } if (lba > disks.v[sess->d].blockc - 1 || (lba + len) > disks.v[sess->d].blockc) { iscsi_err(__FILE__, __LINE__, "attempt to read beyond end of media\n" "max_lba = %" PRIu64 ", requested lba = %u, len = %u\n", disks.v[sess->d].blockc - 1, lba, len); return -1; } if ((unsigned) bytec > MB(1)) { iscsi_err(__FILE__, __LINE__, "bytec > %" PRIu64 "\n", bytec); NO_CLEANUP; return -1; } ptr = malloc(MB(1)); n = 0; do { if (de_lseek(&disks.v[sess->d].lunv->v[lun].de, (off_t)(n + byte_offset), SEEK_SET) == -1) { iscsi_err(__FILE__, __LINE__, "lseek failed\n"); result = -1; goto out; } rc = de_read(&disks.v[sess->d].lunv->v[lun].de, ptr + n, (size_t)(bytec - n)); if (rc <= 0) { iscsi_err(__FILE__, __LINE__, "read failed: rc %d errno %d\n", rc, errno); result = -1; goto out; } n += rc; if (n < bytec) { iscsi_err(__FILE__, __LINE__, "Got partial file read: %d bytes of %" PRIu64 "\n", rc, bytec - n + rc); } } while (n < bytec); ((struct iovec *)(void *)args->send_data)[0].iov_base = ptr + (unsigned) extra; ((struct iovec *)(void *)args->send_data)[0].iov_len = (unsigned) bytec; args->length = (unsigned) bytec; args->send_sg_len = 1; args->status = SCSI_SUCCESS; args->send_buffer = ptr; return 0; out: free(ptr); return result; }