summaryrefslogtreecommitdiff
path: root/fatfs/fat.c
diff options
context:
space:
mode:
authorMarcus Brinkmann <marcus@gnu.org>2002-12-03 20:52:59 +0000
committerMarcus Brinkmann <marcus@gnu.org>2002-12-03 20:52:59 +0000
commit8f48e6fa4324fc242af66ab0d49e467f98656f15 (patch)
treecc222f7c92b8aa3d267833a401e69b897aad92a2 /fatfs/fat.c
parentf56926743a89f4aa10302b5837aebaf5817a4e01 (diff)
Initial check-in.
Diffstat (limited to 'fatfs/fat.c')
-rw-r--r--fatfs/fat.c744
1 files changed, 744 insertions, 0 deletions
diff --git a/fatfs/fat.c b/fatfs/fat.c
new file mode 100644
index 00000000..4d3ba3da
--- /dev/null
+++ b/fatfs/fat.c
@@ -0,0 +1,744 @@
+/* fat.c - Support for FAT filesystems.
+ Copyright (C) 2002 Free Software Foundation, Inc.
+ Written by Marcus Brinkmann.
+
+ This file is part of the GNU Hurd.
+
+ The GNU Hurd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ The GNU Hurd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */
+
+#include <string.h>
+#include <error.h>
+#include <limits.h>
+#include <errno.h>
+#include <assert.h>
+#include <ctype.h>
+#include <time.h>
+
+#include <hurd/store.h>
+#include <hurd/diskfs.h>
+
+#include "fatfs.h"
+
+/* Unprocessed superblock. */
+struct boot_sector *sblock;
+
+/* Processed sblock info. */
+fat_t fat_type;
+size_t bytes_per_sector;
+size_t log2_bytes_per_sector;
+size_t sectors_per_cluster;
+size_t bytes_per_cluster;
+unsigned int log2_bytes_per_cluster;
+size_t sectors_per_fat;
+size_t total_sectors;
+size_t nr_of_root_dir_sectors;
+size_t first_root_dir_byte;
+size_t first_data_sector;
+vm_offset_t first_data_byte;
+size_t first_fat_sector;
+cluster_t nr_of_clusters;
+
+/* Hold this lock while converting times using gmtime. */
+spin_lock_t epoch_to_time_lock = SPIN_LOCK_INITIALIZER;
+
+/* Hold this lock while allocating a new cluster in the FAT. */
+spin_lock_t allocate_free_cluster_lock = SPIN_LOCK_INITIALIZER;
+
+/* Where to look for the next free cluster. This is meant to avoid
+ searching through a nearly full file system from the beginning at
+ every request. It would be better to use the field of the same
+ name in the fs_info block. 2 is the first data cluster in any
+ FAT. */
+cluster_t next_free_cluster = 2;
+
+
+/* Read the superblock. */
+void
+fat_read_sblock (void)
+{
+ int read;
+
+ sblock = malloc (sizeof (struct boot_sector));
+ store_read (store, 0, sizeof (struct boot_sector), (void **) &sblock, &read);
+
+ if (read_word(sblock->id) != BOOT_SECTOR_ID)
+ error (1, 0, "Could not find valid superblock");
+
+ /* Parse some important bits of the superblock. */
+
+ bytes_per_sector = read_word (sblock->bytes_per_sector);
+ switch (bytes_per_sector)
+ {
+ case 512:
+ log2_bytes_per_sector = 9;
+ break;
+
+ case 1024:
+ log2_bytes_per_sector = 10;
+ break;
+
+ case 2048:
+ log2_bytes_per_sector = 11;
+ break;
+
+ case 4096:
+ log2_bytes_per_sector = 12;
+ break;
+
+ default:
+ error (1, 0, "Invalid number of bytes per sector");
+ };
+
+ sectors_per_cluster = sblock->sectors_per_cluster;
+ if (sectors_per_cluster != 1 && sectors_per_cluster != 2
+ && sectors_per_cluster != 4 && sectors_per_cluster != 8
+ && sectors_per_cluster != 16 && sectors_per_cluster != 32
+ && sectors_per_cluster != 64 && sectors_per_cluster != 128)
+ error (1, 0, "Invalid number of sectors per cluster");
+
+ bytes_per_cluster = sectors_per_cluster << log2_bytes_per_sector;
+ switch (bytes_per_cluster)
+ {
+ case 512:
+ log2_bytes_per_cluster = 9;
+ break;
+
+ case 1024:
+ log2_bytes_per_cluster = 10;
+ break;
+
+ case 2048:
+ log2_bytes_per_cluster = 11;
+ break;
+
+ case 4096:
+ log2_bytes_per_cluster = 12;
+ break;
+
+ case 8192:
+ log2_bytes_per_cluster = 13;
+ break;
+
+ case 16384:
+ log2_bytes_per_cluster = 14;
+ break;
+
+ case 32768:
+ log2_bytes_per_cluster = 15;
+ break;
+
+ default:
+ error (1, 0, "Invalid number of bytes per cluster");
+ };
+
+ total_sectors = read_word (sblock->total_sectors_16)
+ ?: read_word (sblock->total_sectors_32);
+ if (total_sectors * bytes_per_sector > store->size)
+ error (1, 0, "Store is smaller then implied by metadata");
+ if (total_sectors == 0)
+ error (1, 0, "Number of total sectors is zero");
+
+ if (bytes_per_sector & (store->block_size - 1))
+ error (1, 0, "Block size of filesystem is not a multiple of the block size "
+ "of the store");
+
+ if (read_word (sblock->reserved_sectors) == 0)
+ error (1, 0, "Number of reserved sectors is zero");
+ if (sblock->nr_of_fat_tables == 0)
+ error (1, 0, "Number of FATs is zero");
+
+ sectors_per_fat = read_word (sblock->sectors_per_fat_16)
+ ?: read_word (sblock->compat.fat32.sectors_per_fat_32);
+ if (sectors_per_fat == 0)
+ error (1, 0, "Number of sectors per fat is zero");
+
+ nr_of_root_dir_sectors = ((read_word (sblock->nr_of_root_dirents) * FAT_DIR_REC_LEN)
+ - 1) / bytes_per_sector + 1;
+ if (nr_of_root_dir_sectors & (sectors_per_cluster - 1))
+ error (1, 0, "Number of root dir sectors is not a multiple of sectors_per_cluster");
+
+ first_root_dir_byte = (read_word (sblock->reserved_sectors)
+ + (sblock->nr_of_fat_tables * sectors_per_fat)) << log2_bytes_per_sector;
+ first_data_sector = (first_root_dir_byte >> log2_bytes_per_sector) + nr_of_root_dir_sectors;
+ first_data_byte = first_data_sector << log2_bytes_per_sector;
+
+ nr_of_clusters = (total_sectors - first_data_sector) / sectors_per_cluster;
+
+ if (nr_of_clusters < FAT12_MAX_NR_OF_CLUSTERS)
+ fat_type = FAT12;
+ else
+ {
+ if (nr_of_clusters < FAT16_MAX_NR_OF_CLUSTERS)
+ fat_type = FAT16;
+ else
+ fat_type = FAT32;
+ }
+
+ if (fat_type == FAT32 && read_word (sblock->compat.fat32.fs_version) != 0)
+ error (1, 0, "Incompatible file system version");
+
+ first_fat_sector = 0;
+ if (fat_type == FAT32 && read_word (sblock->compat.fat32.extension_flags) & 1<<7)
+ {
+ first_fat_sector = (read_word (sblock->compat.fat32.extension_flags) & 0x0f);
+ if (first_fat_sector > sblock->nr_of_fat_tables)
+ error (1, 0, "Active FAT table does not exist");
+ first_fat_sector *= sectors_per_fat;
+ }
+ first_fat_sector += read_word (sblock->reserved_sectors);
+}
+
+
+/* Write NEXT_CLUSTER in the FAT at position CLUSTER.
+ You must call this from inside diskfs_catch_exception.
+ Returns 0 (always succeeds). */
+error_t
+fat_write_next_cluster(cluster_t cluster, cluster_t next_cluster)
+{
+ loff_t fat_entry_offset;
+ cluster_t data;
+
+ /* First data cluster is cluster 2. */
+ assert (cluster >= 2 && cluster < nr_of_clusters + 2);
+
+ switch (fat_type)
+ {
+ case FAT12:
+ if (next_cluster == FAT_BAD_CLUSTER)
+ next_cluster = FAT12_BAD_CLUSTER;
+ else if (next_cluster == FAT_EOC)
+ next_cluster = FAT12_EOC;
+
+ fat_entry_offset = (cluster * 3) / 2;
+ data = read_word (fat_image + fat_entry_offset);
+ if (cluster & 1)
+ data = (data & 0xf) | ((next_cluster & 0xfff) << 4);
+ else
+ data = (data & 0xf000) | (next_cluster & 0xfff);
+
+ write_word (fat_image + fat_entry_offset, data);
+ break;
+
+ case FAT16:
+ if (next_cluster == FAT_BAD_CLUSTER)
+ next_cluster = FAT16_BAD_CLUSTER;
+ else if (next_cluster == FAT_EOC)
+ next_cluster = FAT16_EOC;
+
+ fat_entry_offset = cluster * 2;
+ write_word (fat_image + fat_entry_offset, next_cluster);
+ break;
+
+ case FAT32:
+ default: /* To silence gcc warning. */
+ if (next_cluster == FAT_BAD_CLUSTER)
+ next_cluster = FAT32_BAD_CLUSTER;
+ else if (next_cluster == FAT_EOC)
+ next_cluster = FAT32_EOC;
+
+ fat_entry_offset = cluster * 4;
+ write_dword (fat_image + fat_entry_offset, next_cluster & 0x0fffffff);
+ }
+
+ return 0;
+}
+
+/* Read the FAT entry at position CLUSTER into NEXT_CLUSTER.
+ You must call this from inside diskfs_catch_exception.
+ Returns 0 (always succeeds). */
+error_t
+fat_get_next_cluster(cluster_t cluster, cluster_t *next_cluster)
+{
+ loff_t fat_entry_offset;
+
+ /* First data cluster is cluster 2. */
+ assert (cluster >= 2 && cluster < nr_of_clusters + 2);
+
+ switch (fat_type)
+ {
+ case FAT12:
+ fat_entry_offset = (cluster * 3) / 2;
+ *next_cluster = read_word (fat_image + fat_entry_offset);
+ if (cluster & 1)
+ *next_cluster = *next_cluster >> 4;
+ else
+ *next_cluster &= 0xfff;
+
+ if (*next_cluster == FAT12_BAD_CLUSTER)
+ *next_cluster = FAT_BAD_CLUSTER;
+ else if (*next_cluster >= FAT12_EOC)
+ *next_cluster = FAT_EOC;
+ break;
+
+ case FAT16:
+ fat_entry_offset = cluster * 2;
+ *next_cluster = read_word (fat_image + fat_entry_offset);
+ if (*next_cluster == FAT16_BAD_CLUSTER)
+ *next_cluster = FAT_BAD_CLUSTER;
+ else if (*next_cluster >= FAT16_EOC)
+ *next_cluster = FAT_EOC;
+ break;
+
+ case FAT32:
+ default: /* To silence gcc warning. */
+ fat_entry_offset = cluster * 4;
+ *next_cluster = read_dword (fat_image + fat_entry_offset);
+ *next_cluster &= 0x0fffffff;
+ if (*next_cluster == FAT32_BAD_CLUSTER)
+ *next_cluster = FAT_BAD_CLUSTER;
+ else if (*next_cluster >= FAT32_EOC)
+ *next_cluster = FAT_EOC;
+ }
+
+ return 0;
+}
+
+/* Allocate a new cluster, write CONTENT into the FAT at this new
+ clusters position. At success, 0 is returned and CLUSTER contains
+ the cluster number allocated. Otherwise, ENOSPC is returned if the
+ filesystem is full.
+ You must call this from inside diskfs_catch_exception. */
+error_t
+fat_allocate_cluster (cluster_t content, cluster_t *cluster)
+{
+ error_t err = 0;
+ cluster_t old_next_free_cluster;
+ int wrapped = 0;
+ cluster_t found_cluster = FAT_FREE_CLUSTER;
+
+ assert (content != FAT_FREE_CLUSTER);
+
+ spin_lock (&allocate_free_cluster_lock);
+ old_next_free_cluster = next_free_cluster;
+
+ /* Loop over all clusters, starting from next_free_cluster and
+ wrapping if reaching the end of the FAT, until we either find an
+ unallocated cluster, or we have to give up because all clusters
+ are allocated. */
+ do
+ {
+ cluster_t next_free_content;
+
+ fat_get_next_cluster (next_free_cluster, &next_free_content);
+
+ if (next_free_content == FAT_FREE_CLUSTER)
+ found_cluster = next_free_cluster;
+
+ if (++next_free_cluster == nr_of_clusters + 2)
+ {
+ next_free_cluster = 2;
+ wrapped = 1;
+ }
+ }
+ while (found_cluster == FAT_FREE_CLUSTER
+ && !(wrapped && next_free_cluster == old_next_free_cluster));
+
+ if (found_cluster != FAT_FREE_CLUSTER)
+ {
+ *cluster = found_cluster;
+ fat_write_next_cluster(found_cluster, content);
+ }
+ else
+ err = ENOSPC;
+
+ spin_unlock(&allocate_free_cluster_lock);
+ return err;
+}
+
+/* Extend the cluster chain to maximum size or new_last_cluster,
+ whatever is less. If we reach the end of the file, and CREATE is
+ true, allocate new blocks until there is either no space on the
+ device or new_last_cluster are allocated. (new_last_cluster: 0 is
+ the first cluster of the file). */
+error_t
+fat_extend_chain (struct node *node, cluster_t new_last_cluster, int create)
+{
+ error_t err = 0;
+ struct disknode *dn = node->dn;
+ struct cluster_chain *table;
+ int offs;
+ cluster_t left, prev_cluster, cluster;
+
+ error_t allocate_new_table(struct cluster_chain **table)
+ {
+ struct cluster_chain *t;
+
+ t = *table;
+ *table = malloc (sizeof (struct cluster_chain));
+ if (!*table)
+ return ENOMEM;
+ (*table)->next = 0;
+ if (t)
+ dn->last = t->next = *table;
+ else
+ dn->last = dn->first = *table;
+ return 0;
+ }
+
+ spin_lock(&dn->chain_extension_lock);
+
+ /* If we already have what we need, or we have all clusters that are
+ available without allocating new ones, go out. */
+ if (new_last_cluster < dn->length_of_chain
+ || (!create && dn->chain_complete))
+ return 0;
+
+ left = new_last_cluster + 1 - dn->length_of_chain;
+
+ table = dn->last;
+ if (table)
+ {
+ offs = (dn->length_of_chain - 1) & (CLUSTERS_PER_TABLE - 1);
+ prev_cluster = table->cluster[offs];
+ }
+ else
+ {
+ offs = CLUSTERS_PER_TABLE - 1;
+ prev_cluster = FAT_FREE_CLUSTER;
+ }
+
+ while (left)
+ {
+ if (dn->chain_complete)
+ {
+ err = fat_allocate_cluster(FAT_EOC, &cluster);
+ if (err)
+ break;
+ if (prev_cluster)
+ fat_write_next_cluster(prev_cluster, cluster);
+ else
+ /* XXX: Also write this to dirent structure! */
+ dn->start_cluster = cluster;
+ }
+ else
+ {
+ if (prev_cluster != FAT_FREE_CLUSTER)
+ err = fat_get_next_cluster(prev_cluster, &cluster);
+ else
+ cluster = dn->start_cluster;
+ if (cluster == FAT_EOC || cluster == FAT_FREE_CLUSTER)
+ {
+ dn->chain_complete = 1;
+ if (create)
+ continue;
+ else
+ break;
+ }
+ }
+ prev_cluster = cluster;
+ offs++;
+ if (offs == CLUSTERS_PER_TABLE)
+ {
+ offs = 0;
+ err = allocate_new_table(&table);
+ if (err)
+ break;
+ }
+ table->cluster[offs] = cluster;
+ dn->length_of_chain++;
+ left--;
+ }
+
+ if (dn->length_of_chain << log2_bytes_per_cluster > node->allocsize)
+ node->allocsize = dn->length_of_chain << log2_bytes_per_cluster;
+
+ spin_unlock(&dn->chain_extension_lock);
+ return err;
+}
+
+/* Returns in DISK_CLUSTER the disk cluster corresponding to cluster
+ CLUSTER in NODE. If there is no such cluster yet, but CREATE is
+ true, then it is created, otherwise EINVAL is returned. */
+error_t
+fat_getcluster (struct node *node, cluster_t cluster, int create,
+ cluster_t *disk_cluster)
+{
+ error_t err = 0;
+ cluster_t chains_to_go = cluster >> LOG2_CLUSTERS_PER_TABLE;
+ cluster_t offs = cluster & (CLUSTERS_PER_TABLE - 1);
+ struct cluster_chain *chain;
+
+ if (cluster >= node->dn->length_of_chain)
+ {
+ err = fat_extend_chain (node, cluster, create);
+ if (err)
+ return err;
+ if (cluster >= node->dn->length_of_chain)
+ {
+ assert (!create);
+ return EINVAL;
+ }
+ }
+ chain = node->dn->first;
+ while (chains_to_go--)
+ {
+ assert (chain);
+ chain = chain->next;
+ }
+ assert (chain);
+ *disk_cluster = chain->cluster[offs];
+ return 0;
+}
+
+void
+fat_truncate_node (struct node *node, cluster_t clusters_to_keep)
+{
+ struct cluster_chain *next;
+ cluster_t count;
+ cluster_t offs;
+ cluster_t pos;
+
+ /* The root dir of a FAT12/16 fs is of fixed size, while the root
+ dir of a FAT32 fs must never decease to exist. */
+ assert (! (((fat_type == FAT12 || fat_type == FAT16) && node == diskfs_root_node)
+ || (fat_type == FAT32 && node == diskfs_root_node && clusters_to_keep == 0)));
+
+ /* Expand the cluster chain, because we have to know the complete tail. */
+ fat_extend_chain (node, FAT_EOC, 0);
+ if (clusters_to_keep == node->dn->length_of_chain)
+ return;
+ assert (clusters_to_keep < node->dn->length_of_chain);
+
+ /* Truncation happens here. */
+ next = node->dn->first;
+ if (clusters_to_keep == 0)
+ {
+ /* Deallocate the complete file. */
+ node->dn->start_cluster = 0;
+ pos = count = offs = 0;
+ }
+ else
+ {
+ count = (clusters_to_keep - 1) >> LOG2_CLUSTERS_PER_TABLE;
+ offs = (clusters_to_keep - 1) & (CLUSTERS_PER_TABLE - 1);
+ while (count-- > 0)
+ {
+ assert (next);
+ next = next->next;
+ }
+ assert (next);
+ fat_write_next_cluster (next->cluster[offs++], FAT_EOC);
+ pos = clusters_to_keep;
+ }
+
+ /* Purge dangling clusters. If we die here, scandisk will have to
+ clean up the remains. */
+ while (pos < node->dn->length_of_chain)
+ {
+ if (offs == CLUSTERS_PER_TABLE)
+ {
+ offs = 0;
+ next = next->next;
+ assert(next);
+ }
+ fat_write_next_cluster(next->cluster[offs++], 0);
+ pos++;
+ }
+
+ /* Free now unused tables. (Could be done in one run with the above.) */
+ next = node->dn->first;
+ if (clusters_to_keep != 0)
+ {
+ count = (clusters_to_keep - 1) >> LOG2_CLUSTERS_PER_TABLE;
+ offs = (clusters_to_keep - 1) & (CLUSTERS_PER_TABLE - 1);
+ while (count-- > 0)
+ {
+ assert (next);
+ next = next->next;
+ }
+ assert (next);
+ next = next->next;
+ }
+ while (next)
+ {
+ struct cluster_chain *next_next = next->next;
+ free (next);
+ next = next_next;
+ }
+}
+
+
+/* Count the number of free clusters in the FAT. */
+int
+fat_get_freespace (void)
+{
+ int free_clusters = 0;
+ cluster_t curr_cluster;
+ cluster_t next_cluster;
+ error_t err;
+
+ err = diskfs_catch_exception ();
+ if (!err)
+ {
+ /* First cluster is the 3rd entry in the FAT table. */
+ for (curr_cluster = 2; curr_cluster < nr_of_clusters + 2;
+ curr_cluster++)
+ {
+ fat_get_next_cluster (curr_cluster, &next_cluster);
+ if (next_cluster == FAT_FREE_CLUSTER)
+ free_clusters++;
+ }
+ }
+ diskfs_end_catch_exception ();
+
+ return free_clusters;
+}
+
+
+/* FILE must be a buffer with 13 characters. */
+void fat_to_unix_filename(const char *name, char *file)
+{
+ int npos;
+ int fpos = 0;
+ int ext = 0;
+
+ for (npos = 0; npos < 11; npos++)
+ {
+ if (name[npos] == ' ')
+ {
+ if (ext)
+ {
+ break;
+ }
+ else
+ {
+ file[fpos] = '.';
+ fpos++;
+ ext = 1;
+ while (npos < 7 && name[npos+1] == ' ') npos++;
+ }
+ }
+ else
+ {
+ file[fpos] = name[npos];
+ fpos++;
+ if (npos == 7)
+ {
+ file[fpos] = '.';
+ fpos++;
+ ext = 1;
+ }
+ }
+ }
+ if (ext && file[fpos-1] == '.')
+ file[fpos-1] = '\0';
+ else
+ file[fpos] = '\0';
+}
+
+void
+fat_from_unix_filename(char *fn, const char *un, int ul)
+{
+ int fp = 0;
+ int up = 0;
+ int ext = 0;
+
+ while (fp < 11)
+ {
+ if (up == ul)
+ {
+ /* We parsed the complete unix filename. */
+ while (fp < 11)
+ fn[fp++] = ' ';
+ }
+ else
+ {
+ if (!ext)
+ {
+ if (un[up] == '.')
+ {
+ while (fp < 8)
+ fn[fp++] = ' ';
+ ext = 1;
+ un++;
+ }
+ else if (fp == 8)
+ {
+ while (un[up++] != '.' && up < ul);
+ ext = 1;
+ }
+ else
+ fn[fp++] = toupper(un[ul++]);
+ }
+ else
+ {
+ if (un[up] == '.')
+ {
+ while (fp < 11)
+ fn[fp++] = ' ';
+ }
+ else
+ fn[fp++] = toupper(un[up++]);
+ }
+ }
+ }
+}
+
+
+/* Return Epoch-based time from a MSDOS time/date pair. */
+void
+fat_to_epoch (char *date, char *time, struct timespec *ts)
+{
+ struct tm tm;
+
+ /* Date format:
+ Bits 0-4: Day of month (1-31).
+ Bits 5-8: Month of year (1-12).
+ Bits 9-15: Count of years from 1980 (0-127).
+
+ Time format:
+ Bits 0-4: 2-second count (0-29).
+ Bits 5-10: Minutes (0-59).
+ Bits 11-15: Hours (0-23).
+ */
+
+ tm.tm_year = (read_word (date) >> 9) + 80;
+ tm.tm_mon = ((read_word (date) & 0x1ff) >> 5) - 1;
+ tm.tm_mday = read_word (date) & 0x1f;
+ tm.tm_hour = (read_word (time) >> 11);
+ tm.tm_min = (read_word (time) & 0x7ff) >> 5;
+ tm.tm_sec = read_word (time) & 0x1f;
+ tm.tm_isdst = 0;
+
+ ts->tv_sec = timegm (&tm);
+ ts->tv_nsec = 0;
+}
+
+/* Return MSDOS time/date pair from Epoch-based time. */
+void
+fat_from_epoch (char *date, char *time, time_t *tp)
+{
+ struct tm *tm;
+
+ spin_lock(&epoch_to_time_lock);
+ tm = gmtime (tp);
+
+ /* Date format:
+ Bits 0-4: Day of month (1-31).
+ Bits 5-8: Month of year (1-12).
+ Bits 9-15: Count of years from 1980 (0-127).
+
+ Time format:
+ Bits 0-4: 2-second count (0-29).
+ Bits 5-10: Minutes (0-59).
+ Bits 11-15: Hours (0-23).
+ */
+
+ write_word(date, tm->tm_mday | ((tm->tm_mon + 1) << 5)
+ | ((tm->tm_year - 80) << 9));
+ write_word(time, (tm->tm_hour << 11) | (tm->tm_min << 5)
+ | (tm->tm_sec >> 1));
+ spin_unlock(&epoch_to_time_lock);
+}