chiark / gitweb /
f6d3699758e2dbc143708c11362459c7e5ce02dc
[elogind.git] / extras / volume_id / volume_id / fat.c
1 /*
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 #ifndef _GNU_SOURCE
22 #define _GNU_SOURCE 1
23 #endif
24
25 #ifdef HAVE_CONFIG_H
26 #  include <config.h>
27 #endif
28
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <unistd.h>
32 #include <string.h>
33 #include <errno.h>
34 #include <ctype.h>
35 #include <asm/types.h>
36
37 #include "volume_id.h"
38 #include "logging.h"
39 #include "util.h"
40 #include "fat.h"
41
42 #define FAT12_MAX                       0xff5
43 #define FAT16_MAX                       0xfff5
44 #define FAT_ATTR_VOLUME_ID              0x08
45 #define FAT_ATTR_DIR                    0x10
46 #define FAT_ATTR_LONG_NAME              0x0f
47 #define FAT_ATTR_MASK                   0x3f
48 #define FAT_ENTRY_FREE                  0xe5
49
50 struct vfat_super_block {
51         __u8    boot_jump[3];
52         __u8    sysid[8];
53         __u16   sector_size;
54         __u8    sectors_per_cluster;
55         __u16   reserved;
56         __u8    fats;
57         __u16   dir_entries;
58         __u16   sectors;
59         __u8    media;
60         __u16   fat_length;
61         __u16   secs_track;
62         __u16   heads;
63         __u32   hidden;
64         __u32   total_sect;
65         union {
66                 struct fat_super_block {
67                         __u8    unknown[3];
68                         __u8    serno[4];
69                         __u8    label[11];
70                         __u8    magic[8];
71                         __u8    dummy2[192];
72                         __u8    pmagic[2];
73                 } __attribute__((__packed__)) fat;
74                 struct fat32_super_block {
75                         __u32   fat32_length;
76                         __u16   flags;
77                         __u8    version[2];
78                         __u32   root_cluster;
79                         __u16   insfo_sector;
80                         __u16   backup_boot;
81                         __u16   reserved2[6];
82                         __u8    unknown[3];
83                         __u8    serno[4];
84                         __u8    label[11];
85                         __u8    magic[8];
86                         __u8    dummy2[164];
87                         __u8    pmagic[2];
88                 } __attribute__((__packed__)) fat32;
89         } __attribute__((__packed__)) type;
90 } __attribute__((__packed__));
91
92 struct vfat_dir_entry {
93         __u8    name[11];
94         __u8    attr;
95         __u16   time_creat;
96         __u16   date_creat;
97         __u16   time_acc;
98         __u16   date_acc;
99         __u16   cluster_high;
100         __u16   time_write;
101         __u16   date_write;
102         __u16   cluster_low;
103         __u32   size;
104 } __attribute__((__packed__));
105
106 static char *get_attr_volume_id(struct vfat_dir_entry *dir, unsigned int count)
107 {
108         unsigned int i;
109
110         for (i = 0; i < count; i++) {
111                 /* end marker */
112                 if (dir[i].name[0] == 0x00) {
113                         dbg("end of dir");
114                         break;
115                 }
116
117                 /* empty entry */
118                 if (dir[i].name[0] == FAT_ENTRY_FREE)
119                         continue;
120
121                 /* long name */
122                 if ((dir[i].attr & FAT_ATTR_MASK) == FAT_ATTR_LONG_NAME)
123                         continue;
124
125                 if ((dir[i].attr & (FAT_ATTR_VOLUME_ID | FAT_ATTR_DIR)) == FAT_ATTR_VOLUME_ID) {
126                         /* labels do not have file data */
127                         if (dir[i].cluster_high != 0 || dir[i].cluster_low != 0)
128                                 continue;
129
130                         dbg("found ATTR_VOLUME_ID id in root dir");
131                         return dir[i].name;
132                 }
133
134                 dbg("skip dir entry");
135         }
136
137         return NULL;
138 }
139
140 int volume_id_probe_vfat(struct volume_id *id, __u64 off)
141 {
142         struct vfat_super_block *vs;
143         struct vfat_dir_entry *dir;
144         __u16 sector_size;
145         __u16 dir_entries;
146         __u32 sect_count;
147         __u16 reserved;
148         __u32 fat_size;
149         __u32 root_cluster;
150         __u32 dir_size;
151         __u32 cluster_count;
152         __u32 fat_length;
153         __u64 root_start;
154         __u32 start_data_sect;
155         __u16 root_dir_entries;
156         __u8 *buf;
157         __u32 buf_size;
158         __u8 *label = NULL;
159         __u32 next;
160         int maxloop;
161
162         dbg("probing at offset 0x%llx", (unsigned long long) off);
163
164         vs = (struct vfat_super_block *) volume_id_get_buffer(id, off, 0x200);
165         if (vs == NULL)
166                 return -1;
167
168         /* believe only that's fat, don't trust the version
169          * the cluster_count will tell us
170          */
171         if (memcmp(vs->sysid, "NTFS", 4) == 0)
172                 return -1;
173
174         if (memcmp(vs->type.fat32.magic, "MSWIN", 5) == 0)
175                 goto valid;
176
177         if (memcmp(vs->type.fat32.magic, "FAT32   ", 8) == 0)
178                 goto valid;
179
180         if (memcmp(vs->type.fat.magic, "FAT16   ", 8) == 0)
181                 goto valid;
182
183         if (memcmp(vs->type.fat.magic, "MSDOS", 5) == 0)
184                 goto valid;
185
186         if (memcmp(vs->type.fat.magic, "FAT12   ", 8) == 0)
187                 goto valid;
188
189         /*
190          * There are old floppies out there without a magic, so we check
191          * for well known values and guess if it's a fat volume
192          */
193
194         /* boot jump address check */
195         if ((vs->boot_jump[0] != 0xeb || vs->boot_jump[2] != 0x90) &&
196              vs->boot_jump[0] != 0xe9)
197                 return -1;
198
199         /* heads check */
200         if (vs->heads == 0)
201                 return -1;
202
203         /* cluster size check*/ 
204         if (vs->sectors_per_cluster == 0 ||
205             (vs->sectors_per_cluster & (vs->sectors_per_cluster-1)))
206                 return -1;
207
208         /* media check */
209         if (vs->media < 0xf8 && vs->media != 0xf0)
210                 return -1;
211
212         /* fat count*/
213         if (vs->fats != 2)
214                 return -1;
215
216 valid:
217         /* sector size check */
218         sector_size = le16_to_cpu(vs->sector_size);
219         if (sector_size != 0x200 && sector_size != 0x400 &&
220             sector_size != 0x800 && sector_size != 0x1000)
221                 return -1;
222
223         dbg("sector_size 0x%x", sector_size);
224         dbg("sectors_per_cluster 0x%x", vs->sectors_per_cluster);
225
226         dir_entries = le16_to_cpu(vs->dir_entries);
227         reserved = le16_to_cpu(vs->reserved);
228         dbg("reserved 0x%x", reserved);
229
230         sect_count = le16_to_cpu(vs->sectors);
231         if (sect_count == 0)
232                 sect_count = le32_to_cpu(vs->total_sect);
233         dbg("sect_count 0x%x", sect_count);
234
235         fat_length = le16_to_cpu(vs->fat_length);
236         if (fat_length == 0)
237                 fat_length = le32_to_cpu(vs->type.fat32.fat32_length);
238         dbg("fat_length 0x%x", fat_length);
239
240         fat_size = fat_length * vs->fats;
241         dir_size = ((dir_entries * sizeof(struct vfat_dir_entry)) +
242                         (sector_size-1)) / sector_size;
243         dbg("dir_size 0x%x", dir_size);
244
245         cluster_count = sect_count - (reserved + fat_size + dir_size);
246         cluster_count /= vs->sectors_per_cluster;
247         dbg("cluster_count 0x%x", cluster_count);
248
249         if (cluster_count < FAT12_MAX) {
250                 strcpy(id->type_version, "FAT12");
251         } else if (cluster_count < FAT16_MAX) {
252                 strcpy(id->type_version, "FAT16");
253         } else {
254                 strcpy(id->type_version, "FAT32");
255                 goto fat32;
256         }
257
258         /* the label may be an attribute in the root directory */
259         root_start = (reserved + fat_size) * sector_size;
260         dbg("root dir start 0x%llx", (unsigned long long) root_start);
261         root_dir_entries = le16_to_cpu(vs->dir_entries);
262         dbg("expected entries 0x%x", root_dir_entries);
263
264         buf_size = root_dir_entries * sizeof(struct vfat_dir_entry);
265         buf = volume_id_get_buffer(id, off + root_start, buf_size);
266         if (buf == NULL)
267                 goto found;
268
269         dir = (struct vfat_dir_entry*) buf;
270
271         label = get_attr_volume_id(dir, root_dir_entries);
272
273         vs = (struct vfat_super_block *) volume_id_get_buffer(id, off, 0x200);
274         if (vs == NULL)
275                 return -1;
276
277         if (label != NULL && memcmp(label, "NO NAME    ", 11) != 0) {
278                 volume_id_set_label_raw(id, label, 11);
279                 volume_id_set_label_string(id, label, 11);
280         } else if (memcmp(vs->type.fat.label, "NO NAME    ", 11) != 0) {
281                 volume_id_set_label_raw(id, vs->type.fat.label, 11);
282                 volume_id_set_label_string(id, vs->type.fat.label, 11);
283         }
284         volume_id_set_uuid(id, vs->type.fat.serno, UUID_DOS);
285         goto found;
286
287 fat32:
288         /* FAT32 root dir is a cluster chain like any other directory */
289         buf_size = vs->sectors_per_cluster * sector_size;
290         root_cluster = le32_to_cpu(vs->type.fat32.root_cluster);
291         dbg("root dir cluster %u", root_cluster);
292         start_data_sect = reserved + fat_size;
293
294         next = root_cluster;
295         maxloop = 100;
296         while (--maxloop) {
297                 __u32 next_sect_off;
298                 __u64 next_off;
299                 __u64 fat_entry_off;
300                 int count;
301
302                 dbg("next cluster %u", next);
303                 next_sect_off = (next - 2) * vs->sectors_per_cluster;
304                 next_off = (start_data_sect + next_sect_off) * sector_size;
305                 dbg("cluster offset 0x%llx", (unsigned long long) next_off);
306
307                 /* get cluster */
308                 buf = volume_id_get_buffer(id, off + next_off, buf_size);
309                 if (buf == NULL)
310                         goto found;
311
312                 dir = (struct vfat_dir_entry*) buf;
313                 count = buf_size / sizeof(struct vfat_dir_entry);
314                 dbg("expected entries 0x%x", count);
315
316                 label = get_attr_volume_id(dir, count);
317                 if (label)
318                         break;
319
320                 /* get FAT entry */
321                 fat_entry_off = (reserved * sector_size) + (next * sizeof(__u32));
322                 buf = volume_id_get_buffer(id, off + fat_entry_off, buf_size);
323                 if (buf == NULL)
324                         goto found;
325
326                 /* set next cluster */
327                 next = le32_to_cpu(*((__u32 *) buf) & 0x0fffffff);
328                 if (next == 0)
329                         break;
330         }
331         if (maxloop == 0)
332                 dbg("reached maximum follow count of root cluster chain, give up");
333
334         vs = (struct vfat_super_block *) volume_id_get_buffer(id, off, 0x200);
335         if (vs == NULL)
336                 return -1;
337
338         if (label != NULL && memcmp(label, "NO NAME    ", 11) != 0) {
339                 volume_id_set_label_raw(id, label, 11);
340                 volume_id_set_label_string(id, label, 11);
341         } else if (memcmp(vs->type.fat32.label, "NO NAME    ", 11) != 0) {
342                 volume_id_set_label_raw(id, vs->type.fat32.label, 11);
343                 volume_id_set_label_string(id, vs->type.fat32.label, 11);
344         }
345         volume_id_set_uuid(id, vs->type.fat32.serno, UUID_DOS);
346
347 found:
348         volume_id_set_usage(id, VOLUME_ID_FILESYSTEM);
349         id->type = "vfat";
350
351         return 0;
352 }