📄 scsi.c
字号:
/* * scsi.c * * Copyright (C) 2001, Red Hat, Inc. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * * 08/27/01 - Initial version snagged by Doug Ledford from the scsires * package. */#include <stdio.h>#include <stdlib.h>#include <string.h>#include <fcntl.h>#include <errno.h>#include <unistd.h>#include <sys/types.h>#include <sys/ioctl.h>#include <scsi/scsi.h>#include <scsi/sg.h>#include <scsi/scsi_ioctl.h>#include "scsi.h"/* * Function declarations that are for internal functions and therefore not * included in the scsires.h header file. */static int scsi_get_device_id_page(scsi_sg_dev_t *);static int scsi_get_serial_number_page(scsi_sg_dev_t *);/* * Now to the functions *//* * Function: scsi_print_device_info * * Inputs: * scsi_sg_dev_t * - A pointer to a device structure. Device * information contained in this structure will be used * to print out a description of the device in question. * * Outputs: * None, except to stdout * * Purpose: Print out some basic information about a device for debugging * use. */voidscsi_print_device_info(scsi_sg_dev_t * sg_dev){ if (!sg_dev) return; printf("\nName:\t\t%s\nModel:\t\t%s %s %s\nSCSI Rev:\t%d\n" "Device ID:\t%d:%d:%d:%d\nBlock Size:\t%d bytes\n" "Total Size:\t%d MBytes\n", sg_dev->name, sg_dev->vendor, sg_dev->product, sg_dev->revision, sg_dev->scsi_rev, sg_dev->scsi_dev_host, sg_dev->scsi_dev_bus, sg_dev->scsi_dev_id, sg_dev->scsi_dev_lun, sg_dev->block_size, (sg_dev->block_size * (sg_dev->num_blocks / 1024) / 1024)); if (sg_dev->fd != -1) { if (sg_dev->sg_name) printf("SG Name:\t%s\n", sg_dev->sg_name); }}/* * Function: scsi_init_sg_device * * Inputs: * int - This is a file descriptor that points to a disk device * that the programmer wishes to place a SCSI reservation on. * const char * - This is a pointer to a string that should be * the printable name of the device (possibly as passed in * on the command line) * Outputs: * struct scsi_sg_dev* - A pointer to a newly allocated structure * is returned on success and on partial success. A NULL pointer * is returned on permanent failure. Partial success is defined * as any condition that causes an otherwise init'able drive * to fail to init for temporary conditions (such as we found * all the needed devices and started to set up the device struct * and then found out that we are getting I/O errors on most * commands because someone else already has a reservation on the * device). A calling function should check the returned sg_dev * struct to see if initialized == TRUE. If it does, then the * setup was complete. If it doesn't, then the setup was only * partial and the device is not yet ready for use. The calling * code should then be ready to recall this function later when * it has some reason to believe the code would actually succeed. * After a fully init'ed sg_dev struct is returned, the partially * init'ed sg_dev struct should be free()'d by the calling code. * * Purpose: Initialize an sg device for use by this code. Once the sg * device that corresponds to disk_fd has been found, this function will * issue a couple test commands to the device to test its capabilities, * and will then fill out a newly allocated scsi_sg_dev struct * with the information it finds. Specifically, it will fill in the * following items with the information it finds: * * fd - the new fd that points to the sg device will be put here * disk_fd - the original fd will be put here * scsi_rev - the SCSI revision of the device will be filled in (we * only support revision 2 or 3 devices at the moment) * block_size - the block size of the device as the *device* says it is, * not the block size that the block device layer reports. It * is imperative that the user not confuse this number with * anything else when making reservations. The OS may claim * that a device has a 4K block size when using a 4K * filesystem, but if the device is actually using 512byte blocks, * then we *MUST* use that number when calculating block offsets * or else the reservation will be in the wrong place. * scsi_dev_{id,lun,bus,host} - these will be filled in with the values * that actually belong to this device * * If the function is unable to access the sg devices (the sg driver is * not present in the kernel) or is unable to match any sg device to * the passed in disk_fd device, then a NULL pointer will be returned * instead of a pointer to a filled out structure. (Other mundane * errors may also result in a NULL pointer return) */extern scsi_sg_dev_t *scsi_init_sg_device(int disk_fd, const char *argv){ scsi_sg_dev_t *sg_dev; unsigned int int_array[3], i, fd; scsi_send_command_t cmd; char buffer[128]; if ((sg_dev = malloc(sizeof(scsi_sg_dev_t))) == NULL) { printf("Unable to allocate memory for sg_dev\n"); return (NULL); } memset((void *)sg_dev, 0, sizeof(struct scsi_sg_dev)); sg_dev->fd = -1; sg_dev->disk_fd = disk_fd; sg_dev->name = (char *)argv; sg_dev->initialized = FALSE; /* * Get the device ID parameters from the linux kernel for the disk * device (aka, /dev/sda or whatever was passed in on the command * line or config file). These numbers are unique for any given * device. */ if ((ioctl(disk_fd, SCSI_IOCTL_GET_IDLUN, &int_array[0]) == -1) || (ioctl(disk_fd, SCSI_IOCTL_GET_BUS_NUMBER, &int_array[2]) == -1)) { perror("scsi_init_sg_device: " "Unable to get ID, LUN, bus, or host of drive"); scsi_release_sg_device(sg_dev); return (NULL); } sg_dev->scsi_dev_id = int_array[0] & 0xff; sg_dev->scsi_dev_lun = (int_array[0] >> 8) & 0xff; sg_dev->scsi_dev_bus = (int_array[0] >> 16) & 0xff; sg_dev->scsi_dev_host = int_array[2]; /* * Open the /dev/sg? entries one at a time and check each of them * to see if the device ID values match the device ID values we have * on our disk device. If they do, then we've found the /dev/sg entry * that matches our /dev/sd entry. If we run out of entries, then we * can't go any further reliably. */ i = 0; sprintf(buffer, "/dev/sg%d", i++); while ((fd = open(buffer, O_RDWR | O_NDELAY)) != -1) { if ((ioctl(fd, SCSI_IOCTL_GET_IDLUN, &int_array[0]) == -1) || (ioctl(fd, SCSI_IOCTL_GET_BUS_NUMBER, &int_array[2]) == -1)) { perror(buffer); close(fd); sprintf(buffer, "/dev/sg%d", i++); continue; } if ((sg_dev->scsi_dev_id == (int_array[0] & 0xff)) && (sg_dev->scsi_dev_lun == ((int_array[0] >> 8) & 0xff)) && (sg_dev->scsi_dev_bus == ((int_array[0] >> 16) & 0xff)) && (sg_dev->scsi_dev_host == int_array[2])) { sg_dev->fd = fd; sg_dev->sg_name = malloc(strlen(buffer) + 2); if (sg_dev->sg_name != NULL) sprintf((void *)sg_dev->sg_name, "%s", buffer); break; } close(fd); sprintf(buffer, "/dev/sg%d", i++); } if (sg_dev->fd == -1) { printf("scsi_init_sg_device: unable to find the matching " "sg device\nto go with the target disk device %s\n", sg_dev->name); scsi_release_sg_device(sg_dev); return (NULL); } /* * Be prepared to do the INQUIRY twice incase this is our first * command since a reset. We might have to absorb one failed * INQUIRY due to a UNIT_ATTENTION sense return from the device. */ do { cmd.outsize = 0; cmd.insize = 56; memset(cmd.buf, 0, 56); cmd.buf[0] = INQUIRY; cmd.buf[4] = 56; if (SEND_COMMAND(sg_dev, cmd) == -1) { perror("failed on INQUIRY"); return (sg_dev); } } while (scsi_retryable_error(sg_dev, cmd, FALSE, DEF_WAIT)); if (!WAS_OK(cmd)) { printf("%s: scsi error on INQUIRY, status byte = 0x%x\n", sg_dev->name, STATUS(cmd)); return (sg_dev); } /* * Make sure the device meets all of our "strict" requirements. It * must be a disk drive (Direct Access medium, could actually be * a Direct Access tape drive, but if you start putting reservations * on one of those then what happens is your own fault), may not be * a removable medium drive, and the peripheral qualifier returned * from the INQUIRY command must indicate that this LUN is connected * to the drive. */ if ((cmd.buf[0] & 0x1f) != 0) { printf("%s: non-disk devices are not supported\n", sg_dev->name); scsi_release_sg_device(sg_dev); return (NULL); } if ((cmd.buf[0] & 0xe0) != 0) { printf("%s: this device is not currently ready for use\n", sg_dev->name); scsi_release_sg_device(sg_dev); return (NULL); } if (cmd.buf[1] & 0x80) { printf("%s: removable devices are not supported\n", sg_dev->name); scsi_release_sg_device(sg_dev); return (NULL); } /* * Grab the needed info from the INQUIRY results. */ sg_dev->scsi_rev = cmd.buf[2] & 0x7; memcpy((void *)&sg_dev->vendor[0], (const void *)&cmd.buf[8], 8); memcpy((void *)&sg_dev->product[0], (const void *)&cmd.buf[16], 16); memcpy((void *)&sg_dev->revision[0], (const void *)&cmd.buf[32], 4); sg_dev->vendor[8] = '\0'; sg_dev->product[16] = '\0'; sg_dev->revision[5] = '\0'; /* * First try the device ID page. Then get the serial number. * One of the two should provide reasonable information. */ scsi_get_device_id_page(sg_dev); scsi_get_serial_number_page(sg_dev); /* * Good, we've passed everything now, so mark the device as init'ed * and send it back to the calling function. */ sg_dev->initialized = TRUE; return (sg_dev);}/* * Function: scsi_get_device_id_page * * Inputs: * scsi_sg_dev_t * - The sg_dev we want to get size information for * * Outputs: * int - 0 on success, 1 on any error or failure. * * Purpose: * Used to provide a unique identifier to each device. This can then * be used to detect multipath drives on the fly. This is the preferred * way to get the unique identifier. However, when this isn't supported * and the serial number page is, then we use it as a backup. */static intscsi_get_device_id_page(scsi_sg_dev_t *sg_dev){ scsi_send_command_t cmd; int i; /* * Get the Device Identification page from another INQUIRY command */ do { cmd.outsize = 0; cmd.insize = 255; memset(cmd.buf, 0, 255); cmd.buf[0] = INQUIRY; cmd.buf[1] = 1; /* EVPD bit */ cmd.buf[2] = 0x83; /* Device ID Page */ cmd.buf[4] = 255;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -