/******************************************************************************
 jFileUtil_UNIX.cc

	File utilities implemented for the UNIX System.

	Copyright © 1996 by John Lindal. All rights reserved.

 ******************************************************************************/

#include <jFileUtil.h>
#include <jDirUtil.h>
#include <JStdError.h>
#include <JString.h>
#include <JProcess.h>
#include <unistd.h>
#include <sys/stat.h>
#include <stdio.h>
#include <jErrno.h>
#include <jAssert.h>

/******************************************************************************
 JFileExists

	Returns kTrue if the specified file exists.

 ******************************************************************************/

JBoolean
JFileExists
	(
	const JCharacter* fileName
	)
{
	struct stat info;
	return JI2B(
			lstat(fileName, &info) == 0 &&
			stat( fileName, &info) == 0 &&
			S_ISREG(info.st_mode) );
}

/******************************************************************************
 JFileReadable

	Returns kTrue if the specified file can be read from.

 ******************************************************************************/

JBoolean
JFileReadable
	(
	const JCharacter* fileName
	)
{
	return JI2B( (getuid() == 0 && JFileExists(fileName)) ||
				 access(fileName, R_OK) == 0 );
}

/******************************************************************************
 JFileWritable

	Returns kTrue if the specified file can be written to.

 ******************************************************************************/

JBoolean
JFileWritable
	(
	const JCharacter* fileName
	)
{
	return JI2B( (getuid() == 0 && JFileExists(fileName)) ||
				 access(fileName, W_OK) == 0 );
}

/******************************************************************************
 JFileExecutable

	Returns kTrue if the specified file can be executed.

	Readability is not checked, because this is only an issue for scripts,
	and I can't tell the difference between a script and a binary.

 ******************************************************************************/

JBoolean
JFileExecutable
	(
	const JCharacter* fileName
	)
{
	if (getuid() == 0)
		{
		struct stat stbuf;
		return JI2B( stat(fileName, &stbuf) == 0 &&
					 (stbuf.st_mode & S_IXUSR) != 0 );
		}
	else
		{
		return JI2B( access(fileName, X_OK) == 0 );
		}
}

/******************************************************************************
 JGetFileLength

	Sets *size to the the length of the specified file.
	Can return JFileDoesNotExist.

 ******************************************************************************/

JError
JGetFileLength
	(
	const JCharacter*	name,
	JSize*				size
	)
{
	struct stat info;
	if (stat(name, &info) == 0)
		{
		*size = info.st_size;
		return JNoError();
		}

	*size = 0;

	const int err = jerrno();
	if (err == ENOENT)
		{
		return JFileDoesNotExist();
		}
	else
		{
		return JUnexpectedError(err);
		}
}

/******************************************************************************
 JRenameFile

	Renames the specified file.

	Can return JCantRenameFileToDirectory, JCantRenameAcrossFilesystems,
	JCantRenameToNonemptyDirectory, JFileBusy, JDirectoryCantBeOwnChild,
	JTooManyLinks, JComponentNotDirectory, JSegFault, JAccessDenied,
	JNameTooLong, JBadPath, JNoKernelMemory, JFileSystemReadOnly,
	JPathContainsLoop, JFileSystemFull.

 ******************************************************************************/

JError
JRenameFile
	(
	const JCharacter* oldName,
	const JCharacter* newName
	)
{
	if (JDirectoryExists(newName))
		{
		return JDirectoryAlreadyExists();
		}

	JString on = oldName;
	JStripTrailingDirSeparator(&on);
	JString nn = newName;
	JStripTrailingDirSeparator(&nn);
	if (on == nn)
		{
		return JNoError();
		}

	jclear_errno();
	if (rename(on, nn) == 0)
		{
		return JNoError();
		}

	const int err = jerrno();
	if (err == EISDIR)
		{
		return JCantRenameFileToDirectory();
		}
	else if (err == EXDEV)
		{
		return JCantRenameAcrossFilesystems();
		}
	else if (err == ENOTEMPTY)
		{
		return JCantRenameToNonemptyDirectory();
		}
	else if (err == EBUSY)
		{
		return JFileBusy();
		}
	else if (err == EINVAL)
		{
		return JDirectoryCantBeOwnChild();
		}
	else if (err == EMLINK)
		{
		return JTooManyLinks();
		}
	else if (err == ENOTDIR)
		{
		return JComponentNotDirectory();
		}
	else if (err == EFAULT)
		{
		return JSegFault();
		}
	else if (err == EACCES || err == EPERM)
		{
		return JAccessDenied();
		}
	else if (err == ENAMETOOLONG)
		{
		return JNameTooLong();
		}
	else if (err == ENOENT)
		{
		return JBadPath();
		}
	else if (err == ENOMEM)
		{
		return JNoKernelMemory();
		}
	else if (err == EROFS)
		{
		return JFileSystemReadOnly();
		}
	else if (err == ELOOP)
		{
		return JPathContainsLoop();
		}
	else if (err == ENOSPC)
		{
		return JFileSystemFull();
		}
	else
		{
		return JUnexpectedError(err);
		}
}

/******************************************************************************
 JRemoveFile

	Removes the specified file.

	Can return JSegFault, JAccessDenied, JNameTooLong, JBadPath,
	JComponentNotDirectory, JTriedToRemoveDirectory, JNoKernelMemory,
	JFileSystemReadOnly.

 ******************************************************************************/

JError
JRemoveFile
	(
	const JCharacter* fileName
	)
{
	jclear_errno();
	if (remove(fileName) == 0)
		{
		return JNoError();
		}

	const int err = jerrno();
	if (err == EFAULT)
		{
		return JSegFault();
		}
	else if (err == EACCES || err == EPERM)
		{
		return JAccessDenied();
		}
	else if (err == ENAMETOOLONG)
		{
		return JNameTooLong();
		}
	else if (err == ENOENT)
		{
		return JBadPath();
		}
	else if (err == ENOTDIR)
		{
		return JComponentNotDirectory();
		}
	else if (err == EISDIR)
		{
		return JTriedToRemoveDirectory();
		}
	else if (err == ENOMEM)
		{
		return JNoKernelMemory();
		}
	else if (err == EROFS)
		{
		return JFileSystemReadOnly();
		}
	else
		{
		return JUnexpectedError(err);
		}
}

/******************************************************************************
 JUncompressFile

	Uncompresses the file named origFileName.

	If newFileName is empty, we generate a unique name and return it.
	If dirName is not empty, we place the new file there.
	If process is not NULL,  we return the one we create without blocking.
		Otherwise, we block until the process finishes.

 ******************************************************************************/

JError
JUncompressFile
	(
	const JCharacter*	origFileName,
	JString*			newFileName,
	const JCharacter*	dirName,
	JProcess**			process
	)
{
	// generate a file name if one is not provided

	if (newFileName->IsEmpty())
		{
		*newFileName = JGetTempFileName(dirName);
		}
	else if (!JStringEmpty(dirName))
		{
		*newFileName = JCombinePathAndName(dirName, *newFileName);
		}

	// construct the command

	JString cmd = "gunzip -c ";
	cmd += JPrepArgForExec(origFileName);
	cmd += " > ";
	cmd += JPrepArgForExec(*newFileName);

	// run the command

	if (process != NULL)
		{
		return JProcess::Create(process, cmd);
		}
	else
		{
		JString errText;
		return JRunProgram(cmd, &errText);
		}
}

/******************************************************************************
 JFOpen

	Wrapper for fopen() that returns JError.

 ******************************************************************************/

JError
JFOpen
	(
	const JCharacter*	fileName,
	const JCharacter*	mode,
	FILE**				stream
	)
{
	jclear_errno();
	*stream = fopen(fileName, mode);
	if (*stream != NULL)
		{
		return JNoError();
		}

	const int err = jerrno();
	if (err == EINVAL)
		{
		return JInvalidOpenMode();
		}
	else if (err == EEXIST)
		{
		return JFileAlreadyExists();
		}
	else if (err == EISDIR)
		{
		return JIsADirectory();
		}
	else if (err == EACCES || err == ETXTBSY)
		{
		return JAccessDenied();
		}
	else if (err == EFAULT)
		{
		return JSegFault();
		}
	else if (err == ENAMETOOLONG)
		{
		return JNameTooLong();
		}
	else if (err == ENOENT)
		{
		return JBadPath();
		}
	else if (err == ENOTDIR)
		{
		return JComponentNotDirectory();
		}
	else if (err == EMFILE || err == ENFILE)
		{
		return JTooManyDescriptorsOpen();
		}
	else if (err == ENOMEM)
		{
		return JNoKernelMemory();
		}
	else if (err == EROFS)
		{
		return JFileSystemReadOnly();
		}
	else if (err == ELOOP)
		{
		return JPathContainsLoop();
		}
	else if (err == ENOSPC)
		{
		return JFileSystemFull();
		}
	else
		{
		return JUnexpectedError(err);
		}
}
