/* general.c */

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

#include "kernel.h"
#include "newfs.h"
#include "swis.h"

/***************************************************************************/

icb *image_list;   /* these are initialised in init_fs(..) */
fcb *file_list;

object_hdr curr_obj_hdr;
object_name curr_obj_name;
int curr_obj_pos;

char copy_buffer[COPY_BUFF_SIZE];


/***************************************************************************/

void read_bytes(image_handle ih, char *buff, int n, int pos)

  /* Read n bytes to buff starting at pos from the image file */

{
#if TRACE_IO
  debug("read_bytes:  %d from %d in image &%08x to buff &%08x\n",
        n, pos, (int)ih, (int)buff);
#endif

  _swix(OS_GBPB, I0|I1|I2|I3|I4,
        3,
        ih->fswh,
        buff,
        n,
        pos
       );

  return;
}

/***************************************************************************/

void write_bytes(image_handle ih, char *buff, int n, int pos)

  /* Write n bytes from buff to the image file starting at pos.

     This function also stamps the image if a request to do so is pending.
  */

{
 /* give image new stamp if necessary */
  if (ih->stamp_pending)
  {
    ih->hdr.stamp_value++;
    ih->hdr.stamp_value &= 0x0000ffff;    /* ADFS disc record allocates just
                                              two bytes to this value */
  }

#if TRACE_IO
  debug("write_bytes: %d to %d in image &%08x from buff &%08x\n",
        n, pos, (int)ih, (int)buff);
#endif

 /* write out data as requested */
{
_kernel_oserror *err;

err =  _swix(OS_GBPB, I0|I1|I2|I3|I4,
        1,
        ih->fswh,
        buff,
        n,
        pos
       );
if (err != NULL)
  debug("Normal write error: '%s'\n", err->errmess);
}

 /* if new image stamp given, ensure image header is written back, ask
     FileSwitch to flush the image file to disc, and tell FileSwitch what
     the new stamp value is */
  if (ih->stamp_pending)
  {
   /* write out image header unless we've just done so */
    if (buff != (char*)&ih->hdr || n != sizeof(image_hdr) || pos != 0)
    {

#if TRACE_IO
      debug("write_bytes: %d to %d in image &%08x from buff &%08x\n",
            sizeof(image_hdr), 0, (int)ih, (int)(&ih->hdr));
#endif

{
_kernel_oserror *err;

err =      _swix(OS_GBPB, I0|I1|I2|I3|I4,
            1,
            ih->fswh,
            (char*)&ih->hdr,
            sizeof(image_hdr),
            0
           );
if (err != NULL)
  debug("Stamping write error: '%s'\n", err->errmess);
}
    }

   /* flush new stamp value to media */
{
_kernel_oserror *err;

err = _swix(OS_Args, I0|I1,
            255,
            ih->fswh
           );
if (err != NULL)
  debug("OS_Args 255 (%d) error: '%s'\n", ih->fswh, err->errmess);
}

#if TRACE_IO
    debug("New stamp value %d about to be passed to FileSwitch\n",
          ih->hdr.stamp_value);
#endif

   /* let FileSwitch know what the new stamp value is */
{
_kernel_oserror *err;

err = _swix(OS_Args, I0|I1|I2,
            8,                  /* inform of change of image stamp */
            ih->fswh,           /* file handle */
            ih->hdr.stamp_value /* new stamp value */
           );
if (err != NULL)
  debug("OS_Args 8 (%d, %d) error: '%s'\n", ih->fswh, ih->hdr.stamp_value, err->errmess);
}

    ih->stamp_pending = FALSE;
  }

  return;
} 

/***************************************************************************/

void *check_alloc(int n)

  /* allocates n bytes of storage */

{
  void *p = malloc(n);

  if (p == NULL)
    debug("*** check_alloc - no storage available\n");

  return p;
}

/***************************************************************************/

void check_free(void *p)

  /* frees previously allocated storage */

{
  free(p);

  return;
}

/***************************************************************************/

BOOL for_each_object(
                     image_handle ih,
                     BOOL f(image_handle ih, int pos, void *handle),
                     void *handle
                    )

  /* Calls f(ih, pos, handle) for each object in the image in turn, where pos
      is the offset of the object header within the image - and sets
      the global curr_obj_pos = pos just before the call.

     If the result is TRUE, for_each_obj(..) returns TRUE; otherwise, f(..)
      is called for the next object.

     If no object is "accepted" by f(..), the result is FALSE.
  */

{
  int pos = ih->hdr.size;
  int size;
  int i;

  while (pos < ih->hdr.image_size)
                         /* nb image_size *must* be looked up every time */
  {
    curr_obj_pos = pos;

    if (f(ih, pos, handle)) return TRUE;
    
   /* move on to next object in the image - must skip over header, name and
       contents */
    for (i=0; i<3; i++)
    {
      read_bytes(ih, (char*)&size, 4, pos);
      pos += size;
    }
  }

  return FALSE;
}
    
/***************************************************************************/

void read_obj_hdr_and_name(image_handle ih, int pos)

  /* On entry, pos is the offset of an object header inside the image ih.

     This function reads the object header into curr_obj_hdr and the object
      name into curr_obj_name.
  */

{
  read_bytes(ih, (char*)&curr_obj_hdr, sizeof(object_hdr), pos);
   /* must change this if this module ever has to manage multiple versions
       of NewFS at the same time - where the object header size may vary */

  pos += sizeof(object_hdr);
  read_bytes(ih, (char*)&curr_obj_name, 4, pos);  /* read length of name */
  read_bytes(ih, (char*)&curr_obj_name, curr_obj_name.size, pos);

  return;
}

/***************************************************************************/

void read_obj_name(image_handle ih, int pos)

{
  pos += sizeof(object_hdr);
  read_bytes(ih, (char*)&curr_obj_name, 4, pos);  /* read length of name */
  read_bytes(ih, (char*)&curr_obj_name, curr_obj_name.size, pos);

  return;
}

/***************************************************************************/

void read_obj_hdr(image_handle ih, int pos)

{
  read_bytes(ih, (char*)&curr_obj_hdr, sizeof(object_hdr), pos);

  return;
}

/***************************************************************************/

BOOL is_in_dir(char *dir_name, char *obj_name)

  /* Returns TRUE iff obj_name is the name of an object immediately inside
      the directory whose name is dir_name
  */

{
  int n1 = strlen(dir_name);
  int n2 = strlen(obj_name);
  int i;

  if (n2 <= n1)         /* object name must be longer than directory name */
    return FALSE;

 /* case-insensitive matching */
  i = 0;
  while (i < n1 &&
         tolower(dir_name[i]) == tolower(obj_name[i]))
    i++;

 /* now either: i = n1, and the dir_name is a prefix of the obj_name
            or: i < n1; and the dir_name is not a prefix of the obj_name */
  if (i < n1 ||
      (n1 > 0 && obj_name[i] != '.'))
       /* next char must be '.' unless dir_name is empty */
    return FALSE;

 /* skip over the dot that separates dir_name from obj_name */
  if (n1 > 0)
    i++;

 /* check there are no more dots in the remainder of the obj_name */
  while (i < n2)
    if (obj_name[i] == '.')
      return FALSE;
    else
      i++;

  return TRUE;
}

/***************************************************************************/

BOOL equal_names(char *name1, char *name2)

  /* Returns TRUE iff name1 and name2 are the same, bar case */

{
  int n1 = strlen(name1);
  int n2 = strlen(name2);
  int i;

  if (n1 != n2)
    return FALSE;

  i = 0;
  while (i < n1 &&
         tolower(name1[i]) == tolower(name2[i]))
    i++;

  return (i==n1);
}

/***************************************************************************/

BOOL equal_prefixes(char *name1, char *name2, int n)

  /* Returns TRUE iff name1 and name2 have the same prefixes - bar case -
      of n characters.

     'name1' is the prefix - but this function assumes only that
          n <= strlen(name1)
  */

{
  int i;

  for (i=0; i<n; i++)
  {
    if (name2[i] == 0 ||
        tolower(name1[i]) != tolower(name2[i]))
      return FALSE;
  }

  return TRUE;
}

/***************************************************************************/

static void changed_pos(int pos, int delta)

  /* All addresses >= pos have been changed by delta: this function updates
      any cached values in fcb's.
  */

{
  file_handle fh = file_list;

  while (fh != NULL)
  {
    if (fh->hdr_pos >= pos) fh->hdr_pos += delta;
    if (fh->pos > pos) fh->pos += delta;
     /* note: fh->pos will be equal to pos if fh identifies an empty file
         which is being extended for the first time */

    fh = fh->next;
  }

  return;
}

/***************************************************************************/

void copy_up(image_handle ih, int from, int gap, int size)

  /* Copy size bytes upwards in the image from 'from' to 'from+gap'.

     Any addresses cached inside fcb's are updated.
  */

{
 /* update any cached addresses */
  changed_pos(from, gap);

  from += size;

  while (size > 0)
  {
    int n = COPY_BUFF_SIZE;

    if (n > size)
      n = size;

    from -= n;
    size -= n;

    read_bytes(ih, copy_buffer, n, from);
    write_bytes(ih, copy_buffer, n, from+gap);
  }

  return;
}

/***************************************************************************/

void copy_down(image_handle ih, int from, int gap, int size)

  /* Copy 'size' bytes downwards in the image from 'from' to 'from-gap'.

     Any addresses cached inside fcb's are updated
  */

{
 /* update any cached addresses */
  changed_pos(from, -gap);

  while (size > 0)
  {
    int n = COPY_BUFF_SIZE;

    if (n > size)
      n = size;

    read_bytes(ih, copy_buffer, n, from);
    write_bytes(ih, copy_buffer, n, from-gap);

    from += n;
    size -= n;
  }

  return;
}

/***************************************************************************/

void delete_object(image_handle ih, int pos)

  /* Remove the object at pos from the image file */

{
  int gap;    /* space occupied by object to be deleted */
  int size;   /* space occupied by all objects above the deleted object */
  int n;

 /* set gap to the size of the object to be deleted */
  read_bytes(ih, (char*)&gap, 4, pos);  /* header */
  pos += gap;
  read_bytes(ih, (char*)&n, 4, pos);    /* name */
  gap += n;
  pos += n;
  read_bytes(ih, (char*)&n, 4, pos);    /* contents */
  gap += n;
  pos += n;

 /* pos now addresses the first object beyond that to be deleted */

 /* set size to the space occupied by any objects above that to be deleted */
  size = ih->hdr.image_size - pos;

 /* copy any objects above the deleted one down across it */
  copy_down(ih, pos, gap, size);

 /* adjust image header appropriately */
  ih->hdr.image_size -= gap;

 /* and write it back */
  write_bytes(ih, (char*)&(ih->hdr), sizeof(image_hdr), 0);

  return;  
}

/***************************************************************************/

int round_up_to_block(image_handle ih, int x)

  /* Returns smallest multiple of block_size >= x */

{
  int n = ih->hdr.block_size - 1;

  return (x + n) & ~n;
}

/***************************************************************************/
