diff options
Diffstat (limited to 'extras/cdsymlinks.c')
-rw-r--r-- | extras/cdsymlinks.c | 532 |
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; +} |