· 9 years ago · Jan 14, 2017, 05:34 AM
1local component = require("component")
2local unicode = require("unicode")
3
4local filesystem, fileStream = {}, {}
5local isAutorunEnabled = nil
6local mtab = {name="", children={}, links={}}
7
8local function segments(path)
9 path = path:gsub("\\", "/")
10 repeat local n; path, n = path:gsub("//", "/") until n == 0
11 local parts = {}
12 for part in path:gmatch("[^/]+") do
13 table.insert(parts, part)
14 end
15 local i = 1
16 while i <= #parts do
17 if parts[i] == "." then
18 table.remove(parts, i)
19 elseif parts[i] == ".." then
20 table.remove(parts, i)
21 i = i - 1
22 if i > 0 then
23 table.remove(parts, i)
24 else
25 i = 1
26 end
27 else
28 i = i + 1
29 end
30 end
31 return parts
32end
33
34local function saveConfig()
35 local root = filesystem.get("/")
36 if root and not root.isReadOnly() then
37 filesystem.makeDirectory("/etc")
38 local f = io.open("/etc/filesystem.cfg", "w")
39 if f then
40 f:write("autorun="..tostring(isAutorunEnabled))
41 f:close()
42 end
43 end
44end
45
46local function findNode(path, create, depth)
47 checkArg(1, path, "string")
48 depth = depth or 0
49 if depth > 100 then
50 error("link cycle detected")
51 end
52 local parts = segments(path)
53 local node = mtab
54 while #parts > 0 do
55 local part = parts[1]
56 if not node.children[part] then
57 if node.links[part] then
58 return findNode(filesystem.concat(node.links[part], table.concat(parts, "/", 2)), create, depth + 1)
59 else
60 if create then
61 node.children[part] = {name=part, parent=node, children={}, links={}}
62 else
63 local vnode, vrest = node, table.concat(parts, "/")
64 local rest = vrest
65 while node and not node.fs do
66 rest = filesystem.concat(node.name, rest)
67 node = node.parent
68 end
69 return node, rest, vnode, vrest
70 end
71 end
72 end
73 node = node.children[part]
74 table.remove(parts, 1)
75 end
76 local vnode, vrest = node, nil
77 local rest = nil
78 while node and not node.fs do
79 rest = rest and filesystem.concat(node.name, rest) or node.name
80 node = node.parent
81 end
82 return node, rest, vnode, vrest
83end
84
85local function removeEmptyNodes(node)
86 while node and node.parent and not node.fs and not next(node.children) and not next(node.links) do
87 node.parent.children[node.name] = nil
88 node = node.parent
89 end
90end
91
92-------------------------------------------------------------------------------
93
94function filesystem.isAutorunEnabled()
95 if isAutorunEnabled == nil then
96 local env = {}
97 local config = loadfile("/etc/filesystem.cfg", nil, env)
98 if config then
99 pcall(config)
100 isAutorunEnabled = not not env.autorun
101 else
102 isAutorunEnabled = true
103 end
104 saveConfig()
105 end
106 return isAutorunEnabled
107end
108
109function filesystem.setAutorunEnabled(value)
110 checkArg(1, value, "boolean")
111 isAutorunEnabled = value
112 saveConfig()
113end
114
115filesystem.segments = segments
116
117function filesystem.canonical(path)
118 local result = table.concat(segments(path), "/")
119 if unicode.sub(path, 1, 1) == "/" then
120 return "/" .. result
121 else
122 return result
123 end
124end
125
126function filesystem.concat(pathA, pathB, ...)
127 checkArg(1, pathA, "string")
128 local function concat(n, a, b, ...)
129 if not b then
130 return a
131 end
132 checkArg(n, b, "string")
133 return concat(n + 1, a .. "/" .. b, ...)
134 end
135 return filesystem.canonical(concat(2, pathA, pathB, ...))
136end
137
138function filesystem.get(path)
139 local node, rest = findNode(path)
140 if node.fs then
141 local proxy = node.fs
142 path = ""
143 while node and node.parent do
144 path = filesystem.concat(node.name, path)
145 node = node.parent
146 end
147 path = filesystem.canonical(path)
148 if path ~= "/" then
149 path = "/" .. path
150 end
151 return proxy, path
152 end
153 return nil, "no such file system"
154end
155
156function filesystem.isLink(path)
157 local node, rest, vnode, vrest = findNode(filesystem.path(path))
158 if not vrest and vnode.links[filesystem.name(path)] ~= nil then
159 return true, vnode.links[filesystem.name(path)]
160 end
161 return false
162end
163
164function filesystem.link(target, linkpath)
165 checkArg(1, target, "string")
166 checkArg(2, linkpath, "string")
167
168 if filesystem.exists(linkpath) then
169 return nil, "file already exists"
170 end
171
172 local node, rest, vnode, vrest = findNode(filesystem.path(linkpath), true)
173 vnode.links[filesystem.name(linkpath)] = target
174 return true
175end
176
177function filesystem.mount(fs, path)
178 checkArg(1, fs, "string", "table")
179 if type(fs) == "string" then
180 fs = filesystem.proxy(fs)
181 end
182 assert(type(fs) == "table", "bad argument #1 (file system proxy or address expected)")
183 checkArg(2, path, "string")
184
185 if path ~= "/" and filesystem.exists(path) then
186 return nil, "file already exists"
187 end
188
189 local node, rest, vnode, vrest = findNode(path, true)
190 if vnode.fs then
191 return nil, "another filesystem is already mounted here"
192 end
193 vnode.fs = fs
194 return true
195end
196
197function filesystem.mounts()
198 local function path(node)
199 local result = "/"
200 while node and node.parent do
201 for name, child in pairs(node.parent.children) do
202 if child == node then
203 result = "/" .. name .. result
204 break
205 end
206 end
207 node = node.parent
208 end
209 return result
210 end
211 local queue = {mtab}
212 return function()
213 while #queue > 0 do
214 local node = table.remove(queue)
215 for _, child in pairs(node.children) do
216 table.insert(queue, child)
217 end
218 if node.fs then
219 return node.fs, path(node)
220 end
221 end
222 end
223end
224
225function filesystem.path(path)
226 local parts = segments(path)
227 local result = table.concat(parts, "/", 1, #parts - 1) .. "/"
228 if unicode.sub(path, 1, 1) == "/" and unicode.sub(result, 1, 1) ~= "/" then
229 return "/" .. result
230 else
231 return result
232 end
233end
234
235function filesystem.name(path)
236 local parts = segments(path)
237 return parts[#parts]
238end
239
240function filesystem.proxy(filter)
241 checkArg(1, filter, "string")
242 local address
243 for c in component.list("filesystem", true) do
244 if component.invoke(c, "getLabel") == filter then
245 address = c
246 break
247 end
248 if c:sub(1, filter:len()) == filter then
249 address = c
250 break
251 end
252 end
253 if not address then
254 return nil, "no such file system"
255 end
256 return component.proxy(address)
257end
258
259function filesystem.umount(fsOrPath)
260 checkArg(1, fsOrPath, "string", "table")
261 if type(fsOrPath) == "string" then
262 local node, rest, vnode, vrest = findNode(fsOrPath)
263 if not vrest and vnode.fs then
264 vnode.fs = nil
265 removeEmptyNodes(vnode)
266 return true
267 end
268 end
269 local address = type(fsOrPath) == "table" and fsOrPath.address or fsOrPath
270 local result = false
271 for proxy, path in filesystem.mounts() do
272 local addr = type(proxy) == "table" and proxy.address or proxy
273 if string.sub(addr, 1, address:len()) == address then
274 local node, rest, vnode, vrest = findNode(path)
275 vnode.fs = nil
276 removeEmptyNodes(vnode)
277 result = true
278 end
279 end
280 return result
281end
282
283function filesystem.exists(path)
284 local node, rest, vnode, vrest = findNode(path)
285 if not vrest or vnode.links[vrest] then -- virtual directory or symbolic link
286 return true
287 end
288 if node and node.fs then
289 return node.fs.exists(rest)
290 end
291 return false
292end
293
294function filesystem.size(path)
295 local node, rest, vnode, vrest = findNode(path)
296 if not vnode.fs and (not vrest or vnode.links[vrest]) then
297 return 0 -- virtual directory or symlink
298 end
299 if node.fs and rest then
300 return node.fs.size(rest)
301 end
302 return 0 -- no such file or directory
303end
304
305function filesystem.isDirectory(path)
306 local node, rest, vnode, vrest = findNode(path)
307 if not vnode.fs and not vrest then
308 return true -- virtual directory
309 end
310 if node.fs then
311 return not rest or node.fs.isDirectory(rest)
312 end
313 return false
314end
315
316function filesystem.lastModified(path)
317 local node, rest, vnode, vrest = findNode(path)
318 if not vnode.fs and not vrest then
319 return 0 -- virtual directory
320 end
321 if node.fs and rest then
322 return node.fs.lastModified(rest)
323 end
324 return 0 -- no such file or directory
325end
326
327function filesystem.list(path)
328 local node, rest, vnode, vrest = findNode(path)
329 if not vnode.fs and vrest and not (node and node.fs) then
330 return nil, "no such file or directory"
331 end
332 local result, reason
333 if node and node.fs then
334 result, reason = node.fs.list(rest or "")
335 end
336 result = result or {}
337 if not vrest then
338 for k in pairs(vnode.children) do
339 table.insert(result, k .. "/")
340 end
341 for k in pairs(vnode.links) do
342 table.insert(result, k)
343 end
344 end
345 table.sort(result)
346 local i, f = 1, nil
347 while i <= #result do
348 if result[i] == f then
349 table.remove(result, i)
350 else
351 f = result[i]
352 i = i + 1
353 end
354 end
355 local i = 0
356 return function()
357 i = i + 1
358 return result[i]
359 end
360end
361
362function filesystem.makeDirectory(path)
363 if filesystem.exists(path) then
364 return nil, "file or directory with that name already exists"
365 end
366 local node, rest = findNode(path)
367 if node.fs and rest then
368 local success, reason = node.fs.makeDirectory(rest)
369 if not success and not reason and node.fs.isReadOnly() then
370 reason = "filesystem is readonly"
371 end
372 return success, reason
373 end
374 if node.fs then
375 return nil, "virtual directory with that name already exists"
376 end
377 return nil, "cannot create a directory in a virtual directory"
378end
379
380function filesystem.remove(path)
381 local function removeVirtual()
382 local node, rest, vnode, vrest = findNode(filesystem.path(path))
383 -- vrest represents the remaining path beyond vnode
384 -- vrest is nil if vnode reaches the full path
385 -- thus, if vrest is NOT NIL, then we SHOULD NOT remove children nor links
386 if not vrest then
387 local name = filesystem.name(path)
388 if vnode.children[name] then
389 vnode.children[name] = nil
390 removeEmptyNodes(vnode)
391 return true
392 elseif vnode.links[name] then
393 vnode.links[name] = nil
394 removeEmptyNodes(vnode)
395 return true
396 end
397 end
398 -- return false even if vrest is nil because this means it was a expected
399 -- to be a real file
400 return false
401 end
402 local function removePhysical()
403 node, rest = findNode(path)
404 if node.fs and rest then
405 return node.fs.remove(rest)
406 end
407 return false
408 end
409 local success = removeVirtual()
410 success = removePhysical() or success -- Always run.
411 if success then return true
412 else return nil, "no such file or directory"
413 end
414end
415
416function filesystem.rename(oldPath, newPath)
417 if filesystem.isLink(oldPath) then
418 local node, rest, vnode, vrest = findNode(filesystem.path(oldPath))
419 local target = vnode.links[filesystem.name(oldPath)]
420 local result, reason = filesystem.link(target, newPath)
421 if result then
422 filesystem.remove(oldPath)
423 end
424 return result, reason
425 else
426 local oldNode, oldRest = findNode(oldPath)
427 local newNode, newRest = findNode(newPath)
428 if oldNode.fs and oldRest and newNode.fs and newRest then
429 if oldNode.fs.address == newNode.fs.address then
430 return oldNode.fs.rename(oldRest, newRest)
431 else
432 local result, reason = filesystem.copy(oldPath, newPath)
433 if result then
434 return filesystem.remove(oldPath)
435 else
436 return nil, reason
437 end
438 end
439 end
440 return nil, "trying to read from or write to virtual directory"
441 end
442end
443
444function filesystem.copy(fromPath, toPath)
445 if filesystem.isDirectory(fromPath) then
446 return nil, "cannot copy folders"
447 end
448 local input, reason = io.open(fromPath, "rb")
449 if not input then
450 return nil, reason
451 end
452 local output, reason = io.open(toPath, "wb")
453 if not output then
454 input:close()
455 return nil, reason
456 end
457 repeat
458 local buffer, reason = input:read(1024)
459 if not buffer and reason then
460 return nil, reason
461 elseif buffer then
462 local result, reason = output:write(buffer)
463 if not result then
464 input:close()
465 output:close()
466 return nil, reason
467 end
468 end
469 until not buffer
470 input:close()
471 output:close()
472 return true
473end
474
475function fileStream:close()
476 if self.handle then
477 self.fs.close(self.handle)
478 self.handle = nil
479 end
480end
481
482function fileStream:read(n)
483 if not self.handle then
484 return nil, "file is closed"
485 end
486 return self.fs.read(self.handle, n)
487end
488
489function fileStream:seek(whence, offset)
490 if not self.handle then
491 return nil, "file is closed"
492 end
493 return self.fs.seek(self.handle, whence, offset)
494end
495
496function fileStream:write(str)
497 if not self.handle then
498 return nil, "file is closed"
499 end
500 return self.fs.write(self.handle, str)
501end
502
503function filesystem.open(path, mode)
504 checkArg(1, path, "string")
505 mode = tostring(mode or "r")
506 checkArg(2, mode, "string")
507
508 assert(({r=true, rb=true, w=true, wb=true, a=true, ab=true})[mode],
509 "bad argument #2 (r[b], w[b] or a[b] expected, got " .. mode .. ")")
510
511 local node, rest = findNode(path)
512 if not node.fs or not rest or (({r=true,rb=true})[mode] and not node.fs.exists(rest)) then
513 return nil, "file not found"
514 end
515
516 local handle, reason = node.fs.open(rest, mode)
517 if not handle then
518 return nil, reason
519 end
520
521 local stream = {fs = node.fs, handle = handle}
522
523 local metatable = {__index = fileStream,
524 __metatable = "filestream"}
525 return setmetatable(stream, metatable)
526end
527
528-------------------------------------------------------------------------------
529
530return filesystem