text/plain
•
9.16 KB
•
302 lines
/* 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;
static inline bool isDotOrDotDot(const char *name){
return (name[0] == '.' && name[1] == 0) ||
(name[0] == '.' && name[1] == '.' && name[2] == 0);
}
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(isDotOrDotDot(ffd.cFileName)){ 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(isDotOrDotDot(de->d_name)){ continue; }
if(!showHidden && de->d_name[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);
}