#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include <unistd.h>

#include "../../include/fio.h"
#include "../../include/disk.h"
#include "../../include/prochandle.h"

#include "../lib/endeavour2.h"

#include "ziptoolio.h"
#include "config.h"


static void ZipToolLoadFileToString(
	const gchar *path, gchar *buf, gint buf_len
);

gchar *ZipToolLastError(void);

gint ZipToolMount(edv_device_struct *dev_ptr);
gint ZipToolUnmount(edv_device_struct *dev_ptr);

zip_tool_lock_state ZipToolDeviceIsProtected(edv_device_struct *dev_ptr);
gint ZipToolLock(
	edv_device_struct *dev_ptr, const gchar *password
);
gint ZipToolUnlock(
	edv_device_struct *dev_ptr, const gchar *password
);

gint ZipToolSpinDown(edv_device_struct *dev_ptr);
gint ZipToolEject(edv_device_struct *dev_ptr);


#define ATOI(s)         (((s) != NULL) ? atoi(s) : 0)
#define ATOL(s)         (((s) != NULL) ? atol(s) : 0)
#define ATOF(s)         (((s) != NULL) ? atof(s) : 0.0f)
#define STRDUP(s)       (((s) != NULL) ? g_strdup(s) : NULL)

#define MAX(a,b)        (((a) > (b)) ? (a) : (b))
#define MIN(a,b)        (((a) < (b)) ? (a) : (b))
#define CLIP(a,l,h)     (MIN(MAX((a),(l)),(h)))
#define STRLEN(s)       (((s) != NULL) ? strlen(s) : 0)
#define STRISEMPTY(s)   (((s) != NULL) ? (*(s) == '\0') : TRUE)

#define UNLINK(p)	(STRISEMPTY(p) ? -1 : unlink(p))

#define DEV_NULL_PATH		"/dev/null"
#define ZIP_TOOL_PROG_PATH	"/usr/bin/ziptool"


static gchar last_error[1024];


/*
 *	Reads the contents of the file into the specified string.
 */
static void ZipToolLoadFileToString(
	const gchar *path, gchar *buf, gint buf_len
)
{
	FILE *fp = !STRISEMPTY(path) ? FOpen(path, "rb") : NULL;
	if(fp == NULL)
	{
	    if((buf != NULL) && (buf_len > 0))
		*buf = '\0';
	    return;
	}

	if(buf != NULL)
	{
	    gint bytes_read = fread(buf, sizeof(gchar), buf_len, fp);
	    if(bytes_read >= buf_len)
		bytes_read = buf_len - 1;
	    if(bytes_read >= 0)
		buf[bytes_read] = '\0';
	    else
		*buf = '\0';
	}

	FClose(fp);
}

/*
 *	Returns a statically allocated string describing the last
 *	error or NULL if there was no error.
 */
gchar *ZipToolLastError(void)
{
	return(STRISEMPTY(last_error) ? NULL : last_error);
}

/*
 *	Mounts the device.
 *
 *      Returns:
 *
 *      0       Success (or device is already mounted).
 *      -1      General error
 *      -2      Bad or ambiguous value
 *      -3      Systems error or unable to execute command
 */
gint ZipToolMount(edv_device_struct *dev_ptr)
{
	pid_t p;
	const gchar *tmp_dir;
	gchar *cmd, *stderr_path;

	*last_error = '\0';		/* Reset last error message */

	if(dev_ptr == NULL)
	    return(-2);

	if(STRISEMPTY(dev_ptr->device_path))
	    return(-2);
	if(STRISEMPTY(dev_ptr->mount_path))
	    return(-2);

	/* Get temp dir */
	tmp_dir = g_getenv("TMPDIR");
	if(STRISEMPTY(tmp_dir))      
#ifdef P_tmpdir
	    tmp_dir = P_tmpdir;
#else
	    tmp_dir = "/tmp"; 
#endif

	/* Generate tempory output file paths */
	stderr_path = STRDUP(PrefixPaths(tmp_dir, "ziptoolXXXXXX"));
	mkstemp(stderr_path);                                       

	/* Format and execute eject command */
	cmd = g_strdup_printf(
	    "%s -m %s %s",
	    ZIP_TOOL_PROG_PATH,
	    dev_ptr->device_path, dev_ptr->mount_path
	);
	p = ExecBOE(cmd, DEV_NULL_PATH, stderr_path);
	g_free(cmd);

	/* Get error if any */
	ZipToolLoadFileToString(
	    stderr_path, last_error, sizeof(last_error)  
	);

	/* Remove tempory output files */
	UNLINK(stderr_path);
	g_free(stderr_path);

	return((p == 0) ? -3 : 0);
}

/*
 *	Unmounts the device.
 *
 *      Returns:
 *
 *      0       Success (or device is already unmounted).
 *      -1      General error
 *      -2      Bad or ambiguous value
 *      -3      Systems error or unable to execute command
 */
gint ZipToolUnmount(edv_device_struct *dev_ptr)
{
	pid_t p;
	const gchar *tmp_dir;
	gchar *cmd, *stderr_path;

	*last_error = '\0';		/* Reset last error message */

	if(dev_ptr == NULL)
	    return(-2);

	if(STRISEMPTY(dev_ptr->device_path))
	    return(-2);

	/* Get temp dir */
	tmp_dir = g_getenv("TMPDIR");
	if(STRISEMPTY(tmp_dir))      
#ifdef P_tmpdir
	    tmp_dir = P_tmpdir;
#else
	    tmp_dir = "/tmp"; 
#endif

	/* Generate tempory output file paths */   
	stderr_path = STRDUP(PrefixPaths(tmp_dir, "ziptoolXXXXXX"));
	mkstemp(stderr_path);                                       

	/* Format and execute eject command */
	cmd = g_strdup_printf(
	    "%s -u %s",
	    ZIP_TOOL_PROG_PATH,
	    dev_ptr->device_path
	);
	p = ExecBOE(cmd, DEV_NULL_PATH, stderr_path);
	g_free(cmd);

	/* Get error if any */
	ZipToolLoadFileToString(
	    stderr_path, last_error, sizeof(last_error)
	);

	/* Remove tempory output files */
	UNLINK(stderr_path);
	g_free(stderr_path);

	return((p == 0) ? -3 : 0);
}

/*
 *	Checks if the device is unlocked, locked, or locked with
 *	password.
 */
zip_tool_lock_state ZipToolDeviceIsProtected(edv_device_struct *dev_ptr)
{
	gint status = 0;
	pid_t p;
	const gchar *tmp_dir;
	gchar *cmd, *stdout_path, *stderr_path;

	*last_error = '\0';             /* Reset last error message */

	if(dev_ptr == NULL)
	    return(status);

	if(STRISEMPTY(dev_ptr->device_path))
	    return(status);

	/* Get temp dir */
	tmp_dir = g_getenv("TMPDIR");
	if(STRISEMPTY(tmp_dir))      
#ifdef P_tmpdir
	    tmp_dir = P_tmpdir;
#else
	    tmp_dir = "/tmp"; 
#endif

	/* Generate tempory output file paths */
	stdout_path = STRDUP(PrefixPaths(tmp_dir, "ziptoolXXXXXX"));
	mkstemp(stdout_path);
	stderr_path = STRDUP(PrefixPaths(tmp_dir, "ziptoolXXXXXX"));
	mkstemp(stderr_path);

	/* Format and execute device status command */
	cmd = g_strdup_printf(
	    "%s -s %s",
	    ZIP_TOOL_PROG_PATH,
	    dev_ptr->device_path
	);
	p = ExecBOE(cmd, stdout_path, stderr_path);
	g_free(cmd);
	if(p == 0)
	{
	    /* Error */
	    UNLINK(stdout_path);
	    g_free(stdout_path);
	    UNLINK(stderr_path);
	    g_free(stderr_path);
	    return(status);
	}
	else
	{
	    /* Success, read first line of output file */
	    FILE *stdout_fp = FOpen(stdout_path, "rb");
	    gchar *buf = FGetStringLiteral(stdout_fp);
	    FClose(stdout_fp);

	    /* Got first line of output file? */
	    if(buf != NULL)
	    {
		if(strstr(
		    buf,
		    "password write-protected"
		) != NULL)
		    status = 2;
		else if(strstr(
		    buf,
		    "write-protected"
		) != NULL)
		    status = 1;
		else if(strstr(
		    buf,
		    "not protected"
		) != NULL)
		    status = 0;

		/* All else assume not protected */

		g_free(buf);
	    }
	}

	/* Get error if any */
	ZipToolLoadFileToString(
	    stderr_path, last_error, sizeof(last_error)
	);

	/* Remove tempory output files */
	UNLINK(stdout_path);
	g_free(stdout_path);
	UNLINK(stderr_path);
	g_free(stderr_path);

	return(status);
}


/*
 *	Locks the device (must be currently unlocked). If password
 *	is not NULL then the device will be locked with password.
 *
 *	Returns:
 *
 *	0	Success
 *	-1	General error
 *	-2	Bad or ambiguous value
 *	-3	Systems error or unable to execute command
 *	-4	Unable to lock
 *	-5	Device is already locked (with or without password),
 *		must unlock before locking again
 */
gint ZipToolLock(
	edv_device_struct *dev_ptr, const gchar *password
)
{
	*last_error = '\0';             /* Reset last error message */

	if(dev_ptr == NULL)
	    return(-2);

	if(STRISEMPTY(dev_ptr->device_path))
	    return(-2);

	/* Device is already locked? */
	if(ZipToolDeviceIsProtected(dev_ptr) != ZIP_TOOL_LOCK_STATE_UNLOCKED)
	    return(-5);

	/* Lock with password? */
	if(!STRISEMPTY(password))
	{
	    /* Format and execute lock device with password command */
	    gchar *cmd = g_strdup_printf(
		"%s -rp %s",
		ZIP_TOOL_PROG_PATH,
		dev_ptr->device_path
	    );
	    FILE *fp = popen(cmd, "w");
	    g_free(cmd);
	    if(fp == NULL)
		return(-3);

	    /* Send password */
	    fprintf(fp, "%s\n", password);

	    pclose(fp);
	}
	else
	{
	    /* Format and execute lock device command */
	    gchar *cmd = g_strdup_printf(
		"%s -ro %s",
		ZIP_TOOL_PROG_PATH,
		dev_ptr->device_path
	    );
	    FILE *fp = popen(cmd, "w");
	    g_free(cmd);
	    if(fp == NULL)
		return(-3);

	    /* Do not send any password */

	    pclose(fp);
	}

#if 0
	/* Device was not successfully locked? */
/* This does not work quite well, because the device is ejected after
 * locking and there is no way to see if it was locked correctly
 */
	if(ZipToolDeviceIsProtected(dev_ptr) == ZIP_TOOL_LOCK_STATE_UNLOCKED)
	    return(0);
	else
	    return(-4);
#endif
	return(0);
}


/*
 *	Unlocks the device (if it is already locked) and uses the
 *	given password.
 *
 *	Returns:
 *
 *	0	Success (or device is already unlocked)
 *	-1	General error
 *	-2	Bad or ambiguous value
 *	-3	Systems error or unable to execute command
 *	-4	Bad password or unable to unlock
 */
gint ZipToolUnlock(
	edv_device_struct *dev_ptr, const gchar *password
)
{
	gchar *cmd;
	FILE *fp;

	*last_error = '\0';             /* Reset last error message */

	if(dev_ptr == NULL)
	    return(-2);

	if(STRISEMPTY(dev_ptr->device_path))
	    return(-2);

	/* Device is not locked? */
	if(ZipToolDeviceIsProtected(dev_ptr) == ZIP_TOOL_LOCK_STATE_UNLOCKED)
	    return(0);

	if(password == NULL)
	    password = "";

	/* Format and execute unlock device command */
	cmd = g_strdup_printf(
	    "%s -rw %s",
	    ZIP_TOOL_PROG_PATH,
	    dev_ptr->device_path
	);
	fp = popen(cmd, "w");
	g_free(cmd);
	if(fp == NULL)
	    return(-3);

	/* Send password */
	fprintf(fp, "%s\n", password);

	pclose(fp);

	/* Device was not successfully unlocked? */
	if(ZipToolDeviceIsProtected(dev_ptr) != ZIP_TOOL_LOCK_STATE_UNLOCKED)
	    return(-4);
	else
	    return(0);
}

/*
 *	Spins down the device
 *
 *	Returns:
 *
 *	0	Success (or device already spinned down)
 *	-1	General error
 *	-2	Bad or ambiguous value
 *	-3	Systems error or unable to execute command
 */
gint ZipToolSpinDown(edv_device_struct *dev_ptr)
{
	pid_t p;
	const gchar *tmp_dir;
	gchar *cmd, *stderr_path;

	*last_error = '\0';             /* Reset last error message */

	if(dev_ptr == NULL)
	    return(-2);

	if(STRISEMPTY(dev_ptr->device_path))
	    return(-2);

	/* Get temp dir */
	tmp_dir = g_getenv("TMPDIR");
	if(STRISEMPTY(tmp_dir))
#ifdef P_tmpdir
	    tmp_dir = P_tmpdir;
#else
	    tmp_dir = "/tmp";   
#endif

	/* Generate tempory output file paths */
	stderr_path = STRDUP(PrefixPaths(tmp_dir, "ziptoolXXXXXX"));
	mkstemp(stderr_path);

	/* Format and execute spin down command */
	cmd = g_strdup_printf(
	    "%s -p %s",
	    ZIP_TOOL_PROG_PATH,
	    dev_ptr->device_path
	);
	p = ExecBOE(cmd, DEV_NULL_PATH, stderr_path);
	g_free(cmd);

	/* Get error if any */
	ZipToolLoadFileToString(
	    stderr_path, last_error, sizeof(last_error)
	);
	  
	/* Remove tempory output files */   
	UNLINK(stderr_path);
	g_free(stderr_path);

	return((p == 0) ? -3 : 0);
}

/*
 *	Ejects the media from the device
 *
 *      Returns:
 *
 *      0       Success (or media is already ejected)
 *      -1      General error
 *      -2      Bad or ambiguous value
 *      -3      Systems error or unable to execute command
 *      -4      Device is currently mounted
 */
gint ZipToolEject(edv_device_struct *dev_ptr)
{
	pid_t p;
	const gchar *tmp_dir;
	gchar *cmd, *stderr_path;

	*last_error = '\0';             /* Reset last error message */

	if(dev_ptr == NULL)
	    return(-2);

	if(STRISEMPTY(dev_ptr->device_path))
	    return(-2);

	/* Get temp dir */
	tmp_dir = g_getenv("TMPDIR");
	if(STRISEMPTY(tmp_dir))
#ifdef P_tmpdir
	    tmp_dir = P_tmpdir;
#else
	    tmp_dir = "/tmp";   
#endif

	/* Generate tempory output file paths */
	stderr_path = STRDUP(PrefixPaths(tmp_dir, "ziptoolXXXXXX"));  
	mkstemp(stderr_path);                                         

	/* Format and execute eject command */
	cmd = g_strdup_printf(
	    "%s -e %s",
	    ZIP_TOOL_PROG_PATH,
	    dev_ptr->device_path
	);
	p = ExecBOE(cmd, DEV_NULL_PATH, stderr_path);
	g_free(cmd);

	/* Get error if any */
	ZipToolLoadFileToString(
	    stderr_path, last_error, sizeof(last_error)
	);

	/* Remove tempory output files */   
	UNLINK(stderr_path);
	g_free(stderr_path);

	return((p == 0) ? -3 : 0);
}

