#include <errno.h>
#include <fcntl.h>
#include <limits.h>
+#include <dirent.h>
#include <grub/util/misc.h>
#include <grub/util/deviceiter.h>
}
#endif
-/* Check if DEVICE can be read. If an error occurs, return zero,
- otherwise return non-zero. */
+static struct seen_device
+{
+ struct seen_device *next;
+ const char *name;
+} *seen;
+
+/* Check if DEVICE can be read. Skip any DEVICE that we have already seen.
+ If an error occurs, return zero, otherwise return non-zero. */
static int
-check_device (const char *device)
+check_device_readable_unique (const char *device)
{
+ char *real_device;
char buf[512];
FILE *fp;
+ struct seen_device *seen_elt;
/* If DEVICE is empty, just return error. */
if (*device == 0)
return 0;
+ /* Have we seen this device already? */
+ real_device = canonicalize_file_name (device);
+ if (! real_device)
+ return 0;
+ if (grub_named_list_find (GRUB_AS_NAMED_LIST (seen), real_device))
+ {
+ grub_dprintf ("deviceiter", "Already seen %s (%s)\n",
+ device, real_device);
+ goto fail;
+ }
+
fp = fopen (device, "r");
if (! fp)
{
break;
}
/* Error opening the device. */
- return 0;
+ goto fail;
}
/* Make sure CD-ROMs don't get assigned a BIOS disk number
#ifdef __linux__
# ifdef CDROM_GET_CAPABILITY
if (ioctl (fileno (fp), CDROM_GET_CAPABILITY, 0) >= 0)
- return 0;
+ goto fail;
# else /* ! CDROM_GET_CAPABILITY */
/* Check if DEVICE is a CD-ROM drive by the HDIO_GETGEO ioctl. */
{
struct stat st;
if (fstat (fileno (fp), &st))
- return 0;
+ goto fail;
/* If it is a block device and isn't a floppy, check if HDIO_GETGEO
succeeds. */
if (S_ISBLK (st.st_mode)
&& MAJOR (st.st_rdev) != FLOPPY_MAJOR
&& ioctl (fileno (fp), HDIO_GETGEO, &hdg))
- return 0;
+ goto fail;
}
# endif /* ! CDROM_GET_CAPABILITY */
#endif /* __linux__ */
#if defined(__FreeBSD_kernel__) || defined(__NetBSD__) || defined(__OpenBSD__)
# ifdef CDIOCCLRDEBUG
if (ioctl (fileno (fp), CDIOCCLRDEBUG, 0) >= 0)
- return 0;
+ goto fail;
# endif /* CDIOCCLRDEBUG */
#endif /* __FreeBSD_kernel__ || __NetBSD__ || __OpenBSD__ */
if (fread (buf, 1, 512, fp) != 512)
{
fclose (fp);
- return 0;
+ goto fail;
}
+ /* Remember that we've seen this device. */
+ seen_elt = xmalloc (sizeof (*seen_elt));
+ seen_elt->name = real_device; /* steal memory */
+ grub_list_push (GRUB_AS_LIST_P (&seen), GRUB_AS_LIST (seen_elt));
+
fclose (fp);
return 1;
+
+fail:
+ free (real_device);
+ return 0;
+}
+
+static void
+clear_seen_devices (void)
+{
+ while (seen)
+ {
+ struct seen_device *seen_elt = seen;
+ seen = seen->next;
+ free (seen_elt);
+ }
+ seen = NULL;
}
#ifdef __linux__
-# ifdef HAVE_DEVICE_MAPPER
-struct dmraid_seen
+/* Like strcmp, but doesn't require a cast for use as a qsort comparator. */
+static int
+compare_file_names (const void *a, const void *b)
{
- struct dmraid_seen *next;
- const char *name;
-};
-# endif /* HAVE_DEVICE_MAPPER */
+ const char *left = *(const char **) a;
+ const char *right = *(const char **) b;
+ return strcmp (left, right);
+}
#endif /* __linux__ */
void
{
int i;
+ clear_seen_devices ();
+
/* Floppies. */
for (i = 0; i < floppy_disks; i++)
{
get_floppy_disk_name (name, i);
if (stat (name, &st) < 0)
break;
- /* In floppies, write the map, whether check_device succeeds
- or not, because the user just may not insert floppies. */
+ /* In floppies, write the map, whether check_device_readable_unique
+ succeeds or not, because the user just may not insert floppies. */
if (hook (name, 1))
- return;
+ goto out;
}
#ifdef __linux__
+ {
+ DIR *dir = opendir ("/dev/disk/by-id");
+
+ if (dir)
+ {
+ struct dirent *entry;
+ char **names;
+ size_t names_len = 0, names_max = 1024, i;
+
+ names = xmalloc (names_max * sizeof (*names));
+
+ /* Dump all the directory entries into names, resizing if
+ necessary. */
+ for (entry = readdir (dir); entry; entry = readdir (dir))
+ {
+ /* Skip partition entries. */
+ if (strstr (entry->d_name, "-part"))
+ continue;
+ if (names_len >= names_max)
+ {
+ names_max *= 2;
+ names = xrealloc (names, names_max * sizeof (*names));
+ }
+ names[names_len++] = xasprintf (entry->d_name);
+ }
+
+ /* /dev/disk/by-id/ usually has a few alternative identifications of
+ devices (e.g. ATA vs. SATA). check_device_readable_unique will
+ ensure that we only get one for any given disk, but sort the list
+ so that the choice of which one we get is stable. */
+ qsort (names, names_len, sizeof (*names), &compare_file_names);
+
+ closedir (dir);
+
+ /* Now add all the devices in sorted order. */
+ for (i = 0; i < names_len; ++i)
+ {
+ char *path = xasprintf ("/dev/disk/by-id/%s", names[i]);
+ if (check_device_readable_unique (path))
+ {
+ if (hook (path, 0))
+ goto out;
+ }
+ free (path);
+ free (names[i]);
+ }
+ free (names);
+ }
+ }
+
if (have_devfs ())
{
i = 0;
{
strcat (name, "/disc");
if (hook (name, 0))
- return;
+ goto out;
}
}
- return;
+ goto out;
}
#endif /* __linux__ */
char name[16];
get_ide_disk_name (name, i);
- if (check_device (name))
+ if (check_device_readable_unique (name))
{
if (hook (name, 0))
- return;
+ goto out;
}
}
char name[16];
get_virtio_disk_name (name, i);
- if (check_device (name))
+ if (check_device_readable_unique (name))
{
if (hook (name, 0))
- return;
+ goto out;
}
}
char name[20];
get_ataraid_disk_name (name, i);
- if (check_device (name))
+ if (check_device_readable_unique (name))
{
if (hook (name, 0))
- return;
+ goto out;
}
}
char name[16];
get_xvd_disk_name (name, i);
- if (check_device (name))
+ if (check_device_readable_unique (name))
{
if (hook (name, 0))
- return;
+ goto out;
}
}
#endif /* __linux__ */
char name[16];
get_scsi_disk_name (name, i);
- if (check_device (name))
+ if (check_device_readable_unique (name))
{
if (hook (name, 0))
- return;
+ goto out;
}
}
char name[24];
get_dac960_disk_name (name, controller, drive);
- if (check_device (name))
+ if (check_device_readable_unique (name))
{
if (hook (name, 0))
- return;
+ goto out;
}
}
}
char name[24];
get_acceleraid_disk_name (name, controller, drive);
- if (check_device (name))
+ if (check_device_readable_unique (name))
{
if (hook (name, 0))
- return;
+ goto out;
}
}
}
char name[24];
get_cciss_disk_name (name, controller, drive);
- if (check_device (name))
+ if (check_device_readable_unique (name))
{
if (hook (name, 0))
- return;
+ goto out;
}
}
}
char name[24];
get_ida_disk_name (name, controller, drive);
- if (check_device (name))
+ if (check_device_readable_unique (name))
{
if (hook (name, 0))
- return;
+ goto out;
}
}
}
char name[24];
get_i2o_disk_name (name, unit);
- if (check_device (name))
+ if (check_device_readable_unique (name))
{
if (hook (name, 0))
- return;
+ goto out;
}
}
}
char name[16];
get_mmc_disk_name (name, i);
- if (check_device (name))
+ if (check_device_readable_unique (name))
{
if (hook (name, 0))
- return;
+ goto out;
}
}
unsigned int next = 0;
void *top_handle, *second_handle;
struct dm_tree_node *root, *top, *second;
- struct dmraid_seen *seen = NULL;
/* Build DM tree for all devices. */
tree = dm_tree_create ();
{
const char *node_name, *node_uuid;
char *name;
- struct dmraid_seen *seen_elt;
node_name = dm_tree_node_get_name (second);
dmraid_check (node_name, "dm_tree_node_get_name failed\n");
goto dmraid_next_child;
}
- /* Have we already seen this node? There are typically very few
- DM-RAID disks, so a list should be fast enough. */
- if (grub_named_list_find (GRUB_AS_NAMED_LIST (seen), node_name))
- {
- grub_dprintf ("deviceiter", "Already seen DM device %s\n",
- node_name);
- goto dmraid_next_child;
- }
-
name = xasprintf ("/dev/mapper/%s", node_name);
- if (check_device (name))
+ if (check_device_readable_unique (name))
{
if (hook (name, 0))
{
free (name);
- while (seen)
- {
- struct dmraid_seen *seen_elt = seen;
- seen = seen->next;
- free (seen_elt);
- }
if (task)
dm_task_destroy (task);
if (tree)
dm_tree_free (tree);
- return;
+ goto out;
}
}
free (name);
- seen_elt = xmalloc (sizeof *seen_elt);
- seen_elt->name = node_name;
- grub_list_push (GRUB_AS_LIST_P (&seen), GRUB_AS_LIST (seen_elt));
-
dmraid_next_child:
second = dm_tree_next_child (&second_handle, top, 1);
}
}
dmraid_end:
- while (seen)
- {
- struct dmraid_seen *seen_elt = seen;
- seen = seen->next;
- free (seen_elt);
- }
if (task)
dm_task_destroy (task);
if (tree)
}
# endif /* HAVE_DEVICE_MAPPER */
#endif /* __linux__ */
+
+out:
+ clear_seen_devices ();
}