/** * Read from the emulated device/file. */ static int32_t zbc_fake_pread(struct zbc_device *dev, struct zbc_zone *z, void *buf, uint32_t lba_count, uint64_t start_lba) { zbc_fake_device_t *fdev = zbc_fake_to_file_dev(dev); struct zbc_zone *zone, *next_zone; uint64_t lba; ssize_t ret = -EIO; off_t offset; size_t count; if ( ! fdev->zbd_meta ) { return -ENXIO; } zbc_fake_lock(fdev); /* Find the target zone */ zone = zbc_fake_find_zone(fdev, zbc_zone_start_lba(z)); if ( ! zone ) { goto out; } if ( start_lba > zbc_zone_length(zone) ) { goto out; } lba = zbc_zone_next_lba(zone); next_zone = zbc_fake_find_zone(fdev, lba); start_lba += zbc_zone_start_lba(zone); /* Note: unrestricted read will be added to the standard */ /* and supported by a drive if the URSWRZ bit is set in */ /* VPD page. So this test will need to change. */ if ( zone->zbz_type == ZBC_ZT_SEQUENTIAL_REQ ) { /* Cannot read unwritten data */ if ( (start_lba + lba_count) > lba ) { if ( next_zone ) { dev->zbd_errno.sk = ZBC_E_ILLEGAL_REQUEST; dev->zbd_errno.asc_ascq = ZBC_E_READ_BOUNDARY_VIOLATION; } else { dev->zbd_errno.sk = ZBC_E_ILLEGAL_REQUEST; dev->zbd_errno.asc_ascq = ZBC_E_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; } goto out; } if ( (start_lba + lba_count) > zbc_zone_wp_lba(zone) ) { dev->zbd_errno.sk = ZBC_E_ILLEGAL_REQUEST; dev->zbd_errno.asc_ascq = ZBC_E_ATTEMPT_TO_READ_INVALID_DATA; goto out; } } else { /* Reads spanning other types of zones are OK. */ if ( (start_lba + lba_count) > lba ) { uint64_t count = start_lba + lba_count - lba; while( count && next_zone ) { if ( zbc_zone_sequential_req(next_zone) ) { dev->zbd_errno.sk = ZBC_E_ILLEGAL_REQUEST; dev->zbd_errno.asc_ascq = ZBC_E_ATTEMPT_TO_READ_INVALID_DATA; goto out; } if ( count > zbc_zone_length(next_zone) ) { count -= zbc_zone_length(next_zone); } lba += zbc_zone_length(next_zone); } } } /* XXX: check for overflows */ count = lba_count * dev->zbd_info.zbd_logical_block_size; offset = start_lba * dev->zbd_info.zbd_logical_block_size; ret = pread(dev->zbd_fd, buf, count, offset); if ( ret < 0 ) { ret = -errno; } else { ret /= dev->zbd_info.zbd_logical_block_size; } out: zbc_fake_unlock(fdev); return ret; }
/** * Write to the emulated device/file. */ static int32_t zbc_fake_pwrite(struct zbc_device *dev, struct zbc_zone *z, const void *buf, uint32_t lba_count, uint64_t start_lba) { zbc_fake_device_t *fdev = zbc_fake_to_file_dev(dev); struct zbc_zone *zone, *next_zone; uint64_t lba; off_t offset; size_t count; ssize_t ret = -EIO; if ( ! fdev->zbd_meta ) { return -ENXIO; } zbc_fake_lock(fdev); /* Find the target zone */ zone = zbc_fake_find_zone(fdev, zbc_zone_start_lba(z)); if ( ! zone ) { goto out; } /* Writes cannot span zones */ if ( start_lba > zbc_zone_length(zone) ) { goto out; } lba = zbc_zone_next_lba(zone); next_zone = zbc_fake_find_zone(fdev, lba); start_lba += zone->zbz_start; if ( (start_lba + lba_count) > lba ) { if ( next_zone ) { dev->zbd_errno.sk = ZBC_E_ILLEGAL_REQUEST; dev->zbd_errno.asc_ascq = ZBC_E_WRITE_BOUNDARY_VIOLATION; } else { dev->zbd_errno.sk = ZBC_E_ILLEGAL_REQUEST; dev->zbd_errno.asc_ascq = ZBC_E_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; } goto out; } if ( zbc_zone_sequential_req(zone) ) { /* Can only write at the write pointer */ if ( start_lba != zbc_zone_wp_lba(zone) ) { dev->zbd_errno.sk = ZBC_E_ILLEGAL_REQUEST; dev->zbd_errno.asc_ascq = ZBC_E_UNALIGNED_WRITE_COMMAND; goto out; } /* Can only write an open zone */ if ( ! zbc_zone_is_open(zone) ) { if ( fdev->zbd_meta->zbd_nr_exp_open_zones >= fdev->dev.zbd_info.zbd_max_nr_open_seq_req ) { /* Too many explicit open on-going */ dev->zbd_errno.sk = ZBC_E_ABORTED_COMMAND; dev->zbd_errno.asc_ascq = ZBC_E_INSUFFICIENT_ZONE_RESOURCES; ret = -EIO; goto out; } /* Implicitely open the zone */ if ( fdev->zbd_meta->zbd_nr_imp_open_zones >= fdev->dev.zbd_info.zbd_max_nr_open_seq_req ) { unsigned int i; for(i = 0; i < fdev->zbd_nr_zones; i++) { if ( zbc_zone_imp_open(&fdev->zbd_zones[i]) ) { zbc_zone_do_close(fdev, &fdev->zbd_zones[i]); break; } } } zone->zbz_condition = ZBC_ZC_IMP_OPEN; fdev->zbd_meta->zbd_nr_imp_open_zones++; } } /* XXX: check for overflows */ count = (size_t)lba_count * dev->zbd_info.zbd_logical_block_size; offset = start_lba * dev->zbd_info.zbd_logical_block_size; ret = pwrite(dev->zbd_fd, buf, count, offset); if ( ret < 0 ) { ret = -errno; } else { ret /= dev->zbd_info.zbd_logical_block_size; if ( zbc_zone_sequential_req(zone) ) { /* * XXX: What protects us from a return value that's not LBA aligned? * (Except for hoping the OS implementation isn't insane..) */ if ( (zbc_zone_wp_lba(zone) + lba_count) >= zbc_zone_next_lba(zone) ) { if ( zbc_zone_imp_open(zone) ) { fdev->zbd_meta->zbd_nr_imp_open_zones--; } else { fdev->zbd_meta->zbd_nr_exp_open_zones--; } } /* Advance write pointer */ zbc_zone_wp_lba_inc(zone, lba_count); } } out: zbc_fake_unlock(fdev); return ret; }
/** * Get a device information (capacity & sector sizes). */ static int zbc_scsi_get_capacity(zbc_device_t *dev) { zbc_sg_cmd_t cmd; zbc_zone_t *zones = NULL; int logical_per_physical; unsigned int nr_zones = 0; uint64_t slba = 0; int ret; /* READ CAPACITY 16 */ ret = zbc_sg_cmd_init(&cmd, ZBC_SG_READ_CAPACITY, NULL, ZBC_SG_READ_CAPACITY_REPLY_LEN); if ( ret != 0 ) { zbc_error("zbc_sg_cmd_init failed\n"); return( ret ); } /* Fill command CDB */ cmd.cdb[0] = ZBC_SG_READ_CAPACITY_CDB_OPCODE; cmd.cdb[1] = ZBC_SG_READ_CAPACITY_CDB_SA; zbc_sg_cmd_set_int32(&cmd.cdb[10], ZBC_SG_READ_CAPACITY_REPLY_LEN); /* Send the SG_IO command */ ret = zbc_sg_cmd_exec(dev, &cmd); if ( ret != 0 ) { goto out; } /* Logical block size */ dev->zbd_info.zbd_logical_block_size = zbc_sg_cmd_get_int32(&cmd.out_buf[8]); if ( dev->zbd_info.zbd_logical_block_size <= 0 ) { zbc_error("%s: invalid logical sector size\n", dev->zbd_filename); ret = -EINVAL; goto out; } logical_per_physical = 1 << cmd.out_buf[13] & 0x0f; /* Check RC_BASIS field */ switch( (cmd.out_buf[12] & 0x30) >> 4 ) { case 0x00: /* The logical block address indicates the last LBA of the */ /* conventional zones at the beginning of the disk. To get */ /* the entire disk capacity, we need to get last LBA of */ /* the last zone of the disk. */ ret = zbc_scsi_report_zones(dev, 0, 0, NULL, &nr_zones); if ( ret != 0 ) { zbc_error("zbc_report_zones failed\n"); goto out; } if ( ! nr_zones ) { ret = -EIO; goto out; } /* Allocate zone array */ zones = (zbc_zone_t *) malloc(sizeof(zbc_zone_t) * nr_zones); if ( ! zones ) { zbc_error("No memory\n"); ret = -ENOMEM; goto out; } memset(zones, 0, sizeof(zbc_zone_t) * nr_zones); /* Get all zone information */ unsigned int n, z = 0, nz = 0; while ( nz < nr_zones ) { n= nr_zones - nz; ret = zbc_scsi_report_zones(dev, slba, 0, &zones[z], &n); if ( ret != 0 ) { zbc_error("zbc_report_zones failed\n"); goto out; } if ( n == 0 ) { ret = -EIO; break; } nz += n; z += n; slba = zones[z - 1].zbz_start + zones[z - 1].zbz_length; } /* Get the drive capacity from the last zone last LBA */ dev->zbd_info.zbd_logical_blocks = zbc_zone_next_lba(&zones[nr_zones - 1]); break; case 0x01: /* The disk last LBA was reproted */ dev->zbd_info.zbd_logical_blocks = zbc_sg_cmd_get_int64(&cmd.out_buf[0]) + 1; break; default: zbc_error("%s: invalid RC_BASIS field encountered in READ CAPACITY result\n", dev->zbd_filename); ret = -EIO; goto out; } if ( ! dev->zbd_info.zbd_logical_blocks ) { zbc_error("%s: invalid capacity (logical blocks)\n", dev->zbd_filename); ret = -EINVAL; goto out; } dev->zbd_info.zbd_physical_block_size = dev->zbd_info.zbd_logical_block_size * logical_per_physical; dev->zbd_info.zbd_physical_blocks = dev->zbd_info.zbd_logical_blocks / logical_per_physical; out: zbc_sg_cmd_destroy(&cmd); if ( zones ) { free(zones); } return( ret ); }