`Feature`: implemented (almost) all `io.*` and `file:*` functions in stdlib
All functions are based on and fully comply with the Lua 5.3 manual (https://www.lua.org/manual/5.3/).
Features
- Implemented functions
io.close,io.flush,io.input,io.lines,io.open,io.output,io.read,io.tmpfile,io.type,io.write,file:flush,file:lines,file:read,file:seek,file:write. - Added tests in
io.luafor all new functions. - In
Cargo.toml, thetempfileandeithercrates have been added.tempfileis used forio.tmpfile, andeitheris used as a helper in theIoFiletype.
@Aeledfyr since you are reviewing all PRs, could you please review this one as well?
I can review this, but I'm only merging relatively small changes. Larger changes like this will have to wait until kyren is available to review as well, which will likely be a while from now.
I'll do a first-pass review of the larger PRs (and see if there are sub-parts that can be split out to merge sooner), but it may be a while before they can be fully merged.
Another high-level thing that needs to be considered is how to handle sandboxing this, and how to expose that to the user. The main things we'd need to achieve for reasonable sandboxing:
- Allowing the host to limit access to stdin/stdout/stderr, and/or replace them with custom files/streams
- Allowing the host to limit what files can be opened, and what operations can be done on them
One approach that might make this possible would be to have the user provide the system interface through a trait implementation -- something like this:
type FileRef = Rc<RefCell<dyn IoFile>>;
trait IoContext {
fn open(&self, path: &[u8], mode: Mode) -> Result<FileRef, Error>;
fn tempfile(&self) -> Result<FileRef, Error>;
fn stdin(&self) -> Option<FileRef>;
fn stdout(&self) -> Option<FileRef>;
fn stderr(&self) -> Option<FileRef>;
}
// For files that only support some of the read/write/seek
// operations, make a placeholder impl that always errors.
trait IoFile: Read + Write + Seek {
fn close(&mut self) -> Result<(), Error>;
fn flush(&mut self) -> Result<(), Error>;
}
struct LuaStdin(std::io::Stdin);
impl Read for LuaStdin { /* pass through */ }
impl Write for LuaStdin { /* always error */ }
impl Seek for LuaStdin { /* always error */ }
impl IoFile for LuaStdin {
fn close(&mut self) -> Result<(), Error> { Ok(()) }
fn flush(&mut self) -> Result<(), Error> { Ok(()) }
}
With that, load_io could just take in an implementation of IoContext and rely on the user to provide the implementation / sandboxing.
Another high-level thing that needs to be considered is how to handle sandboxing this, and how to expose that to the user. The main things we'd need to achieve for reasonable sandboxing:
- Allowing the host to limit access to stdin/stdout/stderr, and/or replace them with custom files/streams
- Allowing the host to limit what files can be opened, and what operations can be done on them
One approach that might make this possible would be to have the user provide the system interface through a trait implementation -- something like this:
Yeah, this suggestion to add some kind of trait abstraction around I/O or OS details sounds good, to avoid assuming it's safe/desirable to expose the host OS / filesystem to Lua code.
I've been playing with using piccolo recently and at least for my use case it's a feature that piccolo doesn't support the io APIs which allow code to interact with the host platform.