/* This program initializes Linux SCSI tape drives using the
   inquiry data from the devices and a text database.

   Maintained by Iustin Pop (iustin@k1024.org).
   Copyright 1996-2008 by Kai Mäkisara (Kai.Makisara@kolumbus.fi).
   Distribution of this program is allowed according to the
   GNU Public Licence.

*/

#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <linux/major.h>
#include <scsi/sg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <unistd.h>

#include "mtio.h"
#include "version.h"

#ifndef FALSE
#define TRUE 1
#define FALSE 0
#endif
#define SKIP_WHITE(p) for (; *p == ' ' || *p == '\t'; p++)

typedef struct _modepar_tr {
    int defined;
    int blocksize;
    int density;
    int buffer_writes;
    int async_writes;
    int read_ahead;
    int two_fm;
    int compression;
    int auto_lock;
    int fast_eod;
    int can_bsr;
    int no_blklimits;
    int can_partitions;
    int scsi2logical;
    int sysv;
    int defs_for_writes;
} modepar_tr;

typedef struct _devdef_tr {
    int do_rewind;
    int drive_buffering;
    int timeout;
    int long_timeout;
    int cleaning;
    int nowait;
    int weof_nowait;
    int sili;
    modepar_tr modedefs[4];
} devdef_tr;


#define DEFMAX 2048
#define LINEMAX 256

#define MAX_TAPES 32
#define NBR_MODES 4


static int verbose = 0;

/* The device directories being searched */
typedef struct {
    char dir[PATH_MAX];
    int selective_scan;
} devdir;
static devdir devdirs[] = { { "/dev/scsi", 0 }, { "/dev", 1 }, { "", 0 } };

#define DEVFS_PATH "/dev/tapes"
#define DEVFS_TAPEFMT DEVFS_PATH "/tape%d"

/* The partial names of the tape devices being included in the
   search in selective scan */
static char *tape_name_bases[] = { "st", "nst", "rmt", "nrmt", "tape", NULL };

/* The list of standard definition files being searched */
static char *std_databases[] = { "/etc/stinit.def", NULL };

static char usage(int retval) __attribute__((noreturn));

static FILE *open_database(char *base)
{
    int i;
    FILE *f;

    if (base != NULL) {
        if ((f = fopen(base, "r")) == NULL)
            fprintf(stderr, "stinit: Can't find SCSI tape database '%s'.\n", base);
        return f;
    }

    for (i = 0; std_databases[i] != NULL; i++) {
        if (verbose > 1)
            fprintf(stderr, "Trying to open database '%s'.\n", std_databases[i]);
        if ((f = fopen(std_databases[i], "r")) != NULL) {
            if (verbose > 1)
                fprintf(stderr, "Open succeeded.\n");
            return f;
        }
    }
    fprintf(stderr, "Can't find the tape characteristics database.\n");

    return NULL;
}


static char *find_string(char *s, char *target, char *buf, int buflen)
{
    int have_arg;
    char *cp, *cp2, c, *argp;

    if (buf != NULL && buflen > 0)
        *buf = '\0';

    for (; *s != '\0';) {
        SKIP_WHITE(s);
        if (isalpha(*s)) {
            for (cp = s; isalnum(*cp) || *cp == '-'; cp++)
                ;
            cp2 = cp;
            SKIP_WHITE(cp);
            if (*cp == '=') {
                cp++;
                SKIP_WHITE(cp);
                if (*cp == '"') {
                    cp++;
                    for (cp2 = cp; *cp2 != '"' && *cp2 != '\0'; cp2++)
                        ;
                } else {
                    if (*cp == '\0')
                        return NULL;
                    else
                        for (cp2 = cp + 1; isalnum(*cp2) || *cp2 == '-'; cp2++)
                            ;
                }
                if (*cp2 == '\0')
                    return NULL;
                have_arg = TRUE;
                argp = cp;
            } else {
                have_arg = FALSE;
                argp = "1";
            }

            if (!strncmp(target, s, strlen(target))) {
                c = *cp2;
                *cp2 = '\0';
                if (buf == NULL)
                    buf = strdup(argp);
                else {
                    if (strlen(argp) < (unsigned int)buflen)
                        strcpy(buf, argp);
                    else {
                        strncpy(buf, argp, buflen);
                        buf[buflen - 1] = '\0';
                    }
                }
                if (have_arg && c == '"')
                    cp2++;
                else
                    *cp2 = c;
                if (*cp2 != '\0')
                    memmove(s, cp2, strlen(cp2) + 1);
                else
                    *s = '\0';
                return buf;
            }
            s = cp2;
        } else
            for (; *s != '\0' && *s != ' ' && *s != '\t'; s++)
                ;
    }
    return NULL;
}


static int num_arg(char *t)
{
    int nbr;
    char *tt;

    nbr = strtol(t, &tt, 0);
    if (t != tt) {
        if (*tt == 'k')
            nbr *= 1024;
        else if (*tt == 'M')
            nbr *= 1024 * 1024;
    }
    return nbr;
}


static int next_block(FILE *dbf, char *buf, size_t buflen, int limiter)
{
    size_t len;
    char *cp, *bp;
    static char lbuf[LINEMAX];

    if (limiter == 0) { /* Restart */
        rewind(dbf);
        lbuf[0] = '\0';
        return TRUE;
    }

    for (len = 0;;) {
        bp = buf + len;
        if ((cp = strchr(lbuf, limiter)) != NULL) {
            *cp = '\0';
            strcpy(bp, lbuf);
            cp++;
            SKIP_WHITE(cp);
            memmove(lbuf, cp, strlen(cp) + 1);
            return TRUE;
        }
        if (len + strlen(lbuf) >= buflen) {
            fprintf(stderr, "Too long definition: '%s'\n", buf);
            return FALSE;
        }
        cp = lbuf;
        SKIP_WHITE(cp);
        strcpy(bp, cp);
        strcat(bp, " ");
        len += strlen(cp) + 1;
        if (fgets(lbuf, LINEMAX, dbf) == NULL)
            return FALSE;
        if ((cp = strchr(lbuf, '#')) != NULL)
            *cp = '\0';
        else
            lbuf[strlen(lbuf) - 1] = '\0';
    }
}


static int
find_pars(FILE *dbf, char *company, char *product, char *rev, devdef_tr *defs, int parse_only)
{
    int i, mode, modes_defined, errors;
    char line[LINEMAX], defstr[DEFMAX], comdef[DEFMAX], modebuf[DEFMAX];
    char tmpcomp[LINEMAX], tmpprod[LINEMAX], tmprev[LINEMAX], *cp, c, *t;
    char *nextdef, *curdef, *comptr;
    static int call_nbr = 0;

    call_nbr++;
    defs->drive_buffering = (-1);
    defs->timeout = (-1);
    defs->long_timeout = (-1);
    defs->cleaning = (-1);
    defs->nowait = (-1);
    defs->weof_nowait = (-1);
    defs->sili = (-1);
    for (i = 0; i < NBR_MODES; i++) {
        defs->modedefs[i].defined = FALSE;
        defs->modedefs[i].blocksize = (-1);
        defs->modedefs[i].density = (-1);
        defs->modedefs[i].buffer_writes = (-1);
        defs->modedefs[i].async_writes = (-1);
        defs->modedefs[i].read_ahead = (-1);
        defs->modedefs[i].two_fm = (-1);
        defs->modedefs[i].compression = (-1);
        defs->modedefs[i].auto_lock = (-1);
        defs->modedefs[i].fast_eod = (-1);
        defs->modedefs[i].can_bsr = (-1);
        defs->modedefs[i].no_blklimits = (-1);
        defs->modedefs[i].can_partitions = (-1);
        defs->modedefs[i].scsi2logical = (-1);
        defs->modedefs[i].sysv = (-1);
        defs->modedefs[i].defs_for_writes = (-1);
    }
    next_block(dbf, NULL, 0, 0);

    /* Find matching inquiry block */
    for (errors = 0;;) {

        if (!next_block(dbf, defstr, DEFMAX, '{'))
            break;

        find_string(defstr, "manuf", tmpcomp, LINEMAX);
        find_string(defstr, "model", tmpprod, LINEMAX);
        find_string(defstr, "rev", tmprev, LINEMAX);

        if (!next_block(dbf, defstr, DEFMAX, '}')) {
            fprintf(stderr,
                    "End of definition block not found for ('%s', "
                    "'%s', '%s').\n",
                    tmpcomp, tmpprod, tmprev);
            return FALSE;
        }

        if (!parse_only) {
            if (tmpcomp[0] != '\0' && strncmp(company, tmpcomp, strlen(tmpcomp)))
                continue;
            if (tmpprod[0] != '\0' && strncmp(product, tmpprod, strlen(tmpprod)))
                continue;
            if (tmprev[0] != '\0' && strncmp(rev, tmprev, strlen(tmprev)))
                continue;
        } else if (verbose > 0)
            printf("\nParsing modes for ('%s', '%s', '%s').\n", tmpcomp, tmpprod, tmprev);

        /* Block found, get the characteristics */
        for (nextdef = defstr;
             *nextdef != '\0' && (*nextdef != 'm' || strncmp(nextdef, "mode", 2)); nextdef++)
            ;
        c = *nextdef;
        *nextdef = '\0';
        strcpy(comdef, defstr);
        *nextdef = c;
        comptr = comdef;
        SKIP_WHITE(comptr);

        for (; *nextdef != '\0';) {
            int tmpsize;
            curdef = nextdef;
            SKIP_WHITE(curdef);
            for (nextdef++;
                 *nextdef != '\0' && (*nextdef != 'm' || strncmp(nextdef, "mode", 2));
                 nextdef++)
                ;
            c = *nextdef;
            *nextdef = '\0';

            mode = strtol(curdef + 4, &cp, 0) - 1;
            if (mode < 0 || mode >= NBR_MODES) {
                fprintf(stderr, "Illegal mode for ('%s', '%s', '%s'):\n'%s'\n",
                        tmpcomp, tmpprod, tmprev, curdef);
                *nextdef = c;
                errors++;
                continue;
            }

            if ((tmpsize = snprintf(modebuf, DEFMAX, "%s%s", comptr, cp)) >= DEFMAX) {
                fprintf(stderr, "Definition too large for ('%s', '%s', '%s'): size %d, maximum is %d\n",
                        tmpcomp, tmpprod, tmprev, tmpsize, DEFMAX);
                *nextdef = c;
                errors++;
                continue;
            }

            *nextdef = c;

            if (verbose > 1)
                fprintf(stderr, "Mode %d definition: %s\n", mode + 1, modebuf);

            if ((t = find_string(modebuf, "disab", line, LINEMAX)) != NULL &&
                strtol(t, NULL, 0) != 0) {
                defs->modedefs[mode].defined = FALSE;
                continue;
            }

            if ((t = find_string(modebuf, "drive-", line, LINEMAX)) != NULL)
                defs->drive_buffering = num_arg(t);
            if ((t = find_string(modebuf, "timeout", line, LINEMAX)) != NULL)
                defs->timeout = num_arg(t);
            if ((t = find_string(modebuf, "long-time", line, LINEMAX)) != NULL)
                defs->long_timeout = num_arg(t);
            if ((t = find_string(modebuf, "clean", line, LINEMAX)) != NULL)
                defs->cleaning = num_arg(t);
            if ((t = find_string(modebuf, "no-w", line, LINEMAX)) != NULL)
                defs->nowait = num_arg(t);
            if ((t = find_string(modebuf, "weof-n", line, LINEMAX)) != NULL)
                defs->weof_nowait = num_arg(t);
            if ((t = find_string(modebuf, "sili", line, LINEMAX)) != NULL)
                defs->sili = num_arg(t);

            defs->modedefs[mode].defined = TRUE;
            if ((t = find_string(modebuf, "block", line, LINEMAX)) != NULL)
                defs->modedefs[mode].blocksize = num_arg(t);
            if ((t = find_string(modebuf, "dens", line, LINEMAX)) != NULL)
                defs->modedefs[mode].density = num_arg(t);
            if ((t = find_string(modebuf, "buff", line, LINEMAX)) != NULL)
                defs->modedefs[mode].buffer_writes = num_arg(t);
            if ((t = find_string(modebuf, "async", line, LINEMAX)) != NULL)
                defs->modedefs[mode].async_writes = num_arg(t);
            if ((t = find_string(modebuf, "read", line, LINEMAX)) != NULL)
                defs->modedefs[mode].read_ahead = num_arg(t);
            if ((t = find_string(modebuf, "two", line, LINEMAX)) != NULL)
                defs->modedefs[mode].two_fm = num_arg(t);
            if ((t = find_string(modebuf, "comp", line, LINEMAX)) != NULL)
                defs->modedefs[mode].compression = num_arg(t);
            if ((t = find_string(modebuf, "auto", line, LINEMAX)) != NULL)
                defs->modedefs[mode].auto_lock = num_arg(t);
            if ((t = find_string(modebuf, "fast", line, LINEMAX)) != NULL)
                defs->modedefs[mode].fast_eod = num_arg(t);
            if ((t = find_string(modebuf, "can-b", line, LINEMAX)) != NULL)
                defs->modedefs[mode].can_bsr = num_arg(t);
            if ((t = find_string(modebuf, "noblk", line, LINEMAX)) != NULL)
                defs->modedefs[mode].no_blklimits = num_arg(t);
            if ((t = find_string(modebuf, "can-p", line, LINEMAX)) != NULL)
                defs->modedefs[mode].can_partitions = num_arg(t);
            if ((t = find_string(modebuf, "scsi2", line, LINEMAX)) != NULL)
                defs->modedefs[mode].scsi2logical = num_arg(t);
            if ((t = find_string(modebuf, "sysv", line, LINEMAX)) != NULL)
                defs->modedefs[mode].sysv = num_arg(t);
            if ((t = find_string(modebuf, "defs-for-w", line, LINEMAX)) != NULL)
                defs->modedefs[mode].defs_for_writes = num_arg(t);

            for (t = modebuf; *t == ' ' || *t == '\t'; t++)
                ;
            if (*t != '\0' && call_nbr <= 1) {
                fprintf(stderr,
                        "Warning: errors in definition for ('%s', "
                        "'%s', '%s'):\n%s\n",
                        tmpcomp, tmpprod, tmprev, modebuf);
                errors++;
            }
        }
    }

    if (parse_only) {
        if (verbose > 0)
            printf("\n");
        printf("Definition parse completed. ");
        if (errors) {
            printf("Errors found!\n");
            return FALSE;
        } else {
            printf("No errors found.\n");
            return TRUE;
        }
    } else {
        for (i = modes_defined = 0; i < NBR_MODES; i++)
            if (defs->modedefs[i].defined)
                modes_defined++;
        if (modes_defined == 0) {
            fprintf(stderr, "Warning: No modes in definition for ('%s', '%s', '%s').\n",
                    tmpcomp, tmpprod, tmprev);
            errors++;
        }
    }

    if (modes_defined)
        return TRUE;
    return FALSE;
}


static int sg_io_errcheck(struct sg_io_hdr *hdp)
{
    if ((hdp->status & 0x7e) == 0 || hdp->host_status == 0 || hdp->driver_status == 0)
        return 0;
    return EIO;
}


#define INQUIRY 0x12
#define INQUIRY_CMDLEN 6
#define SENSE_BUFF_LEN 32
#define DEF_TIMEOUT 60000

#ifndef SCSI_IOCTL_SEND_COMMAND
#define SCSI_IOCTL_SEND_COMMAND 1
#endif
#define IOCTL_HEADER_LENGTH 8

static int do_inquiry(char *tname, char *company, char *product, char *rev, int print_non_found)
{
    int fn;
    int result, *ip, i;
#define BUFLEN 256
    unsigned char buffer[BUFLEN], *cmd, *inqptr;
    struct sg_io_hdr io_hdr;
    unsigned char inqCmdBlk[INQUIRY_CMDLEN] = { INQUIRY, 0, 0, 0, 200, 0 };
    unsigned char sense_b[SENSE_BUFF_LEN];

    if ((fn = open(tname, O_RDONLY | O_NONBLOCK)) < 0) {
        if (print_non_found || verbose > 0) {
            if (errno == ENXIO)
                fprintf(stderr, "Device '%s' not found by kernel.\n", tname);
            else
                fprintf(stderr, "Can't open tape device '%s' (errno %d).\n", tname, errno);
        }
        return FALSE;
    }

    /* Try SG_IO first, if it is not supported, use SCSI_IOCTL_SEND_COMMAND */
    memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
    io_hdr.interface_id = 'S';
    io_hdr.cmd_len = sizeof(inqCmdBlk);
    io_hdr.mx_sb_len = sizeof(sense_b);
    io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
    io_hdr.dxfer_len = 200;
    io_hdr.dxferp = buffer;
    io_hdr.cmdp = inqCmdBlk;
    io_hdr.sbp = sense_b;
    io_hdr.timeout = DEF_TIMEOUT;
    inqptr = buffer;

    result = ioctl(fn, SG_IO, &io_hdr);
    if (!result)
        result = sg_io_errcheck(&io_hdr);
    if (result) {
        if (errno == ENOTTY || errno == EINVAL) {
            memset(buffer, 0, BUFLEN);
            ip = (int *)&(buffer[0]);
            *ip = 0;
            *(ip + 1) = BUFLEN - 13;

            cmd = &(buffer[8]);
            cmd[0] = INQUIRY;
            cmd[4] = 200;

            result = ioctl(fn, SCSI_IOCTL_SEND_COMMAND, buffer);
            inqptr = buffer + IOCTL_HEADER_LENGTH;
        }
        if (result) {
            close(fn);
            sprintf((char *)buffer,
                    "The SCSI INQUIRY for device '%s' failed (power off?)", tname);
            perror((char *)buffer);
            return FALSE;
        }
    }

    memcpy(company, inqptr + 8, 8);
    for (i = 8; i > 0 && company[i - 1] == ' '; i--)
        ;
    company[i] = '\0';
    memcpy(product, inqptr + 16, 16);
    for (i = 16; i > 0 && product[i - 1] == ' '; i--)
        ;
    product[i] = '\0';
    memcpy(rev, inqptr + 32, 4);
    for (i = 4; i > 0 && rev[i - 1] == ' '; i--)
        ;
    rev[i] = '\0';

    close(fn);
    return TRUE;
}


static int tapenum(char *name)
{
    int dev;
    struct dirent *dent;
    DIR *dirp;
    char tmpname[PATH_MAX];
    const char *dn;
    const devdir *dvd;
    struct stat statbuf;

    if (strchr(name, '/') != NULL) { /* Complete name */
        if (stat(name, &statbuf) != 0) {
            fprintf(stderr, "Can't stat '%s'.\n", name);
            return (-1);
        }
        if (!S_ISCHR(statbuf.st_mode) || major(statbuf.st_rdev) != SCSI_TAPE_MAJOR)
            return (-1);
        dev = minor(statbuf.st_rdev) & 31;
        return dev;
    } else { /* Search from the device directories */
        for (dvd = devdirs; dvd->dir[0] != 0; dvd++) {
            dn = dvd->dir;
            if ((dirp = opendir(dn)) == NULL)
                continue;

            for (; (dent = readdir(dirp)) != NULL;)
                if (!strcmp(dent->d_name, name)) {
                    strcpy(tmpname, dn);
                    strcat(tmpname, "/");
                    strcat(tmpname, dent->d_name);
                    if (stat(tmpname, &statbuf) != 0) {
                        fprintf(stderr, "Can't stat '%s'.\n", tmpname);
                        continue;
                    }
                    if (!S_ISCHR(statbuf.st_mode) || major(statbuf.st_rdev) != SCSI_TAPE_MAJOR)
                        continue;
                    dev = minor(statbuf.st_rdev) & 31;
                    closedir(dirp);
                    return dev;
                }
            closedir(dirp);
        }
    }

    return (-1);
}


static int accept_tape_name(char *name)
{
    char **npp;

    for (npp = tape_name_bases; *npp; npp++)
        if (!strncmp(name, *npp, strlen(*npp)))
            return TRUE;
    return FALSE;
}


static int find_devfiles(int tapeno, char **names)
{
    int dev, mode, found;
    int non_rew[NBR_MODES];
    struct dirent *dent;
    DIR *dirp;
    char tmpname[PATH_MAX];
    const char *dn;
    static const devdir *dvd;
    const devdir *dvp;
    int tmpdevdirsindex = 0;
    static devdir tmpdevdirs[MAX_TAPES + 1];
    struct stat statbuf;

    for (found = 0; found < NBR_MODES; found++) {
        *names[found] = '\0';
        non_rew[found] = FALSE;
    }

    if (dvd == NULL && !stat(DEVFS_PATH, &statbuf)) {
        if (S_ISDIR(statbuf.st_mode) && (dirp = opendir(DEVFS_PATH)) != NULL) {
            /* Assume devfs, one directory for each tape */
            for (; (dent = readdir(dirp)) != NULL;) {
                if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, ".."))
                    continue;
                snprintf(tmpdevdirs[tmpdevdirsindex].dir,
                         sizeof(tmpdevdirs[tmpdevdirsindex].dir), "%s/%s",
                         DEVFS_PATH, dent->d_name);
                tmpdevdirs[tmpdevdirsindex].selective_scan = FALSE;
                if (++tmpdevdirsindex == MAX_TAPES)
                    break;
            }
            tmpdevdirs[tmpdevdirsindex].dir[0] = 0;
            closedir(dirp);
            dvd = &tmpdevdirs[0];
        }
    }
    if (dvd == NULL)
        dvd = devdirs;

    for (found = 0, dvp = dvd; found < NBR_MODES && dvp->dir[0] != 0; dvp++) {
        dn = dvp->dir;
        if ((dirp = opendir(dn)) == NULL)
            continue;

        for (; (dent = readdir(dirp)) != NULL;) {
            if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, ".."))
                continue;
            /* Ignore non-tape devices to avoid loading all the modules */
            if (dvp->selective_scan && !accept_tape_name(dent->d_name))
                continue;
            strcpy(tmpname, dn);
            strcat(tmpname, "/");
            strcat(tmpname, dent->d_name);
            if (stat(tmpname, &statbuf) != 0) {
                fprintf(stderr, "Can't stat '%s'.\n", tmpname);
                continue;
            }
            if (!S_ISCHR(statbuf.st_mode) || major(statbuf.st_rdev) != SCSI_TAPE_MAJOR)
                continue;
            dev = minor(statbuf.st_rdev);
            if ((dev & 31) != tapeno)
                continue;
            mode = (dev & 127) >> 5;
            if (non_rew[mode])
                continue;
            if (*names[mode] == '\0')
                found++;
            strcpy(names[mode], tmpname);
            non_rew[mode] = (dev & 128) != 0;
        }
        closedir(dirp);
    }

    return (found > 0);
}


static int set_defs(devdef_tr *defs, char **fnames)
{
    int i, tape, fails;
    int clear_set[2];
    struct mtop op;

    for (i = fails = 0; i < NBR_MODES; i++) {
        if (*fnames[i] == '\0' || !defs->modedefs[i].defined)
            continue;

        if ((tape = open(fnames[i], O_RDONLY | O_NONBLOCK)) < 0) {
            fprintf(stderr, "Can't open the tape device '%s' for mode %d.\n",
                    fnames[i], i);
            return FALSE;
        }

        if (i == 0) {
            if (defs->do_rewind) {
                op.mt_op = MTREW;
                op.mt_count = 1;
                if (ioctl(tape, MTIOCTOP, &op) != 0) {
                    fails++;
                    fprintf(stderr, "Rewind of %s fails.\n", fnames[i]);
                }
            }

            if (defs->drive_buffering >= 0) {
                op.mt_op = MTSETDRVBUFFER;
                op.mt_count = MT_ST_DEF_DRVBUFFER | defs->drive_buffering;
                if (ioctl(tape, MTIOCTOP, &op) != 0) {
                    fails++;
                    fprintf(stderr, "Can't set drive buffering to %d.\n", defs->drive_buffering);
                }
            }

            if (defs->timeout >= 0) {
                op.mt_op = MTSETDRVBUFFER;
                op.mt_count = MT_ST_SET_TIMEOUT | defs->timeout;
                if (ioctl(tape, MTIOCTOP, &op) != 0) {
                    fails++;
                    fprintf(stderr, "Can't set device timeout %d s.\n", defs->timeout);
                }
            }

            if (defs->long_timeout >= 0) {
                op.mt_op = MTSETDRVBUFFER;
                op.mt_count = MT_ST_SET_LONG_TIMEOUT | defs->long_timeout;
                if (ioctl(tape, MTIOCTOP, &op) != 0) {
                    fails++;
                    fprintf(stderr, "Can't set device long timeout %d s.\n",
                            defs->long_timeout);
                }
            }

            if (defs->cleaning >= 0) {
                op.mt_op = MTSETDRVBUFFER;
                op.mt_count = MT_ST_SET_CLN | defs->cleaning;
                if (ioctl(tape, MTIOCTOP, &op) != 0) {
                    fails++;
                    fprintf(stderr, "Can't set cleaning request parameter to %x\n",
                            defs->cleaning);
                }
            }
        }

        op.mt_op = MTSETDRVBUFFER;

        clear_set[0] = clear_set[1] = 0;
        if (defs->nowait >= 0)
            clear_set[defs->nowait != 0] |= MT_ST_NOWAIT;
        if (defs->weof_nowait >= 0)
            clear_set[defs->weof_nowait != 0] |= MT_ST_NOWAIT_EOF;
        if (defs->sili >= 0)
            clear_set[defs->sili != 0] |= MT_ST_SILI;
        if (defs->modedefs[i].buffer_writes >= 0)
            clear_set[defs->modedefs[i].buffer_writes != 0] |= MT_ST_BUFFER_WRITES;
        if (defs->modedefs[i].async_writes >= 0)
            clear_set[defs->modedefs[i].async_writes != 0] |= MT_ST_ASYNC_WRITES;
        if (defs->modedefs[i].read_ahead >= 0)
            clear_set[defs->modedefs[i].read_ahead != 0] |= MT_ST_READ_AHEAD;
        if (defs->modedefs[i].two_fm >= 0)
            clear_set[defs->modedefs[i].two_fm != 0] |= MT_ST_TWO_FM;
        if (defs->modedefs[i].fast_eod >= 0)
            clear_set[defs->modedefs[i].fast_eod != 0] |= MT_ST_FAST_MTEOM;
        if (defs->modedefs[i].auto_lock >= 0)
            clear_set[defs->modedefs[i].auto_lock != 0] |= MT_ST_AUTO_LOCK;
        if (defs->modedefs[i].can_bsr >= 0)
            clear_set[defs->modedefs[i].can_bsr != 0] |= MT_ST_CAN_BSR;
        if (defs->modedefs[i].no_blklimits >= 0)
            clear_set[defs->modedefs[i].no_blklimits != 0] |= MT_ST_NO_BLKLIMS;
        if (defs->modedefs[i].can_partitions >= 0)
            clear_set[defs->modedefs[i].can_partitions != 0] |= MT_ST_CAN_PARTITIONS;
        if (defs->modedefs[i].scsi2logical >= 0)
            clear_set[defs->modedefs[i].scsi2logical != 0] |= MT_ST_SCSI2LOGICAL;
        if (defs->modedefs[i].sysv >= 0)
            clear_set[defs->modedefs[i].sysv != 0] |= MT_ST_SYSV;
        if (defs->modedefs[i].defs_for_writes >= 0)
            clear_set[defs->modedefs[i].defs_for_writes != 0] |= MT_ST_DEF_WRITES;

        if (clear_set[0] != 0) {
            op.mt_count = MT_ST_CLEARBOOLEANS | clear_set[0];
            if (ioctl(tape, MTIOCTOP, &op) != 0) {
                fails++;
                fprintf(stderr, "Can't clear the tape options (bits 0x%x, mode %d).\n",
                        clear_set[0], i);
            }
        }
        if (clear_set[1] != 0) {
            op.mt_count = MT_ST_SETBOOLEANS | clear_set[1];
            if (ioctl(tape, MTIOCTOP, &op) != 0) {
                fails++;
                fprintf(stderr, "Can't set the tape options (bits 0x%x, mode %d).\n",
                        clear_set[1], i);
            }
        }

        if (defs->modedefs[i].blocksize >= 0) {
            op.mt_count = MT_ST_DEF_BLKSIZE | defs->modedefs[i].blocksize;
            if (ioctl(tape, MTIOCTOP, &op) != 0) {
                fails++;
                fprintf(stderr, "Can't set blocksize %d for mode %d.\n",
                        defs->modedefs[i].blocksize, i);
            }
        }
        if (defs->modedefs[i].density >= 0) {
            op.mt_count = MT_ST_DEF_DENSITY | defs->modedefs[i].density;
            if (ioctl(tape, MTIOCTOP, &op) != 0) {
                fails++;
                fprintf(stderr, "Can't set density %x for mode %d.\n",
                        defs->modedefs[i].density, i);
            }
        }
        if (defs->modedefs[i].compression >= 0) {
            op.mt_count = MT_ST_DEF_COMPRESSION | defs->modedefs[i].compression;
            if (ioctl(tape, MTIOCTOP, &op) != 0) {
                fails++;
                fprintf(stderr, "Can't set compression %d for mode %d.\n",
                        defs->modedefs[i].compression, i);
            }
        }

        close(tape);
    }
    return (fails == 0);
}


static int define_tape(int tapeno, FILE *dbf, devdef_tr *defptr, int print_non_found)
{
    int i;
    char company[10], product[20], rev[5], *tname, *fnames[NBR_MODES];

    if (verbose > 0)
        printf("\nstinit, processing tape %d\n", tapeno);

    if ((fnames[0] = calloc(NBR_MODES, PATH_MAX)) == NULL) {
        fprintf(stderr, "Can't allocate name buffers.\n");
        return FALSE;
    }
    for (i = 1; i < NBR_MODES; i++)
        fnames[i] = fnames[i - 1] + PATH_MAX;

    if (!find_devfiles(tapeno, fnames) || *fnames[0] == '\0') {
        if (print_non_found)
            fprintf(stderr, "Can't find any device files for tape %d.\n", tapeno);
        free(fnames[0]);
        return FALSE;
    }
    if (verbose > 1)
        for (i = 0; i < NBR_MODES; i++)
            printf("Mode %d, name '%s'\n", i + 1, fnames[i]);

    tname = fnames[0];
    if (!do_inquiry(tname, company, product, rev, print_non_found)) {
        free(fnames[0]);
        return FALSE;
    }
    if (verbose > 0)
        printf("The manufacturer is '%s', product is '%s', and revision "
               "'%s'.\n",
               company, product, rev);

    if (!find_pars(dbf, company, product, rev, defptr, FALSE)) {
        fprintf(stderr, "Can't find defaults for tape number %d.\n", tapeno);
        free(fnames[0]);
        return FALSE;
    }

    if (!set_defs(defptr, fnames)) {
        free(fnames[0]);
        return FALSE;
    }

    free(fnames[0]);
    return TRUE;
}


static char usage(int retval)
{
    fprintf(stderr, "Usage: stinit [-h] [-v] [--version] [-f dbname] [-p] [-r] "
                    "[drivename_or_number ...]\n");
    exit(retval);
}


int main(int argc, char **argv)
{
    FILE *dbf = NULL;
    int argn, retval = 0;
    int tapeno, parse_only = FALSE;
    char *dbname = NULL;
    char *convp;
    devdef_tr defs;

    defs.do_rewind = FALSE;
    for (argn = 1; argn < argc && *argv[argn] == '-'; argn++) {
        if (*(argv[argn] + 1) == 'v')
            verbose++;
        else if (*(argv[argn] + 1) == 'p')
            parse_only = TRUE;
        else if (*(argv[argn] + 1) == 'h')
            usage(0);
        else if (*(argv[argn] + 1) == 'r')
            defs.do_rewind = TRUE;
        else if (*(argv[argn] + 1) == 'f') {
            argn += 1;
            if (argn >= argc)
                usage(1);
            dbname = argv[argn];
        } else if (*(argv[argn] + 1) == '-' && *(argv[argn] + 2) == 'v') {
            printf("stinit v. %s\n", VERSION);
            exit(0);
            break;
        } else
            usage(1);
    }

    if ((dbf = open_database(dbname)) == NULL)
        return 1;

    if (parse_only) {
        if (argc > argn)
            fprintf(stderr, "Extra arguments on command line ignored.\n");
        if (!find_pars(dbf, "xyz", "xyz", "xyz", &defs, TRUE))
            return 1;
        return 0;
    }

    if (argc > argn) { /* Initialize specific drives */
        for (; argn < argc; argn++) {
            if (*argv[argn] == '-') {
                usage(1);
            } else if (isdigit(*argv[argn])) {
                tapeno = strtol(argv[argn], &convp, 0);
                if (*argv[argn] != '\0' && *convp != '\0') {
                    fprintf(stderr,
                            "Invalid tape device index '%s': don't "
                            "know how to parse '%s'\n",
                            argv[argn], convp);
                    continue;
                }
            } else if ((tapeno = tapenum(argv[argn])) < 0) {
                fprintf(stderr, "Can't find tape number for name '%s'.\n", argv[argn]);
                continue;
            }
            if (!define_tape(tapeno, dbf, &defs, TRUE)) {
                fprintf(stderr, "Definition for '%s' failed.\n", argv[argn]);
                retval = 1;
            }
        }
    } else { /* Initialize all SCSI tapes */
        for (tapeno = 0; tapeno < MAX_TAPES; tapeno++)
            if (!define_tape(tapeno, dbf, &defs, FALSE)) {
                fprintf(stderr, "Initialized %d tape device%s.\n", tapeno,
                        (tapeno != 1 ? "s" : ""));
                return 0; /* Process tapes until failure */
            }
    }

    return retval;
}
