/*
 * Copyright (c) 2005-2020 Douglas Gilbert.
 * All rights reserved.
 * Use of this source code is governed by a BSD-style
 * license that can be found in the BSD_LICENSE file.
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

/* A utility program for the Linux OS SCSI subsystem.
 *
 *
 * This program maps a primary SCSI device node name to the corresponding
 * SCSI generic device node name (or vice versa). Targets linux
 * kernel 2.6, 3 and 4 series. Sysfs device names can also be mapped.
 */

/* #define _XOPEN_SOURCE 500 */
/* needed to see DT_REG and friends when compiled with: c99 pedantic */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdbool.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <getopt.h>
#include <dirent.h>
#include <libgen.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>      /* new location for major + minor */
#ifndef major
#include <sys/types.h>
#endif
#include <linux/major.h>

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "sg_lib.h"

static const char * version_str = "1.17 20200501";

#define ME "sg_map26: "

#define NT_NO_MATCH 0
#define NT_SD 1
#define NT_SR 2
#define NT_HD 3
#define NT_ST 4
#define NT_OSST 5
#define NT_SG 6
#define NT_CH 7
#define NT_REG 8
#define NT_DIR 9

#define NAME_LEN_MAX 256
#define D_NAME_LEN_MAX 520

#ifndef SCSI_CHANGER_MAJOR
#define SCSI_CHANGER_MAJOR 86
#endif
#ifndef OSST_MAJOR
#define OSST_MAJOR 206
#endif

/* scandir() and stat() categories */
#define FT_OTHER 0
#define FT_REGULAR 1
#define FT_BLOCK 2
#define FT_CHAR 3
#define FT_DIR 4

/* older major.h headers may not have these */
#ifndef SCSI_DISK8_MAJOR
#define SCSI_DISK8_MAJOR        128
#define SCSI_DISK9_MAJOR        129
#define SCSI_DISK10_MAJOR       130
#define SCSI_DISK11_MAJOR       131
#define SCSI_DISK12_MAJOR       132
#define SCSI_DISK13_MAJOR       133
#define SCSI_DISK14_MAJOR       134
#define SCSI_DISK15_MAJOR       135
#endif

/* st minor decodes from Kai Makisara 20081008 */
#define ST_NBR_MODE_BITS 2
#define ST_MODE_SHIFT (7 - ST_NBR_MODE_BITS)
#define TAPE_NR(minor) ( (((minor) & ~255) >> (ST_NBR_MODE_BITS + 1)) | \
    ((minor) & ~(UINT_MAX << ST_MODE_SHIFT)) )

static const char * sys_sg_dir = "/sys/class/scsi_generic/";
static const char * sys_sd_dir = "/sys/block/";
static const char * sys_sr_dir = "/sys/block/";
static const char * sys_hd_dir = "/sys/block/";
static const char * sys_st_dir = "/sys/class/scsi_tape/";
static const char * sys_sch_dir = "/sys/class/scsi_changer/";
static const char * sys_osst_dir = "/sys/class/onstream_tape/";
static const char * def_dev_dir = "/dev";


static struct option long_options[] = {
        {"dev_dir", required_argument, 0, 'd'},
        {"given_is", required_argument, 0, 'g'},
        {"help", no_argument, 0, 'h'},
        {"result", required_argument, 0, 'r'},
        {"symlink", no_argument, 0, 's'},
        {"verbose", no_argument, 0, 'v'},
        {"version", no_argument, 0, 'V'},
        {0, 0, 0, 0},
};

static const char * nt_names[] = {
        "No matching",
        "disk",
        "cd/dvd",
        "hd",
        "tape",
        "tape (osst)",
        "generic (sg)",
        "changer",
        "regular file",
        "directory",
};

#if defined(__GNUC__) || defined(__clang__)
static int pr2serr(const char * fmt, ...)
        __attribute__ ((format (printf, 1, 2)));
#else
static int pr2serr(const char * fmt, ...);
#endif


static int
pr2serr(const char * fmt, ...)
{
    va_list args;
    int n;

    va_start(args, fmt);
    n = vfprintf(stderr, fmt, args);
    va_end(args);
    return n;
}

static void
usage()
{
        pr2serr("Usage: sg_map26 [--dev_dir=DIR] [--given_is=0...1] [--help] "
                "[--result=0...3]\n"
                "                [--symlink] [--verbose] [--version] "
                "DEVICE\n"
                "  where:\n"
                "    --dev_dir=DIR | -d DIR    search in DIR for "
                "resulting special\n"
                "                            (def: directory of DEVICE "
                "or '/dev')\n"
                "    --given_is=0...1 | -g 0...1    variety of given "
                "DEVICE\n"
                "                                   0->block or char special "
                "(or symlink to)\n"
                "                                   1->sysfs device, 'dev' or "
                "parent\n"
                "    --help | -h       print out usage message\n"
                "    --result=0...3 | -r 0...3    variety of file(s) to "
                "find\n"
                "                                 0->mapped block or char "
                "special(def)\n"
                "                                 1->mapped sysfs path\n"
                "                                 2->matching block or "
                "char special\n"
                "                                 3->matching sysfs "
                "path\n"
                "    --symlink | -s    symlinks to special included in "
                "result\n"
                "    --verbose | -v    increase verbosity of output\n"
                "    --version | -V    print version string and exit\n\n"
                "Maps SCSI device node to corresponding generic node (and "
                "vv)\n"
                );
}


/* ssafe_strerror() contributed by Clayton Weaver <cgweav at email dot com>
   Allows for situation in which strerror() is given a wild value (or the
   C library is incomplete) and returns NULL. Still not thread safe.
 */

static char safe_errbuf[64] = {'u', 'n', 'k', 'n', 'o', 'w', 'n', ' ',
                               'e', 'r', 'r', 'n', 'o', ':', ' ', 0};

static char *
ssafe_strerror(int errnum)
{
        size_t len;
        char * errstr;

        errstr = strerror(errnum);
        if (NULL == errstr) {
                len = strlen(safe_errbuf);
                snprintf(safe_errbuf + len, sizeof(safe_errbuf) - len, "%i",
                         errnum);
                safe_errbuf[sizeof(safe_errbuf) - 1] = '\0';  /* bombproof */
                return safe_errbuf;
        }
        return errstr;
}

static int
nt_typ_from_filename(const char * filename, int * majj, int * minn)
{
        struct stat st;
        int ma, mi;

        if (stat(filename, &st) < 0)
                return -errno;
        ma = major(st.st_rdev);
        mi = minor(st.st_rdev);
        if (majj)
                *majj = ma;
        if (minn)
                *minn = mi;
        if (S_ISCHR(st.st_mode)) {
                switch(ma) {
                case OSST_MAJOR:
                        return NT_OSST;
                case SCSI_GENERIC_MAJOR:
                        return NT_SG;
                case SCSI_TAPE_MAJOR:
                        return NT_ST;
                case SCSI_CHANGER_MAJOR:
                        return NT_CH;
                default:
                        return NT_NO_MATCH;
                }
        } else if (S_ISBLK(st.st_mode)) {
                switch(ma) {
                case SCSI_DISK0_MAJOR: case SCSI_DISK1_MAJOR:
                case SCSI_DISK2_MAJOR: case SCSI_DISK3_MAJOR:
                case SCSI_DISK4_MAJOR: case SCSI_DISK5_MAJOR:
                case SCSI_DISK6_MAJOR: case SCSI_DISK7_MAJOR:
                case SCSI_DISK8_MAJOR: case SCSI_DISK9_MAJOR:
                case SCSI_DISK10_MAJOR: case SCSI_DISK11_MAJOR:
                case SCSI_DISK12_MAJOR: case SCSI_DISK13_MAJOR:
                case SCSI_DISK14_MAJOR: case SCSI_DISK15_MAJOR:
                        return NT_SD;
                case SCSI_CDROM_MAJOR:
                        return NT_SR;
                case IDE0_MAJOR: case IDE1_MAJOR:
                case IDE2_MAJOR: case IDE3_MAJOR:
                case IDE4_MAJOR: case IDE5_MAJOR:
                case IDE6_MAJOR: case IDE7_MAJOR:
                case IDE8_MAJOR: case IDE9_MAJOR:
                        return NT_HD;
                default:
                        return NT_NO_MATCH;
                }
        } else if (S_ISREG(st.st_mode))
                return NT_REG;
        else if (S_ISDIR(st.st_mode))
                return NT_DIR;
        return NT_NO_MATCH;
}

static int
nt_typ_from_major(int ma)
{
        switch(ma) {
        case SCSI_DISK0_MAJOR: case SCSI_DISK1_MAJOR:
        case SCSI_DISK2_MAJOR: case SCSI_DISK3_MAJOR:
        case SCSI_DISK4_MAJOR: case SCSI_DISK5_MAJOR:
        case SCSI_DISK6_MAJOR: case SCSI_DISK7_MAJOR:
        case SCSI_DISK8_MAJOR: case SCSI_DISK9_MAJOR:
        case SCSI_DISK10_MAJOR: case SCSI_DISK11_MAJOR:
        case SCSI_DISK12_MAJOR: case SCSI_DISK13_MAJOR:
        case SCSI_DISK14_MAJOR: case SCSI_DISK15_MAJOR:
                return NT_SD;
        case SCSI_CDROM_MAJOR:
                return NT_SR;
        case IDE0_MAJOR: case IDE1_MAJOR:
        case IDE2_MAJOR: case IDE3_MAJOR:
        case IDE4_MAJOR: case IDE5_MAJOR:
        case IDE6_MAJOR: case IDE7_MAJOR:
        case IDE8_MAJOR: case IDE9_MAJOR:
                return NT_HD;
        case OSST_MAJOR:
                return NT_OSST;
        case SCSI_GENERIC_MAJOR:
                return NT_SG;
        case SCSI_TAPE_MAJOR:
                return NT_ST;
        case SCSI_CHANGER_MAJOR:
                return NT_CH;
        default:
                return NT_NO_MATCH;
        }
        return NT_NO_MATCH;
}


struct node_match_item {
        bool follow_symlink;
        int file_type;
        int majj;
        int minn;
        char dir_name[D_NAME_LEN_MAX];
};

static struct node_match_item nd_match;

static int
nd_match_scandir_select(const struct dirent * s)
{
        bool symlnk = false;
        struct stat st;
        char name[D_NAME_LEN_MAX];

        switch (s->d_type) {
        case DT_BLK:
                if (FT_BLOCK != nd_match.file_type)
                        return 0;
                break;
        case DT_CHR:
                if (FT_CHAR != nd_match.file_type)
                        return 0;
                break;
        case DT_DIR:
                return (FT_DIR == nd_match.file_type) ? 1 : 0;
        case DT_REG:
                return (FT_REGULAR == nd_match.file_type) ? 1 : 0;
        case DT_LNK:    /* follow symlinks */
                if (! nd_match.follow_symlink)
                        return 0;
                symlnk = true;
                break;
        default:
                return 0;
        }
        if ((! symlnk) && (-1 == nd_match.majj) && (-1 == nd_match.minn))
                return 1;
        snprintf(name, sizeof(name), "%.*s/%.*s", NAME_LEN_MAX,
                 nd_match.dir_name, NAME_LEN_MAX, s->d_name);
        memset(&st, 0, sizeof(st));
        if (stat(name, &st) < 0)
                return 0;
        if (symlnk) {
                if (S_ISCHR(st.st_mode)) {
                        if (FT_CHAR != nd_match.file_type)
                                return 0;
                } else if (S_ISBLK(st.st_mode)) {
                        if (FT_BLOCK != nd_match.file_type)
                                return 0;
                } else
                        return 0;
        }
        return (((-1 == nd_match.majj) ||
                 ((unsigned)major(st.st_rdev) == (unsigned)nd_match.majj)) &&
                ((-1 == nd_match.minn) ||
                 ((unsigned)minor(st.st_rdev) == (unsigned)nd_match.minn)))
               ? 1 : 0;
}

static int
list_matching_nodes(const char * dir_name, int file_type, int majj, int minn,
                    bool follow_symlink, int verbose)
{
        struct dirent ** namelist;
        int num, k;

        strncpy(nd_match.dir_name, dir_name, D_NAME_LEN_MAX - 1);
        nd_match.file_type = file_type;
        nd_match.majj = majj;
        nd_match.minn = minn;
        nd_match.follow_symlink = follow_symlink;
        num = scandir(dir_name, &namelist, nd_match_scandir_select, NULL);
        if (num < 0) {
                if (verbose)
                        pr2serr("scandir: %s %s\n", dir_name,
                                ssafe_strerror(errno));
                return -errno;
        }
        for (k = 0; k < num; ++k) {
                printf("%s/%s\n", dir_name, namelist[k]->d_name);
                free(namelist[k]);
        }
        free(namelist);
        return num;
}

struct sg_item_t {
        char name[NAME_LEN_MAX];
        int ft;
        int nt;
        int d_type;
};

static struct sg_item_t for_first;

static int
first_scandir_select(const struct dirent * s)
{
        if (FT_OTHER != for_first.ft)
                return 0;
        if ((DT_LNK != s->d_type) &&
            ((DT_DIR != s->d_type) || ('.' == s->d_name[0])))
                return 0;
        strncpy(for_first.name, s->d_name, NAME_LEN_MAX);
        for_first.ft = FT_CHAR;  /* dummy */
        for_first.d_type =  s->d_type;
        return 1;
}

/* scan for directory entry that is either a symlink or a directory */
static int
scan_for_first(const char * dir_name, int verbose)
{
        char name[NAME_LEN_MAX];
        struct dirent ** namelist;
        int num, k;

        for_first.ft = FT_OTHER;
        num = scandir(dir_name, &namelist, first_scandir_select, NULL);
        if (num < 0) {
                if (verbose > 0) {
                        snprintf(name, NAME_LEN_MAX, "scandir: %s", dir_name);
                        perror(name);
                }
                return -1;
        }
        for (k = 0; k < num; ++k)
                free(namelist[k]);
        free(namelist);
        return num;
}

static struct sg_item_t from_sg;

static int
from_sg_scandir_select(const struct dirent * s)
{
        int len;

        if (FT_OTHER != from_sg.ft)
                return 0;
        if ((DT_LNK != s->d_type) &&
            ((DT_DIR != s->d_type) || ('.' == s->d_name[0])))
                return 0;
        from_sg.d_type = s->d_type;
        if (0 == strncmp("scsi_changer", s->d_name, 12)) {
                strncpy(from_sg.name, s->d_name, NAME_LEN_MAX);
                from_sg.ft = FT_CHAR;
                from_sg.nt = NT_CH;
                return 1;
        } else if (0 == strncmp("block", s->d_name, 5)) {
                strncpy(from_sg.name, s->d_name, NAME_LEN_MAX);
                from_sg.ft = FT_BLOCK;
                return 1;
        } else if (0 == strcmp("tape", s->d_name)) {
                strcpy(from_sg.name, s->d_name);
                from_sg.ft = FT_CHAR;
                from_sg.nt = NT_ST;
                return 1;
        } else if (0 == strncmp("scsi_tape:st", s->d_name, 12)) {
                len = strlen(s->d_name);
                if (isdigit(s->d_name[len - 1])) {
                        /* want 'st<num>' symlink only */
                        strcpy(from_sg.name, s->d_name);
                        from_sg.ft = FT_CHAR;
                        from_sg.nt = NT_ST;
                        return 1;
                } else
                        return 0;
        } else if (0 == strncmp("onstream_tape:os", s->d_name, 16)) {
                strcpy(from_sg.name, s->d_name);
                from_sg.ft = FT_CHAR;
                from_sg.nt = NT_OSST;
                return 1;
        } else
                return 0;
}

static int
from_sg_scan(const char * dir_name, int verbose)
{
        struct dirent ** namelist;
        int num, k;

        from_sg.ft = FT_OTHER;
        from_sg.nt = NT_NO_MATCH;
        num = scandir(dir_name, &namelist, from_sg_scandir_select, NULL);
        if (num < 0) {
                if (verbose)
                        pr2serr("scandir: %s %s\n", dir_name,
                                ssafe_strerror(errno));
                return -errno;
        }
        if (verbose) {
                for (k = 0; k < num; ++k)
                        pr2serr("    %s/%s\n", dir_name,
                                namelist[k]->d_name);
        }
        for (k = 0; k < num; ++k)
                free(namelist[k]);
        free(namelist);
        return num;
}

static struct sg_item_t to_sg;

static int
to_sg_scandir_select(const struct dirent * s)
{
        if (FT_OTHER != to_sg.ft)
                return 0;
        if (DT_LNK != s->d_type)
                return 0;
        if (0 == strncmp("scsi_generic", s->d_name, 12)) {
                strncpy(to_sg.name, s->d_name, NAME_LEN_MAX);
                to_sg.ft = FT_CHAR;
                to_sg.nt = NT_SG;
                return 1;
        } else
                return 0;
}

static int
to_sg_scan(const char * dir_name)
{
        struct dirent ** namelist;
        int num, k;

        to_sg.ft = FT_OTHER;
        to_sg.nt = NT_NO_MATCH;
        num = scandir(dir_name, &namelist, to_sg_scandir_select, NULL);
        if (num < 0)
                return -errno;
        for (k = 0; k < num; ++k)
                free(namelist[k]);
        free(namelist);
        return num;
}

/* Return 1 if directory, else 0 */
static int
if_directory_chdir(const char * dir_name, const char * base_name)
{
        char buff[D_NAME_LEN_MAX];
        struct stat a_stat;

        strcpy(buff, dir_name);
        strcat(buff, "/");
        strcat(buff, base_name);
        if (stat(buff, &a_stat) < 0)
                return 0;
        if (S_ISDIR(a_stat.st_mode)) {
                if (chdir(buff) < 0)
                        return 0;
                return 1;
        }
        return 0;
}

/* Return 1 if directory, else 0 */
static int
if_directory_ch2generic(const char * dir_name)
{
        char buff[NAME_LEN_MAX];
        struct stat a_stat;
        const char * old_name = "generic";

        strcpy(buff, dir_name);
        strcat(buff, "/");
        strcat(buff, old_name);
        if ((stat(buff, &a_stat) >= 0) && S_ISDIR(a_stat.st_mode)) {
                if (chdir(buff) < 0)
                        return 0;
                return 1;
        }
        /* No "generic", so now look for "scsi_generic:sg<n>" */
        if (1 != to_sg_scan(dir_name))
                return 0;
        strcpy(buff, dir_name);
        strcat(buff, "/");
        strcat(buff, to_sg.name);
        if (stat(buff, &a_stat) < 0)
                return 0;
        if (S_ISDIR(a_stat.st_mode)) {
                if (chdir(buff) < 0)
                        return 0;
                return 1;
        }
        return 0;
}

/* Return 1 if found, else 0 if problems */
static int
get_value(const char * dir_name, const char * base_name, char * value,
          int max_value_len)
{
        char buff[D_NAME_LEN_MAX];
        FILE * f;
        int len;

        if ((NULL == dir_name) && (NULL == base_name))
                return 0;
        if (dir_name) {
                strcpy(buff, dir_name);
                if (base_name && (strlen(base_name) > 0)) {
                        strcat(buff, "/");
                        strcat(buff, base_name);
                }
        } else
                strcpy(buff, base_name);
        if (NULL == (f = fopen(buff, "r"))) {
                return 0;
        }
        if (NULL == fgets(value, max_value_len, f)) {
                fclose(f);
                return 0;
        }
        len = strlen(value);
        if ((len > 0) && (value[len - 1] == '\n'))
                value[len - 1] = '\0';
        fclose(f);
        return 1;
}

static int
map_hd(const char * device_dir, int ma, int mi, int result,
       bool follow_symlink, int verbose)
{
        char c, num;

        if (2 == result) {
                num = list_matching_nodes(device_dir, FT_BLOCK,
                                          ma, mi, follow_symlink,
                                          verbose);
                return (num > 0) ? 0 : 1;
        }
        switch (ma) {
        case IDE0_MAJOR: c = 'a'; break;
        case IDE1_MAJOR: c = 'c'; break;
        case IDE2_MAJOR: c = 'e'; break;
        case IDE3_MAJOR: c = 'g'; break;
        case IDE4_MAJOR: c = 'i'; break;
        case IDE5_MAJOR: c = 'k'; break;
        case IDE6_MAJOR: c = 'm'; break;
        case IDE7_MAJOR: c = 'o'; break;
        case IDE8_MAJOR: c = 'q'; break;
        case IDE9_MAJOR: c = 's'; break;
        default: c = '?'; break;
        }
        if (mi > 63)
                ++c;
        printf("%shd%c\n", sys_hd_dir, c);
        return 0;
}

static int
map_sd(const char * device_name, const char * device_dir, int ma, int mi,
       int result, bool follow_symlink, int verbose)
{
        int index, m_mi, m_ma, num;
        char value[D_NAME_LEN_MAX];
        char name[D_NAME_LEN_MAX];

        if (2 == result) {
                num = list_matching_nodes(device_dir, FT_BLOCK, ma, mi,
                                          follow_symlink, verbose);
                return (num > 0) ? 0 : 1;
        }
        if (SCSI_DISK0_MAJOR == ma)
                index = mi / 16;
        else if (ma >= SCSI_DISK8_MAJOR)
                index = (mi / 16) + 128 +
                        ((ma - SCSI_DISK8_MAJOR) * 16);
        else
                index = (mi / 16) + 16 +
                        ((ma - SCSI_DISK1_MAJOR) * 16);
        if (index < 26)
                snprintf(name, sizeof(name), "%ssd%c",
                         sys_sd_dir, 'a' + index % 26);
        else if (index < (26 + 1) * 26)
                snprintf(name, sizeof(name), "%ssd%c%c",
                         sys_sd_dir,
                         'a' + index / 26 - 1,'a' + index % 26);
        else {
                const unsigned int m1 = (index / 26 - 1) / 26 - 1;
                const unsigned int m2 = (index / 26 - 1) % 26;
                const unsigned int m3 =  index % 26;

                snprintf(name, sizeof(name), "%ssd%c%c%c",
                         sys_sd_dir, 'a' + m1, 'a' + m2, 'a' + m3);
        }
        if (3 == result) {
                printf("%s\n", name);
                return 0;
        }
        if (! get_value(name, "dev", value, sizeof(value))) {
                pr2serr("Couldn't find sysfs match for device: %s\n",
                        device_name);
                return 1;
        }
        if (verbose)
                pr2serr("sysfs sd dev: %s\n", value);
        if (! if_directory_chdir(name, "device")) {
                pr2serr("sysfs problem with device: %s\n", device_name);
                return 1;
        }
        if (if_directory_ch2generic(".")) {
                if (1 == result) {
                        if (NULL == getcwd(value, sizeof(value)))
                                value[0] = '\0';
                        printf("%s\n", value);
                        return 0;
                }
                if (! get_value(".", "dev", value, sizeof(value))) {
                        pr2serr("Couldn't find sysfs generic dev\n");
                        return 1;
                }
                if (verbose)
                        printf("matching dev: %s\n", value);
                if (2 != sscanf(value, "%d:%d", &m_ma, &m_mi)) {
                        pr2serr("Couldn't decode mapped dev\n");
                        return 1;
                }
                num = list_matching_nodes(device_dir, FT_CHAR, m_ma, m_mi,
                                          follow_symlink, verbose);
                return (num > 0) ? 0 : 1;
        } else {
                pr2serr("sd device: %s does not match any SCSI generic "
                        "device\n", device_name);
                pr2serr("    perhaps sg module is not loaded\n");
                return 1;
        }
}

static int
map_sr(const char * device_name, const char * device_dir, int ma, int mi,
       int result, bool follow_symlink, int verbose)
{
        int m_mi, m_ma, num;
        char value[D_NAME_LEN_MAX];
        char name[D_NAME_LEN_MAX];

        if (2 == result) {
                num = list_matching_nodes(device_dir, FT_BLOCK, ma, mi,
                                          follow_symlink, verbose);
                return (num > 0) ? 0 : 1;
        }
        snprintf(name, sizeof(name), "%ssr%d", sys_sr_dir, mi);
        if (3 == result) {
                printf("%s\n", name);
                return 0;
        }
        if (! get_value(name, "dev", value, sizeof(value))) {
                pr2serr("Couldn't find sysfs match for device: %s\n",
                        device_name);
                return 1;
        }
        if (verbose)
                pr2serr("sysfs sr dev: %s\n", value);
        if (! if_directory_chdir(name, "device")) {
                pr2serr("sysfs problem with device: %s\n", device_name);
                return 1;
        }
        if (if_directory_ch2generic(".")) {
                if (1 == result) {
                        if (NULL == getcwd(value, sizeof(value)))
                                value[0] = '\0';
                        printf("%s\n", value);
                        return 0;
                }
                if (! get_value(".", "dev", value, sizeof(value))) {
                        pr2serr("Couldn't find sysfs generic dev\n");
                        return 1;
                }
                if (verbose)
                        printf("matching dev: %s\n", value);
                if (2 != sscanf(value, "%d:%d", &m_ma, &m_mi)) {
                        pr2serr("Couldn't decode mapped dev\n");
                        return 1;
                }
                num = list_matching_nodes(device_dir, FT_BLOCK, m_ma, m_mi,
                                          follow_symlink, verbose);
                return (num > 0) ? 0 : 1;
        } else {
                pr2serr("sr device: %s does not match any SCSI generic "
                        "device\n", device_name);
                pr2serr("    perhaps sg module is not loaded\n");
                return 1;
        }
}

static int
map_st(const char * device_name, const char * device_dir, int ma, int mi,
       int result, bool follow_symlink, int verbose)
{
        int m_mi, m_ma, num;
        char value[D_NAME_LEN_MAX];
        char name[D_NAME_LEN_MAX];

        if (2 == result) {
                num = list_matching_nodes(device_dir, FT_CHAR, ma, mi,
                                          follow_symlink, verbose);
                return (num > 0) ? 0 : 1;
        }
        snprintf(name, sizeof(name), "%sst%d", sys_st_dir,
                 TAPE_NR(mi));
        if (3 == result) {
                printf("%s\n", name);
                return 0;
        }
        if (! get_value(name, "dev", value, sizeof(value))) {
                pr2serr("Couldn't find sysfs match for device: %s\n",
                        device_name);
                return 1;
        }
        if (verbose)
                pr2serr("sysfs st dev: %s\n", value);
        if (! if_directory_chdir(name, "device")) {
                pr2serr("sysfs problem with device: %s\n", device_name);
                return 1;
        }
        if (if_directory_ch2generic(".")) {
                if (1 == result) {
                        if (NULL == getcwd(value, sizeof(value)))
                                value[0] = '\0';
                        printf("%s\n", value);
                        return 0;
                }
                if (! get_value(".", "dev", value, sizeof(value))) {
                        pr2serr("Couldn't find sysfs generic dev\n");
                        return 1;
                }
                if (verbose)
                        printf("matching dev: %s\n", value);
                if (2 != sscanf(value, "%d:%d", &m_ma, &m_mi)) {
                        pr2serr("Couldn't decode mapped dev\n");
                        return 1;
                }
                num = list_matching_nodes(device_dir, FT_CHAR, m_ma, m_mi,
                                          follow_symlink, verbose);
                return (num > 0) ? 0 : 1;
        } else {
                pr2serr("st device: %s does not match any SCSI generic "
                        "device\n", device_name);
                pr2serr("    perhaps sg module is not loaded\n");
                return 1;
        }
}

static int
map_osst(const char * device_name, const char * device_dir, int ma, int mi,
         int result, bool follow_symlink, int verbose)
{
        int m_mi, m_ma, num;
        char value[D_NAME_LEN_MAX];
        char name[D_NAME_LEN_MAX];

        if (2 == result) {
                num = list_matching_nodes(device_dir, FT_CHAR, ma, mi,
                                          follow_symlink, verbose);
                return (num > 0) ? 0 : 1;
        }
        snprintf(name, sizeof(name), "%sosst%d", sys_osst_dir,
                 TAPE_NR(mi));
        if (3 == result) {
                printf("%s\n", name);
                return 0;
        }
        if (! get_value(name, "dev", value, sizeof(value))) {
                pr2serr("Couldn't find sysfs match for device: %s\n",
                        device_name);
                return 1;
        }
        if (verbose)
                pr2serr("sysfs osst dev: %s\n", value);
        if (! if_directory_chdir(name, "device")) {
                pr2serr("sysfs problem with device: %s\n", device_name);
                return 1;
        }
        if (if_directory_ch2generic(".")) {
                if (1 == result) {
                        if (NULL == getcwd(value, sizeof(value)))
                                value[0] = '\0';
                        printf("%s\n", value);
                        return 0;
                }
                if (! get_value(".", "dev", value, sizeof(value))) {
                        pr2serr("Couldn't find sysfs generic dev\n");
                        return 1;
                }
                if (verbose)
                        printf("matching dev: %s\n", value);
                if (2 != sscanf(value, "%d:%d", &m_ma, &m_mi)) {
                        pr2serr("Couldn't decode mapped dev\n");
                        return 1;
                }
                num = list_matching_nodes(device_dir, FT_CHAR, m_ma, m_mi,
                                          follow_symlink, verbose);
                return (num > 0) ? 0 : 1;
        } else {
                pr2serr("osst device: %s does not match any SCSI generic "
                        "device\n", device_name);
                pr2serr("    perhaps sg module is not loaded\n");
                return 1;
        }
}

static int
map_ch(const char * device_name, const char * device_dir, int ma, int mi,
       int result, bool follow_symlink, int verbose)
{
        int m_mi, m_ma, num;
        char value[D_NAME_LEN_MAX];
        char name[D_NAME_LEN_MAX];

        if (2 == result) {
                num = list_matching_nodes(device_dir, FT_CHAR, ma, mi,
                                          follow_symlink, verbose);
                return (num > 0) ? 0 : 1;
        }
        snprintf(name, sizeof(name), "%ssch%d", sys_sch_dir, mi);
        if (3 == result) {
                printf("%s\n", name);
                return 0;
        }
        if (! get_value(name, "dev", value, sizeof(value))) {
                pr2serr("Couldn't find sysfs match for device: %s\n",
                        device_name);
                return 1;
        }
        if (verbose)
                pr2serr("sysfs sch dev: %s\n", value);
        if (! if_directory_chdir(name, "device")) {
                pr2serr("sysfs problem with device: %s\n", device_name);
                return 1;
        }
        if (if_directory_ch2generic(".")) {
                if (1 == result) {
                        if (NULL == getcwd(value, sizeof(value)))
                                value[0] = '\0';
                        printf("%s\n", value);
                        return 0;
                }
                if (! get_value(".", "dev", value, sizeof(value))) {
                        pr2serr("Couldn't find sysfs generic dev\n");
                        return 1;
                }
                if (verbose)
                        printf("matching dev: %s\n", value);
                if (2 != sscanf(value, "%d:%d", &m_ma, &m_mi)) {
                        pr2serr("Couldn't decode mapped dev\n");
                        return 1;
                }
                num = list_matching_nodes(device_dir, FT_CHAR, m_ma, m_mi,
                                          follow_symlink, verbose);
                return (num > 0) ? 0 : 1;
        } else {
                pr2serr("sch device: %s does not match any SCSI generic "
                        "device\n", device_name);
                pr2serr("    perhaps sg module is not loaded\n");
                return 1;
        }
}

static int
map_sg(const char * device_name, const char * device_dir, int ma, int mi,
       int result, bool follow_symlink, int verbose)
{
        int m_mi, m_ma, num;
        char value[D_NAME_LEN_MAX];
        char name[D_NAME_LEN_MAX];

        if (2 == result) {
                num = list_matching_nodes(device_dir, FT_CHAR, ma, mi,
                                          follow_symlink, verbose);
                return (num > 0) ? 0 : 1;
        }
        snprintf(name, sizeof(name), "%ssg%d", sys_sg_dir, mi);
        if (3 == result) {
                printf("%s\n", name);
                return 0;
        }
        if (! get_value(name, "dev", value, sizeof(value))) {
                pr2serr("Couldn't find sysfs match for device: %s\n",
                        device_name);
                return 1;
        }
        if (verbose)
                pr2serr("sysfs sg dev: %s\n", value);
        if (! if_directory_chdir(name, "device")) {
                pr2serr("sysfs problem with device: %s\n", device_name);
                return 1;
        }
        if ((1 == from_sg_scan(".", verbose)) &&
            (if_directory_chdir(".", from_sg.name))) {
                if (DT_DIR == from_sg.d_type) {
                        if ((1 == scan_for_first(".", verbose)) &&
                            (if_directory_chdir(".", for_first.name))) {
                                ;
                        } else {
                                pr2serr("unexpected scan_for_first error\n");
                        }
                }
                if (1 == result) {
                        if (NULL == getcwd(value, sizeof(value)))
                                value[0] = '\0';
                        printf("%s\n", value);
                        return 0;
                }
                if (! get_value(".", "dev", value, sizeof(value))) {
                        pr2serr("Couldn't find sysfs block dev\n");
                        return 1;
                }
                if (verbose)
                        printf("matching dev: %s\n", value);
                if (2 != sscanf(value, "%d:%d", &m_ma, &m_mi)) {
                        pr2serr("Couldn't decode mapped dev\n");
                        return 1;
                }
                num = list_matching_nodes(device_dir, from_sg.ft, m_ma, m_mi,
                                          follow_symlink, verbose);
                return (num > 0) ? 0 : 1;
        } else {
                pr2serr("sg device: %s does not match any other SCSI "
                        "device\n", device_name);
                return 1;
        }
}


int
main(int argc, char * argv[])
{
        bool cont;
        int c, num, tt, res;
        int given_is = -1;
        int result = 0;
        int verbose = 0;
        int ret = 1;
        int ma, mi;
        bool do_dev_dir = false;
        bool follow_symlink = false;
        char device_name[D_NAME_LEN_MAX];
        char device_dir[D_NAME_LEN_MAX];
        char value[D_NAME_LEN_MAX];

        memset(device_name, 0, sizeof(device_name));
        memset(device_dir, 0, sizeof(device_dir));
        while (1) {
                int option_index = 0;

                c = getopt_long(argc, argv, "d:hg:r:svV", long_options,
                                &option_index);
                if (c == -1)
                        break;

                switch (c) {
                case 'd':
                        strncpy(device_dir, optarg, sizeof(device_dir) - 1);
                        do_dev_dir = true;
                        break;
                case 'g':
                        num = sscanf(optarg, "%d", &res);
                        if ((1 == num) && ((0 == res) || (1 == res)))
                                given_is = res;
                        else {
                                pr2serr("value for '--given_to=' must be 0 "
                                        "or 1\n");
                                return SG_LIB_SYNTAX_ERROR;
                        }
                        break;
                case 'h':
                case '?':
                        usage();
                        return 0;
                case 'r':
                        num = sscanf(optarg, "%d", &res);
                        if ((1 == num) && (res >= 0) && (res < 4))
                                result = res;
                        else {
                                pr2serr("value for '--result=' must be "
                                        "0..3\n");
                                return SG_LIB_SYNTAX_ERROR;
                        }
                        break;
                case 's':
                        follow_symlink = true;
                        break;
                case 'v':
                        ++verbose;
                        break;
                case 'V':
                        pr2serr(ME "version: %s\n", version_str);
                        return 0;
                default:
                        pr2serr("unrecognised option code 0x%x ??\n", c);
                        usage();
                        return SG_LIB_SYNTAX_ERROR;
                }
        }
        if (optind < argc) {
                if ('\0' == device_name[0]) {
                        strncpy(device_name, argv[optind],
                                sizeof(device_name) - 1);
                        device_name[sizeof(device_name) - 1] = '\0';
                        ++optind;
                }
                if (optind < argc) {
                        for (; optind < argc; ++optind)
                                pr2serr("Unexpected extra argument: %s\n",
                                        argv[optind]);
                        usage();
                        return SG_LIB_SYNTAX_ERROR;
                }
        }

        if (0 == device_name[0]) {
                pr2serr("missing device name!\n");
                usage();
                return SG_LIB_SYNTAX_ERROR;
        }

        ma = 0;
        mi = 0;
        if (do_dev_dir) {
                if (if_directory_chdir(".", device_dir)) {
                        if (getcwd(device_dir, sizeof(device_dir)))
                                device_dir[sizeof(device_dir) - 1] = '\0';
                        else
                                device_dir[0] = '\0';
                        if (verbose > 1)
                                pr2serr("Absolute path to dev_dir: %s\n",
                                        device_dir);
                } else {
                        pr2serr("dev_dir: %s invalid\n", device_dir);
                        return SG_LIB_FILE_ERROR;
                }
        } else {
                strcpy(device_dir, device_name);
                dirname(device_dir);
                if (0 == strcmp(device_dir, device_name)) {
                        if (NULL == getcwd(device_dir, sizeof(device_dir)))
                                device_dir[0] = '\0';
                }
        }
        ret = nt_typ_from_filename(device_name, &ma, &mi);
        if (ret < 0) {
                pr2serr("stat failed on %s: %s\n", device_name,
                        ssafe_strerror(-ret));
                return SG_LIB_FILE_ERROR;
        }
        if (verbose)
                pr2serr(" %s: %s device [maj=%d, min=%d]\n", device_name,
                        nt_names[ret], ma, mi);
        res = 0;
        switch (ret) {
        case NT_SD:
        case NT_SR:
        case NT_HD:
                if (given_is > 0) {
                        pr2serr("block special but '--given_is=' suggested "
                                "sysfs device\n");
                        return SG_LIB_FILE_ERROR;
                }
                break;
        case NT_ST:
        case NT_OSST:
        case NT_CH:
        case NT_SG:
                if (given_is > 0) {
                        pr2serr("character special but '--given_is=' "
                                "suggested sysfs device\n");
                        return SG_LIB_FILE_ERROR;
                }
                break;
        case NT_REG:
                if (0 == given_is) {
                        pr2serr("regular file but '--given_is=' suggested "
                                "block or char special\n");
                        return SG_LIB_FILE_ERROR;
                }
                strcpy(device_dir, def_dev_dir);
                break;
        case NT_DIR:
                if (0 == given_is) {
                        pr2serr("directory but '--given_is=' suggested "
                                "block or char special\n");
                        return SG_LIB_FILE_ERROR;
                }
                strcpy(device_dir, def_dev_dir);
                break;
        default:
                break;
        }

        tt = NT_NO_MATCH;
        do {
                cont = false;
                switch (ret) {
                case NT_NO_MATCH:
                        res = 1;
                        break;
                case NT_SD:
                        res = map_sd(device_name, device_dir, ma, mi, result,
                                     follow_symlink, verbose);
                        break;
                case NT_SR:
                        res = map_sr(device_name, device_dir, ma, mi, result,
                                     follow_symlink, verbose);
                        break;
                case NT_HD:
                        if (result < 2) {
                                pr2serr("a hd device does not map to a sg "
                                        "device\n");
                                return SG_LIB_FILE_ERROR;
                        }
                        res = map_hd(device_dir, ma, mi, result,
                                     follow_symlink, verbose);
                        break;
                case NT_ST:
                        res = map_st(device_name, device_dir, ma, mi, result,
                                     follow_symlink, verbose);
                        break;
                case NT_OSST:
                        res = map_osst(device_name, device_dir, ma, mi,
                                       result, follow_symlink, verbose);
                        break;
                case NT_CH:
                        res = map_ch(device_name, device_dir, ma, mi, result,
                                     follow_symlink, verbose);
                        break;
                case NT_SG:
                        res = map_sg(device_name, device_dir, ma, mi, result,
                                     follow_symlink, verbose);
                        break;
                case NT_REG:
                        if (! get_value(NULL, device_name, value,
                                        sizeof(value))) {
                                pr2serr("Couldn't fetch value from: %s\n",
                                        device_name);
                                return SG_LIB_FILE_ERROR;
                        }
                        if (verbose)
                                pr2serr("value: %s\n", value);
                        if (2 != sscanf(value, "%d:%d", &ma, &mi)) {
                                pr2serr("Couldn't decode value\n");
                                return SG_LIB_FILE_ERROR;
                        }
                        tt = nt_typ_from_major(ma);
                        cont = true;
                        break;
                case NT_DIR:
                        if (! get_value(device_name, "dev", value,
                                        sizeof(value))) {
                                pr2serr("Couldn't fetch value from: %s/dev\n",
                                        device_name);
                                return SG_LIB_FILE_ERROR;
                        }
                        if (verbose)
                                pr2serr("value: %s\n", value);
                        if (2 != sscanf(value, "%d:%d", &ma, &mi)) {
                                pr2serr("Couldn't decode value\n");
                                return SG_LIB_FILE_ERROR;
                        }
                        tt = nt_typ_from_major(ma);
                        cont = true;
                        break;
                default:
                        break;
                }
                ret = tt;
        } while (cont);
        return res;
}
