#!/usr/bin/env lua
--[[ osm[ore]
--
There is no goal to implement every posix call for every operating system.
It supports the calls I need to interact with the system.
The interface uses Lua style (string instead of int flags)
The library is implemented for Linux.
For general purpose, check out:
https://github.com/luaposix/luaposix
https://github.com/wahern/cqueues
]]
local osm, s_err = require("osm")
if not(osm) then
  io.stderr:write(s_err)
  os.exit(1)
end

--[[ bgexec
arg1: external program
optional arg2 - argX
return:
arg1: pid
return on error: nil, message[, errno]
]]
print("bgexec(\"echo\", \"  bgexec child: Hello\")", osm.bgexec("echo", "  bgexec child: Hello"))

--[[ bgflua
arg1: lua file
optional arg2: env table
return:
arg1: pid
return on error: nil, message[, errno]
]]
-- using an env, that will print and fail
local bgflua_env = {
  require = function()
    -- there will be no osm module
    return false, "  bgflua child: Hello\n"
  end,
  -- use stdout, not an error
  io = {stderr = io.stdout},
  os = os
}
print("bgflua(\"Readme.lua\", bgflua_env)", osm.bgflua("Readme.lua", bgflua_env))

--[[ bgslua
arg1: lua string
optional arg2: env table
return:
arg1: pid
return on error: nil, message[, errno]
]]
print("bgslua([[io.stdout:write(\"  bgslua child: Hello\n\")]])",
  osm.bgslua([[io.stdout:write("  bgslua child: Hello\n")]]))

--[[ bgexec, bgflua and bgslua
special: additional arg1 can be a system environment table:
{
  [-1] = true --> clear environment
  [0] = false --> close stdin (stdin, stderr get closed, stdout -> /dev/null)
  [1] = true --> return stdout
  [2] = nil --> not set, just keep stderr (same as current)
  PATH = "/bin:/usr/bin" --> set path
  HOME = false --> unset home
}
if io redirects are set, additional file(s) will be returned:
pid, stdin|nil, stdout|nil, stderr|nil
(stdin accepts writes, stdout|stderr accepts reads)
]]

local n_pid, _, f_rdout = osm.bgslua({true},
  [[io.stdout:write("redirect: Hello") io.stdout:flush()]])
local s_rdout = f_rdout:read("*a")
f_rdout:close()
print("bgslua({true}, [[io.stdout:write(\"redirect: Hello\") io.stdout:flush()]])", n_pid, s_rdout)


--[[ bgwait
optional arg1: number pid or -1
  -> without arg1 (or 0) bgwait will return immediately
  -> -1 will wait for any child
return:
arg1: pid or 0
  -> 0, if no bgexec ended, there will be no other args returned
optional arg2: exit code
  -> if signal exit code will be 128 + signal number
optional arg3: signal / error message
  -> error message will be set, if bgexec failed to start program
return on error: nil, message[, errno]
]]
print(string.format("bgwait(%d)", n_pid), osm.bgwait(n_pid))
print("bgwait(0)", osm.bgwait(0))
print("bgwait(-1)", osm.bgwait(-1))
print("bgwait(-1)", osm.bgwait(-1))

local s_test_temp_path = "/tmp/test_osm.dir"
--[[ mkdir
arg1: string path -> no recursive creation
optional arg2: string mode
return:
arg1: boolean true
return on error: nil, message[, errno]
]]
print(string.format("mkdir(\"%s\")", s_test_temp_path), osm.mkdir(s_test_temp_path))

--[[ mkfile
WHY? -> will "exclusive" create a file, fail, if it exists
arg1: string path
optional arg2: string mode
optional arg3: boolean -> request file
return:
arg1: boolean true or file (if in arg3 is true)
return on file exist: false, message
return on error: nil, message[, errno]
]]
local s_test_temp_file = "/tmp/test_osm.file"
local f_test_temp = osm.mkfile(s_test_temp_file, nil, true)
print(string.format("mkfile(\"%s\", nil, true)", s_test_temp_file), f_test_temp)
f_test_temp:close()

--[[ chmod
arg1: string path
arg2: string mode (set permission in numeric mode, by up to 4 octal digits)
       -> 1st: suid=4, sgid=2, sticky=1 / 2nd=u,3rd=g,4th=o: r=4, w=2, x=1
       -> no parsing of "u+rw" style - can be implemented in Lua, if required
return:
arg1: boolean true
return on error: nil, message[, errno]
]]
print(string.format("chmod(\"%s\", \"%s\")",
  s_test_temp_path, "700"), osm.chmod(s_test_temp_path, "700"))

--[[ fmode
arg1: string filename (or stat table)

return:
arg1: string octal mode
return on error: nil, message[, errno]
]]
print(string.format("fmode(\"%s\")", s_test_temp_path), osm.fmode(s_test_temp_path))
print(string.format("fmode(\"%s\")", s_test_temp_file), osm.fmode(s_test_temp_file))

--[[ ftype
arg1: string filename (or stat table)

return:
arg1: string: "socket", "symlink", "file", "bdev", "dir", "cdev", "fifo"
return on error: nil, message[, errno]
]]
print(string.format("ftype(\"%s\")", s_test_temp_path), osm.ftype(s_test_temp_path))
print(string.format("ftype(\"%s\")", s_test_temp_file), osm.ftype(s_test_temp_file))

--[[ gethostname
return:
arg1: string hostname
return on error: nil, message[, errno]
]]
print("gethostname()", osm.gethostname())

--[[ getpid
return:
arg1: integer pid
]]
print("getpid()", osm.getpid())

--[[ gettime
optional arg1: string: "r[ealtime]" (default), "t[ai]", "m[onotonic]", "c[putime]"
               - only the first letter (lowercase) is important
return:
arg1: integer seconds
arg2: integer nanoseconds
return on error: nil, message[, errno]
]]
print("gettime()", osm.gettime())

--[[ lip -- list ip -> iterator
arg1: string name
optional arg2: string: "[IPv]4" (default), [IPv]6, ANY
return:
iteratorfunction return:
  arg1: string ip
  arg2: string family (IPv4, IPv6)
return on error: iteratorfunction, message
  error iteratorfunction return:
    arg1: nil
    arg2: error
]]
print("lip(\"localhost\", \"\")")
-- set to "ANY"
for ip, family in osm.lip("localhost", "") do
  print(" -", ip, family)
end

--[[ ln
arg1: string filename
arg2: string linkname

return: true
return on error: nil, message[, errno]
]]
local s_test_temp_link = "/tmp/test_osm.link"
print(string.format("ln(\"%s\", \"%s\")", s_test_temp_file, s_test_temp_link),
  osm.ln(s_test_temp_file, s_test_temp_link))

--[[ ls -- list (dir) -> iterator
optional arg1: string dir - default: current dir
iteratorfunction return:
  arg1: string file
   -> it will NOT return "." and ".."
return on error: iteratorfunction, message
  error iteratorfunction return:
    arg1: nil
    arg2: error
]]
print("ls(\"/tmp\") -- print only match test_osm")
for file in osm.ls("/tmp") do
  if string.match(file, "test_osm") then
    print(" -", file)
  end
end

--[[ readlink
arg1: string linkname
return:
arg1: string linkdest
return on error: nil, message[, errno]
]]
print(string.format("readlink(\"%s\")", s_test_temp_link), osm.readlink(s_test_temp_link))

--[[ realpath
arg1: string filename
return:
arg1: string realpath
return on error: nil, message[, errno]
]]
print(string.format("realpath(\"%s\")", s_test_temp_link), osm.realpath(s_test_temp_link))

--[[ setenv
arg1: string name
optional arg1: string value
  -> if nil, unset env
return:
arg1: boolean true
return on error: nil, message[, errno]
]]
print("setenv(\"HELLO\", \"World\")",
  osm.setenv("HELLO", "World"), "os.getenv(\"HELLO\")", os.getenv("HELLO"))
print("setenv(\"HELLO\")", osm.setenv("HELLO"), "os.getenv(\"HELLO\")", os.getenv("HELLO"))

--[[ stat
arg1: string filename
return:
arg1: table stat: "dev", "inode", "mode", "nlink", "uid", "gid", "rdev", "size",
  "blksize", "blocks", "atime", "mtime", "mtins", "ctime", "fmode", "ftype"
return on error: nil, message[, errno]
]]
print(string.format("stat(\"%s\")", s_test_temp_path), osm.stat(s_test_temp_path))
local t_stat = osm.stat(s_test_temp_path)
for k, v in pairs(t_stat) do
  print(" -", k, v)
end

--[[ utime
arg1: string filename
optional arg2: number atime or false
optional arg3: number mtime or false
-> false will not update the time, if no time given NOW (in ns) is used
return on error: nil, message[, errno]
]]
print(string.format("utime(\"%s\", false, 12345678)", s_test_temp_path),
  osm.utime(s_test_temp_path, false, 12345678))
print(" - atime:", osm.stat(s_test_temp_path).atime)
print(" - mtime:", osm.stat(s_test_temp_path).mtime)

--[[ rmdir
arg1: string path
return:
arg1: boolean true
return on error: nil, message[, errno]
]]
print(string.format("rmdir(\"%s\") => %s", s_test_temp_path, osm.rmdir(s_test_temp_path)))
print(string.format("os.remove(\"%s\")", s_test_temp_link), os.remove(s_test_temp_link))
print(string.format("os.remove(\"%s\")", s_test_temp_file), os.remove(s_test_temp_file))

