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