From 79db6912cf8827bf4293d2d15b84cc9633dd7d4e Mon Sep 17 00:00:00 2001 From: "greg@kroah.com" Date: Fri, 15 Oct 2004 19:19:53 -0700 Subject: [PATCH] add very nice cdsymlinks scripts. These are from Darren Salt --- extras/cdsymlinks.c | 532 +++++++++++++++++++++++++++++++++++++++++++++++++ extras/cdsymlinks.conf | 12 ++ extras/cdsymlinks.sh | 184 +++++++++++++++++ 3 files changed, 728 insertions(+) create mode 100644 extras/cdsymlinks.c create mode 100644 extras/cdsymlinks.conf create mode 100644 extras/cdsymlinks.sh 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 + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +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 index 0000000000..e50a2e6082 --- /dev/null +++ b/extras/cdsymlinks.conf @@ -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 index 0000000000..e99be2e544 --- /dev/null +++ b/extras/cdsymlinks.sh @@ -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 + +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 -- cgit v1.2.3-54-g00ecf