testing version of LuaBIOS and OpenOS

people were having issues getting them to work so now we promote consistency
This commit is contained in:
IonutParau 2025-06-28 20:41:49 +02:00
parent 8210e20939
commit 687cfebd00
182 changed files with 14016 additions and 1 deletions

1
.gitignore vendored
View File

@ -1,4 +1,3 @@
.zig-cache/
zig-out/
data/*
!data/.gitkeep

1
data/OpenOS/.prop Normal file
View File

@ -0,0 +1 @@
{label = "OpenOS", reboot=true, setlabel=true, setboot=true, noclobber={"etc/rc.cfg","home/.shrc"}}

View File

@ -0,0 +1,2 @@
local computer = require("computer")
io.write(computer.address(),"\n")

62
data/OpenOS/bin/alias.lua Normal file
View File

@ -0,0 +1,62 @@
local shell = require("shell")
local args, options = shell.parse(...)
local ec, error_prefix = 0, "alias:"
if options.help then
print(string.format("Usage: alias: [name[=value] ... ]"))
return
end
local function validAliasName(k)
return k:match("[/%$`=|&;%(%)<> \t]") == nil
end
local function setAlias(k, v)
if not validAliasName(k) then
io.stderr:write(string.format("%s `%s': invalid alias name\n", error_prefix, k))
else
shell.setAlias(k, v)
end
end
local function printAlias(k)
local v = shell.getAlias(k)
if not v then
io.stderr:write(string.format("%s %s: not found\n", error_prefix, k))
ec = 1
else
io.write(string.format("alias %s='%s'\n", k, v))
end
end
local function splitPair(arg)
local matchBegin, matchEnd = arg:find("=")
if matchBegin == nil or matchBegin == 1 then
return arg
else
return arg:sub(1, matchBegin - 1), arg:sub(matchEnd + 1)
end
end
local function handlePair(k, v)
if v then
return setAlias(k, v)
else
return printAlias(k)
end
end
if not next(args) then -- no args
-- print all aliases
for k,v in shell.aliases() do
print(string.format("alias %s='%s'", k, v))
end
else
for _,v in ipairs(args) do
checkArg(1,v,"string")
handlePair(splitPair(v))
end
end
return ec

39
data/OpenOS/bin/cat.lua Normal file
View File

@ -0,0 +1,39 @@
local shell = require("shell")
local fs = require("filesystem")
local args = shell.parse(...)
if #args == 0 then
args = {"-"}
end
local input_method, input_param = "read", require("tty").getViewport()
for i = 1, #args do
local arg = shell.resolve(args[i])
if fs.isDirectory(arg) then
io.stderr:write(string.format('cat %s: Is a directory\n', arg))
os.exit(1)
else
local file, reason
if args[i] == "-" then
file, reason = io.stdin, "missing stdin"
input_method, input_param = "readLine", false
else
file, reason = fs.open(arg)
end
if not file then
io.stderr:write(string.format("cat: %s: %s\n", args[i], tostring(reason)))
os.exit(1)
else
repeat
local chunk = file[input_method](file, input_param)
if chunk then
io.write(chunk)
end
until not chunk
file:close()
end
end
end
io.stdout:close()

51
data/OpenOS/bin/cd.lua Normal file
View File

@ -0,0 +1,51 @@
local shell = require("shell")
local fs = require("filesystem")
local args, ops = shell.parse(...)
local path = nil
local verbose = false
if ops.help then
print(
[[Usage cd [dir]
For more options, run: man cd]])
return
end
if #args == 0 then
local home = os.getenv("HOME")
if not home then
io.stderr:write("cd: HOME not set\n")
return 1
end
path = home
elseif args[1] == '-' then
verbose = true
local oldpwd = os.getenv("OLDPWD");
if not oldpwd then
io.stderr:write("cd: OLDPWD not set\n")
return 1
end
path = oldpwd
else
path = args[1]
end
local resolved = shell.resolve(path)
if not fs.exists(resolved) then
io.stderr:write("cd: ",path,": No such file or directory\n")
return 1
end
path = resolved
local oldpwd = shell.getWorkingDirectory()
local result, reason = shell.setWorkingDirectory(path)
if not result then
io.stderr:write("cd: ", path, ": ", reason)
return 1
else
os.setenv("OLDPWD", oldpwd)
end
if verbose then
os.execute("pwd")
end

View File

@ -0,0 +1,2 @@
local tty = require("tty")
tty.clear()

View File

@ -0,0 +1,52 @@
local component = require("component")
local shell = require("shell")
local text = require("text")
local args, options = shell.parse(...)
local count = tonumber(options.limit) or math.huge
local components = {}
local padTo = 1
if #args == 0 then -- get all components if no filters given.
args[1] = ""
end
for _, filter in ipairs(args) do
for address, name in component.list(filter) do
if name:len() > padTo then
padTo = name:len() + 2
end
components[address] = name
end
end
padTo = padTo + 8 - padTo % 8
for address, name in pairs(components) do
io.write(text.padRight(name, padTo) .. address .. '\n')
if options.l then
local proxy = component.proxy(address)
local padTo = 1
local methods = {}
for name, member in pairs(proxy) do
if type(member) == "table" or type(member) == "function" then
if name:len() > padTo then
padTo = name:len() + 2
end
table.insert(methods, name)
end
end
table.sort(methods)
padTo = padTo + 8 - padTo % 8
for _, name in ipairs(methods) do
local doc = component.doc(address, name) or tostring(proxy[name])
io.write(" " .. text.padRight(name, padTo) .. doc .. '\n')
end
end
count = count - 1
if count <= 0 then
break
end
end

36
data/OpenOS/bin/cp.lua Normal file
View File

@ -0,0 +1,36 @@
local shell = require("shell")
local transfer = require("tools/transfer")
local args, options = shell.parse(...)
options.h = options.h or options.help
if #args < 2 or options.h then
io.write([[Usage: cp [OPTIONS] <from...> <to>
-i: prompt before overwrite (overrides -n option).
-n: do not overwrite an existing file.
-r: copy directories recursively.
-u: copy only when the SOURCE file differs from the destination
file or when the destination file is missing.
-P: preserve attributes, e.g. symbolic links.
-v: verbose output.
-x: stay on original source file system.
--skip=P: skip files matching lua regex P
]])
return not not options.h
end
-- clean options for copy (as opposed to move)
options =
{
cmd = "cp",
i = options.i,
f = options.f,
n = options.n,
r = options.r,
u = options.u,
P = options.P,
v = options.v,
x = options.x,
skip = {options.skip},
}
return transfer.batch(args, options)

1
data/OpenOS/bin/date.lua Normal file
View File

@ -0,0 +1 @@
io.write(os.date("%F %T").."\n")

76
data/OpenOS/bin/df.lua Normal file
View File

@ -0,0 +1,76 @@
local fs = require("filesystem")
local shell = require("shell")
local text = require("text")
local args, options = shell.parse(...)
local function formatSize(size)
if not options.h then
return tostring(size)
elseif type(size) == "string" then
return size
end
local sizes = {"", "K", "M", "G"}
local unit = 1
local power = options.si and 1000 or 1024
while size > power and unit < #sizes do
unit = unit + 1
size = size / power
end
return math.floor(size * 10) / 10 .. sizes[unit]
end
local mounts = {}
if #args == 0 then
for proxy, path in fs.mounts() do
if not mounts[proxy] or mounts[proxy]:len() > path:len() then
mounts[proxy] = path
end
end
else
for i = 1, #args do
local proxy, path = fs.get(shell.resolve(args[i]))
if not proxy then
io.stderr:write(args[i], ": no such file or directory\n")
else
mounts[proxy] = path
end
end
end
local result = {{"Filesystem", "Used", "Available", "Use%", "Mounted on"}}
for proxy, path in pairs(mounts) do
local label = proxy.getLabel() or proxy.address
local used, total = proxy.spaceUsed(), proxy.spaceTotal()
local available, percent
if total == math.huge then
used = used or "N/A"
available = "unlimited"
percent = "0%"
else
available = total - used
percent = used / total
if percent ~= percent then -- NaN
available = "N/A"
percent = "N/A"
else
percent = math.ceil(percent * 100) .. "%"
end
end
table.insert(result, {label, formatSize(used), formatSize(available), tostring(percent), path})
end
local m = {}
for _, row in ipairs(result) do
for col, value in ipairs(row) do
m[col] = math.max(m[col] or 1, value:len())
end
end
for _, row in ipairs(result) do
for col, value in ipairs(row) do
local padding = col == #row and 0 or 2
io.write(text.padRight(value, m[col] + padding))
end
print()
end

38
data/OpenOS/bin/dmesg.lua Normal file
View File

@ -0,0 +1,38 @@
local event = require("event")
local tty = require("tty")
local args = {...}
local gpu = tty.gpu()
local interactive = io.output().tty
local color, isPal, evt
if interactive then
color, isPal = gpu.getForeground()
end
io.write("Press 'Ctrl-C' to exit\n")
pcall(function()
repeat
if #args > 0 then
evt = table.pack(event.pullMultiple("interrupted", table.unpack(args)))
else
evt = table.pack(event.pull())
end
if interactive then gpu.setForeground(0xCC2200) end
io.write("[" .. os.date("%T") .. "] ")
if interactive then gpu.setForeground(0x44CC00) end
io.write(tostring(evt[1]) .. string.rep(" ", math.max(10 - #tostring(evt[1]), 0) + 1))
if interactive then gpu.setForeground(0xB0B00F) end
io.write(tostring(evt[2]) .. string.rep(" ", 37 - #tostring(evt[2])))
if interactive then gpu.setForeground(0xFFFFFF) end
if evt.n > 2 then
for i = 3, evt.n do
io.write(" " .. tostring(evt[i]))
end
end
io.write("\n")
until evt[1] == "interrupted"
end)
if interactive then
gpu.setForeground(color, isPal)
end

128
data/OpenOS/bin/du.lua Normal file
View File

@ -0,0 +1,128 @@
local shell = require("shell")
local fs = require("filesystem")
local args, options, reason = shell.parse(...)
if #args == 0 then
args[1] = '.'
end
local TRY=[[
Try 'du --help' for more information.]]
local VERSION=[[
du (OpenOS bin) 1.0
Written by payonel, patterned after GNU coreutils du]]
local HELP=[[
Usage: du [OPTION]... [FILE]...
Summarize disk usage of each FILE, recursively for directories.
-h, --human-readable print sizes in human readable format (e.g., 1K 234M 2G)
-s, --summarize display only a total for each argument
--help display this help and exit
--version output version information and exit]]
if options.help then
print(HELP)
return true
end
if options.version then
print(VERSION)
return true
end
local function addTrailingSlash(path)
if path:sub(-1) ~= '/' then
return path .. '/'
else
return path
end
end
local function opCheck(shortName, longName)
local enabled = options[shortName] or options[longName]
options[shortName] = nil
options[longName] = nil
return enabled
end
local bHuman = opCheck('h', 'human-readable')
local bSummary = opCheck('s', 'summarize')
if next(options) then
for op,v in pairs(options) do
io.stderr:write(string.format("du: invalid option -- '%s'\n", op))
end
io.stderr:write(TRY..'\n')
return 1
end
local function formatSize(size)
if not bHuman then
return tostring(size)
end
local sizes = {"", "K", "M", "G"}
local unit = 1
local power = options.si and 1000 or 1024
while size > power and unit < #sizes do
unit = unit + 1
size = size / power
end
return math.floor(size * 10) / 10 .. sizes[unit]
end
local function printSize(size, rpath)
local displaySize = formatSize(size)
io.write(string.format("%s%s\n", string.format("%-12s", displaySize), rpath))
end
local function visitor(rpath)
local subtotal = 0
local dirs = 0
local spath = shell.resolve(rpath)
if fs.isDirectory(spath) then
local list_result = fs.list(spath)
for list_item in list_result do
local vtotal, vdirs = visitor(addTrailingSlash(rpath) .. list_item)
subtotal = subtotal + vtotal
dirs = dirs + vdirs
end
if dirs == 0 then -- no child dirs
if not bSummary then
printSize(subtotal, rpath)
end
end
elseif not fs.isLink(spath) then
subtotal = fs.size(spath)
end
return subtotal, dirs
end
for i,arg in ipairs(args) do
local path = shell.resolve(arg)
if not fs.exists(path) then
io.stderr:write(string.format("du: cannot access '%s': no such file or directory\n", arg))
return 1
else
if fs.isDirectory(path) then
local total = visitor(arg)
if bSummary then
printSize(total, arg)
end
elseif fs.isLink(path) then
printSize(0, arg)
else
printSize(fs.size(path), arg)
end
end
end
return true

22
data/OpenOS/bin/echo.lua Normal file
View File

@ -0,0 +1,22 @@
local args, options = require("shell").parse(...)
if options.help then
io.write([[
`echo` writes the provided string(s) to the standard output.
-n do not output the trialing newline
-e enable interpretation of backslash escapes
--help display this help and exit
]])
return
end
if options.e then
for index,arg in ipairs(args) do
-- use lua load here to interpret escape sequences such as \27
-- instead of writing my own language to interpret them myself
-- note that in a real terminal, \e is used for \27
args[index] = assert(load("return \"" .. arg:gsub('"', [[\"]]) .. "\""))()
end
end
io.write(table.concat(args," "))
if not options.n then
io.write("\n")
end

723
data/OpenOS/bin/edit.lua Normal file
View File

@ -0,0 +1,723 @@
local fs = require("filesystem")
local keyboard = require("keyboard")
local shell = require("shell")
local term = require("term") -- TODO use tty and cursor position instead of global area and gpu
local text = require("text")
local unicode = require("unicode")
if not term.isAvailable() then
return
end
local gpu = term.gpu()
local args, options = shell.parse(...)
if #args == 0 then
io.write("Usage: edit <filename>")
return
end
local filename = shell.resolve(args[1])
local file_parentpath = fs.path(filename)
if fs.exists(file_parentpath) and not fs.isDirectory(file_parentpath) then
io.stderr:write(string.format("Not a directory: %s\n", file_parentpath))
return 1
end
local readonly = options.r or fs.get(filename) == nil or fs.get(filename).isReadOnly()
if fs.isDirectory(filename) then
io.stderr:write("file is a directory\n")
return 1
elseif not fs.exists(filename) and readonly then
io.stderr:write("file system is read only\n")
return 1
end
local function loadConfig()
-- Try to load user settings.
local env = {}
local config = loadfile("/etc/edit.cfg", nil, env)
if config then
pcall(config)
end
-- Fill in defaults.
env.keybinds = env.keybinds or {
left = {{"left"}},
right = {{"right"}},
up = {{"up"}},
down = {{"down"}},
home = {{"home"}},
eol = {{"end"}},
pageUp = {{"pageUp"}},
pageDown = {{"pageDown"}},
backspace = {{"back"}, {"shift", "back"}},
delete = {{"delete"}},
deleteLine = {{"control", "delete"}, {"shift", "delete"}},
newline = {{"enter"}},
save = {{"control", "s"}},
close = {{"control", "w"}},
find = {{"control", "f"}},
findnext = {{"control", "g"}, {"control", "n"}, {"f3"}},
cut = {{"control", "k"}},
uncut = {{"control", "u"}}
}
-- Generate config file if it didn't exist.
if not config then
local root = fs.get("/")
if root and not root.isReadOnly() then
fs.makeDirectory("/etc")
local f = io.open("/etc/edit.cfg", "w")
if f then
local serialization = require("serialization")
for k, v in pairs(env) do
f:write(k.."="..tostring(serialization.serialize(v, math.huge)).."\n")
end
f:close()
end
end
end
return env
end
term.clear()
term.setCursorBlink(true)
local running = true
local buffer = {}
local scrollX, scrollY = 0, 0
local config = loadConfig()
local cutBuffer = {}
-- cutting is true while we're in a cutting operation and set to false when cursor changes lines
-- basically, whenever you change lines, the cutting operation ends, so the next time you cut a new buffer will be created
local cutting = false
local getKeyBindHandler -- forward declaration for refind()
local function helpStatusText()
local function prettifyKeybind(label, command)
local keybind = type(config.keybinds) == "table" and config.keybinds[command]
if type(keybind) ~= "table" or type(keybind[1]) ~= "table" then return "" end
local alt, control, shift, key
for _, value in ipairs(keybind[1]) do
if value == "alt" then alt = true
elseif value == "control" then control = true
elseif value == "shift" then shift = true
else key = value end
end
if not key then return "" end
return label .. ": [" ..
(control and "Ctrl+" or "") ..
(alt and "Alt+" or "") ..
(shift and "Shift+" or "") ..
unicode.upper(key) ..
"] "
end
return prettifyKeybind("Save", "save") ..
prettifyKeybind("Close", "close") ..
prettifyKeybind("Find", "find") ..
prettifyKeybind("Cut", "cut") ..
prettifyKeybind("Uncut", "uncut")
end
-------------------------------------------------------------------------------
local function setStatus(value)
local x, y, w, h = term.getGlobalArea()
value = unicode.wlen(value) > w - 10 and unicode.wtrunc(value, w - 9) or value
value = text.padRight(value, w - 10)
gpu.set(x, y + h - 1, value)
end
local function getArea()
local x, y, w, h = term.getGlobalArea()
return x, y, w, h - 1
end
local function removePrefix(line, length)
if length >= unicode.wlen(line) then
return ""
else
local prefix = unicode.wtrunc(line, length + 1)
local suffix = unicode.sub(line, unicode.len(prefix) + 1)
length = length - unicode.wlen(prefix)
if length > 0 then
suffix = (" "):rep(unicode.charWidth(suffix) - length) .. unicode.sub(suffix, 2)
end
return suffix
end
end
local function lengthToChars(line, length)
if length > unicode.wlen(line) then
return unicode.len(line) + 1
else
local prefix = unicode.wtrunc(line, length)
return unicode.len(prefix) + 1
end
end
local function isWideAtPosition(line, x)
local index = lengthToChars(line, x)
if index > unicode.len(line) then
return false, false
end
local prefix = unicode.sub(line, 1, index)
local char = unicode.sub(line, index, index)
--isWide, isRight
return unicode.isWide(char), unicode.wlen(prefix) == x
end
local function drawLine(x, y, w, h, lineNr)
local yLocal = lineNr - scrollY
if yLocal > 0 and yLocal <= h then
local str = removePrefix(buffer[lineNr] or "", scrollX)
str = unicode.wlen(str) > w and unicode.wtrunc(str, w + 1) or str
str = text.padRight(str, w)
gpu.set(x, y - 1 + lineNr - scrollY, str)
end
end
local function getCursor()
local cx, cy = term.getCursor()
return cx + scrollX, cy + scrollY
end
local function line()
local _, cby = getCursor()
return buffer[cby] or ""
end
local function getNormalizedCursor()
local cbx, cby = getCursor()
local wide, right = isWideAtPosition(buffer[cby], cbx)
if wide and right then
cbx = cbx - 1
end
return cbx, cby
end
local function setCursor(nbx, nby)
local x, y, w, h = getArea()
nbx, nby = math.floor(nbx), math.floor(nby)
nby = math.max(1, math.min(#buffer, nby))
local ncy = nby - scrollY
if ncy > h then
term.setCursorBlink(false)
local sy = nby - h
local dy = math.abs(scrollY - sy)
scrollY = sy
if h > dy then
gpu.copy(x, y + dy, w, h - dy, 0, -dy)
end
for lineNr = nby - (math.min(dy, h) - 1), nby do
drawLine(x, y, w, h, lineNr)
end
elseif ncy < 1 then
term.setCursorBlink(false)
local sy = nby - 1
local dy = math.abs(scrollY - sy)
scrollY = sy
if h > dy then
gpu.copy(x, y, w, h - dy, 0, dy)
end
for lineNr = nby, nby + (math.min(dy, h) - 1) do
drawLine(x, y, w, h, lineNr)
end
end
term.setCursor(term.getCursor(), nby - scrollY)
nbx = math.max(1, math.min(unicode.wlen(line()) + 1, nbx))
local wide, right = isWideAtPosition(line(), nbx)
local ncx = nbx - scrollX
if ncx > w or (ncx + 1 > w and wide and not right) then
term.setCursorBlink(false)
scrollX = nbx - w + ((wide and not right) and 1 or 0)
for lineNr = 1 + scrollY, math.min(h + scrollY, #buffer) do
drawLine(x, y, w, h, lineNr)
end
elseif ncx < 1 or (ncx - 1 < 1 and wide and right) then
term.setCursorBlink(false)
scrollX = nbx - 1 - ((wide and right) and 1 or 0)
for lineNr = 1 + scrollY, math.min(h + scrollY, #buffer) do
drawLine(x, y, w, h, lineNr)
end
end
term.setCursor(nbx - scrollX, nby - scrollY)
--update with term lib
nbx, nby = getCursor()
local locstring = string.format("%d,%d", nby, nbx)
if #cutBuffer > 0 then
locstring = string.format("(#%d) %s", #cutBuffer, locstring)
end
locstring = text.padLeft(locstring, 10)
gpu.set(x + w - #locstring, y + h, locstring)
end
local function highlight(bx, by, length, enabled)
local x, y, w, h = getArea()
local cx, cy = bx - scrollX, by - scrollY
cx = math.max(1, math.min(w, cx))
cy = math.max(1, math.min(h, cy))
length = math.max(1, math.min(w - cx, length))
local fg, fgp = gpu.getForeground()
local bg, bgp = gpu.getBackground()
if enabled then
gpu.setForeground(bg, bgp)
gpu.setBackground(fg, fgp)
end
local indexFrom = lengthToChars(buffer[by], bx)
local value = unicode.sub(buffer[by], indexFrom)
if unicode.wlen(value) > length then
value = unicode.wtrunc(value, length + 1)
end
gpu.set(x - 1 + cx, y - 1 + cy, value)
if enabled then
gpu.setForeground(fg, fgp)
gpu.setBackground(bg, bgp)
end
end
local function home()
local _, cby = getCursor()
setCursor(1, cby)
end
local function ende()
local _, cby = getCursor()
setCursor(unicode.wlen(line()) + 1, cby)
end
local function left()
local cbx, cby = getNormalizedCursor()
if cbx > 1 then
local wideTarget, rightTarget = isWideAtPosition(line(), cbx - 1)
if wideTarget and rightTarget then
setCursor(cbx - 2, cby)
else
setCursor(cbx - 1, cby)
end
return true -- for backspace
elseif cby > 1 then
setCursor(cbx, cby - 1)
ende()
return true -- again, for backspace
end
end
local function right(n)
n = n or 1
local cbx, cby = getNormalizedCursor()
local be = unicode.wlen(line()) + 1
local wide, isRight = isWideAtPosition(line(), cbx + n)
if wide and isRight then
n = n + 1
end
if cbx + n <= be then
setCursor(cbx + n, cby)
elseif cby < #buffer then
setCursor(1, cby + 1)
end
end
local function up(n)
n = n or 1
local cbx, cby = getCursor()
if cby > 1 then
setCursor(cbx, cby - n)
end
cutting = false
end
local function down(n)
n = n or 1
local cbx, cby = getCursor()
if cby < #buffer then
setCursor(cbx, cby + n)
end
cutting = false
end
local function delete(fullRow)
local _, cy = term.getCursor()
local cbx, cby = getCursor()
local x, y, w, h = getArea()
local function deleteRow(row)
local content = table.remove(buffer, row)
local rcy = cy + (row - cby)
if rcy <= h then
gpu.copy(x, y + rcy, w, h - rcy, 0, -1)
drawLine(x, y, w, h, row + (h - rcy))
end
return content
end
if fullRow then
term.setCursorBlink(false)
if #buffer > 1 then
deleteRow(cby)
else
buffer[cby] = ""
gpu.fill(x, y - 1 + cy, w, 1, " ")
end
setCursor(1, cby)
elseif cbx <= unicode.wlen(line()) then
term.setCursorBlink(false)
local index = lengthToChars(line(), cbx)
buffer[cby] = unicode.sub(line(), 1, index - 1) ..
unicode.sub(line(), index + 1)
drawLine(x, y, w, h, cby)
elseif cby < #buffer then
term.setCursorBlink(false)
local append = deleteRow(cby + 1)
buffer[cby] = buffer[cby] .. append
drawLine(x, y, w, h, cby)
else
return
end
setStatus(helpStatusText())
end
local function insert(value)
if not value or unicode.len(value) < 1 then
return
end
term.setCursorBlink(false)
local cbx, cby = getCursor()
local x, y, w, h = getArea()
local index = lengthToChars(line(), cbx)
buffer[cby] = unicode.sub(line(), 1, index - 1) ..
value ..
unicode.sub(line(), index)
drawLine(x, y, w, h, cby)
right(unicode.wlen(value))
setStatus(helpStatusText())
end
local function enter()
term.setCursorBlink(false)
local _, cy = term.getCursor()
local cbx, cby = getCursor()
local x, y, w, h = getArea()
local index = lengthToChars(line(), cbx)
table.insert(buffer, cby + 1, unicode.sub(buffer[cby], index))
buffer[cby] = unicode.sub(buffer[cby], 1, index - 1)
drawLine(x, y, w, h, cby)
if cy < h then
if cy < h - 1 then
gpu.copy(x, y + cy, w, h - (cy + 1), 0, 1)
end
drawLine(x, y, w, h, cby + 1)
end
setCursor(1, cby + 1)
setStatus(helpStatusText())
cutting = false
end
local findText = ""
local function find()
local _, _, _, h = getArea()
local cbx, cby = getCursor()
local ibx, iby = cbx, cby
while running do
if unicode.len(findText) > 0 then
local sx, sy
for syo = 1, #buffer do -- iterate lines with wraparound
sy = (iby + syo - 1 + #buffer - 1) % #buffer + 1
sx = string.find(buffer[sy], findText, syo == 1 and ibx or 1, true)
if sx and (sx >= ibx or syo > 1) then
break
end
end
if not sx then -- special case for single matches
sy = iby
sx = string.find(buffer[sy], findText, nil, true)
end
if sx then
sx = unicode.wlen(string.sub(buffer[sy], 1, sx - 1)) + 1
cbx, cby = sx, sy
setCursor(cbx, cby)
highlight(cbx, cby, unicode.wlen(findText), true)
end
end
term.setCursor(7 + unicode.wlen(findText), h + 1)
setStatus("Find: " .. findText)
local _, address, char, code = term.pull("key_down")
if address == term.keyboard() then
local handler, name = getKeyBindHandler(code)
highlight(cbx, cby, unicode.wlen(findText), false)
if name == "newline" then
break
elseif name == "close" then
handler()
elseif name == "backspace" then
findText = unicode.sub(findText, 1, -2)
elseif name == "find" or name == "findnext" then
ibx = cbx + 1
iby = cby
elseif not keyboard.isControl(char) then
findText = findText .. unicode.char(char)
end
end
end
setCursor(cbx, cby)
setStatus(helpStatusText())
end
local function cut()
if not cutting then
cutBuffer = {}
end
local cbx, cby = getCursor()
table.insert(cutBuffer, buffer[cby])
delete(true)
cutting = true
home()
end
local function uncut()
home()
for _, line in ipairs(cutBuffer) do
insert(line)
enter()
end
end
-------------------------------------------------------------------------------
local keyBindHandlers = {
left = left,
right = right,
up = up,
down = down,
home = home,
eol = ende,
pageUp = function()
local _, _, _, h = getArea()
up(h - 1)
end,
pageDown = function()
local _, _, _, h = getArea()
down(h - 1)
end,
backspace = function()
if not readonly and left() then
delete()
end
end,
delete = function()
if not readonly then
delete()
end
end,
deleteLine = function()
if not readonly then
delete(true)
end
end,
newline = function()
if not readonly then
enter()
end
end,
save = function()
if readonly then return end
local new = not fs.exists(filename)
local backup
if not new then
backup = filename .. "~"
for i = 1, math.huge do
if not fs.exists(backup) then
break
end
backup = filename .. "~" .. i
end
fs.copy(filename, backup)
end
if not fs.exists(file_parentpath) then
fs.makeDirectory(file_parentpath)
end
local f, reason = io.open(filename, "w")
if f then
local chars, firstLine = 0, true
for _, bline in ipairs(buffer) do
if not firstLine then
bline = "\n" .. bline
end
firstLine = false
f:write(bline)
chars = chars + unicode.len(bline)
end
f:close()
local format
if new then
format = [["%s" [New] %dL,%dC written]]
else
format = [["%s" %dL,%dC written]]
end
setStatus(string.format(format, fs.name(filename), #buffer, chars))
else
setStatus(reason)
end
if not new then
fs.remove(backup)
end
end,
close = function()
-- TODO ask to save if changed
running = false
end,
find = function()
findText = ""
find()
end,
findnext = find,
cut = cut,
uncut = uncut
}
getKeyBindHandler = function(code)
if type(config.keybinds) ~= "table" then return end
-- Look for matches, prefer more 'precise' keybinds, e.g. prefer
-- ctrl+del over del.
local result, resultName, resultWeight = nil, nil, 0
for command, keybinds in pairs(config.keybinds) do
if type(keybinds) == "table" and keyBindHandlers[command] then
for _, keybind in ipairs(keybinds) do
if type(keybind) == "table" then
local alt, control, shift, key = false, false, false
for _, value in ipairs(keybind) do
if value == "alt" then alt = true
elseif value == "control" then control = true
elseif value == "shift" then shift = true
else key = value end
end
local keyboardAddress = term.keyboard()
if (alt == not not keyboard.isAltDown(keyboardAddress)) and
(control == not not keyboard.isControlDown(keyboardAddress)) and
(shift == not not keyboard.isShiftDown(keyboardAddress)) and
code == keyboard.keys[key] and
#keybind > resultWeight
then
resultWeight = #keybind
resultName = command
result = keyBindHandlers[command]
end
end
end
end
end
return result, resultName
end
-------------------------------------------------------------------------------
local function onKeyDown(char, code)
local handler = getKeyBindHandler(code)
if handler then
handler()
elseif readonly and code == keyboard.keys.q then
running = false
elseif not readonly then
if not keyboard.isControl(char) then
insert(unicode.char(char))
elseif unicode.char(char) == "\t" then
insert(" ")
end
end
end
local function onClipboard(value)
value = value:gsub("\r\n", "\n")
local start = 1
local l = value:find("\n", 1, true)
if l then
repeat
local next_line = string.sub(value, start, l - 1)
next_line = text.detab(next_line, 2)
insert(next_line)
enter()
start = l + 1
l = value:find("\n", start, true)
until not l
end
insert(string.sub(value, start))
end
local function onClick(x, y)
setCursor(x + scrollX, y + scrollY)
end
local function onScroll(direction)
local cbx, cby = getCursor()
setCursor(cbx, cby - direction * 12)
end
-------------------------------------------------------------------------------
do
local f = io.open(filename)
if f then
local x, y, w, h = getArea()
local chars = 0
for fline in f:lines() do
table.insert(buffer, fline)
chars = chars + unicode.len(fline)
if #buffer <= h then
drawLine(x, y, w, h, #buffer)
end
end
f:close()
if #buffer == 0 then
table.insert(buffer, "")
end
local format
if readonly then
format = [["%s" [readonly] %dL,%dC]]
else
format = [["%s" %dL,%dC]]
end
setStatus(string.format(format, fs.name(filename), #buffer, chars))
else
table.insert(buffer, "")
setStatus(string.format([["%s" [New File] ]], fs.name(filename)))
end
setCursor(1, 1)
end
while running do
local event, address, arg1, arg2, arg3 = term.pull()
if address == term.keyboard() or address == term.screen() then
local blink = true
if event == "key_down" then
onKeyDown(arg1, arg2)
elseif event == "clipboard" and not readonly then
onClipboard(arg1)
elseif event == "touch" or event == "drag" then
local x, y, w, h = getArea()
arg1 = arg1 - x + 1
arg2 = arg2 - y + 1
if arg1 >= 1 and arg2 >= 1 and arg1 <= w and arg2 <= h then
onClick(arg1, arg2)
end
elseif event == "scroll" then
onScroll(arg3)
else
blink = false
end
if blink then
term.setCursorBlink(true)
end
end
end
term.clear()
term.setCursorBlink(true)

132
data/OpenOS/bin/find.lua Normal file
View File

@ -0,0 +1,132 @@
local shell = require("shell")
local fs = require("filesystem")
local text = require("text")
local USAGE =
[===[Usage: find [path] [--type=[dfs]] [--[i]name=EXPR]
--path if not specified, path is assumed to be current working directory
--type returns results of a given type, d:directory, f:file, and s:symlinks
--name specify the file name pattern. Use quote to include *. iname is
case insensitive
--help display this help and exit]===]
local args, options = shell.parse(...)
if (not args or not options) or options.help then
print(USAGE)
if not options.help then
return 1
else
return -- nil return, meaning no error
end
end
if #args > 1 then
io.stderr:write(USAGE..'\n')
return 1
end
local path = #args == 1 and args[1] or "."
local bDirs = true
local bFiles = true
local bSyms = true
local fileNamePattern = ""
local bCaseSensitive = true
if options.iname and options.name then
io.stderr:write("find cannot define both iname and name\n")
return 1
end
if options.type then
bDirs = false
bFiles = false
bSyms = false
if options.type == "f" then
bFiles = true
elseif options.type == "d" then
bDirs = true
elseif options.type == "s" then
bSyms = true
else
io.stderr:write(string.format("find: Unknown argument to type: %s\n", options.type))
io.stderr:write(USAGE..'\n')
return 1
end
end
if options.iname or options.name then
bCaseSensitive = options.iname ~= nil
fileNamePattern = options.iname or options.name
if type(fileNamePattern) ~= "string" then
io.stderr:write('find: missing argument to `name\'\n')
return 1
end
if not bCaseSensitive then
fileNamePattern = fileNamePattern:lower()
end
-- prefix any * with . for gnu find glob matching
fileNamePattern = text.escapeMagic(fileNamePattern)
fileNamePattern = fileNamePattern:gsub("%%%*", ".*")
end
local function isValidType(spath)
if not fs.exists(spath) then
return false
end
if fileNamePattern:len() > 0 then
local fileName = spath:gsub('.*/','')
if fileName:len() == 0 then
return false
end
local caseFileName = fileName
if not bCaseSensitive then
caseFileName = caseFileName:lower()
end
local s, e = caseFileName:find(fileNamePattern)
if not s or not e then
return false
end
if s ~= 1 or e ~= caseFileName:len() then
return false
end
end
if fs.isDirectory(spath) then
return bDirs
elseif fs.isLink(spath) then
return bSyms
else
return bFiles
end
end
local function visit(rpath)
local spath = shell.resolve(rpath)
if isValidType(spath) then
local result = rpath:gsub('/+$','')
print(result)
end
if fs.isDirectory(spath) then
local list_result = fs.list(spath)
for list_item in list_result do
visit(rpath:gsub('/+$', '') .. '/' .. list_item)
end
end
end
visit(path)

88
data/OpenOS/bin/flash.lua Normal file
View File

@ -0,0 +1,88 @@
local component = require("component")
local shell = require("shell")
local fs = require("filesystem")
local args, options = shell.parse(...)
if #args < 1 and not options.l then
io.write("Usage: flash [-qlr] [<bios.lua>] [label]\n")
io.write(" q: quiet mode, don't ask questions.\n")
io.write(" l: print current contents of installed EEPROM.\n")
io.write(" r: save the current contents of installed EEPROM to file.\n")
return
end
local function printRom()
local eeprom = component.eeprom
io.write(eeprom.get())
end
local function readRom()
local eeprom = component.eeprom
local fileName = shell.resolve(args[1])
if not options.q then
if fs.exists(fileName) then
io.write("Are you sure you want to overwrite " .. fileName .. "?\n")
io.write("Type `y` to confirm.\n")
repeat
local response = io.read()
until response and response:lower():sub(1, 1) == "y"
end
io.write("Reading EEPROM " .. eeprom.address .. ".\n" )
end
local bios = eeprom.get()
local file = assert(io.open(fileName, "wb"))
file:write(bios)
file:close()
if not options.q then
io.write("All done!\nThe label is '" .. eeprom.getLabel() .. "'.\n")
end
end
local function writeRom()
local file = assert(io.open(args[1], "rb"))
local bios = file:read("*a")
file:close()
if not options.q then
io.write("Insert the EEPROM you would like to flash.\n")
io.write("When ready to write, type `y` to confirm.\n")
repeat
local response = io.read()
until response and response:lower():sub(1, 1) == "y"
io.write("Beginning to flash EEPROM.\n")
end
local eeprom = component.eeprom
if not options.q then
io.write("Flashing EEPROM " .. eeprom.address .. ".\n")
io.write("Please do NOT power down or restart your computer during this operation!\n")
end
eeprom.set(bios)
local label = args[2]
if not options.q and not label then
io.write("Enter new label for this EEPROM. Leave input blank to leave the label unchanged.\n")
label = io.read()
end
if label and #label > 0 then
eeprom.setLabel(label)
if not options.q then
io.write("Set label to '" .. eeprom.getLabel() .. "'.\n")
end
end
if not options.q then
io.write("All done! You can remove the EEPROM and re-insert the previous one now.\n")
end
end
if options.l then
printRom()
elseif options.r then
readRom()
else
writeRom()
end

8
data/OpenOS/bin/free.lua Normal file
View File

@ -0,0 +1,8 @@
local computer = require("computer")
local total = computer.totalMemory()
local max = 0
for _=1,40 do
max = math.max(max, computer.freeMemory())
os.sleep(0) -- invokes gc
end
io.write(string.format("Total%12d\nUsed%13d\nFree%13d\n", total, total - max, max))

323
data/OpenOS/bin/grep.lua Normal file
View File

@ -0,0 +1,323 @@
--[[
An adaptation of Wobbo's grep
https://raw.githubusercontent.com/OpenPrograms/Wobbo-Programs/master/grep/grep.lua
]]--
-- POSIX grep for OpenComputers
-- one difference is that this version uses Lua regex, not POSIX regex.
local fs = require("filesystem")
local shell = require("shell")
local tty = require("tty")
local computer = require("computer")
-- Process the command line arguments
local args, options = shell.parse(...)
local gpu = tty.gpu()
local function printUsage(ostream, msg)
local s = ostream or io.stdout
if msg then
s:write(msg,'\n')
end
s:write([[Usage: grep [OPTION]... PATTERN [FILE]...
Example: grep -i "hello world" menu.lua main.lua
for more information, run: man grep
]])
end
local PATTERNS = {args[1]}
local FILES = {select(2, table.unpack(args))}
local LABEL_COLOR = 0xb000b0
local LINE_NUM_COLOR = 0x00FF00
local MATCH_COLOR = 0xFF0000
local COLON_COLOR = 0x00FFFF
local function pop(...)
local result
for _,key in ipairs({...}) do
result = options[key] or result
options[key] = nil
end
return result
end
-- Specify the variables for the options
local plain = pop('F','fixed-strings')
plain = not pop('e','--lua-regexp') and plain
local pattern_file = pop('file')
local match_whole_word = pop('w','word-regexp')
local match_whole_line = pop('x','line-regexp')
local ignore_case = pop('i','ignore-case')
local stdin_label = pop('label') or '(standard input)'
local stderr = pop('s','no-messages') and {write=function()end} or io.stderr
local invert_match = not not pop('v','invert-match')
-- no version output, just help
if pop('V','version','help') then
printUsage()
return 0
end
local max_matches = tonumber(pop('max-count')) or math.huge
local print_line_num = pop('n','line-number')
local search_recursively = pop('r','recursive')
-- Table with patterns to check for
if pattern_file then
local pattern_file_path = shell.resolve(pattern_file)
if not fs.exists(pattern_file_path) then
stderr:write('grep: ',pattern_file,': file not found')
return 2
end
table.insert(FILES, 1, PATTERNS[1])
PATTERNS = {}
for line in io.lines(pattern_file_path) do
PATTERNS[#PATTERNS+1] = line
end
end
if #PATTERNS == 0 then
printUsage(stderr)
return 2
end
if #FILES == 0 then
FILES = search_recursively and {'.'} or {'-'}
end
if not options.h and search_recursively then
options.H = true
end
if #FILES < 2 then
options.h = true
end
local f_only = pop('l','files-with-matches')
local no_only = pop('L','files-without-match') and not f_only
local include_filename = pop('H','with-filename')
include_filename = not pop('h','no-filename') or include_filename
local m_only = pop('o','only-matching')
local quiet = pop('q','quiet','silent')
local print_count = pop('c','count')
local colorize = pop('color','colour') and io.output().tty and tty.isAvailable()
local noop = function(...)return ...;end
local setc = colorize and gpu.setForeground or noop
local getc = colorize and gpu.getForeground or noop
local trim = pop('t','trim')
local trim_front = trim and function(s)return s:gsub('^%s+','')end or noop
local trim_back = trim and function(s)return s:gsub('%s+$','')end or noop
if next(options) then
if not quiet then
printUsage(stderr, 'unexpected option: '..next(options))
return 2
end
return 0
end
-- Resolve the location of a file, without searching the path
local function resolve(file)
if file:sub(1,1) == '/' then
return fs.canonical(file)
else
if file:sub(1,2) == './' then
file = file:sub(3, -1)
end
return fs.canonical(fs.concat(shell.getWorkingDirectory(), file))
end
end
--- Builds a case insensitive patterns, code from stackoverflow
--- (questions/11401890/case-insensitive-lua-pattern-matching)
if ignore_case then
for i=1,#PATTERNS do
-- find an optional '%' (group 1) followed by any character (group 2)
PATTERNS[i] = PATTERNS[i]:gsub("(%%?)(.)", function(percent, letter)
if percent ~= "" or not letter:match("%a") then
-- if the '%' matched, or `letter` is not a letter, return "as is"
return percent .. letter
else -- case-insensitive
return string.format("[%s%s]", letter:lower(), letter:upper())
end
end)
end
end
local function getAllFiles(dir, file_list)
for node in fs.list(shell.resolve(dir)) do
local rel_path = dir:gsub("/+$","") .. '/' .. node
local resolved_path = shell.resolve(rel_path)
if fs.isDirectory(resolved_path) then
getAllFiles(rel_path, file_list)
else
file_list[#file_list+1] = rel_path
end
end
end
if search_recursively then
local files = {}
for i,arg in ipairs(FILES) do
if fs.isDirectory(arg) then
getAllFiles(arg, files)
else
files[#files+1]=arg
end
end
FILES=files
end
-- Prepare an iterator for reading files
local function readLines()
local curHand = nil
local curFile = nil
local meta = nil
return function()
if not curFile then
local file = table.remove(FILES, 1)
if not file then
return
end
meta = {line_num=0,hits=0}
if file == "-" then
curFile = file
meta.label = stdin_label
curHand = io.input()
else
meta.label = file
local file, reason = resolve(file)
if fs.exists(file) then
curHand, reason = io.open(file, 'r')
if not curHand then
local msg = string.format("failed to read from %s: %s", meta.label, reason)
stderr:write("grep: ",msg,"\n")
return false, 2
else
curFile = meta.label
end
else
stderr:write("grep: ",file,": file not found\n")
return false, 2
end
end
end
meta.line = nil
if not meta.close and curHand then
meta.line_num = meta.line_num + 1
meta.line = curHand:read("*l")
end
if not meta.line then
curFile = nil
if curHand then
curHand:close()
end
return false, meta
else
return meta, curFile
end
end
end
local function write(part, color)
local prev_color = color and getc()
if color then setc(color) end
io.write(part)
if color then setc(prev_color) end
end
local flush=(f_only or no_only or print_count) and function(m)
if no_only and m.hits == 0 or f_only and m.hits ~= 0 then
write(m.label, LABEL_COLOR)
write('\n')
elseif print_count then
if include_filename then
write(m.label, LABEL_COLOR)
write(':', COLON_COLOR)
end
write(m.hits)
write('\n')
end
end
local ec = nil
local any_hit_ec = 1
local function test(m,p)
local empty_line = true
local last_index, slen = 1, #m.line
local needs_filename, needs_line_num = include_filename, print_line_num
local hit_value = 1
while last_index <= slen and not m.close do
local i, j = m.line:find(p, last_index, plain)
local word_fail, line_fail =
match_whole_word and not (i and not (m.line:sub(i-1,i-1)..m.line:sub(j+1,j+1)):find("[%a_]")),
match_whole_line and not (i==1 and j==slen)
local matched = not ((m_only or last_index==1) and not i)
if (hit_value == 1 and word_fail) or line_fail then
matched,i,j = false
end
if invert_match == matched then break end
if max_matches == 0 then os.exit(1) end
any_hit_ec = 0
m.hits, hit_value = m.hits + hit_value, 0
if f_only or no_only then
m.close = true
end
if flush or quiet then return end
if needs_filename then
write(m.label, LABEL_COLOR)
write(':', COLON_COLOR)
needs_filename = nil
end
if needs_line_num then
write(m.line_num, LINE_NUM_COLOR)
write(':', COLON_COLOR)
needs_line_num = nil
end
local s=m_only and '' or m.line:sub(last_index,(i or 0)-1)
local g=i and m.line:sub(i,j) or ''
if i==1 then g=trim_front(g) elseif last_index==1 then s=trim_front(s) end
if j==slen then g=trim_back(g) elseif not i then s=trim_back(s) end
write(s)
write(g, MATCH_COLOR)
empty_line = false
last_index = (j or slen)+1
if m_only or last_index>slen then
write("\n")
empty_line = true
needs_filename, needs_line_num = include_filename, print_line_num
elseif p:find("^^") and not plain then p="^$" end
end
if not empty_line then write("\n") end
if max_matches ~= math.huge and max_matches >= m.hits then
m.close = true
end
end
local uptime = computer.uptime
local last_sleep = uptime()
for meta,status in readLines() do
if uptime() - last_sleep > 1 then
os.sleep(0)
last_sleep = uptime()
end
if not meta then
if type(status) == 'table' then if flush then
flush(status) end -- this was the last object, closing out
elseif status then
ec = status or ec
end
else
for _,p in ipairs(PATTERNS) do
test(meta,p)
end
end
end
return ec or any_hit_ec

131
data/OpenOS/bin/head.lua Normal file
View File

@ -0,0 +1,131 @@
local shell = require("shell")
local args, options = shell.parse(...)
local error_code = 0
local function pop(key, convert)
local result = options[key]
options[key] = nil
if result and convert then
local c = tonumber(result)
if not c then
io.stderr:write(string.format("use --%s=n where n is a number\n", key))
options.help = true
error_code = 1
end
result = c
end
return result
end
local bytes = pop('bytes', true)
local lines = pop('lines', true)
local quiet = {pop('q'), pop('quiet'), pop('silent')}
quiet = quiet[1] or quiet[2] or quiet[3]
local verbose = {pop('v'), pop('verbose')}
verbose = verbose[1] or verbose[2]
local help = pop('help')
if help or next(options) then
local invalid_key = next(options)
if invalid_key then
invalid_key = string.format('invalid option: %s\n', invalid_key)
error_code = 1
else
invalid_key = ''
end
print(invalid_key .. [[Usage: head [--lines=n] file
Print the first 10 lines of each FILE to stdout.
For more info run: man head]])
os.exit(error_code)
end
if #args == 0 then
args = {'-'}
end
if quiet and verbose then
quiet = false
end
local function new_stream()
return
{
open=true,
capacity=math.abs(lines or bytes or 10),
bytes=bytes,
buffer=(lines and lines < 0 and {}) or (bytes and bytes < 0 and '')
}
end
local function close(stream)
if stream.buffer then
if type(stream.buffer) == 'table' then
stream.buffer = table.concat(stream.buffer)
end
io.stdout:write(stream.buffer)
stream.buffer = nil
end
stream.open = false
end
local function push(stream, line)
if not line then
return close(stream)
end
local cost = stream.bytes and line:len() or 1
stream.capacity = stream.capacity - cost
if not stream.buffer then
if stream.bytes and stream.capacity < 0 then
line = line:sub(1,stream.capacity-1)
end
io.write(line)
if stream.capacity <= 0 then
return close(stream)
end
else
if type(stream.buffer) == 'table' then -- line storage
stream.buffer[#stream.buffer+1] = line
if stream.capacity < 0 then
table.remove(stream.buffer, 1)
stream.capacity = 0 -- zero out
end
else -- byte storage
stream.buffer = stream.buffer .. line
if stream.capacity < 0 then
stream.buffer = stream.buffer:sub(-stream.capacity+1)
stream.capacity = 0 -- zero out
end
end
end
end
for i=1,#args do
local arg = args[i]
local file, reason
if arg == '-' then
arg = 'standard input'
file = io.stdin
else
file, reason = io.open(arg, 'r')
if not file then
io.stderr:write(string.format([[head: cannot open '%s' for reading: %s]], arg, reason))
end
end
if file then
if verbose or #args > 1 then
io.write(string.format('==> %s <==\n', arg))
end
local stream = new_stream()
while stream.open do
push(stream, file:read('*L'))
end
file:close()
end
end

View File

@ -0,0 +1,30 @@
local shell = require("shell")
local args, ops = shell.parse(...)
local hostname = args[1]
if hostname then
local file, reason = io.open("/etc/hostname", "w")
if not file then
io.stderr:write("failed to open for writing: ", reason, "\n")
return 1
end
file:write(hostname)
file:close()
ops.update = true
else
local file = io.open("/etc/hostname")
if file then
hostname = file:read("*l")
file:close()
end
end
if ops.update then
os.setenv("HOSTNAME_SEPARATOR", hostname and #hostname > 0 and ":" or "")
os.setenv("HOSTNAME", hostname)
elseif hostname then
print(hostname)
else
io.stderr:write("Hostname not set\n")
return 1
end

View File

@ -0,0 +1,53 @@
local computer = require("computer")
local options
do
local basic, reason = loadfile("/lib/core/install_basics.lua", "bt", _G)
if not basic then
io.stderr:write("failed to load install: " .. tostring(reason) .. "\n")
return 1
end
options = basic(...)
end
if not options then
return
end
if computer.freeMemory() < 50000 then
print("Low memory, collecting garbage")
for i = 1, 20 do
os.sleep(0)
end
end
local transfer = require("tools/transfer")
for _, inst in ipairs(options.cp_args) do
local ec = transfer.batch(table.unpack(inst))
if ec ~= nil and ec ~= 0 then
return ec
end
end
print("Installation complete!")
if options.setlabel then
pcall(options.target.dev.setLabel, options.label)
end
if options.setboot then
local address = options.target.dev.address
if computer.setBootAddress(address) then
print("Boot address set to " .. address)
end
end
if options.reboot then
io.write("Reboot now? [Y/n] ")
if ((io.read() or "n") .. "y"):match("^%s*[Yy]") then
print("\nRebooting now!\n")
computer.shutdown(true)
end
end
print("Returning to shell.\n")

49
data/OpenOS/bin/label.lua Normal file
View File

@ -0,0 +1,49 @@
local shell = require("shell")
local devfs = require("devfs")
local comp = require("component")
local args, options = shell.parse(...)
if #args < 1 then
io.write("Usage: label [-a] <device> [<label>]\n")
io.write(" -a Device is specified via label or address instead of by path.\n")
return 1
end
local filter = args[1]
local label = args[2]
local proxy, reason
if options.a then
for addr in comp.list() do
if addr:sub(1, filter:len()) == filter then
proxy, reason = comp.proxy(addr)
break
end
local tmp_proxy = comp.proxy(addr)
local tmp_label = devfs.getDeviceLabel(tmp_proxy)
if tmp_label == filter then
proxy = tmp_proxy
break
end
end
else
proxy, reason = devfs.getDevice(filter)
end
if not proxy then
io.stderr:write(reason..'\n')
return 1
end
if #args < 2 then
local label = devfs.getDeviceLabel(proxy)
if label then
print(label)
else
io.stderr:write("no label\n")
return 1
end
else
devfs.setDeviceLabel(proxy, label)
end

149
data/OpenOS/bin/less.lua Normal file
View File

@ -0,0 +1,149 @@
local keys = require("keyboard").keys
local shell = require("shell")
local unicode = require("unicode")
local term = require("term") -- using term for negative scroll feature
local args, ops = shell.parse(...)
if #args > 1 then
io.write("Usage: ", os.getenv("_"):match("/([^/]+)%.lua$"), " <filename>\n")
io.write("- or no args reads stdin\n")
return 1
end
local cat_cmd = table.concat({"cat", ...}, " ")
if not io.output().tty then
return os.execute(cat_cmd)
end
local preader = io.popen(cat_cmd)
local scrollback = not ops.noback and {}
local bottom = 0
local end_of_buffer = false
local width, height = term.getViewport()
local function split(full_line)
local index = 1
local parts = {}
while true do
local sub = full_line:sub(index, index + width*3)
-- checking #sub < width first is faster, save a unicode call
if #sub < width or unicode.wlen(sub) <= width then
parts[#parts + 1] = sub
break
end
parts[#parts + 1] = unicode.wtrunc(sub, width + 1)
index = index + #parts[#parts]
if index > #full_line then
break
end
end
return parts
end
local function scan(num)
local result = {}
local line_count = 0
for i=1, num do
local lines = {}
if scrollback and (bottom + i) <= #scrollback then
lines = {scrollback[bottom + i]}
else
local full_line = preader:read()
if not full_line then preader:close() break end
-- with buffering, we can buffer ahead too, and read more smoothly
local buffering = false
for _,line in ipairs(split(full_line)) do
if not buffering then
lines[#lines + 1] = line
end
if scrollback then
buffering = true
scrollback[#scrollback + 1] = line
end
end
end
for _,line in ipairs(lines) do
result[#result + 1] = line
line_count = line_count + 1
if #result > height then
table.remove(result, 1)
end
end
if line_count >= num then
break
end
end
return result, line_count
end
local function status()
if end_of_buffer then
if ops.noback then
os.exit()
end
io.write("(END)")
end
io.write(":")
end
local function goback(n)
if not scrollback then return end
local current_top = bottom - height + 1
n = math.min(current_top, n)
if n < 1 then return end
local top = current_top - n + 1
term.scroll(-n)
term.setCursor(1, 1)
for i=1, n do
if i >= height then
break
end
print(scrollback[top + i - 1])
end
term.setCursor(1, height)
bottom = bottom - n
end_of_buffer = false
end
local function goforward(n)
term.clearLine()
local update, line_count = scan(n)
for _,line in ipairs(update) do
print(line)
end
if line_count < n then
end_of_buffer = true
end
bottom = bottom + line_count
end
goforward(height - 1)
while true do
term.clearLine()
status()
local e, _, _, code = term.pull()
if e == "interrupted" then
break
elseif e == "key_down" then
if code == keys.q then
term.clearLine()
os.exit() -- abort
elseif code == keys["end"] then
goforward(math.huge)
elseif code == keys.space or code == keys.pageDown then
goforward(height - 1)
elseif code == keys.enter or code == keys.down then
goforward(1)
elseif code == keys.up then
goback(1)
elseif code == keys.pageUp then
goback(height - 1)
elseif code == keys.home then
goback(math.huge)
end
end
end

33
data/OpenOS/bin/list.lua Normal file
View File

@ -0,0 +1,33 @@
local fs = require("filesystem")
local shell = require("shell")
local args, ops = shell.parse(...)
if #args == 0 then
table.insert(args, ".")
end
local arg = args[1]
local path = shell.resolve(arg)
if ops.help then
io.write([[Usage: list [path]
path:
optional argument (defaults to ./)
Displays a list of files in the given path with no added formatting
Intended for low memory systems
]])
return 0
end
local real, why = fs.realPath(path)
if real and not fs.exists(real) then
why = "no such file or directory"
end
if why then
io.stderr:write(string.format("cannot access '%s': %s", arg, tostring(why)))
return 1
end
for item in fs.list(real) do
io.write(item, '\n')
end

34
data/OpenOS/bin/ln.lua Normal file
View File

@ -0,0 +1,34 @@
local fs = require("filesystem")
local shell = require("shell")
local args = shell.parse(...)
if #args == 0 then
io.write("Usage: ln <target> [<name>]\n")
return 1
end
local target_name = args[1]
local target = shell.resolve(target_name)
-- don't link from target if it doesn't exist, unless it is a broken link
if not fs.exists(target) and not fs.isLink(target) then
io.stderr:write("ln: failed to access '" .. target_name .. "': No such file or directory\n")
return 1
end
local linkpath
if #args > 1 then
linkpath = shell.resolve(args[2])
else
linkpath = fs.concat(shell.getWorkingDirectory(), fs.name(target))
end
if fs.isDirectory(linkpath) then
linkpath = fs.concat(linkpath, fs.name(target))
end
local result, reason = fs.link(target_name, linkpath)
if not result then
io.stderr:write(reason..'\n')
return 1
end

18
data/OpenOS/bin/ls.lua Normal file
View File

@ -0,0 +1,18 @@
-- load complex, if we can (might be low on memory)
local ok, why = pcall(function(...)
return loadfile("/lib/core/full_ls.lua", "bt", _G)(...)
end, ...)
if not ok then
if type(why) == "table" then
if why.code == 0 then
return
end
why = why.reason
end
io.stderr:write(tostring(why) .. "\nFor low memory systems, try using `list` instead\n")
return 1
end
return why

42
data/OpenOS/bin/lshw.lua Normal file
View File

@ -0,0 +1,42 @@
local computer = require("computer")
local shell = require("shell")
local text = require("text")
local args, options = shell.parse(...)
local devices = computer.getDeviceInfo()
local columns = {}
if not next(options, nil) then
options.t = true
options.d = true
options.p = true
end
if options.t then table.insert(columns, "Class") end
if options.d then table.insert(columns, "Description") end
if options.p then table.insert(columns, "Product") end
if options.v then table.insert(columns, "Vendor") end
if options.c then table.insert(columns, "Capacity") end
if options.w then table.insert(columns, "Width") end
if options.s then table.insert(columns, "Clock") end
local m = {}
for address, info in pairs(devices) do
for col, name in ipairs(columns) do
m[col] = math.max(m[col] or 1, (info[name:lower()] or ""):len())
end
end
io.write(text.padRight("Address", 10))
for col, name in ipairs(columns) do
io.write(text.padRight(name, m[col] + 2))
end
io.write("\n")
for address, info in pairs(devices) do
io.write(text.padRight(address:sub(1, 5).."...", 10))
for col, name in ipairs(columns) do
io.write(text.padRight(info[name:lower()] or "", m[col] + 2))
end
io.write("\n")
end

27
data/OpenOS/bin/lua.lua Normal file
View File

@ -0,0 +1,27 @@
local shell = require("shell")
local args = shell.parse(...)
if #args == 0 then
args = {"/lib/core/lua_shell.lua"}
end
local filename = args[1]
local buffer, script, reason
buffer = io.lines(filename, "*a")()
if buffer then
buffer = buffer:gsub("^#![^\n]+", "") -- remove shebang if any
script, reason = load(buffer, "="..filename)
else
reason = string.format("could not open %s for reading", filename)
end
if not script then
io.stderr:write(tostring(reason) .. "\n")
os.exit(false)
end
buffer, reason = pcall(script, table.unpack(args, 2))
if not buffer then
io.stderr:write(type(reason) == "table" and reason.reason or tostring(reason), "\n")
os.exit(false)
end

20
data/OpenOS/bin/man.lua Normal file
View File

@ -0,0 +1,20 @@
local fs = require("filesystem")
local shell = require("shell")
local args = shell.parse(...)
if #args == 0 then
io.write("Usage: man <topic>\n")
io.write("Where `topic` will usually be the name of a program or library.\n")
return 1
end
local topic = args[1]
for path in string.gmatch(os.getenv("MANPATH"), "[^:]+") do
path = shell.resolve(fs.concat(path, topic), "man")
if path and fs.exists(path) and not fs.isDirectory(path) then
os.execute(os.getenv("PAGER") .. " " .. path)
os.exit()
end
end
io.stderr:write("No manual entry for " .. topic .. '\n')
return 1

27
data/OpenOS/bin/mkdir.lua Normal file
View File

@ -0,0 +1,27 @@
local fs = require("filesystem")
local shell = require("shell")
local args = shell.parse(...)
if #args == 0 then
io.write("Usage: mkdir <dirname1> [<dirname2> [...]]\n")
return 1
end
local ec = 0
for i = 1, #args do
local path = shell.resolve(args[i])
local result, reason = fs.makeDirectory(path)
if not result then
if not reason then
if fs.exists(path) then
reason = "file or folder with that name already exists"
else
reason = "unknown reason"
end
end
io.stderr:write("mkdir: cannot create directory '" .. tostring(args[i]) .. "': " .. reason .. "\n")
ec = 1
end
end
return ec

70
data/OpenOS/bin/mktmp.lua Normal file
View File

@ -0,0 +1,70 @@
local fs = require("filesystem")
local shell = require("shell")
local sh = require("sh")
local touch = loadfile(shell.resolve("touch", "lua"))
local mkdir = loadfile(shell.resolve("mkdir", "lua"))
if not touch then
local errorMessage = "missing tools for mktmp"
io.stderr:write(errorMessage .. '\n')
return false, errorMessage
end
local args, ops = shell.parse(...)
local function pop(...)
local result
for _,key in ipairs({...}) do
result = ops[key] or result
ops[key] = nil
end
return result
end
local directory = pop('d')
local verbose = pop('v', 'verbose')
local quiet = pop('q', 'quiet')
if pop('help') or #args > 1 or next(ops) then
print([[Usage: mktmp [OPTION] [PATH]
Create a new file with a random name in $TMPDIR or PATH argument if given
-d create a directory instead of a file
-v, --verbose print result to stdout, even if no tty
-q, --quiet do not print results to stdout, even if tty (verbose overrides)
--help print this help message]])
if next(ops) then
io.stderr:write("invalid option: " .. (next(ops)) .. '\n')
return 1
end
return
end
if not verbose then
if not quiet then
if io.stdout.tty then
verbose = true
end
end
end
local prefix = args[1] or os.getenv("TMPDIR") .. '/'
if not fs.exists(prefix) then
io.stderr:write(
string.format(
"cannot create tmp file or directory at %s, it does not exist\n",
prefix))
return 1
end
local tmp = os.tmpname()
local ok, reason = (directory and mkdir or touch)(tmp)
if sh.internal.command_passed(ok) then
if verbose then
print(tmp)
end
return tmp
end
return ok, reason

96
data/OpenOS/bin/mount.lua Normal file
View File

@ -0,0 +1,96 @@
local fs = require("filesystem")
local shell = require("shell")
local function usage()
io.stderr:write([==[
Usage: mount [OPTIONS] [device] [path]")
If no args are given, all current mount points are printed.
<Options> Note that multiple options can be used together
-r, --ro Mount the filesystem read only
--bind Create a mount bind point, folder to folder
<Args>
device Specify filesystem device by one of:
a. label
b. address (can be abbreviated)
c. folder path (requires --bind)
path Target folder path to mount to
See `man mount` for more details
]==])
os.exit(1)
end
-- smart parse, follow arg after -o
local args, opts = shell.parse(...)
opts.readonly = opts.r or opts.readonly
if opts.h or opts.help then
usage()
end
local function print_mounts()
-- for each mount
local mounts = {}
for proxy,path in fs.mounts() do
local device = {}
device.dev_path = proxy.address
device.mount_path = path
device.rw_ro = proxy.isReadOnly() and "ro" or "rw"
device.fs_label = proxy.getLabel() or proxy.address
mounts[device.dev_path] = mounts[device.dev_path] or {}
local dev_mounts = mounts[device.dev_path]
table.insert(dev_mounts, device)
end
local smounts = {}
for key,value in pairs(mounts) do
smounts[#smounts+1] = {key, value}
end
table.sort(smounts, function(a,b) return a[1] < b[1] end)
for _, dev in ipairs(smounts) do
local dev_path, dev_mounts = table.unpack(dev)
for _,device in ipairs(dev_mounts) do
local rw_ro = "(" .. device.rw_ro .. ")"
local fs_label = "\"" .. device.fs_label .. "\""
io.write(string.format("%-8s on %-10s %s %s\n",
dev_path:sub(1,8),
device.mount_path,
rw_ro,
fs_label))
end
end
end
local function do_mount()
-- bind converts a path to a proxy
local proxy, reason = fs.proxy(args[1], opts)
if not proxy then
io.stderr:write("Failed to mount: ", tostring(reason), "\n")
os.exit(1)
end
local result, mount_failure = fs.mount(proxy, shell.resolve(args[2]))
if not result then
io.stderr:write(mount_failure, "\n")
os.exit(2) -- error code
end
end
if #args == 0 then
if next(opts) then
io.stderr:write("Missing argument\n")
usage()
else
print_mounts()
end
elseif #args == 2 then
do_mount()
else
io.stderr:write("wrong number of arguments: ", #args, "\n")
usage()
end

33
data/OpenOS/bin/mv.lua Normal file
View File

@ -0,0 +1,33 @@
local shell = require("shell")
local transfer = require("tools/transfer")
local args, options = shell.parse(...)
options.h = options.h or options.help
if #args < 2 or options.h then
io.write([[Usage: mv [OPTIONS] <from> <to>
-f overwrite without prompt
-i prompt before overwriting
unless -f
-v verbose
-n do not overwrite an existing file
--skip=P ignore paths matching lua regex P
-h, --help show this help
]])
return not not options.h
end
-- clean options for move (as opposed to copy)
options =
{
cmd = "mv",
f = options.f,
i = options.i,
v = options.v,
n = options.n, -- no clobber
skip = {options.skip},
P = true, -- move operations always preserve
r = true, -- move is allowed to move entire dirs
x = true, -- cannot move mount points
}
return transfer.batch(args, options)

View File

@ -0,0 +1,153 @@
--[[ This program allows downloading and uploading from and to pastebin.com.
Authors: Sangar, Vexatos ]]
local component = require("component")
local fs = require("filesystem")
local internet = require("internet")
local shell = require("shell")
if not component.isAvailable("internet") then
io.stderr:write("This program requires an internet card to run.")
return
end
local args, options = shell.parse(...)
-- This gets code from the website and stores it in the specified file.
local function get(pasteId, filename)
local f, reason = io.open(filename, "w")
if not f then
io.stderr:write("Failed opening file for writing: " .. reason)
return
end
io.write("Downloading from pastebin.com... ")
local url = "https://pastebin.com/raw/" .. pasteId
local result, response = pcall(internet.request, url)
if result then
io.write("success.\n")
for chunk in response do
if not options.k then
string.gsub(chunk, "\r\n", "\n")
end
f:write(chunk)
end
f:close()
io.write("Saved data to " .. filename .. "\n")
else
io.write("failed.\n")
f:close()
fs.remove(filename)
io.stderr:write("HTTP request failed: " .. response .. "\n")
end
end
-- This makes a string safe for being used in a URL.
local function encode(code)
if code then
code = string.gsub(code, "([^%w ])", function (c)
return string.format("%%%02X", string.byte(c))
end)
code = string.gsub(code, " ", "+")
end
return code
end
-- This stores the program in a temporary file, which it will
-- delete after the program was executed.
local function run(pasteId, ...)
local tmpFile = os.tmpname()
get(pasteId, tmpFile)
io.write("Running...\n")
local success, reason = shell.execute(tmpFile, nil, ...)
if not success then
io.stderr:write(reason)
end
fs.remove(tmpFile)
end
-- Uploads the specified file as a new paste to pastebin.com.
local function put(path)
local config = {}
local configFile = loadfile("/etc/pastebin.conf", "t", config)
if configFile then
local result, reason = pcall(configFile)
if not result then
io.stderr:write("Failed loading config: " .. reason)
end
end
config.key = config.key or "fd92bd40a84c127eeb6804b146793c97"
local file, reason = io.open(path, "r")
if not file then
io.stderr:write("Failed opening file for reading: " .. reason)
return
end
local data = file:read("*a")
file:close()
io.write("Uploading to pastebin.com... ")
local result, response = pcall(internet.request,
"https://pastebin.com/api/api_post.php",
"api_option=paste&" ..
"api_dev_key=" .. config.key .. "&" ..
"api_paste_format=lua&" ..
"api_paste_expire_date=N&" ..
"api_paste_name=" .. encode(fs.name(path)) .. "&" ..
"api_paste_code=" .. encode(data))
if result then
local info = ""
for chunk in response do
info = info .. chunk
end
if string.match(info, "^Bad API request, ") then
io.write("failed.\n")
io.write(info)
else
io.write("success.\n")
local pasteId = string.match(info, "[^/]+$")
io.write("Uploaded as " .. info .. "\n")
io.write('Run "pastebin get ' .. pasteId .. '" to download anywhere.')
end
else
io.write("failed.\n")
io.stderr:write(response)
end
end
local command = args[1]
if command == "put" then
if #args == 2 then
put(shell.resolve(args[2]))
return
end
elseif command == "get" then
if #args == 3 then
local path = shell.resolve(args[3])
if fs.exists(path) then
if not options.f or not os.remove(path) then
io.stderr:write("file already exists")
return
end
end
get(args[2], path)
return
end
elseif command == "run" then
if #args >= 2 then
run(args[2], table.unpack(args, 3))
return
end
end
-- If we come here there was some invalid input.
io.write("Usages:\n")
io.write("pastebin put [-f] <file>\n")
io.write("pastebin get [-f] <id> <file>\n")
io.write("pastebin run [-f] <id> [<arguments...>]\n")
io.write(" -f: Force overwriting existing files.\n")
io.write(" -k: keep line endings as-is (will convert\n")
io.write(" Windows line endings to Unix otherwise).")

View File

@ -0,0 +1,28 @@
local component = require("component")
local shell = require("shell")
local args = shell.parse(...)
if #args == 0 then
io.write("Usage: primary <type> [<address>]\n")
io.write("Note that the address may be abbreviated.\n")
return 1
end
local componentType = args[1]
if #args > 1 then
local address = args[2]
if not component.get(address) then
io.stderr:write("no component with this address\n")
return 1
else
component.setPrimary(componentType, address)
os.sleep(0.1) -- allow signals to be processed
end
end
if component.isAvailable(componentType) then
io.write(component.getPrimary(componentType).address, "\n")
else
io.stderr:write("no primary component for this type\n")
return 1
end

149
data/OpenOS/bin/ps.lua Normal file
View File

@ -0,0 +1,149 @@
local process = require("process")
local unicode = require("unicode")
local event = require("event")
local event_mt = getmetatable(event.handlers)
-- WARNING this code does not use official kernel API and is likely to change
local data = {}
local widths = {}
local sorted = {}
local moved_indexes = {}
local elbow = unicode.char(0x2514)
local function thread_id(t,p)
if t then
return tostring(t):gsub("^thread: 0x", "")
end
-- find the parent thread
for k,v in pairs(process.list) do
if v == p then
return thread_id(k)
end
end
return "-"
end
local cols =
{
{"PID", thread_id},
{"EVENTS", function(_,p)
local handlers = {}
if event_mt.threaded then
handlers = rawget(p.data, "handlers") or {}
elseif not p.parent then
handlers = event.handlers
end
local count = 0
for _ in pairs(handlers) do
count = count + 1
end
return count == 0 and "-" or tostring(count)
end},
{"THREADS", function(_,p)
-- threads are handles with mt.close == thread.waitForAll
local count = 0
for _,h in ipairs(p.data.handles) do
local mt = getmetatable(h)
if mt and mt.__status then
count = count + 1
end
end
return count == 0 and "-" or tostring(count)
end},
{"PARENT", function(_,p)
for _,process_info in pairs(process.list) do
for i,handle in ipairs(process_info.data.handles) do
local mt = getmetatable(handle)
if mt and mt.__status then
if mt.process == p then
return thread_id(nil, process_info)
end
end
end
end
return thread_id(nil, p.parent)
end},
{"HANDLES", function(_, p)
local count = #p.data.handles
return count == 0 and "-" or tostring(count)
end},
{"CMD", function(_,p) return p.command end},
}
local function add_field(key, value)
if not data[key] then data[key] = {} end
table.insert(data[key], value)
widths[key] = math.max(widths[key] or 0, #value)
end
for _,key in ipairs(cols) do
add_field(key[1], key[1])
end
for thread_handle, process_info in pairs(process.list) do
for _,key in ipairs(cols) do
add_field(key[1], key[2](thread_handle, process_info))
end
end
local parent_index
for index,set in ipairs(cols) do
if set[1] == "PARENT" then
parent_index = index
break
end
end
assert(parent_index, "did not find a parent column")
local function move_to_sorted(index)
if moved_indexes[index] then
return false
end
local entry = {}
for k,v in pairs(data) do
entry[k] = v[index]
end
sorted[#sorted + 1] = entry
moved_indexes[index] = true
return true
end
local function make_elbow(depth)
return (" "):rep(depth - 1) .. (depth > 0 and elbow or "")
end
-- remove COLUMN labels to simplify sort
move_to_sorted(1)
local function update_family(parent, depth)
depth = depth or 0
parent = parent or "-"
for index in ipairs(data.PID) do
local this_parent = data[cols[parent_index][1]][index]
if this_parent == parent then
local dash_cmd = make_elbow(depth) .. data.CMD[index]
data.CMD[index] = dash_cmd
widths.CMD = math.max(widths.CMD or 0, #dash_cmd)
if move_to_sorted(index) then
update_family(data.PID[index], depth + 1)
end
end
end
end
update_family()
table.remove(cols, parent_index) -- don't show parent id
for _,set in ipairs(sorted) do
local split = ""
for _,key in ipairs(cols) do
local label = key[1]
local format = split .. "%-" .. tostring(widths[label]) .. "s"
io.write(string.format(format, set[label]))
split = " "
end
print()
end

14
data/OpenOS/bin/pwd.lua Normal file
View File

@ -0,0 +1,14 @@
local shell = require("shell")
local fs = require("filesystem")
local _,op = shell.parse(...)
local path, why = shell.getWorkingDirectory(), ""
if op.P then
path, why = fs.realPath(path)
end
if not path then
io.stderr:write(string.format("error retrieving current directory: %s", why))
os.exit(1)
end
io.write(path, "\n")

150
data/OpenOS/bin/rc.lua Normal file
View File

@ -0,0 +1,150 @@
local rc = require("rc")
local fs = require("filesystem")
local function loadConfig()
local env = {}
local result, reason = loadfile('/etc/rc.cfg', 't', env)
if result then
result, reason = xpcall(result, debug.traceback)
if result then
return env
end
end
return nil, reason
end
local function saveConfig(conf)
local file, reason = io.open('/etc/rc.cfg', 'w')
if not file then
return nil, reason
end
for key, value in pairs(conf) do
file:write(tostring(key) .. " = " .. require("serialization").serialize(value) .. "\n")
end
file:close()
return true
end
local function load(name, args)
if rc.loaded[name] then
return rc.loaded[name]
end
local fileName = fs.concat('/etc/rc.d/', name .. '.lua')
local env = setmetatable({args = args}, {__index = _G})
local result, reason = loadfile(fileName, 't', env)
if result then
result, reason = xpcall(result, debug.traceback)
if result then
rc.loaded[name] = env
return env
else
return nil, string.format("%s failed to start: %s", fileName, reason)
end
end
return nil, string.format("%s failed to load: %s", fileName, reason)
end
function rc.unload(name)
rc.loaded[name] = nil
end
local function rawRunCommand(conf, name, cmd, args, ...)
local result, what = load(name, args)
if result then
if not cmd then
io.output():write("Commands for service " .. name .. "\n")
for command, val in pairs(result) do
if type(val) == "function" then
io.output():write(tostring(command) .. " ")
end
end
return true
elseif type(result[cmd]) == "function" then
result, what = xpcall(result[cmd], debug.traceback, ...)
if result then
return true
end
elseif cmd == "restart" and type(result["stop"]) == "function" and type(result["start"]) == "function" then
local daemon = result
result, what = xpcall(daemon["stop"], debug.traceback, ...)
if result then
result, what = xpcall(daemon["start"], debug.traceback, ...)
if result then
return true
end
end
elseif cmd == "enable" then
conf.enabled = conf.enabled or {}
for _, _name in ipairs(conf.enabled) do
if name == _name then
return nil, "Service already enabled"
end
end
conf.enabled[#conf.enabled + 1] = name
return saveConfig(conf)
elseif cmd == "disable" then
conf.enabled = conf.enabled or {}
for n, _name in ipairs(conf.enabled) do
if name == _name then
table.remove(conf.enabled, n)
end
end
return saveConfig(conf)
else
what = "Command '" .. cmd .. "' not found in daemon '" .. name .. "'"
end
end
return nil, what
end
local function runCommand(name, cmd, ...)
local conf, reason = loadConfig()
if not conf then
return nil, reason
end
return rawRunCommand(conf, name, cmd, conf[name], ...)
end
local function allRunCommand(cmd, ...)
local conf, reason = loadConfig()
if not conf then
return nil, reason
end
local results = {}
for _, name in ipairs(conf.enabled or {}) do
results[name] = table.pack(rawRunCommand(conf, name, cmd, conf[name], ...))
end
return results
end
local stream = io.stderr
local write = stream.write
if select("#", ...) == 0 then
-- if called during boot, pipe errors to onError handler
if _G.runlevel == "S" then
write = function(_, msg)
require("event").onError(msg)
end
end
local results, reason = allRunCommand("start")
if not results then
local msg = "rc failed to start:"..tostring(reason)
write(stream, msg, "\n")
return
end
for _, result in pairs(results) do
local ok, reason = table.unpack(result)
if not ok then
write(stream, reason, "\n")
end
end
else
local result, reason = runCommand(...)
if not result then
write(stream, reason, "\n")
return 1
end
end

View File

@ -0,0 +1,4 @@
local computer = require("computer")
io.write("Rebooting...")
computer.shutdown(true)

View File

@ -0,0 +1,103 @@
local colors = require("colors")
local component = require("component")
local shell = require("shell")
local sides = require("sides")
if not component.isAvailable("redstone") then
io.stderr:write("This program requires a redstone card or redstone I/O block.\n")
return 1
end
local rs = component.redstone
local args, options = shell.parse(...)
if #args == 0 and not options.w and not options.f then
io.write("Usage:\n")
io.write(" redstone <side> [<value>]\n")
if rs.setBundledOutput then
io.write(" redstone -b <side> <color> [<value>]\n")
end
if rs.setWirelessOutput then
io.write(" redstone -w [<value>]\n")
io.write(" redstone -f [<frequency>]\n")
end
return
end
if options.w then
if not rs.setWirelessOutput then
io.stderr:write("wireless redstone not available\n")
return 1
end
if #args > 0 then
local value = args[1]
if tonumber(value) then
value = tonumber(value) > 0
else
value = ({["true"]=true,["on"]=true,["yes"]=true})[value] ~= nil
end
rs.setWirelessOutput(value)
end
io.write("in: " .. tostring(rs.getWirelessInput()) .. "\n")
io.write("out: " .. tostring(rs.getWirelessOutput()) .. "\n")
elseif options.f then
if not rs.setWirelessOutput then
io.stderr:write("wireless redstone not available\n")
return 1
end
if #args > 0 then
local value = args[1]
if not tonumber(value) then
io.stderr:write("invalid frequency\n")
return 1
end
rs.setWirelessFrequency(tonumber(value))
end
io.write("freq: " .. tostring(rs.getWirelessFrequency()) .. "\n")
else
local side = sides[args[1]]
if not side then
io.stderr:write("invalid side\n")
return 1
end
if type(side) == "string" then
side = sides[side]
end
if options.b then
if not rs.setBundledOutput then
io.stderr:write("bundled redstone not available\n")
return 1
end
local color = colors[args[2]]
if not color then
io.stderr:write("invalid color\n")
return 1
end
if type(color) == "string" then
color = colors[color]
end
if #args > 2 then
local value = args[3]
if tonumber(value) then
value = tonumber(value)
else
value = ({["true"]=true,["on"]=true,["yes"]=true})[value] and 255 or 0
end
rs.setBundledOutput(side, color, value)
end
io.write("in: " .. rs.getBundledInput(side, color) .. "\n")
io.write("out: " .. rs.getBundledOutput(side, color) .. "\n")
else
if #args > 1 then
local value = args[2]
if tonumber(value) then
value = tonumber(value)
else
value = ({["true"]=true,["on"]=true,["yes"]=true})[value] and 15 or 0
end
rs.setOutput(side, value)
end
io.write("in: " .. rs.getInput(side) .. "\n")
io.write("out: " .. rs.getOutput(side) .. "\n")
end
end

View File

@ -0,0 +1,32 @@
local shell = require("shell")
local tty = require("tty")
local args = shell.parse(...)
local gpu = tty.gpu()
if #args == 0 then
local w, h = gpu.getViewport()
io.write(w," ",h,"\n")
return
end
if #args ~= 2 then
print("Usage: resolution [<width> <height>]")
return
end
local w = tonumber(args[1])
local h = tonumber(args[2])
if not w or not h then
io.stderr:write("invalid width or height\n")
return 1
end
local result, reason = gpu.setResolution(w, h)
if not result then
if reason then -- otherwise we didn't change anything
io.stderr:write(reason..'\n')
end
return 1
end
tty.clear()

160
data/OpenOS/bin/rm.lua Normal file
View File

@ -0,0 +1,160 @@
local fs = require("filesystem")
local shell = require("shell")
local function usage()
print("Usage: rm [options] <filename1> [<filename2> [...]]"..[[
-f ignore nonexistent files and arguments, never prompt
-r remove directories and their contents recursively
-v explain what is being done
--help display this help and exit
For complete documentation and more options, run: man rm]])
end
local args, options = shell.parse(...)
if #args == 0 or options.help then
usage()
return 1
end
local bRec = options.r or options.R or options.recursive
local bForce = options.f or options.force
local bVerbose = options.v or options.verbose
local bEmptyDirs = options.d or options.dir
local promptLevel = (options.I and 3) or (options.i and 1) or 0
bVerbose = bVerbose and not bForce
promptLevel = bForce and 0 or promptLevel
local function perr(...)
if not bForce then
io.stderr:write(...)
end
end
local function pout(...)
if not bForce then
io.stdout:write(...)
end
end
local metas = {}
-- promptLevel 3 done before fs.exists
-- promptLevel 1 asks for each, displaying fs.exists on hit as it visits
local function _path(m) return shell.resolve(m.rel) end
local function _link(m) return fs.isLink(_path(m)) end
local function _exists(m) return _link(m) or fs.exists(_path(m)) end
local function _dir(m) return not _link(m) and fs.isDirectory(_path(m)) end
local function _readonly(m) return not _exists(m) or fs.get(_path(m)).isReadOnly() end
local function _empty(m) return _exists(m) and _dir(m) and (fs.list(_path(m))==nil) end
local function createMeta(origin, rel)
local m = {origin=origin,rel=rel:gsub("/+$", "")}
if _dir(m) then
m.rel = m.rel .. '/'
end
return m
end
local function unlink(path)
os.remove(path)
return true
end
local function confirm()
if bForce then
return true
end
local r = io.read()
return r == 'y' or r == 'yes'
end
local remove
local function remove_all(parent)
if parent == nil or not _dir(parent) or _empty(parent) then
return true
end
local all_ok = true
if bRec and promptLevel == 1 then
pout(string.format("rm: descend into directory `%s'? ", parent.rel))
if not confirm() then
return false
end
for file in fs.list(_path(parent)) do
local child = createMeta(parent.origin, parent.rel .. file)
all_ok = remove(child) and all_ok
end
end
return all_ok
end
remove = function(meta)
if not remove_all(meta) then
return false
end
if not _exists(meta) then
perr(string.format("rm: cannot remove `%s': No such file or directory\n", meta.rel))
return false
elseif _dir(meta) and not bRec and not (_empty(meta) and bEmptyDirs) then
if not bEmptyDirs then
perr(string.format("rm: cannot remove `%s': Is a directory\n", meta.rel))
else
perr(string.format("rm: cannot remove `%s': Directory not empty\n", meta.rel))
end
return false
end
local ok = true
if promptLevel == 1 then
if _dir(meta) then
pout(string.format("rm: remove directory `%s'? ", meta.rel))
elseif meta.link then
pout(string.format("rm: remove symbolic link `%s'? ", meta.rel))
else -- file
pout(string.format("rm: remove regular file `%s'? ", meta.rel))
end
ok = confirm()
end
if ok then
if _readonly(meta) then
perr(string.format("rm: cannot remove `%s': Is read only\n", meta.rel))
return false
elseif not unlink(_path(meta)) then
perr(meta.rel .. ": failed to be removed\n")
ok = false
elseif bVerbose then
pout("removed '" .. meta.rel .. "'\n");
end
end
return ok
end
for _,arg in ipairs(args) do
metas[#metas+1] = createMeta(arg, arg)
end
if promptLevel == 3 and #metas > 3 then
pout(string.format("rm: remove %i arguments? ", #metas))
if not confirm() then
return
end
end
local ok = true
for _,meta in ipairs(metas) do
local result = remove(meta)
ok = ok and result
end
return bForce or ok

104
data/OpenOS/bin/rmdir.lua Normal file
View File

@ -0,0 +1,104 @@
local shell = require("shell")
local fs = require("filesystem")
local text = require("text")
local args, options = shell.parse(...)
local function usage()
print(
[[Usage: rmdir [OPTION]... DIRECTORY...
Removes the DIRECTORY(ies), if they are empty.
-q, --ignore-fail-on-non-empty
ignore failures due solely to non-empty directories
-p, --parents remove DIRECTORY and its empty ancestors
e.g. 'rmdir -p a/b/c' is similar to 'rmdir a/b/c a/b a'
-v, --verbose output a diagnostic for every directory processed
--help display this help and exit]])
end
if options.help then
usage()
return 0
end
if #args == 0 then
io.stderr:write("rmdir: missing operand\n")
return 1
end
options.p = options.p or options.parents
options.v = options.v or options.verbose
options.q = options.q or options['ignore-fail-on-non-empty']
local ec = 0
local function ec_bump()
ec = 1
return 1
end
local function remove(path, ...)
-- check to end recursion
if path == nil then
return true
end
if options.v then
print(string.format('rmdir: removing directory, %s', path))
end
local rpath = shell.resolve(path)
if path == '.' then
io.stderr:write('rmdir: failed to remove directory \'.\': Invalid argument\n')
return ec_bump()
elseif not fs.exists(rpath) then
io.stderr:write("rmdir: cannot remove " .. path .. ": path does not exist\n")
return ec_bump()
elseif fs.isLink(rpath) or not fs.isDirectory(rpath) then
io.stderr:write("rmdir: cannot remove " .. path .. ": not a directory\n")
return ec_bump()
else
local list, reason = fs.list(rpath)
if not list then
io.stderr:write(tostring(reason)..'\n')
return ec_bump()
else
if list() then
if not options.q then
io.stderr:write("rmdir: failed to remove " .. path .. ": Directory not empty\n")
end
return ec_bump()
else
-- path exists and is empty?
local ok, reason = fs.remove(rpath)
if not ok then
io.stderr:write(tostring(reason)..'\n')
return ec_bump(), reason
end
return remove(...) -- the final return of all else
end
end
end
end
for _,path in ipairs(args) do
-- clean up the input
path = path:gsub('/+', '/')
local segments = {}
if options.p and path:len() > 1 and path:find('/') then
local chain = text.split(path, {'/'}, true)
local prefix = ''
for _,e in ipairs(chain) do
table.insert(segments, 1, prefix .. e)
prefix = prefix .. e .. '/'
end
else
segments = {path}
end
remove(table.unpack(segments))
end
return ec

23
data/OpenOS/bin/set.lua Normal file
View File

@ -0,0 +1,23 @@
local args = {...}
if #args < 1 then
for k,v in pairs(os.getenv()) do
io.write(k .. "='" .. string.gsub(v, "'", [['"'"']]) .. "'\n")
end
else
local count = 0
for _, expr in ipairs(args) do
local e = expr:find('=')
if e then
os.setenv(expr:sub(1,e-1), expr:sub(e+1))
else
if count == 0 then
for i = 1, os.getenv('#') do
os.setenv(i, nil)
end
end
count = count + 1
os.setenv(count, expr)
end
end
end

50
data/OpenOS/bin/sh.lua Normal file
View File

@ -0,0 +1,50 @@
local shell = require("shell")
local tty = require("tty")
local text = require("text")
local sh = require("sh")
debugprint("a")
local args = shell.parse(...)
shell.prime()
debugprint("b")
if #args == 0 then
debugprint("c")
local has_profile
local input_handler = {hint = sh.hintHandler}
while true do
if io.stdin.tty and io.stdout.tty then
if not has_profile then -- first time run AND interactive
has_profile = true
dofile("/etc/profile.lua")
end
if tty.getCursor() > 1 then
io.write("\n")
end
io.write(sh.expand(os.getenv("PS1") or "$ "))
end
tty.window.cursor = input_handler
local command = io.stdin:readLine(false)
tty.window.cursor = nil
if command then
command = text.trim(command)
if command == "exit" then
return
elseif command ~= "" then
--luacheck: globals _ENV
local result, reason = sh.execute(_ENV, command)
if not result and reason then
io.stderr:write(tostring(reason), "\n")
end
end
elseif command == nil then -- false only means the input was interrupted
return -- eof
end
end
else
-- execute command.
return sh.execute(...)
end

View File

@ -0,0 +1,5 @@
local computer = require("computer")
local tty = require("tty")
tty.clear()
computer.shutdown()

60
data/OpenOS/bin/sleep.lua Normal file
View File

@ -0,0 +1,60 @@
local shell = require("shell")
local args, options = shell.parse(...)
if options.help then
print([[Usage: sleep NUMBER[SUFFIX]...
Pause for NUMBER seconds. SUFFIX may be 's' for seconds (the default),
'm' for minutes, 'h' for hours or 'd' for days. Unlike most implementations
that require NUMBER be an integer, here NUMBER may be an arbitrary floating
point number. Given two or more arguments, pause for the amount of time
specified by the sum of their values.]])
end
local function help(bad_arg)
print("sleep: invalid option -- '"..tostring(bad_arg).."'")
print("Try 'sleep --help' for more information.")
end
local function time_type_multiplier(time_type)
if not time_type or #time_type == 0 or time_type == 's' then
return 1
elseif time_type == 'm' then
return 60
elseif time_type == 'h' then
return 60 * 60
elseif time_type == 'd' then
return 60 * 60 * 24
end
-- weird error, my bad
assert(false,'bug parsing parameter:'..tostring(time_type))
end
options.help = nil
if next(options) then
help(next(options))
return 1
end
local total_time = 0
for _,v in ipairs(args) do
local interval, time_type = v:match('^([%d%.]+)([smhd]?)$')
interval = tonumber(interval)
if not interval or interval < 0 then
help(v)
return 1
end
total_time = total_time + time_type_multiplier(time_type) * interval
end
local ins = io.stdin.stream
local pull = ins.pull
local start = 1
if not pull then
pull = require("event").pull
start = 2
end
pull(select(start, ins, total_time, "interrupted"))

View File

@ -0,0 +1,36 @@
local shell = require("shell")
local process = require("process")
local args, options = shell.parse(...)
if #args ~= 1 then
io.stderr:write("specify a single file to source\n");
return 1
end
local file, open_reason = io.open(args[1], "r")
if not file then
if not options.q then
io.stderr:write(string.format("could not source %s because: %s\n", args[1], open_reason));
end
return 1
end
local lines = file:lines()
while true do
local line = lines()
if not line then
break
end
local current_data = process.info().data
local source_proc = process.load((assert(os.getenv("SHELL"), "no $SHELL set")))
local source_data = process.list[source_proc].data
source_data.aliases = current_data.aliases -- hacks to propogate sub shell env changes
source_data.vars = current_data.vars
process.internal.continue(source_proc, _ENV, line)
end
file:close()

18
data/OpenOS/bin/time.lua Normal file
View File

@ -0,0 +1,18 @@
local computer = require('computer')
local sh = require('sh')
local real_before, cpu_before = computer.uptime(), os.clock()
local cmd_result = 0
if ... then
sh.execute(nil, ...)
cmd_result = sh.getLastExitCode()
end
local real_after, cpu_after = computer.uptime(), os.clock()
local real_diff = real_after - real_before
local cpu_diff = cpu_after - cpu_before
print(string.format('real%5dm%.3fs', math.floor(real_diff/60), real_diff%60))
print(string.format('cpu %5dm%.3fs', math.floor(cpu_diff/60), cpu_diff%60))
return cmd_result

54
data/OpenOS/bin/touch.lua Normal file
View File

@ -0,0 +1,54 @@
--[[Lua implementation of the UN*X touch command--]]
local shell = require("shell")
local fs = require("filesystem")
local args, options = shell.parse(...)
local function usage()
print(
[[Usage: touch [OPTION]... FILE...
Update the modification times of each FILE to the current time.
A FILE argument that does not exist is created empty, unless -c is supplied.
-c, --no-create do not create any files
--help display this help and exit]])
end
if options.help then
usage()
return 0
elseif #args == 0 then
io.stderr:write("touch: missing operand\n")
return 1
end
options.c = options.c or options["no-create"]
local errors = 0
for _,arg in ipairs(args) do
local path = shell.resolve(arg)
if fs.isDirectory(path) then
io.stderr:write(string.format("`%s' ignored: directories not supported\n", arg))
else
local real, reason = fs.realPath(path)
if real then
local file
if fs.exists(real) or not options.c then
file = io.open(real, "a")
end
if not file then
real = options.c
reason = "permission denied"
else
file:close()
end
end
if not real then
io.stderr:write(string.format("touch: cannot touch `%s': %s\n", arg, reason))
errors = 1
end
end
end
return errors

331
data/OpenOS/bin/tree.lua Normal file
View File

@ -0,0 +1,331 @@
local computer = require("computer")
local shell = require("shell")
local fs = require("filesystem")
local tx = require("transforms")
local text = require("text")
local args, opts = shell.parse(...)
local function die(...)
io.stderr:write(...)
os.exit(1)
end
do -- handle cli
if opts.help then
print([[Usage: tree [OPTION]... [FILE]...
-a, --all do not ignore entries starting with .
--full-time with -l, print time in full iso format
-h, --human-readable with -l, print human readable sizes
--si likewise, but use powers of 1000 not 1024
--level=LEVEL descend only LEVEL directories deep
--color=WHEN WHEN can be
auto - colorize output only if writing to a tty,
always - always colorize output,
never - never colorize output; (default: auto)
-l use a long listing format
-f print the full path prefix for each file
-i do not print indentation lines
-p append "/" indicator to directories
-Q, --quote quote filenames with double quotes
-r, --reverse reverse order while sorting
-S sort by file size
-t sort by modification type, newest first
-X sort alphabetically by entry extension
-C do not count files and directories
-R count root directories like other files
--help print this help and exit]])
return 0
end
if #args == 0 then
table.insert(args, ".")
end
opts.level = tonumber(opts.level) or math.huge
if opts.level < 1 then
die("Invalid level, must be greater than 0")
end
opts.color = opts.color or "auto"
if opts.color == "auto" then
opts.color = io.stdout.tty and "always" or "never"
end
if opts.color ~= "always" and opts.color ~= "never" then
die("Invalid value for --color=WHEN option; WHEN should be auto, always or never")
end
end
local lastYield = computer.uptime()
local function yieldopt()
if computer.uptime() - lastYield > 2 then
lastYield = computer.uptime()
os.sleep(0)
end
end
local function peekable(iterator, state, var1)
local nextItem = {iterator(state, var1)}
return setmetatable({
peek = function()
return table.unpack(nextItem)
end
}, {
__call = coroutine.wrap(function()
while true do
local item = nextItem
nextItem = {iterator(state, nextItem[1])}
coroutine.yield(table.unpack(item))
if nextItem[1] == nil then break end
end
end)
})
end
local function filter(entry)
return opts.a or entry:sub(1, 1) ~= "."
end
local function stat(path)
local st = {}
st.path = path
st.name = fs.name(path) or "/"
st.sortName = st.name:gsub("^%.","")
st.time = fs.lastModified(path)
st.isLink = fs.isLink(path)
st.isDirectory = fs.isDirectory(path)
st.size = st.isLink and 0 or fs.size(path)
st.extension = st.name:match("(%.[^.]+)$") or ""
st.fs = fs.get(path)
return st
end
local colorize
if opts.color == "always" then
-- from /lib/core/full_ls.lua
local colors = tx.foreach(text.split(os.getenv("LS_COLORS") or "", {":"}, true), function(e)
local parts = text.split(e, {"="}, true)
return parts[2], parts[1]
end)
function colorize(stat)
return stat.isLink and colors.ln or
stat.isDirectory and colors.di or
colors["*" .. stat.extension] or
colors.fi
end
end
local function list(path)
return coroutine.wrap(function()
local l = {}
for entry in fs.list(path) do
if filter(entry) then
table.insert(l, stat(fs.concat(path, entry)))
end
end
if opts.S then
table.sort(l, function(a, b)
return a.size < b.size
end)
elseif opts.t then
table.sort(l, function(a, b)
return a.time < b.time
end)
elseif opts.X then
table.sort(l, function(a, b)
return a.extension < b.extension
end)
else
table.sort(l, function(a, b)
return a.sortName < b.sortName
end)
end
for i = opts.r and #l or 1, opts.r and 1 or #l, opts.r and -1 or 1 do
coroutine.yield(l[i])
end
end)
end
local function digRoot(rootPath)
coroutine.yield(stat(rootPath), {})
if not fs.isDirectory(rootPath) then
return
end
local iterStack = {peekable(list(rootPath))}
local pathStack = {rootPath}
local levelStack = {not not iterStack[#iterStack]:peek()}
repeat
local entry = iterStack[#iterStack]()
if entry then
levelStack[#levelStack] = not not iterStack[#iterStack]:peek()
local path = fs.concat(fs.concat(table.unpack(pathStack)), entry.name)
coroutine.yield(entry, levelStack)
if entry.isDirectory and opts.level > #levelStack then
table.insert(iterStack, peekable(list(path)))
table.insert(pathStack, entry.name)
table.insert(levelStack, not not iterStack[#iterStack]:peek())
end
else
table.remove(iterStack)
table.remove(pathStack)
table.remove(levelStack)
end
until #iterStack == 0
end
local function dig(roots)
return coroutine.wrap(function()
for _, root in ipairs(roots) do
digRoot(root)
end
end)
end
local function nod(n) -- from /lib/core/full_ls.lua
return n and (tostring(n):gsub("(%.[0-9]+)0+$","%1")) or "0"
end
local function formatFSize(size) -- from /lib/core/full_ls.lua
if not opts.h and not opts["human-readable"] and not opts.si then
return tostring(size)
end
local sizes = {"", "K", "M", "G"}
local unit = 1
local power = opts.si and 1000 or 1024
while size > power and unit < #sizes do
unit = unit + 1
size = size / power
end
return nod(math.floor(size*10)/10)..sizes[unit]
end
local function pad(txt) -- from /lib/core/full_ls.lua
txt = tostring(txt)
return #txt >= 2 and txt or "0" .. txt
end
local function formatTime(epochms) -- from /lib/core/full_ls.lua
local month_names = {"January","February","March","April","May","June",
"July","August","September","October","November","December"}
if epochms == 0 then return "" end
local d = os.date("*t", epochms)
local day, hour, min, sec = nod(d.day), pad(nod(d.hour)), pad(nod(d.min)), pad(nod(d.sec))
if opts["full-time"] then
return string.format("%s-%s-%s %s:%s:%s ", d.year, pad(nod(d.month)), pad(day), hour, min, sec)
else
return string.format("%s %2s %2s:%2s ", month_names[d.month]:sub(1,3), day, hour, pad(min))
end
end
local function writeEntry(entry, levelStack)
for i, hasNext in ipairs(levelStack) do
if opts.i then break end
if i == #levelStack then
if hasNext then
io.write("├── ")
else
io.write("└── ")
end
else
if hasNext then
io.write("│   ")
else
io.write(" ")
end
end
end
if opts.l then
io.write("[")
io.write(entry.isDirectory and "d" or entry.isLink and "l" or "f", "-")
io.write("r", entry.fs.isReadOnly() and "-" or "w", " ")
io.write(formatFSize(entry.size), " ")
io.write(formatTime(entry.time))
io.write("] ")
end
if opts.Q then io.write('"') end
if opts.color == "always" then
io.write("\27[" .. colorize(entry) .. "m")
end
if opts.f then
io.write(entry.path)
else
io.write(entry.name)
end
if opts.color == "always" then
io.write("\27[0m")
end
if opts.p and entry.isDirectory then
io.write("/")
end
if opts.Q then io.write('"') end
io.write("\n")
end
local function writeCount(dirs, files)
io.write("\n")
io.write(dirs, " director", dirs == 1 and "y" or "ies")
io.write(", ")
io.write(files, " file", files == 1 and "" or "s")
io.write("\n")
end
local dirs, files = 0, 0
local roots = {}
for _, arg in ipairs(args) do
local path = shell.resolve(arg)
local real, reason = fs.realPath(path)
if not real then
die("cannot access ", path, ": ", reason or "unknown error")
elseif not fs.exists(path) then
die("cannot access ", path, ":", "No such file or directory")
else
table.insert(roots, real)
end
end
for entry, levelStack in dig(roots) do
if opts.R or #levelStack > 0 then
if entry.isDirectory then
dirs = dirs + 1
else
files = files + 1
end
end
writeEntry(entry, levelStack)
yieldopt()
end
if not opts.C then
writeCount(dirs, files)
end

View File

@ -0,0 +1,37 @@
local fs = require("filesystem")
local shell = require("shell")
local args, options = shell.parse(...)
if #args < 1 then
io.write("Usage: umount [-a] <mount>\n")
io.write(" -a Remove any mounts by file system label or address instead of by path. Note that the address may be abbreviated.\n")
return 1
end
local proxy, reason
if options.a then
proxy, reason = fs.proxy(args[1])
if proxy then
proxy = proxy.address
end
else
local path = shell.resolve(args[1])
proxy, reason = fs.get(path)
if proxy then
proxy = reason -- = path
if proxy ~= path then
io.stderr:write("not a mount point\n")
return 1
end
end
end
if not proxy then
io.stderr:write(tostring(reason)..'\n')
return 1
end
if not fs.umount(proxy) then
io.stderr:write("nothing to unmount here\n")
return 1
end

View File

@ -0,0 +1,19 @@
local shell = require("shell")
local args = shell.parse(...)
if #args < 1 then
io.write("Usage: unalias <name>...\n")
return 2
end
local e = 0
for _,arg in ipairs(args) do
local result = shell.getAlias(arg)
if not result then
io.stderr:write(string.format("unalias: %s: not found\n", arg))
e = 1
else
shell.setAlias(arg, nil)
end
end
return e

View File

@ -0,0 +1,9 @@
local args = {...}
if #args < 1 then
io.write("Usage: unset <varname>[ <varname2> [...]]\n")
else
for _, k in ipairs(args) do
os.setenv(k, nil)
end
end

View File

@ -0,0 +1,13 @@
local computer = require("computer")
local seconds = math.floor(computer.uptime())
local minutes, hours = 0, 0
if seconds >= 60 then
minutes = math.floor(seconds / 60)
seconds = seconds % 60
end
if minutes >= 60 then
hours = math.floor(minutes / 60)
minutes = minutes % 60
end
io.write(string.format("%02d:%02d:%02d\n", hours, minutes, seconds))

View File

@ -0,0 +1,14 @@
local computer = require("computer")
local shell = require("shell")
local args = shell.parse(...)
if #args ~= 1 then
io.write("Usage: useradd <name>\n")
return 1
end
local result, reason = computer.addUser(args[1])
if not result then
io.stderr:write(reason..'\n')
return 1
end

View File

@ -0,0 +1,13 @@
local computer = require("computer")
local shell = require("shell")
local args = shell.parse(...)
if #args ~= 1 then
io.write("Usage: userdel <name>\n")
return 1
end
if not computer.removeUser(args[1]) then
io.stderr:write("no such user\n")
return 1
end

115
data/OpenOS/bin/wget.lua Normal file
View File

@ -0,0 +1,115 @@
local component = require("component")
local fs = require("filesystem")
local internet = require("internet")
local shell = require("shell")
local text = require("text")
if not component.isAvailable("internet") then
io.stderr:write("This program requires an internet card to run.")
return
end
local args, options = shell.parse(...)
options.q = options.q or options.Q
if #args < 1 then
io.write("Usage: wget [-fq] <url> [<filename>]\n")
io.write(" -f: Force overwriting existing files.\n")
io.write(" -q: Quiet mode - no status messages.\n")
io.write(" -Q: Superquiet mode - no error messages.")
return
end
local url = text.trim(args[1])
local filename = args[2]
if not filename then
filename = url
local index = string.find(filename, "/[^/]*$")
if index then
filename = string.sub(filename, index + 1)
end
index = string.find(filename, "?", 1, true)
if index then
filename = string.sub(filename, 1, index - 1)
end
end
filename = text.trim(filename)
if filename == "" then
if not options.Q then
io.stderr:write("could not infer filename, please specify one")
end
return nil, "missing target filename" -- for programs using wget as a function
end
filename = shell.resolve(filename)
local preexisted
if fs.exists(filename) then
preexisted = true
if not options.f then
if not options.Q then
io.stderr:write("file already exists")
end
return nil, "file already exists" -- for programs using wget as a function
end
end
local f, reason = io.open(filename, "a")
if not f then
if not options.Q then
io.stderr:write("failed opening file for writing: " .. reason)
end
return nil, "failed opening file for writing: " .. reason -- for programs using wget as a function
end
f:close()
f = nil
if not options.q then
io.write("Downloading... ")
end
local result, response = pcall(internet.request, url, nil, {["user-agent"]="Wget/OpenComputers"})
if result then
local result, reason = pcall(function()
for chunk in response do
if not f then
f, reason = io.open(filename, "wb")
assert(f, "failed opening file for writing: " .. tostring(reason))
end
f:write(chunk)
end
end)
if not result then
if not options.q then
io.stderr:write("failed.\n")
end
if f then
f:close()
if not preexisted then
fs.remove(filename)
end
end
if not options.Q then
io.stderr:write("HTTP request failed: " .. reason .. "\n")
end
return nil, reason -- for programs using wget as a function
end
if not options.q then
io.write("success.\n")
end
if f then
f:close()
end
if not options.q then
io.write("Saved data to " .. filename .. "\n")
end
else
if not options.q then
io.write("failed.\n")
end
if not options.Q then
io.stderr:write("HTTP request failed: " .. response .. "\n")
end
return nil, response -- for programs using wget as a function
end
return true -- for programs using wget as a function

25
data/OpenOS/bin/which.lua Normal file
View File

@ -0,0 +1,25 @@
local shell = require("shell")
local args = shell.parse(...)
if #args == 0 then
io.write("Usage: which <program>\n")
return 255
end
for i = 1, #args do
local result, reason = shell.resolve(args[i], "lua")
if not result then
result = shell.getAlias(args[i])
if result then
result = args[i] .. ": aliased to " .. result
end
end
if result then
print(result)
else
io.stderr:write(args[i] .. ": " .. reason .. "\n")
return 1
end
end

32
data/OpenOS/bin/yes.lua Normal file
View File

@ -0,0 +1,32 @@
--[[Lua implementation of the UN*X yes command--]]
local shell = require("shell")
local args, options = shell.parse(...)
if options.V or options.version then
io.write("yes v:1.0-3\n")
io.write("Inspired by functionality of yes from GNU coreutils\n")
return 0
end
if options.h or options.help then
io.write("Usage: yes [string]...\n")
io.write("OR: yes [-V/h]\n")
io.write("\n")
io.write("yes prints the command line arguments, or 'y', until is killed.\n")
io.write("\n")
io.write("Options:\n")
io.write(" -V, --version Version\n")
io.write(" -h, --help This help\n")
return 0
end
local msg = #args == 0 and 'y' or table.concat(args, ' ')
msg = msg .. '\n'
while io.write(msg) do
if io.stdout.tty then
os.sleep(0)
end
end
return 0

View File

@ -0,0 +1,42 @@
function loadfile(filename, ...)
if filename:sub(1,1) ~= "/" then
filename = (os.getenv("PWD") or "/") .. "/" .. filename
end
local handle, open_reason = require("filesystem").open(filename)
if not handle then
return nil, open_reason
end
local buffer = {}
while true do
local data, reason = handle:read(1024)
if not data then
handle:close()
if reason then
return nil, reason
end
break
end
buffer[#buffer + 1] = data
end
return load(table.concat(buffer), "=" .. filename, ...)
end
function dofile(filename)
local program, reason = loadfile(filename)
if not program then
return error(reason .. ':' .. filename, 0)
end
return program()
end
function print(...)
local args = table.pack(...)
local stdout = io.stdout
local pre = ""
for i = 1, args.n do
stdout:write(pre, (assert(tostring(args[i]), "'tostring' must return a string to 'print'")))
pre = "\t"
end
stdout:write("\n")
stdout:flush()
end

View File

@ -0,0 +1,84 @@
local process = require("process")
local fs = require("filesystem")
--Initialize coroutine library--
local _coroutine = coroutine -- real coroutine backend
_G.coroutine = setmetatable(
{
resume = function(co, ...)
local proc = process.info(co)
-- proc is nil if the process closed, natural resume will likely complain the coroutine is dead
-- but if proc is dead and an orphan coroutine is alive, it doesn't have any proc data like stack info
-- if the user really wants to resume it, let them
return (proc and proc.data.coroutine_handler.resume or _coroutine.resume)(co, ...)
end
},
{
__index = function(_, key)
local proc = process.info(_coroutine.running())
return (proc and proc.data.coroutine_handler or _coroutine)[key]
end
}
)
package.loaded.coroutine = _G.coroutine
local kernel_load = _G.load
local intercept_load
intercept_load = function(source, label, mode, env)
local prev_load = env and env.load or _G.load
local e = env and setmetatable({
load = function(_source, _label, _mode, _env)
return prev_load(_source, _label, _mode, _env or env)
end}, {
__index = env,
__pairs = function(...) return pairs(env, ...) end,
__newindex = function(_, key, value) env[key] = value end,
})
return kernel_load(source, label, mode, e or process.info().env)
end
_G.load = intercept_load
local kernel_create = _coroutine.create
_coroutine.create = function(f,standAlone)
local co = kernel_create(f)
if not standAlone then
table.insert(process.findProcess().instances, co)
end
return co
end
_coroutine.wrap = function(f)
local thread = coroutine.create(f)
return function(...)
return select(2, coroutine.resume(thread, ...))
end
end
local init_thread = _coroutine.running()
process.list[init_thread] = {
path = "/init.lua",
command = "init",
env = _ENV,
data =
{
vars={},
handles={},
io={}, --init will populate this
coroutine_handler = _coroutine,
signal = error
},
instances = setmetatable({}, {__mode="v"})
}
-- intercept fs open
local fs_open = fs.open
fs.open = function(...)
local fs_open_result = table.pack(fs_open(...))
if fs_open_result[1] then
process.addHandle(fs_open_result[1])
end
return table.unpack(fs_open_result, 1, fs_open_result.n)
end

View File

@ -0,0 +1,41 @@
local computer = require("computer")
local fs = require("filesystem")
local info = require("process").info
local event = require("event")
function os.getenv(varname)
local env = info().data.vars
if not varname then
return env
elseif varname == '#' then
return #env
end
return env[varname]
end
function os.setenv(varname, value)
checkArg(1, varname, "string", "number")
if value ~= nil then
value = tostring(value)
end
info().data.vars[varname] = value
return value
end
function os.sleep(timeout)
checkArg(1, timeout, "number", "nil")
local deadline = computer.uptime() + (timeout or 0)
repeat
event.pull(deadline - computer.uptime())
until computer.uptime() >= deadline
end
os.setenv("PATH", "/bin:/usr/bin:/home/bin:.")
os.setenv("TMP", "/tmp") -- Deprecated
os.setenv("TMPDIR", "/tmp")
if computer.tmpAddress() then
fs.mount(computer.tmpAddress(), "/tmp")
end
require("package").delay(os, "/lib/core/full_filesystem.lua")

View File

@ -0,0 +1,36 @@
local buffer = require("buffer")
local tty_stream = require("tty").stream
local core_stdin = buffer.new("r", tty_stream)
local core_stdout = buffer.new("w", tty_stream)
local core_stderr = buffer.new("w", setmetatable(
{
write = function(_, str)
return tty_stream:write("\27[31m"..str.."\27[37m")
end
}, {__index=tty_stream}))
core_stdout:setvbuf("no")
core_stderr:setvbuf("no")
core_stdin.tty = true
core_stdout.tty = true
core_stderr.tty = true
core_stdin.close = tty_stream.close
core_stdout.close = tty_stream.close
core_stderr.close = tty_stream.close
local io_mt = getmetatable(io) or {}
io_mt.__index = function(_, k)
return
k == 'stdin' and io.input() or
k == 'stdout' and io.output() or
k == 'stderr' and io.error() or
nil
end
setmetatable(io, io_mt)
io.input(core_stdin)
io.output(core_stdout)
io.error(core_stderr)

View File

@ -0,0 +1,204 @@
local component = require("component")
local computer = require("computer")
local event = require("event")
local adding = {}
local primaries = {}
-------------------------------------------------------------------------------
-- This allows writing component.modem.open(123) instead of writing
-- component.getPrimary("modem").open(123), which may be nicer to read.
setmetatable(component, {
__index = function(_, key)
return component.getPrimary(key)
end,
__pairs = function(self)
local parent = false
return function(_, key)
if parent then
return next(primaries, key)
else
local k, v = next(self, key)
if not k then
parent = true
return next(primaries)
else
return k, v
end
end
end
end
})
function component.get(address, componentType)
checkArg(1, address, "string")
checkArg(2, componentType, "string", "nil")
for c in component.list(componentType, true) do
if c:sub(1, address:len()) == address then
return c
end
end
return nil, "no such component"
end
function component.isAvailable(componentType)
checkArg(1, componentType, "string")
if not primaries[componentType] and not adding[componentType] then
-- This is mostly to avoid out of memory errors preventing proxy
-- creation cause confusion by trying to create the proxy again,
-- causing the oom error to be thrown again.
component.setPrimary(componentType, component.list(componentType, true)())
end
return primaries[componentType] ~= nil
end
function component.isPrimary(address)
local componentType = component.type(address)
if componentType then
if component.isAvailable(componentType) then
return primaries[componentType].address == address
end
end
return false
end
function component.getPrimary(componentType)
checkArg(1, componentType, "string")
assert(component.isAvailable(componentType),
"no primary '" .. componentType .. "' available")
return primaries[componentType]
end
function component.setPrimary(componentType, address)
checkArg(1, componentType, "string")
checkArg(2, address, "string", "nil")
debugprint("setPrimary", componentType, address)
if address ~= nil then
address = component.get(address, componentType)
assert(address, "no such component")
end
local wasAvailable = primaries[componentType]
if wasAvailable and address == wasAvailable.address then
return
end
local wasAdding = adding[componentType]
if wasAdding and address == wasAdding.address then
return
end
if wasAdding then
event.cancel(wasAdding.timer)
end
primaries[componentType] = nil
adding[componentType] = nil
local primary = address and component.proxy(address) or nil
if wasAvailable then
computer.pushSignal("component_unavailable", componentType)
end
if primary then
if wasAvailable or wasAdding then
adding[componentType] = {
address=address,
proxy = primary,
timer=event.timer(0.1, function()
adding[componentType] = nil
primaries[componentType] = primary
computer.pushSignal("component_available", componentType)
end)
}
else
primaries[componentType] = primary
computer.pushSignal("component_available", componentType)
end
end
end
-------------------------------------------------------------------------------
local function onComponentAdded(_, address, componentType)
local prev = primaries[componentType] or (adding[componentType] and adding[componentType].proxy)
if prev then
-- special handlers -- some components are just better at being primary
if componentType == "screen" then
--the primary has no keyboards but we do
if #prev.getKeyboards() == 0 then
local first_kb = component.invoke(address, 'getKeyboards')[1]
if first_kb then
-- just in case our kb failed to achieve primary
-- possible if existing primary keyboard became primary first without a screen
-- then prev (a screen) was added without a keyboard
-- and then we attached this screen+kb pair, and our kb fired first - failing to achieve primary
-- also, our kb may fire right after this, which is fine
component.setPrimary("keyboard", first_kb)
prev = nil -- nil meaning we should take this new one over the previous
end
end
elseif componentType == "keyboard" then
-- to reduce signal noise, if this kb is also the prev, we do not need to reset primary
if address ~= prev.address then
--keyboards never replace primary keyboards unless the are the only keyboard on the primary screen
local current_screen = primaries.screen or (adding.screen and adding.screen.proxy)
--if there is not yet a screen, do not use this keyboard, it's not any better
if current_screen then
-- the next phase is complicated
-- there is already a screen and there is already a keyboard
-- this keyboard is only better if this is a keyboard of the primary screen AND the current keyboard is not
-- i don't think we can trust kb order (1st vs 2nd), 2nd could fire first
-- but if there are two kbs on a screen, we can give preferred treatment to the first
-- thus, assume 2nd is not attached for the purposes of primary kb
-- and THUS, whichever (if either) is the 1st kb of the current screen
-- this is only possible if
-- 1. the only kb on the system (current) has no screen
-- 2. a screen is added without a kb
-- 3. this kb is added later manually
-- prev is true when addr is not equal to the primary keyboard of the current screen -- meaning
-- when addr is different, and thus it is not the primary keyboard, then we ignore this
-- keyboard, and keep the previous
-- prev is false means we should take this new keyboard
prev = address ~= current_screen.getKeyboards()[1]
end
end
end
end
if not prev then
component.setPrimary(componentType, address)
end
end
local function onComponentRemoved(_, address, componentType)
if primaries[componentType] and primaries[componentType].address == address or
adding[componentType] and adding[componentType].address == address
then
local next = component.list(componentType, true)()
component.setPrimary(componentType, next)
if componentType == "screen" and next then
-- setPrimary already set the proxy (if successful)
local proxy = (primaries.screen or (adding.screen and adding.screen.proxy))
if proxy then
-- if a screen is removed, and the primary keyboard is actually attached to another, non-primary, screen
-- then the `next` screen, if it has a keyboard, should TAKE priority
local next_kb = proxy.getKeyboards()[1] -- costly, don't call this method often
local old_kb = primaries.keyboard or adding.keyboard
-- if the next screen doesn't have a kb, this operation is without purpose, leave things as they are
-- if there was no previous kb, use the new one
if next_kb and (not old_kb or old_kb.address ~= next_kb) then
component.setPrimary("keyboard", next_kb)
end
end
end
end
end
event.listen("component_added", onComponentAdded)
event.listen("component_removed", onComponentRemoved)
if _G.boot_screen then
component.setPrimary("screen", _G.boot_screen)
end
_G.boot_screen = nil

View File

@ -0,0 +1,23 @@
require("filesystem").mount(
setmetatable({
address = "f5501a9b-9c23-1e7a-4afe-4b65eed9b88a"
},
{
__index=function(tbl,key)
local result =
({
getLabel = "devfs",
spaceTotal = 0,
spaceUsed = 0,
isReadOnly = false,
})[key]
if result ~= nil then
return function() return result end
end
local lib = require("devfs")
lib.register(tbl)
return lib.proxy[key]
end
}), "/dev")

View File

@ -0,0 +1,10 @@
-- Run all enabled rc scripts.
-- /boot/*rc was moved before /boot/*filesystem because
-- 1. rc now loads in via the init signal
-- 2. rc used to load directly
-- Thus, for rc to load prior, it needs to register prior
require("event").listen("init", function()
dofile(require("shell").resolve("rc", "lua"))
return false
end)

View File

@ -0,0 +1,56 @@
local event = require("event")
local fs = require("filesystem")
local shell = require("shell")
local tmp = require("computer").tmpAddress()
local pendingAutoruns = {}
local function onComponentAdded(_, address, componentType)
if componentType == "filesystem" and tmp ~= address then
local proxy = fs.proxy(address)
if proxy then
local name = address:sub(1, 3)
while fs.exists(fs.concat("/mnt", name)) and
name:len() < address:len() -- just to be on the safe side
do
name = address:sub(1, name:len() + 1)
end
name = fs.concat("/mnt", name)
fs.mount(proxy, name)
if not fs.exists("/etc/filesystem.cfg") or fs.isAutorunEnabled() then
local file = shell.resolve(fs.concat(name, "autorun"), "lua") or
shell.resolve(fs.concat(name, ".autorun"), "lua")
if file then
local run = {file, _ENV, proxy}
if pendingAutoruns then
table.insert(pendingAutoruns, run)
else
xpcall(shell.execute, event.onError, table.unpack(run))
end
end
end
end
end
end
local function onComponentRemoved(_, address, componentType)
if componentType == "filesystem" then
if fs.get(shell.getWorkingDirectory()).address == address then
shell.setWorkingDirectory("/")
end
fs.umount(address)
end
end
event.listen("init", function()
for _, run in ipairs(pendingAutoruns) do
xpcall(shell.execute, event.onError, table.unpack(run))
end
pendingAutoruns = nil
return false
end)
event.listen("component_added", onComponentAdded)
event.listen("component_removed", onComponentRemoved)
require("package").delay(fs, "/lib/core/full_filesystem.lua")

View File

@ -0,0 +1,24 @@
local event = require("event")
local function onComponentAvailable(_, componentType)
local component = require("component")
local tty = require("tty")
if (componentType == "screen" and component.isAvailable("gpu")) or
(componentType == "gpu" and component.isAvailable("screen"))
then
local gpu, screen = component.gpu, component.screen
local screen_address = screen.address
if gpu.getScreen() ~= screen_address then
gpu.bind(screen_address)
end
local depth = math.floor(2^(gpu.getDepth()))
os.setenv("TERM", "term-"..depth.."color")
event.push("gpu_bound", gpu.address, screen_address)
if tty.gpu() ~= gpu then
tty.bind(gpu)
event.push("term_available")
end
end
end
event.listen("component_available", onComponentAvailable)

View File

@ -0,0 +1,12 @@
local event = require("event")
local keyboard = require("keyboard")
local function onKeyChange(ev, _, char, code)
-- nil might be slightly more mem friendly during runtime
-- and `or nil` appears to only cost 30 bytes
keyboard.pressedChars[char] = ev == "key_down" or nil
keyboard.pressedCodes[code] = ev == "key_down" or nil
end
event.listen("key_down", onKeyChange)
event.listen("key_up", onKeyChange)

View File

@ -0,0 +1,46 @@
local event = require("event")
local function components_changed(ename, address, type)
local tty = require("tty")
local window = tty.window
if not window then
return
end
if ename == "component_available" or ename == "component_unavailable" then
type = address
end
if ename == "component_removed" or ename == "component_unavailable" then
-- address can be type, when ename is *_unavailable, but *_removed works here and that's all we need
if type == "gpu" and window.gpu.address == address then
window.gpu = nil
window.keyboard = nil
elseif type == "keyboard" then
-- we could check if this was our keyboard
-- i.e. if address == window.keyboard
-- but it is also simple for the terminal to
-- recheck what kb to use
window.keyboard = nil
end
if (type == "screen" or type == "gpu") and not tty.isAvailable() then
event.push("term_unavailable")
end
elseif (ename == "component_added" or ename == "component_available") and type == "keyboard" then
-- we need to clear the current terminals cached keyboard (if any) when
-- a new keyboard becomes available. This is in case the new keyboard was
-- attached to the terminal's window. The terminal library has the code to
-- determine what the best keyboard to use is, but here we'll just set the
-- cache to nil to force term library to reload it. An alternative to this
-- method would be to make sure the terminal library doesn't cache the
-- wrong keybaord to begin with but, users may actually expect that any
-- primary keyboard is a valid keyboard (weird, in my opinion)
window.keyboard = nil
end
end
event.listen("component_removed", components_changed)
event.listen("component_added", components_changed)
event.listen("component_available", components_changed)
event.listen("component_unavailable", components_changed)

View File

@ -0,0 +1,6 @@
-- there doesn't seem to be a reason to update $HOSTNAME after the init signal
-- as user space /etc/profile comes after this point anyways
if require("filesystem").exists("/etc/hostname") then
loadfile("/bin/hostname.lua")("--update")
end
os.setenv("SHELL","/bin/sh.lua")

38
data/OpenOS/etc/motd Normal file
View File

@ -0,0 +1,38 @@
local component = require("component")
local computer = require("computer")
local unicode = require("unicode")
local tty = require("tty")
local f = io.open("/usr/misc/greetings.txt")
local lines = {_OSVERSION .. " (" .. math.floor(computer.totalMemory() / 1024) .. "k RAM)"}
local greeting = ""
if f then
local greetings = {}
pcall(function()
for line in f:lines() do table.insert(greetings, line) end
end)
f:close()
greeting = greetings[math.random(1, math.max(#greetings, 1))] or ""
end
local width = math.min(#greeting, (tty.getViewport() or math.huge) - 5)
local maxLine = #lines[1]
while #greeting > 0 do
local si, ei = greeting:sub(1, width):find("%s%S*$")
local line = #greeting <= width and greeting or greeting:sub(1, si or width)
lines[#lines + 1] = line
maxLine = math.max(maxLine, #line)
greeting = greeting:sub(#line + 1)
end
local borders = {{unicode.char(0x2552), unicode.char(0x2550), unicode.char(0x2555)},
{unicode.char(0x2502), nil, unicode.char(0x2502)},
{unicode.char(0x2514), unicode.char(0x2500), unicode.char(0x2518)}}
io.write(borders[1][1], string.rep(borders[1][2], maxLine + 2), borders[1][3], "\n")
for _,line in ipairs(lines) do
io.write(borders[2][1], " ", line, (" "):rep(maxLine - #line + 1), borders[2][3], " \n")
end
io.write(borders[3][1] .. string.rep(borders[3][2], maxLine + 2) .. borders[3][3] .. "\n")
if require("filesystem").get("home").isReadOnly() then
io.write("\27[33mNote: Your home directory is readonly. Run `install` and reboot.\27[m\n")
end

View File

@ -0,0 +1,44 @@
local shell = require("shell")
local tty = require("tty")
local fs = require("filesystem")
if tty.isAvailable() then
if io.stdout.tty then
io.write("\27[40m\27[37m")
tty.clear()
end
end
dofile("/etc/motd")
shell.setAlias("dir", "ls")
shell.setAlias("move", "mv")
shell.setAlias("rename", "mv")
shell.setAlias("copy", "cp")
shell.setAlias("del", "rm")
shell.setAlias("md", "mkdir")
shell.setAlias("cls", "clear")
shell.setAlias("rs", "redstone")
shell.setAlias("view", "edit -r")
shell.setAlias("help", "man")
shell.setAlias("l", "ls -lhp")
shell.setAlias("..", "cd ..")
shell.setAlias("df", "df -h")
shell.setAlias("grep", "grep --color")
shell.setAlias("more", "less --noback")
shell.setAlias("reset", "resolution `cat /dev/components/by-type/gpu/0/maxResolution`")
os.setenv("EDITOR", "/bin/edit")
os.setenv("HISTSIZE", "10")
os.setenv("HOME", "/home")
os.setenv("IFS", " ")
os.setenv("MANPATH", "/usr/man:.")
os.setenv("PAGER", "less")
os.setenv("PS1", "\27[40m\27[31m$HOSTNAME$HOSTNAME_SEPARATOR$PWD # \27[37m")
os.setenv("LS_COLORS", "di=0;36:fi=0:ln=0;33:*.lua=0;32")
shell.setWorkingDirectory(os.getenv("HOME"))
local home_shrc = shell.resolve(".shrc")
if fs.exists(home_shrc) then
loadfile(shell.resolve("source", "lua"))(home_shrc)
end

3
data/OpenOS/etc/rc.cfg Normal file
View File

@ -0,0 +1,3 @@
enabled = {}
example = "Hello World!"

View File

@ -0,0 +1,14 @@
local count = 0
function start(msg)
print("This script displays a welcome message and counts the number " ..
"of times it has been called. The welcome message can be set in the " ..
"config file /etc/rc.cfg")
print(args)
if msg then
print(msg)
end
print(count)
print("runlevel: " .. require("computer").runlevel())
count = count + 1
end

0
data/OpenOS/home/.shrc Normal file
View File

29
data/OpenOS/init.lua Normal file
View File

@ -0,0 +1,29 @@
do
local addr, invoke = computer.getBootAddress(), component.invoke
local function loadfile(file)
local handle = assert(invoke(addr, "open", file))
local buffer = ""
repeat
local data = invoke(addr, "read", handle, math.maxinteger or math.huge)
buffer = buffer .. (data or "")
until not data
invoke(addr, "close", handle)
return load(buffer, "=" .. file, "bt", _G)
end
loadfile("/lib/core/boot.lua")(loadfile)
end
while true do
debugprint("grabbing shell")
local result, reason = xpcall(assert(require("shell").getShell()), function(msg)
return tostring(msg).."\n"..debug.traceback()
end)
debugprint("resumed", result, reason)
if not result then
debugprint((reason ~= nil and tostring(reason) or "unknown error") .. "\n")
io.stderr:write((reason ~= nil and tostring(reason) or "unknown error") .. "\n")
io.write("Press any key to continue.\n")
os.sleep(0.5)
require("event").pull("key")
end
end

102
data/OpenOS/lib/bit32.lua Normal file
View File

@ -0,0 +1,102 @@
--[[ Backwards compat for Lua 5.3; only loaded in 5.3 because package.loaded is
prepopulated with the existing global bit32 in 5.2. ]]
local bit32 = {}
-------------------------------------------------------------------------------
local function fold(init, op, ...)
local result = init
local args = table.pack(...)
for i = 1, args.n do
result = op(result, args[i])
end
return result
end
local function trim(n)
return n & 0xFFFFFFFF
end
local function mask(w)
return ~(0xFFFFFFFF << w)
end
function bit32.arshift(x, disp)
return x // (2 ^ disp)
end
function bit32.band(...)
return fold(0xFFFFFFFF, function(a, b) return a & b end, ...)
end
function bit32.bnot(x)
return ~x
end
function bit32.bor(...)
return fold(0, function(a, b) return a | b end, ...)
end
function bit32.btest(...)
return bit32.band(...) ~= 0
end
function bit32.bxor(...)
return fold(0, function(a, b) return a ~ b end, ...)
end
local function fieldargs(f, w)
w = w or 1
assert(f >= 0, "field cannot be negative")
assert(w > 0, "width must be positive")
assert(f + w <= 32, "trying to access non-existent bits")
return f, w
end
function bit32.extract(n, field, width)
local f, w = fieldargs(field, width)
return (n >> f) & mask(w)
end
function bit32.replace(n, v, field, width)
local f, w = fieldargs(field, width)
local m = mask(w)
return (n & ~(m << f)) | ((v & m) << f)
end
function bit32.lrotate(x, disp)
if disp == 0 then
return x
elseif disp < 0 then
return bit32.rrotate(x, -disp)
else
disp = disp & 31
x = trim(x)
return trim((x << disp) | (x >> (32 - disp)))
end
end
function bit32.lshift(x, disp)
return trim(x << disp)
end
function bit32.rrotate(x, disp)
if disp == 0 then
return x
elseif disp < 0 then
return bit32.lrotate(x, -disp)
else
disp = disp & 31
x = trim(x)
return trim((x >> disp) | (x << (32 - disp)))
end
end
function bit32.rshift(x, disp)
return trim(x >> disp)
end
-------------------------------------------------------------------------------
return bit32

182
data/OpenOS/lib/buffer.lua Normal file
View File

@ -0,0 +1,182 @@
local computer = require("computer")
local unicode = require("unicode")
local buffer = {}
local metatable = {
__index = buffer,
__metatable = "file"
}
function buffer.new(mode, stream)
local result = {
closed = false,
tty = false,
mode = {},
stream = stream,
bufferRead = "",
bufferWrite = "",
bufferSize = math.max(512, math.min(8 * 1024, computer.freeMemory() / 8)),
bufferMode = "full",
readTimeout = math.huge,
}
mode = mode or "r"
for i = 1, unicode.len(mode) do
result.mode[unicode.sub(mode, i, i)] = true
end
-- when stream closes, result should close first
-- when result closes, stream should close after
-- when stream closes, it is removed from the proc
stream.close = setmetatable({close = stream.close,parent = result},{__call = buffer.close})
return setmetatable(result, metatable)
end
function buffer:close()
-- self is either the buffer, or the stream.close callable
local meta = getmetatable(self)
if meta == metatable.__metatable then
return self.stream:close()
end
local parent = self.parent
if parent.mode.w or parent.mode.a then
parent:flush()
end
parent.closed = true
return self.close(parent.stream)
end
function buffer:flush()
if #self.bufferWrite > 0 then
local tmp = self.bufferWrite
self.bufferWrite = ""
local result, reason = self.stream:write(tmp)
if not result then
return nil, reason or "bad file descriptor"
end
end
return self
end
function buffer:lines(...)
local args = table.pack(...)
return function()
local result = table.pack(self:read(table.unpack(args, 1, args.n)))
if not result[1] and result[2] then
error(result[2])
end
return table.unpack(result, 1, result.n)
end
end
local function readChunk(self)
if computer.uptime() > self.timeout then
error("timeout")
end
local result, reason = self.stream:read(math.max(1,self.bufferSize))
if result then
self.bufferRead = self.bufferRead .. result
return self
else -- error or eof
return result, reason
end
end
function buffer:readLine(chop, timeout)
self.timeout = timeout or (computer.uptime() + self.readTimeout)
local start = 1
while true do
local buf = self.bufferRead
local i = buf:find("[\r\n]", start)
local c = i and buf:sub(i,i)
local is_cr = c == "\r"
if i and (not is_cr or i < #buf) then
local n = buf:sub(i+1,i+1)
if is_cr and n == "\n" then
c = c .. n
end
local result = buf:sub(1, i - 1) .. (chop and "" or c)
self.bufferRead = buf:sub(i + #c)
return result
else
start = #self.bufferRead - (is_cr and 1 or 0)
local result, reason = readChunk(self)
if not result then
if reason then
return result, reason
else -- eof
result = #self.bufferRead > 0 and self.bufferRead or nil
self.bufferRead = ""
return result
end
end
end
end
end
function buffer:read(...)
if not self.mode.r then
return nil, "read mode was not enabled for this stream"
end
if self.mode.w or self.mode.a then
self:flush()
end
if select("#", ...) == 0 then
return self:readLine(true)
end
return self:formatted_read(readChunk, ...)
end
function buffer:setvbuf(mode, size)
mode = mode or self.bufferMode
size = size or self.bufferSize
assert(mode == "no" or mode == "full" or mode == "line",
"bad argument #1 (no, full or line expected, got " .. tostring(mode) .. ")")
assert(mode == "no" or type(size) == "number",
"bad argument #2 (number expected, got " .. type(size) .. ")")
self.bufferMode = mode
self.bufferSize = size
return self.bufferMode, self.bufferSize
end
function buffer:write(...)
if self.closed then
return nil, "bad file descriptor"
end
if not self.mode.w and not self.mode.a then
return nil, "write mode was not enabled for this stream"
end
local args = table.pack(...)
for i = 1, args.n do
if type(args[i]) == "number" then
args[i] = tostring(args[i])
end
checkArg(i, args[i], "string")
end
for i = 1, args.n do
local arg = args[i]
local result, reason
if self.bufferMode == "no" then
result, reason = self.stream:write(arg)
else
result, reason = buffer.buffered_write(self, arg)
end
if not result then
return nil, reason
end
end
return self
end
require("package").delay(buffer, "/lib/core/full_buffer.lua")
return buffer

View File

@ -0,0 +1,30 @@
local colors = {
[0] = "white",
[1] = "orange",
[2] = "magenta",
[3] = "lightblue",
[4] = "yellow",
[5] = "lime",
[6] = "pink",
[7] = "gray",
[8] = "silver",
[9] = "cyan",
[10] = "purple",
[11] = "blue",
[12] = "brown",
[13] = "green",
[14] = "red",
[15] = "black"
}
do
local keys = {}
for k in pairs(colors) do
table.insert(keys, k)
end
for _, k in pairs(keys) do
colors[colors[k]] = k
end
end
return colors

View File

@ -0,0 +1,150 @@
-- called from /init.lua
local raw_loadfile = ...
_G._OSVERSION = "OpenOS 1.8.8"
-- luacheck: globals component computer unicode _OSVERSION
local component = component
local computer = computer
local unicode = unicode
-- Runlevel information.
_G.runlevel = "S"
local shutdown = computer.shutdown
computer.runlevel = function() return _G.runlevel end
computer.shutdown = function(reboot)
_G.runlevel = reboot and 6 or 0
if os.sleep then
computer.pushSignal("shutdown")
os.sleep(0.1) -- Allow shutdown processing.
end
shutdown(reboot)
end
local w, h
local screen = component.list("screen", true)()
local gpu = screen and component.list("gpu", true)()
if gpu then
gpu = component.proxy(gpu)
if not gpu.getScreen() then
gpu.bind(screen)
end
_G.boot_screen = gpu.getScreen()
w, h = gpu.maxResolution()
gpu.setResolution(w, h)
gpu.setBackground(0x000000)
gpu.setForeground(0xFFFFFF)
gpu.fill(1, 1, w, h, " ")
end
-- Report boot progress if possible.
local y = 1
local uptime = computer.uptime
-- we actually want to ref the original pullSignal here because /lib/event intercepts it later
-- because of that, we must re-pushSignal when we use this, else things break badly
local pull = computer.pullSignal
local last_sleep = uptime()
local function status(msg)
if gpu then
gpu.set(1, y, msg)
if y == h then
gpu.copy(1, 2, w, h - 1, 0, -1)
gpu.fill(1, h, w, 1, " ")
else
y = y + 1
end
end
-- boot can be slow in some environments, protect from timeouts
if uptime() - last_sleep > 1 then
local signal = table.pack(pull(0))
-- there might not be any signal
if signal.n > 0 then
-- push the signal back in queue for the system to use it
computer.pushSignal(table.unpack(signal, 1, signal.n))
end
last_sleep = uptime()
end
end
status("Booting " .. _OSVERSION .. "...")
-- Custom low-level dofile implementation reading from our ROM.
local function dofile(file)
status("> " .. file)
local program, reason = raw_loadfile(file)
if program then
return program()
else
error(reason)
end
end
status("Initializing package management...")
-- Load file system related libraries we need to load other stuff moree
-- comfortably. This is basically wrapper stuff for the file streams
-- provided by the filesystem components.
local package = dofile("/lib/package.lua")
do
-- Unclutter global namespace now that we have the package module and a filesystem
_G.component = nil
_G.computer = nil
_G.process = nil
_G.unicode = nil
-- Inject the package modules into the global namespace, as in Lua.
_G.package = package
-- Initialize the package module with some of our own APIs.
package.loaded.component = component
package.loaded.computer = computer
package.loaded.unicode = unicode
package.loaded.buffer = dofile("/lib/buffer.lua")
package.loaded.filesystem = dofile("/lib/filesystem.lua")
-- Inject the io modules
_G.io = dofile("/lib/io.lua")
end
status("Initializing file system...")
-- Mount the ROM and temporary file systems to allow working on the file
-- system module from this point on.
require("filesystem").mount(computer.getBootAddress(), "/")
status("Running boot scripts...")
-- Run library startup scripts. These mostly initialize event handlers.
local function rom_invoke(method, ...)
return component.invoke(computer.getBootAddress(), method, ...)
end
local scripts = {}
for _, file in ipairs(rom_invoke("list", "boot")) do
local path = "boot/" .. file
if not rom_invoke("isDirectory", path) then
table.insert(scripts, path)
end
end
table.sort(scripts)
for i = 1, #scripts do
dofile(scripts[i])
end
status("Initializing components...")
for c, t in component.list() do
computer.pushSignal("component_added", c, t)
end
status("Initializing system...")
require("event").listen("component_added", debugprint)
require("event").listen("component_available", debugprint)
require("event").listen("term_available", debugprint)
computer.pushSignal("init") -- so libs know components are initialized.
require("event").pull(1, "init") -- Allow init processing.
require("tty").bind(component.gpu)
_G.runlevel = 1

View File

@ -0,0 +1,270 @@
debugprint("loading cursor")
local unicode = require("unicode")
debugprint("loaded unicode")
local kb = require("keyboard")
debugprint("loaded keyboard")
local tty = require("tty")
debugprint("loaded tty")
local text = require("text")
debugprint("loaded text")
local computer = require("computer")
debugprint("loaded computer")
local keys = kb.keys
local core_cursor = {}
core_cursor.vertical = {}
function core_cursor.vertical:move(n)
local s = math.max(math.min(self.index + n, self.len), 0)
if s == self.index then return end
local echo_cmd = keys.left
local from = s + 1
local to = self.index
if s > self.index then
echo_cmd, from, to = keys.right, to + 1, s
end
self.index = s
local step = unicode.wlen(unicode.sub(self.data, from, to))
self:echo(echo_cmd, step)
end
-- back is used when arg comes after the cursor
function core_cursor.vertical:update(arg, back)
if not arg then
self.tails = {}
self.data = ""
self.index = 0
self.sy = 0
self.hindex = 0
end
local s1 = unicode.sub(self.data, 1, self.index)
local s2 = unicode.sub(self.data, self.index + 1)
if type(arg) == "string" then
if back == false then
arg, s2 = arg .. s2, ""
else
self.index = self.index + unicode.len(arg)
self:echo(arg)
end
self.data = s1 .. arg
elseif arg then -- number
local has_tail = arg < 0 or #s2 > 0
if arg < 0 then
-- backspace? ignore if at start
if self.index <= 0 then return end
self:move(arg)
s1 = unicode.sub(s1, 1, -1 + arg)
else
-- forward? ignore if at end
if self.index >= self.len then return end
s2 = unicode.sub(s2, 1 + arg)
end
self.data = s1
if has_tail then
self:echo(self.clear)
end
end
self.len = unicode.len(self.data) -- recompute len
self:move(back or 0)
if #s2 > 0 then
self:update(s2, -unicode.len(s2))
end
end
function core_cursor.vertical:echo(arg, num)
local win = tty.window
local gpu = win.gpu
-- we should not use io.write
-- the cursor should echo to the stream it is reading from
-- this makes sense because a process may redirect its io
-- but a cursor reading from a given stdin tty should also
-- echo to that same stream
-- but, if stdin has been piped - we do not echo the cursor
if not io.stdin.tty then
return
end
local out = io.stdin.stream
if not gpu then return end
win.nowrap = self.nowrap
if arg == "" then -- special scroll request
local width, x, y = win.width, win.x, win.y
if x > width then
win.x = ((x - 1) % width) + 1
win.y = y + math.floor(x / width)
out:write("") -- tty.stream:write knows how to scroll vertically
x, y = win.x, win.y
end
if x <= 0 or y <= 0 or y > win.height or not gpu then return end
return table.pack(select(2, pcall(gpu.get, x + win.dx, y + win.dy)))
elseif arg == keys.left then
local x = win.x - num
local y = win.y
while x < 1 do
x = x + win.width - #(self.tails[win.dy + y - self.sy - 1] or "")
y = y - 1
end
win.x, win.y = x, y
arg = ""
elseif arg == keys.right then
local x = win.x + num
local y = win.y
while true do
local width = win.width - #(self.tails[win.dy + y - self.sy] or "")
if x <= width then break end
x = x - width
y = y + 1
end
win.x, win.y = x, y
arg = ""
elseif not arg or arg == true then -- blink
local char = self.char_at_cursor
if (arg == nil and not char) or (arg and not self.blinked) then
char = char or self:echo("") --scroll and get char
if not char[1] then return false end
self.blinked = true
if not arg then
out:write("\0277")
char.saved = win.saved
gpu.setForeground(char[4] or char[2], not not char[4])
gpu.setBackground(char[5] or char[3], not not char[5])
end
out:write("\0277\27[7m"..char[1].."\0278")
elseif (arg and self.blinked) or (arg == false and char) then
self.blinked = false
gpu.set(win.x + win.dx, win.y + win.dy, char[1])
if not arg then
win.saved = char.saved
out:write("\0278")
char = nil
end
end
self.char_at_cursor = char
return true
end
return out:write(arg)
end
function core_cursor.vertical:handle(name, char, code)
if name == "clipboard" then
self.cache = nil -- this stops tab completion
local newline = char:find("\10") or #char
local printable_prefix, remainder = char:sub(1, newline), char:sub(newline + 1)
self:update(printable_prefix)
self:update(remainder, false)
elseif name == "touch" or name == "drag" then
core_cursor.touch(self, char, code)
elseif name == "interrupted" then
self:echo("^C\n")
return false, name
elseif name == "key_down" then
local data = self.data
local backup_cache = self.cache
self.cache = nil
local ctrl = kb.isControlDown()
if ctrl and code == keys.d then
return --nil:close
elseif code == keys.tab then
self.cache = backup_cache
core_cursor.tab(self)
elseif code == keys.enter or code == keys.numpadenter then
self:move(self.len)
self:update("\n")
elseif code == keys.up or code == keys.down then
local ni = self.hindex + (code == keys.up and 1 or -1)
if ni >= 0 and ni <= #self then
self[self.hindex] = data
self.hindex = ni
self:move(self.len)
self:update(-self.len)
self:update(self[ni])
end
elseif code == keys.left or code == keys.back or code == keys.w and ctrl then
local value = ctrl and ((unicode.sub(data, 1, self.index):find("%s[^%s]+%s*$") or 0) - self.index) or -1
if code == keys.left then
self:move(value)
else
self:update(value)
end
elseif code == keys.right then
self:move(ctrl and ((data:find("%s[^%s]", self.index + 1) or self.len) - self.index) or 1)
elseif code == keys.home then self:move(-self.len)
elseif code == keys["end"] then self:move( self.len)
elseif code == keys.delete then self:update(1)
elseif char >= 32 then self:update(unicode.char(char))
else self.cache = backup_cache -- ignored chars shouldn't clear hint cache
end
end
return true
end
-- echo'd to clear the input text in the tty
core_cursor.vertical.clear = "\27[J"
function core_cursor.new(base, index)
-- if base has defined any methods, those are called first
-- any new methods here are "super" methods to base
base = base or {}
base.super = base.super or index or core_cursor.vertical
setmetatable(base, getmetatable(base) or { __index = base.super })
if not base.data then
base:update()
end
return base
end
function core_cursor.read(cursor)
local last = cursor.next or ""
cursor.next = nil
if #last > 0 then
cursor:handle("clipboard", last)
end
-- address checks
local address_check =
{
key_down = tty.keyboard,
clipboard = tty.keyboard,
touch = tty.screen,
drag = tty.screen,
drop = tty.screen
}
while true do
local next_line = cursor.data:find("\10")
if next_line then
local result = cursor.data:sub(1, next_line)
local overflow = cursor.data:sub(next_line + 1)
local history = text.trim(result)
if history ~= "" and history ~= cursor[1] then
table.insert(cursor, 1, history)
cursor[(tonumber(os.getenv("HISTSIZE")) or 10) + 1] = nil
end
cursor[0] = nil
cursor:update()
cursor.next = overflow
return result
end
cursor:echo()
local pack = table.pack(computer.pullSignal(tty.window.blink and .5 or math.huge))
local name = pack[1]
cursor:echo(not name)
if name then
local filter_address = address_check[name]
if not filter_address or filter_address() == pack[2] then
local ret, why = cursor:handle(name, table.unpack(pack, 3, pack.n))
if not ret then
return ret, why
end
end
end
end
end
require("package").delay(core_cursor, "/lib/core/full_cursor.lua")
return core_cursor

View File

@ -0,0 +1,131 @@
local comp = require("component")
local text = require("text")
local dcache = {}
local pcache = {}
local adapter_pwd = "/lib/core/devfs/adapters/"
local adapter_api = {}
function adapter_api.toArgsPack(input, pack)
local split = text.split(input, {"%s"}, true)
local req = pack[1]
local num = #split
if num < req then return nil, "insufficient args" end
local result = {n=num}
for index=1,num do
local typename = pack[index+1]
local token = split[index]
if typename == "boolean" then
if token ~= "true" and token ~= "false" then return nil, "bad boolean value" end
token = token == "true"
elseif typename == "number" then
token = tonumber(token)
if not token then return nil, "bad number value" end
end
result[index] = token
end
return result
end
function adapter_api.createWriter(callback, ...)
local types = table.pack(...)
return function(input)
local args, why = adapter_api.toArgsPack(input, types)
if not args then return why end
return callback(table.unpack(args, 1, args.n))
end
end
function adapter_api.create_toggle(read, write, switch)
return
{
read = read and function() return tostring(read()) end,
write = write and function(value)
value = text.trim(tostring(value))
local on = value == "1" or value == "true"
local off = value == "0" or value == "false"
if not on and not off then
return nil, "bad value"
end
if switch then
(off and switch or write)()
else
write(on)
end
end
}
end
function adapter_api.make_link(list, addr, prefix, bOmitZero)
prefix = prefix or ""
local zero = bOmitZero and "" or "0"
local id = 0
local name
repeat
name = string.format("%s%s", prefix, id == 0 and zero or tostring(id))
id = id + 1
until not list[name]
list[name] = {link=addr}
end
return
{
components =
{
list = function()
local dirs = {}
local types = {}
local labels = {}
local ads = {}
dirs["by-type"] = {list=function()return types end}
dirs["by-label"] = {list=function()return labels end}
dirs["by-address"] = {list=function()return ads end}
-- first sort the addr, primaries first, then sorted by address lexigraphically
local hw_addresses = {}
for addr,type in comp.list() do
local isPrim = comp.isPrimary(addr)
table.insert(hw_addresses, select(isPrim and 1 or 2, 1, {type,addr}))
end
for _,pair in ipairs(hw_addresses) do
local type, addr = table.unpack(pair)
if not dcache[type] then
local adapter_file = adapter_pwd .. type .. ".lua"
local loader = loadfile(adapter_file, "bt", _G)
dcache[type] = loader and loader(adapter_api)
end
local adapter = dcache[type]
if adapter then
local proxy = pcache[addr] or comp.proxy(addr)
pcache[addr] = proxy
ads[addr] =
{
list = function()
local devfs_proxy = adapter(proxy)
devfs_proxy.address = {proxy.address}
devfs_proxy.slot = {proxy.slot}
devfs_proxy.type = {proxy.type}
devfs_proxy.device = {device=proxy}
return devfs_proxy
end
}
-- by type building
local type_dir = types[type] or {list={}}
adapter_api.make_link(type_dir.list, "../../by-address/"..addr)
types[type] = type_dir
-- by label building (labels are only supported in filesystems
local label = require("devfs").getDeviceLabel(proxy)
if label then
adapter_api.make_link(labels, "../by-address/"..addr, label, true)
end
end
end
return dirs
end
},
}

View File

@ -0,0 +1,57 @@
return
{
eeprom =
{
link = "components/by-type/eeprom/0/contents",
isAvailable = function()
local comp = require("component")
return comp.list("eeprom")()
end
},
["eeprom-data"] =
{
link = "components/by-type/eeprom/0/data",
isAvailable = function()
local comp = require("component")
return comp.list("eeprom")()
end
},
null =
{
open = function()
return
{
read = function() end,
write = function() end
}
end
},
random =
{
open = function(mode)
if mode and not mode:match("r") then
return nil, "read only"
end
return
{
read = function(_, n)
local chars = {}
for _=1,n do
table.insert(chars,string.char(math.random(0,255)))
end
return table.concat(chars)
end
}
end
},
zero =
{
open = function()
return
{
read = function(_, n) return ("\0"):rep(n) end,
write = function() end
}
end
},
}

View File

@ -0,0 +1,9 @@
local adapter_api = ...
return function(proxy)
return
{
beep = {write=adapter_api.createWriter(proxy.beep, 0, "number", "number")},
running = adapter_api.create_toggle(proxy.isRunning, proxy.start, proxy.stop),
}
end

View File

@ -0,0 +1,22 @@
local cache = {}
local function cload(callback)
local c = cache[callback]
if not c then
c = callback()
cache[callback] = c
end
return c
end
return function(proxy)
return
{
contents = {read=proxy.get, write=proxy.set},
data = {read=proxy.getData, write=proxy.setData},
checksum = {read=proxy.getChecksum,size=function() return 8 end},
size = {cload(proxy.getSize)},
dataSize = {cload(proxy.getDataSize)},
label = {write=proxy.setLabel,proxy.getLabel()},
makeReadonly = {write=proxy.makeReadonly}
}
end

View File

@ -0,0 +1,25 @@
local fs = require("filesystem")
local text = require("text")
return function(proxy)
return
{
["label"] =
{
read = function() return proxy.getLabel() or "" end,
write= function(v) proxy.setLabel(text.trim(v)) end
},
["isReadOnly"] = {proxy.isReadOnly()},
["spaceUsed"] = {proxy.spaceUsed()},
["spaceTotal"] = {proxy.spaceTotal()},
["mounts"] = {read = function()
local mounts = {}
for mproxy,mpath in fs.mounts() do
if mproxy.address == proxy.address then
table.insert(mounts, mpath)
end
end
return table.concat(mounts, "\n")
end}
}
end

View File

@ -0,0 +1,17 @@
local adapter_api = ...
return function(proxy)
local screen = proxy.getScreen()
screen = screen and ("../" .. screen)
return
{
viewport = {write = adapter_api.createWriter(proxy.setViewport, 2, "number", "number"), proxy.getViewport()},
resolution = {write = adapter_api.createWriter(proxy.setResolution, 2, "number", "number"), proxy.getResolution()},
maxResolution = {proxy.maxResolution()},
screen = {link=screen,isAvailable=proxy.getScreen},
depth = {write = adapter_api.createWriter(proxy.setDepth, 1, "number"), proxy.getDepth()},
maxDepth = {proxy.maxDepth()},
background = {write = adapter_api.createWriter(proxy.setBackground, 1, "number", "boolean"), proxy.getBackground()},
foreground = {write = adapter_api.createWriter(proxy.setForeground, 1, "number", "boolean"), proxy.getForeground()},
}
end

View File

@ -0,0 +1,7 @@
return function(proxy)
return
{
httpEnabled = {proxy.isHttpEnabled()},
tcpEnabled = {proxy.isTcpEnabled()},
}
end

View File

@ -0,0 +1,11 @@
return function(proxy)
return
{
wakeMessage =
{
read = function() return proxy.getWakeMessage() or "" end,
write= function(msg) return proxy.setWakeMessage(msg) end,
},
wireless = {proxy.isWireless()},
}
end

View File

@ -0,0 +1,18 @@
local adapter_api = ...
return function(proxy)
return
{
["aspectRatio"] = {proxy.getAspectRatio()},
["keyboards"] = {read=function()
local ks = {}
for _,ka in ipairs(proxy.getKeyboards()) do
table.insert(ks, ka)
end
return table.concat(ks, "\n")
end},
["on"] = adapter_api.create_toggle(proxy.isOn, proxy.turnOn, proxy.turnOff), -- turnOn and turnOff
["precise"] = adapter_api.create_toggle(proxy.isPrecise, proxy.setPrecise),
["touchModeInverted"] = adapter_api.create_toggle(proxy.isTouchModeInverted, proxy.setTouchModeInverted),
}
end

View File

@ -0,0 +1,101 @@
local fs = require("filesystem")
local lib = {}
local rules_path = "/etc/udev/rules.d/"
local auto_rules = "autogenerated.lua"
local function fs_key(dir, filename)
local long_name = dir .. '/' .. filename
local segments = fs.segments(long_name)
local result = '/' .. table.concat(segments, '/')
return result
end
function lib.loadRules(root_dir)
checkArg(1, root_dir, "string", "nil")
root_dir = (root_dir or rules_path)
lib.rules = {}
lib.rules[fs_key(root_dir, auto_rules)] = {}
for file in fs.list(root_dir) do
if file:match("%.lua$") then
local path = fs_key(root_dir, file)
local file_handle = io.open(path)
if file_handle then
local load_rule = load("return {" .. file_handle:read("*a") .. "}")
file_handle:close()
if load_rule then
local ok, rule = pcall(load_rule)
if ok and type(rule) == "table" then
local irule = {}
lib.rules[path] = irule
for _,v in ipairs(rule) do
if type(v) == "table" then
table.insert(irule, v)
end
-- else invalid rule
end
end
end
end
end
end
end
function lib.saveRule(rule_set, path)
checkArg(1, rule_set, "table")
checkArg(2, path, "string")
local file = io.open(path, "w")
if not file then return end -- fs may be read only, totally fine, this just won't persist
for index, irule in ipairs(rule_set) do
file:write(require("serialization").serialize(irule), ",\n")
end
file:close()
end
function lib.saveRules(rules)
for path, rule_set in pairs(rules) do
lib.saveRule(rule_set, path)
end
end
local function getIRule(proxy)
checkArg(1, proxy, "table")
for path,rule_set in pairs(lib.rules) do
for index, irule in ipairs(rule_set) do
if irule.address == proxy.address then
return irule, index, rule_set, path
end
end
end
end
function lib.getDeviceLabel(proxy)
local irule = getIRule(proxy)
if irule and irule.label then
return irule.label
elseif proxy.getLabel then
return proxy.getLabel()
end
end
function lib.setDeviceLabel(proxy, label)
local irule, index, rule_set, path = getIRule(proxy)
if not irule then
-- if the device supports labels, use it instead
if proxy.setLabel then
return proxy.setLabel(label)
end
path = fs_key(rules_path, auto_rules)
rule_set = lib.rules[path]
index = #rule_set + 1
irule = {address=proxy.address}
table.insert(rule_set, irule)
end
irule.label = label
lib.saveRule(rule_set, path)
end
return lib

View File

@ -0,0 +1,238 @@
local buffer = require("buffer")
local unicode = require("unicode")
function buffer:getTimeout()
return self.readTimeout
end
function buffer:setTimeout(value)
self.readTimeout = tonumber(value)
end
function buffer:seek(whence, offset)
whence = tostring(whence or "cur")
assert(whence == "set" or whence == "cur" or whence == "end",
"bad argument #1 (set, cur or end expected, got " .. whence .. ")")
offset = offset or 0
checkArg(2, offset, "number")
assert(math.floor(offset) == offset, "bad argument #2 (not an integer)")
if self.mode.w or self.mode.a then
self:flush()
elseif whence == "cur" then
offset = offset - #self.bufferRead
end
local result, reason = self.stream:seek(whence, offset)
if result then
self.bufferRead = ""
return result
else
return nil, reason
end
end
function buffer:buffered_write(arg)
local result, reason
if self.bufferMode == "full" then
if self.bufferSize - #self.bufferWrite < #arg then
result, reason = self:flush()
if not result then
return nil, reason
end
end
if #arg > self.bufferSize then
result, reason = self.stream:write(arg)
else
self.bufferWrite = self.bufferWrite .. arg
result = self
end
else--if self.bufferMode == "line" then
local l
repeat
local idx = arg:find("\n", (l or 0) + 1, true)
if idx then
l = idx
end
until not idx
if l or #arg > self.bufferSize then
result, reason = self:flush()
if not result then
return nil, reason
end
end
if l then
result, reason = self.stream:write(arg:sub(1, l))
if not result then
return nil, reason
end
arg = arg:sub(l + 1)
end
if #arg > self.bufferSize then
result, reason = self.stream:write(arg)
else
self.bufferWrite = self.bufferWrite .. arg
result = self
end
end
return result, reason
end
----------------------------------------------------------------------------------------------
function buffer:readNumber(readChunk)
local len, sub
if self.mode.b then
len = rawlen
sub = string.sub
else
len = unicode.len
sub = unicode.sub
end
local number_text = ""
local white_done
local function peek()
if len(self.bufferRead) == 0 then
local result, reason = readChunk(self)
if not result then
return result, reason
end
end
return sub(self.bufferRead, 1, 1)
end
local function pop()
local n = sub(self.bufferRead, 1, 1)
self.bufferRead = sub(self.bufferRead, 2)
return n
end
while true do
local peeked = peek()
if not peeked then
break
end
if peeked:match("[%s]") then
if white_done then
break
end
pop()
else
white_done = true
if not tonumber(number_text .. peeked .. "0") then
break
end
number_text = number_text .. pop() -- add pop to number_text
end
end
return tonumber(number_text)
end
function buffer:readBytesOrChars(readChunk, n)
n = math.max(n, 0)
local len, sub
if self.mode.b then
len = rawlen
sub = string.sub
else
len = unicode.len
sub = unicode.sub
end
local data = ""
while true do
local current_data_len = len(data)
local needed = n - current_data_len
if needed < 1 then
break
end
-- if the buffer is empty OR there is only 1 char left, read next chunk
-- this is to protect that last byte from bad unicode
if #self.bufferRead == 0 then
local result, reason = readChunk(self)
if not result then
if reason then
return result, reason
else -- eof
return current_data_len > 0 and data or nil
end
end
end
local splice = self.bufferRead
if len(self.bufferRead) > needed then
splice = sub(self.bufferRead, 1, needed)
if len(splice) ~= needed then
-- this can happen if the stream does not represent valid utf8 sequences
-- we could search the string for the bad sequence but regardless, we're going to just return the raw data
splice = self.bufferRead -- yes this is more than the user is asking for, but this is better than corrupting the stream
end
-- else -- we will read more chunks
end
data = data .. splice
self.bufferRead = string.sub(self.bufferRead, #splice + 1)
end
return data
end
function buffer:readAll(readChunk)
repeat
local result, reason = readChunk(self)
if not result and reason then
return result, reason
end
until not result -- eof
local result = self.bufferRead
self.bufferRead = ""
return result
end
function buffer:formatted_read(readChunk, ...)
self.timeout = require("computer").uptime() + self.readTimeout
local function read(n, format)
if type(format) == "number" then
return self:readBytesOrChars(readChunk, format)
else
local first_char_index = 1
if type(format) ~= "string" then
error("bad argument #" .. n .. " (invalid option)")
elseif unicode.sub(format, 1, 1) == "*" then
first_char_index = 2
end
format = unicode.sub(format, first_char_index, first_char_index)
if format == "n" then
return self:readNumber(readChunk)
elseif format == "l" then
return self:readLine(true, self.timeout)
elseif format == "L" then
return self:readLine(false, self.timeout)
elseif format == "a" then
return self:readAll(readChunk)
else
error("bad argument #" .. n .. " (invalid format)")
end
end
end
local results = {}
local formats = table.pack(...)
for i = 1, formats.n do
local result, reason = read(i, formats[i])
if result then
results[i] = result
elseif reason then
return nil, reason
end
end
return table.unpack(results, 1, formats.n)
end
function buffer:size()
local len = self.mode.b and rawlen or unicode.len
local size = len(self.bufferRead)
if self.stream.size then
size = size + self.stream:size()
end
return size
end

View File

@ -0,0 +1,123 @@
local core_cursor = require("core/cursor")
local unicode = require("unicode")
local kb = require("keyboard")
local tty = require("tty")
core_cursor.horizontal = {}
function core_cursor.touch(cursor, gx, gy)
if cursor.len > 0 then
local win = tty.window
gx, gy = gx - win.dx, gy - win.dy
while true do
local x, y, d = win.x, win.y, win.width
local dx = ((gy*d+gx)-(y*d+x))
if dx == 1 then
dx = unicode.wlen(unicode.sub(cursor.data, cursor.index + 1, cursor.index + 1)) == 2 and 0 or dx
end
if dx == 0 then
break
end
cursor:move(dx > 0 and 1 or -1)
if x == win.x and y == win.y then
break
end
end
end
end
function core_cursor.tab(cursor)
local hints = cursor.hint
if not hints then return end
if not cursor.cache then
cursor.cache =
type(hints) == "table" and hints or
hints(cursor.data, cursor.index + 1) or
{}
cursor.cache.i = -1
end
local cache = cursor.cache
if #cache == 1 and cache.i == 0 then
-- there was only one solution, and the user is asking for the next
cursor.cache = hints(cache[1], cursor.index + 1)
if not cursor.cache then return end
cursor.cache.i = -1
cache = cursor.cache
end
local change = kb.isShiftDown() and -1 or 1
cache.i = (cache.i + change) % math.max(#cache, 1)
local next = cache[cache.i + 1]
if next then
local tail = unicode.len(cursor.data) - cursor.index
cursor:move(cursor.len)
cursor:update(-cursor.len)
cursor:update(next, -tail)
end
end
function core_cursor.horizontal:scroll(num, final_index)
self:move(self.vindex - self.index) -- go to left edge
-- shift (v)index by num
self.vindex = self.vindex + num
self.index = self.index + num
self:echo("\0277".. -- remember the location
unicode.sub(self.data, self.index + 1).. -- write part after
"\27[K\0278") -- clear tail and restore left edge
self:move(final_index - self.index) -- move to final_index location
end
function core_cursor.horizontal:echo(arg, num)
local w = tty.window
w.nowrap = self.nowrap
if arg == "" then -- special scroll request
local width = w.width
if w.x >= width then
-- the width that matters depends on the following char width
width = width - math.max(unicode.wlen(unicode.sub(self.data, self.index + 1, self.index + 1)) - 1, 0)
if w.x > width then
local s1 = unicode.sub(self.data, self.vindex + 1, self.index)
self:scroll(unicode.len(unicode.wtrunc(s1, w.x - width + 1)), self.index)
end
end
-- scroll is safe now, return as normal below
elseif arg == kb.keys.left then
if self.index < self.vindex then
local s2 = unicode.sub(self.data, self.index + 1)
w.x = w.x - num + unicode.wlen(unicode.sub(s2, 1, self.vindex - self.index))
local current_x = w.x
self:echo(s2)
w.x = current_x
self.vindex = self.index
return true
end
elseif arg == kb.keys.right then
w.x = w.x + num
return self:echo("") -- scroll
end
return core_cursor.vertical.echo(self, arg, num)
end
function core_cursor.horizontal:update(arg, back)
if back then
-- if we're just going to render arg and move back, and we're not wrapping, just render arg
-- back may be more or less from current x
self:update(arg, false)
local x = tty.window.x
self:echo(arg) -- nowrap echo
tty.window.x = x
self:move(self.len - self.index + back) -- back is negative from end
return true
elseif not arg then -- reset
self.nowrap = true
self.clear = "\27[K"
self.vindex = 0 -- visual/virtual index
end
return core_cursor.vertical.update(self, arg, back)
end
setmetatable(core_cursor.horizontal, { __index = core_cursor.vertical })

View File

@ -0,0 +1,75 @@
local event = require("event")
local function createMultipleFilter(...)
local filter = table.pack(...)
if filter.n == 0 then
return nil
end
return function(...)
local signal = table.pack(...)
if type(signal[1]) ~= "string" then
return false
end
for i = 1, filter.n do
if filter[i] ~= nil and signal[1]:match(filter[i]) then
return true
end
end
return false
end
end
function event.pullMultiple(...)
local seconds
local args
if type(...) == "number" then
seconds = ...
args = table.pack(select(2,...))
for i=1,args.n do
checkArg(i+1, args[i], "string", "nil")
end
else
args = table.pack(...)
for i=1,args.n do
checkArg(i, args[i], "string", "nil")
end
end
return event.pullFiltered(seconds, createMultipleFilter(table.unpack(args, 1, args.n)))
end
function event.cancel(timerId)
checkArg(1, timerId, "number")
if event.handlers[timerId] then
event.handlers[timerId] = nil
return true
end
return false
end
function event.ignore(name, callback)
checkArg(1, name, "string")
checkArg(2, callback, "function")
for id, handler in pairs(event.handlers) do
if handler.key == name and handler.callback == callback then
event.handlers[id] = nil
return true
end
end
return false
end
function event.onError(message)
local log = io.open("/tmp/event.log", "a")
if log then
pcall(log.write, log, tostring(message), "\n")
log:close()
end
end
function event.timer(interval, callback, times)
checkArg(1, interval, "number")
checkArg(2, callback, "function")
checkArg(3, times, "number", "nil")
return event.register(false, callback, interval, times)
end

View File

@ -0,0 +1,363 @@
local filesystem = require("filesystem")
local component = require("component")
local shell = require("shell")
function filesystem.makeDirectory(path)
if filesystem.exists(path) then
return nil, "file or directory with that name already exists"
end
local node, rest = filesystem.findNode(path)
if node.fs and rest then
local success, reason = node.fs.makeDirectory(rest)
if not success and not reason and node.fs.isReadOnly() then
reason = "filesystem is readonly"
end
return success, reason
end
if node.fs then
return nil, "virtual directory with that name already exists"
end
return nil, "cannot create a directory in a virtual directory"
end
function filesystem.lastModified(path)
local node, rest, vnode, vrest = filesystem.findNode(path, false, true)
if not node or not vnode.fs and not vrest then
return 0 -- virtual directory
end
if node.fs and rest then
return node.fs.lastModified(rest)
end
return 0 -- no such file or directory
end
function filesystem.mounts()
local tmp = {}
for path,node in pairs(filesystem.fstab) do
table.insert(tmp, {node.fs,path})
end
return function()
local next = table.remove(tmp)
if next then return table.unpack(next) end
end
end
function filesystem.link(target, linkpath)
checkArg(1, target, "string")
checkArg(2, linkpath, "string")
if filesystem.exists(linkpath) then
return nil, "file already exists"
end
local linkpath_parent = filesystem.path(linkpath)
if not filesystem.exists(linkpath_parent) then
return nil, "no such directory"
end
local linkpath_real, reason = filesystem.realPath(linkpath_parent)
if not linkpath_real then
return nil, reason
end
if not filesystem.isDirectory(linkpath_real) then
return nil, "not a directory"
end
local _, _, vnode, _ = filesystem.findNode(linkpath_real, true)
vnode.links[filesystem.name(linkpath)] = target
return true
end
function filesystem.umount(fsOrPath)
checkArg(1, fsOrPath, "string", "table")
local real
local fs
local addr
if type(fsOrPath) == "string" then
real = filesystem.realPath(fsOrPath)
addr = fsOrPath
else -- table
fs = fsOrPath
end
local paths = {}
for path,node in pairs(filesystem.fstab) do
if real == path or addr == node.fs.address or fs == node.fs then
table.insert(paths, path)
end
end
for _,path in ipairs(paths) do
local node = filesystem.fstab[path]
filesystem.fstab[path] = nil
node.fs = nil
node.parent.children[node.name] = nil
end
return #paths > 0
end
function filesystem.size(path)
local node, rest, vnode, vrest = filesystem.findNode(path, false, true)
if not node or not vnode.fs and (not vrest or vnode.links[vrest]) then
return 0 -- virtual directory or symlink
end
if node.fs and rest then
return node.fs.size(rest)
end
return 0 -- no such file or directory
end
function filesystem.isLink(path)
local name = filesystem.name(path)
local node, rest, vnode, vrest = filesystem.findNode(filesystem.path(path), false, true)
if not node then return nil, rest end
local target = vnode.links[name]
-- having vrest here indicates we are not at the
-- owning vnode due to a mount point above this point
-- but we can have a target when there is a link at
-- the mount point root, with the same name
if not vrest and target ~= nil then
return true, target
end
return false
end
function filesystem.copy(fromPath, toPath)
local data = false
local input, reason = filesystem.open(fromPath, "rb")
if input then
local output = filesystem.open(toPath, "wb")
if output then
repeat
data, reason = input:read(1024)
if not data then break end
data, reason = output:write(data)
if not data then data, reason = false, "failed to write" end
until not data
output:close()
end
input:close()
end
return data == nil, reason
end
local function readonly_wrap(proxy)
checkArg(1, proxy, "table")
if proxy.isReadOnly() then
return proxy
end
local function roerr() return nil, "filesystem is readonly" end
return setmetatable({
rename = roerr,
open = function(path, mode)
checkArg(1, path, "string")
checkArg(2, mode, "string")
if mode:match("[wa]") then
return roerr()
end
return proxy.open(path, mode)
end,
isReadOnly = function()
return true
end,
write = roerr,
setLabel = roerr,
makeDirectory = roerr,
remove = roerr,
}, {__index=proxy})
end
local function bind_proxy(path)
local real, reason = filesystem.realPath(path)
if not real then
return nil, reason
end
if not filesystem.isDirectory(real) then
return nil, "must bind to a directory"
end
local real_fs, real_fs_path = filesystem.get(real)
if real == real_fs_path then
return real_fs
end
-- turn /tmp/foo into foo
local rest = real:sub(#real_fs_path + 1)
local function wrap_relative(fp)
return function(mpath, ...)
return fp(filesystem.concat(rest, mpath), ...)
end
end
local bind = {
type = "filesystem_bind",
address = real,
isReadOnly = real_fs.isReadOnly,
list = wrap_relative(real_fs.list),
isDirectory = wrap_relative(real_fs.isDirectory),
size = wrap_relative(real_fs.size),
lastModified = wrap_relative(real_fs.lastModified),
exists = wrap_relative(real_fs.exists),
open = wrap_relative(real_fs.open),
remove = wrap_relative(real_fs.remove),
read = real_fs.read,
write = real_fs.write,
close = real_fs.close,
getLabel = function() return "" end,
setLabel = function() return nil, "cannot set the label of a bind point" end,
}
return bind
end
filesystem.internal = {}
function filesystem.internal.proxy(filter, options)
checkArg(1, filter, "string")
checkArg(2, options, "table", "nil")
options = options or {}
local address, proxy, reason
if options.bind then
proxy, reason = bind_proxy(filter)
else
-- no options: filter should be a label or partial address
for c in component.list("filesystem", true) do
if component.invoke(c, "getLabel") == filter then
address = c
break
end
if c:sub(1, filter:len()) == filter then
address = c
break
end
end
if not address then
return nil, "no such file system"
end
proxy, reason = component.proxy(address)
end
if not proxy then
return proxy, reason
end
if options.readonly then
proxy = readonly_wrap(proxy)
end
return proxy
end
function filesystem.remove(path)
local function removeVirtual()
local _, _, vnode, vrest = filesystem.findNode(filesystem.path(path), false, true)
-- vrest represents the remaining path beyond vnode
-- vrest is nil if vnode reaches the full path
-- thus, if vrest is NOT NIL, then we SHOULD NOT remove children nor links
if not vrest then
local name = filesystem.name(path)
if vnode.children[name] or vnode.links[name] then
vnode.children[name] = nil
vnode.links[name] = nil
while vnode and vnode.parent and not vnode.fs and not next(vnode.children) and not next(vnode.links) do
vnode.parent.children[vnode.name] = nil
vnode = vnode.parent
end
return true
end
end
-- return false even if vrest is nil because this means it was a expected
-- to be a real file
return false
end
local function removePhysical()
local node, rest = filesystem.findNode(path)
if node.fs and rest then
return node.fs.remove(rest)
end
return false
end
local success = removeVirtual()
success = removePhysical() or success -- Always run.
if success then return true
else return nil, "no such file or directory"
end
end
function filesystem.rename(oldPath, newPath)
if filesystem.isLink(oldPath) then
local _, _, vnode, _ = filesystem.findNode(filesystem.path(oldPath))
local target = vnode.links[filesystem.name(oldPath)]
local result, reason = filesystem.link(target, newPath)
if result then
filesystem.remove(oldPath)
end
return result, reason
else
local oldNode, oldRest = filesystem.findNode(oldPath)
local newNode, newRest = filesystem.findNode(newPath)
if oldNode.fs and oldRest and newNode.fs and newRest then
if oldNode.fs.address == newNode.fs.address then
return oldNode.fs.rename(oldRest, newRest)
else
local result, reason = filesystem.copy(oldPath, newPath)
if result then
return filesystem.remove(oldPath)
else
return nil, reason
end
end
end
return nil, "trying to read from or write to virtual directory"
end
end
local isAutorunEnabled = nil
local function saveConfig()
local root = filesystem.get("/")
if root and not root.isReadOnly() then
local f = filesystem.open("/etc/filesystem.cfg", "w")
if f then
f:write("autorun="..tostring(isAutorunEnabled))
f:close()
end
end
end
function filesystem.isAutorunEnabled()
if isAutorunEnabled == nil then
local env = {}
local config = loadfile("/etc/filesystem.cfg", nil, env)
if config then
pcall(config)
isAutorunEnabled = not not env.autorun
else
isAutorunEnabled = true
end
saveConfig()
end
return isAutorunEnabled
end
function filesystem.setAutorunEnabled(value)
checkArg(1, value, "boolean")
isAutorunEnabled = value
saveConfig()
end
-- luacheck: globals os
os.remove = filesystem.remove
os.rename = filesystem.rename
os.execute = function(command)
if not command then
return type(shell) == "table"
end
return shell.execute(command)
end
function os.exit(code)
error({reason="terminated", code=code}, 0)
end
function os.tmpname()
local path = os.getenv("TMPDIR") or "/tmp"
if filesystem.exists(path) then
for _ = 1, 10 do
local name = filesystem.concat(path, tostring(math.random(1, 0x7FFFFFFF)))
if not filesystem.exists(name) then
return name
end
end
end
end

View File

@ -0,0 +1,143 @@
local keyboard = require("keyboard")
keyboard.keys["1"] = 0x02
keyboard.keys["2"] = 0x03
keyboard.keys["3"] = 0x04
keyboard.keys["4"] = 0x05
keyboard.keys["5"] = 0x06
keyboard.keys["6"] = 0x07
keyboard.keys["7"] = 0x08
keyboard.keys["8"] = 0x09
keyboard.keys["9"] = 0x0A
keyboard.keys["0"] = 0x0B
keyboard.keys.a = 0x1E
keyboard.keys.b = 0x30
keyboard.keys.c = 0x2E
keyboard.keys.d = 0x20
keyboard.keys.e = 0x12
keyboard.keys.f = 0x21
keyboard.keys.g = 0x22
keyboard.keys.h = 0x23
keyboard.keys.i = 0x17
keyboard.keys.j = 0x24
keyboard.keys.k = 0x25
keyboard.keys.l = 0x26
keyboard.keys.m = 0x32
keyboard.keys.n = 0x31
keyboard.keys.o = 0x18
keyboard.keys.p = 0x19
keyboard.keys.q = 0x10
keyboard.keys.r = 0x13
keyboard.keys.s = 0x1F
keyboard.keys.t = 0x14
keyboard.keys.u = 0x16
keyboard.keys.v = 0x2F
keyboard.keys.w = 0x11
keyboard.keys.x = 0x2D
keyboard.keys.y = 0x15
keyboard.keys.z = 0x2C
keyboard.keys.apostrophe = 0x28
keyboard.keys.at = 0x91
keyboard.keys.back = 0x0E -- backspace
keyboard.keys.backslash = 0x2B
keyboard.keys.capital = 0x3A -- capslock
keyboard.keys.colon = 0x92
keyboard.keys.comma = 0x33
keyboard.keys.enter = 0x1C
keyboard.keys.equals = 0x0D
keyboard.keys.grave = 0x29 -- accent grave
keyboard.keys.lbracket = 0x1A
keyboard.keys.lcontrol = 0x1D
keyboard.keys.lmenu = 0x38 -- left Alt
keyboard.keys.lshift = 0x2A
keyboard.keys.minus = 0x0C
keyboard.keys.numlock = 0x45
keyboard.keys.pause = 0xC5
keyboard.keys.period = 0x34
keyboard.keys.rbracket = 0x1B
keyboard.keys.rcontrol = 0x9D
keyboard.keys.rmenu = 0xB8 -- right Alt
keyboard.keys.rshift = 0x36
keyboard.keys.scroll = 0x46 -- Scroll Lock
keyboard.keys.semicolon = 0x27
keyboard.keys.slash = 0x35 -- / on main keyboard
keyboard.keys.space = 0x39
keyboard.keys.stop = 0x95
keyboard.keys.tab = 0x0F
keyboard.keys.underline = 0x93
-- Keypad (and numpad with numlock off)
keyboard.keys.up = 0xC8
keyboard.keys.down = 0xD0
keyboard.keys.left = 0xCB
keyboard.keys.right = 0xCD
keyboard.keys.home = 0xC7
keyboard.keys["end"] = 0xCF
keyboard.keys.pageUp = 0xC9
keyboard.keys.pageDown = 0xD1
keyboard.keys.insert = 0xD2
keyboard.keys.delete = 0xD3
-- Function keys
keyboard.keys.f1 = 0x3B
keyboard.keys.f2 = 0x3C
keyboard.keys.f3 = 0x3D
keyboard.keys.f4 = 0x3E
keyboard.keys.f5 = 0x3F
keyboard.keys.f6 = 0x40
keyboard.keys.f7 = 0x41
keyboard.keys.f8 = 0x42
keyboard.keys.f9 = 0x43
keyboard.keys.f10 = 0x44
keyboard.keys.f11 = 0x57
keyboard.keys.f12 = 0x58
keyboard.keys.f13 = 0x64
keyboard.keys.f14 = 0x65
keyboard.keys.f15 = 0x66
keyboard.keys.f16 = 0x67
keyboard.keys.f17 = 0x68
keyboard.keys.f18 = 0x69
keyboard.keys.f19 = 0x71
-- Japanese keyboards
keyboard.keys.kana = 0x70
keyboard.keys.kanji = 0x94
keyboard.keys.convert = 0x79
keyboard.keys.noconvert = 0x7B
keyboard.keys.yen = 0x7D
keyboard.keys.circumflex = 0x90
keyboard.keys.ax = 0x96
-- Numpad
keyboard.keys.numpad0 = 0x52
keyboard.keys.numpad1 = 0x4F
keyboard.keys.numpad2 = 0x50
keyboard.keys.numpad3 = 0x51
keyboard.keys.numpad4 = 0x4B
keyboard.keys.numpad5 = 0x4C
keyboard.keys.numpad6 = 0x4D
keyboard.keys.numpad7 = 0x47
keyboard.keys.numpad8 = 0x48
keyboard.keys.numpad9 = 0x49
keyboard.keys.numpadmul = 0x37
keyboard.keys.numpaddiv = 0xB5
keyboard.keys.numpadsub = 0x4A
keyboard.keys.numpadadd = 0x4E
keyboard.keys.numpaddecimal = 0x53
keyboard.keys.numpadcomma = 0xB3
keyboard.keys.numpadenter = 0x9C
keyboard.keys.numpadequals = 0x8D
-- Create inverse mapping for name lookup.
setmetatable(keyboard.keys,
{
__index = function(tbl, k)
if type(k) ~= "number" then return end
for name,value in pairs(tbl) do
if value == k then
return name
end
end
end
})

View File

@ -0,0 +1,366 @@
local fs = require("filesystem")
local shell = require("shell")
local tty = require("tty")
local unicode = require("unicode")
local tx = require("transforms")
local text = require("text")
local dirsArg, ops = shell.parse(...)
if ops.help then
print([[Usage: ls [OPTION]... [FILE]...
-a, --all do not ignore entries starting with .
--full-time with -l, print time in full iso format
-h, --human-readable with -l and/or -s, print human readable sizes
--si likewise, but use powers of 1000 not 1024
-l use a long listing format
-r, --reverse reverse order while sorting
-R, --recursive list subdirectories recursively
-S sort by file size
-t sort by modification time, newest first
-X sort alphabetically by entry extension
-1 list one file per line
-p append / indicator to directories
-M display Microsoft-style file and directory
count after listing
--no-color Do not colorize the output (default colorized)
--help display this help and exit
For more info run: man ls]])
return 0
end
if #dirsArg == 0 then
table.insert(dirsArg, ".")
end
local ec = 0
local fOut = tty.isAvailable() and io.output().tty
local function perr(msg) io.stderr:write(msg,"\n") ec = 2 end
local set_color = function() end
local function colorize() return end
if fOut and not ops["no-color"] then
local LSC = tx.foreach(text.split(os.getenv("LS_COLORS") or "", {":"}, true), function(e)
local parts = text.split(e, {"="}, true)
return parts[2], parts[1]
end)
colorize = function(info)
return
info.isLink and LSC.ln or
info.isDir and LSC.di or
LSC['*'..info.ext] or
LSC.fi
end
set_color=function(c)
io.write(string.char(0x1b), "[", c or "", "m")
end
end
local msft={reports=0,proxies={}}
function msft.report(files, dirs, used, proxy)
local free = proxy.spaceTotal() - proxy.spaceUsed()
set_color()
local pattern = "%5i File(s) %s bytes\n%5i Dir(s) %11s bytes free\n"
io.write(string.format(pattern, files, tostring(used), dirs, tostring(free)))
end
function msft.tail(names)
local fsproxy = fs.get(names.path)
if not fsproxy then
return
end
local totalSize, totalFiles, totalDirs = 0, 0, 0
for i=1,#names do
local info = names[i]
if info.isDir then
totalDirs = totalDirs + 1
else
totalFiles = totalFiles + 1
end
totalSize = totalSize + info.size
end
msft.report(totalFiles, totalDirs, totalSize, fsproxy)
local ps = msft.proxies
ps[fsproxy] = ps[fsproxy] or {files=0,dirs=0,used=0}
local p = ps[fsproxy]
p.files = p.files + totalFiles
p.dirs = p.dirs + totalDirs
p.used = p.used + totalSize
msft.reports = msft.reports + 1
end
function msft.final()
if msft.reports < 2 then return end
local groups = {}
for proxy,report in pairs(msft.proxies) do
table.insert(groups, {proxy=proxy,report=report})
end
set_color()
print("Total Files Listed:")
for _,pair in ipairs(groups) do
local proxy, report = pair.proxy, pair.report
if #groups>1 then
print("As pertaining to: "..proxy.address)
end
msft.report(report.files, report.dirs, report.used, proxy)
end
end
if not ops.M then
msft.tail=function()end
msft.final=function()end
end
local function nod(n)
return n and (tostring(n):gsub("(%.[0-9]+)0+$","%1")) or "0"
end
local function formatSize(size)
if not ops.h and not ops['human-readable'] and not ops.si then
return tostring(size)
end
local sizes = {"", "K", "M", "G"}
local unit = 1
local power = ops.si and 1000 or 1024
while size > power and unit < #sizes do
unit = unit + 1
size = size / power
end
return nod(math.floor(size*10)/10)..sizes[unit]
end
local function pad(txt)
txt = tostring(txt)
return #txt >= 2 and txt or "0"..txt
end
local function formatDate(epochms)
--local day_names={"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}
local month_names={"January","February","March","April","May","June","July","August","September","October","November","December"}
if epochms == 0 then return "" end
local d = os.date("*t", epochms)
local day, hour, min, sec = nod(d.day), pad(nod(d.hour)), pad(nod(d.min)), pad(nod(d.sec))
if ops["full-time"] then
return string.format("%s-%s-%s %s:%s:%s ", d.year, pad(nod(d.month)), pad(day), hour, min, sec)
else
return string.format("%s %2s %2s:%2s ", month_names[d.month]:sub(1,3), day, hour, pad(min))
end
end
local function filter(names)
if ops.a then
return names
end
local set = { path = names.path }
for _, info in ipairs(names) do
if fs.name(info.name):sub(1, 1) ~= "." then
set[#set + 1] = info
end
end
return set
end
local function sort(names)
local once = false
local function ni(v)
for i=1,#names do
local info = names[i]
if info.key == v.key then
return i
end
end
end
local function sorter(key)
once = true
table.sort(names, function(a, b)
local ast = names[ni(a)]
local bst = names[ni(b)]
return ast[key] > bst[key]
end)
end
if ops.t then sorter("time") end
if ops.X then sorter("ext") end
if ops.S then sorter("size") end
local rev = ops.r or ops.reverse
if not once then sorter("sort_name") rev=not rev end
if rev then
for i=1,#names/2 do
names[i], names[#names - i + 1] = names[#names - i + 1], names[i]
end
end
return names
end
local function dig(names, dirs, dir)
if ops.R then
local di = 1
for i=1,#names do
local info = names[i]
if info.isDir then
local path = dir..(dir:sub(-1) == "/" and "" or "/")
table.insert(dirs, di, path..info.name)
di = di + 1
end
end
end
return names
end
local first_display = true
local function display(names)
local mt={}
local lines = setmetatable({}, mt)
if ops.l then
lines.n = #names
local max_size_width = 1
local max_date_width = 0
for i=1,lines.n do
local info = names[i]
max_size_width = math.max(max_size_width, formatSize(info.size):len())
max_date_width = math.max(max_date_width, formatDate(info.time):len())
end
mt.__index = function(_, index)
local info = names[index]
local file_type = info.isLink and 'l' or info.isDir and 'd' or 'f'
local link_target = info.isLink and string.format(" -> %s", info.link:gsub("/+$", "") .. (info.isDir and "/" or "")) or ""
local write_mode = info.fs.isReadOnly() and '-' or 'w'
local size = formatSize(info.size)
local modDate = formatDate(info.time)
local format = "%s-r%s %"..tostring(max_size_width).."s %"..tostring(max_date_width).."s"
local meta = string.format(format, file_type, write_mode, size, modDate)
local item = info.name..link_target
return {{name = meta}, {color = colorize(info), name = item}}
end
elseif ops["1"] or not fOut then
lines.n = #names
mt.__index = function(_, index)
local info = names[index]
return {{color = colorize(info), name = info.name}}
end
else -- columns
local num_columns, items_per_column, width = 0, 0, tty.getViewport() - 1
local function real(x, y)
local index = y + ((x-1) * items_per_column)
return index <= #names and index or nil
end
local function max_name(column_index)
local max = 0 -- return the width of the max element in column_index
for r=1,items_per_column do
local ri = real(column_index, r)
if not ri then break end
local info = names[ri]
max = math.max(max, unicode.wlen(info.name))
end
return max
end
local function measure()
local total = 0
for column_index=1,num_columns do
total = total + max_name(column_index) + (column_index > 1 and 2 or 0)
end
return total
end
while items_per_column<#names do
items_per_column = items_per_column + 1
num_columns = math.ceil(#names/items_per_column)
if measure() < width then
break
end
end
lines.n = items_per_column
mt.__index=function(_, line_index)
return setmetatable({},{
__len=function()return num_columns end,
__index=function(_, column_index)
local ri = real(column_index, line_index)
if not ri then return end
local info = names[ri]
local name = info.name
return {color = colorize(info), name = name .. string.rep(' ', max_name(column_index) - unicode.wlen(name) + (column_index < num_columns and 2 or 0))}
end,
})
end
end
for line_index=1,lines.n do
local line = lines[line_index]
for element_index=1,#line do
local e = line[element_index]
if not e then break end
first_display = false
set_color(e.color)
io.write(e.name)
end
set_color()
print()
end
msft.tail(names)
end
local header = function() end
if #dirsArg > 1 or ops.R then
header = function(path)
if not first_display then print() end
set_color()
io.write(path,":\n")
end
end
local function stat(path, name)
local info = {}
info.key = name
info.path = name:sub(1, 1) == "/" and "" or path
info.full_path = fs.concat(info.path, name)
info.isDir = fs.isDirectory(info.full_path)
info.name = name:gsub("/+$", "") .. (ops.p and info.isDir and "/" or "")
info.sort_name = info.name:gsub("^%.","")
info.isLink, info.link = fs.isLink(info.full_path)
info.size = info.isLink and 0 or fs.size(info.full_path)
info.time = fs.lastModified(info.full_path)/1000
info.fs = fs.get(info.full_path)
info.ext = info.name:match("(%.[^.]+)$") or ""
return info
end
local function displayDirList(dirs)
while #dirs > 0 do
local dir = table.remove(dirs, 1)
header(dir)
local path = shell.resolve(dir)
local list, reason = fs.list(path)
if not list then
perr(reason)
else
local names = { path = path }
for name in list do
names[#names + 1] = stat(path, name)
end
display(dig(sort(filter(names)), dirs, dir))
end
end
end
local dir_set, file_set = {}, {path=shell.getWorkingDirectory()}
for _,dir in ipairs(dirsArg) do
local path = shell.resolve(dir)
local real, why = fs.realPath(path)
local access_msg = "cannot access " .. tostring(path) .. ": "
if not real then
perr(access_msg .. why)
elseif not fs.exists(path) then
perr(access_msg .. "No such file or directory")
elseif fs.isDirectory(path) then
table.insert(dir_set, dir)
else -- file or link
table.insert(file_set, stat(dir, dir))
end
end
io.output():setvbuf("line")
local ok, msg = pcall(function()
if #file_set > 0 then display(sort(file_set)) end
displayDirList(dir_set)
msft.final()
end)
io.output():flush()
io.output():setvbuf("no")
assert(ok, msg)
return ec

Some files were not shown because too many files have changed in this diff Show More