Login
7 branches 0 tags
Ben (X13/Arch) Switched to Nujel reader f5e68d9 9 days ago 1258 Commits
nujel / bin / io.c
/* Nujel - Copyright (C) 2020-2022 - Benjamin Vincent Schulenburg
 * This project uses the MIT license, a copy should be included under /LICENSE */
#ifndef NUJEL_AMALGAMATION
#include "private.h"
#endif

#if defined(_MSC_VER)
	#include <windows.h>
	#include <tchar.h>
	#include <stdio.h>
	#include <strsafe.h>
#else
	#include <dirent.h>
	#include <unistd.h>
#endif

#include <errno.h>
#include <sys/stat.h>


#if defined(__wasm__) || defined(_MSC_VER)
	#define NO_POPEN
#else
	#define ENABLE_POPEN
#endif

#if (!defined(_WIN32)) && (!defined(__wasi__))
	#include <sys/wait.h>
#endif

lSymbol *lsError;
lSymbol *lsErrorNumber;
lSymbol *lsErrorText;
lSymbol *lsMode;
lSymbol *lsSize;
lSymbol *lsUserID;
lSymbol *lsGroupID;
lSymbol *lsAccessTime;
lSymbol* lsCreationTime;
lSymbol *lsModificationTime;

lSymbol *lsRegularFile;
lSymbol *lsDirectory;
lSymbol *lsCharacterDevice;
lSymbol *lsBlockDevice;
lSymbol *lsNamedPipe;

lSymbol *lSymError;
lSymbol *lSymReplace;
lSymbol *lSymAppend;

void setIOSymbols(){
	lsError            = lSymSM("error?");
	lsErrorNumber      = lSymSM("error-number");
	lsErrorText        = lSymSM("error-text");
	lsMode             = lSymSM("mode");
	lsSize             = lSymSM("size");
	lsUserID           = lSymSM("user-id");
	lsGroupID          = lSymSM("group-id");
	lsAccessTime       = lSymSM("access-time");
	lsCreationTime     = lSymSM("creation-time");
	lsModificationTime = lSymSM("modification-time");

	lsRegularFile      = lSymSM("regular-file?");
	lsDirectory        = lSymSM("directory?");
	lsCharacterDevice  = lSymSM("character-device?");
	lsBlockDevice      = lSymSM("block-device?");
	lsNamedPipe        = lSymSM("named-pipe?");

	lSymError          = lSymSM("error");
	lSymReplace        = lSymSM("replace");
	lSymAppend         = lSymSM("append");
}

static lVal lnfExit(lVal aStatus){
	exit(castToInt(aStatus, 0));
	return NIL;
}

static lVal lnfFileRemove(lVal aPath){
	reqString(aPath);
	unlink(lBufferData(aPath.vString));
	return aPath;
}

#ifdef _MSC_VER
LONGLONG FileTime_to_POSIX(FILETIME ft) {
	LARGE_INTEGER date, adjust;
	date.HighPart = ft.dwHighDateTime;
	date.LowPart = ft.dwLowDateTime;
	adjust.QuadPart = 11644473600000 * 10000;
	date.QuadPart -= adjust.QuadPart;
	return date.QuadPart / 10000000;
}
#endif

static lVal lnfFileStat(lVal aPath){
	reqString(aPath);
#ifdef _MSC_VER
	WIN32_FIND_DATA ffd;
	LARGE_INTEGER filesize;
	size_t length_of_arg = 0;
	HANDLE hFind = INVALID_HANDLE_VALUE;
	DWORD dwError = 0;

	if (unlikely(lStringLength(aPath.vString) >= MAX_PATH)) {
		return lValException(lSymIOError, "Directory path is too long.", lCar(v));
	}

	hFind = FindFirstFile(lBufferData(aPath.vString), &ffd);


	lMap *map = lMapAllocRaw();
	lMapSet(map, lValKeywordS(lsError, lValBool(INVALID_HANDLE_VALUE == hFind)));
	if (likely(INVALID_HANDLE_VALUE != hFind)) {
		LARGE_INTEGER filesize;
		filesize.LowPart = ffd.nFileSizeLow;
		filesize.HighPart = ffd.nFileSizeHigh;
		lMapSet(map, lValKeywordS(lsSize), lValInt(filesize.QuadPart));
		lMapSet(map, lValKeywordS(lsAccessTime), lValInt(FileTime_to_POSIX(ffd.ftLastAccessTime)));
		lMapSet(map, lValKeywordS(lsCreationTime), lValInt(FileTime_to_POSIX(ffd.ftCreationTime)));
		lMapSet(map, lValKeywordS(lsModificationTime), lValInt(FileTime_to_POSIX(ffd.ftLastWriteTime)));
		lMapSet(map, lValKeywordS(lsUserID), lValInt(1000));
		lMapSet(map, lValKeywordS(lsGroupID), lValInt(1000));

		lMapSet(map, lValKeywordS(lsRegularFile), lValBool(!(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)));
		lMapSet(map, lValKeywordS(lsDirectory), lValBool(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY));
		lMapSet(map, lValKeywordS(lsCharacterDevice), lValBool(false));
		lMapSet(map, lValKeywordS(lsBlockDevice), lValBool(false));
		lMapSet(map, lValKeywordS(lsNamedPipe), lValBool(false));
	}
	return lValMap(map);
#else
	struct stat statbuf;
	int err = stat(lBufferData(aPath.vString), &statbuf);
	lMap *map = lMapAllocRaw();
	lMapSet(map, lValKeywordS(lsError), lValBool(err));
	if(err){
		lMapSet(map, lValKeywordS(lsErrorNumber),      lValInt(errno));
		lMapSet(map, lValKeywordS(lsErrorText),        lValString(strerror(errno)));
	}else{
		lMapSet(map, lValKeywordS(lsUserID),           lValInt(statbuf.st_uid));
		lMapSet(map, lValKeywordS(lsGroupID),          lValInt(statbuf.st_gid));
		lMapSet(map, lValKeywordS(lsSize),             lValInt(statbuf.st_size));
		lMapSet(map, lValKeywordS(lsAccessTime),       lValInt(statbuf.st_atime));
		lMapSet(map, lValKeywordS(lsModificationTime), lValInt(statbuf.st_mtime));

		lMapSet(map, lValKeywordS(lsRegularFile),      lValBool(S_ISREG(statbuf.st_mode)));
		lMapSet(map, lValKeywordS(lsDirectory),        lValBool(S_ISDIR(statbuf.st_mode)));
		lMapSet(map, lValKeywordS(lsCharacterDevice),  lValBool(S_ISCHR(statbuf.st_mode)));
		lMapSet(map, lValKeywordS(lsBlockDevice),      lValBool(S_ISBLK(statbuf.st_mode)));
		lMapSet(map, lValKeywordS(lsNamedPipe),        lValBool(S_ISFIFO(statbuf.st_mode)));
	}
	return lValMap(map);
#endif
}

#ifdef ENABLE_POPEN
static lVal lnfPopen(lVal aCommand){
	reqString(aCommand);
	const int readSize = 1<<12;
	int len   = 0;
	int bufSize = readSize;
	char *buf = malloc(readSize);

	FILE *child = popen(lBufferData(aCommand.vString), "r");
	if(child == NULL){
		free(buf);
		return NIL;
	}
	while(1){
		const int ret = fread(&buf[len],1,readSize,child);
		if(ret < readSize){
			if(feof(child)){
				len += ret;
				break;
			}else if(ferror(child)){
				pclose(child);
				return NIL;
			}
		}
		if(ret > 0){len += ret;}

		if((len + readSize) >= bufSize){
			bufSize += readSize;
			buf = realloc(buf,bufSize);
		}
	}
	#ifdef __MINGW32__
	const int exitCode = pclose(child);
	#else
	const int exitStatus = pclose(child);
	const int exitCode = WEXITSTATUS(exitStatus);
	#endif

	buf = realloc(buf,len+1);
	buf[len] = 0;

	return lCons(lValInt(exitCode),lValStringNoCopy(buf,len));
}
#else
static lVal lnfPopen(lVal aCommand){
	(void)aCommand;
	return lValException(lSymNotSupportedOnPlatform, "(popen) is not implemented on your current platform, please try and work around that", aCommand);
}
#endif

static lVal lnfDirectoryRead(lVal aPath, lVal aShowHidden){
	const char *path = aPath.type == ltString ? lBufferData(aPath.vString) : "./";
	const bool showHidden = castToBool(aShowHidden);

#ifdef _MSC_VER
	WIN32_FIND_DATA ffd;
	LARGE_INTEGER filesize;
	TCHAR szDir[MAX_PATH];
	size_t length_of_arg = 0;
	HANDLE hFind = INVALID_HANDLE_VALUE;
	DWORD dwError = 0;

	StringCchLength(path, MAX_PATH, &length_of_arg);
	if (length_of_arg > (MAX_PATH - 3)){
		return lValException(lSymNotSupportedOnPlatform, "Directory path is too long.", lCar(v));
	}

	StringCchCopy(szDir, MAX_PATH, path);
	StringCchCat(szDir, MAX_PATH, TEXT("\\*"));

	hFind = FindFirstFile(szDir, &ffd);

	if (INVALID_HANDLE_VALUE == hFind) {
		return lValException(lSymNotSupportedOnPlatform, "FindFirstFile failed", lCar(v));
	}

	lVal ret = NULL;
	do {
		if ((ffd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) && !showHidden){
			continue;
		}
		if ((ffd.cFileName[0] == '.') && (ffd.cFileName[1] == 0)) { continue; }
		if ((ffd.cFileName[0] == '.') && (ffd.cFileName[1] == '.') && (ffd.cFileName[2] == 0)) { continue; }
		ret = lCons(lValString(ffd.cFileName), ret);
   } while (FindNextFile(hFind, &ffd) != 0);   return ret;
#else
	DIR *dp = opendir(path);
	if(dp == NULL){return NIL;}
	lVal ret = NIL;
	lVal cur = NIL;
	for(struct dirent *de = readdir(dp); de ; de = readdir(dp)){
		if(!showHidden){
			if(de->d_name[0] == '.'){continue;}
		}
		if((de->d_name[0] == '.') && (de->d_name[1] == 0)){continue;}
		if((de->d_name[0] == '.') && (de->d_name[1] == '.') && (de->d_name[2] == 0)){continue;}
		if(cur.type == ltNil){
			ret = cur = lCons(NIL, NIL);
		}else{
			cur = cur.vList->cdr = lCons(NIL, NIL);
		}
		cur.vList->car = lValString(de->d_name);
	}

	closedir(dp);
	return ret;
#endif
}

static lVal lnfDirectoryMake(lVal aPath){
	reqString(aPath);
	return lValBool(makeDir(lBufferData(aPath.vString)) == 0);
}

static lVal lnfDirectoryRemove(lVal aPath){
	reqString(aPath);
	return lValBool(rmdir(lBufferData(aPath.vString)) == 0);
}

static lVal lnfChangeDirectory(lVal aPath){
	reqString(aPath);
	return lValBool(chdir(lBufferData(aPath.vString)) == 0);
}

static lVal lnfGetCurrentWorkingDirectory(){
	char path[512];
	if(!getcwd(path, sizeof(path))){
		return NIL;
	}
	return lValString(path);
}

void lOperationsIO(){
	lAddNativeFuncV ("exit",     "(status)",  "Quits with code a",                                 lnfExit, 0);
	lAddNativeFuncV ("popen",    "(command)", "Return a list of [exit-code stdout stderr)",        lnfPopen, 0);

	lAddNativeFuncV ("file/stat","(path)",        "Return some stats about FILENAME",                  lnfFileStat, 0);
	lAddNativeFuncV ("rm",   "(path)",            "Remove FILENAME from the filesystem, if possible",  lnfFileRemove, 0);
	lAddNativeFuncVV("ls",   "(path show-hidden)","Return all files within $PATH",                     lnfDirectoryRead, 0);
	lAddNativeFuncV ("rmdir","(path)",            "Remove empty directory at PATH",                    lnfDirectoryRemove, 0);
	lAddNativeFuncV ("mkdir","(path)",       "Create a new empty directory at PATH",              lnfDirectoryMake, 0);
	lAddNativeFuncV ("cd",  "(path)",        "Change the current working directory to PATH",      lnfChangeDirectory, 0);
	lAddNativeFunc  ("cwd", "()",            "Return the current working directory",              lnfGetCurrentWorkingDirectory, 0);
}