2004-10-15 19:19:53 -07:00
/* 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 " )
*
2005-05-19 22:24:38 +01:00
* ( c ) 2004 , 2005 Darren Salt < linux @ youmustbejoking . demon . co . uk >
*
* Contributors :
* - J A Magallon < jamagallon @ able . es > ( bug fixes )
*
* Last modified : 2005 - 02 - 15
2004-10-15 19:19:53 -07:00
*/
# 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 ;
2005-05-19 22:24:38 +01:00
static int link_zero = 0 ;
2004-10-15 19:19:53 -07:00
/* 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 } ,
2005-05-19 22:24:38 +01:00
cap_CDMRW = { 0 } , cap_CDRAM = { 0 } ;
2004-10-15 19:19:53 -07:00
/* 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 } ,
2005-05-19 22:24:38 +01:00
dev_CDMRW = { 0 } , dev_CDRAM = { 0 } ;
2004-10-15 19:19:53 -07:00
# define dev_CD Devices
2005-05-19 22:24:38 +01:00
typedef struct {
struct list_t * cap , * dev ;
const char label [ 8 ] , symlink [ 8 ] ;
const char * captext ;
int captextlen ;
} cap_dev_t ;
# define CAPDEV(X) &cap_##X, &dev_##X
static const cap_dev_t cap_dev_info [ ] = {
{ NULL , & dev_CD , " CD " , " cdrom " , NULL , 0 } ,
{ CAPDEV ( CDR ) , " CDR " , " cd-r " , " Can write CD-R: " , 15 } ,
{ CAPDEV ( CDRW ) , " CDRW " , " cdrw " , " Can write CD-RW: " , 16 } ,
{ CAPDEV ( DVD ) , " DVD " , " dvd " , " Can read DVD: " , 13 } ,
{ CAPDEV ( DVDRW ) , " DVDRW " , " dvdrw " , " Can write DVD-R: " , 16 } ,
{ CAPDEV ( DVDRAM ) , " DVDRAM " , " dvdram " , " Can write DVD-RAM: " , 18 } ,
{ CAPDEV ( CDMRW ) , " CDMRW " , " cdm " , " Can read MRW: " , 13 } , /* CDC-MRW R */
{ CAPDEV ( CDWMRW ) , " CDWMRW " , " cdmrw " , " Can write MRW: " , 14 } , /* CDC-MRW W */
{ CAPDEV ( CDRAM ) , " CDRAM " , " cdram " , " Can write RAM: " , 14 } , /* CDC-RAM W */
{ NULL }
} ;
# define foreach_cap_dev(loop) \
for ( ( loop ) = cap_dev_info ; ( loop ) - > label [ 0 ] ; + + ( loop ) )
# define foreach_cap_dev_noCD(loop) \
for ( ( loop ) = cap_dev_info + 1 ; ( loop ) - > label [ 0 ] ; + + ( loop ) )
2004-10-15 19:19:53 -07:00
/*
* 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 , ' : ' ) ;
2005-05-19 22:24:38 +01:00
token = strtok ( token ? token + 1 : text , " \t \n " ) ;
2004-10-15 19:19:53 -07:00
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 ) ;
}
2005-05-19 22:24:38 +01:00
else if ( ! strncmp ( p . we_wordv [ 0 ] , " NUMBERED_LINKS= " , 15 ) )
numbered_links = atoi ( p . we_wordv [ 0 ] + 15 ) ;
else if ( ! strncmp ( p . we_wordv [ 0 ] , " LINK_ZERO= " , 15 ) )
link_zero = atoi ( p . we_wordv [ 0 ] + 15 ) ;
2004-10-15 19:19:53 -07:00
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 ) ;
2005-05-19 22:24:38 +01:00
else
{
const cap_dev_t * cap ;
foreach_cap_dev_noCD ( cap )
if ( ! strncasecmp ( text , cap - > captext , cap - > captextlen ) )
{
list_assign_split ( cap - > cap , text ) ;
break ;
}
}
2004-10-15 19:19:53 -07:00
}
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
2005-05-19 22:24:38 +01:00
do_output ( const char * name , const char * link , const struct list_t * dev ,
int do_link_zero )
2004-10-15 19:19:53 -07:00
{
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 .
*/
2005-05-19 22:24:38 +01:00
if ( do_link_zero )
return ;
2004-10-15 19:19:53 -07:00
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 ] ;
2005-05-19 22:24:38 +01:00
snprintf ( buf , sizeof ( buf ) , count | | do_link_zero ? " %s%d " : " %s " ,
link , count ) ;
2004-10-15 19:19:53 -07:00
/* 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 ) )
2005-05-19 22:24:38 +01:00
{
if ( do_link_zero )
return ;
2004-10-15 19:19:53 -07:00
snprintf ( buf , sizeof ( buf ) , " %s%d " , link , + + count ) ;
2005-05-19 22:24:38 +01:00
}
2004-10-15 19:19:53 -07:00
/* 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 ) )
{
2005-05-19 22:24:38 +01:00
if ( do_link_zero )
return ;
2004-10-15 19:19:53 -07:00
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 [ ] )
{
2005-05-19 22:24:38 +01:00
const cap_dev_t * capdev ;
2004-10-15 19:19:53 -07:00
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 ( ) ;
2005-05-19 22:24:38 +01:00
/* Construct the device lists from the capability lists.
* ( We assume that all relevant devices can read CDs . )
*/
foreach_cap_dev_noCD ( capdev )
populate_device_list ( capdev - > dev , capdev - > cap ) ;
2004-10-15 19:19:53 -07:00
if ( debug )
{
printf ( " Devices: " ) ;
const struct list_item_t * item = ( const struct list_item_t * ) & Devices ;
while ( ( item = item - > next ) ! = NULL )
printf ( " %s " , item - > data ) ;
2005-05-19 22:24:38 +01:00
printf ( " \n CDROM : (all) " ) ;
2004-10-15 19:19:53 -07:00
item = ( const struct list_item_t * ) & dev_CD ;
while ( ( item = item - > next ) ! = NULL )
printf ( " %s " , item - > data ) ;
puts ( " " ) ;
2005-05-19 22:24:38 +01:00
foreach_cap_dev_noCD ( capdev )
{
printf ( " %-10s: " , capdev - > label ) ;
list_print ( capdev - > cap , stdout ) ;
list_print ( capdev - > dev , stdout ) ;
puts ( " " ) ;
}
2004-10-15 19:19:53 -07:00
}
/* Write the symlink names. */
2005-05-19 22:24:38 +01:00
foreach_cap_dev ( capdev )
if ( list_search ( & allowed_output , capdev - > label ) )
{
do_output ( argv [ 1 ] , capdev - > symlink , capdev - > dev , 0 ) ;
if ( link_zero )
do_output ( argv [ 1 ] , capdev - > symlink , capdev - > dev , 1 ) ;
}
2004-10-15 19:19:53 -07:00
puts ( " " ) ;
return 0 ;
}