| /* |
| libparted |
| Copyright (C) 1998, 1999, 2000, 2002, 2007 Free Software Foundation, Inc. |
| |
| This program 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 of the License, or |
| (at your option) any later version. |
| |
| This program 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
| */ |
| |
| #include <config.h> |
| #include "fat.h" |
| |
| #ifndef DISCOVER_ONLY |
| |
| /* returns the minimum size of clusters for a given file system type */ |
| PedSector |
| fat_min_cluster_size (FatType fat_type) { |
| switch (fat_type) { |
| case FAT_TYPE_FAT12: return 1; |
| case FAT_TYPE_FAT16: return 1024/512; |
| case FAT_TYPE_FAT32: return 4096/512; |
| } |
| return 0; |
| } |
| |
| static PedSector |
| _smallest_power2_over (PedSector ceiling) |
| { |
| PedSector result = 1; |
| |
| while (result < ceiling) |
| result *= 2; |
| |
| return result; |
| } |
| |
| /* returns the minimum size of clusters for a given file system type */ |
| PedSector |
| fat_recommend_min_cluster_size (FatType fat_type, PedSector size) { |
| switch (fat_type) { |
| case FAT_TYPE_FAT12: return 1; |
| case FAT_TYPE_FAT16: return fat_min_cluster_size(fat_type); |
| case FAT_TYPE_FAT32: |
| return PED_MAX(_smallest_power2_over(size |
| / MAX_FAT32_CLUSTERS), |
| fat_min_cluster_size (fat_type)); |
| } |
| return 0; |
| } |
| |
| /* returns the maxmimum size of clusters for a given file system type */ |
| PedSector |
| fat_max_cluster_size (FatType fat_type) { |
| switch (fat_type) { |
| case FAT_TYPE_FAT12: return 1; /* dunno... who cares? */ |
| case FAT_TYPE_FAT16: return 32768/512; |
| case FAT_TYPE_FAT32: return 65536/512; |
| } |
| return 0; |
| } |
| |
| /* returns the minimum number of clusters for a given file system type */ |
| FatCluster |
| fat_min_cluster_count (FatType fat_type) { |
| switch (fat_type) { |
| case FAT_TYPE_FAT12: |
| case FAT_TYPE_FAT16: |
| return fat_max_cluster_count (fat_type) / 2; |
| |
| case FAT_TYPE_FAT32: return 0xfff0; |
| } |
| return 0; |
| } |
| |
| /* returns the maximum number of clusters for a given file system type */ |
| FatCluster |
| fat_max_cluster_count (FatType fat_type) { |
| switch (fat_type) { |
| case FAT_TYPE_FAT12: return 0xff0; |
| case FAT_TYPE_FAT16: return 0xfff0; |
| case FAT_TYPE_FAT32: return 0x0ffffff0; |
| } |
| return 0; |
| } |
| |
| /* what is this supposed to be? What drugs are M$ on? (Can I have some? :-) */ |
| PedSector |
| fat_min_reserved_sector_count (FatType fat_type) |
| { |
| return (fat_type == FAT_TYPE_FAT32) ? 32 : 1; |
| } |
| |
| int |
| fat_check_resize_geometry (const PedFileSystem* fs, |
| const PedGeometry* geom, |
| PedSector new_cluster_sectors, |
| FatCluster new_cluster_count) |
| { |
| FatSpecific* fs_info = FAT_SPECIFIC (fs); |
| PedSector free_space; |
| PedSector min_free_space; |
| PedSector total_space; |
| PedSector new_total_space; |
| PedSector dir_space; |
| |
| PED_ASSERT (geom != NULL, return 0); |
| |
| dir_space = fs_info->total_dir_clusters * fs_info->cluster_sectors; |
| free_space = fs_info->fat->free_cluster_count |
| * fs_info->cluster_sectors; |
| total_space = fs_info->fat->cluster_count * fs_info->cluster_sectors; |
| new_total_space = new_cluster_count * new_cluster_sectors; |
| min_free_space = total_space - new_total_space + dir_space; |
| |
| PED_ASSERT (new_cluster_count |
| <= fat_max_cluster_count (FAT_TYPE_FAT32), |
| return 0); |
| |
| if (free_space < min_free_space) { |
| char* needed = ped_unit_format (geom->dev, min_free_space); |
| char* have = ped_unit_format (geom->dev, free_space); |
| ped_exception_throw ( |
| PED_EXCEPTION_ERROR, |
| PED_EXCEPTION_CANCEL, |
| _("You need %s of free disk space to shrink this " |
| "partition to this size. Currently, only %s is " |
| "free."), |
| needed, have); |
| ped_free (needed); |
| ped_free (have); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| |
| /******************************************************************************/ |
| |
| /* DO NOT EDIT THIS ALGORITHM! |
| * As far as I can tell, this is the same algorithm used by Microsoft to |
| * calculate the size of the file allocaion tables, and the number of clusters. |
| * I have not verified this by dissassembling Microsoft code - I came to this |
| * conclusion by empirical analysis (i.e. trial and error - this was HORRIBLE). |
| * |
| * If you think this code makes no sense, then you are right. I will restrain |
| * the urge to inflict serious bodily harm on Microsoft people. |
| */ |
| |
| static int |
| entries_per_sector (FatType fat_type) |
| { |
| switch (fat_type) { |
| case FAT_TYPE_FAT12: |
| return 512 * 3 / 2; |
| case FAT_TYPE_FAT16: |
| return 512 / 2; |
| case FAT_TYPE_FAT32: |
| return 512 / 4; |
| } |
| return 0; |
| } |
| |
| static int |
| calc_sizes (PedSector size, PedSector align, FatType fat_type, |
| PedSector root_dir_sectors, PedSector cluster_sectors, |
| FatCluster* out_cluster_count, PedSector* out_fat_size) |
| { |
| PedSector data_fat_space; /* space available to clusters + FAT */ |
| PedSector fat_space; /* space taken by each FAT */ |
| PedSector cluster_space; /* space taken by clusters */ |
| FatCluster cluster_count; |
| int i; |
| |
| PED_ASSERT (out_cluster_count != NULL, return 0); |
| PED_ASSERT (out_fat_size != NULL, return 0); |
| |
| data_fat_space = size - fat_min_reserved_sector_count (fat_type) |
| - align; |
| if (fat_type == FAT_TYPE_FAT16) |
| data_fat_space -= root_dir_sectors; |
| |
| fat_space = 0; |
| for (i = 0; i < 2; i++) { |
| if (fat_type == FAT_TYPE_FAT32) |
| cluster_space = data_fat_space - fat_space; |
| else |
| cluster_space = data_fat_space - 2 * fat_space; |
| |
| cluster_count = cluster_space / cluster_sectors; |
| fat_space = ped_div_round_up (cluster_count + 2, |
| entries_per_sector (fat_type)); |
| } |
| |
| cluster_space = data_fat_space - 2 * fat_space; |
| cluster_count = cluster_space / cluster_sectors; |
| |
| /* looks like this should be part of the loop condition? |
| * Need to build the Big Table TM again to check |
| */ |
| if (fat_space < ped_div_round_up (cluster_count + 2, |
| entries_per_sector (fat_type))) { |
| fat_space = ped_div_round_up (cluster_count + 2, |
| entries_per_sector (fat_type)); |
| } |
| |
| if (cluster_count > fat_max_cluster_count (fat_type) |
| || cluster_count < fat_min_cluster_count (fat_type)) |
| return 0; |
| |
| *out_cluster_count = cluster_count; |
| *out_fat_size = fat_space; |
| |
| return 1; |
| } |
| |
| /****************************************************************************/ |
| |
| int |
| fat_calc_sizes (PedSector size, PedSector align, FatType fat_type, |
| PedSector root_dir_sectors, |
| PedSector* out_cluster_sectors, FatCluster* out_cluster_count, |
| PedSector* out_fat_size) |
| { |
| PedSector cluster_sectors; |
| |
| PED_ASSERT (out_cluster_sectors != NULL, return 0); |
| PED_ASSERT (out_cluster_count != NULL, return 0); |
| PED_ASSERT (out_fat_size != NULL, return 0); |
| |
| for (cluster_sectors = fat_recommend_min_cluster_size (fat_type, size); |
| cluster_sectors <= fat_max_cluster_size (fat_type); |
| cluster_sectors *= 2) { |
| if (calc_sizes (size, align, fat_type, root_dir_sectors, |
| cluster_sectors, |
| out_cluster_count, out_fat_size)) { |
| *out_cluster_sectors = cluster_sectors; |
| return 1; |
| } |
| } |
| |
| for (cluster_sectors = fat_recommend_min_cluster_size (fat_type, size); |
| cluster_sectors >= fat_min_cluster_size (fat_type); |
| cluster_sectors /= 2) { |
| if (calc_sizes (size, align, fat_type, root_dir_sectors, |
| cluster_sectors, |
| out_cluster_count, out_fat_size)) { |
| *out_cluster_sectors = cluster_sectors; |
| return 1; |
| } |
| } |
| |
| /* only make the cluster size really small (<4k) if a bigger one is |
| * isn't possible. Windows never makes FS's like this, but it |
| * seems to work... (do more tests!) |
| */ |
| for (cluster_sectors = 4; cluster_sectors > 0; cluster_sectors /= 2) { |
| if (calc_sizes (size, align, fat_type, root_dir_sectors, |
| cluster_sectors, |
| out_cluster_count, out_fat_size)) { |
| *out_cluster_sectors = cluster_sectors; |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* Same as fat_calc_sizes, except it only attempts to match a particular |
| * cluster size. This is useful, because the FAT resizer can only shrink the |
| * cluster size. |
| */ |
| int |
| fat_calc_resize_sizes ( |
| const PedGeometry* geom, |
| PedSector align, |
| FatType fat_type, |
| PedSector root_dir_sectors, |
| PedSector cluster_sectors, |
| PedSector* out_cluster_sectors, |
| FatCluster* out_cluster_count, |
| PedSector* out_fat_size) |
| { |
| PED_ASSERT (geom != NULL, return 0); |
| PED_ASSERT (out_cluster_sectors != NULL, return 0); |
| PED_ASSERT (out_cluster_count != NULL, return 0); |
| PED_ASSERT (out_fat_size != NULL, return 0); |
| |
| /* libparted can only reduce the cluster size at this point */ |
| for (*out_cluster_sectors = cluster_sectors; |
| *out_cluster_sectors >= fat_min_cluster_size (fat_type); |
| *out_cluster_sectors /= 2) { |
| if (calc_sizes (geom->length, align, fat_type, root_dir_sectors, |
| *out_cluster_sectors, |
| out_cluster_count, out_fat_size)) |
| return 1; |
| } |
| return 0; |
| } |
| |
| /* Calculates the number of sectors needed to be added to cluster_offset, |
| to make the cluster on the new file system match up with the ones |
| on the old file system. |
| However, some space is reserved by fat_calc_resize_sizes() and |
| friends, to allow room for this space. If too much of this space is left |
| over, everyone will complain, so we have to be greedy, and use it all up... |
| */ |
| PedSector |
| fat_calc_align_sectors (const PedFileSystem* new_fs, |
| const PedFileSystem* old_fs) |
| { |
| FatSpecific* old_fs_info = FAT_SPECIFIC (old_fs); |
| FatSpecific* new_fs_info = FAT_SPECIFIC (new_fs); |
| PedSector raw_old_meta_data_end; |
| PedSector new_meta_data_size; |
| PedSector min_new_meta_data_end; |
| PedSector new_data_size; |
| PedSector new_clusters_size; |
| PedSector align; |
| |
| new_meta_data_size |
| = fat_min_reserved_sector_count (new_fs_info->fat_type) |
| + new_fs_info->fat_sectors * 2; |
| |
| if (new_fs_info->fat_type == FAT_TYPE_FAT16) |
| new_meta_data_size += new_fs_info->root_dir_sector_count; |
| |
| raw_old_meta_data_end = old_fs->geom->start |
| + old_fs_info->cluster_offset; |
| |
| min_new_meta_data_end = new_fs->geom->start + new_meta_data_size; |
| |
| if (raw_old_meta_data_end > min_new_meta_data_end) |
| align = (raw_old_meta_data_end - min_new_meta_data_end) |
| % new_fs_info->cluster_sectors; |
| else |
| align = (new_fs_info->cluster_sectors |
| - ( (min_new_meta_data_end - raw_old_meta_data_end) |
| % new_fs_info->cluster_sectors )) |
| % new_fs_info->cluster_sectors; |
| |
| new_data_size = new_fs->geom->length - new_meta_data_size; |
| new_clusters_size = new_fs_info->cluster_count |
| * new_fs_info->cluster_sectors; |
| |
| while (new_clusters_size + align + new_fs_info->cluster_sectors |
| <= new_data_size) |
| align += new_fs_info->cluster_sectors; |
| |
| return align; |
| } |
| |
| int |
| fat_is_sector_in_clusters (const PedFileSystem* fs, PedSector sector) |
| { |
| FatSpecific* fs_info = FAT_SPECIFIC (fs); |
| |
| return sector >= fs_info->cluster_offset |
| && sector < fs_info->cluster_offset |
| + fs_info->cluster_sectors * fs_info->cluster_count; |
| } |
| |
| FatFragment |
| fat_cluster_to_frag (const PedFileSystem* fs, FatCluster cluster) |
| { |
| FatSpecific* fs_info = FAT_SPECIFIC (fs); |
| |
| PED_ASSERT (cluster >= 2 && cluster < fs_info->cluster_count + 2, |
| return 0); |
| |
| return (cluster - 2) * fs_info->cluster_frags; |
| } |
| |
| FatCluster |
| fat_frag_to_cluster (const PedFileSystem* fs, FatFragment frag) |
| { |
| FatSpecific* fs_info = FAT_SPECIFIC (fs); |
| |
| PED_ASSERT (frag >= 0 && frag < fs_info->frag_count, return 0); |
| |
| return frag / fs_info->cluster_frags + 2; |
| } |
| |
| PedSector |
| fat_frag_to_sector (const PedFileSystem* fs, FatFragment frag) |
| { |
| FatSpecific* fs_info = FAT_SPECIFIC (fs); |
| |
| PED_ASSERT (frag >= 0 && frag < fs_info->frag_count, return 0); |
| |
| return frag * fs_info->frag_sectors + fs_info->cluster_offset; |
| } |
| |
| FatFragment |
| fat_sector_to_frag (const PedFileSystem* fs, PedSector sector) |
| { |
| FatSpecific* fs_info = FAT_SPECIFIC (fs); |
| |
| PED_ASSERT (sector >= fs_info->cluster_offset, return 0); |
| |
| return (sector - fs_info->cluster_offset) / fs_info->frag_sectors; |
| } |
| |
| PedSector |
| fat_cluster_to_sector (const PedFileSystem* fs, FatCluster cluster) |
| { |
| FatSpecific* fs_info = FAT_SPECIFIC (fs); |
| |
| PED_ASSERT (cluster >= 2 && cluster < fs_info->cluster_count + 2, |
| return 0); |
| |
| return (cluster - 2) * fs_info->cluster_sectors |
| + fs_info->cluster_offset; |
| } |
| |
| FatCluster |
| fat_sector_to_cluster (const PedFileSystem* fs, PedSector sector) |
| { |
| FatSpecific* fs_info = FAT_SPECIFIC (fs); |
| |
| PED_ASSERT (sector >= fs_info->cluster_offset, return 0); |
| |
| return (sector - fs_info->cluster_offset) / fs_info->cluster_sectors |
| + 2; |
| } |
| #endif /* !DISCOVER_ONLY */ |