/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015 by Delphix. All rights reserved.
*/
/*
* 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.
*/
#include <sys/errno.h>
#include <ctype.h>
#include <stdlib.h>
#include <time.h>
#include <sys/types.h>
#include <unistd.h>
#include <libzfs.h>
#include <pthread.h>
#include "tlm.h"
#include "tlm_proto.h"
#include <ndmpd_prop.h>
#include <sys/mtio.h>
#include <sys/mnttab.h>
#include <sys/mntent.h>
#include <sys/statvfs.h>
#include <sys/scsi/impl/uscsi.h>
#include <sys/scsi/scsi.h>
#include <sys/mtio.h>
#include <thread.h>
#include <synch.h>
#include <sys/mutex.h>
#include <sys/sysmacros.h>
#include <sys/mkdev.h>
/*
* Tar archiving ops vector
*/
tm_ops_t tm_tar_ops = {
"tar",
tar_putfile,
tar_putdir,
NULL,
tar_getfile,
tar_getdir,
NULL
};
extern libzfs_handle_t *zlibh;
extern mutex_t zlib_mtx;
/*
* get the next tape buffer from the drive's pool of buffers
*/
/*ARGSUSED*/
char *
tlm_get_write_buffer(long want, long *actual_size,
tlm_buffers_t *buffers, int zero)
{
int buf = buffers->tbs_buffer_in;
tlm_buffer_t *buffer = &buffers->tbs_buffer[buf];
int align_size = RECORDSIZE - 1;
char *rec;
/*
* make sure the allocation is in chunks of 512 bytes
*/
want += align_size;
want &= ~align_size;
*actual_size = buffer->tb_buffer_size - buffer->tb_buffer_spot;
if (*actual_size <= 0) {
/*
* no room, send this one
* and wait for a free one
*/
if (!buffer->tb_full) {
/*
* we are now ready to send a full buffer
* instead of trying to get a new buffer
*
* do not send if we failed to get a buffer
* on the previous call
*/
buffer->tb_full = TRUE;
/*
* tell the writer that a buffer is available
*/
tlm_buffer_release_in_buf(buffers);
buffer = tlm_buffer_advance_in_idx(buffers);
}
buffer = tlm_buffer_in_buf(buffers, NULL);
if (buffer->tb_full) {
/*
* wait for the writer to free up a buffer
*/
tlm_buffer_out_buf_timed_wait(buffers, 500);
}
buffer = tlm_buffer_in_buf(buffers, NULL);
if (buffer->tb_full) {
/*
* the next buffer is still full
* of data from previous activity
*
* nothing has changed.
*/
return (0);
}
buffer->tb_buffer_spot = 0;
*actual_size = buffer->tb_buffer_size - buffer->tb_buffer_spot;
}
*actual_size = min(want, *actual_size);
rec = &buffer->tb_buffer_data[buffer->tb_buffer_spot];
buffer->tb_buffer_spot += *actual_size;
buffers->tbs_offset += *actual_size;
if (zero) {
(void) memset(rec, 0, *actual_size);
}
return (rec);
}
/*
* get a read record from the tape buffer,
* and read a tape block if necessary
*/
/*ARGSUSED*/
char *
tlm_get_read_buffer(int want, int *error,
tlm_buffers_t *buffers, int *actual_size)
{
tlm_buffer_t *buffer;
int align_size = RECORDSIZE - 1;
int buf;
int current_size;
char *rec;
buf = buffers->tbs_buffer_out;
buffer = &buffers->tbs_buffer[buf];
/*
* make sure the allocation is in chunks of 512 bytes
*/
want += align_size;
want &= ~align_size;
current_size = buffer->tb_buffer_size - buffer->tb_buffer_spot;
if (buffer->tb_full && current_size <= 0) {
/*
* no more data, release this
* one and go get another
*/
/*
* tell the reader that a buffer is available
*/
buffer->tb_full = FALSE;
tlm_buffer_release_out_buf(buffers);
buffer = tlm_buffer_advance_out_idx(buffers);
current_size = buffer->tb_buffer_size - buffer->tb_buffer_spot;
}
if (!buffer->tb_full) {
/*
* next buffer is not full yet.
* wait for the reader.
*/
tlm_buffer_in_buf_timed_wait(buffers, 500);
buffer = tlm_buffer_out_buf(buffers, NULL);
if (!buffer->tb_full) {
/*
* we do not have anything from the tape yet
*/
return (0);
}
current_size = buffer->tb_buffer_size - buffer->tb_buffer_spot;
}
/* Make sure we got something */
if (current_size <= 0)
return (NULL);
current_size = min(want, current_size);
rec = &buffer->tb_buffer_data[buffer->tb_buffer_spot];
buffer->tb_buffer_spot += current_size;
*actual_size = current_size;
/*
* the error flag is only sent back one time,
* since the flag refers to a previous read
* attempt, not the data in this buffer.
*/
*error = buffer->tb_errno;
return (rec);
}
/*
* unread a previously read buffer back to the tape buffer
*/
void
tlm_unget_read_buffer(tlm_buffers_t *buffers, int size)
{
tlm_buffer_t *buffer;
int align_size = RECORDSIZE - 1;
int buf;
int current_size;
buf = buffers->tbs_buffer_out;
buffer = &buffers->tbs_buffer[buf];
/*
* make sure the allocation is in chunks of 512 bytes
*/
size += align_size;
size &= ~align_size;
current_size = min(size, buffer->tb_buffer_spot);
buffer->tb_buffer_spot -= current_size;
}
/*
* unwrite a previously written buffer
*/
void
tlm_unget_write_buffer(tlm_buffers_t *buffers, int size)
{
tlm_buffer_t *buffer;
int align_size = RECORDSIZE - 1;
int buf;
int current_size;
buf = buffers->tbs_buffer_in;
buffer = &buffers->tbs_buffer[buf];
/*
* make sure the allocation is in chunks of 512 bytes
*/
size += align_size;
size &= ~align_size;
current_size = min(size, buffer->tb_buffer_spot);
buffer->tb_buffer_spot -= current_size;
}
/*
* build a checksum for a TAR header record
*/
void
tlm_build_header_checksum(tlm_tar_hdr_t *r)
{
int i;
int sum = 0;
char *c = (char *)r;
(void) memcpy(r->th_chksum, CHKBLANKS, strlen(CHKBLANKS));
for (i = 0; i < RECORDSIZE; i++) {
sum += c[i] & 0xFF;
}
(void) snprintf(r->th_chksum, sizeof (r->th_chksum), "%6o", sum);
}
/*
* verify the tar header checksum
*/
int
tlm_vfy_tar_checksum(tlm_tar_hdr_t *tar_hdr)
{
int chksum = oct_atoi(tar_hdr->th_chksum);
uchar_t *p = (uchar_t *)tar_hdr;
int sum = 0; /* initial value of checksum */
int i; /* loop counter */
/*
* compute the checksum
*/
for (i = 0; i < RECORDSIZE; i++) {
sum += p[i] & 0xFF;
}
if (sum == 0) {
NDMP_LOG(LOG_DEBUG,
"should be %d, is 0", chksum);
/* a zero record ==> end of tar file */
return (0);
}
/*
* subtract out the label's checksum values
* this lets us undo the old checksum "in-
* place", no need to swap blanks in and out
*/
for (i = 0; i < 8; i++) {
sum -= 0xFF & tar_hdr->th_chksum[i];
}
/*
* replace the old checksum field with blanks
*/
sum += ' ' * 8;
if (sum != chksum)
NDMP_LOG(LOG_DEBUG,
"should be %d, is %d", chksum, sum);
return ((sum == chksum) ? 1 : -1);
}
/*
* get internal scsi_sasd entry for this tape drive
*/
int
tlm_get_scsi_sasd_entry(int lib, int drv)
{
int entry;
int i, n;
scsi_link_t *sl;
tlm_drive_t *dp;
entry = -1;
dp = tlm_drive(lib, drv);
if (!dp) {
NDMP_LOG(LOG_DEBUG, "NULL dp for (%d.%d)", lib, drv);
} else if (!dp->td_slink) {
NDMP_LOG(LOG_DEBUG, "NULL dp->td_slink for (%d.%d)", lib, drv);
} else if (!dp->td_slink->sl_sa) {
NDMP_LOG(LOG_DEBUG, "NULL dp->td_slink->sl_sa for (%d.%d)",
lib, drv);
} else {
/* search through the SASD table */
n = sasd_dev_count();
for (i = 0; i < n; i++) {
sl = sasd_dev_slink(i);
if (!sl)
continue;
if (dp->td_slink->sl_sa == sl->sl_sa &&
dp->td_scsi_id == sl->sl_sid &&
dp->td_lun == sl->sl_lun) {
/* all 3 variables match */
entry = i;
break;
}
}
}
return (entry);
}
/*
* get the OS device name for this tape
*/
char *
tlm_get_tape_name(int lib, int drv)
{
int entry;
entry = tlm_get_scsi_sasd_entry(lib, drv);
if (entry >= 0) {
sasd_drive_t *sd;
if ((sd = sasd_drive(entry)) != 0)
return (sd->sd_name);
}
return ("");
}
/*
* create the IPC area between the reader and writer
*/
tlm_cmd_t *
tlm_create_reader_writer_ipc(boolean_t write, long data_transfer_size)
{
tlm_cmd_t *cmd;
cmd = ndmp_malloc(sizeof (tlm_cmd_t));
if (cmd == NULL)
return (NULL);
cmd->tc_reader = TLM_BACKUP_RUN;
cmd->tc_writer = TLM_BACKUP_RUN;
cmd->tc_ref = 1;
cmd->tc_buffers = tlm_allocate_buffers(write, data_transfer_size);
if (cmd->tc_buffers == NULL) {
free(cmd);
return (NULL);
}
(void) mutex_init(&cmd->tc_mtx, 0, NULL);
(void) cond_init(&cmd->tc_cv, 0, NULL);
return (cmd);
}
/*
* release(destroy) the IPC between the reader and writer
*/
void
tlm_release_reader_writer_ipc(tlm_cmd_t *cmd)
{
if (--cmd->tc_ref <= 0) {
(void) mutex_lock(&cmd->tc_mtx);
tlm_release_buffers(cmd->tc_buffers);
(void) cond_destroy(&cmd->tc_cv);
(void) mutex_unlock(&cmd->tc_mtx);
(void) mutex_destroy(&cmd->tc_mtx);
free(cmd);
}
}
/*
* NDMP support begins here.
*/
/*
* Initialize the file history callback functions
*/
lbr_fhlog_call_backs_t *
lbrlog_callbacks_init(void *cookie, path_hist_func_t log_pname_func,
dir_hist_func_t log_dir_func, node_hist_func_t log_node_func)
{
lbr_fhlog_call_backs_t *p;
p = ndmp_malloc(sizeof (lbr_fhlog_call_backs_t));
if (p == NULL)
return (NULL);
p->fh_cookie = cookie;
p->fh_logpname = (func_t)log_pname_func;
p->fh_log_dir = (func_t)log_dir_func;
p->fh_log_node = (func_t)log_node_func;
return (p);
}
/*
* Cleanup the callbacks
*/
void
lbrlog_callbacks_done(lbr_fhlog_call_backs_t *p)
{
if (p != NULL)
(void) free((char *)p);
}
/*
* Call back for file history directory info
*/
int
tlm_log_fhdir(tlm_job_stats_t *job_stats, char *dir, struct stat64 *stp,
fs_fhandle_t *fhp)
{
int rv;
lbr_fhlog_call_backs_t *cbp; /* callbacks pointer */
rv = 0;
if (job_stats == NULL) {
NDMP_LOG(LOG_DEBUG, "log_fhdir: jstat is NULL");
} else if (dir == NULL) {
NDMP_LOG(LOG_DEBUG, "log_fhdir: dir is NULL");
} else if (stp == NULL) {
NDMP_LOG(LOG_DEBUG, "log_fhdir: stp is NULL");
} else if ((cbp = (lbr_fhlog_call_backs_t *)job_stats->js_callbacks)
== NULL) {
NDMP_LOG(LOG_DEBUG, "log_fhdir: cbp is NULL");
} else if (cbp->fh_log_dir == NULL) {
NDMP_LOG(LOG_DEBUG, "log_fhdir: callback is NULL");
} else
rv = (*cbp->fh_log_dir)(cbp, dir, stp, fhp);
return (rv);
}
/*
* Call back for file history node info
*/
int
tlm_log_fhnode(tlm_job_stats_t *job_stats, char *dir, char *file,
struct stat64 *stp, u_longlong_t off)
{
int rv;
lbr_fhlog_call_backs_t *cbp; /* callbacks pointer */
rv = 0;
if (job_stats == NULL) {
NDMP_LOG(LOG_DEBUG, "log_fhnode: jstat is NULL");
} else if (dir == NULL) {
NDMP_LOG(LOG_DEBUG, "log_fhnode: dir is NULL");
} else if (file == NULL) {
NDMP_LOG(LOG_DEBUG, "log_fhnode: file is NULL");
} else if (stp == NULL) {
NDMP_LOG(LOG_DEBUG, "log_fhnode: stp is NULL");
} else if ((cbp = (lbr_fhlog_call_backs_t *)job_stats->js_callbacks)
== NULL) {
NDMP_LOG(LOG_DEBUG, "log_fhnode: cbp is NULL");
} else if (cbp->fh_log_node == NULL) {
NDMP_LOG(LOG_DEBUG, "log_fhnode: callback is NULL");
} else
rv = (*cbp->fh_log_node)(cbp, dir, file, stp, off);
return (rv);
}
/*
* Call back for file history path info
*/
int
tlm_log_fhpath_name(tlm_job_stats_t *job_stats, char *pathname,
struct stat64 *stp, u_longlong_t off)
{
int rv;
lbr_fhlog_call_backs_t *cbp; /* callbacks pointer */
rv = 0;
if (!job_stats) {
NDMP_LOG(LOG_DEBUG, "log_fhpath_name: jstat is NULL");
} else if (!pathname) {
NDMP_LOG(LOG_DEBUG, "log_fhpath_name: pathname is NULL");
} else if (!stp) {
NDMP_LOG(LOG_DEBUG, "log_fhpath_name: stp is NULL");
} else if ((cbp = (lbr_fhlog_call_backs_t *)job_stats->js_callbacks)
== 0) {
NDMP_LOG(LOG_DEBUG, "log_fhpath_name: cbp is NULL");
} else if (!cbp->fh_logpname) {
NDMP_LOG(LOG_DEBUG, "log_fhpath_name: callback is NULL");
} else
rv = (*cbp->fh_logpname)(cbp, pathname, stp, off);
return (rv);
}
/*
* Log call back to report the entry recovery
*/
int
tlm_entry_restored(tlm_job_stats_t *job_stats, char *name, int pos)
{
lbr_fhlog_call_backs_t *cbp; /* callbacks pointer */
NDMP_LOG(LOG_DEBUG, "name: \"%s\", pos: %d", name, pos);
if (job_stats == NULL) {
NDMP_LOG(LOG_DEBUG, "entry_restored: jstat is NULL");
return (0);
}
cbp = (lbr_fhlog_call_backs_t *)job_stats->js_callbacks;
if (cbp == NULL) {
NDMP_LOG(LOG_DEBUG, "entry_restored is NULL");
return (0);
}
return (*cbp->fh_logpname)(cbp, name, 0, (longlong_t)pos);
}
/*
* NDMP support ends here.
*/
/*
* Function: tlm_cat_path
* Concatenates two path names
* or directory name and file name
* into a buffer passed by the caller. A slash
* is inserted if required. Buffer is assumed
* to hold PATH_MAX characters.
*
* Parameters:
* char *buf - buffer to write new dir/name string
* char *dir - directory name
* char *name - file name
*
* Returns:
* TRUE - No errors. buf contains the dir/name string
* FALSE - Error. buf is not modified.
*/
boolean_t
tlm_cat_path(char *buf, char *dir, char *name)
{
char *fmt;
int dirlen = strlen(dir);
int filelen = strlen(name);
if ((dirlen + filelen + 1) >= PATH_MAX) {
return (FALSE);
}
if (*dir == '\0' || *name == '\0' || dir[dirlen - 1] == '/' ||
*name == '/') {
fmt = "%s%s";
} else {
fmt = "%s/%s";
}
/* check for ".../" and "/...." */
if ((dirlen > 0) && (dir[dirlen - 1] == '/') && (*name == '/'))
name += strspn(name, "/");
/* LINTED variable format */
(void) snprintf(buf, TLM_MAX_PATH_NAME, fmt, dir, name);
return (TRUE);
}
/*
* Get the checkpoint (snapshot) creation time.
* This is necessary to check for checkpoints not being stale.
*/
int
tlm_get_chkpnt_time(char *path, int auto_checkpoint, time_t *tp, char *jname)
{
char volname[TLM_VOLNAME_MAX_LENGTH];
char chk_name[PATH_MAX];
char *cp_nm;
NDMP_LOG(LOG_DEBUG, "path [%s] auto_checkpoint: %d",
path, auto_checkpoint);
if (path == NULL || *path == '\0' || tp == NULL)
return (-1);
if (get_zfsvolname(volname, TLM_VOLNAME_MAX_LENGTH,
path) == -1)
return (-1);
if (auto_checkpoint) {
NDMP_LOG(LOG_DEBUG, "volname [%s]", volname);
(void) snprintf(chk_name, PATH_MAX, "%s", jname);
return (chkpnt_creationtime_bypattern(volname, chk_name, tp));
}
cp_nm = strchr(volname, '@');
NDMP_LOG(LOG_DEBUG, "volname [%s] cp_nm [%s]", volname, cp_nm);
return (chkpnt_creationtime_bypattern(volname, cp_nm, tp));
}
/*
* Release an array of pointers and the pointers themselves.
*/
void
tlm_release_list(char **lpp)
{
char **save;
if ((save = lpp) == 0)
return;
while (*lpp)
free(*lpp++);
free(save);
}
/*
* Print the list of array of strings in the backup log
*/
void
tlm_log_list(char *title, char **lpp)
{
int i;
if (!lpp)
return;
NDMP_LOG(LOG_DEBUG, "%s:", title);
for (i = 0; *lpp; lpp++, i++)
NDMP_LOG(LOG_DEBUG, "%d: [%s]", i, *lpp);
}
/*
* Insert the backup snapshot name into the path.
*
* Input:
* name: Original path name.
*
* Output:
* name: Original name modified to include a snapshot.
*
* Returns:
* Original name modified to include a snapshot.
*/
char *
tlm_build_snapshot_name(char *name, char *sname, char *jname)
{
zfs_handle_t *zhp;
char *rest;
char volname[ZFS_MAX_DATASET_NAME_LEN];
char mountpoint[PATH_MAX];
if (get_zfsvolname(volname, ZFS_MAX_DATASET_NAME_LEN, name) == -1)
goto notzfs;
(void) mutex_lock(&zlib_mtx);
if ((zlibh == NULL) ||
(zhp = zfs_open(zlibh, volname, ZFS_TYPE_DATASET)) == NULL) {
(void) mutex_unlock(&zlib_mtx);
goto notzfs;
}
if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, mountpoint, PATH_MAX, NULL,
NULL, 0, B_FALSE) != 0) {
zfs_close(zhp);
(void) mutex_unlock(&zlib_mtx);
goto notzfs;
}
zfs_close(zhp);
(void) mutex_unlock(&zlib_mtx);
rest = name + strlen(mountpoint);
(void) snprintf(sname, TLM_MAX_PATH_NAME, "%s/%s/%s%s", mountpoint,
TLM_SNAPSHOT_DIR, jname, rest);
return (sname);
notzfs:
(void) strlcpy(sname, name, TLM_MAX_PATH_NAME);
return (sname);
}
/*
* Remove the checkpoint from a path name.
*
* Input:
* name: Full pathname with checkpoint embeded.
*
* Output:
* unchkp_name: real pathname with no checkpoint.
*
* Returns:
* Pointer to the un-checkpointed path.
*/
char *
tlm_remove_checkpoint(char *name, char *unchkp_name)
{
char *cp;
int i;
int plen;
unchkp_name[0] = name[0];
plen = strlen(TLM_SNAPSHOT_PREFIX);
for (i = 1; i <= TLM_VOLNAME_MAX_LENGTH + 1; i++) {
switch (name[i]) {
case '.':
if (strncmp(&name[i], TLM_SNAPSHOT_PREFIX,
plen) == 0) {
unchkp_name[i] = '\0';
i += plen;
if (name[i] == '\0') {
/*
* name == "/v1.chkpnt"
*/
return (unchkp_name);
}
if ((cp = strchr(&name[++i], '/')) != NULL) {
(void) strlcat(unchkp_name, cp,
TLM_VOLNAME_MAX_LENGTH + 1);
}
return (unchkp_name);
} else {
unchkp_name[i] = name[i];
}
break;
case '/':
return (name);
case 0:
return (name);
default:
unchkp_name[i] = name[i];
break;
}
}
return (name);
}
/*
* see if we should exclude this file.
*/
boolean_t
tlm_is_excluded(char *dir, char *name, char **excl_files)
{
int i;
char full_name[TLM_MAX_PATH_NAME];
if (!dir || !name || !excl_files)
return (FALSE);
if (!tlm_cat_path(full_name, dir, name)) {
NDMP_LOG(LOG_DEBUG, "Path too long [%s][%s]",
dir, name);
return (FALSE);
}
for (i = 0; excl_files[i] != 0; i++) {
if (match(excl_files[i], full_name)) {
return (TRUE);
}
}
return (FALSE);
}
/*
* Check if the path is too long
*/
boolean_t
tlm_is_too_long(int checkpointed, char *dir, char *nm)
{
int nlen, tot;
tot = 0;
if (dir)
tot += strlen(dir);
if (checkpointed)
tot += strlen(TLM_SNAPSHOT_DIR) + 1;
if (nm) {
if ((nlen = strlen(nm)) > 0)
tot += nlen + 1;
}
return ((tot >= PATH_MAX) ? TRUE : FALSE);
}
/*
* Get the data offset of inside the buffer
*/
longlong_t
tlm_get_data_offset(tlm_cmd_t *lcmds)
{
if (!lcmds)
return (0LL);
return (lcmds->tc_buffers->tbs_offset);
}
/*
* Enable the barcode capability on the library
*/
void
tlm_enable_barcode(int l)
{
tlm_library_t *lp;
if ((lp = tlm_library(l))) {
lp->tl_capability_barcodes = TRUE;
NDMP_LOG(LOG_DEBUG,
"Barcode capability on library %d enabled.", l);
}
}
/*
* SASD SCSI support
*/
static scsi_adapter_t my_sa;
static int sasd_drive_count = 0;
static scsi_sasd_drive_t *scsi_sasd_drives[128];
/*
* Count of SCSI devices
*/
int
sasd_dev_count(void)
{
return (sasd_drive_count);
}
/*
* Return the SCSI device name
*/
char *
sasd_slink_name(scsi_link_t *slink)
{
int i;
for (i = 0; i < sasd_drive_count; i++) {
if (&scsi_sasd_drives[i]->ss_slink == slink)
return (scsi_sasd_drives[i]->ss_sd.sd_name);
}
return (NULL);
}
/*
* Return the SCSI drive structure
*/
sasd_drive_t *
sasd_slink_drive(scsi_link_t *slink)
{
int i;
for (i = 0; i < sasd_drive_count; i++) {
if (&scsi_sasd_drives[i]->ss_slink == slink)
return (&scsi_sasd_drives[i]->ss_sd);
}
return (NULL);
}
/*
* Return the SCSI link pointer for the given index
*/
scsi_link_t *
sasd_dev_slink(int entry)
{
scsi_link_t *rv;
if (entry >= 0 && entry < sasd_drive_count)
rv = &scsi_sasd_drives[entry]->ss_slink;
else
rv = NULL;
return (rv);
}
/*
* Return the SCSI drive for the given index
*/
sasd_drive_t *
sasd_drive(int entry)
{
sasd_drive_t *rv;
if (entry >= 0 && entry < sasd_drive_count)
rv = &scsi_sasd_drives[entry]->ss_sd;
else
rv = NULL;
return (rv);
}
/*
* Attach the SCSI device by updating the structures
*/
void
scsi_sasd_attach(scsi_adapter_t *sa, int sid, int lun, char *name,
int type)
{
scsi_link_t *sl, *next;
scsi_sasd_drive_t *ssd;
ssd = ndmp_malloc(sizeof (scsi_sasd_drive_t));
if (ssd == NULL)
return;
scsi_sasd_drives[sasd_drive_count++] = ssd;
switch (type) {
case DTYPE_CHANGER:
(void) snprintf(ssd->ss_sd.sd_name,
sizeof (ssd->ss_sd.sd_name), "%s/%s", SCSI_CHANGER_DIR,
name);
break;
case DTYPE_SEQUENTIAL:
(void) snprintf(ssd->ss_sd.sd_name,
sizeof (ssd->ss_sd.sd_name), "%s/%s", SCSI_TAPE_DIR, name);
break;
}
sl = &ssd->ss_slink;
sl->sl_type = type;
sl->sl_sa = sa;
sl->sl_lun = lun;
sl->sl_sid = sid;
sl->sl_requested_max_active = 1;
/* Insert slink */
next = sa->sa_link_head.sl_next;
sa->sa_link_head.sl_next = sl;
sl->sl_next = next;
}
/*
* Go through the attached devices and detect the tape
* and robot by checking the /dev entries
*/
int
probe_scsi(void)
{
DIR *dirp;
struct dirent *dp;
scsi_adapter_t *sa = &my_sa;
char *p;
int lun = 0;
int sid = 0;
char *drive_type;
/* Initialize the scsi adapter link */
sa->sa_link_head.sl_next = &sa->sa_link_head;
/* Scan for the changer */
dirp = opendir(SCSI_CHANGER_DIR);
if (dirp == NULL) {
NDMP_LOG(LOG_DEBUG,
"Changer directory read error %s", SCSI_CHANGER_DIR);
} else {
while ((dp = readdir(dirp)) != NULL) {
if ((strcmp(dp->d_name, ".") == 0) ||
(strcmp(dp->d_name, "..") == 0))
continue;
if ((p = strchr(dp->d_name, 'd')) != NULL) {
lun = atoi(++p);
p = strchr(dp->d_name, 't');
sid = atoi(++p);
}
else
sid = atoi(dp->d_name);
scsi_sasd_attach(sa, 0, lun, dp->d_name,
DTYPE_CHANGER);
}
(void) closedir(dirp);
}
/* Scan for tape drives */
dirp = opendir(SCSI_TAPE_DIR);
if (dirp == NULL) {
NDMP_LOG(LOG_DEBUG,
"Tape directory read error %s", SCSI_TAPE_DIR);
} else {
drive_type = ndmpd_get_prop(NDMP_DRIVE_TYPE);
if ((strcasecmp(drive_type, "sysv") != 0) &&
(strcasecmp(drive_type, "bsd") != 0)) {
NDMP_LOG(LOG_ERR, "Invalid ndmpd/drive-type value. "
"Valid values are 'sysv' and 'bsd'.");
return (-1);
}
while ((dp = readdir(dirp)) != NULL) {
if ((strcmp(dp->d_name, ".") == 0) ||
(strcmp(dp->d_name, "..") == 0))
continue;
/* Skip special modes */
if (strpbrk(dp->d_name, "chlmu") != NULL)
continue;
/* Pick the non-rewind device */
if (strchr(dp->d_name, 'n') == NULL)
continue;
if (strcasecmp(drive_type, "sysv") == 0) {
if (strchr(dp->d_name, 'b') != NULL)
continue;
} else if (strcasecmp(drive_type, "bsd") == 0) {
if (strchr(dp->d_name, 'b') == NULL)
continue;
}
sid = atoi(dp->d_name);
/*
* SCSI ID should match with the ID of the device
* (will be checked by SCSI get elements page later)
*/
scsi_sasd_attach(sa, sid, 0, dp->d_name,
DTYPE_SEQUENTIAL);
}
(void) closedir(dirp);
}
return (0);
}
/*
* Get the SCSI device type (tape, robot)
*/
/*ARGSUSED*/
int
scsi_get_devtype(char *adapter, int sid, int lun)
{
int rv;
scsi_adapter_t *sa = &my_sa;
scsi_link_t *sl, *sh;
rv = -1;
sh = &sa->sa_link_head;
for (sl = sh->sl_next; sl != sh; sl = sl->sl_next)
if (sl->sl_sid == sid && sl->sl_lun == lun)
rv = sl->sl_type;
return (rv);
}
/*
* Check if the SCSI device exists
*/
/*ARGSUSED*/
int
scsi_dev_exists(char *adapter, int sid, int lun)
{
scsi_adapter_t *sa = &my_sa;
scsi_link_t *sl, *sh;
sh = &sa->sa_link_head;
for (sl = sh->sl_next; sl != sh; sl = sl->sl_next)
if (sl->sl_sid == sid && sl->sl_lun == lun)
return (1);
return (0);
}
/*
* Count of SCSI adapters
*/
int
scsi_get_adapter_count(void)
{
/* Currently support one adapter only */
return (1);
}
/*
* Return the SCSI adapter structure
*/
/*ARGSUSED*/
scsi_adapter_t *
scsi_get_adapter(int adapter)
{
return (&my_sa);
}
/*
* IOCTL wrapper with retries
*/
int
tlm_ioctl(int fd, int cmd, void *data)
{
int retries = 0;
NDMP_LOG(LOG_DEBUG, "tlm_ioctl fd %d cmd %d", fd, cmd);
if (fd == 0 || data == NULL)
return (EINVAL);
do {
if (ioctl(fd, cmd, data) == 0)
break;
if (errno != EIO && errno != 0) {
NDMP_LOG(LOG_ERR,
"Failed to send command to device: %m.");
NDMP_LOG(LOG_DEBUG, "IOCTL error %d", errno);
return (errno);
}
(void) sleep(1);
} while (retries++ < MAXIORETRY);
return (0);
}
/*
* Checkpoint or snapshot calls
*/
/*
* Get the snapshot creation time
*/
int
chkpnt_creationtime_bypattern(char *volname, char *pattern, time_t *tp)
{
char chk_name[PATH_MAX];
zfs_handle_t *zhp;
char *p;
if (!volname || !*volname)
return (-1);
/* Should also return -1 if checkpoint not enabled */
/* Remove the leading slash */
p = volname;
while (*p == '/')
p++;
(void) strlcpy(chk_name, p, PATH_MAX);
(void) strlcat(chk_name, "@", PATH_MAX);
(void) strlcat(chk_name, pattern, PATH_MAX);
(void) mutex_lock(&zlib_mtx);
if ((zhp = zfs_open(zlibh, chk_name, ZFS_TYPE_DATASET)) == NULL) {
NDMP_LOG(LOG_DEBUG, "chkpnt_creationtime: open %s failed",
chk_name);
(void) mutex_unlock(&zlib_mtx);
return (-1);
}
*tp = zfs_prop_get_int(zhp, ZFS_PROP_CREATION);
zfs_close(zhp);
(void) mutex_unlock(&zlib_mtx);
return (0);
}
/*
* Get the ZFS volume name out of the given path
*/
int
get_zfsvolname(char *volname, int len, char *path)
{
struct stat64 stbuf;
struct extmnttab ent;
FILE *mntfp;
int rv;
*volname = '\0';
if (stat64(path, &stbuf) != 0) {
return (-1);
}
if ((mntfp = fopen(MNTTAB, "r")) == NULL) {
return (-1);
}
while ((rv = getextmntent(mntfp, &ent, 0)) == 0) {
if (makedevice(ent.mnt_major, ent.mnt_minor) ==
stbuf.st_dev)
break;
}
if (rv == 0 &&
strcmp(ent.mnt_fstype, MNTTYPE_ZFS) == 0)
(void) strlcpy(volname, ent.mnt_special, len);
else
rv = -1;
(void) fclose(mntfp);
return (rv);
}
/*
* Check if the volume type is snapshot volume
*/
boolean_t
fs_is_chkpntvol(char *path)
{
zfs_handle_t *zhp;
char vol[ZFS_MAX_DATASET_NAME_LEN];
if (!path || !*path)
return (FALSE);
if (get_zfsvolname(vol, sizeof (vol), path) == -1)
return (FALSE);
(void) mutex_lock(&zlib_mtx);
if ((zhp = zfs_open(zlibh, vol, ZFS_TYPE_DATASET)) == NULL) {
(void) mutex_unlock(&zlib_mtx);
return (FALSE);
}
if (zfs_get_type(zhp) != ZFS_TYPE_SNAPSHOT) {
zfs_close(zhp);
(void) mutex_unlock(&zlib_mtx);
return (FALSE);
}
zfs_close(zhp);
(void) mutex_unlock(&zlib_mtx);
return (TRUE);
}
/*
* Check if the volume is capable of checkpoints
*/
boolean_t
fs_is_chkpnt_enabled(char *path)
{
zfs_handle_t *zhp;
char vol[ZFS_MAX_DATASET_NAME_LEN];
if (!path || !*path)
return (FALSE);
(void) mutex_lock(&zlib_mtx);
if (get_zfsvolname(vol, sizeof (vol), path) == -1) {
(void) mutex_unlock(&zlib_mtx);
return (FALSE);
}
if ((zhp = zfs_open(zlibh, vol, ZFS_TYPE_DATASET)) == NULL) {
(void) mutex_unlock(&zlib_mtx);
return (FALSE);
}
zfs_close(zhp);
(void) mutex_unlock(&zlib_mtx);
return (TRUE);
}
/*
* Check if the volume is read-only
*/
boolean_t
fs_is_rdonly(char *path)
{
return (fs_is_chkpntvol(path));
}
/*
* Min/max functions
*/
unsigned
min(unsigned a, unsigned b)
{
return (a < b ? a : b);
}
unsigned
max(unsigned a, unsigned b)
{
return (a > b ? a : b);
}
longlong_t
llmin(longlong_t a, longlong_t b)
{
return (a < b ? a : b);
}