//----------------------------------------------------------------------------------
// <copyright file="LqrSMACPseudoTerminalMaster.cpp" company="LiquidRobotics">
//
// Copyright (c) 2014 Liquid Robotics, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// This software is associated with a release.  Liquid Robotics makes no
// representation that the design, interfaces or component distribution will
// remain unchanged in future releases.
//
// </copyright>
//
// <summary>
//   Implementation of the PseudoTerminalMaster class, a controller class that opens
//   a psuedo-terminal and optionally soft-links it to a well-known location.  The
//   controller object can be treated as a ISerialPort.
// </summary>
//
// <owner>Mike Cookson</owner>
//----------------------------------------------------------------------------------

#include "LqrSMACPseudoTerminalMaster.h"
#include "LqrSMACGetErrorDescription.h"
#include <string>
#include <pty.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>

using namespace std;

namespace LiquidRobotics {
namespace SMACFramework {

PseudoTerminalMaster::PseudoTerminalMaster(const string& _wellKnownName, bool _keepSlaveOpen)
: SerialPort(_wellKnownName)
, wellKnownName(_wellKnownName)
, keepSlaveOpen(_keepSlaveOpen)
, slaveFD(-1)
{

}

PseudoTerminalMaster::PseudoTerminalMaster(const string& _wellKnownName, Logger& _logger, bool _keepSlaveOpen)
: SerialPort(_wellKnownName, _logger)
, wellKnownName(_wellKnownName)
, keepSlaveOpen(_keepSlaveOpen)
, slaveFD(-1)
{
}

PseudoTerminalMaster::~PseudoTerminalMaster()
{
}

int PseudoTerminalMaster::DoClosePort(int _fd)
{
	int status = SerialPort::DoClosePort(_fd);

	CloseSlave();
	return status;
}

int PseudoTerminalMaster::DoOpenPort(const string& _portName)
{
	int masterFD = -1;				// file descriptor returned on success

	CloseSlave();
	slaveName.clear();

	// open/create a psuedo-terminal and save error status
	const int openStatus = openpty(&masterFD, &slaveFD, NULL, NULL, NULL);
	const int openError = errno;
	int ttyNameError = EBADF;

	if ((openStatus != -1) && (slaveFD != -1))
	{
		const char* pSlave = ttyname(slaveFD);
		ttyNameError = errno;

		if (pSlave)
			slaveName = pSlave;
	}

	// describe the result of the pseudo-terminal creation
	if (pLogger)
	{
		Logger::Lock logLock(*pLogger);

		if (openStatus == -1)
		{
			pLogger->Error << "openpty() for well-known name '" << wellKnownName << "'"
			               << " returned error#" << openError << " '" << GetErrorDescription(openError) << "'" << endl;
			errno = openError;
		}
		else if (!slaveName.length())
		{
			pLogger->Error << "openpty() for well-known name '" << wellKnownName << "'"
			               << " return slave file descriptor " << slaveFD << " and no valid tty name"
			               << " (error #" << ttyNameError << " '" << GetErrorDescription(ttyNameError) << "')" << endl;
			errno = ttyNameError;
		}
		else
			pLogger->Info << "openpty() for well-known name '" << wellKnownName << "' returned slave tty '" << slaveName << "'" << endl;
	}

	// return if the open failed (errno was preserved above)
	if (openStatus == -1)
		return -1;

	// ensure that the resulting slave filename is readable and writeable by everyone
	const int fchmodStatus = fchmod(slaveFD, 0777);
	const int fchmodError = errno;

	if (fchmodStatus == -1)
	{
		if (pLogger)
		{
			Logger::Lock logLock(*pLogger);
			pLogger->Error << "fchmod(" << slaveFD << ",0777) on slave FD (" << slaveName << ") for well-known name '" << wellKnownName << "'"
			               << " returned error#" << fchmodError << " '" << GetErrorDescription(fchmodError) << "'" << endl;
			errno = fchmodError;
		}

		return -1;
	}

	CloseSlave(false);					// close slave if not keeping it open by configuration

	// if a well-known name was specified, then symbolically link the
	// slave tty name to the well-known name
	if (wellKnownName.length())
	{
		// we're going to fail if the file exists and it's not a symbolic link
		struct stat statBuf;
		const int statStatus = lstat(wellKnownName.c_str(), &statBuf);
		const int statError = errno;

		if ((statStatus == -1) && (statError != ENOENT))
		{
			CloseSlave();
			close(masterFD);

			if (pLogger)
			{
				Logger::Lock logLock(*pLogger);
				pLogger->Error << "lstat(" << wellKnownName << ",)"
				               << " returned error#" << statError << " '" << GetErrorDescription(statError) << "'" << endl;
				errno = statError;
			}

			return -1;
		}
		else if (!statStatus && !S_ISLNK(statBuf.st_mode))
		{
			CloseSlave();
			close(masterFD);

			if (pLogger)
			{
				Logger::Lock logLock(*pLogger);
				pLogger->Error << "Well-known name " << wellKnownName
							   << " refers to a filesystem entry that is not a symbolic link, so PseudoTerminalMaster"
							   << " will not remove it" << endl;
				errno = EACCES;
			}

			return -1;
		}

		// remove the existing entry if any
		if (statStatus != -1)
		{
			const int unlinkStatus = unlink(wellKnownName.c_str());
			const int unlinkError = errno;

			if (unlinkStatus == -1)
			{
				CloseSlave();
				close(masterFD);

				if (pLogger)
				{
					Logger::Lock logLock(*pLogger);
					pLogger->Error << "unlink(" << wellKnownName << ")"
								   << " returned error#" << unlinkError << " '" << GetErrorDescription(unlinkError) << "'" << endl;
					errno = unlinkError;
				}

				return -1;
			}
		}

		// create the new symlink
		const int symlinkStatus = symlink(slaveName.c_str(), wellKnownName.c_str());
		const int symlinkError = errno;

		if (symlinkStatus == -1)
		{
			CloseSlave();
			close(masterFD);				// don't leave the master FD open

			if (pLogger)
			{
				Logger::Lock logLock(*pLogger);
				pLogger->Error << "symlink(" << slaveName << "," << wellKnownName << ")"
							   << " returned error#" << symlinkError << " '" << GetErrorDescription(symlinkError) << "'" << endl;
				errno = symlinkError;
			}

			return -1;
		}
	}

	return masterFD;
}

void PseudoTerminalMaster::CloseSlave(bool forceClose)
{
	if ((forceClose || !keepSlaveOpen) && (slaveFD != -1))
	{
		close(slaveFD);
		slaveFD = -1;
	}
}

}
}
