· 6 years ago · Jun 01, 2019, 03:00 PM
1local filesystem = require("filesystem")
2local component = require("component")
3local shell = require("shell")
4
5function filesystem.makeDirectory(path)
6 if filesystem.exists(path) then
7 return nil, "file or directory with that name already exists"
8 end
9 local node, rest = filesystem.findNode(path)
10 if node.fs and rest then
11 local success, reason = node.fs.makeDirectory(rest)
12 if not success and not reason and node.fs.isReadOnly() then
13 reason = "filesystem is readonly"
14 end
15 return success, reason
16 end
17 if node.fs then
18 return nil, "virtual directory with that name already exists"
19 end
20 return nil, "cannot create a directory in a virtual directory"
21end
22
23function filesystem.lastModified(path)
24 local node, rest, vnode, vrest = filesystem.findNode(path, false, true)
25 if not node or not vnode.fs and not vrest then
26 return 0 -- virtual directory
27 end
28 if node.fs and rest then
29 return node.fs.lastModified(rest)
30 end
31 return 0 -- no such file or directory
32end
33
34function filesystem.mounts()
35 local tmp = {}
36 for path,node in pairs(filesystem.fstab) do
37 table.insert(tmp, {node.fs,path})
38 end
39 return function()
40 local next = table.remove(tmp)
41 if next then return table.unpack(next) end
42 end
43end
44
45function filesystem.link(target, linkpath)
46 checkArg(1, target, "string")
47 checkArg(2, linkpath, "string")
48
49 if filesystem.exists(linkpath) then
50 return nil, "file already exists"
51 end
52 local linkpath_parent = filesystem.path(linkpath)
53 if not filesystem.exists(linkpath_parent) then
54 return nil, "no such directory"
55 end
56 local linkpath_real, reason = filesystem.realPath(linkpath_parent)
57 if not linkpath_real then
58 return nil, reason
59 end
60 if not filesystem.isDirectory(linkpath_real) then
61 return nil, "not a directory"
62 end
63
64 local _, _, vnode, _ = filesystem.findNode(linkpath_real, true)
65 vnode.links[filesystem.name(linkpath)] = target
66 return true
67end
68
69function filesystem.umount(fsOrPath)
70 checkArg(1, fsOrPath, "string", "table")
71 local real
72 local fs
73 local addr
74 if type(fsOrPath) == "string" then
75 real = filesystem.realPath(fsOrPath)
76 addr = fsOrPath
77 else -- table
78 fs = fsOrPath
79 end
80
81 local paths = {}
82 for path,node in pairs(filesystem.fstab) do
83 if real == path or addr == node.fs.address or fs == node.fs then
84 table.insert(paths, path)
85 end
86 end
87 for _,path in ipairs(paths) do
88 local node = filesystem.fstab[path]
89 filesystem.fstab[path] = nil
90 node.fs = nil
91 node.parent.children[node.name] = nil
92 end
93 return #paths > 0
94end
95
96function filesystem.size(path)
97 local node, rest, vnode, vrest = filesystem.findNode(path, false, true)
98 if not node or not vnode.fs and (not vrest or vnode.links[vrest]) then
99 return 0 -- virtual directory or symlink
100 end
101 if node.fs and rest then
102 return node.fs.size(rest)
103 end
104 return 0 -- no such file or directory
105end
106
107function filesystem.isLink(path)
108 local name = filesystem.name(path)
109 local node, rest, vnode, vrest = filesystem.findNode(filesystem.path(path), false, true)
110 if not node then return nil, rest end
111 local target = vnode.links[name]
112 -- having vrest here indicates we are not at the
113 -- owning vnode due to a mount point above this point
114 -- but we can have a target when there is a link at
115 -- the mount point root, with the same name
116 if not vrest and target ~= nil then
117 return true, target
118 end
119 return false
120end
121
122function filesystem.copy(fromPath, toPath)
123 local data = false
124 local input, reason = filesystem.open(fromPath, "rb")
125 if input then
126 local output = filesystem.open(toPath, "wb")
127 if output then
128 repeat
129 data, reason = input:read(1024)
130 if not data then break end
131 data, reason = output:write(data)
132 if not data then data, reason = false, "failed to write" end
133 until not data
134 output:close()
135 end
136 input:close()
137 end
138 return data == nil, reason
139end
140
141local function readonly_wrap(proxy)
142 checkArg(1, proxy, "table")
143 if proxy.isReadOnly() then
144 return proxy
145 end
146
147 local function roerr() return nil, "filesystem is readonly" end
148 return setmetatable({
149 rename = roerr,
150 open = function(path, mode)
151 checkArg(1, path, "string")
152 checkArg(2, mode, "string")
153 if mode:match("[wa]") then
154 return roerr()
155 end
156 return proxy.open(path, mode)
157 end,
158 isReadOnly = function()
159 return true
160 end,
161 write = roerr,
162 setLabel = roerr,
163 makeDirectory = roerr,
164 remove = roerr,
165 }, {__index=proxy})
166end
167
168local function bind_proxy(path)
169 local real, reason = filesystem.realPath(path)
170 if not real then
171 return nil, reason
172 end
173 if not filesystem.isDirectory(real) then
174 return nil, "must bind to a directory"
175 end
176 local real_fs, real_fs_path = filesystem.get(real)
177 if real == real_fs_path then
178 return real_fs
179 end
180 -- turn /tmp/foo into foo
181 local rest = real:sub(#real_fs_path + 1)
182 local function wrap_relative(fp)
183 return function(mpath, ...)
184 return fp(filesystem.concat(rest, mpath), ...)
185 end
186 end
187 local bind = {
188 type = "filesystem_bind",
189 address = real,
190 isReadOnly = real_fs.isReadOnly,
191 list = wrap_relative(real_fs.list),
192 isDirectory = wrap_relative(real_fs.isDirectory),
193 size = wrap_relative(real_fs.size),
194 lastModified = wrap_relative(real_fs.lastModified),
195 exists = wrap_relative(real_fs.exists),
196 open = wrap_relative(real_fs.open),
197 remove = wrap_relative(real_fs.remove),
198 read = real_fs.read,
199 write = real_fs.write,
200 close = real_fs.close,
201 getLabel = function() return "" end,
202 setLabel = function() return nil, "cannot set the label of a bind point" end,
203 }
204 return bind
205end
206
207filesystem.internal = {}
208function filesystem.internal.proxy(filter, options)
209 checkArg(1, filter, "string")
210 checkArg(2, options, "table", "nil")
211 options = options or {}
212 local address, proxy, reason
213 if options.bind then
214 proxy, reason = bind_proxy(filter)
215 else
216 -- no options: filter should be a label or partial address
217 for c in component.list("filesystem", true) do
218 if component.invoke(c, "getLabel") == filter then
219 address = c
220 break
221 end
222 if c:sub(1, filter:len()) == filter then
223 address = c
224 break
225 end
226 end
227 if not address then
228 return nil, "no such file system"
229 end
230 proxy, reason = component.proxy(address)
231 end
232 if not proxy then
233 return proxy, reason
234 end
235 if options.readonly then
236 proxy = readonly_wrap(proxy)
237 end
238 return proxy
239end
240
241function filesystem.remove(path)
242 local function removeVirtual()
243 local _, _, vnode, vrest = filesystem.findNode(filesystem.path(path), false, true)
244 -- vrest represents the remaining path beyond vnode
245 -- vrest is nil if vnode reaches the full path
246 -- thus, if vrest is NOT NIL, then we SHOULD NOT remove children nor links
247 if not vrest then
248 local name = filesystem.name(path)
249 if vnode.children[name] or vnode.links[name] then
250 vnode.children[name] = nil
251 vnode.links[name] = nil
252 while vnode and vnode.parent and not vnode.fs and not next(vnode.children) and not next(vnode.links) do
253 vnode.parent.children[vnode.name] = nil
254 vnode = vnode.parent
255 end
256 return true
257 end
258 end
259 -- return false even if vrest is nil because this means it was a expected
260 -- to be a real file
261 return false
262 end
263 local function removePhysical()
264 local node, rest = filesystem.findNode(path)
265 if node.fs and rest then
266 return node.fs.remove(rest)
267 end
268 return false
269 end
270 local success = removeVirtual()
271 success = removePhysical() or success -- Always run.
272 if success then return true
273 else return nil, "no such file or directory"
274 end
275end
276
277function filesystem.rename(oldPath, newPath)
278 if filesystem.isLink(oldPath) then
279 local _, _, vnode, _ = filesystem.findNode(filesystem.path(oldPath))
280 local target = vnode.links[filesystem.name(oldPath)]
281 local result, reason = filesystem.link(target, newPath)
282 if result then
283 filesystem.remove(oldPath)
284 end
285 return result, reason
286 else
287 local oldNode, oldRest = filesystem.findNode(oldPath)
288 local newNode, newRest = filesystem.findNode(newPath)
289 if oldNode.fs and oldRest and newNode.fs and newRest then
290 if oldNode.fs.address == newNode.fs.address then
291 return oldNode.fs.rename(oldRest, newRest)
292 else
293 local result, reason = filesystem.copy(oldPath, newPath)
294 if result then
295 return filesystem.remove(oldPath)
296 else
297 return nil, reason
298 end
299 end
300 end
301 return nil, "trying to read from or write to virtual directory"
302 end
303end
304
305local isAutorunEnabled = nil
306local function saveConfig()
307 local root = filesystem.get("/")
308 if root and not root.isReadOnly() then
309 local f = filesystem.open("/etc/filesystem.cfg", "w")
310 if f then
311 f:write("autorun="..tostring(isAutorunEnabled))
312 f:close()
313 end
314 end
315end
316
317function filesystem.isAutorunEnabled()
318 if isAutorunEnabled == nil then
319 local env = {}
320 local config = loadfile("/etc/filesystem.cfg", nil, env)
321 if config then
322 pcall(config)
323 isAutorunEnabled = not not env.autorun
324 else
325 isAutorunEnabled = true
326 end
327 saveConfig()
328 end
329 return isAutorunEnabled
330end
331
332function filesystem.setAutorunEnabled(value)
333 checkArg(1, value, "boolean")
334 isAutorunEnabled = value
335 saveConfig()
336end
337
338-- luacheck: globals os
339os.remove = filesystem.remove
340os.rename = filesystem.rename
341
342os.execute = function(command)
343 if not command then
344 return type(shell) == "table"
345 end
346 return shell.execute(command)
347end
348
349function os.exit(code)
350 error({reason="terminated", code=code}, 0)
351end
352
353function os.tmpname()
354 local path = os.getenv("TMPDIR") or "/tmp"
355 if filesystem.exists(path) then
356 for _ = 1, 10 do
357 local name = filesystem.concat(path, tostring(math.random(1, 0x7FFFFFFF)))
358 if not filesystem.exists(name) then
359 return name
360 end
361 end
362 end
363end