1
0
Files
irix-657m-src/eoe/cmd/raid/raid.c
2022-09-29 17:59:04 +03:00

2441 lines
65 KiB
C

/**************************************************************************
* *
* Copyright (C) 1993, Silicon Graphics, Inc. *
* *
* These coded instructions, statements, and computer programs contain *
* unpublished proprietary information of Silicon Graphics, Inc., and *
* are protected by Federal copyright law. They may not be disclosed *
* to third parties or copied or duplicated in any form, in whole or *
* in part, without the prior written consent of Silicon Graphics, Inc. *
* *
**************************************************************************/
#ident "$Revision: 1.13 $"
/*
* raid - UltraStor U144F RAID administrative utility
*/
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#include <syslog.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/fcntl.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <invent.h>
#include <sys/dkio.h>
#include <sys/dvh.h>
#include <sys/usraid_admin.h>
#define RAID4 1 /* sector level striping, fixed parity disk */
#define RAID4_STRIPE 32 /* default RAID4 stripe depth in sectors */
#define RAID5 2 /* sector level striping, floating parity disk */
#define RAID5_STRIPE 32 /* default RAID5 stripe depth in sectors */
#define READ 1 /* transfer from RAID to program */
#define WRITE 2 /* transfer from program to RAID */
#define PART_BASE 32 /* reserve some sectors at the beginning */
/*
* This is the structure that is written to the reserved area on each
* physical disk. It is used to ensure that this disk came from this
* RAID (random config ID, a timestamp, and the disk's slot number).
*/
#define RAID_CONFIG_REV 1 /* current rev number */
#define RAID_CONFIG_MAGIC 0xa102366a /* magic number */
struct raid_config {
int version; /* revision number: backward compat. */
int magic; /* magic number: validity checking */
unsigned int config_id; /* unique ID for this RAID and config */
unsigned long time_sec; /* timestamp at config: seconds */
unsigned long time_usec; /* timestamp at config: microseconds */
int slot_number; /* disks only: which slot is it in */
int mode; /* RAID4 or RAID5 mode */
int stripe_depth; /* stripe depth in sectors */
int num_stripes; /* number of stripes in logical disk */
char vendor[9]; /* vendor ID string */
char product[17]; /* product ID string */
char revision[5]; /* revision ID string */
int totalsize; /* total number sectors on drive */
int blocksize; /* sector size of drive */
};
/*
* This is the structure we use to track all the related informtion
* about a specific RAID.
*/
struct raid {
struct raid *next; /* linked list */
int error; /* entry has error, ignore it */
char pathname[256]; /* /dev/dsk pathname */
char basename[128]; /* base of pathname */
struct usraid_defn_page defn_page; /* global RAID definitions */
struct usraid_part_page part_page; /* per drive definitions */
struct usraid_phys_conn phys_info[5]; /* per drive physical info */
struct usraid_units_down downs; /* down drive info from ctlr */
struct raid_config config[6]; /* per drive config info */
struct volume_header vh; /* volume header from device */
int vh_valid; /* T/F: is volume header valid? */
int conf_valid[6]; /* T/F: is config[x] valid? */
int initted; /* RAID has been initialized */
int wrtprot; /* RAID has been write-protected */
int deadslot; /* down disk, cntlr, or -1 */
int redo_ctlr; /* setup to reprogram controller */
};
struct raid *raidhead; /* list of RAIDs to process */
struct raid *list_raids(char *); /* routine to generate that list */
int inventory_raids(); /* routine to check HINV for RAIDs */
struct raid *allocate_raid(char *);
int raid_fd; /* file desc. of open RAID device */
int avoid_cm_message; /* T/F: don't print "controller bad" msg */
unsigned int gen_config_id();
char *progname; /* argv[0] for error messages */
int syslogit; /* message* will also use SYSLOG */
extern int errno; /* error result of system calls */
extern char *partname(char *, int);
extern int has_fs(char *, int);
extern char *getmountpt(char *, char *);
usage()
{
fprintf(stderr, "usage:\n");
fprintf(stderr, " %s [-L] -p [-v] [path]\t\t# print config for path or all\n", progname);
fprintf(stderr, " %s [-L] -c [-m] [-f] [path]\t\t# check for down drives for path or all\n", progname);
fprintf(stderr, " %s [-L] -i [path]\t\t\t# integrity check path or all\n", progname);
fprintf(stderr, " %s [-L] -d driveno [-f] path\t# mark down a drive on path (force it)\n", progname);
fprintf(stderr, " %s [-L] -r path\t\t\t# rebuild path\n", progname);
fprintf(stderr, " %s [-L] -{3|5} [-s stripe] [-S size] [-f] path\t# RAID3/RAID5 format\n", progname);
fprintf(stderr, " %s [-L] -z [-f] path\t\t# forcibly reset NVRAM to factory specs\n", progname);
fprintf(stderr, " %s [-L] -l firmware [-f] path\t# download new firmware to path\n", progname);
exit(1);
}
main(argc, argv)
int argc;
char *argv[];
{
int type, typeset, stripe, stripeset, size, sizeset;
int force, mark, driveno, verbose;
char *path, *firmpath;
int ch, error = 0;
char *onlymsg = "only specify p, c, i, d, r, 3, 5, z, or l once";
enum {
NONE, PRINT, CHECKDOWN, INTEGRITY, FORCEDOWN,
REBUILD, FORMAT, FACTORY, DOWNLOAD
} request = NONE;
/*
* pick up the command line arguments
*/
#define ASSERT(COND, TEXT) \
if (!(COND)) { \
fprintf(stderr, "%s: usage: %s\n", progname, (TEXT)); \
error++; \
}
type = typeset = stripe = stripeset = size = sizeset = 0;
force = mark = driveno = verbose = 0;
progname = argv[0];
while ((ch = getopt(argc, argv, "pvcmid:fr35zl:s:S:L")) != EOF)
{
switch (ch) {
case 'p':
ASSERT(request == NONE, onlymsg);
request = PRINT;
break;
case 'v':
ASSERT(verbose == 0, "only specify verbose option once");
verbose = 1;
break;
case 'c':
ASSERT(request == NONE, onlymsg);
request = CHECKDOWN;
break;
case 'm':
ASSERT(mark == 0, "only specify marking down option once");
mark = 1;
break;
case 'i':
ASSERT(request == NONE, onlymsg);
request = INTEGRITY;
break;
case 'd':
ASSERT(request == NONE, onlymsg);
request = FORCEDOWN;
driveno = atoi(optarg);
ASSERT(driveno >= 0, "drive number must be >= 0");
ASSERT(driveno <= 4, "drive number must be <= 4");
break;
case 'f':
ASSERT(!force, "only specify force option once");
force++;
break;
case 'r':
ASSERT(request == NONE, onlymsg);
request = REBUILD;
break;
case '3':
ASSERT(request == NONE, onlymsg);
request = FORMAT;
type = RAID4;
typeset++;
if (!stripeset)
stripe = RAID4_STRIPE;
break;
case '5':
ASSERT(request == NONE, onlymsg);
request = FORMAT;
type = RAID5;
typeset++;
if (!stripeset)
stripe = RAID5_STRIPE;
break;
case 'z':
ASSERT(request == NONE, onlymsg);
request = FACTORY;
break;
case 'l':
ASSERT(request == NONE, onlymsg);
request = DOWNLOAD;
firmpath = optarg;
break;
case 's':
ASSERT(!stripeset, "only specify stripe depth once");
stripe = atoi(optarg);
ASSERT(stripe >= 4, "stripe size must be >= 4");
ASSERT(stripe <= 64, "stripe size must be <= 64");
ASSERT(((stripe % 4) == 0), "stripe depth must be a multiple of 4");
stripeset++;
break;
case 'S':
ASSERT(!sizeset, "only specify drive size once");
size = atoi(optarg);
ASSERT(size > 0, "disk size must be > 0");
sizeset++;
break;
case 'L':
ASSERT(!syslogit, "only specify SYSLOG option once");
syslogit++;
break;
default:
error++;
break;
}
}
if (error)
usage();
/*
* allowable argument structures -
* raid [-L] -p [-v] [path] # print config for path or all
* raid [-L] -c [-m] [-f] [path] # check for (and mark) down drives for path or all
* raid [-L] -i [path] # integrity check path or all
* raid [-L] -d driveno [-f] path # force down a drive on path
* raid [-L] -r path # rebuild path
* raid [-L] -{3|5} [-s stripe] [-S size] [-f] path # RAID3/RAID5 format
* raid [-L] -z [-f] path # forcibly zero NVRAM (reset to factory specs)
* raid [-L] -l firmware [-f] path # download new firmware to path
*/
if (request != PRINT) {
ASSERT(!verbose, "-v only valid when printing configurations");
}
if (request != CHECKDOWN) {
ASSERT(!mark, "-m only valid when checking for down drives");
}
if ((request != FORCEDOWN) && (request != CHECKDOWN) &&
(request != FORMAT) && (request != FACTORY) && (request != DOWNLOAD)) {
ASSERT(!force, "-f only valid with '-c', '-d', '-3', '-5', '-z', '-l' or options");
}
if (request != FORMAT) {
ASSERT(!typeset, "-3 or -5 only valid when formatting");
ASSERT(!stripeset, "-s only valid when formatting");
ASSERT(!sizeset, "-S only valid when formatting");
}
switch(request) {
case FORMAT:
ASSERT(typeset, "must specify RAID type (eg: -3 or -5)");
ASSERT(!sizeset || (size % stripe) == 0, "disk size must be a multiple of stripe depth");
/* fallthru */
case FORCEDOWN:
case REBUILD:
case FACTORY:
case DOWNLOAD:
if (optind == (argc-1)) {
path = argv[optind];
} else if (optind == argc) {
ASSERT(0, "must specify RAID pathname");
} else {
ASSERT(0, "must specify at most one pathname");
}
if (geteuid() != 0) {
fprintf(stderr, "%s: must have root permission to execute\n", argv[0]);
exit(0);
}
break;
case PRINT:
case CHECKDOWN:
case INTEGRITY:
if (optind == (argc-1)) {
path = argv[optind];
} else {
ASSERT(optind == argc, "must specify at most one pathname");
path = NULL;
}
break;
default:
error++;
break;
}
if (error)
usage();
#undef ASSERT
openlog("RAID", LOG_CONS, LOG_DAEMON);
/*
* Go do the work
*/
switch(request) {
case FORMAT: error = format(path, stripe, type, size, force); break;
case FORCEDOWN: error = forcedown(path, driveno, force); break;
case REBUILD: error = rebuild(path); break;
case FACTORY: error = factory(path, force); break;
case DOWNLOAD: error = download(path, firmpath, force); break;
case PRINT: error = print(path, verbose); break;
case CHECKDOWN: error = checkdown(path, mark, force); break;
case INTEGRITY: error = integrity(path); break;
}
closelog();
exit(error);
}
/*============================================================================
* User requested operational routines
*/
/*
* Format a new RAID
*
* The sequence of operations to set up a new RAID drive:
* + Execute the "return physical connection" command for each address
* + Force controller to look at and recognize each drive
* + Only look for the 5 known addresses
* + Save size reported for each drive
* + All sizes must match
* + Write page 0x38: Drive Group Definition
* + Define width 4 (ie: 4 data streams)
* + Define stripe block size as some multiple of 4
* + Define drive size as 4 * single disk size (as reported above)
* + Define parity and no mirroring
* + Define RAID4/5 parity diagram (map stream number to parity sector)
* + Write page 0x39: Partition Definition
* + Define both channel and target to be the slot number
* + Ascending order (slot number must map to stream number)
* + Define beginning sector to be 0
* + Define size to be single disk size (as reported above)
* + If !zero then execute the "initialize unit" command
* + Used to force the parity to match the data
* + Can be avoided in emergency situations
* + Reformat with same params, may recover more data that way
* + Write the config info to the reserved sector on each disk
* + Write to the reserved sector of each disk
* + Set the magic number information in the controller
* + Use the NVRAM write commands to set the reserved NVRAM bytes
* + Set NVRAM bit saying LUN 0 initialized
* + Execute a "mode sense" command on LUN 0
* + Just to test that all config has gone smoothly
*/
format(char *path, int stripe, int type, int size, int force)
{
struct raid *raidp;
struct usraid_phys_conn *physp;
struct usraid_units_down downs;
struct usraid_nvram_data data;
struct raid_config *cp;
unsigned int config_id;
struct timeval tv;
int drivesz, error = 0, i;
char buffer[32];
raidp = allocate_raid(path);
/*
* get a handle on the specified RAID
*/
if (error = raidopen(raidp->pathname)) {
message_perror(raidp, "open of RAID failed");
return(error);
}
/*
* Check for mounted filesystems.
*/
if (checkforfs(raidp, 1)) {
error = EPERM;
goto out;
}
/*
* poll each drive and check it's size
*/
for (i = 0; i < 5; i++) {
raidp->phys_info[i].drive = i;
if (ioctl(raid_fd, USRAID_PHYS_CONN, &raidp->phys_info[i]) < 0) {
error = errno;
message_perror(raidp, "Drive %d is missing, formatting failed", i);
break;
}
if (i == 0) {
drivesz = raidp->phys_info[i].totalsize;
} else if (raidp->phys_info[i].totalsize != drivesz) {
error = EINVAL;
message(raidp, "Drive %d is not the same size (%d) as the other drives: %d sectors",
1, i, raidp->phys_info[i].totalsize, drivesz);
break;
}
/*
* Ensure that this drive is supported in a RAID configuration.
*/
if ((strncmp(raidp->phys_info[i].vendor, "SGI ", 8) != 0) ||
(strncmp(raidp->phys_info[i].product, "0664", 4) != 0)) {
error = EINVAL;
message(raidp, "Drive %d is not a supported drive type:",
1, i, 0);
message(raidp, "vendor: %s, product: %s, revision: %s",
1, raidp->phys_info[i].vendor,
raidp->phys_info[i].product);
break;
}
}
if (error)
goto out;
/*
* Adjust the device sizes to reserve sectors for our use.
*/
for (i = 0; i < 5; i++)
raidp->phys_info[i].totalsize -= PART_BASE;
drivesz -= PART_BASE;
if ((size > 0) && (drivesz > size))
drivesz = size;
/*
* Ask for confirmation from the user.
*/
if (!confirm(raidp, force, "format", 0)) {
error = EPERM;
goto out;
}
/*
* update the NVRAM to show the drive has been NOT been initialized
*/
raidp->initted = 0;
data.addr = USRAID_NVRAM_INITTED;
if (ioctl(raid_fd, USRAID_GET_NVRAM, &data) < 0) {
error = errno;
message_perror(raidp, "Read of NVRAM init flag failed");
}
data.data &= ~0x01;
if (ioctl(raid_fd, USRAID_SET_NVRAM, &data) < 0) {
error = errno;
message_perror(raidp, "Write of NVRAM init flag failed");
}
/*
* Clear indication that any drives are down and
* reset the write-protect bit.
*/
for (i = 0; i < 5; i++)
downs.drive[i] = 1;
if (ioctl(raid_fd, USRAID_CLR_DOWN, &downs) < 0) {
error = errno;
message_perror(raidp, "Clearing down disk info failed");
goto out;
}
raidp->wrtprot = 0;
/*
* ensure that drive size is a multiple of the stripe width
*/
if ((drivesz % stripe) != 0)
drivesz -= (drivesz % stripe);
/*
* generate the appropriate configuration information
*/
if (gettimeofday(&tv, NULL) < 0) {
error = errno;
message_perror(raidp, "gettimeofday() failed");
goto out;
}
config_id = gen_config_id();
for (i = 0; i < 6; i++) { /* note: 6 entries, last for cntlr */
cp = &raidp->config[i];
cp->version = RAID_CONFIG_REV;
cp->magic = RAID_CONFIG_MAGIC;
cp->config_id = config_id;
cp->time_sec = tv.tv_sec;
cp->time_usec = tv.tv_usec;
cp->slot_number = i;
cp->mode = type;
cp->stripe_depth = stripe;
cp->num_stripes = drivesz/stripe;
if (i < 5) {
physp = &raidp->phys_info[i];
blankblaster(physp->vendor);
blankblaster(physp->product);
blankblaster(physp->revision);
strcpy(cp->vendor, physp->vendor);
strcpy(cp->product, physp->product);
strcpy(cp->revision, physp->revision);
cp->totalsize = physp->totalsize;
cp->blocksize = physp->blocksize;
}
raidp->conf_valid[i] = 1;
}
raidp->initted = 0;
raidp->deadslot = -1;
/*
* Set up the mode pages on each disk.
*/
for (i = 0; i < 5; i++) {
if (error = program_disk(raidp, i, (i == 0))) {
goto out;
}
}
/*
* program the RAID controller
*/
if (error = write_controller(raidp))
goto out;
/*
* Initialize all sectors on the RAID
*/
if (ioctl(raid_fd, USRAID_INITDRIVE, 0) < 0) {
error = errno;
message_perror(raidp, "RAID initialization failed");
goto out;
}
/*
* write the config info to all 5 disks.
*
* Note that the reserved sectors are the last 128 sectors
* on that drive (ie: relative to the physical disk size)
* of which SGI has control of the first 8.
*/
for (i = 0; i < 5; i++) {
if (error = rw_config(raidp, i, WRITE)) {
goto out;
}
}
/*
* update the NVRAM to show the drive has been initialized
*/
raidp->initted = 1;
data.addr = USRAID_NVRAM_INITTED;
if (ioctl(raid_fd, USRAID_GET_NVRAM, &data) < 0) {
error = errno;
message_perror(raidp, "Read of NVRAM init flag failed");
}
data.data |= 0x01;
if (ioctl(raid_fd, USRAID_SET_NVRAM, &data) < 0) {
error = errno;
message_perror(raidp, "Write of NVRAM init flag failed");
}
/*
* execute an "inquiry" on the drive, just to ensure that no
* errors come back.
*/
if (ioctl(raid_fd, DIOCDRIVETYPE, buffer) < 0) {
error = errno;
message_perror(raidp, "inquiry comand on RAID failed");
}
out:
raidclose();
if (!error) {
i = syslogit;
syslogit = 1;
message(raidp, "Successfully formatted for %d blocks, stripe size: %d",
2, drivesz*4, stripe*4);
syslogit = i;
}
return(error);
}
/*
* Set a RAID to factory new condition, irregardless of attached disks, etc.
*
* The sequence of operations to factory reset a RAID drive:
* + Use "clear NVRAM" command, then reset controller.
* + Firmware will then completely re-initialize NVRAM contents.
*/
factory(char *path, int force)
{
struct raid *raidp;
char buffer[32];
int error = 0, i;
raidp = allocate_raid(path);
/*
* get a handle on the specified RAID
*/
if (error = raidopen(raidp->pathname)) {
message_perror(raidp, "open of RAID failed");
goto out;
}
/*
* Ask for confirmation from the user.
*/
if (checkforfs(raidp, 1) || !confirm(raidp, force, "factory zero", 0)) {
error = EPERM;
goto out;
}
/*
* forces controller to completely re-initialize NVRAM.
*/
if (ioctl(raid_fd, USRAID_CLEAR_NVRAM, 0) < 0) {
error = errno;
message_perror(raidp, "Clear NVRAM ioctl failed");
goto out;
}
/*
* execute an "inquiry" on the drive, just to ensure that no
* errors come back.
*/
if (ioctl(raid_fd, DIOCDRIVETYPE, buffer) < 0) {
error = errno;
message_perror(raidp, "inquiry command on RAID failed");
}
i = syslogit;
syslogit = 1;
message(raidp, "Press the RESET button on the RAID to complete the zeroing", 2);
syslogit = i;
out:
raidclose();
return(errno);
}
/*
* Rebuild a drive on a RAID
*
* The sequence of operations to rebuild a RAID after a drive failure:
* + User should replace the disk hardware
* + Execute the "return units down" command (indirectly thru list_raids)
* + Verify that there is exactly one disk marked as down
* + Execute the "return physical connection" command
* + Forces the controller to look at the new drive
* + Check disk size, etc.
* + Refuse to rebuild if different
* + Spin drive down again
* + Low level format of the new drive (optional)
* + Done via pass-thru commands
* + Also surface analysis if requested
* + Execute the "rebuild" command
* + Wait for it to complete, will take awhile
* + Write the reserved area on the new disk
* + Write to the reserved sector of each disk
* + Clear the "down" bit
*/
rebuild(char *path)
{
struct raid *raidp;
struct usraid_phys_conn *physp;
struct usraid_rebuild rebuild_data;
struct usraid_nvram_data data;
struct raid_config *cp;
unsigned int config_id;
struct timeval tv;
int drivesz, error = 0, i;
raidp = list_raids(path);
if (raidp == NULL)
return(ENOENT);
if (raidp->error) {
message(raidp, "Can't rebuild this RAID", 0);
return(raidp->error);
}
if (raidp->wrtprot) {
message(raidp, "Can't rebuild a RAID that has had a write failure, it must be reformatted.", 0);
return(EIO);
}
/*
* confirm that a single drive has been marked as down
*/
if (raidp->deadslot < 0) {
message(raidp, "All disks are OK in this RAID", 0);
return(0);
}
if (error = raidopen(raidp->pathname)) {
message_perror(raidp, "open of RAID failed");
return(error);
}
/*
* poll each drive and check it's size
*/
for (i = 0; i < 5; i++) {
physp = &raidp->phys_info[i];
physp->drive = i;
if (ioctl(raid_fd, USRAID_PHYS_CONN, physp) < 0) {
error = errno;
message_perror(raidp, "Drive %d is missing, rebuild failed", i);
break;
}
physp->totalsize -= PART_BASE;
if (i == 0) {
drivesz = physp->totalsize;
} else if (physp->totalsize != drivesz) {
error = ENOSPC;
message(raidp, "Drive %d is not the same size as the other drives: %d sectors",
1, i, drivesz);
break;
}
/*
* Ensure that this drive is supported in a RAID configuration.
*/
if ((strncmp(physp->vendor, "SGI ", 8) != 0) ||
(strncmp(physp->product, "0664", 4) != 0)) {
error = EINVAL;
message(raidp, "Drive %d is not a supported drive type:",
1, i, 0);
message(raidp, "vendor: %s, product: %s, revision: %s",
1, physp->vendor, physp->product);
break;
}
}
if (error)
goto out;
/*
* Set up the mode pages on the rebuilt disk.
* If rebuilding drive 0, turn on spindle sync on alternate master
* (drive 1).
*/
if (raidp->deadslot == 0) {
if (error = program_disk(raidp, 0, 1)) {
message(raidp, "Failed to program the mode pages on disk %d:", 1, 0);
goto out;
}
if (error = program_disk(raidp, 1, 0)) {
message(raidp, "Failed to program the mode pages on disk %d:", 1, 1);
goto out;
}
} else if (error = program_disk(raidp, raidp->deadslot, 0)) {
message(raidp, "Failed to program the mode pages on disk %d:", 1, raidp->deadslot);
goto out;
}
/*
* rebuild the new drive from the existing data/parity info
*/
rebuild_data.drive = raidp->deadslot;
rebuild_data.lba = 0;
if (ioctl(raid_fd, USRAID_REBUILD, &rebuild_data) < 0) {
error = errno;
i = syslogit;
syslogit = 1;
message_perror(raidp, "Rebuild failed");
syslogit = i;
goto out;
}
/*
* generate a copy of the config structure for the new drive,
* and write it to the reserved area of that disk.
*
* this is done now so that if power fails while we are doing
* the next section, we have a bit more of a chance of seeing
* all the disks match. If we didn't do this, we are almost
* guaranteeing that we will think we have a dual failure.
*/
cp = &raidp->config[raidp->deadslot];
physp = &raidp->phys_info[raidp->deadslot];
*cp = raidp->config[5]; /* struct copy */
cp->slot_number = raidp->deadslot;
blankblaster(physp->vendor);
blankblaster(physp->product);
blankblaster(physp->revision);
strcpy(cp->vendor, physp->vendor);
strcpy(cp->product, physp->product);
strcpy(cp->revision, physp->revision);
cp->totalsize = physp->totalsize;
cp->blocksize = physp->blocksize;
if (error = rw_config(raidp, raidp->deadslot, WRITE))
goto out;
raidp->conf_valid[raidp->deadslot] = 1;
/*
* Clear the indication that the drive is down.
*/
bzero((char *)&raidp->downs, sizeof(raidp->downs));
raidp->downs.drive[raidp->deadslot] = 1;
if (ioctl(raid_fd, USRAID_CLR_DOWN, &raidp->downs) < 0) {
error = errno;
message_perror(raidp, "Clearing down disk info failed");
goto out;
}
/*
* generate new config info so that our config won't match the disk
* that was just replaced. If we didn't do this, then pulling
* drive A, rebuilding with drive B, and swapping A for B would
* get us bad data. By changing the config, drive A will no
* longer match, and will then be recognized as foreign.
*
* note that this opens a hole where if power failed, we would not
* have consistent config info on the disks. The format "nozero"
* option is our way out of this hole.
*/
if (gettimeofday(&tv, NULL) < 0) {
error = errno;
message_perror(raidp, "gettimeofday() failed");
goto out;
}
config_id = gen_config_id();
for (i = 0; i < 6; i++) {
cp = &raidp->config[i];
cp->config_id = config_id;
cp->time_sec = tv.tv_sec;
cp->time_usec = tv.tv_usec;
if (i < 5) {
if (error = rw_config(raidp, i, WRITE)) {
/* XXXGROT: if this ever fails, the RAID is toast */
goto out;
}
}
}
/*
* write the config ID
*/
data.addr = USRAID_NVRAM_CONFIG_ID;
data.data = cp->config_id;
if (ioctl(raid_fd, USRAID_SET_NVRAM, &data) < 0) {
error = errno;
message_perror(raidp, "Write of NVRAM config-id failed");
}
/*
* write the timestamp
*/
data.addr = USRAID_NVRAM_TIME_SEC;
data.data = cp->time_sec;
if (ioctl(raid_fd, USRAID_SET_NVRAM, &data) < 0) {
error = errno;
message_perror(raidp, "Write of NVRAM timestamp failed");
}
data.addr = USRAID_NVRAM_TIME_USEC;
data.data = cp->time_usec;
if (ioctl(raid_fd, USRAID_SET_NVRAM, &data) < 0) {
error = errno;
message_perror(raidp, "Write of NVRAM timestamp failed");
}
/*
* Use the "return units down" command to verify that the
* rebuild was successful
*/
if (ioctl(raid_fd, USRAID_RET_DOWN, &raidp->downs) < 0) {
error = errno;
message_perror(raidp, "Re-reading down disk info failed");
goto out;
}
for (i = 0; i < 5; i++) {
if (raidp->downs.drive[i]) {
error = EAGAIN;
message(raidp, "Controller still shows disk %d as down", 1, i);
goto out;
}
}
out:
raidclose();
if (!error) {
i = syslogit;
syslogit = 1;
message(raidp, "Disk %d successfully rebuilt", 2, raidp->deadslot);
syslogit = i;
}
return(error);
}
/*
* Download new firmware to a RAID
*
* The sequence of operations to download new firmware to a RAID:
*/
download(char *path, char *firmpath, int force)
{
struct usraid_download download;
struct raid *raidp;
struct stat statb;
int firm_fd, error = 0, i;
char *buffer;
/*
* Get a copy of the firmware into a memory buffer.
*/
if (stat(firmpath, &statb) < 0) {
error = errno;
message_perror(NULL, "stat failed: %s", firmpath);
return(error);
}
if ((buffer = (char *)malloc((unsigned)statb.st_size)) == NULL) {
error = errno;
message_perror(NULL, "malloc failed");
return(error);
}
if ((firm_fd = open(firmpath, O_RDONLY)) < 0) {
error = errno;
message_perror(NULL, "firmware open failed: %s", firmpath);
return(error);
}
if (read(firm_fd, buffer, statb.st_size) < statb.st_size) {
error = errno;
message_perror(NULL, "firmware read failed: %s", firmpath);
close(firm_fd);
return(error);
}
if (close(firm_fd) < 0) {
error = errno;
message_perror(NULL, "firmware close failed: %s", firmpath);
return(error);
}
/*
* Check out the controller
*/
raidp = list_raids(path);
if (raidp == NULL)
return(ENOENT);
/*
* Ask for confirmation from the user.
*/
(void)checkforfs(raidp, 0);
if (!confirm(raidp, force, "download new firmware to", 0))
return(EPERM);
/*
* Download the firmware into the controller.
*/
if (error = raidopen(raidp->pathname)) {
message_perror(raidp, "open of RAID failed");
return(error);
}
download.buffer = buffer;
download.size = statb.st_size;
if (ioctl(raid_fd, USRAID_DOWNLOAD, (caddr_t)&download)) {
error = errno;
message(raidp, "Downloading new firmware failed", 1);
}
raidclose();
i = syslogit;
syslogit = 1;
message(raidp, "Press the RESET button on the RAID to have the new firmware take effect", 2);
syslogit = i;
return(error);
}
/*
* Check the integrity of the parity on a RAID
*
* The sequence of operations to check the integrity of a RAID:
* + Can be run while RAID is active
* + Execute the "integrity check" command on the indicated RAID
* + Will run until an error, or the end of the RAID
* + Execute the "fix integrity" command on any error return
* + Will regenerate the parity sector(s) for the indicated stripe
*/
integrity(char *path)
{
struct raid *raidp;
int error = 0, i;
raidp = list_raids(path);
if (raidp == NULL)
return(ENOENT);
for ( ; raidp; raidp = raidp->next) {
if (raidp->error) {
error = raidp->error;
continue;
}
/*
* refuse to check integrity of a RAID that has a drive down
*/
if (raidp->deadslot >= 0) {
message(raidp, "Cannot check integrity, drive %d is down",
0, raidp->deadslot);
continue;
}
/*
* loop thru drive checking and correcting parity as required
*/
if (error = raidopen(raidp->pathname)) {
message_perror(raidp, "open of RAID failed");
continue;
}
if (ioctl(raid_fd, USRAID_INTEGRITY, 0) < 0) {
error = errno;
i = syslogit;
syslogit = 1;
message_perror(raidp, "Integrity check failed");
syslogit = i;
} else {
message(raidp, "Integrity check successfull", 2);
}
raidclose();
}
return(error);
}
/*
* Force down a specific drive on a specific RAID.
*
* The sequence of operations to force down a drive:
* + Check for any down drives on the raid.
* + Confirmation req'd if we are forcing down a drive and
* one is already down.
* + If a RAID is corrupted (ie: marked with an error), don't do anything.
*/
forcedown(char *path, int driveno, int force)
{
struct usraid_units_down downs;
struct raid *raidp;
int error = 0, doit = 0, i;
char buffer[32];
raidp = list_raids(path);
if (raidp == NULL)
return(ENOENT);
if (raidp->error) {
message(raidp, "Cannot force a drive down on this RAID", 0);
return(raidp->error);
}
/*
* If the odd-man-out is the controller, reprogram it
* with the info from the disks
*/
if (raidp->deadslot == 5) {
error = EIO;
} else if ((raidp->deadslot >= 0) &&
(raidp->downs.drive[raidp->deadslot] == 0)) {
/*
* One drive's config info doesn't match the others,
* but it is claimed to be up by the controller.
*/
if ((driveno == raidp->deadslot) || force) {
doit++;
} else {
error = EIO;
message(raidp, "Aborting: this would down a second disk in the array", 1);
}
} else if (raidp->deadslot >= 0) {
if (driveno == raidp->deadslot) {
error = EIO;
message(raidp, "Drive %d is already marked down", 0, raidp->deadslot);
} else if (force) {
doit++;
} else {
error = EIO;
message(raidp, "Aborting: this would down a second disk in the array", 1);
}
} else {
doit++;
}
if (doit) {
if (error = raidopen(raidp->pathname)) {
message_perror(raidp, "open of RAID failed");
} else {
/*
* Ask for confirmation from the user.
*/
if (!confirm(raidp, force, "force down drive %d on", driveno)) {
return(EPERM);
} else {
/*
* Always SYSLOG the message that we are forcing down a drive.
*/
doit = syslogit;
syslogit = 1;
message(raidp, "Disk %d is being forced down", 0, driveno);
bzero(&downs, sizeof(downs));
downs.drive[driveno] = 1;
if (ioctl(raid_fd, USRAID_SET_DOWN, &downs) < 0) {
error = errno;
message_perror(raidp, "Failed to force disk %d down", driveno);
}
syslogit = doit;
/*
* If we are forcing down drive 0, we must reset the master
* for the spindle synch line.
*/
if ((driveno == 0) && (error = program_disk(raidp, 1, 1))) {
message(raidp, "Failed to program drive 1 to master the spindle synch line:", 1);
}
}
raidclose();
}
}
return(error);
}
/*
* Check for down drives on any RAID on the system.
*
* The sequence of operations to check for down drives:
* + Execute the "return units down" command for each RAID
* + If the RAID has been marked with an error (ie: its corrupted)
* + Spin down the whole RAID.
* + If a drive's config info doesn't match, but it is marked "up":
* + Force that drive down.
*/
checkdown(char *path, int mark, int force)
{
struct usraid_units_down downs;
struct raid *raidp;
int error = 0, i;
avoid_cm_message = mark;
raidp = list_raids(path);
avoid_cm_message = 0;
if (raidp == NULL)
return(ENOENT);
for ( ; raidp; raidp = raidp->next) {
if (raidp->error && !raidp->redo_ctlr) {
error = raidp->error;
continue;
}
/*
* If the odd-man-out is the controller, reprogram it
* with the info from the disks
*/
if ((raidp->deadslot == 5) || (raidp->redo_ctlr)) {
error = EIO;
if (mark) {
if (error = raidopen(raidp->pathname)) {
error = errno;
message_perror(raidp, "open of RAID failed");
} else if (!confirm(raidp, force, "reprogram the controller for", 0)) {
error = EPERM;
} else {
i = syslogit;
syslogit = 1;
message(raidp, "Reprogramming controller to match disks", 0);
/*
* GROT: if this is the 2nd controller of a dual set, we won't
* get the down drive info from the primary
*/
raidp->config[5] = raidp->config[0]; /* struct copy */
raidp->config[5].slot_number = 5;
raidp->initted = 1;
error = write_controller(raidp);
syslogit = i;
raidclose();
}
}
} else if ((raidp->deadslot >= 0) &&
(raidp->downs.drive[raidp->deadslot] == 0)) {
/*
* One drive's config info doesn't match the others,
* but it is claimed to be up by the controller.
*/
error = EIO;
if (mark) {
if (error = raidopen(raidp->pathname)) {
error = errno;
message_perror(raidp, "open of RAID failed");
} else if (!confirm(raidp, force, "force down drive %d on",
raidp->deadslot)) {
error = EPERM;
} else {
i = syslogit;
syslogit = 1;
message(raidp, "Disk %d is being forced down,",
0, raidp->deadslot);
message(raidp, "the RAID needs to be rebuilt", 0);
bzero(&downs, sizeof(downs));
downs.drive[raidp->deadslot] = 1;
if (ioctl(raid_fd, USRAID_SET_DOWN, &downs) < 0) {
error = errno;
message_perror(raidp, "Failed to force disk %d down",
raidp->deadslot);
}
syslogit = i;
/*
* If we are forcing down drive 0, we must reset the master
* for the spindle synch line.
*/
if ((raidp->deadslot == 0) &&
(error = program_disk(raidp, 1, 1))) {
message(raidp, "Failed to program drive 1 to master the spindle synch line:", 1);
}
raidclose();
}
}
} else if (raidp->deadslot >= 0) {
error = EIO;
message(raidp, "Drive %d is down", 0, raidp->deadslot);
if (error = raidopen(raidp->pathname)) {
error = errno;
message_perror(raidp, "open of RAID failed");
} else {
/*
* If we are forcing down drive 0, we must reset the master
* for the spindle synch line.
*/
if ((raidp->deadslot == 0) &&
(error = program_disk(raidp, 1, 1))) {
message(raidp, "Failed to program drive 1 to master the spindle synch line:", 1);
}
raidclose();
}
}
}
return(error);
}
/*
* Print configuration information for the indicated RAID,
* or for all of them if a specific RAID is not given
*
* The sequence of operations to print configuration info:
* + Print: stripe block size, drive size, and parity mode (4/5)
* + Print: size, vendor, and revision info on each physical drive
* + Print: any down disks
* + Print: the LUN 0 initialized bit from the NVRAM
*/
print(char *path, int verbose)
{
struct raid_config *cp;
struct raid *raidp;
int error = 0, i, msgs = 0;
char buffer[256];
raidp = list_raids(path);
if (raidp == NULL)
return(ENOENT);
for ( ; raidp; raidp = raidp->next) {
/*
* print the RAID's general parameters
*/
sprintf(buffer, "%s, sectors: %d, stripe size: %d",
(raidp->defn_page.mode == 0) ? "RAID5" : "RAID3",
raidp->defn_page.num_stripes * raidp->defn_page.stripe_depth * 4,
raidp->defn_page.stripe_depth * 4);
/*
* print a message for various error conditions
*/
if ((raidp->deadslot >= 0) && (raidp->deadslot <= 4)) {
sprintf(buffer, "%s, drive %d down", buffer, raidp->deadslot);
msgs++;
}
if (raidp->deadslot == 5) {
sprintf(buffer, "%s, ctlr replaced", buffer);
msgs++;
}
if (!raidp->initted) {
sprintf(buffer, "%s, not initialized", buffer);
msgs++;
}
if (raidp->error) {
sprintf(buffer, "%s, must reformat", buffer);
msgs++;
error = raidp->error;
}
if (raidp->wrtprot) {
sprintf(buffer, "%s, write protected", buffer);
msgs++;
}
if (!msgs)
sprintf(buffer, "%s, OK", buffer);
message(raidp, buffer, 2);
/*
* If we are using the hidden "verbose" option, print
* all the info about the disks in the RAID.
*/
if (verbose) {
cp = &raidp->config[5];
message(raidp, " Controller: \"%s\", \"%s\", \"%s\"", 2,
cp->vendor, cp->product, cp->revision);
for (i = 0; i < 5; i++) {
if (i == raidp->deadslot) {
message(raidp, " [%d]: *** drive is down ***", 2, i);
} else {
cp = &raidp->config[i];
sprintf(buffer, " [%d]: %d blocks of %d bytes, \"%s\", \"%s\", \"%s\"",
i, cp->totalsize, cp->blocksize,
cp->vendor, cp->product, cp->revision);
message(raidp, buffer, 2);
}
}
}
}
return(error);
}
/*============================================================================
* RAID management/checking functions
*/
/*
* Collect and validate config information for the indicated RAID,
* or for all of them if a specific RAID is not given
*
* The sequence of operations is:
* + If a path is specified:
* + Get info on just the indicated RAID
* + If a path is not specified:
* + Use getinvent() to look for RAID controllers
* + They are SCSI disks, but with more info?
* + In either case, for each selected RAID, validate all info
*/
struct raid *
list_raids(char *path)
{
struct raid *raidp = NULL;
char *ch;
if (path) {
raidp = allocate_raid(path);
} else {
if (scaninvent(inventory_raids, 0) < 0) {
message_perror(NULL, "scaninventory() failed");
exit(1);
}
raidp = raidhead;
}
if (raidp) {
read_raids(raidp);
check_raids(raidp);
}
return(raidp);
}
/*
* Collect but don't validate config information for the indicated RAID,
* or for all of them if a specific RAID is not given
*
* The sequence of operations to collect configuration info:
* + If a path is specified:
* + Get info on just the indicated RAID
* + If a path is not specified:
* + Use getinvent() to look for RAID controllers
* + They are SCSI disks, but with more info?
* + In either case, for each selected RAID, collect the following:
* + Read page 0x38: Drive Group Definition
* + Read: depth, stripe block size, drive size, and parity mode
* + Execute the "return physical connection" command for each address
* + Check info on each physical drive
* + Only look for the 5 known addresses
* + Read page 0x39: Partition Definition
* + Read: target and channel number, beginning sector, and length
* + Execute the "return units down" command for each RAID
* + Read the magic number information in the controller
* + Read the config info from the reserved sectors on each disk
*/
read_raids(struct raid *raidp)
{
int i, j;
for ( ; raidp; raidp = raidp->next) {
if (raidp->error = raidopen(raidp->pathname)) {
message_perror(raidp, "open of RAID failed");
continue;
}
/*
* read the RAID's general parameters
*/
raidp->error = read_controller(raidp);
/*
* check for down drives in the RAID
*/
raidp->deadslot = -1;
if (ioctl(raid_fd, USRAID_RET_DOWN, &raidp->downs) < 0) {
raidp->error = errno;
message_perror(raidp, "Reading Units Down from controller failed");
raidp->conf_valid[5] = 0; /* now ignore controller info */
} else {
for (i = 0; i < 5; i++) {
if (raidp->downs.drive[i]) {
if (raidp->deadslot >= 0) {
message(raidp, "More than one disk is marked down", 1);
raidp->error = EIO;
goto nextone;
}
raidp->deadslot = i;
}
}
}
#ifdef GROT
/*
* The IBM 2.0GB drives take their sweet time about responding to an
* inquiry message, they seem to be doing diagnostics and such. If
* we did the PHYS_CONN here, we would spend 5 seconds on each RAID,
* ie: 5 minutes on a farm of 60 RAIDs.
*/
/*
* read info for each physical drive (even down drives)
*/
for (i = 0; i < 5; i++) {
raidp->phys_info[i].drive = i;
if (ioctl(raid_fd, USRAID_PHYS_CONN, &raidp->phys_info[i]) < 0) {
message_perror(raidp, "Drive %d is missing", i);
}
raidp->phys_info[i].totalsize -= PART_BASE;
}
#endif /* GROT */
/*
* read the config info from the reserved sector on all disks
*
* Note that the reserved sectors are the last 128 sectors
* on that drive (ie: relative to the physical disk size),
* of which SGI has control of the first 8.
*/
for (i = 0; i < 5; i++) {
if (rw_config(raidp, i, READ))
raidp->conf_valid[i] = 0;
else
raidp->conf_valid[i] = 1;
}
/*
* Read up any volume header that may be present on the disk.
*/
if (getdiskheader(raid_fd, &raidp->vh) == -1) {
raidp->vh_valid = 0;
} else {
raidp->vh_valid = 1;
}
nextone:
raidclose();
}
}
/*
* Validate config information for the given list of RAIDs.
*
* The sequence of operations to validate the configuration for each RAID:
* + If one disk is out of date WRT to the other disks and controller
* + Range check all info on "good" disks and controller
* + Dont do anything, wait for rebuild() to fix it
* + If the controller is out of date WRT to all disks
* + Reprogram the controller with data from the disks
* + If more than one odd-man-out in disks and controller
* + RAID is trashed, must format
*/
check_raids(struct raid *raidp)
{
struct raid_config *cp, *tcp;
struct usraid_defn_page *defp;
struct usraid_phys_conn *physp;
int error, i;
for ( ; raidp; raidp = raidp->next) {
/*
* compare config_id's, timestamps, and magic numbers
* this will tell us which (if any) info source is the
* odd-man-out. After this, deadslot is known accurate.
*/
if (find_oddman(raidp) == -2) {
raidp->error = EIO;
message(raidp, "Raid corrupted, must reformat", 1);
continue;
}
/*
* check the config info from reserved sector on each "up" disk
*
* Note that the reserved sectors are the first 32 physical sectors
* on that drive (ie: relative to the physical disk size, not the
* partitions we've defined).
*/
error = 0;
for (i = 0; i < 6; i++) {
if (i == raidp->deadslot)
continue;
cp = &raidp->config[i];
defp = &raidp->defn_page;
if ((cp->magic != RAID_CONFIG_MAGIC) ||
(cp->version != RAID_CONFIG_REV)) {
message(raidp, "Drive %d's config info is unrecognizable", 1, i);
error++;
continue;
}
if ((cp->config_id == 0) ||
((cp->mode != RAID4) && (cp->mode != RAID5)) ||
(cp->stripe_depth < 1) || (cp->num_stripes < 1)) {
message(raidp, "Drive %d's config info contains illegal values", 1, i);
error++;
}
if ((raidp->deadslot != 5) &&
((cp->mode != (defp->mode ? RAID4 : RAID5)) ||
(cp->stripe_depth != defp->stripe_depth))) {
message(raidp, "Disk %d config info doesn't match NVRAM info", 1, i);
error++;
}
if (error)
continue;
if (cp->slot_number != i) {
message(raidp, "Drive %d should be in slot %d, raid ignored",
1, i, cp->slot_number);
raidp->error = EIO;
}
}
if (!raidp->initted)
message(raidp, "Raid not initialized", 0);
/*
* If the odd-man-out is the controller, reprogram it
* with the info from the disks
*/
if (raidp->deadslot == 5) {
raidp->error = EIO;
raidp->redo_ctlr = 1;
if (!avoid_cm_message) {
message(raidp, "The controller has been replaced, use the '-c' and '-m'", 1);
message(raidp, "arguments to reprogram the controller to match the", 1);
message(raidp, "config info on the drives", 1);
}
} else if ((raidp->deadslot >= 0) &&
(raidp->downs.drive[raidp->deadslot] == 0)) {
/*
* One drive's config info doesn't match the others,
* but it is claimed to be up by the controller.
*/
if (!avoid_cm_message) {
message(raidp, "Disk %d has been replaced, but it wasn't marked down",
1, raidp->deadslot);
message(raidp, "use the '-c' and '-m' arguments to mark it down", 1);
}
}
/*
* fatal errors detected, must reformat the drive
*/
if (error > 1) {
message(raidp, "Raid corrupted, must reformat", 1);
raidp->error = EIO;
}
}
}
/*
* Find the odd-man-out between 5 disks and the controller of a RAID
*
* The sequence of operations to validate the configuration for each RAID:
* + Compare config_id's, and timestamps to figure it out
* + Use a voting scheme, any one difference is OK
* + Only one "odd-man-out" allowed, error if more than one
* + We may have a new controller
* + Deadslot may be bogus, reset if necessary
* + Set deadslot to 5 if controller doesn't match
* + We may have a new disk, so ensure the magic number is right
* + We may not have gotten config sector info off all disks.
* + If all else matches, assume that one is deadslot.
* + If not all else matches, reformat.
* + Deadslot will always end up pointing to the odd-man-out
* + if retval >= 0, there is something down, and retval == deadslot
* + if retval == -1, there is nothing down, and deadslot is -1
* + if retval == -2, must reformat, and deadslot is dont care
*/
find_oddman(struct raid *raidp)
{
struct raid_config *cp, *tcp;
int votecnt, retval, i, j;
struct {
int magic;
unsigned int config_id;
unsigned long time_sec;
unsigned long time_usec;
int who;
int refcount;
int valid;
} vote[6];
/*
* Match each config entry against the voting array. Note that we
* may not have gotten the config sector info off all disks, so
* we count an invalid entry as a new pattern.
*/
votecnt = 0;
bzero((char *)&vote[0], sizeof(vote));
for (cp = &raidp->config[0], i = 0; i < 6; cp++, i++) {
for (j = 0; j < votecnt; j++) {
if ((raidp->conf_valid[i]) && (vote[j].valid) &&
(cp->magic == vote[j].magic) &&
(cp->config_id == vote[j].config_id) &&
(cp->time_sec == vote[j].time_sec) &&
(cp->time_usec == vote[j].time_usec)) {
vote[j].who = i;
vote[j].refcount++;
break;
}
}
if (j == votecnt) {
vote[votecnt].magic = cp->magic;
vote[votecnt].config_id = cp->config_id;
vote[votecnt].time_sec = cp->time_sec;
vote[votecnt].time_usec = cp->time_usec;
vote[votecnt].who = i;
vote[votecnt].refcount++;
vote[votecnt].valid = raidp->conf_valid[i];
votecnt++;
}
}
/*
* Check how many distinct "votes" (ie: patterns) we got:
* + one: no inconsistencies
* + use deadslot info from controller NVRAM
* + two: if one vote a singleton, then we found odd-man-out
* + if odd-man-out is controller, set deadslot to controller
* + if odd-man-out is not the deadslot, then reformat
* + two: otherwise, reformat
* + otherwise, reformat
*/
if (votecnt == 1)
retval = raidp->deadslot;
else if (votecnt == 2) {
if (vote[0].refcount == 1)
retval = vote[0].who;
else if (vote[1].refcount == 1)
retval = vote[1].who;
else
retval = -2;
if (retval >= 0) {
if (retval == 5)
raidp->deadslot = 5;
else if (raidp->deadslot == -1)
raidp->deadslot = retval;
else if (raidp->deadslot != retval) {
message(raidp, "Controller and drives disagree on which drive failed,", 1);
message(raidp, "controller says %d failed, while drives say %d failed",
1, raidp->deadslot, retval);
retval = -2;
}
}
} else
retval = -2;
return(retval);
}
/*
* Read the contents of the controller config into the local config structure
*/
read_controller(struct raid *raidp)
{
struct usraid_nvram_data data;
struct raid_config *cp;
int error = 0, i;
char buffer[32];
/*
* Do an inquiry to find firmware rev level, etc.
*/
bzero(buffer, sizeof(buffer));
if (ioctl(raid_fd, DIOCDRIVETYPE, buffer) < 0) {
error = errno;
message_perror(raidp, "Read of inquiry string failed");
}
cp = &raidp->config[5];
bcopy(&buffer[0], cp->vendor, 8);
bcopy(&buffer[8], cp->product, 16);
bcopy(&buffer[24], cp->revision, 4);
blankblaster(cp->vendor);
blankblaster(cp->product);
blankblaster(cp->revision);
/*
* read the RAID's general parameters
*/
if (ioctl(raid_fd, USRAID_GET_DEFN_PG, &raidp->defn_page) < 0) {
error = errno;
message_perror(raidp, "Read of Drive Definition Page failed");
}
/*
* read the physical to logical mapping
*/
if (ioctl(raid_fd, USRAID_GET_PART_PG, &raidp->part_page) < 0) {
error = errno;
message_perror(raidp, "Read of Partition Definition Page failed");
}
/*
* read the bit showing whether the drive has been initialized
*/
data.addr = USRAID_NVRAM_INITTED;
if (ioctl(raid_fd, USRAID_GET_NVRAM, &data) < 0) {
error = errno;
message_perror(raidp, "Read of NVRAM init flag failed");
}
raidp->initted = (data.data & 0x01) ? 1 : 0;
/*
* read the config ID
*/
data.addr = USRAID_NVRAM_CONFIG_ID;
if (ioctl(raid_fd, USRAID_GET_NVRAM, &data) < 0) {
error = errno;
message_perror(raidp, "Read of NVRAM config-id failed");
}
cp->config_id = data.data;
/*
* read the timestamp
*/
data.addr = USRAID_NVRAM_TIME_SEC;
if (ioctl(raid_fd, USRAID_GET_NVRAM, &data) < 0) {
error = errno;
message_perror(raidp, "Read of NVRAM timestamp failed");
}
cp->time_sec = data.data;
data.addr = USRAID_NVRAM_TIME_USEC;
if (ioctl(raid_fd, USRAID_GET_NVRAM, &data) < 0) {
error = errno;
message_perror(raidp, "Read of NVRAM timestamp failed");
}
cp->time_usec = data.data;
/*
* read the status of the write-protect bit
*/
data.addr = USRAID_NVRAM_FLAGBITS;
if (ioctl(raid_fd, USRAID_GET_NVRAM, &data) < 0) {
error = errno;
message_perror(raidp, "Read of NVRAM flag bits failed");
}
if (data.data & USRAID_NVRAM_WRTPROT)
raidp->wrtprot = 1;
/*
* fill in the rest of the config info derived from the controller
*/
cp->version = RAID_CONFIG_REV;
cp->magic = RAID_CONFIG_MAGIC;
cp->stripe_depth = raidp->defn_page.stripe_depth;
cp->num_stripes = raidp->defn_page.num_stripes;
cp->mode = (raidp->defn_page.mode == 0) ? RAID5 : RAID4;
cp->slot_number = 5;
raidp->conf_valid[5] = (error == 0);
return(error);
}
/*
* (Re)program the controller with info from the controller config structure
*/
write_controller(struct raid *raidp)
{
struct usraid_nvram_data data;
int error = 0, i;
/*
* set the RAID's parameters, controller has already been
* forced to look at each drive
*/
raidp->defn_page.stripe_depth = raidp->config[5].stripe_depth;
raidp->defn_page.num_stripes = raidp->config[5].num_stripes;
if (raidp->config[0].mode == RAID5)
raidp->defn_page.mode = 0;
else
raidp->defn_page.mode = 1;
if (ioctl(raid_fd, USRAID_SET_DEFN_PG, &raidp->defn_page) < 0) {
error = errno;
message_perror(raidp, "Write of Drive Definition Page failed");
return(error);
}
/*
* associate the physical disks with the RAID
*/
raidp->part_page.base = PART_BASE; /* reserved sectors are at beginning */
raidp->part_page.size = raidp->config[5].num_stripes * raidp->config[5].stripe_depth;
if (ioctl(raid_fd, USRAID_SET_PART_PG, &raidp->part_page) < 0) {
error = errno;
message_perror(raidp, "Write of Partition Definition Page failed");
return(error);
}
/*
* write the bit showing whether the drive has been initialized
*/
data.addr = USRAID_NVRAM_INITTED;
if (ioctl(raid_fd, USRAID_GET_NVRAM, &data) < 0) {
error = errno;
message_perror(raidp, "Read of NVRAM init flag failed");
}
if (raidp->initted)
data.data |= 0x01;
else
data.data &= 0x01;
if (ioctl(raid_fd, USRAID_SET_NVRAM, &data) < 0) {
error = errno;
message_perror(raidp, "Write of NVRAM init flag failed");
}
/*
* write the config ID
*/
data.addr = USRAID_NVRAM_CONFIG_ID;
data.data = raidp->config[5].config_id;
if (ioctl(raid_fd, USRAID_SET_NVRAM, &data) < 0) {
error = errno;
message_perror(raidp, "Write of NVRAM config-id failed");
}
/*
* write the timestamp
*/
data.addr = USRAID_NVRAM_TIME_SEC;
data.data = raidp->config[5].time_sec;
if (ioctl(raid_fd, USRAID_SET_NVRAM, &data) < 0) {
error = errno;
message_perror(raidp, "Write of NVRAM timestamp failed");
}
data.addr = USRAID_NVRAM_TIME_USEC;
data.data = raidp->config[5].time_usec;
if (ioctl(raid_fd, USRAID_SET_NVRAM, &data) < 0) {
error = errno;
message_perror(raidp, "Write of NVRAM timestamp failed");
}
/*
* write the write-protect bit
*/
data.addr = USRAID_NVRAM_FLAGBITS;
data.data = 0;
if (raidp->wrtprot)
data.data |= USRAID_NVRAM_WRTPROT;
if (ioctl(raid_fd, USRAID_SET_NVRAM, &data) < 0) {
error = errno;
message_perror(raidp, "Write of NVRAM flag bits failed");
}
return(error);
}
/*
* Mode page header info: common to all pages.
*/
struct modehdr {
u_char rsvd1;
u_char mediumtype;
u_char WP:1, rsvd2:2, DPOFUA:1, rsvd3:4;
u_char blkdesc_len;
u_char rsvd4;
u_char blkcount[3];
u_char rsvd5;
u_char blksize[3];
u_char rsvd6:2, pagecode:6;
u_char pagelength;
};
/*
* Specific structure of mode page 0 - Vendor Unique
*/
struct modepage00 {
struct modehdr header;
u_char QPE:1, UQE:1, DWD:1, rsvd1:5;
u_char ASDPE:1, rsvd2:1, CMDAC:1, RPFAE:1, rsvd3:3, CPE:1;
u_char rsvd4;
u_char rsvd5:2, DSN:1, FRDD:1, DPSDP:1, WPEN:1, rsvd6:2;
u_char rsvd7[2];
u_char rsvd8:3, DRD:1, led_mode:4;
u_char rsvd9[3];
};
/*
* Specific structure of mode page 1 - Error Recovery
*/
struct modepage01 {
struct modehdr header;
u_char AWRE:1, ARRE:1, TB:1, RC:1, EER:1, PER:1, DTE:1, DCR:1;
u_char read_retry;
u_char corr_span;
u_char head_offset;
u_char strobe_offset;
u_char rsvd1;
u_char write_retry;
u_char rsvd2;
u_char recovery_time[2];
};
/*
* Specific structure of mode page 4 - Geometry
*/
struct modepage04 {
struct modehdr header;
u_char cylinders[3];
u_char heads;
u_char cylwrite_precomp[3];
u_char cylwrite_reducurr[3];
u_char step_rate[2];
u_char landing_zone[3];
u_char rsvd1:6, RPL:2;
u_char rot_offset;
u_char rsvd2;
u_char rot_rate[2];
u_char rsvd3[2];
};
/*
* Specific structure of mode page 8 - Read caching
*/
struct modepage08 {
struct modehdr header;
u_char rsvd1:5, WCE:1, MF:1, RCD:1;
u_char read_reten:4, write_reten:4;
u_char dispref_len[2];
u_char minpref_len[2];
u_char maxpref_len[2];
u_char maxpref_ceil[2];
u_char rsvd2;
u_char cache_segs;
u_char rsvd3[6];
};
/*
* Program the various mode pages on the disks attached to the RAID.
* The features we are fiddling with are:
* PFA reporting, PER reporting, and spindle synch.
*/
program_disk(struct raid *raidp, int disknum, int syncmaster)
{
struct modepage00 page00;
struct modepage01 page01;
struct modepage04 page04;
struct modepage08 page08;
int error = 0, err;
/*
* Check (and fix up) mode page 0 - Vendor Unique (set PFA)
*/
if (err = passthru_modesense(disknum, 0x00, sizeof(page00), &page00)) {
error = err;
message_perror(raidp, "couldn't read PFA bit on disk %d", disknum);
} else if (page00.RPFAE != 1) {
page00.RPFAE = 1;
if (err = passthru_modeselect(disknum, 0x00, sizeof(page00), &page00)) {
error = err;
message_perror(raidp, "couldn't set PFA bit on disk %d", disknum);
}
}
/*
* Check (and fix up) mode page 1 - Error Recovery (set PER)
*/
if (err = passthru_modesense(disknum, 0x01, sizeof(page01), &page01)) {
error = err;
message_perror(raidp, "couldn't read PER bit on disk %d", disknum);
} else if (page01.PER != 1) {
page01.PER = 1;
if (err = passthru_modeselect(disknum, 0x01, sizeof(page01), &page01)) {
error = err;
message_perror(raidp, "couldn't set PER bit on disk %d", disknum);
}
}
/*
* Check (and fix up) mode page 4 - Geometry (set spindle sync)
*/
if (err = passthru_modesense(disknum, 0x04, sizeof(page04), &page04)) {
error = err;
message_perror(raidp, "couldn't read spindle sync state on disk %d", disknum);
} else if (page04.RPL != (syncmaster ? 0x02 : 0x01)) {
page04.RPL = (syncmaster ? 0x02 : 0x01);
if (err = passthru_modeselect(disknum, 0x04, sizeof(page04), &page04)) {
error = err;
message_perror(raidp, "couldn't set spindle sync state on disk %d", disknum);
}
/* XXXGROT: change in RPL require a spindown/spinup to make it take effect */
}
#if 0
/*
* Check (and fix up) mode page 8 - Read Caching
*/
if (err = passthru_modesense(disknum, 0x08, sizeof(page08), &page08)) {
error = err;
message_perror(raidp, "couldn't read XXXX on disk %d", disknum);
} else if (0) {
/* XXX = 1; */
if (err = passthru_modeselect(disknum, 0x08, sizeof(page08), &page08)) {
error = err;
message_perror(raidp, "couldn't set XXXX on disk %d", disknum);
}
}
#endif /* 0 */
return(error);
}
/*
* Mode Sense Command - opcode 0x1a
* Read mode pages from the device.
*/
struct mode_sense {
u_char opcode;
u_char rsvd1;
u_char pagecontrol:2, pagecode:6;
u_char rsvd2;
u_char alloclen;
u_char rsvd3;
};
/*
* Pass a SCSI mode sense command to the given drive.
*/
passthru_modesense(int disknum, int pagenum, int pagelen, char *buffer)
{
struct usraid_passthru pt_args;
struct mode_sense sense_cmd;
int error = 0;
/*
* Fill out a standard SCSI mode sense command.
*/
bzero(&sense_cmd, sizeof(sense_cmd));
sense_cmd.opcode = 0x1a;
sense_cmd.pagecontrol = 0;
sense_cmd.pagecode = pagenum;
sense_cmd.alloclen = pagelen;
/*
* Fill in the ioctl control parameters.
*/
pt_args.drive = disknum;
pt_args.todisk = 0;
pt_args.fromdisk = 1;
pt_args.xfercount = pagelen;
pt_args.cdblen = sizeof(sense_cmd);
bcopy(&sense_cmd, pt_args.cdb, sizeof(sense_cmd));
pt_args.databuf = buffer;
/*
* Execute the mode sense command.
*/
if (ioctl(raid_fd, USRAID_PASSTHRU, &pt_args) < 0) {
error = errno;
}
return(error);
}
/*
* Mode Select Command - opcode 0x15
* Write new mode pages to the device.
*/
struct mode_select {
u_char opcode;
u_char rsvd1:3, pageflag:1, rsvd2:3, savepage:1;
u_char rsvd3[2];
u_char paramlen;
u_char rsvd4;
};
/*
* Pass a SCSI mode sense command to the given drive.
*/
passthru_modeselect(int disknum, int pagenum, int pagelen, caddr_t buffer)
{
struct usraid_passthru pt_args;
struct mode_select select_cmd;
int error = 0;
/*
* mode select doesn't accept direct mode sense info
*/
buffer[0] = 0;
buffer[12] &= 0x7f;
/*
* Fill out a standard SCSI mode select command.
*/
bzero(&select_cmd, sizeof(select_cmd));
select_cmd.opcode = 0x15;
select_cmd.savepage = 1;
select_cmd.paramlen = pagelen;
/*
* Fill in the ioctl control parameters.
*/
pt_args.drive = disknum;
pt_args.todisk = 1;
pt_args.fromdisk = 0;
pt_args.xfercount = pagelen;
pt_args.cdblen = sizeof(select_cmd);
bcopy(&select_cmd, pt_args.cdb, sizeof(select_cmd));
pt_args.databuf = buffer;
/*
* Execute the mode select command.
*/
if (ioctl(raid_fd, USRAID_PASSTHRU, &pt_args) < 0) {
error = errno;
}
return(error);
}
/*
* The SCSI read and write commands.
*/
struct scsi_rw_cmd {
u_char opcode;
u_char pad1;
u_char lba[4];
u_char pad2;
u_char length[2];
u_char pad3;
};
/*
* Read and write from/to the reserved sectors on disks.
*/
rw_config(struct raid *raidp, int drive, int rw)
{
struct usraid_passthru pt_data;
struct scsi_rw_cmd rw_cmd;
char buffer[512];
int command, error = 0;
/*
* Set up read or write command
*/
bzero(&rw_cmd, sizeof(rw_cmd));
if (rw == WRITE)
rw_cmd.opcode = 0x2a;
else
rw_cmd.opcode = 0x28;
#define B(v,s) ((uchar_t)((v) >> s))
#define USR_MSB_SPLIT2(v,a) (a)[0]=B(v,8), (a)[1]=B(v,0)
#define USR_MSB_SPLIT4(v,a) (a)[0]=B(v,24), (a)[1]=B(v,16), (a)[2]=B(v,8), (a)[3]=B(v,0)
USR_MSB_SPLIT4(0, rw_cmd.lba); /* reserved sector is physical sector 0 */
USR_MSB_SPLIT2(1, rw_cmd.length);
/*
* Set up passthru control block
*/
bzero(buffer, sizeof(buffer));
if (rw == WRITE) {
bcopy(&raidp->config[drive], buffer, sizeof(raidp->config[drive]));
pt_data.todisk = 1;
pt_data.fromdisk = 0;
} else {
pt_data.todisk = 0;
pt_data.fromdisk = 1;
}
pt_data.drive = drive;
pt_data.xfercount = 512;
pt_data.cdblen = sizeof(rw_cmd);
bcopy(&rw_cmd, pt_data.cdb, sizeof(rw_cmd));
pt_data.databuf = buffer;
/*
* Execute the command
*/
if (ioctl(raid_fd, USRAID_PASSTHRU, &pt_data) < 0) {
error = errno;
} else if (rw == READ)
bcopy(buffer, &raidp->config[drive], sizeof(raidp->config[drive]));
return(error);
}
/*============================================================================
* generic utility functions
*/
/*
* generic routine to get a handle for a given RAID
*/
raidopen(char *path)
{
if ((raid_fd = open(path, O_RDWR)) < 0)
return(errno);
return(0);
}
/*
* generic routine to release the handle for the current RAID
*/
raidclose()
{
close(raid_fd);
raid_fd = 0;
}
/*
* allocate_raid - allocate and clear memory for a structure to
* store information about a particular RAID.
*/
struct raid *
allocate_raid(char *path)
{
struct raid *tmp;
char *base;
tmp = (struct raid *)malloc(sizeof(struct raid));
if (tmp == NULL) {
message_perror(NULL, "malloc() failed");
exit(1);
}
bzero(tmp, sizeof(struct raid));
strcpy(tmp->pathname, path);
if (base = strrchr(path, '/'))
strcpy(tmp->basename, ++base);
else
strcpy(tmp->basename, path);
return(tmp);
}
/*
* inventory_raids - used by scaninvent() to find RAID controllers.
*/
inventory_raids(inventory_t *inv, int arg)
{
struct raid *tmp;
char pathname[128], buffer[128], *base;
int error = 0;
if ((inv->inv_class != INV_DISK) ||
(inv->inv_type != INV_SCSIRAID))
return(0);
sprintf(pathname, "/dev/rdsk/%s%dd%dvh", USRAID_DEVNAME,
inv->inv_controller, inv->inv_unit);
/*
* Decide if the RAID is an UltraStor U144 RAID with SGI firmware.
* This program won't deal with any other kind of RAID box.
*/
if (error = raidopen(pathname))
return(0); /* don't stop scanning */
if ( (ioctl(raid_fd, DIOCDRIVETYPE, buffer) >= 0) &&
(strncmp(&buffer[0], "ULTRA ", 8) == 0) &&
(strncmp(&buffer[8], "U144 RAID SGI", 16) == 0) ) {
tmp = allocate_raid(pathname);
tmp->next = raidhead;
raidhead = tmp;
}
raidclose();
return(0);
}
/*
* gen_config_id - generate a number that is as unique as we can
* easily make it. Used to help detect when a disk or controller
* has been replaced.
*/
unsigned int
gen_config_id()
{
struct timeval tv;
int seed;
if (gettimeofday(&tv, NULL) < 0) {
message_perror(NULL, "gettimeofday() failed");
exit(1);
}
seed = (tv.tv_sec % 10000) * 100000;
seed += (tv.tv_usec / 10000) * 1000;
seed += getpid() % 1000;
return(seed);
}
/*
* blankblaster - zero out any trailing blanks in a string
*/
blankblaster(char *str)
{
for ( ; *str; str++) /* find the end of the string */
;
for (str--; *str == ' '; str--) /* NULL out trailing spaces */
*str = 0;
}
/*
* Have the user confirm that they REALLY want to do an operation.
*/
confirm(struct raid *raidp, int force, char *text, int arg1)
{
char buffer[80], inline[256], *token, *ch;
int i;
if (force)
return(1);
sprintf(buffer, text, arg1);
for (i = 0; i < 4; i++) {
fprintf(stderr, "Do you really want to %s %s? (yes, no) ", buffer, raidp->basename);
fgets(inline, sizeof(inline), stdin);
if (token = strtok(inline, " \t\n")) {
for (ch = token; *ch; ch++)
*ch = tolower(*ch);
if (strcmp(token, "yes") == 0)
return(1);
if (strcmp(token, "no") == 0)
break;
}
fprintf(stderr, "Please answer with \"yes\" or \"no\"\n");
}
fprintf(stderr, "Confirmation not given, operation aborted\n");
return(0);
}
/*
* Check for mounted filesystems in a partition on a RAID before we
* do something that may affect the data in the RAID.
* Uses utility routines from libdisk.
*/
checkforfs(struct raid *raidp, int errflag)
{
struct partition_table *part;
char *pname, *mntpt;
int fsvalid, i, error = 0;
if (!raidp->vh_valid) {
/*
* Haven't read it yet, or it wasn't valid, in either case
* read up any volume header that may be present on the disk.
*/
if (getdiskheader(raid_fd, &raidp->vh) == -1) {
raidp->vh_valid = 0;
} else {
raidp->vh_valid = 1;
}
if (!raidp->vh_valid)
return(0);
}
for (i = 0, part = raidp->vh.vh_pt; part < &raidp->vh.vh_pt[NPARTAB]; part++, i++) {
if (part->pt_nblks <= 0)
continue;
if (part->pt_type == PTYPE_EFS) {
/*
* Check the partition for a valid looking superblock
*/
pname = partname(raidp->pathname, i);
fsvalid = has_fs(pname, part->pt_nblks);
if (fsvalid) {
/*
* Check to see if the partition is mounted anywhere
*/
mntpt = getmountpt("/etc/fstab", pname);
if (*mntpt == 0) {
message(raidp, "appears to contain a filesystem, but is not mounted", 0);
} else {
error = EINVAL;
message(raidp, "device contains a filesystem mounted at %s", errflag, mntpt);
}
}
}
}
return(error);
}
/*
* message printing routines - variations on a theme
*/
message(struct raid *raidp, char *text, int errflag,
char *arg1, char *arg2, char *arg3)
{
char buffer[128], *base;
char *type[] = { "warning: ", "error: ", "" };
base = raidp ? raidp->basename : "";
sprintf(buffer, text, arg1, arg2, arg3);
if (raidp)
fprintf(stderr, "%s: %s: %s%s\n", progname, base, type[errflag], buffer);
else
fprintf(stderr, "%s: %s%s\n", progname, type[errflag], buffer);
if (syslogit) {
if (errflag)
syslog(LOG_ALERT, "%s: %s\n", base, buffer);
else
syslog(LOG_ERR, "%s: %s\n", base, buffer);
}
}
message_perror(struct raid *raidp, char *text, char *arg1, char *arg2)
{
char buffer[200], *base;
int error;
error = errno;
base = raidp ? raidp->basename : "";
sprintf(buffer, text, arg1, arg2);
if (raidp)
fprintf(stderr, "%s: %s: %s: %s\n", progname, base, buffer, strerror(error));
else
fprintf(stderr, "%s: %s: %s\n", progname, buffer, strerror(error));
if (syslogit) {
syslog(LOG_ALERT, "%s: %s: %s\n", base, buffer, strerror(error));
}
}