繁体   English   中英

在 Linux Swift 包中使用 sysctlbyname 函数

[英]Use the sysctlbyname function within a Linux Swift Package

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

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

所以我想知道如何在 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 Playgrounds 应用程序开发兼容性,因为它只支持 Swift 目标(一种可能的解决方法是将 C/C++ 目标放在单独的 swift 包中,以便有条件地将其用作 linux 的依赖项,有关更多详细信息,请参阅相关的 swift 包文档)。

所以添加一个 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
    }
    
}

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM