chiark / gitweb /
[PATCH] gentoo fb permission fix.
[elogind.git] / extras / cdsymlinks.c
1 /* cdsymlinks.c
2  *
3  * Map cdrom, cd-r, cdrw, dvd, dvdrw, dvdram to suitable devices.
4  * Prefers cd* for DVD-incapable and cdrom and dvd for read-only devices.
5  * First parameter is the kernel device name.
6  * Second parameter, if present, must be "-d" => output the full mapping.
7  *
8  * Usage:
9  * 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}"
10  * 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}"
11  * 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}"
12  * (this last one is "just in case")
13  *
14  * (c) 2004 Darren Salt <linux@youmustbejoking.demon.co.uk>
15  */
16
17 #define _GNU_SOURCE
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <ctype.h>
23 #include <errno.h>
24
25 #include <strings.h>
26 #include <sys/types.h>
27 #include <dirent.h>
28
29 #include <unistd.h>
30
31 #include <wordexp.h>
32
33 static const char *progname;
34
35 /* This file provides us with our devices and capabilities information. */
36 #define CDROM_INFO "/proc/sys/dev/cdrom/info"
37
38 /* This file contains our default settings. */
39 #define CONFIGURATION "/etc/udev/cdsymlinks.conf"
40 /* Default output types configuration, in the presence of an empty list */
41 #define OUTPUT_DEFAULT "CD CDRW DVD DVDRW DVDRAM"
42
43 static int debug = 0;
44
45 /* List item */
46 struct list_item_t {
47   struct list_item_t *next;
48   char *data;
49 };
50
51 /* List root. Note offset of list_t->head and list_item_t->next */
52 struct list_t {
53   struct list_item_t *head, *tail;
54 };
55
56 /* Configuration variables */
57 static struct list_t allowed_output = {0};
58 static int numbered_links = 1;
59
60 /* Available devices */
61 static struct list_t Devices = {0};
62
63 /* Devices' capabilities in full (same order as available devices list).
64  * There's no cap_CD; all are assumed able to read CDs.
65  */
66 static struct list_t cap_DVDRAM = {0}, cap_DVDRW = {0}, cap_DVD = {0},
67                      cap_CDRW = {0}, cap_CDR = {0}, cap_CDWMRW = {0},
68                      cap_CDMRW = {0};
69
70 /* Device capabilities by name */
71 static struct list_t dev_DVDRAM = {0}, dev_DVDRW = {0}, dev_DVD = {0},
72                      dev_CDRW = {0}, dev_CDR = {0}, dev_CDWMRW = {0},
73                      dev_CDMRW = {0};
74 #define dev_CD Devices
75
76
77 /*
78  * Some library-like bits first...
79  */
80
81 static void
82 errexit (const char *reason)
83 {
84   fprintf (stderr, "%s: %s: %s\n", progname, reason, strerror (errno));
85   exit (2);
86 }
87
88
89 static void
90 msgexit (const char *reason)
91 {
92   fprintf (stderr, "%s: %s\n", progname, reason);
93   exit (2);
94 }
95
96
97 static void
98 errwarn (const char *reason)
99 {
100   fprintf (stderr, "%s: warning: %s: %s\n", progname, reason, strerror (errno));
101 }
102
103
104 static void
105 msgwarn (const char *reason)
106 {
107   fprintf (stderr, "%s: warning: %s\n", progname, reason);
108 }
109
110
111 static void *
112 xmalloc (size_t size)
113 {
114   void *mem = malloc (size);
115   if (size && !mem)
116     msgexit ("malloc failed");
117   return mem;
118 }
119
120
121 static char *
122 xstrdup (const char *text)
123 {
124   char *mem = xmalloc (strlen (text) + 1);
125   return strcpy (mem, text);
126 }
127
128
129 /* Append a string to a list. The string is duplicated. */
130 static void
131 list_append (struct list_t *list, const char *data)
132 {
133   struct list_item_t *node = xmalloc (sizeof (struct list_item_t));
134   node->next = NULL;
135   if (list->tail)
136     list->tail->next = node;
137   list->tail = node;
138   if (!list->head)
139     list->head = node;
140   node->data = xstrdup (data);
141 }
142
143
144 /* Prepend a string to a list. The string is duplicated. */
145 static void
146 list_prepend (struct list_t *list, const char *data)
147 {
148   struct list_item_t *node = xmalloc (sizeof (struct list_item_t));
149   node->next = list->head;
150   list->head = node;
151   if (!list->tail)
152     list->tail = node;
153   node->data = xstrdup (data);
154 }
155
156
157 /* Delete a lists's contents, freeing claimed memory */
158 static void
159 list_delete (struct list_t *list)
160 {
161   struct list_item_t *node = list->head;
162   while (node)
163   {
164     struct list_item_t *n = node;
165     node = node->next;
166     free (n->data);
167     free (n);
168   }
169   list->tail = list->head = NULL;
170 }
171
172
173 /* Print out a list on one line, each item space-prefixed, no LF */
174 static void
175 list_print (const struct list_t *list, FILE *stream)
176 {
177   const struct list_item_t *node = (const struct list_item_t *)list;
178   while ((node = node->next) != NULL)
179     fprintf (stream, " %s", node->data);
180 }
181
182
183 /* Return the nth item in a list (count from 0)
184  * If there aren't enough items in the list, return the requested default
185  */
186 static const struct list_item_t *
187 list_nth (const struct list_t *list, size_t nth)
188 {
189   const struct list_item_t *node = list->head;
190   while (nth && node)
191   {
192     node = node->next;
193     --nth;
194   }
195   return node;
196 }
197
198
199 /* Return the first matching item in a list, or NULL */
200 static const struct list_item_t *
201 list_search (const struct list_t *list, const char *data)
202 {
203   const struct list_item_t *node = list->head;
204   while (node)
205   {
206     if (!strcmp (node->data, data))
207       return node;
208     node = node->next;
209   }
210   return NULL;
211 }
212
213
214 /* Split up a string on whitespace & assign the resulting tokens to a list.
215  * Ignore everything up until the first colon (if present).
216  */
217 static void
218 list_assign_split (struct list_t *list, char *text)
219 {
220   char *token = strchr (text, ':');
221   token = strtok (token ? token + 1 : text, " \t");
222   while (token)
223   {
224     list_prepend (list, token);
225     token = strtok (0, " \t\n");
226   }
227 }
228
229
230
231 /* Gather the default settings. */
232 static void
233 read_defaults (void)
234 {
235   FILE *conf = fopen (CONFIGURATION, "r");
236   if (!conf)
237   {
238     if (errno != ENOENT)
239       errwarn ("error accessing configuration");
240   }
241   else
242   {
243     char *text = NULL;
244     size_t textlen;
245     while (getline (&text, &textlen, conf) != -1)
246     {
247       wordexp_t p = {0};
248       int len = strlen (text);
249       if (len && text[len - 1] == '\n')
250         text[--len] = '\0';
251       if (len && text[len - 1] == '\r')
252         text[--len] = '\0';
253       if (!len)
254         continue;
255       char *token = text + strspn (text, " \t");
256       if (!*token || *token == '#')
257         continue;
258       switch (len = wordexp (text, &p, 0))
259       {
260       case WRDE_NOSPACE:
261         msgexit ("malloc failed");
262       case 0:
263         if (p.we_wordc == 1)
264         {
265           if (!strncmp (p.we_wordv[0], "OUTPUT=", 7))
266           {
267             list_delete (&allowed_output);
268             list_assign_split (&allowed_output, p.we_wordv[0] + 7);
269           }
270           else if (!strncmp (p.we_wordv[0], "NUMBERED_LINKS=", 14))
271             numbered_links = atoi (p.we_wordv[0] + 14);
272           break;
273         }
274         /* fall through */
275       default:
276         msgwarn ("syntax error in configuration file");
277       }
278       wordfree (&p);
279     }
280     if (!feof (conf))
281       errwarn ("error accessing configuration");
282     if (fclose (conf))
283       errwarn ("error accessing configuration");
284     free (text);
285   }
286   if (!allowed_output.head)
287   {
288     char *dflt = strdup (OUTPUT_DEFAULT);
289     list_assign_split (&allowed_output, dflt);
290     free (dflt);
291   }
292 }
293
294
295 /* From information supplied by the kernel:
296  *  + get the names of the available devices
297  *  + populate our capability lists
298  * Order is significant: device item N maps to each capability item N.
299  */
300 static void
301 populate_capability_lists (void)
302 {
303   FILE *info = fopen (CDROM_INFO, "r");
304   if (!info)
305   {
306     if (errno == ENOENT)
307       exit (0);
308     errexit ("error accessing CD/DVD info");
309   }
310
311   char *text = 0;
312   size_t textlen = 0;
313
314   while (getline (&text, &textlen, info) != -1)
315   {
316     if (!strncasecmp (text, "drive name", 10))
317       list_assign_split (&Devices, text);
318     else if (!strncasecmp (text, "Can write DVD-RAM", 17))
319       list_assign_split (&cap_DVDRAM, text);
320     else if (!strncasecmp (text, "Can write DVD-R", 15))
321       list_assign_split (&cap_DVDRW, text);
322     else if (!strncasecmp (text, "Can read DVD", 12))
323       list_assign_split (&cap_DVD, text);
324     else if (!strncasecmp (text, "Can write CD-RW", 15))
325       list_assign_split (&cap_CDRW, text);
326     else if (!strncasecmp (text, "Can write CD-R", 14))
327       list_assign_split (&cap_CDR, text);
328     else if (!strncasecmp (text, "Can read MRW", 14))
329       list_assign_split (&cap_CDMRW, text);
330     else if (!strncasecmp (text, "Can write MRW", 14))
331       list_assign_split (&cap_CDWMRW, text);
332   }
333   if (!feof (info))
334     errexit ("error accessing CD/DVD info");
335   fclose (info);
336   free (text);
337 }
338
339
340 /* Write out the links of type LINK which should be created for device NAME,
341  * taking into account existing links and the capability list for type LINK.
342  */
343 static void
344 do_output (const char *name, const char *link, const struct list_t *dev)
345 {
346   const struct list_item_t *i = (const struct list_item_t *)dev;
347   if (!i->next)
348     return;
349
350   errno = 0;
351
352   size_t link_len = strlen (link);
353   DIR *dir = opendir ("/dev");
354   if (!dir)
355     errexit ("error reading /dev");
356
357   struct list_t devls = {0};    /* symlinks whose name matches LINK */
358   struct list_t devlinks = {0}; /* those symlinks' targets */
359   struct dirent *entry;
360   while ((entry = readdir (dir)) != NULL)
361   {
362     if (strncmp (entry->d_name, link, link_len))
363       continue; /* wrong name: ignore it */
364
365     /* The rest of the name must be null or consist entirely of digits. */
366     const char *p = entry->d_name + link_len - 1;
367     while (*++p)
368       if (!isdigit (*p))
369         break;
370     if (*p)
371       continue; /* wrong format - ignore */
372
373     /* Assume that it's a symlink and try to read its target. */
374     char buf[sizeof (entry->d_name)];
375     int r = readlink (entry->d_name, buf, sizeof (buf) - 1);
376     if (r < 0)
377     {
378       if (errno == EINVAL)
379         continue; /* not a symlink - ignore */
380       errexit ("error reading link in /dev");
381     }
382     /* We have the name and the target, so update our lists. */
383     buf[r] = 0;
384     list_append (&devls, entry->d_name);
385     list_append (&devlinks, buf);
386   }
387   if (errno)
388     errexit ("error reading /dev");
389   if (closedir (dir))
390     errexit ("error closing /dev");
391
392   /* Now we write our output... */
393   size_t count = 0;
394   while ((i = i->next) != NULL)
395   {
396     int isdev = !strcmp (name, i->data); /* current dev == target dev? */
397     int present = 0;
398     size_t li = -1;
399     const struct list_item_t *l = (const struct list_item_t *)&devlinks;
400
401     /* First, we look for existing symlinks to the target device. */
402     while (++li, (l = l->next) != NULL)
403     {
404       if (strcmp (l->data, i->data))
405         continue;
406       /* Existing symlink found - don't output a new one.
407        * If ISDEV, we output the name of the existing symlink.
408        */
409       present = 1;
410       if (isdev)
411         printf (" %s", list_nth (&devls, li)->data);
412     }
413
414     /* If we found no existing symlinks for the target device... */
415     if (!present)
416     {
417       char buf[256];
418       snprintf (buf, sizeof (buf), count ? "%s%d" : "%s", link, count);
419       /* Find the next available (not present) symlink name.
420        * We always need to do this for reasons of output consistency: if a
421        * symlink is created by udev as a result of use of this program, we
422        * DON'T want different output!
423        */
424       while (list_search (&devls, buf))
425         snprintf (buf, sizeof (buf), "%s%d", link, ++count);
426       /* If ISDEV, output it. */
427       if (isdev && (numbered_links || count == 0))
428         printf (" %s", buf);
429       /* If the link isn't in our "existing links" list, add it and increment
430        * our counter.
431        */
432       if (!list_search (&devls, buf))
433       {
434         list_append (&devls, buf);
435         ++count;
436       }
437     }
438   }
439
440   list_delete (&devls);
441   list_delete (&devlinks);
442 }
443
444
445 /* Populate a device list from a capabilities list. */
446 static void
447 populate_device_list (struct list_t *out, const struct list_t *caps)
448 {
449   const struct list_item_t *cap, *dev;
450   cap = (const struct list_item_t *)caps;
451   dev = (const struct list_item_t *)&Devices;
452   while ((cap = cap->next) != NULL && (dev = dev->next) != NULL)
453     if (cap->data[0] != '0')
454       list_append (out, dev->data);
455 }
456
457
458 int
459 main (int argc, char *argv[])
460 {
461   progname = argv[0];
462   debug = argc > 2 && !strcmp (argv[2], "-d");
463
464   if (argc < 2 || argc > 2 + debug)
465     msgexit ("usage: cdsymlinks DEVICE [-d]");
466
467   if (chdir ("/dev"))
468     errexit ("can't chdir /dev");
469
470   read_defaults ();
471   populate_capability_lists ();
472
473   /* Construct the device lists from the capability lists. */
474   populate_device_list (&dev_DVDRAM, &cap_DVDRAM);
475   populate_device_list (&dev_DVDRW, &cap_DVDRW);
476   populate_device_list (&dev_DVD, &cap_DVD);
477   populate_device_list (&dev_CDRW, &cap_CDRW);
478   populate_device_list (&dev_CDR, &cap_CDR);
479   populate_device_list (&dev_CDWMRW, &cap_CDWMRW);
480   populate_device_list (&dev_CDMRW, &cap_CDMRW);
481   /* (All devices can read CDs.) */
482
483   if (debug)
484   {
485 #define printdev(DEV) \
486         printf ("%-7s:", #DEV); \
487         list_print (&cap_##DEV, stdout); \
488         list_print (&dev_##DEV, stdout); \
489         puts ("");
490
491     printf ("Devices:");
492     const struct list_item_t *item = (const struct list_item_t *)&Devices;
493     while ((item = item->next) != NULL)
494       printf (" %s", item->data);
495     puts ("");
496
497     printdev (DVDRAM);
498     printdev (DVDRW);
499     printdev (DVD);
500     printdev (CDRW);
501     printdev (CDR);
502     printdev (CDWMRW);
503     printdev (CDMRW);
504
505     printf ("CDROM  : (all)");
506     item = (const struct list_item_t *)&dev_CD;
507     while ((item = item->next) != NULL)
508       printf (" %s", item->data);
509     puts ("");
510   }
511
512   /* Write the symlink names. */
513   if (list_search (&allowed_output, "CD"))
514     do_output (argv[1], "cdrom",  &dev_CD);
515   if (list_search (&allowed_output, "CDR"))
516     do_output (argv[1], "cd-r",   &dev_CDR);
517   if (list_search (&allowed_output, "CDRW"))
518     do_output (argv[1], "cdrw",   &dev_CDRW);
519   if (list_search (&allowed_output, "DVD"))
520     do_output (argv[1], "dvd",    &dev_DVD);
521   if (list_search (&allowed_output, "DVDRW"))
522     do_output (argv[1], "dvdrw",  &dev_DVDRW);
523   if (list_search (&allowed_output, "DVDRAM"))
524     do_output (argv[1], "dvdram", &dev_DVDRAM);
525   if (list_search (&allowed_output, "CDMRW"))
526     do_output (argv[1], "cdmrw",   &dev_CDMRW);
527   if (list_search (&allowed_output, "CDWMRW"))
528     do_output (argv[1], "cdwmrw",   &dev_CDWMRW);
529   puts ("");
530
531   return 0;
532 }