/*
 * mig_tp.c
 *
 * Library example to handle processing of third-party data from a tape.
 *
 */

/*    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_tp.c,v 1.7 2004/09/29 21:14:54 rh27482 Exp $";
#endif				/* lint */
#pragma ident "$Id: mig_tp.c,v 1.7 2004/09/29 21:14:54 rh27482 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 "sam/lib.h"
#include "pub/mig.h"

#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;

/*
 * 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 this example, we link this request onto our list of stage
 * requests.  For better access to "sequential media" (tape), it is suggested
 * you 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)
{

	sam_syslog(LOG_INFO, "in usam_mig_stage_file_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 third party "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;
	mutex_unlock(&local_stage_list.mutex);
	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
 *
 * mount the media using the sam_mig_mount_media() function.
 * rewind the tape.
 * call sam_mig_stage_file() to inform SAM-FS of incoming stage data.
 * read the data of off tape using read(), send to file system using
 *   sam_mig_stage_write() call.
 * when complete send sam_mig_stage_end() to tell SAM-FS the stage is done.
 *   also send sam_mig_release_device() to release the drive.
 *
 * note that if no data is sent due to error or empty tape, a closing sequence
 *   of sam_mig_stage_end() and sam_mig_release_device() must still be done.
 */

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

	file_data = malloc(DATA_XFER_SIZE);

	/* Since media needs to be mounted, use the sam_mig_mount_media() API */

	sam_syslog(LOG_INFO, "%s: about to mount lt:XXX", ent_pnt);

	s = sam_mig_mount_media("XXX", "lt");

	sam_syslog(LOG_INFO, "%s: s_m_m_m returns %s, errno %d: %m", ent_pnt,
			s ? s : "NULL", errno);

	sprintf(buf, "/usr/bin/mt -f %s rewind", s ? s : "NULL");
	sam_syslog(LOG_INFO, "%s: about to %s", buf);
	(void) pclose(popen(buf, "w"));

	if ((read_fd = open(s, O_RDONLY)) < 0) {

		sam_syslog(LOG_INFO, "%s: open(%s,O_RDONLY) failed: errno %d: %m",
				ent_pnt, s ? s : "NULL", errno);
		sam_mig_release_device(s);

	} else {

		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 */
			if (file_data)
				free(file_data);
			close(read_fd);
			sam_mig_release_device(s);
			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);
				sam_mig_release_device(s);
				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);
					sam_mig_release_device(s);
					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);
		sam_mig_release_device(s);
	}

}
