implement print(), partially implement io.tmpfile()

tmpfile() now errors because filesystem components don't support r+/w+/a+ modes, instead of because TODO
main
cinder 6 months ago
parent a1e6b75b22
commit f0ed0822b8

@ -2,16 +2,17 @@
local bootfs = ... local bootfs = ...
-- ...but [lua bios, openloader] don't, so fall back to the usual way -- ...but [lua bios, openloader] don't, so fall back to the usual way
bootfs = bootfs or computer.getBootAddress() bootfs = bootfs or computer.getBootAddress()
local tmpfs = computer.tmpAddress()
--store all functions we need in locals so userspace mischief can't replace them --store all functions we need in locals so userspace mischief can't replace them
--prefix 'k' (for kernel) on any functions userspace will also have a version of to ensure kernelspace --prefix 'k' (for kernel) on any functions userspace will also have a version of to ensure kernelspace
--function usage is intentional (ie prevent accidentally using the kernel's `load` in a userspace `loadfile`) --function usage is intentional (ie prevent accidentally using the kernel's `load` in a userspace `loadfile`)
local envBase = _G --used for creating process environments local envBase = _G --used for creating process environments
local assert, checkArg, error, kload, ipairs, next, pairs, rawequal, rawset, setmetatable, tostring, type = assert, checkArg, error, load, ipairs, next, pairs, rawequal, rawset, setmetatable, tostring, type local assert, checkArg, error, kload, ipairs, next, pairs, rawequal, rawset, setmetatable, tostring, type = assert, checkArg, error, load, ipairs, next, pairs, rawequal, rawset, setmetatable, tostring, type
local invoke = component.invoke local invoke, component_type = component.invoke, component.type
local kpullSignal, shutdown, uptime = computer.pullSignal, computer.shutdown, computer.uptime local kpullSignal, shutdown, uptime = computer.pullSignal, computer.shutdown, computer.uptime
local co_create, co_status, kresume, kyield = coroutine.create, coroutine.status, coroutine.resume, coroutine.yield local co_create, co_status, kresume, kyield = coroutine.create, coroutine.status, coroutine.resume, coroutine.yield
local huge, math_type = math.huge, math.type local huge, math_type, random = math.huge, math.type, math.random
local find, format, match, sub = string.find, string.format, string.match, string.sub local find, format, match, sub = string.find, string.format, string.match, string.sub
local concat, pack, unpack = table.concat, table.pack, table.unpack local concat, pack, unpack = table.concat, table.pack, table.unpack

@ -14,14 +14,18 @@ do
end end
local function wrapFile(mode, fh) local function wrapFile(mode, fh)
local rwa, bin, plus, b2 = match(mode or 'r', '^([rwa])(b?)(%+?)(b?)$') --match all of x, xb, x+, xb+, x+b local rwa, b, plus, b2 = match(mode or 'r', '^([rwa])(b?)(%+?)(b?)$') --match all of x, xb, x+, xb+, x+b
if not rwa or (bin ~= '' and b2 ~= '') then return nil, 'bad mode' end if not rwa or (b ~= '' and b2 ~= '') then return nil, 'bad mode' end
bin = bin .. b2 --when (bin == '') xor (b2 == ''), this is logical or
local read = rwa == 'r' or plus local read = rwa == 'r' or plus
local write = rwa ~= 'r' or plus local write = rwa ~= 'r' or plus
local append = rwa == 'a' local append = rwa == 'a'
local update = plus ~= '' local update = plus ~= ''
local bin = (b .. b2) ~= ''
--TODO truncate if w+
--TODO restrict seeking on a
--TODO restrict seek to end before write on a+
local file = setmetatable({}, filemt) local file = setmetatable({}, filemt)
filedata[file] = fh filedata[file] = fh
@ -50,6 +54,7 @@ do
local function path_seek(fh, ...) return invoke(fh.address, 'seek', fh.handle, ...) end local function path_seek(fh, ...) return invoke(fh.address, 'seek', fh.handle, ...) end
function openPath(mode, addr, path) function openPath(mode, addr, path)
--TODO do filesystem components support 'a' modes?
local fd, err = invoke(addr, 'open', path, mode) --should we validate `mode` first? local fd, err = invoke(addr, 'open', path, mode) --should we validate `mode` first?
if not fd then if not fd then
return nil, err return nil, err
@ -132,9 +137,7 @@ do
if wbuf ~= '' then return flush(fh) end if wbuf ~= '' then return flush(fh) end
elseif mode == 'full' then elseif mode == 'full' then
local cap = fh._file_wcap or 512 --TODO tune default capacity to e.g. free memory local cap = fh._file_wcap or 512 --TODO tune default capacity to e.g. free memory
if #fh._file_wbuf >= cap then if #fh._file_wbuf >= cap then return flush(fh) end
return flush(fh)
end
else --for line buffering, only flush complete lines else --for line buffering, only flush complete lines
local i1, i2 = find(fh._file_wbuf, '.+[\r\n]') --`i2` is the last cr or lf in the buffer local i1, i2 = find(fh._file_wbuf, '.+[\r\n]') --`i2` is the last cr or lf in the buffer
if i1 then --if there are any newlines if i1 then --if there are any newlines
@ -143,10 +146,10 @@ do
local ok, err = flush(fh) local ok, err = flush(fh)
if ok then fh._file_wbuf = rest end if ok then fh._file_wbuf = rest end
return ok, err return ok, err
else --no newlines, nothing to flush
return true
end end
end end
--never hit any of the branches that `return flush(fh)`ed, so we've successfully written to the buffer
return true
end end
@ -258,7 +261,7 @@ do
local tell = (whence == 'cur' or not whence) and (offset == 0 or not offset) --true if we didn't move ie ftell() local tell = (whence == 'cur' or not whence) and (offset == 0 or not offset) --true if we didn't move ie ftell()
--after seeking, if we moved, invalidate the read buffer --after seeking, if we moved, invalidate the read buffer
if (whence and whence ~= cur) or (offset and offset ~= 0) then if (whence and whence ~= 'cur') or (offset and offset ~= 0) then
fh._file_rbuf = fh._file_rbuf and '' fh._file_rbuf = fh._file_rbuf and ''
end end

@ -9,7 +9,7 @@ given <https://ocdoc.cil.li/tutorial:custom_oses#what_s_available>:
]] ]]
envBase._OSVERSION = 'kitn 0.2.0-beta.1' --prefer [openloader, openos] "$name $ver" over plan9k "$name/$ver" envBase._OSVERSION = 'kitn 0.2.0-beta.2' --prefer [openloader, openos] "$name $ver" over plan9k "$name/$ver"
envBase.kitn = {} envBase.kitn = {}
envBase._G, envBase.load = nil envBase._G, envBase.load = nil
@ -100,7 +100,11 @@ function fillEnv(proc, uenv)
checkArg(1, filename, 'string', 'nil') checkArg(1, filename, 'string', 'nil')
error('TODO io.lines') --iff we open a file, we need to close it error('TODO io.lines') --iff we open a file, we need to close it
end, end,
open = function(filename, mode) return openPath(mode, bootfs, filename) end, open = function(filename, mode)
checkArg(1, filename, 'string')
checkArg(2, filename, 'string', 'nil')
return openPath(mode, bootfs, filename)
end,
output = function(file) output = function(file)
local t = checkArgEx(1, file, 'string', 'table', 'nil') local t = checkArgEx(1, file, 'string', 'table', 'nil')
if t ~= nil then if t ~= nil then
@ -111,8 +115,14 @@ function fillEnv(proc, uenv)
--popen is unimplemented because we don't have a notion of a shell --popen is unimplemented because we don't have a notion of a shell
read = function(...) return uio.stdin:read(...) end, read = function(...) return uio.stdin:read(...) end,
tmpfile = function() tmpfile = function()
--use math.random() to pick a filename on computer.tmpAddress() that doesn't exist yet and create it local deadline = uptime() + 1 --give up after 1s instead of potentially infinitely looping
error('TODO io.tmpfile') --file is deleted on process exit, requires atExit mechanism local filename
repeat
filename = format('tmp.%08x', random(0, 0x7fffffff))
if invoke(tmpfs, 'exists', filename) then filename = nil end
until filename or uptime() >= deadline
assert(filename, 'failed to allocate tmpfile')
return openPath('w+', tmpfs, filename) --TODO close at exit
end, end,
type = function(obj) type = function(obj)
local file, opened = isFile(obj) local file, opened = isFile(obj)
@ -128,6 +138,14 @@ function fillEnv(proc, uenv)
} }
uenv.io = uio uenv.io = uio
function uenv.print(...)
local parts = pack(...)
for i = 1, parts.n do
parts[i] = tostring(parts[i])
end
assert(uenv.io.write(concat(parts, '\t', 1, parts.n), '\n'))
end
--env will automatically mirror envBase.kitn here --env will automatically mirror envBase.kitn here
local ukitn = uenv.kitn local ukitn = uenv.kitn
@ -154,6 +172,18 @@ function fillEnv(proc, uenv)
--TODO return information for the current thread within the process too --TODO return information for the current thread within the process too
return proc, nil return proc, nil
end end
--TODO map all filesystems into a single tree, then remove this method
function ukitn.openOn(address, path, mode)
checkArg(1, address, 'string')
checkArg(2, path, 'string')
checkArg(3, mode, 'string', 'nil')
local t, err = component_type(address)
if t ~= 'filesystem' then
return nil, err or 'component is not a filesystem'
end
return openPath(mode, address, path)
end
end end
-- vi: set ts=2: -- vi: set ts=2:

Loading…
Cancel
Save