/*
 * Worker bees.
 *
 * The bees perform the grunt work of handling a file event
 *
 * This code was written by Peter Lawthers, and placed in the public
 * domain for use by DMAPI implementors and app writers.
 *
 * Standard disclaimer:
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/resource.h>

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

#include <lib/hsm.h>

extern char	*optarg;
extern int	 optind, optopt, opterr;
extern int	 errno;
char		*Progname;
int		 Verbose;

extern int	 restore_filedata(dm_sessid_t, void *, size_t, dm_token_t, 
				void *, size_t, dm_off_t);
extern int	 get_dmchange(dm_sessid_t, void *, size_t, dm_token_t, u_int *);
extern int	 setup_dmapi(dm_sessid_t *);
extern void	 err_msg(char *, ...);
extern void	 errno_msg(char *, ...);

int	stagein_file(dm_sessid_t, dm_token_t, dm_eventmsg_t *);
int	inval_file(dm_sessid_t, dm_token_t, dm_eventmsg_t *);
int	check_lockstate(dm_sessid_t, void *, size_t, dm_token_t);
int	clear_mrgns(dm_sessid_t, void *, size_t, dm_token_t);
int	find_msg(dm_sessid_t, dm_token_t, dm_eventmsg_t	**);
int	get_stghandle(dm_sessid_t, void *, size_t, dm_token_t, void **, 
			size_t *);
void    usage(char *);



void
usage(
	char *prog)
{
	fprintf(stderr, "Usage: %s ", prog);
	fprintf(stderr, " <-i invalidate file> ");
	fprintf(stderr, " <-r restore file> ");
	fprintf(stderr, "[-s sid] [-t token] \n");
}


int
main(
	int	argc, 
	char	*argv[])
{
	
	dm_eventmsg_t	*msgheader;
	char		*sid_str, *token_str;
	dm_sessid_t	 sid;
	dm_token_t	 token;
	int 	 	 c;
	int 	 	 error;
	int	 	 restore_flag, inval_flag;
	char		*cp;

	Progname  = argv[0];
	sid_str   = NULL;
	token_str = NULL;
	restore_flag = 0;
	inval_flag   = 0;

	while ((c = getopt(argc, argv, "s:t:ri")) != EOF) {
		switch (c) {
		case 'r':
			restore_flag++;
			break;

		case 'i':
			inval_flag++;
			break;

		case 's':
			sid_str = optarg;
			break;

		case 't':
			token_str = optarg;
			break;

		case '?':
		default:
			usage(Progname);
			exit(1);
		}
	}
	if (optind < argc) {
		usage(Progname);
		exit(1);
	}
	if (sid_str == NULL || token_str == NULL) {
		usage(Progname);
		exit(1);
	}
	if ((restore_flag > 0) && (inval_flag > 0)) {
		usage(Progname);
		exit(1);
	}

	if (sscanf(sid_str, "%d", &sid) <= 0) {
		err_msg("Can't convert sid");
		exit(1);
	}
	if (sscanf(token_str, "%d", &token) <= 0) {
		err_msg("Can't convert token");
		exit(1);
	}

	/*
	 * Now we have our session and token. We just need to
	 * let the DMAPI know we exist so we can use the interface.
	 * We don't need to create a session since we'll be using
	 * the session of our parent.
	 */
	error = dm_init_service(&cp);
	if (error == -1) {
		errno_msg("Can't init DMAPI");
		exit(1);
	}
	if (strcmp(cp, DM_VER_STR_CONTENTS)) {
		err_msg("Compiled for a different version");
		exit(1);
	}
	
	/*
	 * Find the message our caller wants us to handle
	 */
	error = find_msg(sid, token, &msgheader);
	if (error) 
		exit(1);

	/*
	 * Now service the particular event type
	 */
	if (restore_flag) 
		error = stagein_file(sid, token, msgheader);
	else
		error = inval_file(sid, token, msgheader);

	return(error);
}


/*
 * Find the data event message that correponds to the token.
 *
 * RETURNS:
 *	A pointer to malloc'd memory that contains the message
 * 	we're supposed to handle in the 'msgheader' param.
 */
int
find_msg(
	dm_sessid_t	  sid,
	dm_token_t	  token,
	dm_eventmsg_t	**msgheader)
{
	void	*buf;
	size_t	 buflen, rlen;
	int	 error;

	/*
	 * Malloc a buffer that we think is large enough for
	 * the common message header and the event specific part. 
	 * If it's not large enough, we can always resize it.
	 */
	buflen = sizeof(dm_eventmsg_t) + sizeof(dm_data_event_t) + HANDLE_LEN;
	buf = (void *)malloc(buflen);
	if (buf == NULL) {
		err_msg("Can't alloc memory for event buffer");
		return(1);
	}

	error = dm_find_eventmsg(sid, token, buflen, buf, &rlen);
	if (error == -1) {
		if (errno != E2BIG) {
			free(buf);
			errno_msg("Can't obtain message from token");
			return(1);
		}
		free(buf);
		buflen = rlen;
		buf = (void *)malloc(buflen);
		if (buf == NULL) {
			err_msg("Can't resize event buffer");
			return(1);
		}
		error = dm_find_eventmsg(sid, token, buflen, buf, &rlen);
		if (error == -1) {
			errno_msg("Can't get message with resized buffer");
			return(1);
		}
	}
	
	*msgheader = (dm_eventmsg_t *)buf;
	return(0);
}


/*
 * Check the lock state associated with the file. If the token
 * does not reference exclusive access, try to upgrade our lock.
 * If we can't upgrade, drop the lock and start over
 */
int
check_lockstate(
	dm_sessid_t	 sid, 
	void		*hanp, 
	size_t		 hlen, 
	dm_token_t	 token)
{
	int		error;
	dm_right_t	right;
	u_int		change_start, change_end;

	error = dm_query_right(sid, hanp, hlen, token, &right);
	if (error == -1) {
		errno_msg("Can't query file access rights");
		return(1);
	}
#if defined(__sgi) || defined(linux)
	/*
 	 * There are no access rights on the SGI. 1 means it's
	 * there.
	 */
	if (right == DM_RIGHT_SHARED)
		return 0;
#endif

	if (right != DM_RIGHT_EXCL) {
		error = dm_request_right(sid, hanp, hlen, token, 0,
					 DM_RIGHT_EXCL);
		if (error == -1) {
			if (errno != EAGAIN) {
				errno_msg("Can't upgrade lock");
				return(1);
			}
			error = get_dmchange(sid, hanp, hlen, token, 
						&change_start);
			if (error) 
				return(1);
				
			
			error = dm_release_right(sid, hanp, hlen, token);
			if (error == -1) {
				errno_msg("Can't release file access rights");
				return(1);
			}
			error = dm_request_right(sid, hanp, hlen, token, 
						DM_RR_WAIT, DM_RIGHT_EXCL);
			if (error == -1)  {
				errno_msg("Can't get exclusive right to file");
				return(1);
			}

			/*
			 * If the file changed while we slept, then someone
			 * must have modified the file
			 */
			error = get_dmchange(sid, hanp, hlen, token, 
						&change_end);
			if (error == -1) 
				return(1);
			
			if (change_start != change_end) {
				err_msg("File changed while waiting for lock");
				return(1);
			}
		}
	}
	return(0);
}


/*
 * Stage in the data for a file
 */
int
stagein_file(
	dm_sessid_t	 sid, 
	dm_token_t	 token,
	dm_eventmsg_t	*msgheader)
{

	void		*stg_hanp, *hanp;
	size_t	 	 stg_hlen, hlen;
	int	 	 error, ret_errno;
	dm_response_t	 reply;
	dm_data_event_t	*msg;

	/*
	 * Extract the event-specific info from the message header,
	 * then get the file handle.
	 */
	msg  = DM_GET_VALUE(msgheader, ev_data, dm_data_event_t *);
	hanp = DM_GET_VALUE(msg, de_handle, void *);
	hlen = DM_GET_LEN(msg, de_handle);

	/*
	 * Check our permissions. We need exclusive access to the 
	 * file to stage it back in.
	 */
	error = check_lockstate(sid, hanp, hlen, token);
	if (error) 
		goto out;

	/*
	 * get the staging file handle from it's DM attributes
	 */
	stg_hanp = NULL;
	error = get_stghandle(sid, hanp, hlen, token, &stg_hanp, &stg_hlen);
	if (error) 
		goto out;

	/*
	 * We keep the exclusive lock held for the *entire* duration
	 * of the stagein. This is not required, but just quick and
	 * [sl]easy. For a large file, it is typically better to release
	 * the lock, and have a sliding window of managed regions to allow
	 * people to consume the data as it is being read in.
	 */
	error = restore_filedata(sid, hanp, hlen, token, stg_hanp, stg_hlen,
			   msg->de_offset);
	if (error) 
		goto out;

	/*
	 * Now that the data is restored, and while we still have exclusive
	 * access to the file, clear the managed regions.
	 */
	error = clear_mrgns(sid, hanp, hlen, token);
	if (error) 
		goto out;

out:
	if (stg_hanp)
		free((char *)stg_hanp);

	/*
	 * Figure out what our response to the event will be. Once
	 * we've responded to the event, the token is no longer valid.
	 * On error, we pick the (less than helpful) errno EIO to signal
	 * to the user that something went wrong.
	 */
	if (error) {
		reply = DM_RESP_ABORT;
		ret_errno = EIO;
	} else {
		reply = DM_RESP_CONTINUE;
		ret_errno = 0;
	}
	(void)dm_respond_event(sid, token, reply, ret_errno, 0, 0);

	return(ret_errno);

}

/*
 * Turn off event monitoring for a file. In a real HSM, we would
 * probably want to either invalidate the file's data on 
 * tertiary storage, or start some aging process so that it will
 * eventually go away.
 *
 * The assumption is that for write and truncate events, the file
 * data is about to be invalidated.
 */
int
inval_file(
	dm_sessid_t	 sid, 
	dm_token_t	 token,
	dm_eventmsg_t	*msgheader)
{
	dm_data_event_t	*msg;
	void		*hanp;
	size_t	 	 hlen;
	int	 	 error, ret_errno;
	dm_response_t	 reply;

	/*
	 * Extract the event-specific info from the message header,
	 * then get the file handle.
	 */
	msg  = DM_GET_VALUE(msgheader, ev_data, dm_data_event_t *);
	hanp = DM_GET_VALUE(msg, de_handle, void *);
	hlen = DM_GET_LEN(msg, de_handle);

	/*
	 * Check our permissions. We need exclusive access to the 
	 * file to clear our managed regions.
	 */
	error = check_lockstate(sid, hanp, hlen, token);
	if (error) 
		goto out;

	/*
	 * Clear all the managed regions for the file.
	 */
	error = clear_mrgns(sid, hanp, hlen, token);

out:
	/*
	 * Figure out what our response to the event will be. Once
	 * we've responded to the event, the token is no longer valid.
	 * On error, we pick the (less than helpful) errno EIO to signal
	 * to the user that something went wrong.
	 */
	if (error) {
		reply = DM_RESP_ABORT;
		ret_errno = EIO;
	} else {
		reply = DM_RESP_CONTINUE;
		ret_errno = 0;
	}
	(void)dm_respond_event(sid, token, reply, ret_errno, 0, 0);
	
	return(ret_errno);
}

/*
 * Clear all of the managed regions for a file. 
 */
int
clear_mrgns(
	dm_sessid_t	 sid, 
	void		*hanp, 
	size_t		 hlen, 
	dm_token_t	 token)
{
	dm_region_t	*rgn, *sv_rgn;
	u_int		 nregions, nret;
	u_int		 exact_flag;
	int		 i;
	int		 error, retval;

	/*
	 * We take a guess first and assume there is only one managed
	 * region per file. There should'nt be more than this, but
	 * it never hurts to check, since we want to make sure that
	 * all regions are turned off.
	 *
	 * The main purpose of this is to demonstrate the use of the 
	 * E2BIG paradigm.
	 */
	retval = 1;
	nregions = 1;
	rgn = (dm_region_t *)malloc(nregions * sizeof(dm_region_t));
	if (rgn == NULL) {
		err_msg("Can't allocate memory for region buffers");
		goto out;
	}

	error = dm_get_region(sid, hanp, hlen, token, nregions, rgn, &nret);
	if (error == -1) {
		if (errno != E2BIG) {
			errno_msg("Can't get list of managed regions for file");
			goto out;
		}

		/*
		 * Now we know how many managed regions there are, so we can
		 * resize our buffer
		 */
		nregions = nret;
		free(rgn);
		rgn = (dm_region_t *)malloc(nregions * sizeof(dm_region_t));
		if (rgn == NULL) {
			err_msg("Can't resize region buffers");
			goto out;
		}
		error = dm_get_region(sid, hanp, hlen, token, nregions, rgn, 
					&nret);
		if (error == -1) {
			errno_msg("Can't get list of managed regions for file");
			goto out;
		}
	}

	sv_rgn = rgn;

	/*
	 * Clear all the managed regions
	 */
	for (i=0; i<nregions; i++) {
		rgn->rg_offset = 0;
		rgn->rg_size   = 0;
		rgn->rg_flags  = DM_REGION_NOEVENT;
		rgn++;
	}
	rgn = sv_rgn;

	error = dm_set_region(sid, hanp, hlen, token, nregions, rgn, 
				&exact_flag);
	if (error == -1) {
		errno_msg("Can't clear list of managed regions for file");
	}
	retval = 0;

out:
	if (rgn != NULL) 
		free(rgn);

	return(retval);
}


/*
 * Extract the staging file handle from a file's DM attributes
 */
int
get_stghandle(
	dm_sessid_t	  sid, 
	void		 *hanp, 
	size_t		  hlen, 
	dm_token_t	  token,
	void		**stg_hanp,
	size_t		 *stg_hlen)
{
	void		*han_buf;
	size_t		 han_len;
	int		 error;
	size_t		 rlen;
	dm_attrname_t	 hanp_attrname;
	dm_attrname_t	 hlen_attrname;

	/*
	 * First get the length of the file handle, so we
	 * can size our buffer correctly
	 */
	memcpy((void *)&hlen_attrname.an_chars[0], DLOC_HANLEN, DM_ATTR_NAME_SIZE);
	error = dm_get_dmattr(sid, hanp, hlen, token, &hlen_attrname,
				sizeof(size_t), &han_len, &rlen);
	if (error == -1) {
		/*
		 * On any error, even E2BIG, we bail since the size of
		 * the file handle should be a constant
		 */
		errno_msg("Can't get size of staging file handle");
		return(1);
	}
	if (rlen != sizeof(size_t)) {
		err_msg("File handle length component incorrect");
		return(1);
	}

	/*
	 * Malloc space for our staging file handle, and 
	 * extract it from our DM attributes
	 */
	han_buf = (void *)malloc(han_len);
	if (han_buf == NULL) {
		err_msg("Can't alloc memory for file handle");
		return(1);
	}

	memcpy((void *)&hanp_attrname.an_chars[0], DLOC_HAN, DM_ATTR_NAME_SIZE);
	error = dm_get_dmattr(sid, hanp, hlen, token, &hanp_attrname, 
				han_len, han_buf, &rlen);
	if (error == -1) {
		errno_msg("Can't get staging file handle");
		free(han_buf);
		return(1);
	}
	if (rlen != han_len) {
		err_msg("File handle is incorrect length");
		free(han_buf);
		return(1);
	}
	*stg_hanp = han_buf;
	*stg_hlen = han_len;
	return(0);
}

