chiark / gitweb /
[PATCH] udev_volume_id: new version of volume_id
[elogind.git] / extras / volume_id / volume_id / fat / 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         vs = (struct vfat_super_block *) volume_id_get_buffer(id, off, 0x200);
163         if (vs == NULL)
164                 return -1;
165
166         /* believe only that's fat, don't trust the version
167          * the cluster_count will tell us
168          */
169         if (strncmp(vs->sysid, "NTFS", 4) == 0)
170                 return -1;
171
172         if (strncmp(vs->type.fat32.magic, "MSWIN", 5) == 0)
173                 goto valid;
174
175         if (strncmp(vs->type.fat32.magic, "FAT32   ", 8) == 0)
176                 goto valid;
177
178         if (strncmp(vs->type.fat.magic, "FAT16   ", 8) == 0)
179                 goto valid;
180
181         if (strncmp(vs->type.fat.magic, "MSDOS", 5) == 0)
182                 goto valid;
183
184         if (strncmp(vs->type.fat.magic, "FAT12   ", 8) == 0)
185                 goto valid;
186
187         /*
188          * There are old floppies out there without a magic, so we check
189          * for well known values and guess if it's a fat volume
190          */
191
192         /* boot jump address check */
193         if ((vs->boot_jump[0] != 0xeb || vs->boot_jump[2] != 0x90) &&
194              vs->boot_jump[0] != 0xe9)
195                 return -1;
196
197         /* heads check */
198         if (vs->heads == 0)
199                 return -1;
200
201         /* cluster size check*/ 
202         if (vs->sectors_per_cluster == 0 ||
203             (vs->sectors_per_cluster & (vs->sectors_per_cluster-1)))
204                 return -1;
205
206         /* media check */
207         if (vs->media < 0xf8 && vs->media != 0xf0)
208                 return -1;
209
210         /* fat count*/
211         if (vs->fats != 2)
212                 return -1;
213
214 valid:
215         /* sector size check */
216         sector_size = le16_to_cpu(vs->sector_size);
217         if (sector_size != 0x200 && sector_size != 0x400 &&
218             sector_size != 0x800 && sector_size != 0x1000)
219                 return -1;
220
221         dbg("sector_size 0x%x", sector_size);
222         dbg("sectors_per_cluster 0x%x", vs->sectors_per_cluster);
223
224         dir_entries = le16_to_cpu(vs->dir_entries);
225         reserved = le16_to_cpu(vs->reserved);
226         dbg("reserved 0x%x", reserved);
227
228         sect_count = le16_to_cpu(vs->sectors);
229         if (sect_count == 0)
230                 sect_count = le32_to_cpu(vs->total_sect);
231         dbg("sect_count 0x%x", sect_count);
232
233         fat_length = le16_to_cpu(vs->fat_length);
234         if (fat_length == 0)
235                 fat_length = le32_to_cpu(vs->type.fat32.fat32_length);
236         dbg("fat_length 0x%x", fat_length);
237
238         fat_size = fat_length * vs->fats;
239         dir_size = ((dir_entries * sizeof(struct vfat_dir_entry)) +
240                         (sector_size-1)) / sector_size;
241         dbg("dir_size 0x%x", dir_size);
242
243         cluster_count = sect_count - (reserved + fat_size + dir_size);
244         cluster_count /= vs->sectors_per_cluster;
245         dbg("cluster_count 0x%x", cluster_count);
246
247         if (cluster_count < FAT12_MAX) {
248                 strcpy(id->type_version, "FAT12");
249         } else if (cluster_count < FAT16_MAX) {
250                 strcpy(id->type_version, "FAT16");
251         } else {
252                 strcpy(id->type_version, "FAT32");
253                 goto fat32;
254         }
255
256         /* the label may be an attribute in the root directory */
257         root_start = (reserved + fat_size) * sector_size;
258         dbg("root dir start 0x%llx", root_start);
259         root_dir_entries = le16_to_cpu(vs->dir_entries);
260         dbg("expected entries 0x%x", root_dir_entries);
261
262         buf_size = root_dir_entries * sizeof(struct vfat_dir_entry);
263         buf = volume_id_get_buffer(id, off + root_start, buf_size);
264         if (buf == NULL)
265                 goto found;
266
267         dir = (struct vfat_dir_entry*) buf;
268
269         label = get_attr_volume_id(dir, root_dir_entries);
270
271         vs = (struct vfat_super_block *) volume_id_get_buffer(id, off, 0x200);
272         if (vs == NULL)
273                 return -1;
274
275         if (label != NULL && strncmp(label, "NO NAME    ", 11) != 0) {
276                 volume_id_set_label_raw(id, label, 11);
277                 volume_id_set_label_string(id, label, 11);
278         } else if (strncmp(vs->type.fat.label, "NO NAME    ", 11) != 0) {
279                 volume_id_set_label_raw(id, vs->type.fat.label, 11);
280                 volume_id_set_label_string(id, vs->type.fat.label, 11);
281         }
282         volume_id_set_uuid(id, vs->type.fat.serno, UUID_DOS);
283         goto found;
284
285 fat32:
286         /* FAT32 root dir is a cluster chain like any other directory */
287         buf_size = vs->sectors_per_cluster * sector_size;
288         root_cluster = le32_to_cpu(vs->type.fat32.root_cluster);
289         dbg("root dir cluster %u", root_cluster);
290         start_data_sect = reserved + fat_size;
291
292         next = root_cluster;
293         maxloop = 100;
294         while (--maxloop) {
295                 __u32 next_sect_off;
296                 __u64 next_off;
297                 __u64 fat_entry_off;
298                 int count;
299
300                 dbg("next cluster %u", next);
301                 next_sect_off = (next - 2) * vs->sectors_per_cluster;
302                 next_off = (start_data_sect + next_sect_off) * sector_size;
303                 dbg("cluster offset 0x%llx", next_off);
304
305                 /* get cluster */
306                 buf = volume_id_get_buffer(id, off + next_off, buf_size);
307                 if (buf == NULL)
308                         goto found;
309
310                 dir = (struct vfat_dir_entry*) buf;
311                 count = buf_size / sizeof(struct vfat_dir_entry);
312                 dbg("expected entries 0x%x", count);
313
314                 label = get_attr_volume_id(dir, count);
315                 if (label)
316                         break;
317
318                 /* get FAT entry */
319                 fat_entry_off = (reserved * sector_size) + (next * sizeof(__u32));
320                 buf = volume_id_get_buffer(id, off + fat_entry_off, buf_size);
321                 if (buf == NULL)
322                         goto found;
323
324                 /* set next cluster */
325                 next = le32_to_cpu(*((__u32 *) buf) & 0x0fffffff);
326                 if (next == 0)
327                         break;
328         }
329         if (maxloop == 0)
330                 dbg("reached maximum follow count of root cluster chain, give up");
331
332         vs = (struct vfat_super_block *) volume_id_get_buffer(id, off, 0x200);
333         if (vs == NULL)
334                 return -1;
335
336         if (label != NULL && strncmp(label, "NO NAME    ", 11) != 0) {
337                 volume_id_set_label_raw(id, label, 11);
338                 volume_id_set_label_string(id, label, 11);
339         } else if (strncmp(vs->type.fat32.label, "NO NAME    ", 11) != 0) {
340                 volume_id_set_label_raw(id, vs->type.fat32.label, 11);
341                 volume_id_set_label_string(id, vs->type.fat32.label, 11);
342         }
343         volume_id_set_uuid(id, vs->type.fat32.serno, UUID_DOS);
344
345 found:
346         volume_id_set_usage(id, VOLUME_ID_DISKLABEL);
347         id->type = "vfat";
348
349         return 0;
350 }