Initial public busybox upstream commit
[busybox4maemo] / util-linux / volume_id / get_devname.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Support functions for mounting devices by label/uuid
4  *
5  * Copyright (C) 2006 by Jason Schoon <floydpink@gmail.com>
6  * Some portions cribbed from e2fsprogs, util-linux, dosfstools
7  *
8  * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
9  */
10
11 #include "volume_id_internal.h"
12
13 //#define BLKGETSIZE64 _IOR(0x12,114,size_t)
14
15 static struct uuidCache_s {
16         struct uuidCache_s *next;
17 //      int major, minor;
18         char *device;
19         char *label;
20         char *uc_uuid; /* prefix makes it easier to grep for */
21 } *uuidCache;
22
23 /* Returns !0 on error.
24  * Otherwise, returns malloc'ed strings for label and uuid
25  * (and they can't be NULL, although they can be "") */
26 #if !ENABLE_FEATURE_VOLUMEID_ISO9660
27 #define get_label_uuid(device, label, uuid, iso_only) \
28         get_label_uuid(device, label, uuid)
29 #endif
30 static int
31 get_label_uuid(const char *device, char **label, char **uuid, int iso_only)
32 {
33         int rv = 1;
34         uint64_t size;
35         struct volume_id *vid;
36
37         vid = volume_id_open_node(device);
38         if (!vid)
39                 return rv;
40
41         if (ioctl(vid->fd, BLKGETSIZE64, &size) != 0)
42                 size = 0;
43
44 #if ENABLE_FEATURE_VOLUMEID_ISO9660
45         if ((iso_only ?
46              volume_id_probe_iso9660(vid, 0) :
47              volume_id_probe_all(vid, 0, size)
48             ) != 0
49         ) {
50                 goto ret;
51         }
52 #else
53         if (volume_id_probe_all(vid, 0, size) != 0) {
54                 goto ret;
55         }
56 #endif
57
58         if (vid->label[0] != '\0' || vid->uuid[0] != '\0') {
59                 *label = xstrndup(vid->label, sizeof(vid->label));
60                 *uuid  = xstrndup(vid->uuid, sizeof(vid->uuid));
61                 dbg("found label '%s', uuid '%s' on %s", *label, *uuid, device);
62                 rv = 0;
63         }
64  ret:
65         free_volume_id(vid);
66         return rv;
67 }
68
69 /* NB: we take ownership of (malloc'ed) label and uuid */
70 static void
71 uuidcache_addentry(char *device, /*int major, int minor,*/ char *label, char *uuid)
72 {
73         struct uuidCache_s *last;
74     
75         if (!uuidCache) {
76                 last = uuidCache = xzalloc(sizeof(*uuidCache));
77         } else {
78                 for (last = uuidCache; last->next; last = last->next)
79                         continue;
80                 last->next = xzalloc(sizeof(*uuidCache));
81                 last = last->next;
82         }
83         /*last->next = NULL; - xzalloc did it*/
84 //      last->major = major;
85 //      last->minor = minor;
86         last->device = device;
87         last->label = label;
88         last->uc_uuid = uuid;
89 }
90
91 /* If get_label_uuid() on device_name returns success,
92  * add a cache entry for this device.
93  * If device node does not exist, it will be temporarily created. */
94 #if !ENABLE_FEATURE_VOLUMEID_ISO9660
95 #define uuidcache_check_device(device_name, ma, mi, iso_only) \
96         uuidcache_check_device(device_name, ma, mi)
97 #endif
98 static void
99 uuidcache_check_device(const char *device_name, int ma, int mi, int iso_only)
100 {
101         char *device, *last_slash;
102         char *uuid, *label;
103         char *ptr;
104         int must_remove = 0;
105         int added = 0;
106
107         last_slash = NULL;
108         device = xasprintf("/dev/%s", device_name);
109         if (access(device, F_OK) != 0) {
110                 /* device does not exist, temporarily create */
111                 int slash_cnt = 0;
112
113                 if ((ma | mi) < 0)
114                         goto ret; /* we don't know major:minor! */
115
116                 ptr = device;
117                 while (*ptr)
118                         if (*ptr++ == '/')
119                                 slash_cnt++;
120                 if (slash_cnt > 2) {
121 // BUG: handles only slash_cnt == 3 case
122                         last_slash = strrchr(device, '/');
123                         *last_slash = '\0';
124                         if (mkdir(device, 0644)) {
125                                 bb_perror_msg("can't create directory %s", device);
126                                 *last_slash = '/';
127                                 last_slash = NULL; /* prevents rmdir */
128                         } else {
129                                 *last_slash = '/';
130                         }
131                 }
132                 mknod(device, S_IFBLK | 0600, makedev(ma, mi));
133                 must_remove = 1;
134         }
135
136         uuid = NULL;
137         label = NULL;
138         if (get_label_uuid(device, &label, &uuid, iso_only) == 0) {
139                 uuidcache_addentry(device, /*ma, mi,*/ label, uuid);
140                 /* "device" is owned by cache now, don't free */
141                 added = 1;
142         }
143
144         if (must_remove)
145                 unlink(device);
146         if (last_slash) {
147                 *last_slash = '\0';
148                 rmdir(device);
149         }
150  ret:
151         if (!added)
152                 free(device);
153 }
154
155 /* Run uuidcache_check_device() for every device mentioned
156  * in /proc/partitions */
157 static void
158 uuidcache_init_partitions(void)
159 {
160         char line[100];
161         int ma, mi;
162         unsigned long long sz;
163         FILE *procpt;
164         int firstPass;
165         int handleOnFirst;
166         char *chptr;
167
168         procpt = xfopen("/proc/partitions", "r");
169 /*
170 # cat /proc/partitions
171 major minor  #blocks  name
172
173    8     0  293036184 sda
174    8     1    6835626 sda1
175    8     2          1 sda2
176    8     5     979933 sda5
177    8     6   15623181 sda6
178    8     7   97659103 sda7
179    8     8  171935631 sda8
180 */
181         for (firstPass = 1; firstPass >= 0; firstPass--) {
182                 fseek(procpt, 0, SEEK_SET);
183
184                 while (fgets(line, sizeof(line), procpt)) {
185                         /* The original version of this code used sscanf, but
186                            diet's sscanf is quite limited */
187                         chptr = line;
188                         if (*chptr != ' ') continue;
189                         chptr = skip_whitespace(chptr);
190
191                         ma = bb_strtou(chptr, &chptr, 0);
192                         if (ma < 0) continue;
193                         chptr = skip_whitespace(chptr);
194
195                         mi = bb_strtou(chptr, &chptr, 0);
196                         if (mi < 0) continue;
197                         chptr = skip_whitespace(chptr);
198
199                         sz = bb_strtoull(chptr, &chptr, 0);
200                         if ((long long)sz == -1LL) continue;
201                         chptr = skip_whitespace(chptr);
202
203                         /* skip extended partitions (heuristic: size 1) */
204                         if (sz == 1)
205                                 continue;
206
207                         *strchrnul(chptr, '\n') = '\0';
208                         /* now chptr => device name */
209                         dbg("/proc/partitions: maj:%d min:%d sz:%llu name:'%s'",
210                                                 ma, mi, sz, chptr);
211                         if (!chptr[0])
212                                 continue;
213
214                         /* look only at md devices on first pass */
215                         handleOnFirst = (chptr[0] == 'm' && chptr[1] == 'd');
216                         if (firstPass != handleOnFirst)
217                                 continue;
218
219                         /* heuristic: partition name ends in a digit */
220                         if (isdigit(chptr[strlen(chptr) - 1])) {
221                                 uuidcache_check_device(chptr, ma, mi, 0);
222                         }
223                 }
224         }
225
226         fclose(procpt);
227 }
228
229 static void
230 dev_get_major_minor(char *device_name, int *major, int *minor)
231 {
232         char dev[16];
233         char *dev_path;
234         char *colon;
235         int sz;
236
237         dev_path = xasprintf("/sys/block/%s/dev", device_name);
238         sz = open_read_close(dev_path, dev, sizeof(dev) - 1);
239         if (sz < 0)
240                 goto ret;
241         dev[sz] = '\0';
242
243         colon = strchr(dev, ':');
244         if (!colon)
245                 goto ret;
246         *major = bb_strtou(dev, NULL, 10);
247         *minor = bb_strtou(colon + 1, NULL, 10);
248
249  ret:
250         free(dev_path);
251         return;
252 }
253
254 static void
255 uuidcache_init_cdroms(void)
256 {
257 #define PROC_CDROMS "/proc/sys/dev/cdrom/info"
258         char line[100];
259         int ma, mi;
260         FILE *proccd;
261
262         proccd = fopen(PROC_CDROMS, "r");
263         if (!proccd) {
264 //              static smallint warn = 0;
265 //              if (!warn) {
266 //                      warn = 1;
267 //                      bb_error_msg("can't open %s, UUID and LABEL "
268 //                              "conversion cannot be done for CD-Roms",
269 //                              PROC_CDROMS);
270 //              }
271                 return;
272         }
273
274         while (fgets(line, sizeof(line), proccd)) {
275                 static const char drive_name_string[] ALIGN1 = "drive name:";
276
277                 if (strncmp(line, drive_name_string, sizeof(drive_name_string) - 1) == 0) {
278                         char *device_name;
279
280                         device_name = strtok(skip_whitespace(line + sizeof(drive_name_string) - 1), " \t\n");
281                         while (device_name && device_name[0]) {
282                                 ma = mi = -1;
283                                 dev_get_major_minor(device_name, &ma, &mi);
284                                 uuidcache_check_device(device_name, ma, mi, 1);
285                                 device_name = strtok(NULL, " \t\n");
286                         }
287                         break;
288                 }
289         }
290
291         fclose(proccd);
292 }
293
294 static void
295 uuidcache_init(void)
296 {
297         if (uuidCache)
298                 return;
299
300         uuidcache_init_partitions();
301         uuidcache_init_cdroms();
302 }
303
304 #define UUID   1
305 #define VOL    2
306
307 #ifdef UNUSED
308 static char *
309 get_spec_by_x(int n, const char *t, int *majorPtr, int *minorPtr)
310 {
311         struct uuidCache_s *uc;
312
313         uuidcache_init();
314         uc = uuidCache;
315
316         while (uc) {
317                 switch (n) {
318                 case UUID:
319                         if (strcmp(t, uc->uc_uuid) == 0) {
320                                 *majorPtr = uc->major;
321                                 *minorPtr = uc->minor;
322                                 return uc->device;
323                         }
324                         break;
325                 case VOL:
326                         if (strcmp(t, uc->label) == 0) {
327                                 *majorPtr = uc->major;
328                                 *minorPtr = uc->minor;
329                                 return uc->device;
330                         }
331                         break;
332                 }
333                 uc = uc->next;
334         }
335         return NULL;
336 }
337
338 static unsigned char
339 fromhex(char c)
340 {
341         if (isdigit(c))
342                 return (c - '0');
343         return ((c|0x20) - 'a' + 10);
344 }
345
346 static char *
347 get_spec_by_uuid(const char *s, int *major, int *minor)
348 {
349         unsigned char uuid[16];
350         int i;
351
352         if (strlen(s) != 36 || s[8] != '-' || s[13] != '-'
353          || s[18] != '-' || s[23] != '-'
354         ) {
355                 goto bad_uuid;
356         }
357         for (i = 0; i < 16; i++) {
358                 if (*s == '-')
359                         s++;
360                 if (!isxdigit(s[0]) || !isxdigit(s[1]))
361                         goto bad_uuid;
362                 uuid[i] = ((fromhex(s[0]) << 4) | fromhex(s[1]));
363                 s += 2;
364         }
365         return get_spec_by_x(UUID, (char *)uuid, major, minor);
366
367  bad_uuid:
368         fprintf(stderr, _("mount: bad UUID"));
369         return 0;
370 }
371
372 static char *
373 get_spec_by_volume_label(const char *s, int *major, int *minor)
374 {
375         return get_spec_by_x(VOL, s, major, minor);
376 }
377
378 static int display_uuid_cache(void)
379 {
380         struct uuidCache_s *u;
381         size_t i;
382
383         uuidcache_init();
384
385         u = uuidCache;
386         while (u) {
387                 printf("%s %s %s\n", u->device, u->label, u->uc_uuid);
388                 u = u->next;
389         }
390
391         return 0;
392 }
393 #endif // UNUSED
394
395
396 /* Used by mount and findfs */
397
398 char *get_devname_from_label(const char *spec)
399 {
400         struct uuidCache_s *uc;
401         int spec_len = strlen(spec);
402
403         uuidcache_init();
404         uc = uuidCache;
405         while (uc) {
406 // FIXME: empty label ("LABEL=") matches anything??!
407                 if (uc->label[0] && strncmp(spec, uc->label, spec_len) == 0) {
408                         return xstrdup(uc->device);
409                 }
410                 uc = uc->next;
411         }
412         return NULL;
413 }
414
415 char *get_devname_from_uuid(const char *spec)
416 {
417         struct uuidCache_s *uc;
418
419         uuidcache_init();
420         uc = uuidCache;
421         while (uc) {
422                 /* case of hex numbers doesn't matter */
423                 if (strcasecmp(spec, uc->uc_uuid) == 0) {
424                         return xstrdup(uc->device);
425                 }
426                 uc = uc->next;
427         }
428         return NULL;
429 }