/****************************************************************/
/*                                                              */
/*                           lfnapi.c                           */
/*                                                              */
/*       Directory access functions for LFN helper API          */
/*                                                              */
/****************************************************************/

#include "portab.h"
#include "globals.h"

#ifdef VERSION_STRINGS
static BYTE *lfnaidRcsId =
    "$Id: lfnapi.c 1389 2009-05-20 18:13:37Z bartoldeman $";
#endif

#ifdef WITHLFNAPI

#define RESERVED_FNODES 3  /* # of reserved fnodes for the kernel internal *
                            * usage. If something wrong happens with the   *
                            * driver using helper API and it grabs all     *
                            * possible fnodes, the kernel doesn't hang.    */

/* All possible error codes, returned by directory access functions */
/* Note. SUCCESS is returned if all OK.                             */
#define LHE_INVLDHNDL  -1  /* Invalid inode handle passed.                 */
#define LHE_NOFREEHNDL -2  /* Couldn't allocate another inode handle.      */
#define LHE_IOERROR    -3  /* Couldn't do I/O because of the lack of       *
                            * memory or due to a physical damage.          */
#define LHE_INVLDDRV   -4  /* Invalid drive chosen.                        */
#define LHE_DAMAGEDFS  -5  /* Damaged file system encountered.             */
#define LHE_NOSPACE    -6  /* There are no free clusters on drive.         */
#define LHE_SEEK       -7  /* Attempt to read beyound the end of the dir.  */

#define lfn(fnp) ((struct lfn_entry FAR *)&(fnp->f_dir))
#define CHARS_IN_LFN_ENTRY 13
#define UNICODE_FILLER 0xffff

COUNT ufstrlen(REG UNICODE FAR *); /* fstrlen for UNICODE strings */
UBYTE lfn_checksum(UBYTE FAR *);
COUNT extend_dir(f_node_ptr);
BOOL lfn_to_unicode(UNICODE FAR **name, struct lfn_entry FAR *lep);
VOID unicode_to_lfn(UNICODE FAR **name, struct lfn_entry FAR *lep);

/* Description.
 *  Allocates internal fnode and returns it's handle.
 * Return value.
 *  LHE_NOFREEHNDL, LHE_INVLDDRV
 *  >= 0           - handle of the allocated fnode.
 */
COUNT lfn_allocate_inode(VOID)
{
  f_node_ptr fnp = get_f_node(&fnode[0]);
  struct dpb FAR *dpbp;
  COUNT handle;
  if (fnp == 0) return LHE_NOFREEHNDL;

  handle = xlt_fnp(fnp);
  /* Check if there is at least # RESERVED_FNODES left for the kernel */
  if (f_nodes_cnt - handle < RESERVED_FNODES)
    {
      release_f_node(fnp);
      return LHE_NOFREEHNDL;
    }

  /* Check that default drive is a block device */
  dpbp = get_dpb(default_drive);

  if (dpbp == 0)
    {
      release_f_node(fnp);
      return LHE_INVLDDRV;
    }

  fnp->f_dpb = dpbp;

  if (media_check(dpbp) < 0)
    {
      release_f_node(fnp);
      return LHE_INVLDDRV;
    }

  return handle;
}

/* Description.
 *  Free allocated internal fnode.
 * Return value.
 *  SUCCESS, LHE_INVLDHNDL
 */
COUNT lfn_free_inode(COUNT handle)
{
  f_node_ptr fnp = xlt_fd(handle);
  if (fnp == 0 || fnp->f_count <= 0) return LHE_INVLDHNDL;

  dir_close(fnp);

  return SUCCESS;
}

/* Description.
 *  Initialize internal fnode, so that it'll point to the directory entry
 *  at "diroff" entry offset from the start of the directory with the "dirstart"
 *  starting cluster.
 * Return value.
 *  SUCCESS, LHE_INVLDHNDL
 */
COUNT lfn_setup_inode(COUNT handle, ULONG dirstart, ULONG diroff)
{
  f_node_ptr fnp = xlt_fd(handle);
  if (fnp == 0 || fnp->f_count <= 0) return LHE_INVLDHNDL;

  dir_init_fnode(fnp, (CLUSTER)dirstart);
  fnp->f_diroff = diroff;

  return SUCCESS;
}

/* Description.
 *  Create LFN directory entries for the long name "lip->l_name", followed
 *  by the SFN entry with raw image "lip->l_dir". The "lip->l_diroff" points
 *  to the SFN entry on return.
 *  The internal fnode is released on fatal error, except LHE_INVLDHNDL of
 *  course.
 * Return value.
 *  SUCCESS, LHE_INVLDHNDL, LHE_IOERROR, LHE_NOSPACE
 */
COUNT lfn_create_entries(COUNT handle, lfn_inode_ptr lip)
{
  f_node_ptr fnp = xlt_fd(handle);
  COUNT entries_needed, free_entries, i, rc;
  UNICODE FAR *lfn_name = lip->l_name;
  UBYTE id = 1, sfn_checksum = lfn_checksum(lip->l_dir.dir_name);
  unsigned sfn_offset;
  if (fnp == 0 || fnp->f_count <= 0) return LHE_INVLDHNDL;

  entries_needed = (ufstrlen(lfn_name) + CHARS_IN_LFN_ENTRY - 1)
    / CHARS_IN_LFN_ENTRY + 1; /* We want to create SFN entry too */
  
  /* Scan the directory from the very begining for the free directory entries */
  lfn_setup_inode(handle, fnp->f_dirstart, 0);

  free_entries = 0;
  while (TRUE)
    {
      rc = dir_read(fnp);
      if (rc == 0 || fnp->f_dir.dir_name[0] == DELETED)
        {
          free_entries++;
          if (free_entries == entries_needed)
            break;
        }
      else if (rc == DE_BLKINVLD)
        {
          dir_close(fnp);
          return LHE_IOERROR;
        }
      else if (rc == DE_SEEK)
        {
          if (extend_dir(fnp) != SUCCESS) return LHE_NOSPACE;
          /* fnp points to the first free dir entry on return from extend_dir,
           * so we go to previous entry to read this free entry on next cycle */
          fnp->f_diroff--;
        }
      else free_entries = 0;  /* rc == 1 here => we've read some sfn entry */
    }
  sfn_offset = fnp->f_diroff;

  fnp->f_flags.f_dmod = TRUE;
  /* Write SFN entry */
  fmemcpy(&fnp->f_dir, &lip->l_dir, sizeof(struct dirent));
  dir_write(fnp);
  
  fnp->f_diroff--;
  /* Go in the reverse direction and create LFN entries */
  for (i = 0; i < entries_needed - 1; i++, id++)
    {
      /* If this is the last LFN entry mark it's as those (6th bit is on) */
      if (i == (entries_needed - 2)) id |= 0x40;
      lfn_name = &lip->l_name[i * CHARS_IN_LFN_ENTRY];
      unicode_to_lfn(&lfn_name, lfn(fnp));
      lfn(fnp)->lfn_checksum = sfn_checksum;
      lfn(fnp)->lfn_id = id;
      fnp->f_dir.dir_attrib = D_LFN;
      if (!dir_write(fnp)) return LHE_IOERROR;
      fnp->f_diroff--;
    }
  fnp->f_flags.f_dmod = FALSE;
  
  fnp->f_diroff = sfn_offset;

  return SUCCESS;
}

/* Description.
 *  Read next consequitve long file name. The LFN is stored into the
 *  "lip->l_name" in unicode and the corresponding SFN entry raw image into the
 *  "lip->l_dir". If directory entry being read is a 8.3 file name, then
 *  it's image is stored into the "lip->l_dir" and "lip->l_name" has zero
 *  length, i.e. "lip->l_name[0]" == 0.
 * Return value.
 *  SUCCESS, LHE_INVLDHNDL, LHE_IOERROR, LHE_SEEK, LHE_DAMAGEDFS
 */
COUNT lfn_dir_read(COUNT handle, lfn_inode_ptr lip)
{
  COUNT rc;
  UBYTE id = 1, real_id;
  UNICODE FAR *lfn_name = lip->l_name;
  UWORD sfn_diroff;
  BOOL name_tail;
  f_node_ptr fnp = xlt_fd(handle);
  if (fnp == 0 || fnp->f_count <= 0) return LHE_INVLDHNDL;

  /* Scan a directory for the next valid SFN entry */
  while (TRUE)
    {
      rc = dir_read(fnp);
      if (rc == DE_SEEK) return LHE_SEEK;
      else if (rc == DE_BLKINVLD) return LHE_IOERROR;
      if (fnp->f_dir.dir_name[0] != DELETED && fnp->f_dir.dir_attrib != D_LFN)
        {
          fmemcpy(&lip->l_dir, &fnp->f_dir, sizeof(struct dirent));
          sfn_diroff = fnp->f_diroff;
          break;
        }
    } 
    
  /* Go in the reverse direction and find the LFN entries corresponding to
   * the found SFN entry */
  while (TRUE)
    {
      if (fnp->f_diroff == 0) break;
      fnp->f_diroff -= 2;
      rc = dir_read(fnp);
      if (rc == DE_BLKINVLD) return LHE_IOERROR;
      if (fnp->f_dir.dir_name[0] == DELETED
          || fnp->f_dir.dir_attrib != D_LFN) break;
      real_id = lfn(fnp)->lfn_id;
      if ((real_id & 0x3f) > 20) return LHE_DAMAGEDFS;
      name_tail = lfn_to_unicode(&lfn_name, lfn(fnp));
      if (real_id & 0x40)
        {
          if ((id | 0x40) != real_id) return LHE_DAMAGEDFS;
          break;
        }
      else
        {
          if (name_tail || real_id != id
             || lfn(fnp)->lfn_checksum != lfn_checksum(lip->l_dir.dir_name))
            return LHE_DAMAGEDFS;
        }
        id++;
    }

  *lfn_name = 0;    /* Terminate LFN string */

  fnp->f_diroff = lip->l_diroff = sfn_diroff;

  return SUCCESS;
}

/* Description.
 *  Writes directory entry pointed by internal fnode to disk.
 *  The fnode is released on error.
 * Return value.
 *  SUCCESS, LHE_INVLDHNDL, LHE_IOERROR.
 */
COUNT lfn_dir_write(COUNT handle)
{
  f_node_ptr fnp = xlt_fd(handle);
  if (fnp == 0 || fnp->f_count <= 0) return LHE_INVLDHNDL;

  fnp->f_flags.f_dmod = TRUE;
  if (!dir_write(fnp)) return LHE_IOERROR;
  fnp->f_flags.f_dmod = FALSE;

  return SUCCESS;
}

/*********************************************************************/
/* Miscellaneous functions. These functions are not part of the API. */
/*********************************************************************/

/* Description.
 *  Copy the block of "count" unicode characters from source pointed by
 *  "sptr" to destination pointed by "dptr". The trailing characters of
 *  destination are filled with "UNICODE_FILLER".
 * Return value.
 *  TRUE  - The source string is too short, trailing characters are filled
 *          with "UNICODE_FILLER".
 *  FALSE - The unicode strings have the same size, no filling occured.
 */
BOOL transfer_unicode(UNICODE FAR **dptr, UNICODE FAR **sptr, COUNT count)
{
  COUNT j;
  BOOL found_zerro = FALSE;

  for (j = 0; j < count; j++, (*dptr)++, (*sptr)++)
    {
      if (found_zerro) **dptr = UNICODE_FILLER;
      else **dptr = **sptr;
      if (**sptr == 0) found_zerro = TRUE;
    }

  return found_zerro;
}

/* Description.
 *  Retrieve the LFN string chunk from the directory entry "lep" and store it
 *  to the "name" unicode string.
 * Return value.
 *  TRUE  - The LFN string chunk contains zero character.
 *  FALSE - Doesn't terminate with zero.
 */
BOOL lfn_to_unicode(UNICODE FAR **name, struct lfn_entry FAR *lep)
{
  UNICODE FAR *ptr;
    
  ptr = lep->lfn_name0_4;
  if (transfer_unicode(name, &ptr, 5)) return TRUE;
  ptr = lep->lfn_name5_10;
  if (transfer_unicode(name, &ptr, 6)) return TRUE;
  ptr = lep->lfn_name11_12;
  if (transfer_unicode(name, &ptr, 2)) return TRUE;

  return FALSE;
}

/* Description.
 */
VOID unicode_to_lfn(UNICODE FAR **name, struct lfn_entry FAR *lep)
{
  UNICODE FAR *ptr;
  
  ptr = lep->lfn_name0_4;
  transfer_unicode(&ptr, name, 5);
  ptr = lep->lfn_name5_10;
  transfer_unicode(&ptr, name, 6);
  ptr = lep->lfn_name11_12;
  transfer_unicode(&ptr, name, 2);
}

/* Calculate checksum for the 8.3 name */
UBYTE lfn_checksum(UBYTE FAR *sfn_name)
{
  UBYTE sum;
  COUNT i;

  for (sum = 0, i = 11; --i >= 0; sum += *sfn_name++)
    sum = (sum << 7) | (sum >> 1);

  return sum;
}

COUNT ufstrlen(REG UNICODE FAR *s)
{
  REG COUNT cnt = 0;

  while (*s++ != 0)
    ++cnt;
  return cnt;
}

#endif
