/* 
 *   mig_cd.c 
 * 
 * Library routines to handle processing of third-party data from a CD-ROM.
 * 
 */

/*    LSC_disclaimer_begin 
 *
 *  Copyright (c) 2003 Sun Microsystems, Inc.
 *  All rights reserved.
 *
 *  This file is a product of Sun Microsystems, Inc. and is provided for 
 *  unrestricted use provided that this header is included on all media 
 *  and as a part of the software program in whole or part.  Users may 
 *  copy, modify or distribute this file at will.
 *
 *  This file is provided with no support and without any obligation on 
 *  the part of Sun Microsystems, Inc. to assist in its use, correction, 
 *  modification or enhancement.
 *
 *  THIS FILE IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING
 *  THE WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
 *  PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
 *
 *  SUN MICROSYSTEMS INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE 
 *  INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY THIS FILE 
 *  OR ANY PART THEREOF.
 *
 *  IN NO EVENT WILL SUN MICROSYSTEMS, INC. BE LIABLE FOR ANY LOST REVENUE 
 *  OR PROFITS OR OTHER SPECIAL, INDIRECT AND CONSEQUENTIAL DAMAGES, EVEN 
 *  IF THEY HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 *
 *  Sun Microsystems, Inc.
 *
 *    LSC_disclaimer_end                                            
 */

#ifndef lint
static char rcs_id[] = "@(#)$Id: mig_cd.c,v 1.2.2.3 2003/05/07 17:01:55 jdunn Exp $";
#endif /* lint */
#pragma ident "$Id: mig_cd.c,v 1.2.2.3 2003/05/07 17:01:55 jdunn Exp $"

#include <thread.h>
#include <synch.h>
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <syslog.h>
#include <ndbm.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/syscall.h>

#include "mig.h"

/*
 * location of CD database -- created by this program, where %s will be the
 * CD directory.  For example, if your cd-path is /cdrom/cdrom0 then it will
 * create a database file at:  /tmp/db-cdrom0
 * NOTE:  This constant must be the same as in mig_build_cd.c 
 */
#define DB_PATHSTR "/usr/tmp/db-%s"

#define  DATA_XFER_SIZE   (64 * 1024)	/* units of I/O to read */

/* Function prototypes */
void *local_stage_processor (void *);
void local_stage_file (tp_stage_t *);

/* list of stage requests */
typedef struct
  {
    mutex_t mutex;
    cond_t cond;
    int count;
    tp_stage_t *next;
    tp_stage_t *last;
  }
local_stage_list_t;

local_stage_list_t local_stage_list;
char *current_database = NULL;
DBM *current_db = NULL;

/* sam_mig_initialize

 * Called by the thirdparty "device" to allow the interface to initialize
 * any local structs, threads, etc.
 */

int
usam_mig_initialize (int stage_count)
{
  /* Initialize the stage list to USYNC_THREAD (all zero) */
  memset (&local_stage_list, 0, sizeof (local_stage_list));

  if (thr_create (NULL, 0, local_stage_processor, (void *) NULL,
		  (THR_BOUND | THR_DETACHED | THR_NEW_LWP), NULL))
    {
      sam_syslog (LOG_INFO, "Unable to start local_stage_processor: %m");
      return (-1);
    }
  return (0);
}

/* sam_mig_stage_file_req

 * Called by the thirdparty "device" to inform the interface that the
 * file system has requested a stage.
 *
 * For the simple case, we well just link this request onto our list
 * of stage requests.  For access to "sequential media" (tape), it
 * would be a better idea to keep multiple lists based on physical
 * media and ordered by position.  This would allow the media to
 * be read in a less random mode.  This is not a requirement but it
 * does speed up staging many files from one tape.
 */

int
usam_mig_stage_file_req (tp_stage_t * stage_req)
{

  /* Use the private data region as the next pointer for the list */
  stage_req->tp_data = NULL;

  /* Link the request onto the list of stage requests.
   * Must get the list mutex first to insure that the other
   * threads are not using the list. */
  mutex_lock (&local_stage_list.mutex);

  /* Link this stage request onto the list */
  if (local_stage_list.count++ == 0)	/* no entries on the list */
    local_stage_list.next = stage_req;
  else				/* put it on the end */
    local_stage_list.last->tp_data = stage_req;

  /* Adjust last to point to the new entry */
  local_stage_list.last = stage_req;


  /* Wake up local_stage_processor */
  cond_signal (&local_stage_list.cond);
  mutex_unlock (&local_stage_list.mutex);

  return (0);
}

/* sam_mig_cancel_stage_req

 * Called by the thirdparty "device" to inform the interface that the
 * file system has canceled a stage request.  Most likely the user
 * has terminated their request.
 */

int
usam_mig_cancel_stage_req (tp_stage_t * stage_req)
{
  tp_stage_t *sr_p, *last_sr_p = NULL;

  mutex_lock (&local_stage_list.mutex);
  for (sr_p = local_stage_list.next;
       sr_p != NULL;
       sr_p = (tp_stage_t *) sr_p->tp_data)
    {
      if (sr_p == stage_req)	/* Found it */
	break;
      else
	last_sr_p = sr_p;	/* keep last pointer */
    }

  if (sr_p == NULL)		/* not found */
    {
      mutex_unlock (&local_stage_list.mutex);
      return (-1);		/* cannot cancel (can't find it) */
    }

  if (last_sr_p == NULL)	/* looks like the head of the list */
    local_stage_list.next = (tp_stage_t *) sr_p->tp_data;
  else
    last_sr_p->tp_data = sr_p->tp_data;

  return (0);
}


/* local_stage_processor

 * Wait for stage request to arrive on the list and process them
 * one at a time off the top of the list.
 */

void *
local_stage_processor (void *noarg)
{
  tp_stage_t *stage_req;

  /* Loop forever waiting for a request */
  while (1)
    {
      mutex_lock (&local_stage_list.mutex);
      /* Wait for the count to go non zero */
      while (local_stage_list.count == 0)	/* wait for something */
	cond_wait (&local_stage_list.cond, &local_stage_list.mutex);

      /* Pull the entry off the list, decrement the count */
      stage_req = local_stage_list.next;
      local_stage_list.count--;
      local_stage_list.next = (tp_stage_t *) stage_req->tp_data;

      /* Release the mutex */
      mutex_unlock (&local_stage_list.mutex);

      /* process the stage */
      local_stage_file (stage_req);
    }
}

/* local_stage_file

 * Find the file in the database and do the stage.
 */

void
local_stage_file (tp_stage_t * stage_req)
{
  int read_fd, position = stage_req->position;
  char *file_data;
  char *ent_pnt = "local_stage_file";
  offset_t offset;
  int left;
  datum db_key;
  datum db_data;

  /* If media needs to be mounted, use the fs_mount_media() API */

  /* We use position as the key into our database */
  db_key.dptr = (char *) &position;
  db_key.dsize = sizeof (position);

  /* We used the vsn as the database name, check to see if this request
   * is for the same database as the last request.  If not, close the
   * last database and open the new one.
   */
  if (current_database == NULL ||
      strcmp (current_database, stage_req->vsn) != 0)
    {
      char db_fullpath[MAXPATHLEN];
      if (current_database != NULL)
	{
	  dbm_close (current_db);
	  free (current_database);
	  current_database = NULL;
	}

      if ((current_database = strdup (stage_req->vsn)) == NULL)
	{
	  int hold_err = errno;	/* syslog destroys errno */
	  sam_syslog (LOG_INFO, "%s: Unable to dup database name: %m", ent_pnt);

	  /* Notify file system of the error */
	  sam_mig_stage_error (stage_req, hold_err);
	  return;
	}

      sprintf (db_fullpath, DB_PATHSTR, current_database);
      if ((current_db = dbm_open (db_fullpath, O_RDONLY, 0)) == NULL)
	{
	  int hold_err = errno;	/* syslog destroys errno */
	  sam_syslog (LOG_INFO, "%s: Unable to open database %s: %m.", ent_pnt,
		  db_fullpath);

	  /* Notify file system of the error */
	  sam_mig_stage_error (stage_req, hold_err);
	  return;
	}
    }

  db_data = dbm_fetch (current_db, db_key);
  if (db_data.dptr == NULL)
    {
      sam_syslog (LOG_INFO, "%s: Unable to locate %d: %m,", ent_pnt, position);

      /* Notify file system of the error */
      sam_mig_stage_error (stage_req, ENOENT);
      return;
    }


  if ((read_fd = open (db_data.dptr, O_RDONLY)) < 0)
    {
      sam_syslog (LOG_INFO, "%s: Unable to open %s: %m", ent_pnt, db_data.dptr);

      /* Notify file system of the error */
      sam_mig_stage_error (stage_req, ENOENT);
      return;
    }


  while ((file_data = (char *) malloc (DATA_XFER_SIZE)) == NULL)
    {
      /* Your interface may want to be more critical in this case.
       * malloc failures are usually temporary.
       */
      sam_syslog (LOG_INFO, "%s: Unable to malloc space: %m", ent_pnt);
      sleep (10);
    }

  /* If this is a stage request for a file with stage_never attribute,
   * then the offset will be non zero.  We need to position to this
   * offset before the read.
   */
  if (stage_req->offset != 0)
    llseek (read_fd, stage_req->offset, SEEK_SET);

  /* Set up the file system to receive the stage data */
  if (sam_mig_stage_file (stage_req))
    {
      /* The file system refused the stage request.  This usually
       * happens if the stage requests was canceled (ECANCELED) or
       * there is not enough space to stage the
       * file (or the segment if stage never) (ENOSPC).
       */
      sam_syslog (LOG_INFO, "%s: sam_mig_stage_file returned error: %m", ent_pnt);

      /* Free resources */
      free (file_data);
      close (read_fd);
      return;
    }

  left = stage_req->size;	/* amount of data left to xfer */
  offset = 0;			/* offset for our writes */

  /* Continue the read, stage_write cycle until all requested data
   * has been sent.
   */
  while (left > 0)
    {
      int amt_read, amt_sent, read_size;
      char *buffer = file_data;


      /* Only read the smaller of whats left or the transfer size */
      read_size = left > DATA_XFER_SIZE ? DATA_XFER_SIZE : left;
      amt_read = read (read_fd, file_data, read_size);
      if (amt_read < 0)		/* read error */
	{
	  int hold_err = errno;	/* syslog destroys errno */

	  sam_syslog (LOG_INFO, "%s: Read error %s: %m", ent_pnt, db_data.dptr);
	  /* Free resources */
	  close (read_fd);
	  free (file_data);

	  /* End the stage with the error */
	  sam_mig_stage_end (stage_req, hold_err);
	  return;
	}

      /* Loop sending the data to the file system */
      while (amt_read > 0)
	{
	  amt_sent = sam_mig_stage_write (stage_req, buffer, amt_read, offset);
	  if (amt_sent <= 0)
	    {
	      int hold_err = errno;	/* syslog destroys errno */

	      sam_syslog (LOG_INFO, "%s: sam_mig_stage_write %s: %m", ent_pnt,
		      db_data.dptr);
	      /* Free resources */
	      close (read_fd);
	      free (file_data);

	      /* End the stage with the error */
	      sam_mig_stage_end (stage_req, hold_err);
	      return;
	    }

	  buffer += amt_sent;	/* adjust data pointer */
	  offset += amt_sent;	/* adjust data offset */
	  amt_read -= amt_sent;	/* amount left to send this buffer */
	  left -= amt_sent;	/* amount left to send for file */
	}
    }

  /* File has been sent, free resources and clean up messages */
  free (file_data);
  close (read_fd);

  /* Inform file system that this stage request finished
   * without error.
   */
  sam_mig_stage_end (stage_req, 0);
  return;
}
