/* * os_linux.c * * Home page of code is: http://smartmontools.sourceforge.net * * Copyright (C) 2003 Bruce Allen <smartmontools-support@lists.sourceforge.net> * Copyright (C) 2003 Doug Gilbert <dougg@torque.net> * * Parts of this file are derived from code that was * * Written By: Adam Radford <linux@3ware.com> * Modifications By: Joel Jacobson <linux@3ware.com> * Arnaldo Carvalho de Melo <acme@conectiva.com.br> * Brad Strand <linux@3ware.com> * * Copyright (C) 1999-2003 3ware Inc. * * Kernel compatablity By: Andre Hedrick <andre@suse.com> * Non-Copyright (C) 2000 Andre Hedrick <andre@suse.com> * * Other ars of this file are derived from code that was * * Copyright (C) 1999-2000 Michael Cornwell <cornwell@acm.org> * Copyright (C) 2000 Andre Hedrick <andre@linux-ide.org> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * You should have received a copy of the GNU General Public License * (for example COPYING); if not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * This code was originally developed as a Senior Thesis by Michael Cornwell * at the Concurrent Systems Laboratory (now part of the Storage Systems * Research Center), Jack Baskin School of Engineering, University of * California, Santa Cruz. http://ssrc.soe.ucsc.edu/ * */ // This file contains the linux-specific IOCTL parts of // smartmontools. It includes one interface routine for ATA devices, // one for SCSI devices, and one for ATA devices behind escalade // controllers. #include <string.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> #include <scsi/scsi_ioctl.h> #include "atacmds.h" #include "config.h" #include "os_linux.h" #include "scsicmds.h" #include "smartd.h" #include "utility.h" const char *os_XXXX_c_cvsid="$Id: os_linux.cpp,v 1.9 2003/10/14 14:22:44 ballen4705 Exp $" \ ATACMDS_H_CVSID CONFIG_H_CVSID OS_XXXX_H_CVSID SCSICMDS_H_CVSID SMARTD_H_CVSID UTILITY_H_CVSID; // to hold onto exit code for atexit routine extern int exitstatus; // keep track of memory allocated/deallocated extern long long bytes; // equivalent to open(path, flags) int deviceopen(const char *pathname, char *type){ if (!strcmp(type,"SCSI")) return open(pathname, O_RDWR | O_NONBLOCK); else if (!strcmp(type,"ATA")) return open(pathname, O_RDONLY | O_NONBLOCK); else return -1; } // equivalent to close(file descriptor) int deviceclose(int fd){ return close(fd); } // makes a list of device names to scan, for either ATA or SCSI // devices. Return -1 if no memory remaining, else the number of // devices on the list, which can be >=0. int make_device_names (char*** devlist, const char* name) { char* base=NULL; char** tmp=NULL; int i, n=0; // set correct first name (base name) for ATA or SCSI devices if (!strcmp(name,"SCSI")) { n = MAXSCSIDEVICES; base = CustomStrDup("/dev/sda",1,__LINE__); } else if (!strcmp(name,"ATA")) { n = MAXATADEVICES; base = CustomStrDup("/dev/hda",1,__LINE__); } // bad type or no devices if (!n) return 0; // allocate storage for the list of device names that we'll be // making, and store first name if (!(tmp = *devlist = (char **)calloc(n, sizeof(char *)))) return -1; bytes += n*sizeof(char*); tmp[0]=base; // make device names by incrementing the last letter of the base // name alphabetically a->b->c... for (i = 1; i < n; i++) { tmp[i] = CustomStrDup(tmp[i-1],1,__LINE__); tmp[i][7]++; } return n; } // PURPOSE // This is an interface routine meant to isolate the OS dependent // parts of the code, and to provide a debugging interface. Each // different port and OS needs to provide it's own interface. This // is the linux one. // DETAILED DESCRIPTION OF ARGUMENTS // device: is the file descriptor provided by open() // command: defines the different operations. // select: additional input data if needed (which log, which type of // self-test). // data: location to write output data, if needed (512 bytes). // Note: not all commands use all arguments. // RETURN VALUES // -1 if the command failed // 0 if the command succeeded, // STATUS_CHECK routine: // -1 if the command failed // 0 if the command succeeded and disk SMART status is "OK" // 1 if the command succeeded and disk SMART status is "FAILING" // huge value of buffer size needed because HDIO_DRIVE_CMD assumes // that buff[3] is the data size. Since the SMART_AUTOSAVE and // SMART_AUTO_OFFLINE use values of 0xf1 and 0xf8 we need the space. // Otherwise a 4+512 byte buffer would be enough. #define STRANGE_BUFFER_LENGTH (4+512*0xf8) int ata_command_interface(int device, smart_command_set command, int select, char *data){ unsigned char buff[STRANGE_BUFFER_LENGTH]; int retval, copydata=0; // The two linux IOCTL's that we use. These MUST be consistent with // the same quantities in the kernel include file linux/hdreg.h const int HDIO_DRIVE_TASK = 0x031e; const int HDIO_DRIVE_CMD = 0x031f; const int HDIO_DRIVE_CMD_OFFSET = 4; // See struct hd_drive_cmd_hdr in hdreg.h // buff[0]: ATA COMMAND CODE REGISTER // buff[1]: ATA SECTOR NUMBER REGISTER == LBA LOW REGISTER // buff[2]: ATA FEATURES REGISTER // buff[3]: ATA SECTOR COUNT REGISTER // clear out buff. Large enough for HDIO_DRIVE_CMD (4+512 bytes) memset(buff, 0, STRANGE_BUFFER_LENGTH); buff[0]=WIN_SMART; switch (command){ case READ_VALUES: buff[2]=SMART_READ_VALUES; copydata=buff[3]=1; break; case READ_THRESHOLDS: buff[2]=SMART_READ_THRESHOLDS; copydata=buff[1]=buff[3]=1; break; case READ_LOG: buff[2]=SMART_READ_LOG_SECTOR; buff[1]=select; copydata=buff[3]=1; break; case IDENTIFY: buff[0]=WIN_IDENTIFY; copydata=buff[3]=1; break; case PIDENTIFY: buff[0]=WIN_PIDENTIFY; copydata=buff[3]=1; break; case ENABLE: buff[2]=SMART_ENABLE; buff[1]=1; break; case DISABLE: buff[2]=SMART_DISABLE; buff[1]=1; break; case STATUS: // this command only says if SMART is working. It could be // replaced with STATUS_CHECK below. buff[2]=SMART_STATUS; break; case AUTO_OFFLINE: buff[2]=SMART_AUTO_OFFLINE; buff[3]=select; // YET NOTE - THIS IS A NON-DATA COMMAND!! break; case AUTOSAVE: buff[2]=SMART_AUTOSAVE; buff[3]=select; // YET NOTE - THIS IS A NON-DATA COMMAND!! break; case IMMEDIATE_OFFLINE: buff[2]=SMART_IMMEDIATE_OFFLINE; buff[1]=select; break; case STATUS_CHECK: // This command uses HDIO_DRIVE_TASK and has different syntax than // the other commands. buff[1]=SMART_STATUS; break; default: pout("Unrecognized command %d in linux_ata_command_interface()\n" "Please contact " PACKAGE_BUGREPORT "\n", command); errno=ENOSYS; return -1; } // There are two different types of ioctls(). The HDIO_DRIVE_TASK // one is this: if (command==STATUS_CHECK){ // NOT DOCUMENTED in /usr/src/linux/include/linux/hdreg.h. You // have to read the IDE driver source code. Sigh. // buff[0]: ATA COMMAND CODE REGISTER // buff[1]: ATA FEATURES REGISTER // buff[2]: ATA SECTOR_COUNT // buff[3]: ATA SECTOR NUMBER // buff[4]: ATA CYL LO REGISTER // buff[5]: ATA CYL HI REGISTER // buff[6]: ATA DEVICE HEAD unsigned const char normal_lo=0x4f, normal_hi=0xc2; unsigned const char failed_lo=0xf4, failed_hi=0x2c; buff[4]=normal_lo; buff[5]=normal_hi; if ((retval=ioctl(device, HDIO_DRIVE_TASK, buff))) return -1; // Cyl low and Cyl high unchanged means "Good SMART status" if (buff[4]==normal_lo && buff[5]==normal_hi) return 0; // These values mean "Bad SMART status" if (buff[4]==failed_lo && buff[5]==failed_hi) return 1; // We haven't gotten output that makes sense; print out some debugging info syserror("Error SMART Status command failed"); pout("Please get assistance from %s\n",PROJECTHOME); pout("Register values returned from SMART Status command are:\n"); pout("CMD=0x%02x\n",(int)buff[0]); pout("FR =0x%02x\n",(int)buff[1]); pout("NS =0x%02x\n",(int)buff[2]); pout("SC =0x%02x\n",(int)buff[3]); pout("CL =0x%02x\n",(int)buff[4]); pout("CH =0x%02x\n",(int)buff[5]); pout("SEL=0x%02x\n",(int)buff[6]); return -1; } #if (1) // Note to people doing ports to other OSes -- don't worry about // this block -- you can ignore it. I have put it here because // under linux when you do IDENTIFY DEVICE to a packet device, it // generates an ugly kernel syslog error message. This is harmless // but frightens users. So this block is an attempt to detect // packet devices and make IDENTIFY DEVICE fail "nicely" without a // syslog error message. // This doesn't always work, since ATA-3 did not have a separate // IDENTIFY PACKET DEVICE command. In fact while ATAPI was // introduced in ATA-3, IDENTIFY PACKET DEVICE only appeared in ATA // 4 revision 2. Anyway, this doesn't matter, since packet devices // don't support SMART anyway. // for IDENTIFY command, check if device is a packet device, and if // it is, then we simulate a 'clean' error without calling the // lower-level ioctl that will generate a kernel error log message. if (command==IDENTIFY){ unsigned char junk[512]; const int HDIO_GET_IDENTITY=0x030d; if (!ioctl(device, HDIO_GET_IDENTITY, junk) && (junk[1]>>7)) { // device is PACKET device errno = 5; return -1; } } #endif // We are now doing the HDIO_DRIVE_CMD type ioctl. if ((retval=ioctl(device, HDIO_DRIVE_CMD, buff))) return -1; // if the command returns data, copy it back if (copydata) memcpy(data, buff+HDIO_DRIVE_CMD_OFFSET, 512); return 0; } /* SCSI command transmission interface function, implementation is OS * specific. Returns 0 if SCSI command successfully launched and response * received, else returns a negative errno value */ /* Linux specific code, FreeBSD could conditionally compile in CAM stuff * instead of this. */ /* #include <scsi/scsi.h> bypass for now */ /* #include <scsi/scsi_ioctl.h> bypass for now */ #define MAX_DXFER_LEN 1024 /* can be increased if necessary */ #define SEND_IOCTL_RESP_SENSE_LEN 16 /* ioctl limitation */ #define DRIVER_SENSE 0x8 /* alternate CHECK CONDITION indication */ #ifndef SCSI_IOCTL_SEND_COMMAND #define SCSI_IOCTL_SEND_COMMAND 1 #endif #ifndef SCSI_IOCTL_TEST_UNIT_READY #define SCSI_IOCTL_TEST_UNIT_READY 2 #endif struct linux_ioctl_send_command { int inbufsize; int outbufsize; UINT8 buff[MAX_DXFER_LEN + 16]; }; /* The Linux SCSI_IOCTL_SEND_COMMAND ioctl is primitive and it doesn't * support: CDB length (guesses it from opcode), resid and timeout. * Patches in Linux 2.4.21 and 2.5.70 to extend SEND DIAGNOSTIC timeout * to 2 hours in order to allow long foreground extended self tests. */ int do_scsi_cmnd_io(int dev_fd, struct scsi_cmnd_io * iop, int report) { struct linux_ioctl_send_command wrk; int status, buff_offset; size_t len; memcpy(wrk.buff, iop->cmnd, iop->cmnd_len); buff_offset = iop->cmnd_len; if (report > 0) { int k; const unsigned char * ucp = iop->cmnd; const char * np; np = scsi_get_opcode_name(ucp[0]); pout(" [%s: ", np ? np : "<unknown opcode>"); for (k = 0; k < iop->cmnd_len; ++k) pout("%02x ", ucp[k]); if ((report > 1) && (DXFER_TO_DEVICE == iop->dxfer_dir) && (iop->dxferp)) { int trunc = (iop->dxfer_len > 256) ? 1 : 0; pout("]\n Outgoing data, len=%d%s:\n", (int)iop->dxfer_len, (trunc ? " [only first 256 bytes shown]" : "")); dStrHex(iop->dxferp, (trunc ? 256 : iop->dxfer_len) , 1); } else pout("]"); } switch (iop->dxfer_dir) { case DXFER_NONE: wrk.inbufsize = 0; wrk.outbufsize = 0; break; case DXFER_FROM_DEVICE: wrk.inbufsize = 0; if (iop->dxfer_len > MAX_DXFER_LEN) return -EINVAL; wrk.outbufsize = iop->dxfer_len; break; case DXFER_TO_DEVICE: if (iop->dxfer_len > MAX_DXFER_LEN) return -EINVAL; memcpy(wrk.buff + buff_offset, iop->dxferp, iop->dxfer_len); wrk.inbufsize = iop->dxfer_len; wrk.outbufsize = 0; break; default: pout("do_scsi_cmnd_io: bad dxfer_dir\n"); return -EINVAL; } iop->resp_sense_len = 0; iop->scsi_status = 0; iop->resid = 0; status = ioctl(dev_fd, SCSI_IOCTL_SEND_COMMAND , &wrk); if (-1 == status) { if (report) pout(" status=-1, errno=%d [%s]\n", errno, strerror(errno)); return -errno; } if (0 == status) { if (report > 0) pout(" status=0\n"); if (DXFER_FROM_DEVICE == iop->dxfer_dir) { memcpy(iop->dxferp, wrk.buff, iop->dxfer_len); if (report > 1) { int trunc = (iop->dxfer_len > 256) ? 1 : 0; pout(" Incoming data, len=%d%s:\n", (int)iop->dxfer_len, (trunc ? " [only first 256 bytes shown]" : "")); dStrHex(iop->dxferp, (trunc ? 256 : iop->dxfer_len) , 1); } } return 0; } iop->scsi_status = status & 0x7e; /* bits 0 and 7 used to be for vendors */ if (DRIVER_SENSE == ((status >> 24) & 0xf)) iop->scsi_status = SCSI_STATUS_CHECK_CONDITION; len = (SEND_IOCTL_RESP_SENSE_LEN < iop->max_sense_len) ? SEND_IOCTL_RESP_SENSE_LEN : iop->max_sense_len; if ((SCSI_STATUS_CHECK_CONDITION == iop->scsi_status) && iop->sensep && (len > 0)) { memcpy(iop->sensep, wrk.buff, len); iop->resp_sense_len = len; if (report > 1) { pout(" >>> Sense buffer, len=%d:\n", (int)len); dStrHex(wrk.buff, len , 1); } } if (report) { if (SCSI_STATUS_CHECK_CONDITION == iop->scsi_status) { pout(" status=%x: sense_key=%x asc=%x ascq=%x\n", status & 0xff, wrk.buff[2] & 0xf, wrk.buff[12], wrk.buff[13]); } else pout(" status=0x%x\n", status); } if (iop->scsi_status > 0) return 0; else { if (report > 0) pout(" ioctl status=0x%x but scsi status=0, fail with EIO\n", status); return -EIO; /* give up, assume no device there */ } } void printwarning(smart_command_set command); // PURPOSE // This is an interface routine meant to isolate the OS dependent // parts of the code, and to provide a debugging interface. Each // different port and OS needs to provide it's own interface. This // is the linux interface to the 3ware 3w-xxxx driver. It allows ATA // commands to be passed through the SCSI driver. // DETAILED DESCRIPTION OF ARGUMENTS // fd: is the file descriptor provided by open() // disknum is the disk number (0 to 15) in the RAID array // command: defines the different operations. // select: additional input data if needed (which log, which type of // self-test). // data: location to write output data, if needed (512 bytes). // Note: not all commands use all arguments. // RETURN VALUES // -1 if the command failed // 0 if the command succeeded, // STATUS_CHECK routine: // -1 if the command failed // 0 if the command succeeded and disk SMART status is "OK" // 1 if the command succeeded and disk SMART status is "FAILING" int escalade_command_interface(int fd, int disknum, smart_command_set command, int select, char *data){ // Structures for passing commands through 3Ware Escalade Linux Driver TW_Ioctl ioctlbuf; TW_Passthru passthru; // If command returns 512 bytes, set to 1, else 0 int readdata=0; // Clear out the data structures memset (&ioctlbuf, 0, sizeof(TW_Ioctl)); memset (&passthru, 0, sizeof(TW_Passthru)); // Same for (almost) all commands - but some reset below passthru.byte0.opcode = 0x11; passthru.request_id = 0xFF; passthru.byte3.aport = disknum; passthru.byte3.host_id = 0; passthru.status = 0; passthru.flags = 0x1; passthru.drive_head = 0x0; passthru.sector_num = 0; // All SMART commands use this CL/CH signature. These are magic // values from the ATA specifications. passthru.cylinder_lo = 0x4F; passthru.cylinder_hi = 0xC2; // SMART ATA COMMAND REGISTER value passthru.command = WIN_SMART; // Is this a command that returns 512 bytes? if (command == READ_VALUES || command == READ_THRESHOLDS || command == READ_LOG || command == IDENTIFY) { readdata=1; passthru.byte0.sgloff = 0x5; passthru.size = 0x7; passthru.param = 0xD; passthru.sector_count = 0x1; } else { // Non data command -- but doesn't use large sector // count register values. passthru.param values are: // 0x00 - non data command without TFR write check, // 0x08 - non data command with TFR write check, passthru.byte0.sgloff = 0x0; passthru.size = 0x5; passthru.param = 0x8; passthru.sector_count = 0x0; } // Now set ATA registers depending upon command switch (command){ case READ_VALUES: passthru.features = SMART_READ_VALUES; break; case READ_THRESHOLDS: passthru.features = SMART_READ_THRESHOLDS; break; case READ_LOG: passthru.features = SMART_READ_LOG_SECTOR; // log number to return passthru.sector_num = select; break; case IDENTIFY: // ATA IDENTIFY DEVICE passthru.command = 0xEc; passthru.features = 0; passthru.cylinder_lo = 0; passthru.cylinder_hi = 0; break; case PIDENTIFY: // 3WARE controller can NOT have packet device internally pout("WARNING - NO DEVICE FOUND ON 3WARE CONTROLLER (disk %d)\n", disknum); errno=ENODEV; return -1; case ENABLE: passthru.features = SMART_ENABLE; break; case DISABLE: passthru.features = SMART_DISABLE; break; case AUTO_OFFLINE: passthru.features = SMART_AUTO_OFFLINE; // Enable or disable? passthru.sector_count = select; break; case AUTOSAVE: passthru.features = SMART_AUTOSAVE; // Enable or disable? passthru.sector_count = select; break; case IMMEDIATE_OFFLINE: passthru.features = SMART_IMMEDIATE_OFFLINE; // What test type to run? passthru.sector_num = select; break; case STATUS_CHECK: passthru.features = SMART_STATUS; break; case STATUS: // This is JUST to see if SMART is enabled, by giving SMART status // command. But it doesn't say if status was good, or failing. // See below for the difference. passthru.features = SMART_STATUS; break; default: pout("Unrecognized command %d in linux_3ware_command_interface(disk %d)\n" "Please contact " PACKAGE_BUGREPORT "\n", command, disknum); errno=ENOSYS; return -1; } /* Copy the passthru command into the ioctl input buffer */ memcpy(&ioctlbuf.input_data, &passthru, sizeof(TW_Passthru)); ioctlbuf.cdb[0] = TW_IOCTL; ioctlbuf.opcode = TW_ATA_PASSTHRU; // CHECKME -- IS THIS RIGHT?? Even for non data I/O commands? ioctlbuf.input_length = 512; ioctlbuf.output_length = 512; /* Now send the command down through an ioctl() */ if (ioctl(fd, SCSI_IOCTL_SEND_COMMAND, &ioctlbuf)) { // If error was provoked by driver, tell user how to fix it if ((command==AUTO_OFFLINE || command==AUTOSAVE) && select){ printwarning(command); errno=ENOTSUP; } return -1; } // If this is a read data command, copy data to output buffer if (readdata){ TW_Output *tw_output=(TW_Output *)&ioctlbuf; memcpy(data, tw_output->output_data, 512); } // We are finished with all commands except for STATUS_CHECK if (command!=STATUS_CHECK) { return 0; } else { // To find out if the SMART RETURN STATUS is good or failing, we // need to examine the values of the Cylinder Low and Cylinder // High Registers. TW_Output *tw_output=(TW_Output *)&ioctlbuf; TW_Passthru *tw_passthru_returned=(TW_Passthru *)&(tw_output->output_data); unsigned short cyl_lo=tw_passthru_returned->cylinder_lo; unsigned short cyl_hi=tw_passthru_returned->cylinder_hi; // If values in Cyl-LO and Cyl-HI are unchanged, SMART status is good. if (cyl_lo==0x4F && cyl_hi==0xC2) return 0; // If values in Cyl-LO and Cyl-HI are as follows, SMART status is FAIL if (cyl_lo==0xF4 && cyl_hi==0x2C) return 1; // Any other values mean that something has gone wrong with the command printwarning(command); errno=ENOSYS; return 0; } } // Utility function for printing warnings void printwarning(smart_command_set command){ static int printed1=0,printed2=0,printed3=0; const char* message= "can not be passed through the 3ware 3w-xxxx driver. This can be fixed by\n" "applying a simple 3w-xxxx driver patch that can be found here:\n" PROJECTHOME "\n" "Alternatively, upgrade your 3w-xxxx driver to version 1.02.00.037 or greater.\n\n"; if (command==AUTO_OFFLINE && !printed1) { printed1=1; pout("The SMART AUTO-OFFLINE ENABLE command (smartmontools -o on option/Directive)\n%s", message); } else if (command==AUTOSAVE && !printed2) { printed2=1; pout("The SMART AUTOSAVE ENABLE command (smartmontools -S on option/Directive)\n%s", message); } else if (command==STATUS_CHECK && !printed3) { printed3=1; pout("The SMART RETURN STATUS return value (smartmontools -H option/Directive)\n%s", message); } return; } // Guess device type (ata or scsi) based on device name (Linux // specific) SCSI device name in linux can be sd, sr, scd, st, nst, // osst, nosst and sg. static const char * lin_dev_prefix = "/dev/"; static const char * lin_dev_ata_disk_plus = "h"; static const char * lin_dev_ata_devfs_disk_plus = "ide/"; static const char * lin_dev_scsi_devfs_disk_plus = "scsi/"; static const char * lin_dev_scsi_disk_plus = "s"; static const char * lin_dev_scsi_tape1 = "ns"; static const char * lin_dev_scsi_tape2 = "os"; static const char * lin_dev_scsi_tape3 = "nos"; int guess_device_type(const char * dev_name) { int len; int dev_prefix_len = strlen(lin_dev_prefix); // if dev_name null, or string length zero if (!dev_name || !(len = strlen(dev_name))) return GUESS_DEVTYPE_DONT_KNOW; // Remove the leading /dev/... if it's there if (!strncmp(lin_dev_prefix, dev_name, dev_prefix_len)) { if (len <= dev_prefix_len) // if nothing else in the string, unrecognized return GUESS_DEVTYPE_DONT_KNOW; // else advance pointer to following characters dev_name += dev_prefix_len; } // form /dev/h* or h* if (!strncmp(lin_dev_ata_disk_plus, dev_name, strlen(lin_dev_ata_disk_plus))) return GUESS_DEVTYPE_ATA; // form /dev/ide/* or ide/* if (!strncmp(lin_dev_ata_devfs_disk_plus, dev_name, strlen(lin_dev_ata_devfs_disk_plus))) return GUESS_DEVTYPE_ATA; // form /dev/s* or s* if (!strncmp(lin_dev_scsi_disk_plus, dev_name, strlen(lin_dev_scsi_disk_plus))) return GUESS_DEVTYPE_SCSI; // form /dev/scsi/* or scsi/* if (!strncmp(lin_dev_scsi_devfs_disk_plus, dev_name, strlen(lin_dev_scsi_devfs_disk_plus))) return GUESS_DEVTYPE_SCSI; // form /dev/ns* or ns* if (!strncmp(lin_dev_scsi_tape1, dev_name, strlen(lin_dev_scsi_tape1))) return GUESS_DEVTYPE_SCSI; // form /dev/os* or os* if (!strncmp(lin_dev_scsi_tape2, dev_name, strlen(lin_dev_scsi_tape2))) return GUESS_DEVTYPE_SCSI; // form /dev/nos* or nos* if (!strncmp(lin_dev_scsi_tape3, dev_name, strlen(lin_dev_scsi_tape3))) return GUESS_DEVTYPE_SCSI; // we failed to recognize any of the forms return GUESS_DEVTYPE_DONT_KNOW; }