refactor: rework fs error system

This commit is contained in:
TopchetoEU 2024-02-09 13:46:57 +02:00
parent cf99845f6b
commit 1aad92ecec
Signed by: topchetoeu
GPG Key ID: 6531B8583E5F6ED4
23 changed files with 596 additions and 432 deletions

View File

@ -10,12 +10,12 @@ import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("File")
public class FileLib {
public final File file;
public final File fd;
@Expose public PromiseLib __pointer(Arguments args) {
return PromiseLib.await(args.ctx, () -> {
try {
return file.seek(0, 1);
return fd.seek(0, 1);
}
catch (FilesystemException e) { throw e.toEngineException(); }
});
@ -23,9 +23,9 @@ public class FileLib {
@Expose public PromiseLib __length(Arguments args) {
return PromiseLib.await(args.ctx, () -> {
try {
long curr = file.seek(0, 1);
long res = file.seek(0, 2);
file.seek(curr, 0);
long curr = fd.seek(0, 1);
long res = fd.seek(0, 2);
fd.seek(curr, 0);
return res;
}
catch (FilesystemException e) { throw e.toEngineException(); }
@ -38,7 +38,7 @@ public class FileLib {
try {
var buff = new byte[n];
var res = new ArrayValue();
int resI = file.read(buff);
int resI = fd.read(buff);
for (var i = resI - 1; i >= 0; i--) res.set(args.ctx, i, (int)buff[i]);
return res;
@ -53,7 +53,7 @@ public class FileLib {
var res = new byte[val.size()];
for (var i = 0; i < val.size(); i++) res[i] = (byte)Values.toNumber(args.ctx, val.get(i));
file.write(res);
fd.write(res);
return null;
}
@ -62,7 +62,7 @@ public class FileLib {
}
@Expose public PromiseLib __close(Arguments args) {
return PromiseLib.await(args.ctx, () -> {
file.close();
fd.close();
return null;
});
}
@ -72,13 +72,13 @@ public class FileLib {
var whence = args.getInt(1);
try {
return file.seek(ptr, whence);
return fd.seek(ptr, whence);
}
catch (FilesystemException e) { throw e.toEngineException(); }
});
}
public FileLib(File file) {
this.file = file;
public FileLib(File fd) {
this.fd = fd;
}
}

View File

@ -8,13 +8,14 @@ import me.topchetoeu.jscript.core.engine.Context;
import me.topchetoeu.jscript.core.engine.values.ObjectValue;
import me.topchetoeu.jscript.core.engine.values.Values;
import me.topchetoeu.jscript.core.exceptions.EngineException;
import me.topchetoeu.jscript.utils.filesystem.ActionType;
import me.topchetoeu.jscript.utils.filesystem.EntryType;
import me.topchetoeu.jscript.utils.filesystem.ErrorReason;
import me.topchetoeu.jscript.utils.filesystem.File;
import me.topchetoeu.jscript.utils.filesystem.FileStat;
import me.topchetoeu.jscript.utils.filesystem.Filesystem;
import me.topchetoeu.jscript.utils.filesystem.FilesystemException;
import me.topchetoeu.jscript.utils.filesystem.Mode;
import me.topchetoeu.jscript.utils.filesystem.FilesystemException.FSCode;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeField;
@ -50,11 +51,10 @@ public class FilesystemLib {
try {
if (fs.stat(path).type != EntryType.FILE) {
throw new FilesystemException(path, FSCode.NOT_FILE);
throw new FilesystemException(ErrorReason.DOESNT_EXIST, "Not a file").setAction(ActionType.OPEN);
}
var file = fs.open(path, _mode);
return new FileLib(file);
return new FileLib(fs.open(path, _mode));
}
catch (FilesystemException e) { throw e.toEngineException(); }
});
@ -75,7 +75,7 @@ public class FilesystemLib {
var path = fs.normalize(args.getString(0));
if (fs.stat(path).type != EntryType.FOLDER) {
throw new FilesystemException(path, FSCode.NOT_FOLDER);
throw new FilesystemException(ErrorReason.DOESNT_EXIST, "Not a directory").setAction(ActionType.OPEN);
}
file = fs.open(path, Mode.READ);

View File

@ -152,6 +152,8 @@ public class Internals {
glob.define(null, "Encoding", false, wp.getNamespace(EncodingLib.class));
glob.define(null, "Filesystem", false, wp.getNamespace(FilesystemLib.class));
glob.define(false, wp.getConstr(FileLib.class));
glob.define(false, wp.getConstr(DateLib.class));
glob.define(false, wp.getConstr(ObjectLib.class));
glob.define(false, wp.getConstr(FunctionLib.class));

View File

@ -24,6 +24,7 @@ import me.topchetoeu.jscript.utils.filesystem.MemoryFilesystem;
import me.topchetoeu.jscript.utils.filesystem.Mode;
import me.topchetoeu.jscript.utils.filesystem.PhysicalFilesystem;
import me.topchetoeu.jscript.utils.filesystem.RootFilesystem;
import me.topchetoeu.jscript.utils.filesystem.STDFilesystem;
import me.topchetoeu.jscript.utils.modules.ModuleRepo;
import me.topchetoeu.jscript.utils.permissions.PermissionsManager;
import me.topchetoeu.jscript.utils.permissions.PermissionsProvider;
@ -108,6 +109,7 @@ public class JScriptRepl {
var fs = new RootFilesystem(PermissionsProvider.get(environment));
fs.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE));
fs.protocols.put("file", new PhysicalFilesystem("."));
fs.protocols.put("std", STDFilesystem.ofStd(System.in, System.out, System.err));
environment.add(PermissionsProvider.ENV_KEY, PermissionsManager.ALL_PERMS);
environment.add(Filesystem.ENV_KEY, fs);

View File

@ -0,0 +1,28 @@
package me.topchetoeu.jscript.utils.filesystem;
public enum ActionType {
UNKNOWN(0, "An operation performed upon", "An operation was performed upon"),
READ(1, "Reading from", "Read from"),
WRITE(2, "Writting to", "Wrote to"),
SEEK(3, "Seeking in", "Sought in"),
CLOSE(4, "Closing", "Closed"),
STAT(5, "Stat of", "Statted"),
OPEN(6, "Opening", "Opened"),
CREATE(7, "Creating", "Created"),
DELETE(8, "Deleting", "Deleted"),
CLOSE_FS(9, "Closing filesystem", "Closed filesystem");
public final int code;
public final String continuous, past;
public String readable(boolean usePast) {
if (usePast) return past;
else return continuous;
}
private ActionType(int code, String continuous, String past) {
this.code = code;
this.continuous = continuous;
this.past = past;
}
}

View File

@ -0,0 +1,59 @@
package me.topchetoeu.jscript.utils.filesystem;
public abstract class BaseFile<T> implements File {
private T handle;
private Mode mode;
protected final T handle() {
return handle;
}
protected abstract int onRead(byte[] buff);
protected abstract void onWrite(byte[] buff);
protected abstract long onSeek(long offset, int pos);
protected abstract boolean onClose();
@Override public int read(byte[] buff) {
try {
if (handle == null) throw new FilesystemException(ErrorReason.CLOSED);
if (!mode.readable) throw new FilesystemException(ErrorReason.NO_PERMISSION, "File not open for reading.");
return onRead(buff);
}
catch (FilesystemException e) { throw e.setAction(ActionType.READ); }
}
@Override public void write(byte[] buff) {
try {
if (handle == null) throw new FilesystemException(ErrorReason.CLOSED);
if (!mode.writable) throw new FilesystemException(ErrorReason.NO_PERMISSION, "File not open for writting.");
onWrite(buff);
}
catch (FilesystemException e) { throw e.setAction(ActionType.WRITE); }
}
@Override public long seek(long offset, int pos) {
try {
if (handle == null) throw new FilesystemException(ErrorReason.CLOSED);
if (!mode.writable) throw new FilesystemException(ErrorReason.NO_PERMISSION, "File not open for seeking.");
return onSeek(offset, pos);
}
catch (FilesystemException e) { throw e.setAction(ActionType.SEEK); }
}
@Override public boolean close() {
if (handle != null) {
try {
var res = onClose();
handle = null;
mode = Mode.NONE;
return res;
}
catch (FilesystemException e) { throw e.setAction(ActionType.CLOSE); }
}
else return false;
}
public BaseFile(T handle, Mode mode) {
this.mode = mode;
this.handle = handle;
if (mode == Mode.NONE) this.handle = null;
}
}

View File

@ -0,0 +1,23 @@
package me.topchetoeu.jscript.utils.filesystem;
public enum ErrorReason {
UNKNOWN(0, "failed", false),
NO_PERMISSION(1, "is not allowed", false),
CLOSED(1, "that was closed", true),
UNSUPPORTED(2, "is not supported", false),
ILLEGAL_ARGS(3, "with illegal arguments", true),
DOESNT_EXIST(4, "that doesn't exist", true),
ALREADY_EXISTS(5, "that already exists", true),
ILLEGAL_PATH(6, "with illegal path", true),
NO_PARENT(7, "with a missing parent folder", true);
public final int code;
public final boolean usePast;
public final String readable;
private ErrorReason(int code, String readable, boolean usePast) {
this.code = code;
this.readable = readable;
this.usePast = usePast;
}
}

View File

@ -3,51 +3,42 @@ package me.topchetoeu.jscript.utils.filesystem;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.LinkedList;
import me.topchetoeu.jscript.common.Buffer;
import me.topchetoeu.jscript.utils.LineWriter;
import me.topchetoeu.jscript.utils.filesystem.FilesystemException.FSCode;
import me.topchetoeu.jscript.utils.permissions.Permission;
import me.topchetoeu.jscript.utils.permissions.PermissionsProvider;
public interface File {
int read(byte[] buff);
void write(byte[] buff);
long seek(long offset, int pos);
void close();
default int read(byte[] buff) { throw new FilesystemException(ErrorReason.UNSUPPORTED).setAction(ActionType.READ); }
default void write(byte[] buff) { throw new FilesystemException(ErrorReason.UNSUPPORTED).setAction(ActionType.WRITE); }
default long seek(long offset, int pos) { throw new FilesystemException(ErrorReason.UNSUPPORTED).setAction(ActionType.SEEK); }
default boolean close() { return false; }
default File wrap(String name, PermissionsProvider perms, Permission read, Permission write, Permission seek, Permission close) {
var self = this;
default byte[] readAll() {
var parts = new LinkedList<byte[]>();
var buff = new byte[1024];
var size = 0;
return new File() {
@Override public int read(byte[] buff) {
if (read != null && perms.hasPermission(read, name)) return self.read(buff);
else throw new FilesystemException(name, FSCode.NO_PERMISSIONS_R);
}
@Override public void write(byte[] buff) {
if (write != null && perms.hasPermission(write, name)) self.write(buff);
else throw new FilesystemException(name, FSCode.NO_PERMISSIONS_RW);
}
@Override public long seek(long offset, int pos) {
if (seek != null && perms.hasPermission(seek, name)) return self.seek(offset, pos);
else throw new FilesystemException(name, FSCode.NO_PERMISSIONS_R);
}
@Override public void close() {
if (close != null && perms.hasPermission(close, name)) self.close();
else throw new FilesystemException(name, FSCode.NO_PERMISSIONS_R);
}
};
while (true) {
var n = read(buff);
if (n == 0) break;
parts.add(buff);
size += n;
}
buff = new byte[size];
var i = 0;
for (var part : parts) {
System.arraycopy(part, 0, buff, i, part.length);
i += part.length;
}
return buff;
}
default String readToString() {
long len = seek(0, 2);
if (len < 0) return null;
seek(0, 0);
byte[] res = new byte[(int)len];
len = read(res);
return new String(res);
return new String(readAll());
}
default String readLine() {
var res = new Buffer();
@ -66,74 +57,105 @@ public interface File {
return new String(res.data());
}
public static File ofStream(String name, InputStream str) {
public static File ofStream(InputStream str) {
return new File() {
@Override public int read(byte[] buff) {
try {
return str.read(buff);
try { return str.read(buff); }
catch (NullPointerException e) { throw new FilesystemException(ErrorReason.ILLEGAL_ARGS, e.getMessage()); }
catch (IOException e) { throw new FilesystemException(ErrorReason.UNKNOWN, e.getMessage()); }
}
catch (IOException e) {
throw new FilesystemException(name, FSCode.NO_PERMISSIONS_R);
}
}
@Override public void write(byte[] buff) {
throw new FilesystemException(name, FSCode.NO_PERMISSIONS_RW);
}
@Override public long seek(long offset, int pos) {
throw new FilesystemException(name, FSCode.UNSUPPORTED_OPERATION);
}
@Override public void close() {
throw new FilesystemException(name, FSCode.UNSUPPORTED_OPERATION);
catch (FilesystemException e) { throw e.setAction(ActionType.READ); }
}
};
}
public static File ofStream(String name, OutputStream str) {
public static File ofStream(OutputStream str) {
return new File() {
@Override public int read(byte[] buff) {
throw new FilesystemException(name, FSCode.NO_PERMISSIONS_R);
}
@Override public void write(byte[] buff) {
try {
str.write(buff);
try { str.write(buff); }
catch (NullPointerException e) {throw new FilesystemException(ErrorReason.ILLEGAL_ARGS, e.getMessage()); }
catch (IOException e) { throw new FilesystemException(ErrorReason.UNKNOWN, e.getMessage()); }
}
catch (IOException e) {
throw new FilesystemException(name, FSCode.NO_PERMISSIONS_RW);
}
}
@Override public long seek(long offset, int pos) {
throw new FilesystemException(name, FSCode.UNSUPPORTED_OPERATION);
}
@Override public void close() {
throw new FilesystemException(name, FSCode.UNSUPPORTED_OPERATION);
catch (FilesystemException e) { throw e.setAction(ActionType.WRITE); }
}
};
}
public static File ofLineWriter(String name, LineWriter writer) {
public static File ofLineWriter(LineWriter writer) {
var buff = new Buffer();
return new File() {
@Override public int read(byte[] buff) {
throw new FilesystemException(name, FSCode.NO_PERMISSIONS_R);
}
@Override public void write(byte[] val) {
try {
if (val == null) throw new FilesystemException(ErrorReason.ILLEGAL_ARGS, "Given buffer is null.");
for (var b : val) {
if (b == '\n') {
try {
writer.writeLine(new String(buff.data()));
}
catch (IOException e) {
throw new FilesystemException(name, FSCode.NO_PERMISSIONS_RW);
throw new FilesystemException(ErrorReason.UNKNOWN, e.getMessage());
}
}
else buff.append(b);
}
}
@Override public long seek(long offset, int pos) {
throw new FilesystemException(name, FSCode.UNSUPPORTED_OPERATION);
}
@Override public void close() {
throw new FilesystemException(name, FSCode.UNSUPPORTED_OPERATION);
catch (FilesystemException e) { throw e.setAction(ActionType.WRITE); }
}
};
}
public static File ofLineReader(LineReader reader) {
return new File() {
private int offset = 0;
private byte[] prev = new byte[0];
@Override
public int read(byte[] buff) {
try {
if (buff == null) throw new FilesystemException(ErrorReason.ILLEGAL_ARGS, "Given buffer is null.");
var ptr = 0;
while (true) {
if (prev == null) break;
if (offset >= prev.length) {
try {
var line = reader.readLine();
if (line == null) {
prev = null;
break;
}
else prev = (line + "\n").getBytes();
offset = 0;
}
catch (IOException e) {
throw new FilesystemException(ErrorReason.UNKNOWN, e.getMessage());
}
}
if (ptr + prev.length - offset > buff.length) {
var n = buff.length - ptr;
System.arraycopy(prev, offset, buff, ptr, buff.length - ptr);
offset += n;
ptr += n;
break;
}
else {
var n = prev.length - offset;
System.arraycopy(prev, offset, buff, ptr, n);
offset += n;
ptr += n;
}
}
return ptr;
}
catch (FilesystemException e) { throw e.setAction(ActionType.READ); }
}
};
}
public static File ofIterator(Iterator<String> it) {
return ofLineReader(LineReader.ofIterator(it));
}
}

View File

@ -5,6 +5,9 @@ public class FileStat {
public final EntryType type;
public FileStat(Mode mode, EntryType type) {
if (mode == Mode.NONE) type = EntryType.NONE;
if (type == EntryType.NONE) mode = Mode.NONE;
this.mode = mode;
this.type = type;
}

View File

@ -6,10 +6,11 @@ import me.topchetoeu.jscript.core.engine.values.Symbol;
public interface Filesystem {
public static final Symbol ENV_KEY = Symbol.get("Environment.fs");
String normalize(String... path);
File open(String path, Mode mode) throws FilesystemException;
void create(String path, EntryType type) throws FilesystemException;
FileStat stat(String path) throws FilesystemException;
default String normalize(String... path) { return Paths.normalize(path); }
default boolean create(String path, EntryType type) { throw new FilesystemException(ErrorReason.UNSUPPORTED).setAction(ActionType.CREATE); }
File open(String path, Mode mode);
FileStat stat(String path);
void close();
public static Filesystem get(Extensions exts) {
return exts.get(ENV_KEY);

View File

@ -1,57 +1,90 @@
package me.topchetoeu.jscript.utils.filesystem;
import java.util.ArrayList;
import me.topchetoeu.jscript.core.engine.values.Values;
import me.topchetoeu.jscript.core.exceptions.EngineException;
public class FilesystemException extends RuntimeException {
public static enum FSCode {
DOESNT_EXIST(0x1),
NOT_FILE(0x2),
NOT_FOLDER(0x3),
NO_PERMISSIONS_R(0x4),
NO_PERMISSIONS_RW(0x5),
FOLDER_NOT_EMPTY(0x6),
ALREADY_EXISTS(0x7),
FOLDER_EXISTS(0x8),
UNSUPPORTED_OPERATION(0x9);
public final ErrorReason reason;
public final String details;
private ActionType action;
private EntryType entry = EntryType.FILE;
private String path;
public final int code;
public FilesystemException setPath(String path) {
this.path = path;
return this;
}
public FilesystemException setAction(ActionType action) {
if (action == null) action = ActionType.UNKNOWN;
private FSCode(int code) { this.code = code; }
this.action = action;
return this;
}
public FilesystemException setEntry(EntryType entry) {
if (entry == null) entry = EntryType.NONE;
this.entry = entry;
return this;
}
public static final String[] MESSAGES = {
"How did we get here?",
"The file or folder '%s' doesn't exist or is inaccessible.",
"'%s' is not a file",
"'%s' is not a folder",
"No permissions to read '%s'",
"No permissions to write '%s'",
"Can't delete '%s', since it is a full folder.",
"'%s' already exists.",
"An unsupported operation was performed on the file '%s'."
};
public final String message, filename;
public final FSCode code;
public FilesystemException(String message, String filename, FSCode code) {
super(code + ": " + String.format(message, filename));
this.message = message;
this.code = code;
this.filename = filename;
public ActionType action() {
return action;
}
public FilesystemException(String filename, FSCode code) {
super(code + ": " + String.format(MESSAGES[code.code], filename));
this.message = MESSAGES[code.code];
this.code = code;
this.filename = filename;
public String path() {
return path;
}
public EntryType entry() {
return entry;
}
public EngineException toEngineException() {
var res = EngineException.ofError("IOError", getMessage());
Values.setMember(null, res.value, "code", code);
Values.setMember(null, res.value, "filename", filename.toString());
Values.setMember(null, res.value, "action", action.code);
Values.setMember(null, res.value, "reason", reason.code);
Values.setMember(null, res.value, "path", path);
Values.setMember(null, res.value, "entry", entry.name);
if (details != null) Values.setMember(null, res.value, "details", details);
return res;
}
@Override public String getMessage() {
var parts = new ArrayList<String>(10);
path = String.join(" ", parts).trim();
if (path.isEmpty()) path = null;
parts.clear();
parts.add(action == null ? "An action performed upon " : action.readable(reason.usePast));
if (entry == EntryType.FILE) parts.add("file");
if (entry == EntryType.FOLDER) parts.add("folder");
if (path != null) parts.add(path);
parts.add(reason.readable);
var msg = String.join(" ", parts);
if (details != null) msg += ": " + details;
return msg;
}
public FilesystemException(ErrorReason type, String details) {
super();
if (type == null) type = ErrorReason.UNKNOWN;
this.details = details;
this.reason = type;
}
public FilesystemException(ErrorReason type) {
this(type, null);
}
public FilesystemException() {
this(null);
}
}

View File

@ -0,0 +1,32 @@
package me.topchetoeu.jscript.utils.filesystem;
import java.util.HashSet;
import java.util.Set;
public class HandleManager {
private Set<File> files = new HashSet<>();
public File put(File val) {
var handle = new File() {
@Override public int read(byte[] buff) {
return val.read(buff);
}
@Override public void write(byte[] buff) {
val.write(buff);
}
@Override public long seek(long offset, int pos) {
return val.seek(offset, pos);
}
@Override public boolean close() {
return files.remove(this) && val.close();
}
};
files.add(handle);
return handle;
}
public void close() {
while (!files.isEmpty()) {
files.stream().findFirst().get().close();
}
}
}

View File

@ -0,0 +1,16 @@
package me.topchetoeu.jscript.utils.filesystem;
import java.io.IOException;
import java.util.Iterator;
public interface LineReader {
String readLine() throws IOException;
public static LineReader ofIterator(Iterator<String> it) {
return () -> {
if (it.hasNext()) return it.next();
else return null;
};
}
}

View File

@ -1,4 +1,4 @@
package me.topchetoeu.jscript.utils;
package me.topchetoeu.jscript.utils.filesystem;
import java.io.IOException;

View File

@ -1,73 +0,0 @@
package me.topchetoeu.jscript.utils.filesystem;
import java.io.IOException;
import java.util.Iterator;
import java.util.stream.Stream;
import me.topchetoeu.jscript.utils.filesystem.FilesystemException.FSCode;
public class ListFile implements File {
private Iterator<String> it;
private String filename;
private byte[] currFile;
private long ptr = 0, start = 0, end = 0;
private void next() {
if (it != null && it.hasNext()) {
start = end;
currFile = (it.next() + "\n").getBytes();
end = start + currFile.length;
}
else {
it = null;
currFile = null;
end = -1;
}
}
@Override
public void close() {
it = null;
currFile = null;
}
@Override
public int read(byte[] buff) {
if (ptr < start) return 0;
if (it == null) return 0;
var i = 0;
while (i < buff.length) {
while (i + ptr >= end) {
next();
if (it == null) return 0;
}
int cpyN = Math.min(currFile.length, buff.length - i);
System.arraycopy(currFile, (int)(ptr + i - start), buff, i, cpyN);
i += cpyN;
}
ptr += i;
return i;
}
@Override
public long seek(long offset, int pos) {
if (pos == 2) throw new FilesystemException(filename, FSCode.UNSUPPORTED_OPERATION);
if (pos == 1) offset += ptr;
return ptr = offset;
}
@Override
public void write(byte[] buff) {
throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW);
}
public ListFile(String filename, Stream<String> stream) throws IOException {
this.it = stream.iterator();
this.filename = filename;
}
}

View File

@ -1,62 +1,35 @@
package me.topchetoeu.jscript.utils.filesystem;
import me.topchetoeu.jscript.common.Buffer;
import me.topchetoeu.jscript.utils.filesystem.FilesystemException.FSCode;
public class MemoryFile implements File {
class MemoryFile extends BaseFile<Buffer> {
private int ptr;
private Mode mode;
private Buffer data;
private String filename;
public Buffer data() { return data; }
@Override
public int read(byte[] buff) {
if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
var res = data.read(ptr, buff);
@Override protected int onRead(byte[] buff) {
var res = handle().read(ptr, buff);
ptr += res;
return res;
}
@Override
public void write(byte[] buff) {
if (data == null || !mode.writable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW);
data.write(ptr, buff);
@Override protected void onWrite(byte[] buff) {
handle().write(ptr, buff);
ptr += buff.length;
}
@Override
public long seek(long offset, int pos) {
if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
@Override protected long onSeek(long offset, int pos) {
if (pos == 0) ptr = (int)offset;
else if (pos == 1) ptr += (int)offset;
else if (pos == 2) ptr = data.length() - (int)offset;
else if (pos == 2) ptr = handle().length() - (int)offset;
if (ptr < 0) ptr = 0;
if (ptr > data.length()) ptr = data.length();
if (ptr > handle().length()) ptr = handle().length();
return pos;
}
@Override
public void close() {
mode = Mode.NONE;
@Override protected boolean onClose() {
ptr = 0;
return true;
}
public MemoryFile(String filename, Buffer buff, Mode mode) {
this.filename = filename;
this.data = buff;
this.mode = mode;
}
public static MemoryFile fromFileList(String filename, java.io.File[] list) {
var res = new StringBuilder();
for (var el : list) res.append(el.getName()).append('\n');
return new MemoryFile(filename, new Buffer(res.toString().getBytes()), Mode.READ);
public MemoryFile(Buffer buff, Mode mode) {
super(buff, mode);
}
}

View File

@ -6,75 +6,79 @@ import java.util.HashSet;
import me.topchetoeu.jscript.common.Buffer;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.utils.filesystem.FilesystemException.FSCode;
public class MemoryFilesystem implements Filesystem {
public final Mode mode;
private HashMap<Path, Buffer> files = new HashMap<>();
private HashSet<Path> folders = new HashSet<>();
private HandleManager handles = new HandleManager();
private Path realPath(String path) {
return Filename.normalize(path);
}
@Override
public String normalize(String... path) {
@Override public String normalize(String... path) {
return Paths.normalize(path);
}
@Override
public void create(String _path, EntryType type) {
var path = realPath(_path);
switch (type) {
case FILE:
if (!folders.contains(path.getParent())) throw new FilesystemException(path.toString(), FSCode.DOESNT_EXIST);
if (folders.contains(path) || files.containsKey(path)) throw new FilesystemException(path.toString(), FSCode.ALREADY_EXISTS);
if (folders.contains(path)) throw new FilesystemException(path.toString(), FSCode.ALREADY_EXISTS);
files.put(path, new Buffer());
break;
case FOLDER:
if (!folders.contains(path.getParent())) throw new FilesystemException(_path, FSCode.DOESNT_EXIST);
if (folders.contains(path) || files.containsKey(path)) throw new FilesystemException(path.toString(), FSCode.ALREADY_EXISTS);
folders.add(path);
break;
default:
case NONE:
if (!folders.remove(path) && files.remove(path) == null) throw new FilesystemException(path.toString(), FSCode.DOESNT_EXIST);
}
}
@Override
public File open(String _path, Mode perms) {
@Override public File open(String _path, Mode perms) {
try {
var path = realPath(_path);
var pcount = path.getNameCount();
if (files.containsKey(path)) return new MemoryFile(path.toString(), files.get(path), perms);
if (files.containsKey(path)) return handles.put(new MemoryFile(files.get(path), perms));
else if (folders.contains(path)) {
var res = new StringBuilder();
for (var folder : folders) {
if (pcount + 1 != folder.getNameCount()) continue;
if (!folder.startsWith(path)) continue;
res.append(folder.toFile().getName()).append('\n');
}
for (var file : files.keySet()) {
if (pcount + 1 != file.getNameCount()) continue;
if (!file.startsWith(path)) continue;
res.append(file.toFile().getName()).append('\n');
}
return new MemoryFile(path.toString(), new Buffer(res.toString().getBytes()), perms.intersect(Mode.READ));
}
else throw new FilesystemException(path.toString(), FSCode.DOESNT_EXIST);
}
@Override
public FileStat stat(String _path) {
return handles.put(new MemoryFile(new Buffer(res.toString().getBytes()), perms.intersect(Mode.READ)));
}
else throw new FilesystemException(ErrorReason.DOESNT_EXIST);
}
catch (FilesystemException e) { throw e.setPath(_path).setAction(ActionType.OPEN); }
}
@Override public boolean create(String _path, EntryType type) {
try {
var path = realPath(_path);
switch (type) {
case FILE:
if (!folders.contains(path.getParent())) throw new FilesystemException(ErrorReason.NO_PARENT);
if (folders.contains(path) || files.containsKey(path)) return false;
files.put(path, new Buffer());
return true;
case FOLDER:
if (!folders.contains(path.getParent())) throw new FilesystemException(ErrorReason.NO_PARENT);
if (folders.contains(path) || files.containsKey(path)) return false;
folders.add(path);
return true;
default:
case NONE:
return folders.remove(path) || files.remove(path) != null;
}
}
catch (FilesystemException e) { throw e.setPath(_path).setAction(ActionType.CREATE); }
}
@Override public FileStat stat(String _path) {
var path = realPath(_path);
if (files.containsKey(path)) return new FileStat(mode, EntryType.FILE);
else if (folders.contains(path)) return new FileStat(mode, EntryType.FOLDER);
else return new FileStat(Mode.NONE, EntryType.NONE);
}
@Override public void close() throws FilesystemException {
handles.close();
}
public MemoryFilesystem put(String path, byte[] data) {
var _path = realPath(path);

View File

@ -3,6 +3,7 @@ package me.topchetoeu.jscript.utils.filesystem;
public enum Mode {
NONE("", false, false),
READ("r", true, false),
WRITE("rw", false, true),
READ_WRITE("rw", true, true);
public final String name;
@ -10,9 +11,7 @@ public enum Mode {
public final boolean writable;
public Mode intersect(Mode other) {
if (this == NONE || other == NONE) return NONE;
if (this == READ_WRITE && other == READ_WRITE) return READ_WRITE;
return READ;
return of(readable && other.readable, writable && other.writable);
}
private Mode(String mode, boolean r, boolean w) {
@ -21,9 +20,20 @@ public enum Mode {
this.writable = w;
}
public static Mode of(boolean read, boolean write) {
if (read && write) return READ_WRITE;
if (read) return READ;
if (write) return WRITE;
return NONE;
}
public static Mode parse(String mode) {
switch (mode) {
switch (mode.toLowerCase()) {
case "r": return READ;
case "w": return WRITE;
case "r+":
case "w+":
case "wr":
case "rw": return READ_WRITE;
default: return NONE;
}

View File

@ -3,55 +3,33 @@ package me.topchetoeu.jscript.utils.filesystem;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.Path;
import me.topchetoeu.jscript.utils.filesystem.FilesystemException.FSCode;
public class PhysicalFile implements File {
private String filename;
private RandomAccessFile file;
private Mode mode;
@Override
public int read(byte[] buff) {
if (file == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
else try { return file.read(buff); }
catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); }
public class PhysicalFile extends BaseFile<RandomAccessFile> {
@Override protected int onRead(byte[] buff) {
try { return handle().read(buff); }
catch (IOException e) { throw new FilesystemException(ErrorReason.NO_PERMISSION).setAction(ActionType.READ); }
}
@Override
public void write(byte[] buff) {
if (file == null || !mode.writable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW);
else try { file.write(buff); }
catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW); }
@Override protected void onWrite(byte[] buff) {
try { handle().write(buff); }
catch (IOException e) { throw new FilesystemException(ErrorReason.NO_PERMISSION).setAction(ActionType.WRITE); }
}
@Override
public long seek(long offset, int pos) {
if (file == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
@Override protected long onSeek(long offset, int pos) {
try {
if (pos == 1) offset += file.getFilePointer();
else if (pos == 2) offset += file.length();
file.seek(offset);
if (pos == 1) offset += handle().getFilePointer();
else if (pos == 2) offset += handle().length();
handle().seek(offset);
return offset;
}
catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); }
catch (IOException e) { throw new FilesystemException(ErrorReason.NO_PERMISSION).setAction(ActionType.SEEK); }
}
@Override
public void close() {
if (file == null) return;
try { file.close(); }
@Override protected boolean onClose() {
try { handle().close(); }
catch (IOException e) {} // SHUT
file = null;
mode = Mode.NONE;
return true;
}
public PhysicalFile(String name, String path, Mode mode) throws FileNotFoundException {
this.filename = name;
this.mode = mode;
if (mode == Mode.NONE) file = null;
else try { file = new RandomAccessFile(path, mode.name); }
catch (FileNotFoundException e) { throw new FilesystemException(filename, FSCode.DOESNT_EXIST); }
public PhysicalFile(Path path, Mode mode) throws FileNotFoundException {
super(new RandomAccessFile(path.toFile(), mode.name), mode);
}
}

View File

@ -1,50 +1,48 @@
package me.topchetoeu.jscript.utils.filesystem;
import java.io.IOException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import me.topchetoeu.jscript.utils.filesystem.FilesystemException.FSCode;
public class PhysicalFilesystem implements Filesystem {
public final String root;
private HandleManager handles = new HandleManager();
private void checkMode(Path path, Mode mode) {
if (!path.startsWith(root)) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_R);
if (!path.startsWith(root)) throw new FilesystemException(ErrorReason.NO_PERMISSION, "Tried to jailbreak the sandbox.");
if (mode.readable && !Files.isReadable(path)) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_R);
if (mode.writable && !Files.isWritable(path)) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_RW);
if (mode.readable && !Files.isReadable(path)) throw new FilesystemException(ErrorReason.NO_PERMISSION, "No read permissions");
if (mode.writable && !Files.isWritable(path)) throw new FilesystemException(ErrorReason.NO_PERMISSION, "No write permissions");
}
private Path realPath(String path) {
return Path.of(Paths.chroot(root, path));
}
@Override
public String normalize(String... paths) {
@Override public String normalize(String... paths) {
return Paths.normalize(paths);
}
@Override
public File open(String _path, Mode perms) {
_path = normalize(_path);
var path = realPath(_path);
@Override public File open(String _path, Mode perms) {
try {
var path = realPath(normalize(_path));
checkMode(path, perms);
try {
if (Files.isDirectory(path)) return new ListFile(_path, Files.list(path).map((v -> v.getFileName().toString())));
else return new PhysicalFile(_path, path.toString(), perms);
if (Files.isDirectory(path)) return handles.put(File.ofIterator(
Files.list(path).map(v -> v.getFileName().toString()).iterator()
));
else return handles.put(new PhysicalFile(path, perms));
}
catch (IOException e) { throw new FilesystemException(path.toString(), FSCode.DOESNT_EXIST); }
catch (IOException e) { throw new FilesystemException(ErrorReason.DOESNT_EXIST); }
}
@Override
public void create(String _path, EntryType type) {
catch (FilesystemException e) { throw e.setAction(ActionType.OPEN).setPath(_path); }
}
@Override public boolean create(String _path, EntryType type) {
try {
var path = realPath(_path);
if (type == EntryType.NONE != Files.exists(path)) throw new FilesystemException(path.toString(), FSCode.ALREADY_EXISTS);
try {
switch (type) {
case FILE:
@ -58,11 +56,14 @@ public class PhysicalFilesystem implements Filesystem {
Files.delete(path);
}
}
catch (IOException e) { throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_RW); }
catch (FileAlreadyExistsException | NoSuchFileException e) { return false; }
catch (IOException e) { throw new FilesystemException(ErrorReason.NO_PARENT); }
}
catch (FilesystemException e) { throw e.setAction(ActionType.CREATE).setPath(_path); }
@Override
public FileStat stat(String _path) {
return true;
}
@Override public FileStat stat(String _path) {
var path = realPath(_path);
if (!Files.exists(path)) return new FileStat(Mode.NONE, EntryType.NONE);
@ -81,6 +82,12 @@ public class PhysicalFilesystem implements Filesystem {
Files.isDirectory(path) ? EntryType.FOLDER : EntryType.FILE
);
}
@Override public void close() throws FilesystemException {
try {
handles.close();
}
catch (FilesystemException e) { throw e.setAction(ActionType.CLOSE_FS); }
}
public PhysicalFilesystem(String root) {
this.root = Paths.normalize(Path.of(root).toAbsolutePath().toString());

View File

@ -4,7 +4,6 @@ import java.util.HashMap;
import java.util.Map;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.utils.filesystem.FilesystemException.FSCode;
import me.topchetoeu.jscript.utils.permissions.Matcher;
import me.topchetoeu.jscript.utils.permissions.PermissionsProvider;
@ -20,8 +19,22 @@ public class RootFilesystem implements Filesystem {
}
private void modeAllowed(String _path, Mode mode) throws FilesystemException {
if (mode.readable && perms != null && !canRead(_path)) throw new FilesystemException(_path, FSCode.NO_PERMISSIONS_R);
if (mode.writable && perms != null && !canWrite(_path)) throw new FilesystemException(_path, FSCode.NO_PERMISSIONS_RW);
if (mode.readable && perms != null && !canRead(_path)) {
throw new FilesystemException(ErrorReason.NO_PERMISSION, "No read permissions").setPath(_path);
}
if (mode.writable && perms != null && !canWrite(_path)) {
throw new FilesystemException(ErrorReason.NO_PERMISSION, "No wtrite permissions").setPath(_path);
}
}
private Filesystem getProtocol(Filename filename) {
var protocol = protocols.get(filename.protocol);
if (protocol == null) {
throw new FilesystemException(ErrorReason.DOESNT_EXIST, "The protocol '" + filename.protocol + "' doesn't exist.");
}
return protocol;
}
@Override public String normalize(String... paths) {
@ -37,30 +50,43 @@ public class RootFilesystem implements Filesystem {
}
}
@Override public File open(String path, Mode perms) throws FilesystemException {
try {
var filename = Filename.parse(path);
var protocol = protocols.get(filename.protocol);
if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST);
var protocol = getProtocol(filename);
modeAllowed(filename.toString(), perms);
try { return protocol.open(filename.path, perms); }
catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); }
return protocol.open(filename.path, perms);
}
@Override public void create(String path, EntryType type) throws FilesystemException {
catch (FilesystemException e) { throw e.setPath(path).setAction(ActionType.OPEN); }
}
@Override public boolean create(String path, EntryType type) throws FilesystemException {
try {
var filename = Filename.parse(path);
var protocol = protocols.get(filename.protocol);
if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST);
modeAllowed(filename.toString(), Mode.READ_WRITE);
var protocol = getProtocol(filename);
try { protocol.create(filename.path, type); }
catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); }
modeAllowed(filename.toString(), Mode.WRITE);
return protocol.create(filename.path, type);
}
catch (FilesystemException e) { throw e.setPath(path).setAction(ActionType.CREATE); }
}
@Override public FileStat stat(String path) throws FilesystemException {
try {
var filename = Filename.parse(path);
var protocol = protocols.get(filename.protocol);
if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST);
var protocol = getProtocol(filename);
try { return protocol.stat(filename.path); }
catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); }
return protocol.stat(filename.path);
}
catch (FilesystemException e) { throw e.setPath(path).setAction(ActionType.STAT); }
}
@Override public void close() throws FilesystemException {
try {
for (var protocol : protocols.values()) {
protocol.close();
}
protocols.clear();
}
catch (FilesystemException e) { throw e.setAction(ActionType.CLOSE_FS); }
}
public RootFilesystem(PermissionsProvider perms) {

View File

@ -0,0 +1,42 @@
package me.topchetoeu.jscript.utils.filesystem;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
public class STDFilesystem implements Filesystem {
private final HashMap<String, File> handles = new HashMap<>();
@Override
public String normalize(String... path) {
var res = Paths.normalize(path);
while (res.startsWith("/")) res = res.substring(1);
return res;
}
@Override public File open(String path, Mode mode) {
path = normalize(path);
if (handles.containsKey(path)) return handles.get(path);
else throw new FilesystemException(ErrorReason.DOESNT_EXIST).setAction(ActionType.OPEN).setPath(path);
}
@Override public FileStat stat(String path) {
path = normalize(path);
if (handles.containsKey(path)) return new FileStat(Mode.READ_WRITE, EntryType.FILE);
else return new FileStat(Mode.NONE, EntryType.NONE);
}
@Override public void close() {
handles.clear();
}
public STDFilesystem add(String name, File handle) {
this.handles.put(name, handle);
return this;
}
public static STDFilesystem ofStd(InputStream in, OutputStream out, OutputStream err) {
return new STDFilesystem()
.add("in", File.ofStream(in))
.add("out", File.ofStream(out))
.add("err", File.ofStream(err));
}
}

View File

@ -1,24 +0,0 @@
package me.topchetoeu.jscript.utils.filesystem;
import me.topchetoeu.jscript.core.engine.Extensions;
import me.topchetoeu.jscript.core.engine.values.Symbol;
import me.topchetoeu.jscript.core.exceptions.EngineException;
public class Stdio {
public static final Symbol STDIN = Symbol.get("IO.stdin");
public static final Symbol STDOUT = Symbol.get("IO.stdout");
public static final Symbol STDERR = Symbol.get("IO.stderr");
public static File stdout(Extensions exts) {
if (exts.hasNotNull(STDOUT)) return exts.get(STDOUT);
else throw EngineException.ofError("stdout is not supported.");
}
public static File stdin(Extensions exts) {
if (exts.hasNotNull(STDIN)) return exts.get(STDIN);
else throw EngineException.ofError("stdin is not supported.");
}
public static File stderr(Extensions exts) {
if (exts.hasNotNull(STDERR)) return exts.get(STDERR);
else throw EngineException.ofError("stderr is not supported.");
}
}