summaryrefslogtreecommitdiff
path: root/src/luarocks/core/sysdetect.lua
diff options
context:
space:
mode:
Diffstat (limited to 'src/luarocks/core/sysdetect.lua')
-rw-r--r--src/luarocks/core/sysdetect.lua419
1 files changed, 419 insertions, 0 deletions
diff --git a/src/luarocks/core/sysdetect.lua b/src/luarocks/core/sysdetect.lua
new file mode 100644
index 0000000..06454f2
--- /dev/null
+++ b/src/luarocks/core/sysdetect.lua
@@ -0,0 +1,419 @@
+-- Detect the operating system and architecture without forking a subprocess.
+--
+-- We are not going for exhaustive list of every historical system here,
+-- but aiming to cover every platform where LuaRocks is known to run.
+-- If your system is not detected, patches are welcome!
+
+local sysdetect = {}
+
+local function hex(s)
+ return s:gsub("$(..)", function(x)
+ return string.char(tonumber(x, 16))
+ end)
+end
+
+local function read_int8(fd)
+ if io.type(fd) == "closed file" then
+ return nil
+ end
+ local s = fd:read(1)
+ if not s then
+ fd:close()
+ return nil
+ end
+ return s:byte()
+end
+
+local LITTLE = 1
+-- local BIG = 2
+
+local function bytes2number(s, endian)
+ local r = 0
+ if endian == LITTLE then
+ for i = #s, 1, -1 do
+ r = r*256 + s:byte(i,i)
+ end
+ else
+ for i = 1, #s do
+ r = r*256 + s:byte(i,i)
+ end
+ end
+ return r
+end
+
+local function read(fd, bytes, endian)
+ if io.type(fd) == "closed file" then
+ return nil
+ end
+ local s = fd:read(bytes)
+ if not s
+ then fd:close()
+ return nil
+ end
+ return bytes2number(s, endian)
+end
+
+local function read_int32le(fd)
+ return read(fd, 4, LITTLE)
+end
+
+--------------------------------------------------------------------------------
+-- @section ELF
+--------------------------------------------------------------------------------
+
+local e_osabi = {
+ [0x00] = "sysv",
+ [0x01] = "hpux",
+ [0x02] = "netbsd",
+ [0x03] = "linux",
+ [0x04] = "hurd",
+ [0x06] = "solaris",
+ [0x07] = "aix",
+ [0x08] = "irix",
+ [0x09] = "freebsd",
+ [0x0c] = "openbsd",
+}
+
+local e_machines = {
+ [0x02] = "sparc",
+ [0x03] = "x86",
+ [0x08] = "mips",
+ [0x0f] = "hppa",
+ [0x12] = "sparcv8",
+ [0x14] = "ppc",
+ [0x15] = "ppc64",
+ [0x16] = "s390",
+ [0x28] = "arm",
+ [0x2a] = "superh",
+ [0x2b] = "sparcv9",
+ [0x32] = "ia_64",
+ [0x3E] = "x86_64",
+ [0xB6] = "alpha",
+ [0xB7] = "aarch64",
+ [0xF3] = "riscv64",
+ [0x9026] = "alpha",
+}
+
+local SHT_NOTE = 7
+
+local function read_elf_section_headers(fd, hdr)
+ local endian = hdr.endian
+ local word = hdr.word
+
+ local strtab_offset
+ local sections = {}
+ for i = 0, hdr.e_shnum - 1 do
+ fd:seek("set", hdr.e_shoff + (i * hdr.e_shentsize))
+ local section = {}
+ section.sh_name_off = read(fd, 4, endian)
+ section.sh_type = read(fd, 4, endian)
+ section.sh_flags = read(fd, word, endian)
+ section.sh_addr = read(fd, word, endian)
+ section.sh_offset = read(fd, word, endian)
+ section.sh_size = read(fd, word, endian)
+ section.sh_link = read(fd, 4, endian)
+ section.sh_info = read(fd, 4, endian)
+ if section.sh_type == SHT_NOTE then
+ fd:seek("set", section.sh_offset)
+ section.namesz = read(fd, 4, endian)
+ section.descsz = read(fd, 4, endian)
+ section.type = read(fd, 4, endian)
+ section.namedata = fd:read(section.namesz):gsub("%z.*", "")
+ section.descdata = fd:read(section.descsz)
+ elseif i == hdr.e_shstrndx then
+ strtab_offset = section.sh_offset
+ end
+ table.insert(sections, section)
+ end
+ if strtab_offset then
+ for _, section in ipairs(sections) do
+ fd:seek("set", strtab_offset + section.sh_name_off)
+ section.name = fd:read(32):gsub("%z.*", "")
+ sections[section.name] = section
+ end
+ end
+ return sections
+end
+
+local function detect_elf_system(fd, hdr, sections)
+ local system = e_osabi[hdr.osabi]
+ local endian = hdr.endian
+
+ if system == "sysv" then
+ local abitag = sections[".note.ABI-tag"]
+ if abitag then
+ if abitag.namedata == "GNU" and abitag.type == 1
+ and abitag.descdata:sub(0, 4) == "\0\0\0\0" then
+ return "linux"
+ end
+ elseif sections[".SUNW_version"]
+ or sections[".SUNW_signature"] then
+ return "solaris"
+ elseif sections[".note.netbsd.ident"] then
+ return "netbsd"
+ elseif sections[".note.openbsd.ident"] then
+ return "openbsd"
+ elseif sections[".note.tag"] and
+ sections[".note.tag"].namedata == "DragonFly" then
+ return "dragonfly"
+ end
+
+ local gnu_version_r = sections[".gnu.version_r"]
+ if gnu_version_r then
+
+ local dynstr = sections[".dynstr"].sh_offset
+
+ local idx = 0
+ for _ = 0, gnu_version_r.sh_info - 1 do
+ fd:seek("set", gnu_version_r.sh_offset + idx)
+ assert(read(fd, 2, endian)) -- vn_version
+ local vn_cnt = read(fd, 2, endian)
+ local vn_file = read(fd, 4, endian)
+ local vn_next = read(fd, 2, endian)
+
+ fd:seek("set", dynstr + vn_file)
+ local libname = fd:read(64):gsub("%z.*", "")
+
+ if hdr.e_type == 0x03 and libname == "libroot.so" then
+ return "haiku"
+ elseif libname:match("linux") then
+ return "linux"
+ end
+
+ idx = idx + (vn_next * (vn_cnt + 1))
+ end
+ end
+
+ local procfile = io.open("/proc/sys/kernel/ostype")
+ if procfile then
+ local version = procfile:read(6)
+ procfile:close()
+ if version == "Linux\n" then
+ return "linux"
+ end
+ end
+ end
+
+ return system
+end
+
+local function read_elf_header(fd)
+ local hdr = {}
+
+ hdr.bits = read_int8(fd)
+ hdr.endian = read_int8(fd)
+ hdr.elf_version = read_int8(fd)
+ if hdr.elf_version ~= 1 then
+ return nil
+ end
+ hdr.osabi = read_int8(fd)
+ if not hdr.osabi then
+ return nil
+ end
+
+ local endian = hdr.endian
+ fd:seek("set", 0x10)
+ hdr.e_type = read(fd, 2, endian)
+ local machine = read(fd, 2, endian)
+ local processor = e_machines[machine] or "unknown"
+ if endian == 1 and processor == "ppc64" then
+ processor = "ppc64le"
+ end
+
+ local elfversion = read(fd, 4, endian)
+ if elfversion ~= 1 then
+ return nil
+ end
+
+ local word = (hdr.bits == 1) and 4 or 8
+ hdr.word = word
+
+ hdr.e_entry = read(fd, word, endian)
+ hdr.e_phoff = read(fd, word, endian)
+ hdr.e_shoff = read(fd, word, endian)
+ hdr.e_flags = read(fd, 4, endian)
+ hdr.e_ehsize = read(fd, 2, endian)
+ hdr.e_phentsize = read(fd, 2, endian)
+ hdr.e_phnum = read(fd, 2, endian)
+ hdr.e_shentsize = read(fd, 2, endian)
+ hdr.e_shnum = read(fd, 2, endian)
+ hdr.e_shstrndx = read(fd, 2, endian)
+
+ return hdr, processor
+end
+
+local function detect_elf(fd)
+ local hdr, processor = read_elf_header(fd)
+ if not hdr then
+ return nil
+ end
+ local sections = read_elf_section_headers(fd, hdr)
+ local system = detect_elf_system(fd, hdr, sections)
+ return system, processor
+end
+
+--------------------------------------------------------------------------------
+-- @section Mach Objects (Apple)
+--------------------------------------------------------------------------------
+
+local mach_l64 = {
+ [7] = "x86_64",
+ [12] = "aarch64",
+}
+
+local mach_b64 = {
+ [0] = "ppc64",
+}
+
+local mach_l32 = {
+ [7] = "x86",
+ [12] = "arm",
+}
+
+local mach_b32 = {
+ [0] = "ppc",
+}
+
+local function detect_mach(magic, fd)
+ if not magic then
+ return nil
+ end
+
+ if magic == hex("$CA$FE$BA$BE") then
+ -- fat binary, go for the first one
+ fd:seek("set", 0x12)
+ local offs = read_int8(fd)
+ if not offs then
+ return nil
+ end
+ fd:seek("set", offs * 256)
+ magic = fd:read(4)
+ return detect_mach(magic, fd)
+ end
+
+ local cputype = read_int8(fd)
+
+ if magic == hex("$CF$FA$ED$FE") then
+ return "macosx", mach_l64[cputype] or "unknown"
+ elseif magic == hex("$FE$ED$CF$FA") then
+ return "macosx", mach_b64[cputype] or "unknown"
+ elseif magic == hex("$CE$FA$ED$FE") then
+ return "macosx", mach_l32[cputype] or "unknown"
+ elseif magic == hex("$FE$ED$FA$CE") then
+ return "macosx", mach_b32[cputype] or "unknown"
+ end
+end
+
+--------------------------------------------------------------------------------
+-- @section PE (Windows)
+--------------------------------------------------------------------------------
+
+local pe_machine = {
+ [0x8664] = "x86_64",
+ [0x01c0] = "arm",
+ [0x01c4] = "armv7l",
+ [0xaa64] = "arm64",
+ [0x014c] = "x86",
+}
+
+local function detect_pe(fd)
+ fd:seek("set", 60) -- position of PE header position
+ local peoffset = read_int32le(fd) -- read position of PE header
+ if not peoffset then
+ return nil
+ end
+ local system = "windows"
+ fd:seek("set", peoffset + 4) -- move to position of Machine section
+ local machine = read(fd, 2, LITTLE)
+ local processor = pe_machine[machine]
+
+ local rdata_pos = fd:read(736):match(".rdata%z%z............(....)")
+ if rdata_pos then
+ rdata_pos = bytes2number(rdata_pos, LITTLE)
+ fd:seek("set", rdata_pos)
+ local data = fd:read(512)
+ if data:match("cygwin") or data:match("cyggcc") then
+ system = "cygwin"
+ end
+ end
+
+ return system, processor or "unknown"
+end
+
+--------------------------------------------------------------------------------
+-- @section API
+--------------------------------------------------------------------------------
+
+function sysdetect.detect_file(file)
+ assert(type(file) == "string")
+ local fd = io.open(file, "rb")
+ if not fd then
+ return nil
+ end
+ local magic = fd:read(4)
+ if magic == hex("$7FELF") then
+ return detect_elf(fd)
+ end
+ if magic == hex("MZ$90$00") then
+ return detect_pe(fd)
+ end
+ return detect_mach(magic, fd)
+end
+
+local cache_system
+local cache_processor
+
+function sysdetect.detect(input_file)
+ local dirsep = package.config:sub(1,1)
+ local files
+
+ if input_file then
+ files = { input_file }
+ else
+ if cache_system then
+ return cache_system, cache_processor
+ end
+
+ local PATHsep
+ local interp = arg and arg[-1]
+ if dirsep == "/" then
+ -- Unix
+ files = {
+ "/bin/sh", -- Unix: well-known POSIX path
+ "/proc/self/exe", -- Linux: this should always have a working binary
+ }
+ PATHsep = ":"
+ else
+ -- Windows
+ local systemroot = os.getenv("SystemRoot")
+ files = {
+ systemroot .. "\\system32\\notepad.exe", -- well-known Windows path
+ systemroot .. "\\explorer.exe", -- well-known Windows path
+ }
+ if interp and not interp:lower():match("exe$") then
+ interp = interp .. ".exe"
+ end
+ PATHsep = ";"
+ end
+ if interp then
+ if interp:match(dirsep) then
+ -- interpreter path is absolute
+ table.insert(files, 1, interp)
+ else
+ for d in (os.getenv("PATH") or ""):gmatch("[^"..PATHsep.."]+") do
+ table.insert(files, d .. dirsep .. interp)
+ end
+ end
+ end
+ end
+ for _, f in ipairs(files) do
+ local system, processor = sysdetect.detect_file(f)
+ if system then
+ cache_system = system
+ cache_processor = processor
+ return system, processor
+ end
+ end
+end
+
+return sysdetect