chiark / gitweb /
[PATCH] udev callout for reading filesystem labels
[elogind.git] / extras / volume_id / volume_id.c
1 /*
2  * volume_id - reads filesystem label and uuid
3  *
4  * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
5  *
6  *      The superblock structs are taken from the libblkid living inside
7  *      the e2fsprogs. This is a simple straightforward implementation for
8  *      reading the label strings of only the most common filesystems.
9  *      If you need a full featured library with attribute caching, support for
10  *      much more partition/media types or non-root data access, you may have
11  *      a look at:
12  *              http://e2fsprogs.sourceforge.net.
13  *
14  *      This program is free software; you can redistribute it and/or modify it
15  *      under the terms of the GNU General Public License as published by the
16  *      Free Software Foundation version 2 of the License.
17  *
18  *      This program is distributed in the hope that it will be useful, but
19  *      WITHOUT ANY WARRANTY; without even the implied warranty of
20  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21  *      General Public License for more details.
22  *
23  *      You should have received a copy of the GNU General Public License along
24  *      with this program; if not, write to the Free Software Foundation, Inc.,
25  *      675 Mass Ave, Cambridge, MA 02139, USA.
26  *
27  */
28
29 #include <stdlib.h>
30 #include <stdio.h>
31 #include <string.h>
32 #include <unistd.h>
33 #include <errno.h>
34 #include <ctype.h>
35 #include <fcntl.h>
36 #include <sys/stat.h>
37 #include <asm/types.h>
38
39 #include "volume_id.h"
40
41
42 #define bswap32(x) (__u32)((((__u32)(x) & 0xff000000u) >> 24) | \
43                            (((__u32)(x) & 0x00ff0000u) >>  8) | \
44                            (((__u32)(x) & 0x0000ff00u) <<  8) | \
45                            (((__u32)(x) & 0x000000ffu) << 24))
46
47 #if (__BYTE_ORDER == __LITTLE_ENDIAN) 
48 #define cpu_to_le32(x) (x)
49 #elif (__BYTE_ORDER == __BIG_ENDIAN)
50 #define cpu_to_le32(x) bswap32(x)
51 #endif
52
53 #define VOLUME_ID_BUFFER_SIZE           0x11000 /* reiser offset is 64k */
54
55
56 static void set_label(struct volume_id *id, char *buf, int count)
57 {
58         int i;
59
60         memcpy(id->label, buf, count);
61
62         memcpy(id->label_string, buf, count);
63
64         /* remove trailing whitespace */
65         i = strlen(id->label_string);
66         while (i--) {
67                 if (! isspace(id->label_string[i]))
68                         break;
69         }
70         id->label_string[i+1] = '\0';
71 }
72
73 static void set_uuid(struct volume_id *id, unsigned char *buf, int count)
74 {
75         int i;
76
77         memcpy(id->uuid, buf, count);
78
79         /* create string if uuid is set */
80         for (i = 0; i < count; i++) 
81                 if (buf[i] != 0)
82                         goto set;
83         return;
84
85 set:
86         switch(count) {
87         case 4:
88                 sprintf(id->uuid_string, "%02X%02X-%02X%02X",
89                         buf[3], buf[2], buf[1], buf[0]);
90                 break;
91         case 16:
92                 sprintf(id->uuid_string,
93                         "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
94                         buf[0], buf[1], buf[2], buf[3],
95                         buf[4], buf[5],
96                         buf[6], buf[7],
97                         buf[8], buf[9],
98                         buf[10], buf[11], buf[12], buf[13], buf[14],buf[15]);
99                 break;
100         }
101 }
102
103 static int open_superblock(struct volume_id *id)
104 {
105         /* get buffer to read the first block */
106         if (id->buf == NULL) {
107                 id->buf = malloc(VOLUME_ID_BUFFER_SIZE);
108                 if (id->buf == NULL)
109                         return -1;
110         }
111
112         /* try to read the first 64k, but at least the first block */
113         memset(id->buf, 0x00, VOLUME_ID_BUFFER_SIZE);
114         lseek(id->fd, 0, SEEK_SET);
115         if (read(id->fd, id->buf, VOLUME_ID_BUFFER_SIZE) < 0x200)
116                 return -1;
117
118         return 0;
119 }
120
121 static void close_superblock(struct volume_id *id)
122 {
123         if (id->buf != NULL) {
124                 free(id->buf);
125                 id->buf = NULL;
126         }
127 }
128
129 #define EXT3_FEATURE_COMPAT_HAS_JOURNAL         0x00000004
130 #define EXT3_FEATURE_INCOMPAT_JOURNAL_DEV       0x00000008
131 #define EXT_SUPERBLOCK_OFFSET                   0x400
132 static int probe_ext(struct volume_id *id)
133 {
134         struct ext2_super_block {
135                 __u32           s_inodes_count;
136                 __u32           s_blocks_count;
137                 __u32           s_r_blocks_count;
138                 __u32           s_free_blocks_count;
139                 __u32           s_free_inodes_count;
140                 __u32           s_first_data_block;
141                 __u32           s_log_block_size;
142                 __u32           s_dummy3[7];
143                 unsigned char   s_magic[2];
144                 __u16           s_state;
145                 __u32           s_dummy5[8];
146                 __u32           s_feature_compat;
147                 __u32           s_feature_incompat;
148                 __u32           s_feature_ro_compat;
149                 unsigned char   s_uuid[16];
150                 char            s_volume_name[16];
151         } *es;
152
153         es = (struct ext2_super_block *) (id->buf + EXT_SUPERBLOCK_OFFSET);
154
155         if (es->s_magic[0] != 0123 ||
156             es->s_magic[1] != 0357)
157                 return -1;
158
159         set_label(id, es->s_volume_name, 16);
160         set_uuid(id, es->s_uuid, 16);
161
162         if ((cpu_to_le32(es->s_feature_compat) & EXT3_FEATURE_COMPAT_HAS_JOURNAL) != 0) {
163                 id->fs_type = EXT3;
164                 id->fs_name = "ext3";
165         } else {
166                 id->fs_type = EXT2;
167                 id->fs_name = "ext2";
168         }
169
170         return 0;
171 }
172
173 #define REISER1_SUPERBLOCK_OFFSET               0x2000
174 #define REISER_SUPERBLOCK_OFFSET                0x10000
175 static int probe_reiser(struct volume_id *id)
176 {
177         struct reiser_super_block {
178                 __u32           rs_blocks_count;
179                 __u32           rs_free_blocks;
180                 __u32           rs_root_block;
181                 __u32           rs_journal_block;
182                 __u32           rs_journal_dev;
183                 __u32           rs_orig_journal_size;
184                 __u32           rs_dummy2[5];
185                 __u16           rs_blocksize;
186                 __u16           rs_dummy3[3];
187                 unsigned char   rs_magic[12];
188                 __u32           rs_dummy4[5];
189                 unsigned char   rs_uuid[16];
190                 char            rs_label[16];
191         } *rs;
192
193         rs = (struct reiser_super_block *) &(id->buf[REISER1_SUPERBLOCK_OFFSET]);
194
195         if (strncmp(rs->rs_magic, "ReIsErFs", 8) == 0)
196                 goto found;
197
198         rs = (struct reiser_super_block *) &(id->buf[REISER_SUPERBLOCK_OFFSET]);
199
200         if (strncmp(rs->rs_magic, "ReIsEr2Fs", 9) == 0)
201                 goto found;
202         if (strncmp(rs->rs_magic, "ReIsEr3Fs", 9) == 0)
203                 goto found;
204
205         return -1;
206
207 found:
208         set_label(id, rs->rs_label, 16);
209         set_uuid(id, rs->rs_uuid, 16);
210
211         id->fs_type = REISER;
212         id->fs_name = "reiser";
213
214         return 0;
215 }
216
217 static int probe_xfs(struct volume_id *id)
218 {
219         struct xfs_super_block {
220                 unsigned char   xs_magic[4];
221                 __u32           xs_blocksize;
222                 __u64           xs_dblocks;
223                 __u64           xs_rblocks;
224                 __u32           xs_dummy1[2];
225                 unsigned char   xs_uuid[16];
226                 __u32           xs_dummy2[15];
227                 char            xs_fname[12];
228                 __u32           xs_dummy3[2];
229                 __u64           xs_icount;
230                 __u64           xs_ifree;
231                 __u64           xs_fdblocks;
232         } *xs;
233
234         xs = (struct xfs_super_block *) id->buf;
235
236         if (strncmp(xs->xs_magic, "XFSB", 4) != 0)
237                 return -1;
238
239         set_label(id, xs->xs_fname, 12);
240         set_uuid(id, xs->xs_uuid, 16);
241
242         id->fs_type = XFS;
243         id->fs_name = "xfs";
244
245         return 0;
246 }
247
248 #define JFS_SUPERBLOCK_OFFSET                   0x8000
249 static int probe_jfs(struct volume_id *id)
250 {
251         struct jfs_super_block {
252                 unsigned char   js_magic[4];
253                 __u32           js_version;
254                 __u64           js_size;
255                 __u32           js_bsize;
256                 __u32           js_dummy1;
257                 __u32           js_pbsize;
258                 __u32           js_dummy2[27];
259                 unsigned char   js_uuid[16];
260                 unsigned char   js_label[16];
261                 unsigned char   js_loguuid[16];
262         } *js;
263
264         js = (struct jfs_super_block *) &(id->buf[JFS_SUPERBLOCK_OFFSET]);
265
266         if (strncmp(js->js_magic, "JFS1", 4) != 0)
267                 return -1;
268
269         set_label(id, js->js_label, 16);
270         set_uuid(id, js->js_uuid, 16);
271
272         id->fs_type = JFS;
273         id->fs_name = "jfs";
274
275         return 0;
276 }
277
278 static int probe_vfat(struct volume_id *id)
279 {
280         struct vfat_super_block {
281                 unsigned char   vs_ignored[3];
282                 unsigned char   vs_sysid[8];
283                 unsigned char   vs_sector_size[2];
284                 __u8            vs_cluster_size;
285                 __u16           vs_reserved;
286                 __u8            vs_fats;
287                 unsigned char   vs_dir_entries[2];
288                 unsigned char   vs_sectors[2];
289                 unsigned char   vs_media;
290                 __u16           vs_fat_length;
291                 __u16           vs_secs_track;
292                 __u16           vs_heads;
293                 __u32           vs_hidden;
294                 __u32           vs_total_sect;
295                 __u32           vs_fat32_length;
296                 __u16           vs_flags;
297                 __u8            vs_version[2];
298                 __u32           vs_root_cluster;
299                 __u16           vs_insfo_sector;
300                 __u16           vs_backup_boot;
301                 __u16           vs_reserved2[6];
302                 unsigned char   vs_unknown[3];
303                 unsigned char   vs_serno[4];
304                 char            vs_label[11];
305                 unsigned char   vs_magic[8];
306                 unsigned char   vs_dummy2[164];
307                 unsigned char   vs_pmagic[2];
308         } *vs;
309
310         vs = (struct vfat_super_block *) id->buf;
311
312         if (strncmp(vs->vs_magic, "MSWIN", 5) == 0)
313                 goto found;
314         if (strncmp(vs->vs_magic, "FAT32   ", 8) == 0)
315                 goto found;
316         return -1;
317
318 found:
319         memcpy(id->label, vs->vs_label, 11);
320         memcpy(id->uuid, vs->vs_serno, 4);
321
322         id->fs_type = VFAT;
323         id->fs_name = "vfat";
324
325         return 0;
326 }
327
328 static int probe_msdos(struct volume_id *id)
329 {
330         struct msdos_super_block {
331                 unsigned char   ms_ignored[3];
332                 unsigned char   ms_sysid[8];
333                 unsigned char   ms_sector_size[2];
334                 __u8            ms_cluster_size;
335                 __u16           ms_reserved;
336                 __u8            ms_fats;
337                 unsigned char   ms_dir_entries[2];
338                 unsigned char   ms_sectors[2];
339                 unsigned char   ms_media;
340                 __u16           ms_fat_length;
341                 __u16           ms_secs_track;
342                 __u16           ms_heads;
343                 __u32           ms_hidden;
344                 __u32           ms_total_sect;
345                 unsigned char   ms_unknown[3];
346                 unsigned char   ms_serno[4];
347                 char            ms_label[11];
348                 unsigned char   ms_magic[8];
349                 unsigned char   ms_dummy2[192];
350                 unsigned char   ms_pmagic[2];
351         } *ms;
352
353         ms = (struct msdos_super_block *) id->buf;
354
355         if (strncmp(ms->ms_magic, "MSDOS", 5) == 0)
356                 goto found;
357         if (strncmp(ms->ms_magic, "FAT16   ", 8) == 0)
358                 goto found;
359         if (strncmp(ms->ms_magic, "FAT12   ", 8) == 0)
360                 goto found;
361         return -1;
362
363 found:
364         set_label(id, ms->ms_label, 11);
365         set_uuid(id, ms->ms_serno, 4);
366
367         id->fs_type = MSDOS;
368         id->fs_name = "msdos";
369
370         return 0;
371 }
372
373 static int probe_ntfs(struct volume_id *id)
374 {
375         struct ntfs_super_block {
376                 char jump[3];
377                 char oem_id[4];
378         } *ns;
379
380         ns = (struct ntfs_super_block *) id->buf;
381
382         if (strncmp(ns->oem_id, "NTFS", 4) != 0)
383                 return -1;
384
385         id->fs_type = NTFS;
386         id->fs_name = "ntfs";
387
388         return 0;
389 }
390
391 static int probe_swap(struct volume_id *id)
392 {
393         int magic;
394
395         /* huhh, the swap signature is on the end of the PAGE_SIZE */
396         for (magic = 0x1000; magic <= 0x4000; magic <<= 1) {
397                         if (strncmp(&(id->buf[magic -10]), "SWAP-SPACE", 10) == 0)
398                                 goto found;
399                         if (strncmp(&(id->buf[magic -10]), "SWAPSPACE2", 10) == 0)
400                                 goto found;
401         }
402         return -1;
403
404 found:
405         id->fs_type = SWAP;
406         id->fs_name = "swap";
407
408         return 0;
409 }
410
411 /* probe volume for filesystem type and try to read label+uuid */
412 int volume_id_probe(struct volume_id *id, enum filesystem_type fs_type)
413 {
414         int rc;
415
416         if (id == NULL)
417                 return -EINVAL;
418
419         if (open_superblock(id) != 0)
420                 return -EACCES;
421
422         switch (fs_type) {
423         case EXT3:
424         case EXT2:
425                 rc = probe_ext(id);
426                 break;
427         case REISER:
428                 rc = probe_reiser(id);
429                 break;
430         case XFS:
431                 rc = probe_xfs(id);
432                 break;
433         case JFS:
434                 rc = probe_jfs(id);
435                 break;
436         case MSDOS:
437                 rc = probe_msdos(id);
438                 break;
439         case VFAT:
440                 rc = probe_vfat(id);
441                 break;
442         case NTFS:
443                 rc = probe_ntfs(id);
444                 break;
445         case SWAP:
446                 rc = probe_swap(id);
447                 break;
448         default:
449                 rc = probe_ext(id);
450                 if (rc == 0)
451                         break;
452                 rc = probe_reiser(id);
453                 if (rc == 0)
454                         break;
455                 rc = probe_xfs(id);
456                 if (rc == 0)
457                         break;
458                 rc = probe_jfs(id);
459                 if (rc == 0)
460                         break;
461                 rc = probe_msdos(id);
462                 if (rc == 0)
463                         break;
464                 rc = probe_vfat(id);
465                 if (rc == 0)
466                         break;
467                 rc = probe_ntfs(id);
468                 if (rc == 0)
469                         break;
470                 rc = probe_swap(id);
471                 if (rc == 0)
472                         break;
473                 rc = -1;
474         }
475
476         if (rc == 0)
477                 close_superblock(id);
478
479         return rc;
480 }
481
482 /* open volume by already open file descriptor */
483 struct volume_id *volume_id_open_fd(int fd)
484 {
485         struct volume_id *id;
486
487         id = malloc(sizeof(struct volume_id));
488         if (id == NULL)
489                 return NULL;
490         memset(id, 0x00, sizeof(struct volume_id));
491
492         id->fd = fd;
493
494         return id;
495 }
496
497 /* open volume by device node */
498 struct volume_id *volume_id_open_node(const char *path)
499 {
500         struct volume_id *id;
501         int fd;
502
503         fd = open(path, O_RDONLY);
504         if (fd < 0)
505                 return NULL;
506
507         id = volume_id_open_fd(fd);
508         if (id == NULL)
509                 return NULL;
510
511         /* close fd on device close */
512         id->fd_close = 1;
513
514         return id;
515 }
516
517 /* open volume by major/minor */
518 struct volume_id *volume_id_open_dev_t(dev_t devt)
519 {
520         struct volume_id *id;
521         char tmp_node[VOLUME_ID_PATH_MAX];
522
523         snprintf(tmp_node, VOLUME_ID_PATH_MAX,
524                  "/tmp/volume-%u-%u", major(devt), minor(devt));
525         tmp_node[VOLUME_ID_PATH_MAX] = '\0';
526
527         /* create tempory node to open the block device */
528         if (mknod(tmp_node, (S_IFBLK | 0600), devt) != 0)
529                 return NULL;
530
531         id = volume_id_open_node(tmp_node);
532
533         unlink(tmp_node);
534
535         return id;
536 }
537
538 /* free allocated volume info */
539 void volume_id_close(struct volume_id *id)
540 {
541         if (id == NULL)
542                 return;
543
544         if (id->fd_close != 0)
545                 close(id->fd);
546
547         close_superblock(id);
548
549         free(id);
550 }