/* ----------------------------------------------------------------------- *
 *
 *   Copyright 2004-2008 H. Peter Anvin - All Rights Reserved
 *
 *   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, Inc., 53 Temple Place Ste 330,
 *   Boston MA 02111-1307, USA; either version 2 of the License, or
 *   (at your option) any later version; incorporated herein by reference.
 *
 * ----------------------------------------------------------------------- */

/*
 * fatchain.c
 *
 * Follow a FAT chain
 */

#include "libfatint.h"
#include "ulint.h"

/*
 * Convert a cluster number (or 0 for the root directory) to a
 * sector number.  Return -1 on failure.
 */
libfat_sector_t libfat_clustertosector(const struct libfat_filesystem *fs,
				       int32_t cluster)
{
  if ( cluster == 0 )
    cluster = fs->rootcluster;

  if ( cluster == 0 )
    return fs->rootdir;
  else if ( cluster < 2 || cluster >= fs->endcluster )
    return -1;
  else
    return fs->data + ((libfat_sector_t)(cluster-2) << fs->clustshift);
}

/*
 * Get the next sector of either the root directory or a FAT chain.
 * Returns 0 on end of file and -1 on error.
 */

libfat_sector_t libfat_nextsector(struct libfat_filesystem *fs,
				  libfat_sector_t s)
{
  int32_t cluster, nextcluster;
  uint32_t fatoffset;
  libfat_sector_t fatsect;
  uint8_t *fsdata;
  uint32_t clustmask = fs->clustsize - 1;
  libfat_sector_t rs;

  if ( s < fs->data ) {
    if ( s < fs->rootdir )
      return -1;

    /* Root directory */
    s++;
    return ( s < fs->data ) ? s : 0;
  }

  rs = s - fs->data;

  if ( ~rs & clustmask )
    return s+1;			/* Next sector in cluster */

  cluster = 2 + (rs >> fs->clustshift);

  if ( cluster >= fs->endcluster )
    return -1;

  switch ( fs->fat_type ) {
  case FAT12:
    /* Get first byte */
    fatoffset = cluster + (cluster >> 1);
    fatsect = fs->fat + (fatoffset >> LIBFAT_SECTOR_SHIFT);
    fsdata = libfat_get_sector(fs, fatsect);
    if ( !fsdata )
      return -1;
    nextcluster = fsdata[fatoffset & LIBFAT_SECTOR_MASK];

    /* Get second byte */
    fatoffset++;
    fatsect = fs->fat + (fatoffset >> LIBFAT_SECTOR_SHIFT);
    fsdata = libfat_get_sector(fs, fatsect);
    if ( !fsdata )
      return -1;
    nextcluster |= fsdata[fatoffset & LIBFAT_SECTOR_MASK] << 8;

    /* Extract the FAT entry */
    if ( cluster & 1 )
      nextcluster >>= 4;
    else
      nextcluster &= 0x0FFF;

    if ( nextcluster >= 0x0FF8 )
      return 0;
    break;

  case FAT16:
    fatoffset = cluster << 1;
    fatsect = fs->fat + (fatoffset >> LIBFAT_SECTOR_SHIFT);
    fsdata = libfat_get_sector(fs, fatsect);
    if ( !fsdata )
      return -1;
    nextcluster = read16((le16_t *)&fsdata[fatoffset & LIBFAT_SECTOR_MASK]);

    if ( nextcluster >= 0x0FFF8 )
      return 0;
    break;

  case FAT28:
    fatoffset = cluster << 2;
    fatsect = fs->fat + (fatoffset >> LIBFAT_SECTOR_SHIFT);
    fsdata = libfat_get_sector(fs, fatsect);
    if ( !fsdata )
      return -1;
    nextcluster = read32((le32_t *)&fsdata[fatoffset & LIBFAT_SECTOR_MASK]);
    nextcluster &= 0x0FFFFFFF;

    if ( nextcluster >= 0x0FFFFFF8 )
      return 0;
    break;

  default:
    return -1;			/* WTF? */
  }

  return libfat_clustertosector(fs, nextcluster);
}
