]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
[PATCH] add very nice cdsymlinks scripts.
authorgreg@kroah.com <greg@kroah.com>
Sat, 16 Oct 2004 02:19:53 +0000 (19:19 -0700)
committerGreg KH <gregkh@suse.de>
Wed, 27 Apr 2005 05:02:45 +0000 (22:02 -0700)
These are from Darren Salt <linux@youmustbejoking.demon.co.uk>

extras/cdsymlinks.c [new file with mode: 0644]
extras/cdsymlinks.conf [new file with mode: 0644]
extras/cdsymlinks.sh [new file with mode: 0644]

diff --git a/extras/cdsymlinks.c b/extras/cdsymlinks.c
new file mode 100644 (file)
index 0000000..2889ff4
--- /dev/null
@@ -0,0 +1,532 @@
+/* cdsymlinks.c
+ *
+ * Map cdrom, cd-r, cdrw, dvd, dvdrw, dvdram to suitable devices.
+ * Prefers cd* for DVD-incapable and cdrom and dvd for read-only devices.
+ * First parameter is the kernel device name.
+ * Second parameter, if present, must be "-d" => output the full mapping.
+ *
+ * Usage:
+ * BUS="ide", KERNEL="hd[a-z]", PROGRAM="/etc/udev/cdsymlinks.sh %k", SYMLINK="%c{1} %c{2} %c{3} %c{4} %c{5} %c{6}"
+ * BUS="scsi", KERNEL="sr[0-9]*", PROGRAM="/etc/udev/cdsymlinks.sh %k", SYMLINK="%c{1} %c{2} %c{3} %c{4} %c{5} %c{6}"
+ * BUS="scsi", KERNEL="scd[0-9]*", PROGRAM="/etc/udev/cdsymlinks.sh %k", SYMLINK="%c{1} %c{2} %c{3} %c{4} %c{5} %c{6}"
+ * (this last one is "just in case")
+ *
+ * (c) 2004 Darren Salt <linux@youmustbejoking.demon.co.uk>
+ */
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include <strings.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+#include <unistd.h>
+
+#include <wordexp.h>
+
+static const char *progname;
+
+/* This file provides us with our devices and capabilities information. */
+#define CDROM_INFO "/proc/sys/dev/cdrom/info"
+
+/* This file contains our default settings. */
+#define CONFIGURATION "/etc/udev/cdsymlinks.conf"
+/* Default output types configuration, in the presence of an empty list */
+#define OUTPUT_DEFAULT "CD CDRW DVD DVDRW DVDRAM"
+
+static int debug = 0;
+
+/* List item */
+struct list_item_t {
+  struct list_item_t *next;
+  char *data;
+};
+
+/* List root. Note offset of list_t->head and list_item_t->next */
+struct list_t {
+  struct list_item_t *head, *tail;
+};
+
+/* Configuration variables */
+static struct list_t allowed_output = {0};
+static int numbered_links = 1;
+
+/* Available devices */
+static struct list_t Devices = {0};
+
+/* Devices' capabilities in full (same order as available devices list).
+ * There's no cap_CD; all are assumed able to read CDs.
+ */
+static struct list_t cap_DVDRAM = {0}, cap_DVDRW = {0}, cap_DVD = {0},
+                    cap_CDRW = {0}, cap_CDR = {0}, cap_CDWMRW = {0},
+                    cap_CDMRW = {0};
+
+/* Device capabilities by name */
+static struct list_t dev_DVDRAM = {0}, dev_DVDRW = {0}, dev_DVD = {0},
+                    dev_CDRW = {0}, dev_CDR = {0}, dev_CDWMRW = {0},
+                    dev_CDMRW = {0};
+#define dev_CD Devices
+
+
+/*
+ * Some library-like bits first...
+ */
+
+static void
+errexit (const char *reason)
+{
+  fprintf (stderr, "%s: %s: %s\n", progname, reason, strerror (errno));
+  exit (2);
+}
+
+
+static void
+msgexit (const char *reason)
+{
+  fprintf (stderr, "%s: %s\n", progname, reason);
+  exit (2);
+}
+
+
+static void
+errwarn (const char *reason)
+{
+  fprintf (stderr, "%s: warning: %s: %s\n", progname, reason, strerror (errno));
+}
+
+
+static void
+msgwarn (const char *reason)
+{
+  fprintf (stderr, "%s: warning: %s\n", progname, reason);
+}
+
+
+static void *
+xmalloc (size_t size)
+{
+  void *mem = malloc (size);
+  if (size && !mem)
+    msgexit ("malloc failed");
+  return mem;
+}
+
+
+static char *
+xstrdup (const char *text)
+{
+  char *mem = xmalloc (strlen (text) + 1);
+  return strcpy (mem, text);
+}
+
+
+/* Append a string to a list. The string is duplicated. */
+static void
+list_append (struct list_t *list, const char *data)
+{
+  struct list_item_t *node = xmalloc (sizeof (struct list_item_t));
+  node->next = NULL;
+  if (list->tail)
+    list->tail->next = node;
+  list->tail = node;
+  if (!list->head)
+    list->head = node;
+  node->data = xstrdup (data);
+}
+
+
+/* Prepend a string to a list. The string is duplicated. */
+static void
+list_prepend (struct list_t *list, const char *data)
+{
+  struct list_item_t *node = xmalloc (sizeof (struct list_item_t));
+  node->next = list->head;
+  list->head = node;
+  if (!list->tail)
+    list->tail = node;
+  node->data = xstrdup (data);
+}
+
+
+/* Delete a lists's contents, freeing claimed memory */
+static void
+list_delete (struct list_t *list)
+{
+  struct list_item_t *node = list->head;
+  while (node)
+  {
+    struct list_item_t *n = node;
+    node = node->next;
+    free (n->data);
+    free (n);
+  }
+  list->tail = list->head = NULL;
+}
+
+
+/* Print out a list on one line, each item space-prefixed, no LF */
+static void
+list_print (const struct list_t *list, FILE *stream)
+{
+  const struct list_item_t *node = (const struct list_item_t *)list;
+  while ((node = node->next) != NULL)
+    fprintf (stream, " %s", node->data);
+}
+
+
+/* Return the nth item in a list (count from 0)
+ * If there aren't enough items in the list, return the requested default
+ */
+static const struct list_item_t *
+list_nth (const struct list_t *list, size_t nth)
+{
+  const struct list_item_t *node = list->head;
+  while (nth && node)
+  {
+    node = node->next;
+    --nth;
+  }
+  return node;
+}
+
+
+/* Return the first matching item in a list, or NULL */
+static const struct list_item_t *
+list_search (const struct list_t *list, const char *data)
+{
+  const struct list_item_t *node = list->head;
+  while (node)
+  {
+    if (!strcmp (node->data, data))
+      return node;
+    node = node->next;
+  }
+  return NULL;
+}
+
+
+/* Split up a string on whitespace & assign the resulting tokens to a list.
+ * Ignore everything up until the first colon (if present).
+ */
+static void
+list_assign_split (struct list_t *list, char *text)
+{
+  char *token = strchr (text, ':');
+  token = strtok (token ? token + 1 : text, " \t");
+  while (token)
+  {
+    list_prepend (list, token);
+    token = strtok (0, " \t\n");
+  }
+}
+
+
+
+/* Gather the default settings. */
+static void
+read_defaults (void)
+{
+  FILE *conf = fopen (CONFIGURATION, "r");
+  if (!conf)
+  {
+    if (errno != ENOENT)
+      errwarn ("error accessing configuration");
+  }
+  else
+  {
+    char *text = NULL;
+    size_t textlen;
+    while (getline (&text, &textlen, conf) != -1)
+    {
+      wordexp_t p = {0};
+      int len = strlen (text);
+      if (len && text[len - 1] == '\n')
+       text[--len] = '\0';
+      if (len && text[len - 1] == '\r')
+       text[--len] = '\0';
+      if (!len)
+       continue;
+      char *token = text + strspn (text, " \t");
+      if (!*token || *token == '#')
+       continue;
+      switch (len = wordexp (text, &p, 0))
+      {
+      case WRDE_NOSPACE:
+       msgexit ("malloc failed");
+      case 0:
+       if (p.we_wordc == 1)
+       {
+         if (!strncmp (p.we_wordv[0], "OUTPUT=", 7))
+          {
+            list_delete (&allowed_output);
+            list_assign_split (&allowed_output, p.we_wordv[0] + 7);
+          }
+          else if (!strncmp (p.we_wordv[0], "NUMBERED_LINKS=", 14))
+            numbered_links = atoi (p.we_wordv[0] + 14);
+          break;
+       }
+       /* fall through */
+      default:
+       msgwarn ("syntax error in configuration file");
+      }
+      wordfree (&p);
+    }
+    if (!feof (conf))
+      errwarn ("error accessing configuration");
+    if (fclose (conf))
+      errwarn ("error accessing configuration");
+    free (text);
+  }
+  if (!allowed_output.head)
+  {
+    char *dflt = strdup (OUTPUT_DEFAULT);
+    list_assign_split (&allowed_output, dflt);
+    free (dflt);
+  }
+}
+
+
+/* From information supplied by the kernel:
+ *  + get the names of the available devices
+ *  + populate our capability lists
+ * Order is significant: device item N maps to each capability item N.
+ */
+static void
+populate_capability_lists (void)
+{
+  FILE *info = fopen (CDROM_INFO, "r");
+  if (!info)
+  {
+    if (errno == ENOENT)
+      exit (0);
+    errexit ("error accessing CD/DVD info");
+  }
+
+  char *text = 0;
+  size_t textlen = 0;
+
+  while (getline (&text, &textlen, info) != -1)
+  {
+    if (!strncasecmp (text, "drive name", 10))
+      list_assign_split (&Devices, text);
+    else if (!strncasecmp (text, "Can write DVD-RAM", 17))
+      list_assign_split (&cap_DVDRAM, text);
+    else if (!strncasecmp (text, "Can write DVD-R", 15))
+      list_assign_split (&cap_DVDRW, text);
+    else if (!strncasecmp (text, "Can read DVD", 12))
+      list_assign_split (&cap_DVD, text);
+    else if (!strncasecmp (text, "Can write CD-RW", 15))
+      list_assign_split (&cap_CDRW, text);
+    else if (!strncasecmp (text, "Can write CD-R", 14))
+      list_assign_split (&cap_CDR, text);
+    else if (!strncasecmp (text, "Can read MRW", 14))
+      list_assign_split (&cap_CDMRW, text);
+    else if (!strncasecmp (text, "Can write MRW", 14))
+      list_assign_split (&cap_CDWMRW, text);
+  }
+  if (!feof (info))
+    errexit ("error accessing CD/DVD info");
+  fclose (info);
+  free (text);
+}
+
+
+/* Write out the links of type LINK which should be created for device NAME,
+ * taking into account existing links and the capability list for type LINK.
+ */
+static void
+do_output (const char *name, const char *link, const struct list_t *dev)
+{
+  const struct list_item_t *i = (const struct list_item_t *)dev;
+  if (!i->next)
+    return;
+
+  errno = 0;
+
+  size_t link_len = strlen (link);
+  DIR *dir = opendir ("/dev");
+  if (!dir)
+    errexit ("error reading /dev");
+
+  struct list_t devls = {0};   /* symlinks whose name matches LINK */
+  struct list_t devlinks = {0};        /* those symlinks' targets */
+  struct dirent *entry;
+  while ((entry = readdir (dir)) != NULL)
+  {
+    if (strncmp (entry->d_name, link, link_len))
+      continue; /* wrong name: ignore it */
+
+    /* The rest of the name must be null or consist entirely of digits. */
+    const char *p = entry->d_name + link_len - 1;
+    while (*++p)
+      if (!isdigit (*p))
+        break;
+    if (*p)
+      continue; /* wrong format - ignore */
+
+    /* Assume that it's a symlink and try to read its target. */
+    char buf[sizeof (entry->d_name)];
+    int r = readlink (entry->d_name, buf, sizeof (buf) - 1);
+    if (r < 0)
+    {
+      if (errno == EINVAL)
+        continue; /* not a symlink - ignore */
+      errexit ("error reading link in /dev");
+    }
+    /* We have the name and the target, so update our lists. */
+    buf[r] = 0;
+    list_append (&devls, entry->d_name);
+    list_append (&devlinks, buf);
+  }
+  if (errno)
+    errexit ("error reading /dev");
+  if (closedir (dir))
+    errexit ("error closing /dev");
+
+  /* Now we write our output... */
+  size_t count = 0;
+  while ((i = i->next) != NULL)
+  {
+    int isdev = !strcmp (name, i->data); /* current dev == target dev? */
+    int present = 0;
+    size_t li = -1;
+    const struct list_item_t *l = (const struct list_item_t *)&devlinks;
+
+    /* First, we look for existing symlinks to the target device. */
+    while (++li, (l = l->next) != NULL)
+    {
+      if (strcmp (l->data, i->data))
+        continue;
+      /* Existing symlink found - don't output a new one.
+       * If ISDEV, we output the name of the existing symlink.
+       */
+      present = 1;
+      if (isdev)
+        printf (" %s", list_nth (&devls, li)->data);
+    }
+
+    /* If we found no existing symlinks for the target device... */
+    if (!present)
+    {
+      char buf[256];
+      snprintf (buf, sizeof (buf), count ? "%s%d" : "%s", link, count);
+      /* Find the next available (not present) symlink name.
+       * We always need to do this for reasons of output consistency: if a
+       * symlink is created by udev as a result of use of this program, we
+       * DON'T want different output!
+       */
+      while (list_search (&devls, buf))
+        snprintf (buf, sizeof (buf), "%s%d", link, ++count);
+      /* If ISDEV, output it. */
+      if (isdev && (numbered_links || count == 0))
+        printf (" %s", buf);
+      /* If the link isn't in our "existing links" list, add it and increment
+       * our counter.
+       */
+      if (!list_search (&devls, buf))
+      {
+        list_append (&devls, buf);
+        ++count;
+      }
+    }
+  }
+
+  list_delete (&devls);
+  list_delete (&devlinks);
+}
+
+
+/* Populate a device list from a capabilities list. */
+static void
+populate_device_list (struct list_t *out, const struct list_t *caps)
+{
+  const struct list_item_t *cap, *dev;
+  cap = (const struct list_item_t *)caps;
+  dev = (const struct list_item_t *)&Devices;
+  while ((cap = cap->next) != NULL && (dev = dev->next) != NULL)
+    if (cap->data[0] != '0')
+      list_append (out, dev->data);
+}
+
+
+int
+main (int argc, char *argv[])
+{
+  progname = argv[0];
+  debug = argc > 2 && !strcmp (argv[2], "-d");
+
+  if (argc < 2 || argc > 2 + debug)
+    msgexit ("usage: cdsymlinks DEVICE [-d]");
+
+  if (chdir ("/dev"))
+    errexit ("can't chdir /dev");
+
+  read_defaults ();
+  populate_capability_lists ();
+
+  /* Construct the device lists from the capability lists. */
+  populate_device_list (&dev_DVDRAM, &cap_DVDRAM);
+  populate_device_list (&dev_DVDRW, &cap_DVDRW);
+  populate_device_list (&dev_DVD, &cap_DVD);
+  populate_device_list (&dev_CDRW, &cap_CDRW);
+  populate_device_list (&dev_CDR, &cap_CDR);
+  populate_device_list (&dev_CDWMRW, &cap_CDWMRW);
+  populate_device_list (&dev_CDMRW, &cap_CDMRW);
+  /* (All devices can read CDs.) */
+
+  if (debug)
+  {
+#define printdev(DEV) \
+       printf ("%-7s:", #DEV); \
+        list_print (&cap_##DEV, stdout); \
+        list_print (&dev_##DEV, stdout); \
+       puts ("");
+
+    printf ("Devices:");
+    const struct list_item_t *item = (const struct list_item_t *)&Devices;
+    while ((item = item->next) != NULL)
+      printf (" %s", item->data);
+    puts ("");
+
+    printdev (DVDRAM);
+    printdev (DVDRW);
+    printdev (DVD);
+    printdev (CDRW);
+    printdev (CDR);
+    printdev (CDWMRW);
+    printdev (CDMRW);
+
+    printf ("CDROM  : (all)");
+    item = (const struct list_item_t *)&dev_CD;
+    while ((item = item->next) != NULL)
+      printf (" %s", item->data);
+    puts ("");
+  }
+
+  /* Write the symlink names. */
+  if (list_search (&allowed_output, "CD"))
+    do_output (argv[1], "cdrom",  &dev_CD);
+  if (list_search (&allowed_output, "CDR"))
+    do_output (argv[1], "cd-r",   &dev_CDR);
+  if (list_search (&allowed_output, "CDRW"))
+    do_output (argv[1], "cdrw",   &dev_CDRW);
+  if (list_search (&allowed_output, "DVD"))
+    do_output (argv[1], "dvd",    &dev_DVD);
+  if (list_search (&allowed_output, "DVDRW"))
+    do_output (argv[1], "dvdrw",  &dev_DVDRW);
+  if (list_search (&allowed_output, "DVDRAM"))
+    do_output (argv[1], "dvdram", &dev_DVDRAM);
+  if (list_search (&allowed_output, "CDMRW"))
+    do_output (argv[1], "cdmrw",   &dev_CDMRW);
+  if (list_search (&allowed_output, "CDWMRW"))
+    do_output (argv[1], "cdwmrw",   &dev_CDWMRW);
+  puts ("");
+
+  return 0;
+}
diff --git a/extras/cdsymlinks.conf b/extras/cdsymlinks.conf
new file mode 100644 (file)
index 0000000..e50a2e6
--- /dev/null
@@ -0,0 +1,12 @@
+# Configuration file for cdsymlinks
+
+# Output links for these types of devices.
+# Allowed keywords are CD, CDR, CDRW, DVD, DVDRW, DVDRAM, CDMRW, CDWMRW.
+# Other words are accepted but ignored.
+#OUTPUT="CD CDRW DVD DVDRW DVDRAM"
+
+# Whether to output numbered links.
+# 1 = output 'cdrom1', 'dvd1' etc. for other devices
+# 0 = don't output 'cdrom1', 'dvd1' etc.
+# We always output 'cdrom', 'dvd' etc. for the best-match devices.
+#NUMBERED_LINKS=1
diff --git a/extras/cdsymlinks.sh b/extras/cdsymlinks.sh
new file mode 100644 (file)
index 0000000..e99be2e
--- /dev/null
@@ -0,0 +1,184 @@
+#! /bin/sh -e
+#
+# Map cdrom, cdm, cdmrw, cd-r, cdrw, dvd, dvdrw, dvdram to suitable devices.
+# Prefers cd* for DVD-incapable and cdrom and dvd for read-only devices.
+# First parameter is the kernel device name.
+# Second parameter, if present, must be "-d" => output the full mapping.
+#
+# Usage:
+# BUS="ide", KERNEL="hd[a-z]", PROGRAM="/etc/udev/cdsymlinks.sh %k", SYMLINK="%c{1} %c{2} %c{3} %c{4} %c{5} %c{6}"
+# BUS="scsi", KERNEL="sr[0-9]*", PROGRAM="/etc/udev/cdsymlinks.sh %k", SYMLINK="%c{1} %c{2} %c{3} %c{4} %c{5} %c{6}"
+# BUS="scsi", KERNEL="scd[0-9]*", PROGRAM="/etc/udev/cdsymlinks.sh %k", SYMLINK="%c{1} %c{2} %c{3} %c{4} %c{5} %c{6}"
+# (this last one is "just in case")
+#
+# (c) 2004 Darren Salt <linux@youmustbejoking.demon.co.uk>
+
+test -e /proc/sys/dev/cdrom/info || exit 0
+
+# Defaults; it's better that you alter them in /etc/udev/cdsymlinks.conf
+OUTPUT='CD CDRW DVD DVDRW DVDRAM'
+NUMBERED_LINKS=1
+
+test -e /etc/udev/cdsymlinks.conf && . /etc/udev/cdsymlinks.conf
+
+DEBUG=''
+test "$2" = '-d' && DEBUG=1
+
+
+# Array emulation. 'setArray FOO a b c' gives FOO=3 FOO_1=a FOO_2=b FOO_3=c
+setArray () {
+  local _ARRAY=$1
+  local _INDEX=0
+  shift
+  while test $# -ne 0; do
+    eval ${_ARRAY}_$_INDEX="$1"
+    _INDEX=$(($_INDEX+1))
+    shift
+  done
+  eval $_ARRAY=$_INDEX
+}
+
+ix () { eval echo -n \$$1_$2; }
+ixs () { eval $1_$2="$3"; }
+
+# Args: variable, value
+# Returns true if value is not in variable (a whitespace-separated list)
+notin () {
+  test "$2" = '' && return 0
+  local i
+  for i in `eval echo '$'$1`; do
+    test "$i" != "$2" || return 1
+  done
+}
+
+
+# Scan /proc/sys/dev/cdrom/info for details on the present devices
+setArray DEVICES `sed -re '/^drive name:/I!        d; s/.*://' /proc/sys/dev/cdrom/info`
+setArray DVDRAMs `sed -re '/^Can write DVD-RAM:/I! d; s/.*://' /proc/sys/dev/cdrom/info`
+setArray DVDRWs  `sed -re '/^Can write DVD-R:/I!   d; s/.*://' /proc/sys/dev/cdrom/info`
+setArray DVDs    `sed -re '/^Can read DVD:/I!      d; s/.*://' /proc/sys/dev/cdrom/info`
+setArray CDRWs   `sed -re '/^Can write CD-RW:/I!   d; s/.*://' /proc/sys/dev/cdrom/info`
+setArray CDRs    `sed -re '/^Can write CD-R:/I!    d; s/.*://' /proc/sys/dev/cdrom/info`
+setArray CDMRWs  `sed -re '/^Can write MRW:/I!     d; s/.*://' /proc/sys/dev/cdrom/info`
+setArray CDMs    `sed -re '/^Can read MRW:/I!      d; s/.*://' /proc/sys/dev/cdrom/info`
+
+# How many devices do we have?
+NumDevs=$(($DEVICES-1))
+Count=''
+i=-1
+while test $i -ne $NumDevs; do
+  i=$(($i+1));
+  Count="$i${Count:+ }$Count";
+done
+
+# Fill in any missing capabilities information (assume not capable)
+for i in $Count; do
+  test "`ix DVDRAMs $i`" != '' || ixs DVDRAMs $i 0
+  test "`ix DVDRWs $i`"  != '' || ixs DVDRWs $i 0
+  test "`ix DVDs $i`"    != '' || ixs DVDs $i 0
+  test "`ix CDRWs $i`"   != '' || ixs CDRWs $i 0
+  test "`ix CDRs $i`"    != '' || ixs CDRs $i 0
+  test "`ix CDMRWs $i`"  != '' || ixs CDMRWs $i 0
+  test "`ix CDMs $i`"    != '' || ixs CDMs $i 0
+done
+
+DVDRAM=''
+DVDRW=''
+DVD=''
+CDRW=''
+CDR=''
+CDMRW=''
+CDM=''
+CD=''
+
+# Calculate symlink->device mappings.
+# We need to be careful about how we assign mappings because we want
+# read-only devices to have /dev/cdrom and /dev/dvd.
+# Hot-plugged CD/DVD devices may well cause this scheme some problems.
+for i in $Count; do
+  test "`ix DVDRAMs $i`" = 1 &&        DVDRAM="$DVDRAM `ix DEVICES $i`"
+done
+for i in $Count; do
+  test "`ix DVDRWs $i`"        = 1 &&  DVDRW="$DVDRW `ix DEVICES $i`"
+done
+for i in $Count; do
+  test "`ix DVDs $i`" = 1 &&   DVD="$DVD `ix DEVICES $i`"
+done
+for i in $Count; do
+  test "`ix CDRWs $i`" = 1 &&  CDRW="$CDRW `ix DEVICES $i`"
+done
+for i in $Count; do
+  test "`ix CDRs $i`" = 1 &&   CDR="$CDR `ix DEVICES $i`"
+done
+for i in $Count; do
+  test "`ix CDMRWs $i`" = 1 && CDMRW="$CDMRW `ix DEVICES $i`"
+done
+for i in $Count; do
+  test "`ix CDMs $i`" = 1 &&   CDM="$CDM `ix DEVICES $i`"
+done
+for i in $Count; do
+                               CD="$CD `ix DEVICES $i`"
+done
+
+# Debug output
+if test "$DEBUG" = 1; then
+  echo 'Devices:' `for i in $Count; do ix DEVICES $i; echo -n \ ; done`
+  echo 'DVDRAM :' `for i in $Count; do ix DVDRAMs $i; echo -n \ ; done` $DVDRAM
+  echo 'DVDRW  :' `for i in $Count; do ix DVDRWs  $i; echo -n \ ; done` $DVDRW
+  echo 'DVD    :' `for i in $Count; do ix DVDs    $i; echo -n \ ; done` $DVD
+  echo 'CDRW   :' `for i in $Count; do ix CDRWs   $i; echo -n \ ; done` $CDRW
+  echo 'CD-R   :' `for i in $Count; do ix CDRs    $i; echo -n \ ; done` $CDR
+  echo 'CDMRW  :' `for i in $Count; do ix CDMRWs  $i; echo -n \ ; done` $CDMRW
+  echo 'CDM    :' `for i in $Count; do ix CDMs    $i; echo -n \ ; done` $CDM
+  echo 'CDROM  : (all)' $CD
+fi
+
+# Prepare symlink names output
+output () {
+  test "`eval echo '$'$3`" = '' && return
+  local i
+  local COUNT=''
+  local DEVLS="`ls -dl \"/dev/$2\" \"/dev/$2\"[0-9]* 2>/dev/null`"
+  local PRESENT="`echo "$DEVLS" |
+    sed -re 's!^.* /dev/('$2'[[:digit:]]*) -> [^[:space:]]+$!\1!'`"
+  for i in `eval echo '$'$3`; do
+    # First, we look for existing symlinks to the target device.
+    local DEVPRESENT="`echo "$DEVLS" |
+      sed -re 's!^.* /dev/('$2'[[:digit:]]*) -> '$i'$!\1!; t X; d; :X'`"
+    if test "$DEVPRESENT" != ""; then
+      # Existing symlinks found - don't output a new one.
+      # If the target dev ($1) is the current dev ($i), we output their names.
+      test "$1" = "$i" && echo " $DEVPRESENT" | sed -e 'N; $ s/\n/ /'
+    else
+      # If we found no existing symlinks for the target device...
+      # Find the next available (not present) symlink name.
+      # We always need to do this for reasons of output consistency: if a
+      # symlink is created by udev as a result of use of this program, we
+      # DON'T want different output!
+      until notin PRESENT "$2$COUNT"; do
+       COUNT=$(($COUNT+1))
+      done
+      # If the target dev ($1) is the current dev ($i), we output its name.
+      if test $(($NUMBERED_LINKS)) -ne 0 || test "$COUNT" -eq 0; then
+       test "$i" = "$1" && echo -n " $2$COUNT"
+      fi
+      # If the link isn't in our "existing links" list, add it and increment
+      # our counter.
+      if test ! -e "/dev/$2$COUNT"; then
+       PRESENT="$PRESENT\n$2$COUNT"
+       COUNT=$(($COUNT+1))
+      fi
+    fi
+  done
+}
+
+# And output it
+notin OUTPUT CD     || echo -n "`output "$1" cdrom CD`"
+notin OUTPUT CDMRW  || echo -n "`output "$1" cdmrw CDM`"
+notin OUTPUT CDWMRW || echo -n "`output "$1" cdwmrw CDMRW`"
+notin OUTPUT CDR    || echo -n "`output "$1" cd-r CDR`"
+notin OUTPUT CDRW   || echo -n "`output "$1" cdrw CDRW`"
+notin OUTPUT DVD    || echo -n "`output "$1" dvd DVD`"
+notin OUTPUT DVDRW  || echo -n "`output "$1" dvdrw DVDRW`"
+notin OUTPUT DVDRAM || echo -n "`output "$1" dvdram DVDRAM`"
+echo