blob: bb9e866aa34a2b1466b62dbec4bd9939a5a39ddb [file] [log] [blame]
/*
* Copyright (c) 2011-2015 Douglas Gilbert.
* All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the BSD_LICENSE file.
*/
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <getopt.h>
#define __STDC_FORMAT_MACROS 1
#include <inttypes.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "sg_lib.h"
#include "sg_pt.h"
#include "sg_cmds_basic.h"
#include "sg_cmds_extra.h"
#include "sg_unaligned.h"
#include "sg_pr2serr.h"
static const char * version_str = "1.00 20151219";
/* Not all environments support the Unix sleep() */
#if defined(MSC_VER) || defined(__MINGW32__)
#define HAVE_MS_SLEEP
#endif
#ifdef HAVE_MS_SLEEP
#include <windows.h>
#define sleep_for(seconds) Sleep( (seconds) * 1000)
#else
#define sleep_for(seconds) sleep(seconds)
#endif
#define ME "sg_sanitize: "
#define SANITIZE_OP 0x48
#define SANITIZE_OP_LEN 10
#define SANITIZE_SA_OVERWRITE 0x1
#define SANITIZE_SA_BLOCK_ERASE 0x2
#define SANITIZE_SA_CRYPTO_ERASE 0x3
#define SANITIZE_SA_EXIT_FAIL_MODE 0x1f
#define DEF_REQS_RESP_LEN 252
#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
#define MAX_XFER_LEN 65535
#define EBUFF_SZ 256
#define SHORT_TIMEOUT 20 /* 20 seconds unless immed=0 ... */
#define LONG_TIMEOUT (15 * 3600) /* 15 hours ! */
/* Seagate ST32000444SS 2TB disk takes 9.5 hours to format */
#define POLL_DURATION_SECS 60
static struct option long_options[] = {
{"ause", no_argument, 0, 'A'},
{"block", no_argument, 0, 'B'},
{"count", required_argument, 0, 'c'},
{"crypto", no_argument, 0, 'C'},
{"desc", no_argument, 0, 'd'},
{"early", no_argument, 0, 'e'},
{"fail", no_argument, 0, 'F'},
{"help", no_argument, 0, 'h'},
{"invert", no_argument, 0, 'I'},
{"ipl", required_argument, 0, 'i'},
{"overwrite", no_argument, 0, 'O'},
{"pattern", required_argument, 0, 'p'},
{"quick", no_argument, 0, 'Q'},
{"test", required_argument, 0, 'T'},
{"verbose", no_argument, 0, 'v'},
{"version", no_argument, 0, 'V'},
{"wait", no_argument, 0, 'w'},
{"zero", no_argument, 0, 'z'},
{0, 0, 0, 0},
};
struct opts_t {
int ause;
int block;
int count;
int crypto;
int desc;
int early;
int fail;
int invert;
int ipl; /* initialization pattern length */
int overwrite;
int test;
int quick;
int verbose;
int wait;
int zero;
int znr;
const char * pattern_fn;
};
static void
usage()
{
pr2serr("Usage: sg_sanitize [--ause] [--block] [--count=OC] [--crypto] "
"[--early]\n"
" [--fail] [--help] [--invert] [--ipl=LEN] "
"[--overwrite]\n"
" [--pattern=PF] [--quick] [--test=TE] "
"[--verbose]\n"
" [--version] [--wait] [--zero] [--znr] DEVICE\n"
" where:\n"
" --ause|-A set AUSE bit in cdb\n"
" --block|-B do BLOCK ERASE sanitize\n"
" --count=OC|-c OC OC is overwrite count field (from 1 "
"(def) to 31)\n"
" --crypto|-C do CRYPTOGRAPHIC ERASE sanitize\n"
" --desc|-d polling request sense sets 'desc' "
"field\n"
" (def: clear 'desc' field)\n"
" --early|-e exit once sanitize started (IMMED set "
"in cdb)\n"
" user can monitor progress with REQUEST "
"SENSE\n"
" --fail|-F do EXIT FAILURE MODE sanitize\n"
" --help|-h print out usage message\n"
" --invert|-I set INVERT bit in OVERWRITE parameter "
"list\n"
" --ipl=LEN|-i LEN initialization pattern length (in "
"bytes)\n"
" --overwrite|-O do OVERWRITE sanitize\n"
" --pattern=PF|-p PF PF is file containing initialization "
"pattern\n"
" for OVERWRITE\n"
" --quick|-Q start sanitize without pause for user\n"
" intervention (i.e. no time to "
"reconsider)\n"
" --test=TE|-T TE TE is placed in TEST field of "
"OVERWRITE\n"
" parameter list (def: 0)\n"
" --verbose|-v increase verbosity\n"
" --version|-V print version string then exit\n"
" --wait|-w wait for command to finish (could "
"take hours)\n"
" --zero|-z use pattern of zeros for "
"OVERWRITE\n"
" --znr|-Z set ZNR (zone no reset) bit in cdb\n\n"
"Performs a SCSI SANITIZE command.\n <<<WARNING>>>: all data "
"on DEVICE will be lost.\nDefault action is to give user time to "
"reconsider; then execute SANITIZE\ncommand with IMMED bit set; "
"then use REQUEST SENSE command every 60\nseconds to poll for a "
"progress indication; then exit when there is no\nmore progress "
"indication.\n"
);
}
/* Invoke SCSI SANITIZE command. Returns 0 if successful, otherwise error */
static int
do_sanitize(int sg_fd, const struct opts_t * op, const void * param_lstp,
int param_lst_len)
{
int k, ret, res, sense_cat, immed;
unsigned char sanCmdBlk[SANITIZE_OP_LEN];
unsigned char sense_b[SENSE_BUFF_LEN];
struct sg_pt_base * ptvp;
if (op->early || op->wait)
immed = op->early ? 1 : 0;
else
immed = 1;
memset(sanCmdBlk, 0, sizeof(sanCmdBlk));
sanCmdBlk[0] = SANITIZE_OP;
if (op->overwrite)
sanCmdBlk[1] = SANITIZE_SA_OVERWRITE;
else if (op->block)
sanCmdBlk[1] = SANITIZE_SA_BLOCK_ERASE;
else if (op->crypto)
sanCmdBlk[1] = SANITIZE_SA_CRYPTO_ERASE;
else if (op->fail)
sanCmdBlk[1] = SANITIZE_SA_EXIT_FAIL_MODE;
else
return SG_LIB_SYNTAX_ERROR;
if (immed)
sanCmdBlk[1] |= 0x80;
if (op->znr) /* added sbc4r07 */
sanCmdBlk[1] |= 0x40;
if (op->ause)
sanCmdBlk[1] |= 0x20;
sg_put_unaligned_be16((uint16_t)param_lst_len, sanCmdBlk + 7);
if (op->verbose > 1) {
pr2serr(" Sanitize cmd: ");
for (k = 0; k < SANITIZE_OP_LEN; ++k)
pr2serr("%02x ", sanCmdBlk[k]);
pr2serr("\n");
}
if ((op->verbose > 2) && (param_lst_len > 0)) {
pr2serr(" Parameter list contents:\n");
dStrHexErr((const char *)param_lstp, param_lst_len, 1);
}
ptvp = construct_scsi_pt_obj();
if (NULL == ptvp) {
pr2serr("Sanitize: out of memory\n");
return -1;
}
set_scsi_pt_cdb(ptvp, sanCmdBlk, sizeof(sanCmdBlk));
set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
set_scsi_pt_data_out(ptvp, (unsigned char *)param_lstp, param_lst_len);
res = do_scsi_pt(ptvp, sg_fd, (immed ? SHORT_TIMEOUT : LONG_TIMEOUT),
op->verbose);
ret = sg_cmds_process_resp(ptvp, "Sanitize", res, 0, sense_b,
1 /*noisy */, op->verbose, &sense_cat);
if (-1 == ret)
;
else if (-2 == ret) {
switch (sense_cat) {
case SG_LIB_CAT_RECOVERED:
case SG_LIB_CAT_NO_SENSE:
ret = 0;
break;
case SG_LIB_CAT_MEDIUM_HARD:
{
int valid, slen;
uint64_t ull = 0;
slen = get_scsi_pt_sense_len(ptvp);
valid = sg_get_sense_info_fld(sense_b, slen, &ull);
if (valid)
pr2serr("Medium or hardware error starting at "
"lba=%" PRIu64 " [0x%" PRIx64 "]\n", ull, ull);
}
ret = sense_cat;
break;
default:
ret = sense_cat;
break;
}
} else
ret = 0;
destruct_scsi_pt_obj(ptvp);
return ret;
}
#define VPD_DEVICE_ID 0x83
#define VPD_ASSOC_LU 0
#define VPD_ASSOC_TPORT 1
#define TPROTO_ISCSI 5
static char *
get_lu_name(const unsigned char * ucp, int u_len, char * b, int b_len)
{
int len, off, sns_dlen, dlen, k;
unsigned char u_sns[512];
char * cp;
len = u_len - 4;
ucp += 4;
off = -1;
if (0 == sg_vpd_dev_id_iter(ucp, len, &off, VPD_ASSOC_LU,
8 /* SCSI name string (sns) */,
3 /* UTF-8 */)) {
sns_dlen = ucp[off + 3];
memcpy(u_sns, ucp + off + 4, sns_dlen);
/* now want to check if this is iSCSI */
off = -1;
if (0 == sg_vpd_dev_id_iter(ucp, len, &off, VPD_ASSOC_TPORT,
8 /* SCSI name string (sns) */,
3 /* UTF-8 */)) {
if ((0x80 & ucp[1]) && (TPROTO_ISCSI == (ucp[0] >> 4))) {
snprintf(b, b_len, "%.*s", sns_dlen, u_sns);
return b;
}
}
} else
sns_dlen = 0;
if (0 == sg_vpd_dev_id_iter(ucp, len, &off, VPD_ASSOC_LU,
3 /* NAA */, 1 /* binary */)) {
dlen = ucp[off + 3];
if (! ((8 == dlen) || (16 ==dlen)))
return b;
cp = b;
for (k = 0; ((k < dlen) && (b_len > 1)); ++k) {
snprintf(cp, b_len, "%02x", ucp[off + 4 + k]);
cp += 2;
b_len -= 2;
}
} else if (0 == sg_vpd_dev_id_iter(ucp, len, &off, VPD_ASSOC_LU,
2 /* EUI */, 1 /* binary */)) {
dlen = ucp[off + 3];
if (! ((8 == dlen) || (12 == dlen) || (16 ==dlen)))
return b;
cp = b;
for (k = 0; ((k < dlen) && (b_len > 1)); ++k) {
snprintf(cp, b_len, "%02x", ucp[off + 4 + k]);
cp += 2;
b_len -= 2;
}
} else if (sns_dlen > 0)
snprintf(b, b_len, "%.*s", sns_dlen, u_sns);
return b;
}
#define SAFE_STD_INQ_RESP_LEN 36
#define VPD_SUPPORTED_VPDS 0x0
#define VPD_UNIT_SERIAL_NUM 0x80
#define VPD_DEVICE_ID 0x83
static int
print_dev_id(int fd, unsigned char * sinq_resp, int max_rlen, int verbose)
{
int res, k, n, verb, pdt, has_sn, has_di;
unsigned char b[256];
char a[256];
char pdt_name[64];
verb = (verbose > 1) ? verbose - 1 : 0;
memset(sinq_resp, 0, max_rlen);
res = sg_ll_inquiry(fd, 0, 0 /* evpd */, 0 /* pg_op */, b,
SAFE_STD_INQ_RESP_LEN, 1, verb);
if (res)
return res;
n = b[4] + 5;
if (n > SAFE_STD_INQ_RESP_LEN)
n = SAFE_STD_INQ_RESP_LEN;
memcpy(sinq_resp, b, (n < max_rlen) ? n : max_rlen);
if (n == SAFE_STD_INQ_RESP_LEN) {
pdt = b[0] & 0x1f;
printf(" %.8s %.16s %.4s peripheral_type: %s [0x%x]\n",
(const char *)(b + 8), (const char *)(b + 16),
(const char *)(b + 32),
sg_get_pdt_str(pdt, sizeof(pdt_name), pdt_name), pdt);
if (verbose)
printf(" PROTECT=%d\n", !!(b[5] & 1));
if (b[5] & 1)
printf(" << supports protection information>>\n");
} else {
pr2serr("Short INQUIRY response: %d bytes, expect at least 36\n", n);
return SG_LIB_CAT_OTHER;
}
res = sg_ll_inquiry(fd, 0, 1 /* evpd */, VPD_SUPPORTED_VPDS, b,
SAFE_STD_INQ_RESP_LEN, 1, verb);
if (res) {
if (verbose)
pr2serr("VPD_SUPPORTED_VPDS gave res=%d\n", res);
return 0;
}
if (VPD_SUPPORTED_VPDS != b[1]) {
if (verbose)
pr2serr("VPD_SUPPORTED_VPDS corrupted\n");
return 0;
}
n = sg_get_unaligned_be16(b + 2);
if (n > (SAFE_STD_INQ_RESP_LEN - 4))
n = (SAFE_STD_INQ_RESP_LEN - 4);
for (k = 0, has_sn = 0, has_di = 0; k < n; ++k) {
if (VPD_UNIT_SERIAL_NUM == b[4 + k]) {
if (has_di) {
if (verbose)
pr2serr("VPD_SUPPORTED_VPDS dis-ordered\n");
return 0;
}
++has_sn;
} else if (VPD_DEVICE_ID == b[4 + k]) {
++has_di;
break;
}
}
if (has_sn) {
res = sg_ll_inquiry(fd, 0, 1 /* evpd */, VPD_UNIT_SERIAL_NUM, b,
sizeof(b), 1, verb);
if (res) {
if (verbose)
pr2serr("VPD_UNIT_SERIAL_NUM gave res=%d\n", res);
return 0;
}
if (VPD_UNIT_SERIAL_NUM != b[1]) {
if (verbose)
pr2serr("VPD_UNIT_SERIAL_NUM corrupted\n");
return 0;
}
n = sg_get_unaligned_be16(b + 2);
if (n > (int)(sizeof(b) - 4))
n = (sizeof(b) - 4);
printf(" Unit serial number: %.*s\n", n, (const char *)(b + 4));
}
if (has_di) {
res = sg_ll_inquiry(fd, 0, 1 /* evpd */, VPD_DEVICE_ID, b,
sizeof(b), 1, verb);
if (res) {
if (verbose)
pr2serr("VPD_DEVICE_ID gave res=%d\n", res);
return 0;
}
if (VPD_DEVICE_ID != b[1]) {
if (verbose)
pr2serr("VPD_DEVICE_ID corrupted\n");
return 0;
}
n = sg_get_unaligned_be16(b + 2);
if (n > (int)(sizeof(b) - 4))
n = (sizeof(b) - 4);
n = strlen(get_lu_name(b, n + 4, a, sizeof(a)));
if (n > 0)
printf(" LU name: %.*s\n", n, a);
}
return 0;
}
int
main(int argc, char * argv[])
{
int sg_fd, k, res, c, infd, progress, vb, n, resp_len;
int got_stdin = 0;
int param_lst_len = 0;
const char * device_name = NULL;
char ebuff[EBUFF_SZ];
char b[80];
unsigned char requestSenseBuff[DEF_REQS_RESP_LEN];
unsigned char * wBuff = NULL;
int ret = -1;
struct opts_t opts;
struct opts_t * op;
struct stat a_stat;
unsigned char inq_resp[SAFE_STD_INQ_RESP_LEN];
op = &opts;
memset(op, 0, sizeof(opts));
op->count = 1;
while (1) {
int option_index = 0;
c = getopt_long(argc, argv, "ABc:CdeFhi:IOp:QT:vVwzZ", long_options,
&option_index);
if (c == -1)
break;
switch (c) {
case 'A':
++op->ause;
break;
case 'B':
++op->block;
break;
case 'c':
op->count = sg_get_num(optarg);
if ((op->count < 1) || (op->count > 31)) {
pr2serr("bad argument to '--count', expect 1 to 31\n");
return SG_LIB_SYNTAX_ERROR;
}
break;
case 'C':
++op->crypto;
break;
case 'd':
++op->desc;
break;
case 'e':
++op->early;
break;
case 'F':
++op->fail;
break;
case 'h':
case '?':
usage();
return 0;
case 'i':
op->ipl = sg_get_num(optarg);
if ((op->ipl < 1) || (op->ipl > 65535)) {
pr2serr("bad argument to '--ipl', expect 1 to 65535\n");
return SG_LIB_SYNTAX_ERROR;
}
break;
case 'I':
++op->invert;
break;
case 'O':
++op->overwrite;
break;
case 'p':
op->pattern_fn = optarg;
break;
case 'Q':
++op->quick;
break;
case 'T':
op->test = sg_get_num(optarg);
if ((op->test < 0) || (op->test > 3)) {
pr2serr("bad argument to '--test', expect 0 to 3\n");
return SG_LIB_SYNTAX_ERROR;
}
break;
case 'v':
++op->verbose;
break;
case 'V':
pr2serr(ME "version: %s\n", version_str);
return 0;
case 'w':
++op->wait;
break;
case 'z':
++op->zero;
break;
case 'Z':
++op->znr;
break;
default:
pr2serr("unrecognised option code 0x%x ??\n", c);
usage();
return SG_LIB_SYNTAX_ERROR;
}
}
if (optind < argc) {
if (NULL == device_name) {
device_name = argv[optind];
++optind;
}
if (optind < argc) {
for (; optind < argc; ++optind)
pr2serr("Unexpected extra argument: %s\n", argv[optind]);
usage();
return SG_LIB_SYNTAX_ERROR;
}
}
if (NULL == device_name) {
pr2serr("missing device name!\n");
usage();
return SG_LIB_SYNTAX_ERROR;
}
vb = op->verbose;
n = !!op->block + !!op->crypto + !!op->fail + !!op->overwrite;
if (1 != n) {
pr2serr("one and only one of '--block', '--crypto', '--fail' or "
"'--overwrite' please\n");
return SG_LIB_SYNTAX_ERROR;
}
if (op->overwrite) {
if (op->zero) {
if (op->pattern_fn) {
pr2serr("confused: both '--pattern=PF' and '--zero' "
"options\n");
return SG_LIB_SYNTAX_ERROR;
}
op->ipl = 4;
} else {
if (NULL == op->pattern_fn) {
pr2serr("'--overwrite' requires '--pattern=PF' or '--zero' "
"option\n");
return SG_LIB_SYNTAX_ERROR;
}
got_stdin = (0 == strcmp(op->pattern_fn, "-")) ? 1 : 0;
if (! got_stdin) {
memset(&a_stat, 0, sizeof(a_stat));
if (stat(op->pattern_fn, &a_stat) < 0) {
pr2serr("pattern file: unable to stat(%s): %s\n",
op->pattern_fn, safe_strerror(errno));
return SG_LIB_FILE_ERROR;
}
if (op->ipl <= 0) {
op->ipl = (int)a_stat.st_size;
if (op->ipl > MAX_XFER_LEN) {
pr2serr("pattern file length exceeds 65535 bytes, "
"need '--ipl=LEN' option\n");
return SG_LIB_FILE_ERROR;
}
}
}
if (op->ipl < 1) {
pr2serr("'--overwrite' requires '--ipl=LEN' option if can't "
"get PF length\n");
return SG_LIB_SYNTAX_ERROR;
}
}
}
sg_fd = sg_cmds_open_device(device_name, 0 /* rw */, vb);
if (sg_fd < 0) {
pr2serr(ME "open error: %s: %s\n", device_name,
safe_strerror(-sg_fd));
return SG_LIB_FILE_ERROR;
}
ret = print_dev_id(sg_fd, inq_resp, sizeof(inq_resp), op->verbose);
if (ret)
goto err_out;
if (op->overwrite) {
param_lst_len = op->ipl + 4;
wBuff = (unsigned char*)calloc(op->ipl + 4, 1);
if (NULL == wBuff) {
pr2serr("unable to allocate %d bytes of memory with calloc()\n",
op->ipl + 4);
ret = SG_LIB_SYNTAX_ERROR;
goto err_out;
}
if (op->zero) {
if (2 == op->zero) /* treat -zz as fill with 0xff bytes */
memset(wBuff + 4, 0xff, op->ipl);
else
memset(wBuff + 4, 0, op->ipl);
} else {
if (got_stdin) {
infd = STDIN_FILENO;
if (sg_set_binary_mode(STDIN_FILENO) < 0)
perror("sg_set_binary_mode");
} else {
if ((infd = open(op->pattern_fn, O_RDONLY)) < 0) {
snprintf(ebuff, EBUFF_SZ, ME "could not open %s for "
"reading", op->pattern_fn);
perror(ebuff);
ret = SG_LIB_FILE_ERROR;
goto err_out;
} else if (sg_set_binary_mode(infd) < 0)
perror("sg_set_binary_mode");
}
res = read(infd, wBuff + 4, op->ipl);
if (res < 0) {
snprintf(ebuff, EBUFF_SZ, ME "couldn't read from %s",
op->pattern_fn);
perror(ebuff);
if (! got_stdin)
close(infd);
ret = SG_LIB_FILE_ERROR;
goto err_out;
}
if (res < op->ipl) {
pr2serr("tried to read %d bytes from %s, got %d bytes\n",
op->ipl, op->pattern_fn, res);
pr2serr(" so pad with 0x0 bytes and continue\n");
}
if (! got_stdin)
close(infd);
}
wBuff[0] = op->count & 0x1f;;
if (op->test)
wBuff[0] |= ((op->test & 0x3) << 5);
if (op->invert)
wBuff[0] |= 0x80;
sg_put_unaligned_be16((uint16_t)op->ipl, wBuff + 2);
}
if ((0 == op->quick) && (! op->fail)) {
printf("\nA SANITIZE will commence in 15 seconds\n");
printf(" ALL data on %s will be DESTROYED\n", device_name);
printf(" Press control-C to abort\n");
sleep_for(5);
printf("\nA SANITIZE will commence in 10 seconds\n");
printf(" ALL data on %s will be DESTROYED\n", device_name);
printf(" Press control-C to abort\n");
sleep_for(5);
printf("\nA SANITIZE will commence in 5 seconds\n");
printf(" ALL data on %s will be DESTROYED\n", device_name);
printf(" Press control-C to abort\n");
sleep_for(5);
}
ret = do_sanitize(sg_fd, op, wBuff, param_lst_len);
if (ret) {
sg_get_category_sense_str(ret, sizeof(b), b, vb);
pr2serr("Sanitize failed: %s\n", b);
}
if ((0 == ret) && (0 == op->early) && (0 == op->wait)) {
for (k = 0 ;; ++k) {
sleep_for(POLL_DURATION_SECS);
memset(requestSenseBuff, 0x0, sizeof(requestSenseBuff));
res = sg_ll_request_sense(sg_fd, op->desc, requestSenseBuff,
sizeof(requestSenseBuff), 1, vb);
if (res) {
ret = res;
if (SG_LIB_CAT_INVALID_OP == res)
pr2serr("Request Sense command not supported\n");
else if (SG_LIB_CAT_ILLEGAL_REQ == res) {
pr2serr("bad field in Request Sense cdb\n");
if (1 == op->desc) {
pr2serr("Descriptor type sense may not be supported, "
"try again with fixed type\n");
op->desc = 0;
continue;
}
} else {
sg_get_category_sense_str(res, sizeof(b), b, vb);
pr2serr("Request Sense: %s\n", b);
if (0 == vb)
pr2serr(" try the '-v' option for more "
"information\n");
}
break;
}
/* "Additional sense length" same in descriptor and fixed */
resp_len = requestSenseBuff[7] + 8;
if (vb > 2) {
pr2serr("Parameter data in hex\n");
dStrHexErr((const char *)requestSenseBuff, resp_len, 1);
}
progress = -1;
sg_get_sense_progress_fld(requestSenseBuff, resp_len,
&progress);
if (progress < 0) {
ret = res;
if (vb > 1)
pr2serr("No progress indication found, iteration %d\n",
k + 1);
/* N.B. exits first time there isn't a progress indication */
break;
} else
printf("Progress indication: %d%% done\n",
(progress * 100) / 65536);
}
}
err_out:
if (wBuff)
free(wBuff);
res = sg_cmds_close_device(sg_fd);
if (res < 0) {
pr2serr("close error: %s\n", safe_strerror(-res));
if (0 == ret)
return SG_LIB_FILE_ERROR;
}
return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
}