text/plain
•
9.99 KB
•
333 lines
/*
* Nujel - Copyright (C) 2020-2021 - Benjamin Vincent Schulenburg
*
* This project uses the MIT license, a copy should be included under /LICENSE
*/
#include "io.h"
#include "../misc.h"
#include "../../lib/exception.h"
#include <dirent.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#ifndef __MINGW32__
#include <sys/wait.h>
#endif
lSymbol *lsMode;
lSymbol *lsSize;
lSymbol *lsUserID;
lSymbol *lsGroupID;
lSymbol *lsAccessTime;
lSymbol *lsModificationTime;
lSymbol *lsError;
lSymbol *lsErrorNumber;
lSymbol *lsErrorText;
lSymbol *lsRegularFile;
lSymbol *lsDirectory;
lSymbol *lsCharacterDevice;
lSymbol *lsBlockDevice;
lSymbol *lsNamedPipe;
//lSymbol *lsSymbolicLink;
//lSymbol *lsSocket;
void setIOSymbols(){
lsError = RSYMP(lSymS(":error?"));
lsErrorNumber = RSYMP(lSymS(":error-number"));
lsErrorText = RSYMP(lSymS(":error-text"));
lsMode = RSYMP(lSymS(":mode"));
lsSize = RSYMP(lSymS(":size"));
lsUserID = RSYMP(lSymS(":user-id"));
lsGroupID = RSYMP(lSymS(":group-id"));
lsAccessTime = RSYMP(lSymS(":access-time"));
lsModificationTime = RSYMP(lSymS(":modification-time"));
lsRegularFile = RSYMP(lSymS(":regular-file?"));
lsDirectory = RSYMP(lSymS(":directory?"));
lsCharacterDevice = RSYMP(lSymS(":character-device?"));
lsBlockDevice = RSYMP(lSymS(":block-device?"));
lsNamedPipe = RSYMP(lSymS(":named-pipe?"));
//lsSymbolicLink = RSYMP(lSymS(":symbolic-link?"));
//lsSocket = RSYMP(lSymS(":socket?"));
}
static lVal *lnfQuit(lClosure *c, lVal *v){
(void)c;
exit(castToInt(lCar(v),0));
return NULL;
}
static lVal *lnfInput(lClosure *c, lVal *v){
(void)c;
const char *prompt = castToString(lCar(v),NULL);
if(prompt != NULL){
pf("%s",prompt);
}
char buf[4096];
if(fgets(buf,sizeof(buf),stdin) == NULL){
return NULL;
}
return lValString(buf);
}
static lVal *lnfPrint(lClosure *c, lVal *v){
(void)c;
if(v == NULL){return v;}
lWriteVal(lCar(v));
return NULL;
}
static lVal *lnfError(lClosure *c, lVal *v){
(void)c;
if(v == NULL){return v;}
lDisplayErrorVal(lCar(v));
return NULL;
}
static lVal *lnfFileRead(lClosure *c, lVal *v){
(void)c;
const char *filename = castToString(lCar(v),NULL);
if(filename == NULL){return NULL;}
size_t len = 0;
const char *data = loadFile(filename,&len);
lVal *ret = lValString(data);
free((void *)data);
return ret;
}
static lVal *lnfFileWrite(lClosure *c, lVal *v){
(void)c;
const char *filename = castToString( lCar(v),NULL);
const char *content = castToString(lCadr(v),NULL);
if(filename == NULL){return NULL;}
if(content == NULL){return NULL;}
size_t len = strnlen(content,1<<20);
saveFile(filename,content,len);
return NULL;
}
static lVal *lnfFileRemove(lClosure *c, lVal *v){
(void)c;
const char *filename = castToString( lCar(v),NULL);
if(filename == NULL){return NULL;}
unlink(filename);
return NULL;
}
static lVal *lnfFileStat(lClosure *c, lVal *v){
(void)c;
const char *filename = castToString(lCar(v),NULL);
if(filename == NULL){return NULL;}
struct stat statbuf;
int err = stat(filename,&statbuf);
lVal *ret = RVP(lValTree(NULL));
ret->vTree = lTreeInsert(ret->vTree, lsError, RVP(lValBool(err)));
if(err){
ret->vTree = lTreeInsert(ret->vTree, lsErrorNumber, RVP(lValInt(errno)));
ret->vTree = lTreeInsert(ret->vTree, lsErrorText, RVP(lValString(strerror(errno))));
}else{
ret->vTree = lTreeInsert(ret->vTree, lsMode, RVP(lValInt(statbuf.st_mode)));
ret->vTree = lTreeInsert(ret->vTree, lsUserID, RVP(lValInt(statbuf.st_uid)));
ret->vTree = lTreeInsert(ret->vTree, lsGroupID, RVP(lValInt(statbuf.st_gid)));
ret->vTree = lTreeInsert(ret->vTree, lsSize, RVP(lValInt(statbuf.st_size)));
ret->vTree = lTreeInsert(ret->vTree, lsAccessTime, RVP(lValInt(statbuf.st_atime)));
ret->vTree = lTreeInsert(ret->vTree, lsModificationTime, RVP(lValInt(statbuf.st_mtime)));
ret->vTree = lTreeInsert(ret->vTree, lsRegularFile, RVP(lValBool(S_ISREG(statbuf.st_mode))));
ret->vTree = lTreeInsert(ret->vTree, lsDirectory, RVP(lValBool(S_ISDIR(statbuf.st_mode))));
ret->vTree = lTreeInsert(ret->vTree, lsCharacterDevice, RVP(lValBool(S_ISCHR(statbuf.st_mode))));
ret->vTree = lTreeInsert(ret->vTree, lsBlockDevice, RVP(lValBool(S_ISBLK(statbuf.st_mode))));
ret->vTree = lTreeInsert(ret->vTree, lsNamedPipe, RVP(lValBool(S_ISFIFO(statbuf.st_mode))));
//ret->vTree = lTreeInsert(ret->vTree, lsSymbolicLink, RVP(lValBool(S_ISLNK(statbuf.st_mode))));
//ret->vTree = lTreeInsert(ret->vTree, lsSocket, RVP(lValBool(S_ISSOCK(statbuf.st_mode))));
}
return ret;
}
static lVal *lnfFileTemp(lClosure *c, lVal *v){
(void)c; (void)v;
const char *content = castToString(lCar(v),NULL);
const char *ret = tempFilename();
FILE *fd = fopen(ret,"w");
if(content){
const int len = lStringLength(lCar(v)->vString);
int written = 0;
while(written < len){
int r = fwrite(&content[written],1,len - written,fd);
if(r <= 0){
if(ferror(fd)){
lPrintError("Error while writing to temporary file: %s\n",ret);
break;
}
}else{
written += r;
}
}
}
fclose(fd);
return lValString(ret);
}
#if (!defined(__MSYS__)) || (defined(__MINGW32__))
static lVal *lnfPopen(lClosure *c, lVal *v){
(void)c; (void)v;
#ifdef __EMSCRIPTEN__
lExceptionThrowValClo(":unsupported","Popen is currently unsupported in Emscripten builds, please work around this procedure.",v,c);
return NULL;
#else
const char *command = castToString(lCar(v),NULL);
if(command == NULL){return NULL;}
const int readSize = 1<<12;
int len = 0;
int bufSize = readSize;
char *buf = malloc(readSize);
FILE *child = popen(command,"r");
if(child == NULL){
fpf(stderr,"Error openeing %s\n",command);
return NULL;
}
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 NULL;
}
}
if(ret > 0){len += ret;}
if((len + readSize) >= bufSize){
bufSize += readSize;
buf = realloc(buf,bufSize);
}
}
#ifdef __MINGW32__
int exitCode = pclose(child);
#else
int exitStatus = pclose(child);
int exitCode = WEXITSTATUS(exitStatus);
#endif
buf = realloc(buf,len+1);
buf[len] = 0;
lVal *ret = lRootsValPush(lCons(NULL,NULL));
ret->vList.car = lValInt(exitCode);
ret->vList.cdr = lValStringNoCopy(buf,len);
return ret;
#endif
}
#endif
static lVal *lnfDirectoryRead(lClosure *c, lVal *v){
(void) c;
const char *path = castToString(lCar(v),NULL);
const bool showHidden = castToBool(lCadr(v));
if(path == NULL){
path = getcwd(__builtin_alloca(512),512);
if(path == NULL){return NULL;}
}
DIR *dp = opendir(path);
if(dp == NULL){return NULL;}
lVal *ret = NULL;
lVal *cur = NULL;
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 == NULL){
ret = cur = lRootsValPush(lCons(NULL,NULL));
}else{
cur = cur->vList.cdr = lCons(NULL,NULL);
}
cur->vList.car = lValString(de->d_name);
}
closedir(dp);
return ret;
}
static lVal *lnfDirectoryMake(lClosure *c, lVal *v){
(void) c;
const char *path = castToString(lCar(v),NULL);
if(path == NULL){return NULL;}
int err = makeDir(path);
return lValBool(err == 0);
}
static lVal *lnfDirectoryRemove(lClosure *c, lVal *v){
(void) c;
const char *path = castToString(lCar(v),NULL);
if(path == NULL){return NULL;}
int err = rmdir(path);
return lValBool(err == 0);
}
static lVal *lnfChangeDirectory(lClosure *c, lVal *v){
(void) c;
const char *path = castToString(lCar(v),NULL);
if(path == NULL){return NULL;}
int err = chdir(path);
return lValBool(err == 0);
}
static lVal *lnfGetCurrentWorkingDirectory(lClosure *c, lVal *v){
(void) c; (void) v;
char path[512];
if(getcwd(path,sizeof(path)) == NULL){
return NULL;
}
return lValString(path);
}
void lOperationsIO(lClosure *c){
lAddNativeFunc(c,"error", "[...args]", "Prints ...args to stderr", lnfError);
lAddNativeFunc(c,"print", "[...args]", "Displays ...args", lnfPrint);
lAddNativeFunc(c,"input", "[]", "Reads in a line of user input and returns it", lnfInput);
lAddNativeFunc(c,"exit", "[a]", "Quits with code a", lnfQuit);
#if (!defined(__MSYS__)) || (defined(__MINGW32__))
lAddNativeFunc(c,"popen", "[command]", "Return a list of [exit-code stdout stderr]", lnfPopen);
#endif
lAddNativeFunc(c,"file/read", "[path]", "Load FILENAME and return the contents as a string", lnfFileRead);
lAddNativeFunc(c,"file/write", "[path content]", "Writes CONTENT into FILENAME", lnfFileWrite);
lAddNativeFunc(c,"file/remove", "[path]", "Remove FILENAME from the filesystem, if possible", lnfFileRemove);
lAddNativeFunc(c,"file/temp", "[content]", "Write CONTENT to a temp file and return its path", lnfFileTemp);
lAddNativeFunc(c,"file/stat", "[path]", "Return some stats about FILENAME", lnfFileStat);
lAddNativeFunc(c,"directory/read", "[&path &show-hidden]", "Return all files within $PATH", lnfDirectoryRead);
lAddNativeFunc(c,"directory/remove", "[path]", "Remove empty directory at PATH", lnfDirectoryRemove);
lAddNativeFunc(c,"directory/make", "[path]", "Create a new empty directory at PATH", lnfDirectoryMake);
lAddNativeFunc(c,"path/change", "[path]", "Change the current working directory to PATH", lnfChangeDirectory);
lAddNativeFunc(c,"path/working-directory","[]", "Return the current working directory", lnfGetCurrentWorkingDirectory);
}