OSDN Git Service

blkid: Add support for probing exFAT
[android-x86/external-e2fsprogs.git] / lib / blkid / probe_exfat.c
1 /*
2  * Copyright (C) 2010 Andrew Nayenko <resver@gmail.com>
3  *
4  * This file may be redistributed under the terms of the
5  * GNU Lesser General Public License.
6  */
7
8 #include <stdio.h>
9 #include <string.h>
10 #include <math.h>
11 #include "blkidP.h"
12 #include "probe.h"
13
14 #define le32_to_cpu(x) blkid_le32(x)
15
16 typedef __u8  uint8_t;
17 typedef __u16 uint16_t;
18 typedef __u32 uint32_t;
19 typedef __uint64_t uint64_t;
20
21 typedef struct blkid_probe* blkid_probe;
22
23 struct exfat_super_block {
24         __u8 jump[3];
25         __u8 oem_name[8];
26         __u8    __unused1[53];
27         __u64 block_start;
28         __u64 block_count;
29         __u32 fat_block_start;
30         __u32 fat_block_count;
31         __u32 cluster_block_start;
32         __u32 cluster_count;
33         __u32 rootdir_cluster;
34         __u8 volume_serial[4];
35         struct {
36                 __u8 minor;
37                 __u8 major;
38         } version;
39         __u16 volume_state;
40         __u8 block_bits;
41         __u8 bpc_bits;
42         __u8 fat_count;
43         __u8 drive_no;
44         __u8 allocated_percent;
45 } __attribute__((__packed__));
46
47 struct exfat_entry_label {
48         __u8 type;
49         __u8 length;
50         __u8 name[30];
51 } __attribute__((__packed__));
52
53 #define BLOCK_SIZE(sb) (1 << (sb)->block_bits)
54 #define CLUSTER_SIZE(sb) (BLOCK_SIZE(sb) << (sb)->bpc_bits)
55 #define EXFAT_FIRST_DATA_CLUSTER 2
56 #define EXFAT_LAST_DATA_CLUSTER 0xffffff6
57 #define EXFAT_ENTRY_SIZE 32
58
59 #define EXFAT_ENTRY_EOD         0x00
60 #define EXFAT_ENTRY_LABEL       0x83
61
62 static blkid_loff_t block_to_offset(const struct exfat_super_block *sb,
63                 blkid_loff_t block)
64 {
65         return (blkid_loff_t) block << sb->block_bits;
66 }
67
68 static blkid_loff_t cluster_to_block(const struct exfat_super_block *sb,
69                 uint32_t cluster)
70 {
71         return le32_to_cpu(sb->cluster_block_start) +
72                         ((blkid_loff_t) (cluster - EXFAT_FIRST_DATA_CLUSTER)
73                                         << sb->bpc_bits);
74 }
75
76 static blkid_loff_t cluster_to_offset(const struct exfat_super_block *sb,
77                 uint32_t cluster)
78 {
79         return block_to_offset(sb, cluster_to_block(sb, cluster));
80 }
81
82 extern unsigned char *blkid_probe_get_buffer(struct blkid_probe *pr,
83                           blkid_loff_t off, size_t len);
84
85 static uint32_t next_cluster(blkid_probe pr,
86                 const struct exfat_super_block *sb, uint32_t cluster)
87 {
88         uint32_t *next;
89         blkid_loff_t fat_offset;
90
91         fat_offset = block_to_offset(sb, le32_to_cpu(sb->fat_block_start))
92                 + (blkid_loff_t) cluster * sizeof(cluster);
93         next = (uint32_t *) blkid_probe_get_buffer(pr, fat_offset,
94                         sizeof(uint32_t));
95         if (!next)
96                 return 0;
97         return le32_to_cpu(*next);
98 }
99
100 static struct exfat_entry_label *find_label(blkid_probe pr,
101                 const struct exfat_super_block *sb)
102 {
103         uint32_t cluster = le32_to_cpu(sb->rootdir_cluster);
104         blkid_loff_t offset = cluster_to_offset(sb, cluster);
105         uint8_t *entry;
106
107         for (;;) {
108                 entry = (uint8_t *) blkid_probe_get_buffer(pr, offset,
109                                 EXFAT_ENTRY_SIZE);
110                 if (!entry)
111                         return NULL;
112                 if (entry[0] == EXFAT_ENTRY_EOD)
113                         return NULL;
114                 if (entry[0] == EXFAT_ENTRY_LABEL)
115                         return (struct exfat_entry_label *) entry;
116                 offset += EXFAT_ENTRY_SIZE;
117                 if (offset % CLUSTER_SIZE(sb) == 0) {
118                         cluster = next_cluster(pr, sb, cluster);
119                         if (cluster < EXFAT_FIRST_DATA_CLUSTER)
120                                 return NULL;
121                         if (cluster > EXFAT_LAST_DATA_CLUSTER)
122                                 return NULL;
123                         offset = cluster_to_offset(sb, cluster);
124                 }
125         }
126 }
127
128 static void unicode_16le_to_utf8(unsigned char *str, int out_len,
129                                  const unsigned char *buf, int in_len)
130 {
131         int i, j;
132         unsigned int c;
133
134         for (i = j = 0; i + 2 <= in_len; i += 2) {
135                 c = (buf[i+1] << 8) | buf[i];
136                 if (c == 0) {
137                         str[j] = '\0';
138                         break;
139                 } else if (c < 0x80) {
140                         if (j+1 >= out_len)
141                                 break;
142                         str[j++] = (unsigned char) c;
143                 } else if (c < 0x800) {
144                         if (j+2 >= out_len)
145                                 break;
146                         str[j++] = (unsigned char) (0xc0 | (c >> 6));
147                         str[j++] = (unsigned char) (0x80 | (c & 0x3f));
148                 } else {
149                         if (j+3 >= out_len)
150                                 break;
151                         str[j++] = (unsigned char) (0xe0 | (c >> 12));
152                         str[j++] = (unsigned char) (0x80 | ((c >> 6) & 0x3f));
153                         str[j++] = (unsigned char) (0x80 | (c & 0x3f));
154                 }
155         }
156         str[j] = '\0';
157 }
158
159 int probe_exfat(struct blkid_probe *probe,
160                       struct blkid_magic *id __BLKID_ATTR((unused)),
161                       unsigned char *buf)
162 {
163         struct exfat_super_block *sb;
164         struct exfat_entry_label *label;
165         char serno[10];
166
167         sb = (struct exfat_super_block *) buf;
168
169         sprintf(serno, "%02X%02X-%02X%02X",
170                         sb->volume_serial[3], sb->volume_serial[2],
171                         sb->volume_serial[1], sb->volume_serial[0]);
172
173         blkid_set_tag(probe->dev, "UUID", serno, sizeof(serno)-1);
174         label = find_label(probe, sb);
175         if (label) {
176                 char utf8_label[128];
177                 unicode_16le_to_utf8(utf8_label, sizeof(utf8_label), label->name, label->length * 2);
178                 blkid_set_tag(probe->dev, "LABEL", utf8_label, 0);
179         }
180         return 0;
181 }