6f929519b73057c06b2a9d9def4bc6adbe585d81
[project/luci.git] / libs / nixio / lua / nixio / util.lua
1 --[[
2 nixio - Linux I/O library for lua
3
4 Copyright 2009 Steven Barth <steven@midlink.org>
5
6 Licensed under the Apache License, Version 2.0 (the "License");
7 you may not use this file except in compliance with the License.
8 You may obtain a copy of the License at
9
10 http://www.apache.org/licenses/LICENSE-2.0
11
12 $Id$
13 ]]--
14
15 local table = require "table"
16 local nixio = require "nixio"
17 local getmetatable, assert, pairs, type = getmetatable, assert, pairs, type
18
19 module "nixio.util"
20
21 local BUFFERSIZE = nixio.const.buffersize
22 local ZIOBLKSIZE = 65536
23 local socket = nixio.meta_socket
24 local tls_socket = nixio.meta_tls_socket
25 local file = nixio.meta_file
26
27 function consume(iter)
28         local tbl = {}
29         for obj in iter do
30                 tbl[#tbl+1] = obj
31         end
32         return tbl
33 end
34
35 local meta = {}
36
37 function meta.is_socket(self)
38         return (getmetatable(self) == socket)
39 end
40
41 function meta.is_tls_socket(self)
42         return (getmetatable(self) == tls_socket)
43 end
44
45 function meta.is_file(self)
46         return (getmetatable(self) == file)
47 end
48
49 function meta.readall(self, len)
50         local block, code, msg = self:read(len or BUFFERSIZE)
51
52         if not block then
53                 return nil, code, msg, ""
54         elseif #block == 0 then
55                 return "", nil, nil, ""
56         end
57
58         local data, total = {block}, #block
59
60         while not len or len > total do
61                 block, code, msg = self:read(len and (len - total) or BUFFERSIZE)
62
63                 if not block then
64                         return nil, code, msg, table.concat(data)
65                 elseif #block == 0 then
66                         break
67                 end
68
69                 data[#data+1], total = block, total + #block
70         end
71
72         local data = #data > 1 and table.concat(data) or data[1]
73         return data, nil, nil, data
74 end
75 meta.recvall = meta.readall
76
77 function meta.writeall(self, data)
78         local sent, code, msg = self:write(data)
79
80         if not sent then
81                 return nil, code, msg, 0
82         end
83
84         local total = sent 
85
86         while total < #data do
87                 sent, code, msg = self:write(data, total)
88
89                 if not sent then
90                         return nil, code, msg, total
91                 end
92
93                 total = total + sent
94         end
95         
96         return total, nil, nil, total
97 end
98 meta.sendall = meta.writeall
99
100 function meta.linesource(self, limit)
101         limit = limit or BUFFERSIZE
102         local buffer = ""
103         local bpos = 0
104         return function(flush)
105                 local line, endp, _
106                 
107                 if flush then
108                         line = buffer:sub(bpos + 1)
109                         buffer = type(flush) == "string" and flush or ""
110                         bpos = 0
111                         return line
112                 end
113
114                 while not line do
115                         _, endp, line = buffer:find("(.-)\r?\n", bpos + 1)
116                         if line then
117                                 bpos = endp
118                                 return line
119                         elseif #buffer < limit + bpos then
120                                 local newblock, code, msg = self:read(limit + bpos - #buffer)
121                                 if not newblock then
122                                         return nil, code, msg
123                                 elseif #newblock == 0 then
124                                         return nil
125                                 end
126                                 buffer = buffer:sub(bpos + 1) .. newblock
127                                 bpos = 0
128                         else
129                                 return nil, 0
130                         end
131                 end
132         end
133 end
134
135 function meta.blocksource(self, bs, limit)
136         bs = bs or BUFFERSIZE
137         return function()
138                 local toread = bs
139                 if limit then
140                         if limit < 1 then
141                                 return nil
142                         elseif limit < toread then
143                                 toread = limit
144                         end
145                 end
146
147                 local block, code, msg = self:read(toread)
148
149                 if not block then
150                         return nil, code, msg
151                 elseif #block == 0 then
152                         return nil
153                 else
154                         if limit then
155                                 limit = limit - #block
156                         end
157
158                         return block
159                 end
160         end
161 end
162
163 function meta.sink(self, close)
164         return function(chunk, src_err)
165                 if not chunk and not src_err and close then
166                         if self.shutdown then
167                                 self:shutdown()
168                         end
169                         self:close()
170                 elseif chunk and #chunk > 0 then
171                         return self:writeall(chunk)
172                 end
173                 return true
174         end
175 end
176
177 function meta.copy(self, fdout, size)
178         local source = self:blocksource(nil, size)
179         local sink = fdout:sink()
180         local sent, chunk, code, msg = 0
181         
182         repeat
183                 chunk, code, msg = source()
184                 sink(chunk, code, msg)
185                 sent = chunk and (sent + #chunk) or sent
186         until not chunk
187         return not code and sent or nil, code, msg, sent
188 end
189
190 function meta.copyz(self, fd, size)
191         local sent, lsent, code, msg = 0
192         local splicable
193
194         if self:is_file() then
195                 local ftype = self:stat("type")
196                 if nixio.sendfile and fd:is_socket() and ftype == "reg" then
197                         repeat
198                                 lsent, code, msg = nixio.sendfile(fd, self, size or ZIOBLKSIZE)
199                                 if lsent then
200                                         sent = sent + lsent
201                                         size = size and (size - lsent)
202                                 end
203                         until (not lsent or lsent == 0 or (size and size == 0))
204                         if lsent or (not lsent and sent == 0 and
205                          code ~= nixio.const.ENOSYS and code ~= nixio.const.EINVAL) then
206                                 return lsent and sent, code, msg, sent
207                         end
208                 elseif nixio.splice and not fd:is_tls_socket() and ftype == "fifo" then 
209                         splicable = true
210                 end
211         end
212
213         if nixio.splice and fd:is_file() and not splicable then
214                 splicable = not self:is_tls_socket() and fd:stat("type") == "fifo"
215         end
216
217         if splicable then
218                 repeat
219                         lsent, code, msg = nixio.splice(self, fd, size or ZIOBLKSIZE)
220                         if lsent then
221                                 sent = sent + lsent
222                                 size = size and (size - lsent)
223                         end
224                 until (not lsent or lsent == 0 or (size and size == 0))
225                 if lsent or (not lsent and sent == 0 and
226                  code ~= nixio.const.ENOSYS and code ~= nixio.const.EINVAL) then
227                         return lsent and sent, code, msg, sent
228                 end             
229         end
230
231         return self:copy(fd, size)
232 end
233
234 function tls_socket.close(self)
235         return self.socket:close()
236 end
237
238 function tls_socket.getsockname(self)
239         return self.socket:getsockname()
240 end
241
242 function tls_socket.getpeername(self)
243         return self.socket:getpeername()
244 end
245
246 function tls_socket.getsockopt(self, ...)
247         return self.socket:getsockopt(...)
248 end
249 tls_socket.getopt = tls_socket.getsockopt
250
251 function tls_socket.setsockopt(self, ...)
252         return self.socket:setsockopt(...)
253 end
254 tls_socket.setopt = tls_socket.setsockopt
255
256 for k, v in pairs(meta) do
257         file[k] = v
258         socket[k] = v
259         tls_socket[k] = v
260 end