aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md48
-rw-r--r--LICENSE.md (renamed from LICENSE)29
-rw-r--r--README.md29
-rw-r--r--appveyor.yml2
-rw-r--r--config.ld16
-rw-r--r--doc_topics/01-introduction.md12
-rw-r--r--doc_topics/ldoc.css291
-rw-r--r--luasystem-scm-0.rockspec6
-rw-r--r--rockspecs/luasystem-0.2.1-1.rockspec2
-rw-r--r--spec/01-time_spec.lua77
-rw-r--r--spec/02-random_spec.lua47
-rw-r--r--spec/03-environment_spec.lua81
-rw-r--r--spec/time_spec.lua31
-rw-r--r--src/Makefile4
-rw-r--r--src/compat.h27
-rw-r--r--src/core.c14
-rw-r--r--src/environment.c173
-rw-r--r--src/random.c117
-rw-r--r--src/time.c87
19 files changed, 998 insertions, 95 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..56d1886
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,48 @@
1# CHANGELOG
2
3## Versioning
4
5This library is versioned based on Semantic Versioning ([SemVer](https://semver.org/)).
6
7#### Version scoping
8
9The scope of what is covered by the version number excludes:
10
11- error messages; the text of the messages can change, unless specifically documented.
12
13#### Releasing new versions
14
15- create a release branch
16- update the changelog below
17- update version and copyright-years in `./LICENSE.md` and `./src/time.c` (in module constants)
18- create a new rockspec and update the version inside the new rockspec:<br/>
19 `cp luasystem-scm-0.rockspec ./rockspecs/luasystem-X.Y.Z-1.rockspec`
20- clean and render the docs: run `ldoc .`
21- commit the changes as `Release vX.Y.Z`
22- push the commit, and create a release PR
23- after merging tag the release commit with `vX.Y.Z`
24- upload to LuaRocks:<br/>
25 `luarocks upload ./rockspecs/luasystem-X.Y.Z-1.rockspec --api-key=ABCDEFGH`
26- test the newly created rock:<br/>
27 `luarocks install luasystem`
28
29## Version history
30
31### Version X.Y.Z, unreleased
32
33- Feat: on Windows `sleep` now has a precision parameter
34- Feat: `setenv` added to set environment variables.
35- Feat: `getenvs` added to list environment variables.
36- Feat: `getenv` added to get environment variable previously set (Windows).
37- Feat: `random` added to return high-quality random bytes
38- Feat: `isatty` added to check if a file-handle is a tty
39
40### Version 0.2.1, released 02-Oct-2016
41
42### Version 0.2.0, released 08-May-2016
43
44### Version 0.1.1, released 10-Apr-2016
45
46### Version 0.1.0, released 11-Feb-2016
47
48- initial release
diff --git a/LICENSE b/LICENSE.md
index 0421a4a..df2befb 100644
--- a/LICENSE
+++ b/LICENSE.md
@@ -1,20 +1,21 @@
1MIT License Terms 1# MIT License
2=================
3 2
4Permission is hereby granted, free of charge, to any person obtaining a copy of 3### Copyright (c) 2016-2023 Oscar Lim
5this software and associated documentation files (the "Software"), to deal in 4
6the Software without restriction, including without limitation the rights to 5Permission is hereby granted, free of charge, to any person obtaining a copy of
7use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 6this software and associated documentation files (the "Software"), to deal in
8of the Software, and to permit persons to whom the Software is furnished to do 7the Software without restriction, including without limitation the rights to
8use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9of the Software, and to permit persons to whom the Software is furnished to do
9so, subject to the following conditions: 10so, subject to the following conditions:
10 11
11The above copyright notice and this permission notice shall be included in all 12The above copyright notice and this permission notice shall be included in all
12copies or substantial portions of the Software. 13copies or substantial portions of the Software.
13 14
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20SOFTWARE. 21SOFTWARE.
diff --git a/README.md b/README.md
index 3aac3a1..6f3c9f6 100644
--- a/README.md
+++ b/README.md
@@ -1,20 +1,21 @@
1LuaSystem 1[![Unix build](https://img.shields.io/github/actions/workflow/status/lunarmodules/luasystem/unix_build.yml?branch=master&label=Unix%20build&logo=linux)](https://github.com/lunarmodules/luasystem/actions/workflows/unix_build.yml)
2====== 2[![AppVeyor build status](https://img.shields.io/appveyor/build/Tieske/luasystem/master?label=Windows%20build&logo=windows)](https://ci.appveyor.com/project/Tieske/luasystem/branch/master)
3 3[![Lint](https://github.com/lunarmodules/luasystem/workflows/Lint/badge.svg)](https://github.com/lunarmodules/luasystem/actions/workflows/lint.yml)
4[![travis-ci status](https://travis-ci.org/o-lim/luasystem.svg?branch=master)](https://travis-ci.org/o-lim/luasystem/builds) 4[![SemVer](https://img.shields.io/github/v/tag/lunarmodules/luasystem?color=brightgreen&label=SemVer&logo=semver&sort=semver)](CHANGELOG.md)
5 5
6# LuaSystem
6 7
7luasystem is a platform independent system call library for Lua. 8luasystem is a platform independent system call library for Lua.
8Supports Lua >= 5.1 and luajit >= 2.0.0. 9Supports Unix, Windows, MacOS, `Lua >= 5.1` and `luajit >= 2.0.0`.
10
11## License and copyright
12
13See [LICENSE.md](LICENSE.md)
14
15## Documentation
9 16
10Currently the following functions are supported: 17See [online documentation](https://lunarmodules.github.io/luasystem/)
11* gettime
12* monotime
13* sleep
14 18
15License 19## Changelog & Versioning
16-------
17 20
18This code and its accompanying README are 21See [CHANGELOG.md](CHANGELOG.md)
19[MIT licensed](http://www.opensource.org/licenses/mit-license.php).
20See LICENSE for details.
diff --git a/appveyor.yml b/appveyor.yml
index 596c846..f39445b 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -26,4 +26,4 @@ build_script:
26 - luarocks make 26 - luarocks make
27 27
28test_script: 28test_script:
29 - busted 29 - busted --Xoutput "--color"
diff --git a/config.ld b/config.ld
new file mode 100644
index 0000000..c13936d
--- /dev/null
+++ b/config.ld
@@ -0,0 +1,16 @@
1project='Lua-System'
2title='Lua-System docs'
3description='Platform independent system calls for Lua'
4
5format='markdown'
6use_markdown_titles = true
7style="./doc_topics/"
8
9file={'./src/', './system/'}
10topics={'./doc_topics/', './LICENSE.md', './CHANGELOG.md'}
11-- examples = {'./examples'}
12
13dir='docs'
14sort=true
15sort_modules=true
16all=false
diff --git a/doc_topics/01-introduction.md b/doc_topics/01-introduction.md
new file mode 100644
index 0000000..9cc2347
--- /dev/null
+++ b/doc_topics/01-introduction.md
@@ -0,0 +1,12 @@
1# 1. Introduction
2
3luasystem is a platform independent system call library for Lua.
4Supports Unix, Windows, MacOS, `Lua >= 5.1` and `luajit >= 2.0.0`.
5
6Lua is typically platform independent, but it requires adhering to very old C
7standards. This in turn means that many common features (according to todays standards)
8are not available. This module attempts to overcome some of those hurdles by providing
9functions that cover those common needs.
10
11This is not a kitchen sink library, but a minimalistic one with a focus on platform
12independence.
diff --git a/doc_topics/ldoc.css b/doc_topics/ldoc.css
new file mode 100644
index 0000000..5b9fbbf
--- /dev/null
+++ b/doc_topics/ldoc.css
@@ -0,0 +1,291 @@
1body {
2 color: #47555c;
3 font-size: 16px;
4 font-family: "Open Sans", sans-serif;
5 margin: 0;
6 background: #eff4ff;
7}
8
9a:link { color: #008fee; }
10a:visited { color: #008fee; }
11a:hover { color: #22a7ff; }
12
13h1 { font-size:26px; font-weight: normal; }
14h2 { font-size:22px; font-weight: normal; }
15h3 { font-size:18px; font-weight: normal; }
16h4 { font-size:16px; font-weight: bold; }
17
18hr {
19 height: 1px;
20 background: #c1cce4;
21 border: 0px;
22 margin: 15px 0;
23}
24
25code, tt {
26 font-family: monospace;
27}
28span.parameter {
29 font-family: monospace;
30 font-weight: bold;
31 color: rgb(99, 115, 131);
32}
33span.parameter:after {
34 content:":";
35}
36span.types:before {
37 content:"(";
38}
39span.types:after {
40 content:")";
41}
42.type {
43 font-weight: bold; font-style:italic
44}
45
46p.name {
47 font-family: "Andale Mono", monospace;
48}
49
50#navigation {
51 float: left;
52 background-color: white;
53 border-right: 1px solid #d3dbec;
54 border-bottom: 1px solid #d3dbec;
55
56 width: 14em;
57 vertical-align: top;
58 overflow: visible;
59}
60
61#navigation br {
62 display: none;
63}
64
65#navigation h1 {
66 background-color: white;
67 border-bottom: 1px solid #d3dbec;
68 padding: 15px;
69 margin-top: 0px;
70 margin-bottom: 0px;
71}
72
73#navigation h2 {
74 font-size: 18px;
75 background-color: white;
76 border-bottom: 1px solid #d3dbec;
77 padding-left: 15px;
78 padding-right: 15px;
79 padding-top: 10px;
80 padding-bottom: 10px;
81 margin-top: 30px;
82 margin-bottom: 0px;
83}
84
85#content h1 {
86 background-color: #2c3e67;
87 color: white;
88 padding: 15px;
89 margin: 0px;
90}
91
92#content h2 {
93 background-color: #6c7ea7;
94 color: white;
95 padding: 15px;
96 padding-top: 15px;
97 padding-bottom: 15px;
98 margin-top: 0px;
99}
100
101#content h2 a {
102 background-color: #6c7ea7;
103 color: white;
104 text-decoration: none;
105}
106
107#content h2 a:hover {
108 text-decoration: underline;
109}
110
111#content h3 {
112 font-style: italic;
113 padding-top: 15px;
114 padding-bottom: 4px;
115 margin-right: 15px;
116 margin-left: 15px;
117 margin-bottom: 5px;
118 border-bottom: solid 1px #bcd;
119}
120
121#content h4 {
122 margin-right: 15px;
123 margin-left: 15px;
124 border-bottom: solid 1px #bcd;
125}
126
127#content pre {
128 margin: 15px;
129}
130
131pre {
132 background-color: rgb(50, 55, 68);
133 color: white;
134 border-radius: 3px;
135 /* border: 1px solid #C0C0C0; /* silver */
136 padding: 15px;
137 overflow: auto;
138 font-family: "Andale Mono", monospace;
139}
140
141#content ul pre.example {
142 margin-left: 0px;
143}
144
145table.index {
146/* border: 1px #00007f; */
147}
148table.index td { text-align: left; vertical-align: top; }
149
150#navigation ul
151{
152 font-size:1em;
153 list-style-type: none;
154 margin: 1px 1px 10px 1px;
155 padding-left: 20px;
156}
157
158#navigation li {
159 text-indent: -1em;
160 display: block;
161 margin: 3px 0px 0px 22px;
162}
163
164#navigation li li a {
165 margin: 0px 3px 0px -1em;
166}
167
168#content {
169 margin-left: 14em;
170}
171
172#content p {
173 padding-left: 15px;
174 padding-right: 15px;
175}
176
177#content table {
178 padding-left: 15px;
179 padding-right: 15px;
180 background-color: white;
181}
182
183#content p, #content table, #content ol, #content ul, #content dl {
184 max-width: 900px;
185}
186
187#about {
188 padding: 15px;
189 padding-left: 16em;
190 background-color: white;
191 border-top: 1px solid #d3dbec;
192 border-bottom: 1px solid #d3dbec;
193}
194
195table.module_list, table.function_list {
196 border-width: 1px;
197 border-style: solid;
198 border-color: #cccccc;
199 border-collapse: collapse;
200 margin: 15px;
201}
202table.module_list td, table.function_list td {
203 border-width: 1px;
204 padding-left: 10px;
205 padding-right: 10px;
206 padding-top: 5px;
207 padding-bottom: 5px;
208 border: solid 1px rgb(193, 204, 228);
209}
210table.module_list td.name, table.function_list td.name {
211 background-color: white; min-width: 200px; border-right-width: 0px;
212}
213table.module_list td.summary, table.function_list td.summary {
214 background-color: white; width: 100%; border-left-width: 0px;
215}
216
217dl.function {
218 margin-right: 15px;
219 margin-left: 15px;
220 border-bottom: solid 1px rgb(193, 204, 228);
221 border-left: solid 1px rgb(193, 204, 228);
222 border-right: solid 1px rgb(193, 204, 228);
223 background-color: white;
224}
225
226dl.function dt {
227 color: rgb(99, 123, 188);
228 font-family: monospace;
229 border-top: solid 1px rgb(193, 204, 228);
230 padding: 15px;
231}
232
233dl.function dd {
234 margin-left: 15px;
235 margin-right: 15px;
236 margin-top: 5px;
237 margin-bottom: 15px;
238}
239
240#content dl.function dd h3 {
241 margin-top: 0px;
242 margin-left: 0px;
243 padding-left: 0px;
244 font-size: 16px;
245 color: rgb(128, 128, 128);
246 border-bottom: solid 1px #def;
247}
248
249#content dl.function dd ul, #content dl.function dd ol {
250 padding: 0px;
251 padding-left: 15px;
252 list-style-type: none;
253}
254
255ul.nowrap {
256 overflow:auto;
257 white-space:nowrap;
258}
259
260.section-description {
261 padding-left: 15px;
262 padding-right: 15px;
263}
264
265/* stop sublists from having initial vertical space */
266ul ul { margin-top: 0px; }
267ol ul { margin-top: 0px; }
268ol ol { margin-top: 0px; }
269ul ol { margin-top: 0px; }
270
271/* make the target distinct; helps when we're navigating to a function */
272a:target + * {
273 background-color: #FF9;
274}
275
276
277/* styles for prettification of source */
278pre .comment { color: #bbccaa; }
279pre .constant { color: #a8660d; }
280pre .escape { color: #844631; }
281pre .keyword { color: #ffc090; font-weight: bold; }
282pre .library { color: #0e7c6b; }
283pre .marker { color: #512b1e; background: #fedc56; font-weight: bold; }
284pre .string { color: #8080ff; }
285pre .number { color: #f8660d; }
286pre .operator { color: #2239a8; font-weight: bold; }
287pre .preprocessor, pre .prepro { color: #a33243; }
288pre .global { color: #c040c0; }
289pre .user-keyword { color: #800080; }
290pre .prompt { color: #558817; }
291pre .url { color: #272fc2; text-decoration: underline; }
diff --git a/luasystem-scm-0.rockspec b/luasystem-scm-0.rockspec
index 74e301d..96f10ae 100644
--- a/luasystem-scm-0.rockspec
+++ b/luasystem-scm-0.rockspec
@@ -11,7 +11,7 @@ version = package_version.."-"..rockspec_revision
11source = { 11source = {
12 url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", 12 url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git",
13 branch = (package_version == "scm") and "master" or nil, 13 branch = (package_version == "scm") and "master" or nil,
14 tag = (package_version ~= "scm") and package_version or nil, 14 tag = (package_version ~= "scm") and "v"..package_version or nil,
15} 15}
16 16
17description = { 17description = {
@@ -39,13 +39,13 @@ local function make_platform(plat)
39 linux = { "rt" }, 39 linux = { "rt" },
40 unix = { }, 40 unix = { },
41 macosx = { }, 41 macosx = { },
42 win32 = { }, 42 win32 = { "advapi32", "winmm" },
43 mingw32 = { }, 43 mingw32 = { },
44 } 44 }
45 return { 45 return {
46 modules = { 46 modules = {
47 ['system.core'] = { 47 ['system.core'] = {
48 sources = { 'src/core.c', 'src/compat.c', 'src/time.c', }, 48 sources = { 'src/core.c', 'src/compat.c', 'src/time.c', 'src/environment.c', 'src/random.c' },
49 defines = defines[plat], 49 defines = defines[plat],
50 libraries = libraries[plat], 50 libraries = libraries[plat],
51 }, 51 },
diff --git a/rockspecs/luasystem-0.2.1-1.rockspec b/rockspecs/luasystem-0.2.1-1.rockspec
index 7d8b9b0..e14118d 100644
--- a/rockspecs/luasystem-0.2.1-1.rockspec
+++ b/rockspecs/luasystem-0.2.1-1.rockspec
@@ -11,7 +11,7 @@ version = package_version.."-"..rockspec_revision
11source = { 11source = {
12 url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", 12 url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git",
13 branch = (package_version == "scm") and "master" or nil, 13 branch = (package_version == "scm") and "master" or nil,
14 tag = (package_version ~= "scm") and package_version or nil, 14 tag = (package_version ~= "scm") and "v"..package_version or nil,
15} 15}
16 16
17description = { 17description = {
diff --git a/spec/01-time_spec.lua b/spec/01-time_spec.lua
new file mode 100644
index 0000000..80faa75
--- /dev/null
+++ b/spec/01-time_spec.lua
@@ -0,0 +1,77 @@
1local system = require 'system.core'
2
3describe('Test time functions', function()
4
5 -- returns the new second, on the new second
6 local function wait_for_second_rollover()
7 local start_time = math.floor(os.time())
8 local end_time = math.floor(os.time())
9 while end_time == start_time do
10 end_time = math.floor(os.time())
11 end
12 return end_time
13 end
14
15
16 describe("time()", function()
17
18 it('returns current time', function()
19 wait_for_second_rollover()
20 local expected_time = wait_for_second_rollover()
21 local received_time = system.gettime()
22 assert.is.near(expected_time, received_time, 0.02)
23
24 wait_for_second_rollover()
25 assert.is.near(1, system.gettime() - received_time, 0.02)
26 end)
27
28 end)
29
30
31
32 describe("monotime()", function()
33
34 it('returns monotonically increasing time', function()
35 local starttime = system.monotime()
36 local endtime = system.monotime()
37 local delta = endtime - starttime
38 assert.is_true(starttime > 0)
39 assert.is_true(delta >= 0)
40 assert.is_true(system.monotime() - endtime >= 0)
41 end)
42
43 end)
44
45
46
47 describe("sleep()", function()
48
49 it("should sleep for the specified time", function()
50 local start_time = system.gettime()
51 system.sleep(1, 1)
52 local end_time = system.gettime()
53 local elapsed_time = end_time - start_time
54 assert.is.near(elapsed_time, 1, 0.01)
55 end)
56
57
58 it("should sleep for the specified time; fractional", function()
59 local start_time = system.gettime()
60 system.sleep(0.5, 1)
61 local end_time = system.gettime()
62 local elapsed_time = end_time - start_time
63 assert.is.near(0.5, elapsed_time, 0.01)
64 end)
65
66
67 it("should return immediately for a non-positive sleep time", function()
68 local start_time = system.gettime()
69 system.sleep(-1)
70 local end_time = system.gettime()
71 local elapsed_time = end_time - start_time
72 assert.is.near(elapsed_time, 0, 0.01)
73 end)
74
75 end)
76
77end)
diff --git a/spec/02-random_spec.lua b/spec/02-random_spec.lua
new file mode 100644
index 0000000..23b6d95
--- /dev/null
+++ b/spec/02-random_spec.lua
@@ -0,0 +1,47 @@
1local system = require("system")
2
3describe("Random:", function()
4
5 describe("random()", function()
6
7 it("should return random bytes for a valid number of bytes", function()
8 local num_bytes = 1
9 local result, err_msg = system.random(num_bytes)
10 assert.is_nil(err_msg)
11 assert.is.string(result)
12 assert.is_equal(num_bytes, #result)
13 end)
14
15
16 it("should return an empty string for 0 bytes", function()
17 local num_bytes = 0
18 local result, err_msg = system.random(num_bytes)
19 assert.is_nil(err_msg)
20 assert.are.equal("", result)
21 end)
22
23
24 it("should return an error message for an invalid number of bytes", function()
25 local num_bytes = -1
26 local result, err_msg = system.random(num_bytes)
27 assert.is.falsy(result)
28 assert.are.equal("invalid number of bytes, must not be less than 0", err_msg)
29 end)
30
31
32 it("should not return duplicate results", function()
33 local num_bytes = 1025
34 local result1, err_msg = system.random(num_bytes)
35 assert.is_nil(err_msg)
36 assert.is.string(result1)
37
38 local result2, err_msg = system.random(num_bytes)
39 assert.is_nil(err_msg)
40 assert.is.string(result2)
41
42 assert.is_not.equal(result1, result2)
43 end)
44
45 end)
46
47end)
diff --git a/spec/03-environment_spec.lua b/spec/03-environment_spec.lua
new file mode 100644
index 0000000..842ed6f
--- /dev/null
+++ b/spec/03-environment_spec.lua
@@ -0,0 +1,81 @@
1-- Import the library that contains the environment-related functions
2local system = require("system")
3
4describe("Environment Variables:", function()
5
6 describe("setenv()", function()
7
8 it("should set an environment variable", function()
9 assert.is_true(system.setenv("TEST_VAR", "test_value"))
10 assert.is_equal("test_value", system.getenv("TEST_VAR"))
11 end)
12
13
14 local func = system.windows and pending or it --pending on Windows
15 -- Windows will unset a variable if set as an empty string
16 func("should set an empty environment variable value", function()
17 assert.is_true(system.setenv("TEST_VAR", ""))
18 assert.is_equal("", system.getenv("TEST_VAR"))
19 end)
20
21
22 it("should unset an environment variable on nil", function()
23 assert.is_true(system.setenv("TEST_VAR", "test_value"))
24 assert.is_equal("test_value", system.getenv("TEST_VAR"))
25
26 assert.is_true(system.setenv("TEST_VAR", nil))
27 assert.is_nil(system.getenv("TEST_VAR"))
28 end)
29
30
31 it("should error on input bad type", function()
32 assert.has_error(function()
33 system.setenv("TEST_VAR", {})
34 end)
35 assert.has_error(function()
36 system.setenv({}, "test_value")
37 end)
38 end)
39
40
41 it("should return success on deleting a variable that doesn't exist", function()
42 if system.getenv("TEST_VAR") ~= nil then
43 -- clear if it was already set
44 assert.is_true(system.setenv("TEST_VAR", nil))
45 end
46
47 assert.is_true(system.setenv("TEST_VAR", nil)) -- clear again shouldn't fail
48 end)
49
50 end)
51
52
53
54 describe("getenvs()", function()
55
56 it("should list environment variables", function()
57 assert.is_true(system.setenv("TEST_VAR1", nil))
58 assert.is_true(system.setenv("TEST_VAR2", nil))
59 assert.is_true(system.setenv("TEST_VAR3", nil))
60 local envVars1 = system.getenvs()
61 assert.is_true(system.setenv("TEST_VAR1", "test_value1"))
62 assert.is_true(system.setenv("TEST_VAR2", "test_value2"))
63 assert.is_true(system.setenv("TEST_VAR3", "test_value3"))
64 local envVars2 = system.getenvs()
65 assert.is_true(system.setenv("TEST_VAR1", nil))
66 assert.is_true(system.setenv("TEST_VAR2", nil))
67 assert.is_true(system.setenv("TEST_VAR3", nil))
68
69 for k,v in pairs(envVars1) do
70 envVars2[k] = nil
71 end
72 assert.are.same({
73 TEST_VAR1 = "test_value1",
74 TEST_VAR2 = "test_value2",
75 TEST_VAR3 = "test_value3",
76 }, envVars2)
77 end)
78
79 end)
80
81end)
diff --git a/spec/time_spec.lua b/spec/time_spec.lua
deleted file mode 100644
index a017cfe..0000000
--- a/spec/time_spec.lua
+++ /dev/null
@@ -1,31 +0,0 @@
1local system = require 'system.core'
2
3describe('Test time functions', function()
4 it('gettime returns current time', function()
5 local starttime = system.gettime()
6 local expected = os.time()
7 local endtime = system.gettime()
8 local delta = endtime - starttime
9 local avg = starttime + delta/2
10 assert.is_true(expected >= math.floor(starttime))
11 assert.is_true(expected <= math.ceil(endtime))
12 assert.is_near(expected, avg, 1 + delta)
13 end)
14
15 it('monottime returns monotonically increasing time', function()
16 local starttime = system.monotime()
17 local endtime = system.monotime()
18 local delta = endtime - starttime
19 assert.is_true(starttime > 0)
20 assert.is_true(delta >= 0)
21 assert.is_true(system.monotime() - endtime >= 0)
22 end)
23
24 it('sleep will wait for specified amount of time', function()
25 local starttime = system.gettime()
26 local starttick = system.monotime()
27 system.sleep(0.5)
28 assert.is_near(0.5, system.gettime() - starttime, 0.15)
29 assert.is_near(0.5, system.monotime() - starttick, 0.15)
30 end)
31end)
diff --git a/src/Makefile b/src/Makefile
index 10fc31a..119f95e 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -85,7 +85,7 @@ PLATFORM_win32?=Release
85CDIR_win32?=bin/lua/$(LUA_VERSION)/$(PLATFORM_win32) 85CDIR_win32?=bin/lua/$(LUA_VERSION)/$(PLATFORM_win32)
86LDIR_win32?=bin/lua/$(LUA_VERSION)/$(PLATFORM_win32)/lua 86LDIR_win32?=bin/lua/$(LUA_VERSION)/$(PLATFORM_win32)/lua
87LUALIB_win32?=$(LUAPREFIX_win32)/lib/lua/$(LUA_VERSION)/$(PLATFORM_win32) 87LUALIB_win32?=$(LUAPREFIX_win32)/lib/lua/$(LUA_VERSION)/$(PLATFORM_win32)
88LUALIBNAME_win32?=lua$(subst .,,$(LUA_VERSION)).lib 88LUALIBNAME_win32?=lua$(subst .,,$(LUA_VERSION)).lib
89 89
90 90
91# prefix: /usr/local /usr /opt/local /sw 91# prefix: /usr/local /usr /opt/local /sw
@@ -217,7 +217,7 @@ LUALIB= $(LUALIB_$(PLAT))
217#------ 217#------
218# Objects 218# Objects
219# 219#
220OBJS=core.$(O) compat.$(O) time.$(O) 220OBJS=core.$(O) compat.$(O) time.$(O) environment.$(O) random.$(O)
221 221
222#------ 222#------
223# Targets 223# Targets
diff --git a/src/compat.h b/src/compat.h
index f523fd9..5aca6df 100644
--- a/src/compat.h
+++ b/src/compat.h
@@ -8,4 +8,31 @@
8void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup); 8void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup);
9#endif 9#endif
10 10
11// Windows doesn't have ssize_t, so we define it here
12#ifdef _WIN32
13#if SIZE_MAX == UINT_MAX
14typedef int ssize_t; /* common 32 bit case */
15#define SSIZE_MIN INT_MIN
16#define SSIZE_MAX INT_MAX
17#elif SIZE_MAX == ULONG_MAX
18typedef long ssize_t; /* linux 64 bits */
19#define SSIZE_MIN LONG_MIN
20#define SSIZE_MAX LONG_MAX
21#elif SIZE_MAX == ULLONG_MAX
22typedef long long ssize_t; /* windows 64 bits */
23#define SSIZE_MIN LLONG_MIN
24#define SSIZE_MAX LLONG_MAX
25#elif SIZE_MAX == USHRT_MAX
26typedef short ssize_t; /* is this even possible? */
27#define SSIZE_MIN SHRT_MIN
28#define SSIZE_MAX SHRT_MAX
29#elif SIZE_MAX == UINTMAX_MAX
30typedef intmax_t ssize_t; /* last resort, chux suggestion */
31#define SSIZE_MIN INTMAX_MIN
32#define SSIZE_MAX INTMAX_MAX
33#else
34#error platform has exotic SIZE_MAX
35#endif
36#endif
37
11#endif 38#endif
diff --git a/src/core.c b/src/core.c
index 6c46981..fffedd1 100644
--- a/src/core.c
+++ b/src/core.c
@@ -1,3 +1,6 @@
1/// Platform independent system calls for Lua.
2// @module system
3
1#include <lua.h> 4#include <lua.h>
2#include <lauxlib.h> 5#include <lauxlib.h>
3 6
@@ -10,6 +13,8 @@
10#endif 13#endif
11 14
12void time_open(lua_State *L); 15void time_open(lua_State *L);
16void environment_open(lua_State *L);
17void random_open(lua_State *L);
13 18
14/*------------------------------------------------------------------------- 19/*-------------------------------------------------------------------------
15 * Initializes all library modules. 20 * Initializes all library modules.
@@ -19,6 +24,15 @@ LUAEXPORT int luaopen_system_core(lua_State *L) {
19 lua_pushstring(L, "_VERSION"); 24 lua_pushstring(L, "_VERSION");
20 lua_pushstring(L, LUASYSTEM_VERSION); 25 lua_pushstring(L, LUASYSTEM_VERSION);
21 lua_rawset(L, -3); 26 lua_rawset(L, -3);
27 lua_pushstring(L, "windows");
28#ifdef _WIN32
29 lua_pushboolean(L, 1);
30#else
31 lua_pushboolean(L, 0);
32#endif
33 lua_rawset(L, -3);
22 time_open(L); 34 time_open(L);
35 random_open(L);
36 environment_open(L);
23 return 1; 37 return 1;
24} 38}
diff --git a/src/environment.c b/src/environment.c
new file mode 100644
index 0000000..5f1c3da
--- /dev/null
+++ b/src/environment.c
@@ -0,0 +1,173 @@
1/// @submodule system
2#include <lua.h>
3#include <lauxlib.h>
4#include "compat.h"
5#include <stdlib.h>
6#include <string.h>
7
8#ifdef _WIN32
9#include "windows.h"
10#endif
11
12/***
13Gets the value of an environment variable.
14
15__NOTE__: Windows has multiple copies of environment variables. For this reason,
16the `setenv` function will not work with Lua's `os.getenv` on Windows. If you want
17to use `setenv` then consider patching `os.getenv` with this implementation of `getenv`.
18@function getenv
19@tparam string name name of the environment variable
20@treturn string|nil value of the environment variable, or nil if the variable is not set
21*/
22static int lua_get_environment_variable(lua_State* L) {
23 const char* variableName = luaL_checkstring(L, 1);
24
25#ifdef _WIN32
26 // On Windows, use GetEnvironmentVariable to retrieve the value
27 DWORD bufferSize = GetEnvironmentVariable(variableName, NULL, 0);
28 if (bufferSize > 0) {
29 char* buffer = (char*)malloc(bufferSize);
30 if (GetEnvironmentVariable(variableName, buffer, bufferSize) > 0) {
31 lua_pushstring(L, buffer);
32 free(buffer);
33 return 1;
34 }
35 free(buffer);
36 }
37#else
38 // On non-Windows platforms, use getenv to retrieve the value
39 const char* variableValue = getenv(variableName);
40 if (variableValue != NULL) {
41 lua_pushstring(L, variableValue);
42 return 1;
43 }
44#endif
45
46 // If the variable is not set or an error occurs, push nil
47 lua_pushnil(L);
48 return 1;
49}
50
51
52/***
53Returns a table with all environment variables.
54@function getenvs
55@treturn table table with all environment variables and their values
56*/
57static int lua_list_environment_variables(lua_State* L) {
58 lua_newtable(L);
59
60#ifdef _WIN32
61 char* envStrings = GetEnvironmentStrings();
62 char* envString = envStrings;
63
64 if (envStrings == NULL) {
65 lua_pushnil(L);
66 return 1;
67 }
68
69 while (*envString != '\0') {
70 const char* envVar = envString;
71
72 // Split the environment variable into key and value
73 char* equals = strchr(envVar, '=');
74 if (equals != NULL) {
75 lua_pushlstring(L, envVar, equals - envVar); // Push the key
76 lua_pushstring(L, equals + 1); // Push the value
77 lua_settable(L, -3); // Set the key-value pair in the table
78 }
79
80 envString += strlen(envString) + 1;
81 }
82
83 FreeEnvironmentStrings(envStrings);
84#else
85 extern char** environ;
86
87 if (environ != NULL) {
88 for (char** envVar = environ; *envVar != NULL; envVar++) {
89 const char* envVarStr = *envVar;
90
91 // Split the environment variable into key and value
92 char* equals = strchr(envVarStr, '=');
93 if (equals != NULL) {
94 lua_pushlstring(L, envVarStr, equals - envVarStr); // Push the key
95 lua_pushstring(L, equals + 1); // Push the value
96 lua_settable(L, -3); // Set the key-value pair in the table
97 }
98 }
99 }
100#endif
101
102 return 1;
103}
104
105
106/***
107Sets an environment variable.
108
109__NOTE__: Windows has multiple copies of environment variables. For this reason, the
110`setenv` function will not work with Lua's `os.getenv` on Windows. If you want to use
111it then consider patching `os.getenv` with the implementation of `system.getenv`.
112@function setenv
113@tparam string name name of the environment variable
114@tparam[opt] string value value of the environment variable, if `nil` the variable will be deleted (on
115Windows, setting an empty string, will also delete the variable)
116@treturn boolean success
117*/
118static int lua_set_environment_variable(lua_State* L) {
119 const char* variableName = luaL_checkstring(L, 1);
120 const char* variableValue = luaL_optstring(L, 2, NULL);
121
122#ifdef _WIN32
123 // if (variableValue == NULL) {
124 // // If the value is nil, delete the environment variable
125 // if (SetEnvironmentVariable(variableName, NULL)) {
126 // lua_pushboolean(L, 1);
127 // } else {
128 // lua_pushboolean(L, 0);
129 // }
130 // } else {
131 // Set the environment variable with the provided value
132 if (SetEnvironmentVariable(variableName, variableValue)) {
133 lua_pushboolean(L, 1);
134 } else {
135 lua_pushboolean(L, 0);
136 }
137 // }
138#else
139 if (variableValue == NULL) {
140 // If the value is nil, delete the environment variable
141 if (unsetenv(variableName) == 0) {
142 lua_pushboolean(L, 1);
143 } else {
144 lua_pushboolean(L, 0);
145 }
146 } else {
147 // Set the environment variable with the provided value
148 if (setenv(variableName, variableValue, 1) == 0) {
149 lua_pushboolean(L, 1);
150 } else {
151 lua_pushboolean(L, 0);
152 }
153 }
154#endif
155
156 return 1;
157}
158
159
160
161static luaL_Reg func[] = {
162 { "getenv", lua_get_environment_variable },
163 { "setenv", lua_set_environment_variable },
164 { "getenvs", lua_list_environment_variables },
165 { NULL, NULL }
166};
167
168/*-------------------------------------------------------------------------
169 * Initializes module
170 *-------------------------------------------------------------------------*/
171void environment_open(lua_State *L) {
172 luaL_setfuncs(L, func, 0);
173}
diff --git a/src/random.c b/src/random.c
new file mode 100644
index 0000000..90fb3f2
--- /dev/null
+++ b/src/random.c
@@ -0,0 +1,117 @@
1/// @submodule system
2#include <lua.h>
3#include <lauxlib.h>
4#include "compat.h"
5#include <fcntl.h>
6
7#ifdef _WIN32
8#include "windows.h"
9#include "wincrypt.h"
10#else
11#include <errno.h>
12#include <unistd.h>
13#include <string.h>
14#endif
15
16
17/***
18Generate random bytes.
19This uses `CryptGenRandom()` on Windows, and `/dev/urandom` on other platforms. It will return the
20requested number of bytes, or an error, never a partial result.
21@function random
22@tparam[opt=1] int length number of bytes to get
23@treturn[1] string string of random bytes
24@treturn[2] nil
25@treturn[2] string error message
26*/
27static int lua_get_random_bytes(lua_State* L) {
28 int num_bytes = luaL_optinteger(L, 1, 1); // Number of bytes, default to 1 if not provided
29
30 if (num_bytes <= 0) {
31 if (num_bytes == 0) {
32 lua_pushliteral(L, "");
33 return 1;
34 }
35 lua_pushnil(L);
36 lua_pushstring(L, "invalid number of bytes, must not be less than 0");
37 return 2;
38 }
39
40 unsigned char* buffer = (unsigned char*)lua_newuserdata(L, num_bytes);
41 if (buffer == NULL) {
42 lua_pushnil(L);
43 lua_pushstring(L, "failed to allocate memory for random buffer");
44 return 2;
45 }
46
47 ssize_t n;
48 ssize_t total_read = 0;
49
50#ifdef _WIN32
51 HCRYPTPROV hCryptProv;
52 if (!CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
53 DWORD error = GetLastError();
54 lua_pushnil(L);
55 lua_pushfstring(L, "failed to acquire cryptographic context: %lu", error);
56 return 2;
57 }
58
59 if (!CryptGenRandom(hCryptProv, num_bytes, buffer)) {
60 DWORD error = GetLastError();
61 lua_pushnil(L);
62 lua_pushfstring(L, "failed to get random data: %lu", error);
63 CryptReleaseContext(hCryptProv, 0);
64 return 2;
65 }
66
67 CryptReleaseContext(hCryptProv, 0);
68#else
69
70 // for macOS/unixes use /dev/urandom for non-blocking
71 int fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC);
72 if (fd < 0) {
73 lua_pushnil(L);
74 lua_pushstring(L, "failed opening /dev/urandom");
75 return 2;
76 }
77
78 while (total_read < num_bytes) {
79 n = read(fd, buffer + total_read, num_bytes - total_read);
80
81 if (n < 0) {
82 if (errno == EINTR) {
83 continue; // Interrupted, retry
84
85 } else {
86 lua_pushnil(L);
87 lua_pushfstring(L, "failed reading /dev/urandom: %s", strerror(errno));
88 close(fd);
89 return 2;
90 }
91 }
92
93 total_read += n;
94 }
95
96 close(fd);
97#endif
98
99 lua_pushlstring(L, (const char*)buffer, num_bytes);
100 return 1;
101}
102
103
104
105static luaL_Reg func[] = {
106 { "random", lua_get_random_bytes },
107 { NULL, NULL }
108};
109
110
111
112/*-------------------------------------------------------------------------
113 * Initializes module
114 *-------------------------------------------------------------------------*/
115void random_open(lua_State *L) {
116 luaL_setfuncs(L, func, 0);
117}
diff --git a/src/time.c b/src/time.c
index 8c6a4f2..5f0ead0 100644
--- a/src/time.c
+++ b/src/time.c
@@ -1,3 +1,4 @@
1/// @submodule system
1#include <lua.h> 2#include <lua.h>
2#include <lauxlib.h> 3#include <lauxlib.h>
3 4
@@ -50,11 +51,8 @@ static double time_gettime(void) {
50} 51}
51#endif 52#endif
52 53
53/*------------------------------------------------------------------------- 54
54 * Gets monotonic time in s 55
55 * Returns
56 * time in s.
57 *-------------------------------------------------------------------------*/
58#ifdef _WIN32 56#ifdef _WIN32
59WINBASEAPI ULONGLONG WINAPI GetTickCount64(VOID); 57WINBASEAPI ULONGLONG WINAPI GetTickCount64(VOID);
60 58
@@ -70,53 +68,84 @@ static double time_monotime(void) {
70} 68}
71#endif 69#endif
72 70
73/*------------------------------------------------------------------------- 71
74 * Returns the current system time, 1970 (UTC), in secconds. 72
75 *-------------------------------------------------------------------------*/ 73/***
74Get system time.
75The time is returned as the seconds since the epoch (1 January 1970 00:00:00).
76@function gettime
77@treturn number seconds (fractional)
78*/
76static int time_lua_gettime(lua_State *L) 79static int time_lua_gettime(lua_State *L)
77{ 80{
78 lua_pushnumber(L, time_gettime()); 81 lua_pushnumber(L, time_gettime());
79 return 1; 82 return 1;
80} 83}
81 84
82/*------------------------------------------------------------------------- 85
83 * Returns the monotonic time the system has been up, in secconds. 86
84 *-------------------------------------------------------------------------*/ 87/***
88Get monotonic time.
89The time is returned as the seconds since system start.
90@function monotime
91@treturn number seconds (fractional)
92*/
85static int time_lua_monotime(lua_State *L) 93static int time_lua_monotime(lua_State *L)
86{ 94{
87 lua_pushnumber(L, time_monotime()); 95 lua_pushnumber(L, time_monotime());
88 return 1; 96 return 1;
89} 97}
90 98
91/*------------------------------------------------------------------------- 99
92 * Sleep for n seconds. 100
93 *-------------------------------------------------------------------------*/ 101/***
102Sleep without a busy loop.
103This function will sleep, without doing a busy-loop and wasting CPU cycles.
104@function sleep
105@tparam number seconds seconds to sleep (fractional).
106@tparam[opt=16] integer precision minimum stepsize in milliseconds (Windows only, ignored elsewhere)
107@return `true` on success, or `nil+err` on failure
108*/
94#ifdef _WIN32 109#ifdef _WIN32
95static int time_lua_sleep(lua_State *L) 110static int time_lua_sleep(lua_State *L)
96{ 111{
97 double n = luaL_checknumber(L, 1); 112 double n = luaL_checknumber(L, 1);
98 if (n < 0.0) n = 0.0; 113
99 if (n < DBL_MAX/1000.0) n *= 1000.0; 114 int precision = luaL_optinteger(L, 2, 16);
100 if (n > INT_MAX) n = INT_MAX; 115 if (precision < 0 || precision > 16) precision = 16;
101 Sleep((int)n); 116
102 return 0; 117 if (n > 0.0) {
118 if (n < DBL_MAX/1000.0) n *= 1000.0;
119 if (n > INT_MAX) n = INT_MAX;
120 if (timeBeginPeriod(precision) != TIMERR_NOERROR) {
121 lua_pushnil(L);
122 lua_pushstring(L, "failed to set timer precision");
123 return 2;
124 };
125 Sleep((int)n);
126 timeEndPeriod(precision);
127 }
128 lua_pushboolean(L, 1);
129 return 1;
103} 130}
104#else 131#else
105static int time_lua_sleep(lua_State *L) 132static int time_lua_sleep(lua_State *L)
106{ 133{
107 double n = luaL_checknumber(L, 1); 134 double n = luaL_checknumber(L, 1);
108 struct timespec t, r; 135 struct timespec t, r;
109 if (n < 0.0) n = 0.0; 136 if (n > 0.0) {
110 if (n > INT_MAX) n = INT_MAX; 137 if (n > INT_MAX) n = INT_MAX;
111 t.tv_sec = (int) n; 138 t.tv_sec = (int) n;
112 n -= t.tv_sec; 139 n -= t.tv_sec;
113 t.tv_nsec = (int) (n * 1000000000); 140 t.tv_nsec = (int) (n * 1000000000);
114 if (t.tv_nsec >= 1000000000) t.tv_nsec = 999999999; 141 if (t.tv_nsec >= 1000000000) t.tv_nsec = 999999999;
115 while (nanosleep(&t, &r) != 0) { 142 while (nanosleep(&t, &r) != 0) {
116 t.tv_sec = r.tv_sec; 143 t.tv_sec = r.tv_sec;
117 t.tv_nsec = r.tv_nsec; 144 t.tv_nsec = r.tv_nsec;
145 }
118 } 146 }
119 return 0; 147 lua_pushboolean(L, 1);
148 return 1;
120} 149}
121#endif 150#endif
122 151