在Linux Swift包中使用sysctlbyname函数



我正试图将linux支持添加到我的swift包库中以获取系统信息,但我不知道如何在swift包中访问linux上的sysctlbyname函数。

对于它的所有检测库依赖于sysctlbyname函数,可以通过在苹果平台上导入Dariwn.sys.sysctl轻松访问,但是我找不到任何Swift方法来访问linux上的该函数,尽管事实上你可以在C中通过在任何unix平台上导入sys/sysctl.h访问它。

所以我想知道如何在linux上访问我的Swift库中的函数,如果有可能做到这一点,而不必使用C或其他非Swift的东西,也因为我想保持我的代码与苹果系统的Swift游乐场应用程序兼容,它不支持带有C导入的SPM库。

只是作为参考,我在这里留下负责在我的项目中与sysctlbyname接口的代码的一部分:


import Foundation
#if os(Linux)
import Glibc //? not sure about where i can find `sysctlbyname` in linux without using C headers
#else
import Darwin.sys.sysctl
#endif
///Generic protocol to allow easy fetching of values out of `sysctlbyname`
public protocol SysctlFetch{
static var namePrefix: String {get}
}
public extension SysctlFetch{

///Gets a `String` from the `sysctlbyname` function
static func getString(_ valueName: String) -> String?{

var size: size_t = 0

let name = namePrefix + valueName

var res = sysctlbyname(name, nil, &size, nil, 0)

if res != 0 {
return nil
}

var ret = [CChar].init(repeating: 0, count: size + 1)

res = sysctlbyname(name, &ret, &size, nil, 0)

return res == 0 ? String(cString: ret) : nil
}

///Gets an Integer value from the `sysctlbyname` function
static func getInteger<T: FixedWidthInteger>(_ valueName: String) -> T?{
var ret = T()

var size = MemoryLayout.size(ofValue: ret)

let res = sysctlbyname(namePrefix + valueName, &ret, &size, nil, 0)

return res == 0 ? ret : nil
}

///Gets a `Bool` value from the `sysctlbyname` function
static func getBool(_ valueName: String) -> Bool?{
guard let res: Int32 = getInteger(valueName) else{
return nil
}

return res == 1
}

}

和一个如何在代码中使用它的例子(诅咒它被用来检索更多的东西):

///Kernel info
final class KernelInfo: SysctlFetch{

static var namePrefix: String{
#if os(Linux)
return "kernel."
#else
return "kern."
#endif
}

///The os kernel name
static var ostype: String?{
return Self.getString("ostype")
}
/* Other static vars here */
}

所以可以添加C, c++或Objective-C目标到Swift包中,这样就可以导入所需的系统头文件,然后创建一些包装器函数,这使得需要的东西可以被Swift访问,但这破坏了Swift应用程序开发的兼容性,因为它只支持Swift目标(一个可能的解决方案是将C/c++目标放在一个单独的Swift包中,以便有条件地将其作为依赖项仅用于linux)。

所以添加一个C/c++目标可以解决这个问题,但问题是,在Linux内核5.5及以后的版本中,sysctl函数已经被弃用,甚至在旧的内核中,它们在Linux支持的所有cpu架构上都不可用,因此在运行最新内核或某些特定的非x86 cpu架构的计算机上,这样的Swift包将无法成功构建。

当前访问sysctl函数提供的信息的方法是直接从/proc/sys目录内的文件系统中读取,它适用于所有支持的cpu体系结构,并且sysctl命令行实用程序可以获得该数据。

所以只有在linux上,代码必须像这样修改,才能成功地在所有平台上收集数据:


import Foundation
#if os(Linux)
import Glibc //? not sure about where i can find `sysctlbyname` in linux without using C headers
#else
import Darwin.sys.sysctl
#endif
///Generic protocol to allow easy fetching of values out of `sysctlbyname`
public protocol SysctlFetch{
static var namePrefix: String {get}
}
public extension SysctlFetch{

#if !os(Linux)
///Gets a `String` from the `sysctlbyname` function
static func getString(_ valueName: String) -> String?{

var size: size_t = 0

let name = namePrefix + valueName

var res = sysctlbyname(name, nil, &size, nil, 0)

if res != 0 {
return nil
}

var ret = [CChar].init(repeating: 0, count: size + 1)

res = sysctlbyname(name, &ret, &size, nil, 0)

return res == 0 ? String(cString: ret) : nil
}

///Gets an Integer value from the `sysctlbyname` function
static func getInteger<T: FixedWidthInteger>(_ valueName: String) -> T?{
var ret = T()

var size = MemoryLayout.size(ofValue: ret)

let res = sysctlbyname(namePrefix + valueName, &ret, &size, nil, 0)

return res == 0 ? ret : nil
}
#else
///Gets a `String` from `/proc/sys`
static func getString(_ valueName: String) -> String?{

let path = "/proc/sys/" + (namePrefix + valueName).replacingOccurrences(of: ".", with: "/")
var contents = ""

do{
contents = try String(contentsOfFile: path)
}catch let err{
return nil
}

if contents.last == "n"{
contents.removeLast()
}

return contents
}

///Gets an Integer value from `/proc/sys`
static func getInteger<T: FixedWidthInteger>(_ valueName: String) -> T?{
guard let str = getString(valueName) else { return nil }
return T(str)
}
#endif

///Gets a `Bool` value from the `sysctlbyname` function
static func getBool(_ valueName: String) -> Bool?{
guard let res: Int32 = getInteger(valueName) else{
return nil
}

return res == 1
}

}

所以最后我自己弄明白了,我希望这能对任何做同样事情的人有用。

最新更新