/*
    libparted
    Copyright (C) 1998-2000, 2007, 2009-2014, 2019-2023 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 3 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, see <http://www.gnu.org/licenses/>.
*/

#include <config.h>
#include "fat.h"
#include "traverse.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifndef DISCOVER_ONLY

/*
    prints out the sequence of clusters for a given file chain, beginning
    at start_cluster.
*/
#ifdef PED_VERBOSE
static void
print_chain (PedFileSystem* fs, FatCluster start)
{
	FatSpecific*	fs_info = FAT_SPECIFIC (fs);
	FatCluster	clst;
	int		this_row;

	this_row = 0;
	for (clst = start; !fat_table_is_eof (fs_info->fat, clst);
	     clst = fat_table_get (fs_info->fat, clst)) {
		printf ("  %d", (int) clst);
		if (++this_row == 7) {
                        putchar ('\n');
			this_row = 0;
		}
	}
	putchar ('\n');
}
#endif /* PED_VERBOSE */

static PedSector
remainder_round_up (PedSector a, PedSector b)
{
	PedSector	result;

	result = a % b;
	if (!result)
		result = b;
	return result;
}

/*
    traverse the FAT for a file/directory, marking each entry's flag
    to "flag".
*/
static int
flag_traverse_fat (PedFileSystem* fs, const char* chain_name, FatCluster start,
		   FatClusterFlag flag, PedSector size)
{
	FatSpecific*	fs_info = FAT_SPECIFIC (fs);
	FatCluster	clst;
	FatCluster	prev_clst;
	int		last_cluster_usage;
	FatCluster	chain_length = 0;

	if (fat_table_is_eof (fs_info->fat, start)) {
		if (ped_exception_throw (
			PED_EXCEPTION_ERROR,
			PED_EXCEPTION_IGNORE_CANCEL,
			_("Bad directory entry for %s: first cluster is the "
			  "end of file marker."),
			chain_name)
				!= PED_EXCEPTION_IGNORE)
			return 0;
	}

	for (prev_clst = clst = start; !fat_table_is_eof (fs_info->fat, clst);
	     prev_clst = clst, clst = fat_table_get (fs_info->fat, clst)) {
		chain_length++;
		if (!clst) {
			ped_exception_throw (PED_EXCEPTION_FATAL,
				PED_EXCEPTION_CANCEL,
				_("Bad FAT: unterminated chain for %s.  You "
				  "should run dosfsck or scandisk."),
				chain_name);
			return 0;
		}

		if (clst >= fs_info->fat->cluster_count + 2) {
			ped_exception_throw (PED_EXCEPTION_FATAL,
				PED_EXCEPTION_CANCEL,
				_("Bad FAT: cluster %d outside file system "
				  "in chain for %s.  You should run dosfsck "
				  "or scandisk."),
				(int) clst, chain_name);
			return 0;
		}

		if (fs_info->cluster_info [clst].flag != FAT_FLAG_FREE ) {
			ped_exception_throw (PED_EXCEPTION_FATAL,
				PED_EXCEPTION_CANCEL,
				_("Bad FAT: cluster %d is cross-linked for "
				  "%s.  You should run dosfsck or scandisk."),
				(int) clst, chain_name);
			return 0;
		}

		if (flag == FAT_FLAG_DIRECTORY)
			fs_info->total_dir_clusters++;

		fs_info->cluster_info [clst].flag = flag;
		fs_info->cluster_info [clst].units_used = 0;	/* 0 == 64 */
	}

	if (size
	    && chain_length
	    		!= ped_div_round_up (size, fs_info->cluster_sectors)) {
		if (ped_exception_throw (
			PED_EXCEPTION_ERROR,
			PED_EXCEPTION_IGNORE_CANCEL,
			_("%s is %dk, but it has %d clusters (%dk)."),
			chain_name,
			(int) size / 2,
			(int) chain_length,
			(int) chain_length * fs_info->cluster_sectors / 2)
				!= PED_EXCEPTION_IGNORE)
			return 0;
	}

	last_cluster_usage
		= ped_div_round_up (64 * remainder_round_up (size,
						fs_info->cluster_sectors),
				fs_info->cluster_sectors);

	fs_info->cluster_info [prev_clst].units_used = last_cluster_usage;

	return 1;
}

/*
    recursively traverses a directory, flagging all clusters in the process.
    It frees the traverse_info structure before returning.
*/
static int
flag_traverse_dir (FatTraverseInfo* trav_info) {
	PedFileSystem*		fs = trav_info->fs;
	FatDirEntry*		this_entry;
	FatTraverseInfo*	subdir_trav_info;
	char			file_name [4096];
	char*			file_name_start;
	FatCluster		first_cluster;
	PedSector		size;

	PED_ASSERT (trav_info != NULL);

	strcpy (file_name, trav_info->dir_name);
	file_name_start = file_name + strlen (file_name);

	while ( (this_entry = fat_traverse_next_dir_entry (trav_info)) ) {
		if (fat_dir_entry_is_null_term (this_entry))
			break;
		if (!fat_dir_entry_has_first_cluster (this_entry, fs))
			continue;
		if (this_entry->name [0] == '.')
			continue;	/* skip . and .. entries */

		fat_dir_entry_get_name (this_entry, file_name_start);
		first_cluster = fat_dir_entry_get_first_cluster(this_entry, fs);
		size = ped_div_round_up (fat_dir_entry_get_length (this_entry),
					 512);

#ifdef PED_VERBOSE
		printf ("%s: ", file_name);
		print_chain (fs, first_cluster);
#endif

		if (fat_dir_entry_is_directory (this_entry)) {
			if (!flag_traverse_fat (fs, file_name, first_cluster,
						FAT_FLAG_DIRECTORY, size))
				return 0;

			subdir_trav_info = fat_traverse_directory (trav_info,
								   this_entry);
			if (!subdir_trav_info)
				return 0;
			if (!flag_traverse_dir (subdir_trav_info))
				return 0;
		} else if (fat_dir_entry_is_file (this_entry)) {
			if (!flag_traverse_fat (fs, file_name, first_cluster,
						FAT_FLAG_FILE, size))
				return 0;
		}
	}

	fat_traverse_complete (trav_info);
	return 1;
}

static void
_mark_bad_clusters (PedFileSystem* fs)
{
	FatSpecific*	fs_info = FAT_SPECIFIC (fs);
	FatCluster	cluster;

	for (cluster = 2; cluster < fs_info->cluster_count + 2; cluster++) {
		if (fat_table_is_bad (fs_info->fat, cluster))
			fs_info->cluster_info [cluster].flag = FAT_FLAG_BAD;
	}
}

/*
    fills in cluster_info.  Each FAT entry (= cluster) is flagged as either
    FAT_FLAG_FREE, FAT_FLAG_FILE or FAT_FLAG_DIRECTORY.

    Also, the fraction of each cluster (x/64) is recorded
*/
int
fat_collect_cluster_info (PedFileSystem* fs) {
	FatSpecific*		fs_info = FAT_SPECIFIC (fs);
	FatTraverseInfo*	trav_info;

	/* set all clusters to unused as a default */
	memset (fs_info->cluster_info, 0, fs_info->fat->cluster_count + 2);
	fs_info->total_dir_clusters = 0;

	if (fs_info->fat_type == FAT_TYPE_FAT32) {
		trav_info = fat_traverse_begin (fs, fs_info->root_cluster,
						"\\");
		if (!flag_traverse_dir (trav_info))
			return 0;
		if (!flag_traverse_fat (fs, "\\", fs_info->root_cluster,
                                        FAT_FLAG_DIRECTORY, 0))
			return 0;
	} else {
		trav_info = fat_traverse_begin (fs, FAT_ROOT, "\\");
		if (!flag_traverse_dir (trav_info))
			return 0;
	}

	_mark_bad_clusters (fs);
	return 1;
}

FatClusterFlag _GL_ATTRIBUTE_PURE
fat_get_cluster_flag (PedFileSystem* fs, FatCluster cluster)
{
	FatSpecific*		fs_info = FAT_SPECIFIC (fs);

	return fs_info->cluster_info [cluster].flag;
}

PedSector _GL_ATTRIBUTE_PURE
fat_get_cluster_usage (PedFileSystem* fs, FatCluster cluster)
{
	FatSpecific*		fs_info = FAT_SPECIFIC (fs);
	int			fraction;

	if (fs_info->cluster_info [cluster].flag == FAT_FLAG_FREE)
		return 0;

	fraction = fs_info->cluster_info [cluster].units_used;
	if (fraction == 0)
		fraction = 64;

	return fraction * fs_info->cluster_sectors / 64;
}

FatClusterFlag
fat_get_fragment_flag (PedFileSystem* fs, FatFragment frag)
{
	FatSpecific*	fs_info = FAT_SPECIFIC (fs);
	FatCluster	cluster = fat_frag_to_cluster (fs, frag);
	FatFragment	offset = frag % fs_info->cluster_frags;
	FatFragment	last_frag_used;
	FatClusterFlag	flag;

	PED_ASSERT (cluster >= 2 && cluster < fs_info->cluster_count + 2);

	flag = fat_get_cluster_flag (fs, cluster);
	if (flag != FAT_FLAG_FILE && flag != FAT_FLAG_DIRECTORY)
		return flag;
	last_frag_used = (fat_get_cluster_usage (fs, cluster) - 1)
				/ fs_info->frag_sectors;
	if (offset > last_frag_used)
		return FAT_FLAG_FREE;
	else
		return flag;
}

int
fat_is_fragment_active (PedFileSystem* fs, FatFragment frag)
{
	switch (fat_get_fragment_flag (fs, frag)) {
		case FAT_FLAG_FREE:
		case FAT_FLAG_BAD:
			return 0;

		case FAT_FLAG_FILE:
		case FAT_FLAG_DIRECTORY:
			return 1;
	}
	return 0;
}

#endif /* !DISCOVER_ONLY */
