/* rock.c:
 *
 * Support for the Rock Ridge filing system.
 *
 * ----------------------------------------------------------------------
 * This code is (C) Copyright 1993 by Frank Munkert.
 * All rights reserved.
 * This software may be freely distributed and redistributed for
 * non-commercial purposes, provided this notice is included.
 */

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

#include <exec/memory.h>
#include <clib/exec_protos.h>

#include "rock.h"

/* Check whether the given volume uses the Rock Ridge Interchange Protocol.
 * The protocol is identified by the sequence
 *            'S' 'P' 7 1 0xbe 0xef
 * in the system use field of the (00) directory in the root directory of
 * the volume.
 *
 * Returns 1 iff the RR protocol is used; 0 otherwise.
 * If the RR protocol is used, p_volume->skip will be set to the skip length
 * specified in the SP system use field.
 */

int Uses_Rock_Ridge_Protocol (VOLUME *p_volume)
{
  unsigned long loc = p_volume->pvd.root.extent_loc_m;
  directory_record *dir;
  int system_use_pos;
  unsigned char *sys;

  if (!(dir = Get_Directory_Record (p_volume, loc, 0)))
    return 0;
  
  system_use_pos = 33 + dir->file_id_length;
  if (system_use_pos & 1)
    system_use_pos++;

  if (system_use_pos >= dir->length)
    return 0;

  sys = (unsigned char *) dir + system_use_pos;
  if (sys[0] == 'S' && sys[1] == 'P' && sys[2] == 7 &&
      sys[3] == 1 && sys[4] == 0xbe && sys[5] == 0xef) {
    p_volume->skip = sys[6];
    return 1;
  } else
    return 0;
}

/* Searches for the system use field with name p_name in the directory record
 * p_dir and fills the buffer p_buf (with length p_buf_len) with the information
 * contained in the system use field.
 *
 * p_index is the ordinal number of the system use field (if more than one
 * system use field with the same name is recorded.) 0=first occurrence,
 * 1=second occurrence, and so on.
 *
 * 1 is returned if the system use field has been found; otherwise 0
 * is returned.
 */

int Get_System_Use_Field (VOLUME *p_volume, directory_record *p_dir,
			  char *p_name, char *p_buf, int p_buf_len,
			  int p_index)
{
  int system_use_pos;
  int slen, len;
  unsigned long length = p_dir->length;
  unsigned char *buf = (unsigned char *) p_dir;

  system_use_pos = 33 + p_dir->file_id_length;
  if (system_use_pos & 1)
    system_use_pos++;
  system_use_pos += p_volume->skip;

  /* the system use field must be at least 4 bytes long */
  while (system_use_pos + 3 < length) {
    slen = buf[system_use_pos+2];
    if (buf[system_use_pos] == p_name[0] &&
        buf[system_use_pos+1] == p_name[1]) {
      if (p_index)
        p_index--;
      else {
        len = (slen < p_buf_len) ? slen : p_buf_len;
        memcpy (p_buf, buf + system_use_pos, len);
        return 1;
      }
    }
    /* look for continuation area: */
    if (buf[system_use_pos] == 'C' &&
        buf[system_use_pos+1] == 'E') {
      unsigned long newloc, offset;
      memcpy (&newloc, buf + system_use_pos + 8, 4);
      memcpy (&offset, buf + system_use_pos + 16, 4);
      memcpy (&length, buf + system_use_pos + 24, 4);
      if (!Read_Sector (p_volume->cd, newloc))
        return 0;
      buf = p_volume->cd->buffer;
      system_use_pos = offset;
      continue;
    }
    
    /* look for system use field terminator: */
    if (buf[system_use_pos] == 'S' &&
        buf[system_use_pos+1] == 'T')
      return 0;

    system_use_pos += slen;
  }
  return 0;
}

/* Determines the Rock Ridge file name of the CDROM object p_obj.
 * The file name will be stored in the buffer p_buf (with length p_buf_len).
 * The file name will NOT be null-terminated. The number of characters in
 * the file name is returned. If there is no Rock Ridge file name for
 * p_obj, then -1 is returned.
 */

int Get_RR_File_Name (VOLUME *p_volume, directory_record *p_dir,
		      char *p_buf, int p_buf_len)
{
  struct nm_system_use_field {
    char	  id[2];
    unsigned char length;
    unsigned char version;
    unsigned char flags;
    char          name[210];
  } nm;
  int len, slen;
  int index = 0;
  int total = 0;

  for (;;) {
    if (!Get_System_Use_Field (p_volume, p_dir, "NM",
  			       (char *) &nm, sizeof (nm), index))
      return -1;

    slen = nm.length-5;
    len = (p_buf_len < slen) ? p_buf_len : slen;
    if (len)
      memcpy (p_buf, nm.name, len);

    total += len;
    if (!(nm.flags & 1))
      return total;

    p_buf += len;
    p_buf_len -= len;
    index++;
  }
}

/* Get parent directory.
 */

unsigned long Get_Parent_Directory_RR (VOLUME *p_volume, unsigned long p_loc)
{
  directory_record *dir;
  
  /* skip first directory record: */
  if (!(dir = Get_Directory_Record (p_volume, p_loc, 0)))
    return 0;
  
  /* get directory record for parent directory: */
  if (!(dir = Get_Directory_Record (p_volume, p_loc, dir->length)))
    return 0;
  
  return dir->extent_loc_m;
}

/* Open a "CDROM object".
 */

CDROM_OBJ *Open_Object_RR (CDROM_OBJ *p_result,
			   CDROM_OBJ *p_current_dir, char *p_name)
{
  CDROM_OBJ *obj = p_result;
  char *cp = p_name;
  char name[256];
  char *np;
  directory_record *dir = 0;
  unsigned long location, old_loc;

  if (*cp == ':') {
    location = p_current_dir->volume->pvd.root.extent_loc_m;
    cp++;
  } else {
    location = p_current_dir->dir_record->extent_loc_m;
    while (*cp == '/') {    
      location = Get_Parent_Directory_RR (p_current_dir->volume, location);
      cp++;
    }
  }

  old_loc = location;

  while (*cp) {
    for (np = name; *cp && *cp != '/'; )
      *np++ = *cp++;
    *np = 0;
    
    if (name[0] == 0) {
      iso_errno = ISOERR_ILLEGAL_NAME;
      return NULL;
    }

    dir = Find_Directory_Entry (p_current_dir->volume, location, name);
    if (!dir)
	return NULL;

    if (!(dir->flags & 2)) {
      /* directory entry is a file: */
      if (*cp != 0) {
        iso_errno = ISOERR_NOT_FOUND;
	return NULL;
      }
      obj->directory_f = FALSE;
      obj->parent = location;
      obj->dir_record = AllocMem (dir->length, MEMF_PUBLIC);
      if (!obj->dir_record) {
        iso_errno = ISOERR_NO_MEMORY;
        return NULL;    
      }
      memcpy (obj->dir_record, dir, dir->length);

      return obj;
    }

    old_loc = location;
    location = dir->extent_loc_m;

    if (*cp == '/')
      cp++;
  }

  if (!dir)
    if (!(dir = Get_Directory_Record (p_current_dir->volume, location, 0)))
      return NULL;    

  obj->directory_f = TRUE;
  obj->path_table_pos = 0; /* not needed for Rock Ridge */
  obj->parent = old_loc;
  obj->dir_record = AllocMem (dir->length, MEMF_PUBLIC);
  if (!obj->dir_record) {
    iso_errno = ISOERR_NO_MEMORY;
    return NULL;
  }
  memcpy (obj->dir_record, dir, dir->length);

  return obj;
}

/* Find parent directory by location.
 */

directory_record *Find_Parent_In_Directory (VOLUME *p_volume,
					    unsigned long p_dir_location,
			                    unsigned long p_son_location)
{
  unsigned long length;
  directory_record *dir;
  unsigned long offset = 0;

  if (!(dir = Get_Directory_Record (p_volume, p_dir_location, 0)))
    return NULL;

  length = dir->data_length_m;
  
  for (;;) {
    offset += dir->length;
    for (;;) {
      if (offset >= length) {
        iso_errno = ISOERR_NOT_FOUND;
        return NULL;
      }
      dir = Get_Directory_Record (p_volume, p_dir_location, offset);
      if (!dir)
        return NULL;

      if (dir->length == 0)
        /* goto next logical sector: */
        offset = (offset & 0xfffff800) + 2048;
      else
        break;
    }
    
    if (dir->extent_loc_m == p_son_location)
      return dir;
  }
}

/* Find parent of "CDROM object."
 */

CDROM_OBJ *Find_Parent_RR (CDROM_OBJ *p_object)
{
  directory_record *dir;
  CDROM_OBJ *obj;
  unsigned long loc;

  obj = AllocMem (sizeof (CDROM_OBJ), MEMF_PUBLIC);
  if (!obj) {
    iso_errno = ISOERR_NO_MEMORY;
    return NULL;
  }

  obj->pos = 0;
  obj->volume = p_object->volume;

  /* determine location of grandfather of 'p_object': */
  loc = p_object->parent;
  if (!(dir = Get_Directory_Record (p_object->volume, loc, 0))) {  
    FreeMem (obj, sizeof (CDROM_OBJ));
    return NULL;
  }
  if (!(dir = Get_Directory_Record (p_object->volume, loc, dir->length))) {  
    FreeMem (obj, sizeof (CDROM_OBJ));
    return NULL;
  }
  loc = obj->parent = dir->extent_loc_m;

  if (!(dir = Find_Parent_In_Directory (p_object->volume, loc,
  					p_object->parent))) {
    FreeMem (obj, sizeof (CDROM_OBJ));
    return NULL;
  }

  obj->directory_f = TRUE;
  obj->path_table_pos = 0; /* not needed for Rock Ridge */
  obj->dir_record = AllocMem (dir->length, MEMF_PUBLIC);
  if (!obj->dir_record) {
    FreeMem (obj, sizeof (CDROM_OBJ));
    iso_errno = ISOERR_NO_MEMORY;
    return NULL;
  }
  memcpy (obj->dir_record, dir, dir->length);

  return obj;
}
