target: Fix up handling of short INQUIRY buffers
If the initiator sends us an INQUIRY command with an allocation length that's shorter than what we want to return, we're simply supposed to truncate our response and return what the initiator gave us space for, without signaling any error. Current target code has various tests that don't fill out the full response if the buffer is too short and sometimes return errors incorrectly. Fix this up by allocating a bounce buffer for INQUIRY responses if we need to, ie if we have cmd->data_length too small as well as SCF_PASSTHROUGH_SG_TO_MEM_NOALLOC set in cmd->se_cmd_flags -- for most fabrics, we always allocate at least a full page, but for tcm_loop we may have a small buffer coming directly from the SCSI stack. This lets us delete a lot of cmd->data_length checking, and also makes our INQUIRY handling correct per SPC in a lot more cases. Signed-off-by: Roland Dreier <roland@purestorage.com> Cc: stable@vger.kernel.org Signed-off-by: Nicholas Bellinger <nab@linux-iscsi.org>
This commit is contained in:
Родитель
3d28934aaa
Коммит
d95b82461c
|
@ -66,24 +66,11 @@ target_fill_alua_data(struct se_port *port, unsigned char *buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
target_emulate_inquiry_std(struct se_cmd *cmd)
|
target_emulate_inquiry_std(struct se_cmd *cmd, char *buf)
|
||||||
{
|
{
|
||||||
struct se_lun *lun = cmd->se_lun;
|
struct se_lun *lun = cmd->se_lun;
|
||||||
struct se_device *dev = cmd->se_dev;
|
struct se_device *dev = cmd->se_dev;
|
||||||
struct se_portal_group *tpg = lun->lun_sep->sep_tpg;
|
struct se_portal_group *tpg = lun->lun_sep->sep_tpg;
|
||||||
unsigned char *buf;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Make sure we at least have 6 bytes of INQUIRY response
|
|
||||||
* payload going back for EVPD=0
|
|
||||||
*/
|
|
||||||
if (cmd->data_length < 6) {
|
|
||||||
pr_err("SCSI Inquiry payload length: %u"
|
|
||||||
" too small for EVPD=0\n", cmd->data_length);
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf = transport_kmap_data_sg(cmd);
|
|
||||||
|
|
||||||
if (dev == tpg->tpg_virt_lun0.lun_se_dev) {
|
if (dev == tpg->tpg_virt_lun0.lun_se_dev) {
|
||||||
buf[0] = 0x3f; /* Not connected */
|
buf[0] = 0x3f; /* Not connected */
|
||||||
|
@ -112,29 +99,13 @@ target_emulate_inquiry_std(struct se_cmd *cmd)
|
||||||
if (dev->se_sub_dev->t10_alua.alua_type == SPC3_ALUA_EMULATED)
|
if (dev->se_sub_dev->t10_alua.alua_type == SPC3_ALUA_EMULATED)
|
||||||
target_fill_alua_data(lun->lun_sep, buf);
|
target_fill_alua_data(lun->lun_sep, buf);
|
||||||
|
|
||||||
if (cmd->data_length < 8) {
|
|
||||||
buf[4] = 1; /* Set additional length to 1 */
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf[7] = 0x32; /* Sync=1 and CmdQue=1 */
|
buf[7] = 0x32; /* Sync=1 and CmdQue=1 */
|
||||||
|
|
||||||
/*
|
|
||||||
* Do not include vendor, product, reversion info in INQUIRY
|
|
||||||
* response payload for cdbs with a small allocation length.
|
|
||||||
*/
|
|
||||||
if (cmd->data_length < 36) {
|
|
||||||
buf[4] = 3; /* Set additional length to 3 */
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
snprintf(&buf[8], 8, "LIO-ORG");
|
snprintf(&buf[8], 8, "LIO-ORG");
|
||||||
snprintf(&buf[16], 16, "%s", dev->se_sub_dev->t10_wwn.model);
|
snprintf(&buf[16], 16, "%s", dev->se_sub_dev->t10_wwn.model);
|
||||||
snprintf(&buf[32], 4, "%s", dev->se_sub_dev->t10_wwn.revision);
|
snprintf(&buf[32], 4, "%s", dev->se_sub_dev->t10_wwn.revision);
|
||||||
buf[4] = 31; /* Set additional length to 31 */
|
buf[4] = 31; /* Set additional length to 31 */
|
||||||
|
|
||||||
out:
|
|
||||||
transport_kunmap_data_sg(cmd);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,12 +123,6 @@ target_emulate_evpd_80(struct se_cmd *cmd, unsigned char *buf)
|
||||||
unit_serial_len = strlen(dev->se_sub_dev->t10_wwn.unit_serial);
|
unit_serial_len = strlen(dev->se_sub_dev->t10_wwn.unit_serial);
|
||||||
unit_serial_len++; /* For NULL Terminator */
|
unit_serial_len++; /* For NULL Terminator */
|
||||||
|
|
||||||
if (((len + 4) + unit_serial_len) > cmd->data_length) {
|
|
||||||
len += unit_serial_len;
|
|
||||||
buf[2] = ((len >> 8) & 0xff);
|
|
||||||
buf[3] = (len & 0xff);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
len += sprintf(&buf[4], "%s",
|
len += sprintf(&buf[4], "%s",
|
||||||
dev->se_sub_dev->t10_wwn.unit_serial);
|
dev->se_sub_dev->t10_wwn.unit_serial);
|
||||||
len++; /* Extra Byte for NULL Terminator */
|
len++; /* Extra Byte for NULL Terminator */
|
||||||
|
@ -229,9 +194,6 @@ target_emulate_evpd_83(struct se_cmd *cmd, unsigned char *buf)
|
||||||
if (!(dev->se_sub_dev->su_dev_flags & SDF_EMULATED_VPD_UNIT_SERIAL))
|
if (!(dev->se_sub_dev->su_dev_flags & SDF_EMULATED_VPD_UNIT_SERIAL))
|
||||||
goto check_t10_vend_desc;
|
goto check_t10_vend_desc;
|
||||||
|
|
||||||
if (off + 20 > cmd->data_length)
|
|
||||||
goto check_t10_vend_desc;
|
|
||||||
|
|
||||||
/* CODE SET == Binary */
|
/* CODE SET == Binary */
|
||||||
buf[off++] = 0x1;
|
buf[off++] = 0x1;
|
||||||
|
|
||||||
|
@ -283,12 +245,6 @@ check_t10_vend_desc:
|
||||||
strlen(&dev->se_sub_dev->t10_wwn.unit_serial[0]);
|
strlen(&dev->se_sub_dev->t10_wwn.unit_serial[0]);
|
||||||
unit_serial_len++; /* For NULL Terminator */
|
unit_serial_len++; /* For NULL Terminator */
|
||||||
|
|
||||||
if ((len + (id_len + 4) +
|
|
||||||
(prod_len + unit_serial_len)) >
|
|
||||||
cmd->data_length) {
|
|
||||||
len += (prod_len + unit_serial_len);
|
|
||||||
goto check_port;
|
|
||||||
}
|
|
||||||
id_len += sprintf(&buf[off+12], "%s:%s", prod,
|
id_len += sprintf(&buf[off+12], "%s:%s", prod,
|
||||||
&dev->se_sub_dev->t10_wwn.unit_serial[0]);
|
&dev->se_sub_dev->t10_wwn.unit_serial[0]);
|
||||||
}
|
}
|
||||||
|
@ -306,7 +262,6 @@ check_t10_vend_desc:
|
||||||
/*
|
/*
|
||||||
* struct se_port is only set for INQUIRY VPD=1 through $FABRIC_MOD
|
* struct se_port is only set for INQUIRY VPD=1 through $FABRIC_MOD
|
||||||
*/
|
*/
|
||||||
check_port:
|
|
||||||
port = lun->lun_sep;
|
port = lun->lun_sep;
|
||||||
if (port) {
|
if (port) {
|
||||||
struct t10_alua_lu_gp *lu_gp;
|
struct t10_alua_lu_gp *lu_gp;
|
||||||
|
@ -323,10 +278,6 @@ check_port:
|
||||||
* Get the PROTOCOL IDENTIFIER as defined by spc4r17
|
* Get the PROTOCOL IDENTIFIER as defined by spc4r17
|
||||||
* section 7.5.1 Table 362
|
* section 7.5.1 Table 362
|
||||||
*/
|
*/
|
||||||
if (((len + 4) + 8) > cmd->data_length) {
|
|
||||||
len += 8;
|
|
||||||
goto check_tpgi;
|
|
||||||
}
|
|
||||||
buf[off] =
|
buf[off] =
|
||||||
(tpg->se_tpg_tfo->get_fabric_proto_ident(tpg) << 4);
|
(tpg->se_tpg_tfo->get_fabric_proto_ident(tpg) << 4);
|
||||||
buf[off++] |= 0x1; /* CODE SET == Binary */
|
buf[off++] |= 0x1; /* CODE SET == Binary */
|
||||||
|
@ -350,15 +301,10 @@ check_port:
|
||||||
* Get the PROTOCOL IDENTIFIER as defined by spc4r17
|
* Get the PROTOCOL IDENTIFIER as defined by spc4r17
|
||||||
* section 7.5.1 Table 362
|
* section 7.5.1 Table 362
|
||||||
*/
|
*/
|
||||||
check_tpgi:
|
|
||||||
if (dev->se_sub_dev->t10_alua.alua_type !=
|
if (dev->se_sub_dev->t10_alua.alua_type !=
|
||||||
SPC3_ALUA_EMULATED)
|
SPC3_ALUA_EMULATED)
|
||||||
goto check_scsi_name;
|
goto check_scsi_name;
|
||||||
|
|
||||||
if (((len + 4) + 8) > cmd->data_length) {
|
|
||||||
len += 8;
|
|
||||||
goto check_lu_gp;
|
|
||||||
}
|
|
||||||
tg_pt_gp_mem = port->sep_alua_tg_pt_gp_mem;
|
tg_pt_gp_mem = port->sep_alua_tg_pt_gp_mem;
|
||||||
if (!tg_pt_gp_mem)
|
if (!tg_pt_gp_mem)
|
||||||
goto check_lu_gp;
|
goto check_lu_gp;
|
||||||
|
@ -391,10 +337,6 @@ check_tpgi:
|
||||||
* section 7.7.3.8
|
* section 7.7.3.8
|
||||||
*/
|
*/
|
||||||
check_lu_gp:
|
check_lu_gp:
|
||||||
if (((len + 4) + 8) > cmd->data_length) {
|
|
||||||
len += 8;
|
|
||||||
goto check_scsi_name;
|
|
||||||
}
|
|
||||||
lu_gp_mem = dev->dev_alua_lu_gp_mem;
|
lu_gp_mem = dev->dev_alua_lu_gp_mem;
|
||||||
if (!lu_gp_mem)
|
if (!lu_gp_mem)
|
||||||
goto check_scsi_name;
|
goto check_scsi_name;
|
||||||
|
@ -435,10 +377,6 @@ check_scsi_name:
|
||||||
/* Header size + Designation descriptor */
|
/* Header size + Designation descriptor */
|
||||||
scsi_name_len += 4;
|
scsi_name_len += 4;
|
||||||
|
|
||||||
if (((len + 4) + scsi_name_len) > cmd->data_length) {
|
|
||||||
len += scsi_name_len;
|
|
||||||
goto set_len;
|
|
||||||
}
|
|
||||||
buf[off] =
|
buf[off] =
|
||||||
(tpg->se_tpg_tfo->get_fabric_proto_ident(tpg) << 4);
|
(tpg->se_tpg_tfo->get_fabric_proto_ident(tpg) << 4);
|
||||||
buf[off++] |= 0x3; /* CODE SET == UTF-8 */
|
buf[off++] |= 0x3; /* CODE SET == UTF-8 */
|
||||||
|
@ -474,7 +412,6 @@ check_scsi_name:
|
||||||
/* Header size + Designation descriptor */
|
/* Header size + Designation descriptor */
|
||||||
len += (scsi_name_len + 4);
|
len += (scsi_name_len + 4);
|
||||||
}
|
}
|
||||||
set_len:
|
|
||||||
buf[2] = ((len >> 8) & 0xff);
|
buf[2] = ((len >> 8) & 0xff);
|
||||||
buf[3] = (len & 0xff); /* Page Length for VPD 0x83 */
|
buf[3] = (len & 0xff); /* Page Length for VPD 0x83 */
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -484,9 +421,6 @@ set_len:
|
||||||
static int
|
static int
|
||||||
target_emulate_evpd_86(struct se_cmd *cmd, unsigned char *buf)
|
target_emulate_evpd_86(struct se_cmd *cmd, unsigned char *buf)
|
||||||
{
|
{
|
||||||
if (cmd->data_length < 60)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
buf[3] = 0x3c;
|
buf[3] = 0x3c;
|
||||||
/* Set HEADSUP, ORDSUP, SIMPSUP */
|
/* Set HEADSUP, ORDSUP, SIMPSUP */
|
||||||
buf[5] = 0x07;
|
buf[5] = 0x07;
|
||||||
|
@ -512,20 +446,6 @@ target_emulate_evpd_b0(struct se_cmd *cmd, unsigned char *buf)
|
||||||
if (dev->se_sub_dev->se_dev_attrib.emulate_tpu || dev->se_sub_dev->se_dev_attrib.emulate_tpws)
|
if (dev->se_sub_dev->se_dev_attrib.emulate_tpu || dev->se_sub_dev->se_dev_attrib.emulate_tpws)
|
||||||
have_tp = 1;
|
have_tp = 1;
|
||||||
|
|
||||||
if (cmd->data_length < (0x10 + 4)) {
|
|
||||||
pr_debug("Received data_length: %u"
|
|
||||||
" too small for EVPD 0xb0\n",
|
|
||||||
cmd->data_length);
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (have_tp && cmd->data_length < (0x3c + 4)) {
|
|
||||||
pr_debug("Received data_length: %u"
|
|
||||||
" too small for TPE=1 EVPD 0xb0\n",
|
|
||||||
cmd->data_length);
|
|
||||||
have_tp = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf[0] = dev->transport->get_device_type(dev);
|
buf[0] = dev->transport->get_device_type(dev);
|
||||||
buf[3] = have_tp ? 0x3c : 0x10;
|
buf[3] = have_tp ? 0x3c : 0x10;
|
||||||
|
|
||||||
|
@ -548,10 +468,9 @@ target_emulate_evpd_b0(struct se_cmd *cmd, unsigned char *buf)
|
||||||
put_unaligned_be32(dev->se_sub_dev->se_dev_attrib.optimal_sectors, &buf[12]);
|
put_unaligned_be32(dev->se_sub_dev->se_dev_attrib.optimal_sectors, &buf[12]);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Exit now if we don't support TP or the initiator sent a too
|
* Exit now if we don't support TP.
|
||||||
* short buffer.
|
|
||||||
*/
|
*/
|
||||||
if (!have_tp || cmd->data_length < (0x3c + 4))
|
if (!have_tp)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -589,10 +508,7 @@ target_emulate_evpd_b1(struct se_cmd *cmd, unsigned char *buf)
|
||||||
|
|
||||||
buf[0] = dev->transport->get_device_type(dev);
|
buf[0] = dev->transport->get_device_type(dev);
|
||||||
buf[3] = 0x3c;
|
buf[3] = 0x3c;
|
||||||
|
buf[5] = dev->se_sub_dev->se_dev_attrib.is_nonrot ? 1 : 0;
|
||||||
if (cmd->data_length >= 5 &&
|
|
||||||
dev->se_sub_dev->se_dev_attrib.is_nonrot)
|
|
||||||
buf[5] = 1;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -671,8 +587,6 @@ target_emulate_evpd_00(struct se_cmd *cmd, unsigned char *buf)
|
||||||
{
|
{
|
||||||
int p;
|
int p;
|
||||||
|
|
||||||
if (cmd->data_length < 8)
|
|
||||||
return 0;
|
|
||||||
/*
|
/*
|
||||||
* Only report the INQUIRY EVPD=1 pages after a valid NAA
|
* Only report the INQUIRY EVPD=1 pages after a valid NAA
|
||||||
* Registered Extended LUN WWN has been set via ConfigFS
|
* Registered Extended LUN WWN has been set via ConfigFS
|
||||||
|
@ -681,8 +595,7 @@ target_emulate_evpd_00(struct se_cmd *cmd, unsigned char *buf)
|
||||||
if (cmd->se_dev->se_sub_dev->su_dev_flags &
|
if (cmd->se_dev->se_sub_dev->su_dev_flags &
|
||||||
SDF_EMULATED_VPD_UNIT_SERIAL) {
|
SDF_EMULATED_VPD_UNIT_SERIAL) {
|
||||||
buf[3] = ARRAY_SIZE(evpd_handlers);
|
buf[3] = ARRAY_SIZE(evpd_handlers);
|
||||||
for (p = 0; p < min_t(int, ARRAY_SIZE(evpd_handlers),
|
for (p = 0; p < ARRAY_SIZE(evpd_handlers); ++p)
|
||||||
cmd->data_length - 4); ++p)
|
|
||||||
buf[p + 4] = evpd_handlers[p].page;
|
buf[p + 4] = evpd_handlers[p].page;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -693,45 +606,50 @@ int target_emulate_inquiry(struct se_task *task)
|
||||||
{
|
{
|
||||||
struct se_cmd *cmd = task->task_se_cmd;
|
struct se_cmd *cmd = task->task_se_cmd;
|
||||||
struct se_device *dev = cmd->se_dev;
|
struct se_device *dev = cmd->se_dev;
|
||||||
unsigned char *buf;
|
unsigned char *buf, *map_buf;
|
||||||
unsigned char *cdb = cmd->t_task_cdb;
|
unsigned char *cdb = cmd->t_task_cdb;
|
||||||
int p, ret;
|
int p, ret;
|
||||||
|
|
||||||
|
map_buf = transport_kmap_data_sg(cmd);
|
||||||
|
/*
|
||||||
|
* If SCF_PASSTHROUGH_SG_TO_MEM_NOALLOC is not set, then we
|
||||||
|
* know we actually allocated a full page. Otherwise, if the
|
||||||
|
* data buffer is too small, allocate a temporary buffer so we
|
||||||
|
* don't have to worry about overruns in all our INQUIRY
|
||||||
|
* emulation handling.
|
||||||
|
*/
|
||||||
|
if (cmd->data_length < SE_INQUIRY_BUF &&
|
||||||
|
(cmd->se_cmd_flags & SCF_PASSTHROUGH_SG_TO_MEM_NOALLOC)) {
|
||||||
|
buf = kzalloc(SE_INQUIRY_BUF, GFP_KERNEL);
|
||||||
|
if (!buf) {
|
||||||
|
transport_kunmap_data_sg(cmd);
|
||||||
|
cmd->scsi_sense_reason = TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE;
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buf = map_buf;
|
||||||
|
}
|
||||||
|
|
||||||
if (!(cdb[1] & 0x1)) {
|
if (!(cdb[1] & 0x1)) {
|
||||||
if (cdb[2]) {
|
if (cdb[2]) {
|
||||||
pr_err("INQUIRY with EVPD==0 but PAGE CODE=%02x\n",
|
pr_err("INQUIRY with EVPD==0 but PAGE CODE=%02x\n",
|
||||||
cdb[2]);
|
cdb[2]);
|
||||||
cmd->scsi_sense_reason = TCM_INVALID_CDB_FIELD;
|
cmd->scsi_sense_reason = TCM_INVALID_CDB_FIELD;
|
||||||
return -EINVAL;
|
ret = -EINVAL;
|
||||||
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = target_emulate_inquiry_std(cmd);
|
ret = target_emulate_inquiry_std(cmd, buf);
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Make sure we at least have 4 bytes of INQUIRY response
|
|
||||||
* payload for 0x00 going back for EVPD=1. Note that 0x80
|
|
||||||
* and 0x83 will check for enough payload data length and
|
|
||||||
* jump to set_len: label when there is not enough inquiry EVPD
|
|
||||||
* payload length left for the next outgoing EVPD metadata
|
|
||||||
*/
|
|
||||||
if (cmd->data_length < 4) {
|
|
||||||
pr_err("SCSI Inquiry payload length: %u"
|
|
||||||
" too small for EVPD=1\n", cmd->data_length);
|
|
||||||
cmd->scsi_sense_reason = TCM_INVALID_CDB_FIELD;
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf = transport_kmap_data_sg(cmd);
|
|
||||||
|
|
||||||
buf[0] = dev->transport->get_device_type(dev);
|
buf[0] = dev->transport->get_device_type(dev);
|
||||||
|
|
||||||
for (p = 0; p < ARRAY_SIZE(evpd_handlers); ++p) {
|
for (p = 0; p < ARRAY_SIZE(evpd_handlers); ++p) {
|
||||||
if (cdb[2] == evpd_handlers[p].page) {
|
if (cdb[2] == evpd_handlers[p].page) {
|
||||||
buf[1] = cdb[2];
|
buf[1] = cdb[2];
|
||||||
ret = evpd_handlers[p].emulate(cmd, buf);
|
ret = evpd_handlers[p].emulate(cmd, buf);
|
||||||
goto out_unmap;
|
goto out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -739,9 +657,13 @@ int target_emulate_inquiry(struct se_task *task)
|
||||||
cmd->scsi_sense_reason = TCM_INVALID_CDB_FIELD;
|
cmd->scsi_sense_reason = TCM_INVALID_CDB_FIELD;
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
|
|
||||||
out_unmap:
|
|
||||||
transport_kunmap_data_sg(cmd);
|
|
||||||
out:
|
out:
|
||||||
|
if (buf != map_buf) {
|
||||||
|
memcpy(map_buf, buf, cmd->data_length);
|
||||||
|
kfree(buf);
|
||||||
|
}
|
||||||
|
transport_kunmap_data_sg(cmd);
|
||||||
|
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
task->task_scsi_status = GOOD;
|
task->task_scsi_status = GOOD;
|
||||||
transport_complete_task(task, 1);
|
transport_complete_task(task, 1);
|
||||||
|
|
|
@ -118,9 +118,9 @@
|
||||||
/* Queue Algorithm Modifier default for restricted reordering in control mode page */
|
/* Queue Algorithm Modifier default for restricted reordering in control mode page */
|
||||||
#define DA_EMULATE_REST_REORD 0
|
#define DA_EMULATE_REST_REORD 0
|
||||||
|
|
||||||
|
#define SE_INQUIRY_BUF 512
|
||||||
#define SE_MODE_PAGE_BUF 512
|
#define SE_MODE_PAGE_BUF 512
|
||||||
|
|
||||||
|
|
||||||
/* struct se_hba->hba_flags */
|
/* struct se_hba->hba_flags */
|
||||||
enum hba_flags_table {
|
enum hba_flags_table {
|
||||||
HBA_FLAGS_INTERNAL_USE = 0x01,
|
HBA_FLAGS_INTERNAL_USE = 0x01,
|
||||||
|
|
Загрузка…
Ссылка в новой задаче