1--[[
2 POSIX library for Lua 5.1, 5.2, 5.3 & 5.4.
3 Copyright (C) 2014-2021 Gary V. Vaughan
4]]
5--[[--
6 Legacy Lua POSIX bindings.
7
8 APIs for maintaining compatibility with previous releases.
9
10 @module posix
11]]
12
13
14local LOG_MASK = require 'posix.syslog'.LOG_MASK
15local MODE_MAP = require 'posix._base'.MODE_MAP
16local O_CREAT = require 'posix.fcntl'.O_CREAT
17local O_TRUNC = require 'posix.fcntl'.O_TRUNC
18local O_WRONLY = require 'posix.fcntl'.O_WRONLY
19local S_IRGRP = require 'posix.sys.stat'.S_IRGRP
20local S_IROTH = require 'posix.sys.stat'.S_IROTH
21local S_IRUSR = require 'posix.sys.stat'.S_IRUSR
22local S_IRWXG = require 'posix.sys.stat'.S_IRWXG
23local S_IRWXO= require 'posix.sys.stat'.S_IRWXO
24local S_IRWXU = require 'posix.sys.stat'.S_IRWXU
25local S_ISGID = require 'posix.sys.stat'.S_ISGID
26local S_ISUID = require 'posix.sys.stat'.S_ISUID
27local S_IWGRP = require 'posix.sys.stat'.S_IWGRP
28local S_IWOTH = require 'posix.sys.stat'.S_IWOTH
29local S_IWUSR = require 'posix.sys.stat'.S_IWUSR
30local S_IXGRP = require 'posix.sys.stat'.S_IXGRP
31local S_IXOTH = require 'posix.sys.stat'.S_IXOTH
32local S_IXUSR = require 'posix.sys.stat'.S_IXUSR
33
34local argerror = require 'posix._base'.argerror
35local argscheck = require 'posix._base'.argscheck
36local chmod = require 'posix.sys.stat'.chmod
37local band = require 'posix._base'.band
38local bnot = require 'posix._base'.bnot
39local bor = require 'posix._base'.bor
40local concat = table.concat
41local gsub = string.gsub
42local match = string.match
43local mkdir = require 'posix.sys.stat'.mkdir
44local mkfifo = require 'posix.sys.stat'.mkfifo
45local msgget = require 'posix.sys.msg'.msgget
46local open = require 'posix.fcntl'.open
47local pushmode = require 'posix._base'.pushmode
48local setlogmask = require 'posix.syslog'.setlogmask
49local stat = require 'posix.sys.stat'.stat
50local sub = string.sub
51local tonumber = tonumber
52local umask = require 'posix.sys.stat'.umask
53
54
55local RWXALL = bor(S_IRWXU, S_IRWXG, S_IRWXO)
56local FCREAT = bor(O_CREAT, O_WRONLY, O_TRUNC)
57
58
59local function rwxrwxrwx(modestr)
60   local mode = 0
61   for i = 1, 9 do
62      if sub(modestr, i, i) == MODE_MAP[i].c then
63         mode = bor(mode, MODE_MAP[i].b)
64      elseif sub(modestr, i, i) == 's' then
65         if i == 3 then
66            mode = bor(mode, S_ISUID, S_IXUSR)
67         elseif i == 6 then
68            mode = bor(mode, S_ISGID, S_IXGRP)
69         else
70            return nil   -- bad mode
71         end
72      end
73   end
74   return mode
75end
76
77
78local function octal_mode(modestr)
79   local mode = 0
80   for i = 1, #modestr do
81      mode = mode * 8 + tonumber(sub(modestr, i, i))
82   end
83   return mode
84end
85
86
87local function mode_munch(mode, modestr)
88   if modestr == nil then
89      return nil, 'string expected, got no value'
90   elseif #modestr == 9 and match(modestr, '^[-rswx]+$') then
91      return rwxrwxrwx(modestr)
92   elseif match(modestr, '^[0-7]+$') then
93      return octal_mode(modestr)
94   elseif match(modestr, '^[ugoa]+%s*[-+=]%s*[rswx]+,*') then
95      gsub(modestr, '%s*(%a+)%s*(.)%s*(%a+),*', function(who, op, what)
96         local bits, bobs = 0, 0
97         if match(who, '[ua]') then
98            bits = bor(bits, S_ISUID, S_IRWXU)
99         end
100         if match(who, '[ga]') then
101            bits = bor(bits, S_ISGID, S_IRWXG)
102         end
103         if match(who, '[oa]') then
104            bits = bor(bits, S_IRWXO)
105         end
106         if match(what, 'r') then
107            bobs = bor(bobs, S_IRUSR, S_IRGRP, S_IROTH)
108         end
109         if match(what, 'w') then
110            bobs = bor(bobs, S_IWUSR, S_IWGRP, S_IWOTH)
111         end
112         if match(what, 'x') then
113            bobs = bor(bobs, S_IXUSR, S_IXGRP, S_IXOTH)
114         end
115         if match(what, 's') then
116            bobs = bor(bobs, S_ISUID, S_ISGID)
117         end
118         if op == '+' then
119            -- mode |= bits & bobs
120            mode = bor(mode, band(bits, bobs))
121         elseif op == '-' then
122            -- mode &= ~(bits & bobs)
123            mode = band(mode, bnot(band(bits, bobs)))
124         elseif op == '=' then
125            -- mode =(mode & ~bits) |(bits & bobs)
126            mode = bor(band(mode, bnot(bits)), band(bits, bobs))
127         end
128      end)
129      return mode
130   else
131      return nil, 'bad mode'
132   end
133end
134
135
136return {
137   --- Change the mode of the path.
138   -- @function chmod
139   -- @string path existing file path
140   -- @string mode one of the following formats:
141   --
142   --    * 'rwxrwxrwx' (e.g. 'rw-rw-r--')
143   --    * 'ugo+-=rwx' (e.g. 'u+w')
144   --    * '+-=rwx'    (e.g. '+w')
145   --
146   -- @return[1] int `0`, if successful
147   -- @return[2] nil
148   -- @treturn[2] string error message
149   -- @treturn[2] int errnum
150   -- @see chmod(2)
151   -- @usage chmod('bin/dof', '+x')
152   chmod = argscheck('chmod(string, string)', function(path, modestr)
153      local mode = (stat(path) or {}).st_mode
154      local bits, err = mode_munch(mode or 0, modestr)
155      if bits == nil then
156         argerror('chmod', 2, err, 2)
157      end
158      return chmod(path, band(bits, RWXALL))
159   end),
160
161   --- Create a file.
162   -- This function is obsoleted by @{posix.fcntl.open} with `posix.O_CREAT`.
163   -- @function creat
164   -- @string path name of file to create
165   -- @string mode permissions with which to create file
166   -- @treturn[1] int file descriptor of file at *path*, if successful
167   -- @return[2] nil
168   -- @treturn[2] string error message
169   -- @treturn[2] int errnum
170   -- @see creat(2)
171   -- @see posix.chmod
172   -- @usage
173   --    fd = creat('data', 'rw-r-----')
174   creat = argscheck('creat(string, string)', function(path, modestr)
175      local bits, err = mode_munch(0, modestr)
176      if bits == nil then
177         argerror('creat', 2, err, 2)
178      end
179      return open(path, FCREAT, band(bits, RWXALL))
180   end),
181
182   --- Make a directory.
183   -- @function mkdir
184   -- @string path location in file system to create directory
185   -- @treturn[1] int `0`, if successful
186   -- @return[2] nil
187   -- @treturn[2] string error message
188   -- @treturn[2] int errnum
189   mkdir = argscheck('mkdir(string)', function(path)
190      return mkdir(path, RWXALL)
191   end),
192
193   --- Make a FIFO pipe.
194   -- @function mkfifo
195   -- @string path location in file system to create fifo
196   -- @treturn[1] int `0`, if successful
197   -- @return[2] nil
198   -- @treturn[2] string error message
199   -- @treturn[2] int errnum
200   mkfifo = argscheck('mkfifo(string)', function(path)
201      return mkfifo(path, RWXALL)
202   end),
203
204   --- Get a message queue identifier
205   -- @function msgget
206   -- @int key message queue id, or `IPC_PRIVATE` for a new queue
207   -- @int[opt=0] flags bitwise OR of zero or more from `IPC_CREAT` and `IPC_EXCL`
208   -- @string[opt='rw-rw-rw-'] mode execute bits are ignored
209   -- @treturn[1] int message queue identifier, if successful
210   -- @return[2] nil
211   -- @treturn[2] string error message
212   -- @treturn[2] int errnum
213   -- @see msgget(2)
214   msgget = argscheck('msgget(int, ?int, ?string)', function(key, msgflg, modestr)
215      local bits, err = mode_munch(0, modestr)
216      if bits == nil then
217         argerror('msgget', 3, err, 2)
218      end
219      return msgget(key, bor(msgflg, band(bits, RWXALL)))
220   end),
221
222   --- Open a file.
223   -- @function open
224   -- @string path file to act on
225   -- @int oflags bitwise OR of zero or more of `O_RDONLY`, `O_WRONLY`, `O_RDWR`,
226   --  `O_APPEND`, `O_CREAT`, `O_DSYNC`, `O_EXCL`, `O_NOCTTY`, `O_NONBLOCK`,
227   --  `O_RSYNC`, `O_SYNC`, `O_TRUNC`
228   -- @string modestr(used with `O_CREAT`; see @{chmod} for format)
229   -- @treturn[1] int file descriptor for *path*, if successful
230   -- @return[2] nil
231   -- @treturn[2] string error message
232   -- @treturn[2] int errnum
233   -- @see open(2)
234   -- @usage
235   -- fd = posix.open('data', bit.bor(posix.O_CREAT, posix.O_RDWR), 'rw-r-----')
236   open = argscheck('open(string, int, [string])', function(path, oflags, modestr)
237      local bits
238      if band(oflags, O_CREAT) ~= 0 then
239         bits, err = mode_munch(0, modestr)
240         if bits == nil then
241            argerror('open', 3, err, 2)
242         end
243         bits = band(bits, RWXALL)
244      end
245      return open(path, oflags, bits)
246   end),
247
248   --- Set log priority mask
249   -- @function setlogmask
250   -- @int ... zero or more of `LOG_EMERG`, `LOG_ALERT`, `LOG_CRIT`,
251   --  `LOG_WARNING`, `LOG_NOTICE`, `LOG_INFO` and `LOG_DEBUG`
252   -- @treturn[1] int `0`, if successful
253   -- @return[2] nil
254   -- @treturn[2] string error message
255   -- @treturn[2] int errnum
256   setlogmask = argscheck('setlogmask(?int...)', function(...)
257      local mask, i, t = 0, 1, {...}
258      while t[i] do
259         mask = bor(mask, LOG_MASK(t[i]))
260         i = i + 1
261      end
262      return setlogmask(mask)
263   end),
264
265   --- Set file mode creation mask.
266   -- @function umask
267   -- @string[opt] mode file creation mask string
268   -- @treturn string previous umask
269   -- @see umask(2)
270   -- @see posix.sys.stat.umask
271   umask = argscheck('umask(?string)', function(modestr)
272      modestr = modestr or ''
273      local mode = umask(0)
274      umask(mode)
275      mode = band(bnot(mode), RWXALL)
276      if modestr ~= '' then
277         local bits, err = mode_munch(mode, modestr)
278         if bits == nil then
279            argerror('umask', 1, err, 2)
280         end
281         mode = band(bits, RWXALL)
282         umask(bnot(mode))
283      end
284      return pushmode(mode)
285   end),
286}
287