2 * fat.handler - FAT12/16/32 filesystem handler
4 * Copyright © 2006 Marek Szyprowski
5 * Copyright © 2007-2008 The AROS Development Team
7 * This program is free software; you can redistribute it and/or modify it
8 * under the same terms as AROS itself.
13 #include <aros/macros.h>
14 #include <exec/errors.h>
15 #include <exec/types.h>
17 #include <dos/dosextens.h>
18 #include <dos/filehandler.h>
20 #include <proto/exec.h>
21 #include <proto/dos.h>
23 #include <clib/macros.h>
29 #include "fat_protos.h"
32 #define DEBUG DEBUG_MISC
35 /* helper function to get the location of a fat entry for a cluster. it used
36 * to be a define until it got too crazy */
37 static UBYTE
*GetFatEntryPtr(struct FSSuper
*sb
, ULONG offset
, struct cache_block
**rb
) {
38 ULONG entry_cache_block
= offset
>> sb
->fat_cachesize_bits
;
39 ULONG entry_cache_offset
= offset
& (sb
->fat_cachesize
- 1);
41 /* if the target cluster is not within the currently loaded chunk of fat,
42 * we need to get the right data in */
43 if (sb
->fat_cache_block
!= entry_cache_block
) {
44 D(bug("[fat] loading %ld FAT sectors starting at sector %ld\n", sb
->fat_blocks_count
, entry_cache_block
<< (sb
->fat_cachesize_bits
- sb
->sectorsize_bits
)));
45 /* put the old ones back */
46 if (sb
->fat_cache_block
!= 0xffffffff)
47 cache_put_blocks(sb
->cache
, sb
->fat_blocks
, sb
->fat_blocks_count
, 0);
50 cache_get_blocks(sb
->cache
,
51 sb
->first_device_sector
+ sb
->first_fat_sector
+
52 (entry_cache_block
<< (sb
->fat_cachesize_bits
- sb
->sectorsize_bits
)),
57 /* remember where we are for next time */
58 sb
->fat_cache_block
= entry_cache_block
;
61 /* give the block back if they asked for it (needed to mark the block
62 * dirty if they're writing */
64 *rb
= sb
->fat_blocks
[entry_cache_offset
>> sb
->sectorsize_bits
];
66 /* compute the pointer location and return it */
67 return sb
->fat_blocks
[entry_cache_offset
>> sb
->sectorsize_bits
]->data
+
68 (entry_cache_offset
& (sb
->sectorsize
-1));
71 /* FAT12 has, as the name suggests, 12-bit FAT entries. This means that two
72 * entries are condensed into three bytes, like so:
74 * entry: aaaaaaaa aaaabbbb bbbbbbbb
75 * bytes: xxxxxxxx xxxxxxxx xxxxxxxx
77 * To get at the entry we want, we find and grab the word starting at either
78 * byte 0 or 1 of the three-byte set, then shift up or down as needed. FATdoc
79 * 1.03 p16-17 describes the method
81 * The only tricky bit is if the word falls such that the first byte is the
82 * last byte of the block and the second byte is the first byte of the next
83 * block. Since our block data are stored within cache block structures, a
84 * simple cast won't do (hell, the second block may not even be in memory if
85 * we're at the end of the FAT cache). So we get it a byte at a time, and
86 * build the word ourselves.
88 static ULONG
GetFat12Entry(struct FSSuper
*sb
, ULONG n
) {
89 ULONG offset
= n
+ n
/2;
92 if ((offset
& (sb
->sectorsize
-1)) == sb
->sectorsize
-1) {
93 D(bug("[fat] fat12 cluster pair on block boundary, compensating\n"));
95 val
= *GetFatEntryPtr(sb
, offset
+ 1, NULL
) << 8;
96 val
|= *GetFatEntryPtr(sb
, offset
, NULL
);
99 val
= AROS_LE2WORD(*((UWORD
*) GetFatEntryPtr(sb
, offset
, NULL
)));
110 * FAT16 and FAT32, on the other hand, have nice neat entry widths, so simple
111 * word/long casts are fine. There's also no chance that the entry can be
112 * split across blocks. Why can't everything be this simple?
114 static ULONG
GetFat16Entry(struct FSSuper
*sb
, ULONG n
) {
115 return AROS_LE2WORD(*((UWORD
*) GetFatEntryPtr(sb
, n
<< 1, NULL
)));
118 static ULONG
GetFat32Entry(struct FSSuper
*sb
, ULONG n
) {
119 return AROS_LE2LONG(*((ULONG
*) GetFatEntryPtr(sb
, n
<< 2, NULL
)));
122 static void SetFat12Entry(struct FSSuper
*sb
, ULONG n
, ULONG val
) {
123 struct cache_block
*b
;
124 ULONG offset
= n
+ n
/2;
125 BOOL boundary
= FALSE
;
128 if ((offset
& (sb
->sectorsize
-1)) == sb
->sectorsize
-1) {
131 D(bug("[fat] fat12 cluster pair on block boundary, compensating\n"));
133 newval
= *GetFatEntryPtr(sb
, offset
+ 1, NULL
) << 8;
134 newval
|= *GetFatEntryPtr(sb
, offset
, NULL
);
137 fat
= (UWORD
*) GetFatEntryPtr(sb
, offset
, &b
);
138 newval
= AROS_LE2WORD(*fat
);
143 newval
= (newval
& 0xf) | val
;
146 newval
= (newval
& 0xf000) | val
;
150 /* XXX ideally we'd mark both blocks dirty at the same time or only do
151 * it once if they're the same block. unfortunately b is essentially
152 * invalid after a call to GetFatEntryPtr, as it may have swapped the
153 * previous cache out. This is probably safe enough. */
154 *GetFatEntryPtr(sb
, offset
+1, &b
) = newval
>> 8;
155 cache_mark_block_dirty(sb
->cache
, b
);
156 *GetFatEntryPtr(sb
, offset
, &b
) = newval
& 0xff;
157 cache_mark_block_dirty(sb
->cache
, b
);
160 *fat
= AROS_WORD2LE(newval
);
161 cache_mark_block_dirty(sb
->cache
, b
);
165 static void SetFat16Entry(struct FSSuper
*sb
, ULONG n
, ULONG val
) {
166 struct cache_block
*b
;
168 *((UWORD
*) GetFatEntryPtr(sb
, n
<< 1, &b
)) = AROS_WORD2LE((UWORD
) val
);
170 cache_mark_block_dirty(sb
->cache
, b
);
173 static void SetFat32Entry(struct FSSuper
*sb
, ULONG n
, ULONG val
) {
174 struct cache_block
*b
;
175 ULONG
*fat
= (ULONG
*) GetFatEntryPtr(sb
, n
<< 2, &b
);
177 *fat
= (*fat
& 0xf0000000) | val
;
179 cache_mark_block_dirty(sb
->cache
, b
);
182 LONG
ReadFATSuper(struct FSSuper
*sb
) {
183 struct DosEnvec
*de
= BADDR(glob
->fssm
->fssm_Environ
);
185 ULONG bsize
= de
->de_SizeBlock
* 4;
186 struct FATBootSector
*boot
;
187 BOOL invalid
= FALSE
;
190 D(bug("[fat] reading boot sector\n"));
192 boot
= AllocMem(bsize
, MEMF_ANY
);
194 return ERROR_NO_FREE_STORE
;
196 * Read the boot sector. We go direct because we don't have a cache yet,
197 * and can't create one until we know the sector size, which is held in
198 * the boot sector. In practice it doesn't matter - we're going use this
199 * once and once only.
202 sb
->first_device_sector
= de
->de_BlocksPerTrack
*
205 D(bug("[fat] boot sector at sector %ld\n", sb
->first_device_sector
));
207 if ((err
= AccessDisk(FALSE
, sb
->first_device_sector
, 1, bsize
, (UBYTE
*)boot
)) != 0) {
208 D(bug("[fat] couldn't read boot block (%ld)\n", err
));
209 FreeMem(boot
, bsize
);
213 D(bug("\tBoot sector:\n"));
215 sb
->sectorsize
= AROS_LE2WORD(boot
->bpb_bytes_per_sect
);
216 sb
->sectorsize_bits
= log2(sb
->sectorsize
);
217 D(bug("\tSectorSize = %ld\n", sb
->sectorsize
));
218 D(bug("\tSectorSize Bits = %ld\n", sb
->sectorsize_bits
));
220 sb
->cluster_sectors
= boot
->bpb_sect_per_clust
;
221 sb
->clustersize
= sb
->sectorsize
* boot
->bpb_sect_per_clust
;
222 sb
->clustersize_bits
= log2(sb
->clustersize
);
223 sb
->cluster_sectors_bits
= sb
->clustersize_bits
- sb
->sectorsize_bits
;
225 D(bug("\tSectorsPerCluster = %ld\n", (ULONG
)boot
->bpb_sect_per_clust
));
226 D(bug("\tClusterSize = %ld\n", sb
->clustersize
));
227 D(bug("\tClusterSize Bits = %ld\n", sb
->clustersize_bits
));
228 D(bug("\tCluster Sectors Bits = %ld\n", sb
->cluster_sectors_bits
));
230 sb
->first_fat_sector
= AROS_LE2WORD(boot
->bpb_rsvd_sect_count
);
231 D(bug("\tFirst FAT Sector = %ld\n", sb
->first_fat_sector
));
233 if (boot
->bpb_fat_size_16
!= 0)
234 sb
->fat_size
= AROS_LE2WORD(boot
->bpb_fat_size_16
);
236 sb
->fat_size
= AROS_LE2LONG(boot
->type
.fat32
.bpb_fat_size_32
);
237 D(bug("\tFAT Size = %ld\n", sb
->fat_size
));
239 if (boot
->bpb_total_sectors_16
!= 0)
240 sb
->total_sectors
= AROS_LE2WORD(boot
->bpb_total_sectors_16
);
242 sb
->total_sectors
= AROS_LE2LONG(boot
->bpb_total_sectors_32
);
243 D(bug("\tTotal Sectors = %ld\n", sb
->total_sectors
));
245 sb
->rootdir_sectors
= ((AROS_LE2WORD(boot
->bpb_root_entries_count
) * sizeof(struct FATDirEntry
)) + (sb
->sectorsize
- 1)) >> sb
->sectorsize_bits
;
246 D(bug("\tRootDir Sectors = %ld\n", sb
->rootdir_sectors
));
248 sb
->data_sectors
= sb
->total_sectors
- (sb
->first_fat_sector
+ (boot
->bpb_num_fats
* sb
->fat_size
) + sb
->rootdir_sectors
);
249 D(bug("\tData Sectors = %ld\n", sb
->data_sectors
));
251 sb
->clusters_count
= sb
->data_sectors
>> sb
->cluster_sectors_bits
;
252 D(bug("\tClusters Count = %ld\n", sb
->clusters_count
));
254 sb
->first_rootdir_sector
= sb
->first_fat_sector
+ (boot
->bpb_num_fats
* sb
->fat_size
);
255 D(bug("\tFirst RootDir Sector = %ld\n", sb
->first_rootdir_sector
));
257 sb
->first_data_sector
= sb
->first_fat_sector
+ (boot
->bpb_num_fats
* sb
->fat_size
) + sb
->rootdir_sectors
;
258 D(bug("\tFirst Data Sector = %ld\n", sb
->first_data_sector
));
260 sb
->free_clusters
= 0xffffffff;
262 /* check if disk is in fact FAT filesystem */
264 /* valid sector size: 512, 1024, 2048, 4096 */
265 if (sb
->sectorsize
!= 512 && sb
->sectorsize
!= 1024 && sb
->sectorsize
!= 2048 && sb
->sectorsize
!= 4096)
268 /* valid bpb_sect_per_clust: 1, 2, 4, 8, 16, 32, 64, 128 */
269 if ((boot
->bpb_sect_per_clust
& (boot
->bpb_sect_per_clust
- 1)) != 0 || boot
->bpb_sect_per_clust
== 0 || boot
->bpb_sect_per_clust
> 128)
272 /* valid cluster size: 512, 1024, 2048, 4096, 8192, 16k, 32k, 64k */
273 if (sb
->clustersize
> 64 * 1024)
276 if (sb
->first_fat_sector
== 0)
279 if (boot
->bpb_num_fats
== 0)
282 if (boot
->bpb_media
< 0xF0)
285 /* FAT "signature" */
286 if (boot
->bpb_signature
[0] != 0x55 || boot
->bpb_signature
[1] != 0xaa)
290 D(bug("\tInvalid FAT Boot Sector\n"));
291 FreeMem(boot
, bsize
);
292 return ERROR_NOT_A_DOS_DISK
;
294 end
= 0xFFFFFFFF / sb
->sectorsize
;
295 if ((sb
->first_device_sector
+ sb
->total_sectors
- 1 > end
) && (glob
->readcmd
== CMD_READ
)) {
296 D(bug("\tDevice is too large\n"));
297 FreeMem(boot
, bsize
);
298 return IOERR_BADADDRESS
;
301 sb
->cache
= cache_new(64, 256, sb
->sectorsize
, CACHE_WRITETHROUGH
);
303 if (sb
->clusters_count
< 4085) {
304 D(bug("\tFAT12 filesystem detected\n"));
306 sb
->eoc_mark
= 0x0FF8;
307 sb
->func_get_fat_entry
= GetFat12Entry
;
308 sb
->func_set_fat_entry
= SetFat12Entry
;
310 else if (sb
->clusters_count
< 65525) {
311 D(bug("\tFAT16 filesystem detected\n"));
313 sb
->eoc_mark
= 0xFFF8;
314 sb
->func_get_fat_entry
= GetFat16Entry
;
315 sb
->func_set_fat_entry
= SetFat16Entry
;
318 D(bug("\tFAT32 filesystem detected\n"));
320 sb
->eoc_mark
= 0x0FFFFFF8;
321 sb
->func_get_fat_entry
= GetFat32Entry
;
322 sb
->func_set_fat_entry
= SetFat32Entry
;
325 /* setup the FAT cache and load the first blocks */
326 sb
->fat_cachesize
= 4096;
327 sb
->fat_cachesize_bits
= log2(sb
->fat_cachesize
);
328 sb
->fat_cache_block
= 0xffffffff;
330 sb
->fat_blocks_count
= MIN(sb
->fat_size
, sb
->fat_cachesize
>> sb
->sectorsize_bits
);
331 sb
->fat_blocks
= AllocVecPooled(glob
->mempool
, sizeof(struct cache_block
*) * sb
->fat_blocks_count
);
333 if (sb
->type
!= 32) { /* FAT 12/16 */
334 /* setup volume id */
335 sb
->volume_id
= AROS_LE2LONG(boot
->type
.fat16
.bs_volid
);
337 /* location of root directory */
338 sb
->rootdir_cluster
= 0;
339 sb
->rootdir_sector
= sb
->first_rootdir_sector
;
342 /* setup volume id */
343 sb
->volume_id
= AROS_LE2LONG(boot
->type
.fat32
.bs_volid
);
345 /* location of root directory */
346 sb
->rootdir_cluster
= AROS_LE2LONG(boot
->type
.fat32
.bpb_root_cluster
);
347 sb
->rootdir_sector
= 0;
350 D(bug("[fat] rootdir at cluster %ld sector %ld\n", sb
->rootdir_cluster
, sb
->rootdir_sector
));
352 if (GetVolumeInfo(sb
, &(sb
->volume
)) != 0) {
354 UBYTE
*uu
= (void *)&sb
->volume_id
;
360 sb
->volume
.name
[i
++]='-';
363 sb
->volume
.name
[i
++] = (d
< 10) ? '0' + d
: 'A' - 10 + d
;
364 d
= ((*uu
) & 0xf0)>>4;
365 sb
->volume
.name
[i
++] = (d
< 10) ? '0' + d
: 'A' - 10 + d
;
370 sb
->volume
.name
[i
] = '\0';
371 sb
->volume
.name
[0] = 9;
374 NEWLIST(&(sb
->locks
));
376 sb
->root_lock
.dir_cluster
= FAT_ROOTDIR_MARK
;
377 sb
->root_lock
.dir_entry
= FAT_ROOTDIR_MARK
;
378 sb
->root_lock
.access
= SHARED_LOCK
;
379 sb
->root_lock
.first_cluster
= 0;
380 sb
->root_lock
.attr
= ATTR_DIRECTORY
;
381 sb
->root_lock
.size
= 0;
382 CopyMem(sb
->volume
.name
, sb
->root_lock
.name
, sb
->volume
.name
[0]+1);
383 NEWLIST(&sb
->root_lock
.locks
);
385 NEWLIST(&(sb
->notifies
));
387 D(bug("\tFAT Filesystem succesfully detected.\n"));
388 FreeMem(boot
, bsize
);
392 LONG
GetVolumeInfo(struct FSSuper
*sb
, struct VolumeInfo
*volume
) {
398 D(bug("[fat] searching root directory for volume name\n"));
400 /* search the directory for the volume id entry. it would've been nice to
401 * just use GetNextDirEntry but I didn't want a flag or something to tell
402 * it not to skip the volume name */
403 InitDirHandle(sb
, 0, &dh
, FALSE
);
405 while ((err
= GetDirEntry(&dh
, dh
.cur_index
+ 1, &de
)) == 0) {
407 /* match the volume id entry */
408 if ((de
.e
.entry
.attr
& ATTR_VOLUME_ID_MASK
) == ATTR_VOLUME_ID
) {
409 D(bug("[fat] found volume id entry %ld\n", dh
.cur_index
));
411 /* copy the name in. volume->name is a BSTR */
413 volume
->name
[1] = de
.e
.entry
.name
[0];
414 volume
->name
[12] = '\0';
416 for (i
= 1; i
< 11; i
++)
417 volume
->name
[i
+1] = tolower(de
.e
.entry
.name
[i
]);
419 for (i
= 10; i
> 1; i
--)
420 if (volume
->name
[i
+1] == ' ')
421 volume
->name
[i
+1] = '\0';
423 volume
->name
[0] = strlen(&(volume
->name
[1]));
425 /* get the volume creation date date too */
426 ConvertFATDate(de
.e
.entry
.create_date
, de
.e
.entry
.create_time
, &volume
->create_time
);
428 D(bug("[fat] volume name is '%s'\n", &(volume
->name
[1])));
433 /* bail out if we hit the end of the dir */
434 if (de
.e
.entry
.name
[0] == 0x00) {
435 D(bug("[fat] found end-of-directory marker, volume name entry not found\n"));
436 err
= ERROR_OBJECT_NOT_FOUND
;
441 ReleaseDirHandle(&dh
);
445 LONG
SetVolumeName(struct FSSuper
*sb
, UBYTE
*name
) {
451 D(bug("[fat] searching root directory for volume name\n"));
453 /* search the directory for the volume id entry. it would've been nice to
454 * just use GetNextDirEntry but I didn't want a flag or something to tell
455 * it not to skip the volume name */
456 InitDirHandle(sb
, 0, &dh
, FALSE
);
458 while ((err
= GetDirEntry(&dh
, dh
.cur_index
+ 1, &de
)) == 0) {
460 /* match the volume id entry */
461 if ((de
.e
.entry
.attr
& ATTR_LONG_NAME_MASK
) == ATTR_VOLUME_ID
) {
462 D(bug("[fat] found volume id entry %ld\n", dh
.cur_index
));
464 /* copy the name in. name is a BSTR */
465 de
.e
.entry
.name
[0] = name
[1];
466 for (i
= 1; i
< 11; i
++)
468 de
.e
.entry
.name
[i
] = tolower(name
[i
+1]);
470 de
.e
.entry
.name
[i
] = ' ';
472 if ((err
= UpdateDirEntry(&de
)) != 0) {
473 D(bug("[fat] couldn't change volume name\n"));
477 sb
->volume
.name
[0] = name
[0] < 10 ? name
[0] : 10;
478 CopyMem(&name
[1], &(sb
->volume
.name
[1]), sb
->volume
.name
[0]);
479 sb
->volume
.name
[sb
->volume
.name
[0]+1] = '\0';
481 D(bug("[fat] new volume name is '%s'\n", &(sb
->volume
.name
[1])));
486 /* bail out if we hit the end of the dir */
487 if (de
.e
.entry
.name
[0] == 0x00) {
488 D(bug("[fat] found end-of-directory marker, volume name entry not found\n"));
489 err
= ERROR_OBJECT_NOT_FOUND
;
494 ReleaseDirHandle(&dh
);
498 LONG
FindFreeCluster(struct FSSuper
*sb
, ULONG
*rcluster
) {
502 * XXX this implementation is extremely naive. things we
503 * could do to make it better:
505 * - don't start looking for a free cluster at the start
506 * each time. start from the current cluster and wrap
507 * around when we hit the end
508 * - track where we last found a free cluster and start
510 * - allocate several contiguous clusters at a time to
511 * reduce fragmentation
514 for (cluster
= 2; cluster
< sb
->clusters_count
&& GET_NEXT_CLUSTER(sb
, cluster
) != 0; cluster
++);
516 if (cluster
== sb
->clusters_count
) {
517 D(bug("[fat] no more free clusters, we're out of space\n"));
518 return ERROR_DISK_FULL
;
521 D(bug("[fat] found free cluster %ld\n", cluster
));
528 void FreeFATSuper(struct FSSuper
*sb
) {
529 D(bug("\tRemoving Super Block from memory\n"));
530 cache_free(sb
->cache
);
531 FreeVecPooled(glob
->mempool
, sb
->fat_blocks
);
532 sb
->fat_blocks
= NULL
;
535 LONG
CompareFATSuper(struct FSSuper
*s1
, struct FSSuper
*s2
) {
538 if ((res
= memcmp(s1
->volume
.name
, s2
->volume
.name
, s1
->volume
.name
[0])) != 0)
541 return s1
->volume_id
- s2
->volume_id
;
545 /* see how many unused clusters are available */
546 void CountFreeClusters(struct FSSuper
*sb
) {
550 /* don't calculate this if we already have it */
551 if (sb
->free_clusters
!= 0xffffffff)
554 /* loop over all the data clusters */
555 for (cluster
= 2; cluster
< sb
->clusters_count
+ 2; cluster
++)
557 /* record the free ones */
558 if (GET_NEXT_CLUSTER(sb
, cluster
) == 0)
561 /* put the value away for later */
562 sb
->free_clusters
= free
;
564 D(bug("\tfree clusters: %ld\n", free
));
567 static const UWORD mdays
[] = { 0, 31, 59, 90, 120, 151, 181, 212, 143, 273, 304, 334 };
569 void ConvertFATDate(UWORD date
, UWORD time
, struct DateStamp
*ds
) {
570 UBYTE year
, month
, day
, hours
, mins
, secs
;
573 /* date bits: yyyy yyym mmmd dddd */
574 year
= (date
& 0xfe00) >> 9; /* bits 15-9 */
575 month
= (date
& 0x01e0) >> 5; /* bits 8-5 */
576 day
= date
& 0x001f; /* bits 4-0 */
578 /* time bits: hhhh hmmm mmms ssss */
579 hours
= (time
& 0xf800) >> 11; /* bits 15-11 */
580 mins
= (time
& 0x07e0) >> 5; /* bits 8-5 */
581 secs
= time
& 0x001f; /* bits 4-0 */
583 D(bug("[fat] converting fat date: year %d month %d day %d hours %d mins %d secs %d\n", year
, month
, day
, hours
, mins
, secs
));
585 /* number of leap years in before this year. note this is only dividing by
586 * four, which is fine because FAT dates range 1980-2107. The only year in
587 * that range that is divisible by four but not a leap year is 2100. If
588 * this code is still being used then, feel free to fix it :) */
591 /* if this year is a leap year and it's March or later, adjust for this
593 if (year
& 0x03 && month
>= 3)
596 /* calculate days since 1978-01-01 (DOS epoch):
597 * 730 days in 1978+1979, getting us to the FAT epoch 1980-01-01
600 * days in all the months before this one
601 * day of this month */
602 ds
->ds_Days
= 730 + year
* 365 + nleap
+ mdays
[month
-1] + day
-1;
604 /* minutes since midnight */
605 ds
->ds_Minute
= hours
* 60 + mins
;
607 /* 1/50 sec ticks. FAT dates are 0-29, so we have to multiply them by two
609 ds
->ds_Tick
= (secs
<< 1) * TICKS_PER_SECOND
;
611 D(bug("[fat] converted fat date: days %ld minutes %ld ticks %ld\n", ds
->ds_Days
, ds
->ds_Minute
, ds
->ds_Tick
));
614 void ConvertAROSDate(struct DateStamp ds
, UWORD
*date
, UWORD
*time
) {
615 UBYTE year
, month
, day
, hours
, mins
, secs
;
618 /* converting no. of days since 1978-01-01 (DOS epoch) to
619 * years/months/days since FAT epoch (1980-01-01) */
621 /* subtract 730 days in 1978/1979 */
624 /* years are 365 days */
625 year
= ds
.ds_Days
/ 365;
626 ds
.ds_Days
-= year
* 365;
628 /* leap years. same algorithm as above. get the number of leap years
629 * before this year, and subtract that many days */
630 ds
.ds_Days
-= year
>> 2;
632 /* figure out if we need to adjust for a leap year this year. day 60 is
634 leap
= (year
& 0x03 && ds
.ds_Days
>= 60);
636 /* find the month by checking it against the days-in-month array */
637 for (month
= 1; month
< 12 && ds
.ds_Days
> mdays
[month
]; month
++);
639 /* day of month is whatever's left (+1, since we count from the 1st) */
640 day
= ds
.ds_Days
- mdays
[month
- 1] + 1;
642 /* subtract a day if we're after march in a leap year */
650 day
= ds
.ds_Days
- mdays
[month
] + 1;
654 /* time is easy by comparison. convert minutes since midnight to
655 * hours and seconds */
656 hours
= ds
.ds_Minute
/ 60;
657 mins
= ds
.ds_Minute
- (hours
* 60);
659 /* FAT seconds are 0-29 */
660 secs
= (ds
.ds_Tick
/ TICKS_PER_SECOND
) >> 1;
662 /* all that remains is to bit-encode the whole lot */
664 /* date bits: yyyy yyym mmmd dddd */
665 *date
= (((ULONG
) year
) << 9) | (((ULONG
) month
) << 5) | day
;
667 /* time bits: hhhh hmmm mmms ssss */
668 *time
= (((ULONG
) hours
) << 11) | (((ULONG
) mins
) << 5) | secs
;