在 Linux Swift 包中使用 sysctlbyname 函数

Use the sysctlbyname function within a Linux Swift Package

我正在尝试为我的 swift 包库添加 linux 支持以获取系统信息,但我不知道如何访问 [=29] 上的 sysctlbyname 功能=] 在 Swift 包中。

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

所以我想知道如何在 linux 上的 Swift 库中访问该函数,以及是否可以在不使用 C 或其他非 Swift 的情况下访问该函数东西,也是因为我想让我的代码与苹果系统的 Swift playgrounds 应用程序兼容,它不支持具有 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 playgrounds 应用程序开发兼容性,因为它支持 Swift-only 目标(一个可能的解决方法是将 C/C++ 目标放在单独的 swift 包有条件地将其用作 linux 的依赖项,有关更多详细信息,请参阅相关的 swift 包文档。

所以添加一个 C/C++ 目标本来可以解决问题,但问题是在 Linux 内核版本 5.5 及以后的版本中 sysctl 函数已被弃用并且即使在较旧的内核上,它们也不适用于所有 cpu 架构 Linux 支持,因此在计算机上 运行 最近的内核或某些特定的非 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
    }
    
}

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