about summary refs log tree commit diff
path: root/fs.lua
blob: f612a593522661b29e60c365a0d2b0829ce7d551 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
-- cqueue filesystem with luaposix
-- TODO: closing files
-- TODO: error handling. right now it *always* returns errno instead of
-- throwing it, which may be what we want. code depends on this
local unistd = require("posix.unistd")
local cqueues = require("cqueues")

local file = {}

function file.new(fd, mode)
	if not mode then mode = "r" end
	local f = setmetatable({}, { __index = file })
	f.pollfd = fd
	f.mode = mode
	if string.match(mode, "r") then
		f.rbuf = {}
	end

	if string.match(mode, "w") then
		f.wbuf = {}
	end

	return f
end

function file.events(f)
	return f.mode
end

-- input/output buffers
--
-- buffers are arrays of objects containing "data" (a string) and "index" (their position)
-- indexing allows us to avoid recreating string objects

local function buffer_length(buf)
	local total = 0
	for _, entry in ipairs(buf) do
		total = total + #entry.data - entry.index + 1
	end
	return total
end

-- returns nil on empty length
local function buffer_get(buf, length)
	local out = {}
	if not length then length = buffer_length(buf) end
	while length > 0 do
		assert(#buf > 0)

		local x = string.sub(buf[1].data, buf[1].index, buf[1].index + length - 1)
		table.insert(out, x)
		if #buf[1].data - buf[1].index + 1 <= length then
			table.remove(buf, 1)
		else
			buf[1].index = buf[1].index + length
		end
		length = length - #x
	end
	if out[1] then return table.concat(out) else return nil end
end

local function buffer_char_index(buf, char)
	local num = 1
	for _, entry in ipairs(buf) do
		if string.find(entry.data, char, entry.index, true) then
			return num + string.find(entry.data, char, entry.index, true) - entry.index
		else
			num = num + #entry.data - entry.index + 1
		end
	end

	return nil
end

do
	local test_buf = { { data = "abcde", index = 2 }, { data = "\na", index = 1 } }
	assert(buffer_length(test_buf) == 6)
	assert(buffer_char_index(test_buf, "\n") == 5)
	assert(buffer_get(test_buf, 5) == "bcde\n")
	assert(buffer_length(test_buf) == 1)
end

local function try_read(buffer, fd, max)
	cqueues.poll({ pollfd = fd, events = function () return "r" end })

	local data, _, errno = unistd.read(fd, max)

	if not data then return nil, errno end
	if #data == 0 then return nil end

	table.insert(buffer, { data = data, index = 1 })

	return #data
end

function file.read(f, what)
	assert(f.rbuf)

	if (type(what) == "number" and what > 0) then
		while buffer_length(f.rbuf) < what do
			if not try_read(f.rbuf, f.pollfd, what - buffer_length(f.rbuf)) then
				break
			end
		end

		return buffer_get(f.rbuf, math.min(what, buffer_length(f.rbuf)))
	elseif (type(what) == "number" and what < 0) then
		while buffer_length(f.rbuf) == 0 do
			if not try_read(f.rbuf, f.pollfd, 0 - what) then return nil end
		end
		return buffer_get(f.rbuf, math.min(0 - what, buffer_length(f.rbuf)))
	elseif what == "*l" or what == "*L" then
		while not buffer_char_index(f.rbuf, "\n") do
			-- TODO: constantize this
			local x = try_read(f.rbuf, f.pollfd, 1024)
			if not x then
				break
			end
		end

		local line = buffer_get(f.rbuf, buffer_char_index(f.rbuf, "\n"))

		if not line then
			return nil
		end
		local ret = string.gsub(
			line,
			"\n",
			(what == "*l" and "" or "\n")
		)

		return ret
	else
		-- TODO
	end
end

function file.lines(f, what)
	return function () return file.read(f, what or "*l") end
end

function file.write(f, data)
	local index = 1
	while index <= #data do
		cqueues.poll({ pollfd = f.pollfd, events = function () return "w" end })
		local nb, _, errno = unistd.write(f.pollfd, string.sub(data, index))
		if nb then
			index = index + nb
		else
			return nil, errno -- TODO?
		end
	end

	return f
end

function file.close(f)
	cqueues.cancel(f.pollfd)
end

return file