/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* BSD 3 Clause License
*
* Copyright (c) 2007, The Storage Networking Industry Association.
*
* 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.
*
* - Neither the name of The Storage Networking Industry Association (SNIA)
* nor the names of its contributors may 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 THE COPYRIGHT OWNER 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.
*/
/* Copyright (c) 2007, The Storage Networking Industry Association. */
/* Copyright (c) 1996, 1997 PDC, Network Appliance. All Rights Reserved */
/* Copyright 2014 Nexenta Systems, Inc. All rights reserved. */
#include <sys/param.h>
#include <fcntl.h>
#include <sys/mtio.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "ndmpd_common.h"
#include "ndmpd.h"
static void tape_open_send_reply(ndmp_connection_t *connection, int err);
static void unbuffered_read(ndmpd_session_t *session, char *buf, long wanted,
ndmp_tape_read_reply *reply);
static boolean_t validmode(int mode);
static void common_tape_open(ndmp_connection_t *connection, char *devname,
int ndmpmode);
static void common_tape_close(ndmp_connection_t *connection);
/*
* Configurable delay & time when the tape is
* busy during opening the tape.
*/
int ndmp_tape_open_retries = 5;
int ndmp_tape_open_delay = 1000;
/*
* A few words about EOT (end-of-tape) and EOM handling on tapes with SVR4
* semantic:
*
* We adhere to terminology as used in st driver. EOT means end of recorded
* data on a tape. This is different from EOM (somewhere referred to as LEOT)
* which is the end of tape medium. EOT is meaningful only for reads while EOM
* is meaningful only for writes. It's not possible to read after EOT (fails
* with EIO), but it's possible to write data after EOM. EOM returned by st
* driver on modern tape drives is just indication that the physical end of
* tape medium is nearing and that writer should write just the necessary
* minimum and stop writing. When physical end of tape is reached all writes
* return EIO. If EOM is crossed during read operation then st driver doesn't
* bother to report it to client and that's alright because reads don't care
* where medium physically ends but they care about meaningful data recorded on
* the tape and as long as there are such data reads should continue to work.
*
* When reading EOT is signalled by st driver by two empty consecutive reads
* (with FSF done between them). When writing EOM is signalled by empty write
* (a write which writes zero bytes). Following writes succeed until physical
* end of tape is reached in which case EIO is returned.
*/
/*
* ************************************************************************
* NDMP V2 HANDLERS
* ************************************************************************
*/
/*
* ndmpd_tape_open_v2
*
* This handler opens the specified tape device.
*
* Parameters:
* connection (input) - connection handle.
* body (input) - request message body.
*
* Returns:
* void
*/
void
ndmpd_tape_open_v2(ndmp_connection_t *connection, void *body)
{
ndmp_tape_open_request_v2 *request = (ndmp_tape_open_request_v2 *) body;
ndmpd_session_t *session = ndmp_get_client_data(connection);
char adptnm[SCSI_MAX_NAME];
int mode;
int sid, lun;
int err;
scsi_adapter_t *sa;
int devid;
err = NDMP_NO_ERR;
if (session->ns_tape.td_fd != -1 || session->ns_scsi.sd_is_open != -1) {
NDMP_LOG(LOG_INFO,
"Connection already has a tape or scsi device open");
err = NDMP_DEVICE_OPENED_ERR;
} else if (request->mode != NDMP_TAPE_READ_MODE &&
request->mode != NDMP_TAPE_WRITE_MODE &&
request->mode != NDMP_TAPE_RAW1_MODE) {
err = NDMP_ILLEGAL_ARGS_ERR;
}
if ((sa = scsi_get_adapter(0)) != NULL) {
NDMP_LOG(LOG_DEBUG,
"Adapter device opened: %s", request->device.name);
(void) strlcpy(adptnm, request->device.name, SCSI_MAX_NAME-2);
adptnm[SCSI_MAX_NAME-1] = '\0';
sid = lun = -1;
}
/* try to get the scsi id etc.... */
if (sa) {
scsi_find_sid_lun(sa, request->device.name, &sid, &lun);
if (ndmp_open_list_find(request->device.name, sid, lun) == 0 &&
(devid = tape_open(request->device.name,
O_RDWR | O_NDELAY)) < 0) {
NDMP_LOG(LOG_ERR, "Failed to open device %s: %m.",
request->device.name);
err = NDMP_NO_DEVICE_ERR;
}
else
(void) close(devid);
} else {
NDMP_LOG(LOG_ERR, "%s: No such tape device.",
request->device.name);
err = NDMP_NO_DEVICE_ERR;
}
if (err != NDMP_NO_ERR) {
tape_open_send_reply(connection, err);
return;
}
switch (ndmp_open_list_add(connection, adptnm, sid, lun, devid)) {
case 0:
err = NDMP_NO_ERR;
break;
case EBUSY:
err = NDMP_DEVICE_BUSY_ERR;
break;
case ENOMEM:
err = NDMP_NO_MEM_ERR;
break;
default:
err = NDMP_IO_ERR;
}
if (err != NDMP_NO_ERR) {
tape_open_send_reply(connection, err);
return;
}
/*
* According to Connectathon 2001, the 0x7fffffff is a secret
* code between "Workstartion Solutions" and * net_app.
* If mode is set to this value, tape_open() won't fail if
* the tape device is not ready.
*/
if (request->mode != NDMP_TAPE_RAW1_MODE &&
!is_tape_unit_ready(adptnm, 0)) {
(void) ndmp_open_list_del(adptnm, sid, lun);
tape_open_send_reply(connection, NDMP_NO_TAPE_LOADED_ERR);
return;
}
mode = (request->mode == NDMP_TAPE_READ_MODE) ? O_RDONLY : O_RDWR;
mode |= O_NDELAY;
if ((session->ns_tape.td_fd = open(request->device.name, mode)) < 0) {
NDMP_LOG(LOG_ERR, "Failed to open tape device %s: %m.",
request->device.name);
switch (errno) {
case EACCES:
err = NDMP_WRITE_PROTECT_ERR;
break;
case ENXIO:
case ENOENT:
err = NDMP_NO_DEVICE_ERR;
break;
case EBUSY:
err = NDMP_DEVICE_BUSY_ERR;
break;
default:
err = NDMP_IO_ERR;
}
(void) ndmp_open_list_del(adptnm, sid, lun);
tape_open_send_reply(connection, err);
return;
}
session->ns_tape.td_mode = request->mode;
session->ns_tape.td_sid = sid;
session->ns_tape.td_lun = lun;
(void) strlcpy(session->ns_tape.td_adapter_name, adptnm, SCSI_MAX_NAME);
session->ns_tape.td_record_count = 0;
NDMP_LOG(LOG_DEBUG, "Tape is opened fd: %d", session->ns_tape.td_fd);
tape_open_send_reply(connection, NDMP_NO_ERR);
}
/*
* ndmpd_tape_close_v2
*
* This handler closes the currently open tape device.
*
* Parameters:
* connection (input) - connection handle.
* body (input) - request message body.
*
* Returns:
* void
*/
/*ARGSUSED*/
void
ndmpd_tape_close_v2(ndmp_connection_t *connection, void *body)
{
ndmp_tape_close_reply reply;
ndmpd_session_t *session = ndmp_get_client_data(connection);
if (session->ns_tape.td_fd == -1) {
NDMP_LOG(LOG_ERR, "Tape device is not open.");
reply.error = NDMP_DEV_NOT_OPEN_ERR;
ndmp_send_reply(connection, (void *) &reply,
"sending tape_close reply");
return;
}
common_tape_close(connection);
}
/*
* ndmpd_tape_get_state_v2
*
* This handler handles the tape_get_state request.
* Status information for the currently open tape device is returned.
*
* Parameters:
* connection (input) - connection handle.
* body (input) - request message body.
*
* Returns:
* void
*/
/*ARGSUSED*/
void
ndmpd_tape_get_state_v2(ndmp_connection_t *connection, void *body)
{
ndmp_tape_get_state_reply_v2 reply;
ndmpd_session_t *session = ndmp_get_client_data(connection);
struct mtget mtstatus;
struct mtdrivetype_request dtpr;
struct mtdrivetype dtp;
if (session->ns_tape.td_fd == -1) {
NDMP_LOG(LOG_ERR, "Tape device is not open.");
reply.error = NDMP_DEV_NOT_OPEN_ERR;
ndmp_send_reply(connection, (void *) &reply,
"sending tape_get_state reply");
return;
}
if (ioctl(session->ns_tape.td_fd, MTIOCGET, &mtstatus) < 0) {
NDMP_LOG(LOG_ERR, "Failed to get status from tape: %m.");
NDMP_LOG(LOG_DEBUG, "ioctl(MTIOCGET) error: %m.");
reply.error = NDMP_IO_ERR;
ndmp_send_reply(connection, (void *)&reply,
"sending tape_get_state reply");
return;
}
dtpr.size = sizeof (struct mtdrivetype);
dtpr.mtdtp = &dtp;
if (ioctl(session->ns_tape.td_fd, MTIOCGETDRIVETYPE, &dtpr) == -1) {
NDMP_LOG(LOG_ERR,
"Failed to get drive type information from tape: %m.");
NDMP_LOG(LOG_DEBUG, "ioctl(MTIOCGETDRIVETYPE) error: %m.");
reply.error = NDMP_IO_ERR;
ndmp_send_reply(connection, (void *)&reply,
"sending tape_get_state reply");
return;
}
reply.flags = 0;
reply.file_num = mtstatus.mt_fileno;
reply.soft_errors = 0;
reply.block_size = dtp.bsize;
if (dtp.bsize == 0)
reply.blockno = mtstatus.mt_blkno;
else
reply.blockno = mtstatus.mt_blkno *
(session->ns_mover.md_record_size / dtp.bsize);
reply.soft_errors = 0;
reply.total_space = long_long_to_quad(0); /* not supported */
reply.space_remain = long_long_to_quad(0); /* not supported */
NDMP_LOG(LOG_DEBUG,
"flags: 0x%x, file_num: %d, block_size: %d, blockno: %d",
reply.flags, reply.file_num, reply.block_size, reply.blockno);
reply.error = NDMP_NO_ERR;
ndmp_send_reply(connection, (void *) &reply,
"sending tape_get_state reply");
}
/*
* ndmpd_tape_mtio_v2
*
* This handler handles tape_mtio requests.
*
* Parameters:
* connection (input) - connection handle.
* body (input) - request message body.
*
* Returns:
* void
*/
void
ndmpd_tape_mtio_v2(ndmp_connection_t *connection, void *body)
{
ndmp_tape_mtio_request *request = (ndmp_tape_mtio_request *) body;
ndmp_tape_mtio_reply reply;
ndmpd_session_t *session = ndmp_get_client_data(connection);
struct mtop tapeop;
struct mtget mtstatus;
int retry = 0;
int rc;
reply.resid_count = 0;
if (session->ns_tape.td_fd == -1) {
NDMP_LOG(LOG_ERR, "Tape device is not open.");
reply.error = NDMP_DEV_NOT_OPEN_ERR;
ndmp_send_reply(connection, (void *) &reply,
"sending tape_mtio reply");
return;
}
reply.error = NDMP_NO_ERR;
switch (request->tape_op) {
case NDMP_MTIO_FSF:
tapeop.mt_op = MTFSF;
break;
case NDMP_MTIO_BSF:
tapeop.mt_op = MTBSF;
break;
case NDMP_MTIO_FSR:
tapeop.mt_op = MTFSR;
break;
case NDMP_MTIO_BSR:
tapeop.mt_op = MTBSR;
break;
case NDMP_MTIO_REW:
tapeop.mt_op = MTREW;
break;
case NDMP_MTIO_EOF:
if (session->ns_tape.td_mode == NDMP_TAPE_READ_MODE)
reply.error = NDMP_PERMISSION_ERR;
tapeop.mt_op = MTWEOF;
break;
case NDMP_MTIO_OFF:
tapeop.mt_op = MTOFFL;
break;
case NDMP_MTIO_TUR: /* test unit ready */
if (is_tape_unit_ready(session->ns_tape.td_adapter_name,
session->ns_tape.td_fd) == 0)
/* tape not ready ? */
reply.error = NDMP_NO_TAPE_LOADED_ERR;
break;
default:
reply.error = NDMP_ILLEGAL_ARGS_ERR;
}
if (reply.error == NDMP_NO_ERR && request->tape_op != NDMP_MTIO_TUR) {
tapeop.mt_count = request->count;
do {
NS_UPD(twait, trun);
errno = 0;
rc = ioctl(session->ns_tape.td_fd, MTIOCTOP, &tapeop);
NS_UPD(trun, twait);
NDMP_LOG(LOG_DEBUG,
"ioctl MTIO rc:%d, cmd:%d, retry:%d, error: %d",
rc, tapeop.mt_op, retry, errno);
} while (rc < 0 && errno == EIO &&
retry++ < 5);
/*
* Ignore I/O errors since these usually are the result of
* attempting to position past the beginning or end of the tape.
* The residual count will be returned and can be used to
* determine that the call was not completely successful.
*/
if (rc < 0) {
NDMP_LOG(LOG_ERR,
"Failed to send command to tape: %m.");
NDMP_LOG(LOG_DEBUG, "ioctl(MTIOCTOP) error: %m.");
/* MTWEOF doesnt have residual count */
if (tapeop.mt_op == MTWEOF)
reply.error = NDMP_IO_ERR;
else
reply.error = NDMP_NO_ERR;
reply.resid_count = tapeop.mt_count;
ndmp_send_reply(connection, (void *)&reply,
"sending tape_mtio reply");
return;
}
if (request->tape_op != NDMP_MTIO_REW &&
request->tape_op != NDMP_MTIO_OFF) {
if (ioctl(session->ns_tape.td_fd, MTIOCGET,
&mtstatus) < 0) {
NDMP_LOG(LOG_ERR,
"Failed to send command to tape: %m.");
NDMP_LOG(LOG_DEBUG,
"ioctl(MTIOCGET) error: %m.");
reply.error = NDMP_IO_ERR;
ndmp_send_reply(connection, (void *)&reply,
"sending tape_mtio reply");
return;
}
reply.resid_count = labs(mtstatus.mt_resid);
}
}
NDMP_LOG(LOG_DEBUG, "resid_count: %d",
reply.resid_count);
ndmp_send_reply(connection, (void *) &reply, "sending tape_mtio reply");
}
/*
* ndmpd_tape_read_v2
*
* This handler handles tape_read requests.
* This interface is a non-buffered interface. Each read request
* maps directly to a read to the tape device. It is the responsibility
* of the NDMP client to issue read requests with a length that is at
* least as large as the record size used write the tape. The tape driver
* always reads a full record. Data is discarded if the read request is
* smaller than the record size.
* It is the responsibility of the NDMP client to ensure that the
* length is a multiple of the tape block size if the tape device
* is in fixed block mode.
*
* Parameters:
* connection (input) - connection handle.
* body (input) - request message body.
*
* Returns:
* void
*/
void
ndmpd_tape_read_v2(ndmp_connection_t *connection, void *body)
{
ndmp_tape_read_request *request = (ndmp_tape_read_request *) body;
ndmp_tape_read_reply reply;
ndmpd_session_t *session = ndmp_get_client_data(connection);
char *buf;
reply.data_in.data_in_len = 0;
if (session->ns_tape.td_fd == -1) {
NDMP_LOG(LOG_ERR, "Tape device is not open.");
reply.error = NDMP_DEV_NOT_OPEN_ERR;
ndmp_send_reply(connection, (void *)&reply,
"sending tape_read reply");
return;
}
if (request->count == 0) {
reply.error = NDMP_NO_ERR;
ndmp_send_reply(connection, (void *)&reply,
"sending tape_read reply");
return;
}
if ((buf = ndmp_malloc(request->count)) == 0) {
reply.error = NDMP_NO_MEM_ERR;
ndmp_send_reply(connection, (void *)&reply,
"sending tape_read reply");
return;
}
unbuffered_read(session, buf, request->count, &reply);
ndmp_send_reply(connection, (void *) &reply, "sending tape_read reply");
(void) free(buf);
}
/*
* ndmpd_tape_execute_cdb_v2
*
* This handler handles tape_execute_cdb requests.
*
* Parameters:
* connection (input) - connection handle.
* body (input) - request message body.
*
* Returns:
* void
*/
void
ndmpd_tape_execute_cdb_v2(ndmp_connection_t *connection, void *body)
{
ndmp_tape_execute_cdb_request *request;
ndmp_tape_execute_cdb_reply reply;
ndmpd_session_t *session = ndmp_get_client_data(connection);
request = (ndmp_tape_execute_cdb_request *) body;
if (session->ns_tape.td_fd == -1) {
(void) memset((void *) &reply, 0, sizeof (reply));
NDMP_LOG(LOG_ERR, "Tape device is not open.");
reply.error = NDMP_DEV_NOT_OPEN_ERR;
ndmp_send_reply(connection, (void *) &reply,
"sending tape_execute_cdb reply");
} else {
ndmp_execute_cdb(session, session->ns_tape.td_adapter_name,
session->ns_tape.td_sid, session->ns_tape.td_lun,
(ndmp_execute_cdb_request *)request);
}
}
/*
* ************************************************************************
* NDMP V3 HANDLERS
* ************************************************************************
*/
/*
* ndmpd_tape_open_v3
*
* This handler opens the specified tape device.
*
* Parameters:
* connection (input) - connection handle.
* body (input) - request message body.
*
* Returns:
* void
*/
void
ndmpd_tape_open_v3(ndmp_connection_t *connection, void *body)
{
ndmp_tape_open_request_v3 *request = (ndmp_tape_open_request_v3 *)body;
common_tape_open(connection, request->device, request->mode);
}
/*
* ndmpd_tape_get_state_v3
*
* This handler handles the ndmp_tape_get_state_request.
* Status information for the currently open tape device is returned.
*
* Parameters:
* connection (input) - connection handle.
* body (input) - request message body.
*
* Returns:
* void
*/
/*ARGSUSED*/
void
ndmpd_tape_get_state_v3(ndmp_connection_t *connection, void *body)
{
ndmp_tape_get_state_reply_v3 reply;
ndmpd_session_t *session = ndmp_get_client_data(connection);
struct mtdrivetype_request dtpr;
struct mtdrivetype dtp;
struct mtget mtstatus;
if (session->ns_tape.td_fd == -1) {
NDMP_LOG(LOG_ERR, "Tape device is not open.");
reply.error = NDMP_DEV_NOT_OPEN_ERR;
ndmp_send_reply(connection, (void *) &reply,
"sending tape_get_state reply");
return;
}
if (ioctl(session->ns_tape.td_fd, MTIOCGET, &mtstatus) == -1) {
NDMP_LOG(LOG_ERR, "Failed to get status from tape: %m.");
NDMP_LOG(LOG_DEBUG, "ioctl(MTIOCGET) error: %m.");
reply.error = NDMP_IO_ERR;
ndmp_send_reply(connection, (void *)&reply,
"sending tape_get_state reply");
return;
}
dtpr.size = sizeof (struct mtdrivetype);
dtpr.mtdtp = &dtp;
if (ioctl(session->ns_tape.td_fd, MTIOCGETDRIVETYPE, &dtpr) == -1) {
NDMP_LOG(LOG_ERR,
"Failed to get drive type information from tape: %m.");
NDMP_LOG(LOG_DEBUG, "ioctl(MTIOCGETDRIVETYPE) error: %m.");
reply.error = NDMP_IO_ERR;
ndmp_send_reply(connection, (void *)&reply,
"sending tape_get_state reply");
return;
}
reply.flags = 0;
reply.file_num = mtstatus.mt_fileno;
reply.soft_errors = 0;
reply.block_size = dtp.bsize;
if (dtp.bsize == 0)
reply.blockno = mtstatus.mt_blkno;
else
reply.blockno = mtstatus.mt_blkno *
(session->ns_mover.md_record_size / dtp.bsize);
reply.total_space = long_long_to_quad(0); /* not supported */
reply.space_remain = long_long_to_quad(0); /* not supported */
reply.partition = 0; /* not supported */
reply.soft_errors = 0;
reply.total_space = long_long_to_quad(0LL);
reply.space_remain = long_long_to_quad(0LL);
reply.invalid = NDMP_TAPE_STATE_SOFT_ERRORS_INVALID |
NDMP_TAPE_STATE_TOTAL_SPACE_INVALID |
NDMP_TAPE_STATE_SPACE_REMAIN_INVALID |
NDMP_TAPE_STATE_PARTITION_INVALID;
NDMP_LOG(LOG_DEBUG, "f 0x%x, fnum %d, bsize %d, bno: %d",
reply.flags, reply.file_num, reply.block_size, reply.blockno);
reply.error = NDMP_NO_ERR;
ndmp_send_reply(connection, (void *) &reply,
"sending tape_get_state reply");
}
/*
* tape_is_at_bot
*
* Returns 1 if tape is at BOT, 0 on error or not at BOT.
*
*/
int
tape_is_at_bot(ndmpd_session_t *session)
{
struct mtget mtstatus;
if (ioctl(session->ns_tape.td_fd, MTIOCGET, &mtstatus) == 0 &&
mtstatus.mt_fileno == 0 && mtstatus.mt_blkno == 0)
return (1);
return (0);
}
/*
* If we are at the beginning of a file (block # is zero) and read returns
* zero bytes then this has to be end of recorded data on the tape. Repeated
* reads at EOT return EIO. In both cases (zero read and EIO read) this
* function should be used to test if we are at EOT.
*
* Returns 1 if tape is at BOF, 0 on error or not at BOF.
*/
int
tape_is_at_bof(ndmpd_session_t *session)
{
struct mtget mtstatus;
if ((ioctl(session->ns_tape.td_fd, MTIOCGET, &mtstatus) == 0) &&
(mtstatus.mt_fileno > 0) && (mtstatus.mt_blkno == 0))
return (1);
return (0);
}
/*
* Skips forward over a file mark and then back before the file mark. Why is
* this needed? There are two reasons for it:
*
* 1) Because NDMPv4 spec requires that when EOF is encountered, the tape
* position should remain on BOT side of the file mark. When st driver reaches
* end of file get-position mtioctl reports position before file mark, however
* the file mark has already been read and the real position is thus after the
* file mark (real position as reported for example by uscsi commands). Thus we
* need to do FSF, which does nothing but only updates file & block counter in
* st driver and then BSF, which sets the position before the file mark. Thus
* current position as reported by scsi and mtioctl will be in sync.
*
* 2) st driver returns EIO for repeated reads at EOF while according to NDMP
* spec we should continue to return zero bytes until FSF is done. By skipping
* forward and backward, st driver will return zero bytes for the next read
* again and we don't need to specifically handle this case.
*/
void
fm_dance(ndmpd_session_t *session)
{
(void) ndmp_mtioctl(session->ns_tape.td_fd, MTFSF, 1);
(void) ndmp_mtioctl(session->ns_tape.td_fd, MTBSF, 1);
}
/*
* ndmpd_tape_write_v3
*
* This handler handles tape_write requests. This interface is a non-buffered
* interface. Each write request maps directly to a write to the tape device.
* It is the responsibility of the NDMP client to pad the data to the desired
* record size. It is the responsibility of the NDMP client to ensure that the
* length is a multiple of the tape block size if the tape device is in fixed
* block mode.
*
* A logical end of tape will return number of bytes written less than
* requested, and one more request to write will give 0 and NDMP_EOM_ERR,
* followed by NDMP_NO_ERR until NDMP_IO_ERR when physical end of tape is
* reached.
*
* Parameters:
* connection (input) - connection handle.
* body (input) - request message body.
*/
void ndmpd_tape_write_v3(ndmp_connection_t *connection, void *body) {
ndmp_tape_write_request *request = (ndmp_tape_write_request *)body;
ndmp_tape_write_reply reply; ndmpd_session_t *session =
ndmp_get_client_data(connection); ssize_t n;
reply.count = 0;
if (session->ns_tape.td_fd == -1) {
NDMP_LOG(LOG_ERR, "Tape device is not open.");
reply.error = NDMP_DEV_NOT_OPEN_ERR;
ndmp_send_reply(connection, (void *) &reply,
"sending tape_write reply");
return;
}
if (session->ns_tape.td_mode == NDMP_TAPE_READ_MODE) {
NDMP_LOG(LOG_INFO, "Tape device opened in read-only mode");
reply.error = NDMP_PERMISSION_ERR;
ndmp_send_reply(connection, (void *) &reply,
"sending tape_write reply");
return;
}
if (request->data_out.data_out_len == 0) {
reply.error = NDMP_NO_ERR;
ndmp_send_reply(connection, (void *) &reply,
"sending tape_write reply");
return;
}
/*
* V4 suggests that this should not be accepted
* when mover is in listen or active state
*/
if (session->ns_protocol_version == NDMPV4 &&
(session->ns_mover.md_state == NDMP_MOVER_STATE_LISTEN ||
session->ns_mover.md_state == NDMP_MOVER_STATE_ACTIVE)) {
reply.error = NDMP_DEVICE_BUSY_ERR;
ndmp_send_reply(connection, (void *) &reply,
"sending tape_write reply");
return;
}
n = write(session->ns_tape.td_fd, request->data_out.data_out_val,
request->data_out.data_out_len);
if (n < 0) {
NDMP_LOG(LOG_ERR, "Tape write error: %m.");
reply.error = NDMP_IO_ERR;
} else if (n == 0) {
NDMP_LOG(LOG_INFO, "EOM detected");
reply.error = NDMP_EOM_ERR;
} else {
NS_ADD(wtape, n);
reply.count = n;
reply.error = NDMP_NO_ERR;
if (n < request->data_out.data_out_len)
NDMP_LOG(LOG_DEBUG,
"EOM is coming (partial write of %d bytes)", n);
}
ndmp_send_reply(connection, (void *) &reply,
"sending tape_write reply");
}
/*
* ndmpd_tape_read_v3
*
* This handler handles tape_read requests. This interface is a non-buffered
* interface. Each read request maps directly to a read to the tape device. It
* is the responsibility of the NDMP client to issue read requests with a
* length that is at least as large as the record size used write the tape. The
* tape driver always reads a full record. Data is discarded if the read
* request is smaller than the record size. It is the responsibility of the
* NDMP client to ensure that the length is a multiple of the tape block size
* if the tape device is in fixed block mode.
*
* A logical end of tape will return less bytes than requested, and one more
* request to read will give 0 and NDMP_EOM_ERR. All subsequent reads will
* return NDMP_EOM_ERR until the tape is repositioned.
*
* Parameters:
* connection (input) - connection handle.
* body (input) - request message body.
*/
void
ndmpd_tape_read_v3(ndmp_connection_t *connection, void *body)
{
ndmp_tape_read_request *request = (ndmp_tape_read_request *) body;
ndmp_tape_read_reply reply;
ndmpd_session_t *session = ndmp_get_client_data(connection);
char *buf;
int n;
reply.data_in.data_in_len = 0;
if (session->ns_tape.td_fd == -1) {
NDMP_LOG(LOG_ERR, "Tape device is not open.");
reply.error = NDMP_DEV_NOT_OPEN_ERR;
ndmp_send_reply(connection, (void *) &reply,
"sending tape_read reply");
return;
}
if (request->count == 0) {
reply.error = NDMP_NO_ERR;
ndmp_send_reply(connection, (void *) &reply,
"sending tape_read reply");
return;
}
/*
* V4 suggests that this should not be accepted
* when mover is in listen or active state
*/
if (session->ns_protocol_version == NDMPV4 &&
(session->ns_mover.md_state == NDMP_MOVER_STATE_LISTEN ||
session->ns_mover.md_state == NDMP_MOVER_STATE_ACTIVE)) {
reply.error = NDMP_DEVICE_BUSY_ERR;
ndmp_send_reply(connection, (void *) &reply,
"sending tape_read reply");
return;
}
if ((buf = ndmp_malloc(request->count)) == NULL) {
reply.error = NDMP_NO_MEM_ERR;
ndmp_send_reply(connection, (void *) &reply,
"sending tape_read reply");
return;
}
n = read(session->ns_tape.td_fd, buf, request->count);
if (n < 0) {
/*
* This fix is for Symantec during importing
* of spanned data between the tapes.
*/
if (errno == ENOSPC) {
reply.error = NDMP_EOF_ERR;
}
/*
* If at beginning of file and read fails with EIO, then it's
* repeated attempt to read at EOT.
*/
else if (errno == EIO && tape_is_at_bof(session)) {
NDMP_LOG(LOG_DEBUG, "Repeated read at EOT");
reply.error = NDMP_EOM_ERR;
}
/*
* According to NDMPv4 spec preferred error code when
* trying to read from blank tape is NDMP_EOM_ERR.
*/
else if (errno == EIO && tape_is_at_bot(session)) {
NDMP_LOG(LOG_ERR, "Blank tape detected, returning EOM");
reply.error = NDMP_EOM_ERR;
} else {
NDMP_LOG(LOG_ERR, "Tape read error: %m.");
reply.error = NDMP_IO_ERR;
}
} else if (n == 0) {
if (tape_is_at_bof(session)) {
NDMP_LOG(LOG_DEBUG, "EOT detected");
reply.error = NDMP_EOM_ERR;
} else {
/* reposition the tape to BOT side of FM */
fm_dance(session);
NDMP_LOG(LOG_DEBUG, "EOF detected");
reply.error = NDMP_EOF_ERR;
}
} else {
session->ns_tape.td_pos += n;
reply.data_in.data_in_len = n;
reply.data_in.data_in_val = buf;
reply.error = NDMP_NO_ERR;
NS_ADD(rtape, n);
}
ndmp_send_reply(connection, (void *) &reply, "sending tape_read reply");
free(buf);
}
/*
* ************************************************************************
* NDMP V4 HANDLERS
* ************************************************************************
*/
/*
* ndmpd_tape_get_state_v4
*
* This handler handles the ndmp_tape_get_state_request.
* Status information for the currently open tape device is returned.
*
* Parameters:
* connection (input) - connection handle.
* body (input) - request message body.
*
* Returns:
* void
*/
/*ARGSUSED*/
void
ndmpd_tape_get_state_v4(ndmp_connection_t *connection, void *body)
{
ndmp_tape_get_state_reply_v4 reply;
ndmpd_session_t *session = ndmp_get_client_data(connection);
struct mtget mtstatus;
struct mtdrivetype_request dtpr;
struct mtdrivetype dtp;
if (session->ns_tape.td_fd == -1) {
NDMP_LOG(LOG_ERR, "Tape device is not open.");
reply.error = NDMP_DEV_NOT_OPEN_ERR;
ndmp_send_reply(connection, (void *) &reply,
"sending tape_get_state reply");
return;
}
/*
* Need code to detect NDMP_TAPE_STATE_NOREWIND
*/
if (ioctl(session->ns_tape.td_fd, MTIOCGET, &mtstatus) == -1) {
NDMP_LOG(LOG_ERR,
"Failed to get status information from tape: %m.");
NDMP_LOG(LOG_DEBUG, "ioctl(MTIOCGET) error: %m.");
reply.error = NDMP_IO_ERR;
ndmp_send_reply(connection, (void *)&reply,
"sending tape_get_state reply");
return;
}
dtpr.size = sizeof (struct mtdrivetype);
dtpr.mtdtp = &dtp;
if (ioctl(session->ns_tape.td_fd, MTIOCGETDRIVETYPE, &dtpr) == -1) {
NDMP_LOG(LOG_ERR,
"Failed to get drive type information from tape: %m.");
NDMP_LOG(LOG_DEBUG, "ioctl(MTIOCGETDRIVETYPE) error: %m.");
reply.error = NDMP_IO_ERR;
ndmp_send_reply(connection, (void *)&reply,
"sending tape_get_state reply");
return;
}
reply.flags = NDMP_TAPE_NOREWIND;
reply.file_num = mtstatus.mt_fileno;
reply.soft_errors = 0;
reply.block_size = dtp.bsize;
if (dtp.bsize == 0)
reply.blockno = mtstatus.mt_blkno;
else
reply.blockno = mtstatus.mt_blkno /
(session->ns_mover.md_record_size / dtp.bsize);
reply.total_space = long_long_to_quad(0LL); /* not supported */
reply.space_remain = long_long_to_quad(0LL); /* not supported */
reply.soft_errors = 0;
reply.unsupported = NDMP_TAPE_STATE_SOFT_ERRORS_INVALID |
NDMP_TAPE_STATE_TOTAL_SPACE_INVALID |
NDMP_TAPE_STATE_SPACE_REMAIN_INVALID |
NDMP_TAPE_STATE_PARTITION_INVALID;
NDMP_LOG(LOG_DEBUG, "f 0x%x, fnum %d, bsize %d, bno: %d",
reply.flags, reply.file_num, reply.block_size, reply.blockno);
reply.error = NDMP_NO_ERR;
ndmp_send_reply(connection, (void *) &reply,
"sending tape_get_state reply");
}
/*
* ndmpd_tape_close_v4
*
* This handler (v4) closes the currently open tape device.
*
* Parameters:
* connection (input) - connection handle.
* body (input) - request message body.
*
* Returns:
* void
*/
/*ARGSUSED*/
void
ndmpd_tape_close_v4(ndmp_connection_t *connection, void *body)
{
ndmp_tape_close_reply reply;
ndmpd_session_t *session = ndmp_get_client_data(connection);
if (session->ns_tape.td_fd == -1) {
NDMP_LOG(LOG_ERR, "Tape device is not open.");
reply.error = NDMP_DEV_NOT_OPEN_ERR;
ndmp_send_reply(connection, (void *) &reply,
"sending tape_close reply");
return;
}
/*
* V4 suggests that this should not be accepted
* when mover is in listen or active state
*/
if (session->ns_mover.md_state == NDMP_MOVER_STATE_LISTEN ||
session->ns_mover.md_state == NDMP_MOVER_STATE_ACTIVE) {
reply.error = NDMP_DEVICE_BUSY_ERR;
ndmp_send_reply(connection, (void *) &reply,
"sending tape_close reply");
return;
}
common_tape_close(connection);
}
/*
* ************************************************************************
* LOCALS
* ************************************************************************
*/
/*
* tape_open_send_reply
*
* Send a reply to the tape open message
*
* Parameters:
* connection (input) - connection handle.
* err (input) - NDMP error
*
* Returns:
* void
*/
static void
tape_open_send_reply(ndmp_connection_t *connection, int err)
{
ndmp_tape_open_reply reply;
reply.error = err;
ndmp_send_reply(connection, (void *) &reply, "sending tape_open reply");
}
/*
* unbuffered_read
*
* Perform tape read without read-ahead
*
* Parameters:
* session (input) - session handle
* bp (output) - read buffer
* wanted (input) - number of bytes wanted
* reply (output) - tape read reply message
*
* Returns:
* void
*/
static void
unbuffered_read(ndmpd_session_t *session, char *buf, long wanted,
ndmp_tape_read_reply *reply)
{
int n, len;
n = read(session->ns_tape.td_fd, buf, wanted);
if (n < 0) {
/*
* This fix is for Symantec during importing
* of spanned data between the tapes.
*/
if (errno == ENOSPC) {
reply->error = NDMP_EOF_ERR;
} else {
NDMP_LOG(LOG_ERR, "Tape read error: %m.");
reply->error = NDMP_IO_ERR;
}
} else if (n == 0) {
NDMP_LOG(LOG_DEBUG, "NDMP_EOF_ERR");
reply->error = NDMP_EOF_ERR;
(void) ndmp_mtioctl(session->ns_tape.td_fd, MTFSF, 1);
len = strlen(NDMP_EOM_MAGIC);
(void) memset(buf, 0, len);
n = read(session->ns_tape.td_fd, buf, len);
buf[len] = '\0';
NDMP_LOG(LOG_DEBUG, "Checking EOM: nread %d [%s]", n, buf);
(void) ndmp_mtioctl(session->ns_tape.td_fd, MTBSF, 1);
if (strncmp(buf, NDMP_EOM_MAGIC, len) != 0)
(void) ndmp_mtioctl(session->ns_tape.td_fd, MTFSF, 1);
} else {
session->ns_tape.td_pos += n;
reply->data_in.data_in_len = n;
reply->data_in.data_in_val = buf;
reply->error = NDMP_NO_ERR;
NS_ADD(rtape, n);
}
}
/*
* validmode
*
* Check the tape read mode is valid
*/
static boolean_t
validmode(int mode)
{
boolean_t rv;
switch (mode) {
case NDMP_TAPE_READ_MODE:
case NDMP_TAPE_WRITE_MODE:
case NDMP_TAPE_RAW1_MODE:
case NDMP_TAPE_RAW2_MODE:
rv = TRUE;
break;
default:
rv = FALSE;
}
return (rv);
}
/*
* common_tape_open
*
* Generic function for opening the tape for all versions
*
* Parameters:
* connection (input) - connection handle.
* devname (input) - tape device name to open.
* ndmpmode (input) - mode of opening (read, write, raw)
*
* Returns:
* void
*/
static void
common_tape_open(ndmp_connection_t *connection, char *devname, int ndmpmode)
{
ndmpd_session_t *session = ndmp_get_client_data(connection);
char adptnm[SCSI_MAX_NAME];
int err;
int mode;
int sid, lun;
scsi_adapter_t *sa;
int devid;
err = NDMP_NO_ERR;
if (session->ns_tape.td_fd != -1 || session->ns_scsi.sd_is_open != -1) {
NDMP_LOG(LOG_INFO,
"Connection already has a tape or scsi device open");
err = NDMP_DEVICE_OPENED_ERR;
} else if (!validmode(ndmpmode))
err = NDMP_ILLEGAL_ARGS_ERR;
if ((sa = scsi_get_adapter(0)) != NULL) {
NDMP_LOG(LOG_DEBUG, "Adapter device opened: %s", devname);
(void) strlcpy(adptnm, devname, SCSI_MAX_NAME-2);
adptnm[SCSI_MAX_NAME-1] = '\0';
sid = lun = -1;
}
if (sa) {
scsi_find_sid_lun(sa, devname, &sid, &lun);
if (ndmp_open_list_find(devname, sid, lun) == 0 &&
(devid = open(devname, O_RDWR | O_NDELAY)) < 0) {
NDMP_LOG(LOG_ERR,
"Failed to open device %s: %m.", devname);
err = NDMP_NO_DEVICE_ERR;
} else {
(void) close(devid);
}
} else {
NDMP_LOG(LOG_ERR, "%s: No such tape device.", devname);
err = NDMP_NO_DEVICE_ERR;
}
if (err != NDMP_NO_ERR) {
tape_open_send_reply(connection, err);
return;
}
/*
* If tape is not opened in raw mode and tape is not loaded
* return error.
*/
if (ndmpmode != NDMP_TAPE_RAW1_MODE &&
ndmpmode != NDMP_TAPE_RAW2_MODE &&
!is_tape_unit_ready(adptnm, 0)) {
tape_open_send_reply(connection, NDMP_NO_TAPE_LOADED_ERR);
return;
}
mode = (ndmpmode == NDMP_TAPE_READ_MODE) ? O_RDONLY : O_RDWR;
mode |= O_NDELAY;
session->ns_tape.td_fd = open(devname, mode);
if (session->ns_protocol_version == NDMPV4 &&
session->ns_tape.td_fd < 0 &&
ndmpmode == NDMP_TAPE_RAW_MODE && errno == EACCES) {
/*
* V4 suggests that if the tape is open in raw mode
* and could not be opened with write access, it should
* be opened read only instead.
*/
ndmpmode = NDMP_TAPE_READ_MODE;
session->ns_tape.td_fd = open(devname, O_RDONLY);
}
if (session->ns_tape.td_fd < 0) {
NDMP_LOG(LOG_ERR, "Failed to open tape device %s: %m.",
devname);
switch (errno) {
case EACCES:
err = NDMP_WRITE_PROTECT_ERR;
break;
case ENOENT:
err = NDMP_NO_DEVICE_ERR;
break;
case EBUSY:
err = NDMP_DEVICE_BUSY_ERR;
break;
case EPERM:
err = NDMP_PERMISSION_ERR;
break;
default:
err = NDMP_IO_ERR;
}
tape_open_send_reply(connection, err);
return;
}
switch (ndmp_open_list_add(connection,
adptnm, sid, lun, session->ns_tape.td_fd)) {
case 0:
err = NDMP_NO_ERR;
break;
case EBUSY:
err = NDMP_DEVICE_BUSY_ERR;
break;
case ENOMEM:
err = NDMP_NO_MEM_ERR;
break;
default:
err = NDMP_IO_ERR;
}
if (err != NDMP_NO_ERR) {
tape_open_send_reply(connection, err);
return;
}
session->ns_tape.td_mode = ndmpmode;
session->ns_tape.td_sid = sid;
session->ns_tape.td_lun = lun;
(void) strlcpy(session->ns_tape.td_adapter_name, adptnm, SCSI_MAX_NAME);
session->ns_tape.td_record_count = 0;
NDMP_LOG(LOG_DEBUG, "Tape is opened fd: %d", session->ns_tape.td_fd);
tape_open_send_reply(connection, NDMP_NO_ERR);
}
/*
* common_tape_close
*
* Generic function for closing the tape
*
* Parameters:
* connection (input) - connection handle.
*
* Returns:
* void
*/
static void
common_tape_close(ndmp_connection_t *connection)
{
ndmpd_session_t *session = ndmp_get_client_data(connection);
ndmp_tape_close_reply reply;
(void) ndmp_open_list_del(session->ns_tape.td_adapter_name,
session->ns_tape.td_sid, session->ns_tape.td_lun);
(void) close(session->ns_tape.td_fd);
session->ns_tape.td_fd = -1;
session->ns_tape.td_sid = 0;
session->ns_tape.td_lun = 0;
(void) memset(session->ns_tape.td_adapter_name, 0,
sizeof (session->ns_tape.td_adapter_name));
session->ns_tape.td_record_count = 0;
reply.error = NDMP_NO_ERR;
ndmp_send_reply(connection, (void *) &reply,
"sending tape_close reply");
}
/*
* tape_open
*
* Will try to open the tape with the given flags and
* path using the given retries and delay intervals
*/
int
tape_open(char *path, int flags)
{
int fd;
int i = 0;
while ((fd = open(path, flags)) == -1 &&
i++ < ndmp_tape_open_retries) {
if (errno != EBUSY)
break;
(void) usleep(ndmp_tape_open_delay);
}
return (fd);
}