diff --git a/sys/apis/fs/redfs.lua b/sys/apis/fs/redfs.lua new file mode 100644 index 0000000..66c97f7 --- /dev/null +++ b/sys/apis/fs/redfs.lua @@ -0,0 +1,57 @@ +--[[ + Mount a readonly file system from another computer across rednet. The + target computer must be running OpusOS or redserver. + + Syntax: + rn:///directory/subdir + + Examples: + rn://12/usr/etc + rn://8/usr +]]-- + +local rttp = require('rttp') + +local fs = _G.fs + +local redfs = { } + +local function getListing(uri) + local success, response = rttp.get(uri .. '?recursive=true') + + if not success then + error(response) + end + + if response.statusCode ~= 200 then + error('Received response ' .. response.statusCode) + end + + local list = { } + for _,v in pairs(response.data) do + if not v.isDir then + list[v.path] = { + url = uri .. '/' .. v.path, + size = v.size, + } + end + end + + return list +end + +function redfs.mount(dir, uri) + if not uri then + error('redfs syntax: uri') + end + + local list = getListing(uri) + for path, entry in pairs(list) do + if not fs.exists(fs.combine(dir, path)) then + local node = fs.mount(fs.combine(dir, path), 'urlfs', entry.url) + node.size = entry.size + end + end +end + +return redfs diff --git a/sys/apis/fs/urlfs.lua b/sys/apis/fs/urlfs.lua index 1c3c60d..c0305b4 100644 --- a/sys/apis/fs/urlfs.lua +++ b/sys/apis/fs/urlfs.lua @@ -1,3 +1,4 @@ +local rttp = require('rttp') local Util = require('util') local fs = _G.fs @@ -50,7 +51,12 @@ function urlfs.open(node, fn, fl) local c = node.cache if not c then - c = Util.httpGet(node.url) + if node.url:match('^([%w][%w%+%-%.]*)%:') == 'rn' then + local s, response = rttp.get(node.url) + c = s and response.statusCode == 200 and response.data + else + c = Util.httpGet(node.url) + end if c then node.cache = c node.size = #c diff --git a/sys/apis/rttp.lua b/sys/apis/rttp.lua new file mode 100644 index 0000000..8696e9e --- /dev/null +++ b/sys/apis/rttp.lua @@ -0,0 +1,95 @@ +local device = _G.device +local os = _G.os + +local rttp = { } +local computerId = os.getComputerID() + +local function parse(url, default) + -- initialize default parameters + local parsed = {} + local authority + + for i,v in pairs(default or parsed) do parsed[i] = v end + -- remove whitespace + -- url = string.gsub(url, "%s", "") + -- Decode unreserved characters + url = string.gsub(url, "%%(%x%x)", function(hex) + local char = string.char(tonumber(hex, 16)) + if string.match(char, "[a-zA-Z0-9._~-]") then + return char + end + -- Hex encodings that are not unreserved must be preserved. + return nil + end) + -- get fragment + url = string.gsub(url, "#(.*)$", function(f) + parsed.fragment = f + return "" + end) + -- get scheme. Lower-case according to RFC 3986 section 3.1. + url = string.gsub(url, "^(%w[%w.+-]*):", + function(s) parsed.scheme = string.lower(s); return "" end) + -- get authority + url = string.gsub(url, "^//([^/]*)", function(n) + authority = n + return "" + end) + -- get query stringing + url = string.gsub(url, "%?(.*)", function(q) + parsed.query = q + return "" + end) + -- get params + url = string.gsub(url, "%;(.*)", function(p) + parsed.params = p + return "" + end) + + -- path is whatever was left + parsed.path = url + + -- Represents host:port, port = nil if not used. + if authority then + authority = string.gsub(authority, ":(%d+)$", + function(p) parsed.port = tonumber(p); return "" end) + if authority ~= "" then + parsed.host = authority + end + end + return parsed +end + +function rttp.get(url) + local modem = device.wireless_modem or error('Modem not found') + local parsed = parse(url, { port = 80 }) + + parsed.host = tonumber(parsed.host) or error('Invalid url') + + for i = 16384, 32767 do + if not modem.isOpen(i) then + modem.open(i) + local path = parsed.query and parsed.path .. '?' .. parsed.query or parsed.path + + modem.transmit(parsed.port, parsed.host, { + method = 'GET', + replyAddress = computerId, + replyPort = i, + path = path, + }) + local timerId = os.startTimer(3) + repeat + local event, id, dport, dhost, response = os.pullEvent() + if event == 'modem_message' and + dport == i and + dhost == computerId and + type(response) == 'table' then + modem.close(i) + return true, response + end + until event == 'timer' and id == timerId + return false, 'timeout' + end + end +end + +return rttp diff --git a/sys/boot/opus.boot b/sys/boot/opus.boot index ef0e8ed..32c7010 100644 --- a/sys/boot/opus.boot +++ b/sys/boot/opus.boot @@ -2,7 +2,7 @@ local fs = _G.fs local http = _G.http -_G.OPUS_BRANCH = 'master-1.8' +_G.OPUS_BRANCH = 'develop-1.8' local GIT_REPO = 'kepler155c/opus/' .. _G.OPUS_BRANCH local BASE = 'https://raw.githubusercontent.com/' .. GIT_REPO diff --git a/sys/extensions/2.vfs.lua b/sys/extensions/2.vfs.lua index 2cf39ab..3558c60 100644 --- a/sys/extensions/2.vfs.lua +++ b/sys/extensions/2.vfs.lua @@ -149,6 +149,7 @@ function fs.complete(partial, dir, includeFiles, includeSlash) end function fs.listEx(dir) + dir = fs.combine(dir, '') local node = getNode(dir) if node.fs.listEx then return node.fs.listEx(node, dir) diff --git a/sys/kernel.lua b/sys/kernel.lua index 7ff088e..2f5b457 100644 --- a/sys/kernel.lua +++ b/sys/kernel.lua @@ -252,7 +252,7 @@ local function init(...) local files = fs.list(dir) table.sort(files) for _,file in ipairs(files) do - local level = file:match('(%d).%S+.lua') + local level = file:match('(%d).%S+.lua') or 99 if tonumber(level) <= runLevel then local s, m = shell.run(fs.combine(dir, file)) if not s then diff --git a/sys/network/redserver.lua b/sys/network/redserver.lua new file mode 100644 index 0000000..f4f9351 --- /dev/null +++ b/sys/network/redserver.lua @@ -0,0 +1,109 @@ +local Event = require('event') +local Util = require('util') + +local fs = _G.fs +local modem = _G.device.wireless_modem +local os = _G.os + +local computerId = os.getComputerID() + +modem.open(80) + +-- https://github.com/golgote/neturl/blob/master/lib/net/url.lua +local function parseQuery(str) + local sep = '&' + + local values = {} + for key,val in str:gmatch(string.format('([^%q=]+)(=*[^%q=]*)', sep, sep)) do + --local key = decode(key) + local keys = {} + key = key:gsub('%[([^%]]*)%]', function(v) + -- extract keys between balanced brackets + if string.find(v, "^-?%d+$") then + v = tonumber(v) + --else + --v = decode(v) + end + table.insert(keys, v) + return "=" + end) + key = key:gsub('=+.*$', "") + key = key:gsub('%s', "_") -- remove spaces in parameter name + val = val:gsub('^=+', "") + + if not values[key] then + values[key] = {} + end + if #keys > 0 and type(values[key]) ~= 'table' then + values[key] = {} + elseif #keys == 0 and type(values[key]) == 'table' then + values[key] = val --decode(val) + end + + local t = values[key] + for i,k in ipairs(keys) do + if type(t) ~= 'table' then + t = {} + end + if k == "" then + k = #t+1 + end + if not t[k] then + t[k] = {} + end + if i == #keys then + t[k] = val --decode(val) + end + t = t[k] + end + end + return values +end + +local function getListing(path, recursive) + local list = { } + local function listing(p) + for _, f in pairs(fs.listEx(p)) do + local abs = fs.combine(p, f.name) + table.insert(list, { + isDir = f.isDir, + path = string.sub(abs, #path + 1), + size = f.size, + }) + if recursive and f.isDir then + listing(abs) + end + end + end + listing(path) + return list +end + +Event.on('modem_message', function(_, _, dport, dhost, request) + if dport == 80 and dhost == computerId and type(request) == 'table' then + if request.method == 'GET' then + local query + local path = request.path:gsub('%?(.*)', function(v) + query = parseQuery(v) + return '' + end) + if fs.isDir(path) then + modem.transmit(request.replyPort, request.replyAddress, { + statusCode = 200, + contentType = 'table/directory', + data = getListing(path, query and query.recursive == 'true'), + }) + elseif fs.exists(path) then + modem.transmit(request.replyPort, request.replyAddress, { + statusCode = 200, + contentType = 'table/file', + data = Util.readFile(path), + }) + else + modem.transmit(request.replyPort, request.replyAddress, { + statusCode = 404, + }) + end + end + end +end) diff --git a/sys/network/transport.lua b/sys/network/transport.lua index 57fe323..77342c2 100644 --- a/sys/network/transport.lua +++ b/sys/network/transport.lua @@ -74,7 +74,7 @@ Event.on('timer', function(_, timerId) end) Event.on('modem_message', function(_, _, dport, dhost, msg, distance) - if dhost == computerId and msg then + if dhost == computerId and type(msg) == 'table' then local socket = transport.sockets[dport] if socket and socket.connected then