summaryrefslogtreecommitdiff
path: root/extras/cdsymlinks.c
diff options
context:
space:
mode:
Diffstat (limited to 'extras/cdsymlinks.c')
-rw-r--r--extras/cdsymlinks.c532
1 files changed, 532 insertions, 0 deletions
diff --git a/extras/cdsymlinks.c b/extras/cdsymlinks.c
new file mode 100644
index 0000000000..2889ff446c
--- /dev/null
+++ b/extras/cdsymlinks.c
@@ -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;
+}