/*
 * lsraid.c - A tool for displaying md device information.
 *
 * Copyright (C) 2002 Oracle Corporation, Joel Becker
 * <joel.becker@oracle.com>
 * All rights reserved.
 *
 * 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 of the License, 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 recieved a copy of the GNU General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 021110-1307, USA.
 */

#include "common.h"
#include "parser.h"
#include "raid_io.h"
#include "raidlib.h"
#include "version.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <time.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

#include <linux/types.h>
#include <linux/major.h>
#include <linux/fs.h>
#include <linux/raid/md_u.h>
#include <linux/raid/md_p.h>

/* Local copies of things I use - Clean up later */
#include "list.h"


/*
 * Defines
 */
#define LSRAID_VERSION "0.7.0"

/* Imagine the fun when we get to different major,minor sizes :-) */
#define MAJORBITS 8
#define MINORBITS 8
#define MAJORS (1 << MAJORBITS)
#define MINORS (1 << MINORBITS)

/* Debugging for device scanning */
#define SCAN_DEBUG 0


/*
 * Typedefs
 */
typedef struct _LSRContext LSRContext;
typedef struct _LSRNamEnt  LSRNamEnt;
typedef struct _LSRDir     LSRDir;
typedef struct _LSRArray   LSRArray;
typedef struct _LSRDisk    LSRDisk;



/*
 * Enums
 */
typedef enum
{
    LSR_ARRAY = 'A',
    LSR_DISK = 'D',
    LSR_RAIDTAB = 'R'
} LSRAction;

typedef enum
{
    LSR_ARRAY_GOOD   = 1 << 0,
    LSR_ARRAY_SPARE  = 1 << 1,
    LSR_ARRAY_FAILED = 1 << 2,
    LSR_DISK_LONG    = 1 << 3,
    LSR_DEV_SCAN     = 1 << 4
} LSRFlags;



/*
 * Structures
 */
struct _LSRContext
{
    LSRAction action;
    LSRFlags flags;
    struct list_head o_arrays;  /* Array names from parse_options */
    struct list_head o_disks;   /* Disk names from parse_options */
    struct list_head arrays;    /* Anchor for list of arrays (a_list) */
    struct list_head disks;     /* Anchor for list of disks (d_list) */
    char ***devtable;           /* Table of known device names
                                   (major, minor) -> name */
};

struct _LSRNamEnt
{
    struct list_head list;
    char *name;
    union
    {
        LSRArray *array;
        LSRDisk *disk;
    } u;
};

struct _LSRDir
{
    struct list_head list;
    char name[PATH_MAX + 1];
    DIR *dir;
};

struct _LSRArray
{
    struct list_head a_list;    /* Hook into list of arrays */
    struct list_head disks;     /* Anchor for array disks (d_array) */
    char *name;                 /* Device name */
    unsigned int uuid0;         /* Array uuid */
    unsigned int uuid1;
    unsigned int uuid2;
    unsigned int uuid3;
    int minor;                  /* Array minor (in MD major) */
    mdu_array_info_t info;      /* Online info */
};

struct _LSRDisk
{
    struct list_head d_list;    /* Hook into list of disks */
    struct list_head d_array;   /* Hook into array disks */
    LSRArray *array;            /* Pointer to owning array */
    char *name;                 /* Device name */
    int major;                  /* Device major */
    int minor;                  /* Device minor */
    mdu_disk_info_t info;       /* Online info */
    mdp_super_t sb;             /* On-disk superblock */
};


/*
 * Prototypes
 */
static void print_usage(int rc);
static void print_usage_long();
static void print_version();
static void parse_options(LSRContext *ctxt, int argc, char *argv[]);
static LSRContext *alloc_context();
static LSRNamEnt *alloc_nament();
static LSRArray *alloc_array();
static LSRDisk *alloc_disk();
static int read_md_super(LSRContext *ctxt, LSRDisk *disk);
static int query_array(LSRContext *ctxt, LSRArray *array);
static int fill_array(LSRContext *ctxt, LSRArray *array);
static int fill_disk(LSRContext *ctxt, LSRDisk *disk);
static LSRArray *find_add_array(LSRContext *ctxt, char *name,
                                int minor);
static LSRDisk *find_add_disk(LSRContext *ctxt, char *name,
                              int major, int minor);
static int find_device(LSRContext *ctxt, char **name,
                       int major, int minor);
static void maybe_add_device(LSRContext *ctxt,
                             int major, int minor);
static int load_partitions(LSRContext *ctxt);
static LSRDir *alloc_dir();
static LSRDir *pop_dir(struct list_head *dtree);
static LSRDir *push_dir(struct list_head *dtree, char *name);
static int scan_slash_dev(LSRContext *ctxt);
static int scan_arrays(LSRContext *ctxt);
static int scan_disks(LSRContext *ctxt);
static void clean_context(LSRContext *ctxt);
static void disk_state_offline(LSRDisk *disk, char *state);
static void disk_state_online(LSRDisk *disk, char *state);
static const char *disk_state(LSRDisk *disk);
static void dump_disk_long(LSRDisk *disk);
static void dump_disk_short(LSRDisk *disk);
static void dump_disks(LSRContext *ctxt);
static void dump_array_diskinfo(LSRContext *ctxt,
                                char **diskinfo,
                                int numdisks,
                                int raid_disks);
static void dump_array_online(LSRContext *ctxt, LSRArray *array);
static void dump_array_offline(LSRContext *ctxt, LSRArray *array);
static void dump_array(LSRContext *ctxt, LSRArray *array);
static void dump_arrays(LSRContext *ctxt);
static void dump_raidtab_array_online(LSRArray *array);
static void dump_raidtab_array_offline(LSRArray *array);
static void dump_raidtab_array(LSRArray *array);
static void dump_raidtab(LSRContext *ctxt);
#if 0 /* Currently unused */
static int sb_equal(LSRDisk *disk1, LSRDisk *disk2);
#endif  /* Currently unused */


/*
 * Functions
 */


static void print_version()
{
    fprintf(stdout, "lsraid version " LSRAID_VERSION "\n");
    exit(0);
}  /* print_version() */


static void print_usage_long()
{
    /* FIXME */
    print_usage(0);
}  /* print_usage_long() */


static void print_usage(int rc)
{
    FILE *output = rc ? stderr : stdout;

    fprintf(output,
            "Usage: lsraid -A [-g|-f|-s] {-a <device> | -d <device>} ...\n"
            "       lsraid -A [-g|-f|-s] -p\n"
            "       lsraid -D [-l] {-a <device> | -d <device>} ...\n"
            "       lsraid -D [-l] -p\n"
            "       lsraid -R {-a <device> | -d <device>} ...\n"
            "       lsraid -R -p\n"
            "       lsraid -h\n"
            "       lsriad -V\n");

    exit(rc);
}  /* print_usage() */


static LSRContext *alloc_context()
{
    LSRContext *ctxt;
    ctxt = (LSRContext *)malloc(sizeof(LSRContext));
    if (ctxt)
    {
        memset(ctxt, 0, sizeof(LSRContext));
        INIT_LIST_HEAD(&ctxt->o_disks);
        INIT_LIST_HEAD(&ctxt->o_arrays);
        INIT_LIST_HEAD(&ctxt->disks);
        INIT_LIST_HEAD(&ctxt->arrays);
        ctxt->devtable = (char ***)malloc(MAJORS * sizeof(char **));
        if (!ctxt->devtable)
        {
            free(ctxt);
            return(NULL);
        }
        memset(ctxt->devtable, 0, MAJORS * sizeof(char **));
    }
    return(ctxt);
}  /* alloc_context() */


static LSRNamEnt *alloc_nament()
{
    LSRNamEnt *ent;

    ent = (LSRNamEnt *)malloc(sizeof(LSRNamEnt));
    if (ent)
    {
        memset(ent, 0, sizeof(LSRNamEnt));
        INIT_LIST_HEAD(&ent->list);
    }
    return(ent);
}  /* alloc_nament() */


static LSRArray *alloc_array()
{
    LSRArray *array;
    array = (LSRArray *)malloc(sizeof(LSRArray));
    if (array)
    {
        memset(array, 0, sizeof(LSRArray));
        INIT_LIST_HEAD(&array->a_list);
        INIT_LIST_HEAD(&array->disks);
        array->minor = -1;
    }
    return(array);
}  /* alloc_array() */


static LSRDisk *alloc_disk()
{
    LSRDisk *disk;
    disk = (LSRDisk *)malloc(sizeof(LSRDisk));
    if (disk)
    {
        memset(disk, 0, sizeof(LSRDisk));
        INIT_LIST_HEAD(&disk->d_list);
        INIT_LIST_HEAD(&disk->d_array);
    }
    return(disk);
}  /* alloc_disk() */


static void parse_options(LSRContext *ctxt, int argc, char *argv[])
{
    int i;
    LSRNamEnt *ent;

    /* Pre-clear */
    ctxt->action = 0;
    ctxt->flags = 0;

    for (i = 1; i < argc; i++)
    {
        if (argv[i][0] != '-')
            break;

        switch (argv[i][1])
        {
            case '-':
                if (argv[i][2] == '\0')
                {
                    i++;
                    break;
                }
                else if (strcmp(argv[i], "--help") == 0)
                    print_usage_long();
                else if (strcmp(argv[i], "--version") == 0)
                    print_version();
                else
                {
                    fprintf(stderr, "lsraid: Invalid option: %s\n",
                            argv[i]);
                }
                break;

            case 'h':
            case '?':
            case 'H':
                print_usage(0);
                break;

            case 'V':
                print_version();
                break;

            case 'A':
            case 'D':
            case 'R':
                if (ctxt->action)
                {
                    fprintf(stderr,
                            "lsraid: Options %c and %c cannot be combined\n",
                            argv[i][1], ctxt->action);
                    print_usage(EINVAL);
                }
                ctxt->action = argv[i][1];
                break;

            case 'g':
                ctxt->flags |= LSR_ARRAY_GOOD;
                break;

            case 's':
                ctxt->flags |= LSR_ARRAY_SPARE;
                break;

            case 'f':
                ctxt->flags |= LSR_ARRAY_FAILED;
                break;

            case 'p':
                ctxt->flags |= LSR_DEV_SCAN;
                break;

            case 'a':
                i++;
                if (i >= argc)
                {
                    fprintf(stderr,
                            "lsraid: Option \'-a\' requires an argument\n");
                    print_usage(EINVAL);
                }
                ent = alloc_nament();
                if (!ent)
                {
                    fprintf(stderr,
                            "lsraid: Unable to allocate memory for information on array \"%s\": %s\n",
                            argv[i],
                            strerror(errno));
                    return;
                }
                ent->name = argv[i];
                list_add_tail(&ent->list, &ctxt->o_arrays);
                break;

            case 'd':
                i++;
                if (i >= argc)
                {
                    fprintf(stderr,
                            "lsraid: Option \'-d\' requires an argument\n");
                    print_usage(EINVAL);
                }
                ent = alloc_nament();
                if (!ent)
                {
                    fprintf(stderr,
                            "lsraid: Unable to allocate memory for information on device \"%s\": %s\n",
                            argv[i],
                            strerror(errno));
                    return;
                }
                ent->name = argv[i];
                list_add_tail(&ent->list, &ctxt->o_disks);
                break;

            case 'l':
                ctxt->flags |= LSR_DISK_LONG;
                break;

            default:
                fprintf(stderr,
                        "lsraid: Invalid option: %s\n",
                        argv[i]);
                print_usage(EINVAL);
                break;
        }
    }

    if ((ctxt->flags & LSR_DISK_LONG) && (ctxt->action != LSR_DISK))
    {
        fprintf(stderr,
                "lsraid: '-l' option requires '-D' option\n");
        print_usage(EINVAL);
    }

    if ((ctxt->flags &
         (LSR_ARRAY_GOOD | LSR_ARRAY_FAILED | LSR_ARRAY_SPARE)) &&
        (ctxt->action != LSR_ARRAY))
    {
        fprintf(stderr,
                "lsraid: '-g', '-f', and '-s' options require '-A' option\n");
        print_usage(EINVAL);
    }

    if (list_empty(&ctxt->o_arrays) && list_empty(&ctxt->o_disks))
    {
        if (!(ctxt->flags & LSR_DEV_SCAN))
        {
            fprintf(stderr,
                    "lsraid: You must specify a device to query\n");
            print_usage(EINVAL);
        }
    }
    else if (ctxt->flags & LSR_DEV_SCAN)
    {
        fprintf(stderr,
                "lsraid: The '-p' option cannot be combined with the '-a' option\n"
                "        or the '-d' option\n");
        print_usage(EINVAL);
    }

    if (ctxt->action == '\0')
        ctxt->action = LSR_ARRAY;

}  /* parse_options() */


static int read_md_super(LSRContext *ctxt, LSRDisk *disk)
{
    int fd, rc, tot;
    mdp_super_t *sb;
    struct stat stat_buf;
    unsigned long dsize;

    if (!disk)
        return(-EINVAL);

    sb = &disk->sb;
    sb->md_magic = 0;

    if (!disk->name)
        return(-EINVAL);

    fd = open(disk->name, O_RDONLY);
    if (fd < 0)
    {
        rc = -errno;
        if (!(ctxt->flags & LSR_DEV_SCAN))
            fprintf(stderr,
                    "lsraid: Unable to open device \"%s\": %s\n",
                    disk->name, strerror(errno));
        goto out;
    }

    rc = fstat(fd, &stat_buf);
    if (rc)
    {
        rc = -errno;
        if (!(ctxt->flags & LSR_DEV_SCAN))
            fprintf(stderr,
                    "lsraid: Unable to query device \"%s\": %s\n",
                    disk->name, strerror(errno));
        goto out_close;
    }
    if (!S_ISBLK(stat_buf.st_mode))
    {
        rc = -EINVAL;
        if (!(ctxt->flags & LSR_DEV_SCAN))
            fprintf(stderr, "lsraid: \"%s\" is not a block device\n",
                    disk->name);
        goto out_close;
    }
    disk->major = major(stat_buf.st_rdev);
    disk->minor = minor(stat_buf.st_rdev);

    rc = ioctl(fd, BLKGETSIZE, &dsize);
    if (rc)
    {
        rc = -errno;
        if (!(ctxt->flags & LSR_DEV_SCAN))
            fprintf(stderr,
                    "lsraid: Unable to query size of \"%s\": %s\n",
                    disk->name, strerror(errno));
        goto out_close;
    }

    /* Shift to 1k blocks because MD_NEW_SIZE_BLOCKS wants it */
    dsize >>= 1;

    if (raidseek(fd, MD_NEW_SIZE_BLOCKS(dsize)) == (long long)-1)
    {
        rc = -errno;
        if (!(ctxt->flags & LSR_DEV_SCAN))
            fprintf(stderr,
                    "lsraid: Unable to find superblock for \"%s\": %s\n",
                    disk->name, strerror(errno));
        goto out_close;
    }

    tot = 0;
    while (tot < MD_SB_BYTES)
    {
        rc = read(fd, sb + tot, MD_SB_BYTES - tot);
        if (rc == 0)
            break;
        else if (rc < 0)
        {
            if ((errno == EAGAIN) || (errno == EINTR))
                continue;
            else
                break;
        }
        else
            tot += rc;
    }

    if (tot < MD_SB_BYTES)
    {
        if (!(ctxt->flags & LSR_DEV_SCAN))
            fprintf(stderr,
                    "lsraid: Unable to read raid superblock from device \"%s\": %s\n",
                    disk->name,
                    rc ? strerror(errno) : "Unexpected EOF");
        rc = -EAGAIN;
    }
    else
        rc = 0;

    if (sb->md_magic != MD_SB_MAGIC)
    {
        if (!(ctxt->flags & LSR_DEV_SCAN))
            fprintf(stderr,
                    "lsraid: Device \"%s\" does not have a valid raid superblock\n",
                    disk->name);
        rc = -EINVAL;
        goto out_close;
    }

    if ((sb->major_version < 0) ||
        (sb->minor_version < 90))
    {
        if (!(ctxt->flags & LSR_DEV_SCAN))
            fprintf(stderr,
                    "lsraid: Device \"%s\" is too old (raid version %d.%d)\n",
                    disk->name, sb->major_version, sb->minor_version);
        rc = -EINVAL;
    }

out_close:
    close(fd);

out:
    if (rc)
        sb->md_magic = 0;

    return(rc);
}  /* read_md_super() */


static int query_array(LSRContext *ctxt, LSRArray *array)
{
    int fd, rc, i, numdisks;
    mdu_disk_info_t dinfo;
    struct stat stat_buf;
    LSRDisk *disk;

    rc = -EINVAL;
    if (!array || !array->name)
        goto out;

    fd = open(array->name, O_RDONLY);
    if (fd < 0)
    {
        if ((errno == ENXIO) || (errno == ENODEV))
            rc = 0;
        else
        {
            rc = -errno;
            if (!(ctxt->flags & LSR_DEV_SCAN))
                fprintf(stderr,
                        "lsraid: Unable to query md device \"%s\": %s\n",
                        array->name, strerror(errno));
        }
        goto out;
    }

    rc = fstat(fd, &stat_buf);
    if (rc)
    {
        rc = -errno;
        if (!(ctxt->flags & LSR_DEV_SCAN))
            fprintf(stderr,
                    "lsraid: Unable to query md device \"%s\": %s\n",
                    array->name, strerror(errno));
        goto out_close;
    }
    if (!S_ISBLK(stat_buf.st_mode) ||
        (major(stat_buf.st_rdev) != MD_MAJOR))
    {
        rc = -EINVAL;
        if (!(ctxt->flags & LSR_DEV_SCAN))
            fprintf(stderr, "lsraid: \"%s\" is not an md device\n",
                    array->name);
        goto out_close;
    }
    array->minor = minor(stat_buf.st_rdev);

    /*
     * Ok, here we consider the array "valid."  IOW, it can be
     * considered to exist, at least in a possibly offline state.
     * This is important, because if the array is online, calling
     * find_add_disk() below is going to be looking for the array.  It
     * had better be there.
     */
    list_add_tail(&array->a_list, &ctxt->arrays);

    rc = ioctl(fd, GET_ARRAY_INFO, &array->info);
    if (rc)
    {
        /* Array is offline */
        rc = 0;
        goto out_close;
    }

    for (i = 0, numdisks = 0;
         (numdisks < array->info.nr_disks) &&
         (i < (array->info.raid_disks + array->info.spare_disks)) &&
         (i < MD_SB_DISKS);
         i++)
    {
        memset(&dinfo, 0, sizeof(mdu_disk_info_t));
        dinfo.number = i;
        rc = ioctl(fd, GET_DISK_INFO, &dinfo);
        if (rc)
        {
            /*
             * Note, older raid 0.90 as a bug where it thinks
             * dinfo.number > array->info.nr_disks is -EINVAL.  This is
             * not the case, as a missing disk counts in .number but
             * not in .nr_disks.  Oh, well, get a newer raid.
             */
            if (!(ctxt->flags & LSR_DEV_SCAN))
                fprintf(stderr,
                        "lsraid: Unable to query disk %d of md device \"%s\": %s\n",
                        i, array->name, strerror(errno));
            break;
        }

        /* Empty entry */
        if (!dinfo.major)
            continue;

        disk = find_add_disk(ctxt, NULL, dinfo.major, dinfo.minor);
        if (!disk)
            continue;

        if (disk->info.major)
        {
            /* 
             * disk->info has been filled already.  That's weird, since
             * only one online array should have it.  Let's check it.
             */
            if ((disk->info.major != dinfo.major) ||
                (disk->info.minor != dinfo.minor) ||
                (disk->sb.md_minor != array->minor))
            {
                fprintf(stderr,
                        "lsraid: Very weird.  Array %s [dev %d, %d] is online and claims to have disk\n"
                        "        %s [dev %d, %d].  However, the disk believes it belongs to\n"
                        "        md device [dev %d, %d].\n",
                        array->name ? array->name : "(unknown)",
                        MD_MAJOR, array->minor,
                        disk->name ? disk->name : "(unknown)",
                        dinfo.major, dinfo.minor,
                        MD_MAJOR, disk->sb.md_minor);
            }
        }
        else
            disk->info = dinfo;

        numdisks++;
    }

    /* Disk errors are not array errors */
    rc = 0;

out_close:
    close(fd);

out:
    return(rc);
}  /* query_array() */


static int fill_array(LSRContext *ctxt, LSRArray *array)
{
    if (!array)
        return(-EINVAL);

    if (!array->name)
    {
        if (find_device(ctxt, &array->name,
                        MD_MAJOR, array->minor))
        {
            if (!(ctxt->flags & LSR_DEV_SCAN))
                fprintf(stderr,
                        "lsraid: md device [dev %d, %d] has no entry on this system\n",
                        MD_MAJOR, array->minor);
            return(-ENOENT);
        }
    }

    return(query_array(ctxt, array));
}  /* fill_array() */


static int fill_disk(LSRContext *ctxt, LSRDisk *disk)
{
    int i, numdisks;

    if (!disk)
        return(-EINVAL);

    if (!disk->name)
    {
        if (!disk->major)
            return(-EINVAL);
        if (find_device(ctxt, &disk->name,
                        disk->major, disk->minor))
        {
            if (!(ctxt->flags & LSR_DEV_SCAN))
                fprintf(stderr,
                        "lsraid: Device [dev %d, %d] has no entry on this system\n",
                        disk->major, disk->minor);
            return(-ENOENT);
        }
    }

    read_md_super(ctxt, disk);
    if (!disk->sb.md_magic)
        return(-ENOENT);

    /*
     * We consider the disk "valid" here.  It has a md superblock
     * and can be looked at, even if it no longer belongs to anything.
     * This is important because the find_add_array() call below will
     * be looking for the disk.
     */
    list_add_tail(&disk->d_list, &ctxt->disks);

    find_add_array(ctxt, NULL, disk->sb.md_minor);

    for (i = 0, numdisks = 0;
         (numdisks < disk->sb.nr_disks) &&
         (i < (disk->sb.raid_disks + disk->sb.spare_disks)) &&
         (i < MD_SB_DISKS);
         i++)
    {
        /* Only if non-empty */
        if (disk->sb.disks[i].major)
        {
            find_add_disk(ctxt, NULL,
                          disk->sb.disks[i].major,
                          disk->sb.disks[i].minor);
            numdisks++;
        }
    }

    return(0);
}  /* fill_disk() */


/* minor is ignored if -1, name is ignored if NULL */
static LSRArray *find_add_array(LSRContext *ctxt, char *name,
                                int minor)
{
    LSRArray *array;
    struct list_head *pos;

    /* No valid search */
    if ((name == NULL) && (minor == -1))
    {
        fprintf(stderr,
                "lsraid: find_add_array() called with no valid search\n");
        return(NULL);
    }

    array = NULL;
    list_for_each(pos, &ctxt->arrays)
    {
        array = list_entry(pos, LSRArray, a_list);
        if (!name && (minor == array->minor))
            break;
        if ((minor == -1) && array->name && !strcmp(name, array->name))
            break;
        if ((minor == array->minor) && array->name &&
            !strcmp(name, array->name))
            break;
        array = NULL;
    }

    if (array == NULL)
    {
        array = alloc_array();
        if (!array)
        {
            if (name)
                fprintf(stderr,
                        "lsraid: Unable to allocate memory for information on array \"%s\": %s\n",
                    name,
                    strerror(errno));
            else
                fprintf(stderr,
                        "lsraid: Unable to allocate memory for information on array [dev %d, %d]: %s\n",
                    MD_MAJOR, minor,
                    strerror(errno));

            return(NULL);
        }
        if (name)
            array->name = name;
        if (minor > -1)
            array->minor = minor;

        /* Will add array to ctxt->arrays */
        if (fill_array(ctxt, array))
        {
            free(array);
            array = NULL;
        }
    }

    return(array);
}  /* find_add_array() */


/* major/minor is ignored if major is 0, name is ignored if NULL */
static LSRDisk *find_add_disk(LSRContext *ctxt, char *name,
                              int major, int minor)
{
    LSRDisk *disk;
    struct list_head *pos;

    /* No valid search */
    if ((name == NULL) && (major == 0))
    {
        fprintf(stderr,
                "lsraid: find_add_disk() called with no valid search\n");
        return(NULL);
    }

    disk = NULL;
    list_for_each(pos, &ctxt->disks)
    {
        disk = list_entry(pos, LSRDisk, d_list);
        if (!name && (major == disk->major) && (minor == disk->minor))
            break;
        if ((major == 0) && disk->name && !strcmp(name, disk->name))
            break;
        if ((major == disk->major) && (minor == disk->minor) &&
            disk->name && !strcmp(name, disk->name))
            break;
        disk = NULL;
    }

    if (disk == NULL)
    {
        disk = alloc_disk();
        if (!disk)
        {
            if (name)
                fprintf(stderr,
                        "lsraid: Unable to allocate memory for information on disk \"%s\": %s\n",
                    name,
                    strerror(errno));
            else
                fprintf(stderr,
                        "lsraid: Unable to allocate memory for information on adisk [dev %d, %d]: %s\n",
                    major, minor,
                    strerror(errno));

            return(NULL);
        }
        if (name)
            disk->name = name;
        if (major > 0)
        {
            disk->major = major;
            disk->minor = minor;
        }

        /* Will add the disk to ctxt->disks */
        if (fill_disk(ctxt, disk))
        {
            free(disk);
            disk = NULL;
        }
    }

    return(disk);
}  /* find_add_disk() */


static void maybe_add_device(LSRContext *ctxt,
                             int major, int minor)
{
    FILE *f;
    char *dev_base = NULL;
    static char *proc_name = NULL;
    LSRNamEnt *ent;

    if (!ctxt || !ctxt->devtable || !major)
        return;

    if (!proc_name)
    {
        proc_name = (char *)malloc(sizeof(char) * (PATH_MAX + 1));
        if (!proc_name)
        {
            fprintf(stderr,
                    "lsraid: Unable to allocate memory for information on device \"%s\": %s\n",
                    ctxt->devtable[major][minor],
                    strerror(errno));
            return;
        }
    }
    proc_name[0] = '\0';

    dev_base = strrchr(ctxt->devtable[major][minor], '/');
    if ((dev_base == NULL) ||
        (dev_base[1] == '\0'))
        return;

    /* Welcome to Hardcoded /proc Land */
    if (strlen(dev_base) >
        (PATH_MAX - strlen("/proc/ide//media")))
        return;

    sprintf(proc_name, "/proc/ide/%s/media", dev_base);

    f = fopen(proc_name, "r");
    if (f)
    {
        if (fgets(proc_name, PATH_MAX + 1, f))
        {
            /* IDE devices we don't want to probe */
            if (!strcmp(proc_name, "cdrom") ||
                !strcmp(proc_name, "tape"))
            {
                fclose(f);
                return;
            }
        }
        fclose(f);
    }

    ent = alloc_nament();
    if (!ent)
    {
        fprintf(stderr,
                "lsraid: Unable to allocate memory for information on device \"%s\": %s\n",
                ctxt->devtable[major][minor],
                strerror(errno));
        return;
    }
    ent->name = ctxt->devtable[major][minor];

    /* Should we add MD_MAJOR devices to o_disks too? */
    if (major == MD_MAJOR)
        list_add_tail(&ent->list, &ctxt->o_arrays);
    else
        list_add_tail(&ent->list, &ctxt->o_disks);
}  /* maybe_add_device() */


static int load_partitions(LSRContext *ctxt)
{
    FILE *proc;
    int major, minor, rc;
    char line[MAX_LINE_LENGTH], name[MAX_LINE_LENGTH];
    struct stat stat_buf;

    if (!ctxt || !ctxt->devtable)
        return(-EINVAL);

    /* Here we are in wonderful Hardcoded /proc Land */
    proc = fopen("/proc/partitions", "r");
    if (!proc)
        return(-errno);

    while (1)
    {
        rc = 0;
        if ((fgets(line, MAX_LINE_LENGTH, proc)) == NULL)
            break;

        name[0] = 0;
        major = minor = 0;

        /* FIXME: I'm lazy.  Can this be overrun? */
        if (sscanf(line, "%d %d %*d %99[^ \t\n]",
                   &major, &minor, name) < 3)
            continue;

        if (*name && major)
        {
            if (!ctxt->devtable[major])
            {
                ctxt->devtable[major] =
                    (char **)malloc(MINORS * sizeof(char *));
                if (!ctxt->devtable[major])
                {
                    rc = -ENOMEM;
                    break;
                }
                memset(ctxt->devtable[major], 0,
                       MINORS * sizeof(char *));
            }

            if (ctxt->devtable[major][minor])
                continue;

            /*
             * This is all '/dev' specific.  If it isn't found there,
             * it is going to fall back to /dev scanning (think 
             * /dev/usb/disk1 or something in devfs).  This heuristic
             * should probably check whatever devfs puts together
             * for 'hda1 in /proc/partitions', but I don't know what
             * devfs does, and I don't much care right now.
             */
            ctxt->devtable[major][minor] =
                (char *)malloc((strlen(name) + strlen("/dev/") + 1) *
                               sizeof(char));
            if (!ctxt->devtable[major][minor])
                continue;
            sprintf(ctxt->devtable[major][minor], "/dev/%s", name);

            rc = stat(ctxt->devtable[major][minor], &stat_buf);
            if ((rc != 0) && (!S_ISBLK(stat_buf.st_mode)))
            {
                free(ctxt->devtable[major][minor]);
                ctxt->devtable[major][minor] = NULL;
            }
            else if (ctxt->flags & LSR_DEV_SCAN)
                maybe_add_device(ctxt, major, minor);
        }
    }

    fclose(proc);
    return(rc);
}  /* load_partitions() */


static LSRDir *alloc_dir()
{
    LSRDir *dir;

    dir = (LSRDir *)malloc(sizeof(LSRDir));
    if (dir)
    {
        memset(dir, 0, sizeof(LSRDir));
        INIT_LIST_HEAD(&dir->list);
    }
    return(dir);
}  /* alloc_dir() */


static LSRDir *push_dir(struct list_head *dtree, char *name)
{
    int len;
    LSRDir *dir, *pdir;

    if (!dtree)
        return(NULL);
    if (!list_empty(dtree))
        pdir = list_entry(dtree->next, LSRDir, list);
    else
        pdir = NULL;

    if (!name)
        return(pdir);
    len = strlen(name);
    if (len > NAME_MAX)
        return(pdir);
    if (pdir)
    {
        len += strlen("/");
        len += strlen(pdir->name);
        if (len > PATH_MAX)
            return(pdir);
    }

    dir = alloc_dir();
    if (!dir)
        return(pdir);

    sprintf(dir->name, "%s/%s", pdir ? pdir->name : "", name);

    dir->dir = opendir(dir->name);
    if (!dir->dir)
    {
        free(dir);
        return(pdir);
    }

    list_add(&dir->list, dtree);

    return(dir);
}


static LSRDir *pop_dir(struct list_head *dtree)
{
    struct list_head *top;
    LSRDir *dir;

    if (!dtree)
        return(NULL);
    if (list_empty(dtree))
        return(NULL);

    top = dtree->next;
    dir = list_entry(top, LSRDir, list);
    closedir(dir->dir);
    list_del(&dir->list);
    free(dir);

    if (list_empty(dtree))
        return(NULL);
    top = dtree->next;
    return(list_entry(top, LSRDir, list));
}  /* pop_dir() */


static int scan_slash_dev(LSRContext *ctxt)
{
    int rc, dirnamelen, major, minor;
    char name[PATH_MAX + 1];
    struct list_head dtree;
    struct dirent *dirent;
    struct stat stat_buf;
    LSRDir *dir;

    if (!ctxt || !ctxt->devtable)
        return(-EINVAL);

    INIT_LIST_HEAD(&dtree);

    dir = push_dir(&dtree, "dev");
    if (!dir)
        return(-ENOENT);
    dirnamelen = strlen(dir->name);

    rc = 0;
    while (!list_empty(&dtree))
    {
        /* sanity */
        if (!dir || !dir->dir)
            break;

        dirent = readdir(dir->dir);
        if (dirent == NULL)
        {
            dir = pop_dir(&dtree);
            if (dir)
                dirnamelen = strlen(dir->name);
            continue;
        }
        if (!strcmp(dirent->d_name, ".") ||
            !strcmp(dirent->d_name, ".."))
            continue;

        if ((dirnamelen + strlen("/") +
             strlen(dirent->d_name)) > PATH_MAX)
            continue;
        sprintf(name, "%s/%s", dir->name, dirent->d_name);
        rc = lstat(name, &stat_buf);
        if (rc != 0)
            continue;
        if (S_ISDIR(stat_buf.st_mode))
        {
            dir = push_dir(&dtree, dirent->d_name);
            if (dir)
                dirnamelen = strlen(dir->name);
        }
        else if (S_ISBLK(stat_buf.st_mode))
        {
            major = major(stat_buf.st_rdev);
            minor = minor(stat_buf.st_rdev);
            if (!ctxt->devtable[major])
            {
                ctxt->devtable[major] =
                    (char **)malloc(MINORS * sizeof(char *));
                if (!ctxt->devtable[major])
                {
                    /* FIXME: what error if any? rc = -ENOMEM; */
                    continue;
                }
                memset(ctxt->devtable[major], 0,
                       MINORS * sizeof(char *));
            }

            if (ctxt->devtable[major][minor])
                continue;
            
            ctxt->devtable[major][minor] =
                (char *)malloc((strlen(name) + 1) * sizeof(char));
            if (ctxt->devtable[major][minor])
                strcpy(ctxt->devtable[major][minor], name);
        }
    }

    return(rc);
}  /* scan_slash_dev() */


/*
 * We may want to build a raidtab, or at least we like pretty device
 * names.  However, the raid superblock only stores major,minor, so we
 * have to look up the name.  The algorithm is mostly brute.  99% of
 * devices will exist on the system, and hence will be in
 * /proc/partitions.  If, however, a device has been removed or moved
 * such that the major,minor are not in /proc/partitions, we have to
 * scan /dev.  Ewww.  We scan it once in scan_slash_dev().  The device
 * table holds the devices we've already looked at, either from
 * /proc/partitions or /dev.
 *
 * This assumes the 8/8 major/minor.
 */
static int find_device(LSRContext *ctxt, char **name,
                       int major, int minor)
{
    static int loaded_partitions = 0;
    static int scanned_dev = 0;
    int rc = 0;

    /* sanity */
    if (!ctxt || !ctxt->devtable)
        return(-EINVAL);
    if (!name)
        return(-EINVAL);
    if (*name)
        return(-EEXIST);
    if (!major)
        return(-EINVAL);

    if (ctxt->devtable[major] && ctxt->devtable[major][minor])
        goto out;

    if (!loaded_partitions)
    {
        load_partitions(ctxt);
        loaded_partitions = 1;
        if (ctxt->devtable[major] && ctxt->devtable[major][minor])
            goto out;
    }

    if (!scanned_dev)
    {
        scan_slash_dev(ctxt);
        scanned_dev = 1;
        if (ctxt->devtable[major] && ctxt->devtable[major][minor])
            goto out;
    }

    rc = -ENOENT;

out:
    if (ctxt->devtable[major])
        *name = ctxt->devtable[major][minor];
    return(rc);
}  /* find_device() */


static int scan_arrays(LSRContext *ctxt)
{
    LSRNamEnt *ent;
    struct list_head *pos;

    list_for_each(pos, &ctxt->o_arrays)
    {
        ent = list_entry(pos, LSRNamEnt, list);
        ent->u.array = find_add_array(ctxt, ent->name, -1);
    }

    return(0);
}  /* scan_arrays */


/* Ripped from md.c */
#if 0 /* Currently unused */
static int sb_equal(LSRDisk *disk1, LSRDisk *disk2)
{
	int ret;
	mdp_super_t *tmp1, *tmp2;

	tmp1 = (mdp_super_t *)malloc(sizeof(*tmp1));
	tmp2 = (mdp_super_t *)malloc(sizeof(*tmp2));

	if (!tmp1 || !tmp2) {
		ret = 0;
		goto abort;
	}

	*tmp1 = disk1->sb;
	*tmp2 = disk2->sb;

	/*
	 * nr_disks is not constant
	 */
	tmp1->nr_disks = 0;
	tmp2->nr_disks = 0;

	if (memcmp(tmp1, tmp2, MD_SB_GENERIC_CONSTANT_WORDS * 4))
		ret = 0;
	else
		ret = 1;

abort:
	if (tmp1)
		free(tmp1);
	if (tmp2)
		free(tmp2);

	return ret;
}
#endif  /* Currently unused */


static int scan_disks(LSRContext *ctxt)
{
    LSRNamEnt *ent;
    struct list_head *pos;

    list_for_each(pos, &ctxt->o_disks)
    {
        ent = list_entry(pos, LSRNamEnt, list);
        ent->u.disk = find_add_disk(ctxt, ent->name, 0, 0);
    }

    return(0);
}  /* scan_disks() */


static void clean_context(LSRContext *ctxt)
{
    int i;
    struct list_head *pos, *n;
    LSRArray *array;
    LSRDisk *disk;
    LSRNamEnt *ent;

    list_for_each_safe(pos, n, &ctxt->disks)
    {
        disk = list_entry(pos, LSRDisk, d_list);
        free(disk);
    }
    list_for_each_safe(pos, n, &ctxt->arrays)
    {
        array = list_entry(pos, LSRArray, a_list);
        free(array);
    }
    list_for_each_safe(pos, n, &ctxt->o_disks)
    {
        ent = list_entry(pos, LSRNamEnt, list);
        free(ent);
    }
    list_for_each_safe(pos, n, &ctxt->o_arrays)
    {
        ent = list_entry(pos, LSRNamEnt, list);
        free(ent);
    }
    if (ctxt->devtable)
    {
        for (i = 0; i < MAJORS; i++)
            if (ctxt->devtable[i])
                free(ctxt->devtable[i]);
        free(ctxt->devtable);
    }
    free(ctxt);
}  /* clean_context() */


static void disk_state_online(LSRDisk *disk, char *state)
{
    if (!disk || !state)
        return;

    if (disk->info.state & (1<<MD_DISK_REMOVED))
        sprintf(state, "removed");
    else if (disk->info.state & (1<<MD_DISK_FAULTY))
        sprintf(state, "failed");
    else if (disk->info.state & (1<<MD_DISK_ACTIVE))
        sprintf(state, "good");
    else
        sprintf(state, "spare");
}  /* disk_state_online() */


static void disk_state_offline(LSRDisk *disk, char *state)
{
    if (!disk || !state)
        return;

    if (disk->sb.this_disk.state & (1<<MD_DISK_REMOVED))
        sprintf(state, "removed");
    else if (disk->sb.this_disk.state & (1<<MD_DISK_FAULTY))
        sprintf(state, "failed");
    else if (disk->sb.this_disk.state & (1<<MD_DISK_ACTIVE))
        sprintf(state, "good");
    else
        sprintf(state, "spare");

#if 0 /* I don't think this matters */
    if (!(disk->sb.this_disk.state & (1<<MD_DISK_SYNC)))
        strcat(state, " (unsynced)");
#endif
}  /* disk_state_offline() */


/* For convenience, this is a static buffer */
static const char *disk_state(LSRDisk *disk)
{
    static char *state = NULL;

    if (!state)
    {
        state = (char *)malloc(255 * sizeof(char));
        if (!state)
            return("unknown");
    }

    state[0] = '\0';
    if (disk->info.major)
        disk_state_online(disk, state);
    else if (disk->array)
        disk_state_offline(disk, state);
    else
        strcat(state, "unknown");

    return(state);
}  /* disk_state() */


static void dump_disk_short(LSRDisk *disk)
{
    const char *state;

    if (!disk || !disk->major || !disk->sb.md_magic)
        return;
    
    state = disk_state(disk);

    fprintf(stdout,
            "[dev %d, %d] %s:\n",
            disk->major, disk->minor,
            disk->name ? disk->name : "(unknown)\n");

    if (disk->array)
    {
        fprintf(stdout,
                "\tmd device\t= [dev %d, %d] %s\n"
                "\tmd uuid\t\t= %08X.%08X.%08X.%08X\n",
                MD_MAJOR, disk->array->minor,
                disk->array->name,
                disk->array->uuid0, disk->array->uuid1,
                disk->array->uuid2, disk->array->uuid3);
    }
    else
    {
        fprintf(stdout,
                "\told md device\t= [dev %d, %d]\n"
                "\told md uuid\t= %08X.%08X.%08X.%08X\n",
                MD_MAJOR, disk->sb.md_minor,
                disk->sb.set_uuid0, disk->sb.set_uuid1,
                disk->sb.set_uuid2, disk->sb.set_uuid3);
    }

    fprintf(stdout,
            "\tstate\t\t= %s\n",
            state);

    if (*state == 'a')
        fprintf(stdout, "\tarray position\t= %d\n",
                disk->info.major ?
                disk->info.raid_disk :
                disk->sb.this_disk.raid_disk);

    fprintf(stdout, "\n");
}  /* dump_disk_short() */


static void dump_disk_long(LSRDisk *disk)
{
    int len;
    const char *state;
    char ctimestr[NAME_MAX + 1], utimestr[NAME_MAX + 1];

    if (!disk || !disk->major || !disk->sb.md_magic)
        return;

    state = ctime((time_t *)&disk->sb.ctime);
    len = strlen(state);
    if (len > NAME_MAX)
        len = NAME_MAX + 1;
    memcpy(ctimestr, state, len);
    ctimestr[len - 1] = '\0';

    state = ctime((time_t *)&disk->sb.utime);
    len = strlen(state);
    if (len > NAME_MAX)
        len = NAME_MAX + 1;
    memcpy(utimestr, state, len);
    utimestr[len - 1] = '\0';

    state = disk_state(disk);
    fprintf(stdout,
            "[dev %d, %d] %s:\n"
            "\tmd version\t\t= %d.%d.%d\n"
            "\tsuperblock uuid\t\t= %08X.%08X.%08X.%08X\n"
            "\tmd minor number\t\t= %d\n"
            "\tcreated\t\t\t= %d (%s)\n"
            "\tlast updated\t\t= %d (%s)\n"
            "\traid level\t\t= %d\n"
            "\tchunk size\t\t= %d KB\n"
            "\tapparent disk size\t= %d KB\n"
            "\tdisks in array\t\t= %d\n"
            "\trequired disks\t\t= %d\n"
            "\tactive disks\t\t= %d\n"
            "\tworking disks\t\t= %d\n"
            "\tfailed disks\t\t= %d\n"
            "\tspare disks\t\t= %d\n"
            "\tposition in disk list\t= %d\n"
            "\tposition in md device\t= %d\n"
            "\tstate\t\t\t= %s\n"
            "\n",
            disk->major, disk->minor,
            disk->name ? disk->name : "(unknown)",
            disk->sb.major_version, disk->sb.minor_version,
            disk->sb.patch_version,
            disk->sb.set_uuid0, disk->sb.set_uuid1,
            disk->sb.set_uuid2, disk->sb.set_uuid3,
            disk->sb.md_minor,
            disk->sb.ctime, ctimestr,
            disk->sb.utime, utimestr,
            disk->sb.level, disk->sb.chunk_size / 1024, disk->sb.size,
            disk->sb.nr_disks, disk->sb.raid_disks,
            disk->sb.active_disks, disk->sb.working_disks,
            disk->sb.failed_disks, disk->sb.spare_disks,
            disk->sb.this_disk.number,
            (disk->sb.this_disk.number < disk->sb.raid_disks) ?
            disk->sb.this_disk.raid_disk : -1,
            state
            );

}  /* dump_disk_long() */

static void dump_array_diskinfo(LSRContext *ctxt,
                                char **diskinfo,
                                int numdisks,
                                int raid_disks)
{
    int i;

    if (!ctxt || !diskinfo)
        return;

    /*
     * This is complicated by the '-g', '-s', and '-f' flags to array
     * operation.  I suspect there is a simpler way to check these, but
     * I'm lazy right now.
     */
    for (i = 0; i < numdisks; i++)
    {
        if ((ctxt->flags & LSR_ARRAY_GOOD) &&
            (!diskinfo[i] || !strstr(diskinfo[i], "good\n")))
            continue;
        else if ((ctxt->flags & LSR_ARRAY_SPARE) &&
                 (!diskinfo[i] || !strstr(diskinfo[i], "spare\n")))
            continue;
        else if ((ctxt->flags & LSR_ARRAY_FAILED) &&
                 (!diskinfo[i] || !strstr(diskinfo[i], "failed\n")))
            continue;
        else if (diskinfo[i])
            fprintf(stdout, "%s", diskinfo[i]);
        else if (i < raid_disks)
            fprintf(stdout,
                    "[dev   ?,   ?] %-16s %08X.%08X.%08X.%08X missing\n",
                    "(unknown)", 0, 0, 0, 0);
    }
}  /* dump_array_diskinfo() */


static void dump_array_online(LSRContext *ctxt, LSRArray *array)
{
    int numdisks, i;
    char *tmp = NULL;
    char **diskinfo;
    LSRDisk *disk;
    struct list_head *pos;

    if (!ctxt || !array)
        return;

    if (!array->info.major_version && !array->info.minor_version)
        return;

    numdisks = array->info.raid_disks + array->info.spare_disks +
       array->info.failed_disks;

    /* The diskinfo hoops are for printing disks in .number order */
    diskinfo =
        (char **)malloc(numdisks * sizeof(char*));
    if (!diskinfo)
    {
        fprintf(stderr,
                "lsraid: Unable to allocate memory while querying md device\n"
                "[dev %3d, %3d] %s: %s\n",
                MD_MAJOR, array->minor,
                array->name ? array->name : "(unknown)",
                strerror(errno));
        return;
    }
    memset(diskinfo, 0, numdisks * sizeof(char*));
        
    list_for_each(pos, &array->disks)
    {
        disk = list_entry(pos, LSRDisk, d_array);

        /* Shouldn't happen */
        if (!disk->info.major)
            continue;

        tmp = (char *)malloc(sizeof(char) * MAX_LINE_LENGTH);
        if (!tmp)
            continue;
        tmp[0] = '\0';

        sprintf(tmp,
                "[dev % 3d, % 3d] %-16s %08X.%08X.%08X.%08X %.17s\n",
                disk->major, disk->minor,
                disk->name ? disk->name : "(unknown)",
                disk->sb.set_uuid0, disk->sb.set_uuid1,
                disk->sb.set_uuid2, disk->sb.set_uuid3,
                disk_state(disk));

        i = disk->info.number;

        if (i < numdisks)
            diskinfo[i] = tmp;
        else
            free(tmp);
    }

    fprintf(stdout,
            "[dev % 3d, % 3d] %-16s %08X.%08X.%08X.%08X online\n",
            MD_MAJOR, array->minor,
            array->name ? array->name : "(unknown)",
            array->uuid0, array->uuid1, array->uuid2, array->uuid3);

    dump_array_diskinfo(ctxt, diskinfo, numdisks,
                        array->info.raid_disks);
    fprintf(stdout, "\n");

/* out_free: */
    for (i = 0; i < numdisks; i++)
        if (diskinfo[i])
            free(diskinfo[i]);
    free(diskinfo);
}  /* dump_array_online() */


static void dump_array_offline(LSRContext *ctxt, LSRArray *array)
{
    int numdisks, i, j;
    char *name, *tmp = NULL;
    const char *state;
    char **diskinfo;
    LSRDisk *disk, *a_disk = NULL;
    struct list_head *pos;

    if (!ctxt || !array)
        return;

    if (list_empty(&array->disks))
    {
        fprintf(stderr,
                "lsraid: md device [dev %d, %d] %s is offline: Please specify a disk to query\n",
                MD_MAJOR, array->minor,
                array->name ? array->name : "(unknown)");
        return;
    }
    a_disk = list_entry(array->disks.next, LSRDisk, d_array);
    numdisks = a_disk->sb.raid_disks + a_disk->sb.spare_disks +
        a_disk->sb.failed_disks;

    /* The diskinfo hoops are for printing disks in .number order */
    diskinfo =
        (char **)malloc(numdisks * sizeof(char*));
    if (!diskinfo)
    {
        fprintf(stderr,
                "lsraid: Unable to allocate memory while querying md device\n"
                "[dev %3d, %3d] %s: %s\n",
                MD_MAJOR, array->minor,
                array->name ? array->name : "(unknown)",
                strerror(errno));
        return;
    }
    memset(diskinfo, 0, numdisks * sizeof(char*));
        
    for (j = 0; j < numdisks; j++)
    {
        /* Empty slot */
        if (!a_disk->sb.disks[j].major)
            continue;

        tmp = (char *)malloc(sizeof(char) * MAX_LINE_LENGTH);
        if (!tmp)
            continue;
        tmp[0] = '\0';

        /* Oh, my, this is inefficient */
        name = "(unknown)";
        state = "unknown";
        list_for_each(pos, &array->disks)
        {
            disk = list_entry(pos, LSRDisk, d_array);
            if ((disk->major == a_disk->sb.disks[j].major) &&
                (disk->minor == a_disk->sb.disks[j].minor))
            {
                if (disk->name)
                    name = disk->name;
                state = disk_state(disk);
                break;
            }
        }
 
        /* UUID is guaranteed to be identical across &array->disks */
        sprintf(tmp,
                "[dev % 3d, % 3d] %-16s %08X.%08X.%08X.%08X %.17s\n",
                a_disk->sb.disks[j].major, a_disk->sb.disks[j].minor,
                name,
                a_disk->sb.set_uuid0, a_disk->sb.set_uuid1,
                a_disk->sb.set_uuid2, a_disk->sb.set_uuid3,
                state);

        i = a_disk->sb.disks[j].number;
        if (i < numdisks)
            diskinfo[i] = tmp;
        else
            free(tmp);
    }

    fprintf(stdout,
            "[dev % 3d, % 3d] %-16s %08X.%08X.%08X.%08X offline\n",
            MD_MAJOR, array->minor,
            array->name ? array->name : "(unknown)",
            array->uuid0, array->uuid1, array->uuid2, array->uuid3);

    dump_array_diskinfo(ctxt, diskinfo, numdisks,
                        a_disk->sb.raid_disks);
    fprintf(stdout, "\n");

/* out_free: */
    for (i = 0; i < numdisks; i++)
        if (diskinfo[i])
            free(diskinfo[i]);
    free(diskinfo);
}  /* dump_array_offline() */


static void dump_array(LSRContext *ctxt, LSRArray *array)
{
    if (!ctxt || !array)
        return;

    if (array->info.major_version || array->info.minor_version)
        dump_array_online(ctxt, array);
    else
        dump_array_offline(ctxt, array);
}  /* dump_array() */


static void dump_arrays(LSRContext *ctxt)
{
    struct list_head *pos;
    LSRArray *array;
    LSRDisk *disk;
    LSRNamEnt *ent;

    /*
     * It is possible that one of the arrays came from a disk that
     * no longer belongs to the array.  The result is that the array
     * does not have the disk, so the disk on the command line is not
     * printed from calls to dump_array().  The solution is to run
     * through &ctxt->o_disks.  Any disk where disk->array is NULL has
     * not been printed by dump_array() and is a candidate.
     */
    list_for_each(pos, &ctxt->arrays)
    {
        array = list_entry(pos, LSRArray, a_list);
        dump_array(ctxt, array);
    }

    list_for_each(pos, &ctxt->o_disks)
    {
        ent = list_entry(pos, LSRNamEnt, list);

        disk = ent->u.disk;
        if (!disk)
            continue;
        if (disk->array)
            continue;

        fprintf(stdout,
                "[dev % 3d, % 3d] %-16s %08X.%08X.%08X.%08X unbound\n",
                disk->major, disk->minor,
                disk->name ? disk->name : "(unknown)",
                disk->sb.set_uuid0, disk->sb.set_uuid1,
                disk->sb.set_uuid2, disk->sb.set_uuid3);
    }
}  /* dump_array() */


static void dump_disks(LSRContext *ctxt)
{
    int exists;
    struct list_head *dpos, *apos;
    LSRNamEnt *dent, *aent;
    LSRArray *array;
    LSRDisk *disk;

    list_for_each(apos, &ctxt->o_arrays)
    {
        aent = list_entry(apos, LSRNamEnt, list);
        array = aent->u.array;
        if (!array)
            continue;

        if (list_empty(&array->disks))
        {
            fprintf(stderr,
                    "lsraid: md device [dev %d, %d] %s is offline: Please specify a disk to query\n",
                    MD_MAJOR, array->minor,
                    array->name ? array->name : "(unknown)");
            continue;
        }
        list_for_each(dpos, &array->disks)
        {
            disk = list_entry(dpos, LSRDisk, d_array);
            if (!disk->major || !disk->sb.md_magic)
                continue;
            if (ctxt->flags & LSR_DISK_LONG)
                dump_disk_long(disk);
            else
                dump_disk_short(disk);
        }
    }

    list_for_each(dpos, &ctxt->o_disks)
    {
        dent = list_entry(dpos, LSRNamEnt, list);
        disk = dent->u.disk;
        if (!disk)
            continue;
        if (!disk->major || !disk->sb.md_magic)
            continue;

        /* Was it seen in the loop over &ctxt->o_arrays */
        if (disk->array)
        {
            /* Can you smell the O(n^2)?  Please make it better */
            exists = 0;
            list_for_each(apos, &ctxt->o_arrays)
            {
                aent = list_entry(apos, LSRNamEnt, list);
                array = aent->u.array;
                if (!array)
                    continue;
                if (disk->array == array)
                {
                    exists = 1;
                    break;
                }
            }

            if (exists)
                continue;
        }

        if (ctxt->flags & LSR_DISK_LONG)
            dump_disk_long(disk);
        else
            dump_disk_short(disk);
    }
}  /* dump_disks() */


static void dump_raidtab_array_online(LSRArray *array)
{
    int i, n, s, numdisks, failedstart;
    LSRDisk *disk;
    struct list_head *pos;
    char **diskinfo;
    char *tmp, *type;

    if (!array)
        return;

    if (!array->name)
    {
        /* FIXME: probably should bleat here */
        return;
    }

    /*
     * raid_disks slots for good disks
     * spare_disks slots for good disks
     * raid_disks slots for failed/missing disks,
     *
     * This attempts to follow the order of raidtab rules, rather
     * than any current order.  (eg, spare-disks should come after
     * raid-disks in the raidtab, even if the first disk in the
     * online array is a spare-disk).
     *
     * A good disk is placed in its raid_disk location in the first
     * raid_disks slots.  A spare disk is placed sequentially in the
     * spare_disks slots.  A failed or missing disk is placed in its
     * raid_disk location in the last raid_disks slots.  If more than
     * one failed disk exists for a given raid_disk slot, the first is
     * given only.
     *
     * Eg, a raid5 array, where there are three disks in the array plus
     * one spare.  The second disk has failed:
     *
     * diskinfo[0] = good disk 0 = disk0
     * diskinfo[1] = good disk 1 = NULL
     * diskinfo[2] = good disk 2 = disk2
     * diskinfo[3] = spare disk 0 = disk3
     * diskinfo[4] = failed disk 0 = NULL
     * diskinfo[5] = failed disk 1 = disk1
     * diskinfo[6] = failed disk 2 = NULL
     */
    numdisks = array->info.raid_disks + array->info.spare_disks +
        array->info.raid_disks;
    failedstart = array->info.raid_disks + array->info.spare_disks;
    diskinfo = (char **)malloc(sizeof(char *) * numdisks);
    if (!diskinfo)
    {
        fprintf(stderr,
                "lsraid: Unable to allocate memory while creating raidtab entry for md device\n"
                "[dev %d, %d] %s: %s\n",
                MD_MAJOR, array->minor,
                array->name ? array->name : "(unknown)",
                strerror(errno));

        return;
    }
    memset(diskinfo, 0, sizeof(char *) * numdisks);

    /* diskinfo [i]ndex, type [n]umber, [s]pare number */
    i = n = s = 0;
    list_for_each(pos, &array->disks)
    {
        disk = list_entry(pos, LSRDisk, d_array);

        tmp = (char *)malloc(sizeof(char) * MAX_LINE_LENGTH);
        if (!tmp)
            continue;
        tmp[0] = '\0';

        if (disk->info.state & (1<<MD_DISK_ACTIVE))
        {
            type = "raid-disk";
            i = n = disk->info.raid_disk;
        }
        else if (disk->info.state & (1<<MD_DISK_FAULTY))
        {
            type = "failed-disk";
            n = disk->info.raid_disk;
            i = failedstart + disk->info.raid_disk;
        }
        else if (disk->info.state == 0)
        {
            type = "spare-disk";
            n = s++;
            i = n + array->info.raid_disks;
        }
        else
        {
            type = "error";
            n = 0;
            i = numdisks; /* out of bounds */
        }
        sprintf(tmp,
                "\tdevice\t\t%s\n"
                "\t%s\t\t%d\n",
                disk->name ? disk->name : "(unknown)",
                type, n);
        if ((i < numdisks) && !diskinfo[i])
            diskinfo[i] = tmp;
        else
            free(tmp);
    }

    /* Scan for missing disks */
    for (i = 0; i < array->info.raid_disks; i++)
    {
        if (!diskinfo[i] && !diskinfo[failedstart + i])
        {
            tmp = (char *)malloc(sizeof(char) * MAX_LINE_LENGTH);
            if (!tmp)
                continue;
            tmp[0] = '\0';

            sprintf(tmp,
                "\tdevice\t\t/dev/null\n"
                "\tfailed-disk\t\t%d\n",
                i);
            if ((failedstart + i) < numdisks)
                diskinfo[failedstart + i] = tmp;
            else
                free(tmp);
        }
    }

    fprintf(stdout,
            "# md device [dev %d, %d] %s queried online\n"
            "raiddev %s\n"
            "\traid-level\t\t%d\n"
            "\tnr-raid-disks\t\t%d\n"
            "\tnr-spare-disks\t\t%d\n"
            "\tpersistent-superblock\t%d\n"
            "\tchunk-size\t\t%d\n",
            MD_MAJOR, array->minor,
            array->name,
            array->name,
            array->info.level,
            array->info.raid_disks,
            array->info.spare_disks,
            array->info.not_persistent ? 0 : 1,
            /* raid docs are wrong.  raidtab chunk size is in K */
            array->info.chunk_size / 1024);
    fprintf(stdout, "\n");

    for (i = 0; i < numdisks; i++)
        if (diskinfo[i])
            fprintf(stdout, "%s", diskinfo[i]);

    fprintf(stdout, "\n");

/* out_free: */
    for (i = 0; i < numdisks; i++)
        if (diskinfo[i])
            free(diskinfo[i]);
    free(diskinfo);
}  /* dump_raidtab_array_online() */


static void dump_raidtab_array_offline(LSRArray *array)
{
    int j, i, n, s, numdisks, failedstart;
    LSRDisk *disk, *a_disk;
    struct list_head *pos;
    char **diskinfo;
    char *tmp, *type, *name;

    if (!array)
        return;

    if (!array->name)
    {
        /* FIXME: probably should bleat here */
        return;
    }

    if (list_empty(&array->disks))
    {
        /*
         * We really shouldn't be able to get here, as an offline array
         * is discovered from its disks
         */
        fprintf(stderr,
                "lsraid: device [dev %d, %d] %s has no disks\n",
                MD_MAJOR, array->minor,
                array->name ? array->name : "(Unknown)");
        return;
    }

    a_disk = list_entry(array->disks.next, LSRDisk, d_array);

    /*
     * raid_disks slots for good disks
     * spare_disks slots for good disks
     * raid_disks slots for failed/missing disks,
     *
     * This attempts to follow the order of raidtab rules, rather
     * than any current order.  (eg, spare-disks should come after
     * raid-disks in the raidtab, even if the first disk in the
     * online array is a spare-disk.
     *
     * A good disk is placed in its raid_disk location in the first
     * raid_disks slots.  A spare disk is placed sequentially in the
     * spare_disks slots.  A failed or missing disk is placed in its
     * raid_disk location in the last raid_disks slots.  If more than
     * one failed disk exists for a given raid_disk, only the first is
     * given.
     *
     * Eg, a raid5 array, where there are three disks in the array plus
     * one spare.  The second disk has failed:
     *
     * diskinfo[0] = good disk 0 = disk0
     * diskinfo[1] = good disk 1 = NULL
     * diskinfo[2] = good disk 2 = disk2
     * diskinfo[3] = spare disk 0 = disk3
     * diskinfo[4] = failed disk 0 = NULL
     * diskinfo[5] = failed disk 1 = disk1
     * diskinfo[6] = failed disk 2 = NULL
     */
    numdisks = a_disk->sb.raid_disks + a_disk->sb.spare_disks +
        a_disk->sb.raid_disks;
    failedstart = a_disk->sb.raid_disks + a_disk->sb.spare_disks;
    diskinfo = (char **)malloc(sizeof(char *) * numdisks);
    if (!diskinfo)
    {
        fprintf(stderr,
                "lsraid: Unable to allocate memory while creating raidtab entry for md device\n"
                "[dev %d, %d] %s: %s\n",
                MD_MAJOR, array->minor,
                array->name ? array->name : "(unknown)",
                strerror(errno));

        return;
    }
    memset(diskinfo, 0, sizeof(char *) * numdisks);

    /* diskinfo [i]ndex, location [n]umber, [s]pare number */
    i = n = s = 0;
    for (j = 0; (j < numdisks) && (j < MD_SB_DISKS); j++)
    {
        if (!a_disk->sb.disks[j].major)
            continue;

        tmp = (char *)malloc(sizeof(char) * MAX_LINE_LENGTH);
        if (!tmp)
            continue;
        tmp[0] = '\0';

        if (a_disk->sb.disks[j].state & (1<<MD_DISK_ACTIVE))
        {
            type = "raid-disk";
            i = n = a_disk->sb.disks[j].raid_disk;
        }
        else if (a_disk->sb.disks[j].state & (1<<MD_DISK_FAULTY))
        {
            type = "failed-disk";
            n = a_disk->sb.disks[j].raid_disk;
            i = failedstart + a_disk->sb.disks[j].raid_disk;;
        }
        else if (a_disk->sb.disks[j].state == 0)
        {
            type = "spare-disk";
            n = s++;
            i = n + a_disk->sb.raid_disks;
        }
        else
        {
            type = "error";
            n = 0;
            i = numdisks; /* out of bounds */
        }

        /* Oh, my, this is inefficient */
        name = "(unknown)";
        list_for_each(pos, &array->disks)
        {
            disk = list_entry(pos, LSRDisk, d_array);
            if ((disk->major == a_disk->sb.disks[j].major) &&
                (disk->minor == a_disk->sb.disks[j].minor))
            {
                if (disk->name)
                    name = disk->name;
                break;
            }
        }
        sprintf(tmp,
                "\tdevice\t\t%s\n"
                "\t%s\t\t%d\n",
                name, type, n);
        if ((i < numdisks) && !diskinfo[i])
            diskinfo[i] = tmp;
        else
            free(tmp);
    }

    /* Scan for missing disks */
    for (i = 0; i < a_disk->sb.raid_disks; i++)
    {
        if (!diskinfo[i] && !diskinfo[failedstart + i])
        {
            tmp = (char *)malloc(sizeof(char) * MAX_LINE_LENGTH);
            if (!tmp)
                continue;
            tmp[0] = '\0';

            sprintf(tmp,
                "\tdevice\t\t/dev/null\n"
                "\tfailed-disk\t\t%d\n",
                i);
            if ((failedstart + i) < numdisks)
                diskinfo[failedstart + i] = tmp;
            else
                free(tmp);
        }
    }

    fprintf(stdout,
            "# md device [dev %d, %d] %s queried offline\n"
            "# Authoritative device is [dev %d, %d] %s\n"
            "raiddev %s\n"
            "\traid-level\t\t%d\n"
            "\tnr-raid-disks\t\t%d\n"
            "\tnr-spare-disks\t\t%d\n"
            "\tpersistent-superblock\t%d\n"
            "\tchunk-size\t\t%d\n",
            MD_MAJOR, array->minor,
            array->name,
            a_disk->major, a_disk->minor,
            a_disk->name ? a_disk->name : "(unknown)",
            array->name,
            a_disk->sb.level,
            a_disk->sb.raid_disks,
            a_disk->sb.spare_disks,
            a_disk->sb.not_persistent ? 0 : 1,
            /* raid docs are wrong.  raidtab chunk size is in K */
            a_disk->sb.chunk_size / 1024);
    fprintf(stdout, "\n");

    for (i = 0; i < numdisks; i++)
        if (diskinfo[i])
            fprintf(stdout, "%s", diskinfo[i]);

    fprintf(stdout, "\n");

/* out_free: */
    for (i = 0; i < numdisks; i++)
        if (diskinfo[i])
            free(diskinfo[i]);
    free(diskinfo);}  /* dump_raidtab_array_offline() */


static void dump_raidtab_array(LSRArray *array)
{
    if (array->info.major_version || array->info.minor_version)
        dump_raidtab_array_online(array);
    else
        dump_raidtab_array_offline(array);
}  /* dump_raidtab_array() */


static void dump_raidtab(LSRContext *ctxt)
{
    LSRArray *array;
    LSRNamEnt *ent;
    struct list_head *pos;

    fprintf(stdout,
            "# This raidtab was generated by lsraid version %s.\n"
            "# It was created from a query on the following devices:\n",
            LSRAID_VERSION);

    list_for_each(pos, &ctxt->o_arrays)
    {
        ent = list_entry(pos, LSRNamEnt, list);
        fprintf(stdout, "#\t%s\n", ent->name);
    }
    list_for_each(pos, &ctxt->o_disks)
    {
        ent = list_entry(pos, LSRNamEnt, list);
        fprintf(stdout, "#\t%s\n", ent->name);
    }

    fprintf(stdout, "\n");

    /*
     * Grr, ignores the issue of raid 1+0.  The admin should be
     * bright enough...
     */
    list_for_each(pos, &ctxt->arrays)
    {
        array = list_entry(pos, LSRArray, a_list);
        dump_raidtab_array(array);
    }
}  /* dump_raidtab() */


/*
 * The link_arrays(), link_array(), link_array_online(), and
 * link_array_offline() functions handle matching disks with their
 * arrays.
 *
 * The easy case: The md device is online.
 * o The array must have array->info.
 * o The disks must have disk->info.
 * o Scan ctxt->disks.  Every disk that is free, has disk->info, and 
 *   matches disk->sb.md_minor with array->minor gets selected.  Set
 *   array->uuid* from the first disk and verify they all match.
 *
 * The harder case: The md device is offline.
 * o First pass, scan all disks that are free and have disk->sb.md_minor
 *   matching array->minor.  The disk with the most recent
 *   disk->sb.utime is authoritative for the array.  Add it to the array
 *   and set array->uuid* from it.
 * o Second pass.  For each disk in the authoritative disk's sb.disks
 *   array, scan all disks that are free.  If a disk matches major,
 *   minor, md_minor, and uuid*, add it to the array.  Note the
 *   lack of efficiency.  Fix it.
 */

static void link_array_online(LSRContext *ctxt, LSRArray *array)
{
    LSRDisk *disk;
    struct list_head *pos;

    list_for_each(pos, &ctxt->disks)
    {
        disk = list_entry(pos, LSRDisk, d_list);

        if (disk->array) /* Used */
            continue;
        if (!disk->info.major) /* No disk->info, hence offline */
            continue;
        if (disk->sb.md_minor != array->minor) /* Not our array */
            continue;

        if (array->uuid0 || array->uuid1 ||
            array->uuid2 || array->uuid3)
        {
            if ((array->uuid0 != disk->sb.set_uuid0) ||
                (array->uuid1 != disk->sb.set_uuid1) ||
                (array->uuid2 != disk->sb.set_uuid2) ||
                (array->uuid3 != disk->sb.set_uuid3))
            {
                /* FIXME: Should it bleat here? */
                continue;
            }
        }
        else
        {
            array->uuid0 = disk->sb.set_uuid0;
            array->uuid1 = disk->sb.set_uuid1;
            array->uuid2 = disk->sb.set_uuid2;
            array->uuid3 = disk->sb.set_uuid3;
        }
        disk->array = array;
        list_add_tail(&disk->d_array, &array->disks);
    }
}  /* link_array_online() */


static void link_array_offline(LSRContext *ctxt, LSRArray *array)
{
    int i, numdisks;
    LSRDisk *disk, *a_disk;
    struct list_head *pos;

    a_disk = NULL;
    list_for_each(pos, &ctxt->disks)
    {
        disk = list_entry(pos, LSRDisk, d_list);
        if (disk->array) /* Used */
            continue;
        if (disk->sb.md_minor != array->minor) /* Not our array */
            continue;

        if (!a_disk || (disk->sb.utime > a_disk->sb.utime))
            a_disk = disk;
    }

    if (!a_disk)
        return;

    a_disk->array = array;
    list_add_tail(&a_disk->d_array, &array->disks);
    array->uuid0 = a_disk->sb.set_uuid0;
    array->uuid1 = a_disk->sb.set_uuid1;
    array->uuid2 = a_disk->sb.set_uuid2;
    array->uuid3 = a_disk->sb.set_uuid3;

    /* Here comes the inefficiency */
    for (i = 0, numdisks = 0;
         (numdisks < a_disk->sb.nr_disks) &&
         (i < (a_disk->sb.raid_disks + a_disk->sb.spare_disks)) &&
         (i < MD_SB_DISKS);
         i++)
    {
        if (i == a_disk->sb.this_disk.number) /* self */
        {
            numdisks++;
            continue;
        }
        list_for_each(pos, &ctxt->disks)
        {
            disk = list_entry(pos, LSRDisk, d_list);
            if (disk->array) /* Used */
                continue;
            if (disk->sb.md_minor != array->minor)
                continue;
            if ((disk->major != a_disk->sb.disks[i].major) ||
                (disk->minor != a_disk->sb.disks[i].minor))
                continue;
            if ((disk->sb.set_uuid0 != array->uuid0) ||
                (disk->sb.set_uuid1 != array->uuid1) ||
                (disk->sb.set_uuid2 != array->uuid2) ||
                (disk->sb.set_uuid3 != array->uuid3))
            {
                /* FIXME: Bleat here? */
                continue;
            }

            /*
             * If the authoritative disk has a difference of opinion,
             * tell the current disk to take it and like it.
             */
            if (memcmp(&disk->sb.this_disk, &a_disk->sb.disks[i],
                       sizeof(mdp_disk_t)))
                disk->sb.this_disk = a_disk->sb.disks[i];

            disk->array = array;
            list_add_tail(&disk->d_array, &array->disks);
            numdisks++;
        }
    }
}  /* link_array_offline() */


static void link_array(LSRContext *ctxt, LSRArray *array)
{
    if (array->info.major_version || array->info.minor_version)
        link_array_online(ctxt, array);
    else
        link_array_offline(ctxt, array);
}  /* link_array() */


static void link_arrays(LSRContext *ctxt)
{
    LSRArray *array;
    struct list_head *pos;

    list_for_each(pos, &ctxt->arrays)
    {
        array = list_entry(pos, LSRArray, a_list);
        link_array(ctxt, array);
    }
}  /* link_arrays() */


#if SCAN_DEBUG
/*
 * SCAN_DEBUG code is a set of state dumps to verify the algorithms used
 * for collecting the devices queried.
 */
static void dump_array_list(LSRContext *ctxt)
{
    LSRNamEnt *ent;
    LSRArray *array;
    struct list_head *pos;

    fprintf(stderr, "*** Passed arrays: ");
    if (list_empty(&ctxt->o_arrays))
        fprintf(stderr, "None");
    list_for_each(pos, &ctxt->o_arrays)
    {
        ent = list_entry(pos, LSRNamEnt, list);
        fprintf(stderr, "%s ", ent->name);
    }
    fprintf(stderr, "\n");
    
    fprintf(stderr, "*** Arrays collected:\n");
    if (list_empty(&ctxt->arrays))
        fprintf(stderr, "None\n");
    list_for_each(pos, &ctxt->arrays)
    {
        array = list_entry(pos, LSRArray, a_list);
        fprintf(stderr, "*** [dev % 3d, % 3d] %-16s\n",
                MD_MAJOR, array->minor,
                array->name ? array->name : "(unknown)");
    }
}  /* dump_array_list() */

static void dump_disk_list(LSRContext *ctxt)
{
    LSRNamEnt *ent;
    LSRDisk *disk;
    struct list_head *pos;

    fprintf(stderr, "*** Passed disks: ");
    if (list_empty(&ctxt->o_disks))
        fprintf(stderr, "None");
    list_for_each(pos, &ctxt->o_disks)
    {
        ent = list_entry(pos, LSRNamEnt, list);
        fprintf(stderr, "%s ", ent->name);
    }
    fprintf(stderr, "\n");
    
    fprintf(stderr, "*** Disks collected:\n");
    if (list_empty(&ctxt->disks))
        fprintf(stderr, "None\n");
    list_for_each(pos, &ctxt->disks)
    {
        disk = list_entry(pos, LSRDisk, d_list);
        fprintf(stderr, "*** [dev % 3d, % 3d] %-16s\n",
                disk->major, disk->minor,
                disk->name ? disk->name : "(unknown)");
    }
}  /* dump_disk_list() */

static void dump_array_links(LSRContext *ctxt)
{
    LSRArray *array;
    LSRDisk *disk;
    struct list_head *apos, *dpos;

    fprintf(stderr, "*** Array links:\n");
    if (list_empty(&ctxt->arrays))
    {
        fprintf(stderr, "None\n");
        return;
    }
    list_for_each(apos, &ctxt->arrays)
    {
        array = list_entry(apos, LSRArray, a_list);
        fprintf(stderr,
                "*** [dev % 3d, % 3d] %-16s %08X.%08X.%08X.%08X\n",
                MD_MAJOR, array->minor,
                array->name ? array->name : "(unknown)",
                array->uuid0, array->uuid1, array->uuid2, array->uuid3);

        if (list_empty(&array->disks))
            fprintf(stderr, "None\n");
        list_for_each(dpos, &array->disks)
        {
            disk = list_entry(dpos, LSRDisk, d_array);
            fprintf(stderr,
                    "***  [dev % 3d, % 3d] %-16s %08X.%08X.%08X.%08X\n",
                    disk->major, disk->minor,
                    disk->name ? disk->name : "(unknown)",
                    disk->sb.set_uuid0, disk->sb.set_uuid1,
                    disk->sb.set_uuid2, disk->sb.set_uuid3);
        }
    }
}  /* dump_array_links() */
#endif /* SCAN_DEBUG */

/* 
 * Main program
 */

int main(int argc, char *argv[])
{
    int i;
    LSRContext *ctxt;

    ctxt = alloc_context();
    if (!ctxt)
    {
        i = -errno;
        fprintf(stderr, "lsraid: Unable to allocate memory: %s\n",
                strerror(errno));
        return(i);
    }

    parse_options(ctxt, argc, argv);

    if (ctxt->flags & LSR_DEV_SCAN)
        load_partitions(ctxt);

    scan_disks(ctxt);
    scan_arrays(ctxt);
    link_arrays(ctxt);

#if SCAN_DEBUG
    /* Dump query lists */
    dump_array_list(ctxt);
    dump_disk_list(ctxt);
    dump_array_links(ctxt);
#endif  /* SCAN_DEBUG */

    if (ctxt->action == LSR_DISK)
        dump_disks(ctxt);
    else if (ctxt->action == LSR_ARRAY)
        dump_arrays(ctxt);
    else if (ctxt->action == LSR_RAIDTAB)
        dump_raidtab(ctxt);

    clean_context(ctxt);
    return(0);
}  /* main() */
