forked from Hyperion/HyperionOS
Path traversal fixes, 26_tty removal, ctrl+key fixes
This commit is contained in:
@@ -810,6 +810,160 @@ builtinCmds.df = function(...)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function listDir(dir, prefix)
|
||||||
|
local ok, entries = pcall(syscall.listdir, dir)
|
||||||
|
if not ok or not entries then return {} end
|
||||||
|
local results = {}
|
||||||
|
for _, e in ipairs(entries) do
|
||||||
|
if prefix == "" or e:sub(1, #prefix) == prefix then
|
||||||
|
local fullpath = (dir == "/" and "/" or dir.."/")..e
|
||||||
|
local t = syscall.type(fullpath)
|
||||||
|
results[#results+1] = t == "directory" and (e.."/") or e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.sort(results)
|
||||||
|
return results
|
||||||
|
end
|
||||||
|
|
||||||
|
local function listCommands(prefix)
|
||||||
|
local results = {}
|
||||||
|
local seen = {}
|
||||||
|
for name in pairs(builtinCmds) do
|
||||||
|
if prefix == "" or name:sub(1, #prefix) == prefix then
|
||||||
|
if not seen[name] then results[#results+1] = name; seen[name] = true end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local paths = string.split(syscall.getEnviron("PATH") or "/bin/", ":")
|
||||||
|
for _, p in ipairs(paths) do
|
||||||
|
local ok, entries = pcall(syscall.listdir, p)
|
||||||
|
if ok and entries then
|
||||||
|
for _, e in ipairs(entries) do
|
||||||
|
local fullpath = (p:sub(-1)=="/" and p or p.."/")..e
|
||||||
|
local xok = pcall(syscall.access, fullpath, "x")
|
||||||
|
if xok and (prefix == "" or e:sub(1, #prefix) == prefix) then
|
||||||
|
if not seen[e] then results[#results+1] = e; seen[e] = true end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.sort(results)
|
||||||
|
return results
|
||||||
|
end
|
||||||
|
|
||||||
|
local function commonPrefix(list)
|
||||||
|
if #list == 0 then return "" end
|
||||||
|
local pre = list[1]
|
||||||
|
for i = 2, #list do
|
||||||
|
local s = list[i]
|
||||||
|
local j = 1
|
||||||
|
while j <= #pre and j <= #s and pre:sub(j,j) == s:sub(j,j) do j = j+1 end
|
||||||
|
pre = pre:sub(1, j-1)
|
||||||
|
if pre == "" then return "" end
|
||||||
|
end
|
||||||
|
return pre
|
||||||
|
end
|
||||||
|
|
||||||
|
local function showCompletions(completions)
|
||||||
|
syscall.write(1, "\n")
|
||||||
|
local W = 51
|
||||||
|
local maxlen = 0
|
||||||
|
for _, c in ipairs(completions) do if #c > maxlen then maxlen = #c end end
|
||||||
|
local colw = maxlen + 2
|
||||||
|
local cols = math.max(1, math.floor(W / colw))
|
||||||
|
local i = 0
|
||||||
|
for _, c in ipairs(completions) do
|
||||||
|
local padded = c .. string.rep(" ", colw - #c)
|
||||||
|
syscall.write(1, padded)
|
||||||
|
i = i + 1
|
||||||
|
if i % cols == 0 then syscall.write(1, "\n") end
|
||||||
|
end
|
||||||
|
if i % cols ~= 0 then syscall.write(1, "\n") end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parseForCompletion(input, cursorPos)
|
||||||
|
local sofar = input:sub(1, cursorPos - 1)
|
||||||
|
local tokens = {}
|
||||||
|
for tok in sofar:gmatch("%S+") do tokens[#tokens+1] = tok end
|
||||||
|
local partial = sofar:match("%S+$") or ""
|
||||||
|
local isFirst = (#tokens == 0) or (sofar:sub(-1) ~= " " and #tokens == 1)
|
||||||
|
|
||||||
|
local partialDir, partialBase
|
||||||
|
if partial:find("/") then
|
||||||
|
partialDir = partial:match("^(.*/)") or "/"
|
||||||
|
partialBase = partial:match("[^/]*$") or ""
|
||||||
|
else
|
||||||
|
partialDir = nil
|
||||||
|
partialBase = partial
|
||||||
|
end
|
||||||
|
|
||||||
|
return tokens, partial, isFirst, partialDir, partialBase
|
||||||
|
end
|
||||||
|
|
||||||
|
local tabState = { last = nil, idx = 0, list = {} }
|
||||||
|
|
||||||
|
local function doTabComplete(input, cursorPos)
|
||||||
|
local tokens, partial, isFirst, partialDir, partialBase = parseForCompletion(input, cursorPos)
|
||||||
|
|
||||||
|
local candidates
|
||||||
|
if isFirst then
|
||||||
|
if partialDir then
|
||||||
|
local dir = partialDir:sub(1,1) == "/" and partialDir or (syscall.getcwd().."/"..partialDir)
|
||||||
|
candidates = listDir(dir, partialBase)
|
||||||
|
for i, c in ipairs(candidates) do candidates[i] = partialDir..c end
|
||||||
|
else
|
||||||
|
candidates = listCommands(partialBase)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local dir, base
|
||||||
|
if partialDir then
|
||||||
|
dir = partialDir:sub(1,1) == "/" and partialDir or (syscall.getcwd().."/"..partialDir)
|
||||||
|
base = partialBase
|
||||||
|
else
|
||||||
|
dir = syscall.getcwd()
|
||||||
|
base = partialBase
|
||||||
|
end
|
||||||
|
candidates = listDir(dir, base)
|
||||||
|
if partialDir then
|
||||||
|
for i, c in ipairs(candidates) do candidates[i] = partialDir..c end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #candidates == 0 then
|
||||||
|
return input, cursorPos, false
|
||||||
|
end
|
||||||
|
|
||||||
|
local context = input.."\0"..tostring(cursorPos)
|
||||||
|
if tabState.last ~= context then
|
||||||
|
tabState.last = context
|
||||||
|
tabState.idx = 0
|
||||||
|
tabState.list = candidates
|
||||||
|
end
|
||||||
|
|
||||||
|
if #candidates == 1 then
|
||||||
|
local completed = candidates[1]
|
||||||
|
local before = input:sub(1, cursorPos - 1 - #partial)
|
||||||
|
local after = input:sub(cursorPos)
|
||||||
|
local newInput = before .. completed .. after
|
||||||
|
local newCursor = #before + #completed + 1
|
||||||
|
tabState.last = nil
|
||||||
|
return newInput, newCursor, true
|
||||||
|
end
|
||||||
|
|
||||||
|
local pre = commonPrefix(candidates)
|
||||||
|
if #pre > #partial then
|
||||||
|
local before = input:sub(1, cursorPos - 1 - #partial)
|
||||||
|
local after = input:sub(cursorPos)
|
||||||
|
local newInput = before .. pre .. after
|
||||||
|
local newCursor = #before + #pre + 1
|
||||||
|
tabState.last = newInput.."\0"..tostring(newCursor)
|
||||||
|
tabState.list = candidates
|
||||||
|
return newInput, newCursor, true
|
||||||
|
else
|
||||||
|
showCompletions(candidates)
|
||||||
|
return input, cursorPos, true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function getUserInput()
|
local function getUserInput()
|
||||||
syscall.devctl(1,"sfgc",3)
|
syscall.devctl(1,"sfgc",3)
|
||||||
syscall.write(1, userhost)
|
syscall.write(1, userhost)
|
||||||
@@ -829,6 +983,41 @@ local function getUserInput()
|
|||||||
local history = 0
|
local history = 0
|
||||||
local dirty = true
|
local dirty = true
|
||||||
|
|
||||||
|
local function getGhostSuffix()
|
||||||
|
if #input == 0 then return "" end
|
||||||
|
local _, partial, isFirst, partialDir, partialBase = parseForCompletion(input, cursorPos)
|
||||||
|
if cursorPos ~= #input + 1 then return "" end
|
||||||
|
local candidates
|
||||||
|
if isFirst then
|
||||||
|
if partialDir then
|
||||||
|
local dir = partialDir:sub(1,1) == "/" and partialDir or (syscall.getcwd().."/"..partialDir)
|
||||||
|
candidates = listDir(dir, partialBase)
|
||||||
|
for i, c in ipairs(candidates) do candidates[i] = partialDir..c end
|
||||||
|
else
|
||||||
|
candidates = listCommands(partialBase)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local dir, base
|
||||||
|
if partialDir then
|
||||||
|
dir = partialDir:sub(1,1) == "/" and partialDir or (syscall.getcwd().."/"..partialDir)
|
||||||
|
base = partialBase
|
||||||
|
else
|
||||||
|
dir = syscall.getcwd()
|
||||||
|
base = partialBase
|
||||||
|
end
|
||||||
|
candidates = listDir(dir, base)
|
||||||
|
if partialDir then
|
||||||
|
for i, c in ipairs(candidates) do candidates[i] = partialDir..c end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if #candidates == 0 then return "" end
|
||||||
|
local pre = commonPrefix(candidates)
|
||||||
|
if #pre > #partial then
|
||||||
|
return pre:sub(#partial + 1)
|
||||||
|
end
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
|
||||||
local function redraw()
|
local function redraw()
|
||||||
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
|
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
|
||||||
syscall.write(1, string.sub(input, 1, cursorPos-1))
|
syscall.write(1, string.sub(input, 1, cursorPos-1))
|
||||||
@@ -841,21 +1030,31 @@ local function getUserInput()
|
|||||||
syscall.write(1, string.sub(input, cursorPos, cursorPos))
|
syscall.write(1, string.sub(input, cursorPos, cursorPos))
|
||||||
end
|
end
|
||||||
syscall.devctl(1,"sfgc",1); syscall.devctl(1,"sbgc",16)
|
syscall.devctl(1,"sfgc",1); syscall.devctl(1,"sbgc",16)
|
||||||
syscall.write(1, string.sub(input, cursorPos+1) .. " ")
|
local after = string.sub(input, cursorPos+1)
|
||||||
|
syscall.write(1, after)
|
||||||
|
local ghost = getGhostSuffix()
|
||||||
|
if #ghost > 0 then
|
||||||
|
syscall.devctl(1,"sfgc",14)
|
||||||
|
syscall.write(1, ghost)
|
||||||
|
syscall.devctl(1,"sfgc",1)
|
||||||
|
syscall.write(1, " ")
|
||||||
|
else
|
||||||
|
syscall.write(1, " ")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
local key = syscall.read(0)
|
local key = syscall.read(0)
|
||||||
if key and key ~= "" then
|
if key and key ~= "" then
|
||||||
if key=="\19" then if cursorPos>1 then cursorPos=cursorPos-1;dirty=true end
|
if key=="[D" then if cursorPos>1 then cursorPos=cursorPos-1;dirty=true end
|
||||||
elseif key=="\20" then if cursorPos<=#input then cursorPos=cursorPos+1;dirty=true end
|
elseif key=="[C" then if cursorPos<=#input then cursorPos=cursorPos+1;dirty=true end
|
||||||
elseif key=="\17" then
|
elseif key=="[A" then
|
||||||
if history<#commandHistory then
|
if history<#commandHistory then
|
||||||
history=history+1
|
history=history+1
|
||||||
input=commandHistory[#commandHistory-history+1]
|
input=commandHistory[#commandHistory-history+1]
|
||||||
cursorPos=#input+1;dirty=true
|
cursorPos=#input+1;dirty=true
|
||||||
end
|
end
|
||||||
elseif key=="\18" then
|
elseif key=="[B" then
|
||||||
if history>1 then
|
if history>1 then
|
||||||
history=history-1
|
history=history-1
|
||||||
input=commandHistory[#commandHistory-history+1]
|
input=commandHistory[#commandHistory-history+1]
|
||||||
@@ -863,6 +1062,38 @@ local function getUserInput()
|
|||||||
elseif history==1 then
|
elseif history==1 then
|
||||||
history=0;input="";cursorPos=1;dirty=true
|
history=0;input="";cursorPos=1;dirty=true
|
||||||
end
|
end
|
||||||
|
elseif key=="[H" then cursorPos=1;dirty=true
|
||||||
|
elseif key=="[F" then cursorPos=#input+1;dirty=true
|
||||||
|
elseif key=="[3~" then
|
||||||
|
if cursorPos<=#input then
|
||||||
|
input=string.sub(input,1,cursorPos-1)..string.sub(input,cursorPos+1)
|
||||||
|
dirty=true
|
||||||
|
end
|
||||||
|
elseif key=="\t" then
|
||||||
|
local newInput, newCursor, needsRedraw = doTabComplete(input, cursorPos)
|
||||||
|
if needsRedraw then
|
||||||
|
input = newInput; cursorPos = newCursor
|
||||||
|
local posStr = syscall.devctl(1, "gpos")
|
||||||
|
local sep = posStr:find(";")
|
||||||
|
local px = tonumber(posStr:sub(1, sep-1))
|
||||||
|
local py = tonumber(posStr:sub(sep+1))
|
||||||
|
if px > 1 then
|
||||||
|
syscall.devctl(1,"spos",1,py)
|
||||||
|
local tsz = syscall.devctl(1,"size") or "51;19"
|
||||||
|
local tw = tonumber(tsz:match("^(%d+)")) or 51
|
||||||
|
syscall.write(1, string.rep(" ", tw))
|
||||||
|
syscall.devctl(1,"spos",1,py)
|
||||||
|
end
|
||||||
|
syscall.devctl(1,"sfgc",3); syscall.write(1, userhost)
|
||||||
|
syscall.devctl(1,"sfgc",1); syscall.write(1, ":")
|
||||||
|
syscall.devctl(1,"sfgc",10); syscall.write(1, syscall.getcwd())
|
||||||
|
syscall.devctl(1,"sfgc",1); syscall.write(1, "$ ")
|
||||||
|
posStr = syscall.devctl(1, "gpos")
|
||||||
|
sep = posStr:find(";")
|
||||||
|
curOffsetX = tonumber(posStr:sub(1, sep-1))
|
||||||
|
curOffsetY = tonumber(posStr:sub(sep+1))
|
||||||
|
dirty = true
|
||||||
|
end
|
||||||
elseif key=="\b" then
|
elseif key=="\b" then
|
||||||
if cursorPos>1 then
|
if cursorPos>1 then
|
||||||
input=string.sub(input,1,cursorPos-2)..string.sub(input,cursorPos)
|
input=string.sub(input,1,cursorPos-2)..string.sub(input,cursorPos)
|
||||||
@@ -873,7 +1104,7 @@ local function getUserInput()
|
|||||||
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
|
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
|
||||||
syscall.write(1, input.." \n")
|
syscall.write(1, input.." \n")
|
||||||
return input
|
return input
|
||||||
else
|
elseif #key == 1 and key:byte(1) >= 32 and key:byte(1) < 127 then
|
||||||
input=string.sub(input,1,cursorPos-1)..key..string.sub(input,cursorPos)
|
input=string.sub(input,1,cursorPos-1)..key..string.sub(input,cursorPos)
|
||||||
cursorPos=cursorPos+1;dirty=true
|
cursorPos=cursorPos+1;dirty=true
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ local function prompt(label, default)
|
|||||||
tbg(16); tfg(1)
|
tbg(16); tfg(1)
|
||||||
local key = syscall.read(0)
|
local key = syscall.read(0)
|
||||||
if not key or key == "" then sleep(0.02)
|
if not key or key == "" then sleep(0.02)
|
||||||
elseif key == "\27" then return nil
|
elseif key == "" then return nil
|
||||||
elseif key == "\n" then return inp
|
elseif key == "\n" then return inp
|
||||||
elseif key == "\b" then if #inp > 0 then inp = inp:sub(1,-2) end
|
elseif key == "\b" then if #inp > 0 then inp = inp:sub(1,-2) end
|
||||||
else
|
else
|
||||||
@@ -359,31 +359,29 @@ while running do
|
|||||||
local key = syscall.read(0)
|
local key = syscall.read(0)
|
||||||
if key and key ~= "" then
|
if key and key ~= "" then
|
||||||
local b = key:byte(1)
|
local b = key:byte(1)
|
||||||
if key == "\17" then moveCursorUp(map); dirty=true
|
if key == "[A" then moveCursorUp(map); dirty=true
|
||||||
elseif key == "\18" then moveCursorDown(map); dirty=true
|
elseif key == "[B" then moveCursorDown(map); dirty=true
|
||||||
elseif key == "\19" then
|
elseif key == "[C" then
|
||||||
if cx > 1 then cx=cx-1
|
|
||||||
elseif cy > 1 then cy=cy-1; cx=#lines[cy]+1 end
|
|
||||||
dirty=true
|
|
||||||
elseif key == "\20" then
|
|
||||||
if cx <= #lines[cy] then cx=cx+1
|
if cx <= #lines[cy] then cx=cx+1
|
||||||
elseif cy < #lines then cy=cy+1; cx=1 end
|
elseif cy < #lines then cy=cy+1; cx=1 end
|
||||||
dirty=true
|
dirty=true
|
||||||
|
elseif key == "[D" then
|
||||||
|
if cx > 1 then cx=cx-1
|
||||||
|
elseif cy > 1 then cy=cy-1; cx=#lines[cy]+1 end
|
||||||
|
dirty=true
|
||||||
|
elseif key == "[H" then cx=1; dirty=true
|
||||||
|
elseif key == "[F" then cx=#lines[cy]+1; dirty=true
|
||||||
|
elseif key == "[5~" then for _=1,ROWS do moveCursorUp(map) end; dirty=true
|
||||||
|
elseif key == "[6~" then for _=1,ROWS do moveCursorDown(map) end; dirty=true
|
||||||
|
elseif key == "[3~" then delRight()
|
||||||
elseif key == "\n" then newline()
|
elseif key == "\n" then newline()
|
||||||
elseif key == "\b" then delLeft()
|
elseif key == "\b" then delLeft()
|
||||||
elseif key == "\t" then for _=1,4 do insChar(" ") end
|
elseif key == "\t" then for _=1,4 do insChar(" ") end
|
||||||
elseif b == 1 then cx=1; dirty=true
|
|
||||||
elseif b == 2 then
|
|
||||||
for _=1,ROWS do moveCursorUp(map) end; dirty=true
|
|
||||||
elseif b == 4 then delRight()
|
|
||||||
elseif b == 5 then cx=#lines[cy]+1; dirty=true
|
|
||||||
elseif b == 6 then
|
elseif b == 6 then
|
||||||
local p=prompt("Find: ",sPat); dirty=true
|
local p=prompt("Find: ",sPat); dirty=true
|
||||||
if p then sPat=p; sLine=0; findNext() end
|
if p then sPat=p; sLine=0; findNext() end
|
||||||
elseif b == 7 then goToLine()
|
elseif b == 7 then goToLine()
|
||||||
elseif b == 11 then cutLine()
|
elseif b == 11 then cutLine()
|
||||||
elseif b == 12 then
|
|
||||||
for _=1,ROWS do moveCursorDown(map) end; dirty=true
|
|
||||||
elseif b == 14 then
|
elseif b == 14 then
|
||||||
if sPat=="" then
|
if sPat=="" then
|
||||||
local p=prompt("Find: ",""); dirty=true
|
local p=prompt("Find: ",""); dirty=true
|
||||||
|
|||||||
@@ -317,7 +317,18 @@ kernel.processes.cctmond = function()
|
|||||||
local eventType = event[1]
|
local eventType = event[1]
|
||||||
local charOrKey = event[3]
|
local charOrKey = event[3]
|
||||||
|
|
||||||
-- Update modifier keys
|
local ctrlKeyMap = {
|
||||||
|
[apis.keys.a]=1, [apis.keys.b]=2, [apis.keys.c]=3,
|
||||||
|
[apis.keys.d]=4, [apis.keys.e]=5, [apis.keys.f]=6,
|
||||||
|
[apis.keys.g]=7, [apis.keys.h]=8, [apis.keys.i]=9,
|
||||||
|
[apis.keys.j]=10, [apis.keys.k]=11, [apis.keys.l]=12,
|
||||||
|
[apis.keys.m]=13, [apis.keys.n]=14, [apis.keys.o]=15,
|
||||||
|
[apis.keys.p]=16, [apis.keys.q]=17, [apis.keys.r]=18,
|
||||||
|
[apis.keys.s]=19, [apis.keys.t]=20, [apis.keys.u]=21,
|
||||||
|
[apis.keys.v]=22, [apis.keys.w]=23, [apis.keys.x]=24,
|
||||||
|
[apis.keys.y]=25, [apis.keys.z]=26,
|
||||||
|
}
|
||||||
|
|
||||||
if eventType == "keyPressed" then
|
if eventType == "keyPressed" then
|
||||||
if charOrKey == apis.keys.leftCtrl or charOrKey == apis.keys.rightCtrl then
|
if charOrKey == apis.keys.leftCtrl or charOrKey == apis.keys.rightCtrl then
|
||||||
ctrl = true
|
ctrl = true
|
||||||
@@ -325,11 +336,31 @@ kernel.processes.cctmond = function()
|
|||||||
alt = true
|
alt = true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Handle Ctrl+C
|
if ctrl then
|
||||||
if ctrl and charOrKey == apis.keys.c then
|
local ctrlByte = ctrlKeyMap[charOrKey]
|
||||||
|
if ctrlByte then
|
||||||
|
if ctrlByte == 3 then
|
||||||
for _, task in ipairs(syscall.getTasks()) do
|
for _, task in ipairs(syscall.getTasks()) do
|
||||||
syscall.sigsend(task, 1) -- SIGINT
|
syscall.sigsend(task, 1)
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
fifo.push(string.char(ctrlByte))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local specialKeyMap = {
|
||||||
|
[apis.keys.up] = "[A",
|
||||||
|
[apis.keys.down] = "[B",
|
||||||
|
[apis.keys.right] = "[C",
|
||||||
|
[apis.keys.left] = "[D",
|
||||||
|
[apis.keys.home] = "[H",
|
||||||
|
[apis.keys["end"]] = "[F",
|
||||||
|
[apis.keys.pageUp] = "[5~",
|
||||||
|
[apis.keys.pageDown] = "[6~",
|
||||||
|
[apis.keys.delete] = "[3~",
|
||||||
|
}
|
||||||
|
local special = specialKeyMap[charOrKey]
|
||||||
|
if special then fifo.push(special) end
|
||||||
end
|
end
|
||||||
|
|
||||||
elseif eventType == "keyReleased" then
|
elseif eventType == "keyReleased" then
|
||||||
|
|||||||
@@ -113,57 +113,23 @@ end
|
|||||||
|
|
||||||
local SAFE_COMPONENT_PATTERN = "^[A-Za-z0-9_.+%-%@%(%)%[%]]+$"
|
local SAFE_COMPONENT_PATTERN = "^[A-Za-z0-9_.+%-%@%(%)%[%]]+$"
|
||||||
|
|
||||||
local function normalizePath(path)
|
local function tokenizePath(path)
|
||||||
local task = kernel.currentTask
|
local isAbsolute = (path:sub(1,1) == "/")
|
||||||
local cwd = task.cwd or "/"
|
local tokens = {}
|
||||||
|
for comp in (path .. "/"):gmatch("([^/]*)/") do
|
||||||
if path:sub(1, 1) ~= "/" then
|
table.insert(tokens, comp)
|
||||||
path = cwd .. "/" .. path
|
end
|
||||||
|
return isAbsolute, tokens
|
||||||
end
|
end
|
||||||
|
|
||||||
local stack = {}
|
local function validateComponent(comp)
|
||||||
local i = 1
|
local lower = comp:lower()
|
||||||
local len = #path
|
if not lower:match(SAFE_COMPONENT_PATTERN) then
|
||||||
while i <= len do
|
error("EINVAL: illegal characters in path component: " .. comp, 3)
|
||||||
local j = path:find("/", i, true)
|
|
||||||
local comp
|
|
||||||
if j then
|
|
||||||
comp = path:sub(i, j - 1)
|
|
||||||
i = j + 1
|
|
||||||
else
|
|
||||||
comp = path:sub(i)
|
|
||||||
i = len + 1
|
|
||||||
end
|
end
|
||||||
|
if lower == ".meta" then
|
||||||
comp = comp:match("^%s*(.-)%s*$")
|
error("EINVAL: reserved path component: .meta", 3)
|
||||||
|
|
||||||
if comp == "" or comp == "." then
|
|
||||||
elseif comp == ".." then
|
|
||||||
if #stack > 0 then
|
|
||||||
table.remove(stack)
|
|
||||||
end
|
end
|
||||||
else
|
|
||||||
comp = comp:lower()
|
|
||||||
if not comp:match(SAFE_COMPONENT_PATTERN) then
|
|
||||||
error("EINVAL: illegal characters in path component: " .. comp, 2)
|
|
||||||
end
|
|
||||||
if comp == ".meta" then
|
|
||||||
error("EINVAL: reserved path component: " .. comp, 2)
|
|
||||||
end
|
|
||||||
table.insert(stack, comp)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local result = "/" .. table.concat(stack, "/")
|
|
||||||
|
|
||||||
local root = task and task.root
|
|
||||||
if root and root ~= "/" then
|
|
||||||
if result ~= root and result:sub(1, #root + 1) ~= root .. "/" then
|
|
||||||
result = root
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return result
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function vfs.splitPath(path)
|
function vfs.splitPath(path)
|
||||||
@@ -222,47 +188,205 @@ local function readMetaEntry(disk, parentDiskPath, filename)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local MAX_SYMLINK = 16
|
local MAX_SYMLINK = 16
|
||||||
local function resolveSymlinks(path, noFollow, _depth)
|
|
||||||
_depth = _depth or 0
|
|
||||||
if _depth > MAX_SYMLINK then error("ELOOP") end
|
|
||||||
path = normalizePath(path)
|
|
||||||
|
|
||||||
local parts = {}
|
local function namei(path, noFollow, symDepth)
|
||||||
for p in path:gmatch("[^/]+") do table.insert(parts, p) end
|
symDepth = symDepth or 0
|
||||||
|
if symDepth > MAX_SYMLINK then error("ELOOP") end
|
||||||
|
|
||||||
local resolved = ""
|
local task = kernel.currentTask
|
||||||
|
local euid = (task and (task.euid or task.uid)) or kernel.uid
|
||||||
|
local groups = (task and task.groups) or kernel.groups or {}
|
||||||
|
local root = (task and task.root) or "/"
|
||||||
|
local cwd = (task and task.cwd) or "/"
|
||||||
|
|
||||||
for i, part in ipairs(parts) do
|
if root ~= "/" and root:sub(-1) == "/" then root = root:sub(1,-2) end
|
||||||
local candidate = resolved == "" and ("/" .. part) or (resolved .. "/" .. part)
|
|
||||||
|
|
||||||
if noFollow and i == #parts then
|
local function canTraverse(entry)
|
||||||
resolved = candidate
|
if euid == 0 then return true end
|
||||||
break
|
if not entry then return true end
|
||||||
|
local bits = entry.perms
|
||||||
|
if euid == entry.owner and bit_is_set(bits, 9) then return true end
|
||||||
|
if entry.group then
|
||||||
|
for _, gid in ipairs(groups) do
|
||||||
|
if gid == entry.group and bit_is_set(bits, 8) then return true end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return bit_is_set(bits, 7)
|
||||||
end
|
end
|
||||||
|
|
||||||
local disk, parentDisk = resolveMount(resolved == "" and "/" or resolved)
|
local isAbsolute, tokens = tokenizePath(path)
|
||||||
local entry = readMetaEntry(disk, parentDisk, part)
|
|
||||||
|
local stack = {}
|
||||||
|
|
||||||
|
if isAbsolute then
|
||||||
|
stack = {}
|
||||||
|
else
|
||||||
|
for seg in cwd:gmatch("[^/]+") do table.insert(stack, seg) end
|
||||||
|
end
|
||||||
|
|
||||||
|
local i = 1
|
||||||
|
while i <= #tokens do
|
||||||
|
local comp = tokens[i]
|
||||||
|
i = i + 1
|
||||||
|
|
||||||
|
comp = comp:match("^%s*(.-)%s*$")
|
||||||
|
|
||||||
|
if comp == "" or comp == "." then
|
||||||
|
elseif comp == ".." then
|
||||||
|
local currentPath = "/" .. table.concat(stack, "/")
|
||||||
|
|
||||||
|
local jailStack = {}
|
||||||
|
if root ~= "/" then
|
||||||
|
for seg in root:gmatch("[^/]+") do table.insert(jailStack, seg) end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #stack <= #jailStack then
|
||||||
|
stack = {}
|
||||||
|
for _, seg in ipairs(jailStack) do table.insert(stack, seg) end
|
||||||
|
else
|
||||||
|
local exitName = stack[#stack]
|
||||||
|
local parentPath = "/" .. table.concat(stack, "/", 1, #stack - 1)
|
||||||
|
if parentPath == "/" then parentPath = "/" end
|
||||||
|
|
||||||
|
local okM, diskM, dpM = pcall(resolveMount, parentPath == "" and "/" or parentPath)
|
||||||
|
if okM and diskM then
|
||||||
|
local entry = readMetaEntry(diskM, dpM, exitName)
|
||||||
|
if entry then
|
||||||
|
if entry.etype ~= 0x00 then
|
||||||
|
error("ENOTDIR: not a directory: " .. currentPath)
|
||||||
|
end
|
||||||
|
if not canTraverse(entry) then
|
||||||
|
error("EACCES: permission denied traversing " .. currentPath)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local okD, diskD, dpD = pcall(resolveMount, currentPath)
|
||||||
|
if okD and diskD then
|
||||||
|
local dtype = diskD:type(dpD)
|
||||||
|
if dtype ~= nil and dtype ~= "directory" then
|
||||||
|
error("ENOTDIR: not a directory: " .. currentPath)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
table.remove(stack)
|
||||||
|
end
|
||||||
|
|
||||||
|
else
|
||||||
|
validateComponent(comp)
|
||||||
|
local lname = comp:lower()
|
||||||
|
|
||||||
|
local curPath = "/" .. table.concat(stack, "/")
|
||||||
|
|
||||||
|
local okM, diskM, dpM = pcall(resolveMount, curPath == "/" and "/" or curPath)
|
||||||
|
local entry = nil
|
||||||
|
if okM and diskM then
|
||||||
|
entry = readMetaEntry(diskM, dpM, lname)
|
||||||
|
end
|
||||||
|
|
||||||
|
local isFinal = (i > #tokens)
|
||||||
|
|
||||||
if entry and entry.etype == 0x01 then
|
if entry and entry.etype == 0x01 then
|
||||||
|
if isFinal and noFollow then
|
||||||
|
table.insert(stack, lname)
|
||||||
|
else
|
||||||
|
symDepth = symDepth + 1
|
||||||
|
if symDepth > MAX_SYMLINK then error("ELOOP") end
|
||||||
|
|
||||||
local target = entry.cmeta
|
local target = entry.cmeta
|
||||||
if target:sub(1,1) ~= "/" then
|
if not target or target == "" then
|
||||||
target = (resolved == "" and "/" or resolved) .. "/" .. target
|
error("ENOENT: empty symlink target")
|
||||||
end
|
|
||||||
if i < #parts then
|
|
||||||
target = target .. "/" .. table.concat(parts, "/", i+1, #parts)
|
|
||||||
end
|
|
||||||
return resolveSymlinks(normalizePath(target), noFollow, _depth + 1)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
resolved = candidate
|
local symIsAbs, symTokens = tokenizePath(target)
|
||||||
|
|
||||||
|
if symIsAbs then
|
||||||
|
stack = {}
|
||||||
|
if root ~= "/" then
|
||||||
|
for seg in root:gmatch("[^/]+") do table.insert(stack, seg) end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if resolved == "" then resolved = "/" end
|
local fresh = {}
|
||||||
return resolved
|
for j = 1, i - 2 do table.insert(fresh, tokens[j]) end
|
||||||
|
local insertAt = #fresh + 1
|
||||||
|
for _, t in ipairs(symTokens) do table.insert(fresh, t) end
|
||||||
|
for j = i, #tokens do table.insert(fresh, tokens[j]) end
|
||||||
|
tokens = fresh
|
||||||
|
i = insertAt
|
||||||
|
end
|
||||||
|
else
|
||||||
|
table.insert(stack, lname)
|
||||||
|
|
||||||
|
if not isFinal then
|
||||||
|
local newPath = "/" .. table.concat(stack, "/")
|
||||||
|
local okD, diskD, dpD = pcall(resolveMount, newPath)
|
||||||
|
if okD and diskD then
|
||||||
|
local dtype = diskD:type(dpD)
|
||||||
|
if dtype ~= nil and dtype ~= "directory" then
|
||||||
|
error("ENOTDIR: not a directory: " .. newPath)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not canTraverse(entry) then
|
||||||
|
error("EACCES: permission denied traversing " .. newPath)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local result = "/" .. table.concat(stack, "/")
|
||||||
|
|
||||||
|
if root ~= "/" then
|
||||||
|
if result ~= root and result:sub(1, #root + 1) ~= root .. "/" then
|
||||||
|
result = root
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
local function normalizePath(path)
|
||||||
|
local task = kernel.currentTask
|
||||||
|
local cwd = (task and task.cwd) or "/"
|
||||||
|
local root = (task and task.root) or "/"
|
||||||
|
if root ~= "/" and root:sub(-1) == "/" then root = root:sub(1,-2) end
|
||||||
|
|
||||||
|
local isAbsolute, tokens = tokenizePath(path)
|
||||||
|
local stack = {}
|
||||||
|
|
||||||
|
if not isAbsolute then
|
||||||
|
for seg in cwd:gmatch("[^/]+") do table.insert(stack, seg) end
|
||||||
|
end
|
||||||
|
|
||||||
|
local jailStack = {}
|
||||||
|
if root ~= "/" then
|
||||||
|
for seg in root:gmatch("[^/]+") do table.insert(jailStack, seg) end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, comp in ipairs(tokens) do
|
||||||
|
comp = comp:match("^%s*(.-)%s*$")
|
||||||
|
if comp == "" or comp == "." then
|
||||||
|
elseif comp == ".." then
|
||||||
|
if #stack > #jailStack then
|
||||||
|
table.remove(stack)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
table.insert(stack, comp:lower())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local result = "/" .. table.concat(stack, "/")
|
||||||
|
if root ~= "/" then
|
||||||
|
if result ~= root and result:sub(1, #root + 1) ~= root .. "/" then
|
||||||
|
result = root
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
local function resolvePath(path, noFollow)
|
local function resolvePath(path, noFollow)
|
||||||
local real = resolveSymlinks(path, noFollow)
|
local real = namei(path, noFollow)
|
||||||
local disk, diskPath = resolveMount(real)
|
local disk, diskPath = resolveMount(real)
|
||||||
if kernel.config.logPathResolution then
|
if kernel.config.logPathResolution then
|
||||||
kernel.log("resolvePath '"..path.."' -> '"..real.."' diskPath '"..diskPath.."'")
|
kernel.log("resolvePath '"..path.."' -> '"..real.."' diskPath '"..diskPath.."'")
|
||||||
@@ -271,24 +395,23 @@ local function resolvePath(path, noFollow)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function getFileMeta(path, noFollow)
|
local function getFileMeta(path, noFollow)
|
||||||
local real = resolveSymlinks(path, noFollow)
|
local real = namei(path, noFollow)
|
||||||
|
|
||||||
local parts = {}
|
if real == "/" then
|
||||||
for p in real:gmatch("[^/]+") do table.insert(parts, p) end
|
return { etype = 0x00, owner = 0, group = 0, perms = 63, cmeta = "" }
|
||||||
|
end
|
||||||
|
|
||||||
local default = { etype = 0x00, owner = 0, group = 0, perms = 63, cmeta = "" }
|
local parent, name = real:match("^(.*)/([^/]+)$")
|
||||||
if #parts == 0 then return default end
|
if not parent or parent == "" then parent = "/" end
|
||||||
|
|
||||||
local parentNorm = "/" .. table.concat(parts, "/", 1, #parts - 1)
|
local disk, parentDiskPath = resolveMount(parent)
|
||||||
if parentNorm == "" then parentNorm = "/" end
|
local entry = readMetaEntry(disk, parentDiskPath, name)
|
||||||
local disk, parentDiskPath = resolveMount(parentNorm)
|
|
||||||
local entry = readMetaEntry(disk, parentDiskPath, parts[#parts])
|
|
||||||
if entry then return entry end
|
if entry then return entry end
|
||||||
return default
|
return { etype = 0x00, owner = 0, group = 0, perms = 63, cmeta = "" }
|
||||||
end
|
end
|
||||||
|
|
||||||
local function writeMetaEntry(path, name, entry, noFollow)
|
local function writeMetaEntry(path, name, entry, noFollow)
|
||||||
local real = resolveSymlinks(path, noFollow)
|
local real = namei(path, noFollow)
|
||||||
local disk, diskPath = resolveMount(real)
|
local disk, diskPath = resolveMount(real)
|
||||||
|
|
||||||
local mp
|
local mp
|
||||||
@@ -668,7 +791,7 @@ function vfs.mkdir(path)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function vfs.remove(path)
|
function vfs.remove(path)
|
||||||
local norm = resolveSymlinks(path, true)
|
local norm = namei(path, true)
|
||||||
local parent = norm:match("^(.*)/[^/]+$") or "/"
|
local parent = norm:match("^(.*)/[^/]+$") or "/"
|
||||||
if parent == "" then parent = "/" end
|
if parent == "" then parent = "/" end
|
||||||
local parentMeta = getFileMeta(parent)
|
local parentMeta = getFileMeta(parent)
|
||||||
@@ -681,7 +804,7 @@ function vfs.remove(path)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if meta.etype == 0x01 then
|
if meta.etype == 0x01 then
|
||||||
local norm = resolveSymlinks(path, true)
|
local norm = namei(path, true)
|
||||||
local parent = norm:match("^(.*)/[^/]+$") or "/"
|
local parent = norm:match("^(.*)/[^/]+$") or "/"
|
||||||
if parent == "" then parent = "/" end
|
if parent == "" then parent = "/" end
|
||||||
local name = norm:match("[^/]+$")
|
local name = norm:match("[^/]+$")
|
||||||
@@ -747,7 +870,7 @@ function vfs.access(path, mode)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function updateMeta(path, fn, noFollow)
|
local function updateMeta(path, fn, noFollow)
|
||||||
local real = resolveSymlinks(path, noFollow)
|
local real = namei(path, noFollow)
|
||||||
local norm = real
|
local norm = real
|
||||||
local parent = norm:match("^(.*)/[^/]+$") or "/"
|
local parent = norm:match("^(.*)/[^/]+$") or "/"
|
||||||
if parent == "" then parent = "/" end
|
if parent == "" then parent = "/" end
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
--:Minify:--
|
|
||||||
local kernel = ...
|
|
||||||
kernel.vfs.open("/dev/tty/1","r")
|
|
||||||
kernel.vfs.open("/dev/tty/1","w")
|
|
||||||
kernel.vfs.open("/dev/null","w")
|
|
||||||
Reference in New Issue
Block a user