blob: 9ab1cef02114d3eb95a68efd1c252fa46bcda16d [file] [log] [blame]
Denis Vlasenkode7684a2008-02-18 21:08:49 +00001/*
2 * volume_id - reads filesystem label and uuid
3 *
4 * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21#include "volume_id_internal.h"
22
23#define FAT12_MAX 0xff5
24#define FAT16_MAX 0xfff5
25#define FAT_ATTR_VOLUME_ID 0x08
26#define FAT_ATTR_DIR 0x10
27#define FAT_ATTR_LONG_NAME 0x0f
28#define FAT_ATTR_MASK 0x3f
29#define FAT_ENTRY_FREE 0xe5
30
31struct vfat_super_block {
32 uint8_t boot_jump[3];
33 uint8_t sysid[8];
34 uint16_t sector_size;
35 uint8_t sectors_per_cluster;
36 uint16_t reserved;
37 uint8_t fats;
38 uint16_t dir_entries;
39 uint16_t sectors;
40 uint8_t media;
41 uint16_t fat_length;
42 uint16_t secs_track;
43 uint16_t heads;
44 uint32_t hidden;
45 uint32_t total_sect;
46 union {
47 struct fat_super_block {
48 uint8_t unknown[3];
49 uint8_t serno[4];
50 uint8_t label[11];
51 uint8_t magic[8];
52 uint8_t dummy2[192];
53 uint8_t pmagic[2];
54 } __attribute__((__packed__)) fat;
55 struct fat32_super_block {
56 uint32_t fat32_length;
57 uint16_t flags;
58 uint8_t version[2];
59 uint32_t root_cluster;
60 uint16_t insfo_sector;
61 uint16_t backup_boot;
62 uint16_t reserved2[6];
63 uint8_t unknown[3];
64 uint8_t serno[4];
65 uint8_t label[11];
66 uint8_t magic[8];
67 uint8_t dummy2[164];
68 uint8_t pmagic[2];
69 } __attribute__((__packed__)) fat32;
70 } __attribute__((__packed__)) type;
71} __attribute__((__packed__));
72
73struct vfat_dir_entry {
74 uint8_t name[11];
75 uint8_t attr;
76 uint16_t time_creat;
77 uint16_t date_creat;
78 uint16_t time_acc;
79 uint16_t date_acc;
80 uint16_t cluster_high;
81 uint16_t time_write;
82 uint16_t date_write;
83 uint16_t cluster_low;
84 uint32_t size;
85} __attribute__((__packed__));
86
87static uint8_t *get_attr_volume_id(struct vfat_dir_entry *dir, unsigned count)
88{
89 unsigned i;
90
91 for (i = 0; i < count; i++) {
92 /* end marker */
93 if (dir[i].name[0] == 0x00) {
94 dbg("end of dir");
95 break;
96 }
97
98 /* empty entry */
99 if (dir[i].name[0] == FAT_ENTRY_FREE)
100 continue;
101
102 /* long name */
103 if ((dir[i].attr & FAT_ATTR_MASK) == FAT_ATTR_LONG_NAME)
104 continue;
105
106 if ((dir[i].attr & (FAT_ATTR_VOLUME_ID | FAT_ATTR_DIR)) == FAT_ATTR_VOLUME_ID) {
107 /* labels do not have file data */
108 if (dir[i].cluster_high != 0 || dir[i].cluster_low != 0)
109 continue;
110
111 dbg("found ATTR_VOLUME_ID id in root dir");
112 return dir[i].name;
113 }
114
115 dbg("skip dir entry");
116 }
117
118 return NULL;
119}
120
121int volume_id_probe_vfat(struct volume_id *id, uint64_t off)
122{
123 struct vfat_super_block *vs;
124 struct vfat_dir_entry *dir;
125 uint16_t sector_size;
126 uint16_t dir_entries;
127 uint32_t sect_count;
128 uint16_t reserved;
129 uint32_t fat_size;
130 uint32_t root_cluster;
131 uint32_t dir_size;
132 uint32_t cluster_count;
133 uint32_t fat_length;
134 uint64_t root_start;
135 uint32_t start_data_sect;
136 uint16_t root_dir_entries;
137 uint8_t *buf;
138 uint32_t buf_size;
139 uint8_t *label = NULL;
140 uint32_t next;
141 int maxloop;
142
143 dbg("probing at offset 0x%llx", (unsigned long long) off);
144
145 vs = volume_id_get_buffer(id, off, 0x200);
146 if (vs == NULL)
147 return -1;
148
149 /* believe only that's fat, don't trust the version
150 * the cluster_count will tell us
151 */
152 if (memcmp(vs->sysid, "NTFS", 4) == 0)
153 return -1;
154
155 if (memcmp(vs->type.fat32.magic, "MSWIN", 5) == 0)
156 goto valid;
157
158 if (memcmp(vs->type.fat32.magic, "FAT32 ", 8) == 0)
159 goto valid;
160
161 if (memcmp(vs->type.fat.magic, "FAT16 ", 8) == 0)
162 goto valid;
163
164 if (memcmp(vs->type.fat.magic, "MSDOS", 5) == 0)
165 goto valid;
166
167 if (memcmp(vs->type.fat.magic, "FAT12 ", 8) == 0)
168 goto valid;
169
170 /*
171 * There are old floppies out there without a magic, so we check
172 * for well known values and guess if it's a fat volume
173 */
174
175 /* boot jump address check */
176 if ((vs->boot_jump[0] != 0xeb || vs->boot_jump[2] != 0x90) &&
177 vs->boot_jump[0] != 0xe9)
178 return -1;
179
180 /* heads check */
181 if (vs->heads == 0)
182 return -1;
183
Denis Vlasenkoc6938402008-03-24 02:18:03 +0000184 /* cluster size check */
Denis Vlasenkode7684a2008-02-18 21:08:49 +0000185 if (vs->sectors_per_cluster == 0 ||
186 (vs->sectors_per_cluster & (vs->sectors_per_cluster-1)))
187 return -1;
188
189 /* media check */
190 if (vs->media < 0xf8 && vs->media != 0xf0)
191 return -1;
192
193 /* fat count*/
194 if (vs->fats != 2)
195 return -1;
196
197 valid:
198 /* sector size check */
199 sector_size = le16_to_cpu(vs->sector_size);
200 if (sector_size != 0x200 && sector_size != 0x400 &&
201 sector_size != 0x800 && sector_size != 0x1000)
202 return -1;
203
204 dbg("sector_size 0x%x", sector_size);
205 dbg("sectors_per_cluster 0x%x", vs->sectors_per_cluster);
206
207 dir_entries = le16_to_cpu(vs->dir_entries);
208 reserved = le16_to_cpu(vs->reserved);
209 dbg("reserved 0x%x", reserved);
210
211 sect_count = le16_to_cpu(vs->sectors);
212 if (sect_count == 0)
213 sect_count = le32_to_cpu(vs->total_sect);
214 dbg("sect_count 0x%x", sect_count);
215
216 fat_length = le16_to_cpu(vs->fat_length);
217 if (fat_length == 0)
218 fat_length = le32_to_cpu(vs->type.fat32.fat32_length);
219 dbg("fat_length 0x%x", fat_length);
220
221 fat_size = fat_length * vs->fats;
222 dir_size = ((dir_entries * sizeof(struct vfat_dir_entry)) +
223 (sector_size-1)) / sector_size;
224 dbg("dir_size 0x%x", dir_size);
225
226 cluster_count = sect_count - (reserved + fat_size + dir_size);
227 cluster_count /= vs->sectors_per_cluster;
228 dbg("cluster_count 0x%x", cluster_count);
229
Denis Vlasenkoc5b73722008-03-17 09:21:26 +0000230// if (cluster_count < FAT12_MAX) {
231// strcpy(id->type_version, "FAT12");
232// } else if (cluster_count < FAT16_MAX) {
233// strcpy(id->type_version, "FAT16");
234// } else {
235// strcpy(id->type_version, "FAT32");
236// goto fat32;
237// }
238 if (cluster_count >= FAT16_MAX)
Denis Vlasenkode7684a2008-02-18 21:08:49 +0000239 goto fat32;
Denis Vlasenkode7684a2008-02-18 21:08:49 +0000240
241 /* the label may be an attribute in the root directory */
242 root_start = (reserved + fat_size) * sector_size;
243 dbg("root dir start 0x%llx", (unsigned long long) root_start);
244 root_dir_entries = le16_to_cpu(vs->dir_entries);
245 dbg("expected entries 0x%x", root_dir_entries);
246
247 buf_size = root_dir_entries * sizeof(struct vfat_dir_entry);
248 buf = volume_id_get_buffer(id, off + root_start, buf_size);
249 if (buf == NULL)
250 goto found;
251
252 dir = (struct vfat_dir_entry*) buf;
253
254 label = get_attr_volume_id(dir, root_dir_entries);
255
256 vs = volume_id_get_buffer(id, off, 0x200);
257 if (vs == NULL)
258 return -1;
259
260 if (label != NULL && memcmp(label, "NO NAME ", 11) != 0) {
Denis Vlasenkoc5b73722008-03-17 09:21:26 +0000261// volume_id_set_label_raw(id, label, 11);
Denis Vlasenkode7684a2008-02-18 21:08:49 +0000262 volume_id_set_label_string(id, label, 11);
263 } else if (memcmp(vs->type.fat.label, "NO NAME ", 11) != 0) {
Denis Vlasenkoc5b73722008-03-17 09:21:26 +0000264// volume_id_set_label_raw(id, vs->type.fat.label, 11);
Denis Vlasenkode7684a2008-02-18 21:08:49 +0000265 volume_id_set_label_string(id, vs->type.fat.label, 11);
266 }
267 volume_id_set_uuid(id, vs->type.fat.serno, UUID_DOS);
268 goto found;
269
270 fat32:
271 /* FAT32 root dir is a cluster chain like any other directory */
272 buf_size = vs->sectors_per_cluster * sector_size;
273 root_cluster = le32_to_cpu(vs->type.fat32.root_cluster);
274 dbg("root dir cluster %u", root_cluster);
275 start_data_sect = reserved + fat_size;
276
277 next = root_cluster;
278 maxloop = 100;
279 while (--maxloop) {
280 uint32_t next_sect_off;
281 uint64_t next_off;
282 uint64_t fat_entry_off;
283 int count;
284
285 dbg("next cluster %u", next);
286 next_sect_off = (next - 2) * vs->sectors_per_cluster;
287 next_off = (start_data_sect + next_sect_off) * sector_size;
288 dbg("cluster offset 0x%llx", (unsigned long long) next_off);
289
290 /* get cluster */
291 buf = volume_id_get_buffer(id, off + next_off, buf_size);
292 if (buf == NULL)
293 goto found;
294
295 dir = (struct vfat_dir_entry*) buf;
296 count = buf_size / sizeof(struct vfat_dir_entry);
297 dbg("expected entries 0x%x", count);
298
299 label = get_attr_volume_id(dir, count);
300 if (label)
301 break;
302
303 /* get FAT entry */
304 fat_entry_off = (reserved * sector_size) + (next * sizeof(uint32_t));
305 buf = volume_id_get_buffer(id, off + fat_entry_off, buf_size);
306 if (buf == NULL)
307 goto found;
308
309 /* set next cluster */
310 next = le32_to_cpu(*((uint32_t *) buf) & 0x0fffffff);
311 if (next == 0)
312 break;
313 }
314 if (maxloop == 0)
315 dbg("reached maximum follow count of root cluster chain, give up");
316
317 vs = volume_id_get_buffer(id, off, 0x200);
318 if (vs == NULL)
319 return -1;
320
321 if (label != NULL && memcmp(label, "NO NAME ", 11) != 0) {
Denis Vlasenkoc5b73722008-03-17 09:21:26 +0000322// volume_id_set_label_raw(id, label, 11);
Denis Vlasenkode7684a2008-02-18 21:08:49 +0000323 volume_id_set_label_string(id, label, 11);
324 } else if (memcmp(vs->type.fat32.label, "NO NAME ", 11) != 0) {
Denis Vlasenkoc5b73722008-03-17 09:21:26 +0000325// volume_id_set_label_raw(id, vs->type.fat32.label, 11);
Denis Vlasenkode7684a2008-02-18 21:08:49 +0000326 volume_id_set_label_string(id, vs->type.fat32.label, 11);
327 }
328 volume_id_set_uuid(id, vs->type.fat32.serno, UUID_DOS);
329
330 found:
Denis Vlasenkoc5b73722008-03-17 09:21:26 +0000331// volume_id_set_usage(id, VOLUME_ID_FILESYSTEM);
332// id->type = "vfat";
Denis Vlasenkode7684a2008-02-18 21:08:49 +0000333
334 return 0;
335}