sg_cmds_basic.c (sdparm-1.11.tgz) | : | sg_cmds_basic.c (sdparm-1.12.tgz) | ||
---|---|---|---|---|
/* | /* | |||
* Copyright (c) 1999-2019 Douglas Gilbert. | * Copyright (c) 1999-2020 Douglas Gilbert. | |||
* All rights reserved. | * All rights reserved. | |||
* Use of this source code is governed by a BSD-style | * Use of this source code is governed by a BSD-style | |||
* license that can be found in the BSD_LICENSE file. | * license that can be found in the BSD_LICENSE file. | |||
* | * | |||
* SPDX-License-Identifier: BSD-2-Clause | * SPDX-License-Identifier: BSD-2-Clause | |||
*/ | */ | |||
/* | /* | |||
* CONTENTS | * CONTENTS | |||
* Some SCSI commands are executed in many contexts and hence began | * Some SCSI commands are executed in many contexts and hence began | |||
skipping to change at line 44 | skipping to change at line 44 | |||
#include "sg_cmds_basic.h" | #include "sg_cmds_basic.h" | |||
#include "sg_pt.h" | #include "sg_pt.h" | |||
#include "sg_unaligned.h" | #include "sg_unaligned.h" | |||
#include "sg_pr2serr.h" | #include "sg_pr2serr.h" | |||
/* Needs to be after config.h */ | /* Needs to be after config.h */ | |||
#ifdef SG_LIB_LINUX | #ifdef SG_LIB_LINUX | |||
#include <errno.h> | #include <errno.h> | |||
#endif | #endif | |||
static const char * const version_str = "1.95 20191219"; | static const char * const version_str = "1.97 20200722"; | |||
#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */ | #define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */ | |||
#define EBUFF_SZ 256 | #define EBUFF_SZ 256 | |||
#define DEF_PT_TIMEOUT 60 /* 60 seconds */ | #define DEF_PT_TIMEOUT 60 /* 60 seconds */ | |||
#define START_PT_TIMEOUT 120 /* 120 seconds == 2 minutes */ | #define START_PT_TIMEOUT 120 /* 120 seconds == 2 minutes */ | |||
#define LONG_PT_TIMEOUT 7200 /* 7,200 seconds == 120 minutes */ | #define LONG_PT_TIMEOUT 7200 /* 7,200 seconds == 120 minutes */ | |||
#define INQUIRY_CMD 0x12 | #define INQUIRY_CMD 0x12 | |||
#define INQUIRY_CMDLEN 6 | #define INQUIRY_CMDLEN 6 | |||
skipping to change at line 182 | skipping to change at line 182 | |||
} | } | |||
if (o_sense_cat) | if (o_sense_cat) | |||
*o_sense_cat = scat; | *o_sense_cat = scat; | |||
return -2; | return -2; | |||
} | } | |||
/* This is a helper function used by sg_cmds_* implementations after the | /* This is a helper function used by sg_cmds_* implementations after the | |||
* call to the pass-through. pt_res is returned from do_scsi_pt(). If valid | * call to the pass-through. pt_res is returned from do_scsi_pt(). If valid | |||
* sense data is found it is decoded and output to sg_warnings_strm (def: | * sense data is found it is decoded and output to sg_warnings_strm (def: | |||
* stderr); depending on the 'noisy' and 'verbose' settings. Returns -2 for | * stderr); depending on the 'noisy' and 'verbose' settings. Returns -2 for | |||
* "sense" category (may not be fatal), -1 for failed, 0, or a positive | * o_sense_cat (sense category) written which may not be fatal. Returns | |||
* number. If din type command (or bidi) returns actual number of bytes read | * -1 for other types of failure. Returns 0, or a positive number. If data-in | |||
* (din_len - resid); otherwise returns 0. If -2 returned then sense category | * type command (or bidi) then returns actual number of bytes read | |||
* output via 'o_sense_cat' pointer (if not NULL). Note that several sense | * (din_len - resid); otherwise returns 0. Note that several sense categories | |||
* categories also have data in bytes received; -2 is still returned. */ | * also have data in bytes received; -2 is still returned. */ | |||
int | int | |||
sg_cmds_process_resp(struct sg_pt_base * ptvp, const char * leadin, | sg_cmds_process_resp(struct sg_pt_base * ptvp, const char * leadin, | |||
int pt_res, bool noisy, int verbose, int * o_sense_cat) | int pt_res, bool noisy, int verbose, int * o_sense_cat) | |||
{ | { | |||
bool transport_sense; | bool favour_sense; | |||
int cat, slen, resp_code, sstat, req_din_x, req_dout_x; | int cat, slen, resp_code, sstat, req_din_x, req_dout_x; | |||
int act_din_x, act_dout_x; | int act_din_x, act_dout_x; | |||
const uint8_t * sbp; | const uint8_t * sbp; | |||
char b[1024]; | char b[1024]; | |||
if (NULL == leadin) | if (NULL == leadin) | |||
leadin = ""; | leadin = ""; | |||
if (pt_res < 0) { | if (pt_res < 0) { | |||
#ifdef SG_LIB_LINUX | #ifdef SG_LIB_LINUX | |||
if (verbose) | if (verbose) | |||
skipping to change at line 321 | skipping to change at line 321 | |||
return -1; | return -1; | |||
case SCSI_PT_RESULT_SENSE: | case SCSI_PT_RESULT_SENSE: | |||
return sg_cmds_process_helper(leadin, req_din_x, act_din_x, | return sg_cmds_process_helper(leadin, req_din_x, act_din_x, | |||
req_dout_x, act_dout_x, sbp, slen, | req_dout_x, act_dout_x, sbp, slen, | |||
noisy, verbose, o_sense_cat); | noisy, verbose, o_sense_cat); | |||
case SCSI_PT_RESULT_TRANSPORT_ERR: | case SCSI_PT_RESULT_TRANSPORT_ERR: | |||
if (verbose || noisy) { | if (verbose || noisy) { | |||
get_scsi_pt_transport_err_str(ptvp, sizeof(b), b); | get_scsi_pt_transport_err_str(ptvp, sizeof(b), b); | |||
pr2ws("%s: transport: %s\n", leadin, b); | pr2ws("%s: transport: %s\n", leadin, b); | |||
} | } | |||
/* Shall we favour sense data over a transport error (given both) */ | ||||
#ifdef SG_LIB_LINUX | #ifdef SG_LIB_LINUX | |||
transport_sense = (slen > 0); | favour_sense = false; /* DRIVER_SENSE is not passed through */ | |||
#else | #else | |||
transport_sense = ((SAM_STAT_CHECK_CONDITION == | favour_sense = ((SAM_STAT_CHECK_CONDITION == | |||
get_scsi_pt_status_response(ptvp)) && (slen > 0)); | get_scsi_pt_status_response(ptvp)) && (slen > 0)); | |||
#endif | #endif | |||
if (transport_sense) | if (favour_sense) | |||
return sg_cmds_process_helper(leadin, req_din_x, act_din_x, | return sg_cmds_process_helper(leadin, req_din_x, act_din_x, | |||
req_dout_x, act_dout_x, sbp, slen, | req_dout_x, act_dout_x, sbp, slen, | |||
noisy, verbose, o_sense_cat); | noisy, verbose, o_sense_cat); | |||
else | else | |||
return -1; | return -1; | |||
case SCSI_PT_RESULT_OS_ERR: | case SCSI_PT_RESULT_OS_ERR: | |||
if (verbose || noisy) { | if (verbose || noisy) { | |||
get_scsi_pt_os_err_str(ptvp, sizeof(b), b); | get_scsi_pt_os_err_str(ptvp, sizeof(b), b); | |||
pr2ws("%s: os: %s\n", leadin, b); | pr2ws("%s: os: %s\n", leadin, b); | |||
} | } | |||
skipping to change at line 366 | skipping to change at line 367 | |||
if (NULL == ptvp) | if (NULL == ptvp) | |||
pr2ws("%s: out of memory\n", cname); | pr2ws("%s: out of memory\n", cname); | |||
return ptvp; | return ptvp; | |||
} | } | |||
static const char * const inquiry_s = "inquiry"; | static const char * const inquiry_s = "inquiry"; | |||
/* Returns 0 on success, while positive values are SG_LIB_CAT_* errors | /* Returns 0 on success, while positive values are SG_LIB_CAT_* errors | |||
* (e.g. SG_LIB_CAT_MALFORMED). If OS error, returns negated errno or -1. */ | * (e.g. SG_LIB_CAT_MALFORMED). If OS error, returns negated errno or -1. */ | |||
static int | static int | |||
sg_ll_inquiry_com(struct sg_pt_base * ptvp, bool cmddt, bool evpd, int pg_op, | sg_ll_inquiry_com(struct sg_pt_base * ptvp, int sg_fd, bool cmddt, bool evpd, | |||
void * resp, int mx_resp_len, int timeout_secs, | int pg_op, void * resp, int mx_resp_len, int timeout_secs, | |||
int * residp, bool noisy, int verbose) | int * residp, bool noisy, int verbose) | |||
{ | { | |||
bool ptvp_given = false; | ||||
bool local_sense = true; | ||||
bool local_cdb = true; | ||||
int res, ret, sense_cat, resid; | int res, ret, sense_cat, resid; | |||
uint8_t inq_cdb[INQUIRY_CMDLEN] = {INQUIRY_CMD, 0, 0, 0, 0, 0}; | uint8_t inq_cdb[INQUIRY_CMDLEN] = {INQUIRY_CMD, 0, 0, 0, 0, 0}; | |||
uint8_t sense_b[SENSE_BUFF_LEN]; | uint8_t sense_b[SENSE_BUFF_LEN]; | |||
uint8_t * up; | uint8_t * up; | |||
if (resp == NULL) { | if (resp == NULL) { | |||
if (verbose) | if (verbose) | |||
pr2ws("Got NULL `resp` pointer"); | pr2ws("Got NULL `resp` pointer"); | |||
return SG_LIB_CAT_MALFORMED; | return SG_LIB_CAT_MALFORMED; | |||
} | } | |||
skipping to change at line 402 | skipping to change at line 406 | |||
b)); | b)); | |||
} | } | |||
if (resp && (mx_resp_len > 0)) { | if (resp && (mx_resp_len > 0)) { | |||
up = (uint8_t *)resp; | up = (uint8_t *)resp; | |||
up[0] = 0x7f; /* defensive prefill */ | up[0] = 0x7f; /* defensive prefill */ | |||
if (mx_resp_len > 4) | if (mx_resp_len > 4) | |||
up[4] = 0; | up[4] = 0; | |||
} | } | |||
if (timeout_secs <= 0) | if (timeout_secs <= 0) | |||
timeout_secs = DEF_PT_TIMEOUT; | timeout_secs = DEF_PT_TIMEOUT; | |||
set_scsi_pt_cdb(ptvp, inq_cdb, sizeof(inq_cdb)); | if (ptvp) { | |||
set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); | ptvp_given = true; | |||
partial_clear_scsi_pt_obj(ptvp); | ||||
if (get_scsi_pt_cdb_buf(ptvp)) | ||||
local_cdb = false; /* N.B. Ignores locally built cdb */ | ||||
else | ||||
set_scsi_pt_cdb(ptvp, inq_cdb, sizeof(inq_cdb)); | ||||
if (get_scsi_pt_sense_buf(ptvp)) | ||||
local_sense = false; | ||||
else | ||||
set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); | ||||
} else { | ||||
ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose); | ||||
if (NULL == ptvp) | ||||
return sg_convert_errno(ENOMEM); | ||||
set_scsi_pt_cdb(ptvp, inq_cdb, sizeof(inq_cdb)); | ||||
set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); | ||||
} | ||||
set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len); | set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len); | |||
res = do_scsi_pt(ptvp, -1, timeout_secs, verbose); | res = do_scsi_pt(ptvp, -1, timeout_secs, verbose); | |||
ret = sg_cmds_process_resp(ptvp, inquiry_s, res, noisy, verbose, | ret = sg_cmds_process_resp(ptvp, inquiry_s, res, noisy, verbose, | |||
&sense_cat); | &sense_cat); | |||
resid = get_scsi_pt_resid(ptvp); | resid = get_scsi_pt_resid(ptvp); | |||
if (residp) | if (residp) | |||
*residp = resid; | *residp = resid; | |||
if (-1 == ret) | if (-1 == ret) | |||
ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); | ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); | |||
else if (-2 == ret) { | else if (-2 == ret) { | |||
skipping to change at line 434 | skipping to change at line 454 | |||
if (verbose) | if (verbose) | |||
pr2ws("%s: got too few bytes (%d)\n", __func__, ret); | pr2ws("%s: got too few bytes (%d)\n", __func__, ret); | |||
ret = SG_LIB_CAT_MALFORMED; | ret = SG_LIB_CAT_MALFORMED; | |||
} else | } else | |||
ret = 0; | ret = 0; | |||
if (resid > 0) { | if (resid > 0) { | |||
if (resid > mx_resp_len) { | if (resid > mx_resp_len) { | |||
pr2ws("%s resid (%d) should never exceed requested " | pr2ws("%s resid (%d) should never exceed requested " | |||
"len=%d\n", inquiry_s, resid, mx_resp_len); | "len=%d\n", inquiry_s, resid, mx_resp_len); | |||
return ret ? ret : SG_LIB_CAT_MALFORMED; | if (0 == ret) | |||
ret = SG_LIB_CAT_MALFORMED; | ||||
goto fini; | ||||
} | } | |||
/* zero unfilled section of response buffer, based on resid */ | /* zero unfilled section of response buffer, based on resid */ | |||
memset((uint8_t *)resp + (mx_resp_len - resid), 0, resid); | memset((uint8_t *)resp + (mx_resp_len - resid), 0, resid); | |||
} | } | |||
fini: | ||||
if (ptvp_given) { | ||||
if (local_sense) /* stop caller trying to access local sense */ | ||||
set_scsi_pt_sense(ptvp, NULL, 0); | ||||
if (local_cdb) | ||||
set_scsi_pt_cdb(ptvp, NULL, 0); | ||||
} else { | ||||
if (ptvp) | ||||
destruct_scsi_pt_obj(ptvp); | ||||
} | ||||
return ret; | return ret; | |||
} | } | |||
/* Invokes a SCSI INQUIRY command and yields the response. Returns 0 when | /* Invokes a SCSI INQUIRY command and yields the response. Returns 0 when | |||
* successful, various SG_LIB_CAT_* positive values, negated errno or | * successful, various SG_LIB_CAT_* positive values, negated errno or | |||
* -1 -> other errors. The CMDDT field is obsolete in the INQUIRY cdb. */ | * -1 -> other errors. The CMDDT field is obsolete in the INQUIRY cdb. */ | |||
int | int | |||
sg_ll_inquiry(int sg_fd, bool cmddt, bool evpd, int pg_op, void * resp, | sg_ll_inquiry(int sg_fd, bool cmddt, bool evpd, int pg_op, void * resp, | |||
int mx_resp_len, bool noisy, int verbose) | int mx_resp_len, bool noisy, int verbose) | |||
{ | { | |||
int ret; | return sg_ll_inquiry_com(NULL, sg_fd, cmddt, evpd, pg_op, resp, | |||
struct sg_pt_base * ptvp; | mx_resp_len, 0 /* timeout_sec */, NULL, noisy, | |||
verbose); | ||||
ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose); | ||||
if (NULL == ptvp) | ||||
return sg_convert_errno(ENOMEM); | ||||
ret = sg_ll_inquiry_com(ptvp, cmddt, evpd, pg_op, resp, mx_resp_len, | ||||
0 /* timeout_sec */, NULL, noisy, verbose); | ||||
destruct_scsi_pt_obj(ptvp); | ||||
return ret; | ||||
} | } | |||
/* Invokes a SCSI INQUIRY command and yields the response. Returns 0 when | /* Invokes a SCSI INQUIRY command and yields the response. Returns 0 when | |||
* successful, various SG_LIB_CAT_* positive values or -1 -> other errors. | * successful, various SG_LIB_CAT_* positive values or -1 -> other errors. | |||
* The CMDDT field is obsolete in the INQUIRY cdb (since spc3r16 in 2003) so | * The CMDDT field is obsolete in the INQUIRY cdb (since spc3r16 in 2003) so | |||
* an argument to set it has been removed (use the REPORT SUPPORTED OPERATION | * an argument to set it has been removed (use the REPORT SUPPORTED OPERATION | |||
* CODES command instead). Adds the ability to set the command abort timeout | * CODES command instead). Adds the ability to set the command abort timeout | |||
* and the ability to report the residual count. If timeout_secs is zero | * and the ability to report the residual count. If timeout_secs is zero | |||
* or less the default command abort timeout (60 seconds) is used. | * or less the default command abort timeout (60 seconds) is used. | |||
* If residp is non-NULL then the residual value is written where residp | * If residp is non-NULL then the residual value is written where residp | |||
* points. A residual value of 0 implies mx_resp_len bytes have be written | * points. A residual value of 0 implies mx_resp_len bytes have be written | |||
* where resp points. If the residual value equals mx_resp_len then no | * where resp points. If the residual value equals mx_resp_len then no | |||
* bytes have been written. */ | * bytes have been written. */ | |||
int | int | |||
sg_ll_inquiry_v2(int sg_fd, bool evpd, int pg_op, void * resp, | sg_ll_inquiry_v2(int sg_fd, bool evpd, int pg_op, void * resp, | |||
int mx_resp_len, int timeout_secs, int * residp, | int mx_resp_len, int timeout_secs, int * residp, | |||
bool noisy, int verbose) | bool noisy, int verbose) | |||
{ | { | |||
int ret; | return sg_ll_inquiry_com(NULL, sg_fd, false, evpd, pg_op, resp, | |||
struct sg_pt_base * ptvp; | mx_resp_len, timeout_secs, residp, noisy, | |||
verbose); | ||||
ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose); | ||||
if (NULL == ptvp) | ||||
return sg_convert_errno(ENOMEM); | ||||
ret = sg_ll_inquiry_com(ptvp, false, evpd, pg_op, resp, mx_resp_len, | ||||
timeout_secs, residp, noisy, verbose); | ||||
destruct_scsi_pt_obj(ptvp); | ||||
return ret; | ||||
} | } | |||
/* Similar to _v2 but takes a pointer to an object (derived from) sg_pt_base. | /* Similar to _v2 but takes a pointer to an object (derived from) sg_pt_base. | |||
* That object is assumed to be constructed and have a device file descriptor | * That object is assumed to be constructed and have a device file descriptor | |||
* associated with it. Caller is responsible for lifetime of ptp. */ | * associated with it. Caller is responsible for lifetime of ptp. */ | |||
int | int | |||
sg_ll_inquiry_pt(struct sg_pt_base * ptvp, bool evpd, int pg_op, void * resp, | sg_ll_inquiry_pt(struct sg_pt_base * ptvp, bool evpd, int pg_op, void * resp, | |||
int mx_resp_len, int timeout_secs, int * residp, bool noisy, | int mx_resp_len, int timeout_secs, int * residp, bool noisy, | |||
int verbose) | int verbose) | |||
{ | { | |||
clear_scsi_pt_obj(ptvp); | return sg_ll_inquiry_com(ptvp, -1, false, evpd, pg_op, resp, mx_resp_len, | |||
return sg_ll_inquiry_com(ptvp, false, evpd, pg_op, resp, mx_resp_len, | ||||
timeout_secs, residp, noisy, verbose); | timeout_secs, residp, noisy, verbose); | |||
} | } | |||
/* Yields most of first 36 bytes of a standard INQUIRY (evpd==0) response. | /* Yields most of first 36 bytes of a standard INQUIRY (evpd==0) response. | |||
* Returns 0 when successful, various SG_LIB_CAT_* positive values, negated | * Returns 0 when successful, various SG_LIB_CAT_* positive values, negated | |||
* errno or -1 -> other errors */ | * errno or -1 -> other errors */ | |||
int | int | |||
sg_simple_inquiry(int sg_fd, struct sg_simple_inquiry_resp * inq_data, | sg_simple_inquiry(int sg_fd, struct sg_simple_inquiry_resp * inq_data, | |||
bool noisy, int verbose) | bool noisy, int verbose) | |||
{ | { | |||
int ret; | int ret; | |||
skipping to change at line 524 | skipping to change at line 540 | |||
if (inq_data) { | if (inq_data) { | |||
memset(inq_data, 0, sizeof(* inq_data)); | memset(inq_data, 0, sizeof(* inq_data)); | |||
inq_data->peripheral_qualifier = 0x3; | inq_data->peripheral_qualifier = 0x3; | |||
inq_data->peripheral_type = 0x1f; | inq_data->peripheral_type = 0x1f; | |||
} | } | |||
inq_resp = sg_memalign(SAFE_STD_INQ_RESP_LEN, 0, &free_irp, false); | inq_resp = sg_memalign(SAFE_STD_INQ_RESP_LEN, 0, &free_irp, false); | |||
if (NULL == inq_resp) { | if (NULL == inq_resp) { | |||
pr2ws("%s: out of memory\n", __func__); | pr2ws("%s: out of memory\n", __func__); | |||
return sg_convert_errno(ENOMEM); | return sg_convert_errno(ENOMEM); | |||
} | } | |||
ret = sg_ll_inquiry_v2(sg_fd, false, 0, inq_resp, SAFE_STD_INQ_RESP_LEN, | ret = sg_ll_inquiry_com(NULL, sg_fd, false, false, 0, inq_resp, | |||
0, NULL, noisy, verbose); | SAFE_STD_INQ_RESP_LEN, 0, NULL, noisy, verbose); | |||
if (inq_data && (0 == ret)) { | if (inq_data && (0 == ret)) { | |||
inq_data->peripheral_qualifier = (inq_resp[0] >> 5) & 0x7; | inq_data->peripheral_qualifier = (inq_resp[0] >> 5) & 0x7; | |||
inq_data->peripheral_type = inq_resp[0] & 0x1f; | inq_data->peripheral_type = inq_resp[0] & 0x1f; | |||
inq_data->byte_1 = inq_resp[1]; | inq_data->byte_1 = inq_resp[1]; | |||
inq_data->version = inq_resp[2]; | inq_data->version = inq_resp[2]; | |||
inq_data->byte_3 = inq_resp[3]; | inq_data->byte_3 = inq_resp[3]; | |||
inq_data->byte_5 = inq_resp[5]; | inq_data->byte_5 = inq_resp[5]; | |||
inq_data->byte_6 = inq_resp[6]; | inq_data->byte_6 = inq_resp[6]; | |||
inq_data->byte_7 = inq_resp[7]; | inq_data->byte_7 = inq_resp[7]; | |||
skipping to change at line 566 | skipping to change at line 582 | |||
if (inq_data) { | if (inq_data) { | |||
memset(inq_data, 0, sizeof(* inq_data)); | memset(inq_data, 0, sizeof(* inq_data)); | |||
inq_data->peripheral_qualifier = 0x3; | inq_data->peripheral_qualifier = 0x3; | |||
inq_data->peripheral_type = 0x1f; | inq_data->peripheral_type = 0x1f; | |||
} | } | |||
inq_resp = sg_memalign(SAFE_STD_INQ_RESP_LEN, 0, &free_irp, false); | inq_resp = sg_memalign(SAFE_STD_INQ_RESP_LEN, 0, &free_irp, false); | |||
if (NULL == inq_resp) { | if (NULL == inq_resp) { | |||
pr2ws("%s: out of memory\n", __func__); | pr2ws("%s: out of memory\n", __func__); | |||
return sg_convert_errno(ENOMEM); | return sg_convert_errno(ENOMEM); | |||
} | } | |||
ret = sg_ll_inquiry_pt(ptvp, false, 0, inq_resp, SAFE_STD_INQ_RESP_LEN, | ret = sg_ll_inquiry_com(ptvp, -1, false, false, 0, inq_resp, | |||
0, NULL, noisy, verbose); | SAFE_STD_INQ_RESP_LEN, 0, NULL, noisy, verbose); | |||
if (inq_data && (0 == ret)) { | if (inq_data && (0 == ret)) { | |||
inq_data->peripheral_qualifier = (inq_resp[0] >> 5) & 0x7; | inq_data->peripheral_qualifier = (inq_resp[0] >> 5) & 0x7; | |||
inq_data->peripheral_type = inq_resp[0] & 0x1f; | inq_data->peripheral_type = inq_resp[0] & 0x1f; | |||
inq_data->byte_1 = inq_resp[1]; | inq_data->byte_1 = inq_resp[1]; | |||
inq_data->version = inq_resp[2]; | inq_data->version = inq_resp[2]; | |||
inq_data->byte_3 = inq_resp[3]; | inq_data->byte_3 = inq_resp[3]; | |||
inq_data->byte_5 = inq_resp[5]; | inq_data->byte_5 = inq_resp[5]; | |||
inq_data->byte_6 = inq_resp[6]; | inq_data->byte_6 = inq_resp[6]; | |||
inq_data->byte_7 = inq_resp[7]; | inq_data->byte_7 = inq_resp[7]; | |||
memcpy(inq_data->vendor, inq_resp + 8, 8); | memcpy(inq_data->vendor, inq_resp + 8, 8); | |||
memcpy(inq_data->product, inq_resp + 16, 16); | memcpy(inq_data->product, inq_resp + 16, 16); | |||
memcpy(inq_data->revision, inq_resp + 32, 4); | memcpy(inq_data->revision, inq_resp + 32, 4); | |||
} | } | |||
if (free_irp) | if (free_irp) | |||
free(free_irp); | free(free_irp); | |||
return ret; | return ret; | |||
} | } | |||
/* Invokes a SCSI TEST UNIT READY command. | /* Invokes a SCSI TEST UNIT READY command. | |||
* N.B. To access the sense buffer outside this routine then one be | ||||
* provided by the caller. | ||||
* 'pack_id' is just for diagnostics, safe to set to 0. | * 'pack_id' is just for diagnostics, safe to set to 0. | |||
* Looks for progress indicator if 'progress' non-NULL; | * Looks for progress indicator if 'progress' non-NULL; | |||
* if found writes value [0..65535] else write -1. | * if found writes value [0..65535] else write -1. | |||
* Returns 0 when successful, various SG_LIB_CAT_* positive values or | * Returns 0 when successful, various SG_LIB_CAT_* positive values or | |||
* -1 -> other errors */ | * -1 -> other errors */ | |||
int | static int | |||
sg_ll_test_unit_ready_progress_pt(struct sg_pt_base * ptvp, int pack_id, | sg_ll_test_unit_ready_com(struct sg_pt_base * ptvp, int sg_fd, int pack_id, | |||
int * progress, bool noisy, int verbose) | int * progress, bool noisy, int verbose) | |||
{ | { | |||
static const char * const tur_s = "test unit ready"; | static const char * const tur_s = "test unit ready"; | |||
bool ptvp_given = false; | ||||
bool local_sense = true; | ||||
bool local_cdb = true; | ||||
int res, ret, sense_cat; | int res, ret, sense_cat; | |||
uint8_t tur_cdb[TUR_CMDLEN] = {TUR_CMD, 0, 0, 0, 0, 0}; | uint8_t tur_cdb[TUR_CMDLEN] = {TUR_CMD, 0, 0, 0, 0, 0}; | |||
uint8_t sense_b[SENSE_BUFF_LEN]; | uint8_t sense_b[SENSE_BUFF_LEN]; | |||
if (verbose) { | if (verbose) { | |||
char b[128]; | char b[128]; | |||
pr2ws(" %s cdb: %s\n", tur_s, | pr2ws(" %s cdb: %s\n", tur_s, | |||
sg_get_command_str(tur_cdb, TUR_CMDLEN, false, sizeof(b), b)); | sg_get_command_str(tur_cdb, TUR_CMDLEN, false, sizeof(b), b)); | |||
} | } | |||
if (ptvp) { | ||||
clear_scsi_pt_obj(ptvp); | ptvp_given = true; | |||
set_scsi_pt_cdb(ptvp, tur_cdb, sizeof(tur_cdb)); | partial_clear_scsi_pt_obj(ptvp); | |||
set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); | if (get_scsi_pt_cdb_buf(ptvp)) | |||
local_cdb = false; /* N.B. Ignores locally built cdb */ | ||||
else | ||||
set_scsi_pt_cdb(ptvp, tur_cdb, sizeof(tur_cdb)); | ||||
if (get_scsi_pt_sense_buf(ptvp)) | ||||
local_sense = false; | ||||
else | ||||
set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); | ||||
} else { | ||||
ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose); | ||||
if (NULL == ptvp) | ||||
return sg_convert_errno(ENOMEM); | ||||
set_scsi_pt_cdb(ptvp, tur_cdb, sizeof(tur_cdb)); | ||||
set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); | ||||
} | ||||
set_scsi_pt_packet_id(ptvp, pack_id); | set_scsi_pt_packet_id(ptvp, pack_id); | |||
res = do_scsi_pt(ptvp, -1, DEF_PT_TIMEOUT, verbose); | res = do_scsi_pt(ptvp, -1, DEF_PT_TIMEOUT, verbose); | |||
ret = sg_cmds_process_resp(ptvp, tur_s, res, noisy, verbose, &sense_cat); | ret = sg_cmds_process_resp(ptvp, tur_s, res, noisy, verbose, &sense_cat); | |||
if (-1 == ret) | if (-1 == ret) | |||
ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); | ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); | |||
else if (-2 == ret) { | else if (-2 == ret) { | |||
if (progress) { | if (progress) { | |||
int slen = get_scsi_pt_sense_len(ptvp); | int slen = get_scsi_pt_sense_len(ptvp); | |||
if (! sg_get_sense_progress_fld(sense_b, slen, progress)) | if (! sg_get_sense_progress_fld(sense_b, slen, progress)) | |||
skipping to change at line 635 | skipping to change at line 670 | |||
case SG_LIB_CAT_RECOVERED: | case SG_LIB_CAT_RECOVERED: | |||
case SG_LIB_CAT_NO_SENSE: | case SG_LIB_CAT_NO_SENSE: | |||
ret = 0; | ret = 0; | |||
break; | break; | |||
default: | default: | |||
ret = sense_cat; | ret = sense_cat; | |||
break; | break; | |||
} | } | |||
} else | } else | |||
ret = 0; | ret = 0; | |||
if (ptvp_given) { | ||||
if (local_sense) /* stop caller trying to access local sense */ | ||||
set_scsi_pt_sense(ptvp, NULL, 0); | ||||
if (local_cdb) | ||||
set_scsi_pt_cdb(ptvp, NULL, 0); | ||||
} else { | ||||
if (ptvp) | ||||
destruct_scsi_pt_obj(ptvp); | ||||
} | ||||
return ret; | return ret; | |||
} | } | |||
int | int | |||
sg_ll_test_unit_ready_progress_pt(struct sg_pt_base * ptvp, int pack_id, | ||||
int * progress, bool noisy, int verbose) | ||||
{ | ||||
return sg_ll_test_unit_ready_com(ptvp, -1, pack_id, progress, noisy, | ||||
verbose); | ||||
} | ||||
int | ||||
sg_ll_test_unit_ready_progress(int sg_fd, int pack_id, int * progress, | sg_ll_test_unit_ready_progress(int sg_fd, int pack_id, int * progress, | |||
bool noisy, int verbose) | bool noisy, int verbose) | |||
{ | { | |||
int ret; | return sg_ll_test_unit_ready_com(NULL, sg_fd, pack_id, progress, noisy, | |||
struct sg_pt_base * ptvp; | verbose); | |||
ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose); | ||||
if (NULL == ptvp) | ||||
return sg_convert_errno(ENOMEM); | ||||
ret = sg_ll_test_unit_ready_progress_pt(ptvp, pack_id, progress, noisy, | ||||
verbose); | ||||
destruct_scsi_pt_obj(ptvp); | ||||
return ret; | ||||
} | } | |||
/* Invokes a SCSI TEST UNIT READY command. | /* Invokes a SCSI TEST UNIT READY command. | |||
* 'pack_id' is just for diagnostics, safe to set to 0. | * 'pack_id' is just for diagnostics, safe to set to 0. | |||
* Returns 0 when successful, various SG_LIB_CAT_* positive values or | * Returns 0 when successful, various SG_LIB_CAT_* positive values or | |||
* -1 -> other errors */ | * -1 -> other errors */ | |||
int | int | |||
sg_ll_test_unit_ready(int sg_fd, int pack_id, bool noisy, int verbose) | sg_ll_test_unit_ready(int sg_fd, int pack_id, bool noisy, int verbose) | |||
{ | { | |||
int ret; | return sg_ll_test_unit_ready_com(NULL, sg_fd, pack_id, NULL, noisy, | |||
struct sg_pt_base * ptvp; | verbose); | |||
ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose); | ||||
if (NULL == ptvp) | ||||
return sg_convert_errno(ENOMEM); | ||||
ret = sg_ll_test_unit_ready_progress_pt(ptvp, pack_id, NULL, noisy, | ||||
verbose); | ||||
destruct_scsi_pt_obj(ptvp); | ||||
return ret; | ||||
} | } | |||
int | int | |||
sg_ll_test_unit_ready_pt(struct sg_pt_base * ptvp, int pack_id, bool noisy, | sg_ll_test_unit_ready_pt(struct sg_pt_base * ptvp, int pack_id, bool noisy, | |||
int verbose) | int verbose) | |||
{ | { | |||
return sg_ll_test_unit_ready_progress_pt(ptvp, pack_id, NULL, noisy, | return sg_ll_test_unit_ready_com(ptvp, -1, pack_id, NULL, noisy, verbose); | |||
verbose); | ||||
} | } | |||
/* Invokes a SCSI REQUEST SENSE command. Returns 0 when successful, various | /* Invokes a SCSI REQUEST SENSE command. Returns 0 when successful, various | |||
* SG_LIB_CAT_* positive values or -1 -> other errors */ | * SG_LIB_CAT_* positive values or -1 -> other errors */ | |||
static int | static int | |||
sg_ll_request_sense_com(struct sg_pt_base * ptvp, int sg_fd, bool desc, | sg_ll_request_sense_com(struct sg_pt_base * ptvp, int sg_fd, bool desc, | |||
void * resp, int mx_resp_len, bool noisy, int verbose) | void * resp, int mx_resp_len, bool noisy, int verbose) | |||
{ | { | |||
bool ptvp_given = false; | bool ptvp_given = false; | |||
bool local_cdb = true; | ||||
bool local_sense = true; | ||||
int ret, res, sense_cat; | int ret, res, sense_cat; | |||
static const char * const rq_s = "request sense"; | static const char * const rq_s = "request sense"; | |||
uint8_t rs_cdb[REQUEST_SENSE_CMDLEN] = | uint8_t rs_cdb[REQUEST_SENSE_CMDLEN] = | |||
{REQUEST_SENSE_CMD, 0, 0, 0, 0, 0}; | {REQUEST_SENSE_CMD, 0, 0, 0, 0, 0}; | |||
uint8_t sense_b[SENSE_BUFF_LEN]; | uint8_t sense_b[SENSE_BUFF_LEN]; | |||
if (desc) | if (desc) | |||
rs_cdb[1] |= 0x1; | rs_cdb[1] |= 0x1; | |||
if (mx_resp_len > 0xff) { | if (mx_resp_len > 0xff) { | |||
pr2ws("mx_resp_len cannot exceed 255\n"); | pr2ws("mx_resp_len cannot exceed 255\n"); | |||
return -1; | return -1; | |||
} | } | |||
rs_cdb[4] = mx_resp_len & 0xff; | rs_cdb[4] = mx_resp_len & 0xff; | |||
if (verbose) { | if (verbose) { | |||
char b[128]; | char b[128]; | |||
pr2ws(" %s cdb: %s\n", rq_s, | pr2ws(" %s cdb: %s\n", rq_s, | |||
sg_get_command_str(rs_cdb, REQUEST_SENSE_CMDLEN, false, | sg_get_command_str(rs_cdb, REQUEST_SENSE_CMDLEN, false, | |||
sizeof(b), b)); | sizeof(b), b)); | |||
} | } | |||
if (ptvp) | if (ptvp) { | |||
ptvp_given = true; | ptvp_given = true; | |||
else { | if (get_scsi_pt_sense_buf(ptvp)) | |||
local_cdb = false; | ||||
else | ||||
set_scsi_pt_cdb(ptvp, rs_cdb, sizeof(rs_cdb)); | ||||
if (get_scsi_pt_sense_buf(ptvp)) | ||||
local_sense = false; | ||||
else | ||||
set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); | ||||
} else { | ||||
ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose); | ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose); | |||
if (NULL == ptvp) | if (NULL == ptvp) | |||
return sg_convert_errno(ENOMEM); | return sg_convert_errno(ENOMEM); | |||
set_scsi_pt_cdb(ptvp, rs_cdb, sizeof(rs_cdb)); | ||||
set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); | ||||
} | } | |||
set_scsi_pt_cdb(ptvp, rs_cdb, sizeof(rs_cdb)); | ||||
set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); | ||||
set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len); | set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len); | |||
res = do_scsi_pt(ptvp, -1, DEF_PT_TIMEOUT, verbose); | res = do_scsi_pt(ptvp, -1, DEF_PT_TIMEOUT, verbose); | |||
ret = sg_cmds_process_resp(ptvp, rq_s, res, noisy, verbose, &sense_cat); | ret = sg_cmds_process_resp(ptvp, rq_s, res, noisy, verbose, &sense_cat); | |||
if (-1 == ret) | if (-1 == ret) | |||
ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); | ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); | |||
else if (-2 == ret) { | else if (-2 == ret) { | |||
switch (sense_cat) { | switch (sense_cat) { | |||
case SG_LIB_CAT_RECOVERED: | case SG_LIB_CAT_RECOVERED: | |||
case SG_LIB_CAT_NO_SENSE: | case SG_LIB_CAT_NO_SENSE: | |||
ret = 0; | ret = 0; | |||
skipping to change at line 741 | skipping to change at line 786 | |||
} | } | |||
} else { | } else { | |||
if ((mx_resp_len >= 8) && (ret < 8)) { | if ((mx_resp_len >= 8) && (ret < 8)) { | |||
if (verbose) | if (verbose) | |||
pr2ws(" %s: got %d bytes in response, too short\n", rq_s, | pr2ws(" %s: got %d bytes in response, too short\n", rq_s, | |||
ret); | ret); | |||
ret = -1; | ret = -1; | |||
} else | } else | |||
ret = 0; | ret = 0; | |||
} | } | |||
if ((! ptvp_given) && ptvp) | if (ptvp_given) { | |||
if (local_sense) /* stop caller accessing local sense */ | ||||
set_scsi_pt_sense(ptvp, NULL, 0); | ||||
if (local_cdb) /* stop caller accessing local sense */ | ||||
set_scsi_pt_cdb(ptvp, NULL, 0); | ||||
} else if (ptvp) | ||||
destruct_scsi_pt_obj(ptvp); | destruct_scsi_pt_obj(ptvp); | |||
return ret; | return ret; | |||
} | } | |||
int | int | |||
sg_ll_request_sense(int sg_fd, bool desc, void * resp, int mx_resp_len, | sg_ll_request_sense(int sg_fd, bool desc, void * resp, int mx_resp_len, | |||
bool noisy, int verbose) | bool noisy, int verbose) | |||
{ | { | |||
return sg_ll_request_sense_com(NULL, sg_fd, desc, resp, mx_resp_len, | return sg_ll_request_sense_com(NULL, sg_fd, desc, resp, mx_resp_len, | |||
noisy, verbose); | noisy, verbose); | |||
} | } | |||
int | int | |||
sg_ll_request_sense_pt(struct sg_pt_base * ptvp, bool desc, void * resp, | sg_ll_request_sense_pt(struct sg_pt_base * ptvp, bool desc, void * resp, | |||
int mx_resp_len, bool noisy, int verbose) | int mx_resp_len, bool noisy, int verbose) | |||
{ | { | |||
clear_scsi_pt_obj(ptvp); | ||||
return sg_ll_request_sense_com(ptvp, -1, desc, resp, mx_resp_len, | return sg_ll_request_sense_com(ptvp, -1, desc, resp, mx_resp_len, | |||
noisy, verbose); | noisy, verbose); | |||
} | } | |||
/* Invokes a SCSI REPORT LUNS command. Return of 0 -> success, | /* Invokes a SCSI REPORT LUNS command. Return of 0 -> success, | |||
* various SG_LIB_CAT_* positive values or -1 -> other errors */ | * various SG_LIB_CAT_* positive values or -1 -> other errors */ | |||
static int | static int | |||
sg_ll_report_luns_com(struct sg_pt_base * ptvp, int sg_fd, int select_report, | sg_ll_report_luns_com(struct sg_pt_base * ptvp, int sg_fd, int select_report, | |||
void * resp, int mx_resp_len, bool noisy, int verbose) | void * resp, int mx_resp_len, bool noisy, int verbose) | |||
{ | { | |||
static const char * const report_luns_s = "report luns"; | static const char * const report_luns_s = "report luns"; | |||
bool ptvp_given = false; | bool ptvp_given = false; | |||
bool local_cdb = true; | ||||
bool local_sense = true; | ||||
int ret, res, sense_cat; | int ret, res, sense_cat; | |||
uint8_t rl_cdb[REPORT_LUNS_CMDLEN] = | uint8_t rl_cdb[REPORT_LUNS_CMDLEN] = | |||
{REPORT_LUNS_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; | {REPORT_LUNS_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; | |||
uint8_t sense_b[SENSE_BUFF_LEN]; | uint8_t sense_b[SENSE_BUFF_LEN]; | |||
rl_cdb[2] = select_report & 0xff; | rl_cdb[2] = select_report & 0xff; | |||
sg_put_unaligned_be32((uint32_t)mx_resp_len, rl_cdb + 6); | sg_put_unaligned_be32((uint32_t)mx_resp_len, rl_cdb + 6); | |||
if (verbose) { | if (verbose) { | |||
char b[128]; | char b[128]; | |||
pr2ws(" %s cdb: %s\n", report_luns_s, | pr2ws(" %s cdb: %s\n", report_luns_s, | |||
sg_get_command_str(rl_cdb, REPORT_LUNS_CMDLEN, false, | sg_get_command_str(rl_cdb, REPORT_LUNS_CMDLEN, false, | |||
sizeof(b), b)); | sizeof(b), b)); | |||
} | } | |||
if (ptvp) | if (ptvp) { | |||
ptvp_given = true; | ptvp_given = true; | |||
else if (NULL == ((ptvp = create_pt_obj(report_luns_s)))) | partial_clear_scsi_pt_obj(ptvp); | |||
return sg_convert_errno(ENOMEM); | if (get_scsi_pt_cdb_buf(ptvp)) | |||
set_scsi_pt_cdb(ptvp, rl_cdb, sizeof(rl_cdb)); | local_cdb = false; | |||
set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); | else | |||
set_scsi_pt_cdb(ptvp, rl_cdb, sizeof(rl_cdb)); | ||||
if (get_scsi_pt_sense_buf(ptvp)) | ||||
local_sense = false; | ||||
else | ||||
set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); | ||||
} else { | ||||
if (NULL == ((ptvp = create_pt_obj(report_luns_s)))) | ||||
return sg_convert_errno(ENOMEM); | ||||
set_scsi_pt_cdb(ptvp, rl_cdb, sizeof(rl_cdb)); | ||||
set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); | ||||
} | ||||
set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len); | set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len); | |||
res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); | res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); | |||
ret = sg_cmds_process_resp(ptvp, report_luns_s, res, noisy, verbose, | ret = sg_cmds_process_resp(ptvp, report_luns_s, res, noisy, verbose, | |||
&sense_cat); | &sense_cat); | |||
if (-1 == ret) | if (-1 == ret) | |||
ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); | ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); | |||
else if (-2 == ret) { | else if (-2 == ret) { | |||
switch (sense_cat) { | switch (sense_cat) { | |||
case SG_LIB_CAT_RECOVERED: | case SG_LIB_CAT_RECOVERED: | |||
case SG_LIB_CAT_NO_SENSE: | case SG_LIB_CAT_NO_SENSE: | |||
ret = 0; | ret = 0; | |||
break; | break; | |||
default: | default: | |||
ret = sense_cat; | ret = sense_cat; | |||
break; | break; | |||
} | } | |||
} else | } else | |||
ret = 0; | ret = 0; | |||
if ((! ptvp_given) && ptvp) | if (ptvp_given) { | |||
destruct_scsi_pt_obj(ptvp); | if (local_sense) /* stop caller accessing local sense */ | |||
set_scsi_pt_sense(ptvp, NULL, 0); | ||||
if (local_cdb) | ||||
set_scsi_pt_cdb(ptvp, NULL, 0); | ||||
} else { | ||||
if (ptvp) | ||||
destruct_scsi_pt_obj(ptvp); | ||||
} | ||||
return ret; | return ret; | |||
} | } | |||
/* Invokes a SCSI REPORT LUNS command. Return of 0 -> success, | /* Invokes a SCSI REPORT LUNS command. Return of 0 -> success, | |||
* various SG_LIB_CAT_* positive values or -1 -> other errors, | * various SG_LIB_CAT_* positive values or -1 -> other errors, | |||
* Expects sg_fd to be >= 0 representing an open device fd. */ | * Expects sg_fd to be >= 0 representing an open device fd. */ | |||
int | int | |||
sg_ll_report_luns(int sg_fd, int select_report, void * resp, int mx_resp_len, | sg_ll_report_luns(int sg_fd, int select_report, void * resp, int mx_resp_len, | |||
bool noisy, int verbose) | bool noisy, int verbose) | |||
{ | { | |||
skipping to change at line 832 | skipping to change at line 901 | |||
mx_resp_len, noisy, verbose); | mx_resp_len, noisy, verbose); | |||
} | } | |||
/* Invokes a SCSI REPORT LUNS command. Return of 0 -> success, | /* Invokes a SCSI REPORT LUNS command. Return of 0 -> success, | |||
* various SG_LIB_CAT_* positive values or -1 -> other errors. | * various SG_LIB_CAT_* positive values or -1 -> other errors. | |||
* Expects a non-NULL ptvp containing an open device fd. */ | * Expects a non-NULL ptvp containing an open device fd. */ | |||
int | int | |||
sg_ll_report_luns_pt(struct sg_pt_base * ptvp, int select_report, | sg_ll_report_luns_pt(struct sg_pt_base * ptvp, int select_report, | |||
void * resp, int mx_resp_len, bool noisy, int verbose) | void * resp, int mx_resp_len, bool noisy, int verbose) | |||
{ | { | |||
clear_scsi_pt_obj(ptvp); | ||||
return sg_ll_report_luns_com(ptvp, -1, select_report, resp, | return sg_ll_report_luns_com(ptvp, -1, select_report, resp, | |||
mx_resp_len, noisy, verbose); | mx_resp_len, noisy, verbose); | |||
} | } | |||
End of changes. 40 change blocks. | ||||
86 lines changed or deleted | 154 lines changed or added |